@pmndrs/viverse 0.1.14 → 0.1.16

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.
@@ -38,6 +38,9 @@ export declare class BvhCharacterPhysics {
38
38
  private readonly stateVelocity;
39
39
  readonly inputVelocity: Vector3;
40
40
  private notGroundedSeconds;
41
+ private readonly segment;
42
+ private readonly aabbox;
43
+ private radius;
41
44
  get isGrounded(): boolean;
42
45
  constructor(character: Object3D, world: BvhPhysicsWorld);
43
46
  applyVelocity(velocity: Vector3): void;
@@ -45,6 +48,7 @@ export declare class BvhCharacterPhysics {
45
48
  * @param delta in seconds
46
49
  */
47
50
  update(fullDelta: number, options?: BvhCharacterPhysicsOptions): void;
51
+ private updateBoundingShapes;
48
52
  destroy(): void;
49
53
  shapecastCapsule(position: Vector3, maxGroundSlope: number, options: Exclude<BvhCharacterPhysicsOptions, boolean>): boolean;
50
54
  }
@@ -1,10 +1,8 @@
1
1
  import { Box3, Line3, Matrix4, Vector3 } from 'three';
2
2
  //for this is a kinematic character controller
3
- //helper variables
4
- const aabbox = new Box3();
5
- const segment = new Line3();
6
3
  const triPoint = new Vector3();
7
4
  const capsulePoint = new Vector3();
5
+ const centerHelper = new Vector3();
8
6
  const collisionFreePosition = new Vector3();
9
7
  const position = new Vector3();
10
8
  const invertedParentMatrix = new Matrix4();
@@ -19,6 +17,9 @@ export class BvhCharacterPhysics {
19
17
  stateVelocity = new Vector3();
20
18
  inputVelocity = new Vector3();
21
19
  notGroundedSeconds = 0;
20
+ segment = new Line3();
21
+ aabbox = new Box3();
22
+ radius = 0;
22
23
  get isGrounded() {
23
24
  return this.notGroundedSeconds < 0.2;
24
25
  }
@@ -82,49 +83,56 @@ export class BvhCharacterPhysics {
82
83
  this.stateVelocity.set(0, (options.gravity ?? -20) * 0.01, 0);
83
84
  }
84
85
  }
86
+ this.updateBoundingShapes(options);
87
+ centerHelper.addVectors(this.segment.start, this.segment.end).multiplyScalar(0.5);
88
+ this.world.updateSensors(centerHelper, (bounds) => bounds.intersectsBox(this.aabbox), (triangle) => triangle.closestPointToSegment(this.segment) < this.radius);
89
+ }
90
+ updateBoundingShapes(options) {
91
+ //compute the bounding capsule and bounding box
92
+ this.radius = options.capsuleRadius ?? 0.4;
93
+ const height = options.capsuleHeight ?? 1.7;
94
+ this.segment.start.copy(position);
95
+ this.segment.start.y += this.radius;
96
+ this.segment.end.copy(position);
97
+ this.segment.end.y += height - this.radius;
98
+ this.aabbox.makeEmpty();
99
+ this.aabbox.expandByPoint(this.segment.start);
100
+ this.aabbox.expandByPoint(this.segment.end);
101
+ this.aabbox.min.addScalar(-this.radius);
102
+ this.aabbox.max.addScalar(this.radius);
85
103
  }
86
104
  destroy() {
87
105
  this.destroyed = true;
88
106
  }
