action-engine-js 1.0.0
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/LICENSE +45 -0
- package/README.md +348 -0
- package/actionengine/3rdparty/goblin/goblin.js +9609 -0
- package/actionengine/3rdparty/goblin/goblin.min.js +5 -0
- package/actionengine/camera/actioncamera.js +90 -0
- package/actionengine/camera/cameracollisionhandler.js +69 -0
- package/actionengine/character/actioncharacter.js +360 -0
- package/actionengine/character/actioncharacter3D.js +61 -0
- package/actionengine/core/app.js +430 -0
- package/actionengine/debug/basedebugpanel.js +858 -0
- package/actionengine/display/canvasmanager.js +75 -0
- package/actionengine/display/gl/programmanager.js +570 -0
- package/actionengine/display/gl/shaders/lineshader.js +118 -0
- package/actionengine/display/gl/shaders/objectshader.js +1756 -0
- package/actionengine/display/gl/shaders/particleshader.js +43 -0
- package/actionengine/display/gl/shaders/shadowshader.js +319 -0
- package/actionengine/display/gl/shaders/spriteshader.js +100 -0
- package/actionengine/display/gl/shaders/watershader.js +67 -0
- package/actionengine/display/graphics/actionmodel3D.js +191 -0
- package/actionengine/display/graphics/actionsprite3D.js +230 -0
- package/actionengine/display/graphics/lighting/actiondirectionalshadowlight.js +864 -0
- package/actionengine/display/graphics/lighting/actionlight.js +211 -0
- package/actionengine/display/graphics/lighting/actionomnidirectionalshadowlight.js +862 -0
- package/actionengine/display/graphics/lighting/lightingconstants.js +263 -0
- package/actionengine/display/graphics/lighting/lightmanager.js +789 -0
- package/actionengine/display/graphics/renderableobject.js +44 -0
- package/actionengine/display/graphics/renderers/actionrenderer2D.js +341 -0
- package/actionengine/display/graphics/renderers/actionrenderer3D/actionrenderer3D.js +655 -0
- package/actionengine/display/graphics/renderers/actionrenderer3D/canvasmanager3D.js +82 -0
- package/actionengine/display/graphics/renderers/actionrenderer3D/debugrenderer3D.js +493 -0
- package/actionengine/display/graphics/renderers/actionrenderer3D/objectrenderer3D.js +790 -0
- package/actionengine/display/graphics/renderers/actionrenderer3D/spriteRenderer3D.js +266 -0
- package/actionengine/display/graphics/renderers/actionrenderer3D/sunrenderer3D.js +140 -0
- package/actionengine/display/graphics/renderers/actionrenderer3D/waterrenderer3D.js +173 -0
- package/actionengine/display/graphics/renderers/actionrenderer3D/weatherrenderer3D.js +87 -0
- package/actionengine/display/graphics/texture/proceduraltexture.js +192 -0
- package/actionengine/display/graphics/texture/texturemanager.js +242 -0
- package/actionengine/display/graphics/texture/textureregistry.js +177 -0
- package/actionengine/input/actionscrollablearea.js +1405 -0
- package/actionengine/input/inputhandler.js +1647 -0
- package/actionengine/math/geometry/geometrybuilder.js +161 -0
- package/actionengine/math/geometry/glbexporter.js +364 -0
- package/actionengine/math/geometry/glbloader.js +722 -0
- package/actionengine/math/geometry/modelcodegenerator.js +97 -0
- package/actionengine/math/geometry/triangle.js +33 -0
- package/actionengine/math/geometry/triangleutils.js +34 -0
- package/actionengine/math/mathutils.js +25 -0
- package/actionengine/math/matrix4.js +785 -0
- package/actionengine/math/physics/actionphysics.js +108 -0
- package/actionengine/math/physics/actionphysicsobject3D.js +164 -0
- package/actionengine/math/physics/actionphysicsworld3D.js +238 -0
- package/actionengine/math/physics/actionraycast.js +129 -0
- package/actionengine/math/physics/shapes/actionphysicsbox3D.js +158 -0
- package/actionengine/math/physics/shapes/actionphysicscapsule3D.js +200 -0
- package/actionengine/math/physics/shapes/actionphysicscompoundshape3D.js +147 -0
- package/actionengine/math/physics/shapes/actionphysicscone3D.js +126 -0
- package/actionengine/math/physics/shapes/actionphysicsconvexshape3D.js +72 -0
- package/actionengine/math/physics/shapes/actionphysicscylinder3D.js +117 -0
- package/actionengine/math/physics/shapes/actionphysicsmesh3D.js +74 -0
- package/actionengine/math/physics/shapes/actionphysicsplane3D.js +100 -0
- package/actionengine/math/physics/shapes/actionphysicssphere3D.js +95 -0
- package/actionengine/math/quaternion.js +61 -0
- package/actionengine/math/vector2.js +277 -0
- package/actionengine/math/vector3.js +318 -0
- package/actionengine/math/viewfrustum.js +136 -0
- package/actionengine/network/ACTIONNETREADME.md +810 -0
- package/actionengine/network/client/ActionNetManager.js +802 -0
- package/actionengine/network/client/ActionNetManagerGUI.js +1709 -0
- package/actionengine/network/client/ActionNetManagerP2P.js +1537 -0
- package/actionengine/network/client/SyncSystem.js +422 -0
- package/actionengine/network/p2p/ActionNetPeer.js +142 -0
- package/actionengine/network/p2p/ActionNetTrackerClient.js +623 -0
- package/actionengine/network/p2p/DataConnection.js +282 -0
- package/actionengine/network/p2p/README.md +510 -0
- package/actionengine/network/p2p/example.html +502 -0
- package/actionengine/network/server/ActionNetServer.js +577 -0
- package/actionengine/network/server/ActionNetServerSSL.js +579 -0
- package/actionengine/network/server/ActionNetServerUtils.js +458 -0
- package/actionengine/network/server/SERVERREADME.md +314 -0
- package/actionengine/network/server/package-lock.json +35 -0
- package/actionengine/network/server/package.json +13 -0
- package/actionengine/network/server/start.bat +27 -0
- package/actionengine/network/server/start.sh +25 -0
- package/actionengine/network/server/startwss.bat +27 -0
- package/actionengine/sound/audiomanager.js +1589 -0
- package/actionengine/sound/soundfont/ACTIONSOUNDFONT_README.md +205 -0
- package/actionengine/sound/soundfont/actionparser.js +718 -0
- package/actionengine/sound/soundfont/actionreverb.js +252 -0
- package/actionengine/sound/soundfont/actionsoundfont.js +543 -0
- package/actionengine/sound/soundfont/sf2playerlicence.txt +29 -0
- package/actionengine/sound/soundfont/soundfont.js +2 -0
- package/dist/action-engine.min.js +328 -0
- package/package.json +35 -0
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
// actionengine/math/physics/actionphysics.js
|
|
2
|
+
class ActionPhysics {
|
|
3
|
+
static initialize(ammo, world) {
|
|
4
|
+
this.ammo = ammo;
|
|
5
|
+
this.world = world;
|
|
6
|
+
|
|
7
|
+
// Cache common physics objects
|
|
8
|
+
this._rayStart = new ammo.btVector3(0, 0, 0);
|
|
9
|
+
this._rayEnd = new ammo.btVector3(0, 0, 0);
|
|
10
|
+
this._rayCallback = new ammo.ClosestRayResultCallback(this._rayStart, this._rayEnd);
|
|
11
|
+
this._sweepStart = new ammo.btTransform();
|
|
12
|
+
this._sweepEnd = new ammo.btTransform();
|
|
13
|
+
this._tmpVec = new ammo.btVector3(0, 0, 0);
|
|
14
|
+
this._halfExtents = new ammo.btVector3(0, 0, 0);
|
|
15
|
+
this._sweepCallback = new ammo.ClosestConvexResultCallback(this._rayStart, this._rayEnd);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
static rayTest(body, direction, distance) {
|
|
19
|
+
const origin = body.getWorldTransform().getOrigin();
|
|
20
|
+
|
|
21
|
+
// Use cached vectors
|
|
22
|
+
this._rayStart.setValue(origin.x(), origin.y(), origin.z());
|
|
23
|
+
this._rayEnd.setValue(
|
|
24
|
+
origin.x() + direction.x * distance,
|
|
25
|
+
origin.y() + direction.y * distance,
|
|
26
|
+
origin.z() + direction.z * distance
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
// Reset and reuse callback
|
|
30
|
+
this._rayCallback.set_m_closestHitFraction(1);
|
|
31
|
+
this._rayCallback.set_m_collisionObject(null);
|
|
32
|
+
|
|
33
|
+
this.world.rayTest(this._rayStart, this._rayEnd, this._rayCallback);
|
|
34
|
+
return this._rayCallback.hasHit();
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
static boxSweepTest(position, size, direction, distance) {
|
|
38
|
+
// Set up start transform at current position
|
|
39
|
+
this._sweepStart.setIdentity();
|
|
40
|
+
this._sweepStart.setOrigin(new this.ammo.btVector3(
|
|
41
|
+
position.x,
|
|
42
|
+
position.y,
|
|
43
|
+
position.z
|
|
44
|
+
));
|
|
45
|
+
|
|
46
|
+
// Set up end transform
|
|
47
|
+
this._sweepEnd.setIdentity();
|
|
48
|
+
this._tmpVec.setValue(
|
|
49
|
+
position.x + direction.x * distance,
|
|
50
|
+
position.y + direction.y * distance,
|
|
51
|
+
position.z + direction.z * distance
|
|
52
|
+
);
|
|
53
|
+
this._sweepEnd.setOrigin(this._tmpVec);
|
|
54
|
+
|
|
55
|
+
// Use cached halfExtents vector
|
|
56
|
+
this._halfExtents.setValue(
|
|
57
|
+
size.x * 0.5,
|
|
58
|
+
size.y * 0.5,
|
|
59
|
+
size.z * 0.5
|
|
60
|
+
);
|
|
61
|
+
const boxShape = new this.ammo.btBoxShape(this._halfExtents);
|
|
62
|
+
|
|
63
|
+
// Reset and reuse callback
|
|
64
|
+
this._sweepCallback.set_m_closestHitFraction(1);
|
|
65
|
+
|
|
66
|
+
this.world.convexSweepTest(
|
|
67
|
+
boxShape,
|
|
68
|
+
this._sweepStart,
|
|
69
|
+
this._sweepEnd,
|
|
70
|
+
this._sweepCallback,
|
|
71
|
+
0.0
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
const hasHit = this._sweepCallback.hasHit();
|
|
75
|
+
|
|
76
|
+
// Only the shape needs cleanup now
|
|
77
|
+
Ammo.destroy(boxShape);
|
|
78
|
+
|
|
79
|
+
return {
|
|
80
|
+
hasHit,
|
|
81
|
+
hitFraction: this._sweepCallback.m_closestHitFraction
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
static cleanup() {
|
|
85
|
+
if (this._rayStart) {
|
|
86
|
+
Ammo.destroy(this._rayStart);
|
|
87
|
+
this._rayStart = null;
|
|
88
|
+
}
|
|
89
|
+
if (this._rayEnd) {
|
|
90
|
+
Ammo.destroy(this._rayEnd);
|
|
91
|
+
this._rayEnd = null;
|
|
92
|
+
}
|
|
93
|
+
if (this._rayCallback) {
|
|
94
|
+
Ammo.destroy(this._rayCallback);
|
|
95
|
+
this._rayCallback = null;
|
|
96
|
+
}
|
|
97
|
+
if (this._sweepStart) Ammo.destroy(this._sweepStart);
|
|
98
|
+
if (this._sweepEnd) Ammo.destroy(this._sweepEnd);
|
|
99
|
+
if (this._tmpVec) Ammo.destroy(this._tmpVec);
|
|
100
|
+
if (this._halfExtents) Ammo.destroy(this._halfExtents);
|
|
101
|
+
if (this._sweepCallback) Ammo.destroy(this._sweepCallback);
|
|
102
|
+
this._sweepStart = null;
|
|
103
|
+
this._sweepEnd = null;
|
|
104
|
+
this._tmpVec = null;
|
|
105
|
+
this._halfExtents = null;
|
|
106
|
+
this._sweepCallback = null;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
class ActionPhysicsObject3D extends RenderableObject {
|
|
2
|
+
constructor(physicsWorld, triangles, options = {}) {
|
|
3
|
+
super();
|
|
4
|
+
if (!physicsWorld) {
|
|
5
|
+
console.error("[ActionPhysicsObject3D] Physics world is required. Stack trace:", new Error().stack);
|
|
6
|
+
throw new Error("[ActionPhysicsObject3D] Physics world is required - check console for stack trace");
|
|
7
|
+
}
|
|
8
|
+
this.physicsWorld = physicsWorld;
|
|
9
|
+
|
|
10
|
+
// Store the original triangles for later toggling
|
|
11
|
+
this._originalTriangles = triangles.slice();
|
|
12
|
+
|
|
13
|
+
// Set visibility property
|
|
14
|
+
this.isVisible = options && options.isVisible !== undefined ? options.isVisible : true;
|
|
15
|
+
|
|
16
|
+
// Initialize triangles based on visibility
|
|
17
|
+
this.triangles = this.isVisible ? this._originalTriangles : [];
|
|
18
|
+
|
|
19
|
+
this.originalNormals = [];
|
|
20
|
+
this.originalVerts = [];
|
|
21
|
+
this.position = new Vector3(0, 0, 0);
|
|
22
|
+
|
|
23
|
+
// Calculate bounding sphere for frustum culling
|
|
24
|
+
this.calculateBoundingSphere();
|
|
25
|
+
|
|
26
|
+
// We need to store normals and verts even for invisible objects
|
|
27
|
+
// so when they become visible again we can update them correctly
|
|
28
|
+
// Use _originalTriangles to ensure we always have the data
|
|
29
|
+
this._originalTriangles.forEach((triangle) => {
|
|
30
|
+
this.originalNormals.push(new Vector3(
|
|
31
|
+
triangle.normal.x,
|
|
32
|
+
triangle.normal.y,
|
|
33
|
+
triangle.normal.z
|
|
34
|
+
));
|
|
35
|
+
|
|
36
|
+
triangle.vertices.forEach((vertex) => {
|
|
37
|
+
this.originalVerts.push(new Vector3(
|
|
38
|
+
vertex.x,
|
|
39
|
+
vertex.y,
|
|
40
|
+
vertex.z
|
|
41
|
+
));
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Add a method to toggle visibility
|
|
47
|
+
setVisibility(visible) {
|
|
48
|
+
if (this.isVisible === visible) return; // No change needed
|
|
49
|
+
|
|
50
|
+
this.isVisible = visible;
|
|
51
|
+
|
|
52
|
+
// Update triangles based on visibility - this is the key part
|
|
53
|
+
// When invisible, we set triangles to an empty array
|
|
54
|
+
// When visible, we restore the original triangles
|
|
55
|
+
this.triangles = visible ? this._originalTriangles : [];
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
updateVisual() {
|
|
59
|
+
if (!this.body) return;
|
|
60
|
+
|
|
61
|
+
// Cache frequently accessed properties
|
|
62
|
+
const pos = this.body.position;
|
|
63
|
+
const rot = this.body.rotation;
|
|
64
|
+
const { x: posX, y: posY, z: posZ } = pos;
|
|
65
|
+
|
|
66
|
+
// Check if object has moved since last update
|
|
67
|
+
if (!this._visualDirty && this._lastPosition && this._lastRotation) {
|
|
68
|
+
// Check position
|
|
69
|
+
if (Math.abs(this._lastPosition.x - posX) < 0.001 &&
|
|
70
|
+
Math.abs(this._lastPosition.y - posY) < 0.001 &&
|
|
71
|
+
Math.abs(this._lastPosition.z - posZ) < 0.001) {
|
|
72
|
+
|
|
73
|
+
// Check rotation - using dot product as a quick comparison
|
|
74
|
+
// If dot product is very close to 1, rotation hasn't changed significantly
|
|
75
|
+
const lastQuat = this._lastRotation;
|
|
76
|
+
const curQuat = rot;
|
|
77
|
+
const dot = lastQuat.x * curQuat.x + lastQuat.y * curQuat.y +
|
|
78
|
+
lastQuat.z * curQuat.z + lastQuat.w * curQuat.w;
|
|
79
|
+
|
|
80
|
+
if (Math.abs(dot) > 0.9999) {
|
|
81
|
+
// Position and rotation haven't changed, skip update
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Update position once
|
|
88
|
+
this.position.set(posX, posY, posZ);
|
|
89
|
+
|
|
90
|
+
// Cache current position and rotation for next comparison
|
|
91
|
+
if (!this._lastPosition) this._lastPosition = new Vector3();
|
|
92
|
+
this._lastPosition.set(posX, posY, posZ);
|
|
93
|
+
|
|
94
|
+
if (!this._lastRotation) this._lastRotation = new Goblin.Quaternion();
|
|
95
|
+
this._lastRotation.x = rot.x;
|
|
96
|
+
this._lastRotation.y = rot.y;
|
|
97
|
+
this._lastRotation.z = rot.z;
|
|
98
|
+
this._lastRotation.w = rot.w;
|
|
99
|
+
|
|
100
|
+
// Mark as clean since we're updating now
|
|
101
|
+
this._visualDirty = false;
|
|
102
|
+
|
|
103
|
+
// Preallocate reusable vector to avoid garbage collection
|
|
104
|
+
const relativeVert = new Goblin.Vector3();
|
|
105
|
+
|
|
106
|
+
// Use a for loop instead of forEach for better performance
|
|
107
|
+
for (let i = 0; i < this.triangles.length; i++) {
|
|
108
|
+
const triangle = this.triangles[i];
|
|
109
|
+
|
|
110
|
+
// Update normal - can be done with direct rotation
|
|
111
|
+
triangle.normal = this.rotateVector(this.originalNormals[i], rot);
|
|
112
|
+
|
|
113
|
+
// Update vertices
|
|
114
|
+
const baseIndex = i * 3;
|
|
115
|
+
for (let j = 0; j < 3; j++) {
|
|
116
|
+
const vertex = triangle.vertices[j];
|
|
117
|
+
const origVert = this.originalVerts[baseIndex + j];
|
|
118
|
+
|
|
119
|
+
// Reuse vector instead of creating new one
|
|
120
|
+
relativeVert.set(origVert.x, origVert.y, origVert.z);
|
|
121
|
+
|
|
122
|
+
// Rotate and translate in place
|
|
123
|
+
rot.transformVector3(relativeVert);
|
|
124
|
+
vertex.x = relativeVert.x + posX;
|
|
125
|
+
vertex.y = relativeVert.y + posY;
|
|
126
|
+
vertex.z = relativeVert.z + posZ;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
rotateVector(vector, rotation) {
|
|
132
|
+
// Create Goblin vector
|
|
133
|
+
const v = new Goblin.Vector3(vector.x, vector.y, vector.z);
|
|
134
|
+
// Apply rotation
|
|
135
|
+
rotation.transformVector3(v);
|
|
136
|
+
// Return as our Vector3
|
|
137
|
+
return new Vector3(v.x, v.y, v.z);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Calculate the bounding sphere radius for frustum culling
|
|
141
|
+
calculateBoundingSphere() {
|
|
142
|
+
if (!this.triangles || this.triangles.length === 0) {
|
|
143
|
+
// Default radius if no triangles
|
|
144
|
+
this.boundingSphereRadius = 20;
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Find the maximum distance from center to any vertex
|
|
149
|
+
let maxDistanceSquared = 0;
|
|
150
|
+
|
|
151
|
+
for (const triangle of this.triangles) {
|
|
152
|
+
for (const vertex of triangle.vertices) {
|
|
153
|
+
// Simple distance from origin - assuming most objects are centered
|
|
154
|
+
const distSquared = vertex.x * vertex.x + vertex.y * vertex.y + vertex.z * vertex.z;
|
|
155
|
+
if (distSquared > maxDistanceSquared) {
|
|
156
|
+
maxDistanceSquared = distSquared;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Set radius with a small buffer for safety
|
|
162
|
+
this.boundingSphereRadius = Math.sqrt(maxDistanceSquared) * 1.1;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
// actionengine/math/physics/actionphysicsworld3D.js
|
|
2
|
+
class ActionPhysicsWorld3D {
|
|
3
|
+
constructor(fixedTimestep = 1 / 60) {
|
|
4
|
+
this.broadphase = new Goblin.SAPBroadphase();
|
|
5
|
+
this.narrowphase = new Goblin.NarrowPhase();
|
|
6
|
+
this.solver = new Goblin.IterativeSolver();
|
|
7
|
+
this.world = new Goblin.World(this.broadphase, this.narrowphase, this.solver);
|
|
8
|
+
|
|
9
|
+
this.world.gravity = new Goblin.Vector3(0, -98.1, 0);
|
|
10
|
+
|
|
11
|
+
this.objects = new Set();
|
|
12
|
+
|
|
13
|
+
// Physics timing variables
|
|
14
|
+
this.fixedTimeStep = fixedTimestep;
|
|
15
|
+
this.physicsAccumulator = 0;
|
|
16
|
+
this.lastPhysicsTime = performance.now();
|
|
17
|
+
this.isPaused = false;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
fixed_update(fixedDeltaTime) {
|
|
21
|
+
if (!this.world || this.isPaused) return;
|
|
22
|
+
|
|
23
|
+
// Store pre-physics state for objects that need to know if they've moved
|
|
24
|
+
const movedObjects = new Set();
|
|
25
|
+
|
|
26
|
+
// Capture pre-step positions and rotations for moving objects
|
|
27
|
+
this.objects.forEach(object => {
|
|
28
|
+
if (object.body && !object.body.is_static) {
|
|
29
|
+
movedObjects.add(object);
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
try {
|
|
34
|
+
// Step physics with the fixed timestep
|
|
35
|
+
this.world.step(fixedDeltaTime);
|
|
36
|
+
|
|
37
|
+
// Mark objects that moved during physics as visually dirty
|
|
38
|
+
movedObjects.forEach(object => {
|
|
39
|
+
if (typeof object.markVisualDirty === 'function') {
|
|
40
|
+
object.markVisualDirty();
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
} catch (error) {
|
|
44
|
+
if (error.toString().includes("silhouette")) {
|
|
45
|
+
console.error("===== SILHOUETTE ERROR DETECTED =====");
|
|
46
|
+
console.error("Error message:", error.message);
|
|
47
|
+
console.error("Error stack:", error.stack);
|
|
48
|
+
|
|
49
|
+
// Log object counts and world state
|
|
50
|
+
console.error("Physics objects count:", this.objects.size);
|
|
51
|
+
console.error("Rigid bodies count:", this.world.rigid_bodies.length);
|
|
52
|
+
|
|
53
|
+
// Log character information if available
|
|
54
|
+
if (window.gameCharacter) {
|
|
55
|
+
console.error("Character information:");
|
|
56
|
+
console.error(" - Type:", window.gameCharacter.constructor.name);
|
|
57
|
+
console.error(" - Position:",
|
|
58
|
+
window.gameCharacter.body.position.x,
|
|
59
|
+
window.gameCharacter.body.position.y,
|
|
60
|
+
window.gameCharacter.body.position.z
|
|
61
|
+
);
|
|
62
|
+
console.error(" - Has body?", !!window.gameCharacter.body);
|
|
63
|
+
console.error(" - Body in world?", this.world.rigid_bodies.includes(window.gameCharacter.body));
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Log object pool sizes - these are key to understanding the issue
|
|
67
|
+
if (window.Goblin && window.Goblin.ObjectPool) {
|
|
68
|
+
console.error("Goblin object pools:");
|
|
69
|
+
Object.keys(window.Goblin.ObjectPool.pools).forEach(key => {
|
|
70
|
+
console.error(` - ${key}: ${window.Goblin.ObjectPool.pools[key].length} objects`);
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Continue execution - skip this physics step
|
|
75
|
+
console.error("Skipping current physics step due to error");
|
|
76
|
+
|
|
77
|
+
// Log a counter of how many times this has happened
|
|
78
|
+
this._silhouetteErrorCount = (this._silhouetteErrorCount || 0) + 1;
|
|
79
|
+
console.error(`Total silhouette errors: ${this._silhouetteErrorCount}`);
|
|
80
|
+
|
|
81
|
+
// Keep track of what the character was doing when this happened
|
|
82
|
+
if (window.gameCharacter && window.gameCharacter.debugInfo) {
|
|
83
|
+
const state = window.gameCharacter.debugInfo.state;
|
|
84
|
+
console.error("Character state when error occurred:", state ? state.current : "unknown");
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Store detailed information for later analysis
|
|
88
|
+
if (!window._silhouetteErrorLog) window._silhouetteErrorLog = [];
|
|
89
|
+
window._silhouetteErrorLog.push({
|
|
90
|
+
timestamp: new Date().toISOString(),
|
|
91
|
+
errorMessage: error.message,
|
|
92
|
+
characterPosition: window.gameCharacter ?
|
|
93
|
+
[window.gameCharacter.body.position.x,
|
|
94
|
+
window.gameCharacter.body.position.y,
|
|
95
|
+
window.gameCharacter.body.position.z] : null,
|
|
96
|
+
characterState: window.gameCharacter && window.gameCharacter.debugInfo ?
|
|
97
|
+
window.gameCharacter.debugInfo.state.current : null
|
|
98
|
+
});
|
|
99
|
+
} else {
|
|
100
|
+
// Re-throw any other errors
|
|
101
|
+
throw error;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
update(deltaTime) {
|
|
107
|
+
// This is now a lightweight wrapper for backward compatibility
|
|
108
|
+
// Physics is now handled in fixed_update()
|
|
109
|
+
if (!this.world || this.isPaused) return;
|
|
110
|
+
|
|
111
|
+
// Update any non-physics related components here that need variable timestep
|
|
112
|
+
// (none currently, all physics moved to fixed_update)
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
addConstraint(constraint) {
|
|
116
|
+
if (!constraint) {
|
|
117
|
+
console.warn("[PhysicsWorld] Attempted to add null constraint");
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
//console.log("[PhysicsWorld] Adding constraint:", constraint);
|
|
121
|
+
this.world.addConstraint(constraint);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
addObject(object) {
|
|
125
|
+
this.objects.add(object);
|
|
126
|
+
if (object.body) {
|
|
127
|
+
//console.log("[PhysicsWorld] Adding object body:", object.body);
|
|
128
|
+
this.world.addRigidBody(object.body);
|
|
129
|
+
}
|
|
130
|
+
if (object.rigidBodies) {
|
|
131
|
+
object.rigidBodies.forEach((body) => {
|
|
132
|
+
console.log("[PhysicsWorld] Adding object body:", body);
|
|
133
|
+
this.world.addRigidBody(body);
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
if (object.constraints) {
|
|
137
|
+
object.constraints.forEach((constraint) => {
|
|
138
|
+
console.log("[PhysicsWorld] Adding object constraint:", constraint);
|
|
139
|
+
this.world.addConstraint(constraint);
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
addRigidBody(body, group = 0, mask = 0) {
|
|
145
|
+
/* //disable masking for now
|
|
146
|
+
if (group !== undefined || mask !== undefined) {
|
|
147
|
+
body.collision_groups = group || 1;
|
|
148
|
+
body.collision_mask = mask || -1;
|
|
149
|
+
}
|
|
150
|
+
*/
|
|
151
|
+
this.world.addRigidBody(body);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
addTerrainBody(body, group = 0, mask = 0) {
|
|
155
|
+
//console.log("[PhysicsWorld] Adding terrain body:", body);
|
|
156
|
+
if (this.terrainBody) {
|
|
157
|
+
//console.log("[PhysicsWorld] Removing old terrain body");
|
|
158
|
+
this.world.removeRigidBody(this.terrainBody);
|
|
159
|
+
}
|
|
160
|
+
this.terrainBody = body;
|
|
161
|
+
|
|
162
|
+
//disable masking for now
|
|
163
|
+
//body.collision_groups = group;
|
|
164
|
+
//body.collision_mask = mask;
|
|
165
|
+
|
|
166
|
+
//console.log("[PhysicsWorld] Adding to world with groups:", group, "mask:", mask);
|
|
167
|
+
this.world.addRigidBody(body);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
removeRigidBody(body) {
|
|
171
|
+
this.world.removeRigidBody(body);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
removeConstraint(constraint) {
|
|
175
|
+
if (!constraint) {
|
|
176
|
+
console.warn("[PhysicsWorld] Attempted to remove null constraint");
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
this.world.removeConstraint(constraint);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
removeObject(object) {
|
|
183
|
+
if (object.body) {
|
|
184
|
+
this.world.removeRigidBody(object.body);
|
|
185
|
+
}
|
|
186
|
+
// Check for additional bodies
|
|
187
|
+
if (object.rigidBodies) {
|
|
188
|
+
object.rigidBodies.forEach((body) => {
|
|
189
|
+
//console.log("[PhysicsWorld] Removing object body:", body);
|
|
190
|
+
this.world.removeRigidBody(body);
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
if (object.constraints) {
|
|
194
|
+
object.constraints.forEach((constraint) => {
|
|
195
|
+
//console.log("[PhysicsWorld] Removing object constraint:", constraint);
|
|
196
|
+
this.world.removeConstraint(constraint);
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
this.objects.delete(object);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
pause() {
|
|
203
|
+
this.isPaused = true;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
resume() {
|
|
207
|
+
this.isPaused = false;
|
|
208
|
+
this.lastPhysicsTime = performance.now();
|
|
209
|
+
this.physicsAccumulator = 0;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
reset() {
|
|
213
|
+
// nuke it
|
|
214
|
+
this.broadphase = new Goblin.SAPBroadphase();
|
|
215
|
+
this.narrowphase = new Goblin.NarrowPhase();
|
|
216
|
+
this.solver = new Goblin.IterativeSolver();
|
|
217
|
+
|
|
218
|
+
// Clear all object pools
|
|
219
|
+
Object.keys(Goblin.ObjectPool.pools).forEach(key => {
|
|
220
|
+
Goblin.ObjectPool.pools[key].length = 0;
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
if (this.terrainBody) {
|
|
224
|
+
this.world.removeRigidBody(this.terrainBody);
|
|
225
|
+
this.terrainBody = null;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
this.objects.forEach((obj) => {
|
|
229
|
+
this.removeObject(obj);
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
this.objects.clear();
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
getWorld() {
|
|
236
|
+
return this.world;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
// actionengine/physics/actionraycast.js
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Utility class for performing raycasts in the ActionEngine world.
|
|
5
|
+
* Provides a simplified wrapper around the Goblin Physics raycasting system.
|
|
6
|
+
*/
|
|
7
|
+
class ActionRaycast {
|
|
8
|
+
/**
|
|
9
|
+
* Cast a ray from start to end in the physics world
|
|
10
|
+
* @param {Vector3|{x,y,z}} start - Starting point of the ray
|
|
11
|
+
* @param {Vector3|{x,y,z}} end - End point of the ray
|
|
12
|
+
* @param {ActionPhysicsWorld3D} physicsWorld - The physics world to cast in
|
|
13
|
+
* @param {Object} options - Optional settings
|
|
14
|
+
* @param {string[]} options.ignoreObjects - Array of object debugNames to ignore
|
|
15
|
+
* @param {number} options.minDistance - Minimum distance threshold (ignore hits closer than this)
|
|
16
|
+
* @returns {Object|null} Hit result or null if no hit
|
|
17
|
+
*/
|
|
18
|
+
static cast(start, end, physicsWorld, options = {}) {
|
|
19
|
+
const ignoreList = options.ignoreObjects || [];
|
|
20
|
+
const minDistance = options.minDistance || 0;
|
|
21
|
+
|
|
22
|
+
// Ensure start and end are correctly formatted
|
|
23
|
+
const rayStart = {
|
|
24
|
+
x: start.x,
|
|
25
|
+
y: start.y,
|
|
26
|
+
z: start.z
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const rayEnd = {
|
|
30
|
+
x: end.x,
|
|
31
|
+
y: end.y,
|
|
32
|
+
z: end.z
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
// Perform the raycast using Goblin physics
|
|
36
|
+
const intersections = physicsWorld.getWorld().rayIntersect(rayStart, rayEnd);
|
|
37
|
+
|
|
38
|
+
if (!intersections || intersections.length === 0) {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Filter and find the first valid intersection
|
|
43
|
+
for (let i = 0; i < intersections.length; i++) {
|
|
44
|
+
const hit = intersections[i];
|
|
45
|
+
|
|
46
|
+
// Skip if hit is too close
|
|
47
|
+
if (hit.t < minDistance) {
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Skip if object is in ignore list
|
|
52
|
+
if (hit.object && hit.object.debugName &&
|
|
53
|
+
ignoreList.some(name =>
|
|
54
|
+
hit.object.debugName === name ||
|
|
55
|
+
hit.object.debugName.includes(name)
|
|
56
|
+
)
|
|
57
|
+
) {
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Return formatted hit result
|
|
62
|
+
return {
|
|
63
|
+
object: hit.object,
|
|
64
|
+
point: { x: hit.point.x, y: hit.point.y, z: hit.point.z },
|
|
65
|
+
normal: { x: hit.normal.x, y: hit.normal.y, z: hit.normal.z },
|
|
66
|
+
distance: hit.t,
|
|
67
|
+
rayDirection: this._calculateDirection(rayStart, rayEnd)
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Cast multiple rays from a single origin in different directions
|
|
76
|
+
* @param {Vector3|{x,y,z}} origin - Origin point for all rays
|
|
77
|
+
* @param {Array<Vector3|{x,y,z}>} directions - Array of direction vectors
|
|
78
|
+
* @param {number} length - Length of each ray
|
|
79
|
+
* @param {ActionPhysicsWorld3D} physicsWorld - The physics world to cast in
|
|
80
|
+
* @param {Object} options - Optional settings (same as cast method)
|
|
81
|
+
* @returns {Array} Array of hit results (null for each ray with no hit)
|
|
82
|
+
*/
|
|
83
|
+
static multicast(origin, directions, length, physicsWorld, options = {}) {
|
|
84
|
+
return directions.map(dir => {
|
|
85
|
+
const end = {
|
|
86
|
+
x: origin.x + dir.x * length,
|
|
87
|
+
y: origin.y + dir.y * length,
|
|
88
|
+
z: origin.z + dir.z * length
|
|
89
|
+
};
|
|
90
|
+
return this.cast(origin, end, physicsWorld, options);
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Calculate normalized direction vector from start to end
|
|
96
|
+
* @private
|
|
97
|
+
*/
|
|
98
|
+
static _calculateDirection(start, end) {
|
|
99
|
+
const dx = end.x - start.x;
|
|
100
|
+
const dy = end.y - start.y;
|
|
101
|
+
const dz = end.z - start.z;
|
|
102
|
+
const length = Math.sqrt(dx*dx + dy*dy + dz*dz);
|
|
103
|
+
|
|
104
|
+
if (length < 0.0001) {
|
|
105
|
+
return { x: 0, y: 0, z: 0 };
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return {
|
|
109
|
+
x: dx / length,
|
|
110
|
+
y: dy / length,
|
|
111
|
+
z: dz / length
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Calculate a point along the ray at a specific distance
|
|
117
|
+
* @param {Vector3|{x,y,z}} start - Starting point of the ray
|
|
118
|
+
* @param {Vector3|{x,y,z}} direction - Normalized direction vector
|
|
119
|
+
* @param {number} distance - Distance along the ray
|
|
120
|
+
* @returns {Vector3} Point at the specified distance
|
|
121
|
+
*/
|
|
122
|
+
static getPointOnRay(start, direction, distance) {
|
|
123
|
+
return new Vector3(
|
|
124
|
+
start.x + direction.x * distance,
|
|
125
|
+
start.y + direction.y * distance,
|
|
126
|
+
start.z + direction.z * distance
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
}
|