You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							710 lines
						
					
					
						
							33 KiB
						
					
					
				
			
		
		
		
			
			
			
				
					
				
				
					
				
			
		
		
	
	
							710 lines
						
					
					
						
							33 KiB
						
					
					
				
								<!DOCTYPE html><html lang="en"><head>
							 | 
						|
								    <meta charset="utf-8">
							 | 
						|
								    <title>Loading a .GLTF File</title>
							 | 
						|
								    <meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
							 | 
						|
								    <meta name="twitter:card" content="summary_large_image">
							 | 
						|
								    <meta name="twitter:site" content="@threejs">
							 | 
						|
								    <meta name="twitter:title" content="Three.js – Loading a .GLTF File">
							 | 
						|
								    <meta property="og:image" content="https://threejs.org/files/share.png">
							 | 
						|
								    <link rel="shortcut icon" href="/files/favicon_white.ico" media="(prefers-color-scheme: dark)">
							 | 
						|
								    <link rel="shortcut icon" href="/files/favicon.ico" media="(prefers-color-scheme: light)">
							 | 
						|
								
							 | 
						|
								    <link rel="stylesheet" href="/manual/resources/lesson.css">
							 | 
						|
								    <link rel="stylesheet" href="/manual/resources/lang.css">
							 | 
						|
								<!-- Import maps polyfill -->
							 | 
						|
								<!-- Remove this when import maps will be widely supported -->
							 | 
						|
								<script async src="https://unpkg.com/es-module-shims@1.3.6/dist/es-module-shims.js"></script>
							 | 
						|
								
							 | 
						|
								<script type="importmap">
							 | 
						|
								{
							 | 
						|
								  "imports": {
							 | 
						|
								    "three": "../../build/three.module.js"
							 | 
						|
								  }
							 | 
						|
								}
							 | 
						|
								</script>
							 | 
						|
								  </head>
							 | 
						|
								  <body>
							 | 
						|
								    <div class="container">
							 | 
						|
								      <div class="lesson-title">
							 | 
						|
								        <h1>Loading a .GLTF File</h1>
							 | 
						|
								      </div>
							 | 
						|
								      <div class="lesson">
							 | 
						|
								        <div class="lesson-main">
							 | 
						|
								          <p>In a previous lesson we <a href="load-obj.html">loaded an .OBJ file</a>. If
							 | 
						|
								you haven't read it you might want to check it out first.</p>
							 | 
						|
								<p>As pointed out over there the .OBJ file format is very old and fairly
							 | 
						|
								simple. It provides no scene graph so everything loaded is one large
							 | 
						|
								mesh. It was designed mostly as a simple way to pass data between
							 | 
						|
								3D editors.</p>
							 | 
						|
								<p><a href="https://github.com/KhronosGroup/glTF">The gLTF format</a> is actually
							 | 
						|
								a format designed from the ground up for be used for displaying
							 | 
						|
								graphics. 3D formats can be divided into 3 or 4 basic types.</p>
							 | 
						|
								<ul>
							 | 
						|
								<li><p>3D Editor Formats</p>
							 | 
						|
								<p>This are formats specific to a single app. .blend (Blender), .max (3d Studio Max),
							 | 
						|
								.mb and .ma (Maya), etc...</p>
							 | 
						|
								</li>
							 | 
						|
								<li><p>Exchange formats</p>
							 | 
						|
								<p>These are formats like .OBJ, .DAE (Collada), .FBX. They are designed to help exchange
							 | 
						|
								information between 3D editors. As such they are usually much larger than needed with
							 | 
						|
								extra info used only inside 3d editors</p>
							 | 
						|
								</li>
							 | 
						|
								<li><p>App formats</p>
							 | 
						|
								<p>These are usually specific to certain apps, usually games.</p>
							 | 
						|
								</li>
							 | 
						|
								<li><p>Transmission formats</p>
							 | 
						|
								<p>gLTF might be the first true transmission format. I suppose VRML might be considered
							 | 
						|
								one but VRML was actually a pretty poor format.</p>
							 | 
						|
								<p>gLTF is designed to do some things well that all those other formats don't do</p>
							 | 
						|
								<ol>
							 | 
						|
								<li><p>Be small for transmission</p>
							 | 
						|
								<p>For example this means much of their large data, like vertices, is stored in
							 | 
						|
								binary. When you download a .gLTF file that data can be uploaded to the GPU
							 | 
						|
								with zero processing. It's ready as is. This is in contrast to say VRML, .OBJ,
							 | 
						|
								or .DAE where vertices are stored as text and have to be parsed. Text vertex
							 | 
						|
								positions can easily be 3x to 5x larger than binary.</p>
							 | 
						|
								</li>
							 | 
						|
								<li><p>Be ready to render</p>
							 | 
						|
								<p>This again is different from other formats except maybe App formats. The data
							 | 
						|
								in a glTF file is mean to be rendered, not edited. Data that's not important to
							 | 
						|
								rendering has generally been removed. Polygons have been converted to triangles.
							 | 
						|
								Materials have known values that are supposed to work everywhere.</p>
							 | 
						|
								</li>
							 | 
						|
								</ol>
							 | 
						|
								</li>
							 | 
						|
								</ul>
							 | 
						|
								<p>gLTF was specifically designed so you should be able to download a glTF file and
							 | 
						|
								display it with a minimum of trouble. Let's cross our fingers that's truly the case
							 | 
						|
								as none of the other formats have been able to do this.</p>
							 | 
						|
								<p>I wasn't really sure what I should show. At some level loading and displaying a gLTF file
							 | 
						|
								is simpler than an .OBJ file. Unlike a .OBJ file materials are directly part of the format.
							 | 
						|
								That said I thought I should at least load one up and I think going over the issues I ran 
							 | 
						|
								into might provide some good info.</p>
							 | 
						|
								<p>Searching the net I found <a href="https://sketchfab.com/models/edd1c604e1e045a0a2a552ddd9a293e6">this low-poly city</a>
							 | 
						|
								by <a href="https://sketchfab.com/antonmoek">antonmoek</a> which seemed like if we're lucky
							 | 
						|
								might make a good example.</p>
							 | 
						|
								<div class="threejs_center"><img src="../resources/images/cartoon_lowpoly_small_city_free_pack.jpg"></div>
							 | 
						|
								
							 | 
						|
								<p>Starting with <a href="load-obj.html">an example from the .OBJ article</a> I removed the code
							 | 
						|
								for loading .OBJ and replaced it with code for loading .GLTF</p>
							 | 
						|
								<p>The old .OBJ code was</p>
							 | 
						|
								<pre class="prettyprint showlinemods notranslate lang-js" translate="no">const mtlLoader = new MTLLoader();
							 | 
						|
								mtlLoader.loadMtl('resources/models/windmill/windmill-fixed.mtl', (mtl) => {
							 | 
						|
								  mtl.preload();
							 | 
						|
								  mtl.materials.Material.side = THREE.DoubleSide;
							 | 
						|
								  objLoader.setMaterials(mtl);
							 | 
						|
								  objLoader.load('resources/models/windmill/windmill.obj', (event) => {
							 | 
						|
								    const root = event.detail.loaderRootNode;
							 | 
						|
								    scene.add(root);
							 | 
						|
								    ...
							 | 
						|
								  });
							 | 
						|
								});
							 | 
						|
								</pre>
							 | 
						|
								<p>The new .GLTF code is</p>
							 | 
						|
								<pre class="prettyprint showlinemods notranslate lang-js" translate="no">{
							 | 
						|
								  const gltfLoader = new GLTFLoader();
							 | 
						|
								  const url = 'resources/models/cartoon_lowpoly_small_city_free_pack/scene.gltf';
							 | 
						|
								  gltfLoader.load(url, (gltf) => {
							 | 
						|
								    const root = gltf.scene;
							 | 
						|
								    scene.add(root);
							 | 
						|
								    ...
							 | 
						|
								  });
							 | 
						|
								</pre>
							 | 
						|
								<p>I kept the auto framing code as before</p>
							 | 
						|
								<p>We also need to include the <a href="/docs/#examples/loaders/GLTFLoader"><code class="notranslate" translate="no">GLTFLoader</code></a> and we can get rid of the <a href="/docs/#examples/loaders/OBJLoader"><code class="notranslate" translate="no">OBJLoader</code></a>.</p>
							 | 
						|
								<pre class="prettyprint showlinemods notranslate lang-html" translate="no">-import {LoaderSupport} from 'three/addons/loaders/LoaderSupport.js';
							 | 
						|
								-import {OBJLoader} from 'three/addons/loaders/OBJLoader.js';
							 | 
						|
								-import {MTLLoader} from 'three/addons/loaders/MTLLoader.js';
							 | 
						|
								+import {GLTFLoader} from 'three/addons/loaders/GLTFLoader.js';
							 | 
						|
								</pre>
							 | 
						|
								<p>And running that we get</p>
							 | 
						|
								<p></p><div translate="no" class="threejs_example_container notranslate">
							 | 
						|
								  <div><iframe class="threejs_example notranslate" translate="no" style=" " src="/manual/examples/resources/editor.html?url=/manual/examples/load-gltf.html"></iframe></div>
							 | 
						|
								  <a class="threejs_center" href="/manual/examples/load-gltf.html" target="_blank">click here to open in a separate window</a>
							 | 
						|
								</div>
							 | 
						|
								
							 | 
						|
								<p></p>
							 | 
						|
								<p>Magic! It just works, textures and all.</p>
							 | 
						|
								<p>Next I wanted to see if I could animate the cars driving around so
							 | 
						|
								I needed to check if the scene had the cars as separate entities
							 | 
						|
								and if they were setup in a way I could use them.</p>
							 | 
						|
								<p>I wrote some code to dump put the scenegraph to the <a href="debugging-javascript.html">JavaScript
							 | 
						|
								console</a>.</p>
							 | 
						|
								<p>Here's the code to print out the scenegraph.</p>
							 | 
						|
								<pre class="prettyprint showlinemods notranslate lang-js" translate="no">function dumpObject(obj, lines = [], isLast = true, prefix = '') {
							 | 
						|
								  const localPrefix = isLast ? '└─' : '├─';
							 | 
						|
								  lines.push(`${prefix}${prefix ? localPrefix : ''}${obj.name || '*no-name*'} [${obj.type}]`);
							 | 
						|
								  const newPrefix = prefix + (isLast ? '  ' : '│ ');
							 | 
						|
								  const lastNdx = obj.children.length - 1;
							 | 
						|
								  obj.children.forEach((child, ndx) => {
							 | 
						|
								    const isLast = ndx === lastNdx;
							 | 
						|
								    dumpObject(child, lines, isLast, newPrefix);
							 | 
						|
								  });
							 | 
						|
								  return lines;
							 | 
						|
								}
							 | 
						|
								</pre>
							 | 
						|
								<p>And I just called it right after loading the scene.</p>
							 | 
						|
								<pre class="prettyprint showlinemods notranslate lang-js" translate="no">const gltfLoader = new GLTFLoader();
							 | 
						|
								gltfLoader.load('resources/models/cartoon_lowpoly_small_city_free_pack/scene.gltf', (gltf) => {
							 | 
						|
								  const root = gltf.scene;
							 | 
						|
								  scene.add(root);
							 | 
						|
								  console.log(dumpObject(root).join('\n'));
							 | 
						|
								</pre>
							 | 
						|
								<p><a href="../examples/load-gltf-dump-scenegraph.html">Running that</a> I got this listing</p>
							 | 
						|
								<pre class="prettyprint showlinemods notranslate lang-text" translate="no">OSG_Scene [Scene]
							 | 
						|
								  └─RootNode_(gltf_orientation_matrix) [Object3D]
							 | 
						|
								    └─RootNode_(model_correction_matrix) [Object3D]
							 | 
						|
								      └─4d4100bcb1c640e69699a87140df79d7fbx [Object3D]
							 | 
						|
								        └─RootNode [Object3D]
							 | 
						|
								          │ ...
							 | 
						|
								          ├─Cars [Object3D]
							 | 
						|
								          │ ├─CAR_03_1 [Object3D]
							 | 
						|
								          │ │ └─CAR_03_1_World_ap_0 [Mesh]
							 | 
						|
								          │ ├─CAR_03 [Object3D]
							 | 
						|
								          │ │ └─CAR_03_World_ap_0 [Mesh]
							 | 
						|
								          │ ├─Car_04 [Object3D]
							 | 
						|
								          │ │ └─Car_04_World_ap_0 [Mesh]
							 | 
						|
								          │ ├─CAR_03_2 [Object3D]
							 | 
						|
								          │ │ └─CAR_03_2_World_ap_0 [Mesh]
							 | 
						|
								          │ ├─Car_04_1 [Object3D]
							 | 
						|
								          │ │ └─Car_04_1_World_ap_0 [Mesh]
							 | 
						|
								          │ ├─Car_04_2 [Object3D]
							 | 
						|
								          │ │ └─Car_04_2_World_ap_0 [Mesh]
							 | 
						|
								          │ ├─Car_04_3 [Object3D]
							 | 
						|
								          │ │ └─Car_04_3_World_ap_0 [Mesh]
							 | 
						|
								          │ ├─Car_04_4 [Object3D]
							 | 
						|
								          │ │ └─Car_04_4_World_ap_0 [Mesh]
							 | 
						|
								          │ ├─Car_08_4 [Object3D]
							 | 
						|
								          │ │ └─Car_08_4_World_ap8_0 [Mesh]
							 | 
						|
								          │ ├─Car_08_3 [Object3D]
							 | 
						|
								          │ │ └─Car_08_3_World_ap9_0 [Mesh]
							 | 
						|
								          │ ├─Car_04_1_2 [Object3D]
							 | 
						|
								          │ │ └─Car_04_1_2_World_ap_0 [Mesh]
							 | 
						|
								          │ ├─Car_08_2 [Object3D]
							 | 
						|
								          │ │ └─Car_08_2_World_ap11_0 [Mesh]
							 | 
						|
								          │ ├─CAR_03_1_2 [Object3D]
							 | 
						|
								          │ │ └─CAR_03_1_2_World_ap_0 [Mesh]
							 | 
						|
								          │ ├─CAR_03_2_2 [Object3D]
							 | 
						|
								          │ │ └─CAR_03_2_2_World_ap_0 [Mesh]
							 | 
						|
								          │ ├─Car_04_2_2 [Object3D]
							 | 
						|
								          │ │ └─Car_04_2_2_World_ap_0 [Mesh]
							 | 
						|
								          ...
							 | 
						|
								</pre>
							 | 
						|
								<p>From that we can see all the cars happen to be under a parent
							 | 
						|
								called <code class="notranslate" translate="no">"Cars"</code></p>
							 | 
						|
								<pre class="prettyprint showlinemods notranslate lang-text" translate="no">*          ├─Cars [Object3D]
							 | 
						|
								          │ ├─CAR_03_1 [Object3D]
							 | 
						|
								          │ │ └─CAR_03_1_World_ap_0 [Mesh]
							 | 
						|
								          │ ├─CAR_03 [Object3D]
							 | 
						|
								          │ │ └─CAR_03_World_ap_0 [Mesh]
							 | 
						|
								          │ ├─Car_04 [Object3D]
							 | 
						|
								          │ │ └─Car_04_World_ap_0 [Mesh]
							 | 
						|
								</pre>
							 | 
						|
								<p>So as a simple test I thought I would just try rotating
							 | 
						|
								all the children of the "Cars" node around their Y axis.</p>
							 | 
						|
								<p>I looked up the "Cars" node after loading the scene
							 | 
						|
								and saved the result.</p>
							 | 
						|
								<pre class="prettyprint showlinemods notranslate lang-js" translate="no">+let cars;
							 | 
						|
								{
							 | 
						|
								  const gltfLoader = new GLTFLoader();
							 | 
						|
								  gltfLoader.load('resources/models/cartoon_lowpoly_small_city_free_pack/scene.gltf', (gltf) => {
							 | 
						|
								    const root = gltf.scene;
							 | 
						|
								    scene.add(root);
							 | 
						|
								+    cars = root.getObjectByName('Cars');
							 | 
						|
								</pre>
							 | 
						|
								<p>Then in the <code class="notranslate" translate="no">render</code> function we can just set the rotation
							 | 
						|
								of each child of <code class="notranslate" translate="no">cars</code>.</p>
							 | 
						|
								<pre class="prettyprint showlinemods notranslate lang-js" translate="no">+function render(time) {
							 | 
						|
								+  time *= 0.001;  // convert to seconds
							 | 
						|
								
							 | 
						|
								  if (resizeRendererToDisplaySize(renderer)) {
							 | 
						|
								    const canvas = renderer.domElement;
							 | 
						|
								    camera.aspect = canvas.clientWidth / canvas.clientHeight;
							 | 
						|
								    camera.updateProjectionMatrix();
							 | 
						|
								  }
							 | 
						|
								
							 | 
						|
								+  if (cars) {
							 | 
						|
								+    for (const car of cars.children) {
							 | 
						|
								+      car.rotation.y = time;
							 | 
						|
								+    }
							 | 
						|
								+  }
							 | 
						|
								
							 | 
						|
								  renderer.render(scene, camera);
							 | 
						|
								
							 | 
						|
								  requestAnimationFrame(render);
							 | 
						|
								}
							 | 
						|
								</pre>
							 | 
						|
								<p>And we get</p>
							 | 
						|
								<p></p><div translate="no" class="threejs_example_container notranslate">
							 | 
						|
								  <div><iframe class="threejs_example notranslate" translate="no" style=" " src="/manual/examples/resources/editor.html?url=/manual/examples/load-gltf-rotate-cars.html"></iframe></div>
							 | 
						|
								  <a class="threejs_center" href="/manual/examples/load-gltf-rotate-cars.html" target="_blank">click here to open in a separate window</a>
							 | 
						|
								</div>
							 | 
						|
								
							 | 
						|
								<p></p>
							 | 
						|
								<p>Hmmm, it looks like unfortunately this scene wasn't designed to
							 | 
						|
								animate the cars as their origins are not setup for that purpose.
							 | 
						|
								The trucks are rotating in the wrong direction.</p>
							 | 
						|
								<p>This brings up an important point which is if you're going to
							 | 
						|
								do something in 3D you need to plan ahead and design your assets
							 | 
						|
								so they have their origins in the correct places, so they are
							 | 
						|
								the correct scale, etc.</p>
							 | 
						|
								<p>Since I'm not an artist and I don't know blender that well I
							 | 
						|
								will hack this example. We'll take each car and parent it to
							 | 
						|
								another <a href="/docs/#api/en/core/Object3D"><code class="notranslate" translate="no">Object3D</code></a>. We will then move those <a href="/docs/#api/en/core/Object3D"><code class="notranslate" translate="no">Object3D</code></a> objects
							 | 
						|
								to move the cars but separately we can set the car's original
							 | 
						|
								<a href="/docs/#api/en/core/Object3D"><code class="notranslate" translate="no">Object3D</code></a> to re-orient it so it's about where we really need it.</p>
							 | 
						|
								<p>Looking back at the scene graph listing it looks like there
							 | 
						|
								are really only 3 types of cars, "Car_08", "CAR_03", and "Car_04".
							 | 
						|
								Hopefully each type of car will work with the same adjustments.</p>
							 | 
						|
								<p>I wrote this code to go through each car, parent it to a new
							 | 
						|
								<a href="/docs/#api/en/core/Object3D"><code class="notranslate" translate="no">Object3D</code></a>, parent that new <a href="/docs/#api/en/core/Object3D"><code class="notranslate" translate="no">Object3D</code></a> to the scene, and apply
							 | 
						|
								some per car <em>type</em> settings to fix its orientation, and add
							 | 
						|
								the new <a href="/docs/#api/en/core/Object3D"><code class="notranslate" translate="no">Object3D</code></a> a <code class="notranslate" translate="no">cars</code> array.</p>
							 | 
						|
								<pre class="prettyprint showlinemods notranslate lang-js" translate="no">-let cars;
							 | 
						|
								+const cars = [];
							 | 
						|
								{
							 | 
						|
								  const gltfLoader = new GLTFLoader();
							 | 
						|
								  gltfLoader.load('resources/models/cartoon_lowpoly_small_city_free_pack/scene.gltf', (gltf) => {
							 | 
						|
								    const root = gltf.scene;
							 | 
						|
								    scene.add(root);
							 | 
						|
								
							 | 
						|
								-    cars = root.getObjectByName('Cars');
							 | 
						|
								+    const loadedCars = root.getObjectByName('Cars');
							 | 
						|
								+    const fixes = [
							 | 
						|
								+      { prefix: 'Car_08', rot: [Math.PI * .5, 0, Math.PI * .5], },
							 | 
						|
								+      { prefix: 'CAR_03', rot: [0, Math.PI, 0], },
							 | 
						|
								+      { prefix: 'Car_04', rot: [0, Math.PI, 0], },
							 | 
						|
								+    ];
							 | 
						|
								+
							 | 
						|
								+    root.updateMatrixWorld();
							 | 
						|
								+    for (const car of loadedCars.children.slice()) {
							 | 
						|
								+      const fix = fixes.find(fix => car.name.startsWith(fix.prefix));
							 | 
						|
								+      const obj = new THREE.Object3D();
							 | 
						|
								+      car.getWorldPosition(obj.position);
							 | 
						|
								+      car.position.set(0, 0, 0);
							 | 
						|
								+      car.rotation.set(...fix.rot);
							 | 
						|
								+      obj.add(car);
							 | 
						|
								+      scene.add(obj);
							 | 
						|
								+      cars.push(obj);
							 | 
						|
								+    }
							 | 
						|
								     ...
							 | 
						|
								</pre>
							 | 
						|
								<p>This fixes the orientation of the cars. </p>
							 | 
						|
								<p></p><div translate="no" class="threejs_example_container notranslate">
							 | 
						|
								  <div><iframe class="threejs_example notranslate" translate="no" style=" " src="/manual/examples/resources/editor.html?url=/manual/examples/load-gltf-rotate-cars-fixed.html"></iframe></div>
							 | 
						|
								  <a class="threejs_center" href="/manual/examples/load-gltf-rotate-cars-fixed.html" target="_blank">click here to open in a separate window</a>
							 | 
						|
								</div>
							 | 
						|
								
							 | 
						|
								<p></p>
							 | 
						|
								<p>Now let's drive them around.</p>
							 | 
						|
								<p>Making even a simple driving system is too much for this post but
							 | 
						|
								it seems instead we could just make one convoluted path that
							 | 
						|
								drives down all the roads and then put the cars on the path.
							 | 
						|
								Here's a picture from Blender about half way through building
							 | 
						|
								the path.</p>
							 | 
						|
								<div class="threejs_center"><img src="../resources/images/making-path-for-cars.jpg" style="width: 1094px"></div>
							 | 
						|
								
							 | 
						|
								<p>I needed a way to get the data for that path out of Blender.
							 | 
						|
								Fortunately I was able to select just my path and export .OBJ checking "write nurbs".</p>
							 | 
						|
								<div class="threejs_center"><img src="../resources/images/blender-export-obj-write-nurbs.jpg" style="width: 498px"></div>
							 | 
						|
								
							 | 
						|
								<p>Opening the .OBJ file I was able to get a list of points
							 | 
						|
								which I formatted into this</p>
							 | 
						|
								<pre class="prettyprint showlinemods notranslate lang-js" translate="no">const controlPoints = [
							 | 
						|
								  [1.118281, 5.115846, -3.681386],
							 | 
						|
								  [3.948875, 5.115846, -3.641834],
							 | 
						|
								  [3.960072, 5.115846, -0.240352],
							 | 
						|
								  [3.985447, 5.115846, 4.585005],
							 | 
						|
								  [-3.793631, 5.115846, 4.585006],
							 | 
						|
								  [-3.826839, 5.115846, -14.736200],
							 | 
						|
								  [-14.542292, 5.115846, -14.765865],
							 | 
						|
								  [-14.520929, 5.115846, -3.627002],
							 | 
						|
								  [-5.452815, 5.115846, -3.634418],
							 | 
						|
								  [-5.467251, 5.115846, 4.549161],
							 | 
						|
								  [-13.266233, 5.115846, 4.567083],
							 | 
						|
								  [-13.250067, 5.115846, -13.499271],
							 | 
						|
								  [4.081842, 5.115846, -13.435463],
							 | 
						|
								  [4.125436, 5.115846, -5.334928],
							 | 
						|
								  [-14.521364, 5.115846, -5.239871],
							 | 
						|
								  [-14.510466, 5.115846, 5.486727],
							 | 
						|
								  [5.745666, 5.115846, 5.510492],
							 | 
						|
								  [5.787942, 5.115846, -14.728308],
							 | 
						|
								  [-5.423720, 5.115846, -14.761919],
							 | 
						|
								  [-5.373599, 5.115846, -3.704133],
							 | 
						|
								  [1.004861, 5.115846, -3.641834],
							 | 
						|
								];
							 | 
						|
								</pre>
							 | 
						|
								<p>THREE.js has some curve classes. The <a href="/docs/#api/en/extras/curves/CatmullRomCurve3"><code class="notranslate" translate="no">CatmullRomCurve3</code></a> seemed
							 | 
						|
								like it might work. The thing about that kind of curve is
							 | 
						|
								it tries to make a smooth curve going through the points.</p>
							 | 
						|
								<p>In fact putting those points in directly will generate
							 | 
						|
								a curve like this</p>
							 | 
						|
								<div class="threejs_center"><img src="../resources/images/car-curves-before.png" style="width: 400px"></div>
							 | 
						|
								
							 | 
						|
								<p>but we want a sharper corners. It seemed like if we computed
							 | 
						|
								some extra points we could get what we want. For each pair
							 | 
						|
								of points we'll compute a point 10% of the way between
							 | 
						|
								the 2 points and another 90% of the way between the 2 points
							 | 
						|
								and pass the result to <a href="/docs/#api/en/extras/curves/CatmullRomCurve3"><code class="notranslate" translate="no">CatmullRomCurve3</code></a>.</p>
							 | 
						|
								<p>This will give us a curve like this</p>
							 | 
						|
								<div class="threejs_center"><img src="../resources/images/car-curves-after.png" style="width: 400px"></div>
							 | 
						|
								
							 | 
						|
								<p>Here's the code to make the curve </p>
							 | 
						|
								<pre class="prettyprint showlinemods notranslate lang-js" translate="no">let curve;
							 | 
						|
								let curveObject;
							 | 
						|
								{
							 | 
						|
								  const controlPoints = [
							 | 
						|
								    [1.118281, 5.115846, -3.681386],
							 | 
						|
								    [3.948875, 5.115846, -3.641834],
							 | 
						|
								    [3.960072, 5.115846, -0.240352],
							 | 
						|
								    [3.985447, 5.115846, 4.585005],
							 | 
						|
								    [-3.793631, 5.115846, 4.585006],
							 | 
						|
								    [-3.826839, 5.115846, -14.736200],
							 | 
						|
								    [-14.542292, 5.115846, -14.765865],
							 | 
						|
								    [-14.520929, 5.115846, -3.627002],
							 | 
						|
								    [-5.452815, 5.115846, -3.634418],
							 | 
						|
								    [-5.467251, 5.115846, 4.549161],
							 | 
						|
								    [-13.266233, 5.115846, 4.567083],
							 | 
						|
								    [-13.250067, 5.115846, -13.499271],
							 | 
						|
								    [4.081842, 5.115846, -13.435463],
							 | 
						|
								    [4.125436, 5.115846, -5.334928],
							 | 
						|
								    [-14.521364, 5.115846, -5.239871],
							 | 
						|
								    [-14.510466, 5.115846, 5.486727],
							 | 
						|
								    [5.745666, 5.115846, 5.510492],
							 | 
						|
								    [5.787942, 5.115846, -14.728308],
							 | 
						|
								    [-5.423720, 5.115846, -14.761919],
							 | 
						|
								    [-5.373599, 5.115846, -3.704133],
							 | 
						|
								    [1.004861, 5.115846, -3.641834],
							 | 
						|
								  ];
							 | 
						|
								  const p0 = new THREE.Vector3();
							 | 
						|
								  const p1 = new THREE.Vector3();
							 | 
						|
								  curve = new THREE.CatmullRomCurve3(
							 | 
						|
								    controlPoints.map((p, ndx) => {
							 | 
						|
								      p0.set(...p);
							 | 
						|
								      p1.set(...controlPoints[(ndx + 1) % controlPoints.length]);
							 | 
						|
								      return [
							 | 
						|
								        (new THREE.Vector3()).copy(p0),
							 | 
						|
								        (new THREE.Vector3()).lerpVectors(p0, p1, 0.1),
							 | 
						|
								        (new THREE.Vector3()).lerpVectors(p0, p1, 0.9),
							 | 
						|
								      ];
							 | 
						|
								    }).flat(),
							 | 
						|
								    true,
							 | 
						|
								  );
							 | 
						|
								  {
							 | 
						|
								    const points = curve.getPoints(250);
							 | 
						|
								    const geometry = new THREE.BufferGeometry().setFromPoints(points);
							 | 
						|
								    const material = new THREE.LineBasicMaterial({color: 0xff0000});
							 | 
						|
								    curveObject = new THREE.Line(geometry, material);
							 | 
						|
								    scene.add(curveObject);
							 | 
						|
								  }
							 | 
						|
								}
							 | 
						|
								</pre>
							 | 
						|
								<p>The first part of that code makes a curve.
							 | 
						|
								The second part of that code generates 250 points
							 | 
						|
								from the curve and then creates an object to display
							 | 
						|
								the lines made by connecting those 250 points.</p>
							 | 
						|
								<p>Running <a href="../examples/load-gltf-car-path.html">the example</a> I didn't see
							 | 
						|
								the curve. To make it visible I made it ignore the depth test and
							 | 
						|
								render last</p>
							 | 
						|
								<pre class="prettyprint showlinemods notranslate lang-js" translate="no">    curveObject = new THREE.Line(geometry, material);
							 | 
						|
								+    material.depthTest = false;
							 | 
						|
								+    curveObject.renderOrder = 1;
							 | 
						|
								</pre>
							 | 
						|
								<p>And that's when I discovered it was way too small.</p>
							 | 
						|
								<div class="threejs_center"><img src="../resources/images/car-curves-too-small.png" style="width: 498px"></div>
							 | 
						|
								
							 | 
						|
								<p>Checking the hierarchy in Blender I found out that the artist had
							 | 
						|
								scaled the node all the cars are parented to.</p>
							 | 
						|
								<div class="threejs_center"><img src="../resources/images/cars-scale-0.01.png" style="width: 342px;"></div>
							 | 
						|
								
							 | 
						|
								<p>Scaling is bad for real time 3D apps. It causes all kinds of
							 | 
						|
								issues and ends up being no end of frustration when doing
							 | 
						|
								real time 3D. Artists often don't know this because it's so
							 | 
						|
								easy to scale an entire scene in a 3D editing program but
							 | 
						|
								if you decide to make a real time 3D app I suggest you request your
							 | 
						|
								artists to never scale anything. If they change the scale
							 | 
						|
								they should find a way to apply that scale to the vertices
							 | 
						|
								so that when it ends up making it to your app you can ignore
							 | 
						|
								scale.</p>
							 | 
						|
								<p>And, not just scale, in this case the cars are rotated and offset
							 | 
						|
								by their parent, the <code class="notranslate" translate="no">Cars</code> node. This will make it hard at runtime
							 | 
						|
								to move the cars around in world space. To be clear, in this case
							 | 
						|
								we want cars to drive around in world space which is why these
							 | 
						|
								issues are coming up. If something that is meant to be manipulated
							 | 
						|
								in a local space, like the moon revolving around the earth this
							 | 
						|
								is less of an issue.</p>
							 | 
						|
								<p>Going back to the function we wrote above to dump the scene graph,
							 | 
						|
								let's dump the position, rotation, and scale of each node.</p>
							 | 
						|
								<pre class="prettyprint showlinemods notranslate lang-js" translate="no">+function dumpVec3(v3, precision = 3) {
							 | 
						|
								+  return `${v3.x.toFixed(precision)}, ${v3.y.toFixed(precision)}, ${v3.z.toFixed(precision)}`;
							 | 
						|
								+}
							 | 
						|
								
							 | 
						|
								function dumpObject(obj, lines, isLast = true, prefix = '') {
							 | 
						|
								  const localPrefix = isLast ? '└─' : '├─';
							 | 
						|
								  lines.push(`${prefix}${prefix ? localPrefix : ''}${obj.name || '*no-name*'} [${obj.type}]`);
							 | 
						|
								+  const dataPrefix = obj.children.length
							 | 
						|
								+     ? (isLast ? '  │ ' : '│ │ ')
							 | 
						|
								+     : (isLast ? '    ' : '│   ');
							 | 
						|
								+  lines.push(`${prefix}${dataPrefix}  pos: ${dumpVec3(obj.position)}`);
							 | 
						|
								+  lines.push(`${prefix}${dataPrefix}  rot: ${dumpVec3(obj.rotation)}`);
							 | 
						|
								+  lines.push(`${prefix}${dataPrefix}  scl: ${dumpVec3(obj.scale)}`);
							 | 
						|
								  const newPrefix = prefix + (isLast ? '  ' : '│ ');
							 | 
						|
								  const lastNdx = obj.children.length - 1;
							 | 
						|
								  obj.children.forEach((child, ndx) => {
							 | 
						|
								    const isLast = ndx === lastNdx;
							 | 
						|
								    dumpObject(child, lines, isLast, newPrefix);
							 | 
						|
								  });
							 | 
						|
								  return lines;
							 | 
						|
								}
							 | 
						|
								</pre>
							 | 
						|
								<p>And the result from <a href="../examples/load-gltf-dump-scenegraph-extra.html">running it</a></p>
							 | 
						|
								<pre class="prettyprint showlinemods notranslate lang-text" translate="no">OSG_Scene [Scene]
							 | 
						|
								  │   pos: 0.000, 0.000, 0.000
							 | 
						|
								  │   rot: 0.000, 0.000, 0.000
							 | 
						|
								  │   scl: 1.000, 1.000, 1.000
							 | 
						|
								  └─RootNode_(gltf_orientation_matrix) [Object3D]
							 | 
						|
								    │   pos: 0.000, 0.000, 0.000
							 | 
						|
								    │   rot: -1.571, 0.000, 0.000
							 | 
						|
								    │   scl: 1.000, 1.000, 1.000
							 | 
						|
								    └─RootNode_(model_correction_matrix) [Object3D]
							 | 
						|
								      │   pos: 0.000, 0.000, 0.000
							 | 
						|
								      │   rot: 0.000, 0.000, 0.000
							 | 
						|
								      │   scl: 1.000, 1.000, 1.000
							 | 
						|
								      └─4d4100bcb1c640e69699a87140df79d7fbx [Object3D]
							 | 
						|
								        │   pos: 0.000, 0.000, 0.000
							 | 
						|
								        │   rot: 1.571, 0.000, 0.000
							 | 
						|
								        │   scl: 1.000, 1.000, 1.000
							 | 
						|
								        └─RootNode [Object3D]
							 | 
						|
								          │   pos: 0.000, 0.000, 0.000
							 | 
						|
								          │   rot: 0.000, 0.000, 0.000
							 | 
						|
								          │   scl: 1.000, 1.000, 1.000
							 | 
						|
								          ├─Cars [Object3D]
							 | 
						|
								*          │ │   pos: -369.069, -90.704, -920.159
							 | 
						|
								*          │ │   rot: 0.000, 0.000, 0.000
							 | 
						|
								*          │ │   scl: 1.000, 1.000, 1.000
							 | 
						|
								          │ ├─CAR_03_1 [Object3D]
							 | 
						|
								          │ │ │   pos: 22.131, 14.663, -475.071
							 | 
						|
								          │ │ │   rot: -3.142, 0.732, 3.142
							 | 
						|
								          │ │ │   scl: 1.500, 1.500, 1.500
							 | 
						|
								          │ │ └─CAR_03_1_World_ap_0 [Mesh]
							 | 
						|
								          │ │       pos: 0.000, 0.000, 0.000
							 | 
						|
								          │ │       rot: 0.000, 0.000, 0.000
							 | 
						|
								          │ │       scl: 1.000, 1.000, 1.000
							 | 
						|
								</pre>
							 | 
						|
								<p>This shows us that <code class="notranslate" translate="no">Cars</code> in the original scene has had its rotation and scale
							 | 
						|
								removed and applied to its children. That suggests either whatever exporter was
							 | 
						|
								used to create the .GLTF file did some special work here or more likely the
							 | 
						|
								artist exported a different version of the file than the corresponding .blend
							 | 
						|
								file, which is why things don't match.</p>
							 | 
						|
								<p>The moral of that is I should have probably downloaded the .blend
							 | 
						|
								file and exported myself. Before exporting I should have inspected
							 | 
						|
								all the major nodes and removed any transformations.</p>
							 | 
						|
								<p>All these nodes at the top</p>
							 | 
						|
								<pre class="prettyprint showlinemods notranslate lang-text" translate="no">OSG_Scene [Scene]
							 | 
						|
								  │   pos: 0.000, 0.000, 0.000
							 | 
						|
								  │   rot: 0.000, 0.000, 0.000
							 | 
						|
								  │   scl: 1.000, 1.000, 1.000
							 | 
						|
								  └─RootNode_(gltf_orientation_matrix) [Object3D]
							 | 
						|
								    │   pos: 0.000, 0.000, 0.000
							 | 
						|
								    │   rot: -1.571, 0.000, 0.000
							 | 
						|
								    │   scl: 1.000, 1.000, 1.000
							 | 
						|
								    └─RootNode_(model_correction_matrix) [Object3D]
							 | 
						|
								      │   pos: 0.000, 0.000, 0.000
							 | 
						|
								      │   rot: 0.000, 0.000, 0.000
							 | 
						|
								      │   scl: 1.000, 1.000, 1.000
							 | 
						|
								      └─4d4100bcb1c640e69699a87140df79d7fbx [Object3D]
							 | 
						|
								        │   pos: 0.000, 0.000, 0.000
							 | 
						|
								        │   rot: 1.571, 0.000, 0.000
							 | 
						|
								        │   scl: 1.000, 1.000, 1.000
							 | 
						|
								</pre>
							 | 
						|
								<p>are also a waste.</p>
							 | 
						|
								<p>Ideally the scene would consist of a single "root" node with no position,
							 | 
						|
								rotation, or scale. At runtime I could then pull all the children out of that
							 | 
						|
								root and parent them to the scene itself. There might be children of the root
							 | 
						|
								like "Cars" which would help me find all the cars but ideally it would also have
							 | 
						|
								no translation, rotation, or scale so I could re-parent the cars to the scene
							 | 
						|
								with the minimal amount of work.</p>
							 | 
						|
								<p>In any case the quickest though maybe not the best fix is to just
							 | 
						|
								adjust the object we're using to view the curve.</p>
							 | 
						|
								<p>Here's what I ended up with.</p>
							 | 
						|
								<p>First I adjusted the position of the curve and found values
							 | 
						|
								that seemed to work. I then hid it.</p>
							 | 
						|
								<pre class="prettyprint showlinemods notranslate lang-js" translate="no">{
							 | 
						|
								  const points = curve.getPoints(250);
							 | 
						|
								  const geometry = new THREE.BufferGeometry().setFromPoints(points);
							 | 
						|
								  const material = new THREE.LineBasicMaterial({color: 0xff0000});
							 | 
						|
								  curveObject = new THREE.Line(geometry, material);
							 | 
						|
								+  curveObject.scale.set(100, 100, 100);
							 | 
						|
								+  curveObject.position.y = -621;
							 | 
						|
								+  curveObject.visible = false;
							 | 
						|
								  material.depthTest = false;
							 | 
						|
								  curveObject.renderOrder = 1;
							 | 
						|
								  scene.add(curveObject);
							 | 
						|
								}
							 | 
						|
								</pre>
							 | 
						|
								<p>Then I wrote code to move the cars along the curve. For each car we pick a
							 | 
						|
								position from 0 to 1 along the curve and compute a point in world space using
							 | 
						|
								the <code class="notranslate" translate="no">curveObject</code> to transform the point. We then pick another point slightly
							 | 
						|
								further down the curve. We set the car's orientation using <code class="notranslate" translate="no">lookAt</code> and put the
							 | 
						|
								car at the mid point between the 2 points.</p>
							 | 
						|
								<pre class="prettyprint showlinemods notranslate lang-js" translate="no">// create 2 Vector3s we can use for path calculations
							 | 
						|
								const carPosition = new THREE.Vector3();
							 | 
						|
								const carTarget = new THREE.Vector3();
							 | 
						|
								
							 | 
						|
								function render(time) {
							 | 
						|
								  ...
							 | 
						|
								
							 | 
						|
								-  for (const car of cars) {
							 | 
						|
								-    car.rotation.y = time;
							 | 
						|
								-  }
							 | 
						|
								
							 | 
						|
								+  {
							 | 
						|
								+    const pathTime = time * .01;
							 | 
						|
								+    const targetOffset = 0.01;
							 | 
						|
								+    cars.forEach((car, ndx) => {
							 | 
						|
								+      // a number between 0 and 1 to evenly space the cars
							 | 
						|
								+      const u = pathTime + ndx / cars.length;
							 | 
						|
								+
							 | 
						|
								+      // get the first point
							 | 
						|
								+      curve.getPointAt(u % 1, carPosition);
							 | 
						|
								+      carPosition.applyMatrix4(curveObject.matrixWorld);
							 | 
						|
								+
							 | 
						|
								+      // get a second point slightly further down the curve
							 | 
						|
								+      curve.getPointAt((u + targetOffset) % 1, carTarget);
							 | 
						|
								+      carTarget.applyMatrix4(curveObject.matrixWorld);
							 | 
						|
								+
							 | 
						|
								+      // put the car at the first point (temporarily)
							 | 
						|
								+      car.position.copy(carPosition);
							 | 
						|
								+      // point the car the second point
							 | 
						|
								+      car.lookAt(carTarget);
							 | 
						|
								+
							 | 
						|
								+      // put the car between the 2 points
							 | 
						|
								+      car.position.lerpVectors(carPosition, carTarget, 0.5);
							 | 
						|
								+    });
							 | 
						|
								+  }
							 | 
						|
								</pre>
							 | 
						|
								<p>and when I ran it I found out for each type of car, their height above their origins
							 | 
						|
								are not consistently set and so I needed to offset each one
							 | 
						|
								a little.</p>
							 | 
						|
								<pre class="prettyprint showlinemods notranslate lang-js" translate="no">const loadedCars = root.getObjectByName('Cars');
							 | 
						|
								const fixes = [
							 | 
						|
								-  { prefix: 'Car_08', rot: [Math.PI * .5, 0, Math.PI * .5], },
							 | 
						|
								-  { prefix: 'CAR_03', rot: [0, Math.PI, 0], },
							 | 
						|
								-  { prefix: 'Car_04', rot: [0, Math.PI, 0], },
							 | 
						|
								+  { prefix: 'Car_08', y: 0,  rot: [Math.PI * .5, 0, Math.PI * .5], },
							 | 
						|
								+  { prefix: 'CAR_03', y: 33, rot: [0, Math.PI, 0], },
							 | 
						|
								+  { prefix: 'Car_04', y: 40, rot: [0, Math.PI, 0], },
							 | 
						|
								];
							 | 
						|
								
							 | 
						|
								root.updateMatrixWorld();
							 | 
						|
								for (const car of loadedCars.children.slice()) {
							 | 
						|
								  const fix = fixes.find(fix => car.name.startsWith(fix.prefix));
							 | 
						|
								  const obj = new THREE.Object3D();
							 | 
						|
								  car.getWorldPosition(obj.position);
							 | 
						|
								-  car.position.set(0, 0, 0);
							 | 
						|
								+  car.position.set(0, fix.y, 0);
							 | 
						|
								  car.rotation.set(...fix.rot);
							 | 
						|
								  obj.add(car);
							 | 
						|
								  scene.add(obj);
							 | 
						|
								  cars.push(obj);
							 | 
						|
								}
							 | 
						|
								</pre>
							 | 
						|
								<p>And the result.</p>
							 | 
						|
								<p></p><div translate="no" class="threejs_example_container notranslate">
							 | 
						|
								  <div><iframe class="threejs_example notranslate" translate="no" style=" " src="/manual/examples/resources/editor.html?url=/manual/examples/load-gltf-animated-cars.html"></iframe></div>
							 | 
						|
								  <a class="threejs_center" href="/manual/examples/load-gltf-animated-cars.html" target="_blank">click here to open in a separate window</a>
							 | 
						|
								</div>
							 | 
						|
								
							 | 
						|
								<p></p>
							 | 
						|
								<p>Not bad for a few minutes work.</p>
							 | 
						|
								<p>The last thing I wanted to do is turn on shadows.</p>
							 | 
						|
								<p>To do this I grabbed all the GUI code from the <a href="/docs/#api/en/lights/DirectionalLight"><code class="notranslate" translate="no">DirectionalLight</code></a> shadows
							 | 
						|
								example in <a href="shadows.html">the article on shadows</a> and pasted it
							 | 
						|
								into our latest code.</p>
							 | 
						|
								<p>Then, after loading, we need to turn on shadows on all the objects.</p>
							 | 
						|
								<pre class="prettyprint showlinemods notranslate lang-js" translate="no">{
							 | 
						|
								  const gltfLoader = new GLTFLoader();
							 | 
						|
								  gltfLoader.load('resources/models/cartoon_lowpoly_small_city_free_pack/scene.gltf', (gltf) => {
							 | 
						|
								    const root = gltf.scene;
							 | 
						|
								    scene.add(root);
							 | 
						|
								
							 | 
						|
								+    root.traverse((obj) => {
							 | 
						|
								+      if (obj.castShadow !== undefined) {
							 | 
						|
								+        obj.castShadow = true;
							 | 
						|
								+        obj.receiveShadow = true;
							 | 
						|
								+      }
							 | 
						|
								+    });
							 | 
						|
								</pre>
							 | 
						|
								<p>I then spent nearly 4 hours trying to figure out why the shadow helpers
							 | 
						|
								were not working. It was because I forgot to enable shadows with</p>
							 | 
						|
								<pre class="prettyprint showlinemods notranslate lang-js" translate="no">renderer.shadowMap.enabled = true;
							 | 
						|
								</pre>
							 | 
						|
								<p>😭</p>
							 | 
						|
								<p>I then adjusted the values until our <code class="notranslate" translate="no">DirectionLight</code>'s shadow camera
							 | 
						|
								had a frustum that covered the entire scene. These are the settings
							 | 
						|
								I ended up with.</p>
							 | 
						|
								<pre class="prettyprint showlinemods notranslate lang-js" translate="no">{
							 | 
						|
								  const color = 0xFFFFFF;
							 | 
						|
								  const intensity = 1;
							 | 
						|
								  const light = new THREE.DirectionalLight(color, intensity);
							 | 
						|
								+  light.castShadow = true;
							 | 
						|
								*  light.position.set(-250, 800, -850);
							 | 
						|
								*  light.target.position.set(-550, 40, -450);
							 | 
						|
								
							 | 
						|
								+  light.shadow.bias = -0.004;
							 | 
						|
								+  light.shadow.mapSize.width = 2048;
							 | 
						|
								+  light.shadow.mapSize.height = 2048;
							 | 
						|
								
							 | 
						|
								  scene.add(light);
							 | 
						|
								  scene.add(light.target);
							 | 
						|
								+  const cam = light.shadow.camera;
							 | 
						|
								+  cam.near = 1;
							 | 
						|
								+  cam.far = 2000;
							 | 
						|
								+  cam.left = -1500;
							 | 
						|
								+  cam.right = 1500;
							 | 
						|
								+  cam.top = 1500;
							 | 
						|
								+  cam.bottom = -1500;
							 | 
						|
								...
							 | 
						|
								</pre>
							 | 
						|
								<p>and I set the background color to light blue.</p>
							 | 
						|
								<pre class="prettyprint showlinemods notranslate lang-js" translate="no">const scene = new THREE.Scene();
							 | 
						|
								-scene.background = new THREE.Color('black');
							 | 
						|
								+scene.background = new THREE.Color('#DEFEFF');
							 | 
						|
								</pre>
							 | 
						|
								<p>And ... shadows</p>
							 | 
						|
								<p></p><div translate="no" class="threejs_example_container notranslate">
							 | 
						|
								  <div><iframe class="threejs_example notranslate" translate="no" style=" " src="/manual/examples/resources/editor.html?url=/manual/examples/load-gltf-shadows.html"></iframe></div>
							 | 
						|
								  <a class="threejs_center" href="/manual/examples/load-gltf-shadows.html" target="_blank">click here to open in a separate window</a>
							 | 
						|
								</div>
							 | 
						|
								
							 | 
						|
								<p></p>
							 | 
						|
								<p>I hope walking through this project was useful and showed some
							 | 
						|
								good examples of working though some of the issues of loading
							 | 
						|
								a file with a scenegraph.</p>
							 | 
						|
								<p>One interesting thing is that comparing the .blend file to the .gltf
							 | 
						|
								file, the .blend file has several lights but they are not lights
							 | 
						|
								after being loaded into the scene. A .GLTF file is just a JSON
							 | 
						|
								file so you can easily look inside. It consists of several
							 | 
						|
								arrays of things and each item in an array is referenced by index
							 | 
						|
								else where. While there are extensions in the works they point
							 | 
						|
								to a problem with almost all 3d formats. <strong>They can never cover every
							 | 
						|
								case</strong>.</p>
							 | 
						|
								<p>There is always a need for more data. For example we manually exported
							 | 
						|
								a path for the cars to follow. Ideally that info could have been in
							 | 
						|
								the .GLTF file but to do that we'd need to write our own exporter
							 | 
						|
								and some how mark nodes for how we want them exported or use a
							 | 
						|
								naming scheme or something along those lines to get data from
							 | 
						|
								whatever tool we're using to create the data into our app.</p>
							 | 
						|
								<p>All of that is left as an exercise to the reader.</p>
							 | 
						|
								
							 | 
						|
								        </div>
							 | 
						|
								      </div>
							 | 
						|
								    </div>
							 | 
						|
								  
							 | 
						|
								  <script src="/manual/resources/prettify.js"></script>
							 | 
						|
								  <script src="/manual/resources/lesson.js"></script>
							 | 
						|
								
							 | 
						|
								
							 | 
						|
								
							 | 
						|
								
							 | 
						|
								</body></html>
							 | 
						|
								
							 |