89
107
  shapecastCapsule(position, maxGroundSlope, options) {
90
- const radius = options.capsuleRadius ?? 0.4;
91
- const height = options.capsuleHeight ?? 1.7;
92
- segment.start.copy(position);
93
- segment.start.y += radius;
94
- segment.end.copy(position);
95
- segment.end.y += height - radius;
96
- aabbox.makeEmpty();
97
- aabbox.expandByPoint(segment.start);
98
- aabbox.expandByPoint(segment.end);
99
- aabbox.min.addScalar(-radius);
100
- aabbox.max.addScalar(radius);
108
+ this.updateBoundingShapes(options);
101
109
  let grounded = false;
102
- this.world.shapecast((bounds) => bounds.intersectsBox(aabbox), (tri) => {
110
+ this.world.shapecast((bounds) => bounds.intersectsBox(this.aabbox), (tri) => {
103
111
  // Use your existing triangle vs segment closestPointToSegment
104
- const distance = tri.closestPointToSegment(segment, triPoint, capsulePoint);
112
+ const distance = tri.closestPointToSegment(this.segment, triPoint, capsulePoint);
105
113
  if (distance === 0) {
106
- const isCloserToSegmentStart = capsulePoint.distanceTo(segment.start) < capsulePoint.distanceTo(segment.end);
114
+ const isCloserToSegmentStart = capsulePoint.distanceTo(this.segment.start) < capsulePoint.distanceTo(this.segment.end);
107
115
  if (isCloserToSegmentStart) {
108
116
  grounded = true;
109
117
  }
110
- const scaledDirection = capsulePoint.sub(isCloserToSegmentStart ? segment.start : segment.end);
111
- scaledDirection.y += radius;
112
- segment.start.add(scaledDirection);
113
- segment.end.add(scaledDirection);
118
+ const scaledDirection = capsulePoint.sub(isCloserToSegmentStart ? this.segment.start : this.segment.end);
119
+ scaledDirection.y += this.radius;
120
+ this.segment.start.add(scaledDirection);
121
+ this.segment.end.add(scaledDirection);
114
122
  }
115
- else if (distance < radius) {
116
- const depthInsideCapsule = radius - distance;
123
+ else if (distance < this.radius) {
124
+ const depthInsideCapsule = this.radius - distance;
117
125
  const direction = capsulePoint.sub(triPoint).divideScalar(distance);
118
126
  const slope = Math.tan(Math.acos(direction.dot(YAxis)));
119
127
  if (direction.y > 0 && slope <= maxGroundSlope) {
120
128
  grounded = true;
121
129
  }
122
- segment.start.addScaledVector(direction, depthInsideCapsule);
123
- segment.end.addScaledVector(direction, depthInsideCapsule);
130
+ this.segment.start.addScaledVector(direction, depthInsideCapsule);
131
+ this.segment.end.addScaledVector(direction, depthInsideCapsule);
124
132
  }
125
133
  });
126
- position.copy(segment.start);
127
- position.y -= radius;
134
+ position.copy(this.segment.start);
135
+ position.y -= this.radius;
128
136
  return grounded;
129
137
  }
130
138
  }
@@ -1,14 +1,20 @@
1
- import { Box3, Object3D, Ray } from 'three';
1
+ import { Box3, Object3D, Ray, Vector3 } from 'three';
2
2
  import { ExtendedTriangle } from 'three-mesh-bvh';
3
3
  export declare class BvhPhysicsWorld {
4
- private map;
4
+ private bodies;
5
+ private sensors;
5
6
  /**
6
7
  * @deprecated use addBody(object, false) instead
7
8
  */
8
9
  addFixedBody(object: Object3D): void;
10
+ addSensor(object: Object3D, isStatic: boolean, onIntersectedChanged: (intersected: boolean) => void): void;
11
+ removeSensor(object: Object3D): void;
9
12
  addBody(object: Object3D, kinematic: boolean): void;
13
+ private computeBvhEntries;
10
14
  removeBody(object: Object3D): void;
11
15
  private computeMatrix;
16
+ updateSensors(playerCenter: Vector3, intersectsBounds: (box: Box3) => boolean, intersectsTriangle: (triangle: ExtendedTriangle) => boolean): void;
12
17
  shapecast(intersectsBounds: (box: Box3) => boolean, intersectsTriangle: (triangle: ExtendedTriangle) => void): void;
18
+ private shapecastEntry;
13
19
  raycast(ray: Ray, far: number): number | undefined;
14
20
  }
@@ -1,4 +1,4 @@
1
- import { Box3, InstancedMesh, Matrix4, Ray, Vector3 } from 'three';
1
+ import { Box3, DoubleSide, InstancedMesh, Matrix4, Ray, Vector3 } from 'three';
2
2
  import { computeBoundsTree, StaticGeometryGenerator, ExtendedTriangle } from 'three-mesh-bvh';
3
3
  const rayHelper = new Ray();
4
4
  const farPointHelper = new Vector3();
@@ -6,47 +6,59 @@ const boxHelper = new Box3();
6
6
  const triangleHelper = new ExtendedTriangle();
7
7
  const matrixHelper = new Matrix4();
8
8
  export class BvhPhysicsWorld {
9
- map = [];
9
+ bodies = [];
10
+ sensors = [];
10
11
  /**
11
12
  * @deprecated use addBody(object, false) instead
12
13
  */
13
14
  addFixedBody(object) {
14
15
  this.addBody(object, false);
15
16
  }
17
+ addSensor(object, isStatic, onIntersectedChanged) {
18
+ this.sensors.push(...this.computeBvhEntries(object, isStatic).map((entry) => ({
19
+ ...entry,
20
+ onIntersectedChanged,
21
+ intersected: false,
22
+ })));
23
+ }
24
+ removeSensor(object) {
25
+ this.sensors = this.sensors.filter((entry) => entry.object != object);
26
+ }
16
27
  addBody(object, kinematic) {
28
+ this.bodies.push(...this.computeBvhEntries(object, !kinematic));
29
+ }
30
+ computeBvhEntries(object, isStatic) {
17
31
  object.updateWorldMatrix(true, true);
18
32
  if (!(object instanceof InstancedMesh)) {
19
33
  const parent = object.parent;
20
- if (kinematic) {
34
+ if (!isStatic) {
21
35
  object.parent = null;
22
36
  object.updateMatrixWorld(true);
23
37
  }
24
38
  const geometry = new StaticGeometryGenerator(object).generate();
25
- this.map.push({ object, bvh: computeBoundsTree.apply(geometry), kinematic });
26
- if (kinematic) {
39
+ const bvh = computeBoundsTree.apply(geometry);
40
+ if (!isStatic) {
27
41
  object.parent = parent;
28
42
  object.updateMatrixWorld(true);
29
43
  }
30
- return;
44
+ return [{ object, bvh, isStatic }];
31
45
  }
32
46
  if (object.children.length > 0) {
33
47
  throw new Error(`cannot add InstancedMesh with children`);
34
48
  }
35
49
  const bvh = computeBoundsTree.apply(object.geometry);
36
- for (let i = 0; i < object.instanceMatrix.count; i++) {
37
- this.map.push({
38
- object,
39
- bvh,
40
- instanceIndex: i,
41
- kinematic,
42
- });
43
- }
50
+ return new Array(object.instanceMatrix).fill(undefined).map((_, i) => ({
51
+ object,
52
+ bvh,
53
+ instanceIndex: i,
54
+ isStatic,
55
+ }));
44
56
  }
45
57
  removeBody(object) {
46
- this.map = this.map.filter((entry) => entry.object != object);
58
+ this.bodies = this.bodies.filter((entry) => entry.object != object);
47
59
  }
48
- computeMatrix({ kinematic, object, instanceIndex }, target) {
49
- if (!kinematic && instanceIndex == null) {
60
+ computeMatrix({ isStatic, object, instanceIndex }, target) {
61
+ if (isStatic && instanceIndex == null) {
50
62
  return false;
51
63
  }
52
64
  if (instanceIndex == null) {
@@ -58,31 +70,57 @@ export class BvhPhysicsWorld {
58
70
  target.premultiply(object.matrixWorld);
59
71
  return true;
60
72
  }
73
+ updateSensors(playerCenter, intersectsBounds, intersectsTriangle) {
74
+ for (const entry of this.sensors) {
75
+ //check surface intersection
76
+ let intersected = this.shapecastEntry(entry, intersectsBounds, intersectsTriangle);
77
+ if (!intersected) {
78
+ //check if we are entirely inside
79
+ rayHelper.origin.copy(playerCenter);
80
+ rayHelper.direction.set(0, -1, 0);
81
+ if (this.computeMatrix(entry, matrixHelper)) {
82
+ matrixHelper.invert();
83
+ rayHelper.applyMatrix4(matrixHelper);
84
+ }
85
+ intersected = entry.bvh.raycast(rayHelper, DoubleSide).length % 2 == 1;
86
+ }
87
+ if (entry.intersected === intersected) {
88
+ continue;
89
+ }
90
+ entry.onIntersectedChanged(intersected);
91
+ entry.intersected = intersected;
92
+ }
93
+ }
61
94
  shapecast(intersectsBounds, intersectsTriangle) {
62
- for (const entry of this.map) {
63
- entry.bvh.shapecast({
64
- intersectsBounds: (box) => {
65
- boxHelper.copy(box);
66
- if (this.computeMatrix(entry, matrixHelper)) {
67
- boxHelper.applyMatrix4(matrixHelper);
68
- }
69
- return intersectsBounds(boxHelper);
70
- },
71
- intersectsTriangle: (triangle) => {
72
- triangleHelper.copy(triangle);
73
- if (this.computeMatrix(entry, matrixHelper)) {
74
- triangleHelper.a.applyMatrix4(matrixHelper);
75
- triangleHelper.b.applyMatrix4(matrixHelper);
76
- triangleHelper.c.applyMatrix4(matrixHelper);
77
- }
78
- intersectsTriangle(triangleHelper);
79
- },
80
- });
95
+ for (const entry of this.bodies) {
96
+ this.shapecastEntry(entry, intersectsBounds, intersectsTriangle);
81
97
  }
82
98
  }
99
+ shapecastEntry(entry, intersectsBounds, intersectsTriangle) {
100
+ return entry.bvh.shapecast({
101
+ intersectsBounds: (box) => {
102
+ boxHelper.copy(box);
103
+ if (this.computeMatrix(entry, matrixHelper)) {
104
+ boxHelper.applyMatrix4(matrixHelper);
105
+ }
106
+ return intersectsBounds(boxHelper);
107
+ },
108
+ intersectsTriangle: (triangle) => {
109
+ triangleHelper.copy(triangle);
110
+ if (this.computeMatrix(entry, matrixHelper)) {
111
+ triangleHelper.a.applyMatrix4(matrixHelper);
112
+ triangleHelper.b.applyMatrix4(matrixHelper);
113
+ triangleHelper.c.applyMatrix4(matrixHelper);
114
+ }
115
+ if (intersectsTriangle(triangleHelper) === true) {
116
+ return true;
117
+ }
118
+ },
119
+ });
120
+ }
83
121
  raycast(ray, far) {
84
122
  let result;
85
- for (const entry of this.map.values()) {
123
+ for (const entry of this.bodies.values()) {
86
124
  rayHelper.copy(ray);
87
125
  let farHelper = far;
88
126
  if (this.computeMatrix(entry, matrixHelper)) {
package/package.json CHANGED
@@ -21,7 +21,7 @@
21
21
  "peerDependencies": {
22
22
  "three": "*"
23
23
  },
24
- "version": "0.1.14",
24
+ "version": "0.1.16",
25
25
  "type": "module",
26
26
  "dependencies": {
27
27
  "@pixiv/three-vrm": "^3.4.2",