@soonspacejs/plugin-first-person-controls 2.5.1 → 2.5.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import SoonSpace, { Position, Rotation } from 'soonspacejs';
2
- import { PerspectiveCamera, Vector3, Vector2 } from 'three';
2
+ import type { PerspectiveCamera, Vector3, Vector2, Intersection, Sphere, Object3D, Box3 } from 'three';
3
3
  export declare type ClashCheckAxis = 'front' | 'back' | 'left' | 'right' | 'up' | 'down';
4
4
  export interface StartOptions {
5
5
  position: Position;
@@ -10,6 +10,30 @@ export interface StartOptions {
10
10
  jumpHeight?: number;
11
11
  enableClash?: boolean;
12
12
  enableGravity?: boolean;
13
+ /**
14
+ * 模型对象的搜索半径的系数
15
+ * @remarks
16
+ * 搜索半径是 clashCheckDistance 的多少倍
17
+ */
18
+ searchRadiusFactor?: number;
19
+ /**
20
+ * 碰撞距离
21
+ */
22
+ clashDistance?: number;
23
+ /**
24
+ * 碰撞检测距离
25
+ */
26
+ clashCheckDistance?: number;
27
+ /**
28
+ * 重力速度
29
+ */
30
+ gravitySpeed?: number;
31
+ /**
32
+ * 重力搜索系数
33
+ * @remarks
34
+ * 重力搜索系数 表示 向下搜索多少个 eyeHeight 的深度
35
+ */
36
+ gravitySearchFactor?: number;
13
37
  }
14
38
  export default class FirstPersonControlsPlugin {
15
39
  ssp: SoonSpace;
@@ -30,7 +54,23 @@ export default class FirstPersonControlsPlugin {
30
54
  moveUp: boolean;
31
55
  moveDown: boolean;
32
56
  };
57
+ /**
58
+ * 移动速度
59
+ */
33
60
  moveSpeed: number;
61
+ /**
62
+ * 重力速度
63
+ */
64
+ gravitySpeed: number;
65
+ jumpOffset: number;
66
+ /**
67
+ * 碰撞检测距离
68
+ */
69
+ clashCheckDistance: number;
70
+ /**
71
+ * 碰撞距离
72
+ */
73
+ clashDistance: number;
34
74
  eyeHeight: number;
35
75
  kneeHeight: number;
36
76
  jumpHeight: number;
@@ -45,13 +85,35 @@ export default class FirstPersonControlsPlugin {
45
85
  onMouseDown(event: MouseEvent): void;
46
86
  onMouseUp(event: MouseEvent): void;
47
87
  onMouseMove(event: MouseEvent): void;
48
- onRotate(): void;
49
- onMoveForward(distance: number): void;
50
- onMoveRight(distance: number): void;
51
- onMoveUp(distance: number): void;
52
- onMoveDown(distance: number): void;
53
- onClashCheck(clashCheckAxis: ClashCheckAxis): boolean;
88
+ clearClashCache(): void;
89
+ lastDirection: Vector3;
90
+ onClashCheck(origin: Vector3, direction: Vector3): boolean;
91
+ /**
92
+ * 模型对象的搜索半径的系数
93
+ * @remarks
94
+ * 搜索半径是 clashCheckDistance 的多少倍
95
+ */
96
+ searchRadiusFactor: number;
97
+ checkedObjects: Object3D[] | null;
98
+ checkedSphere: Sphere;
99
+ sceneObjectsChanged: () => void;
100
+ getCheckedObjects(origin: Vector3): Object3D<import("soonspacejs").Event>[];
101
+ /**
102
+ * 重力搜索系数
103
+ * @remarks
104
+ * 重力搜索系数 表示 向下搜索多少个 eyeHeight 的深度
105
+ */
106
+ gravitySearchFactor: number;
107
+ gravityCheckedObjects: Object3D[] | null;
108
+ gravityCheckedBox: Box3;
109
+ getGravityCheckedObjects(origin: Vector3, dropY: number): Object3D<import("soonspacejs").Event>[];
110
+ gravityClashCheck(origin: Vector3, dropY: number): Intersection<Object3D<import("soonspacejs").Event>>;
54
111
  start(options: StartOptions): void;
55
112
  stop(): void;
113
+ needUpdate(): void;
114
+ gravityInterObject?: Intersection | null;
115
+ kneeInterObject?: Intersection | null;
116
+ eyeInterObject?: Intersection | null;
117
+ hasUpdated: boolean;
56
118
  update(): void;
57
119
  }
package/dist/index.esm.js CHANGED
@@ -1 +1 @@
1
- let e=performance.now();let t;class s{constructor(e){this.ssp=e,this.viewport=e.viewport,this.camera=this.viewport.cameraManager.createCamera("firstPersonCamera"),this.enabled=!1,this.rotationAngle={min:0,max:Math.PI},this.state={moveForward:!1,moveBackward:!1,moveLeft:!1,moveRight:!1,moveUp:!1,moveDown:!1,canJump:!1,canRotate:!1},this.moveSpeed=1,this.eyeHeight=160,this.kneeHeight=50,this.jumpHeight=110,this.enableClash=!0,this.enableGravity=!0,this.onKeyDown=this.onKeyDown.bind(this),this.onKeyUp=this.onKeyUp.bind(this),this.onMouseDown=this.onMouseDown.bind(this),this.onMouseUp=this.onMouseUp.bind(this),this.onMouseMove=this.onMouseMove.bind(this),this.velocity=new this.ssp.THREE.Vector3,this.vector=new this.ssp.THREE.Vector3,this.movement=new this.ssp.THREE.Vector2}onKeyDown(e){if(!1!==this.enabled)switch(e.code){case"ArrowUp":case"KeyW":this.state.moveForward=!0;break;case"ArrowLeft":case"KeyA":this.state.moveLeft=!0;break;case"ArrowDown":case"KeyS":this.state.moveBackward=!0;break;case"ArrowRight":case"KeyD":this.state.moveRight=!0;break;case"KeyI":this.state.canRotate=!0,this.movement.x=0,this.movement.y=-10;break;case"KeyJ":this.state.canRotate=!0,this.movement.x=-10,this.movement.y=0;break;case"KeyK":this.state.canRotate=!0,this.movement.x=0,this.movement.y=10;break;case"KeyL":this.state.canRotate=!0,this.movement.x=10,this.movement.y=0;break;case"KeyQ":this.state.moveUp=!0;break;case"KeyE":this.state.moveDown=!0;break;case"Space":!0===this.state.canJump&&(this.velocity.y+=3*this.jumpHeight),this.state.canJump=!1}}onKeyUp(e){if(!1!==this.enabled)switch(e.code){case"ArrowUp":case"KeyW":this.state.moveForward=!1;break;case"ArrowLeft":case"KeyA":this.state.moveLeft=!1;break;case"ArrowDown":case"KeyS":this.state.moveBackward=!1;break;case"ArrowRight":case"KeyD":this.state.moveRight=!1;break;case"KeyI":case"KeyJ":case"KeyK":case"KeyL":this.state.canRotate=!1;break;case"KeyQ":this.state.moveUp=!1;break;case"KeyE":this.state.moveDown=!1}}onMouseDown(e){this.state.canRotate=!0}onMouseUp(e){this.state.canRotate=!1}onMouseMove(e){if(this.enabled&&this.state.canRotate){const t=e.movementY;let s=e.movementX;0===s&&(e.clientX<20?s=-10:e.clientX>this.viewport.interactiveContainer.clientWidth-20&&(s=10)),this.movement.x=s,this.movement.y=t}}onRotate(){const e=new this.ssp.THREE.Euler(0,0,0,"YXZ");e.setFromQuaternion(this.camera.quaternion),e.x-=.003*this.movement.y,e.y-=.003*this.movement.x,e.x=Math.max(Math.PI/2-this.rotationAngle.max,Math.min(Math.PI/2-this.rotationAngle.min,e.x)),this.camera.quaternion.setFromEuler(e)}onMoveForward(e){this.vector.setFromMatrixColumn(this.camera.matrix,0),this.vector.crossVectors(this.camera.up,this.vector),this.camera.position.addScaledVector(this.vector,e)}onMoveRight(e){this.vector.setFromMatrixColumn(this.camera.matrix,0),this.camera.position.addScaledVector(this.vector,e)}onMoveUp(e){this.vector.setFromMatrixColumn(this.camera.matrix,0),this.camera.position.addScaledVector(new this.ssp.THREE.Vector3(0,1,0),e)}onMoveDown(e){this.vector.setFromMatrixColumn(this.camera.matrix,0),this.camera.position.addScaledVector(this.vector,e)}onClashCheck(e){const t=this.camera.getWorldDirection(new this.ssp.THREE.Vector3),s=new this.ssp.THREE.Vector3;"front"===e?s.copy(t.clone()):"back"===e?s.copy(t.clone().applyAxisAngle(new this.ssp.THREE.Vector3(0,1,0),Math.PI)):"left"===e?s.copy(t.clone().applyAxisAngle(new this.ssp.THREE.Vector3(0,1,0),Math.PI/2)):"right"===e?s.copy(t.clone().applyAxisAngle(new this.ssp.THREE.Vector3(0,1,0),-Math.PI/2)):"up"===e?s.copy(new this.ssp.THREE.Vector3(0,1,0)):"down"===e&&s.copy(new this.ssp.THREE.Vector3(0,-1,0));const i=this.camera.position.clone(),o=new this.ssp.THREE.Raycaster(i,s,0,this.eyeHeight/3);o.camera=this.camera;const a=o.intersectObjects(this.viewport.scener.intersectsList.meshOfModelList),h=i.clone().setY(i.y-this.eyeHeight+this.kneeHeight),n=new this.ssp.THREE.Raycaster(h,s,0,this.eyeHeight/3);n.camera=this.camera;const r=n.intersectObjects(this.viewport.scener.intersectsList.meshOfModelList);return 0!==a.length||0!==r.length}start(e){const{position:s,rotation:i={x:0,y:0,z:0},moveSpeed:o,eyeHeight:a,kneeHeight:h,jumpHeight:n,enableClash:r,enableGravity:c}=e;t=this.ssp.getCameraViewpoint(),this.viewport.controls.currentControls.enabled=!1,this.enabled=!0,a&&(this.eyeHeight=a),h&&(this.kneeHeight=h),n&&(this.jumpHeight=n),this.enableClash=null==r||r,this.enableGravity=null==c||c,this.viewport.cameraManager.setCurrentCamera(this.camera),this.camera.position.set(s.x,s.y+this.eyeHeight,s.z),this.camera.rotation.set(i.x,i.y,i.z),o&&(this.moveSpeed=o),this.ssp.signals.cameraChange.dispatch(this.camera.position.clone()),this.viewport.postRender.set("FirstPersonControls",this.update.bind(this)),this.ssp.signals.mouseDown.add(this.onMouseDown),this.ssp.signals.mouseUp.add(this.onMouseUp),this.ssp.signals.mouseMove.add(this.onMouseMove),this.ssp.signals.keyDown.add(this.onKeyDown),this.ssp.signals.keyUp.add(this.onKeyUp)}stop(){t&&this.ssp.setCameraViewpoint(t),this.viewport.controls.currentControls.enabled=!0,this.enabled=!1,this.viewport.cameraManager.setCurrentCamera(this.viewport.cameraManager.getMainCamera()),this.viewport.postRender.delete("FirstPersonControls"),this.ssp.signals.mouseDown.remove(this.onMouseDown),this.ssp.signals.mouseUp.remove(this.onMouseUp),this.ssp.signals.mouseMove.remove(this.onMouseMove),this.ssp.signals.keyDown.remove(this.onKeyDown),this.ssp.signals.keyUp.remove(this.onKeyUp),this.ssp.signals.cameraChange.dispatch(this.camera.position.clone())}update(){const t=performance.now();if(!0===this.enabled){const s=new this.ssp.THREE.Raycaster(this.camera.position,new this.ssp.THREE.Vector3(0,-1,0));s.camera=this.camera;const i=s.intersectObjects(this.viewport.scener.intersectsList.getAll()),o=(t-e)/1e3;if(this.velocity.x=0,this.velocity.z=0,this.enableGravity?this.velocity.y-=1e3*o:this.velocity.y=0,this.state.moveForward&&(this.enableClash&&this.onClashCheck("front")?this.velocity.z=0:this.velocity.z-=400*o),this.state.moveBackward&&(this.enableClash&&this.onClashCheck("back")?this.velocity.z=0:this.velocity.z+=400*o),this.state.moveLeft&&(this.enableClash&&this.onClashCheck("left")?this.velocity.x=0:this.velocity.x+=400*o),this.state.moveRight&&(this.enableClash&&this.onClashCheck("right")?this.velocity.x=0:this.velocity.x-=400*o),this.state.moveUp&&(this.enableClash&&this.onClashCheck("up")?this.velocity.y=0:this.velocity.y+=400*o),this.state.moveDown&&(this.enableClash&&this.onClashCheck("down")?this.velocity.y=0:this.velocity.y-=400*o),this.onMoveRight(-this.velocity.x*o*10*this.moveSpeed),this.onMoveForward(-this.velocity.z*o*10*this.moveSpeed),this.enableGravity?this.camera.position.y+=this.velocity.y*o:this.onMoveUp(-this.velocity.y*o*10*this.moveSpeed),i.length>0){const e=i[0].point.y+this.eyeHeight;this.camera.position.y<e&&(this.camera.position.y=e,this.velocity.y=0,this.state.canJump=!0)}else this.enableGravity?(this.velocity.y-=100*o,this.camera.position.y+=this.velocity.y*o,this.state.canJump=!1):this.velocity.y=0;this.state.canRotate&&this.onRotate()}e=t,this.ssp.signals.cameraChange.dispatch(this.camera.position.clone())}}export{s as default};
1
+ let e=performance.now();let t;class s{constructor(e){this.gravitySpeed=100,this.jumpOffset=0,this.clashCheckDistance=200,this.clashDistance=50,this.searchRadiusFactor=3,this.checkedObjects=null,this.sceneObjectsChanged=()=>{this.checkedObjects=null},this.gravitySearchFactor=3,this.gravityCheckedObjects=null,this.gravityInterObject=null,this.kneeInterObject=null,this.eyeInterObject=null,this.hasUpdated=!1,this.ssp=e,this.viewport=e.viewport,this.camera=this.viewport.cameraManager.createCamera("firstPersonCamera"),this.enabled=!1,this.rotationAngle={min:-Math.PI/2,max:Math.PI/2},this.state={moveForward:!1,moveBackward:!1,moveLeft:!1,moveRight:!1,moveUp:!1,moveDown:!1,canJump:!1,canRotate:!1},this.moveSpeed=100,this.eyeHeight=160,this.kneeHeight=50,this.jumpHeight=110,this.enableClash=!0,this.enableGravity=!0,this.onKeyDown=this.onKeyDown.bind(this),this.onKeyUp=this.onKeyUp.bind(this),this.onMouseDown=this.onMouseDown.bind(this),this.onMouseUp=this.onMouseUp.bind(this),this.onMouseMove=this.onMouseMove.bind(this);const t=this.ssp.THREE,{Vector3:s,Sphere:i,Vector2:a,Box3:n}=t;this.velocity=new s,this.vector=new s,this.movement=new a,this.lastDirection=new s,this.checkedSphere=new i,this.gravityCheckedBox=new n}onKeyDown(e){if(!1!==this.enabled){switch(e.code){case"ArrowUp":case"KeyW":this.state.moveForward=!0;break;case"ArrowLeft":case"KeyA":this.state.moveLeft=!0;break;case"ArrowDown":case"KeyS":this.state.moveBackward=!0;break;case"ArrowRight":case"KeyD":this.state.moveRight=!0;break;case"KeyI":this.state.canRotate=!0,this.movement.x=0,this.movement.y=-10;break;case"KeyJ":this.state.canRotate=!0,this.movement.x=-10,this.movement.y=0;break;case"KeyK":this.state.canRotate=!0,this.movement.x=0,this.movement.y=10;break;case"KeyL":this.state.canRotate=!0,this.movement.x=10,this.movement.y=0;break;case"KeyQ":this.state.moveUp=!0;break;case"KeyE":this.state.moveDown=!0;break;case"Space":!0===this.state.canJump&&(this.jumpOffset+=3*this.jumpHeight),this.state.canJump=!1}(this.jumpOffset||Object.values(this.state).some((e=>e)))&&this.needUpdate()}}onKeyUp(e){if(!1!==this.enabled)switch(e.code){case"ArrowUp":case"KeyW":this.state.moveForward=!1;break;case"ArrowLeft":case"KeyA":this.state.moveLeft=!1;break;case"ArrowDown":case"KeyS":this.state.moveBackward=!1;break;case"ArrowRight":case"KeyD":this.state.moveRight=!1;break;case"KeyI":case"KeyJ":case"KeyK":case"KeyL":this.state.canRotate=!1;break;case"KeyQ":this.state.moveUp=!1;break;case"KeyE":this.state.moveDown=!1}}onMouseDown(e){this.state.canRotate=!0}onMouseUp(e){this.state.canRotate=!1}onMouseMove(e){if(this.enabled&&this.state.canRotate){const t=e.movementY;let s=e.movementX;0===s&&(e.clientX<20?s=-10:e.clientX>this.viewport.interactiveContainer.clientWidth-20&&(s=10)),this.movement.x=s,this.movement.y=t,this.needUpdate()}}clearClashCache(){this.kneeInterObject=null,this.eyeInterObject=null}onClashCheck(e,t){const s=t.length(),i=t.clone().divideScalar(s);i.distanceToSquared(this.lastDirection)>1e-9&&this.clearClashCache(),this.lastDirection=i;const a=this.ssp.THREE,{eyeHeight:n,kneeHeight:o,camera:c,clashCheckDistance:h,clashDistance:r}=this;let{kneeInterObject:l,eyeInterObject:m}=this;if(!l){const t=e.clone().setY(e.y-n+o),s=new this.ssp.THREE.Raycaster(t,i,0,h);s.camera=c;const a=this.getCheckedObjects(t),r=s.intersectObjects(a);this.kneeInterObject=l=r[0]}let d=-s;if(l&&(d+=l.distance,d<r))return!0;if(!m){const t=new a.Raycaster(e,i,0,h);t.camera=c;const s=this.getCheckedObjects(e),n=t.intersectObjects(s);this.eyeInterObject=m=n[0]}let p=-s;if(m){if(p+=m.distance,p<r)return!0;m.distance=p}return l&&(l.distance=d),!1}getCheckedObjects(e){const{clashDistance:t,clashCheckDistance:s,searchRadiusFactor:i}=this,a=s*i,n=this.checkedSphere;let o=this.checkedObjects;if(o){e.distanceTo(n.center)>a&&(o=null)}if(!o){const s=a+t;n.set(e,s);const i=this.viewport.scener.intersectsList.getAll();this.checkedObjects=o=i.filter((e=>{const t=e.geometry;if(!t)return!0;let s=t.boundingBox;return s||(t.computeBoundingBox(),s=t.boundingBox),!s||n.intersectsBox(s)}))}return o}getGravityCheckedObjects(e,t){const s=this.gravityCheckedBox;let i=this.gravityCheckedObjects;if(i){const a=e.clone();a.y=t,s.containsPoint(e)&&s.containsPoint(a)||(i=null)}if(!i){const{eyeHeight:t,clashCheckDistance:a,gravitySearchFactor:n,clashDistance:o}=this,c=a+o,h=new this.ssp.THREE.Vector3(c,c,0),r=e.clone().add(h),l=e.clone().sub(h);l.y-=t*n,s.set(l,r);const m=this.viewport.scener.intersectsList.getAll();this.checkedObjects=i=m.filter((e=>{const t=e.geometry;if(!t)return!0;let i=t.boundingBox;return i||(t.computeBoundingBox(),i=t.boundingBox),!i||s.intersectsBox(i)}))}return i}gravityClashCheck(e,t){var s;let i=this.gravityInterObject;if(i){const{Vector3:t}=this.ssp.THREE,{object:a,face:n}=i,o=null===(s=a.geometry)||void 0===s?void 0:s.getAttribute("position");if(o&&n){let s=i.facePoints;if(!s){const e=a.matrixWorld,{a:c,b:h,c:r}=n,l=new t(o.getX(c),o.getY(c),o.getZ(c));l.applyMatrix4(e),l.setY(0);const m=new t(o.getX(h),o.getY(h),o.getZ(h));m.applyMatrix4(e),m.setY(0);const d=new t(o.getX(r),o.getY(r),o.getZ(r));d.applyMatrix4(e),d.setY(0),i.facePoints=s=[l,m,d]}let c=s[2],h=null;const r=e.clone();r.y=0;s.every((e=>{const t=e.clone();t.sub(c);const s=r.clone();if(s.sub(c),t.cross(s),h){if(t.dot(h)<=0)return!1}return h=t,c=e,!0}))||(this.gravityInterObject=i=null)}}if(!i){const s=new THREE.Raycaster(e,new THREE.Vector3(0,-1,0));s.camera=this.camera;const a=this.getGravityCheckedObjects(e,t),n=s.intersectObjects(a);this.gravityInterObject=i=n[0]}return i}start(e){const{position:s,rotation:i={x:0,y:0,z:0},moveSpeed:a,eyeHeight:n,kneeHeight:o,jumpHeight:c,enableClash:h,enableGravity:r,searchRadiusFactor:l,clashDistance:m,clashCheckDistance:d,gravitySpeed:p,gravitySearchFactor:v}=e;t=this.ssp.getCameraViewpoint(),this.viewport.controls.currentControls.enabled=!1,this.enabled=!0,n&&(this.eyeHeight=n),o&&(this.kneeHeight=o),c&&(this.jumpHeight=c),l&&(this.searchRadiusFactor=l),m&&(this.clashDistance=m),d&&(this.clashCheckDistance=d),p&&(this.gravitySpeed=p),v&&(this.gravitySearchFactor=v),this.enableClash=null==h||h,this.enableGravity=null==r||r,this.viewport.cameraManager.setCurrentCamera(this.camera),this.camera.position.set(s.x,s.y+this.eyeHeight,s.z),this.camera.rotation.set(i.x,i.y,i.z),a&&(this.moveSpeed=a),this.ssp.signals.cameraChange.dispatch(this.camera.position.clone()),this.viewport.postRender.set("FirstPersonControls",this.update.bind(this)),this.ssp.signals.mouseDown.add(this.onMouseDown),this.ssp.signals.mouseUp.add(this.onMouseUp),this.ssp.signals.mouseMove.add(this.onMouseMove),this.ssp.signals.keyDown.add(this.onKeyDown),this.ssp.signals.keyUp.add(this.onKeyUp),this.ssp.signals.objectAdded.add(this.sceneObjectsChanged),this.ssp.signals.objectRemoved.add(this.sceneObjectsChanged)}stop(){t&&this.ssp.setCameraViewpoint(t),this.viewport.controls.currentControls.enabled=!0,this.enabled=!1,this.viewport.cameraManager.setCurrentCamera(this.viewport.cameraManager.getMainCamera()),this.viewport.postRender.delete("FirstPersonControls"),this.ssp.signals.mouseDown.remove(this.onMouseDown),this.ssp.signals.mouseUp.remove(this.onMouseUp),this.ssp.signals.mouseMove.remove(this.onMouseMove),this.ssp.signals.keyDown.remove(this.onKeyDown),this.ssp.signals.keyUp.remove(this.onKeyUp),this.ssp.signals.objectAdded.remove(this.sceneObjectsChanged),this.ssp.signals.objectRemoved.remove(this.sceneObjectsChanged),this.needUpdate()}needUpdate(){this.hasUpdated&&(this.hasUpdated=!1,e=performance.now(),this.ssp.signals.cameraChange.dispatch(this.camera.position.clone()))}update(){if(!this.enabled)return;const t=performance.now(),s=(t-e)/1e3;e=t;const i=this.ssp.THREE,{Vector3:a}=i,n=new i.Vector3,{gravitySpeed:o,moveSpeed:c,state:h,camera:r,eyeHeight:l}=this,{position:m,quaternion:d}=r;let p=0,v=!1;h.moveForward&&(n.z-=c),h.moveBackward&&(n.z+=c),h.moveLeft&&(n.x-=c),h.moveRight&&(n.x+=c),h.moveUp&&(p+=c),h.moveDown&&(p-=c),n.multiplyScalar(s);const y=new i.Euler(0,0,0,"YXZ");y.setFromQuaternion(d);const u=this.movement;if(h.canRotate&&!u.equals(new i.Vector2)){y.x-=.003*this.movement.y,y.y-=.003*this.movement.x;const{max:e,min:t}=this.rotationAngle;y.x=Math.min(e,Math.max(y.x,t)),r.quaternion.setFromEuler(y),v=!0}const g=this.jumpOffset+p*s;if(0!==y.y&&!n.equals(new a)){const e=new i.Quaternion;e.setFromAxisAngle(new a(0,1,0),y.y),n.applyQuaternion(e)}const b=m.clone();!g&&n.equals(new a)||(n.y+=g,this.onClashCheck(m,n)||b.add(n));let w=b.y;if(this.enableGravity){const e=w;w-=o*s,h.canJump=!1;const t=this.gravityClashCheck(b,w);if(t){const e=t.point.y+l;w<e&&(w=e,h.canJump=!0)}e!==w&&this.clearClashCache()}b.y=w;const k=!m.equals(b);k&&r.position.copy(b),this.jumpOffset=0,(k||v)&&this.ssp.signals.cameraChange.dispatch(this.camera.position.clone()),this.hasUpdated=!0}}export{s as default};
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- !function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):(e="undefined"!=typeof globalThis?globalThis:e||self).FirstPersonControlsPlugin=t()}(this,(function(){"use strict";let e=performance.now();let t;return class{constructor(e){this.ssp=e,this.viewport=e.viewport,this.camera=this.viewport.cameraManager.createCamera("firstPersonCamera"),this.enabled=!1,this.rotationAngle={min:0,max:Math.PI},this.state={moveForward:!1,moveBackward:!1,moveLeft:!1,moveRight:!1,moveUp:!1,moveDown:!1,canJump:!1,canRotate:!1},this.moveSpeed=1,this.eyeHeight=160,this.kneeHeight=50,this.jumpHeight=110,this.enableClash=!0,this.enableGravity=!0,this.onKeyDown=this.onKeyDown.bind(this),this.onKeyUp=this.onKeyUp.bind(this),this.onMouseDown=this.onMouseDown.bind(this),this.onMouseUp=this.onMouseUp.bind(this),this.onMouseMove=this.onMouseMove.bind(this),this.velocity=new this.ssp.THREE.Vector3,this.vector=new this.ssp.THREE.Vector3,this.movement=new this.ssp.THREE.Vector2}onKeyDown(e){if(!1!==this.enabled)switch(e.code){case"ArrowUp":case"KeyW":this.state.moveForward=!0;break;case"ArrowLeft":case"KeyA":this.state.moveLeft=!0;break;case"ArrowDown":case"KeyS":this.state.moveBackward=!0;break;case"ArrowRight":case"KeyD":this.state.moveRight=!0;break;case"KeyI":this.state.canRotate=!0,this.movement.x=0,this.movement.y=-10;break;case"KeyJ":this.state.canRotate=!0,this.movement.x=-10,this.movement.y=0;break;case"KeyK":this.state.canRotate=!0,this.movement.x=0,this.movement.y=10;break;case"KeyL":this.state.canRotate=!0,this.movement.x=10,this.movement.y=0;break;case"KeyQ":this.state.moveUp=!0;break;case"KeyE":this.state.moveDown=!0;break;case"Space":!0===this.state.canJump&&(this.velocity.y+=3*this.jumpHeight),this.state.canJump=!1}}onKeyUp(e){if(!1!==this.enabled)switch(e.code){case"ArrowUp":case"KeyW":this.state.moveForward=!1;break;case"ArrowLeft":case"KeyA":this.state.moveLeft=!1;break;case"ArrowDown":case"KeyS":this.state.moveBackward=!1;break;case"ArrowRight":case"KeyD":this.state.moveRight=!1;break;case"KeyI":case"KeyJ":case"KeyK":case"KeyL":this.state.canRotate=!1;break;case"KeyQ":this.state.moveUp=!1;break;case"KeyE":this.state.moveDown=!1}}onMouseDown(e){this.state.canRotate=!0}onMouseUp(e){this.state.canRotate=!1}onMouseMove(e){if(this.enabled&&this.state.canRotate){const t=e.movementY;let s=e.movementX;0===s&&(e.clientX<20?s=-10:e.clientX>this.viewport.interactiveContainer.clientWidth-20&&(s=10)),this.movement.x=s,this.movement.y=t}}onRotate(){const e=new this.ssp.THREE.Euler(0,0,0,"YXZ");e.setFromQuaternion(this.camera.quaternion),e.x-=.003*this.movement.y,e.y-=.003*this.movement.x,e.x=Math.max(Math.PI/2-this.rotationAngle.max,Math.min(Math.PI/2-this.rotationAngle.min,e.x)),this.camera.quaternion.setFromEuler(e)}onMoveForward(e){this.vector.setFromMatrixColumn(this.camera.matrix,0),this.vector.crossVectors(this.camera.up,this.vector),this.camera.position.addScaledVector(this.vector,e)}onMoveRight(e){this.vector.setFromMatrixColumn(this.camera.matrix,0),this.camera.position.addScaledVector(this.vector,e)}onMoveUp(e){this.vector.setFromMatrixColumn(this.camera.matrix,0),this.camera.position.addScaledVector(new this.ssp.THREE.Vector3(0,1,0),e)}onMoveDown(e){this.vector.setFromMatrixColumn(this.camera.matrix,0),this.camera.position.addScaledVector(this.vector,e)}onClashCheck(e){const t=this.camera.getWorldDirection(new this.ssp.THREE.Vector3),s=new this.ssp.THREE.Vector3;"front"===e?s.copy(t.clone()):"back"===e?s.copy(t.clone().applyAxisAngle(new this.ssp.THREE.Vector3(0,1,0),Math.PI)):"left"===e?s.copy(t.clone().applyAxisAngle(new this.ssp.THREE.Vector3(0,1,0),Math.PI/2)):"right"===e?s.copy(t.clone().applyAxisAngle(new this.ssp.THREE.Vector3(0,1,0),-Math.PI/2)):"up"===e?s.copy(new this.ssp.THREE.Vector3(0,1,0)):"down"===e&&s.copy(new this.ssp.THREE.Vector3(0,-1,0));const i=this.camera.position.clone(),o=new this.ssp.THREE.Raycaster(i,s,0,this.eyeHeight/3);o.camera=this.camera;const a=o.intersectObjects(this.viewport.scener.intersectsList.meshOfModelList),h=i.clone().setY(i.y-this.eyeHeight+this.kneeHeight),n=new this.ssp.THREE.Raycaster(h,s,0,this.eyeHeight/3);n.camera=this.camera;const r=n.intersectObjects(this.viewport.scener.intersectsList.meshOfModelList);return 0!==a.length||0!==r.length}start(e){const{position:s,rotation:i={x:0,y:0,z:0},moveSpeed:o,eyeHeight:a,kneeHeight:h,jumpHeight:n,enableClash:r,enableGravity:c}=e;t=this.ssp.getCameraViewpoint(),this.viewport.controls.currentControls.enabled=!1,this.enabled=!0,a&&(this.eyeHeight=a),h&&(this.kneeHeight=h),n&&(this.jumpHeight=n),this.enableClash=null==r||r,this.enableGravity=null==c||c,this.viewport.cameraManager.setCurrentCamera(this.camera),this.camera.position.set(s.x,s.y+this.eyeHeight,s.z),this.camera.rotation.set(i.x,i.y,i.z),o&&(this.moveSpeed=o),this.ssp.signals.cameraChange.dispatch(this.camera.position.clone()),this.viewport.postRender.set("FirstPersonControls",this.update.bind(this)),this.ssp.signals.mouseDown.add(this.onMouseDown),this.ssp.signals.mouseUp.add(this.onMouseUp),this.ssp.signals.mouseMove.add(this.onMouseMove),this.ssp.signals.keyDown.add(this.onKeyDown),this.ssp.signals.keyUp.add(this.onKeyUp)}stop(){t&&this.ssp.setCameraViewpoint(t),this.viewport.controls.currentControls.enabled=!0,this.enabled=!1,this.viewport.cameraManager.setCurrentCamera(this.viewport.cameraManager.getMainCamera()),this.viewport.postRender.delete("FirstPersonControls"),this.ssp.signals.mouseDown.remove(this.onMouseDown),this.ssp.signals.mouseUp.remove(this.onMouseUp),this.ssp.signals.mouseMove.remove(this.onMouseMove),this.ssp.signals.keyDown.remove(this.onKeyDown),this.ssp.signals.keyUp.remove(this.onKeyUp),this.ssp.signals.cameraChange.dispatch(this.camera.position.clone())}update(){const t=performance.now();if(!0===this.enabled){const s=new this.ssp.THREE.Raycaster(this.camera.position,new this.ssp.THREE.Vector3(0,-1,0));s.camera=this.camera;const i=s.intersectObjects(this.viewport.scener.intersectsList.getAll()),o=(t-e)/1e3;if(this.velocity.x=0,this.velocity.z=0,this.enableGravity?this.velocity.y-=1e3*o:this.velocity.y=0,this.state.moveForward&&(this.enableClash&&this.onClashCheck("front")?this.velocity.z=0:this.velocity.z-=400*o),this.state.moveBackward&&(this.enableClash&&this.onClashCheck("back")?this.velocity.z=0:this.velocity.z+=400*o),this.state.moveLeft&&(this.enableClash&&this.onClashCheck("left")?this.velocity.x=0:this.velocity.x+=400*o),this.state.moveRight&&(this.enableClash&&this.onClashCheck("right")?this.velocity.x=0:this.velocity.x-=400*o),this.state.moveUp&&(this.enableClash&&this.onClashCheck("up")?this.velocity.y=0:this.velocity.y+=400*o),this.state.moveDown&&(this.enableClash&&this.onClashCheck("down")?this.velocity.y=0:this.velocity.y-=400*o),this.onMoveRight(-this.velocity.x*o*10*this.moveSpeed),this.onMoveForward(-this.velocity.z*o*10*this.moveSpeed),this.enableGravity?this.camera.position.y+=this.velocity.y*o:this.onMoveUp(-this.velocity.y*o*10*this.moveSpeed),i.length>0){const e=i[0].point.y+this.eyeHeight;this.camera.position.y<e&&(this.camera.position.y=e,this.velocity.y=0,this.state.canJump=!0)}else this.enableGravity?(this.velocity.y-=100*o,this.camera.position.y+=this.velocity.y*o,this.state.canJump=!1):this.velocity.y=0;this.state.canRotate&&this.onRotate()}e=t,this.ssp.signals.cameraChange.dispatch(this.camera.position.clone())}}}));
1
+ !function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):(e="undefined"!=typeof globalThis?globalThis:e||self).FirstPersonControlsPlugin=t()}(this,(function(){"use strict";let e=performance.now();let t;return class{constructor(e){this.gravitySpeed=100,this.jumpOffset=0,this.clashCheckDistance=200,this.clashDistance=50,this.searchRadiusFactor=3,this.checkedObjects=null,this.sceneObjectsChanged=()=>{this.checkedObjects=null},this.gravitySearchFactor=3,this.gravityCheckedObjects=null,this.gravityInterObject=null,this.kneeInterObject=null,this.eyeInterObject=null,this.hasUpdated=!1,this.ssp=e,this.viewport=e.viewport,this.camera=this.viewport.cameraManager.createCamera("firstPersonCamera"),this.enabled=!1,this.rotationAngle={min:-Math.PI/2,max:Math.PI/2},this.state={moveForward:!1,moveBackward:!1,moveLeft:!1,moveRight:!1,moveUp:!1,moveDown:!1,canJump:!1,canRotate:!1},this.moveSpeed=100,this.eyeHeight=160,this.kneeHeight=50,this.jumpHeight=110,this.enableClash=!0,this.enableGravity=!0,this.onKeyDown=this.onKeyDown.bind(this),this.onKeyUp=this.onKeyUp.bind(this),this.onMouseDown=this.onMouseDown.bind(this),this.onMouseUp=this.onMouseUp.bind(this),this.onMouseMove=this.onMouseMove.bind(this);const t=this.ssp.THREE,{Vector3:s,Sphere:i,Vector2:a,Box3:n}=t;this.velocity=new s,this.vector=new s,this.movement=new a,this.lastDirection=new s,this.checkedSphere=new i,this.gravityCheckedBox=new n}onKeyDown(e){if(!1!==this.enabled){switch(e.code){case"ArrowUp":case"KeyW":this.state.moveForward=!0;break;case"ArrowLeft":case"KeyA":this.state.moveLeft=!0;break;case"ArrowDown":case"KeyS":this.state.moveBackward=!0;break;case"ArrowRight":case"KeyD":this.state.moveRight=!0;break;case"KeyI":this.state.canRotate=!0,this.movement.x=0,this.movement.y=-10;break;case"KeyJ":this.state.canRotate=!0,this.movement.x=-10,this.movement.y=0;break;case"KeyK":this.state.canRotate=!0,this.movement.x=0,this.movement.y=10;break;case"KeyL":this.state.canRotate=!0,this.movement.x=10,this.movement.y=0;break;case"KeyQ":this.state.moveUp=!0;break;case"KeyE":this.state.moveDown=!0;break;case"Space":!0===this.state.canJump&&(this.jumpOffset+=3*this.jumpHeight),this.state.canJump=!1}(this.jumpOffset||Object.values(this.state).some((e=>e)))&&this.needUpdate()}}onKeyUp(e){if(!1!==this.enabled)switch(e.code){case"ArrowUp":case"KeyW":this.state.moveForward=!1;break;case"ArrowLeft":case"KeyA":this.state.moveLeft=!1;break;case"ArrowDown":case"KeyS":this.state.moveBackward=!1;break;case"ArrowRight":case"KeyD":this.state.moveRight=!1;break;case"KeyI":case"KeyJ":case"KeyK":case"KeyL":this.state.canRotate=!1;break;case"KeyQ":this.state.moveUp=!1;break;case"KeyE":this.state.moveDown=!1}}onMouseDown(e){this.state.canRotate=!0}onMouseUp(e){this.state.canRotate=!1}onMouseMove(e){if(this.enabled&&this.state.canRotate){const t=e.movementY;let s=e.movementX;0===s&&(e.clientX<20?s=-10:e.clientX>this.viewport.interactiveContainer.clientWidth-20&&(s=10)),this.movement.x=s,this.movement.y=t,this.needUpdate()}}clearClashCache(){this.kneeInterObject=null,this.eyeInterObject=null}onClashCheck(e,t){const s=t.length(),i=t.clone().divideScalar(s);i.distanceToSquared(this.lastDirection)>1e-9&&this.clearClashCache(),this.lastDirection=i;const a=this.ssp.THREE,{eyeHeight:n,kneeHeight:o,camera:c,clashCheckDistance:h,clashDistance:r}=this;let{kneeInterObject:l,eyeInterObject:m}=this;if(!l){const t=e.clone().setY(e.y-n+o),s=new this.ssp.THREE.Raycaster(t,i,0,h);s.camera=c;const a=this.getCheckedObjects(t),r=s.intersectObjects(a);this.kneeInterObject=l=r[0]}let d=-s;if(l&&(d+=l.distance,d<r))return!0;if(!m){const t=new a.Raycaster(e,i,0,h);t.camera=c;const s=this.getCheckedObjects(e),n=t.intersectObjects(s);this.eyeInterObject=m=n[0]}let p=-s;if(m){if(p+=m.distance,p<r)return!0;m.distance=p}return l&&(l.distance=d),!1}getCheckedObjects(e){const{clashDistance:t,clashCheckDistance:s,searchRadiusFactor:i}=this,a=s*i,n=this.checkedSphere;let o=this.checkedObjects;if(o){e.distanceTo(n.center)>a&&(o=null)}if(!o){const s=a+t;n.set(e,s);const i=this.viewport.scener.intersectsList.getAll();this.checkedObjects=o=i.filter((e=>{const t=e.geometry;if(!t)return!0;let s=t.boundingBox;return s||(t.computeBoundingBox(),s=t.boundingBox),!s||n.intersectsBox(s)}))}return o}getGravityCheckedObjects(e,t){const s=this.gravityCheckedBox;let i=this.gravityCheckedObjects;if(i){const a=e.clone();a.y=t,s.containsPoint(e)&&s.containsPoint(a)||(i=null)}if(!i){const{eyeHeight:t,clashCheckDistance:a,gravitySearchFactor:n,clashDistance:o}=this,c=a+o,h=new this.ssp.THREE.Vector3(c,c,0),r=e.clone().add(h),l=e.clone().sub(h);l.y-=t*n,s.set(l,r);const m=this.viewport.scener.intersectsList.getAll();this.checkedObjects=i=m.filter((e=>{const t=e.geometry;if(!t)return!0;let i=t.boundingBox;return i||(t.computeBoundingBox(),i=t.boundingBox),!i||s.intersectsBox(i)}))}return i}gravityClashCheck(e,t){var s;let i=this.gravityInterObject;if(i){const{Vector3:t}=this.ssp.THREE,{object:a,face:n}=i,o=null===(s=a.geometry)||void 0===s?void 0:s.getAttribute("position");if(o&&n){let s=i.facePoints;if(!s){const e=a.matrixWorld,{a:c,b:h,c:r}=n,l=new t(o.getX(c),o.getY(c),o.getZ(c));l.applyMatrix4(e),l.setY(0);const m=new t(o.getX(h),o.getY(h),o.getZ(h));m.applyMatrix4(e),m.setY(0);const d=new t(o.getX(r),o.getY(r),o.getZ(r));d.applyMatrix4(e),d.setY(0),i.facePoints=s=[l,m,d]}let c=s[2],h=null;const r=e.clone();r.y=0;s.every((e=>{const t=e.clone();t.sub(c);const s=r.clone();if(s.sub(c),t.cross(s),h){if(t.dot(h)<=0)return!1}return h=t,c=e,!0}))||(this.gravityInterObject=i=null)}}if(!i){const s=new THREE.Raycaster(e,new THREE.Vector3(0,-1,0));s.camera=this.camera;const a=this.getGravityCheckedObjects(e,t),n=s.intersectObjects(a);this.gravityInterObject=i=n[0]}return i}start(e){const{position:s,rotation:i={x:0,y:0,z:0},moveSpeed:a,eyeHeight:n,kneeHeight:o,jumpHeight:c,enableClash:h,enableGravity:r,searchRadiusFactor:l,clashDistance:m,clashCheckDistance:d,gravitySpeed:p,gravitySearchFactor:u}=e;t=this.ssp.getCameraViewpoint(),this.viewport.controls.currentControls.enabled=!1,this.enabled=!0,n&&(this.eyeHeight=n),o&&(this.kneeHeight=o),c&&(this.jumpHeight=c),l&&(this.searchRadiusFactor=l),m&&(this.clashDistance=m),d&&(this.clashCheckDistance=d),p&&(this.gravitySpeed=p),u&&(this.gravitySearchFactor=u),this.enableClash=null==h||h,this.enableGravity=null==r||r,this.viewport.cameraManager.setCurrentCamera(this.camera),this.camera.position.set(s.x,s.y+this.eyeHeight,s.z),this.camera.rotation.set(i.x,i.y,i.z),a&&(this.moveSpeed=a),this.ssp.signals.cameraChange.dispatch(this.camera.position.clone()),this.viewport.postRender.set("FirstPersonControls",this.update.bind(this)),this.ssp.signals.mouseDown.add(this.onMouseDown),this.ssp.signals.mouseUp.add(this.onMouseUp),this.ssp.signals.mouseMove.add(this.onMouseMove),this.ssp.signals.keyDown.add(this.onKeyDown),this.ssp.signals.keyUp.add(this.onKeyUp),this.ssp.signals.objectAdded.add(this.sceneObjectsChanged),this.ssp.signals.objectRemoved.add(this.sceneObjectsChanged)}stop(){t&&this.ssp.setCameraViewpoint(t),this.viewport.controls.currentControls.enabled=!0,this.enabled=!1,this.viewport.cameraManager.setCurrentCamera(this.viewport.cameraManager.getMainCamera()),this.viewport.postRender.delete("FirstPersonControls"),this.ssp.signals.mouseDown.remove(this.onMouseDown),this.ssp.signals.mouseUp.remove(this.onMouseUp),this.ssp.signals.mouseMove.remove(this.onMouseMove),this.ssp.signals.keyDown.remove(this.onKeyDown),this.ssp.signals.keyUp.remove(this.onKeyUp),this.ssp.signals.objectAdded.remove(this.sceneObjectsChanged),this.ssp.signals.objectRemoved.remove(this.sceneObjectsChanged),this.needUpdate()}needUpdate(){this.hasUpdated&&(this.hasUpdated=!1,e=performance.now(),this.ssp.signals.cameraChange.dispatch(this.camera.position.clone()))}update(){if(!this.enabled)return;const t=performance.now(),s=(t-e)/1e3;e=t;const i=this.ssp.THREE,{Vector3:a}=i,n=new i.Vector3,{gravitySpeed:o,moveSpeed:c,state:h,camera:r,eyeHeight:l}=this,{position:m,quaternion:d}=r;let p=0,u=!1;h.moveForward&&(n.z-=c),h.moveBackward&&(n.z+=c),h.moveLeft&&(n.x-=c),h.moveRight&&(n.x+=c),h.moveUp&&(p+=c),h.moveDown&&(p-=c),n.multiplyScalar(s);const y=new i.Euler(0,0,0,"YXZ");y.setFromQuaternion(d);const v=this.movement;if(h.canRotate&&!v.equals(new i.Vector2)){y.x-=.003*this.movement.y,y.y-=.003*this.movement.x;const{max:e,min:t}=this.rotationAngle;y.x=Math.min(e,Math.max(y.x,t)),r.quaternion.setFromEuler(y),u=!0}const g=this.jumpOffset+p*s;if(0!==y.y&&!n.equals(new a)){const e=new i.Quaternion;e.setFromAxisAngle(new a(0,1,0),y.y),n.applyQuaternion(e)}const b=m.clone();!g&&n.equals(new a)||(n.y+=g,this.onClashCheck(m,n)||b.add(n));let w=b.y;if(this.enableGravity){const e=w;w-=o*s,h.canJump=!1;const t=this.gravityClashCheck(b,w);if(t){const e=t.point.y+l;w<e&&(w=e,h.canJump=!0)}e!==w&&this.clearClashCache()}b.y=w;const f=!m.equals(b);f&&r.position.copy(b),this.jumpOffset=0,(f||u)&&this.ssp.signals.cameraChange.dispatch(this.camera.position.clone()),this.hasUpdated=!0}}}));
@@ -0,0 +1,28 @@
1
+ # 2022年9月13日
2
+ ## 原来的问题
3
+ 原来存在以下问题
4
+ + 在无操作下仍然消耗大量计算资源
5
+ + 操作时卡顿更严重
6
+ + 只能对上、下、左、右、前、后 几个固定方向进行碰撞检测,无法对任意方向进行碰撞检测
7
+ + 在卡顿较严重的情况下,平移会一下子跳很远
8
+
9
+ ## 优化
10
+ 现在做了以下优化
11
+ _以下关于帧率的测试是在 个人性能较差一点的 Mac 笔该本上进行的测试,仅作参考_
12
+ + 在无操作下不消耗资源,使其从原来的 18左右 率率优化到 508 左右的帧率,提高到了3倍
13
+ + 在旋转操作时,使其从原来的 10 多帧率,优化到了 30 - 40 帧,提高到了2倍多
14
+ + 在平移操作时,优化到了 30 - 40 多帧(由于原来的卡顿较严重,暂时测不出原来的帧率)
15
+
16
+ ## 新增特性
17
+ 现在增加了以下特性
18
+ + 可以对任意方向进行碰撞检测,不只是上、下、左、右、前、后 几个固定方向
19
+ + 即使在卡顿较严重的情况下,平移也不会一下子跳很远
20
+ + 自定义碰撞检测范围、重力检测范围 等,用以实现根据具体场景来个性化优化性能
21
+
22
+
23
+ ## 优化手段
24
+ + 合并运算:原来需要多次运算才能达到的目的,现在使用数学的方法,合并成了一次运算
25
+ + 缩小碰撞检测范围
26
+ + 优化碰撞检测逻辑
27
+ + 缩小重力检测范围
28
+ + 优化重力检测逻辑
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@soonspacejs/plugin-first-person-controls",
3
3
  "pluginName": "FirstPersonControlsPlugin",
4
- "version": "2.5.1",
4
+ "version": "2.5.3",
5
5
  "description": "FirstPersonControls plugin for SoonSpace.js",
6
6
  "main": "dist/index.js",
7
7
  "module": "dist/index.esm.js",
@@ -13,5 +13,5 @@
13
13
  ],
14
14
  "author": "xuek",
15
15
  "license": "UNLICENSED",
16
- "gitHead": "49430ab52ae6ad1f3e6c4aab55a14e2564d775ac"
16
+ "gitHead": "2a5623644a00cc610219341317a92a0427d850b0"
17
17
  }