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.
Files changed (93) hide show
  1. package/LICENSE +45 -0
  2. package/README.md +348 -0
  3. package/actionengine/3rdparty/goblin/goblin.js +9609 -0
  4. package/actionengine/3rdparty/goblin/goblin.min.js +5 -0
  5. package/actionengine/camera/actioncamera.js +90 -0
  6. package/actionengine/camera/cameracollisionhandler.js +69 -0
  7. package/actionengine/character/actioncharacter.js +360 -0
  8. package/actionengine/character/actioncharacter3D.js +61 -0
  9. package/actionengine/core/app.js +430 -0
  10. package/actionengine/debug/basedebugpanel.js +858 -0
  11. package/actionengine/display/canvasmanager.js +75 -0
  12. package/actionengine/display/gl/programmanager.js +570 -0
  13. package/actionengine/display/gl/shaders/lineshader.js +118 -0
  14. package/actionengine/display/gl/shaders/objectshader.js +1756 -0
  15. package/actionengine/display/gl/shaders/particleshader.js +43 -0
  16. package/actionengine/display/gl/shaders/shadowshader.js +319 -0
  17. package/actionengine/display/gl/shaders/spriteshader.js +100 -0
  18. package/actionengine/display/gl/shaders/watershader.js +67 -0
  19. package/actionengine/display/graphics/actionmodel3D.js +191 -0
  20. package/actionengine/display/graphics/actionsprite3D.js +230 -0
  21. package/actionengine/display/graphics/lighting/actiondirectionalshadowlight.js +864 -0
  22. package/actionengine/display/graphics/lighting/actionlight.js +211 -0
  23. package/actionengine/display/graphics/lighting/actionomnidirectionalshadowlight.js +862 -0
  24. package/actionengine/display/graphics/lighting/lightingconstants.js +263 -0
  25. package/actionengine/display/graphics/lighting/lightmanager.js +789 -0
  26. package/actionengine/display/graphics/renderableobject.js +44 -0
  27. package/actionengine/display/graphics/renderers/actionrenderer2D.js +341 -0
  28. package/actionengine/display/graphics/renderers/actionrenderer3D/actionrenderer3D.js +655 -0
  29. package/actionengine/display/graphics/renderers/actionrenderer3D/canvasmanager3D.js +82 -0
  30. package/actionengine/display/graphics/renderers/actionrenderer3D/debugrenderer3D.js +493 -0
  31. package/actionengine/display/graphics/renderers/actionrenderer3D/objectrenderer3D.js +790 -0
  32. package/actionengine/display/graphics/renderers/actionrenderer3D/spriteRenderer3D.js +266 -0
  33. package/actionengine/display/graphics/renderers/actionrenderer3D/sunrenderer3D.js +140 -0
  34. package/actionengine/display/graphics/renderers/actionrenderer3D/waterrenderer3D.js +173 -0
  35. package/actionengine/display/graphics/renderers/actionrenderer3D/weatherrenderer3D.js +87 -0
  36. package/actionengine/display/graphics/texture/proceduraltexture.js +192 -0
  37. package/actionengine/display/graphics/texture/texturemanager.js +242 -0
  38. package/actionengine/display/graphics/texture/textureregistry.js +177 -0
  39. package/actionengine/input/actionscrollablearea.js +1405 -0
  40. package/actionengine/input/inputhandler.js +1647 -0
  41. package/actionengine/math/geometry/geometrybuilder.js +161 -0
  42. package/actionengine/math/geometry/glbexporter.js +364 -0
  43. package/actionengine/math/geometry/glbloader.js +722 -0
  44. package/actionengine/math/geometry/modelcodegenerator.js +97 -0
  45. package/actionengine/math/geometry/triangle.js +33 -0
  46. package/actionengine/math/geometry/triangleutils.js +34 -0
  47. package/actionengine/math/mathutils.js +25 -0
  48. package/actionengine/math/matrix4.js +785 -0
  49. package/actionengine/math/physics/actionphysics.js +108 -0
  50. package/actionengine/math/physics/actionphysicsobject3D.js +164 -0
  51. package/actionengine/math/physics/actionphysicsworld3D.js +238 -0
  52. package/actionengine/math/physics/actionraycast.js +129 -0
  53. package/actionengine/math/physics/shapes/actionphysicsbox3D.js +158 -0
  54. package/actionengine/math/physics/shapes/actionphysicscapsule3D.js +200 -0
  55. package/actionengine/math/physics/shapes/actionphysicscompoundshape3D.js +147 -0
  56. package/actionengine/math/physics/shapes/actionphysicscone3D.js +126 -0
  57. package/actionengine/math/physics/shapes/actionphysicsconvexshape3D.js +72 -0
  58. package/actionengine/math/physics/shapes/actionphysicscylinder3D.js +117 -0
  59. package/actionengine/math/physics/shapes/actionphysicsmesh3D.js +74 -0
  60. package/actionengine/math/physics/shapes/actionphysicsplane3D.js +100 -0
  61. package/actionengine/math/physics/shapes/actionphysicssphere3D.js +95 -0
  62. package/actionengine/math/quaternion.js +61 -0
  63. package/actionengine/math/vector2.js +277 -0
  64. package/actionengine/math/vector3.js +318 -0
  65. package/actionengine/math/viewfrustum.js +136 -0
  66. package/actionengine/network/ACTIONNETREADME.md +810 -0
  67. package/actionengine/network/client/ActionNetManager.js +802 -0
  68. package/actionengine/network/client/ActionNetManagerGUI.js +1709 -0
  69. package/actionengine/network/client/ActionNetManagerP2P.js +1537 -0
  70. package/actionengine/network/client/SyncSystem.js +422 -0
  71. package/actionengine/network/p2p/ActionNetPeer.js +142 -0
  72. package/actionengine/network/p2p/ActionNetTrackerClient.js +623 -0
  73. package/actionengine/network/p2p/DataConnection.js +282 -0
  74. package/actionengine/network/p2p/README.md +510 -0
  75. package/actionengine/network/p2p/example.html +502 -0
  76. package/actionengine/network/server/ActionNetServer.js +577 -0
  77. package/actionengine/network/server/ActionNetServerSSL.js +579 -0
  78. package/actionengine/network/server/ActionNetServerUtils.js +458 -0
  79. package/actionengine/network/server/SERVERREADME.md +314 -0
  80. package/actionengine/network/server/package-lock.json +35 -0
  81. package/actionengine/network/server/package.json +13 -0
  82. package/actionengine/network/server/start.bat +27 -0
  83. package/actionengine/network/server/start.sh +25 -0
  84. package/actionengine/network/server/startwss.bat +27 -0
  85. package/actionengine/sound/audiomanager.js +1589 -0
  86. package/actionengine/sound/soundfont/ACTIONSOUNDFONT_README.md +205 -0
  87. package/actionengine/sound/soundfont/actionparser.js +718 -0
  88. package/actionengine/sound/soundfont/actionreverb.js +252 -0
  89. package/actionengine/sound/soundfont/actionsoundfont.js +543 -0
  90. package/actionengine/sound/soundfont/sf2playerlicence.txt +29 -0
  91. package/actionengine/sound/soundfont/soundfont.js +2 -0
  92. package/dist/action-engine.min.js +328 -0
  93. 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
+ }