threejs-scenegraph-tank.html 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293
  1. <!-- Licensed under a BSD license. See license.html for license -->
  2. <!DOCTYPE html>
  3. <html>
  4. <head>
  5. <meta charset="utf-8">
  6. <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes">
  7. <title>Three.js - Scenegraph - Tank</title>
  8. <style>
  9. body {
  10. margin: 0;
  11. }
  12. #c {
  13. width: 100vw;
  14. height: 100vh;
  15. display: block;
  16. }
  17. #info {
  18. position: absolute;
  19. left: 1em;
  20. top: 1em;
  21. background: rgba(0,0,0,.8);
  22. padding: .5em;
  23. color: white;
  24. font-family: monospace;
  25. }
  26. </style>
  27. </head>
  28. <body>
  29. <canvas id="c"></canvas>
  30. <div id="info"></div>
  31. </body>
  32. <script src="resources/threejs/r105/three.min.js"></script>
  33. <script>
  34. 'use strict';
  35. /* global THREE */
  36. function main() {
  37. const canvas = document.querySelector('#c');
  38. const renderer = new THREE.WebGLRenderer({canvas: canvas});
  39. renderer.setClearColor(0xAAAAAA);
  40. renderer.shadowMap.enabled = true;
  41. function makeCamera(fov = 40) {
  42. const aspect = 2; // the canvas default
  43. const zNear = 0.1;
  44. const zFar = 1000;
  45. return new THREE.PerspectiveCamera(fov, aspect, zNear, zFar);
  46. }
  47. const camera = makeCamera();
  48. camera.position.set(8, 4, 10).multiplyScalar(3);
  49. camera.lookAt(0, 0, 0);
  50. const scene = new THREE.Scene();
  51. {
  52. const light = new THREE.DirectionalLight(0xffffff, 1);
  53. light.position.set(0, 20, 0);
  54. scene.add(light);
  55. light.castShadow = true;
  56. light.shadow.mapSize.width = 2048;
  57. light.shadow.mapSize.height = 2048;
  58. const d = 50;
  59. light.shadow.camera.left = -d;
  60. light.shadow.camera.right = d;
  61. light.shadow.camera.top = d;
  62. light.shadow.camera.bottom = -d;
  63. light.shadow.camera.near = 1;
  64. light.shadow.camera.far = 50;
  65. light.shadow.bias = 0.001;
  66. }
  67. {
  68. const light = new THREE.DirectionalLight(0xffffff, 1);
  69. light.position.set(1, 2, 4);
  70. scene.add(light);
  71. }
  72. const groundGeometry = new THREE.PlaneBufferGeometry(50, 50);
  73. const groundMaterial = new THREE.MeshPhongMaterial({color: 0xCC8866});
  74. const groundMesh = new THREE.Mesh(groundGeometry, groundMaterial);
  75. groundMesh.rotation.x = Math.PI * -.5;
  76. groundMesh.receiveShadow = true;
  77. scene.add(groundMesh);
  78. const carWidth = 4;
  79. const carHeight = 1;
  80. const carLength = 8;
  81. const tank = new THREE.Object3D();
  82. scene.add(tank);
  83. const bodyGeometry = new THREE.BoxBufferGeometry(carWidth, carHeight, carLength);
  84. const bodyMaterial = new THREE.MeshPhongMaterial({color: 0x6688AA});
  85. const bodyMesh = new THREE.Mesh(bodyGeometry, bodyMaterial);
  86. bodyMesh.position.y = 1.4;
  87. bodyMesh.castShadow = true;
  88. tank.add(bodyMesh);
  89. const tankCameraFov = 75;
  90. const tankCamera = makeCamera(tankCameraFov);
  91. tankCamera.position.y = 3;
  92. tankCamera.position.z = -6;
  93. tankCamera.rotation.y = Math.PI;
  94. bodyMesh.add(tankCamera);
  95. const wheelRadius = 1;
  96. const wheelThickness = .5;
  97. const wheelSegments = 6;
  98. const wheelGeometry = new THREE.CylinderBufferGeometry(
  99. wheelRadius, // top radius
  100. wheelRadius, // bottom radius
  101. wheelThickness, // height of cylinder
  102. wheelSegments);
  103. const wheelMaterial = new THREE.MeshPhongMaterial({color: 0x888888});
  104. const wheelPositions = [
  105. [-carWidth / 2 - wheelThickness / 2, -carHeight / 2, carLength / 3],
  106. [ carWidth / 2 + wheelThickness / 2, -carHeight / 2, carLength / 3],
  107. [-carWidth / 2 - wheelThickness / 2, -carHeight / 2, 0],
  108. [ carWidth / 2 + wheelThickness / 2, -carHeight / 2, 0],
  109. [-carWidth / 2 - wheelThickness / 2, -carHeight / 2, -carLength / 3],
  110. [ carWidth / 2 + wheelThickness / 2, -carHeight / 2, -carLength / 3],
  111. ];
  112. const wheelMeshes = wheelPositions.map((position) => {
  113. const mesh = new THREE.Mesh(wheelGeometry, wheelMaterial);
  114. mesh.position.set(...position);
  115. mesh.rotation.z = Math.PI * .5;
  116. mesh.castShadow = true;
  117. bodyMesh.add(mesh);
  118. return mesh;
  119. });
  120. const domeRadius = 2;
  121. const domeWidthSubdivisions = 12;
  122. const domeHeightSubdivisions = 12;
  123. const domePhiStart = 0;
  124. const domePhiEnd = Math.PI * 2;
  125. const domeThetaStart = 0;
  126. const domeThetaEnd = Math.PI * .5;
  127. const domeGeometry = new THREE.SphereBufferGeometry(
  128. domeRadius, domeWidthSubdivisions, domeHeightSubdivisions,
  129. domePhiStart, domePhiEnd, domeThetaStart, domeThetaEnd);
  130. const domeMesh = new THREE.Mesh(domeGeometry, bodyMaterial);
  131. domeMesh.castShadow = true;
  132. bodyMesh.add(domeMesh);
  133. domeMesh.position.y = .5;
  134. const turretWidth = .1;
  135. const turretHeight = .1;
  136. const turretLength = carLength * .75 * .2;
  137. const turretGeometry = new THREE.BoxBufferGeometry(
  138. turretWidth, turretHeight, turretLength);
  139. const turretMesh = new THREE.Mesh(turretGeometry, bodyMaterial);
  140. const turretPivot = new THREE.Object3D();
  141. turretMesh.castShadow = true;
  142. turretPivot.scale.set(5, 5, 5);
  143. turretPivot.position.y = .5;
  144. turretMesh.position.z = turretLength * .5;
  145. turretPivot.add(turretMesh);
  146. bodyMesh.add(turretPivot);
  147. const turretCamera = makeCamera();
  148. turretCamera.position.y = .75 * .2;
  149. turretMesh.add(turretCamera);
  150. const targetGeometry = new THREE.SphereBufferGeometry(.5, 6, 3);
  151. const targetMaterial = new THREE.MeshPhongMaterial({color: 0x00FF00, flatShading: true});
  152. const targetMesh = new THREE.Mesh(targetGeometry, targetMaterial);
  153. const targetOrbit = new THREE.Object3D();
  154. const targetElevation = new THREE.Object3D();
  155. const targetBob = new THREE.Object3D();
  156. targetMesh.castShadow = true;
  157. scene.add(targetOrbit);
  158. targetOrbit.add(targetElevation);
  159. targetElevation.position.z = carLength * 2;
  160. targetElevation.position.y = 8;
  161. targetElevation.add(targetBob);
  162. targetBob.add(targetMesh);
  163. const targetCamera = makeCamera();
  164. const targetCameraPivot = new THREE.Object3D();
  165. targetCamera.position.y = 1;
  166. targetCamera.position.z = -2;
  167. targetCamera.rotation.y = Math.PI;
  168. targetBob.add(targetCameraPivot);
  169. targetCameraPivot.add(targetCamera);
  170. // Create a sine-like wave
  171. const curve = new THREE.SplineCurve( [
  172. new THREE.Vector2( -10, 0 ),
  173. new THREE.Vector2( -5, 5 ),
  174. new THREE.Vector2( 0, 0 ),
  175. new THREE.Vector2( 5, -5 ),
  176. new THREE.Vector2( 10, 0 ),
  177. new THREE.Vector2( 5, 10 ),
  178. new THREE.Vector2( -5, 10 ),
  179. new THREE.Vector2( -10, -10 ),
  180. new THREE.Vector2( -15, -8 ),
  181. new THREE.Vector2( -10, 0 ),
  182. ] );
  183. const points = curve.getPoints( 50 );
  184. const geometry = new THREE.BufferGeometry().setFromPoints( points );
  185. const material = new THREE.LineBasicMaterial( { color : 0xff0000 } );
  186. const splineObject = new THREE.Line( geometry, material );
  187. splineObject.rotation.x = Math.PI * .5;
  188. splineObject.position.y = 0.05;
  189. scene.add(splineObject);
  190. function resizeRendererToDisplaySize(renderer) {
  191. const canvas = renderer.domElement;
  192. const width = canvas.clientWidth;
  193. const height = canvas.clientHeight;
  194. const needResize = canvas.width !== width || canvas.height !== height;
  195. if (needResize) {
  196. renderer.setSize(width, height, false);
  197. }
  198. return needResize;
  199. }
  200. const targetPosition = new THREE.Vector3();
  201. const tankPosition = new THREE.Vector2();
  202. const tankTarget = new THREE.Vector2();
  203. const cameras = [
  204. { cam: camera, desc: 'detached camera', },
  205. { cam: turretCamera, desc: 'on turret looking at target', },
  206. { cam: targetCamera, desc: 'near target looking at tank', },
  207. { cam: tankCamera, desc: 'above back of tank', },
  208. ];
  209. const infoElem = document.querySelector('#info');
  210. function render(time) {
  211. time *= 0.001;
  212. if (resizeRendererToDisplaySize(renderer)) {
  213. const canvas = renderer.domElement;
  214. cameras.forEach((cameraInfo) => {
  215. const camera = cameraInfo.cam;
  216. camera.aspect = canvas.clientWidth / canvas.clientHeight;
  217. camera.updateProjectionMatrix();
  218. });
  219. }
  220. // move target
  221. targetOrbit.rotation.y = time * .27;
  222. targetBob.position.y = Math.sin(time * 2) * 4;
  223. targetMesh.rotation.x = time * 7;
  224. targetMesh.rotation.y = time * 13;
  225. targetMaterial.emissive.setHSL(time * 10 % 1, 1, .25);
  226. targetMaterial.color.setHSL(time * 10 % 1, 1, .25);
  227. // move tank
  228. const tankTime = time * .05;
  229. curve.getPointAt(tankTime % 1, tankPosition);
  230. curve.getPointAt((tankTime + 0.01) % 1, tankTarget);
  231. tank.position.set(tankPosition.x, 0, tankPosition.y);
  232. tank.lookAt(tankTarget.x, 0, tankTarget.y);
  233. // face turret at target
  234. targetMesh.getWorldPosition(targetPosition);
  235. turretPivot.lookAt(targetPosition);
  236. // make the turretCamera look at target
  237. turretCamera.lookAt(targetPosition);
  238. // make the targetCameraPivot look at the at the tank
  239. tank.getWorldPosition(targetPosition);
  240. targetCameraPivot.lookAt(targetPosition);
  241. wheelMeshes.forEach((obj) => {
  242. obj.rotation.x = time * 3;
  243. });
  244. const camera = cameras[time * .25 % cameras.length | 0];
  245. infoElem.textContent = camera.desc;
  246. renderer.render(scene, camera.cam);
  247. requestAnimationFrame(render);
  248. }
  249. requestAnimationFrame(render);
  250. }
  251. main();
  252. </script>
  253. </html>