scenegraph-tank.html 8.9 KB

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