scenegraph-tank.html 9.1 KB

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