webgpu_xr_native_layers.html 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721
  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <title>three.js vr - xr layers</title>
  5. <meta charset="utf-8">
  6. <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
  7. <meta property="og:title" content="three.js vr - xr layers">
  8. <meta property="og:type" content="website">
  9. <meta property="og:url" content="https://threejs.org/examples/webgpu_xr_native_layers.html">
  10. <meta property="og:image" content="https://threejs.org/examples/screenshots/webgpu_xr_native_layers.jpg">
  11. <link type="text/css" rel="stylesheet" href="main.css">
  12. </head>
  13. <body>
  14. <div id="info">
  15. <a href="https://threejs.org" target="_blank" rel="noopener">three.js</a> vr - xr layers
  16. </div>
  17. <script type="importmap">
  18. {
  19. "imports": {
  20. "three": "../build/three.webgpu.js",
  21. "three/webgpu": "../build/three.webgpu.js",
  22. "three/tsl": "../build/three.tsl.js",
  23. "three/addons/": "./jsm/"
  24. }
  25. }
  26. </script>
  27. <script type="module">
  28. import * as THREE from 'three/webgpu';
  29. import { BoxLineGeometry } from 'three/addons/geometries/BoxLineGeometry.js';
  30. import { VRButton } from 'three/addons/webxr/VRButton.js';
  31. import { XRControllerModelFactory } from 'three/addons/webxr/XRControllerModelFactory.js';
  32. import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
  33. import { InteractiveGroup } from 'three/addons/interactive/InteractiveGroup.js';
  34. import {
  35. RollerCoasterGeometry,
  36. RollerCoasterShadowGeometry,
  37. RollerCoasterLiftersGeometry,
  38. TreesGeometry,
  39. SkyGeometry
  40. } from 'three/addons/misc/RollerCoaster.js';
  41. import { HTMLMesh } from 'three/addons/interactive/HTMLMesh.js';
  42. import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
  43. let camera, scene, renderer;
  44. let controller1, controller2;
  45. let controllerGrip1, controllerGrip2;
  46. let room;
  47. let count = 0;
  48. const radius = 0.08;
  49. let normal = new THREE.Vector3();
  50. const relativeVelocity = new THREE.Vector3();
  51. const timer = new THREE.Timer();
  52. timer.connect( document );
  53. const funfairs = [];
  54. const train = new THREE.Object3D();
  55. const rcdelta = timer.getDelta() * 0.8; // slow down simulation
  56. const PI2 = Math.PI * 2;
  57. let rccamera = null;
  58. let rcscene = null;
  59. const tempMatrix = new THREE.Matrix4();
  60. let raycaster = null;
  61. const curve = ( function () {
  62. const vector = new THREE.Vector3();
  63. const vector2 = new THREE.Vector3();
  64. return {
  65. getPointAt: function ( t ) {
  66. t = t * PI2;
  67. const x = Math.sin( t * 3 ) * Math.cos( t * 4 ) * 50;
  68. const y = Math.sin( t * 10 ) * 2 + Math.cos( t * 17 ) * 2 + 5;
  69. const z = Math.sin( t ) * Math.sin( t * 4 ) * 50;
  70. return vector.set( x, y, z ).multiplyScalar( 2 );
  71. },
  72. getTangentAt: function ( t ) {
  73. const delta = 0.0001;
  74. const t1 = Math.max( 0, t - delta );
  75. const t2 = Math.min( 1, t + delta );
  76. return vector2.copy( this.getPointAt( t2 ) )
  77. .sub( this.getPointAt( t1 ) ).normalize();
  78. }
  79. };
  80. } )();
  81. let horseCamera = null;
  82. let horseScene = null;
  83. let horseMixer = null;
  84. let horseTheta = 0;
  85. let horseMesh = null;
  86. const horseRadius = 600;
  87. let guiScene = null;
  88. let guiCamera = null;
  89. let guiGroup = null;
  90. let rollercoasterLayer = null;
  91. let horseLayer = null;
  92. let guiLayer = null;
  93. const parameters = {
  94. radius: 0.6,
  95. tube: 0.2,
  96. tubularSegments: 150,
  97. radialSegments: 20,
  98. p: 2,
  99. q: 3,
  100. thickness: 0.5
  101. };
  102. init();
  103. function getIntersections( controller ) {
  104. tempMatrix.identity().extractRotation( controller.matrixWorld );
  105. raycaster.ray.origin.setFromMatrixPosition( controller.matrixWorld );
  106. raycaster.ray.direction.set( 0, 0, - 1 ).applyMatrix4( tempMatrix );
  107. return raycaster.intersectObjects( scene.children, false );
  108. }
  109. function init() {
  110. scene = new THREE.Scene();
  111. scene.background = new THREE.Color( 0x505050 );
  112. raycaster = new THREE.Raycaster();
  113. camera = new THREE.PerspectiveCamera( 50, window.innerWidth / window.innerHeight, 0.1, 10 );
  114. camera.position.set( 0, 1.6, 3 );
  115. room = new THREE.LineSegments(
  116. new BoxLineGeometry( 6, 6, 6, 10, 10, 10 ),
  117. new THREE.LineBasicMaterial( { color: 0x808080 } )
  118. );
  119. room.geometry.translate( 0, 3, 0 );
  120. scene.add( room );
  121. scene.add( new THREE.HemisphereLight( 0x606060, 0x404040 ) );
  122. const light = new THREE.DirectionalLight( 0xffffff );
  123. light.position.set( 1, 1, 1 ).normalize();
  124. scene.add( light );
  125. const geometry = new THREE.IcosahedronGeometry( radius, 3 );
  126. for ( let i = 0; i < 200; i ++ ) {
  127. const object = new THREE.Mesh( geometry, new THREE.MeshLambertMaterial( { color: Math.random() * 0xffffff } ) );
  128. object.position.x = Math.random() * 4 - 2;
  129. object.position.y = Math.random() * 4;
  130. object.position.z = Math.random() * 4 - 2;
  131. object.userData.velocity = new THREE.Vector3();
  132. object.userData.velocity.x = Math.random() * 0.01 - 0.005;
  133. object.userData.velocity.y = Math.random() * 0.01 - 0.005;
  134. object.userData.velocity.z = Math.random() * 0.01 - 0.005;
  135. room.add( object );
  136. }
  137. //
  138. renderer = new THREE.WebGPURenderer( { antialias: true, forceWebGL: true, outputBufferType: THREE.UnsignedByteType, multiview: true } );
  139. renderer.setPixelRatio( window.devicePixelRatio );
  140. renderer.setSize( window.innerWidth, window.innerHeight );
  141. document.body.appendChild( renderer.domElement );
  142. renderer.setAnimationLoop( render );
  143. renderer.xr.enabled = true;
  144. //
  145. document.body.appendChild( VRButton.createButton( renderer ) );
  146. // controllers
  147. function onSqueezeStart( ) {
  148. this.userData.isSelecting = true;
  149. }
  150. function onSqueezeEnd() {
  151. this.userData.isSelecting = false;
  152. }
  153. function onSelectStart( event ) {
  154. const controller = event.target;
  155. const intersections = getIntersections( controller );
  156. let hadSelection = false;
  157. for ( let x = 0; x < intersections.length; x ++ ) {
  158. if ( intersections[ x ].object == horseLayer ) {
  159. horseLayer.visible = false;
  160. hadSelection = true;
  161. }
  162. if ( intersections[ x ].object == rollercoasterLayer ) {
  163. controller.attach( rollercoasterLayer );
  164. hadSelection = true;
  165. }
  166. if ( intersections[ x ].object == guiLayer ) {
  167. const uv = intersections[ x ].uv;
  168. guiGroup.children[ 0 ].dispatchEvent( { type: 'mousedown', data: { x: uv.x, y: 1 - uv.y }, target: guiGroup } );
  169. hadSelection = true;
  170. }
  171. }
  172. this.userData.isSelecting = hadSelection === false;
  173. }
  174. function onSelectEnd( ) {
  175. horseLayer.visible = true;
  176. scene.attach( rollercoasterLayer );
  177. guiGroup.children[ 0 ].dispatchEvent( { type: 'mouseup', data: { x: 0, y: 0 }, target: guiGroup } );
  178. this.userData.isSelecting = false;
  179. }
  180. controller1 = renderer.xr.getController( 0 );
  181. controller1.addEventListener( 'selectstart', onSelectStart );
  182. controller1.addEventListener( 'selectend', onSelectEnd );
  183. controller1.addEventListener( 'squeezestart', onSqueezeStart );
  184. controller1.addEventListener( 'squeezeend', onSqueezeEnd );
  185. controller1.addEventListener( 'connected', function ( event ) {
  186. this.add( buildController( event.data ) );
  187. } );
  188. controller1.addEventListener( 'disconnected', function () {
  189. this.remove( this.children[ 0 ] );
  190. } );
  191. scene.add( controller1 );
  192. controller2 = renderer.xr.getController( 1 );
  193. controller2.addEventListener( 'selectstart', onSelectStart );
  194. controller2.addEventListener( 'selectend', onSelectEnd );
  195. controller2.addEventListener( 'squeezestart', onSqueezeStart );
  196. controller2.addEventListener( 'squeezeend', onSqueezeEnd );
  197. controller2.addEventListener( 'connected', function ( event ) {
  198. this.add( buildController( event.data ) );
  199. } );
  200. controller2.addEventListener( 'disconnected', function () {
  201. this.remove( this.children[ 0 ] );
  202. } );
  203. scene.add( controller2 );
  204. // The XRControllerModelFactory will automatically fetch controller models
  205. // that match what the user is holding as closely as possible. The models
  206. // should be attached to the object returned from getControllerGrip in
  207. // order to match the orientation of the held device.
  208. const controllerModelFactory = new XRControllerModelFactory();
  209. controllerGrip1 = renderer.xr.getControllerGrip( 0 );
  210. controllerGrip1.add( controllerModelFactory.createControllerModel( controllerGrip1 ) );
  211. scene.add( controllerGrip1 );
  212. controllerGrip2 = renderer.xr.getControllerGrip( 1 );
  213. controllerGrip2.add( controllerModelFactory.createControllerModel( controllerGrip2 ) );
  214. scene.add( controllerGrip2 );
  215. //
  216. window.addEventListener( 'resize', onWindowResize );
  217. // set up rollercoaster
  218. rollercoasterLayer = renderer.xr.createCylinderLayer( 1, Math.PI / 2, 2, new THREE.Vector3( 0, 1.5, - 0.5 ), new THREE.Quaternion(), 1500, 1000, renderRollercoaster );
  219. scene.add( rollercoasterLayer );
  220. rcscene = new THREE.Scene();
  221. rcscene.background = new THREE.Color( 0xf0f0ff );
  222. const rclight = new THREE.HemisphereLight( 0xfff0f0, 0x606066 );
  223. rclight.position.set( 1, 1, 1 );
  224. rcscene.add( rclight );
  225. rcscene.add( train );
  226. rccamera = new THREE.PerspectiveCamera( 50, 1, 0.1, 500 );
  227. train.add( rccamera );
  228. // environment
  229. let rcgeometry = new THREE.PlaneGeometry( 500, 500, 15, 15 );
  230. rcgeometry.rotateX( - Math.PI / 2 );
  231. const positions = rcgeometry.attributes.position.array;
  232. const vertex = new THREE.Vector3();
  233. for ( let i = 0; i < positions.length; i += 3 ) {
  234. vertex.fromArray( positions, i );
  235. vertex.x += Math.random() * 10 - 5;
  236. vertex.z += Math.random() * 10 - 5;
  237. const distance = ( vertex.distanceTo( scene.position ) / 5 ) - 25;
  238. vertex.y = Math.random() * Math.max( 0, distance );
  239. vertex.toArray( positions, i );
  240. }
  241. rcgeometry.computeVertexNormals();
  242. let rcmaterial = new THREE.MeshLambertMaterial( {
  243. color: 0x407000
  244. } );
  245. let rcmesh = new THREE.Mesh( rcgeometry, rcmaterial );
  246. rcscene.add( rcmesh );
  247. rcgeometry = new TreesGeometry( rcmesh );
  248. rcmaterial = new THREE.MeshBasicMaterial( {
  249. side: THREE.DoubleSide, vertexColors: true
  250. } );
  251. rcmesh = new THREE.Mesh( rcgeometry, rcmaterial );
  252. rcscene.add( rcmesh );
  253. rcgeometry = new SkyGeometry();
  254. rcmaterial = new THREE.MeshBasicMaterial( { color: 0xffffff } );
  255. rcmesh = new THREE.Mesh( rcgeometry, rcmaterial );
  256. rcscene.add( rcmesh );
  257. //
  258. rcgeometry = new RollerCoasterGeometry( curve, 1500 );
  259. rcmaterial = new THREE.MeshPhongMaterial( {
  260. vertexColors: true
  261. } );
  262. rcmesh = new THREE.Mesh( rcgeometry, rcmaterial );
  263. rcscene.add( rcmesh );
  264. rcgeometry = new RollerCoasterLiftersGeometry( curve, 100 );
  265. rcmaterial = new THREE.MeshPhongMaterial();
  266. rcmesh = new THREE.Mesh( rcgeometry, rcmaterial );
  267. rcmesh.position.y = 0.1;
  268. rcscene.add( rcmesh );
  269. rcgeometry = new RollerCoasterShadowGeometry( curve, 500 );
  270. rcmaterial = new THREE.MeshBasicMaterial( {
  271. color: 0x305000, depthWrite: false, transparent: true
  272. } );
  273. rcmesh = new THREE.Mesh( rcgeometry, rcmaterial );
  274. rcmesh.position.y = 0.1;
  275. rcscene.add( rcmesh );
  276. //
  277. rcgeometry = new THREE.CylinderGeometry( 10, 10, 5, 15 );
  278. rcmaterial = new THREE.MeshLambertMaterial( {
  279. color: 0xff8080
  280. } );
  281. rcmesh = new THREE.Mesh( rcgeometry, rcmaterial );
  282. rcmesh.position.set( - 80, 10, - 70 );
  283. rcmesh.rotation.x = Math.PI / 2;
  284. rcscene.add( rcmesh );
  285. funfairs.push( rcmesh );
  286. rcgeometry = new THREE.CylinderGeometry( 5, 6, 4, 10 );
  287. rcmaterial = new THREE.MeshLambertMaterial( {
  288. color: 0x8080ff
  289. } );
  290. rcmesh = new THREE.Mesh( rcgeometry, rcmaterial );
  291. rcmesh.position.set( 50, 2, 30 );
  292. rcscene.add( rcmesh );
  293. funfairs.push( rcmesh );
  294. // set up horse animation
  295. horseLayer = renderer.xr.createQuadLayer( 1, 1, new THREE.Vector3( - 1.5, 1.5, - 1.5 ), new THREE.Quaternion(), 800, 800, renderQuad );
  296. scene.add( horseLayer );
  297. horseLayer.geometry = new THREE.CircleGeometry( .5, 64 );
  298. horseCamera = new THREE.PerspectiveCamera( 50, 1, 1, 10000 );
  299. horseCamera.position.y = 300;
  300. horseScene = new THREE.Scene();
  301. horseScene.background = new THREE.Color( 0xf0f0f0 );
  302. //
  303. const light1 = new THREE.DirectionalLight( 0xefefff, 1.5 );
  304. light1.position.set( 1, 1, 1 ).normalize();
  305. horseScene.add( light1 );
  306. const light2 = new THREE.DirectionalLight( 0xffefef, 1.5 );
  307. light2.position.set( - 1, - 1, - 1 ).normalize();
  308. horseScene.add( light2 );
  309. const loader = new GLTFLoader();
  310. loader.load( 'models/gltf/Horse.glb', function ( gltf ) {
  311. horseMesh = gltf.scene.children[ 0 ];
  312. horseMesh.scale.set( 1.5, 1.5, 1.5 );
  313. horseScene.add( horseMesh );
  314. horseMixer = new THREE.AnimationMixer( horseMesh );
  315. horseMixer.clipAction( gltf.animations[ 0 ] ).setDuration( 1 ).play();
  316. } );
  317. function onChange() { }
  318. function onThicknessChange() { }
  319. // set up ui
  320. guiScene = new THREE.Scene();
  321. guiScene.background = new THREE.Color( 0x0 );
  322. guiCamera = new THREE.OrthographicCamera( - 1, 1, 1, - 1, 0, 1 );
  323. guiScene.add( guiCamera );
  324. const gui = new GUI( { width: 300 } );
  325. gui.add( parameters, 'radius', 0.0, 1.0 ).onChange( onChange );
  326. gui.add( parameters, 'tube', 0.0, 1.0 ).onChange( onChange );
  327. gui.add( parameters, 'tubularSegments', 10, 150, 1 ).onChange( onChange );
  328. gui.add( parameters, 'radialSegments', 2, 20, 1 ).onChange( onChange );
  329. gui.add( parameters, 'p', 1, 10, 1 ).onChange( onChange );
  330. gui.add( parameters, 'q', 0, 10, 1 ).onChange( onChange );
  331. gui.add( parameters, 'thickness', 0, 1 ).onChange( onThicknessChange );
  332. gui.domElement.style.visibility = 'hidden';
  333. guiGroup = new InteractiveGroup();
  334. guiScene.add( guiGroup );
  335. const mesh = new HTMLMesh( gui.domElement );
  336. guiGroup.add( mesh );
  337. const bbox = new THREE.Box3().setFromObject( guiScene );
  338. guiLayer = renderer.xr.createQuadLayer( 1.2, .8, new THREE.Vector3( 1.5, 1.5, - 1.5 ), new THREE.Quaternion(), 1280, 800, renderGui );
  339. scene.add( guiLayer );
  340. guiCamera.left = bbox.min.x;
  341. guiCamera.right = bbox.max.x;
  342. guiCamera.top = bbox.max.y;
  343. guiCamera.bottom = bbox.min.y;
  344. guiCamera.updateProjectionMatrix();
  345. }
  346. function renderGui() {
  347. renderer.render( guiScene, guiCamera );
  348. }
  349. function renderQuad() {
  350. horseTheta += 0.1;
  351. horseCamera.position.x = horseRadius * Math.sin( THREE.MathUtils.degToRad( horseTheta ) );
  352. horseCamera.position.z = horseRadius * Math.cos( THREE.MathUtils.degToRad( horseTheta ) );
  353. horseCamera.lookAt( 0, 150, 0 );
  354. if ( horseMixer ) {
  355. const time = Date.now();
  356. horseMixer.update( ( time - prevTime ) * 0.001 );
  357. prevTime = time;
  358. }
  359. renderer.render( horseScene, horseCamera );
  360. }
  361. const rcposition = new THREE.Vector3();
  362. const tangent = new THREE.Vector3();
  363. const lookAt = new THREE.Vector3();
  364. let rcvelocity = 0;
  365. let progress = 0;
  366. let prevTime = performance.now();
  367. function renderRollercoaster() {
  368. const time = performance.now();
  369. for ( let i = 0; i < funfairs.length; i ++ ) {
  370. funfairs[ i ].rotation.y = time * 0.0004;
  371. }
  372. //
  373. progress += rcvelocity;
  374. progress = progress % 1;
  375. rcposition.copy( curve.getPointAt( progress ) );
  376. rcposition.y += 0.3;
  377. train.position.copy( rcposition );
  378. tangent.copy( curve.getTangentAt( progress ) );
  379. rcvelocity -= tangent.y * 0.0000001 * rcdelta;
  380. rcvelocity = Math.max( 0.00004, Math.min( 0.0002, rcvelocity ) );
  381. train.lookAt( lookAt.copy( rcposition ).sub( tangent ) );
  382. //
  383. renderer.render( rcscene, rccamera );
  384. }
  385. function buildController( data ) {
  386. let geometry, material;
  387. switch ( data.targetRayMode ) {
  388. case 'tracked-pointer':
  389. geometry = new THREE.BufferGeometry();
  390. geometry.setAttribute( 'position', new THREE.Float32BufferAttribute( [ 0, 0, 0, 0, 0, - 1 ], 3 ) );
  391. geometry.setAttribute( 'color', new THREE.Float32BufferAttribute( [ 0.5, 0.5, 0.5, 0, 0, 0 ], 3 ) );
  392. material = new THREE.LineBasicMaterial( { vertexColors: true, blending: THREE.AdditiveBlending } );
  393. return new THREE.Line( geometry, material );
  394. case 'gaze':
  395. geometry = new THREE.RingGeometry( 0.02, 0.04, 32 ).translate( 0, 0, - 1 );
  396. material = new THREE.MeshBasicMaterial( { opacity: 0.5, transparent: true } );
  397. return new THREE.Mesh( geometry, material );
  398. }
  399. }
  400. function onWindowResize() {
  401. camera.aspect = window.innerWidth / window.innerHeight;
  402. camera.updateProjectionMatrix();
  403. renderer.setSize( window.innerWidth, window.innerHeight );
  404. }
  405. function handleController( controller ) {
  406. if ( controller.userData.isSelecting ) {
  407. const object = room.children[ count ++ ];
  408. object.position.copy( controller.position );
  409. object.userData.velocity.x = ( Math.random() - 0.5 ) * 3;
  410. object.userData.velocity.y = ( Math.random() - 0.5 ) * 3;
  411. object.userData.velocity.z = ( Math.random() - 9 );
  412. object.userData.velocity.applyQuaternion( controller.quaternion );
  413. if ( count === room.children.length ) count = 0;
  414. }
  415. const intersections = getIntersections( controller );
  416. for ( let x = 0; x < intersections.length; x ++ ) {
  417. if ( intersections[ x ].object == guiLayer ) {
  418. const uv = intersections[ x ].uv;
  419. guiGroup.children[ 0 ].dispatchEvent( { type: 'mousemove', data: { x: uv.x, y: 1 - uv.y }, target: guiGroup } );
  420. }
  421. }
  422. }
  423. //
  424. function render() {
  425. timer.update();
  426. renderer.xr.renderLayers( );
  427. handleController( controller1 );
  428. handleController( controller2 );
  429. // rotate horse
  430. horseLayer.rotation.y -= 0.02;
  431. //
  432. const delta = timer.getDelta() * 0.8;
  433. const range = 3 - radius;
  434. for ( let i = 0; i < room.children.length; i ++ ) {
  435. const object = room.children[ i ];
  436. object.position.x += object.userData.velocity.x * delta;
  437. object.position.y += object.userData.velocity.y * delta;
  438. object.position.z += object.userData.velocity.z * delta;
  439. // keep objects inside room
  440. if ( object.position.x < - range || object.position.x > range ) {
  441. object.position.x = THREE.MathUtils.clamp( object.position.x, - range, range );
  442. object.userData.velocity.x = - object.userData.velocity.x;
  443. }
  444. if ( object.position.y < radius || object.position.y > 6 ) {
  445. object.position.y = Math.max( object.position.y, radius );
  446. object.userData.velocity.x *= 0.98;
  447. object.userData.velocity.y = - object.userData.velocity.y * 0.8;
  448. object.userData.velocity.z *= 0.98;
  449. }
  450. if ( object.position.z < - range || object.position.z > range ) {
  451. object.position.z = THREE.MathUtils.clamp( object.position.z, - range, range );
  452. object.userData.velocity.z = - object.userData.velocity.z;
  453. }
  454. for ( let j = i + 1; j < room.children.length; j ++ ) {
  455. const object2 = room.children[ j ];
  456. normal.copy( object.position ).sub( object2.position );
  457. const distance = normal.length();
  458. if ( distance < 2 * radius ) {
  459. normal.multiplyScalar( 0.5 * distance - radius );
  460. object.position.sub( normal );
  461. object2.position.add( normal );
  462. normal.normalize();
  463. relativeVelocity.copy( object.userData.velocity ).sub( object2.userData.velocity );
  464. normal = normal.multiplyScalar( relativeVelocity.dot( normal ) );
  465. object.userData.velocity.sub( normal );
  466. object2.userData.velocity.add( normal );
  467. }
  468. }
  469. object.userData.velocity.y -= 9.8 * delta;
  470. }
  471. renderer.render( scene, camera );
  472. }
  473. </script>
  474. </body>
  475. </html>
粤ICP备19079148号