@playdrop/playdrop-cli 0.7.13 → 0.7.16
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/config/client-meta.json +1 -1
- package/node_modules/@playdrop/ai-client/dist/index.js +461 -444
- package/node_modules/@playdrop/ai-client/package.json +1 -1
- package/node_modules/@playdrop/api-client/dist/client.js +903 -882
- package/node_modules/@playdrop/api-client/dist/core/errors.js +69 -45
- package/node_modules/@playdrop/api-client/dist/core/request.js +188 -159
- package/node_modules/@playdrop/api-client/dist/domains/admin.js +516 -491
- package/node_modules/@playdrop/api-client/dist/domains/ai.js +40 -14
- package/node_modules/@playdrop/api-client/dist/domains/apps.js +480 -462
- package/node_modules/@playdrop/api-client/dist/domains/asset-packs.js +439 -419
- package/node_modules/@playdrop/api-client/dist/domains/assets.js +696 -676
- package/node_modules/@playdrop/api-client/dist/domains/auth.js +346 -320
- package/node_modules/@playdrop/api-client/dist/domains/comments.js +124 -98
- package/node_modules/@playdrop/api-client/dist/domains/free-credits.js +91 -65
- package/node_modules/@playdrop/api-client/dist/domains/me.js +71 -45
- package/node_modules/@playdrop/api-client/dist/domains/payments.js +407 -386
- package/node_modules/@playdrop/api-client/dist/domains/player-meta.js +144 -118
- package/node_modules/@playdrop/api-client/dist/domains/search.js +117 -104
- package/node_modules/@playdrop/api-client/dist/domains/tags.js +188 -162
- package/node_modules/@playdrop/api-client/dist/index.js +993 -552
- package/node_modules/@playdrop/api-client/package.json +1 -1
- package/node_modules/@playdrop/boxel-three/dist/src/animations.js +84 -62
- package/node_modules/@playdrop/boxel-three/dist/src/builders.js +341 -308
- package/node_modules/@playdrop/boxel-three/dist/src/context.js +55 -29
- package/node_modules/@playdrop/boxel-three/dist/src/exporters/glb.js +856 -858
- package/node_modules/@playdrop/boxel-three/dist/src/exporters/image.js +267 -261
- package/node_modules/@playdrop/boxel-three/dist/src/index.js +64 -15
- package/node_modules/@playdrop/boxel-three/dist/src/instantiate.js +89 -63
- package/node_modules/@playdrop/boxel-three/dist/src/nodes.js +81 -56
- package/node_modules/@playdrop/boxel-three/dist/src/overlays.js +112 -86
- package/node_modules/@playdrop/boxel-three/dist/src/primitives.js +45 -17
- package/node_modules/@playdrop/boxel-three/dist/src/scene.js +160 -136
- package/node_modules/@playdrop/boxel-three/dist/src/skinned-mesh.js +582 -584
- package/node_modules/@playdrop/boxel-three/dist/src/texture-atlas.js +123 -97
- package/node_modules/@playdrop/boxel-three/dist/src/textures.js +207 -182
- package/node_modules/@playdrop/boxel-three/dist/src/types.js +15 -1
- package/node_modules/@playdrop/boxel-three/dist/src/voxels/faces.js +451 -425
- package/node_modules/@playdrop/boxel-three/dist/src/voxels/mesher.js +109 -84
- package/node_modules/@playdrop/boxel-three/dist/test/export-image.playwright.test.js +127 -106
- package/node_modules/@playdrop/boxel-three/dist/test/fixtures/render-worker.js +73 -51
- package/node_modules/@playdrop/boxel-three/dist/test/glb-skinned.test.js +97 -79
- package/node_modules/@playdrop/boxel-three/dist/test/index.test.js +29 -7
- package/node_modules/@playdrop/boxel-three/dist/test/instantiate.test.js +80 -60
- package/node_modules/@playdrop/boxel-three/dist/test/overlays.test.js +41 -19
- package/node_modules/@playdrop/boxel-three/dist/test/scene-filter.test.js +72 -50
- package/node_modules/@playdrop/boxel-three/dist/test/scene-smoke.test.js +84 -62
- package/node_modules/@playdrop/boxel-three/dist/test/skinned-mesh.test.js +69 -47
- package/node_modules/@playdrop/boxel-three/dist/test/textured-overlay.test.js +129 -109
- package/node_modules/@playdrop/boxel-three/dist/test/voxels.test.js +40 -18
- package/node_modules/@playdrop/boxel-three/package.json +1 -1
- package/node_modules/@playdrop/config/client-meta.json +1 -1
- package/node_modules/@playdrop/config/dist/tsconfig.tsbuildinfo +1 -1
- package/node_modules/@playdrop/types/dist/api.js +289 -203
- package/node_modules/@playdrop/types/dist/app-capability-filters.js +112 -62
- package/node_modules/@playdrop/types/dist/app.js +91 -45
- package/node_modules/@playdrop/types/dist/asset-pack.js +37 -5
- package/node_modules/@playdrop/types/dist/asset-spec.js +170 -90
- package/node_modules/@playdrop/types/dist/asset.js +186 -108
- package/node_modules/@playdrop/types/dist/content-license.js +49 -15
- package/node_modules/@playdrop/types/dist/creator-public-image.js +60 -32
- package/node_modules/@playdrop/types/dist/ecs.js +102 -82
- package/node_modules/@playdrop/types/dist/engine-builtins.js +603 -573
- package/node_modules/@playdrop/types/dist/entity.js +63 -53
- package/node_modules/@playdrop/types/dist/graph.js +116 -80
- package/node_modules/@playdrop/types/dist/index.js +47 -20
- package/node_modules/@playdrop/types/dist/owned-assets.js +55 -33
- package/node_modules/@playdrop/types/dist/player-meta.js +151 -100
- package/node_modules/@playdrop/types/dist/realtime.js +27 -7
- package/node_modules/@playdrop/types/dist/version.js +182 -124
- package/node_modules/@playdrop/types/package.json +1 -1
- package/package.json +1 -1
|
@@ -1,931 +1,929 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
3
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
4
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
5
|
+
var __export = (target, all) => {
|
|
6
|
+
for (var name in all)
|
|
7
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
8
|
+
};
|
|
9
|
+
var __copyProps = (to, from, except, desc) => {
|
|
10
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
11
|
+
for (let key of __getOwnPropNames(from))
|
|
12
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
13
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
14
|
+
}
|
|
15
|
+
return to;
|
|
16
|
+
};
|
|
17
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
18
|
+
var glb_exports = {};
|
|
19
|
+
__export(glb_exports, {
|
|
20
|
+
__testing: () => __testing,
|
|
21
|
+
createBoxelSceneGraph: () => createBoxelSceneGraph,
|
|
22
|
+
exportModelToGLB: () => exportModelToGLB
|
|
23
|
+
});
|
|
24
|
+
module.exports = __toCommonJS(glb_exports);
|
|
25
|
+
var import_faces = require("../voxels/faces.js");
|
|
26
|
+
var import_primitives = require("../primitives.js");
|
|
27
|
+
var import_scene = require("../scene.js");
|
|
28
|
+
var import_skinned_mesh = require("../skinned-mesh.js");
|
|
29
|
+
var import_animations = require("../animations.js");
|
|
6
30
|
const DEFAULT_COLOR = [59, 130, 246];
|
|
7
31
|
const DEFAULT_OPACITY = 0.45;
|
|
8
32
|
const ATLAS_PADDING = 1;
|
|
9
|
-
const FALLBACK_TEXTURE_ID =
|
|
10
|
-
const OVERLAY_FALLBACK_TEXTURE_ID =
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
const exporter = new GLTFExporterCtor();
|
|
31
|
-
const dispose = () => {
|
|
32
|
-
skinned.dispose();
|
|
33
|
-
};
|
|
34
|
-
return new Promise((resolve, reject) => {
|
|
35
|
-
try {
|
|
36
|
-
exporter.parse(root, result => {
|
|
37
|
-
try {
|
|
38
|
-
const blob = result instanceof ArrayBuffer
|
|
39
|
-
? new Blob([result], { type: 'model/gltf-binary' })
|
|
40
|
-
: new Blob([JSON.stringify(result)], { type: 'model/gltf+json' });
|
|
41
|
-
dispose();
|
|
42
|
-
resolve(blob);
|
|
43
|
-
}
|
|
44
|
-
catch (error) {
|
|
45
|
-
dispose();
|
|
46
|
-
reject(error);
|
|
47
|
-
}
|
|
48
|
-
}, error => {
|
|
49
|
-
dispose();
|
|
50
|
-
reject(error instanceof Error ? error : new Error('gltf_export_failed'));
|
|
51
|
-
}, { binary, animations: clips });
|
|
52
|
-
}
|
|
53
|
-
catch (error) {
|
|
54
|
-
dispose();
|
|
55
|
-
reject(error instanceof Error ? error : new Error(String(error)));
|
|
56
|
-
}
|
|
57
|
-
});
|
|
58
|
-
}
|
|
59
|
-
const context = createExportContext(THREE, model);
|
|
60
|
-
const sceneGraph = buildSceneGraph(context, mode);
|
|
61
|
-
const exporter = new GLTFExporterCtor();
|
|
62
|
-
const exportScene = new THREE.Scene();
|
|
63
|
-
exportScene.background = null;
|
|
64
|
-
exportScene.add(sceneGraph.root);
|
|
65
|
-
exportScene.updateMatrixWorld(true);
|
|
66
|
-
const animationClips = resolveAnimationClips(sceneGraph, mode, animationIds);
|
|
33
|
+
const FALLBACK_TEXTURE_ID = "__fallback__";
|
|
34
|
+
const OVERLAY_FALLBACK_TEXTURE_ID = "__overlay_fallback__";
|
|
35
|
+
async function exportModelToGLB(options) {
|
|
36
|
+
const { THREE, GLTFExporterCtor, model, mode = "merged", binary = true, animationIds } = options;
|
|
37
|
+
if (mode === "skinned") {
|
|
38
|
+
const skinned = (0, import_skinned_mesh.buildEntitySkinnedMesh)({ THREE, entity: model });
|
|
39
|
+
const root = new THREE.Group();
|
|
40
|
+
root.add(skinned.mesh);
|
|
41
|
+
root.updateMatrixWorld(true);
|
|
42
|
+
const nodeLookup = /* @__PURE__ */ new Map();
|
|
43
|
+
skinned.boneMap.forEach((bone, nodeId) => nodeLookup.set(nodeId, bone));
|
|
44
|
+
const animations = (0, import_animations.buildAnimations)(THREE, model, nodeLookup);
|
|
45
|
+
const clips = animationIds === null ? [] : animations.filter((animation) => {
|
|
46
|
+
if (!Array.isArray(animationIds) || animationIds.length === 0)
|
|
47
|
+
return true;
|
|
48
|
+
return animationIds.includes(animation.id);
|
|
49
|
+
}).map((animation) => animation.clip);
|
|
50
|
+
const exporter2 = new GLTFExporterCtor();
|
|
51
|
+
const dispose = () => {
|
|
52
|
+
skinned.dispose();
|
|
53
|
+
};
|
|
67
54
|
return new Promise((resolve, reject) => {
|
|
55
|
+
try {
|
|
56
|
+
exporter2.parse(root, (result) => {
|
|
57
|
+
try {
|
|
58
|
+
const blob = result instanceof ArrayBuffer ? new Blob([result], { type: "model/gltf-binary" }) : new Blob([JSON.stringify(result)], { type: "model/gltf+json" });
|
|
59
|
+
dispose();
|
|
60
|
+
resolve(blob);
|
|
61
|
+
} catch (error) {
|
|
62
|
+
dispose();
|
|
63
|
+
reject(error);
|
|
64
|
+
}
|
|
65
|
+
}, (error) => {
|
|
66
|
+
dispose();
|
|
67
|
+
reject(error instanceof Error ? error : new Error("gltf_export_failed"));
|
|
68
|
+
}, { binary, animations: clips });
|
|
69
|
+
} catch (error) {
|
|
70
|
+
dispose();
|
|
71
|
+
reject(error instanceof Error ? error : new Error(String(error)));
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
const context = createExportContext(THREE, model);
|
|
76
|
+
const sceneGraph = buildSceneGraph(context, mode);
|
|
77
|
+
const exporter = new GLTFExporterCtor();
|
|
78
|
+
const exportScene = new THREE.Scene();
|
|
79
|
+
exportScene.background = null;
|
|
80
|
+
exportScene.add(sceneGraph.root);
|
|
81
|
+
exportScene.updateMatrixWorld(true);
|
|
82
|
+
const animationClips = resolveAnimationClips(sceneGraph, mode, animationIds);
|
|
83
|
+
return new Promise((resolve, reject) => {
|
|
84
|
+
try {
|
|
85
|
+
exporter.parse(exportScene, (result) => {
|
|
68
86
|
try {
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
}
|
|
78
|
-
catch (error) {
|
|
79
|
-
sceneGraph.dispose();
|
|
80
|
-
disposeExportContext(context);
|
|
81
|
-
reject(error);
|
|
82
|
-
}
|
|
83
|
-
}, error => {
|
|
84
|
-
sceneGraph.dispose();
|
|
85
|
-
disposeExportContext(context);
|
|
86
|
-
reject(error instanceof Error ? error : new Error('gltf_export_failed'));
|
|
87
|
-
}, { binary, animations: animationClips });
|
|
87
|
+
const blob = result instanceof ArrayBuffer ? new Blob([result], { type: "model/gltf-binary" }) : new Blob([JSON.stringify(result)], { type: "model/gltf+json" });
|
|
88
|
+
sceneGraph.dispose();
|
|
89
|
+
disposeExportContext(context);
|
|
90
|
+
resolve(blob);
|
|
91
|
+
} catch (error) {
|
|
92
|
+
sceneGraph.dispose();
|
|
93
|
+
disposeExportContext(context);
|
|
94
|
+
reject(error);
|
|
88
95
|
}
|
|
89
|
-
|
|
90
|
-
sceneGraph.dispose();
|
|
91
|
-
disposeExportContext(context);
|
|
92
|
-
reject(error instanceof Error ? error : new Error(String(error)));
|
|
93
|
-
}
|
|
94
|
-
});
|
|
95
|
-
}
|
|
96
|
-
export function createBoxelSceneGraph(options) {
|
|
97
|
-
const { THREE, model } = options;
|
|
98
|
-
const mode = options.mode ?? 'merged';
|
|
99
|
-
const context = createExportContext(THREE, model);
|
|
100
|
-
const sceneGraph = buildSceneGraph(context, mode);
|
|
101
|
-
let disposed = false;
|
|
102
|
-
const dispose = () => {
|
|
103
|
-
if (disposed)
|
|
104
|
-
return;
|
|
96
|
+
}, (error) => {
|
|
105
97
|
sceneGraph.dispose();
|
|
106
98
|
disposeExportContext(context);
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
99
|
+
reject(error instanceof Error ? error : new Error("gltf_export_failed"));
|
|
100
|
+
}, { binary, animations: animationClips });
|
|
101
|
+
} catch (error) {
|
|
102
|
+
sceneGraph.dispose();
|
|
103
|
+
disposeExportContext(context);
|
|
104
|
+
reject(error instanceof Error ? error : new Error(String(error)));
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
function createBoxelSceneGraph(options) {
|
|
109
|
+
const { THREE, model } = options;
|
|
110
|
+
const mode = options.mode ?? "merged";
|
|
111
|
+
const context = createExportContext(THREE, model);
|
|
112
|
+
const sceneGraph = buildSceneGraph(context, mode);
|
|
113
|
+
let disposed = false;
|
|
114
|
+
const dispose = () => {
|
|
115
|
+
if (disposed)
|
|
116
|
+
return;
|
|
117
|
+
sceneGraph.dispose();
|
|
118
|
+
disposeExportContext(context);
|
|
119
|
+
disposed = true;
|
|
120
|
+
};
|
|
121
|
+
return {
|
|
122
|
+
root: sceneGraph.root,
|
|
123
|
+
nodeLookup: sceneGraph.nodeLookup,
|
|
124
|
+
animations: sceneGraph.animations,
|
|
125
|
+
dispose
|
|
126
|
+
};
|
|
115
127
|
}
|
|
116
128
|
function createExportContext(THREE, model) {
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
});
|
|
138
|
-
if (surfaces.length === 0) {
|
|
139
|
-
return;
|
|
140
|
-
}
|
|
141
|
-
const cachedSurfaces = surfaces.map((surface, index) => {
|
|
142
|
-
const textureId = `__voxel__${primitive.id}__${surface.face}__${index}`;
|
|
143
|
-
voxelAtlasEntries.push({
|
|
144
|
-
id: textureId,
|
|
145
|
-
width: surface.textureWidth,
|
|
146
|
-
height: surface.textureHeight,
|
|
147
|
-
color: surface.color,
|
|
148
|
-
emissive: surface.emissive,
|
|
149
|
-
hasEmissive: surface.hasEmissive,
|
|
150
|
-
hasTransparency: surface.hasTransparency,
|
|
151
|
-
});
|
|
152
|
-
return { ...surface, textureId };
|
|
153
|
-
});
|
|
154
|
-
voxelSurfacesByPrimitive.set(primitive.id, cachedSurfaces);
|
|
129
|
+
const materialsById = new Map(model.materials.map((material) => [material.id, material]));
|
|
130
|
+
const primitivesById = new Map(model.primitives.map((primitive) => [primitive.id, primitive]));
|
|
131
|
+
const texturesById = new Map(model.textures.map((texture) => [texture.id, texture]));
|
|
132
|
+
const patternsById = new Map(model.patterns.map((pattern) => [pattern.id, pattern]));
|
|
133
|
+
const voxelAtlasEntries = [];
|
|
134
|
+
const voxelSurfacesByPrimitive = /* @__PURE__ */ new Map();
|
|
135
|
+
let overlayFallbackNeeded = false;
|
|
136
|
+
model.primitives.forEach((primitive) => {
|
|
137
|
+
if (primitive.kind === "textured_box") {
|
|
138
|
+
if (hasOverlayTextures(primitive)) {
|
|
139
|
+
overlayFallbackNeeded = true;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
if (primitive.kind !== "voxel_box") {
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
const surfaces = (0, import_faces.generateVoxelFaceSurfaces)({
|
|
146
|
+
primitive,
|
|
147
|
+
materialsById,
|
|
148
|
+
defaultColor: DEFAULT_COLOR
|
|
155
149
|
});
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
150
|
+
if (surfaces.length === 0) {
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
const cachedSurfaces = surfaces.map((surface, index) => {
|
|
154
|
+
const textureId = `__voxel__${primitive.id}__${surface.face}__${index}`;
|
|
155
|
+
voxelAtlasEntries.push({
|
|
156
|
+
id: textureId,
|
|
157
|
+
width: surface.textureWidth,
|
|
158
|
+
height: surface.textureHeight,
|
|
159
|
+
color: surface.color,
|
|
160
|
+
emissive: surface.emissive,
|
|
161
|
+
hasEmissive: surface.hasEmissive,
|
|
162
|
+
hasTransparency: surface.hasTransparency
|
|
163
|
+
});
|
|
164
|
+
return { ...surface, textureId };
|
|
165
|
+
});
|
|
166
|
+
voxelSurfacesByPrimitive.set(primitive.id, cachedSurfaces);
|
|
167
|
+
});
|
|
168
|
+
const extraEntries = [...voxelAtlasEntries];
|
|
169
|
+
if (overlayFallbackNeeded) {
|
|
170
|
+
extraEntries.push(buildOverlayFallbackTexturePixels());
|
|
171
|
+
}
|
|
172
|
+
const usedTextureIds = collectUsedTextureIds(model, primitivesById);
|
|
173
|
+
const atlas = buildTextureAtlas({ THREE, materialsById, texturesById, patternsById, usedTextureIds }, extraEntries);
|
|
174
|
+
return {
|
|
175
|
+
THREE,
|
|
176
|
+
model,
|
|
177
|
+
materialsById,
|
|
178
|
+
primitivesById,
|
|
179
|
+
texturesById,
|
|
180
|
+
patternsById,
|
|
181
|
+
atlas,
|
|
182
|
+
voxelSurfacesByPrimitive,
|
|
183
|
+
plainMaterialCache: /* @__PURE__ */ new Map()
|
|
184
|
+
};
|
|
173
185
|
}
|
|
174
186
|
function disposeExportContext(context) {
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
187
|
+
if (context.atlas) {
|
|
188
|
+
context.atlas.map.dispose();
|
|
189
|
+
context.atlas.emissiveMap?.dispose();
|
|
190
|
+
}
|
|
191
|
+
context.plainMaterialCache.forEach((material) => material.dispose());
|
|
192
|
+
context.plainMaterialCache.clear();
|
|
181
193
|
}
|
|
182
194
|
function buildTextureAtlas(options, extraEntries = []) {
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
});
|
|
203
|
-
}
|
|
204
|
-
const fallbackEntry = buildFallbackTexturePixels();
|
|
205
|
-
entries.push(fallbackEntry);
|
|
206
|
-
const paddedWidths = entries.map(entry => entry.width + ATLAS_PADDING * 2);
|
|
207
|
-
const paddedHeights = entries.map(entry => entry.height + ATLAS_PADDING * 2);
|
|
208
|
-
const cellWidth = Math.max(...paddedWidths);
|
|
209
|
-
const cellHeight = Math.max(...paddedHeights);
|
|
210
|
-
const textureCount = entries.length;
|
|
211
|
-
const columns = Math.max(1, Math.ceil(Math.sqrt(textureCount)));
|
|
212
|
-
const rows = Math.max(1, Math.ceil(textureCount / columns));
|
|
213
|
-
const atlasWidth = THREE.MathUtils.ceilPowerOfTwo(columns * cellWidth);
|
|
214
|
-
const atlasHeight = THREE.MathUtils.ceilPowerOfTwo(rows * cellHeight);
|
|
215
|
-
const colorData = new Uint8Array(atlasWidth * atlasHeight * 4);
|
|
216
|
-
const emissiveData = new Uint8Array(atlasWidth * atlasHeight * 4);
|
|
217
|
-
let hasEmissive = false;
|
|
218
|
-
let hasTransparency = false;
|
|
219
|
-
const regions = new Map();
|
|
220
|
-
entries.forEach((entry, index) => {
|
|
221
|
-
const column = index % columns;
|
|
222
|
-
const row = Math.floor(index / columns);
|
|
223
|
-
const originX = column * cellWidth;
|
|
224
|
-
const originY = row * cellHeight;
|
|
225
|
-
const paddedX = originX + ATLAS_PADDING;
|
|
226
|
-
const paddedY = originY + ATLAS_PADDING;
|
|
227
|
-
blitTexture(entry.color, entry.width, entry.height, atlasWidth, colorData, paddedX, paddedY);
|
|
228
|
-
extendEdgePixels(colorData, entry.width, entry.height, atlasWidth, atlasHeight, paddedX, paddedY);
|
|
229
|
-
if (entry.hasEmissive && entry.emissive) {
|
|
230
|
-
blitTexture(entry.emissive, entry.width, entry.height, atlasWidth, emissiveData, paddedX, paddedY);
|
|
231
|
-
extendEdgePixels(emissiveData, entry.width, entry.height, atlasWidth, atlasHeight, paddedX, paddedY);
|
|
232
|
-
hasEmissive = true;
|
|
233
|
-
}
|
|
234
|
-
else {
|
|
235
|
-
fillTransparentRegion(emissiveData, entry.width, entry.height, atlasWidth, paddedX, paddedY);
|
|
236
|
-
}
|
|
237
|
-
hasTransparency = hasTransparency || entry.hasTransparency;
|
|
238
|
-
const region = {
|
|
239
|
-
uMin: (paddedX + 0) / atlasWidth,
|
|
240
|
-
vMin: (paddedY + 0) / atlasHeight,
|
|
241
|
-
uMax: (paddedX + entry.width) / atlasWidth,
|
|
242
|
-
vMax: (paddedY + entry.height) / atlasHeight,
|
|
243
|
-
};
|
|
244
|
-
regions.set(entry.id, region);
|
|
195
|
+
const { THREE, materialsById, texturesById, patternsById, usedTextureIds } = options;
|
|
196
|
+
if (usedTextureIds.size === 0) {
|
|
197
|
+
if (extraEntries.length === 0) {
|
|
198
|
+
return null;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
const entries = [];
|
|
202
|
+
for (const textureId of usedTextureIds) {
|
|
203
|
+
const textureDef = texturesById.get(textureId);
|
|
204
|
+
if (!textureDef)
|
|
205
|
+
continue;
|
|
206
|
+
const pattern = patternsById.get(textureDef.pattern);
|
|
207
|
+
if (!pattern)
|
|
208
|
+
continue;
|
|
209
|
+
entries.push(buildTexturePixels(textureDef, pattern, materialsById));
|
|
210
|
+
}
|
|
211
|
+
if (extraEntries.length > 0) {
|
|
212
|
+
extraEntries.forEach((entry) => {
|
|
213
|
+
entries.push(entry);
|
|
245
214
|
});
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
const
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
215
|
+
}
|
|
216
|
+
const fallbackEntry = buildFallbackTexturePixels();
|
|
217
|
+
entries.push(fallbackEntry);
|
|
218
|
+
const paddedWidths = entries.map((entry) => entry.width + ATLAS_PADDING * 2);
|
|
219
|
+
const paddedHeights = entries.map((entry) => entry.height + ATLAS_PADDING * 2);
|
|
220
|
+
const cellWidth = Math.max(...paddedWidths);
|
|
221
|
+
const cellHeight = Math.max(...paddedHeights);
|
|
222
|
+
const textureCount = entries.length;
|
|
223
|
+
const columns = Math.max(1, Math.ceil(Math.sqrt(textureCount)));
|
|
224
|
+
const rows = Math.max(1, Math.ceil(textureCount / columns));
|
|
225
|
+
const atlasWidth = THREE.MathUtils.ceilPowerOfTwo(columns * cellWidth);
|
|
226
|
+
const atlasHeight = THREE.MathUtils.ceilPowerOfTwo(rows * cellHeight);
|
|
227
|
+
const colorData = new Uint8Array(atlasWidth * atlasHeight * 4);
|
|
228
|
+
const emissiveData = new Uint8Array(atlasWidth * atlasHeight * 4);
|
|
229
|
+
let hasEmissive = false;
|
|
230
|
+
let hasTransparency = false;
|
|
231
|
+
const regions = /* @__PURE__ */ new Map();
|
|
232
|
+
entries.forEach((entry, index) => {
|
|
233
|
+
const column = index % columns;
|
|
234
|
+
const row = Math.floor(index / columns);
|
|
235
|
+
const originX = column * cellWidth;
|
|
236
|
+
const originY = row * cellHeight;
|
|
237
|
+
const paddedX = originX + ATLAS_PADDING;
|
|
238
|
+
const paddedY = originY + ATLAS_PADDING;
|
|
239
|
+
blitTexture(entry.color, entry.width, entry.height, atlasWidth, colorData, paddedX, paddedY);
|
|
240
|
+
extendEdgePixels(colorData, entry.width, entry.height, atlasWidth, atlasHeight, paddedX, paddedY);
|
|
241
|
+
if (entry.hasEmissive && entry.emissive) {
|
|
242
|
+
blitTexture(entry.emissive, entry.width, entry.height, atlasWidth, emissiveData, paddedX, paddedY);
|
|
243
|
+
extendEdgePixels(emissiveData, entry.width, entry.height, atlasWidth, atlasHeight, paddedX, paddedY);
|
|
244
|
+
hasEmissive = true;
|
|
245
|
+
} else {
|
|
246
|
+
fillTransparentRegion(emissiveData, entry.width, entry.height, atlasWidth, paddedX, paddedY);
|
|
247
|
+
}
|
|
248
|
+
hasTransparency = hasTransparency || entry.hasTransparency;
|
|
249
|
+
const region = {
|
|
250
|
+
uMin: (paddedX + 0) / atlasWidth,
|
|
251
|
+
vMin: (paddedY + 0) / atlasHeight,
|
|
252
|
+
uMax: (paddedX + entry.width) / atlasWidth,
|
|
253
|
+
vMax: (paddedY + entry.height) / atlasHeight
|
|
274
254
|
};
|
|
275
|
-
|
|
255
|
+
regions.set(entry.id, region);
|
|
256
|
+
});
|
|
257
|
+
const map = new THREE.DataTexture(colorData, atlasWidth, atlasHeight);
|
|
258
|
+
map.needsUpdate = true;
|
|
259
|
+
map.magFilter = THREE.NearestFilter;
|
|
260
|
+
map.minFilter = THREE.NearestFilter;
|
|
261
|
+
map.wrapS = THREE.ClampToEdgeWrapping;
|
|
262
|
+
map.wrapT = THREE.ClampToEdgeWrapping;
|
|
263
|
+
map.flipY = false;
|
|
264
|
+
if ("colorSpace" in map) {
|
|
265
|
+
map.colorSpace = THREE.SRGBColorSpace ?? map.colorSpace;
|
|
266
|
+
} else if ("encoding" in map) {
|
|
267
|
+
map.encoding = THREE.sRGBEncoding ?? map.encoding;
|
|
268
|
+
}
|
|
269
|
+
let emissiveMap = null;
|
|
270
|
+
if (hasEmissive) {
|
|
271
|
+
emissiveMap = new THREE.DataTexture(emissiveData, atlasWidth, atlasHeight);
|
|
272
|
+
emissiveMap.needsUpdate = true;
|
|
273
|
+
emissiveMap.magFilter = THREE.NearestFilter;
|
|
274
|
+
emissiveMap.minFilter = THREE.NearestFilter;
|
|
275
|
+
emissiveMap.wrapS = THREE.ClampToEdgeWrapping;
|
|
276
|
+
emissiveMap.wrapT = THREE.ClampToEdgeWrapping;
|
|
277
|
+
emissiveMap.flipY = false;
|
|
278
|
+
}
|
|
279
|
+
const fallbackRegion = regions.get(FALLBACK_TEXTURE_ID) ?? {
|
|
280
|
+
uMin: 0,
|
|
281
|
+
vMin: 0,
|
|
282
|
+
uMax: 1,
|
|
283
|
+
vMax: 1
|
|
284
|
+
};
|
|
285
|
+
return { regions, fallbackRegion, map, emissiveMap, hasTransparency };
|
|
276
286
|
}
|
|
277
287
|
function buildTexturePixels(textureDef, pattern, materialsById) {
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
return { id: textureDef.id, width, height, color, emissive: hasEmissive ? emissive : undefined, hasEmissive, hasTransparency };
|
|
288
|
+
const [width, height] = pattern.size;
|
|
289
|
+
const color = new Uint8Array(width * height * 4);
|
|
290
|
+
const emissive = new Uint8Array(width * height * 4);
|
|
291
|
+
let hasEmissive = false;
|
|
292
|
+
let hasTransparency = false;
|
|
293
|
+
for (let y = 0; y < height; y += 1) {
|
|
294
|
+
for (let x = 0; x < width; x += 1) {
|
|
295
|
+
const sourceIndex = y * width + x;
|
|
296
|
+
const targetY = height - 1 - y;
|
|
297
|
+
const targetIndex = (targetY * width + x) * 4;
|
|
298
|
+
const paletteIndex = pattern.data[sourceIndex];
|
|
299
|
+
const materialId = textureDef.materials[paletteIndex];
|
|
300
|
+
const material = materialId ? materialsById.get(materialId) : void 0;
|
|
301
|
+
const baseColor = material?.baseColor ?? DEFAULT_COLOR;
|
|
302
|
+
const opacity = material?.opacity !== void 0 ? clamp(material.opacity, 0, 1) : 1;
|
|
303
|
+
color[targetIndex] = baseColor[0];
|
|
304
|
+
color[targetIndex + 1] = baseColor[1];
|
|
305
|
+
color[targetIndex + 2] = baseColor[2];
|
|
306
|
+
color[targetIndex + 3] = Math.round(opacity * 255);
|
|
307
|
+
if (opacity < 1) {
|
|
308
|
+
hasTransparency = true;
|
|
309
|
+
}
|
|
310
|
+
if (material?.emissiveColor) {
|
|
311
|
+
emissive[targetIndex] = material.emissiveColor[0];
|
|
312
|
+
emissive[targetIndex + 1] = material.emissiveColor[1];
|
|
313
|
+
emissive[targetIndex + 2] = material.emissiveColor[2];
|
|
314
|
+
emissive[targetIndex + 3] = 255;
|
|
315
|
+
hasEmissive = true;
|
|
316
|
+
} else {
|
|
317
|
+
emissive[targetIndex] = 0;
|
|
318
|
+
emissive[targetIndex + 1] = 0;
|
|
319
|
+
emissive[targetIndex + 2] = 0;
|
|
320
|
+
emissive[targetIndex + 3] = 0;
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
return { id: textureDef.id, width, height, color, emissive: hasEmissive ? emissive : void 0, hasEmissive, hasTransparency };
|
|
316
325
|
}
|
|
317
326
|
function buildFallbackTexturePixels() {
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
327
|
+
const color = new Uint8Array(4);
|
|
328
|
+
color[0] = DEFAULT_COLOR[0];
|
|
329
|
+
color[1] = DEFAULT_COLOR[1];
|
|
330
|
+
color[2] = DEFAULT_COLOR[2];
|
|
331
|
+
color[3] = 255;
|
|
332
|
+
const emissive = new Uint8Array(4);
|
|
333
|
+
emissive[0] = 0;
|
|
334
|
+
emissive[1] = 0;
|
|
335
|
+
emissive[2] = 0;
|
|
336
|
+
emissive[3] = 0;
|
|
337
|
+
return {
|
|
338
|
+
id: FALLBACK_TEXTURE_ID,
|
|
339
|
+
width: 1,
|
|
340
|
+
height: 1,
|
|
341
|
+
color,
|
|
342
|
+
emissive,
|
|
343
|
+
hasEmissive: false,
|
|
344
|
+
hasTransparency: false
|
|
345
|
+
};
|
|
337
346
|
}
|
|
338
347
|
function buildOverlayFallbackTexturePixels() {
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
348
|
+
const color = new Uint8Array(4);
|
|
349
|
+
color[0] = 0;
|
|
350
|
+
color[1] = 0;
|
|
351
|
+
color[2] = 0;
|
|
352
|
+
color[3] = 0;
|
|
353
|
+
const emissive = new Uint8Array(4);
|
|
354
|
+
emissive[0] = 0;
|
|
355
|
+
emissive[1] = 0;
|
|
356
|
+
emissive[2] = 0;
|
|
357
|
+
emissive[3] = 0;
|
|
358
|
+
return {
|
|
359
|
+
id: OVERLAY_FALLBACK_TEXTURE_ID,
|
|
360
|
+
width: 1,
|
|
361
|
+
height: 1,
|
|
362
|
+
color,
|
|
363
|
+
emissive,
|
|
364
|
+
hasEmissive: false,
|
|
365
|
+
hasTransparency: true
|
|
366
|
+
};
|
|
358
367
|
}
|
|
359
368
|
function blitTexture(source, width, height, atlasWidth, target, offsetX, offsetY) {
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
369
|
+
for (let y = 0; y < height; y += 1) {
|
|
370
|
+
for (let x = 0; x < width; x += 1) {
|
|
371
|
+
const sx = x;
|
|
372
|
+
const sy = y;
|
|
373
|
+
const sourceIndex = (sy * width + sx) * 4;
|
|
374
|
+
const tx = offsetX + x;
|
|
375
|
+
const ty = offsetY + y;
|
|
376
|
+
const targetIndex = (ty * atlasWidth + tx) * 4;
|
|
377
|
+
target[targetIndex] = source[sourceIndex];
|
|
378
|
+
target[targetIndex + 1] = source[sourceIndex + 1];
|
|
379
|
+
target[targetIndex + 2] = source[sourceIndex + 2];
|
|
380
|
+
target[targetIndex + 3] = source[sourceIndex + 3];
|
|
381
|
+
}
|
|
382
|
+
}
|
|
374
383
|
}
|
|
375
384
|
function fillTransparentRegion(target, width, height, atlasWidth, offsetX, offsetY) {
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
385
|
+
for (let y = 0; y < height; y += 1) {
|
|
386
|
+
for (let x = 0; x < width; x += 1) {
|
|
387
|
+
const tx = offsetX + x;
|
|
388
|
+
const ty = offsetY + y;
|
|
389
|
+
const targetIndex = (ty * atlasWidth + tx) * 4;
|
|
390
|
+
target[targetIndex] = 0;
|
|
391
|
+
target[targetIndex + 1] = 0;
|
|
392
|
+
target[targetIndex + 2] = 0;
|
|
393
|
+
target[targetIndex + 3] = 0;
|
|
394
|
+
}
|
|
395
|
+
}
|
|
387
396
|
}
|
|
388
397
|
function extendEdgePixels(target, width, height, atlasWidth, atlasHeight, offsetX, offsetY) {
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
398
|
+
const startX = offsetX;
|
|
399
|
+
const endX = offsetX + width - 1;
|
|
400
|
+
const startY = offsetY;
|
|
401
|
+
const endY = offsetY + height - 1;
|
|
402
|
+
if (startY > 0) {
|
|
403
|
+
for (let x = startX; x <= endX; x += 1) {
|
|
404
|
+
copyPixel(target, atlasWidth, x, startY, x, startY - 1);
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
if (endY + 1 < atlasHeight) {
|
|
408
|
+
for (let x = startX; x <= endX; x += 1) {
|
|
409
|
+
copyPixel(target, atlasWidth, x, endY, x, endY + 1);
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
if (startX > 0) {
|
|
413
|
+
for (let y = startY; y <= endY; y += 1) {
|
|
414
|
+
copyPixel(target, atlasWidth, startX, y, startX - 1, y);
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
if (endX + 1 < atlasWidth) {
|
|
418
|
+
for (let y = startY; y <= endY; y += 1) {
|
|
419
|
+
copyPixel(target, atlasWidth, endX, y, endX + 1, y);
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
const cornerTargets = [];
|
|
423
|
+
if (startX > 0 && startY > 0)
|
|
424
|
+
cornerTargets.push({ sx: startX, sy: startY, dx: startX - 1, dy: startY - 1 });
|
|
425
|
+
if (startX > 0 && endY + 1 < atlasHeight)
|
|
426
|
+
cornerTargets.push({ sx: startX, sy: endY, dx: startX - 1, dy: endY + 1 });
|
|
427
|
+
if (endX + 1 < atlasWidth && startY > 0)
|
|
428
|
+
cornerTargets.push({ sx: endX, sy: startY, dx: endX + 1, dy: startY - 1 });
|
|
429
|
+
if (endX + 1 < atlasWidth && endY + 1 < atlasHeight)
|
|
430
|
+
cornerTargets.push({ sx: endX, sy: endY, dx: endX + 1, dy: endY + 1 });
|
|
431
|
+
cornerTargets.forEach(({ sx, sy, dx, dy }) => {
|
|
432
|
+
copyPixel(target, atlasWidth, sx, sy, dx, dy);
|
|
433
|
+
});
|
|
425
434
|
}
|
|
426
435
|
function copyPixel(buffer, atlasWidth, sourceX, sourceY, destX, destY) {
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
436
|
+
const sourceIndex = (sourceY * atlasWidth + sourceX) * 4;
|
|
437
|
+
const destIndex = (destY * atlasWidth + destX) * 4;
|
|
438
|
+
buffer[destIndex] = buffer[sourceIndex];
|
|
439
|
+
buffer[destIndex + 1] = buffer[sourceIndex + 1];
|
|
440
|
+
buffer[destIndex + 2] = buffer[sourceIndex + 2];
|
|
441
|
+
buffer[destIndex + 3] = buffer[sourceIndex + 3];
|
|
433
442
|
}
|
|
434
443
|
function clamp(value, min, max) {
|
|
435
|
-
|
|
444
|
+
return Math.min(max, Math.max(min, value));
|
|
436
445
|
}
|
|
437
446
|
function hasOverlayTextures(box) {
|
|
438
|
-
|
|
439
|
-
box.bottomOverlay ||
|
|
440
|
-
box.northOverlay ||
|
|
441
|
-
box.southOverlay ||
|
|
442
|
-
box.eastOverlay ||
|
|
443
|
-
box.westOverlay ||
|
|
444
|
-
box.sidesOverlay ||
|
|
445
|
-
box.allOverlay);
|
|
447
|
+
return Boolean(box.topOverlay || box.bottomOverlay || box.northOverlay || box.southOverlay || box.eastOverlay || box.westOverlay || box.sidesOverlay || box.allOverlay);
|
|
446
448
|
}
|
|
447
449
|
function collectUsedTextureIds(model, primitivesById) {
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
450
|
+
const textureIds = /* @__PURE__ */ new Set();
|
|
451
|
+
const visitPrimitive = (primitive, visited) => {
|
|
452
|
+
if (visited.has(primitive.id))
|
|
453
|
+
return;
|
|
454
|
+
visited.add(primitive.id);
|
|
455
|
+
switch (primitive.kind) {
|
|
456
|
+
case "plain_box":
|
|
457
|
+
case "voxel_box":
|
|
458
|
+
break;
|
|
459
|
+
case "textured_box": {
|
|
460
|
+
const textured = primitive;
|
|
461
|
+
import_faces.FACE_ORDER.forEach((face) => {
|
|
462
|
+
const textureId = getFaceTextureId(textured, face);
|
|
463
|
+
if (textureId)
|
|
464
|
+
textureIds.add(textureId);
|
|
465
|
+
});
|
|
466
|
+
import_faces.FACE_ORDER.forEach((face) => {
|
|
467
|
+
const overlayId = getOverlayTextureId(textured, face);
|
|
468
|
+
if (overlayId)
|
|
469
|
+
textureIds.add(overlayId);
|
|
470
|
+
});
|
|
471
|
+
break;
|
|
472
|
+
}
|
|
473
|
+
case "combo_box": {
|
|
474
|
+
const combo = primitive;
|
|
475
|
+
combo.slots.forEach((slot) => {
|
|
476
|
+
const child = primitivesById.get(slot.primitive);
|
|
477
|
+
if (child) {
|
|
478
|
+
visitPrimitive(child, new Set(visited));
|
|
479
|
+
}
|
|
480
|
+
});
|
|
481
|
+
break;
|
|
482
|
+
}
|
|
483
|
+
default:
|
|
484
|
+
break;
|
|
485
|
+
}
|
|
486
|
+
};
|
|
487
|
+
const traverseNode = (node) => {
|
|
488
|
+
if (node.mesh) {
|
|
489
|
+
const primitive = primitivesById.get(node.mesh);
|
|
490
|
+
if (primitive) {
|
|
491
|
+
visitPrimitive(primitive, /* @__PURE__ */ new Set());
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
node.children.forEach(traverseNode);
|
|
495
|
+
};
|
|
496
|
+
traverseNode(model.geometry.root);
|
|
497
|
+
return textureIds;
|
|
498
|
+
}
|
|
499
|
+
function buildSceneGraph(context, mode) {
|
|
500
|
+
if (mode === "merged") {
|
|
501
|
+
const scene2 = (0, import_scene.createBoxelScene)({ THREE: context.THREE, entity: context.model });
|
|
502
|
+
const root = buildMergedGroupFromScene(context, scene2);
|
|
503
|
+
scene2.dispose();
|
|
504
|
+
const dispose = () => {
|
|
505
|
+
const geometries = /* @__PURE__ */ new Set();
|
|
506
|
+
const materials = /* @__PURE__ */ new Set();
|
|
507
|
+
root.traverse((object) => {
|
|
508
|
+
if (object instanceof context.THREE.Mesh) {
|
|
509
|
+
const mesh = object;
|
|
510
|
+
if (mesh.geometry)
|
|
511
|
+
geometries.add(mesh.geometry);
|
|
512
|
+
const material = mesh.material;
|
|
513
|
+
if (Array.isArray(material))
|
|
514
|
+
material.forEach((mat) => materials.add(mat));
|
|
515
|
+
else if (material)
|
|
516
|
+
materials.add(material);
|
|
483
517
|
}
|
|
518
|
+
});
|
|
519
|
+
for (const geometry of geometries) {
|
|
520
|
+
geometry.dispose?.();
|
|
521
|
+
}
|
|
522
|
+
for (const materialEntry of materials) {
|
|
523
|
+
const material = materialEntry;
|
|
524
|
+
material.map?.dispose?.();
|
|
525
|
+
material.emissiveMap?.dispose?.();
|
|
526
|
+
material.dispose?.();
|
|
527
|
+
}
|
|
484
528
|
};
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
529
|
+
return { root, nodeLookup: /* @__PURE__ */ new Map(), animations: [], dispose };
|
|
530
|
+
}
|
|
531
|
+
if (mode === "skinned") {
|
|
532
|
+
const skinned = (0, import_skinned_mesh.buildEntitySkinnedMesh)({ THREE: context.THREE, entity: context.model });
|
|
533
|
+
const root = new context.THREE.Group();
|
|
534
|
+
root.add(skinned.mesh);
|
|
535
|
+
const nodeLookup2 = /* @__PURE__ */ new Map();
|
|
536
|
+
skinned.boneMap.forEach((bone, nodeId) => nodeLookup2.set(nodeId, bone));
|
|
537
|
+
const animations = (0, import_animations.buildAnimations)(context.THREE, context.model, nodeLookup2);
|
|
538
|
+
const dispose = () => {
|
|
539
|
+
skinned.dispose();
|
|
493
540
|
};
|
|
494
|
-
traverseNode(model.geometry.root);
|
|
495
|
-
return textureIds;
|
|
496
|
-
}
|
|
497
|
-
function buildSceneGraph(context, mode) {
|
|
498
|
-
if (mode === 'merged') {
|
|
499
|
-
const scene = createBoxelScene({ THREE: context.THREE, entity: context.model });
|
|
500
|
-
const root = buildMergedGroupFromScene(context, scene);
|
|
501
|
-
scene.dispose();
|
|
502
|
-
const dispose = () => {
|
|
503
|
-
const geometries = new Set();
|
|
504
|
-
const materials = new Set();
|
|
505
|
-
root.traverse(object => {
|
|
506
|
-
if (object instanceof context.THREE.Mesh) {
|
|
507
|
-
const mesh = object;
|
|
508
|
-
if (mesh.geometry)
|
|
509
|
-
geometries.add(mesh.geometry);
|
|
510
|
-
const material = mesh.material;
|
|
511
|
-
if (Array.isArray(material))
|
|
512
|
-
material.forEach(mat => materials.add(mat));
|
|
513
|
-
else if (material)
|
|
514
|
-
materials.add(material);
|
|
515
|
-
}
|
|
516
|
-
});
|
|
517
|
-
for (const geometry of geometries) {
|
|
518
|
-
geometry.dispose?.();
|
|
519
|
-
}
|
|
520
|
-
for (const materialEntry of materials) {
|
|
521
|
-
const material = materialEntry;
|
|
522
|
-
material.map?.dispose?.();
|
|
523
|
-
material.emissiveMap?.dispose?.();
|
|
524
|
-
material.dispose?.();
|
|
525
|
-
}
|
|
526
|
-
};
|
|
527
|
-
return { root, nodeLookup: new Map(), animations: [], dispose };
|
|
528
|
-
}
|
|
529
|
-
if (mode === 'skinned') {
|
|
530
|
-
const skinned = buildEntitySkinnedMesh({ THREE: context.THREE, entity: context.model });
|
|
531
|
-
const root = new context.THREE.Group();
|
|
532
|
-
root.add(skinned.mesh);
|
|
533
|
-
const nodeLookup = new Map();
|
|
534
|
-
skinned.boneMap.forEach((bone, nodeId) => nodeLookup.set(nodeId, bone));
|
|
535
|
-
const animations = buildAnimations(context.THREE, context.model, nodeLookup);
|
|
536
|
-
const dispose = () => {
|
|
537
|
-
skinned.dispose();
|
|
538
|
-
};
|
|
539
|
-
return {
|
|
540
|
-
root,
|
|
541
|
-
nodeLookup,
|
|
542
|
-
animations,
|
|
543
|
-
dispose,
|
|
544
|
-
};
|
|
545
|
-
}
|
|
546
|
-
const scene = createBoxelScene({ THREE: context.THREE, entity: context.model });
|
|
547
|
-
const nodeLookup = new Map();
|
|
548
|
-
scene.nodes.forEach(meta => {
|
|
549
|
-
nodeLookup.set(meta.id, meta.object);
|
|
550
|
-
});
|
|
551
541
|
return {
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
542
|
+
root,
|
|
543
|
+
nodeLookup: nodeLookup2,
|
|
544
|
+
animations,
|
|
545
|
+
dispose
|
|
556
546
|
};
|
|
547
|
+
}
|
|
548
|
+
const scene = (0, import_scene.createBoxelScene)({ THREE: context.THREE, entity: context.model });
|
|
549
|
+
const nodeLookup = /* @__PURE__ */ new Map();
|
|
550
|
+
scene.nodes.forEach((meta) => {
|
|
551
|
+
nodeLookup.set(meta.id, meta.object);
|
|
552
|
+
});
|
|
553
|
+
return {
|
|
554
|
+
root: scene.root,
|
|
555
|
+
nodeLookup,
|
|
556
|
+
animations: scene.animations,
|
|
557
|
+
dispose: scene.dispose
|
|
558
|
+
};
|
|
557
559
|
}
|
|
558
560
|
function resolveAnimationClips(sceneGraph, mode, animationIds) {
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
: null;
|
|
568
|
-
return sceneGraph.animations
|
|
569
|
-
.filter(animation => (allowed ? allowed.has(animation.id) : true))
|
|
570
|
-
.map(animation => animation.clip);
|
|
561
|
+
if (mode !== "hierarchical")
|
|
562
|
+
return [];
|
|
563
|
+
if (animationIds === null)
|
|
564
|
+
return [];
|
|
565
|
+
if (Array.isArray(animationIds) && animationIds.length === 0)
|
|
566
|
+
return [];
|
|
567
|
+
const allowed = Array.isArray(animationIds) && animationIds.length > 0 ? new Set(animationIds) : null;
|
|
568
|
+
return sceneGraph.animations.filter((animation) => allowed ? allowed.has(animation.id) : true).map((animation) => animation.clip);
|
|
571
569
|
}
|
|
572
570
|
function buildMergedGroupFromScene(context, scene) {
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
571
|
+
const { THREE, materialsById, primitivesById, atlas, voxelSurfacesByPrimitive } = context;
|
|
572
|
+
const group = new THREE.Group();
|
|
573
|
+
const texturedAccumulator = atlas ? new MeshAccumulator(true) : null;
|
|
574
|
+
const plainBuckets = /* @__PURE__ */ new Map();
|
|
575
|
+
scene.root.updateMatrixWorld(true);
|
|
576
|
+
scene.root.traverse((object) => {
|
|
577
|
+
if (!(object instanceof THREE.Mesh)) {
|
|
578
|
+
return;
|
|
579
|
+
}
|
|
580
|
+
const mesh = object;
|
|
581
|
+
const primitiveId = mesh.userData?.primitiveId;
|
|
582
|
+
const matrix = mesh.matrixWorld.clone();
|
|
583
|
+
const sizeFromUserData = mesh.userData?.size;
|
|
584
|
+
if (!primitiveId) {
|
|
585
|
+
if (mesh.userData?.placeholder && sizeFromUserData) {
|
|
586
|
+
const bucket = ensurePlainBucket({
|
|
587
|
+
THREE,
|
|
588
|
+
plainBuckets,
|
|
589
|
+
options: {
|
|
590
|
+
baseColor: DEFAULT_COLOR,
|
|
591
|
+
emissive: null,
|
|
592
|
+
opacity: DEFAULT_OPACITY
|
|
593
|
+
}
|
|
594
|
+
});
|
|
595
|
+
appendBoxToAccumulator({
|
|
596
|
+
THREE,
|
|
597
|
+
accumulator: bucket.accumulator,
|
|
598
|
+
size: sizeFromUserData,
|
|
599
|
+
matrix
|
|
600
|
+
});
|
|
601
|
+
}
|
|
602
|
+
return;
|
|
603
|
+
}
|
|
604
|
+
const primitive = primitivesById.get(primitiveId);
|
|
605
|
+
if (!primitive) {
|
|
606
|
+
console.warn("[entity-export] primitive not found for merged export", primitiveId);
|
|
607
|
+
return;
|
|
608
|
+
}
|
|
609
|
+
switch (primitive.kind) {
|
|
610
|
+
case "plain_box": {
|
|
611
|
+
const plain = primitive;
|
|
612
|
+
const materialDef = materialsById.get(plain.material);
|
|
613
|
+
const bucket = ensurePlainBucket({
|
|
614
|
+
THREE,
|
|
615
|
+
plainBuckets,
|
|
616
|
+
options: {
|
|
617
|
+
baseColor: materialDef?.baseColor ?? DEFAULT_COLOR,
|
|
618
|
+
emissive: materialDef?.emissiveColor ?? null,
|
|
619
|
+
opacity: materialDef?.opacity ?? 1
|
|
620
|
+
}
|
|
621
|
+
});
|
|
622
|
+
appendBoxToAccumulator({ THREE, accumulator: bucket.accumulator, size: plain.size, matrix });
|
|
623
|
+
break;
|
|
624
|
+
}
|
|
625
|
+
case "textured_box": {
|
|
626
|
+
const box = primitive;
|
|
627
|
+
const isOverlayMesh = mesh.userData?.overlay === true;
|
|
628
|
+
if (!atlas || !texturedAccumulator) {
|
|
629
|
+
if (isOverlayMesh) {
|
|
630
|
+
break;
|
|
631
|
+
}
|
|
632
|
+
const bucket = ensurePlainBucket({
|
|
633
|
+
THREE,
|
|
634
|
+
plainBuckets,
|
|
635
|
+
options: { baseColor: DEFAULT_COLOR, emissive: null, opacity: 1 }
|
|
636
|
+
});
|
|
637
|
+
appendBoxToAccumulator({ THREE, accumulator: bucket.accumulator, size: box.size, matrix });
|
|
638
|
+
break;
|
|
605
639
|
}
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
640
|
+
appendTexturedBoxToAccumulator({
|
|
641
|
+
THREE,
|
|
642
|
+
accumulator: texturedAccumulator,
|
|
643
|
+
atlas,
|
|
644
|
+
box,
|
|
645
|
+
matrix,
|
|
646
|
+
size: isOverlayMesh ? sizeFromUserData ?? box.size : void 0,
|
|
647
|
+
getTextureId: isOverlayMesh ? (candidate, face) => getOverlayTextureId(candidate, face) ?? OVERLAY_FALLBACK_TEXTURE_ID : void 0
|
|
648
|
+
});
|
|
649
|
+
break;
|
|
650
|
+
}
|
|
651
|
+
case "voxel_box": {
|
|
652
|
+
const voxel = primitive;
|
|
653
|
+
if (!voxel.voxels || voxel.voxels.length === 0) {
|
|
654
|
+
break;
|
|
610
655
|
}
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
opacity: materialDef?.opacity ?? 1,
|
|
622
|
-
},
|
|
623
|
-
});
|
|
624
|
-
appendBoxToAccumulator({ THREE, accumulator: bucket.accumulator, size: plain.size, matrix });
|
|
625
|
-
break;
|
|
626
|
-
}
|
|
627
|
-
case 'textured_box': {
|
|
628
|
-
const box = primitive;
|
|
629
|
-
const isOverlayMesh = mesh.userData?.overlay === true;
|
|
630
|
-
if (!atlas || !texturedAccumulator) {
|
|
631
|
-
if (isOverlayMesh) {
|
|
632
|
-
break;
|
|
633
|
-
}
|
|
634
|
-
const bucket = ensurePlainBucket({
|
|
635
|
-
THREE,
|
|
636
|
-
plainBuckets,
|
|
637
|
-
options: { baseColor: DEFAULT_COLOR, emissive: null, opacity: 1 },
|
|
638
|
-
});
|
|
639
|
-
appendBoxToAccumulator({ THREE, accumulator: bucket.accumulator, size: box.size, matrix });
|
|
640
|
-
break;
|
|
641
|
-
}
|
|
642
|
-
appendTexturedBoxToAccumulator({
|
|
643
|
-
THREE,
|
|
644
|
-
accumulator: texturedAccumulator,
|
|
645
|
-
atlas,
|
|
646
|
-
box,
|
|
647
|
-
matrix,
|
|
648
|
-
size: isOverlayMesh ? sizeFromUserData ?? box.size : undefined,
|
|
649
|
-
getTextureId: isOverlayMesh
|
|
650
|
-
? (candidate, face) => getOverlayTextureId(candidate, face) ?? OVERLAY_FALLBACK_TEXTURE_ID
|
|
651
|
-
: undefined,
|
|
652
|
-
});
|
|
653
|
-
break;
|
|
654
|
-
}
|
|
655
|
-
case 'voxel_box': {
|
|
656
|
-
const voxel = primitive;
|
|
657
|
-
if (!voxel.voxels || voxel.voxels.length === 0) {
|
|
658
|
-
break;
|
|
659
|
-
}
|
|
660
|
-
const surfaces = voxelSurfacesByPrimitive.get(voxel.id);
|
|
661
|
-
if (!atlas || !texturedAccumulator || !surfaces || surfaces.length === 0) {
|
|
662
|
-
const fallbackSize = voxel.size ?? [1, 1, 1];
|
|
663
|
-
const bucket = ensurePlainBucket({
|
|
664
|
-
THREE,
|
|
665
|
-
plainBuckets,
|
|
666
|
-
options: { baseColor: DEFAULT_COLOR, emissive: null, opacity: DEFAULT_OPACITY },
|
|
667
|
-
});
|
|
668
|
-
appendBoxToAccumulator({ THREE, accumulator: bucket.accumulator, size: fallbackSize, matrix });
|
|
669
|
-
break;
|
|
670
|
-
}
|
|
671
|
-
surfaces.forEach(surface => {
|
|
672
|
-
const region = atlas.regions.get(surface.textureId) ?? atlas.fallbackRegion;
|
|
673
|
-
const geometry = new THREE.BufferGeometry();
|
|
674
|
-
geometry.setAttribute('position', new THREE.Float32BufferAttribute(surface.geometry.positions, 3));
|
|
675
|
-
geometry.setAttribute('normal', new THREE.Float32BufferAttribute(surface.geometry.normals, 3));
|
|
676
|
-
const baseUVAttr = new THREE.Float32BufferAttribute(surface.geometry.uvs, 2);
|
|
677
|
-
geometry.setAttribute('uv', baseUVAttr);
|
|
678
|
-
geometry.setIndex(new THREE.BufferAttribute(surface.geometry.indices, 1));
|
|
679
|
-
const customUVs = new Float32Array(baseUVAttr.count * 2);
|
|
680
|
-
const uSpan = region.uMax - region.uMin;
|
|
681
|
-
const vSpan = region.vMax - region.vMin;
|
|
682
|
-
for (let i = 0; i < baseUVAttr.count; i += 1) {
|
|
683
|
-
const baseU = baseUVAttr.getX(i);
|
|
684
|
-
const baseV = baseUVAttr.getY(i);
|
|
685
|
-
customUVs[i * 2] = region.uMin + baseU * uSpan;
|
|
686
|
-
customUVs[i * 2 + 1] = region.vMin + baseV * vSpan;
|
|
687
|
-
}
|
|
688
|
-
texturedAccumulator.appendGeometry({ THREE, geometry, matrix, customUVs });
|
|
689
|
-
});
|
|
690
|
-
break;
|
|
691
|
-
}
|
|
692
|
-
case 'combo_box':
|
|
693
|
-
break;
|
|
694
|
-
default:
|
|
695
|
-
break;
|
|
656
|
+
const surfaces = voxelSurfacesByPrimitive.get(voxel.id);
|
|
657
|
+
if (!atlas || !texturedAccumulator || !surfaces || surfaces.length === 0) {
|
|
658
|
+
const fallbackSize = voxel.size ?? [1, 1, 1];
|
|
659
|
+
const bucket = ensurePlainBucket({
|
|
660
|
+
THREE,
|
|
661
|
+
plainBuckets,
|
|
662
|
+
options: { baseColor: DEFAULT_COLOR, emissive: null, opacity: DEFAULT_OPACITY }
|
|
663
|
+
});
|
|
664
|
+
appendBoxToAccumulator({ THREE, accumulator: bucket.accumulator, size: fallbackSize, matrix });
|
|
665
|
+
break;
|
|
696
666
|
}
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
667
|
+
surfaces.forEach((surface) => {
|
|
668
|
+
const region = atlas.regions.get(surface.textureId) ?? atlas.fallbackRegion;
|
|
669
|
+
const geometry = new THREE.BufferGeometry();
|
|
670
|
+
geometry.setAttribute("position", new THREE.Float32BufferAttribute(surface.geometry.positions, 3));
|
|
671
|
+
geometry.setAttribute("normal", new THREE.Float32BufferAttribute(surface.geometry.normals, 3));
|
|
672
|
+
const baseUVAttr = new THREE.Float32BufferAttribute(surface.geometry.uvs, 2);
|
|
673
|
+
geometry.setAttribute("uv", baseUVAttr);
|
|
674
|
+
geometry.setIndex(new THREE.BufferAttribute(surface.geometry.indices, 1));
|
|
675
|
+
const customUVs = new Float32Array(baseUVAttr.count * 2);
|
|
676
|
+
const uSpan = region.uMax - region.uMin;
|
|
677
|
+
const vSpan = region.vMax - region.vMin;
|
|
678
|
+
for (let i = 0; i < baseUVAttr.count; i += 1) {
|
|
679
|
+
const baseU = baseUVAttr.getX(i);
|
|
680
|
+
const baseV = baseUVAttr.getY(i);
|
|
681
|
+
customUVs[i * 2] = region.uMin + baseU * uSpan;
|
|
682
|
+
customUVs[i * 2 + 1] = region.vMin + baseV * vSpan;
|
|
683
|
+
}
|
|
684
|
+
texturedAccumulator.appendGeometry({ THREE, geometry, matrix, customUVs });
|
|
685
|
+
});
|
|
686
|
+
break;
|
|
687
|
+
}
|
|
688
|
+
case "combo_box":
|
|
689
|
+
break;
|
|
690
|
+
default:
|
|
691
|
+
break;
|
|
692
|
+
}
|
|
693
|
+
});
|
|
694
|
+
if (texturedAccumulator && texturedAccumulator.hasGeometry()) {
|
|
695
|
+
const geometry = texturedAccumulator.build(THREE);
|
|
696
|
+
const material = createTexturedMaterial(THREE, atlas);
|
|
697
|
+
const mesh = new THREE.Mesh(geometry, material);
|
|
698
|
+
group.add(mesh);
|
|
699
|
+
}
|
|
700
|
+
for (const bucket of plainBuckets.values()) {
|
|
701
|
+
if (!bucket.accumulator.hasGeometry())
|
|
702
|
+
continue;
|
|
703
|
+
const geometry = bucket.accumulator.build(THREE);
|
|
704
|
+
const mesh = new THREE.Mesh(geometry, bucket.material);
|
|
705
|
+
group.add(mesh);
|
|
706
|
+
}
|
|
707
|
+
return group;
|
|
712
708
|
}
|
|
713
709
|
function appendTexturedBoxToAccumulator(options) {
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
});
|
|
710
|
+
const { THREE, accumulator, atlas, box, matrix } = options;
|
|
711
|
+
const size = options.size ?? box.size;
|
|
712
|
+
const textureResolver = options.getTextureId ?? getFaceTextureId;
|
|
713
|
+
const geometry = (0, import_primitives.createBoxGeometry)(THREE, size);
|
|
714
|
+
const uvAttr = geometry.getAttribute("uv");
|
|
715
|
+
const customUVs = new Float32Array(uvAttr.count * 2);
|
|
716
|
+
customUVs.set(uvAttr.array);
|
|
717
|
+
const faceVertexSets = (0, import_primitives.collectGroupVertexIndices)(geometry);
|
|
718
|
+
if (faceVertexSets.length === 0) {
|
|
719
|
+
geometry.dispose();
|
|
720
|
+
return;
|
|
721
|
+
}
|
|
722
|
+
import_faces.FACE_ORDER.forEach((face, faceIndex) => {
|
|
723
|
+
const resolvedId = textureResolver(box, face);
|
|
724
|
+
const region = atlas.regions.get(resolvedId ?? FALLBACK_TEXTURE_ID) ?? atlas.fallbackRegion;
|
|
725
|
+
const vertexIndices = faceVertexSets[faceIndex];
|
|
726
|
+
if (!vertexIndices || vertexIndices.size === 0)
|
|
727
|
+
return;
|
|
728
|
+
const uSpan = region.uMax - region.uMin;
|
|
729
|
+
const vSpan = region.vMax - region.vMin;
|
|
730
|
+
vertexIndices.forEach((vertexIndex) => {
|
|
731
|
+
const baseU = uvAttr.getX(vertexIndex);
|
|
732
|
+
const baseV = uvAttr.getY(vertexIndex);
|
|
733
|
+
customUVs[vertexIndex * 2] = region.uMin + baseU * uSpan;
|
|
734
|
+
customUVs[vertexIndex * 2 + 1] = region.vMin + baseV * vSpan;
|
|
740
735
|
});
|
|
741
|
-
|
|
736
|
+
});
|
|
737
|
+
accumulator.appendGeometry({ THREE, geometry, matrix, customUVs });
|
|
742
738
|
}
|
|
743
739
|
function appendBoxToAccumulator(options) {
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
740
|
+
const { THREE, accumulator, size, matrix } = options;
|
|
741
|
+
const geometry = (0, import_primitives.createBoxGeometry)(THREE, size);
|
|
742
|
+
accumulator.appendGeometry({ THREE, geometry, matrix });
|
|
747
743
|
}
|
|
748
744
|
function getFaceTextureId(box, face) {
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
745
|
+
switch (face) {
|
|
746
|
+
case "top":
|
|
747
|
+
return box.top ?? box.all ?? box.sides;
|
|
748
|
+
case "bottom":
|
|
749
|
+
return box.bottom ?? box.all ?? box.sides;
|
|
750
|
+
case "north":
|
|
751
|
+
return box.north ?? box.sides ?? box.all;
|
|
752
|
+
case "south":
|
|
753
|
+
return box.south ?? box.sides ?? box.all;
|
|
754
|
+
case "east":
|
|
755
|
+
return box.east ?? box.sides ?? box.all;
|
|
756
|
+
case "west":
|
|
757
|
+
return box.west ?? box.sides ?? box.all;
|
|
758
|
+
default:
|
|
759
|
+
return box.all;
|
|
760
|
+
}
|
|
765
761
|
}
|
|
766
762
|
function getOverlayTextureId(box, face) {
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
763
|
+
switch (face) {
|
|
764
|
+
case "top":
|
|
765
|
+
return box.topOverlay ?? box.allOverlay ?? box.sidesOverlay;
|
|
766
|
+
case "bottom":
|
|
767
|
+
return box.bottomOverlay ?? box.allOverlay ?? box.sidesOverlay;
|
|
768
|
+
case "north":
|
|
769
|
+
return box.northOverlay ?? box.sidesOverlay ?? box.allOverlay;
|
|
770
|
+
case "south":
|
|
771
|
+
return box.southOverlay ?? box.sidesOverlay ?? box.allOverlay;
|
|
772
|
+
case "east":
|
|
773
|
+
return box.eastOverlay ?? box.sidesOverlay ?? box.allOverlay;
|
|
774
|
+
case "west":
|
|
775
|
+
return box.westOverlay ?? box.sidesOverlay ?? box.allOverlay;
|
|
776
|
+
default:
|
|
777
|
+
return box.allOverlay ?? box.sidesOverlay;
|
|
778
|
+
}
|
|
783
779
|
}
|
|
784
780
|
function createTexturedMaterial(THREE, atlas) {
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
781
|
+
const params = {
|
|
782
|
+
map: atlas.map,
|
|
783
|
+
metalness: 0,
|
|
784
|
+
roughness: 1,
|
|
785
|
+
transparent: atlas.hasTransparency
|
|
786
|
+
};
|
|
787
|
+
if (atlas.emissiveMap) {
|
|
788
|
+
params.emissiveMap = atlas.emissiveMap;
|
|
789
|
+
}
|
|
790
|
+
const material = new THREE.MeshStandardMaterial(params);
|
|
791
|
+
if (atlas.emissiveMap) {
|
|
792
|
+
material.emissive = new THREE.Color(1, 1, 1);
|
|
793
|
+
}
|
|
794
|
+
return material;
|
|
799
795
|
}
|
|
800
796
|
function createPlainMaterial(THREE, options) {
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
797
|
+
const { baseColor, emissive, opacity } = options;
|
|
798
|
+
const material = new THREE.MeshStandardMaterial({
|
|
799
|
+
color: new THREE.Color(baseColor[0] / 255, baseColor[1] / 255, baseColor[2] / 255),
|
|
800
|
+
metalness: 0,
|
|
801
|
+
roughness: 1,
|
|
802
|
+
transparent: opacity < 1,
|
|
803
|
+
opacity
|
|
804
|
+
});
|
|
805
|
+
if (emissive) {
|
|
806
|
+
material.emissive = new THREE.Color(emissive[0] / 255, emissive[1] / 255, emissive[2] / 255);
|
|
807
|
+
}
|
|
808
|
+
material.userData = material.userData ?? {};
|
|
809
|
+
material.userData.gltfExtras = {
|
|
810
|
+
playdropBaseColor: baseColor,
|
|
811
|
+
playdropOpacity: opacity
|
|
812
|
+
};
|
|
813
|
+
return material;
|
|
818
814
|
}
|
|
819
815
|
function getPlainMaterial(context, options) {
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
816
|
+
const key = plainMaterialKey(options);
|
|
817
|
+
const cached = context.plainMaterialCache.get(key);
|
|
818
|
+
if (cached) {
|
|
819
|
+
return cached;
|
|
820
|
+
}
|
|
821
|
+
const material = createPlainMaterial(context.THREE, options);
|
|
822
|
+
context.plainMaterialCache.set(key, material);
|
|
823
|
+
return material;
|
|
828
824
|
}
|
|
829
825
|
function ensurePlainBucket({ THREE, plainBuckets, options }) {
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
826
|
+
const key = plainMaterialKey(options);
|
|
827
|
+
let bucket = plainBuckets.get(key);
|
|
828
|
+
if (!bucket) {
|
|
829
|
+
bucket = { accumulator: new MeshAccumulator(false), material: createPlainMaterial(THREE, options) };
|
|
830
|
+
plainBuckets.set(key, bucket);
|
|
831
|
+
}
|
|
832
|
+
return bucket;
|
|
837
833
|
}
|
|
838
834
|
function plainMaterialKey(options) {
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
835
|
+
const base = `${options.baseColor[0]}-${options.baseColor[1]}-${options.baseColor[2]}`;
|
|
836
|
+
const emissive = options.emissive ? `${options.emissive[0]}-${options.emissive[1]}-${options.emissive[2]}` : "none";
|
|
837
|
+
const opacity = options.opacity.toFixed(4);
|
|
838
|
+
return `plain|${base}|${emissive}|${opacity}`;
|
|
843
839
|
}
|
|
844
840
|
class MeshAccumulator {
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
841
|
+
constructor(withUVs) {
|
|
842
|
+
this.positions = [];
|
|
843
|
+
this.normals = [];
|
|
844
|
+
this.indices = [];
|
|
845
|
+
this.vertexCount = 0;
|
|
846
|
+
this.uvs = withUVs ? [] : null;
|
|
847
|
+
}
|
|
848
|
+
appendGeometry(options) {
|
|
849
|
+
const { THREE, geometry, matrix, customUVs } = options;
|
|
850
|
+
const rawPositionAttr = geometry.getAttribute("position");
|
|
851
|
+
const rawNormalAttr = geometry.getAttribute("normal");
|
|
852
|
+
const rawUvAttr = geometry.getAttribute("uv");
|
|
853
|
+
const indexAttr = geometry.index;
|
|
854
|
+
if (!rawPositionAttr || !rawNormalAttr || !indexAttr) {
|
|
855
|
+
geometry.dispose();
|
|
856
|
+
return;
|
|
857
|
+
}
|
|
858
|
+
if (!(rawPositionAttr instanceof THREE.BufferAttribute) || !(rawNormalAttr instanceof THREE.BufferAttribute)) {
|
|
859
|
+
geometry.dispose();
|
|
860
|
+
return;
|
|
861
|
+
}
|
|
862
|
+
const positionAttr = rawPositionAttr;
|
|
863
|
+
const normalAttr = rawNormalAttr;
|
|
864
|
+
const uvAttr = rawUvAttr && rawUvAttr instanceof THREE.BufferAttribute ? rawUvAttr : null;
|
|
865
|
+
if (!(indexAttr instanceof THREE.BufferAttribute)) {
|
|
866
|
+
geometry.dispose();
|
|
867
|
+
return;
|
|
868
|
+
}
|
|
869
|
+
const vertex = new THREE.Vector3();
|
|
870
|
+
const normal = new THREE.Vector3();
|
|
871
|
+
const uv = new THREE.Vector2();
|
|
872
|
+
const normalMatrix = new THREE.Matrix3().getNormalMatrix(matrix);
|
|
873
|
+
for (let i = 0; i < positionAttr.count; i += 1) {
|
|
874
|
+
vertex.fromBufferAttribute(positionAttr, i);
|
|
875
|
+
vertex.applyMatrix4(matrix);
|
|
876
|
+
this.positions.push(vertex.x, vertex.y, vertex.z);
|
|
877
|
+
normal.fromBufferAttribute(normalAttr, i);
|
|
878
|
+
normal.applyMatrix3(normalMatrix).normalize();
|
|
879
|
+
this.normals.push(normal.x, normal.y, normal.z);
|
|
880
|
+
if (this.uvs) {
|
|
881
|
+
if (customUVs) {
|
|
882
|
+
this.uvs.push(customUVs[i * 2], customUVs[i * 2 + 1]);
|
|
883
|
+
} else if (uvAttr) {
|
|
884
|
+
uv.fromBufferAttribute(uvAttr, i);
|
|
885
|
+
this.uvs.push(uv.x, uv.y);
|
|
886
|
+
} else {
|
|
887
|
+
this.uvs.push(0, 0);
|
|
874
888
|
}
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
for (let i = 0; i < typedIndexAttr.count; i += 1) {
|
|
902
|
-
this.indices.push(this.vertexCount + indexArray[i]);
|
|
903
|
-
}
|
|
904
|
-
this.vertexCount += positionAttr.count;
|
|
905
|
-
geometry.dispose();
|
|
906
|
-
}
|
|
907
|
-
hasGeometry() {
|
|
908
|
-
return this.positions.length > 0;
|
|
909
|
-
}
|
|
910
|
-
build(THREE) {
|
|
911
|
-
const geometry = new THREE.BufferGeometry();
|
|
912
|
-
geometry.setAttribute('position', new THREE.Float32BufferAttribute(this.positions, 3));
|
|
913
|
-
geometry.setAttribute('normal', new THREE.Float32BufferAttribute(this.normals, 3));
|
|
914
|
-
if (this.uvs) {
|
|
915
|
-
geometry.setAttribute('uv', new THREE.Float32BufferAttribute(this.uvs, 2));
|
|
916
|
-
}
|
|
917
|
-
const indexArray = this.vertexCount > 65535 ? new Uint32Array(this.indices) : new Uint16Array(this.indices);
|
|
918
|
-
geometry.setIndex(new THREE.BufferAttribute(indexArray, 1));
|
|
919
|
-
geometry.computeBoundingBox();
|
|
920
|
-
geometry.computeBoundingSphere();
|
|
921
|
-
return geometry;
|
|
922
|
-
}
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
const typedIndexAttr = indexAttr;
|
|
892
|
+
const indexArray = typedIndexAttr.array;
|
|
893
|
+
for (let i = 0; i < typedIndexAttr.count; i += 1) {
|
|
894
|
+
this.indices.push(this.vertexCount + indexArray[i]);
|
|
895
|
+
}
|
|
896
|
+
this.vertexCount += positionAttr.count;
|
|
897
|
+
geometry.dispose();
|
|
898
|
+
}
|
|
899
|
+
hasGeometry() {
|
|
900
|
+
return this.positions.length > 0;
|
|
901
|
+
}
|
|
902
|
+
build(THREE) {
|
|
903
|
+
const geometry = new THREE.BufferGeometry();
|
|
904
|
+
geometry.setAttribute("position", new THREE.Float32BufferAttribute(this.positions, 3));
|
|
905
|
+
geometry.setAttribute("normal", new THREE.Float32BufferAttribute(this.normals, 3));
|
|
906
|
+
if (this.uvs) {
|
|
907
|
+
geometry.setAttribute("uv", new THREE.Float32BufferAttribute(this.uvs, 2));
|
|
908
|
+
}
|
|
909
|
+
const indexArray = this.vertexCount > 65535 ? new Uint32Array(this.indices) : new Uint16Array(this.indices);
|
|
910
|
+
geometry.setIndex(new THREE.BufferAttribute(indexArray, 1));
|
|
911
|
+
geometry.computeBoundingBox();
|
|
912
|
+
geometry.computeBoundingSphere();
|
|
913
|
+
return geometry;
|
|
914
|
+
}
|
|
923
915
|
}
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
916
|
+
const __testing = {
|
|
917
|
+
buildTextureAtlas,
|
|
918
|
+
collectUsedTextureIds,
|
|
919
|
+
MeshAccumulator,
|
|
920
|
+
createExportContext,
|
|
921
|
+
buildSceneGraph,
|
|
922
|
+
resolveAnimationClips
|
|
931
923
|
};
|
|
924
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
925
|
+
0 && (module.exports = {
|
|
926
|
+
__testing,
|
|
927
|
+
createBoxelSceneGraph,
|
|
928
|
+
exportModelToGLB
|
|
929
|
+
});
|