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,191 @@
|
|
|
1
|
+
class ActionModel3D {
|
|
2
|
+
constructor() {
|
|
3
|
+
// Node structure
|
|
4
|
+
this.nodes = []; // All nodes with full properties
|
|
5
|
+
this.rootNodes = []; // Top-level node indices
|
|
6
|
+
this.meshNodes = []; // Nodes with meshes
|
|
7
|
+
this.jointNodes = []; // Nodes used as bones
|
|
8
|
+
this.skinNodes = []; // Nodes using skins
|
|
9
|
+
this.nodeMap = {}; // Look up nodes by name
|
|
10
|
+
|
|
11
|
+
// Mesh data
|
|
12
|
+
this.meshes = []; // Complete mesh data for each mesh
|
|
13
|
+
this.originalTriangles = []; // Initial triangle geometry
|
|
14
|
+
this.triangles = []; // Current triangle geometry
|
|
15
|
+
|
|
16
|
+
// Original skin definitions
|
|
17
|
+
this.skins = []; // Complete skin definitions from GLB
|
|
18
|
+
|
|
19
|
+
// Bone/joint relationships
|
|
20
|
+
this.jointToSkinIndex = {}; // Which skin each joint belongs to
|
|
21
|
+
this.nodeToSkinIndex = {}; // Which skin each node uses
|
|
22
|
+
this.inverseBindMatrices = {}; // Joint index -> its starting pose matrix
|
|
23
|
+
|
|
24
|
+
// Per-vertex skinning data
|
|
25
|
+
this.vertexJoints = []; // Which joints affect each vertex
|
|
26
|
+
this.vertexWeights = []; // How much each joint affects each vertex
|
|
27
|
+
|
|
28
|
+
// Animation data
|
|
29
|
+
this.animations = {}; // All animation data
|
|
30
|
+
|
|
31
|
+
this.vertexToTriangleMap = {}; // Maps vertex positions to array of triangle indices that use that vertex
|
|
32
|
+
this.nodeToVertexMap = {}; // Maps node indices to Set of vertex positions influenced by that node based on skinning data
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
createBoxModel(size, height) {
|
|
36
|
+
// Character model is made out of Triangles
|
|
37
|
+
const halfSize = size / 2;
|
|
38
|
+
const halfHeight = height / 2;
|
|
39
|
+
const yOffset = 0;
|
|
40
|
+
// Define vertices
|
|
41
|
+
const v = {
|
|
42
|
+
ftl: new Vector3(-halfSize, halfHeight + yOffset, halfSize),
|
|
43
|
+
ftr: new Vector3(halfSize, halfHeight + yOffset, halfSize),
|
|
44
|
+
fbl: new Vector3(-halfSize, -halfHeight + yOffset, halfSize),
|
|
45
|
+
fbr: new Vector3(halfSize, -halfHeight + yOffset, halfSize),
|
|
46
|
+
btl: new Vector3(-halfSize, halfHeight + yOffset, -halfSize),
|
|
47
|
+
btr: new Vector3(halfSize, halfHeight + yOffset, -halfSize),
|
|
48
|
+
bbl: new Vector3(-halfSize, -halfHeight + yOffset, -halfSize),
|
|
49
|
+
bbr: new Vector3(halfSize, -halfHeight + yOffset, -halfSize)
|
|
50
|
+
};
|
|
51
|
+
return [
|
|
52
|
+
// Front face (yellow)
|
|
53
|
+
new Triangle(v.ftl, v.fbl, v.ftr, "#FFFF00"),
|
|
54
|
+
new Triangle(v.fbl, v.fbr, v.ftr, "#FFFF00"),
|
|
55
|
+
// Back face
|
|
56
|
+
new Triangle(v.btr, v.bbl, v.btl, "#FF0000"),
|
|
57
|
+
new Triangle(v.btr, v.bbr, v.bbl, "#FF0000"),
|
|
58
|
+
// Right face
|
|
59
|
+
new Triangle(v.ftr, v.fbr, v.btr, "#FF0000"),
|
|
60
|
+
new Triangle(v.fbr, v.bbr, v.btr, "#FF0000"),
|
|
61
|
+
// Left face
|
|
62
|
+
new Triangle(v.btl, v.bbl, v.ftl, "#FF0000"),
|
|
63
|
+
new Triangle(v.ftl, v.bbl, v.fbl, "#FF0000"),
|
|
64
|
+
// Top face
|
|
65
|
+
new Triangle(v.ftl, v.ftr, v.btr, "#FF0000"),
|
|
66
|
+
new Triangle(v.ftl, v.btr, v.btl, "#FF0000"),
|
|
67
|
+
// Bottom face
|
|
68
|
+
new Triangle(v.fbl, v.bbl, v.fbr, "#FF0000"),
|
|
69
|
+
new Triangle(v.bbl, v.bbr, v.fbr, "#FF0000")
|
|
70
|
+
];
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
createCapsuleModel(size, height) {
|
|
74
|
+
const segments = 16; // Number of segments around the capsule
|
|
75
|
+
const triangles = [];
|
|
76
|
+
const radius = size / 2;
|
|
77
|
+
const cylinderHeight = height - size; // Subtract diameter to account for hemispheres
|
|
78
|
+
const halfCylinderHeight = cylinderHeight / 2;
|
|
79
|
+
|
|
80
|
+
// Helper function to create vertex on hemisphere
|
|
81
|
+
const createSphereVertex = (phi, theta, yOffset) => {
|
|
82
|
+
return new Vector3(
|
|
83
|
+
radius * Math.sin(phi) * Math.cos(theta),
|
|
84
|
+
yOffset + radius * Math.cos(phi),
|
|
85
|
+
radius * Math.sin(phi) * Math.sin(theta)
|
|
86
|
+
);
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
// Create top hemisphere
|
|
90
|
+
for (let lat = 0; lat <= segments / 2; lat++) {
|
|
91
|
+
const phi = (lat / segments) * Math.PI;
|
|
92
|
+
const nextPhi = ((lat + 1) / segments) * Math.PI;
|
|
93
|
+
|
|
94
|
+
for (let lon = 0; lon < segments; lon++) {
|
|
95
|
+
const theta = (lon / segments) * 2 * Math.PI;
|
|
96
|
+
const nextTheta = ((lon + 1) / segments) * 2 * Math.PI;
|
|
97
|
+
|
|
98
|
+
if (lat === 0) {
|
|
99
|
+
// Top cap triangle (this one was correct)
|
|
100
|
+
triangles.push(
|
|
101
|
+
new Triangle(
|
|
102
|
+
new Vector3(0, halfCylinderHeight + radius, 0),
|
|
103
|
+
createSphereVertex(Math.PI / segments, nextTheta, halfCylinderHeight),
|
|
104
|
+
createSphereVertex(Math.PI / segments, theta, halfCylinderHeight),
|
|
105
|
+
"#FFFF00"
|
|
106
|
+
)
|
|
107
|
+
);
|
|
108
|
+
} else {
|
|
109
|
+
// Hemisphere body triangles (fixing winding order)
|
|
110
|
+
const v1 = createSphereVertex(phi, theta, halfCylinderHeight);
|
|
111
|
+
const v2 = createSphereVertex(nextPhi, theta, halfCylinderHeight);
|
|
112
|
+
const v3 = createSphereVertex(nextPhi, nextTheta, halfCylinderHeight);
|
|
113
|
+
const v4 = createSphereVertex(phi, nextTheta, halfCylinderHeight);
|
|
114
|
+
triangles.push(new Triangle(v1, v3, v2, "#FFFF00"));
|
|
115
|
+
triangles.push(new Triangle(v1, v4, v3, "#FFFF00"));
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Create cylinder body
|
|
121
|
+
for (let lon = 0; lon < segments; lon++) {
|
|
122
|
+
const theta = (lon / segments) * 2 * Math.PI;
|
|
123
|
+
const nextTheta = ((lon + 1) / segments) * 2 * Math.PI;
|
|
124
|
+
|
|
125
|
+
const topLeft = new Vector3(radius * Math.cos(theta), halfCylinderHeight, radius * Math.sin(theta));
|
|
126
|
+
const topRight = new Vector3(
|
|
127
|
+
radius * Math.cos(nextTheta),
|
|
128
|
+
halfCylinderHeight,
|
|
129
|
+
radius * Math.sin(nextTheta)
|
|
130
|
+
);
|
|
131
|
+
const bottomLeft = new Vector3(radius * Math.cos(theta), -halfCylinderHeight, radius * Math.sin(theta));
|
|
132
|
+
const bottomRight = new Vector3(
|
|
133
|
+
radius * Math.cos(nextTheta),
|
|
134
|
+
-halfCylinderHeight,
|
|
135
|
+
radius * Math.sin(nextTheta)
|
|
136
|
+
);
|
|
137
|
+
|
|
138
|
+
triangles.push(new Triangle(topLeft, topRight, bottomLeft, "#FF0000"));
|
|
139
|
+
triangles.push(new Triangle(bottomLeft, topRight, bottomRight, "#FF0000"));
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Create bottom hemisphere
|
|
143
|
+
// Stop BEFORE the last segment
|
|
144
|
+
for (let lat = segments / 2; lat < segments - 1; lat++) {
|
|
145
|
+
const phi = (lat / segments) * Math.PI;
|
|
146
|
+
const nextPhi = ((lat + 1) / segments) * Math.PI;
|
|
147
|
+
|
|
148
|
+
for (let lon = 0; lon < segments; lon++) {
|
|
149
|
+
const theta = (lon / segments) * 2 * Math.PI;
|
|
150
|
+
const nextTheta = ((lon + 1) / segments) * 2 * Math.PI;
|
|
151
|
+
|
|
152
|
+
if (lat === segments - 1) {
|
|
153
|
+
// Bottom cap triangle
|
|
154
|
+
triangles.push(
|
|
155
|
+
new Triangle(
|
|
156
|
+
new Vector3(0, -halfCylinderHeight - radius, 0),
|
|
157
|
+
createSphereVertex(Math.PI - Math.PI / segments, theta, -halfCylinderHeight),
|
|
158
|
+
createSphereVertex(Math.PI - Math.PI / segments, nextTheta, -halfCylinderHeight),
|
|
159
|
+
"#FF0000"
|
|
160
|
+
)
|
|
161
|
+
);
|
|
162
|
+
} else {
|
|
163
|
+
// Hemisphere body triangles
|
|
164
|
+
const v1 = createSphereVertex(phi, theta, -halfCylinderHeight);
|
|
165
|
+
const v2 = createSphereVertex(nextPhi, theta, -halfCylinderHeight);
|
|
166
|
+
const v3 = createSphereVertex(nextPhi, nextTheta, -halfCylinderHeight);
|
|
167
|
+
const v4 = createSphereVertex(phi, nextTheta, -halfCylinderHeight);
|
|
168
|
+
triangles.push(new Triangle(v1, v3, v2, "#FF0000"));
|
|
169
|
+
triangles.push(new Triangle(v1, v4, v3, "#FF0000"));
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Separately create just the bottom cap triangles once
|
|
175
|
+
for (let lon = 0; lon < segments; lon++) {
|
|
176
|
+
const theta = (lon / segments) * 2 * Math.PI;
|
|
177
|
+
const nextTheta = ((lon + 1) / segments) * 2 * Math.PI;
|
|
178
|
+
|
|
179
|
+
triangles.push(
|
|
180
|
+
new Triangle(
|
|
181
|
+
new Vector3(0, -halfCylinderHeight - radius, 0),
|
|
182
|
+
createSphereVertex(Math.PI - Math.PI / segments, theta, -halfCylinderHeight),
|
|
183
|
+
createSphereVertex(Math.PI - Math.PI / segments, nextTheta, -halfCylinderHeight),
|
|
184
|
+
"#FF0000"
|
|
185
|
+
)
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
return triangles;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
// actionengine/display/graphics/actionsprite3D.js
|
|
2
|
+
|
|
3
|
+
class ActionSprite3D extends RenderableObject {
|
|
4
|
+
constructor(options = {}) {
|
|
5
|
+
super();
|
|
6
|
+
|
|
7
|
+
// Required options
|
|
8
|
+
if (!options.base64Data) {
|
|
9
|
+
throw new Error('ActionSprite3D: base64Data is required');
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// Store sprite properties
|
|
13
|
+
this.base64Data = options.base64Data;
|
|
14
|
+
this.position = options.position || new Vector3(0, 0, 0);
|
|
15
|
+
this.width = options.width || 1.0;
|
|
16
|
+
this.height = options.height || 1.0;
|
|
17
|
+
this.color = options.color || [1.0, 1.0, 1.0]; // RGB tint
|
|
18
|
+
this.alpha = options.alpha !== undefined ? options.alpha : 1.0;
|
|
19
|
+
this.blendMode = options.blendMode || 'normal'; // 'normal', 'additive', 'multiply'
|
|
20
|
+
|
|
21
|
+
// Billboard mode - defaults to true for backward compatibility
|
|
22
|
+
this.isBillboard = options.billboard !== undefined ? options.billboard : true;
|
|
23
|
+
|
|
24
|
+
// Sprite orientation (for non-billboard mode)
|
|
25
|
+
this.forward = options.forward || new Vector3(0, 0, 1); // Default facing positive Z
|
|
26
|
+
this.up = options.up || new Vector3(0, 1, 0); // Default up is positive Y
|
|
27
|
+
|
|
28
|
+
// Attachment properties
|
|
29
|
+
this.attachedTo = null;
|
|
30
|
+
this.localOffset = new Vector3(0, 0, 0);
|
|
31
|
+
|
|
32
|
+
// WebGL resources
|
|
33
|
+
this.texture = null;
|
|
34
|
+
this.isTextureLoaded = false;
|
|
35
|
+
|
|
36
|
+
// Create texture from base64 data
|
|
37
|
+
this._createTextureFromBase64();
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Create WebGL texture from base64 image data
|
|
42
|
+
* @private
|
|
43
|
+
*/
|
|
44
|
+
_createTextureFromBase64() {
|
|
45
|
+
// Check if base64 data is placeholder or invalid
|
|
46
|
+
if (!this.base64Data || this.base64Data === "replace_this_base_64_string") {
|
|
47
|
+
console.warn('ActionSprite3D: Using placeholder base64 data, creating default texture');
|
|
48
|
+
this._createDefaultTexture();
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Create image element
|
|
53
|
+
const image = new Image();
|
|
54
|
+
|
|
55
|
+
image.onload = () => {
|
|
56
|
+
// Will be set by the billboard renderer when GL context is available
|
|
57
|
+
this._imageData = image;
|
|
58
|
+
this.isTextureLoaded = true;
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
image.onerror = () => {
|
|
62
|
+
console.error('ActionSprite3D: Failed to load image from base64 data, using fallback');
|
|
63
|
+
this._createDefaultTexture();
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
// Set source as data URL
|
|
67
|
+
image.src = `data:image/png;base64,${this.base64Data}`;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Create a default texture when base64 data is missing or invalid
|
|
72
|
+
* @private
|
|
73
|
+
*/
|
|
74
|
+
_createDefaultTexture() {
|
|
75
|
+
// Create a small default texture (4x4 orange gradient)
|
|
76
|
+
const canvas = document.createElement('canvas');
|
|
77
|
+
canvas.width = 4;
|
|
78
|
+
canvas.height = 4;
|
|
79
|
+
const ctx = canvas.getContext('2d');
|
|
80
|
+
|
|
81
|
+
// Create a simple gradient
|
|
82
|
+
const gradient = ctx.createLinearGradient(0, 0, 4, 4);
|
|
83
|
+
gradient.addColorStop(0, '#ff6600'); // Orange
|
|
84
|
+
gradient.addColorStop(1, '#ff3300'); // Red-orange
|
|
85
|
+
|
|
86
|
+
ctx.fillStyle = gradient;
|
|
87
|
+
ctx.fillRect(0, 0, 4, 4);
|
|
88
|
+
|
|
89
|
+
this._imageData = canvas;
|
|
90
|
+
this.isTextureLoaded = true;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Create the actual WebGL texture (called by renderer)
|
|
95
|
+
* @param {WebGLRenderingContext} gl - WebGL context
|
|
96
|
+
*/
|
|
97
|
+
createWebGLTexture(gl) {
|
|
98
|
+
if (!this._imageData || this.texture) {
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
this.texture = gl.createTexture();
|
|
103
|
+
gl.bindTexture(gl.TEXTURE_2D, this.texture);
|
|
104
|
+
|
|
105
|
+
// Upload the image data
|
|
106
|
+
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, this._imageData);
|
|
107
|
+
|
|
108
|
+
// Set texture parameters
|
|
109
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
|
|
110
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
|
|
111
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
|
112
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
|
113
|
+
|
|
114
|
+
// Unbind
|
|
115
|
+
gl.bindTexture(gl.TEXTURE_2D, null);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Attach this sprite to another object with a local offset
|
|
120
|
+
* @param {Object} object - Object to attach to (must have position property)
|
|
121
|
+
* @param {Vector3} offset - Local offset from the object's position
|
|
122
|
+
*/
|
|
123
|
+
attachTo(object, offset = new Vector3(0, 0, 0)) {
|
|
124
|
+
this.attachedTo = object;
|
|
125
|
+
this.localOffset = offset;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Detach this sprite from any attached object
|
|
130
|
+
*/
|
|
131
|
+
detach() {
|
|
132
|
+
this.attachedTo = null;
|
|
133
|
+
this.localOffset = new Vector3(0, 0, 0);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Get the world position of this sprite
|
|
138
|
+
* @returns {Vector3} World position
|
|
139
|
+
*/
|
|
140
|
+
getWorldPosition() {
|
|
141
|
+
if (this.attachedTo && this.attachedTo.position) {
|
|
142
|
+
// If attached to an object that has a transformVertex method (like Arwing),
|
|
143
|
+
// use it to properly transform the local offset
|
|
144
|
+
if (this.attachedTo.transformVertex) {
|
|
145
|
+
// Transform the local offset using the ship's transformation
|
|
146
|
+
const transformedOffset = this.attachedTo.transformVertex(this.localOffset);
|
|
147
|
+
// The transformVertex already includes position, so return it directly
|
|
148
|
+
return transformedOffset;
|
|
149
|
+
} else {
|
|
150
|
+
// Fallback to simple position + offset
|
|
151
|
+
return this.attachedTo.position.add(this.localOffset);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
return this.position;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Update sprite (called by rendering system)
|
|
159
|
+
* @param {number} deltaTime - Time since last update
|
|
160
|
+
*/
|
|
161
|
+
update(deltaTime) {
|
|
162
|
+
// Override in subclasses for animation, etc.
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Set the color tint of the sprite
|
|
167
|
+
* @param {Array} color - RGB color array [r, g, b] with values 0-1
|
|
168
|
+
*/
|
|
169
|
+
setColor(color) {
|
|
170
|
+
this.color = color;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Set the alpha transparency of the sprite
|
|
175
|
+
* @param {number} alpha - Alpha value 0-1
|
|
176
|
+
*/
|
|
177
|
+
setAlpha(alpha) {
|
|
178
|
+
this.alpha = Math.max(0, Math.min(1, alpha));
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Set the size of the sprite
|
|
183
|
+
* @param {number} width - Width in world units
|
|
184
|
+
* @param {number} height - Height in world units
|
|
185
|
+
*/
|
|
186
|
+
setSize(width, height) {
|
|
187
|
+
this.width = width;
|
|
188
|
+
this.height = height;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Set the blend mode for rendering
|
|
193
|
+
* @param {string} mode - 'normal', 'additive', or 'multiply'
|
|
194
|
+
*/
|
|
195
|
+
setBlendMode(mode) {
|
|
196
|
+
this.blendMode = mode;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Set the orientation of the sprite (for non-billboard mode)
|
|
201
|
+
* @param {Vector3} forward - Forward direction vector
|
|
202
|
+
* @param {Vector3} up - Up direction vector
|
|
203
|
+
*/
|
|
204
|
+
setOrientation(forward, up = new Vector3(0, 1, 0)) {
|
|
205
|
+
this.forward = forward;
|
|
206
|
+
this.up = up;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Cleanup WebGL resources
|
|
211
|
+
* @param {WebGLRenderingContext} gl - WebGL context
|
|
212
|
+
*/
|
|
213
|
+
dispose(gl) {
|
|
214
|
+
if (this.texture) {
|
|
215
|
+
gl.deleteTexture(this.texture);
|
|
216
|
+
this.texture = null;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Get model matrix (required by RenderableObject)
|
|
222
|
+
* For billboards, this is just the position
|
|
223
|
+
*/
|
|
224
|
+
getModelMatrix() {
|
|
225
|
+
const matrix = Matrix4.create();
|
|
226
|
+
const worldPos = this.getWorldPosition();
|
|
227
|
+
Matrix4.translate(matrix, matrix, worldPos.toArray());
|
|
228
|
+
return matrix;
|
|
229
|
+
}
|
|
230
|
+
}
|