threejs-scenegraph-tank.html 8.9 KB

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