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,161 @@
1
+ // actionengine/math/geometry/geometrybuilder.js
2
+ // Smart triangle winding system that automatically determines correct triangle orientation
3
+ // using configurable reference points
4
+
5
+ class GeometryBuilder {
6
+ constructor(referencePoint = {x: 0, y: 0, z: 0}) {
7
+ this.referencePoint = referencePoint;
8
+ }
9
+
10
+ /**
11
+ * Set the reference point used for winding calculations
12
+ * @param {Object} point - Reference point {x, y, z}
13
+ */
14
+ setReferencePoint(point) {
15
+ this.referencePoint = point;
16
+ }
17
+
18
+ /**
19
+ * Triangle creation that uses vertex positions to determine correct winding
20
+ * AND then allows an additional forced flip afterwards if requested
21
+ * If doubleSided is set true then we make an additional triangle and point the normal in the opposite direction
22
+ */
23
+ createTriangle(indices, positions, a, b, c, forceFlip = false, doubleSided = false) {
24
+ // Get vertex positions
25
+ const ax = positions[a * 3],
26
+ ay = positions[a * 3 + 1],
27
+ az = positions[a * 3 + 2];
28
+ const bx = positions[b * 3],
29
+ by = positions[b * 3 + 1],
30
+ bz = positions[b * 3 + 2];
31
+ const cx = positions[c * 3],
32
+ cy = positions[c * 3 + 1],
33
+ cz = positions[c * 3 + 2];
34
+
35
+ // Calculate face normal using cross product
36
+ const ux = bx - ax,
37
+ uy = by - ay,
38
+ uz = bz - az;
39
+ const vx = cx - ax,
40
+ vy = cy - ay,
41
+ vz = cz - az;
42
+ const nx = uy * vz - uz * vy;
43
+ const ny = uz * vx - ux * vz;
44
+ const nz = ux * vy - uy * vx;
45
+
46
+ // Calculate centroid
47
+ const centroidX = (ax + bx + cx) / 3;
48
+ const centroidY = (ay + by + cy) / 3;
49
+ const centroidZ = (az + bz + cz) / 3;
50
+
51
+ // Vector from reference point to centroid
52
+ const toCentroidX = centroidX - this.referencePoint.x;
53
+ const toCentroidY = centroidY - this.referencePoint.y;
54
+ const toCentroidZ = centroidZ - this.referencePoint.z;
55
+
56
+ // Determine if normal points away from reference point
57
+ const dotProduct = nx * toCentroidX + ny * toCentroidY + nz * toCentroidZ;
58
+
59
+ // Add epsilon tolerance to handle ambiguous cases
60
+ const epsilon = 1;
61
+ let finalDotProduct = dotProduct;
62
+
63
+ if (Math.abs(dotProduct) < epsilon) {
64
+ // Too close to tell - nudge in the direction it was already leaning
65
+ finalDotProduct = dotProduct >= 0 ? epsilon : -epsilon;
66
+ }
67
+
68
+ // STEP 1: First determine winding based on reference point calculations
69
+ let v1, v2, v3;
70
+ if (finalDotProduct >= 0) {
71
+ v1 = a;
72
+ v2 = b;
73
+ v3 = c;
74
+ } else {
75
+ v1 = a;
76
+ v2 = c;
77
+ v3 = b;
78
+ }
79
+
80
+ // STEP 2: AFTER reference-based calculation, apply forceFlip if requested
81
+ if (forceFlip) {
82
+ // Swap v2 and v3 to flip the triangle
83
+ const temp = v2;
84
+ v2 = v3;
85
+ v3 = temp;
86
+ }
87
+
88
+ // Add the triangle with final winding
89
+ indices.push(v1, v2, v3);
90
+
91
+ // If double-sided, add a second triangle with opposite winding
92
+ if (doubleSided) {
93
+ indices.push(v1, v3, v2);
94
+ }
95
+ }
96
+
97
+ /**
98
+ * Quad helper method
99
+ */
100
+ createQuad(indices, positions, a, b, c, d, forceFlip = false, doubleSided = false) {
101
+ this.createTriangle(indices, positions, a, b, c, forceFlip, doubleSided);
102
+ this.createTriangle(indices, positions, a, c, d, forceFlip, doubleSided);
103
+ }
104
+
105
+ /**
106
+ * Convert GeometryBuilder output to ActionEngine physics object
107
+ * This bridges the gap between GeometryBuilder and ActionEngine's physics system
108
+ * @param {Object} physicsWorld - ActionEngine physics world to add object to
109
+ * @param {Array} vertices - Flat vertex array [x,y,z, x,y,z, ...]
110
+ * @param {Array} normals - Vertex normals (currently unused but kept for future)
111
+ * @param {Array} colors - Vertex colors [r,g,b, r,g,b, ...]
112
+ * @param {Array} indices - Triangle indices [i1,i2,i3, ...]
113
+ * @param {number} mass - Physics mass (0 = static)
114
+ * @param {Vector3} position - World position
115
+ * @returns {ActionPhysicsMesh3D} - Ready-to-use physics object
116
+ */
117
+ createPhysicsObject(physicsWorld, vertices, normals, colors, indices, mass, position) {
118
+ // Convert flat vertex array to Vector3 array
119
+ const vector3Vertices = [];
120
+ for (let i = 0; i < vertices.length; i += 3) {
121
+ vector3Vertices.push(new Vector3(vertices[i], vertices[i+1], vertices[i+2]));
122
+ }
123
+
124
+ // Convert colors to hex strings for triangles
125
+ const triangleColors = [];
126
+ for (let i = 0; i < indices.length; i += 3) {
127
+ // Use first vertex color for the triangle
128
+ const vertexIndex = indices[i];
129
+ const r = Math.round(colors[vertexIndex * 3] * 255);
130
+ const g = Math.round(colors[vertexIndex * 3 + 1] * 255);
131
+ const b = Math.round(colors[vertexIndex * 3 + 2] * 255);
132
+ const color = `#${r.toString(16).padStart(2, '0')}${g.toString(16).padStart(2, '0')}${b.toString(16).padStart(2, '0')}`;
133
+ triangleColors.push(color);
134
+ }
135
+
136
+ // Use ActionEngine's proper mesh physics class
137
+ const physicsObject = new ActionPhysicsMesh3D(
138
+ physicsWorld,
139
+ vector3Vertices,
140
+ indices,
141
+ mass,
142
+ position,
143
+ triangleColors
144
+ );
145
+
146
+ // Add to physics world
147
+ physicsWorld.addObject(physicsObject);
148
+ return physicsObject;
149
+ }
150
+
151
+ // Static methods for backward compatibility with existing code that uses GeometryBuilder.createTriangle()
152
+ static createTriangle(indices, positions, a, b, c, forceFlip = false, doubleSided = false) {
153
+ const builder = new GeometryBuilder();
154
+ return builder.createTriangle(indices, positions, a, b, c, forceFlip, doubleSided);
155
+ }
156
+
157
+ static createQuad(indices, positions, a, b, c, d, forceFlip = false, doubleSided = false) {
158
+ const builder = new GeometryBuilder();
159
+ return builder.createQuad(indices, positions, a, b, c, d, forceFlip, doubleSided);
160
+ }
161
+ }
@@ -0,0 +1,364 @@
1
+ // actionengine/math/geometry/glbexporter.js
2
+
3
+ /**
4
+ * GLBExporter handles exporting ActionEngine Triangle arrays to GLTF/GLB format.
5
+ * Uses materials for each color with proper primitive separation.
6
+ * Pure ActionEngine format.
7
+ */
8
+ class GLBExporter {
9
+ constructor() {
10
+ this.textEncoder = new TextEncoder();
11
+ }
12
+
13
+ /**
14
+ * Export Triangle array to GLB file and trigger download
15
+ * @param {Triangle[]} triangles - Array of Triangle objects
16
+ * @param {string} filename - Output filename (without extension)
17
+ */
18
+ static exportTriangles(triangles, filename = 'model') {
19
+ try {
20
+ const exporter = new GLBExporter();
21
+ const glbBuffer = exporter.createGLBFromTriangles(triangles, filename);
22
+ exporter.downloadFile(glbBuffer, `${filename}.glb`);
23
+ console.log(`Exported ${filename}.glb from ${triangles.length} triangles`);
24
+ } catch (error) {
25
+ console.error('GLB export failed:', error);
26
+ throw error;
27
+ }
28
+ }
29
+
30
+ /**
31
+ * Create GLB from triangles using materials for each color
32
+ */
33
+ createGLBFromTriangles(triangles, modelName) {
34
+ // Group triangles by color
35
+ const colorGroups = new Map();
36
+ const allVertices = [];
37
+
38
+ triangles.forEach((triangle, triIndex) => {
39
+ const color = triangle.color || '#808080';
40
+
41
+ if (!colorGroups.has(color)) {
42
+ colorGroups.set(color, {
43
+ triangles: [],
44
+ indices: []
45
+ });
46
+ }
47
+
48
+ // Add vertices to global array
49
+ const startIndex = allVertices.length;
50
+ triangle.vertices.forEach(vertex => {
51
+ allVertices.push(vertex);
52
+ });
53
+
54
+ // Store indices for this color group
55
+ colorGroups.get(color).indices.push(startIndex, startIndex + 1, startIndex + 2);
56
+ colorGroups.get(color).triangles.push(triangle);
57
+ });
58
+
59
+ console.log(`GLB Export: ${triangles.length} triangles, ${colorGroups.size} colors:`, Array.from(colorGroups.keys()));
60
+
61
+ // Create GLTF structure
62
+ const gltf = this.createGLTFWithMaterials(allVertices, colorGroups, modelName);
63
+
64
+ // Create binary data
65
+ const binaryData = this.createBinaryData(allVertices, colorGroups);
66
+
67
+ return this.assembleGLB(gltf, binaryData);
68
+ }
69
+
70
+ /**
71
+ * Create GLTF structure with separate materials and primitives
72
+ */
73
+ createGLTFWithMaterials(allVertices, colorGroups, modelName) {
74
+ const vertexCount = allVertices.length;
75
+
76
+ // Calculate buffer sizes
77
+ const positionsSize = vertexCount * 3 * 4; // Float32
78
+ const normalsSize = vertexCount * 3 * 4; // Float32
79
+
80
+ // Calculate index buffer sizes for each color group
81
+ let totalIndicesSize = 0;
82
+ for (const group of colorGroups.values()) {
83
+ totalIndicesSize += group.indices.length * 2; // Uint16
84
+ }
85
+
86
+ const totalBufferSize = positionsSize + normalsSize + totalIndicesSize;
87
+
88
+ let bufferOffset = 0;
89
+ let accessorIndex = 0;
90
+ let bufferViewIndex = 0;
91
+
92
+ const gltf = {
93
+ asset: {
94
+ version: "2.0",
95
+ generator: "ActionEngine GLBExporter"
96
+ },
97
+ scene: 0,
98
+ scenes: [{ nodes: [0] }],
99
+ nodes: [{ mesh: 0, name: modelName }],
100
+ meshes: [{
101
+ name: modelName,
102
+ primitives: []
103
+ }],
104
+ materials: [],
105
+ buffers: [{ byteLength: totalBufferSize }],
106
+ bufferViews: [],
107
+ accessors: []
108
+ };
109
+
110
+ // Create shared position and normal accessors
111
+ // Positions buffer view
112
+ gltf.bufferViews.push({
113
+ buffer: 0,
114
+ byteOffset: bufferOffset,
115
+ byteLength: positionsSize,
116
+ target: 34962 // ARRAY_BUFFER
117
+ });
118
+ bufferOffset += positionsSize;
119
+
120
+ // Positions accessor
121
+ gltf.accessors.push({
122
+ bufferView: bufferViewIndex++,
123
+ componentType: 5126, // FLOAT
124
+ count: vertexCount,
125
+ type: "VEC3",
126
+ min: this.calculateMinVertices(allVertices),
127
+ max: this.calculateMaxVertices(allVertices)
128
+ });
129
+ const positionAccessor = accessorIndex++;
130
+
131
+ // Normals buffer view
132
+ gltf.bufferViews.push({
133
+ buffer: 0,
134
+ byteOffset: bufferOffset,
135
+ byteLength: normalsSize,
136
+ target: 34962 // ARRAY_BUFFER
137
+ });
138
+ bufferOffset += normalsSize;
139
+
140
+ // Normals accessor
141
+ gltf.accessors.push({
142
+ bufferView: bufferViewIndex++,
143
+ componentType: 5126, // FLOAT
144
+ count: vertexCount,
145
+ type: "VEC3"
146
+ });
147
+ const normalAccessor = accessorIndex++;
148
+
149
+ // Create material and primitive for each color group
150
+ let materialIndex = 0;
151
+ for (const [color, group] of colorGroups) {
152
+ // Create material
153
+ const rgb = this.hexToRgb(color);
154
+ gltf.materials.push({
155
+ name: `Material_${color.slice(1)}`,
156
+ pbrMetallicRoughness: {
157
+ baseColorFactor: [rgb.r / 255, rgb.g / 255, rgb.b / 255, 1.0],
158
+ metallicFactor: 0.0,
159
+ roughnessFactor: 1.0
160
+ }
161
+ });
162
+
163
+ // Create indices buffer view for this color group
164
+ const indicesSize = group.indices.length * 2;
165
+ gltf.bufferViews.push({
166
+ buffer: 0,
167
+ byteOffset: bufferOffset,
168
+ byteLength: indicesSize,
169
+ target: 34963 // ELEMENT_ARRAY_BUFFER
170
+ });
171
+ bufferOffset += indicesSize;
172
+
173
+ // Create indices accessor
174
+ gltf.accessors.push({
175
+ bufferView: bufferViewIndex++,
176
+ componentType: 5123, // UNSIGNED_SHORT
177
+ count: group.indices.length,
178
+ type: "SCALAR"
179
+ });
180
+
181
+ // Create primitive
182
+ gltf.meshes[0].primitives.push({
183
+ attributes: {
184
+ POSITION: positionAccessor,
185
+ NORMAL: normalAccessor
186
+ },
187
+ indices: accessorIndex++,
188
+ material: materialIndex++,
189
+ mode: 4 // TRIANGLES
190
+ });
191
+ }
192
+
193
+ return gltf;
194
+ }
195
+
196
+ /**
197
+ * Create binary data with shared vertices and separate indices
198
+ */
199
+ createBinaryData(allVertices, colorGroups) {
200
+ // Create position data
201
+ const positions = new Float32Array(allVertices.length * 3);
202
+ const normals = new Float32Array(allVertices.length * 3);
203
+
204
+ // We need to calculate normals from triangles since vertices are shared
205
+ const vertexNormals = new Array(allVertices.length).fill(null).map(() => new Vector3(0, 0, 0));
206
+ const vertexCounts = new Array(allVertices.length).fill(0);
207
+
208
+ // Calculate normals by averaging triangle normals
209
+ let vertexIndex = 0;
210
+ for (const [color, group] of colorGroups) {
211
+ group.triangles.forEach(triangle => {
212
+ for (let i = 0; i < 3; i++) {
213
+ vertexNormals[vertexIndex].x += triangle.normal.x;
214
+ vertexNormals[vertexIndex].y += triangle.normal.y;
215
+ vertexNormals[vertexIndex].z += triangle.normal.z;
216
+ vertexCounts[vertexIndex]++;
217
+ vertexIndex++;
218
+ }
219
+ });
220
+ }
221
+
222
+ // Normalize and fill arrays
223
+ for (let i = 0; i < allVertices.length; i++) {
224
+ const vertex = allVertices[i];
225
+ positions[i * 3] = vertex.x;
226
+ positions[i * 3 + 1] = vertex.y;
227
+ positions[i * 3 + 2] = vertex.z;
228
+
229
+ // Normalize the accumulated normal
230
+ if (vertexCounts[i] > 0) {
231
+ vertexNormals[i].x /= vertexCounts[i];
232
+ vertexNormals[i].y /= vertexCounts[i];
233
+ vertexNormals[i].z /= vertexCounts[i];
234
+ const length = Math.sqrt(
235
+ vertexNormals[i].x * vertexNormals[i].x +
236
+ vertexNormals[i].y * vertexNormals[i].y +
237
+ vertexNormals[i].z * vertexNormals[i].z
238
+ );
239
+ if (length > 0) {
240
+ vertexNormals[i].x /= length;
241
+ vertexNormals[i].y /= length;
242
+ vertexNormals[i].z /= length;
243
+ }
244
+ }
245
+
246
+ normals[i * 3] = vertexNormals[i].x;
247
+ normals[i * 3 + 1] = vertexNormals[i].y;
248
+ normals[i * 3 + 2] = vertexNormals[i].z;
249
+ }
250
+
251
+ // Create index buffers for each color group
252
+ const indexBuffers = [];
253
+ for (const [color, group] of colorGroups) {
254
+ const indices = new Uint16Array(group.indices);
255
+ indexBuffers.push(indices);
256
+ }
257
+
258
+ // Combine all buffers
259
+ const totalSize = positions.byteLength + normals.byteLength +
260
+ indexBuffers.reduce((sum, buf) => sum + buf.byteLength, 0);
261
+
262
+ const combinedBuffer = new ArrayBuffer(totalSize);
263
+ const view = new Uint8Array(combinedBuffer);
264
+
265
+ let offset = 0;
266
+
267
+ // Copy positions
268
+ view.set(new Uint8Array(positions.buffer), offset);
269
+ offset += positions.byteLength;
270
+
271
+ // Copy normals
272
+ view.set(new Uint8Array(normals.buffer), offset);
273
+ offset += normals.byteLength;
274
+
275
+ // Copy index buffers
276
+ for (const indexBuffer of indexBuffers) {
277
+ view.set(new Uint8Array(indexBuffer.buffer), offset);
278
+ offset += indexBuffer.byteLength;
279
+ }
280
+
281
+ return combinedBuffer;
282
+ }
283
+
284
+ /**
285
+ * Standard GLB assembly
286
+ */
287
+ assembleGLB(gltf, binaryData) {
288
+ const jsonString = JSON.stringify(gltf);
289
+ const jsonBuffer = this.textEncoder.encode(jsonString);
290
+
291
+ const jsonPadding = (4 - (jsonBuffer.length % 4)) % 4;
292
+ const paddedJsonLength = jsonBuffer.length + jsonPadding;
293
+
294
+ const binaryPadding = (4 - (binaryData.byteLength % 4)) % 4;
295
+ const paddedBinaryLength = binaryData.byteLength + binaryPadding;
296
+
297
+ const totalSize = 12 + 8 + paddedJsonLength + 8 + paddedBinaryLength;
298
+
299
+ const glb = new ArrayBuffer(totalSize);
300
+ const view = new DataView(glb);
301
+ const bytes = new Uint8Array(glb);
302
+
303
+ let offset = 0;
304
+
305
+ // GLB header
306
+ view.setUint32(offset, 0x46546C67, true); // 'glTF'
307
+ view.setUint32(offset + 4, 2, true); // version
308
+ view.setUint32(offset + 8, totalSize, true);
309
+ offset += 12;
310
+
311
+ // JSON chunk
312
+ view.setUint32(offset, paddedJsonLength, true);
313
+ view.setUint32(offset + 4, 0x4E4F534A, true); // 'JSON'
314
+ offset += 8;
315
+ bytes.set(jsonBuffer, offset);
316
+ offset += jsonBuffer.length;
317
+ for (let i = 0; i < jsonPadding; i++) {
318
+ bytes[offset++] = 0x20;
319
+ }
320
+
321
+ // Binary chunk
322
+ view.setUint32(offset, paddedBinaryLength, true);
323
+ view.setUint32(offset + 4, 0x004E4942, true); // 'BIN\0'
324
+ offset += 8;
325
+ bytes.set(new Uint8Array(binaryData), offset);
326
+
327
+ return glb;
328
+ }
329
+
330
+ downloadFile(buffer, filename) {
331
+ const blob = new Blob([buffer], { type: 'application/octet-stream' });
332
+ const url = URL.createObjectURL(blob);
333
+ const a = document.createElement('a');
334
+ a.href = url;
335
+ a.download = filename;
336
+ a.click();
337
+ URL.revokeObjectURL(url);
338
+ }
339
+
340
+ hexToRgb(hex) {
341
+ const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
342
+ return result ? {
343
+ r: parseInt(result[1], 16),
344
+ g: parseInt(result[2], 16),
345
+ b: parseInt(result[3], 16)
346
+ } : { r: 128, g: 128, b: 128 };
347
+ }
348
+
349
+ calculateMinVertices(vertices) {
350
+ return [
351
+ Math.min(...vertices.map(v => v.x)),
352
+ Math.min(...vertices.map(v => v.y)),
353
+ Math.min(...vertices.map(v => v.z))
354
+ ];
355
+ }
356
+
357
+ calculateMaxVertices(vertices) {
358
+ return [
359
+ Math.max(...vertices.map(v => v.x)),
360
+ Math.max(...vertices.map(v => v.y)),
361
+ Math.max(...vertices.map(v => v.z))
362
+ ];
363
+ }
364
+ }