@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.
- package/dist/physics/index.d.ts +4 -0
- package/dist/physics/index.js +35 -27
- package/dist/physics/world.d.ts +8 -2
- package/dist/physics/world.js +75 -37
- package/package.json +1 -1
package/dist/physics/index.d.ts
CHANGED
|
@@ -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
|
}
|
package/dist/physics/index.js
CHANGED
|
@@ -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
|
-
|
|
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
|
}
|
package/dist/physics/world.d.ts
CHANGED
|
@@ -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
|
|
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
|
}
|
package/dist/physics/world.js
CHANGED
|
@@ -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
|
-
|
|
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 (
|
|
34
|
+
if (!isStatic) {
|
|
21
35
|
object.parent = null;
|
|
22
36
|
object.updateMatrixWorld(true);
|
|
23
37
|
}
|
|
24
38
|
const geometry = new StaticGeometryGenerator(object).generate();
|
|
25
|
-
|
|
26
|
-
if (
|
|
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
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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.
|
|
58
|
+
this.bodies = this.bodies.filter((entry) => entry.object != object);
|
|
47
59
|
}
|
|
48
|
-
computeMatrix({
|
|
49
|
-
if (
|
|
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.
|
|
63
|
-
|
|
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.
|
|
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)) {
|