Viewport.XR.js 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183
  1. import * as THREE from 'three';
  2. import { SetPositionCommand } from './commands/SetPositionCommand.js';
  3. import { SetRotationCommand } from './commands/SetRotationCommand.js';
  4. import { HTMLMesh } from 'three/addons/interactive/HTMLMesh.js';
  5. import { InteractiveGroup } from 'three/addons/interactive/InteractiveGroup.js';
  6. import { XRControllerModelFactory } from 'three/addons/webxr/XRControllerModelFactory.js';
  7. class XR {
  8. constructor( editor ) {
  9. const signals = editor.signals;
  10. let group = null;
  11. let renderer = null;
  12. const camera = new THREE.PerspectiveCamera();
  13. const onSessionStarted = async ( session ) => {
  14. camera.copy( editor.camera );
  15. const sidebar = document.getElementById( 'sidebar' );
  16. sidebar.style.width = '300px';
  17. sidebar.style.height = '700px';
  18. //
  19. if ( group === null ) {
  20. group = new InteractiveGroup();
  21. group.listenToXRControllerEvents( renderer );
  22. const mesh = new HTMLMesh( sidebar );
  23. mesh.position.set( 1, 1.5, - 0.5 );
  24. mesh.rotation.y = - 0.5;
  25. mesh.scale.setScalar( 2 );
  26. group.add( mesh );
  27. // controllers
  28. const raycaster = new THREE.Raycaster();
  29. const tempMatrix = new THREE.Matrix4();
  30. function getIntersections( controller ) {
  31. tempMatrix.identity().extractRotation( controller.matrixWorld );
  32. raycaster.ray.origin.setFromMatrixPosition( controller.matrixWorld );
  33. raycaster.ray.direction.set( 0, 0, - 1 ).applyMatrix4( tempMatrix );
  34. return raycaster.intersectObjects( editor.scene.children, false );
  35. }
  36. function onSelectStart( event ) {
  37. const controller = event.target;
  38. const intersections = getIntersections( controller );
  39. if ( intersections.length > 0 ) {
  40. const intersection = intersections[ 0 ];
  41. const object = intersection.object;
  42. signals.objectSelected.dispatch( object );
  43. controller.userData.selected = object;
  44. controller.userData.position = object.position.clone();
  45. controller.userData.rotation = object.rotation.clone();
  46. controller.attach( object );
  47. }
  48. }
  49. function onSelectEnd( event ) {
  50. const controller = event.target;
  51. if ( controller.userData.selected !== undefined ) {
  52. const object = controller.userData.selected;
  53. editor.scene.attach( object );
  54. controller.userData.selected = undefined;
  55. editor.execute( new SetPositionCommand( editor, object, object.position, controller.userData.position ) );
  56. editor.execute( new SetRotationCommand( editor, object, object.rotation, controller.userData.rotation ) );
  57. signals.objectChanged.dispatch( object );
  58. } else {
  59. signals.objectSelected.dispatch( null );
  60. }
  61. }
  62. const geometry = new THREE.BufferGeometry();
  63. geometry.setFromPoints( [ new THREE.Vector3( 0, 0, 0 ), new THREE.Vector3( 0, 0, - 5 ) ] );
  64. const controller1 = renderer.xr.getController( 0 );
  65. controller1.addEventListener( 'selectstart', onSelectStart );
  66. controller1.addEventListener( 'selectend', onSelectEnd );
  67. controller1.add( new THREE.Line( geometry ) );
  68. group.add( controller1 );
  69. const controller2 = renderer.xr.getController( 1 );
  70. controller2.addEventListener( 'selectstart', onSelectStart );
  71. controller2.addEventListener( 'selectend', onSelectEnd );
  72. controller2.add( new THREE.Line( geometry ) );
  73. group.add( controller2 );
  74. //
  75. const controllerModelFactory = new XRControllerModelFactory();
  76. const controllerGrip1 = renderer.xr.getControllerGrip( 0 );
  77. controllerGrip1.add( controllerModelFactory.createControllerModel( controllerGrip1 ) );
  78. group.add( controllerGrip1 );
  79. const controllerGrip2 = renderer.xr.getControllerGrip( 1 );
  80. controllerGrip2.add( controllerModelFactory.createControllerModel( controllerGrip2 ) );
  81. group.add( controllerGrip2 );
  82. }
  83. editor.sceneHelpers.add( group );
  84. renderer.xr.enabled = true;
  85. renderer.xr.addEventListener( 'sessionend', onSessionEnded );
  86. await renderer.xr.setSession( session );
  87. };
  88. const onSessionEnded = async () => {
  89. editor.sceneHelpers.remove( group );
  90. const sidebar = document.getElementById( 'sidebar' );
  91. sidebar.style.width = '';
  92. sidebar.style.height = '';
  93. renderer.xr.removeEventListener( 'sessionend', onSessionEnded );
  94. renderer.xr.enabled = false;
  95. editor.camera.copy( camera );
  96. signals.windowResize.dispatch();
  97. };
  98. // signals
  99. signals.enterXR.add( ( mode ) => {
  100. if ( 'xr' in navigator ) {
  101. const sessionInit = { optionalFeatures: [ 'local-floor' ] };
  102. navigator.xr.requestSession( mode, sessionInit ).then( onSessionStarted );
  103. }
  104. } );
  105. signals.rendererCreated.add( ( value ) => {
  106. renderer = value;
  107. } );
  108. }
  109. }
  110. export { XR };