@animech-public/playcanvas
Version:
PlayCanvas WebGL game engine
2 lines (1 loc) • 14.5 kB
JavaScript
import{platform as e}from"../../core/platform.js";import{Vec3 as t}from"../../core/math/vec3.js";import{Vec4 as s}from"../../core/math/vec4.js";import{Ray as n}from"../../core/shape/ray.js";import{Mouse as i}from"../../platform/input/mouse.js";import{getTouchTargetCoords as h}from"../../platform/input/touch-event.js";import{getApplication as r}from"../globals.js";let o,c;const l=new t,a=new t,d=new n,u=new n,_=new n;d.end=new t,u.end=new t,_.end=new t;const m=new t,p=new t,E=new t,v=new t,f=new t,y=new t,w=new t,g=new t,S=new t,x=new t,T=new t,b=new t,H=new t,k=new t,X=new t,L=new t,M=new t,C=new t,P=new t,W=new t,Y=new s;function R(e,t,s){return T.cross(e,t).dot(s)}class D{constructor(e,t,s){this.event=e,this.element=t,this.camera=s,this._stopPropagation=!1}stopPropagation(){this._stopPropagation=!0,this.event&&(this.event.stopImmediatePropagation(),this.event.stopPropagation())}}class z extends D{constructor(e,t,s,n,h,r,o){super(e,t,s),this.x=n,this.y=h,this.ctrlKey=e.ctrlKey||!1,this.altKey=e.altKey||!1,this.shiftKey=e.shiftKey||!1,this.metaKey=e.metaKey||!1,this.button=e.button,i.isPointerLocked()?(this.dx=e.movementX||e.webkitMovementX||e.mozMovementX||0,this.dy=e.movementY||e.webkitMovementY||e.mozMovementY||0):(this.dx=n-r,this.dy=h-o),this.wheelDelta=0,"wheel"===e.type&&(e.deltaY>0?this.wheelDelta=1:e.deltaY<0&&(this.wheelDelta=-1))}}class F extends D{constructor(e,t,s,n,i,h){super(e,t,s),this.touches=e.touches,this.changedTouches=e.changedTouches,this.x=n,this.y=i,this.touch=h}}class B extends D{constructor(e,t,s,n){super(e,t,s),this.inputSource=n}}class I{constructor(t,s){this._app=null,this._attached=!1,this._target=null,this._enabled=!0,this._lastX=0,this._lastY=0,this._upHandler=this._handleUp.bind(this),this._downHandler=this._handleDown.bind(this),this._moveHandler=this._handleMove.bind(this),this._wheelHandler=this._handleWheel.bind(this),this._touchstartHandler=this._handleTouchStart.bind(this),this._touchendHandler=this._handleTouchEnd.bind(this),this._touchcancelHandler=this._touchendHandler,this._touchmoveHandler=this._handleTouchMove.bind(this),this._sortHandler=this._sortElements.bind(this),this._elements=[],this._hoveredElement=null,this._pressedElement=null,this._touchedElements={},this._touchesForWhichTouchLeaveHasFired={},this._selectedElements={},this._selectedPressedElements={},this._useMouse=!s||!1!==s.useMouse,this._useTouch=!s||!1!==s.useTouch,this._useXr=!s||!1!==s.useXr,this._selectEventsAttached=!1,e.touch&&(this._clickedEntities={}),this.attach(t)}set enabled(e){this._enabled=e}get enabled(){return this._enabled}set app(e){this._app=e}get app(){return this._app||r()}attach(t){this._attached&&(this._attached=!1,this.detach()),this._target=t,this._attached=!0;const s=!!e.passiveEvents&&{passive:!0};this._useMouse&&(window.addEventListener("mouseup",this._upHandler,s),window.addEventListener("mousedown",this._downHandler,s),window.addEventListener("mousemove",this._moveHandler,s),window.addEventListener("wheel",this._wheelHandler,s)),this._useTouch&&e.touch&&(this._target.addEventListener("touchstart",this._touchstartHandler,s),this._target.addEventListener("touchend",this._touchendHandler,!1),this._target.addEventListener("touchmove",this._touchmoveHandler,!1),this._target.addEventListener("touchcancel",this._touchcancelHandler,!1)),this.attachSelectEvents()}attachSelectEvents(){!this._selectEventsAttached&&this._useXr&&this.app&&this.app.xr&&this.app.xr.supported&&(this._clickedEntities||(this._clickedEntities={}),this._selectEventsAttached=!0,this.app.xr.on("start",this._onXrStart,this))}detach(){if(!this._attached)return;this._attached=!1;const t=!!e.passiveEvents&&{passive:!0};this._useMouse&&(window.removeEventListener("mouseup",this._upHandler,t),window.removeEventListener("mousedown",this._downHandler,t),window.removeEventListener("mousemove",this._moveHandler,t),window.removeEventListener("wheel",this._wheelHandler,t)),this._useTouch&&(this._target.removeEventListener("touchstart",this._touchstartHandler,t),this._target.removeEventListener("touchend",this._touchendHandler,!1),this._target.removeEventListener("touchmove",this._touchmoveHandler,!1),this._target.removeEventListener("touchcancel",this._touchcancelHandler,!1)),this._selectEventsAttached&&(this._selectEventsAttached=!1,this.app.xr.off("start",this._onXrStart,this),this.app.xr.off("end",this._onXrEnd,this),this.app.xr.off("update",this._onXrUpdate,this),this.app.xr.input.off("selectstart",this._onSelectStart,this),this.app.xr.input.off("selectend",this._onSelectEnd,this),this.app.xr.input.off("remove",this._onXrInputRemove,this)),this._target=null}addElement(e){-1===this._elements.indexOf(e)&&this._elements.push(e)}removeElement(e){const t=this._elements.indexOf(e);-1!==t&&this._elements.splice(t,1)}_handleUp(e){this._enabled&&(i.isPointerLocked()||(this._calcMouseCoords(e),this._onElementMouseEvent("mouseup",e)))}_handleDown(e){this._enabled&&(i.isPointerLocked()||(this._calcMouseCoords(e),this._onElementMouseEvent("mousedown",e)))}_handleMove(e){this._enabled&&(this._calcMouseCoords(e),this._onElementMouseEvent("mousemove",e),this._lastX=o,this._lastY=c)}_handleWheel(e){this._enabled&&(this._calcMouseCoords(e),this._onElementMouseEvent("mousewheel",e))}_determineTouchedElements(e){const t={},s=this.app.systems.camera.cameras;for(let n=s.length-1;n>=0;n--){const i=s[n];let r=0;const o=e.changedTouches.length;for(let s=0;s<o;s++){if(t[e.changedTouches[s].identifier]){r++;continue}const n=h(e.changedTouches[s]),o=this._getTargetElementByCoords(i,n.x,n.y);o&&(r++,t[e.changedTouches[s].identifier]={element:o,camera:i,x:n.x,y:n.y})}if(r===o)break}return t}_handleTouchStart(e){if(!this._enabled)return;const t=this._determineTouchedElements(e);for(let s=0,n=e.changedTouches.length;s<n;s++){const n=e.changedTouches[s],i=t[n.identifier],h=this._touchedElements[n.identifier];!i||h&&i.element===h.element||(this._fireEvent(e.type,new F(e,i.element,i.camera,i.x,i.y,n)),this._touchesForWhichTouchLeaveHasFired[n.identifier]=!1)}for(const e in t)this._touchedElements[e]=t[e]}_handleTouchEnd(e){if(!this._enabled)return;const t=this.app.systems.camera.cameras;for(const e in this._clickedEntities)delete this._clickedEntities[e];for(let s=0,n=e.changedTouches.length;s<n;s++){const n=e.changedTouches[s],i=this._touchedElements[n.identifier];if(!i)continue;const r=i.element,o=i.camera,c=i.x,l=i.y;delete this._touchedElements[n.identifier],delete this._touchesForWhichTouchLeaveHasFired[n.identifier];const a=h(n);for(let s=t.length-1;s>=0;s--){this._getTargetElementByCoords(t[s],a.x,a.y)===r&&(this._clickedEntities[r.entity.getGuid()]||(this._fireEvent("click",new F(e,r,o,c,l,n)),this._clickedEntities[r.entity.getGuid()]=Date.now()))}this._fireEvent(e.type,new F(e,r,o,c,l,n))}}_handleTouchMove(e){if(e.preventDefault(),!this._enabled)return;const t=this._determineTouchedElements(e);for(let s=0,n=e.changedTouches.length;s<n;s++){const n=e.changedTouches[s],i=t[n.identifier],r=this._touchedElements[n.identifier];if(r){const t=h(n);i&&i.element===r.element||this._touchesForWhichTouchLeaveHasFired[n.identifier]||(this._fireEvent("touchleave",new F(e,r.element,r.camera,t.x,t.y,n)),this._touchesForWhichTouchLeaveHasFired[n.identifier]=!0),this._fireEvent("touchmove",new F(e,r.element,r.camera,t.x,t.y,n))}}}_onElementMouseEvent(e,t){let s=null;const n=this._hoveredElement;this._hoveredElement=null;const i=this.app.systems.camera.cameras;let h;for(let e=i.length-1;e>=0&&(h=i[e],s=this._getTargetElementByCoords(h,o,c),!s);e--);if(this._hoveredElement=s,"mousemove"!==e&&"mouseup"!==e||!this._pressedElement?s&&(this._fireEvent(e,new z(t,s,h,o,c,this._lastX,this._lastY)),"mousedown"===e&&(this._pressedElement=s)):this._fireEvent(e,new z(t,this._pressedElement,h,o,c,this._lastX,this._lastY)),n!==this._hoveredElement&&(n&&this._fireEvent("mouseleave",new z(t,n,h,o,c,this._lastX,this._lastY)),this._hoveredElement&&this._fireEvent("mouseenter",new z(t,this._hoveredElement,h,o,c,this._lastX,this._lastY))),"mouseup"===e&&this._pressedElement){if(this._pressedElement===this._hoveredElement){const e=this._hoveredElement.entity.getGuid();let s=!this._clickedEntities;if(this._clickedEntities){const t=this._clickedEntities[e]||0;s=Date.now()-t>300,delete this._clickedEntities[e]}s&&this._fireEvent("click",new z(t,this._hoveredElement,h,o,c,this._lastX,this._lastY))}this._pressedElement=null}}_onXrStart(){this.app.xr.on("end",this._onXrEnd,this),this.app.xr.on("update",this._onXrUpdate,this),this.app.xr.input.on("selectstart",this._onSelectStart,this),this.app.xr.input.on("selectend",this._onSelectEnd,this),this.app.xr.input.on("remove",this._onXrInputRemove,this)}_onXrEnd(){this.app.xr.off("update",this._onXrUpdate,this),this.app.xr.input.off("selectstart",this._onSelectStart,this),this.app.xr.input.off("selectend",this._onSelectEnd,this),this.app.xr.input.off("remove",this._onXrInputRemove,this)}_onXrUpdate(){if(!this._enabled)return;const e=this.app.xr.input.inputSources;for(let t=0;t<e.length;t++)this._onElementSelectEvent("selectmove",e[t],null)}_onXrInputRemove(e){const t=this._selectedElements[e.id];t&&(e._elementEntity=null,this._fireEvent("selectleave",new B(null,t,null,e))),delete this._selectedElements[e.id],delete this._selectedPressedElements[e.id]}_onSelectStart(e,t){this._enabled&&this._onElementSelectEvent("selectstart",e,t)}_onSelectEnd(e,t){this._enabled&&this._onElementSelectEvent("selectend",e,t)}_onElementSelectEvent(e,t,s){let n;const i=this._selectedElements[t.id];let h;const r=this.app.systems.camera.cameras;let o;if(t.elementInput){_.set(t.getOrigin(),t.getDirection());for(let e=r.length-1;e>=0&&(o=r[e],n=this._getTargetElementByRay(_,o),!n);e--);}t._elementEntity=n||null,n?(this._selectedElements[t.id]=n,h=n):delete this._selectedElements[t.id],i!==h&&(i&&this._fireEvent("selectleave",new B(s,i,o,t)),h&&this._fireEvent("selectenter",new B(s,h,o,t)));const c=this._selectedPressedElements[t.id];"selectmove"===e&&c&&this._fireEvent("selectmove",new B(s,c,o,t)),"selectstart"===e&&(this._selectedPressedElements[t.id]=h,h&&this._fireEvent("selectstart",new B(s,h,o,t))),!t.elementInput&&c&&(delete this._selectedPressedElements[t.id],i&&this._fireEvent("selectend",new B(s,c,o,t))),"selectend"===e&&t.elementInput&&(delete this._selectedPressedElements[t.id],c&&this._fireEvent("selectend",new B(s,c,o,t)),c&&c===i&&this._fireEvent("click",new B(s,c,o,t)))}_fireEvent(e,t){let s=t.element;for(;s.fire(e,t),!t._stopPropagation&&s.entity.parent&&(s=s.entity.parent.element,s););}_calcMouseCoords(e){const t=this._target.getBoundingClientRect(),s=Math.floor(t.left),n=Math.floor(t.top);o=e.clientX-s,c=e.clientY-n}_sortElements(e,t){const s=this.app.scene.layers.sortTransparentLayers(e.layers,t.layers);return 0!==s?s:e.screen&&!t.screen?-1:!e.screen&&t.screen?1:e.screen||t.screen?e.screen.screen.screenSpace&&!t.screen.screen.screenSpace?-1:t.screen.screen.screenSpace&&!e.screen.screen.screenSpace?1:t.drawOrder-e.drawOrder:0}_getTargetElementByCoords(e,t,s){const n=this._calculateRayScreen(t,s,e,d)?d:null,i=this._calculateRay3d(t,s,e,u)?u:null;return this._getTargetElement(e,n,i)}_getTargetElementByRay(e,t){d.origin.copy(e.origin),d.direction.copy(e.direction),d.end.copy(d.direction).mulScalar(2*t.farClip).add(d.origin);const s=d,n=t.worldToScreen(s.origin,l),i=this._calculateRayScreen(n.x,n.y,t,u)?u:null;return this._getTargetElement(t,i,s)}_getTargetElement(e,t,s){let n=null,i=1/0;this._elements.sort(this._sortHandler);for(let h=0,r=this._elements.length;h<r;h++){const r=this._elements[h];if(r.layers.some((t=>e.layersSet.has(t))))if(r.screen&&r.screen.screen.screenSpace){if(!t)continue;if(this._checkElement(t,r,!0)>=0){n=r;break}}else{if(!s)continue;const e=this._checkElement(s,r,!1);if(e>=0&&(e<i&&(n=r,i=e),r.screen)){n=r;break}}}return n}_calculateRayScreen(e,t,s,n){const i=this.app.graphicsDevice.width,h=this.app.graphicsDevice.height,r=s.rect.z*i,o=s.rect.w*h,c=s.rect.x*i,l=c+r,a=(1-s.rect.y)*h,d=a-o;let u=e*i/this._target.clientWidth,_=t*h/this._target.clientHeight;return u>=c&&u<=l&&_<=a&&_>=d&&(u=i*(u-c)/r,_=h*(_-d)/o,_=h-_,n.origin.set(u,_,1),n.direction.set(0,0,-1),n.end.copy(n.direction).mulScalar(2).add(n.origin),!0)}_calculateRay3d(e,t,s,n){const i=this._target.clientWidth,h=this._target.clientHeight,r=s.rect.z*i,o=s.rect.w*h,c=s.rect.x*i,d=c+r,u=(1-s.rect.y)*h,_=u-o;let m=e,p=t;return e>=c&&e<=d&&t<=u&&p>=_&&(m=i*(m-c)/r,p=h*(p-_)/o,s.screenToWorld(m,p,s.nearClip,l),s.screenToWorld(m,p,s.farClip,a),n.origin.copy(l),n.direction.set(0,0,-1),n.end.copy(a),!0)}_checkElement(e,t,s){if(t.maskedBy&&this._checkElement(e,t.maskedBy.element,s)<0)return-1;let n;n=s?I.calculateScaleToScreen(t):I.calculateScaleToWorld(t);const i=I.buildHitCorners(t,s?t.screenCorners:t.worldCorners,n);return function(e,t,s){m.sub2(t,e),p.sub2(s[0],e),E.sub2(s[1],e),v.sub2(s[2],e),y.cross(v,m);let n,i,h=p.dot(y);if(h>=0){if(n=-E.dot(y),n<0)return-1;if(i=R(m,E,p),i<0)return-1;const e=1/(n+h+i);w.copy(s[0]).mulScalar(n*e),g.copy(s[1]).mulScalar(h*e),S.copy(s[2]).mulScalar(i*e),x.copy(w).add(g).add(S)}else{if(f.sub2(s[3],e),n=f.dot(y),n<0)return-1;if(i=R(m,p,f),i<0)return-1;h=-h;const t=1/(n+h+i);w.copy(s[0]).mulScalar(n*t),g.copy(s[3]).mulScalar(h*t),S.copy(s[2]).mulScalar(i*t),x.copy(w).add(g).add(S)}return m.sub2(s[0],s[2]).lengthSq()<1e-8||m.sub2(s[1],s[3]).lengthSq()<1e-8?-1:x.sub(e).lengthSq()}(e.origin,e.end,i)}static buildHitCorners(e,t,s){let n=t;if(e.entity&&e.entity.button){const t=e.entity.button.hitPadding||Y;H.copy(e.entity.up),k.copy(H).mulScalar(-1),L.copy(e.entity.right),X.copy(L).mulScalar(-1),H.mulScalar(t.w*s.y),k.mulScalar(t.y*s.y),L.mulScalar(t.z*s.x),X.mulScalar(t.x*s.x),M.copy(n[0]).add(k).add(X),C.copy(n[1]).add(k).add(L),P.copy(n[2]).add(H).add(L),W.copy(n[3]).add(H).add(X),n=[M,C,P,W]}if(s.x<0){const e=n[2].x,t=n[0].x;n[0].x=e,n[1].x=t,n[2].x=t,n[3].x=e}if(s.y<0){const e=n[2].y,t=n[0].y;n[0].y=e,n[1].y=e,n[2].y=t,n[3].y=t}if(s.z<0){const e=n[2].x,t=n[2].y,s=n[2].z;n[2].x=n[0].x,n[2].y=n[0].y,n[2].z=n[0].z,n[0].x=e,n[0].y=t,n[0].z=s}return n}static calculateScaleToScreen(e){let t=e.entity;const s=e.screen.screen.scale;for(b.set(s,s,s);t&&!t.screen;)b.mul(t.getLocalScale()),t=t.parent;return b}static calculateScaleToWorld(e){let t=e.entity;for(b.set(1,1,1);t;)b.mul(t.getLocalScale()),t=t.parent;return b}}export{I as ElementInput,D as ElementInputEvent,z as ElementMouseEvent,B as ElementSelectEvent,F as ElementTouchEvent};