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,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
|
+
}
|