├── .gitignore
├── README.md
├── doc
├── blender_params.jpg
├── blender_params.psd
└── moz_text_workflow.png
├── index.html
├── io_scene_gltf2_moz_text.zip
├── io_scene_gltf2_moz_text
└── __init__.py
├── test
├── GLTFLoader.module.js
├── blendfiles
│ ├── align.blend
│ ├── basic.blend
│ ├── letterspacing.blend
│ ├── lineheight.blend
│ ├── lorem ipsun.blend
│ ├── materials.blend
│ ├── overflow.blend
│ ├── sizes.blend
│ └── types.blend
├── bundle.js
├── bundle.js.map
├── fonts
│ ├── Arial.json
│ ├── Arial.ttf
│ ├── Comic Sans MS Bold.ttf
│ ├── Georgia.ttf
│ ├── Impact.json
│ └── Impact.ttf
├── index.html
├── index.js
├── models
│ ├── align.gltf
│ ├── basic.gltf
│ ├── letterspacing.gltf
│ ├── lineheight.gltf
│ ├── lorem ipsun.gltf
│ ├── materials.gltf
│ ├── overflow.gltf
│ ├── sizes.gltf
│ └── types.gltf
├── package.json
└── webpack.config.js
└── threejs
├── GLTFLoader.module.js
└── README.md
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | package-lock.json
4 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # MOZ_text: Text extension for glTF
2 |
3 | This is an extension for glTF to support text objects.
4 |
5 | The aim is not to save the mesh but only metadata about the content, font and text properties, so it is in the final application the decision of how to interpret and render it.
6 |
7 | Currently only [threejs](https://github.com/mrdoob/three.js) is implemeted, temporarily modifying the [GLTFLoader](https://github.com/mrdoob/three.js/blob/dev/examples/jsm/loaders/GLTFLoader.js) while the [support for plugins](https://github.com/mrdoob/three.js/pull/18484) is not officially merged.
8 |
9 | The ultimate goal would be to have a perfect match between Blender (or other 3d package) and the render engine, but given the multiple differences of how text is handled in all the steps, this is far from being perfect right now. Instead of that, this project aims to a very practical approach: to have a useful tool that we can use today for simple use cases.
10 |
11 | For comparisons between Blender and threejs scenes, try the [test scene](https://mixedreality.mozilla.org/MOZ_text) and compare it with the [blender scenes](https://github.com/feiss/MOZ_text/tree/master/test/blendfiles)
12 |
13 | ## Workflow and usage
14 |
15 | This is meant to be used as follows:
16 | 1. **Install Add-on**
17 |
18 | In Blender, install the [io_scene_gltf2_moz_text](https://github.com/feiss/MOZ_text/blob/master/io_scene_gltf2_moz_text.zip) add-on (`Edit > Preferences > Add-ons > "Install..."`), and turn on the add-on checkbox (see diagram below, 1).
19 |
20 | 2. **Add texts**
21 |
22 | Add and edit all the texts object in your scene (`shift+a > "Text"`). There is a MOZ_text panel at the bottom of the text properties (see diagram below, 2).
23 |
24 | 3. **Export**
25 |
26 | Export the scene (or selected objects) to glTF (`File > Export > glTF 2.0`), and find the extension general options at the end of the glTF options (see diagram below, 3).
27 |
28 | 4. **Add font files**
29 |
30 | Add the neccesary font files in your project folders. Use the same relative path as the one specified in the previous step.
31 |
32 | 5. **Load glTF**
33 |
34 | Use the custom GLTFLoader provided to load the glTF in threejs.
35 |
36 | 
37 |
38 |
39 | ## Extension parameters
40 |
41 | Inside the glTF, the extension is added to a node as "MOZ_text", with these parameters:
42 |
43 | ```json
44 | "extensions" : {
45 | "MOZ_text" : {
46 | "index" : 1,
47 | "type" : "sdf | texture | geometry",
48 | "color" : [1.0, 0.98, 1.0, 1.0],
49 | "alignX" : "left",
50 | "alignY" : "top",
51 | "value" : "Lorem\nIpsun",
52 | "fontName" : "Impact",
53 | "fontFile" : "fonts/Impact.ttf",
54 | "size" : 2,
55 | "maxWidth" : 0,
56 | "overflow" : "NONE",
57 | "letterSpacing" : 0,
58 | "lineSpacing" : 0.56,
59 | "dimensions" : [9.7, 2.5],
60 | "extrude" : 0.63,
61 | "extrudeResolution" : 2,
62 | "bevel" : 0.039,
63 | "bevelOffset" : -0.019,
64 | "bevelResolution" : 1
65 | }
66 | }
67 | ```
68 |
69 | ## How to run examples
70 |
71 | If you want to run https://mixedreality.mozilla.org/MOZ_text locally:
72 |
73 | 1. clone repo
74 | 2. `cd MOZ_text/test`
75 | 3. `npm install`
76 | 4. `npm run start`
77 | 5. open `localhost:8080`
78 |
79 | ## Blender parameters support
80 |
81 | In Blender, the MOZ_text panel under the text properties allows to specify how the text should be rendered in the application. Current possible values are `SDF`, `Texture` or `Geometry`.
82 |
83 | Currently only these text parameters are exported:
84 |
85 | 
86 |
87 | Depending on `type`, some parameters are ignored. For example, bevel and extrude parameters are only used if text `type` is `geometry`.
88 |
89 |
--------------------------------------------------------------------------------
/doc/blender_params.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MozillaReality/MOZ_text/5672b7c08b8106d8930d45b540b5cb67eaec879f/doc/blender_params.jpg
--------------------------------------------------------------------------------
/doc/blender_params.psd:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MozillaReality/MOZ_text/5672b7c08b8106d8930d45b540b5cb67eaec879f/doc/blender_params.psd
--------------------------------------------------------------------------------
/doc/moz_text_workflow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MozillaReality/MOZ_text/5672b7c08b8106d8930d45b540b5cb67eaec879f/doc/moz_text_workflow.png
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/io_scene_gltf2_moz_text.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MozillaReality/MOZ_text/5672b7c08b8106d8930d45b540b5cb67eaec879f/io_scene_gltf2_moz_text.zip
--------------------------------------------------------------------------------
/io_scene_gltf2_moz_text/__init__.py:
--------------------------------------------------------------------------------
1 | import bpy
2 | from os import path
3 | from bpy.app.handlers import persistent
4 |
5 | bl_info = {
6 | "name": "MOZ_text glTF Extension",
7 | "category": "Import-Export",
8 | "version": (1, 0, 0),
9 | "blender": (2, 80, 0),
10 | 'location': 'File > Export > glTF 2.0',
11 | 'description': 'Custom glTF extension to support text nodes',
12 | 'tracker_url': "https://", # Replace with your issue tracker
13 | 'isDraft': False,
14 | 'developer': "Diego F. Goberna", # Replace this
15 | 'url': 'https://', # Replace this
16 | }
17 |
18 | # glTF extensions are named following a convention with known prefixes.
19 | # See: https://github.com/KhronosGroup/glTF/tree/master/extensions#about-gltf-extensions
20 | # also: https://github.com/KhronosGroup/glTF/blob/master/extensions/Prefixes.md
21 | glTF_extension_name = "MOZ_text"
22 |
23 | # Support for an extension is "required" if a typical glTF viewer cannot be expected
24 | # to load a given model without understanding the contents of the extension.
25 | # For example, a compression scheme or new image format (with no fallback included)
26 | # would be "required", but physics metadata or app-specific settings could be optional.
27 | extension_is_required = False
28 |
29 | class MOZTextProperties(bpy.types.PropertyGroup):
30 | font_path: bpy.props.StringProperty(
31 | name='Font path',
32 | description='Relative folder in your application to the font files.',
33 | default='fonts/'
34 | )
35 |
36 | def register():
37 | bpy.utils.register_class(MOZTextProperties)
38 | bpy.types.Scene.MOZTextProperties = bpy.props.PointerProperty(type=MOZTextProperties)
39 | bpy.utils.register_class(AdditionalTextPropertiesPanel)
40 |
41 | bpy.app.handlers.frame_change_pre.append(afterExport)
42 |
43 | def register_panel():
44 | # Register the panel on demand, we need to be sure to only register it once
45 | # This is necessary because the panel is a child of the extensions panel,
46 | # which may not be registered when we try to register this extension
47 | try:
48 | bpy.utils.register_class(GLTF_PT_UserExtensionPanel)
49 | except Exception:
50 | pass
51 |
52 | # If the glTF exporter is disabled, we need to unregister the extension panel
53 | # Just return a function to the exporter so it can unregister the panel
54 | return unregister_panel
55 |
56 |
57 | def unregister_panel():
58 | # Since panel is registered on demand, it is possible it is not registered
59 | try:
60 | bpy.utils.unregister_class(GLTF_PT_UserExtensionPanel)
61 | bpy.utils.unregister_class(AdditionalTextPropertiesPanel)
62 | except Exception:
63 | pass
64 |
65 |
66 | def unregister():
67 | unregister_panel()
68 | bpy.utils.unregister_class(MOZTextProperties)
69 | del bpy.types.Scene.MOZTextProperties
70 |
71 | bpy.app.handlers.frame_change_pre.remove(afterExport)
72 |
73 | class GLTF_PT_UserExtensionPanel(bpy.types.Panel):
74 |
75 | bl_space_type = 'FILE_BROWSER'
76 | bl_region_type = 'TOOL_PROPS'
77 | bl_label = "Enabled"
78 | bl_parent_id = "GLTF_PT_export_user_extensions"
79 | bl_options = {'DEFAULT_CLOSED'}
80 |
81 | @classmethod
82 | def poll(cls, context):
83 | sfile = context.space_data
84 | operator = sfile.active_operator
85 | return operator.bl_idname == "EXPORT_SCENE_OT_gltf"
86 |
87 | def draw_header(self, context):
88 | props = bpy.context.scene.MOZTextProperties
89 | self.layout.prop(props, 'enabled')
90 |
91 | def draw(self, context):
92 | layout = self.layout
93 | layout.use_property_split = True
94 | layout.use_property_decorate = False # No animation.
95 |
96 | props = bpy.context.scene.MOZTextProperties
97 | layout.active = props.enabled
98 |
99 | props = bpy.context.scene.MOZTextProperties
100 | layout.prop(props, 'font_path')
101 |
102 |
103 | class glTF2ExportUserExtension:
104 | def __init__(self):
105 | # We need to wait until we create the gltf2UserExtension to import the gltf2 modules
106 | # Otherwise, it may fail because the gltf2 may not be loaded yet
107 | from io_scene_gltf2.io.com.gltf2_io_extensions import Extension
108 | self.Extension = Extension
109 | self.properties = bpy.context.scene.MOZTextProperties
110 | self.text_index = 0
111 |
112 | def gather_node_hook(self, gltf2_object, blender_object, export_settings):
113 | if not blender_object.type == 'FONT':
114 | return
115 |
116 | o = blender_object
117 |
118 | o.undoExportActions = True
119 |
120 | fontPath = bpy.context.scene.MOZTextProperties.font_path.strip()
121 | if fontPath and fontPath[-1] != '/': fontPath += '/'
122 |
123 | ext_data = dict()
124 | ext_data['index'] = self.text_index
125 | ext_data['type'] = o.text_type.lower()
126 | ext_data['color'] = (o.text_color[0], o.text_color[1], o.text_color[2], o.text_color[3])
127 | ext_data['alignX'] = o.data.align_x.lower()
128 | ext_data['alignY'] = o.data.align_y.lower()
129 | ext_data['value'] = o.data.body
130 | ext_data['fontName'] = o.data.font.name # postscript name
131 | ext_data['fontFile'] = fontPath + o.data.font.filepath.split(path.sep).pop()
132 | ext_data['size'] = o.data.size
133 | ext_data['maxWidth'] = o.data.text_boxes[0].width
134 | ext_data['overflow'] = o.data.overflow
135 | ext_data['letterSpacing'] = o.data.space_character - 1
136 | ext_data['lineSpacing'] = o.data.space_line
137 | ext_data['dimensions'] = (o.dimensions.x, o.dimensions.y)
138 | # extrude/bevel
139 | if ext_data['type'] == 'geometry':
140 | ext_data['extrude'] = o.data.extrude
141 | ext_data['extrudeResolution'] = o.data.resolution_u
142 | ext_data['bevel'] = o.data.bevel_depth
143 | ext_data['bevelOffset'] = o.data.offset
144 | ext_data['bevelResolution'] = o.data.bevel_resolution
145 |
146 | # renderEngine = 'CYCLES' if bpy.context.scene.render.engine == 'CYCLES' else 'EEVEE'
147 | # material = o.active_material
148 | # if material.use_nodes:
149 | # color = material.node_tree.get_output_node(renderEngine).inputs[0].links[0].from_node.inputs[0].default_value
150 | # else:
151 | # color = material.diffuse_color
152 | # ext_data['color'] = [color[0], color[1], color[2]]
153 |
154 | self.text_index += 1
155 |
156 | if self.properties.enabled:
157 | if gltf2_object.extensions is None:
158 | gltf2_object.extensions = {}
159 | gltf2_object.extensions[glTF_extension_name] = self.Extension(
160 | name = glTF_extension_name,
161 | extension = ext_data,
162 | required = extension_is_required
163 | )
164 |
165 | text_type = (
166 | ('TEXTURE', "Texture", "Text rendered on a texture"),
167 | ('GEOMETRY', "Geometry", "Text rendered with a polygon mesh"),
168 | ('SDF', "SDF", "Text rendered with a SDF shader")
169 | )
170 |
171 | bpy.types.Object.text_type = bpy.props.EnumProperty(
172 | name = "Text Type",
173 | description = "How this text should be rendered in realtime",
174 | items = text_type,
175 | default = 'SDF',
176 | )
177 |
178 | bpy.types.Object.text_color = bpy.props.FloatVectorProperty(
179 | name = "Text Color",
180 | description = "Text color",
181 | default = (1.0, 1.0, 1.0, 1.0),
182 | size = 4,
183 | min = 0,
184 | max = 1,
185 | subtype = 'COLOR'
186 | )
187 |
188 |
189 | class AdditionalTextPropertiesPanel(bpy.types.Panel):
190 | """Creates a Panel in the data properties window"""
191 | bl_label = "MOZ_text extension"
192 | bl_idname = "OBJECT_PT_moztextpanel"
193 | bl_space_type = 'PROPERTIES'
194 | bl_region_type = 'WINDOW'
195 | bl_context = "data"
196 |
197 | def draw(self, context):
198 | layout = self.layout
199 |
200 | obj = context.object
201 | if type(obj.data).__name__ != 'TextCurve': return
202 |
203 | row = layout.row()
204 | row.prop(obj, "text_type")
205 | row = layout.row()
206 | row.prop(obj, "text_color")
207 |
208 |
209 | bpy.types.Object.undoExportActions = bpy.props.BoolProperty(
210 | name = "undoExportActions",
211 | description = "whether undoing modifications made during glTF export is needed",
212 | default = False
213 | )
214 |
215 | @persistent
216 | def afterExport(scene):
217 | for obj in bpy.data.objects:
218 | if not obj.type == 'FONT' or not obj.undoExportActions: continue
219 | obj.undoExportActions = False
220 |
221 |
--------------------------------------------------------------------------------
/test/GLTFLoader.module.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @author Rich Tibbett / https://github.com/richtr
3 | * @author mrdoob / http://mrdoob.com/
4 | * @author Tony Parisi / http://www.tonyparisi.com/
5 | * @author Takahiro / https://github.com/takahirox
6 | * @author Don McCurdy / https://www.donmccurdy.com
7 | */
8 |
9 |
10 | import {
11 | AnimationClip,
12 | Bone,
13 | Box3,
14 | BufferAttribute,
15 | BufferGeometry,
16 | ClampToEdgeWrapping,
17 | Color,
18 | DirectionalLight,
19 | DoubleSide,
20 | FileLoader,
21 | FrontSide,
22 | Group,
23 | InterleavedBuffer,
24 | InterleavedBufferAttribute,
25 | Interpolant,
26 | InterpolateDiscrete,
27 | InterpolateLinear,
28 | Line,
29 | LineBasicMaterial,
30 | LineLoop,
31 | LineSegments,
32 | LinearFilter,
33 | LinearMipmapLinearFilter,
34 | LinearMipmapNearestFilter,
35 | Loader,
36 | LoaderUtils,
37 | Material,
38 | MathUtils,
39 | Matrix4,
40 | Mesh,
41 | MeshBasicMaterial,
42 | MeshStandardMaterial,
43 | MirroredRepeatWrapping,
44 | NearestFilter,
45 | NearestMipmapLinearFilter,
46 | NearestMipmapNearestFilter,
47 | NumberKeyframeTrack,
48 | Object3D,
49 | OrthographicCamera,
50 | PerspectiveCamera,
51 | PointLight,
52 | Points,
53 | PointsMaterial,
54 | PropertyBinding,
55 | QuaternionKeyframeTrack,
56 | RGBAFormat,
57 | RGBFormat,
58 | RepeatWrapping,
59 | Skeleton,
60 | SkinnedMesh,
61 | Sphere,
62 | SpotLight,
63 | TangentSpaceNormalMap,
64 | TextureLoader,
65 | TriangleFanDrawMode,
66 | TriangleStripDrawMode,
67 | Vector2,
68 | Vector3,
69 | VectorKeyframeTrack,
70 | sRGBEncoding,
71 | FontLoader,
72 | TextBufferGeometry,
73 | PlaneBufferGeometry,
74 | CanvasTexture,
75 | MeshLambertMaterial
76 | } from "three";
77 |
78 | import {TextMesh} from 'troika-3d-text/dist/textmesh-standalone.esm.js';
79 |
80 |
81 | var GLTFLoader = ( function () {
82 |
83 | function GLTFLoader( manager ) {
84 |
85 | Loader.call( this, manager );
86 |
87 | this.dracoLoader = null;
88 | this.ddsLoader = null;
89 |
90 | }
91 |
92 | GLTFLoader.prototype = Object.assign( Object.create( Loader.prototype ), {
93 |
94 | constructor: GLTFLoader,
95 |
96 | load: function ( url, onLoad, onProgress, onError ) {
97 |
98 | var scope = this;
99 |
100 | var resourcePath;
101 |
102 | if ( this.resourcePath !== '' ) {
103 |
104 | resourcePath = this.resourcePath;
105 |
106 | } else if ( this.path !== '' ) {
107 |
108 | resourcePath = this.path;
109 |
110 | } else {
111 |
112 | resourcePath = LoaderUtils.extractUrlBase( url );
113 |
114 | }
115 |
116 | // Tells the LoadingManager to track an extra item, which resolves after
117 | // the model is fully loaded. This means the count of items loaded will
118 | // be incorrect, but ensures manager.onLoad() does not fire early.
119 | scope.manager.itemStart( url );
120 |
121 | var _onError = function ( e ) {
122 |
123 | if ( onError ) {
124 |
125 | onError( e );
126 |
127 | } else {
128 |
129 | console.error( e );
130 |
131 | }
132 |
133 | scope.manager.itemError( url );
134 | scope.manager.itemEnd( url );
135 |
136 | };
137 |
138 | var loader = new FileLoader( scope.manager );
139 |
140 | loader.setPath( this.path );
141 | loader.setResponseType( 'arraybuffer' );
142 |
143 | if ( scope.crossOrigin === 'use-credentials' ) {
144 |
145 | loader.setWithCredentials( true );
146 |
147 | }
148 |
149 | loader.load( url, function ( data ) {
150 |
151 | try {
152 |
153 | scope.parse( data, resourcePath, function ( gltf ) {
154 |
155 | onLoad( gltf );
156 |
157 | scope.manager.itemEnd( url );
158 |
159 | }, _onError );
160 |
161 | } catch ( e ) {
162 |
163 | _onError( e );
164 |
165 | }
166 |
167 | }, onProgress, _onError );
168 |
169 | },
170 |
171 | setDRACOLoader: function ( dracoLoader ) {
172 |
173 | this.dracoLoader = dracoLoader;
174 | return this;
175 |
176 | },
177 |
178 | setDDSLoader: function ( ddsLoader ) {
179 |
180 | this.ddsLoader = ddsLoader;
181 | return this;
182 |
183 | },
184 |
185 | parse: function ( data, path, onLoad, onError ) {
186 |
187 | var content;
188 | var extensions = {};
189 |
190 | if ( typeof data === 'string' ) {
191 |
192 | content = data;
193 |
194 | } else {
195 |
196 | var magic = LoaderUtils.decodeText( new Uint8Array( data, 0, 4 ) );
197 |
198 | if ( magic === BINARY_EXTENSION_HEADER_MAGIC ) {
199 |
200 | try {
201 |
202 | extensions[ EXTENSIONS.KHR_BINARY_GLTF ] = new GLTFBinaryExtension( data );
203 |
204 | } catch ( error ) {
205 |
206 | if ( onError ) onError( error );
207 | return;
208 |
209 | }
210 |
211 | content = extensions[ EXTENSIONS.KHR_BINARY_GLTF ].content;
212 |
213 | } else {
214 |
215 | content = LoaderUtils.decodeText( new Uint8Array( data ) );
216 |
217 | }
218 |
219 | }
220 |
221 | var json = JSON.parse( content );
222 |
223 | if ( json.asset === undefined || json.asset.version[ 0 ] < 2 ) {
224 |
225 | if ( onError ) onError( new Error( 'GLTFLoader: Unsupported asset. glTF versions >=2.0 are supported.' ) );
226 | return;
227 |
228 | }
229 |
230 | if ( json.extensionsUsed ) {
231 |
232 | for ( var i = 0; i < json.extensionsUsed.length; ++ i ) {
233 |
234 | var extensionName = json.extensionsUsed[ i ];
235 | var extensionsRequired = json.extensionsRequired || [];
236 |
237 | switch ( extensionName ) {
238 |
239 | case EXTENSIONS.KHR_LIGHTS_PUNCTUAL:
240 | extensions[ extensionName ] = new GLTFLightsExtension( json );
241 | break;
242 |
243 | case EXTENSIONS.KHR_MATERIALS_CLEARCOAT:
244 | extensions[ extensionName ] = new GLTFMaterialsClearcoatExtension();
245 | break;
246 |
247 | case EXTENSIONS.KHR_MATERIALS_UNLIT:
248 | extensions[ extensionName ] = new GLTFMaterialsUnlitExtension();
249 | break;
250 |
251 | case EXTENSIONS.KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS:
252 | extensions[ extensionName ] = new GLTFMaterialsPbrSpecularGlossinessExtension();
253 | break;
254 |
255 | case EXTENSIONS.KHR_DRACO_MESH_COMPRESSION:
256 | extensions[ extensionName ] = new GLTFDracoMeshCompressionExtension( json, this.dracoLoader );
257 | break;
258 |
259 | case EXTENSIONS.MSFT_TEXTURE_DDS:
260 | extensions[ extensionName ] = new GLTFTextureDDSExtension( this.ddsLoader );
261 | break;
262 |
263 | case EXTENSIONS.KHR_TEXTURE_TRANSFORM:
264 | extensions[ extensionName ] = new GLTFTextureTransformExtension();
265 | break;
266 |
267 | case EXTENSIONS.KHR_MESH_QUANTIZATION:
268 | extensions[ extensionName ] = new GLTFMeshQuantizationExtension();
269 | break;
270 |
271 | case EXTENSIONS.MOZ_TEXT:
272 | extensions[ extensionName ] = new GLTFTextExtension( json );
273 | break;
274 |
275 | default:
276 |
277 | if ( extensionsRequired.indexOf( extensionName ) >= 0 ) {
278 |
279 | console.warn( 'GLTFLoader: Unknown extension "' + extensionName + '".' );
280 |
281 | }
282 |
283 | }
284 |
285 | }
286 |
287 | }
288 |
289 | var parser = new GLTFParser( json, extensions, {
290 |
291 | path: path || this.resourcePath || '',
292 | crossOrigin: this.crossOrigin,
293 | manager: this.manager
294 |
295 | } );
296 |
297 | parser.parse( onLoad, onError );
298 |
299 | }
300 |
301 | } );
302 |
303 | /* GLTFREGISTRY */
304 |
305 | function GLTFRegistry() {
306 |
307 | var objects = {};
308 |
309 | return {
310 |
311 | get: function ( key ) {
312 |
313 | return objects[ key ];
314 |
315 | },
316 |
317 | add: function ( key, object ) {
318 |
319 | objects[ key ] = object;
320 |
321 | },
322 |
323 | remove: function ( key ) {
324 |
325 | delete objects[ key ];
326 |
327 | },
328 |
329 | removeAll: function () {
330 |
331 | objects = {};
332 |
333 | }
334 |
335 | };
336 |
337 | }
338 |
339 | /*********************************/
340 | /********** EXTENSIONS ***********/
341 | /*********************************/
342 |
343 | var EXTENSIONS = {
344 | KHR_BINARY_GLTF: 'KHR_binary_glTF',
345 | KHR_DRACO_MESH_COMPRESSION: 'KHR_draco_mesh_compression',
346 | KHR_LIGHTS_PUNCTUAL: 'KHR_lights_punctual',
347 | KHR_MATERIALS_CLEARCOAT: 'KHR_materials_clearcoat',
348 | KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS: 'KHR_materials_pbrSpecularGlossiness',
349 | KHR_MATERIALS_UNLIT: 'KHR_materials_unlit',
350 | KHR_TEXTURE_TRANSFORM: 'KHR_texture_transform',
351 | KHR_MESH_QUANTIZATION: 'KHR_mesh_quantization',
352 | MSFT_TEXTURE_DDS: 'MSFT_texture_dds',
353 | MOZ_TEXT: 'MOZ_text'
354 | };
355 |
356 | /**
357 | * DDS Texture Extension
358 | *
359 | * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Vendor/MSFT_texture_dds
360 | *
361 | */
362 | function GLTFTextureDDSExtension( ddsLoader ) {
363 |
364 | if ( ! ddsLoader ) {
365 |
366 | throw new Error( 'GLTFLoader: Attempting to load .dds texture without importing DDSLoader' );
367 |
368 | }
369 |
370 | this.name = EXTENSIONS.MSFT_TEXTURE_DDS;
371 | this.ddsLoader = ddsLoader;
372 |
373 | }
374 |
375 | /**
376 | * Punctual Lights Extension
377 | *
378 | * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_lights_punctual
379 | */
380 | function GLTFLightsExtension( json ) {
381 |
382 | this.name = EXTENSIONS.KHR_LIGHTS_PUNCTUAL;
383 |
384 | var extension = ( json.extensions && json.extensions[ EXTENSIONS.KHR_LIGHTS_PUNCTUAL ] ) || {};
385 | this.lightDefs = extension.lights || [];
386 |
387 | }
388 |
389 | GLTFLightsExtension.prototype.loadLight = function ( lightIndex ) {
390 |
391 | var lightDef = this.lightDefs[ lightIndex ];
392 | var lightNode;
393 |
394 | var color = new Color( 0xffffff );
395 | if ( lightDef.color !== undefined ) color.fromArray( lightDef.color );
396 |
397 | var range = lightDef.range !== undefined ? lightDef.range : 0;
398 |
399 | switch ( lightDef.type ) {
400 |
401 | case 'directional':
402 | lightNode = new DirectionalLight( color );
403 | lightNode.target.position.set( 0, 0, - 1 );
404 | lightNode.add( lightNode.target );
405 | break;
406 |
407 | case 'point':
408 | lightNode = new PointLight( color );
409 | lightNode.distance = range;
410 | break;
411 |
412 | case 'spot':
413 | lightNode = new SpotLight( color );
414 | lightNode.distance = range;
415 | // Handle spotlight properties.
416 | lightDef.spot = lightDef.spot || {};
417 | lightDef.spot.innerConeAngle = lightDef.spot.innerConeAngle !== undefined ? lightDef.spot.innerConeAngle : 0;
418 | lightDef.spot.outerConeAngle = lightDef.spot.outerConeAngle !== undefined ? lightDef.spot.outerConeAngle : Math.PI / 4.0;
419 | lightNode.angle = lightDef.spot.outerConeAngle;
420 | lightNode.penumbra = 1.0 - lightDef.spot.innerConeAngle / lightDef.spot.outerConeAngle;
421 | lightNode.target.position.set( 0, 0, - 1 );
422 | lightNode.add( lightNode.target );
423 | break;
424 |
425 | default:
426 | throw new Error( 'GLTFLoader: Unexpected light type, "' + lightDef.type + '".' );
427 |
428 | }
429 |
430 | // Some lights (e.g. spot) default to a position other than the origin. Reset the position
431 | // here, because node-level parsing will only override position if explicitly specified.
432 | lightNode.position.set( 0, 0, 0 );
433 |
434 | lightNode.decay = 2;
435 |
436 | if ( lightDef.intensity !== undefined ) lightNode.intensity = lightDef.intensity;
437 |
438 | lightNode.name = lightDef.name || ( 'light_' + lightIndex );
439 |
440 | return Promise.resolve( lightNode );
441 |
442 | };
443 |
444 | /**
445 | * Unlit Materials Extension
446 | *
447 | * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_unlit
448 | */
449 | function GLTFMaterialsUnlitExtension() {
450 |
451 | this.name = EXTENSIONS.KHR_MATERIALS_UNLIT;
452 |
453 | }
454 |
455 | GLTFMaterialsUnlitExtension.prototype.getMaterialType = function () {
456 |
457 | return MeshBasicMaterial;
458 |
459 | };
460 |
461 | GLTFMaterialsUnlitExtension.prototype.extendParams = function ( materialParams, materialDef, parser ) {
462 |
463 | var pending = [];
464 |
465 | materialParams.color = new Color( 1.0, 1.0, 1.0 );
466 | materialParams.opacity = 1.0;
467 |
468 | var metallicRoughness = materialDef.pbrMetallicRoughness;
469 |
470 | if ( metallicRoughness ) {
471 |
472 | if ( Array.isArray( metallicRoughness.baseColorFactor ) ) {
473 |
474 | var array = metallicRoughness.baseColorFactor;
475 |
476 | materialParams.color.fromArray( array );
477 | materialParams.opacity = array[ 3 ];
478 |
479 | }
480 |
481 | if ( metallicRoughness.baseColorTexture !== undefined ) {
482 |
483 | pending.push( parser.assignTexture( materialParams, 'map', metallicRoughness.baseColorTexture ) );
484 |
485 | }
486 |
487 | }
488 |
489 | return Promise.all( pending );
490 |
491 | };
492 |
493 | /**
494 | * Clearcoat Materials Extension
495 | *
496 | * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_clearcoat
497 | */
498 | function GLTFMaterialsClearcoatExtension() {
499 |
500 | this.name = EXTENSIONS.KHR_MATERIALS_CLEARCOAT;
501 |
502 | }
503 |
504 | GLTFMaterialsClearcoatExtension.prototype.getMaterialType = function () {
505 |
506 | return MeshPhysicalMaterial;
507 |
508 | };
509 |
510 | GLTFMaterialsClearcoatExtension.prototype.extendParams = function ( materialParams, materialDef, parser ) {
511 |
512 | var pending = [];
513 |
514 | var extension = materialDef.extensions[ this.name ];
515 |
516 | if ( extension.clearcoatFactor !== undefined ) {
517 |
518 | materialParams.clearcoat = extension.clearcoatFactor;
519 |
520 | }
521 |
522 | if ( extension.clearcoatTexture !== undefined ) {
523 |
524 | pending.push( parser.assignTexture( materialParams, 'clearcoatMap', extension.clearcoatTexture ) );
525 |
526 | }
527 |
528 | if ( extension.clearcoatRoughnessFactor !== undefined ) {
529 |
530 | materialParams.clearcoatRoughness = extension.clearcoatRoughnessFactor;
531 |
532 | }
533 |
534 | if ( extension.clearcoatRoughnessTexture !== undefined ) {
535 |
536 | pending.push( parser.assignTexture( materialParams, 'clearcoatRoughnessMap', extension.clearcoatRoughnessTexture ) );
537 |
538 | }
539 |
540 | if ( extension.clearcoatNormalTexture !== undefined ) {
541 |
542 | pending.push( parser.assignTexture( materialParams, 'clearcoatNormalMap', extension.clearcoatNormalTexture ) );
543 |
544 | if ( extension.clearcoatNormalTexture.scale !== undefined ) {
545 |
546 | var scale = extension.clearcoatNormalTexture.scale;
547 |
548 | materialParams.clearcoatNormalScale = new Vector2( scale, scale );
549 |
550 | }
551 |
552 | }
553 |
554 | return Promise.all( pending );
555 |
556 | };
557 |
558 | /* BINARY EXTENSION */
559 | var BINARY_EXTENSION_HEADER_MAGIC = 'glTF';
560 | var BINARY_EXTENSION_HEADER_LENGTH = 12;
561 | var BINARY_EXTENSION_CHUNK_TYPES = { JSON: 0x4E4F534A, BIN: 0x004E4942 };
562 |
563 | function GLTFBinaryExtension( data ) {
564 |
565 | this.name = EXTENSIONS.KHR_BINARY_GLTF;
566 | this.content = null;
567 | this.body = null;
568 |
569 | var headerView = new DataView( data, 0, BINARY_EXTENSION_HEADER_LENGTH );
570 |
571 | this.header = {
572 | magic: LoaderUtils.decodeText( new Uint8Array( data.slice( 0, 4 ) ) ),
573 | version: headerView.getUint32( 4, true ),
574 | length: headerView.getUint32( 8, true )
575 | };
576 |
577 | if ( this.header.magic !== BINARY_EXTENSION_HEADER_MAGIC ) {
578 |
579 | throw new Error( 'GLTFLoader: Unsupported glTF-Binary header.' );
580 |
581 | } else if ( this.header.version < 2.0 ) {
582 |
583 | throw new Error( 'GLTFLoader: Legacy binary file detected.' );
584 |
585 | }
586 |
587 | var chunkView = new DataView( data, BINARY_EXTENSION_HEADER_LENGTH );
588 | var chunkIndex = 0;
589 |
590 | while ( chunkIndex < chunkView.byteLength ) {
591 |
592 | var chunkLength = chunkView.getUint32( chunkIndex, true );
593 | chunkIndex += 4;
594 |
595 | var chunkType = chunkView.getUint32( chunkIndex, true );
596 | chunkIndex += 4;
597 |
598 | if ( chunkType === BINARY_EXTENSION_CHUNK_TYPES.JSON ) {
599 |
600 | var contentArray = new Uint8Array( data, BINARY_EXTENSION_HEADER_LENGTH + chunkIndex, chunkLength );
601 | this.content = LoaderUtils.decodeText( contentArray );
602 |
603 | } else if ( chunkType === BINARY_EXTENSION_CHUNK_TYPES.BIN ) {
604 |
605 | var byteOffset = BINARY_EXTENSION_HEADER_LENGTH + chunkIndex;
606 | this.body = data.slice( byteOffset, byteOffset + chunkLength );
607 |
608 | }
609 |
610 | // Clients must ignore chunks with unknown types.
611 |
612 | chunkIndex += chunkLength;
613 |
614 | }
615 |
616 | if ( this.content === null ) {
617 |
618 | throw new Error( 'GLTFLoader: JSON content not found.' );
619 |
620 | }
621 |
622 | }
623 |
624 | /**
625 | * DRACO Mesh Compression Extension
626 | *
627 | * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_draco_mesh_compression
628 | */
629 | function GLTFDracoMeshCompressionExtension( json, dracoLoader ) {
630 |
631 | if ( ! dracoLoader ) {
632 |
633 | throw new Error( 'GLTFLoader: No DRACOLoader instance provided.' );
634 |
635 | }
636 |
637 | this.name = EXTENSIONS.KHR_DRACO_MESH_COMPRESSION;
638 | this.json = json;
639 | this.dracoLoader = dracoLoader;
640 | this.dracoLoader.preload();
641 |
642 | }
643 |
644 | GLTFDracoMeshCompressionExtension.prototype.decodePrimitive = function ( primitive, parser ) {
645 |
646 | var json = this.json;
647 | var dracoLoader = this.dracoLoader;
648 | var bufferViewIndex = primitive.extensions[ this.name ].bufferView;
649 | var gltfAttributeMap = primitive.extensions[ this.name ].attributes;
650 | var threeAttributeMap = {};
651 | var attributeNormalizedMap = {};
652 | var attributeTypeMap = {};
653 |
654 | for ( var attributeName in gltfAttributeMap ) {
655 |
656 | var threeAttributeName = ATTRIBUTES[ attributeName ] || attributeName.toLowerCase();
657 |
658 | threeAttributeMap[ threeAttributeName ] = gltfAttributeMap[ attributeName ];
659 |
660 | }
661 |
662 | for ( attributeName in primitive.attributes ) {
663 |
664 | var threeAttributeName = ATTRIBUTES[ attributeName ] || attributeName.toLowerCase();
665 |
666 | if ( gltfAttributeMap[ attributeName ] !== undefined ) {
667 |
668 | var accessorDef = json.accessors[ primitive.attributes[ attributeName ] ];
669 | var componentType = WEBGL_COMPONENT_TYPES[ accessorDef.componentType ];
670 |
671 | attributeTypeMap[ threeAttributeName ] = componentType;
672 | attributeNormalizedMap[ threeAttributeName ] = accessorDef.normalized === true;
673 |
674 | }
675 |
676 | }
677 |
678 | return parser.getDependency( 'bufferView', bufferViewIndex ).then( function ( bufferView ) {
679 |
680 | return new Promise( function ( resolve ) {
681 |
682 | dracoLoader.decodeDracoFile( bufferView, function ( geometry ) {
683 |
684 | for ( var attributeName in geometry.attributes ) {
685 |
686 | var attribute = geometry.attributes[ attributeName ];
687 | var normalized = attributeNormalizedMap[ attributeName ];
688 |
689 | if ( normalized !== undefined ) attribute.normalized = normalized;
690 |
691 | }
692 |
693 | resolve( geometry );
694 |
695 | }, threeAttributeMap, attributeTypeMap );
696 |
697 | } );
698 |
699 | } );
700 |
701 | };
702 |
703 | /**
704 | * Texture Transform Extension
705 | *
706 | * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_texture_transform
707 | */
708 | function GLTFTextureTransformExtension() {
709 |
710 | this.name = EXTENSIONS.KHR_TEXTURE_TRANSFORM;
711 |
712 | }
713 |
714 | GLTFTextureTransformExtension.prototype.extendTexture = function ( texture, transform ) {
715 |
716 | texture = texture.clone();
717 |
718 | if ( transform.offset !== undefined ) {
719 |
720 | texture.offset.fromArray( transform.offset );
721 |
722 | }
723 |
724 | if ( transform.rotation !== undefined ) {
725 |
726 | texture.rotation = transform.rotation;
727 |
728 | }
729 |
730 | if ( transform.scale !== undefined ) {
731 |
732 | texture.repeat.fromArray( transform.scale );
733 |
734 | }
735 |
736 | if ( transform.texCoord !== undefined ) {
737 |
738 | console.warn( 'GLTFLoader: Custom UV sets in "' + this.name + '" extension not yet supported.' );
739 |
740 | }
741 |
742 | texture.needsUpdate = true;
743 |
744 | return texture;
745 |
746 | };
747 |
748 | /**
749 | * Specular-Glossiness Extension
750 | *
751 | * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_pbrSpecularGlossiness
752 | */
753 |
754 | /**
755 | * A sub class of StandardMaterial with some of the functionality
756 | * changed via the `onBeforeCompile` callback
757 | * @pailhead
758 | */
759 |
760 | function GLTFMeshStandardSGMaterial( params ) {
761 |
762 | MeshStandardMaterial.call( this );
763 |
764 | this.isGLTFSpecularGlossinessMaterial = true;
765 |
766 | //various chunks that need replacing
767 | var specularMapParsFragmentChunk = [
768 | '#ifdef USE_SPECULARMAP',
769 | ' uniform sampler2D specularMap;',
770 | '#endif'
771 | ].join( '\n' );
772 |
773 | var glossinessMapParsFragmentChunk = [
774 | '#ifdef USE_GLOSSINESSMAP',
775 | ' uniform sampler2D glossinessMap;',
776 | '#endif'
777 | ].join( '\n' );
778 |
779 | var specularMapFragmentChunk = [
780 | 'vec3 specularFactor = specular;',
781 | '#ifdef USE_SPECULARMAP',
782 | ' vec4 texelSpecular = texture2D( specularMap, vUv );',
783 | ' texelSpecular = sRGBToLinear( texelSpecular );',
784 | ' // reads channel RGB, compatible with a glTF Specular-Glossiness (RGBA) texture',
785 | ' specularFactor *= texelSpecular.rgb;',
786 | '#endif'
787 | ].join( '\n' );
788 |
789 | var glossinessMapFragmentChunk = [
790 | 'float glossinessFactor = glossiness;',
791 | '#ifdef USE_GLOSSINESSMAP',
792 | ' vec4 texelGlossiness = texture2D( glossinessMap, vUv );',
793 | ' // reads channel A, compatible with a glTF Specular-Glossiness (RGBA) texture',
794 | ' glossinessFactor *= texelGlossiness.a;',
795 | '#endif'
796 | ].join( '\n' );
797 |
798 | var lightPhysicalFragmentChunk = [
799 | 'PhysicalMaterial material;',
800 | 'material.diffuseColor = diffuseColor.rgb;',
801 | 'vec3 dxy = max( abs( dFdx( geometryNormal ) ), abs( dFdy( geometryNormal ) ) );',
802 | 'float geometryRoughness = max( max( dxy.x, dxy.y ), dxy.z );',
803 | 'material.specularRoughness = max( 1.0 - glossinessFactor, 0.0525 );// 0.0525 corresponds to the base mip of a 256 cubemap.',
804 | 'material.specularRoughness += geometryRoughness;',
805 | 'material.specularRoughness = min( material.specularRoughness, 1.0 );',
806 | 'material.specularColor = specularFactor.rgb;',
807 | ].join( '\n' );
808 |
809 | var uniforms = {
810 | specular: { value: new Color().setHex( 0xffffff ) },
811 | glossiness: { value: 1 },
812 | specularMap: { value: null },
813 | glossinessMap: { value: null }
814 | };
815 |
816 | this._extraUniforms = uniforms;
817 |
818 | // please see #14031 or #13198 for an alternate approach
819 | this.onBeforeCompile = function ( shader ) {
820 |
821 | for ( var uniformName in uniforms ) {
822 |
823 | shader.uniforms[ uniformName ] = uniforms[ uniformName ];
824 |
825 | }
826 |
827 | shader.fragmentShader = shader.fragmentShader.replace( 'uniform float roughness;', 'uniform vec3 specular;' );
828 | shader.fragmentShader = shader.fragmentShader.replace( 'uniform float metalness;', 'uniform float glossiness;' );
829 | shader.fragmentShader = shader.fragmentShader.replace( '#include ', specularMapParsFragmentChunk );
830 | shader.fragmentShader = shader.fragmentShader.replace( '#include ', glossinessMapParsFragmentChunk );
831 | shader.fragmentShader = shader.fragmentShader.replace( '#include ', specularMapFragmentChunk );
832 | shader.fragmentShader = shader.fragmentShader.replace( '#include ', glossinessMapFragmentChunk );
833 | shader.fragmentShader = shader.fragmentShader.replace( '#include ', lightPhysicalFragmentChunk );
834 |
835 | };
836 |
837 | /*eslint-disable*/
838 | Object.defineProperties(
839 | this,
840 | {
841 | specular: {
842 | get: function () { return uniforms.specular.value; },
843 | set: function ( v ) { uniforms.specular.value = v; }
844 | },
845 | specularMap: {
846 | get: function () { return uniforms.specularMap.value; },
847 | set: function ( v ) { uniforms.specularMap.value = v; }
848 | },
849 | glossiness: {
850 | get: function () { return uniforms.glossiness.value; },
851 | set: function ( v ) { uniforms.glossiness.value = v; }
852 | },
853 | glossinessMap: {
854 | get: function () { return uniforms.glossinessMap.value; },
855 | set: function ( v ) {
856 |
857 | uniforms.glossinessMap.value = v;
858 | //how about something like this - @pailhead
859 | if ( v ) {
860 |
861 | this.defines.USE_GLOSSINESSMAP = '';
862 | // set USE_ROUGHNESSMAP to enable vUv
863 | this.defines.USE_ROUGHNESSMAP = '';
864 |
865 | } else {
866 |
867 | delete this.defines.USE_ROUGHNESSMAP;
868 | delete this.defines.USE_GLOSSINESSMAP;
869 |
870 | }
871 |
872 | }
873 | }
874 | }
875 | );
876 |
877 | /*eslint-enable*/
878 | delete this.metalness;
879 | delete this.roughness;
880 | delete this.metalnessMap;
881 | delete this.roughnessMap;
882 |
883 | this.setValues( params );
884 |
885 | }
886 |
887 | GLTFMeshStandardSGMaterial.prototype = Object.create( MeshStandardMaterial.prototype );
888 | GLTFMeshStandardSGMaterial.prototype.constructor = GLTFMeshStandardSGMaterial;
889 |
890 | GLTFMeshStandardSGMaterial.prototype.copy = function ( source ) {
891 |
892 | MeshStandardMaterial.prototype.copy.call( this, source );
893 | this.specularMap = source.specularMap;
894 | this.specular.copy( source.specular );
895 | this.glossinessMap = source.glossinessMap;
896 | this.glossiness = source.glossiness;
897 | delete this.metalness;
898 | delete this.roughness;
899 | delete this.metalnessMap;
900 | delete this.roughnessMap;
901 | return this;
902 |
903 | };
904 |
905 | function GLTFMaterialsPbrSpecularGlossinessExtension() {
906 |
907 | return {
908 |
909 | name: EXTENSIONS.KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS,
910 |
911 | specularGlossinessParams: [
912 | 'color',
913 | 'map',
914 | 'lightMap',
915 | 'lightMapIntensity',
916 | 'aoMap',
917 | 'aoMapIntensity',
918 | 'emissive',
919 | 'emissiveIntensity',
920 | 'emissiveMap',
921 | 'bumpMap',
922 | 'bumpScale',
923 | 'normalMap',
924 | 'normalMapType',
925 | 'displacementMap',
926 | 'displacementScale',
927 | 'displacementBias',
928 | 'specularMap',
929 | 'specular',
930 | 'glossinessMap',
931 | 'glossiness',
932 | 'alphaMap',
933 | 'envMap',
934 | 'envMapIntensity',
935 | 'refractionRatio',
936 | ],
937 |
938 | getMaterialType: function () {
939 |
940 | return GLTFMeshStandardSGMaterial;
941 |
942 | },
943 |
944 | extendParams: function ( materialParams, materialDef, parser ) {
945 |
946 | var pbrSpecularGlossiness = materialDef.extensions[ this.name ];
947 |
948 | materialParams.color = new Color( 1.0, 1.0, 1.0 );
949 | materialParams.opacity = 1.0;
950 |
951 | var pending = [];
952 |
953 | if ( Array.isArray( pbrSpecularGlossiness.diffuseFactor ) ) {
954 |
955 | var array = pbrSpecularGlossiness.diffuseFactor;
956 |
957 | materialParams.color.fromArray( array );
958 | materialParams.opacity = array[ 3 ];
959 |
960 | }
961 |
962 | if ( pbrSpecularGlossiness.diffuseTexture !== undefined ) {
963 |
964 | pending.push( parser.assignTexture( materialParams, 'map', pbrSpecularGlossiness.diffuseTexture ) );
965 |
966 | }
967 |
968 | materialParams.emissive = new Color( 0.0, 0.0, 0.0 );
969 | materialParams.glossiness = pbrSpecularGlossiness.glossinessFactor !== undefined ? pbrSpecularGlossiness.glossinessFactor : 1.0;
970 | materialParams.specular = new Color( 1.0, 1.0, 1.0 );
971 |
972 | if ( Array.isArray( pbrSpecularGlossiness.specularFactor ) ) {
973 |
974 | materialParams.specular.fromArray( pbrSpecularGlossiness.specularFactor );
975 |
976 | }
977 |
978 | if ( pbrSpecularGlossiness.specularGlossinessTexture !== undefined ) {
979 |
980 | var specGlossMapDef = pbrSpecularGlossiness.specularGlossinessTexture;
981 | pending.push( parser.assignTexture( materialParams, 'glossinessMap', specGlossMapDef ) );
982 | pending.push( parser.assignTexture( materialParams, 'specularMap', specGlossMapDef ) );
983 |
984 | }
985 |
986 | return Promise.all( pending );
987 |
988 | },
989 |
990 | createMaterial: function ( materialParams ) {
991 |
992 | var material = new GLTFMeshStandardSGMaterial( materialParams );
993 | material.fog = true;
994 |
995 | material.color = materialParams.color;
996 |
997 | material.map = materialParams.map === undefined ? null : materialParams.map;
998 |
999 | material.lightMap = null;
1000 | material.lightMapIntensity = 1.0;
1001 |
1002 | material.aoMap = materialParams.aoMap === undefined ? null : materialParams.aoMap;
1003 | material.aoMapIntensity = 1.0;
1004 |
1005 | material.emissive = materialParams.emissive;
1006 | material.emissiveIntensity = 1.0;
1007 | material.emissiveMap = materialParams.emissiveMap === undefined ? null : materialParams.emissiveMap;
1008 |
1009 | material.bumpMap = materialParams.bumpMap === undefined ? null : materialParams.bumpMap;
1010 | material.bumpScale = 1;
1011 |
1012 | material.normalMap = materialParams.normalMap === undefined ? null : materialParams.normalMap;
1013 | material.normalMapType = TangentSpaceNormalMap;
1014 |
1015 | if ( materialParams.normalScale ) material.normalScale = materialParams.normalScale;
1016 |
1017 | material.displacementMap = null;
1018 | material.displacementScale = 1;
1019 | material.displacementBias = 0;
1020 |
1021 | material.specularMap = materialParams.specularMap === undefined ? null : materialParams.specularMap;
1022 | material.specular = materialParams.specular;
1023 |
1024 | material.glossinessMap = materialParams.glossinessMap === undefined ? null : materialParams.glossinessMap;
1025 | material.glossiness = materialParams.glossiness;
1026 |
1027 | material.alphaMap = null;
1028 |
1029 | material.envMap = materialParams.envMap === undefined ? null : materialParams.envMap;
1030 | material.envMapIntensity = 1.0;
1031 |
1032 | material.refractionRatio = 0.98;
1033 |
1034 | return material;
1035 |
1036 | },
1037 |
1038 | };
1039 |
1040 | }
1041 |
1042 | /**
1043 | * Mesh Quantization Extension
1044 | *
1045 | * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_mesh_quantization
1046 | */
1047 | function GLTFMeshQuantizationExtension() {
1048 |
1049 | this.name = EXTENSIONS.KHR_MESH_QUANTIZATION;
1050 |
1051 | }
1052 |
1053 | /**
1054 | * Text Extension
1055 | *
1056 | * Specification: ???
1057 | */
1058 | function GLTFTextExtension(json) {
1059 |
1060 | this.name = EXTENSIONS.MOZ_TEXT;
1061 |
1062 | var extension = ( json.extensions && json.extensions[ EXTENSIONS.MOZ_TEXT ] ) || {};
1063 | this.json = json;
1064 | }
1065 |
1066 | GLTFTextExtension.prototype.createText = async function ( index ) {
1067 | // find node
1068 | for (var i = 0; i < this.json.nodes.length; i++){
1069 | if (this.json.nodes[i].extensions
1070 | && this.json.nodes[i].extensions[ EXTENSIONS.MOZ_TEXT ]
1071 | && this.json.nodes[i].extensions[ EXTENSIONS.MOZ_TEXT ]['index'] === index ) {
1072 |
1073 | const jsonNode = this.json.nodes[i];
1074 | const textConfig = jsonNode.extensions[ EXTENSIONS.MOZ_TEXT ];
1075 | let node;
1076 | switch(textConfig.type) {
1077 | case 'sdf': node = this.createSDFTextNode(textConfig, jsonNode); break;
1078 | case 'texture': node = this.createTextureTextNode(textConfig, jsonNode); break;
1079 | case 'geometry': node = await this.createGeometryTextNode(textConfig, jsonNode); break;
1080 | }
1081 |
1082 | return Promise.resolve( node );
1083 | }
1084 | }
1085 | }
1086 |
1087 | const SDF_TEXT_MAGIC_SIZE_FACTOR = 1.4;
1088 | const SDF_TEXT_MAGIC_LINESPACING_FACTOR = 1.3;
1089 | const SDF_TEXT_MAGIC_LETTERSPACING_FACTOR = 0.66;
1090 |
1091 | GLTFTextExtension.prototype.createSDFTextNode = function ( text, jsonNode ) {
1092 | var anchor = [
1093 | text.alignX == 'right' ? 1 : (text.alignX == 'center' ? 0.5 : 0),
1094 | text.alignY == 'bottom' ? 1 : (text.alignY == 'center' ? 0.5 : 0),
1095 | ];
1096 | if (text.alignY == 'top_baseline') {
1097 | const numLines = text.value.split('\n').length;
1098 | anchor[1] = 1 / numLines;
1099 | }
1100 | const node = new TextMesh();
1101 | node.orientation = '+x-z';
1102 | node.text = text.value;
1103 | if (text.fontFile !== '') {
1104 | node.font = text.fontFile;
1105 | }
1106 | node.fontSize = text.size / SDF_TEXT_MAGIC_SIZE_FACTOR;
1107 | node.textAlign = text.alignX;
1108 | node.lineHeight = text.lineSpacing * SDF_TEXT_MAGIC_LINESPACING_FACTOR;
1109 | node.letterSpacing = text.letterSpacing * SDF_TEXT_MAGIC_LETTERSPACING_FACTOR;
1110 | node.anchor = anchor;
1111 | node.maxWidth = text.maxWidth === 0 ? Infinity : text.maxWidth;
1112 | node.whiteSpace = node.maxWidth === Infinity ? 'nowrap' : 'normal';
1113 | node.material = new MeshBasicMaterial({
1114 | color: new Color(text.color[0], text.color[1], text.color[2]),
1115 | transparent: text.color[3] < 1,
1116 | opacity: text.color[3]
1117 | });
1118 | node.sync();
1119 | return node;
1120 | }
1121 |
1122 | const TEXTURE_TEXT_MAGIC_SIZE_FACTOR = 0.62;
1123 | const TEXTURE_TEXT_SAFE_MARGIN = 2; // to avoid texture clipping
1124 |
1125 | GLTFTextExtension.prototype.createTextureTextNode = function ( text, jsonNode ) {
1126 | const scalex = jsonNode.scale && jsonNode.scale[0] || 1;
1127 | const scaley = jsonNode.scale && jsonNode.scale[1] || 1;
1128 | const width = text.dimensions[0] / scalex;
1129 | const height = text.dimensions[1] / scaley;
1130 | const sizeRatio = height / width;
1131 | const geometry = new PlaneBufferGeometry(width, height);
1132 | const canvas = document.createElement('canvas');
1133 |
1134 | /* // show canvas for debug
1135 | canvas.style.position = "absolute";
1136 | canvas.style.left = '0';
1137 | canvas.style.top = '0';
1138 | canvas.style.background = '#000';
1139 | canvas.zIndex = 9999999;
1140 | document.body.appendChild(canvas)
1141 | */
1142 |
1143 | canvas.width = 1024;
1144 | canvas.height = 1024 * sizeRatio;
1145 | const c = canvas.getContext('2d');
1146 | c.font = Math.floor(canvas.width / width * text.size * TEXTURE_TEXT_MAGIC_SIZE_FACTOR) + "px " + text.fontName;
1147 | const metrics = c.measureText(text.value);
1148 | c.textAlign = text.alignX;
1149 | const descent = metrics.actualBoundingBoxDescent;
1150 | const ascent = metrics.actualBoundingBoxAscent;
1151 | c.fillStyle = '#FFF';
1152 | var tx = TEXTURE_TEXT_SAFE_MARGIN;
1153 | switch(text.alignX) {
1154 | case 'center': tx = canvas.width / 2; break;
1155 | case 'right': tx = canvas.width - TEXTURE_TEXT_SAFE_MARGIN; break;
1156 | }
1157 |
1158 | const lines = text.value.split('\n');
1159 | for (var i = 0; i < lines.length; i++) {
1160 | c.fillText(
1161 | lines[i],
1162 | tx,
1163 | ascent + i * ascent * text.lineSpacing * 2 + TEXTURE_TEXT_SAFE_MARGIN);
1164 | }
1165 |
1166 | var ty = 0;
1167 | switch (text.alignY){
1168 | case 'bottom_baseline': ty = height / 2 - height * descent / canvas.height; break;
1169 | case 'bottom': ty = height / 2; break;
1170 | case 'top': ty = -height / 2; break;
1171 | case 'top_baseline': ty = -height / 2 + ascent / canvas.height * height; break;
1172 | }
1173 |
1174 | geometry.translate(0, ty, 0)
1175 | geometry.rotateX(-Math.PI / 2);
1176 |
1177 | const texture = new CanvasTexture(canvas);
1178 | const material = new MeshBasicMaterial({
1179 | color: new Color(text.color[0], text.color[1], text.color[2]),
1180 | map: texture,
1181 | transparent: true,
1182 | opacity: text.color[3]
1183 | });
1184 | const node = new Mesh(geometry, material);
1185 | return node;
1186 | }
1187 |
1188 | const GEOMETRY_TEXT_MAGIC_SIZE_FACTOR = 1.88;
1189 | const GEOMETRY_TEXT_MAGIC_BB_YMIN = 0;
1190 | const GEOMETRY_TEXT_MAGIC_BB_YMAX = 2000;
1191 | const GEOMETRY_TEXT_MAGIC_BEVEL_THICKNESS = 0.1;
1192 |
1193 | GLTFTextExtension.prototype.createGeometryTextNode = async function ( text, jsonNode ) {
1194 | return new Promise(resolve => {
1195 |
1196 | if (text.fontFile == '') {
1197 | console.warn("GLTFLoader: geometry text does not have a font defined (\"" + text.value + "\")");
1198 | resolve(new Object3D());
1199 | }
1200 |
1201 | new FontLoader().load(
1202 | text.fontFile.substring(0, text.fontFile.lastIndexOf('.')) + '.json',
1203 |
1204 | font => {
1205 | //magic boundingBox change to better match Blender's look
1206 | font.data.boundingBox.yMin = GEOMETRY_TEXT_MAGIC_BB_YMIN;
1207 | font.data.boundingBox.yMax = GEOMETRY_TEXT_MAGIC_BB_YMAX * text.lineSpacing;
1208 | const geometry = new TextBufferGeometry(
1209 | text.value,
1210 | {
1211 | font: font,
1212 | size: text.size / GEOMETRY_TEXT_MAGIC_SIZE_FACTOR,
1213 | height: text.extrude * 2,
1214 | curveSegments: text.extrudeResolution,
1215 | bevelEnabled: text.bevel > 0,
1216 | bevelThickness: GEOMETRY_TEXT_MAGIC_BEVEL_THICKNESS,
1217 | bevelSize: text.bevel,
1218 | bevelOffset: text.bevelOffset,
1219 | bevelSegments: text.bevelResolution
1220 | }
1221 | );
1222 |
1223 | geometry.computeBoundingBox();
1224 | const size = geometry.boundingBox.getSize();
1225 |
1226 | var dx;
1227 | switch(text.alignX) {
1228 | case 'left': dx = 0; break;
1229 | case 'right': dx = -size.x; break;
1230 | default: dx = -size.x / 2; break;
1231 | }
1232 |
1233 | var dy;
1234 | switch(text.alignY) {
1235 | case 'top': case 'top_baseline': dy = -size.y / 2; break;
1236 | case 'bottom': case 'bottom_baseline': dy = size.y; break;
1237 | default: dy = 0; break;
1238 | }
1239 |
1240 | geometry.translate(dx, dy, -text.extrude);
1241 | geometry.rotateX(-Math.PI / 2);
1242 |
1243 | const material = new MeshLambertMaterial({
1244 | color: new Color(text.color[0], text.color[1], text.color[2]),
1245 | transparent: text.color[3] < 1,
1246 | opacity: text.color[3],
1247 | });
1248 |
1249 | resolve(new Mesh(geometry, material));
1250 | });
1251 | });
1252 |
1253 | }
1254 |
1255 |
1256 | /*********************************/
1257 | /********** INTERPOLATION ********/
1258 | /*********************************/
1259 |
1260 | // Spline Interpolation
1261 | // Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#appendix-c-spline-interpolation
1262 | function GLTFCubicSplineInterpolant( parameterPositions, sampleValues, sampleSize, resultBuffer ) {
1263 |
1264 | Interpolant.call( this, parameterPositions, sampleValues, sampleSize, resultBuffer );
1265 |
1266 | }
1267 |
1268 | GLTFCubicSplineInterpolant.prototype = Object.create( Interpolant.prototype );
1269 | GLTFCubicSplineInterpolant.prototype.constructor = GLTFCubicSplineInterpolant;
1270 |
1271 | GLTFCubicSplineInterpolant.prototype.copySampleValue_ = function ( index ) {
1272 |
1273 | // Copies a sample value to the result buffer. See description of glTF
1274 | // CUBICSPLINE values layout in interpolate_() function below.
1275 |
1276 | var result = this.resultBuffer,
1277 | values = this.sampleValues,
1278 | valueSize = this.valueSize,
1279 | offset = index * valueSize * 3 + valueSize;
1280 |
1281 | for ( var i = 0; i !== valueSize; i ++ ) {
1282 |
1283 | result[ i ] = values[ offset + i ];
1284 |
1285 | }
1286 |
1287 | return result;
1288 |
1289 | };
1290 |
1291 | GLTFCubicSplineInterpolant.prototype.beforeStart_ = GLTFCubicSplineInterpolant.prototype.copySampleValue_;
1292 |
1293 | GLTFCubicSplineInterpolant.prototype.afterEnd_ = GLTFCubicSplineInterpolant.prototype.copySampleValue_;
1294 |
1295 | GLTFCubicSplineInterpolant.prototype.interpolate_ = function ( i1, t0, t, t1 ) {
1296 |
1297 | var result = this.resultBuffer;
1298 | var values = this.sampleValues;
1299 | var stride = this.valueSize;
1300 |
1301 | var stride2 = stride * 2;
1302 | var stride3 = stride * 3;
1303 |
1304 | var td = t1 - t0;
1305 |
1306 | var p = ( t - t0 ) / td;
1307 | var pp = p * p;
1308 | var ppp = pp * p;
1309 |
1310 | var offset1 = i1 * stride3;
1311 | var offset0 = offset1 - stride3;
1312 |
1313 | var s2 = - 2 * ppp + 3 * pp;
1314 | var s3 = ppp - pp;
1315 | var s0 = 1 - s2;
1316 | var s1 = s3 - pp + p;
1317 |
1318 | // Layout of keyframe output values for CUBICSPLINE animations:
1319 | // [ inTangent_1, splineVertex_1, outTangent_1, inTangent_2, splineVertex_2, ... ]
1320 | for ( var i = 0; i !== stride; i ++ ) {
1321 |
1322 | var p0 = values[ offset0 + i + stride ]; // splineVertex_k
1323 | var m0 = values[ offset0 + i + stride2 ] * td; // outTangent_k * (t_k+1 - t_k)
1324 | var p1 = values[ offset1 + i + stride ]; // splineVertex_k+1
1325 | var m1 = values[ offset1 + i ] * td; // inTangent_k+1 * (t_k+1 - t_k)
1326 |
1327 | result[ i ] = s0 * p0 + s1 * m0 + s2 * p1 + s3 * m1;
1328 |
1329 | }
1330 |
1331 | return result;
1332 |
1333 | };
1334 |
1335 | /*********************************/
1336 | /********** INTERNALS ************/
1337 | /*********************************/
1338 |
1339 | /* CONSTANTS */
1340 |
1341 | var WEBGL_CONSTANTS = {
1342 | FLOAT: 5126,
1343 | //FLOAT_MAT2: 35674,
1344 | FLOAT_MAT3: 35675,
1345 | FLOAT_MAT4: 35676,
1346 | FLOAT_VEC2: 35664,
1347 | FLOAT_VEC3: 35665,
1348 | FLOAT_VEC4: 35666,
1349 | LINEAR: 9729,
1350 | REPEAT: 10497,
1351 | SAMPLER_2D: 35678,
1352 | POINTS: 0,
1353 | LINES: 1,
1354 | LINE_LOOP: 2,
1355 | LINE_STRIP: 3,
1356 | TRIANGLES: 4,
1357 | TRIANGLE_STRIP: 5,
1358 | TRIANGLE_FAN: 6,
1359 | UNSIGNED_BYTE: 5121,
1360 | UNSIGNED_SHORT: 5123
1361 | };
1362 |
1363 | var WEBGL_COMPONENT_TYPES = {
1364 | 5120: Int8Array,
1365 | 5121: Uint8Array,
1366 | 5122: Int16Array,
1367 | 5123: Uint16Array,
1368 | 5125: Uint32Array,
1369 | 5126: Float32Array
1370 | };
1371 |
1372 | var WEBGL_FILTERS = {
1373 | 9728: NearestFilter,
1374 | 9729: LinearFilter,
1375 | 9984: NearestMipmapNearestFilter,
1376 | 9985: LinearMipmapNearestFilter,
1377 | 9986: NearestMipmapLinearFilter,
1378 | 9987: LinearMipmapLinearFilter
1379 | };
1380 |
1381 | var WEBGL_WRAPPINGS = {
1382 | 33071: ClampToEdgeWrapping,
1383 | 33648: MirroredRepeatWrapping,
1384 | 10497: RepeatWrapping
1385 | };
1386 |
1387 | var WEBGL_TYPE_SIZES = {
1388 | 'SCALAR': 1,
1389 | 'VEC2': 2,
1390 | 'VEC3': 3,
1391 | 'VEC4': 4,
1392 | 'MAT2': 4,
1393 | 'MAT3': 9,
1394 | 'MAT4': 16
1395 | };
1396 |
1397 | var ATTRIBUTES = {
1398 | POSITION: 'position',
1399 | NORMAL: 'normal',
1400 | TANGENT: 'tangent',
1401 | TEXCOORD_0: 'uv',
1402 | TEXCOORD_1: 'uv2',
1403 | COLOR_0: 'color',
1404 | WEIGHTS_0: 'skinWeight',
1405 | JOINTS_0: 'skinIndex',
1406 | };
1407 |
1408 | var PATH_PROPERTIES = {
1409 | scale: 'scale',
1410 | translation: 'position',
1411 | rotation: 'quaternion',
1412 | weights: 'morphTargetInfluences'
1413 | };
1414 |
1415 | var INTERPOLATION = {
1416 | CUBICSPLINE: undefined, // We use a custom interpolant (GLTFCubicSplineInterpolation) for CUBICSPLINE tracks. Each
1417 | // keyframe track will be initialized with a default interpolation type, then modified.
1418 | LINEAR: InterpolateLinear,
1419 | STEP: InterpolateDiscrete
1420 | };
1421 |
1422 | var ALPHA_MODES = {
1423 | OPAQUE: 'OPAQUE',
1424 | MASK: 'MASK',
1425 | BLEND: 'BLEND'
1426 | };
1427 |
1428 | var MIME_TYPE_FORMATS = {
1429 | 'image/png': RGBAFormat,
1430 | 'image/jpeg': RGBFormat
1431 | };
1432 |
1433 | /* UTILITY FUNCTIONS */
1434 |
1435 | function resolveURL( url, path ) {
1436 |
1437 | // Invalid URL
1438 | if ( typeof url !== 'string' || url === '' ) return '';
1439 |
1440 | // Host Relative URL
1441 | if ( /^https?:\/\//i.test( path ) && /^\//.test( url ) ) {
1442 |
1443 | path = path.replace( /(^https?:\/\/[^\/]+).*/i, '$1' );
1444 |
1445 | }
1446 |
1447 | // Absolute URL http://,https://,//
1448 | if ( /^(https?:)?\/\//i.test( url ) ) return url;
1449 |
1450 | // Data URI
1451 | if ( /^data:.*,.*$/i.test( url ) ) return url;
1452 |
1453 | // Blob URL
1454 | if ( /^blob:.*$/i.test( url ) ) return url;
1455 |
1456 | // Relative URL
1457 | return path + url;
1458 |
1459 | }
1460 |
1461 | /**
1462 | * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#default-material
1463 | */
1464 | function createDefaultMaterial( cache ) {
1465 |
1466 | if ( cache[ 'DefaultMaterial' ] === undefined ) {
1467 |
1468 | cache[ 'DefaultMaterial' ] = new MeshStandardMaterial( {
1469 | color: 0xFFFFFF,
1470 | emissive: 0x000000,
1471 | metalness: 1,
1472 | roughness: 1,
1473 | transparent: false,
1474 | depthTest: true,
1475 | side: FrontSide
1476 | } );
1477 |
1478 | }
1479 |
1480 | return cache[ 'DefaultMaterial' ];
1481 |
1482 | }
1483 |
1484 | function addUnknownExtensionsToUserData( knownExtensions, object, objectDef ) {
1485 |
1486 | // Add unknown glTF extensions to an object's userData.
1487 |
1488 | for ( var name in objectDef.extensions ) {
1489 |
1490 | if ( knownExtensions[ name ] === undefined ) {
1491 |
1492 | object.userData.gltfExtensions = object.userData.gltfExtensions || {};
1493 | object.userData.gltfExtensions[ name ] = objectDef.extensions[ name ];
1494 |
1495 | }
1496 |
1497 | }
1498 |
1499 | }
1500 |
1501 | /**
1502 | * @param {Object3D|Material|BufferGeometry} object
1503 | * @param {GLTF.definition} gltfDef
1504 | */
1505 | function assignExtrasToUserData( object, gltfDef ) {
1506 |
1507 | if ( gltfDef.extras !== undefined ) {
1508 |
1509 | if ( typeof gltfDef.extras === 'object' ) {
1510 |
1511 | Object.assign( object.userData, gltfDef.extras );
1512 |
1513 | } else {
1514 |
1515 | console.warn( 'GLTFLoader: Ignoring primitive type .extras, ' + gltfDef.extras );
1516 |
1517 | }
1518 |
1519 | }
1520 |
1521 | }
1522 |
1523 | /**
1524 | * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#morph-targets
1525 | *
1526 | * @param {BufferGeometry} geometry
1527 | * @param {Array} targets
1528 | * @param {GLTFParser} parser
1529 | * @return {Promise}
1530 | */
1531 | function addMorphTargets( geometry, targets, parser ) {
1532 |
1533 | var hasMorphPosition = false;
1534 | var hasMorphNormal = false;
1535 |
1536 | for ( var i = 0, il = targets.length; i < il; i ++ ) {
1537 |
1538 | var target = targets[ i ];
1539 |
1540 | if ( target.POSITION !== undefined ) hasMorphPosition = true;
1541 | if ( target.NORMAL !== undefined ) hasMorphNormal = true;
1542 |
1543 | if ( hasMorphPosition && hasMorphNormal ) break;
1544 |
1545 | }
1546 |
1547 | if ( ! hasMorphPosition && ! hasMorphNormal ) return Promise.resolve( geometry );
1548 |
1549 | var pendingPositionAccessors = [];
1550 | var pendingNormalAccessors = [];
1551 |
1552 | for ( var i = 0, il = targets.length; i < il; i ++ ) {
1553 |
1554 | var target = targets[ i ];
1555 |
1556 | if ( hasMorphPosition ) {
1557 |
1558 | var pendingAccessor = target.POSITION !== undefined
1559 | ? parser.getDependency( 'accessor', target.POSITION )
1560 | : geometry.attributes.position;
1561 |
1562 | pendingPositionAccessors.push( pendingAccessor );
1563 |
1564 | }
1565 |
1566 | if ( hasMorphNormal ) {
1567 |
1568 | var pendingAccessor = target.NORMAL !== undefined
1569 | ? parser.getDependency( 'accessor', target.NORMAL )
1570 | : geometry.attributes.normal;
1571 |
1572 | pendingNormalAccessors.push( pendingAccessor );
1573 |
1574 | }
1575 |
1576 | }
1577 |
1578 | return Promise.all( [
1579 | Promise.all( pendingPositionAccessors ),
1580 | Promise.all( pendingNormalAccessors )
1581 | ] ).then( function ( accessors ) {
1582 |
1583 | var morphPositions = accessors[ 0 ];
1584 | var morphNormals = accessors[ 1 ];
1585 |
1586 | if ( hasMorphPosition ) geometry.morphAttributes.position = morphPositions;
1587 | if ( hasMorphNormal ) geometry.morphAttributes.normal = morphNormals;
1588 | geometry.morphTargetsRelative = true;
1589 |
1590 | return geometry;
1591 |
1592 | } );
1593 |
1594 | }
1595 |
1596 | /**
1597 | * @param {Mesh} mesh
1598 | * @param {GLTF.Mesh} meshDef
1599 | */
1600 | function updateMorphTargets( mesh, meshDef ) {
1601 |
1602 | mesh.updateMorphTargets();
1603 |
1604 | if ( meshDef.weights !== undefined ) {
1605 |
1606 | for ( var i = 0, il = meshDef.weights.length; i < il; i ++ ) {
1607 |
1608 | mesh.morphTargetInfluences[ i ] = meshDef.weights[ i ];
1609 |
1610 | }
1611 |
1612 | }
1613 |
1614 | // .extras has user-defined data, so check that .extras.targetNames is an array.
1615 | if ( meshDef.extras && Array.isArray( meshDef.extras.targetNames ) ) {
1616 |
1617 | var targetNames = meshDef.extras.targetNames;
1618 |
1619 | if ( mesh.morphTargetInfluences.length === targetNames.length ) {
1620 |
1621 | mesh.morphTargetDictionary = {};
1622 |
1623 | for ( var i = 0, il = targetNames.length; i < il; i ++ ) {
1624 |
1625 | mesh.morphTargetDictionary[ targetNames[ i ] ] = i;
1626 |
1627 | }
1628 |
1629 | } else {
1630 |
1631 | console.warn( 'GLTFLoader: Invalid extras.targetNames length. Ignoring names.' );
1632 |
1633 | }
1634 |
1635 | }
1636 |
1637 | }
1638 |
1639 | function createPrimitiveKey( primitiveDef ) {
1640 |
1641 | var dracoExtension = primitiveDef.extensions && primitiveDef.extensions[ EXTENSIONS.KHR_DRACO_MESH_COMPRESSION ];
1642 | var geometryKey;
1643 |
1644 | if ( dracoExtension ) {
1645 |
1646 | geometryKey = 'draco:' + dracoExtension.bufferView
1647 | + ':' + dracoExtension.indices
1648 | + ':' + createAttributesKey( dracoExtension.attributes );
1649 |
1650 | } else {
1651 |
1652 | geometryKey = primitiveDef.indices + ':' + createAttributesKey( primitiveDef.attributes ) + ':' + primitiveDef.mode;
1653 |
1654 | }
1655 |
1656 | return geometryKey;
1657 |
1658 | }
1659 |
1660 | function createAttributesKey( attributes ) {
1661 |
1662 | var attributesKey = '';
1663 |
1664 | var keys = Object.keys( attributes ).sort();
1665 |
1666 | for ( var i = 0, il = keys.length; i < il; i ++ ) {
1667 |
1668 | attributesKey += keys[ i ] + ':' + attributes[ keys[ i ] ] + ';';
1669 |
1670 | }
1671 |
1672 | return attributesKey;
1673 |
1674 | }
1675 |
1676 | /* GLTF PARSER */
1677 |
1678 | function GLTFParser( json, extensions, options ) {
1679 |
1680 | this.json = json || {};
1681 | this.extensions = extensions || {};
1682 | this.options = options || {};
1683 |
1684 | // loader object cache
1685 | this.cache = new GLTFRegistry();
1686 |
1687 | // BufferGeometry caching
1688 | this.primitiveCache = {};
1689 |
1690 | this.textureLoader = new TextureLoader( this.options.manager );
1691 | this.textureLoader.setCrossOrigin( this.options.crossOrigin );
1692 |
1693 | this.fileLoader = new FileLoader( this.options.manager );
1694 | this.fileLoader.setResponseType( 'arraybuffer' );
1695 |
1696 | if ( this.options.crossOrigin === 'use-credentials' ) {
1697 |
1698 | this.fileLoader.setWithCredentials( true );
1699 |
1700 | }
1701 |
1702 | }
1703 |
1704 | GLTFParser.prototype.parse = function ( onLoad, onError ) {
1705 |
1706 | var parser = this;
1707 | var json = this.json;
1708 | var extensions = this.extensions;
1709 |
1710 | // Clear the loader cache
1711 | this.cache.removeAll();
1712 |
1713 | // Mark the special nodes/meshes in json for efficient parse
1714 | this.markDefs();
1715 |
1716 | Promise.all( [
1717 |
1718 | this.getDependencies( 'scene' ),
1719 | this.getDependencies( 'animation' ),
1720 | this.getDependencies( 'camera' ),
1721 |
1722 | ] ).then( function ( dependencies ) {
1723 |
1724 | var result = {
1725 | scene: dependencies[ 0 ][ json.scene || 0 ],
1726 | scenes: dependencies[ 0 ],
1727 | animations: dependencies[ 1 ],
1728 | cameras: dependencies[ 2 ],
1729 | asset: json.asset,
1730 | parser: parser,
1731 | userData: {}
1732 | };
1733 |
1734 | addUnknownExtensionsToUserData( extensions, result, json );
1735 |
1736 | assignExtrasToUserData( result, json );
1737 |
1738 | onLoad( result );
1739 |
1740 | } ).catch( onError );
1741 |
1742 | };
1743 |
1744 | /**
1745 | * Marks the special nodes/meshes in json for efficient parse.
1746 | */
1747 | GLTFParser.prototype.markDefs = function () {
1748 |
1749 | var nodeDefs = this.json.nodes || [];
1750 | var skinDefs = this.json.skins || [];
1751 | var meshDefs = this.json.meshes || [];
1752 |
1753 | var meshReferences = {};
1754 | var meshUses = {};
1755 |
1756 | // Nothing in the node definition indicates whether it is a Bone or an
1757 | // Object3D. Use the skins' joint references to mark bones.
1758 | for ( var skinIndex = 0, skinLength = skinDefs.length; skinIndex < skinLength; skinIndex ++ ) {
1759 |
1760 | var joints = skinDefs[ skinIndex ].joints;
1761 |
1762 | for ( var i = 0, il = joints.length; i < il; i ++ ) {
1763 |
1764 | nodeDefs[ joints[ i ] ].isBone = true;
1765 |
1766 | }
1767 |
1768 | }
1769 |
1770 | // Meshes can (and should) be reused by multiple nodes in a glTF asset. To
1771 | // avoid having more than one Mesh with the same name, count
1772 | // references and rename instances below.
1773 | //
1774 | // Example: CesiumMilkTruck sample model reuses "Wheel" meshes.
1775 | for ( var nodeIndex = 0, nodeLength = nodeDefs.length; nodeIndex < nodeLength; nodeIndex ++ ) {
1776 |
1777 | var nodeDef = nodeDefs[ nodeIndex ];
1778 |
1779 | if ( nodeDef.mesh !== undefined ) {
1780 |
1781 | if ( meshReferences[ nodeDef.mesh ] === undefined ) {
1782 |
1783 | meshReferences[ nodeDef.mesh ] = meshUses[ nodeDef.mesh ] = 0;
1784 |
1785 | }
1786 |
1787 | meshReferences[ nodeDef.mesh ] ++;
1788 |
1789 | // Nothing in the mesh definition indicates whether it is
1790 | // a SkinnedMesh or Mesh. Use the node's mesh reference
1791 | // to mark SkinnedMesh if node has skin.
1792 | if ( nodeDef.skin !== undefined ) {
1793 |
1794 | meshDefs[ nodeDef.mesh ].isSkinnedMesh = true;
1795 |
1796 | }
1797 |
1798 | }
1799 |
1800 | }
1801 |
1802 | this.json.meshReferences = meshReferences;
1803 | this.json.meshUses = meshUses;
1804 |
1805 | };
1806 |
1807 | /**
1808 | * Requests the specified dependency asynchronously, with caching.
1809 | * @param {string} type
1810 | * @param {number} index
1811 | * @return {Promise}
1812 | */
1813 | GLTFParser.prototype.getDependency = function ( type, index ) {
1814 |
1815 | var cacheKey = type + ':' + index;
1816 | var dependency = this.cache.get( cacheKey );
1817 |
1818 | if ( ! dependency ) {
1819 |
1820 | switch ( type ) {
1821 |
1822 | case 'scene':
1823 | dependency = this.loadScene( index );
1824 | break;
1825 |
1826 | case 'node':
1827 | dependency = this.loadNode( index );
1828 | break;
1829 |
1830 | case 'mesh':
1831 | dependency = this.loadMesh( index );
1832 | break;
1833 |
1834 | case 'accessor':
1835 | dependency = this.loadAccessor( index );
1836 | break;
1837 |
1838 | case 'bufferView':
1839 | dependency = this.loadBufferView( index );
1840 | break;
1841 |
1842 | case 'buffer':
1843 | dependency = this.loadBuffer( index );
1844 | break;
1845 |
1846 | case 'material':
1847 | dependency = this.loadMaterial( index );
1848 | break;
1849 |
1850 | case 'texture':
1851 | dependency = this.loadTexture( index );
1852 | break;
1853 |
1854 | case 'skin':
1855 | dependency = this.loadSkin( index );
1856 | break;
1857 |
1858 | case 'animation':
1859 | dependency = this.loadAnimation( index );
1860 | break;
1861 |
1862 | case 'camera':
1863 | dependency = this.loadCamera( index );
1864 | break;
1865 |
1866 | case 'light':
1867 | dependency = this.extensions[ EXTENSIONS.KHR_LIGHTS_PUNCTUAL ].loadLight( index );
1868 | break;
1869 |
1870 | case 'text':
1871 | dependency = this.extensions[ EXTENSIONS.MOZ_TEXT ].createText( index );
1872 | break;
1873 |
1874 | default:
1875 | throw new Error( 'Unknown type: ' + type );
1876 |
1877 | }
1878 |
1879 | this.cache.add( cacheKey, dependency );
1880 |
1881 | }
1882 |
1883 | return dependency;
1884 |
1885 | };
1886 |
1887 | /**
1888 | * Requests all dependencies of the specified type asynchronously, with caching.
1889 | * @param {string} type
1890 | * @return {Promise>}
1891 | */
1892 | GLTFParser.prototype.getDependencies = function ( type ) {
1893 |
1894 | var dependencies = this.cache.get( type );
1895 |
1896 | if ( ! dependencies ) {
1897 |
1898 | var parser = this;
1899 | var defs = this.json[ type + ( type === 'mesh' ? 'es' : 's' ) ] || [];
1900 |
1901 | dependencies = Promise.all( defs.map( function ( def, index ) {
1902 |
1903 | return parser.getDependency( type, index );
1904 |
1905 | } ) );
1906 |
1907 | this.cache.add( type, dependencies );
1908 |
1909 | }
1910 |
1911 | return dependencies;
1912 |
1913 | };
1914 |
1915 | /**
1916 | * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#buffers-and-buffer-views
1917 | * @param {number} bufferIndex
1918 | * @return {Promise}
1919 | */
1920 | GLTFParser.prototype.loadBuffer = function ( bufferIndex ) {
1921 |
1922 | var bufferDef = this.json.buffers[ bufferIndex ];
1923 | var loader = this.fileLoader;
1924 |
1925 | if ( bufferDef.type && bufferDef.type !== 'arraybuffer' ) {
1926 |
1927 | throw new Error( 'GLTFLoader: ' + bufferDef.type + ' buffer type is not supported.' );
1928 |
1929 | }
1930 |
1931 | // If present, GLB container is required to be the first buffer.
1932 | if ( bufferDef.uri === undefined && bufferIndex === 0 ) {
1933 |
1934 | return Promise.resolve( this.extensions[ EXTENSIONS.KHR_BINARY_GLTF ].body );
1935 |
1936 | }
1937 |
1938 | var options = this.options;
1939 |
1940 | return new Promise( function ( resolve, reject ) {
1941 |
1942 | loader.load( resolveURL( bufferDef.uri, options.path ), resolve, undefined, function () {
1943 |
1944 | reject( new Error( 'GLTFLoader: Failed to load buffer "' + bufferDef.uri + '".' ) );
1945 |
1946 | } );
1947 |
1948 | } );
1949 |
1950 | };
1951 |
1952 | /**
1953 | * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#buffers-and-buffer-views
1954 | * @param {number} bufferViewIndex
1955 | * @return {Promise}
1956 | */
1957 | GLTFParser.prototype.loadBufferView = function ( bufferViewIndex ) {
1958 |
1959 | var bufferViewDef = this.json.bufferViews[ bufferViewIndex ];
1960 |
1961 | return this.getDependency( 'buffer', bufferViewDef.buffer ).then( function ( buffer ) {
1962 |
1963 | var byteLength = bufferViewDef.byteLength || 0;
1964 | var byteOffset = bufferViewDef.byteOffset || 0;
1965 | return buffer.slice( byteOffset, byteOffset + byteLength );
1966 |
1967 | } );
1968 |
1969 | };
1970 |
1971 | /**
1972 | * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#accessors
1973 | * @param {number} accessorIndex
1974 | * @return {Promise}
1975 | */
1976 | GLTFParser.prototype.loadAccessor = function ( accessorIndex ) {
1977 |
1978 | var parser = this;
1979 | var json = this.json;
1980 |
1981 | var accessorDef = this.json.accessors[ accessorIndex ];
1982 |
1983 | if ( accessorDef.bufferView === undefined && accessorDef.sparse === undefined ) {
1984 |
1985 | // Ignore empty accessors, which may be used to declare runtime
1986 | // information about attributes coming from another source (e.g. Draco
1987 | // compression extension).
1988 | return Promise.resolve( null );
1989 |
1990 | }
1991 |
1992 | var pendingBufferViews = [];
1993 |
1994 | if ( accessorDef.bufferView !== undefined ) {
1995 |
1996 | pendingBufferViews.push( this.getDependency( 'bufferView', accessorDef.bufferView ) );
1997 |
1998 | } else {
1999 |
2000 | pendingBufferViews.push( null );
2001 |
2002 | }
2003 |
2004 | if ( accessorDef.sparse !== undefined ) {
2005 |
2006 | pendingBufferViews.push( this.getDependency( 'bufferView', accessorDef.sparse.indices.bufferView ) );
2007 | pendingBufferViews.push( this.getDependency( 'bufferView', accessorDef.sparse.values.bufferView ) );
2008 |
2009 | }
2010 |
2011 | return Promise.all( pendingBufferViews ).then( function ( bufferViews ) {
2012 |
2013 | var bufferView = bufferViews[ 0 ];
2014 |
2015 | var itemSize = WEBGL_TYPE_SIZES[ accessorDef.type ];
2016 | var TypedArray = WEBGL_COMPONENT_TYPES[ accessorDef.componentType ];
2017 |
2018 | // For VEC3: itemSize is 3, elementBytes is 4, itemBytes is 12.
2019 | var elementBytes = TypedArray.BYTES_PER_ELEMENT;
2020 | var itemBytes = elementBytes * itemSize;
2021 | var byteOffset = accessorDef.byteOffset || 0;
2022 | var byteStride = accessorDef.bufferView !== undefined ? json.bufferViews[ accessorDef.bufferView ].byteStride : undefined;
2023 | var normalized = accessorDef.normalized === true;
2024 | var array, bufferAttribute;
2025 |
2026 | // The buffer is not interleaved if the stride is the item size in bytes.
2027 | if ( byteStride && byteStride !== itemBytes ) {
2028 |
2029 | // Each "slice" of the buffer, as defined by 'count' elements of 'byteStride' bytes, gets its own InterleavedBuffer
2030 | // This makes sure that IBA.count reflects accessor.count properly
2031 | var ibSlice = Math.floor( byteOffset / byteStride );
2032 | var ibCacheKey = 'InterleavedBuffer:' + accessorDef.bufferView + ':' + accessorDef.componentType + ':' + ibSlice + ':' + accessorDef.count;
2033 | var ib = parser.cache.get( ibCacheKey );
2034 |
2035 | if ( ! ib ) {
2036 |
2037 | array = new TypedArray( bufferView, ibSlice * byteStride, accessorDef.count * byteStride / elementBytes );
2038 |
2039 | // Integer parameters to IB/IBA are in array elements, not bytes.
2040 | ib = new InterleavedBuffer( array, byteStride / elementBytes );
2041 |
2042 | parser.cache.add( ibCacheKey, ib );
2043 |
2044 | }
2045 |
2046 | bufferAttribute = new InterleavedBufferAttribute( ib, itemSize, ( byteOffset % byteStride ) / elementBytes, normalized );
2047 |
2048 | } else {
2049 |
2050 | if ( bufferView === null ) {
2051 |
2052 | array = new TypedArray( accessorDef.count * itemSize );
2053 |
2054 | } else {
2055 |
2056 | array = new TypedArray( bufferView, byteOffset, accessorDef.count * itemSize );
2057 |
2058 | }
2059 |
2060 | bufferAttribute = new BufferAttribute( array, itemSize, normalized );
2061 |
2062 | }
2063 |
2064 | // https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#sparse-accessors
2065 | if ( accessorDef.sparse !== undefined ) {
2066 |
2067 | var itemSizeIndices = WEBGL_TYPE_SIZES.SCALAR;
2068 | var TypedArrayIndices = WEBGL_COMPONENT_TYPES[ accessorDef.sparse.indices.componentType ];
2069 |
2070 | var byteOffsetIndices = accessorDef.sparse.indices.byteOffset || 0;
2071 | var byteOffsetValues = accessorDef.sparse.values.byteOffset || 0;
2072 |
2073 | var sparseIndices = new TypedArrayIndices( bufferViews[ 1 ], byteOffsetIndices, accessorDef.sparse.count * itemSizeIndices );
2074 | var sparseValues = new TypedArray( bufferViews[ 2 ], byteOffsetValues, accessorDef.sparse.count * itemSize );
2075 |
2076 | if ( bufferView !== null ) {
2077 |
2078 | // Avoid modifying the original ArrayBuffer, if the bufferView wasn't initialized with zeroes.
2079 | bufferAttribute = new BufferAttribute( bufferAttribute.array.slice(), bufferAttribute.itemSize, bufferAttribute.normalized );
2080 |
2081 | }
2082 |
2083 | for ( var i = 0, il = sparseIndices.length; i < il; i ++ ) {
2084 |
2085 | var index = sparseIndices[ i ];
2086 |
2087 | bufferAttribute.setX( index, sparseValues[ i * itemSize ] );
2088 | if ( itemSize >= 2 ) bufferAttribute.setY( index, sparseValues[ i * itemSize + 1 ] );
2089 | if ( itemSize >= 3 ) bufferAttribute.setZ( index, sparseValues[ i * itemSize + 2 ] );
2090 | if ( itemSize >= 4 ) bufferAttribute.setW( index, sparseValues[ i * itemSize + 3 ] );
2091 | if ( itemSize >= 5 ) throw new Error( 'GLTFLoader: Unsupported itemSize in sparse BufferAttribute.' );
2092 |
2093 | }
2094 |
2095 | }
2096 |
2097 | return bufferAttribute;
2098 |
2099 | } );
2100 |
2101 | };
2102 |
2103 | /**
2104 | * Specification: https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#textures
2105 | * @param {number} textureIndex
2106 | * @return {Promise}
2107 | */
2108 | GLTFParser.prototype.loadTexture = function ( textureIndex ) {
2109 |
2110 | var parser = this;
2111 | var json = this.json;
2112 | var options = this.options;
2113 | var textureLoader = this.textureLoader;
2114 |
2115 | var URL = self.URL || self.webkitURL;
2116 |
2117 | var textureDef = json.textures[ textureIndex ];
2118 |
2119 | var textureExtensions = textureDef.extensions || {};
2120 |
2121 | var source;
2122 |
2123 | if ( textureExtensions[ EXTENSIONS.MSFT_TEXTURE_DDS ] ) {
2124 |
2125 | source = json.images[ textureExtensions[ EXTENSIONS.MSFT_TEXTURE_DDS ].source ];
2126 |
2127 | } else {
2128 |
2129 | source = json.images[ textureDef.source ];
2130 |
2131 | }
2132 |
2133 | var sourceURI = source.uri;
2134 | var isObjectURL = false;
2135 |
2136 | if ( source.bufferView !== undefined ) {
2137 |
2138 | // Load binary image data from bufferView, if provided.
2139 |
2140 | sourceURI = parser.getDependency( 'bufferView', source.bufferView ).then( function ( bufferView ) {
2141 |
2142 | isObjectURL = true;
2143 | var blob = new Blob( [ bufferView ], { type: source.mimeType } );
2144 | sourceURI = URL.createObjectURL( blob );
2145 | return sourceURI;
2146 |
2147 | } );
2148 |
2149 | }
2150 |
2151 | return Promise.resolve( sourceURI ).then( function ( sourceURI ) {
2152 |
2153 | // Load Texture resource.
2154 |
2155 | var loader = options.manager.getHandler( sourceURI );
2156 |
2157 | if ( ! loader ) {
2158 |
2159 | loader = textureExtensions[ EXTENSIONS.MSFT_TEXTURE_DDS ]
2160 | ? parser.extensions[ EXTENSIONS.MSFT_TEXTURE_DDS ].ddsLoader
2161 | : textureLoader;
2162 |
2163 | }
2164 |
2165 | return new Promise( function ( resolve, reject ) {
2166 |
2167 | loader.load( resolveURL( sourceURI, options.path ), resolve, undefined, reject );
2168 |
2169 | } );
2170 |
2171 | } ).then( function ( texture ) {
2172 |
2173 | // Clean up resources and configure Texture.
2174 |
2175 | if ( isObjectURL === true ) {
2176 |
2177 | URL.revokeObjectURL( sourceURI );
2178 |
2179 | }
2180 |
2181 | texture.flipY = false;
2182 |
2183 | if ( textureDef.name ) texture.name = textureDef.name;
2184 |
2185 | // Ignore unknown mime types, like DDS files.
2186 | if ( source.mimeType in MIME_TYPE_FORMATS ) {
2187 |
2188 | texture.format = MIME_TYPE_FORMATS[ source.mimeType ];
2189 |
2190 | }
2191 |
2192 | var samplers = json.samplers || {};
2193 | var sampler = samplers[ textureDef.sampler ] || {};
2194 |
2195 | texture.magFilter = WEBGL_FILTERS[ sampler.magFilter ] || LinearFilter;
2196 | texture.minFilter = WEBGL_FILTERS[ sampler.minFilter ] || LinearMipmapLinearFilter;
2197 | texture.wrapS = WEBGL_WRAPPINGS[ sampler.wrapS ] || RepeatWrapping;
2198 | texture.wrapT = WEBGL_WRAPPINGS[ sampler.wrapT ] || RepeatWrapping;
2199 |
2200 | return texture;
2201 |
2202 | } );
2203 |
2204 | };
2205 |
2206 | /**
2207 | * Asynchronously assigns a texture to the given material parameters.
2208 | * @param {Object} materialParams
2209 | * @param {string} mapName
2210 | * @param {Object} mapDef
2211 | * @return {Promise}
2212 | */
2213 | GLTFParser.prototype.assignTexture = function ( materialParams, mapName, mapDef ) {
2214 |
2215 | var parser = this;
2216 |
2217 | return this.getDependency( 'texture', mapDef.index ).then( function ( texture ) {
2218 |
2219 | if ( ! texture.isCompressedTexture ) {
2220 |
2221 | switch ( mapName ) {
2222 |
2223 | case 'aoMap':
2224 | case 'emissiveMap':
2225 | case 'metalnessMap':
2226 | case 'normalMap':
2227 | case 'roughnessMap':
2228 | texture.format = RGBFormat;
2229 | break;
2230 |
2231 | }
2232 |
2233 | }
2234 |
2235 | // Materials sample aoMap from UV set 1 and other maps from UV set 0 - this can't be configured
2236 | // However, we will copy UV set 0 to UV set 1 on demand for aoMap
2237 | if ( mapDef.texCoord !== undefined && mapDef.texCoord != 0 && ! ( mapName === 'aoMap' && mapDef.texCoord == 1 ) ) {
2238 |
2239 | console.warn( 'GLTFLoader: Custom UV set ' + mapDef.texCoord + ' for texture ' + mapName + ' not yet supported.' );
2240 |
2241 | }
2242 |
2243 | if ( parser.extensions[ EXTENSIONS.KHR_TEXTURE_TRANSFORM ] ) {
2244 |
2245 | var transform = mapDef.extensions !== undefined ? mapDef.extensions[ EXTENSIONS.KHR_TEXTURE_TRANSFORM ] : undefined;
2246 |
2247 | if ( transform ) {
2248 |
2249 | texture = parser.extensions[ EXTENSIONS.KHR_TEXTURE_TRANSFORM ].extendTexture( texture, transform );
2250 |
2251 | }
2252 |
2253 | }
2254 |
2255 | materialParams[ mapName ] = texture;
2256 |
2257 | } );
2258 |
2259 | };
2260 |
2261 | /**
2262 | * Assigns final material to a Mesh, Line, or Points instance. The instance
2263 | * already has a material (generated from the glTF material options alone)
2264 | * but reuse of the same glTF material may require multiple threejs materials
2265 | * to accomodate different primitive types, defines, etc. New materials will
2266 | * be created if necessary, and reused from a cache.
2267 | * @param {Object3D} mesh Mesh, Line, or Points instance.
2268 | */
2269 | GLTFParser.prototype.assignFinalMaterial = function ( mesh ) {
2270 |
2271 | var geometry = mesh.geometry;
2272 | var material = mesh.material;
2273 |
2274 | var useVertexTangents = geometry.attributes.tangent !== undefined;
2275 | var useVertexColors = geometry.attributes.color !== undefined;
2276 | var useFlatShading = geometry.attributes.normal === undefined;
2277 | var useSkinning = mesh.isSkinnedMesh === true;
2278 | var useMorphTargets = Object.keys( geometry.morphAttributes ).length > 0;
2279 | var useMorphNormals = useMorphTargets && geometry.morphAttributes.normal !== undefined;
2280 |
2281 | if ( mesh.isPoints ) {
2282 |
2283 | var cacheKey = 'PointsMaterial:' + material.uuid;
2284 |
2285 | var pointsMaterial = this.cache.get( cacheKey );
2286 |
2287 | if ( ! pointsMaterial ) {
2288 |
2289 | pointsMaterial = new PointsMaterial();
2290 | Material.prototype.copy.call( pointsMaterial, material );
2291 | pointsMaterial.color.copy( material.color );
2292 | pointsMaterial.map = material.map;
2293 | pointsMaterial.sizeAttenuation = false; // glTF spec says points should be 1px
2294 |
2295 | this.cache.add( cacheKey, pointsMaterial );
2296 |
2297 | }
2298 |
2299 | material = pointsMaterial;
2300 |
2301 | } else if ( mesh.isLine ) {
2302 |
2303 | var cacheKey = 'LineBasicMaterial:' + material.uuid;
2304 |
2305 | var lineMaterial = this.cache.get( cacheKey );
2306 |
2307 | if ( ! lineMaterial ) {
2308 |
2309 | lineMaterial = new LineBasicMaterial();
2310 | Material.prototype.copy.call( lineMaterial, material );
2311 | lineMaterial.color.copy( material.color );
2312 |
2313 | this.cache.add( cacheKey, lineMaterial );
2314 |
2315 | }
2316 |
2317 | material = lineMaterial;
2318 |
2319 | }
2320 |
2321 | // Clone the material if it will be modified
2322 | if ( useVertexTangents || useVertexColors || useFlatShading || useSkinning || useMorphTargets ) {
2323 |
2324 | var cacheKey = 'ClonedMaterial:' + material.uuid + ':';
2325 |
2326 | if ( material.isGLTFSpecularGlossinessMaterial ) cacheKey += 'specular-glossiness:';
2327 | if ( useSkinning ) cacheKey += 'skinning:';
2328 | if ( useVertexTangents ) cacheKey += 'vertex-tangents:';
2329 | if ( useVertexColors ) cacheKey += 'vertex-colors:';
2330 | if ( useFlatShading ) cacheKey += 'flat-shading:';
2331 | if ( useMorphTargets ) cacheKey += 'morph-targets:';
2332 | if ( useMorphNormals ) cacheKey += 'morph-normals:';
2333 |
2334 | var cachedMaterial = this.cache.get( cacheKey );
2335 |
2336 | if ( ! cachedMaterial ) {
2337 |
2338 | cachedMaterial = material.clone();
2339 |
2340 | if ( useSkinning ) cachedMaterial.skinning = true;
2341 | if ( useVertexTangents ) cachedMaterial.vertexTangents = true;
2342 | if ( useVertexColors ) cachedMaterial.vertexColors = true;
2343 | if ( useFlatShading ) cachedMaterial.flatShading = true;
2344 | if ( useMorphTargets ) cachedMaterial.morphTargets = true;
2345 | if ( useMorphNormals ) cachedMaterial.morphNormals = true;
2346 |
2347 | this.cache.add( cacheKey, cachedMaterial );
2348 |
2349 | }
2350 |
2351 | material = cachedMaterial;
2352 |
2353 | }
2354 |
2355 | // workarounds for mesh and geometry
2356 |
2357 | if ( material.aoMap && geometry.attributes.uv2 === undefined && geometry.attributes.uv !== undefined ) {
2358 |
2359 | geometry.setAttribute( 'uv2', new BufferAttribute( geometry.attributes.uv.array, 2 ) );
2360 |
2361 | }
2362 |
2363 | // https://github.com/mrdoob/js/issues/11438#issuecomment-507003995
2364 | if ( material.normalScale && ! useVertexTangents ) {
2365 |
2366 | material.normalScale.y = - material.normalScale.y;
2367 |
2368 | }
2369 |
2370 | if ( material.clearcoatNormalScale && ! useVertexTangents ) {
2371 |
2372 | material.clearcoatNormalScale.y = - material.clearcoatNormalScale.y;
2373 |
2374 | }
2375 |
2376 | mesh.material = material;
2377 |
2378 | };
2379 |
2380 | /**
2381 | * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#materials
2382 | * @param {number} materialIndex
2383 | * @return {Promise}
2384 | */
2385 | GLTFParser.prototype.loadMaterial = function ( materialIndex ) {
2386 |
2387 | var parser = this;
2388 | var json = this.json;
2389 | var extensions = this.extensions;
2390 | var materialDef = json.materials[ materialIndex ];
2391 |
2392 | var materialType;
2393 | var materialParams = {};
2394 | var materialExtensions = materialDef.extensions || {};
2395 |
2396 | var pending = [];
2397 |
2398 | if ( materialExtensions[ EXTENSIONS.KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS ] ) {
2399 |
2400 | var sgExtension = extensions[ EXTENSIONS.KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS ];
2401 | materialType = sgExtension.getMaterialType();
2402 | pending.push( sgExtension.extendParams( materialParams, materialDef, parser ) );
2403 |
2404 | } else if ( materialExtensions[ EXTENSIONS.KHR_MATERIALS_UNLIT ] ) {
2405 |
2406 | var kmuExtension = extensions[ EXTENSIONS.KHR_MATERIALS_UNLIT ];
2407 | materialType = kmuExtension.getMaterialType();
2408 | pending.push( kmuExtension.extendParams( materialParams, materialDef, parser ) );
2409 |
2410 | } else {
2411 |
2412 | // Specification:
2413 | // https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#metallic-roughness-material
2414 |
2415 | materialType = MeshStandardMaterial;
2416 |
2417 | var metallicRoughness = materialDef.pbrMetallicRoughness || {};
2418 |
2419 | materialParams.color = new Color( 1.0, 1.0, 1.0 );
2420 | materialParams.opacity = 1.0;
2421 |
2422 | if ( Array.isArray( metallicRoughness.baseColorFactor ) ) {
2423 |
2424 | var array = metallicRoughness.baseColorFactor;
2425 |
2426 | materialParams.color.fromArray( array );
2427 | materialParams.opacity = array[ 3 ];
2428 |
2429 | }
2430 |
2431 | if ( metallicRoughness.baseColorTexture !== undefined ) {
2432 |
2433 | pending.push( parser.assignTexture( materialParams, 'map', metallicRoughness.baseColorTexture ) );
2434 |
2435 | }
2436 |
2437 | materialParams.metalness = metallicRoughness.metallicFactor !== undefined ? metallicRoughness.metallicFactor : 1.0;
2438 | materialParams.roughness = metallicRoughness.roughnessFactor !== undefined ? metallicRoughness.roughnessFactor : 1.0;
2439 |
2440 | if ( metallicRoughness.metallicRoughnessTexture !== undefined ) {
2441 |
2442 | pending.push( parser.assignTexture( materialParams, 'metalnessMap', metallicRoughness.metallicRoughnessTexture ) );
2443 | pending.push( parser.assignTexture( materialParams, 'roughnessMap', metallicRoughness.metallicRoughnessTexture ) );
2444 |
2445 | }
2446 |
2447 | }
2448 |
2449 | if ( materialDef.doubleSided === true ) {
2450 |
2451 | materialParams.side = DoubleSide;
2452 |
2453 | }
2454 |
2455 | var alphaMode = materialDef.alphaMode || ALPHA_MODES.OPAQUE;
2456 |
2457 | if ( alphaMode === ALPHA_MODES.BLEND ) {
2458 |
2459 | materialParams.transparent = true;
2460 |
2461 | // See: https://github.com/mrdoob/js/issues/17706
2462 | materialParams.depthWrite = false;
2463 |
2464 | } else {
2465 |
2466 | materialParams.transparent = false;
2467 |
2468 | if ( alphaMode === ALPHA_MODES.MASK ) {
2469 |
2470 | materialParams.alphaTest = materialDef.alphaCutoff !== undefined ? materialDef.alphaCutoff : 0.5;
2471 |
2472 | }
2473 |
2474 | }
2475 |
2476 | if ( materialDef.normalTexture !== undefined && materialType !== MeshBasicMaterial ) {
2477 |
2478 | pending.push( parser.assignTexture( materialParams, 'normalMap', materialDef.normalTexture ) );
2479 |
2480 | materialParams.normalScale = new Vector2( 1, 1 );
2481 |
2482 | if ( materialDef.normalTexture.scale !== undefined ) {
2483 |
2484 | materialParams.normalScale.set( materialDef.normalTexture.scale, materialDef.normalTexture.scale );
2485 |
2486 | }
2487 |
2488 | }
2489 |
2490 | if ( materialDef.occlusionTexture !== undefined && materialType !== MeshBasicMaterial ) {
2491 |
2492 | pending.push( parser.assignTexture( materialParams, 'aoMap', materialDef.occlusionTexture ) );
2493 |
2494 | if ( materialDef.occlusionTexture.strength !== undefined ) {
2495 |
2496 | materialParams.aoMapIntensity = materialDef.occlusionTexture.strength;
2497 |
2498 | }
2499 |
2500 | }
2501 |
2502 | if ( materialDef.emissiveFactor !== undefined && materialType !== MeshBasicMaterial ) {
2503 |
2504 | materialParams.emissive = new Color().fromArray( materialDef.emissiveFactor );
2505 |
2506 | }
2507 |
2508 | if ( materialDef.emissiveTexture !== undefined && materialType !== MeshBasicMaterial ) {
2509 |
2510 | pending.push( parser.assignTexture( materialParams, 'emissiveMap', materialDef.emissiveTexture ) );
2511 |
2512 | }
2513 |
2514 | if ( materialExtensions[ EXTENSIONS.KHR_MATERIALS_CLEARCOAT ] ) {
2515 |
2516 | var clearcoatExtension = extensions[ EXTENSIONS.KHR_MATERIALS_CLEARCOAT ];
2517 | materialType = clearcoatExtension.getMaterialType();
2518 | pending.push( clearcoatExtension.extendParams( materialParams, { extensions: materialExtensions }, parser ) );
2519 |
2520 | }
2521 |
2522 | return Promise.all( pending ).then( function () {
2523 |
2524 | var material;
2525 |
2526 | if ( materialType === GLTFMeshStandardSGMaterial ) {
2527 |
2528 | material = extensions[ EXTENSIONS.KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS ].createMaterial( materialParams );
2529 |
2530 | } else {
2531 |
2532 | material = new materialType( materialParams );
2533 |
2534 | }
2535 |
2536 | if ( materialDef.name ) material.name = materialDef.name;
2537 |
2538 | // baseColorTexture, emissiveTexture, and specularGlossinessTexture use sRGB encoding.
2539 | if ( material.map ) material.map.encoding = sRGBEncoding;
2540 | if ( material.emissiveMap ) material.emissiveMap.encoding = sRGBEncoding;
2541 |
2542 | assignExtrasToUserData( material, materialDef );
2543 |
2544 | if ( materialDef.extensions ) addUnknownExtensionsToUserData( extensions, material, materialDef );
2545 |
2546 | return material;
2547 |
2548 | } );
2549 |
2550 | };
2551 |
2552 | /**
2553 | * @param {BufferGeometry} geometry
2554 | * @param {GLTF.Primitive} primitiveDef
2555 | * @param {GLTFParser} parser
2556 | */
2557 | function computeBounds( geometry, primitiveDef, parser ) {
2558 |
2559 | var attributes = primitiveDef.attributes;
2560 |
2561 | var box = new Box3();
2562 |
2563 | if ( attributes.POSITION !== undefined ) {
2564 |
2565 | var accessor = parser.json.accessors[ attributes.POSITION ];
2566 |
2567 | var min = accessor.min;
2568 | var max = accessor.max;
2569 |
2570 | // glTF requires 'min' and 'max', but VRM (which extends glTF) currently ignores that requirement.
2571 |
2572 | if ( min !== undefined && max !== undefined ) {
2573 |
2574 | box.set(
2575 | new Vector3( min[ 0 ], min[ 1 ], min[ 2 ] ),
2576 | new Vector3( max[ 0 ], max[ 1 ], max[ 2 ] ) );
2577 |
2578 | } else {
2579 |
2580 | console.warn( 'GLTFLoader: Missing min/max properties for accessor POSITION.' );
2581 |
2582 | return;
2583 |
2584 | }
2585 |
2586 | } else {
2587 |
2588 | return;
2589 |
2590 | }
2591 |
2592 | var targets = primitiveDef.targets;
2593 |
2594 | if ( targets !== undefined ) {
2595 |
2596 | var maxDisplacement = new Vector3();
2597 | var vector = new Vector3();
2598 |
2599 | for ( var i = 0, il = targets.length; i < il; i ++ ) {
2600 |
2601 | var target = targets[ i ];
2602 |
2603 | if ( target.POSITION !== undefined ) {
2604 |
2605 | var accessor = parser.json.accessors[ target.POSITION ];
2606 | var min = accessor.min;
2607 | var max = accessor.max;
2608 |
2609 | // glTF requires 'min' and 'max', but VRM (which extends glTF) currently ignores that requirement.
2610 |
2611 | if ( min !== undefined && max !== undefined ) {
2612 |
2613 | // we need to get max of absolute components because target weight is [-1,1]
2614 | vector.setX( Math.max( Math.abs( min[ 0 ] ), Math.abs( max[ 0 ] ) ) );
2615 | vector.setY( Math.max( Math.abs( min[ 1 ] ), Math.abs( max[ 1 ] ) ) );
2616 | vector.setZ( Math.max( Math.abs( min[ 2 ] ), Math.abs( max[ 2 ] ) ) );
2617 |
2618 | // Note: this assumes that the sum of all weights is at most 1. This isn't quite correct - it's more conservative
2619 | // to assume that each target can have a max weight of 1. However, for some use cases - notably, when morph targets
2620 | // are used to implement key-frame animations and as such only two are active at a time - this results in very large
2621 | // boxes. So for now we make a box that's sometimes a touch too small but is hopefully mostly of reasonable size.
2622 | maxDisplacement.max( vector );
2623 |
2624 | } else {
2625 |
2626 | console.warn( 'GLTFLoader: Missing min/max properties for accessor POSITION.' );
2627 |
2628 | }
2629 |
2630 | }
2631 |
2632 | }
2633 |
2634 | // As per comment above this box isn't conservative, but has a reasonable size for a very large number of morph targets.
2635 | box.expandByVector( maxDisplacement );
2636 |
2637 | }
2638 |
2639 | geometry.boundingBox = box;
2640 |
2641 | var sphere = new Sphere();
2642 |
2643 | box.getCenter( sphere.center );
2644 | sphere.radius = box.min.distanceTo( box.max ) / 2;
2645 |
2646 | geometry.boundingSphere = sphere;
2647 |
2648 | }
2649 |
2650 | /**
2651 | * @param {BufferGeometry} geometry
2652 | * @param {GLTF.Primitive} primitiveDef
2653 | * @param {GLTFParser} parser
2654 | * @return {Promise}
2655 | */
2656 | function addPrimitiveAttributes( geometry, primitiveDef, parser ) {
2657 |
2658 | var attributes = primitiveDef.attributes;
2659 |
2660 | var pending = [];
2661 |
2662 | function assignAttributeAccessor( accessorIndex, attributeName ) {
2663 |
2664 | return parser.getDependency( 'accessor', accessorIndex )
2665 | .then( function ( accessor ) {
2666 |
2667 | geometry.setAttribute( attributeName, accessor );
2668 |
2669 | } );
2670 |
2671 | }
2672 |
2673 | for ( var gltfAttributeName in attributes ) {
2674 |
2675 | var threeAttributeName = ATTRIBUTES[ gltfAttributeName ] || gltfAttributeName.toLowerCase();
2676 |
2677 | // Skip attributes already provided by e.g. Draco extension.
2678 | if ( threeAttributeName in geometry.attributes ) continue;
2679 |
2680 | pending.push( assignAttributeAccessor( attributes[ gltfAttributeName ], threeAttributeName ) );
2681 |
2682 | }
2683 |
2684 | if ( primitiveDef.indices !== undefined && ! geometry.index ) {
2685 |
2686 | var accessor = parser.getDependency( 'accessor', primitiveDef.indices ).then( function ( accessor ) {
2687 |
2688 | geometry.setIndex( accessor );
2689 |
2690 | } );
2691 |
2692 | pending.push( accessor );
2693 |
2694 | }
2695 |
2696 | assignExtrasToUserData( geometry, primitiveDef );
2697 |
2698 | computeBounds( geometry, primitiveDef, parser );
2699 |
2700 | return Promise.all( pending ).then( function () {
2701 |
2702 | return primitiveDef.targets !== undefined
2703 | ? addMorphTargets( geometry, primitiveDef.targets, parser )
2704 | : geometry;
2705 |
2706 | } );
2707 |
2708 | }
2709 |
2710 | /**
2711 | * @param {BufferGeometry} geometry
2712 | * @param {Number} drawMode
2713 | * @return {BufferGeometry}
2714 | */
2715 | function toTrianglesDrawMode( geometry, drawMode ) {
2716 |
2717 | var index = geometry.getIndex();
2718 |
2719 | // generate index if not present
2720 |
2721 | if ( index === null ) {
2722 |
2723 | var indices = [];
2724 |
2725 | var position = geometry.getAttribute( 'position' );
2726 |
2727 | if ( position !== undefined ) {
2728 |
2729 | for ( var i = 0; i < position.count; i ++ ) {
2730 |
2731 | indices.push( i );
2732 |
2733 | }
2734 |
2735 | geometry.setIndex( indices );
2736 | index = geometry.getIndex();
2737 |
2738 | } else {
2739 |
2740 | console.error( 'GLTFLoader.toTrianglesDrawMode(): Undefined position attribute. Processing not possible.' );
2741 | return geometry;
2742 |
2743 | }
2744 |
2745 | }
2746 |
2747 | //
2748 |
2749 | var numberOfTriangles = index.count - 2;
2750 | var newIndices = [];
2751 |
2752 | if ( drawMode === TriangleFanDrawMode ) {
2753 |
2754 | // gl.TRIANGLE_FAN
2755 |
2756 | for ( var i = 1; i <= numberOfTriangles; i ++ ) {
2757 |
2758 | newIndices.push( index.getX( 0 ) );
2759 | newIndices.push( index.getX( i ) );
2760 | newIndices.push( index.getX( i + 1 ) );
2761 |
2762 | }
2763 |
2764 | } else {
2765 |
2766 | // gl.TRIANGLE_STRIP
2767 |
2768 | for ( var i = 0; i < numberOfTriangles; i ++ ) {
2769 |
2770 | if ( i % 2 === 0 ) {
2771 |
2772 | newIndices.push( index.getX( i ) );
2773 | newIndices.push( index.getX( i + 1 ) );
2774 | newIndices.push( index.getX( i + 2 ) );
2775 |
2776 |
2777 | } else {
2778 |
2779 | newIndices.push( index.getX( i + 2 ) );
2780 | newIndices.push( index.getX( i + 1 ) );
2781 | newIndices.push( index.getX( i ) );
2782 |
2783 | }
2784 |
2785 | }
2786 |
2787 | }
2788 |
2789 | if ( ( newIndices.length / 3 ) !== numberOfTriangles ) {
2790 |
2791 | console.error( 'GLTFLoader.toTrianglesDrawMode(): Unable to generate correct amount of triangles.' );
2792 |
2793 | }
2794 |
2795 | // build final geometry
2796 |
2797 | var newGeometry = geometry.clone();
2798 | newGeometry.setIndex( newIndices );
2799 |
2800 | return newGeometry;
2801 |
2802 | }
2803 |
2804 | /**
2805 | * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#geometry
2806 | *
2807 | * Creates BufferGeometries from primitives.
2808 | *
2809 | * @param {Array} primitives
2810 | * @return {Promise>}
2811 | */
2812 | GLTFParser.prototype.loadGeometries = function ( primitives ) {
2813 |
2814 | var parser = this;
2815 | var extensions = this.extensions;
2816 | var cache = this.primitiveCache;
2817 |
2818 | function createDracoPrimitive( primitive ) {
2819 |
2820 | return extensions[ EXTENSIONS.KHR_DRACO_MESH_COMPRESSION ]
2821 | .decodePrimitive( primitive, parser )
2822 | .then( function ( geometry ) {
2823 |
2824 | return addPrimitiveAttributes( geometry, primitive, parser );
2825 |
2826 | } );
2827 |
2828 | }
2829 |
2830 | var pending = [];
2831 |
2832 | for ( var i = 0, il = primitives.length; i < il; i ++ ) {
2833 |
2834 | var primitive = primitives[ i ];
2835 | var cacheKey = createPrimitiveKey( primitive );
2836 |
2837 | // See if we've already created this geometry
2838 | var cached = cache[ cacheKey ];
2839 |
2840 | if ( cached ) {
2841 |
2842 | // Use the cached geometry if it exists
2843 | pending.push( cached.promise );
2844 |
2845 | } else {
2846 |
2847 | var geometryPromise;
2848 |
2849 | if ( primitive.extensions && primitive.extensions[ EXTENSIONS.KHR_DRACO_MESH_COMPRESSION ] ) {
2850 |
2851 | // Use DRACO geometry if available
2852 | geometryPromise = createDracoPrimitive( primitive );
2853 |
2854 | } else {
2855 |
2856 | // Otherwise create a new geometry
2857 | geometryPromise = addPrimitiveAttributes( new BufferGeometry(), primitive, parser );
2858 |
2859 | }
2860 |
2861 | // Cache this geometry
2862 | cache[ cacheKey ] = { primitive: primitive, promise: geometryPromise };
2863 |
2864 | pending.push( geometryPromise );
2865 |
2866 | }
2867 |
2868 | }
2869 |
2870 | return Promise.all( pending );
2871 |
2872 | };
2873 |
2874 | /**
2875 | * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#meshes
2876 | * @param {number} meshIndex
2877 | * @return {Promise}
2878 | */
2879 | GLTFParser.prototype.loadMesh = function ( meshIndex ) {
2880 |
2881 | var parser = this;
2882 | var json = this.json;
2883 |
2884 | var meshDef = json.meshes[ meshIndex ];
2885 | var primitives = meshDef.primitives;
2886 |
2887 | var pending = [];
2888 |
2889 | for ( var i = 0, il = primitives.length; i < il; i ++ ) {
2890 |
2891 | var material = primitives[ i ].material === undefined
2892 | ? createDefaultMaterial( this.cache )
2893 | : this.getDependency( 'material', primitives[ i ].material );
2894 |
2895 | pending.push( material );
2896 |
2897 | }
2898 |
2899 | pending.push( parser.loadGeometries( primitives ) );
2900 |
2901 | return Promise.all( pending ).then( function ( results ) {
2902 |
2903 | var materials = results.slice( 0, results.length - 1 );
2904 | var geometries = results[ results.length - 1 ];
2905 |
2906 | var meshes = [];
2907 |
2908 | for ( var i = 0, il = geometries.length; i < il; i ++ ) {
2909 |
2910 | var geometry = geometries[ i ];
2911 | var primitive = primitives[ i ];
2912 |
2913 | // 1. create Mesh
2914 |
2915 | var mesh;
2916 |
2917 | var material = materials[ i ];
2918 |
2919 | if ( primitive.mode === WEBGL_CONSTANTS.TRIANGLES ||
2920 | primitive.mode === WEBGL_CONSTANTS.TRIANGLE_STRIP ||
2921 | primitive.mode === WEBGL_CONSTANTS.TRIANGLE_FAN ||
2922 | primitive.mode === undefined ) {
2923 |
2924 | // .isSkinnedMesh isn't in glTF spec. See .markDefs()
2925 | mesh = meshDef.isSkinnedMesh === true
2926 | ? new SkinnedMesh( geometry, material )
2927 | : new Mesh( geometry, material );
2928 |
2929 | if ( mesh.isSkinnedMesh === true && ! mesh.geometry.attributes.skinWeight.normalized ) {
2930 |
2931 | // we normalize floating point skin weight array to fix malformed assets (see #15319)
2932 | // it's important to skip this for non-float32 data since normalizeSkinWeights assumes non-normalized inputs
2933 | mesh.normalizeSkinWeights();
2934 |
2935 | }
2936 |
2937 | if ( primitive.mode === WEBGL_CONSTANTS.TRIANGLE_STRIP ) {
2938 |
2939 | mesh.geometry = toTrianglesDrawMode( mesh.geometry, TriangleStripDrawMode );
2940 |
2941 | } else if ( primitive.mode === WEBGL_CONSTANTS.TRIANGLE_FAN ) {
2942 |
2943 | mesh.geometry = toTrianglesDrawMode( mesh.geometry, TriangleFanDrawMode );
2944 |
2945 | }
2946 |
2947 | } else if ( primitive.mode === WEBGL_CONSTANTS.LINES ) {
2948 |
2949 | mesh = new LineSegments( geometry, material );
2950 |
2951 | } else if ( primitive.mode === WEBGL_CONSTANTS.LINE_STRIP ) {
2952 |
2953 | mesh = new Line( geometry, material );
2954 |
2955 | } else if ( primitive.mode === WEBGL_CONSTANTS.LINE_LOOP ) {
2956 |
2957 | mesh = new LineLoop( geometry, material );
2958 |
2959 | } else if ( primitive.mode === WEBGL_CONSTANTS.POINTS ) {
2960 |
2961 | mesh = new Points( geometry, material );
2962 |
2963 | } else {
2964 |
2965 | throw new Error( 'GLTFLoader: Primitive mode unsupported: ' + primitive.mode );
2966 |
2967 | }
2968 |
2969 | if ( Object.keys( mesh.geometry.morphAttributes ).length > 0 ) {
2970 |
2971 | updateMorphTargets( mesh, meshDef );
2972 |
2973 | }
2974 |
2975 | mesh.name = meshDef.name || ( 'mesh_' + meshIndex );
2976 |
2977 | if ( geometries.length > 1 ) mesh.name += '_' + i;
2978 |
2979 | assignExtrasToUserData( mesh, meshDef );
2980 |
2981 | parser.assignFinalMaterial( mesh );
2982 |
2983 | meshes.push( mesh );
2984 |
2985 | }
2986 |
2987 | if ( meshes.length === 1 ) {
2988 |
2989 | return meshes[ 0 ];
2990 |
2991 | }
2992 |
2993 | var group = new Group();
2994 |
2995 | for ( var i = 0, il = meshes.length; i < il; i ++ ) {
2996 |
2997 | group.add( meshes[ i ] );
2998 |
2999 | }
3000 |
3001 | return group;
3002 |
3003 | } );
3004 |
3005 | };
3006 |
3007 | /**
3008 | * Specification: https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#cameras
3009 | * @param {number} cameraIndex
3010 | * @return {Promise}
3011 | */
3012 | GLTFParser.prototype.loadCamera = function ( cameraIndex ) {
3013 |
3014 | var camera;
3015 | var cameraDef = this.json.cameras[ cameraIndex ];
3016 | var params = cameraDef[ cameraDef.type ];
3017 |
3018 | if ( ! params ) {
3019 |
3020 | console.warn( 'GLTFLoader: Missing camera parameters.' );
3021 | return;
3022 |
3023 | }
3024 |
3025 | if ( cameraDef.type === 'perspective' ) {
3026 |
3027 | camera = new PerspectiveCamera( MathUtils.radToDeg( params.yfov ), params.aspectRatio || 1, params.znear || 1, params.zfar || 2e6 );
3028 |
3029 | } else if ( cameraDef.type === 'orthographic' ) {
3030 |
3031 | camera = new OrthographicCamera( params.xmag / - 2, params.xmag / 2, params.ymag / 2, params.ymag / - 2, params.znear, params.zfar );
3032 |
3033 | }
3034 |
3035 | if ( cameraDef.name ) camera.name = cameraDef.name;
3036 |
3037 | assignExtrasToUserData( camera, cameraDef );
3038 |
3039 | return Promise.resolve( camera );
3040 |
3041 | };
3042 |
3043 | /**
3044 | * Specification: https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#skins
3045 | * @param {number} skinIndex
3046 | * @return {Promise}
3047 | */
3048 | GLTFParser.prototype.loadSkin = function ( skinIndex ) {
3049 |
3050 | var skinDef = this.json.skins[ skinIndex ];
3051 |
3052 | var skinEntry = { joints: skinDef.joints };
3053 |
3054 | if ( skinDef.inverseBindMatrices === undefined ) {
3055 |
3056 | return Promise.resolve( skinEntry );
3057 |
3058 | }
3059 |
3060 | return this.getDependency( 'accessor', skinDef.inverseBindMatrices ).then( function ( accessor ) {
3061 |
3062 | skinEntry.inverseBindMatrices = accessor;
3063 |
3064 | return skinEntry;
3065 |
3066 | } );
3067 |
3068 | };
3069 |
3070 | /**
3071 | * Specification: https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#animations
3072 | * @param {number} animationIndex
3073 | * @return {Promise}
3074 | */
3075 | GLTFParser.prototype.loadAnimation = function ( animationIndex ) {
3076 |
3077 | var json = this.json;
3078 |
3079 | var animationDef = json.animations[ animationIndex ];
3080 |
3081 | var pendingNodes = [];
3082 | var pendingInputAccessors = [];
3083 | var pendingOutputAccessors = [];
3084 | var pendingSamplers = [];
3085 | var pendingTargets = [];
3086 |
3087 | for ( var i = 0, il = animationDef.channels.length; i < il; i ++ ) {
3088 |
3089 | var channel = animationDef.channels[ i ];
3090 | var sampler = animationDef.samplers[ channel.sampler ];
3091 | var target = channel.target;
3092 | var name = target.node !== undefined ? target.node : target.id; // NOTE: target.id is deprecated.
3093 | var input = animationDef.parameters !== undefined ? animationDef.parameters[ sampler.input ] : sampler.input;
3094 | var output = animationDef.parameters !== undefined ? animationDef.parameters[ sampler.output ] : sampler.output;
3095 |
3096 | pendingNodes.push( this.getDependency( 'node', name ) );
3097 | pendingInputAccessors.push( this.getDependency( 'accessor', input ) );
3098 | pendingOutputAccessors.push( this.getDependency( 'accessor', output ) );
3099 | pendingSamplers.push( sampler );
3100 | pendingTargets.push( target );
3101 |
3102 | }
3103 |
3104 | return Promise.all( [
3105 |
3106 | Promise.all( pendingNodes ),
3107 | Promise.all( pendingInputAccessors ),
3108 | Promise.all( pendingOutputAccessors ),
3109 | Promise.all( pendingSamplers ),
3110 | Promise.all( pendingTargets )
3111 |
3112 | ] ).then( function ( dependencies ) {
3113 |
3114 | var nodes = dependencies[ 0 ];
3115 | var inputAccessors = dependencies[ 1 ];
3116 | var outputAccessors = dependencies[ 2 ];
3117 | var samplers = dependencies[ 3 ];
3118 | var targets = dependencies[ 4 ];
3119 |
3120 | var tracks = [];
3121 |
3122 | for ( var i = 0, il = nodes.length; i < il; i ++ ) {
3123 |
3124 | var node = nodes[ i ];
3125 | var inputAccessor = inputAccessors[ i ];
3126 | var outputAccessor = outputAccessors[ i ];
3127 | var sampler = samplers[ i ];
3128 | var target = targets[ i ];
3129 |
3130 | if ( node === undefined ) continue;
3131 |
3132 | node.updateMatrix();
3133 | node.matrixAutoUpdate = true;
3134 |
3135 | var TypedKeyframeTrack;
3136 |
3137 | switch ( PATH_PROPERTIES[ target.path ] ) {
3138 |
3139 | case PATH_PROPERTIES.weights:
3140 |
3141 | TypedKeyframeTrack = NumberKeyframeTrack;
3142 | break;
3143 |
3144 | case PATH_PROPERTIES.rotation:
3145 |
3146 | TypedKeyframeTrack = QuaternionKeyframeTrack;
3147 | break;
3148 |
3149 | case PATH_PROPERTIES.position:
3150 | case PATH_PROPERTIES.scale:
3151 | default:
3152 |
3153 | TypedKeyframeTrack = VectorKeyframeTrack;
3154 | break;
3155 |
3156 | }
3157 |
3158 | var targetName = node.name ? node.name : node.uuid;
3159 |
3160 | var interpolation = sampler.interpolation !== undefined ? INTERPOLATION[ sampler.interpolation ] : InterpolateLinear;
3161 |
3162 | var targetNames = [];
3163 |
3164 | if ( PATH_PROPERTIES[ target.path ] === PATH_PROPERTIES.weights ) {
3165 |
3166 | // Node may be a Group (glTF mesh with several primitives) or a Mesh.
3167 | node.traverse( function ( object ) {
3168 |
3169 | if ( object.isMesh === true && object.morphTargetInfluences ) {
3170 |
3171 | targetNames.push( object.name ? object.name : object.uuid );
3172 |
3173 | }
3174 |
3175 | } );
3176 |
3177 | } else {
3178 |
3179 | targetNames.push( targetName );
3180 |
3181 | }
3182 |
3183 | var outputArray = outputAccessor.array;
3184 |
3185 | if ( outputAccessor.normalized ) {
3186 |
3187 | var scale;
3188 |
3189 | if ( outputArray.constructor === Int8Array ) {
3190 |
3191 | scale = 1 / 127;
3192 |
3193 | } else if ( outputArray.constructor === Uint8Array ) {
3194 |
3195 | scale = 1 / 255;
3196 |
3197 | } else if ( outputArray.constructor == Int16Array ) {
3198 |
3199 | scale = 1 / 32767;
3200 |
3201 | } else if ( outputArray.constructor === Uint16Array ) {
3202 |
3203 | scale = 1 / 65535;
3204 |
3205 | } else {
3206 |
3207 | throw new Error( 'GLTFLoader: Unsupported output accessor component type.' );
3208 |
3209 | }
3210 |
3211 | var scaled = new Float32Array( outputArray.length );
3212 |
3213 | for ( var j = 0, jl = outputArray.length; j < jl; j ++ ) {
3214 |
3215 | scaled[ j ] = outputArray[ j ] * scale;
3216 |
3217 | }
3218 |
3219 | outputArray = scaled;
3220 |
3221 | }
3222 |
3223 | for ( var j = 0, jl = targetNames.length; j < jl; j ++ ) {
3224 |
3225 | var track = new TypedKeyframeTrack(
3226 | targetNames[ j ] + '.' + PATH_PROPERTIES[ target.path ],
3227 | inputAccessor.array,
3228 | outputArray,
3229 | interpolation
3230 | );
3231 |
3232 | // Override interpolation with custom factory method.
3233 | if ( sampler.interpolation === 'CUBICSPLINE' ) {
3234 |
3235 | track.createInterpolant = function InterpolantFactoryMethodGLTFCubicSpline( result ) {
3236 |
3237 | // A CUBICSPLINE keyframe in glTF has three output values for each input value,
3238 | // representing inTangent, splineVertex, and outTangent. As a result, track.getValueSize()
3239 | // must be divided by three to get the interpolant's sampleSize argument.
3240 |
3241 | return new GLTFCubicSplineInterpolant( this.times, this.values, this.getValueSize() / 3, result );
3242 |
3243 | };
3244 |
3245 | // Mark as CUBICSPLINE. `track.getInterpolation()` doesn't support custom interpolants.
3246 | track.createInterpolant.isInterpolantFactoryMethodGLTFCubicSpline = true;
3247 |
3248 | }
3249 |
3250 | tracks.push( track );
3251 |
3252 | }
3253 |
3254 | }
3255 |
3256 | var name = animationDef.name ? animationDef.name : 'animation_' + animationIndex;
3257 |
3258 | return new AnimationClip( name, undefined, tracks );
3259 |
3260 | } );
3261 |
3262 | };
3263 |
3264 | /**
3265 | * Specification: https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#nodes-and-hierarchy
3266 | * @param {number} nodeIndex
3267 | * @return {Promise}
3268 | */
3269 | GLTFParser.prototype.loadNode = function ( nodeIndex ) {
3270 |
3271 | var json = this.json;
3272 | var extensions = this.extensions;
3273 | var parser = this;
3274 |
3275 | var meshReferences = json.meshReferences;
3276 | var meshUses = json.meshUses;
3277 |
3278 | var nodeDef = json.nodes[ nodeIndex ];
3279 |
3280 | return ( function () {
3281 |
3282 | var pending = [];
3283 |
3284 | if ( nodeDef.mesh !== undefined ) {
3285 |
3286 | pending.push( parser.getDependency( 'mesh', nodeDef.mesh ).then( function ( mesh ) {
3287 |
3288 | var node;
3289 |
3290 | if ( meshReferences[ nodeDef.mesh ] > 1 ) {
3291 |
3292 | var instanceNum = meshUses[ nodeDef.mesh ] ++;
3293 |
3294 | node = mesh.clone();
3295 | node.name += '_instance_' + instanceNum;
3296 |
3297 | } else {
3298 |
3299 | node = mesh;
3300 |
3301 | }
3302 |
3303 | // if weights are provided on the node, override weights on the mesh.
3304 | if ( nodeDef.weights !== undefined ) {
3305 |
3306 | node.traverse( function ( o ) {
3307 |
3308 | if ( ! o.isMesh ) return;
3309 |
3310 | for ( var i = 0, il = nodeDef.weights.length; i < il; i ++ ) {
3311 |
3312 | o.morphTargetInfluences[ i ] = nodeDef.weights[ i ];
3313 |
3314 | }
3315 |
3316 | } );
3317 |
3318 | }
3319 |
3320 | return node;
3321 |
3322 | } ) );
3323 |
3324 | } else {
3325 |
3326 | // no mesh
3327 | if ( nodeDef.extensions
3328 | && nodeDef.extensions[ EXTENSIONS.MOZ_TEXT ]
3329 | && nodeDef.extensions[ EXTENSIONS.MOZ_TEXT ].index !== undefined ) {
3330 |
3331 | pending.push( parser.getDependency( 'text', nodeDef.extensions[ EXTENSIONS.MOZ_TEXT ].index ) );
3332 |
3333 | }
3334 | }
3335 |
3336 | if ( nodeDef.camera !== undefined ) {
3337 |
3338 | pending.push( parser.getDependency( 'camera', nodeDef.camera ) );
3339 |
3340 | }
3341 |
3342 | if ( nodeDef.extensions
3343 | && nodeDef.extensions[ EXTENSIONS.KHR_LIGHTS_PUNCTUAL ]
3344 | && nodeDef.extensions[ EXTENSIONS.KHR_LIGHTS_PUNCTUAL ].light !== undefined ) {
3345 |
3346 | pending.push( parser.getDependency( 'light', nodeDef.extensions[ EXTENSIONS.KHR_LIGHTS_PUNCTUAL ].light ) );
3347 |
3348 | }
3349 |
3350 | return Promise.all( pending );
3351 |
3352 | }() ).then( function ( objects ) {
3353 |
3354 | var node;
3355 |
3356 | // .isBone isn't in glTF spec. See .markDefs
3357 | if ( nodeDef.isBone === true ) {
3358 |
3359 | node = new Bone();
3360 |
3361 | } else if ( objects.length > 1 ) {
3362 |
3363 | node = new Group();
3364 |
3365 | } else if ( objects.length === 1 ) {
3366 |
3367 | node = objects[ 0 ];
3368 |
3369 | } else {
3370 |
3371 | node = new Object3D();
3372 |
3373 | }
3374 |
3375 | if ( node !== objects[ 0 ] ) {
3376 |
3377 | for ( var i = 0, il = objects.length; i < il; i ++ ) {
3378 |
3379 | node.add( objects[ i ] );
3380 |
3381 | }
3382 |
3383 | }
3384 |
3385 | if ( nodeDef.name ) {
3386 |
3387 | node.userData.name = nodeDef.name;
3388 | node.name = PropertyBinding.sanitizeNodeName( nodeDef.name );
3389 |
3390 | }
3391 |
3392 | assignExtrasToUserData( node, nodeDef );
3393 |
3394 | if ( nodeDef.extensions ) addUnknownExtensionsToUserData( extensions, node, nodeDef );
3395 |
3396 | if ( nodeDef.matrix !== undefined ) {
3397 |
3398 | var matrix = new Matrix4();
3399 | matrix.fromArray( nodeDef.matrix );
3400 | node.applyMatrix4( matrix );
3401 |
3402 | } else {
3403 |
3404 | if ( nodeDef.translation !== undefined ) {
3405 |
3406 | node.position.fromArray( nodeDef.translation );
3407 |
3408 | }
3409 |
3410 | if ( nodeDef.rotation !== undefined ) {
3411 |
3412 | node.quaternion.fromArray( nodeDef.rotation );
3413 |
3414 | }
3415 |
3416 | if ( nodeDef.scale !== undefined ) {
3417 |
3418 | node.scale.fromArray( nodeDef.scale );
3419 |
3420 | }
3421 |
3422 | }
3423 |
3424 | return node;
3425 |
3426 | } );
3427 |
3428 | };
3429 |
3430 | /**
3431 | * Specification: https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#scenes
3432 | * @param {number} sceneIndex
3433 | * @return {Promise}
3434 | */
3435 | GLTFParser.prototype.loadScene = function () {
3436 |
3437 | // scene node hierachy builder
3438 |
3439 | function buildNodeHierachy( nodeId, parentObject, json, parser ) {
3440 |
3441 | var nodeDef = json.nodes[ nodeId ];
3442 |
3443 | return parser.getDependency( 'node', nodeId ).then( function ( node ) {
3444 |
3445 | if ( nodeDef.skin === undefined ) return node;
3446 |
3447 | // build skeleton here as well
3448 |
3449 | var skinEntry;
3450 |
3451 | return parser.getDependency( 'skin', nodeDef.skin ).then( function ( skin ) {
3452 |
3453 | skinEntry = skin;
3454 |
3455 | var pendingJoints = [];
3456 |
3457 | for ( var i = 0, il = skinEntry.joints.length; i < il; i ++ ) {
3458 |
3459 | pendingJoints.push( parser.getDependency( 'node', skinEntry.joints[ i ] ) );
3460 |
3461 | }
3462 |
3463 | return Promise.all( pendingJoints );
3464 |
3465 | } ).then( function ( jointNodes ) {
3466 |
3467 | node.traverse( function ( mesh ) {
3468 |
3469 | if ( ! mesh.isMesh ) return;
3470 |
3471 | var bones = [];
3472 | var boneInverses = [];
3473 |
3474 | for ( var j = 0, jl = jointNodes.length; j < jl; j ++ ) {
3475 |
3476 | var jointNode = jointNodes[ j ];
3477 |
3478 | if ( jointNode ) {
3479 |
3480 | bones.push( jointNode );
3481 |
3482 | var mat = new Matrix4();
3483 |
3484 | if ( skinEntry.inverseBindMatrices !== undefined ) {
3485 |
3486 | mat.fromArray( skinEntry.inverseBindMatrices.array, j * 16 );
3487 |
3488 | }
3489 |
3490 | boneInverses.push( mat );
3491 |
3492 | } else {
3493 |
3494 | console.warn( 'GLTFLoader: Joint "%s" could not be found.', skinEntry.joints[ j ] );
3495 |
3496 | }
3497 |
3498 | }
3499 |
3500 | mesh.bind( new Skeleton( bones, boneInverses ), mesh.matrixWorld );
3501 |
3502 | } );
3503 |
3504 | return node;
3505 |
3506 | } );
3507 |
3508 | } ).then( function ( node ) {
3509 |
3510 | // build node hierachy
3511 |
3512 | parentObject.add( node );
3513 |
3514 | var pending = [];
3515 |
3516 | if ( nodeDef.children ) {
3517 |
3518 | var children = nodeDef.children;
3519 |
3520 | for ( var i = 0, il = children.length; i < il; i ++ ) {
3521 |
3522 | var child = children[ i ];
3523 | pending.push( buildNodeHierachy( child, node, json, parser ) );
3524 |
3525 | }
3526 |
3527 | }
3528 |
3529 | return Promise.all( pending );
3530 |
3531 | } );
3532 |
3533 | }
3534 |
3535 | return function loadScene( sceneIndex ) {
3536 |
3537 | var json = this.json;
3538 | var extensions = this.extensions;
3539 | var sceneDef = this.json.scenes[ sceneIndex ];
3540 | var parser = this;
3541 |
3542 | // Loader returns Group, not Scene.
3543 | // See: https://github.com/mrdoob/js/issues/18342#issuecomment-578981172
3544 | var scene = new Group();
3545 | if ( sceneDef.name ) scene.name = sceneDef.name;
3546 |
3547 | assignExtrasToUserData( scene, sceneDef );
3548 |
3549 | if ( sceneDef.extensions ) addUnknownExtensionsToUserData( extensions, scene, sceneDef );
3550 |
3551 | var nodeIds = sceneDef.nodes || [];
3552 |
3553 | var pending = [];
3554 |
3555 | for ( var i = 0, il = nodeIds.length; i < il; i ++ ) {
3556 |
3557 | pending.push( buildNodeHierachy( nodeIds[ i ], scene, json, parser ) );
3558 |
3559 | }
3560 |
3561 | return Promise.all( pending ).then( function () {
3562 |
3563 | return scene;
3564 |
3565 | } );
3566 |
3567 | };
3568 |
3569 | }();
3570 |
3571 | return GLTFLoader;
3572 |
3573 | } )();
3574 |
3575 | export { GLTFLoader };
3576 |
--------------------------------------------------------------------------------
/test/blendfiles/align.blend:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MozillaReality/MOZ_text/5672b7c08b8106d8930d45b540b5cb67eaec879f/test/blendfiles/align.blend
--------------------------------------------------------------------------------
/test/blendfiles/basic.blend:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MozillaReality/MOZ_text/5672b7c08b8106d8930d45b540b5cb67eaec879f/test/blendfiles/basic.blend
--------------------------------------------------------------------------------
/test/blendfiles/letterspacing.blend:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MozillaReality/MOZ_text/5672b7c08b8106d8930d45b540b5cb67eaec879f/test/blendfiles/letterspacing.blend
--------------------------------------------------------------------------------
/test/blendfiles/lineheight.blend:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MozillaReality/MOZ_text/5672b7c08b8106d8930d45b540b5cb67eaec879f/test/blendfiles/lineheight.blend
--------------------------------------------------------------------------------
/test/blendfiles/lorem ipsun.blend:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MozillaReality/MOZ_text/5672b7c08b8106d8930d45b540b5cb67eaec879f/test/blendfiles/lorem ipsun.blend
--------------------------------------------------------------------------------
/test/blendfiles/materials.blend:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MozillaReality/MOZ_text/5672b7c08b8106d8930d45b540b5cb67eaec879f/test/blendfiles/materials.blend
--------------------------------------------------------------------------------
/test/blendfiles/overflow.blend:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MozillaReality/MOZ_text/5672b7c08b8106d8930d45b540b5cb67eaec879f/test/blendfiles/overflow.blend
--------------------------------------------------------------------------------
/test/blendfiles/sizes.blend:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MozillaReality/MOZ_text/5672b7c08b8106d8930d45b540b5cb67eaec879f/test/blendfiles/sizes.blend
--------------------------------------------------------------------------------
/test/blendfiles/types.blend:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MozillaReality/MOZ_text/5672b7c08b8106d8930d45b540b5cb67eaec879f/test/blendfiles/types.blend
--------------------------------------------------------------------------------
/test/fonts/Arial.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MozillaReality/MOZ_text/5672b7c08b8106d8930d45b540b5cb67eaec879f/test/fonts/Arial.ttf
--------------------------------------------------------------------------------
/test/fonts/Comic Sans MS Bold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MozillaReality/MOZ_text/5672b7c08b8106d8930d45b540b5cb67eaec879f/test/fonts/Comic Sans MS Bold.ttf
--------------------------------------------------------------------------------
/test/fonts/Georgia.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MozillaReality/MOZ_text/5672b7c08b8106d8930d45b540b5cb67eaec879f/test/fonts/Georgia.ttf
--------------------------------------------------------------------------------
/test/fonts/Impact.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MozillaReality/MOZ_text/5672b7c08b8106d8930d45b540b5cb67eaec879f/test/fonts/Impact.ttf
--------------------------------------------------------------------------------
/test/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | MOZ_text tests
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/test/index.js:
--------------------------------------------------------------------------------
1 | import * as THREE from 'three';
2 | import {OrbitControls} from './node_modules/three/examples/jsm/controls/OrbitControls.js'
3 |
4 | import {GLTFLoader} from "./GLTFLoader.module.js";
5 | import * as dat from 'dat.gui';
6 |
7 | const CAMERA_DISTANCE = 30;
8 | var gui, scene, camera, camera1, camera2, renderer, model, controls1, controls2, controls;
9 |
10 | window.onload = () => { init() };
11 |
12 | function init() {
13 | scene = new THREE.Scene();
14 | camera1 = new THREE.OrthographicCamera(
15 | window.innerWidth / - 100,
16 | window.innerWidth / 100,
17 | window.innerHeight / 100,
18 | window.innerHeight / - 100,
19 | 1, 1000 );
20 | camera2 = new THREE.PerspectiveCamera(50, window.innerWidth/window.innerHeight, 0.1, 100);
21 | camera1.position.set(0, 0, CAMERA_DISTANCE);
22 | camera2.position.set(0, 0, CAMERA_DISTANCE);
23 | camera = camera1;
24 | renderer = new THREE.WebGLRenderer();
25 | renderer.setSize(window.innerWidth, window.innerHeight);
26 | document.body.appendChild(renderer.domElement);
27 | renderer.outputEncoding = THREE.sRGBEncoding;
28 | renderer.setClearColor(new THREE.Color(0.3, 0.3, 0.3), 1.0);
29 | renderer.setAnimationLoop(renderLoop);
30 |
31 | const light = new THREE.PointLight();
32 | light.position.set(10, 40, 50);
33 | scene.add(light);
34 |
35 | controls1 = new OrbitControls(camera1, renderer.domElement);
36 | controls2 = new OrbitControls(camera2, renderer.domElement);
37 | controls = controls1
38 |
39 | var ControlsData = function() {
40 | this.ortho = true;
41 | this.scene = 'basic';
42 | this.SCENES = [
43 | 'align',
44 | 'letterspacing',
45 | 'materials',
46 | 'sizes',
47 | 'basic',
48 | 'lineheight',
49 | 'overflow',
50 | 'types',
51 | 'lorem ipsun'
52 | ];
53 | this.ResetCamera = () => {
54 | camera1.position.set(0, 0, CAMERA_DISTANCE);
55 | camera2.position.set(0, 0, CAMERA_DISTANCE);
56 | }
57 | };
58 |
59 | var controlsData = new ControlsData();
60 | gui = new dat.GUI();
61 | gui.add(controlsData, 'scene', controlsData.SCENES).listen().onChange(val => {
62 | //location.hash = val;
63 | loadScene(val);
64 | });
65 | gui.add(controlsData, 'ortho').onChange(val => {
66 | camera = val ? camera1 : camera2;
67 | controls = val ? controls1 : controls2;
68 | });
69 | gui.add(controlsData, 'ResetCamera');
70 |
71 | const file = location.hash ? location.hash.substr(1) : 'basic';
72 | controlsData.scene = file;
73 | loadScene(file);
74 | }
75 |
76 | function loadScene(file) {
77 | if (model) {
78 | scene.remove(model);
79 | }
80 | new GLTFLoader().load(`models/${file}.gltf`, gltf => {
81 | model = gltf.scene;
82 | scene.add(model);
83 | for (var i = 0; i < model.children.length; i++){
84 | var t = model.children[i];
85 |
86 | const origin = new THREE.SphereGeometry(0.03);
87 | t.add(new THREE.Mesh(origin));
88 | }
89 | });
90 | }
91 |
92 | function renderLoop(t) {
93 | controls.update();
94 | renderer.render(scene, camera);
95 | //camera.lookAt(0, 0, 0);
96 | }
97 |
--------------------------------------------------------------------------------
/test/models/align.gltf:
--------------------------------------------------------------------------------
1 | {
2 | "asset" : {
3 | "generator" : "Khronos glTF Blender I/O v1.1.45",
4 | "version" : "2.0"
5 | },
6 | "extensionsUsed" : [
7 | "MOZ_text"
8 | ],
9 | "scene" : 0,
10 | "scenes" : [
11 | {
12 | "name" : "Scene",
13 | "nodes" : [
14 | 0,
15 | 1,
16 | 2,
17 | 3,
18 | 4,
19 | 5,
20 | 6,
21 | 7,
22 | 8,
23 | 9,
24 | 10,
25 | 11,
26 | 12,
27 | 13,
28 | 14
29 | ]
30 | }
31 | ],
32 | "nodes" : [
33 | {
34 | "extensions" : {
35 | "MOZ_text" : {
36 | "index" : 0,
37 | "type" : "sdf",
38 | "color" : [
39 | 1.0,
40 | 1.0,
41 | 1.0,
42 | 1.0
43 | ],
44 | "alignX" : "center",
45 | "alignY" : "top",
46 | "value" : "center-top",
47 | "fontName" : "ArialMT",
48 | "fontFile" : "fonts/Arial.ttf",
49 | "size" : 1,
50 | "maxWidth" : 0,
51 | "overflow" : "NONE",
52 | "letterSpacing" : 0,
53 | "lineSpacing" : 1,
54 | "dimensions" : [
55 | 4.25600004196167,
56 | 0.7720000147819519
57 | ]
58 | }
59 | },
60 | "name" : "Text.001",
61 | "rotation" : [
62 | 0.7071067690849304,
63 | 0,
64 | 0,
65 | 0.7071068286895752
66 | ]
67 | },
68 | {
69 | "extensions" : {
70 | "MOZ_text" : {
71 | "index" : 1,
72 | "type" : "sdf",
73 | "color" : [
74 | 1.0,
75 | 1.0,
76 | 1.0,
77 | 1.0
78 | ],
79 | "alignX" : "center",
80 | "alignY" : "bottom",
81 | "value" : "center-bottom",
82 | "fontName" : "ArialMT",
83 | "fontFile" : "fonts/Arial.ttf",
84 | "size" : 1,
85 | "maxWidth" : 0,
86 | "overflow" : "NONE",
87 | "letterSpacing" : 0,
88 | "lineSpacing" : 1,
89 | "dimensions" : [
90 | 5.889000415802002,
91 | 0.6910000443458557
92 | ]
93 | }
94 | },
95 | "name" : "Text.002",
96 | "rotation" : [
97 | 0.7071067690849304,
98 | 0,
99 | 0,
100 | 0.7071068286895752
101 | ]
102 | },
103 | {
104 | "extensions" : {
105 | "MOZ_text" : {
106 | "index" : 2,
107 | "type" : "sdf",
108 | "color" : [
109 | 1.0,
110 | 1.0,
111 | 1.0,
112 | 1.0
113 | ],
114 | "alignX" : "center",
115 | "alignY" : "top_baseline",
116 | "value" : "center-top baseline",
117 | "fontName" : "ArialMT",
118 | "fontFile" : "fonts/Arial.ttf",
119 | "size" : 1,
120 | "maxWidth" : 0,
121 | "overflow" : "NONE",
122 | "letterSpacing" : 0,
123 | "lineSpacing" : 1,
124 | "dimensions" : [
125 | 7.74500036239624,
126 | 0.9120000600814819
127 | ]
128 | }
129 | },
130 | "name" : "Text.003",
131 | "rotation" : [
132 | 0.7071067690849304,
133 | 0,
134 | 0,
135 | 0.7071068286895752
136 | ]
137 | },
138 | {
139 | "extensions" : {
140 | "MOZ_text" : {
141 | "index" : 3,
142 | "type" : "sdf",
143 | "color" : [
144 | 1.0,
145 | 1.0,
146 | 1.0,
147 | 1.0
148 | ],
149 | "alignX" : "center",
150 | "alignY" : "center",
151 | "value" : "center-center",
152 | "fontName" : "ArialMT",
153 | "fontFile" : "fonts/Arial.ttf",
154 | "size" : 1,
155 | "maxWidth" : 0,
156 | "overflow" : "NONE",
157 | "letterSpacing" : 0,
158 | "lineSpacing" : 1,
159 | "dimensions" : [
160 | 5.5329999923706055,
161 | 0.5510000586509705
162 | ]
163 | }
164 | },
165 | "name" : "Text.004",
166 | "rotation" : [
167 | 0.7071067690849304,
168 | 0,
169 | 0,
170 | 0.7071068286895752
171 | ]
172 | },
173 | {
174 | "extensions" : {
175 | "MOZ_text" : {
176 | "index" : 4,
177 | "type" : "sdf",
178 | "color" : [
179 | 1.0,
180 | 1.0,
181 | 1.0,
182 | 1.0
183 | ],
184 | "alignX" : "center",
185 | "alignY" : "bottom_baseline",
186 | "value" : "center-bottom baseline",
187 | "fontName" : "ArialMT",
188 | "fontFile" : "fonts/Arial.ttf",
189 | "size" : 1,
190 | "maxWidth" : 0,
191 | "overflow" : "NONE",
192 | "letterSpacing" : 0,
193 | "lineSpacing" : 1,
194 | "dimensions" : [
195 | 9.4010009765625,
196 | 0.6910000443458557
197 | ]
198 | }
199 | },
200 | "name" : "Text.005",
201 | "rotation" : [
202 | 0.7071067690849304,
203 | 0,
204 | 0,
205 | 0.7071068286895752
206 | ]
207 | },
208 | {
209 | "extensions" : {
210 | "MOZ_text" : {
211 | "index" : 5,
212 | "type" : "sdf",
213 | "color" : [
214 | 1.0,
215 | 1.0,
216 | 1.0,
217 | 1.0
218 | ],
219 | "alignX" : "left",
220 | "alignY" : "top",
221 | "value" : "center-top",
222 | "fontName" : "ArialMT",
223 | "fontFile" : "fonts/Arial.ttf",
224 | "size" : 1,
225 | "maxWidth" : 0,
226 | "overflow" : "NONE",
227 | "letterSpacing" : 0,
228 | "lineSpacing" : 1,
229 | "dimensions" : [
230 | 4.25600004196167,
231 | 0.7720000147819519
232 | ]
233 | }
234 | },
235 | "name" : "Text.006",
236 | "rotation" : [
237 | 0.7071067690849304,
238 | 0,
239 | 0,
240 | 0.7071068286895752
241 | ],
242 | "translation" : [
243 | -11.681417465209961,
244 | 0,
245 | 0
246 | ]
247 | },
248 | {
249 | "extensions" : {
250 | "MOZ_text" : {
251 | "index" : 6,
252 | "type" : "sdf",
253 | "color" : [
254 | 1.0,
255 | 1.0,
256 | 1.0,
257 | 1.0
258 | ],
259 | "alignX" : "left",
260 | "alignY" : "bottom",
261 | "value" : "center-bottom",
262 | "fontName" : "ArialMT",
263 | "fontFile" : "fonts/Arial.ttf",
264 | "size" : 1,
265 | "maxWidth" : 0,
266 | "overflow" : "NONE",
267 | "letterSpacing" : 0,
268 | "lineSpacing" : 1,
269 | "dimensions" : [
270 | 5.889000415802002,
271 | 0.6910000443458557
272 | ]
273 | }
274 | },
275 | "name" : "Text.007",
276 | "rotation" : [
277 | 0.7071067690849304,
278 | 0,
279 | 0,
280 | 0.7071068286895752
281 | ],
282 | "translation" : [
283 | -11.681417465209961,
284 | 0,
285 | 0
286 | ]
287 | },
288 | {
289 | "extensions" : {
290 | "MOZ_text" : {
291 | "index" : 7,
292 | "type" : "sdf",
293 | "color" : [
294 | 1.0,
295 | 1.0,
296 | 1.0,
297 | 1.0
298 | ],
299 | "alignX" : "left",
300 | "alignY" : "top_baseline",
301 | "value" : "center-top baseline",
302 | "fontName" : "ArialMT",
303 | "fontFile" : "fonts/Arial.ttf",
304 | "size" : 1,
305 | "maxWidth" : 0,
306 | "overflow" : "NONE",
307 | "letterSpacing" : 0,
308 | "lineSpacing" : 1,
309 | "dimensions" : [
310 | 7.74500036239624,
311 | 0.9120000600814819
312 | ]
313 | }
314 | },
315 | "name" : "Text.008",
316 | "rotation" : [
317 | 0.7071067690849304,
318 | 0,
319 | 0,
320 | 0.7071068286895752
321 | ],
322 | "translation" : [
323 | -11.681417465209961,
324 | 0,
325 | 0
326 | ]
327 | },
328 | {
329 | "extensions" : {
330 | "MOZ_text" : {
331 | "index" : 8,
332 | "type" : "sdf",
333 | "color" : [
334 | 1.0,
335 | 1.0,
336 | 1.0,
337 | 1.0
338 | ],
339 | "alignX" : "left",
340 | "alignY" : "center",
341 | "value" : "center-center",
342 | "fontName" : "ArialMT",
343 | "fontFile" : "fonts/Arial.ttf",
344 | "size" : 1,
345 | "maxWidth" : 0,
346 | "overflow" : "NONE",
347 | "letterSpacing" : 0,
348 | "lineSpacing" : 1,
349 | "dimensions" : [
350 | 5.5329999923706055,
351 | 0.5510000586509705
352 | ]
353 | }
354 | },
355 | "name" : "Text.009",
356 | "rotation" : [
357 | 0.7071067690849304,
358 | 0,
359 | 0,
360 | 0.7071068286895752
361 | ],
362 | "translation" : [
363 | -11.681417465209961,
364 | 0,
365 | 0
366 | ]
367 | },
368 | {
369 | "extensions" : {
370 | "MOZ_text" : {
371 | "index" : 9,
372 | "type" : "sdf",
373 | "color" : [
374 | 1.0,
375 | 1.0,
376 | 1.0,
377 | 1.0
378 | ],
379 | "alignX" : "left",
380 | "alignY" : "bottom_baseline",
381 | "value" : "center-bottom baseline",
382 | "fontName" : "ArialMT",
383 | "fontFile" : "fonts/Arial.ttf",
384 | "size" : 1,
385 | "maxWidth" : 0,
386 | "overflow" : "NONE",
387 | "letterSpacing" : 0,
388 | "lineSpacing" : 1,
389 | "dimensions" : [
390 | 9.401000022888184,
391 | 0.6910000443458557
392 | ]
393 | }
394 | },
395 | "name" : "Text.010",
396 | "rotation" : [
397 | 0.7071067690849304,
398 | 0,
399 | 0,
400 | 0.7071068286895752
401 | ],
402 | "translation" : [
403 | -11.681417465209961,
404 | 0,
405 | 0
406 | ]
407 | },
408 | {
409 | "extensions" : {
410 | "MOZ_text" : {
411 | "index" : 10,
412 | "type" : "sdf",
413 | "color" : [
414 | 1.0,
415 | 1.0,
416 | 1.0,
417 | 1.0
418 | ],
419 | "alignX" : "right",
420 | "alignY" : "top",
421 | "value" : "center-top",
422 | "fontName" : "ArialMT",
423 | "fontFile" : "fonts/Arial.ttf",
424 | "size" : 1,
425 | "maxWidth" : 0,
426 | "overflow" : "NONE",
427 | "letterSpacing" : 0,
428 | "lineSpacing" : 1,
429 | "dimensions" : [
430 | 4.25600004196167,
431 | 0.7720000147819519
432 | ]
433 | }
434 | },
435 | "name" : "Text.011",
436 | "rotation" : [
437 | 0.7071067690849304,
438 | 0,
439 | 0,
440 | 0.7071068286895752
441 | ],
442 | "translation" : [
443 | 11.71873664855957,
444 | 0,
445 | 0
446 | ]
447 | },
448 | {
449 | "extensions" : {
450 | "MOZ_text" : {
451 | "index" : 11,
452 | "type" : "sdf",
453 | "color" : [
454 | 1.0,
455 | 1.0,
456 | 1.0,
457 | 1.0
458 | ],
459 | "alignX" : "right",
460 | "alignY" : "bottom",
461 | "value" : "center-bottom",
462 | "fontName" : "ArialMT",
463 | "fontFile" : "fonts/Arial.ttf",
464 | "size" : 1,
465 | "maxWidth" : 0,
466 | "overflow" : "NONE",
467 | "letterSpacing" : 0,
468 | "lineSpacing" : 1,
469 | "dimensions" : [
470 | 5.889000415802002,
471 | 0.6910000443458557
472 | ]
473 | }
474 | },
475 | "name" : "Text.012",
476 | "rotation" : [
477 | 0.7071067690849304,
478 | 0,
479 | 0,
480 | 0.7071068286895752
481 | ],
482 | "translation" : [
483 | 11.71873664855957,
484 | 0,
485 | 0
486 | ]
487 | },
488 | {
489 | "extensions" : {
490 | "MOZ_text" : {
491 | "index" : 12,
492 | "type" : "sdf",
493 | "color" : [
494 | 1.0,
495 | 1.0,
496 | 1.0,
497 | 1.0
498 | ],
499 | "alignX" : "right",
500 | "alignY" : "top_baseline",
501 | "value" : "center-top baseline",
502 | "fontName" : "ArialMT",
503 | "fontFile" : "fonts/Arial.ttf",
504 | "size" : 1,
505 | "maxWidth" : 0,
506 | "overflow" : "NONE",
507 | "letterSpacing" : 0,
508 | "lineSpacing" : 1,
509 | "dimensions" : [
510 | 7.74500036239624,
511 | 0.9120000600814819
512 | ]
513 | }
514 | },
515 | "name" : "Text.013",
516 | "rotation" : [
517 | 0.7071067690849304,
518 | 0,
519 | 0,
520 | 0.7071068286895752
521 | ],
522 | "translation" : [
523 | 11.71873664855957,
524 | 0,
525 | 0
526 | ]
527 | },
528 | {
529 | "extensions" : {
530 | "MOZ_text" : {
531 | "index" : 13,
532 | "type" : "sdf",
533 | "color" : [
534 | 1.0,
535 | 1.0,
536 | 1.0,
537 | 1.0
538 | ],
539 | "alignX" : "right",
540 | "alignY" : "center",
541 | "value" : "center-center",
542 | "fontName" : "ArialMT",
543 | "fontFile" : "fonts/Arial.ttf",
544 | "size" : 1,
545 | "maxWidth" : 0,
546 | "overflow" : "NONE",
547 | "letterSpacing" : 0,
548 | "lineSpacing" : 1,
549 | "dimensions" : [
550 | 5.5329999923706055,
551 | 0.5510000586509705
552 | ]
553 | }
554 | },
555 | "name" : "Text.014",
556 | "rotation" : [
557 | 0.7071067690849304,
558 | 0,
559 | 0,
560 | 0.7071068286895752
561 | ],
562 | "translation" : [
563 | 11.71873664855957,
564 | 0,
565 | 0
566 | ]
567 | },
568 | {
569 | "extensions" : {
570 | "MOZ_text" : {
571 | "index" : 14,
572 | "type" : "sdf",
573 | "color" : [
574 | 1.0,
575 | 1.0,
576 | 1.0,
577 | 1.0
578 | ],
579 | "alignX" : "right",
580 | "alignY" : "bottom_baseline",
581 | "value" : "center-bottom baseline",
582 | "fontName" : "ArialMT",
583 | "fontFile" : "fonts/Arial.ttf",
584 | "size" : 1,
585 | "maxWidth" : 0,
586 | "overflow" : "NONE",
587 | "letterSpacing" : 0,
588 | "lineSpacing" : 1,
589 | "dimensions" : [
590 | 9.401000022888184,
591 | 0.6910000443458557
592 | ]
593 | }
594 | },
595 | "name" : "Text.015",
596 | "rotation" : [
597 | 0.7071067690849304,
598 | 0,
599 | 0,
600 | 0.7071068286895752
601 | ],
602 | "translation" : [
603 | 11.71873664855957,
604 | 0,
605 | 0
606 | ]
607 | }
608 | ]
609 | }
610 |
--------------------------------------------------------------------------------
/test/models/basic.gltf:
--------------------------------------------------------------------------------
1 | {
2 | "asset" : {
3 | "generator" : "Khronos glTF Blender I/O v1.1.45",
4 | "version" : "2.0"
5 | },
6 | "extensionsUsed" : [
7 | "MOZ_text"
8 | ],
9 | "scene" : 0,
10 | "scenes" : [
11 | {
12 | "name" : "Scene",
13 | "nodes" : [
14 | 0,
15 | 1,
16 | 2,
17 | 3
18 | ]
19 | }
20 | ],
21 | "nodes" : [
22 | {
23 | "extensions" : {
24 | "MOZ_text" : {
25 | "index" : 0,
26 | "type" : "sdf",
27 | "color" : [
28 | 1.0,
29 | 1.0,
30 | 1.0,
31 | 1.0
32 | ],
33 | "alignX" : "center",
34 | "alignY" : "center",
35 | "value" : "Lorem\nIPSUM",
36 | "fontName" : "Impact",
37 | "fontFile" : "fonts/Impact.ttf",
38 | "size" : 1.247999906539917,
39 | "maxWidth" : 0,
40 | "overflow" : "NONE",
41 | "letterSpacing" : 0.10000002384185791,
42 | "lineSpacing" : 0.7000000476837158,
43 | "dimensions" : [
44 | 3.4475998878479004,
45 | 1.735967993736267
46 | ]
47 | }
48 | },
49 | "name" : "Text",
50 | "rotation" : [
51 | 0.7071067690849304,
52 | 0,
53 | 0,
54 | 0.7071067690849304
55 | ],
56 | "translation" : [
57 | 0,
58 | 0.3041502833366394,
59 | 0.09999999403953552
60 | ]
61 | },
62 | {
63 | "mesh" : 0,
64 | "name" : "Plane",
65 | "rotation" : [
66 | 0.7071067690849304,
67 | 0,
68 | 0,
69 | 0.7071067690849304
70 | ]
71 | },
72 | {
73 | "extensions" : {
74 | "MOZ_text" : {
75 | "index" : 1,
76 | "type" : "sdf",
77 | "color" : [
78 | 1.0,
79 | 1.0,
80 | 1.0,
81 | 1.0
82 | ],
83 | "alignX" : "center",
84 | "alignY" : "center",
85 | "value" : "Lorem\nIPSUM",
86 | "fontName" : "Georgia",
87 | "fontFile" : "fonts/Georgia.ttf",
88 | "size" : 1,
89 | "maxWidth" : 0,
90 | "overflow" : "NONE",
91 | "letterSpacing" : -0.10000002384185791,
92 | "lineSpacing" : 1.0000001192092896,
93 | "dimensions" : [
94 | 2.3625001907348633,
95 | 1.69100022315979
96 | ]
97 | }
98 | },
99 | "name" : "Text.001",
100 | "rotation" : [
101 | 0.7071067690849304,
102 | 0,
103 | 0,
104 | 0.7071067690849304
105 | ],
106 | "translation" : [
107 | 0,
108 | -1.3189244270324707,
109 | 0.09999999403953552
110 | ]
111 | },
112 | {
113 | "extensions" : {
114 | "MOZ_text" : {
115 | "index" : 2,
116 | "type" : "sdf",
117 | "color" : [
118 | 1.0,
119 | 1.0,
120 | 1.0,
121 | 1.0
122 | ],
123 | "alignX" : "center",
124 | "alignY" : "center",
125 | "value" : "Lorem\nIPSUM",
126 | "fontName" : "ArialMT",
127 | "fontFile" : "fonts/Arial.ttf",
128 | "size" : 1,
129 | "maxWidth" : 0,
130 | "overflow" : "NONE",
131 | "letterSpacing" : 1.3999998569488525,
132 | "lineSpacing" : 0.5,
133 | "dimensions" : [
134 | 5.362500190734863,
135 | 1.190999984741211
136 | ]
137 | }
138 | },
139 | "name" : "Text.002",
140 | "rotation" : [
141 | 0.7071067690849304,
142 | 0,
143 | 0,
144 | 0.7071067690849304
145 | ],
146 | "translation" : [
147 | 0,
148 | 1.905092477798462,
149 | 0.09999999403953552
150 | ]
151 | }
152 | ],
153 | "materials" : [
154 | {
155 | "doubleSided" : true,
156 | "emissiveFactor" : [
157 | 0,
158 | 0,
159 | 0
160 | ],
161 | "name" : "Material.004",
162 | "pbrMetallicRoughness" : {
163 | "baseColorFactor" : [
164 | 0.09817364811897278,
165 | 0.529207170009613,
166 | 0.8000000715255737,
167 | 1
168 | ],
169 | "metallicFactor" : 0,
170 | "roughnessFactor" : 0.5
171 | }
172 | }
173 | ],
174 | "meshes" : [
175 | {
176 | "name" : "Plane",
177 | "primitives" : [
178 | {
179 | "attributes" : {
180 | "POSITION" : 0,
181 | "NORMAL" : 1,
182 | "TEXCOORD_0" : 2
183 | },
184 | "indices" : 3,
185 | "material" : 0
186 | }
187 | ]
188 | }
189 | ],
190 | "accessors" : [
191 | {
192 | "bufferView" : 0,
193 | "componentType" : 5126,
194 | "count" : 32,
195 | "max" : [
196 | 1.8382400274276733,
197 | 1.040037602706434e-07,
198 | 3.601489782333374
199 | ],
200 | "min" : [
201 | -1.8382400274276733,
202 | -8.061077494403435e-08,
203 | -3.4391820430755615
204 | ],
205 | "type" : "VEC3"
206 | },
207 | {
208 | "bufferView" : 1,
209 | "componentType" : 5126,
210 | "count" : 32,
211 | "type" : "VEC3"
212 | },
213 | {
214 | "bufferView" : 2,
215 | "componentType" : 5126,
216 | "count" : 32,
217 | "type" : "VEC2"
218 | },
219 | {
220 | "bufferView" : 3,
221 | "componentType" : 5123,
222 | "count" : 48,
223 | "type" : "SCALAR"
224 | }
225 | ],
226 | "bufferViews" : [
227 | {
228 | "buffer" : 0,
229 | "byteLength" : 384,
230 | "byteOffset" : 0
231 | },
232 | {
233 | "buffer" : 0,
234 | "byteLength" : 384,
235 | "byteOffset" : 384
236 | },
237 | {
238 | "buffer" : 0,
239 | "byteLength" : 256,
240 | "byteOffset" : 768
241 | },
242 | {
243 | "buffer" : 0,
244 | "byteLength" : 96,
245 | "byteOffset" : 1024
246 | }
247 | ],
248 | "buffers" : [
249 | {
250 | "byteLength" : 1120,
251 | "uri" : "data:application/octet-stream;base64,c0vrPz4crbMPbyTAc0vrP5VHlrOPG1zAcnSVP5VHlrOPG1zAcnSVPz4crbMPbyTAc0vrP6xY3zN61Q5Ac0vrPz4crbMPbyTAcnSVPz4crbMPbyTAcnSVP6xY3zN61Q5Ac0vrP++DpTPPfmZAc0vrP6xY3zN61Q5AcnSVP6xY3zN61Q5AcnSVP++DpTPPfmZAIMOYv++DpTPPfmZAIMOYv6xY3zN61Q5Ac0vrv6xY3zN61Q5Ac0vrv++DpTPPfmZAIMOYv6xY3zN61Q5AIMOYvz4crbMPbyTAc0vrvz4crbMPbyTAc0vrv6xY3zN61Q5AIMOYvz4crbMPbyTAIMOYv5VHlrOPG1zAc0vrv5VHlrOPG1zAc0vrvz4crbMPbyTAcnSVP++DpTPPfmZAcnSVP6xY3zN61Q5AIMOYv6xY3zN61Q5AIMOYv++DpTPPfmZAcnSVPz4crbMPbyTAcnSVP5VHlrOPG1zAIMOYv5VHlrOPG1zAIMOYvz4crbMPbyTAAAAAAP//fz/19VEyAAAAAP//fz/19VEyAAAAAP//fz/19VEyAAAAAP//fz/19VEyAAAAAAAAgD9lJyWzAAAAAAAAgD9lJyWzAAAAAAAAgD9lJyWzAAAAAAAAgD9lJyWzAAAAAP//fz+o4qgyAAAAAP//fz+o4qgyAAAAAP//fz+o4qgyAAAAAP//fz+o4qgyAAAAAP//fz+n4qgyAAAAAP//fz+n4qgyAAAAAP//fz+n4qgyAAAAAP//fz+n4qgyAAAAAP//fz9lJyWzAAAAAP//fz9lJyWzAAAAAP//fz9lJyWzAAAAAP//fz9lJyWzAAAAAAAAgD/09VEyAAAAAAAAgD/09VEyAAAAAAAAgD/09VEyAAAAAAAAgD/09VEyAAAAAAAAgD+o4qgyAAAAAAAAgD+o4qgyAAAAAAAAgD+o4qgyAAAAAAAAgD+o4qgyAAAAAAAAgD/19VEyAAAAAAAAgD/19VEyAAAAAAAAgD/19VEyAAAAAAAAgD/19VEyAACAP24Emz4AAIA/AAAAAN8faT8AAAAA3x9pP24Emz4AAIA/fFgCPwAAgD9uBJs+3x9pP24Emz7fH2k/fFgCPwAAgD8AAIA/AACAP3xYAj/fH2k/fFgCP98faT8AAIA/RJyoPQAAgD9EnKg9fFgCPwAAAAB8WAI/AAAAAAAAgD9EnKg9fFgCP0ScqD1uBJs+AAAAAG4Emz4AAAAAfFgCP0ScqD1uBJs+RJyoPQAAAAAAAAAAAAAAAAAAAABuBJs+3x9pPwAAgD/fH2k/fFgCP0ScqD18WAI/RJyoPQAAgD/fH2k/bgSbPt8faT8AAAAARJyoPQAAAABEnKg9bgSbPgAAAQACAAAAAgADAAQABQAGAAQABgAHAAgACQAKAAgACgALAAwADQAOAAwADgAPABAAEQASABAAEgATABQAFQAWABQAFgAXABgAGQAaABgAGgAbABwAHQAeABwAHgAfAA=="
252 | }
253 | ]
254 | }
255 |
--------------------------------------------------------------------------------
/test/models/letterspacing.gltf:
--------------------------------------------------------------------------------
1 | {
2 | "asset" : {
3 | "generator" : "Khronos glTF Blender I/O v1.1.45",
4 | "version" : "2.0"
5 | },
6 | "extensionsUsed" : [
7 | "MOZ_text"
8 | ],
9 | "scene" : 0,
10 | "scenes" : [
11 | {
12 | "name" : "Scene",
13 | "nodes" : [
14 | 0,
15 | 1,
16 | 2,
17 | 3,
18 | 4
19 | ]
20 | }
21 | ],
22 | "nodes" : [
23 | {
24 | "extensions" : {
25 | "MOZ_text" : {
26 | "index" : 0,
27 | "type" : "sdf",
28 | "color" : [
29 | 1.0,
30 | 1.0,
31 | 1.0,
32 | 1.0
33 | ],
34 | "alignX" : "left",
35 | "alignY" : "center",
36 | "value" : "0123456789abcdefghijklmnopqrstuvwxyz",
37 | "fontName" : "ArialMT",
38 | "fontFile" : "fonts/Arial.ttf",
39 | "size" : 1,
40 | "maxWidth" : 0,
41 | "overflow" : "NONE",
42 | "letterSpacing" : 0,
43 | "lineSpacing" : 1,
44 | "dimensions" : [
45 | 16.573999404907227,
46 | 0.9214074611663818
47 | ]
48 | }
49 | },
50 | "name" : "Text",
51 | "rotation" : [
52 | 0.7071067690849304,
53 | 0,
54 | 0,
55 | 0.7071068286895752
56 | ],
57 | "translation" : [
58 | -7.698817253112793,
59 | 0.22439904510974884,
60 | 0.22439903020858765
61 | ]
62 | },
63 | {
64 | "extensions" : {
65 | "MOZ_text" : {
66 | "index" : 1,
67 | "type" : "sdf",
68 | "color" : [
69 | 1.0,
70 | 1.0,
71 | 1.0,
72 | 1.0
73 | ],
74 | "alignX" : "left",
75 | "alignY" : "center",
76 | "value" : "0123456789abcdefghijklmnopqrstuvwxyz",
77 | "fontName" : "ArialMT",
78 | "fontFile" : "fonts/Arial.ttf",
79 | "size" : 1,
80 | "maxWidth" : 0,
81 | "overflow" : "NONE",
82 | "letterSpacing" : -0.5000000298023224,
83 | "lineSpacing" : 1,
84 | "dimensions" : [
85 | 7.824000358581543,
86 | 0.9214074611663818
87 | ]
88 | }
89 | },
90 | "name" : "Text.001",
91 | "rotation" : [
92 | 0.7071067690849304,
93 | 0,
94 | 0,
95 | 0.7071068286895752
96 | ],
97 | "translation" : [
98 | -7.698817253112793,
99 | 1.864822268486023,
100 | 0.22439903020858765
101 | ]
102 | },
103 | {
104 | "extensions" : {
105 | "MOZ_text" : {
106 | "index" : 2,
107 | "type" : "sdf",
108 | "color" : [
109 | 1.0,
110 | 1.0,
111 | 1.0,
112 | 1.0
113 | ],
114 | "alignX" : "left",
115 | "alignY" : "center",
116 | "value" : "0123456789abcdefghijklmnopqrstuvwxyz",
117 | "fontName" : "ArialMT",
118 | "fontFile" : "fonts/Arial.ttf",
119 | "size" : 1,
120 | "maxWidth" : 0,
121 | "overflow" : "NONE",
122 | "letterSpacing" : -0.25,
123 | "lineSpacing" : 1,
124 | "dimensions" : [
125 | 12.198999404907227,
126 | 0.9214074611663818
127 | ]
128 | }
129 | },
130 | "name" : "Text.002",
131 | "rotation" : [
132 | 0.7071067690849304,
133 | 0,
134 | 0,
135 | 0.7071068286895752
136 | ],
137 | "translation" : [
138 | -7.698817253112793,
139 | 1.0656417608261108,
140 | 0.22439903020858765
141 | ]
142 | },
143 | {
144 | "extensions" : {
145 | "MOZ_text" : {
146 | "index" : 3,
147 | "type" : "sdf",
148 | "color" : [
149 | 1.0,
150 | 1.0,
151 | 1.0,
152 | 1.0
153 | ],
154 | "alignX" : "left",
155 | "alignY" : "center",
156 | "value" : "0123456789abcdefghijklmnopqrstuvwxyz",
157 | "fontName" : "ArialMT",
158 | "fontFile" : "fonts/Arial.ttf",
159 | "size" : 1,
160 | "maxWidth" : 0,
161 | "overflow" : "NONE",
162 | "letterSpacing" : 0.25,
163 | "lineSpacing" : 1,
164 | "dimensions" : [
165 | 20.948999404907227,
166 | 0.9214074611663818
167 | ]
168 | }
169 | },
170 | "name" : "Text.003",
171 | "rotation" : [
172 | 0.7071067690849304,
173 | 0,
174 | 0,
175 | 0.7071068286895752
176 | ],
177 | "translation" : [
178 | -7.698817253112793,
179 | -0.5958125591278076,
180 | 0.22439903020858765
181 | ]
182 | },
183 | {
184 | "extensions" : {
185 | "MOZ_text" : {
186 | "index" : 4,
187 | "type" : "sdf",
188 | "color" : [
189 | 1.0,
190 | 1.0,
191 | 1.0,
192 | 1.0
193 | ],
194 | "alignX" : "left",
195 | "alignY" : "center",
196 | "value" : "0123456789abcdefghijklmnopqrstuvwxyz",
197 | "fontName" : "ArialMT",
198 | "fontFile" : "fonts/Arial.ttf",
199 | "size" : 1,
200 | "maxWidth" : 0,
201 | "overflow" : "NONE",
202 | "letterSpacing" : 0.5,
203 | "lineSpacing" : 1,
204 | "dimensions" : [
205 | 25.323999404907227,
206 | 0.9214074611663818
207 | ]
208 | }
209 | },
210 | "name" : "Text.004",
211 | "rotation" : [
212 | 0.7071067690849304,
213 | 0,
214 | 0,
215 | 0.7071068286895752
216 | ],
217 | "translation" : [
218 | -7.698817253112793,
219 | -1.416024088859558,
220 | 0.2243988960981369
221 | ]
222 | }
223 | ]
224 | }
225 |
--------------------------------------------------------------------------------
/test/models/lineheight.gltf:
--------------------------------------------------------------------------------
1 | {
2 | "asset" : {
3 | "generator" : "Khronos glTF Blender I/O v1.1.45",
4 | "version" : "2.0"
5 | },
6 | "extensionsUsed" : [
7 | "MOZ_text"
8 | ],
9 | "scene" : 0,
10 | "scenes" : [
11 | {
12 | "name" : "Scene",
13 | "nodes" : [
14 | 0,
15 | 1,
16 | 2,
17 | 3,
18 | 4,
19 | 5
20 | ]
21 | }
22 | ],
23 | "nodes" : [
24 | {
25 | "extensions" : {
26 | "MOZ_text" : {
27 | "index" : 0,
28 | "type" : "sdf",
29 | "color" : [
30 | 1.0,
31 | 1.0,
32 | 1.0,
33 | 1.0
34 | ],
35 | "alignX" : "center",
36 | "alignY" : "top",
37 | "value" : "abc\ndef\nGHI\nJKL",
38 | "fontName" : "ArialMT",
39 | "fontFile" : "fonts/Arial.ttf",
40 | "size" : 1,
41 | "maxWidth" : 0,
42 | "overflow" : "NONE",
43 | "letterSpacing" : 0,
44 | "lineSpacing" : 0.75,
45 | "dimensions" : [
46 | 1.5930001735687256,
47 | 3.1619999408721924
48 | ]
49 | }
50 | },
51 | "name" : "Text",
52 | "rotation" : [
53 | 0.7071067690849304,
54 | 0,
55 | 0,
56 | 0.7071068286895752
57 | ],
58 | "translation" : [
59 | -1.9517333507537842,
60 | 2.6924829483032227,
61 | 0.0392022468149662
62 | ]
63 | },
64 | {
65 | "extensions" : {
66 | "MOZ_text" : {
67 | "index" : 1,
68 | "type" : "sdf",
69 | "color" : [
70 | 1.0,
71 | 1.0,
72 | 1.0,
73 | 1.0
74 | ],
75 | "alignX" : "center",
76 | "alignY" : "top",
77 | "value" : "abc\ndef\nGHI\nJKL",
78 | "fontName" : "ArialMT",
79 | "fontFile" : "fonts/Arial.ttf",
80 | "size" : 1,
81 | "maxWidth" : 0,
82 | "overflow" : "NONE",
83 | "letterSpacing" : 0,
84 | "lineSpacing" : 1,
85 | "dimensions" : [
86 | 1.5930001735687256,
87 | 3.9119997024536133
88 | ]
89 | }
90 | },
91 | "name" : "Text.001",
92 | "rotation" : [
93 | 0.7071067690849304,
94 | 0,
95 | 0,
96 | 0.7071068286895752
97 | ],
98 | "translation" : [
99 | 0.005494356155395508,
100 | 2.6924829483032227,
101 | 0.0392022468149662
102 | ]
103 | },
104 | {
105 | "extensions" : {
106 | "MOZ_text" : {
107 | "index" : 2,
108 | "type" : "sdf",
109 | "color" : [
110 | 1.0,
111 | 1.0,
112 | 1.0,
113 | 1.0
114 | ],
115 | "alignX" : "center",
116 | "alignY" : "top",
117 | "value" : "abc\ndef\nGHI\nJKL",
118 | "fontName" : "ArialMT",
119 | "fontFile" : "fonts/Arial.ttf",
120 | "size" : 1,
121 | "maxWidth" : 0,
122 | "overflow" : "NONE",
123 | "letterSpacing" : 0,
124 | "lineSpacing" : 0.5,
125 | "dimensions" : [
126 | 1.5930001735687256,
127 | 2.4119999408721924
128 | ]
129 | }
130 | },
131 | "name" : "Text.002",
132 | "rotation" : [
133 | 0.7071067690849304,
134 | 0,
135 | 0,
136 | 0.7071068286895752
137 | ],
138 | "translation" : [
139 | -3.9276013374328613,
140 | 2.6924829483032227,
141 | 0.0392022468149662
142 | ]
143 | },
144 | {
145 | "extensions" : {
146 | "MOZ_text" : {
147 | "index" : 3,
148 | "type" : "sdf",
149 | "color" : [
150 | 1.0,
151 | 1.0,
152 | 1.0,
153 | 1.0
154 | ],
155 | "alignX" : "center",
156 | "alignY" : "top",
157 | "value" : "abc\ndef\nGHI\nJKL",
158 | "fontName" : "ArialMT",
159 | "fontFile" : "fonts/Arial.ttf",
160 | "size" : 1,
161 | "maxWidth" : 0,
162 | "overflow" : "NONE",
163 | "letterSpacing" : 0,
164 | "lineSpacing" : 1.5,
165 | "dimensions" : [
166 | 1.5930001735687256,
167 | 5.4120001792907715
168 | ]
169 | }
170 | },
171 | "name" : "Text.003",
172 | "rotation" : [
173 | 0.7071067690849304,
174 | 0,
175 | 0,
176 | 0.7071068286895752
177 | ],
178 | "translation" : [
179 | 4.069072723388672,
180 | 2.6924829483032227,
181 | 0.0392022468149662
182 | ]
183 | },
184 | {
185 | "extensions" : {
186 | "MOZ_text" : {
187 | "index" : 4,
188 | "type" : "sdf",
189 | "color" : [
190 | 1.0,
191 | 1.0,
192 | 1.0,
193 | 1.0
194 | ],
195 | "alignX" : "center",
196 | "alignY" : "top",
197 | "value" : "abc\ndef\nGHI\nJKL",
198 | "fontName" : "ArialMT",
199 | "fontFile" : "fonts/Arial.ttf",
200 | "size" : 1,
201 | "maxWidth" : 0,
202 | "overflow" : "NONE",
203 | "letterSpacing" : 0,
204 | "lineSpacing" : 1.25,
205 | "dimensions" : [
206 | 1.5930001735687256,
207 | 4.6620001792907715
208 | ]
209 | }
210 | },
211 | "name" : "Text.004",
212 | "rotation" : [
213 | 0.7071067690849304,
214 | 0,
215 | 0,
216 | 0.7071068286895752
217 | ],
218 | "translation" : [
219 | 2.0372836589813232,
220 | 2.6924829483032227,
221 | 0.0392022468149662
222 | ]
223 | },
224 | {
225 | "mesh" : 0,
226 | "name" : "Plane.001",
227 | "translation" : [
228 | -5.6806206703186035,
229 | -0.186402827501297,
230 | 0.007173687219619751
231 | ]
232 | }
233 | ],
234 | "materials" : [
235 | {
236 | "doubleSided" : true,
237 | "emissiveFactor" : [
238 | 0,
239 | 0,
240 | 0
241 | ],
242 | "name" : "Material.002",
243 | "pbrMetallicRoughness" : {
244 | "baseColorFactor" : [
245 | 0.4819340705871582,
246 | 0.020676877349615097,
247 | 0.11108145862817764,
248 | 1
249 | ],
250 | "metallicFactor" : 0,
251 | "roughnessFactor" : 0.5
252 | }
253 | }
254 | ],
255 | "meshes" : [
256 | {
257 | "name" : "Plane.003",
258 | "primitives" : [
259 | {
260 | "attributes" : {
261 | "POSITION" : 0,
262 | "NORMAL" : 1,
263 | "TEXCOORD_0" : 2
264 | },
265 | "indices" : 3,
266 | "material" : 0
267 | }
268 | ]
269 | }
270 | ],
271 | "accessors" : [
272 | {
273 | "bufferView" : 0,
274 | "componentType" : 5126,
275 | "count" : 36,
276 | "max" : [
277 | 11.320137023925781,
278 | 3.4544122219085693,
279 | 3.5762786865234375e-07
280 | ],
281 | "min" : [
282 | 0.2135324478149414,
283 | -3.0401837825775146,
284 | -3.5762786865234375e-07
285 | ],
286 | "type" : "VEC3"
287 | },
288 | {
289 | "bufferView" : 1,
290 | "componentType" : 5126,
291 | "count" : 36,
292 | "type" : "VEC3"
293 | },
294 | {
295 | "bufferView" : 2,
296 | "componentType" : 5126,
297 | "count" : 36,
298 | "type" : "VEC2"
299 | },
300 | {
301 | "bufferView" : 3,
302 | "componentType" : 5123,
303 | "count" : 48,
304 | "type" : "SCALAR"
305 | }
306 | ],
307 | "bufferViews" : [
308 | {
309 | "buffer" : 0,
310 | "byteLength" : 432,
311 | "byteOffset" : 0
312 | },
313 | {
314 | "buffer" : 0,
315 | "byteLength" : 432,
316 | "byteOffset" : 432
317 | },
318 | {
319 | "buffer" : 0,
320 | "byteLength" : 288,
321 | "byteOffset" : 864
322 | },
323 | {
324 | "buffer" : 0,
325 | "byteLength" : 96,
326 | "byteOffset" : 1152
327 | }
328 | ],
329 | "buffers" : [
330 | {
331 | "byteLength" : 1248,
332 | "uri" : "data:application/octet-stream;base64,weERQbrxFsAAAAA0QKhaPl+SQsDx71k0SB81QV+SQsDx71k0jmAmQbnxFsAAAMA0SB81QRcVXUCD9g20jmAmQXN0MUAAAMC0weERQbrxFsAAAAA0weERQTBOyL/IMm8zQKhaPl+SQsDx71k0QKhaPl+SQsDx71k0rpviQNwFUr+gDU0zVJehQNYFUr/AGjmyQKhaPl+SQsDx71k01MKhQAhdlr0ckmcyd+VEQB8pir2lhqyzQKhaPhcVXUCD9g20QKhaPl+SQsDx71k01UqRP+xJLj/KEL2y1UqRP3J0MUAAAAC0jmAmQXN0MUAAAMC0SB81QRcVXUCD9g20QKhaPl+SQsDx71k0weERQTBOyL/IMm8znn/iQHwyyL83OgkzQKhaPl+SQsDx71k0nn/iQHwyyL83OgkzrpviQNwFUr+gDU0zQKhaPl+SQsDx71k0VJehQNYFUr/AGjmy1MKhQAhdlr0ckmcyQKhaPl+SQsDx71k0cvhEQIe1Lj+gwj+x1UqRP+xJLj/KEL2ycvhEQIe1Lj+gwj+xQKhaPl+SQsDx71k0d+VEQB8pir2lhqyzcqQAswf0pTMAAIA/cqQAswf0pTMAAIA/cqQAswf0pTMAAIA/cqQAswf0pTMAAIA/cqQAswf0pTMAAIA/cqQAswf0pTMAAIA/a2FhMUjfqzP//38/a2FhMUjfqzP//38/a2FhMUjfqzP//38/12X3sjLEKjQAAIA/12X3sjLEKjQAAIA/12X3sjLEKjQAAIA/O9dJs4LtFjQAAIA/O9dJs4LtFjQAAIA/O9dJs4LtFjQAAIA/l7yjMtverbMAAIA/l7yjMtverbMAAIA/l7yjMtverbMAAIA/l7yjMtverbMAAIA/l7yjMtverbMAAIA/l7yjMtverbMAAIA/usdGsl8sNjQAAIA/usdGsl8sNjQAAIA/usdGsl8sNjQAAIA/Z3v9Mm6Vt7IAAIA/Z3v9Mm6Vt7IAAIA/Z3v9Mm6Vt7IAAIA/vql+M2RDDbMAAIA/vql+M2RDDbMAAIA/vql+M2RDDbMAAIA/7+MqstYhhzP//38/7+MqstYhhzP//38/7+MqstYhhzP//38/YOZdNBPB3rMAAIA/YOZdNBPB3rMAAIA/YOZdNBPB3rMAAIA/JVcgPlUfTD8AAAAAAACAPwAAgD8AAIA/NupXP1UfTD8AAIA/AAAAADbqVz+ogk8+JVcgPlUfTD8lVyA+u580PwAAAAAAAIA/AAAAAJN8Hj8lVyA+knwePyVXID6G4hQ/AAAAAGNYCD8lVyA+d5sIPyVXID4AUvE+AAAAAAAAAAAAAAAA8uPjPiVXID7w4+M+JVcgPqyCTz426lc/qIJPPgAAgD8AAAAAAAAAALyfND8lVyA+u580PyVXID6aKic/AAAAADJhKz8lVyA+mionPyVXID6SfB4/AAAAAIbiFD8lVyA+huIUPyVXID53mwg/AAAAAAsIAD8lVyA+ECDqPiVXID7w4+M+JVcgPhAg6j4AAAAACwgAPyVXID4AUvE+AAABAAIAAAACAAMAAwACAAQABQADAAQABgAHAAgACQAKAAsADAANAA4ADwAQABEADwARABIADwASABMADwATABQAFQAWABcAGAAZABoAGwAcAB0AHgAfACAAIQAiACMA"
333 | }
334 | ]
335 | }
336 |
--------------------------------------------------------------------------------
/test/models/lorem ipsun.gltf:
--------------------------------------------------------------------------------
1 | {
2 | "asset" : {
3 | "generator" : "Khronos glTF Blender I/O v1.1.45",
4 | "version" : "2.0"
5 | },
6 | "extensionsUsed" : [
7 | "MOZ_text"
8 | ],
9 | "scene" : 0,
10 | "scenes" : [
11 | {
12 | "name" : "Scene",
13 | "nodes" : [
14 | 0
15 | ]
16 | }
17 | ],
18 | "nodes" : [
19 | {
20 | "extensions" : {
21 | "MOZ_text" : {
22 | "index" : 0,
23 | "type" : "sdf",
24 | "color" : [
25 | 1.0,
26 | 0.08084166049957275,
27 | 0.03721405938267708,
28 | 1.0
29 | ],
30 | "alignX" : "center",
31 | "alignY" : "center",
32 | "value" : "Lorem\nIpsun",
33 | "fontName" : "Impact",
34 | "fontFile" : "fonts/Impact.ttf",
35 | "size" : 1,
36 | "maxWidth" : 0,
37 | "overflow" : "NONE",
38 | "letterSpacing" : 0,
39 | "lineSpacing" : 1,
40 | "dimensions" : [
41 | 1.6402738094329834,
42 | 1.5891170501708984
43 | ]
44 | }
45 | },
46 | "name" : "Text",
47 | "rotation" : [
48 | 0.7071067690849304,
49 | 0,
50 | 0,
51 | 0.7071068286895752
52 | ]
53 | }
54 | ]
55 | }
56 |
--------------------------------------------------------------------------------
/test/models/materials.gltf:
--------------------------------------------------------------------------------
1 | {
2 | "asset" : {
3 | "generator" : "Khronos glTF Blender I/O v1.1.45",
4 | "version" : "2.0"
5 | },
6 | "extensionsUsed" : [
7 | "MOZ_text"
8 | ],
9 | "scene" : 0,
10 | "scenes" : [
11 | {
12 | "name" : "Scene",
13 | "nodes" : [
14 | 0,
15 | 1,
16 | 2,
17 | 3,
18 | 4,
19 | 5,
20 | 6
21 | ]
22 | }
23 | ],
24 | "nodes" : [
25 | {
26 | "extensions" : {
27 | "MOZ_text" : {
28 | "index" : 0,
29 | "type" : "sdf",
30 | "color" : [
31 | 1.0,
32 | 0.005511130206286907,
33 | 0.010657832026481628,
34 | 1.0
35 | ],
36 | "alignX" : "left",
37 | "alignY" : "center",
38 | "value" : "COLOR",
39 | "fontName" : "Impact",
40 | "fontFile" : "fonts/Impact.ttf",
41 | "size" : 1,
42 | "maxWidth" : 0,
43 | "overflow" : "NONE",
44 | "letterSpacing" : 0,
45 | "lineSpacing" : 1,
46 | "dimensions" : [
47 | 3.3999998569488525,
48 | 0.7000000476837158
49 | ]
50 | }
51 | },
52 | "name" : "Text.001",
53 | "rotation" : [
54 | 0.7071067690849304,
55 | 0,
56 | 0,
57 | 0.7071068286895752
58 | ],
59 | "translation" : [
60 | -2.0110862255096436,
61 | 0.763002336025238,
62 | 0
63 | ]
64 | },
65 | {
66 | "extensions" : {
67 | "MOZ_text" : {
68 | "index" : 1,
69 | "type" : "sdf",
70 | "color" : [
71 | 1.0,
72 | 0.2460407316684723,
73 | 0.007256365846842527,
74 | 1.0
75 | ],
76 | "alignX" : "left",
77 | "alignY" : "center",
78 | "value" : "COLOR",
79 | "fontName" : "Impact",
80 | "fontFile" : "fonts/Impact.ttf",
81 | "size" : 1,
82 | "maxWidth" : 0,
83 | "overflow" : "NONE",
84 | "letterSpacing" : 0,
85 | "lineSpacing" : 1,
86 | "dimensions" : [
87 | 3.3999998569488525,
88 | 0.7000000476837158
89 | ]
90 | }
91 | },
92 | "name" : "Text.002",
93 | "rotation" : [
94 | 0.7071067690849304,
95 | 0,
96 | 0,
97 | 0.7071068286895752
98 | ],
99 | "translation" : [
100 | -2.0110862255096436,
101 | 0.0032998323440551758,
102 | 0
103 | ]
104 | },
105 | {
106 | "extensions" : {
107 | "MOZ_text" : {
108 | "index" : 2,
109 | "type" : "sdf",
110 | "color" : [
111 | 1.0,
112 | 0.6041886210441589,
113 | 0.0,
114 | 1.0
115 | ],
116 | "alignX" : "left",
117 | "alignY" : "center",
118 | "value" : "COLOR",
119 | "fontName" : "Impact",
120 | "fontFile" : "fonts/Impact.ttf",
121 | "size" : 1,
122 | "maxWidth" : 0,
123 | "overflow" : "NONE",
124 | "letterSpacing" : 0,
125 | "lineSpacing" : 1,
126 | "dimensions" : [
127 | 3.3999998569488525,
128 | 0.7000000476837158
129 | ]
130 | }
131 | },
132 | "name" : "Text.003",
133 | "rotation" : [
134 | 0.7071067690849304,
135 | 0,
136 | 0,
137 | 0.7071068286895752
138 | ],
139 | "translation" : [
140 | -2.0110862255096436,
141 | -0.7949102520942688,
142 | 0
143 | ]
144 | },
145 | {
146 | "extensions" : {
147 | "MOZ_text" : {
148 | "index" : 3,
149 | "type" : "sdf",
150 | "color" : [
151 | 0.004522898234426975,
152 | 1.0,
153 | 0.011849410831928253,
154 | 1.0
155 | ],
156 | "alignX" : "left",
157 | "alignY" : "center",
158 | "value" : "COLOR",
159 | "fontName" : "Impact",
160 | "fontFile" : "fonts/Impact.ttf",
161 | "size" : 1,
162 | "maxWidth" : 0,
163 | "overflow" : "NONE",
164 | "letterSpacing" : 0,
165 | "lineSpacing" : 1,
166 | "dimensions" : [
167 | 3.3999998569488525,
168 | 0.7000000476837158
169 | ]
170 | }
171 | },
172 | "name" : "Text.004",
173 | "rotation" : [
174 | 0.7071067690849304,
175 | 0,
176 | 0,
177 | 0.7071068286895752
178 | ],
179 | "translation" : [
180 | 0.3059403598308563,
181 | 0.763002336025238,
182 | 0
183 | ]
184 | },
185 | {
186 | "extensions" : {
187 | "MOZ_text" : {
188 | "index" : 4,
189 | "type" : "sdf",
190 | "color" : [
191 | 0.0020003956742584705,
192 | 0.915272057056427,
193 | 1.0,
194 | 1.0
195 | ],
196 | "alignX" : "left",
197 | "alignY" : "center",
198 | "value" : "COLOR",
199 | "fontName" : "Impact",
200 | "fontFile" : "fonts/Impact.ttf",
201 | "size" : 1,
202 | "maxWidth" : 0,
203 | "overflow" : "NONE",
204 | "letterSpacing" : 0,
205 | "lineSpacing" : 1,
206 | "dimensions" : [
207 | 3.3999998569488525,
208 | 0.7000000476837158
209 | ]
210 | }
211 | },
212 | "name" : "Text.005",
213 | "rotation" : [
214 | 0.7071067690849304,
215 | 0,
216 | 0,
217 | 0.7071068286895752
218 | ],
219 | "translation" : [
220 | 0.3059403598308563,
221 | 0.0032998323440551758,
222 | 0
223 | ]
224 | },
225 | {
226 | "extensions" : {
227 | "MOZ_text" : {
228 | "index" : 5,
229 | "type" : "sdf",
230 | "color" : [
231 | 0.017666403204202652,
232 | 0.0,
233 | 1.0,
234 | 1.0
235 | ],
236 | "alignX" : "left",
237 | "alignY" : "center",
238 | "value" : "COLOR",
239 | "fontName" : "Impact",
240 | "fontFile" : "fonts/Impact.ttf",
241 | "size" : 1,
242 | "maxWidth" : 0,
243 | "overflow" : "NONE",
244 | "letterSpacing" : 0,
245 | "lineSpacing" : 1,
246 | "dimensions" : [
247 | 3.3999998569488525,
248 | 0.7000000476837158
249 | ]
250 | }
251 | },
252 | "name" : "Text.006",
253 | "rotation" : [
254 | 0.7071067690849304,
255 | 0,
256 | 0,
257 | 0.7071068286895752
258 | ],
259 | "translation" : [
260 | 0.3059403598308563,
261 | -0.7949102520942688,
262 | 0
263 | ]
264 | },
265 | {
266 | "extensions" : {
267 | "MOZ_text" : {
268 | "index" : 6,
269 | "type" : "sdf",
270 | "color" : [
271 | 1.0,
272 | 0.03717641904950142,
273 | 0.25428420305252075,
274 | 0.4928571581840515
275 | ],
276 | "alignX" : "center",
277 | "alignY" : "center",
278 | "value" : "TRANS\nPARENT",
279 | "fontName" : "ArialMT",
280 | "fontFile" : "fonts/Arial.ttf",
281 | "size" : 1,
282 | "maxWidth" : 0,
283 | "overflow" : "NONE",
284 | "letterSpacing" : 0,
285 | "lineSpacing" : 0.6000000238418579,
286 | "dimensions" : [
287 | 6.370627403259277,
288 | 2.297982692718506
289 | ]
290 | }
291 | },
292 | "name" : "Text.007",
293 | "rotation" : [
294 | 0.7071067690849304,
295 | 0,
296 | 0,
297 | 0.7071068286895752
298 | ],
299 | "scale" : [
300 | 1.7800019979476929,
301 | 1.7800019979476929,
302 | 1.7800019979476929
303 | ],
304 | "translation" : [
305 | 0,
306 | 0,
307 | 0.11286976933479309
308 | ]
309 | }
310 | ]
311 | }
312 |
--------------------------------------------------------------------------------
/test/models/overflow.gltf:
--------------------------------------------------------------------------------
1 | {
2 | "asset" : {
3 | "generator" : "Khronos glTF Blender I/O v1.1.45",
4 | "version" : "2.0"
5 | },
6 | "extensionsUsed" : [
7 | "MOZ_text",
8 | "KHR_materials_unlit"
9 | ],
10 | "scene" : 0,
11 | "scenes" : [
12 | {
13 | "name" : "Scene",
14 | "nodes" : [
15 | 0,
16 | 1,
17 | 2,
18 | 3,
19 | 4
20 | ]
21 | }
22 | ],
23 | "nodes" : [
24 | {
25 | "extensions" : {
26 | "MOZ_text" : {
27 | "index" : 0,
28 | "type" : "sdf",
29 | "color" : [
30 | 1.0,
31 | 1.0,
32 | 1.0,
33 | 1.0
34 | ],
35 | "alignX" : "left",
36 | "alignY" : "center",
37 | "value" : "No wrap:\nTo be, or not to be, that is the question",
38 | "fontName" : "ArialMT",
39 | "fontFile" : "fonts/Arial.ttf",
40 | "size" : 1,
41 | "maxWidth" : 0,
42 | "overflow" : "NONE",
43 | "letterSpacing" : 0,
44 | "lineSpacing" : 1,
45 | "dimensions" : [
46 | 16.171001434326172,
47 | 1.9119999408721924
48 | ]
49 | }
50 | },
51 | "name" : "Text",
52 | "rotation" : [
53 | 0.7071067690849304,
54 | 0,
55 | 0,
56 | 0.7071068286895752
57 | ],
58 | "translation" : [
59 | -7.937744140625,
60 | 4.8026323318481445,
61 | 0
62 | ]
63 | },
64 | {
65 | "extensions" : {
66 | "MOZ_text" : {
67 | "index" : 1,
68 | "type" : "sdf",
69 | "color" : [
70 | 1.0,
71 | 1.0,
72 | 1.0,
73 | 1.0
74 | ],
75 | "alignX" : "left",
76 | "alignY" : "top",
77 | "value" : "Overflow:\nTo be, or not to be, that is the question",
78 | "fontName" : "ArialMT",
79 | "fontFile" : "fonts/Arial.ttf",
80 | "size" : 1,
81 | "maxWidth" : 5,
82 | "overflow" : "NONE",
83 | "letterSpacing" : 0,
84 | "lineSpacing" : 1,
85 | "dimensions" : [
86 | 4.0450005531311035,
87 | 4.921407222747803
88 | ]
89 | }
90 | },
91 | "name" : "Text.001",
92 | "rotation" : [
93 | 0.7071067690849304,
94 | 0,
95 | 0,
96 | 0.7071068286895752
97 | ],
98 | "translation" : [
99 | -8.024392127990723,
100 | 2.1087992191314697,
101 | 0
102 | ]
103 | },
104 | {
105 | "extensions" : {
106 | "MOZ_text" : {
107 | "index" : 2,
108 | "type" : "sdf",
109 | "color" : [
110 | 1.0,
111 | 1.0,
112 | 1.0,
113 | 1.0
114 | ],
115 | "alignX" : "left",
116 | "alignY" : "top",
117 | "value" : "Scale to Fit:\nTo be, or not to be, that is the question",
118 | "fontName" : "ArialMT",
119 | "fontFile" : "fonts/Arial.ttf",
120 | "size" : 1,
121 | "maxWidth" : 5,
122 | "overflow" : "SCALE",
123 | "letterSpacing" : 0,
124 | "lineSpacing" : 1,
125 | "dimensions" : [
126 | 4.975997447967529,
127 | 0.5911130905151367
128 | ]
129 | }
130 | },
131 | "name" : "Text.002",
132 | "rotation" : [
133 | 0.7071067690849304,
134 | 0,
135 | 0,
136 | 0.7071068286895752
137 | ],
138 | "translation" : [
139 | -2.0540249347686768,
140 | 2.1087992191314697,
141 | 0
142 | ]
143 | },
144 | {
145 | "extensions" : {
146 | "MOZ_text" : {
147 | "index" : 3,
148 | "type" : "sdf",
149 | "color" : [
150 | 1.0,
151 | 1.0,
152 | 1.0,
153 | 1.0
154 | ],
155 | "alignX" : "left",
156 | "alignY" : "top",
157 | "value" : "Truncate:\nTo be, or not to be, that is the question",
158 | "fontName" : "ArialMT",
159 | "fontFile" : "fonts/Arial.ttf",
160 | "size" : 1,
161 | "maxWidth" : 5,
162 | "overflow" : "TRUNCATE",
163 | "letterSpacing" : 0,
164 | "lineSpacing" : 1,
165 | "dimensions" : [
166 | 3.9640004634857178,
167 | 2.799999952316284
168 | ]
169 | }
170 | },
171 | "name" : "Text.003",
172 | "rotation" : [
173 | 0.7071067690849304,
174 | 0,
175 | 0,
176 | 0.7071068286895752
177 | ],
178 | "translation" : [
179 | 3.6424720287323,
180 | 2.1087992191314697,
181 | 0
182 | ]
183 | },
184 | {
185 | "mesh" : 0,
186 | "name" : "Plane",
187 | "rotation" : [
188 | 0.7071067690849304,
189 | 0,
190 | 0,
191 | 0.7071068286895752
192 | ],
193 | "translation" : [
194 | 0,
195 | 3.532923460006714,
196 | 0
197 | ]
198 | }
199 | ],
200 | "materials" : [
201 | {
202 | "doubleSided" : true,
203 | "extensions" : {
204 | "KHR_materials_unlit" : {}
205 | },
206 | "name" : "Material.002",
207 | "pbrMetallicRoughness" : {
208 | "baseColorFactor" : [
209 | 0.18731386959552765,
210 | 0.18731386959552765,
211 | 0.18731386959552765,
212 | 1
213 | ]
214 | }
215 | }
216 | ],
217 | "meshes" : [
218 | {
219 | "name" : "Plane",
220 | "primitives" : [
221 | {
222 | "attributes" : {
223 | "POSITION" : 0,
224 | "NORMAL" : 1,
225 | "TEXCOORD_0" : 2
226 | },
227 | "indices" : 3,
228 | "material" : 0
229 | }
230 | ]
231 | }
232 | ],
233 | "accessors" : [
234 | {
235 | "bufferView" : 0,
236 | "componentType" : 5126,
237 | "count" : 4,
238 | "max" : [
239 | 7.998596668243408,
240 | 5.67506390325434e-07,
241 | 0.4817476272583008
242 | ],
243 | "min" : [
244 | -7.998596668243408,
245 | -5.67506390325434e-07,
246 | -0.4817471504211426
247 | ],
248 | "type" : "VEC3"
249 | },
250 | {
251 | "bufferView" : 1,
252 | "componentType" : 5126,
253 | "count" : 4,
254 | "type" : "VEC3"
255 | },
256 | {
257 | "bufferView" : 2,
258 | "componentType" : 5126,
259 | "count" : 4,
260 | "type" : "VEC2"
261 | },
262 | {
263 | "bufferView" : 3,
264 | "componentType" : 5123,
265 | "count" : 6,
266 | "type" : "SCALAR"
267 | }
268 | ],
269 | "bufferViews" : [
270 | {
271 | "buffer" : 0,
272 | "byteLength" : 48,
273 | "byteOffset" : 0
274 | },
275 | {
276 | "buffer" : 0,
277 | "byteLength" : 48,
278 | "byteOffset" : 48
279 | },
280 | {
281 | "buffer" : 0,
282 | "byteLength" : 32,
283 | "byteOffset" : 96
284 | },
285 | {
286 | "buffer" : 0,
287 | "byteLength" : 12,
288 | "byteOffset" : 128
289 | }
290 | ],
291 | "buffers" : [
292 | {
293 | "byteLength" : 140,
294 | "uri" : "data:application/octet-stream;base64,gfT/QL5WGDWgp/Y+gfT/QL5WGLWQp/a+gfT/wL5WGLWQp/a+gfT/wL5WGDWgp/Y+AAAAAAAAgD9XHJ61AAAAAAAAgD9XHJ61AAAAAAAAgD9XHJ61AAAAAAAAgD9XHJ61AACAPwAAgD8AAIA/AAAAAAAAAAAAAAAAAAAAAAAAgD8AAAEAAgAAAAIAAwA="
295 | }
296 | ]
297 | }
298 |
--------------------------------------------------------------------------------
/test/models/sizes.gltf:
--------------------------------------------------------------------------------
1 | {
2 | "asset" : {
3 | "generator" : "Khronos glTF Blender I/O v1.1.45",
4 | "version" : "2.0"
5 | },
6 | "extensionsUsed" : [
7 | "MOZ_text"
8 | ],
9 | "scene" : 0,
10 | "scenes" : [
11 | {
12 | "name" : "Scene",
13 | "nodes" : [
14 | 0,
15 | 1,
16 | 2,
17 | 3,
18 | 4
19 | ]
20 | }
21 | ],
22 | "nodes" : [
23 | {
24 | "extensions" : {
25 | "MOZ_text" : {
26 | "index" : 0,
27 | "type" : "sdf",
28 | "color" : [
29 | 1.0,
30 | 1.0,
31 | 1.0,
32 | 1.0
33 | ],
34 | "alignX" : "left",
35 | "alignY" : "center",
36 | "value" : "0123456789abcdefghijklmnopqrstuvwxyz",
37 | "fontName" : "ArialMT",
38 | "fontFile" : "fonts/Arial.ttf",
39 | "size" : 1,
40 | "maxWidth" : 0,
41 | "overflow" : "NONE",
42 | "letterSpacing" : 0,
43 | "lineSpacing" : 1,
44 | "dimensions" : [
45 | 16.573999404907227,
46 | 0.9214074611663818
47 | ]
48 | }
49 | },
50 | "name" : "Text",
51 | "rotation" : [
52 | 0.7071067690849304,
53 | 0,
54 | 0,
55 | 0.7071068286895752
56 | ],
57 | "translation" : [
58 | -8.981712341308594,
59 | 0.5188339948654175,
60 | 0.22439903020858765
61 | ]
62 | },
63 | {
64 | "extensions" : {
65 | "MOZ_text" : {
66 | "index" : 1,
67 | "type" : "sdf",
68 | "color" : [
69 | 1.0,
70 | 1.0,
71 | 1.0,
72 | 1.0
73 | ],
74 | "alignX" : "left",
75 | "alignY" : "center",
76 | "value" : "0123456789abcdefghijklmnopqrstuvwxyz",
77 | "fontName" : "ArialMT",
78 | "fontFile" : "fonts/Arial.ttf",
79 | "size" : 0.5,
80 | "maxWidth" : 0,
81 | "overflow" : "NONE",
82 | "letterSpacing" : 0,
83 | "lineSpacing" : 1,
84 | "dimensions" : [
85 | 8.286999702453613,
86 | 0.4607037305831909
87 | ]
88 | }
89 | },
90 | "name" : "Text.001",
91 | "rotation" : [
92 | 0.7071067690849304,
93 | 0,
94 | 0,
95 | 0.7071068286895752
96 | ],
97 | "translation" : [
98 | -8.981712341308594,
99 | 1.974169135093689,
100 | 0.22439885139465332
101 | ]
102 | },
103 | {
104 | "extensions" : {
105 | "MOZ_text" : {
106 | "index" : 2,
107 | "type" : "sdf",
108 | "color" : [
109 | 1.0,
110 | 1.0,
111 | 1.0,
112 | 1.0
113 | ],
114 | "alignX" : "left",
115 | "alignY" : "center",
116 | "value" : "0123456789abcdefghijklmnopqrstuvwxyz",
117 | "fontName" : "ArialMT",
118 | "fontFile" : "fonts/Arial.ttf",
119 | "size" : 0.75,
120 | "maxWidth" : 0,
121 | "overflow" : "NONE",
122 | "letterSpacing" : 0,
123 | "lineSpacing" : 1,
124 | "dimensions" : [
125 | 12.430500030517578,
126 | 0.6910556554794312
127 | ]
128 | }
129 | },
130 | "name" : "Text.002",
131 | "rotation" : [
132 | 0.7071067690849304,
133 | 0,
134 | 0,
135 | 0.7071068286895752
136 | ],
137 | "translation" : [
138 | -8.981712341308594,
139 | 1.3232488632202148,
140 | 0.2243989109992981
141 | ]
142 | },
143 | {
144 | "extensions" : {
145 | "MOZ_text" : {
146 | "index" : 3,
147 | "type" : "sdf",
148 | "color" : [
149 | 1.0,
150 | 1.0,
151 | 1.0,
152 | 1.0
153 | ],
154 | "alignX" : "left",
155 | "alignY" : "center",
156 | "value" : "0123456789abcdefghijklmnopqrstuvwxyz",
157 | "fontName" : "ArialMT",
158 | "fontFile" : "fonts/Arial.ttf",
159 | "size" : 1.25,
160 | "maxWidth" : 0,
161 | "overflow" : "NONE",
162 | "letterSpacing" : 0,
163 | "lineSpacing" : 1,
164 | "dimensions" : [
165 | 20.717500686645508,
166 | 1.151759386062622
167 | ]
168 | }
169 | },
170 | "name" : "Text.003",
171 | "rotation" : [
172 | 0.7071067690849304,
173 | 0,
174 | 0,
175 | 0.7071068286895752
176 | ],
177 | "translation" : [
178 | -8.981712341308594,
179 | -0.38100188970565796,
180 | 0.22439908981323242
181 | ]
182 | },
183 | {
184 | "extensions" : {
185 | "MOZ_text" : {
186 | "index" : 4,
187 | "type" : "sdf",
188 | "color" : [
189 | 1.0,
190 | 1.0,
191 | 1.0,
192 | 1.0
193 | ],
194 | "alignX" : "left",
195 | "alignY" : "center",
196 | "value" : "0123456789abcdefghijklmnopqrstuvwxyz",
197 | "fontName" : "ArialMT",
198 | "fontFile" : "fonts/Arial.ttf",
199 | "size" : 1.5,
200 | "maxWidth" : 0,
201 | "overflow" : "NONE",
202 | "letterSpacing" : 0,
203 | "lineSpacing" : 1,
204 | "dimensions" : [
205 | 24.861000061035156,
206 | 1.3821113109588623
207 | ]
208 | }
209 | },
210 | "name" : "Text.004",
211 | "rotation" : [
212 | 0.7071067690849304,
213 | 0,
214 | 0,
215 | 0.7071068286895752
216 | ],
217 | "translation" : [
218 | -8.981712341308594,
219 | -1.4793606996536255,
220 | 0.22439932823181152
221 | ]
222 | }
223 | ]
224 | }
225 |
--------------------------------------------------------------------------------
/test/models/types.gltf:
--------------------------------------------------------------------------------
1 | {
2 | "asset" : {
3 | "generator" : "Khronos glTF Blender I/O v1.1.45",
4 | "version" : "2.0"
5 | },
6 | "extensionsUsed" : [
7 | "MOZ_text"
8 | ],
9 | "scene" : 0,
10 | "scenes" : [
11 | {
12 | "name" : "Scene",
13 | "nodes" : [
14 | 0,
15 | 1,
16 | 2,
17 | 3
18 | ]
19 | }
20 | ],
21 | "nodes" : [
22 | {
23 | "extensions" : {
24 | "MOZ_text" : {
25 | "index" : 0,
26 | "type" : "sdf",
27 | "color" : [
28 | 1.0,
29 | 1.0,
30 | 1.0,
31 | 1.0
32 | ],
33 | "alignX" : "center",
34 | "alignY" : "center",
35 | "value" : "SDF \nthis is a test",
36 | "fontName" : "Impact",
37 | "fontFile" : "fonts/Impact.ttf",
38 | "size" : 2,
39 | "maxWidth" : 0,
40 | "overflow" : "NONE",
41 | "letterSpacing" : 0,
42 | "lineSpacing" : 0.5,
43 | "dimensions" : [
44 | 9.66200065612793,
45 | 2.4000000953674316
46 | ]
47 | }
48 | },
49 | "name" : "Text",
50 | "rotation" : [
51 | 0.7071067690849304,
52 | 0,
53 | 0,
54 | 0.7071068286895752
55 | ],
56 | "translation" : [
57 | 0,
58 | 4.204799652099609,
59 | 0
60 | ]
61 | },
62 | {
63 | "extensions" : {
64 | "MOZ_text" : {
65 | "index" : 1,
66 | "type" : "geometry",
67 | "color" : [
68 | 1.0,
69 | 0.986351490020752,
70 | 1.0,
71 | 1.0
72 | ],
73 | "alignX" : "left",
74 | "alignY" : "top",
75 | "value" : "Geometry \nthis is a test",
76 | "fontName" : "Impact",
77 | "fontFile" : "fonts/Impact.ttf",
78 | "size" : 2,
79 | "maxWidth" : 0,
80 | "overflow" : "NONE",
81 | "letterSpacing" : 0,
82 | "lineSpacing" : 0.5600000023841858,
83 | "dimensions" : [
84 | 9.702001571655273,
85 | 2.5611627101898193
86 | ],
87 | "extrude" : 0.6399999260902405,
88 | "extrudeResolution" : 2,
89 | "bevel" : 0.03999999165534973,
90 | "bevelOffset" : -0.019999980926513672,
91 | "bevelResolution" : 1
92 | }
93 | },
94 | "name" : "Text.002",
95 | "rotation" : [
96 | 0.7071067690849304,
97 | 0,
98 | 0,
99 | 0.7071068286895752
100 | ],
101 | "translation" : [
102 | -3.314697742462158,
103 | -0.32887446880340576,
104 | 0
105 | ]
106 | },
107 | {
108 | "extensions" : {
109 | "MOZ_text" : {
110 | "index" : 2,
111 | "type" : "texture",
112 | "color" : [
113 | 1.0,
114 | 1.0,
115 | 1.0,
116 | 1.0
117 | ],
118 | "alignX" : "center",
119 | "alignY" : "center",
120 | "value" : "Texture \nthis is a test",
121 | "fontName" : "Impact",
122 | "fontFile" : "fonts/Impact.ttf",
123 | "size" : 2,
124 | "maxWidth" : 0,
125 | "overflow" : "NONE",
126 | "letterSpacing" : 0,
127 | "lineSpacing" : 0.5,
128 | "dimensions" : [
129 | 9.66200065612793,
130 | 2.381999969482422
131 | ]
132 | }
133 | },
134 | "name" : "Text.001",
135 | "rotation" : [
136 | 0.7071067690849304,
137 | 0,
138 | 0,
139 | 0.7071068286895752
140 | ],
141 | "translation" : [
142 | 0,
143 | 1.3620043992996216,
144 | 0.051436007022857666
145 | ]
146 | },
147 | {
148 | "mesh" : 0,
149 | "name" : "Plane",
150 | "translation" : [
151 | 0,
152 | 0,
153 | -0.12198308855295181
154 | ]
155 | }
156 | ],
157 | "materials" : [
158 | {
159 | "doubleSided" : true,
160 | "emissiveFactor" : [
161 | 0,
162 | 0,
163 | 0
164 | ],
165 | "name" : "Material.002",
166 | "pbrMetallicRoughness" : {
167 | "baseColorFactor" : [
168 | 0.03484096750617027,
169 | 0.1259651929140091,
170 | 0.010935947299003601,
171 | 1
172 | ],
173 | "metallicFactor" : 0,
174 | "roughnessFactor" : 0.5
175 | }
176 | }
177 | ],
178 | "meshes" : [
179 | {
180 | "name" : "Plane",
181 | "primitives" : [
182 | {
183 | "attributes" : {
184 | "POSITION" : 0,
185 | "NORMAL" : 1,
186 | "TEXCOORD_0" : 2
187 | },
188 | "indices" : 3,
189 | "material" : 0
190 | }
191 | ]
192 | }
193 | ],
194 | "accessors" : [
195 | {
196 | "bufferView" : 0,
197 | "componentType" : 5126,
198 | "count" : 70,
199 | "max" : [
200 | 4.996329307556152,
201 | 6.826893329620361,
202 | 0.010078459978103638
203 | ],
204 | "min" : [
205 | -5.157411575317383,
206 | -4.638166904449463,
207 | 0.010077983140945435
208 | ],
209 | "type" : "VEC3"
210 | },
211 | {
212 | "bufferView" : 1,
213 | "componentType" : 5126,
214 | "count" : 70,
215 | "type" : "VEC3"
216 | },
217 | {
218 | "bufferView" : 2,
219 | "componentType" : 5126,
220 | "count" : 70,
221 | "type" : "VEC2"
222 | },
223 | {
224 | "bufferView" : 3,
225 | "componentType" : 5123,
226 | "count" : 108,
227 | "type" : "SCALAR"
228 | }
229 | ],
230 | "bufferViews" : [
231 | {
232 | "buffer" : 0,
233 | "byteLength" : 840,
234 | "byteOffset" : 0
235 | },
236 | {
237 | "buffer" : 0,
238 | "byteLength" : 840,
239 | "byteOffset" : 840
240 | },
241 | {
242 | "buffer" : 0,
243 | "byteLength" : 560,
244 | "byteOffset" : 1680
245 | },
246 | {
247 | "buffer" : 0,
248 | "byteLength" : 216,
249 | "byteOffset" : 2240
250 | }
251 | ],
252 | "buffers" : [
253 | {
254 | "byteLength" : 2456,
255 | "uri" : "data:application/octet-stream;base64,dgJcQOl12kAgICU8dgJcQGN+qkDgHyU87uGfQGN+qkDgHyU87uGfQOl12kAgICU8dgJcQGN+qkDgHyU8dgJcQBRiRkCAHyU87uGfQBRiRkCAHyU87uGfQGN+qkDgHyU8dgJcQBRiRkCAHyU8dgJcQDFOGkBgHyU87uGfQDFOGkBgHyU87uGfQBRiRkCAHyU8dgJcQDFOGkBgHyU8dgJcQCPWmz4AHyU87uGfQCTWmz4AHyU87uGfQDFOGkBgHyU8dgJcQCPWmz4AHyU8dgJcQBmxBr/gHiU87uGfQBixBr/gHiU87uGfQCTWmz4AHyU8dgJcQBmxBr/gHiU8dgJcQDC0OMCAHiU87uGfQDC0OMCAHiU87uGfQBixBr/gHiU8dgJcQDC0OMCAHiU8dgJcQN1rlMAgHiU87uGfQN1rlMAgHiU87uGfQDC0OMCAHiU8hAmlwDC0OMCAHiU8hAmlwN1rlMAgHiU8cSxbwN1rlMAgHiU8cSxbwDC0OMCAHiU8hAmlwByxBr/gHiU8hAmlwDC0OMCAHiU8cSxbwDC0OMCAHiU8cSxbwBuxBr/gHiU8hAmlwCDWmz4AHyU8hAmlwByxBr/gHiU8cSxbwBuxBr/gHiU8cSxbwCHWmz4AHyU8hAmlwDFOGkBgHyU8hAmlwCDWmz4AHyU8cSxbwCHWmz4AHyU8cSxbwDFOGkBgHyU8hAmlwBRiRkCAHyU8hAmlwDFOGkBgHyU8cSxbwDFOGkBgHyU8cSxbwBRiRkCAHyU8hAmlwGN+qkDgHyU8hAmlwBRiRkCAHyU8cSxbwBRiRkCAHyU8cSxbwGN+qkDgHyU8hAmlwOl12kAgICU8hAmlwGN+qkDgHyU8cSxbwGN+qkDgHyU8cSxbwOl12kAgICU8cSxbwDC0OMCAHiU8cSxbwN1rlMAgHiU8dgJcQN1rlMAgHiU8dgJcQDC0OMCAHiU8cSxbwCHWmz4AHyU8cSxbwBuxBr/gHiU8dgJcQBmxBr/gHiU8dgJcQCPWmz4AHyU8cSxbwBRiRkCAHyU8cSxbwDFOGkBgHyU8dgJcQDFOGkBgHyU8dgJcQBRiRkCAHyU8cSxbwOl12kAgICU8cSxbwGN+qkDgHyU8AAAAANTIKrMAAIA/AAAAANTIKrMAAIA/AAAAANTIKrMAAIA/AAAAANTIKrMAAIA/AAAAAElWLLMAAIA/AAAAAElWLLMAAIA/AAAAAElWLLMAAIA/AAAAAElWLLMAAIA/AAAAAIvaObMAAIA/AAAAAIvaObMAAIA/AAAAAIvaObMAAIA/AAAAAIvaObMAAIA/MO2bJ5FHNrP//38/MO2bJ5FHNrP//38/MO2bJ5FHNrP//38/MO2bJ5FHNrP//38/rsJFJmAfGrMAAIA/rsJFJmAfGrMAAIA/rsJFJmAfGrMAAIA/rsJFJmAfGrMAAIA/AAAAAL64IrMAAIA/AAAAAL64IrMAAIA/AAAAAL64IrMAAIA/AAAAAL64IrMAAIA/AAAAACwoW7MAAIA/AAAAACwoW7MAAIA/AAAAACwoW7MAAIA/AAAAACwoW7MAAIA/AAAAAC0oW7MAAIA/AAAAAC0oW7MAAIA/AAAAAC0oW7MAAIA/AAAAAC0oW7MAAIA/AAAAAL+4IrMAAIA/AAAAAL+4IrMAAIA/AAAAAL+4IrMAAIA/AAAAAL+4IrMAAIA/z+IxJl8fGrP//38/z+IxJl8fGrP//38/z+IxJl8fGrP//38/z+IxJl8fGrP//38/AAAAAJBHNrMAAIA/AAAAAJBHNrMAAIA/AAAAAJBHNrMAAIA/AAAAAJBHNrMAAIA/AAAAAIzaObMAAIA/AAAAAIzaObMAAIA/AAAAAIzaObMAAIA/AAAAAIzaObMAAIA/AAAAAElWLLMAAIA/AAAAAElWLLMAAIA/AAAAAElWLLMAAIA/AAAAAElWLLMAAIA/AAAAANTIKrMAAIA/AAAAANTIKrMAAIA/AAAAANTIKrMAAIA/AAAAANTIKrMAAIA/AAAAACwoW7MAAIA/AAAAACwoW7MAAIA/AAAAACwoW7MAAIA/AAAAACwoW7MAAIA/F62zJV8fGrP//38/F62zJV8fGrP//38/F62zJV8fGrP//38/F62zJV8fGrP//38/AAAAAIvaObMAAIA/AAAAAIvaObMAAIA/AAAAAIvaObMAAIA/AAAAAIvaObMAAIA/AAAAANTIKrMAAIA/AAAAANTIKrMAAIA/tbNYPwAAAAC2s1g/QOEFPgAAgD9A4QU+AACAPwAAAAC2s1g/QOEFPrazWD/8caY+AACAP/xxpj4AAIA/QOEFPrazWD/8caY+trNYP5AzxT4AAIA/kDPFPgAAgD/8caY+trNYP5AzxT61s1g/u6MRPwAAgD+7oxE/AACAP5AzxT61s1g/u6MRP7WzWD8KLyQ/AACAPwovJD8AAIA/u6MRP7WzWD8KLyQ/trNYP1fgWD8AAIA/V+BYPwAAgD8KLyQ/trNYP1fgWD+1s1g/AACAPwAAgD8AAIA/AACAP1fgWD8AAAAAVuBYPwAAAAAAAIA/JsEuPgAAgD8nwS4+VuBYPwAAAAAJLyQ/AAAAAFbgWD8nwS4+VuBYPyfBLj4JLyQ/AAAAALqjET8AAAAACS8kPyfBLj4JLyQ/J8EuPrqjET8AAAAAjDPFPgAAAAC6oxE/J8EuPrqjET8nwS4+jDPFPgAAAAD4caY+AAAAAIwzxT4nwS4+jDPFPifBLj74caY+AAAAADzhBT4AAAAA+HGmPifBLj74caY+J8EuPkDhBT4AAAAAAAAAAAAAAAA84QU+J8EuPkDhBT4owS4+AAAAACfBLj5W4Fg/JsEuPgAAgD+1s1g/AACAP7azWD9X4Fg/J8EuPrqjET8nwS4+CS8kP7WzWD8KLyQ/tbNYP7ujET8nwS4++HGmPifBLj6MM8U+trNYP5AzxT62s1g//HGmPijBLj4AAAAAJ8EuPkDhBT4AAAEAAgAAAAIAAwAEAAUABgAEAAYABwAIAAkACgAIAAoACwAMAA0ADgAMAA4ADwAQABEAEgAQABIAEwAUABUAFgAUABYAFwAYABkAGgAYABoAGwAcAB0AHgAcAB4AHwAgACEAIgAgACIAIwAkACUAJgAkACYAJwAoACkAKgAoACoAKwAsAC0ALgAsAC4ALwAwADEAMgAwADIAMwA0ADUANgA0ADYANwA4ADkAOgA4ADoAOwA8AD0APgA8AD4APwBAAEEAQgBAAEIAQwBEAEUAAQBEAAEAAAA="
256 | }
257 | ]
258 | }
259 |
--------------------------------------------------------------------------------
/test/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "moz_text_test",
3 | "version": "1.0.0",
4 | "description": "test for MOZ_text glTF extension",
5 | "main": "index.js",
6 | "directories": {
7 | "test": "tests"
8 | },
9 | "scripts": {
10 | "start": "webpack-dev-server -d --host 0.0.0.0",
11 | "build": "webpack -p",
12 | "watch": "webpack --watch",
13 | "test": "echo \"Error: no test specified\" && exit 1"
14 | },
15 | "repository": {},
16 | "keywords": [
17 | "gltf",
18 | "text",
19 | "extension",
20 | "threejs"
21 | ],
22 | "author": "Diego F. Goberna",
23 | "license": "MIT",
24 | "dependencies": {
25 | "@babel/core": "^7.9.0",
26 | "babel-loader": "^8.1.0",
27 | "dat.gui": "^0.7.7",
28 | "three": "^0.115.0",
29 | "troika-3d-text": "^0.22.0",
30 | "webpack": "^4.42.1",
31 | "webpack-dev-server": "^3.10.1"
32 | },
33 | "devDependencies": {
34 | "webpack-cli": "^3.3.11"
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/test/webpack.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | mode: 'development',
3 | entry: './index.js',
4 | output: {
5 | filename: 'bundle.js',
6 | path: __dirname
7 | },
8 | devtool: 'source-map',
9 | module: {
10 | rules: [
11 | {
12 | test: /\.(js|mjs)$/,
13 | exclude: /(node_modules)/,
14 | use: { loader: 'babel-loader' }
15 | }
16 | ]
17 | },
18 | watchOptions: {
19 | ignored: [/node_modules/],
20 | }
21 | };
22 |
--------------------------------------------------------------------------------
/threejs/README.md:
--------------------------------------------------------------------------------
1 |
2 | This is a modification to the GLTFLoader.js to support the MOZ_text
3 |
--------------------------------------------------------------------------------