Browse Source

add tank example

Gregg Tavares 6 years ago
parent
commit
dc6f3c70e8

File diff suppressed because it is too large
+ 0 - 0
threejs/lessons/resources/images/scenegraph-tank.svg


+ 1 - 1
threejs/lessons/resources/moon-orbit.html

@@ -29,7 +29,7 @@ p {
 <p><canvas id="orbit"></canvas>
 <canvas id="c"></canvas></p>
 </body>
-<script src="../../resources/threejs-lessons-helper.js"></script>
+<script src="../../resources/lessons-helper.js"></script>
 <script src="../../resources/threejs-utils.js"></script>
 <script src="canvas-wrapper.js"></script>
 <script>

+ 116 - 0
threejs/lessons/threejs-scenegraph.md

@@ -380,6 +380,122 @@ that scene graph above is simplified. For example you might extend it
 to cover the every finger (at least another 28 nodes) and every toe
 (yet another 28 nodes) plus ones for the and jaw, the eyes and maybe more.
 
+Let's make one semi-complex scenegraph. We'll make a tank. The tank will have
+6 wheels and a turret. The tank will follow a path. There will be a sphere that
+moves around and the tank will target the sphere.
+
+Here's the scene graph. The meshes are colored in green, the `Object3D`s in blue,
+and the lights in gold, and the cameras in purple. One camera has not been added
+to the scene graph.
+
+<div class="threejs_center"><img src="resources/images/scenegraph-tank.svg" style="width: 800px;"></div>
+
+Look in the code to see the setup of all of these nodes.
+
+For the target, the thing the tank is aiming at, there is a `targetOrbit`
+(`Object3D`) which just rotates similar to the `earthOrbit` above. A
+`targetElevation` (`Object3D`) which is a child of the `targetOrbit` provides an
+offset from the `targetOrbit` and a base elevation. Childed to that is another
+`Object3D` called `targetBob` which just bobs up and down relative to the
+`targetElevation`. Finally there's the `targetMesh` which is just a cube we
+rotate and change it's colors
+
+```js
+// move target
+targetOrbit.rotation.y = time * .27;
+targetBob.position.y = Math.sin(time * 2) * 4;
+targetMesh.rotation.x = time * 7;
+targetMesh.rotation.y = time * 13;
+targetMaterial.emissive.setHSL(time * 10 % 1, 1, .25);
+targetMaterial.color.setHSL(time * 10 % 1, 1, .25);
+```
+
+For the tank there's an `Object3D` called `tank` which used to move everything
+below it around. The code uses a `SplineCurve` which it can ask for positions
+along that curve. 0.0 is the start of the curve. 1.0 is the end of the curve. It
+asks for the current position where it puts the tank. It then asks for a
+position slightly further down the curve and uses that to point the tank in that
+direction using `Object3D.lookAt`.
+
+```js
+const tankPosition = new THREE.Vector2();
+const tankTarget = new THREE.Vector2();
+
+...
+
+// move tank
+const tankTime = time * .05;
+curve.getPointAt(tankTime % 1, tankPosition);
+curve.getPointAt((tankTime + 0.01) % 1, tankTarget);
+tank.position.set(tankPosition.x, 0, tankPosition.y);
+tank.lookAt(tankTarget.x, 0, tankTarget.y);
+```
+
+The turret on top of the tank is moved automatically by being a child
+of the tank. To point it at the target we just ask for the target's world position
+and then again use `Object3D.lookAt`
+
+```js
+const targetPosition = new THREE.Vector3();
+
+...
+
+// face turret at target
+targetMesh.getWorldPosition(targetPosition);
+turretPivot.lookAt(targetPosition);
+```
+
+There's a `turretCamera` which is a child of the `turretMesh` so
+it will move up and down and rotate with the turret. We make that
+aim at the target.
+
+```js
+// make the turretCamera look at target
+turretCamera.lookAt(targetPosition);
+```
+
+There is also a `targetCameraPivot` which is a child of `targetBob` so it floats
+around with the target. We aim that back at the tank. It's purpose is to allow the
+`targetCamera` to be offset from the target itself. If we instead made the camera
+a child of `targetBob` and just aimed the camera itself it would be inside the
+target.
+
+```js
+// make the targetCameraPivot look at the at the tank
+tank.getWorldPosition(targetPosition);
+targetCameraPivot.lookAt(targetPosition);
+```
+
+Finally we rotate all the wheels
+
+```js
+wheelMeshes.forEach((obj) => {
+  obj.rotation.x = time * 3;
+});
+```
+
+For the cameras we setup an array of all 4 cameras at init time with descriptions.
+
+```js
+const cameras = [
+  { cam: camera, desc: 'detached camera', },
+  { cam: turretCamera, desc: 'on turret looking at target', },
+  { cam: targetCamera, desc: 'near target looking at tank', },
+  { cam: tankCamera, desc: 'above back of tank', },
+];
+
+const infoElem = document.querySelector('#info');
+```
+
+and cycle through our cameras at render time.
+
+```js
+const camera = cameras[time * .25 % cameras.length | 0];
+infoElem.textContent = camera.desc;
+```
+
+{{{example url="../threejs-scenegraph-tank.html"}}}
+
 I hope this gives some idea of how scene graphs work and how you might use them.
 Making `Object3D` nodes and parenting things to them is an important step to using
 a 3D engine like three.js well. Often it might seem like some complex math is necessary

+ 293 - 0
threejs/threejs-scenegraph-tank.html

@@ -0,0 +1,293 @@
+<!-- Licensed under a BSD license. See license.html for license -->
+<!DOCTYPE html>
+<html>
+  <head>
+    <meta charset="utf-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes">
+    <title>Three.js - Scenegraph - Tank</title>
+    <style>
+    body {
+        margin: 0;
+    }
+    #c {
+        width: 100vw;
+        height: 100vh;
+        display: block;
+    }
+    #info { 
+        position: absolute; 
+        left: 1em; 
+        top: 1em; 
+        background: rgba(0,0,0,.8); 
+        padding: .5em;
+        color: white;
+        font-family: monospace;
+    }
+  </style>
+  </head>
+  <body>
+    <canvas id="c"></canvas>
+    <div id="info"></div>
+  </body>
+<script src="resources/threejs/r105/three.min.js"></script>
+<script>
+'use strict';
+
+/* global THREE */
+
+function main() {
+  const canvas = document.querySelector('#c');
+  const renderer = new THREE.WebGLRenderer({canvas: canvas});
+  renderer.setClearColor(0xAAAAAA);
+  renderer.shadowMap.enabled = true;
+
+  function makeCamera(fov = 40) {
+    const aspect = 2;  // the canvas default
+    const zNear = 0.1;
+    const zFar = 1000;
+    return new THREE.PerspectiveCamera(fov, aspect, zNear, zFar);
+  }
+  const camera = makeCamera();
+  camera.position.set(8, 4, 10).multiplyScalar(3);
+  camera.lookAt(0, 0, 0);
+
+  const scene = new THREE.Scene();
+
+  {
+    const light = new THREE.DirectionalLight(0xffffff, 1);
+    light.position.set(0, 20, 0);
+    scene.add(light);
+    light.castShadow = true;
+    light.shadow.mapSize.width = 2048;
+    light.shadow.mapSize.height = 2048;
+
+    const d = 50;
+    light.shadow.camera.left = -d;
+    light.shadow.camera.right = d;
+    light.shadow.camera.top = d;
+    light.shadow.camera.bottom = -d;
+    light.shadow.camera.near = 1;
+    light.shadow.camera.far = 50;
+    light.shadow.bias = 0.001;
+  }
+
+  {
+    const light = new THREE.DirectionalLight(0xffffff, 1);
+    light.position.set(1, 2, 4);
+    scene.add(light);
+  }
+
+  const groundGeometry = new THREE.PlaneBufferGeometry(50, 50);
+  const groundMaterial = new THREE.MeshPhongMaterial({color: 0xCC8866});
+  const groundMesh = new THREE.Mesh(groundGeometry, groundMaterial);
+  groundMesh.rotation.x = Math.PI * -.5;
+  groundMesh.receiveShadow = true;
+  scene.add(groundMesh);
+
+  const carWidth = 4;
+  const carHeight = 1;
+  const carLength = 8;
+
+  const tank = new THREE.Object3D();
+  scene.add(tank);
+
+  const bodyGeometry = new THREE.BoxBufferGeometry(carWidth, carHeight, carLength);
+  const bodyMaterial = new THREE.MeshPhongMaterial({color: 0x6688AA});
+  const bodyMesh = new THREE.Mesh(bodyGeometry, bodyMaterial);
+  bodyMesh.position.y = 1.4;
+  bodyMesh.castShadow = true;
+  tank.add(bodyMesh);
+
+  const tankCameraFov = 75;
+  const tankCamera = makeCamera(tankCameraFov);
+  tankCamera.position.y = 3;
+  tankCamera.position.z = -6;
+  tankCamera.rotation.y = Math.PI;
+  bodyMesh.add(tankCamera);
+
+  const wheelRadius = 1;
+  const wheelThickness = .5;
+  const wheelSegments = 6;
+  const wheelGeometry = new THREE.CylinderBufferGeometry(
+      wheelRadius,     // top radius
+      wheelRadius,     // bottom radius
+      wheelThickness,  // height of cylinder
+      wheelSegments);
+  const wheelMaterial = new THREE.MeshPhongMaterial({color: 0x888888});
+  const wheelPositions = [
+    [-carWidth / 2 - wheelThickness / 2, -carHeight / 2,  carLength / 3],
+    [ carWidth / 2 + wheelThickness / 2, -carHeight / 2,  carLength / 3],
+    [-carWidth / 2 - wheelThickness / 2, -carHeight / 2, 0],
+    [ carWidth / 2 + wheelThickness / 2, -carHeight / 2, 0],
+    [-carWidth / 2 - wheelThickness / 2, -carHeight / 2, -carLength / 3],
+    [ carWidth / 2 + wheelThickness / 2, -carHeight / 2, -carLength / 3],
+  ];
+  const wheelMeshes = wheelPositions.map((position) => {
+    const mesh = new THREE.Mesh(wheelGeometry, wheelMaterial);
+    mesh.position.set(...position);
+    mesh.rotation.z = Math.PI * .5;
+    mesh.castShadow = true;
+    bodyMesh.add(mesh);
+    return mesh;
+  });
+
+  const domeRadius = 2;
+  const domeWidthSubdivisions = 12;
+  const domeHeightSubdivisions = 12;
+  const domePhiStart = 0;
+  const domePhiEnd = Math.PI * 2;
+  const domeThetaStart = 0;
+  const domeThetaEnd = Math.PI * .5;
+  const domeGeometry = new THREE.SphereBufferGeometry(
+    domeRadius, domeWidthSubdivisions, domeHeightSubdivisions,
+    domePhiStart, domePhiEnd, domeThetaStart, domeThetaEnd);
+  const domeMesh = new THREE.Mesh(domeGeometry, bodyMaterial);
+  domeMesh.castShadow = true;
+  bodyMesh.add(domeMesh);
+  domeMesh.position.y = .5;
+
+  const turretWidth = .1;
+  const turretHeight = .1;
+  const turretLength = carLength * .75 * .2;
+  const turretGeometry = new THREE.BoxBufferGeometry(
+      turretWidth, turretHeight, turretLength);
+  const turretMesh = new THREE.Mesh(turretGeometry, bodyMaterial);
+  const turretPivot = new THREE.Object3D();
+  turretMesh.castShadow = true;
+  turretPivot.scale.set(5, 5, 5);
+  turretPivot.position.y = .5;
+  turretMesh.position.z = turretLength * .5;
+  turretPivot.add(turretMesh);
+  bodyMesh.add(turretPivot);
+
+  const turretCamera = makeCamera();
+  turretCamera.position.y = .75 * .2;
+  turretMesh.add(turretCamera);
+
+  const targetGeometry = new THREE.SphereBufferGeometry(.5, 6, 3);
+  const targetMaterial = new THREE.MeshPhongMaterial({color: 0x00FF00, flatShading: true});
+  const targetMesh = new THREE.Mesh(targetGeometry, targetMaterial);
+  const targetOrbit = new THREE.Object3D();
+  const targetElevation = new THREE.Object3D();
+  const targetBob = new THREE.Object3D();
+  targetMesh.castShadow = true;
+  scene.add(targetOrbit);
+  targetOrbit.add(targetElevation);
+  targetElevation.position.z = carLength * 2;
+  targetElevation.position.y = 8;
+  targetElevation.add(targetBob);
+  targetBob.add(targetMesh);
+
+  const targetCamera = makeCamera();
+  const targetCameraPivot = new THREE.Object3D();
+  targetCamera.position.y = 1;
+  targetCamera.position.z = -2;
+  targetCamera.rotation.y = Math.PI;
+  targetBob.add(targetCameraPivot);
+  targetCameraPivot.add(targetCamera);
+
+  // Create a sine-like wave
+  const curve = new THREE.SplineCurve( [
+    new THREE.Vector2( -10, 0 ),
+    new THREE.Vector2( -5, 5 ),
+    new THREE.Vector2( 0, 0 ),
+    new THREE.Vector2( 5, -5 ),
+    new THREE.Vector2( 10, 0 ),
+    new THREE.Vector2( 5, 10 ),
+    new THREE.Vector2( -5, 10 ),
+    new THREE.Vector2( -10, -10 ),
+    new THREE.Vector2( -15, -8 ),
+    new THREE.Vector2( -10, 0 ),
+  ] );
+
+  const points = curve.getPoints( 50 );
+  const geometry = new THREE.BufferGeometry().setFromPoints( points );
+  const material = new THREE.LineBasicMaterial( { color : 0xff0000 } );
+  const splineObject = new THREE.Line( geometry, material );
+  splineObject.rotation.x = Math.PI * .5;
+  splineObject.position.y = 0.05;
+  scene.add(splineObject);
+
+  function resizeRendererToDisplaySize(renderer) {
+    const canvas = renderer.domElement;
+    const width = canvas.clientWidth;
+    const height = canvas.clientHeight;
+    const needResize = canvas.width !== width || canvas.height !== height;
+    if (needResize) {
+      renderer.setSize(width, height, false);
+    }
+    return needResize;
+  }
+
+  const targetPosition = new THREE.Vector3();
+  const tankPosition = new THREE.Vector2();
+  const tankTarget = new THREE.Vector2();
+
+  const cameras = [
+    { cam: camera, desc: 'detached camera', },
+    { cam: turretCamera, desc: 'on turret looking at target', },
+    { cam: targetCamera, desc: 'near target looking at tank', },
+    { cam: tankCamera, desc: 'above back of tank', },
+  ];
+
+  const infoElem = document.querySelector('#info');
+
+  function render(time) {
+    time *= 0.001;
+
+    if (resizeRendererToDisplaySize(renderer)) {
+      const canvas = renderer.domElement;
+      cameras.forEach((cameraInfo) => {
+        const camera = cameraInfo.cam;
+        camera.aspect = canvas.clientWidth / canvas.clientHeight;
+        camera.updateProjectionMatrix();
+      });
+    }
+
+    // move target
+    targetOrbit.rotation.y = time * .27;
+    targetBob.position.y = Math.sin(time * 2) * 4;
+    targetMesh.rotation.x = time * 7;
+    targetMesh.rotation.y = time * 13;
+    targetMaterial.emissive.setHSL(time * 10 % 1, 1, .25);
+    targetMaterial.color.setHSL(time * 10 % 1, 1, .25);
+
+    // move tank
+    const tankTime = time * .05;
+    curve.getPointAt(tankTime % 1, tankPosition);
+    curve.getPointAt((tankTime + 0.01) % 1, tankTarget);
+    tank.position.set(tankPosition.x, 0, tankPosition.y);
+    tank.lookAt(tankTarget.x, 0, tankTarget.y);
+
+    // face turret at target
+    targetMesh.getWorldPosition(targetPosition);
+    turretPivot.lookAt(targetPosition);
+
+    // make the turretCamera look at target
+    turretCamera.lookAt(targetPosition);
+
+    // make the targetCameraPivot look at the at the tank
+    tank.getWorldPosition(targetPosition);
+    targetCameraPivot.lookAt(targetPosition);
+
+    wheelMeshes.forEach((obj) => {
+      obj.rotation.x = time * 3;
+    });
+
+    const camera = cameras[time * .25 % cameras.length | 0];
+    infoElem.textContent = camera.desc;
+
+    renderer.render(scene, camera.cam);
+
+    requestAnimationFrame(render);
+  }
+
+  requestAnimationFrame(render);
+}
+
+main();
+</script>
+</html>
+
+
+

Some files were not shown because too many files changed in this diff