@threenative/assets 0.3.3 → 0.3.5
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/README.md +22 -0
- package/dist/apply-worker.js +1586 -62
- package/dist/blender-gpl/fixtures/make_world_fixture.py +184 -0
- package/dist/blender-gpl/recipes/_common.py +11 -0
- package/dist/blender-gpl/recipes/decimate.py +11 -5
- package/dist/blender-gpl/recipes/export_world.py +682 -0
- package/dist/index.d.ts +144 -2
- package/dist/index.js +4883 -2602
- package/package.json +2 -2
package/dist/apply-worker.js
CHANGED
|
@@ -4590,12 +4590,12 @@ var WriterContext = class {
|
|
|
4590
4590
|
source: this.imageIndexMap.get(texture),
|
|
4591
4591
|
sampler: this.samplerDefIndexMap.get(samplerKey)
|
|
4592
4592
|
};
|
|
4593
|
-
const
|
|
4594
|
-
if (!this.textureDefIndexMap.has(
|
|
4595
|
-
this.textureDefIndexMap.set(
|
|
4593
|
+
const textureKey2 = JSON.stringify(textureDef);
|
|
4594
|
+
if (!this.textureDefIndexMap.has(textureKey2)) {
|
|
4595
|
+
this.textureDefIndexMap.set(textureKey2, this.jsonDoc.json.textures.length);
|
|
4596
4596
|
this.jsonDoc.json.textures.push(textureDef);
|
|
4597
4597
|
}
|
|
4598
|
-
const textureInfoDef = { index: this.textureDefIndexMap.get(
|
|
4598
|
+
const textureInfoDef = { index: this.textureDefIndexMap.get(textureKey2) };
|
|
4599
4599
|
if (textureInfo.getTexCoord() !== 0) textureInfoDef.texCoord = textureInfo.getTexCoord();
|
|
4600
4600
|
if (Object.keys(textureInfo.getExtras()).length > 0) textureInfoDef.extras = textureInfo.getExtras();
|
|
4601
4601
|
this.textureInfoDefMap.set(textureInfo, textureInfoDef);
|
|
@@ -10789,7 +10789,7 @@ var TNDiscreteLod = class extends Extension {
|
|
|
10789
10789
|
};
|
|
10790
10790
|
function attachDiscreteLod(document, extension, primitive, chain) {
|
|
10791
10791
|
const buffer = document.getRoot().listBuffers()[0] ?? document.createBuffer();
|
|
10792
|
-
const property = extension.createDiscreteLod().setStrategy("discrete").setBaselineTriangles(chain.baselineTriangles).setLod0Triangles(chain.lod0Triangles).setErrorScale(chain.errorScale).setCounts([...chain.counts]).setErrors([...chain.errors]).setAbsoluteErrors([...chain.absoluteErrors]).setSharedVertexBuffers(true);
|
|
10792
|
+
const property = extension.createDiscreteLod().setStrategy(chain.strategy ?? "discrete").setBaselineTriangles(chain.baselineTriangles).setLod0Triangles(chain.lod0Triangles).setErrorScale(chain.errorScale).setCounts([...chain.counts]).setErrors([...chain.errors]).setAbsoluteErrors([...chain.absoluteErrors]).setSharedVertexBuffers(true);
|
|
10793
10793
|
for (const indices of chain.indices) {
|
|
10794
10794
|
property.addIndices(
|
|
10795
10795
|
document.createAccessor().setArray(Uint32Array.from(indices)).setType("SCALAR").setBuffer(buffer)
|
|
@@ -11324,6 +11324,15 @@ function listNodeScenes(node) {
|
|
|
11324
11324
|
}
|
|
11325
11325
|
return child.listParents().filter((parent2) => parent2 instanceof Scene);
|
|
11326
11326
|
}
|
|
11327
|
+
function clearNodeParent(node) {
|
|
11328
|
+
const scenes = listNodeScenes(node);
|
|
11329
|
+
const parent = node.getParentNode();
|
|
11330
|
+
if (!parent) return node;
|
|
11331
|
+
node.setMatrix(node.getWorldMatrix());
|
|
11332
|
+
parent.removeChild(node);
|
|
11333
|
+
for (const scene of scenes) scene.addChild(node);
|
|
11334
|
+
return node;
|
|
11335
|
+
}
|
|
11327
11336
|
var ARRAY_TYPE2 = typeof Float32Array !== "undefined" ? Float32Array : Array;
|
|
11328
11337
|
function invert$1(out, a) {
|
|
11329
11338
|
var a00 = a[0], a01 = a[1], a02 = a[2], a03 = a[3];
|
|
@@ -12191,6 +12200,12 @@ function hasModifier(prop, cache) {
|
|
|
12191
12200
|
cache.set(prop, false);
|
|
12192
12201
|
return false;
|
|
12193
12202
|
}
|
|
12203
|
+
function dequantizeAttribute(attribute) {
|
|
12204
|
+
const srcArray = attribute.getArray();
|
|
12205
|
+
if (!srcArray) return;
|
|
12206
|
+
const dstArray = dequantizeAttributeArray(srcArray, attribute.getComponentType(), attribute.getNormalized());
|
|
12207
|
+
attribute.setArray(dstArray).setNormalized(false);
|
|
12208
|
+
}
|
|
12194
12209
|
function dequantizeAttributeArray(srcArray, componentType, normalized) {
|
|
12195
12210
|
const dstArray = new Float32Array(srcArray.length);
|
|
12196
12211
|
for (let i = 0, il = srcArray.length; i < il; i++) if (normalized) dstArray[i] = MathUtils.decodeNormalizedInt(srcArray[i], componentType);
|
|
@@ -12572,6 +12587,121 @@ async function maybeGetPixels(texture) {
|
|
|
12572
12587
|
return null;
|
|
12573
12588
|
}
|
|
12574
12589
|
}
|
|
12590
|
+
var NAME$19 = "instance";
|
|
12591
|
+
var INSTANCE_DEFAULTS = { min: 5 };
|
|
12592
|
+
function instance(_options = INSTANCE_DEFAULTS) {
|
|
12593
|
+
const options = assignDefaults(INSTANCE_DEFAULTS, _options);
|
|
12594
|
+
return createTransform(NAME$19, (doc) => {
|
|
12595
|
+
const logger = doc.getLogger();
|
|
12596
|
+
const root = doc.getRoot();
|
|
12597
|
+
if (root.listAnimations().length) {
|
|
12598
|
+
logger.warn(`${NAME$19}: Instancing is not currently supported for animated models.`);
|
|
12599
|
+
logger.debug(`${NAME$19}: Complete.`);
|
|
12600
|
+
return;
|
|
12601
|
+
}
|
|
12602
|
+
const batchExtension = doc.createExtension(EXTMeshGPUInstancing);
|
|
12603
|
+
let numBatches = 0;
|
|
12604
|
+
let numInstances = 0;
|
|
12605
|
+
for (const scene of root.listScenes()) {
|
|
12606
|
+
const meshInstances = /* @__PURE__ */ new Map();
|
|
12607
|
+
scene.traverse((node) => {
|
|
12608
|
+
const mesh = node.getMesh();
|
|
12609
|
+
if (!mesh) return;
|
|
12610
|
+
if (node.getExtension("EXT_mesh_gpu_instancing")) return;
|
|
12611
|
+
meshInstances.set(mesh, (meshInstances.get(mesh) || /* @__PURE__ */ new Set()).add(node));
|
|
12612
|
+
});
|
|
12613
|
+
const modifiedNodes = [];
|
|
12614
|
+
for (const mesh of Array.from(meshInstances.keys())) {
|
|
12615
|
+
const nodes = Array.from(meshInstances.get(mesh));
|
|
12616
|
+
if (nodes.length < options.min) continue;
|
|
12617
|
+
if (nodes.some((node) => node.getSkin())) continue;
|
|
12618
|
+
if (mesh.listPrimitives().some(hasVolume) && nodes.some(hasScale)) continue;
|
|
12619
|
+
const batch = createBatch(doc, batchExtension, mesh, nodes.length);
|
|
12620
|
+
const batchTranslation = batch.getAttribute("TRANSLATION");
|
|
12621
|
+
const batchRotation = batch.getAttribute("ROTATION");
|
|
12622
|
+
const batchScale = batch.getAttribute("SCALE");
|
|
12623
|
+
const batchNode = doc.createNode().setMesh(mesh).setExtension("EXT_mesh_gpu_instancing", batch);
|
|
12624
|
+
scene.addChild(batchNode);
|
|
12625
|
+
let needsTranslation = false;
|
|
12626
|
+
let needsRotation = false;
|
|
12627
|
+
let needsScale = false;
|
|
12628
|
+
for (let i = 0; i < nodes.length; i++) {
|
|
12629
|
+
let t, r, s;
|
|
12630
|
+
const node = nodes[i];
|
|
12631
|
+
batchTranslation.setElement(i, t = node.getWorldTranslation());
|
|
12632
|
+
batchRotation.setElement(i, r = node.getWorldRotation());
|
|
12633
|
+
batchScale.setElement(i, s = node.getWorldScale());
|
|
12634
|
+
if (!MathUtils.eq(t, [
|
|
12635
|
+
0,
|
|
12636
|
+
0,
|
|
12637
|
+
0
|
|
12638
|
+
])) needsTranslation = true;
|
|
12639
|
+
if (!MathUtils.eq(r, [
|
|
12640
|
+
0,
|
|
12641
|
+
0,
|
|
12642
|
+
0,
|
|
12643
|
+
1
|
|
12644
|
+
])) needsRotation = true;
|
|
12645
|
+
if (!MathUtils.eq(s, [
|
|
12646
|
+
1,
|
|
12647
|
+
1,
|
|
12648
|
+
1
|
|
12649
|
+
])) needsScale = true;
|
|
12650
|
+
}
|
|
12651
|
+
if (!needsTranslation) batchTranslation.dispose();
|
|
12652
|
+
if (!needsRotation) batchRotation.dispose();
|
|
12653
|
+
if (!needsScale) batchScale.dispose();
|
|
12654
|
+
if (!needsTranslation && !needsRotation && !needsScale) {
|
|
12655
|
+
batchNode.dispose();
|
|
12656
|
+
batch.dispose();
|
|
12657
|
+
continue;
|
|
12658
|
+
}
|
|
12659
|
+
for (const node of nodes) {
|
|
12660
|
+
node.setMesh(null);
|
|
12661
|
+
modifiedNodes.push(node);
|
|
12662
|
+
}
|
|
12663
|
+
numBatches++;
|
|
12664
|
+
numInstances += nodes.length;
|
|
12665
|
+
}
|
|
12666
|
+
pruneUnusedNodes(modifiedNodes, logger);
|
|
12667
|
+
}
|
|
12668
|
+
if (numBatches > 0) logger.info(`${NAME$19}: Created ${numBatches} batches, with ${numInstances} total instances.`);
|
|
12669
|
+
else logger.info(`${NAME$19}: No meshes with >=${options.min} parent nodes were found.`);
|
|
12670
|
+
if (batchExtension.listProperties().length === 0) batchExtension.dispose();
|
|
12671
|
+
logger.debug(`${NAME$19}: Complete.`);
|
|
12672
|
+
});
|
|
12673
|
+
}
|
|
12674
|
+
function pruneUnusedNodes(nodes, logger) {
|
|
12675
|
+
let node;
|
|
12676
|
+
let unusedNodes = 0;
|
|
12677
|
+
while (node = nodes.pop()) {
|
|
12678
|
+
if (node.listChildren().length || node.getCamera() || node.getMesh() || node.getSkin() || node.listExtensions().length) continue;
|
|
12679
|
+
const nodeParent = node.getParentNode();
|
|
12680
|
+
if (nodeParent) nodes.push(nodeParent);
|
|
12681
|
+
node.dispose();
|
|
12682
|
+
unusedNodes++;
|
|
12683
|
+
}
|
|
12684
|
+
logger.debug(`${NAME$19}: Removed ${unusedNodes} unused nodes.`);
|
|
12685
|
+
}
|
|
12686
|
+
function hasVolume(prim) {
|
|
12687
|
+
const material = prim.getMaterial();
|
|
12688
|
+
return !!(material && material.getExtension("KHR_materials_volume"));
|
|
12689
|
+
}
|
|
12690
|
+
function hasScale(node) {
|
|
12691
|
+
const scale3 = node.getWorldScale();
|
|
12692
|
+
return !MathUtils.eq(scale3, [
|
|
12693
|
+
1,
|
|
12694
|
+
1,
|
|
12695
|
+
1
|
|
12696
|
+
]);
|
|
12697
|
+
}
|
|
12698
|
+
function createBatch(doc, batchExtension, mesh, count) {
|
|
12699
|
+
const buffer = mesh.listPrimitives()[0].getAttribute("POSITION").getBuffer();
|
|
12700
|
+
const batchTranslation = doc.createAccessor().setType("VEC3").setArray(new Float32Array(3 * count)).setBuffer(buffer);
|
|
12701
|
+
const batchRotation = doc.createAccessor().setType("VEC4").setArray(new Float32Array(4 * count)).setBuffer(buffer);
|
|
12702
|
+
const batchScale = doc.createAccessor().setType("VEC3").setArray(new Float32Array(3 * count)).setBuffer(buffer);
|
|
12703
|
+
return batchExtension.createInstancedMesh().setAttribute("TRANSLATION", batchTranslation).setAttribute("ROTATION", batchRotation).setAttribute("SCALE", batchScale);
|
|
12704
|
+
}
|
|
12575
12705
|
var JOIN_PRIMITIVE_DEFAULTS = { skipValidation: false };
|
|
12576
12706
|
var EMPTY_U32 = 2 ** 32 - 1;
|
|
12577
12707
|
function joinPrimitives(prims, _options = {}) {
|
|
@@ -12656,7 +12786,145 @@ function remapIndices(srcIndices, remap, dstIndices, dstOffset) {
|
|
|
12656
12786
|
}
|
|
12657
12787
|
}
|
|
12658
12788
|
}
|
|
12789
|
+
var NAME$18 = "join";
|
|
12659
12790
|
var { ROOT, NODE, MESH, PRIMITIVE, ACCESSOR } = PropertyType;
|
|
12791
|
+
var _matrix = [
|
|
12792
|
+
0,
|
|
12793
|
+
0,
|
|
12794
|
+
0,
|
|
12795
|
+
0,
|
|
12796
|
+
0,
|
|
12797
|
+
0,
|
|
12798
|
+
0,
|
|
12799
|
+
0,
|
|
12800
|
+
0,
|
|
12801
|
+
0,
|
|
12802
|
+
0,
|
|
12803
|
+
0,
|
|
12804
|
+
0,
|
|
12805
|
+
0,
|
|
12806
|
+
0,
|
|
12807
|
+
0
|
|
12808
|
+
];
|
|
12809
|
+
var JOIN_DEFAULTS = {
|
|
12810
|
+
keepMeshes: false,
|
|
12811
|
+
keepNamed: false,
|
|
12812
|
+
cleanup: true,
|
|
12813
|
+
filter: () => true
|
|
12814
|
+
};
|
|
12815
|
+
function join(_options = JOIN_DEFAULTS) {
|
|
12816
|
+
const options = assignDefaults(JOIN_DEFAULTS, _options);
|
|
12817
|
+
return createTransform(NAME$18, async (document) => {
|
|
12818
|
+
const root = document.getRoot();
|
|
12819
|
+
const logger = document.getLogger();
|
|
12820
|
+
const srcRestartModeCount = _getPrimRestartModeCount(document);
|
|
12821
|
+
for (const scene of root.listScenes()) {
|
|
12822
|
+
_joinLevel(document, scene, options);
|
|
12823
|
+
scene.traverse((node) => _joinLevel(document, node, options));
|
|
12824
|
+
}
|
|
12825
|
+
if (_getPrimRestartModeCount(document) < srcRestartModeCount) document.createExtension(KHRMeshPrimitiveRestart).setRequired(true);
|
|
12826
|
+
if (options.cleanup) await document.transform(prune({
|
|
12827
|
+
propertyTypes: [
|
|
12828
|
+
NODE,
|
|
12829
|
+
MESH,
|
|
12830
|
+
PRIMITIVE,
|
|
12831
|
+
ACCESSOR
|
|
12832
|
+
],
|
|
12833
|
+
keepAttributes: true,
|
|
12834
|
+
keepIndices: true,
|
|
12835
|
+
keepLeaves: false
|
|
12836
|
+
}));
|
|
12837
|
+
logger.debug(`${NAME$18}: Complete.`);
|
|
12838
|
+
});
|
|
12839
|
+
}
|
|
12840
|
+
function _joinLevel(document, parent, options) {
|
|
12841
|
+
const logger = document.getLogger();
|
|
12842
|
+
const groups = {};
|
|
12843
|
+
const children = parent.listChildren();
|
|
12844
|
+
for (let nodeIndex = 0; nodeIndex < children.length; nodeIndex++) {
|
|
12845
|
+
const node = children[nodeIndex];
|
|
12846
|
+
if (!options.filter(node)) continue;
|
|
12847
|
+
if (node.listParents().some((p) => p instanceof AnimationChannel)) continue;
|
|
12848
|
+
const mesh = node.getMesh();
|
|
12849
|
+
if (!mesh) continue;
|
|
12850
|
+
if (node.getExtension("EXT_mesh_gpu_instancing")) continue;
|
|
12851
|
+
if (node.getSkin()) continue;
|
|
12852
|
+
for (const prim of mesh.listPrimitives()) {
|
|
12853
|
+
if (prim.listTargets().length > 0) continue;
|
|
12854
|
+
const material = prim.getMaterial();
|
|
12855
|
+
if (material && material.getExtension("KHR_materials_volume")) continue;
|
|
12856
|
+
compactPrimitive(prim);
|
|
12857
|
+
dequantizeTransformableAttributes(prim);
|
|
12858
|
+
let key = createPrimGroupKey(prim);
|
|
12859
|
+
const isNamed = mesh.getName() || node.getName();
|
|
12860
|
+
if (options.keepMeshes || options.keepNamed && isNamed) key += `|${nodeIndex}`;
|
|
12861
|
+
if (!(key in groups)) groups[key] = {
|
|
12862
|
+
prims: [],
|
|
12863
|
+
primMeshes: [],
|
|
12864
|
+
primNodes: [],
|
|
12865
|
+
dstNode: node,
|
|
12866
|
+
dstMesh: void 0
|
|
12867
|
+
};
|
|
12868
|
+
const group = groups[key];
|
|
12869
|
+
group.prims.push(prim);
|
|
12870
|
+
group.primNodes.push(node);
|
|
12871
|
+
}
|
|
12872
|
+
}
|
|
12873
|
+
const joinGroups = Object.values(groups).filter(({ prims }) => prims.length > 1);
|
|
12874
|
+
const srcNodes = new Set(joinGroups.flatMap((group) => group.primNodes));
|
|
12875
|
+
for (const node of srcNodes) {
|
|
12876
|
+
const mesh = node.getMesh();
|
|
12877
|
+
if (mesh.listParents().some((parent2) => {
|
|
12878
|
+
return parent2.propertyType !== ROOT && node !== parent2;
|
|
12879
|
+
})) node.setMesh(mesh.clone());
|
|
12880
|
+
}
|
|
12881
|
+
for (const group of joinGroups) {
|
|
12882
|
+
const { dstNode, primNodes } = group;
|
|
12883
|
+
group.dstMesh = dstNode.getMesh();
|
|
12884
|
+
group.primMeshes = primNodes.map((node) => node.getMesh());
|
|
12885
|
+
}
|
|
12886
|
+
for (const group of joinGroups) {
|
|
12887
|
+
const { prims, primNodes, primMeshes, dstNode, dstMesh } = group;
|
|
12888
|
+
const dstMatrix = dstNode.getMatrix();
|
|
12889
|
+
for (let i = 0; i < prims.length; i++) {
|
|
12890
|
+
const primNode = primNodes[i];
|
|
12891
|
+
const primMesh = primMeshes[i];
|
|
12892
|
+
let prim = prims[i];
|
|
12893
|
+
primMesh.removePrimitive(prim);
|
|
12894
|
+
if (isUsed(prim)) prim = prims[i] = _deepClonePrimitive(prims[i]);
|
|
12895
|
+
if (primNode !== dstNode) {
|
|
12896
|
+
multiply$2(_matrix, invert$1(_matrix, dstMatrix), primNode.getMatrix());
|
|
12897
|
+
transformPrimitive(prim, _matrix);
|
|
12898
|
+
}
|
|
12899
|
+
}
|
|
12900
|
+
const dstPrim = joinPrimitives(prims);
|
|
12901
|
+
const dstVertexCount = dstPrim.listAttributes()[0].getCount();
|
|
12902
|
+
dstMesh.addPrimitive(dstPrim);
|
|
12903
|
+
logger.debug(`${NAME$18}: Joined Primitives (${prims.length}) containing ${formatLong(dstVertexCount)} vertices under Node "${dstNode.getName()}".`);
|
|
12904
|
+
}
|
|
12905
|
+
}
|
|
12906
|
+
function _deepClonePrimitive(src) {
|
|
12907
|
+
const dst = src.clone();
|
|
12908
|
+
for (const semantic of dst.listSemantics()) dst.setAttribute(semantic, dst.getAttribute(semantic).clone());
|
|
12909
|
+
const indices = dst.getIndices();
|
|
12910
|
+
if (indices) dst.setIndices(indices.clone());
|
|
12911
|
+
return dst;
|
|
12912
|
+
}
|
|
12913
|
+
function _getPrimRestartModeCount(document) {
|
|
12914
|
+
let count = 0;
|
|
12915
|
+
for (const mesh of document.getRoot().listMeshes()) for (const prim of mesh.listPrimitives()) if (isPrimitiveRestartMode(prim.getMode())) count++;
|
|
12916
|
+
return count;
|
|
12917
|
+
}
|
|
12918
|
+
function dequantizeTransformableAttributes(prim) {
|
|
12919
|
+
for (const semantic of [
|
|
12920
|
+
"POSITION",
|
|
12921
|
+
"NORMAL",
|
|
12922
|
+
"TANGENT"
|
|
12923
|
+
]) {
|
|
12924
|
+
const attribute = prim.getAttribute(semantic);
|
|
12925
|
+
if (attribute) dequantizeAttribute(attribute);
|
|
12926
|
+
}
|
|
12927
|
+
}
|
|
12660
12928
|
function sortPrimitiveWeights(prim, limit = Infinity) {
|
|
12661
12929
|
if (Number.isFinite(limit) && limit % 4 || limit <= 0) throw new Error(`Limit must be positive multiple of four.`);
|
|
12662
12930
|
const vertexCount = prim.getAttribute("POSITION").getCount();
|
|
@@ -12964,7 +13232,7 @@ function transformBatch(document, batch, nodeTransform) {
|
|
|
12964
13232
|
1,
|
|
12965
13233
|
1
|
|
12966
13234
|
];
|
|
12967
|
-
const
|
|
13235
|
+
const instanceMatrix2 = [
|
|
12968
13236
|
1,
|
|
12969
13237
|
0,
|
|
12970
13238
|
0,
|
|
@@ -12984,9 +13252,9 @@ function transformBatch(document, batch, nodeTransform) {
|
|
|
12984
13252
|
];
|
|
12985
13253
|
const transformMatrix = fromTransform(nodeTransform);
|
|
12986
13254
|
for (let i = 0, count = tpl.getCount(); i < count; i++) {
|
|
12987
|
-
MathUtils.compose(instanceTranslation ? instanceTranslation.getElement(i, t) : T_IDENTITY, instanceRotation ? instanceRotation.getElement(i, r) : R_IDENTITY, instanceScale ? instanceScale.getElement(i, s) : S_IDENTITY,
|
|
12988
|
-
multiply$2(
|
|
12989
|
-
MathUtils.decompose(
|
|
13255
|
+
MathUtils.compose(instanceTranslation ? instanceTranslation.getElement(i, t) : T_IDENTITY, instanceRotation ? instanceRotation.getElement(i, r) : R_IDENTITY, instanceScale ? instanceScale.getElement(i, s) : S_IDENTITY, instanceMatrix2);
|
|
13256
|
+
multiply$2(instanceMatrix2, instanceMatrix2, transformMatrix);
|
|
13257
|
+
MathUtils.decompose(instanceMatrix2, t, r, s);
|
|
12990
13258
|
if (instanceTranslation) instanceTranslation.setElement(i, t);
|
|
12991
13259
|
if (instanceRotation) instanceRotation.setElement(i, r);
|
|
12992
13260
|
if (instanceScale) instanceScale.setElement(i, s);
|
|
@@ -13371,6 +13639,460 @@ function _simplifyPoints(document, prim, options) {
|
|
|
13371
13639
|
return prim;
|
|
13372
13640
|
}
|
|
13373
13641
|
|
|
13642
|
+
// src/content/dedupe-materials.ts
|
|
13643
|
+
function normalise(value) {
|
|
13644
|
+
if (value === void 0) return "\0";
|
|
13645
|
+
if (Array.isArray(value)) return value.map((entry) => normalise(entry)).join(",");
|
|
13646
|
+
if (typeof value === "number") {
|
|
13647
|
+
return Object.is(value, -0) ? "0" : String(value);
|
|
13648
|
+
}
|
|
13649
|
+
return String(value);
|
|
13650
|
+
}
|
|
13651
|
+
function materialSignature(material) {
|
|
13652
|
+
const parts = [];
|
|
13653
|
+
for (const [slot, texture] of Object.entries(material.textures).sort(
|
|
13654
|
+
([left], [right]) => left < right ? -1 : 1
|
|
13655
|
+
))
|
|
13656
|
+
parts.push(`t:${slot}=${texture ?? "\0"}`);
|
|
13657
|
+
for (const [name, value] of Object.entries(material.uniforms).sort(
|
|
13658
|
+
([left], [right]) => left < right ? -1 : 1
|
|
13659
|
+
))
|
|
13660
|
+
parts.push(`u:${name}=${normalise(value)}`);
|
|
13661
|
+
for (const [name, value] of Object.entries(material.flags).sort(
|
|
13662
|
+
([left], [right]) => left < right ? -1 : 1
|
|
13663
|
+
))
|
|
13664
|
+
parts.push(`f:${name}=${normalise(value)}`);
|
|
13665
|
+
return parts.join("|");
|
|
13666
|
+
}
|
|
13667
|
+
function dedupeMaterials(materials) {
|
|
13668
|
+
const bySignature = /* @__PURE__ */ new Map();
|
|
13669
|
+
for (let index = 0; index < materials.length; index += 1) {
|
|
13670
|
+
const material = materials[index];
|
|
13671
|
+
if (material === void 0) continue;
|
|
13672
|
+
const signature = materialSignature(material);
|
|
13673
|
+
const members = bySignature.get(signature);
|
|
13674
|
+
const name = material.name ?? `material-${String(index)}`;
|
|
13675
|
+
if (members === void 0) bySignature.set(signature, [name]);
|
|
13676
|
+
else members.push(name);
|
|
13677
|
+
}
|
|
13678
|
+
const buckets = [...bySignature.entries()].map(([signature, members]) => ({
|
|
13679
|
+
members,
|
|
13680
|
+
signature
|
|
13681
|
+
}));
|
|
13682
|
+
return {
|
|
13683
|
+
buckets,
|
|
13684
|
+
census: {
|
|
13685
|
+
buckets: buckets.length,
|
|
13686
|
+
materials: materials.length,
|
|
13687
|
+
singletons: buckets.filter((bucket) => bucket.members.length === 1).length
|
|
13688
|
+
}
|
|
13689
|
+
};
|
|
13690
|
+
}
|
|
13691
|
+
|
|
13692
|
+
// src/content/census.ts
|
|
13693
|
+
function textureKey(texture) {
|
|
13694
|
+
if (texture === null) return void 0;
|
|
13695
|
+
const uri = texture.getURI();
|
|
13696
|
+
if (uri !== "") return uri;
|
|
13697
|
+
const name = texture.getName();
|
|
13698
|
+
if (name !== "") return `name:${name}`;
|
|
13699
|
+
const image = texture.getImage();
|
|
13700
|
+
return image === null ? void 0 : `bytes:${String(image.byteLength)}:${String(image[0] ?? 0)}`;
|
|
13701
|
+
}
|
|
13702
|
+
function materialStateOf(material) {
|
|
13703
|
+
const textures = {
|
|
13704
|
+
baseColor: textureKey(material.getBaseColorTexture()),
|
|
13705
|
+
emissive: textureKey(material.getEmissiveTexture()),
|
|
13706
|
+
metallicRoughness: textureKey(material.getMetallicRoughnessTexture()),
|
|
13707
|
+
normal: textureKey(material.getNormalTexture()),
|
|
13708
|
+
occlusion: textureKey(material.getOcclusionTexture())
|
|
13709
|
+
};
|
|
13710
|
+
return {
|
|
13711
|
+
flags: {
|
|
13712
|
+
alphaMode: material.getAlphaMode(),
|
|
13713
|
+
doubleSided: material.getDoubleSided()
|
|
13714
|
+
},
|
|
13715
|
+
name: material.getName(),
|
|
13716
|
+
textures,
|
|
13717
|
+
uniforms: {
|
|
13718
|
+
alphaCutoff: material.getAlphaCutoff(),
|
|
13719
|
+
baseColorFactor: [...material.getBaseColorFactor()],
|
|
13720
|
+
emissiveFactor: [...material.getEmissiveFactor()],
|
|
13721
|
+
metallic: material.getMetallicFactor(),
|
|
13722
|
+
normalScale: material.getNormalScale(),
|
|
13723
|
+
occlusionStrength: material.getOcclusionStrength(),
|
|
13724
|
+
roughness: material.getRoughnessFactor()
|
|
13725
|
+
}
|
|
13726
|
+
};
|
|
13727
|
+
}
|
|
13728
|
+
|
|
13729
|
+
// src/foliage.ts
|
|
13730
|
+
var CUTOUT_ALPHA_CUTOFF = 0.5;
|
|
13731
|
+
var VOLUME_EXTENSIONS = ["transmission", "volume", "displacement"];
|
|
13732
|
+
function cutoutSkipReason(material, primitives) {
|
|
13733
|
+
if (material.getBaseColorTexture() === null) return "no-texture-alpha";
|
|
13734
|
+
if ((material.getBaseColorFactor()[3] ?? 1) < 1) return "uniform-transparency";
|
|
13735
|
+
for (const extension of material.listExtensions()) {
|
|
13736
|
+
const name = extension.extensionName.toLowerCase();
|
|
13737
|
+
if (VOLUME_EXTENSIONS.some((needle) => name.includes(needle))) return "volume-or-transmission";
|
|
13738
|
+
}
|
|
13739
|
+
if (primitives.some(vertexAlphaVaries)) return "vertex-colour-alpha";
|
|
13740
|
+
return null;
|
|
13741
|
+
}
|
|
13742
|
+
function vertexAlphaVaries(primitive) {
|
|
13743
|
+
const color = primitive.getAttribute("COLOR_0");
|
|
13744
|
+
if (color === null || color.getElementSize() !== 4) return false;
|
|
13745
|
+
const element = [0, 0, 0, 0];
|
|
13746
|
+
for (let vertex = 0; vertex < color.getCount(); vertex += 1) {
|
|
13747
|
+
color.getElement(vertex, element);
|
|
13748
|
+
if (element[3] < 1) return true;
|
|
13749
|
+
}
|
|
13750
|
+
return false;
|
|
13751
|
+
}
|
|
13752
|
+
function usesMaterial(primitives, material) {
|
|
13753
|
+
return primitives.filter((primitive) => primitive.getMaterial() === material);
|
|
13754
|
+
}
|
|
13755
|
+
function convertFoliageCutout(document) {
|
|
13756
|
+
const root = document.getRoot();
|
|
13757
|
+
const primitives = root.listMeshes().flatMap((mesh) => [...mesh.listPrimitives()]);
|
|
13758
|
+
const converted = [];
|
|
13759
|
+
const kept = [];
|
|
13760
|
+
for (const material of root.listMaterials()) {
|
|
13761
|
+
if (material.getAlphaMode() !== "BLEND") continue;
|
|
13762
|
+
const name = material.getName();
|
|
13763
|
+
const reason = cutoutSkipReason(material, usesMaterial(primitives, material));
|
|
13764
|
+
if (reason !== null) {
|
|
13765
|
+
kept.push({ name, reason });
|
|
13766
|
+
continue;
|
|
13767
|
+
}
|
|
13768
|
+
material.setAlphaMode("MASK").setAlphaCutoff(CUTOUT_ALPHA_CUTOFF);
|
|
13769
|
+
converted.push(name);
|
|
13770
|
+
}
|
|
13771
|
+
return { converted, kept };
|
|
13772
|
+
}
|
|
13773
|
+
function mergeIdenticalMaterials(document) {
|
|
13774
|
+
const root = document.getRoot();
|
|
13775
|
+
const materials = root.listMaterials();
|
|
13776
|
+
const states = materials.map(materialStateOf);
|
|
13777
|
+
const before = dedupeMaterials(states).census;
|
|
13778
|
+
const groups = /* @__PURE__ */ new Map();
|
|
13779
|
+
states.forEach((state, index) => {
|
|
13780
|
+
const signature = materialSignature(state);
|
|
13781
|
+
const group = groups.get(signature);
|
|
13782
|
+
if (group === void 0) groups.set(signature, [index]);
|
|
13783
|
+
else group.push(index);
|
|
13784
|
+
});
|
|
13785
|
+
const merged = [];
|
|
13786
|
+
for (const group of groups.values()) {
|
|
13787
|
+
const [keeper, ...victims] = group;
|
|
13788
|
+
const keep = keeper === void 0 ? void 0 : materials[keeper];
|
|
13789
|
+
if (keep === void 0) continue;
|
|
13790
|
+
for (const index of victims) {
|
|
13791
|
+
const victim = materials[index];
|
|
13792
|
+
if (victim === void 0 || victim === keep) continue;
|
|
13793
|
+
for (const mesh of root.listMeshes()) {
|
|
13794
|
+
for (const primitive of mesh.listPrimitives()) {
|
|
13795
|
+
if (primitive.getMaterial() === victim) primitive.setMaterial(keep);
|
|
13796
|
+
}
|
|
13797
|
+
}
|
|
13798
|
+
merged.push(victim.getName());
|
|
13799
|
+
victim.dispose();
|
|
13800
|
+
}
|
|
13801
|
+
}
|
|
13802
|
+
const after = root.listMaterials();
|
|
13803
|
+
return {
|
|
13804
|
+
distinct: {
|
|
13805
|
+
after: dedupeMaterials(after.map(materialStateOf)).census.buckets,
|
|
13806
|
+
before: before.buckets
|
|
13807
|
+
},
|
|
13808
|
+
materials: { after: after.length, before: before.materials },
|
|
13809
|
+
merged
|
|
13810
|
+
};
|
|
13811
|
+
}
|
|
13812
|
+
|
|
13813
|
+
// src/lod/cards.ts
|
|
13814
|
+
var MAX_CARD_TRIANGLES = 8;
|
|
13815
|
+
var CARD_KEEP_RATIOS = [0.5, 0.25];
|
|
13816
|
+
var TRIANGLE = 3;
|
|
13817
|
+
function findCards(positions, indices) {
|
|
13818
|
+
const vertexCount = Math.floor(positions.length / 3);
|
|
13819
|
+
const parent = new Int32Array(vertexCount);
|
|
13820
|
+
for (let vertex = 0; vertex < vertexCount; vertex += 1) parent[vertex] = vertex;
|
|
13821
|
+
const find = (start) => {
|
|
13822
|
+
let root = start;
|
|
13823
|
+
while (parent[root] !== root) root = parent[root];
|
|
13824
|
+
let walk = start;
|
|
13825
|
+
while (parent[walk] !== root) {
|
|
13826
|
+
const next = parent[walk];
|
|
13827
|
+
parent[walk] = root;
|
|
13828
|
+
walk = next;
|
|
13829
|
+
}
|
|
13830
|
+
return root;
|
|
13831
|
+
};
|
|
13832
|
+
for (let triangle = 0; triangle + 2 < indices.length; triangle += TRIANGLE) {
|
|
13833
|
+
const a = find(indices[triangle]);
|
|
13834
|
+
const b = find(indices[triangle + 1]);
|
|
13835
|
+
const c = find(indices[triangle + 2]);
|
|
13836
|
+
if (a === c) continue;
|
|
13837
|
+
const low = Math.min(a, b, c);
|
|
13838
|
+
for (const root of [a, b, c]) if (root !== low) parent[root] = low;
|
|
13839
|
+
}
|
|
13840
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
13841
|
+
for (let triangle = 0; triangle + 2 < indices.length; triangle += TRIANGLE) {
|
|
13842
|
+
const root = find(indices[triangle]);
|
|
13843
|
+
const group = grouped.get(root);
|
|
13844
|
+
if (group === void 0) grouped.set(root, [triangle]);
|
|
13845
|
+
else group.push(triangle);
|
|
13846
|
+
if ((group?.length ?? 1) > MAX_CARD_TRIANGLES) return null;
|
|
13847
|
+
}
|
|
13848
|
+
const cards = [];
|
|
13849
|
+
for (const triangles of grouped.values()) {
|
|
13850
|
+
if (triangles.length === 0) continue;
|
|
13851
|
+
let area = 0;
|
|
13852
|
+
let sx = 0;
|
|
13853
|
+
let sy = 0;
|
|
13854
|
+
let sz = 0;
|
|
13855
|
+
let vertices = 0;
|
|
13856
|
+
const seen = /* @__PURE__ */ new Set();
|
|
13857
|
+
for (const triangle of triangles) {
|
|
13858
|
+
for (let corner = 0; corner < TRIANGLE; corner += 1) {
|
|
13859
|
+
const vertex = indices[triangle + corner];
|
|
13860
|
+
if (seen.has(vertex)) continue;
|
|
13861
|
+
seen.add(vertex);
|
|
13862
|
+
vertices += 1;
|
|
13863
|
+
sx += positions[vertex * 3];
|
|
13864
|
+
sy += positions[vertex * 3 + 1];
|
|
13865
|
+
sz += positions[vertex * 3 + 2];
|
|
13866
|
+
}
|
|
13867
|
+
const a = indices[triangle] * 3;
|
|
13868
|
+
const b = indices[triangle + 1] * 3;
|
|
13869
|
+
const c = indices[triangle + 2] * 3;
|
|
13870
|
+
const ux = positions[b] - positions[a];
|
|
13871
|
+
const uy = positions[b + 1] - positions[a + 1];
|
|
13872
|
+
const uz = positions[b + 2] - positions[a + 2];
|
|
13873
|
+
const vx = positions[c] - positions[a];
|
|
13874
|
+
const vy = positions[c + 1] - positions[a + 1];
|
|
13875
|
+
const vz = positions[c + 2] - positions[a + 2];
|
|
13876
|
+
const nx = uy * vz - uz * vy;
|
|
13877
|
+
const ny = uz * vx - ux * vz;
|
|
13878
|
+
const nz = ux * vy - uy * vx;
|
|
13879
|
+
area += Math.sqrt(nx * nx + ny * ny + nz * nz) / 2;
|
|
13880
|
+
}
|
|
13881
|
+
cards.push({
|
|
13882
|
+
area,
|
|
13883
|
+
centroid: vertices === 0 ? [0, 0, 0] : [sx / vertices, sy / vertices, sz / vertices],
|
|
13884
|
+
triangles
|
|
13885
|
+
});
|
|
13886
|
+
}
|
|
13887
|
+
return cards.length > 0 ? cards : null;
|
|
13888
|
+
}
|
|
13889
|
+
function positionKey(centroid, quantum) {
|
|
13890
|
+
const q = (value) => Math.round(value / quantum) | 0;
|
|
13891
|
+
let hash = q(centroid[0]) * 73856093 ^ q(centroid[1]) * 19349663 ^ q(centroid[2]) * 83492791;
|
|
13892
|
+
hash = Math.imul(hash ^ hash >>> 15, 739982445);
|
|
13893
|
+
hash = Math.imul(hash ^ hash >>> 12, 695872825);
|
|
13894
|
+
return hash ^ hash >>> 15 | 0;
|
|
13895
|
+
}
|
|
13896
|
+
function placeCards(cards, cellSize) {
|
|
13897
|
+
const placed = cards.map((card, index) => {
|
|
13898
|
+
const [x, y, z] = card.centroid;
|
|
13899
|
+
return {
|
|
13900
|
+
card,
|
|
13901
|
+
// One integer key per cell: the grid is a hash, not a sort order.
|
|
13902
|
+
cell: Math.floor(x / cellSize) * 73856093 ^ Math.floor(y / cellSize) * 19349663 ^ Math.floor(z / cellSize) * 83492791,
|
|
13903
|
+
first: index,
|
|
13904
|
+
key: positionKey(card.centroid, cellSize / 32)
|
|
13905
|
+
};
|
|
13906
|
+
});
|
|
13907
|
+
return { cells: new Set(placed.map((entry) => entry.cell)).size, placed };
|
|
13908
|
+
}
|
|
13909
|
+
function orderPlaced(a, b) {
|
|
13910
|
+
return a.key - b.key || a.card.centroid[0] - b.card.centroid[0] || a.card.centroid[1] - b.card.centroid[1] || a.card.centroid[2] - b.card.centroid[2] || a.first - b.first;
|
|
13911
|
+
}
|
|
13912
|
+
function bucketsOf(placed) {
|
|
13913
|
+
const buckets = /* @__PURE__ */ new Map();
|
|
13914
|
+
for (const entry of placed) {
|
|
13915
|
+
const bucket = buckets.get(entry.cell);
|
|
13916
|
+
if (bucket === void 0) buckets.set(entry.cell, [entry]);
|
|
13917
|
+
else bucket.push(entry);
|
|
13918
|
+
}
|
|
13919
|
+
const cells = [...buckets.keys()].sort((a, b) => a - b);
|
|
13920
|
+
for (const cell of cells) buckets.get(cell).sort(orderPlaced);
|
|
13921
|
+
return { buckets, cells };
|
|
13922
|
+
}
|
|
13923
|
+
function thinLevel(placed, keep, lod0Area, lod0Cells) {
|
|
13924
|
+
const { buckets, cells: order } = bucketsOf(placed);
|
|
13925
|
+
const kept = [];
|
|
13926
|
+
let area = 0;
|
|
13927
|
+
let cells = 0;
|
|
13928
|
+
for (const cell of order) {
|
|
13929
|
+
const bucket = buckets.get(cell);
|
|
13930
|
+
const survivors = Math.max(1, Math.min(bucket.length, Math.round(keep * bucket.length)));
|
|
13931
|
+
for (const entry of bucket.slice(0, survivors)) {
|
|
13932
|
+
kept.push(entry.card);
|
|
13933
|
+
area += entry.card.area;
|
|
13934
|
+
}
|
|
13935
|
+
cells += 1;
|
|
13936
|
+
}
|
|
13937
|
+
return {
|
|
13938
|
+
cards: kept,
|
|
13939
|
+
summary: {
|
|
13940
|
+
areaCoverage: lod0Area === 0 ? 1 : area / lod0Area,
|
|
13941
|
+
cards: kept.length,
|
|
13942
|
+
cellCoverage: lod0Cells === 0 ? 1 : cells / lod0Cells,
|
|
13943
|
+
keep
|
|
13944
|
+
}
|
|
13945
|
+
};
|
|
13946
|
+
}
|
|
13947
|
+
function indicesOf(cards, source) {
|
|
13948
|
+
let length4 = 0;
|
|
13949
|
+
for (const card of cards) length4 += card.triangles.length * TRIANGLE;
|
|
13950
|
+
const out = new Uint32Array(length4);
|
|
13951
|
+
let at = 0;
|
|
13952
|
+
for (const card of cards)
|
|
13953
|
+
for (const triangle of card.triangles) {
|
|
13954
|
+
out[at] = source[triangle];
|
|
13955
|
+
out[at + 1] = source[triangle + 1];
|
|
13956
|
+
out[at + 2] = source[triangle + 2];
|
|
13957
|
+
at += TRIANGLE;
|
|
13958
|
+
}
|
|
13959
|
+
return out;
|
|
13960
|
+
}
|
|
13961
|
+
function cardTriangles(card) {
|
|
13962
|
+
return card.triangles.length;
|
|
13963
|
+
}
|
|
13964
|
+
function terminalCardLevel(cards, lod0Placed, lod0Cells, lod0Area, cellSize, target, previousTriangles) {
|
|
13965
|
+
if (target >= previousTriangles) return null;
|
|
13966
|
+
let factor = 1;
|
|
13967
|
+
let { buckets, cells } = bucketsOf(lod0Placed);
|
|
13968
|
+
const baseTriangles = () => cells.reduce(
|
|
13969
|
+
(total, cell) => total + cardTriangles(buckets.get(cell)[0]?.card),
|
|
13970
|
+
0
|
|
13971
|
+
);
|
|
13972
|
+
while (baseTriangles() > target && cells.length > 1) {
|
|
13973
|
+
factor *= 2;
|
|
13974
|
+
({ buckets, cells } = bucketsOf(placeCards(cards, cellSize * factor).placed));
|
|
13975
|
+
}
|
|
13976
|
+
const kept = [];
|
|
13977
|
+
const keptSet = /* @__PURE__ */ new Set();
|
|
13978
|
+
let triangles = 0;
|
|
13979
|
+
for (const cell of cells) {
|
|
13980
|
+
const first = buckets.get(cell)[0];
|
|
13981
|
+
kept.push(first.card);
|
|
13982
|
+
keptSet.add(first.card);
|
|
13983
|
+
triangles += cardTriangles(first.card);
|
|
13984
|
+
}
|
|
13985
|
+
for (const cell of cells) {
|
|
13986
|
+
const bucket = buckets.get(cell);
|
|
13987
|
+
for (let index = 1; index < bucket.length; index += 1) {
|
|
13988
|
+
const card = bucket[index].card;
|
|
13989
|
+
const cost = cardTriangles(card);
|
|
13990
|
+
if (triangles + cost > target) continue;
|
|
13991
|
+
kept.push(card);
|
|
13992
|
+
keptSet.add(card);
|
|
13993
|
+
triangles += cost;
|
|
13994
|
+
}
|
|
13995
|
+
}
|
|
13996
|
+
if (kept.length === 0) return null;
|
|
13997
|
+
const covered = /* @__PURE__ */ new Set();
|
|
13998
|
+
for (const entry of lod0Placed) if (keptSet.has(entry.card)) covered.add(entry.cell);
|
|
13999
|
+
let area = 0;
|
|
14000
|
+
for (const card of kept) area += card.area;
|
|
14001
|
+
return {
|
|
14002
|
+
cards: kept,
|
|
14003
|
+
factor,
|
|
14004
|
+
summary: {
|
|
14005
|
+
areaCoverage: lod0Area === 0 ? 1 : area / lod0Area,
|
|
14006
|
+
cards: kept.length,
|
|
14007
|
+
cellCoverage: lod0Cells === 0 ? 1 : covered.size / lod0Cells,
|
|
14008
|
+
keep: kept.length / cards.length
|
|
14009
|
+
}
|
|
14010
|
+
};
|
|
14011
|
+
}
|
|
14012
|
+
function generateCardChain(positions, indices, maxLevels, minSaving, scale3, keepRatios = CARD_KEEP_RATIOS, terminalTriangles) {
|
|
14013
|
+
const cards = findCards(positions, indices);
|
|
14014
|
+
if (cards === null) return null;
|
|
14015
|
+
const lod0Triangles = Math.floor(indices.length / TRIANGLE);
|
|
14016
|
+
if (lod0Triangles <= 0) return null;
|
|
14017
|
+
let minX = Number.POSITIVE_INFINITY;
|
|
14018
|
+
let minY = Number.POSITIVE_INFINITY;
|
|
14019
|
+
let minZ = Number.POSITIVE_INFINITY;
|
|
14020
|
+
let maxX = Number.NEGATIVE_INFINITY;
|
|
14021
|
+
let maxY = Number.NEGATIVE_INFINITY;
|
|
14022
|
+
let maxZ = Number.NEGATIVE_INFINITY;
|
|
14023
|
+
for (let vertex = 0; vertex + 2 < positions.length; vertex += 3) {
|
|
14024
|
+
const x = positions[vertex];
|
|
14025
|
+
const y = positions[vertex + 1];
|
|
14026
|
+
const z = positions[vertex + 2];
|
|
14027
|
+
if (x < minX) minX = x;
|
|
14028
|
+
if (y < minY) minY = y;
|
|
14029
|
+
if (z < minZ) minZ = z;
|
|
14030
|
+
if (x > maxX) maxX = x;
|
|
14031
|
+
if (y > maxY) maxY = y;
|
|
14032
|
+
if (z > maxZ) maxZ = z;
|
|
14033
|
+
}
|
|
14034
|
+
const diagonal = Math.hypot(maxX - minX, maxY - minY, maxZ - minZ);
|
|
14035
|
+
const cellSize = Math.max(diagonal / Math.cbrt(cards.length), 1e-6);
|
|
14036
|
+
const { cells: lod0Cells, placed } = placeCards(cards, cellSize);
|
|
14037
|
+
const lod0Area = cards.reduce((total, card) => total + card.area, 0);
|
|
14038
|
+
const levels = [];
|
|
14039
|
+
const counts = [];
|
|
14040
|
+
const errors = [];
|
|
14041
|
+
const absoluteErrors = [];
|
|
14042
|
+
const buffers = [];
|
|
14043
|
+
let previousTriangles = lod0Triangles;
|
|
14044
|
+
for (const keep of keepRatios.slice(0, Math.max(0, maxLevels - 1))) {
|
|
14045
|
+
if (levels.length >= Math.max(0, maxLevels - 1)) break;
|
|
14046
|
+
const level = thinLevel(placed, keep, lod0Area, lod0Cells);
|
|
14047
|
+
if (level.cards.length === 0 || level.cards.length === cards.length) continue;
|
|
14048
|
+
const buffer = indicesOf(level.cards, indices);
|
|
14049
|
+
const triangles = Math.floor(buffer.length / TRIANGLE);
|
|
14050
|
+
if (triangles >= previousTriangles) continue;
|
|
14051
|
+
if ((previousTriangles - triangles) / previousTriangles < minSaving) continue;
|
|
14052
|
+
const absoluteError = cellSize * (1 - keep);
|
|
14053
|
+
levels.push(level.summary);
|
|
14054
|
+
counts.push(triangles);
|
|
14055
|
+
absoluteErrors.push(absoluteError);
|
|
14056
|
+
errors.push(absoluteError / Math.max(scale3, 1e-6));
|
|
14057
|
+
buffers.push(buffer);
|
|
14058
|
+
previousTriangles = triangles;
|
|
14059
|
+
}
|
|
14060
|
+
if (levels.length === 0) return null;
|
|
14061
|
+
if (terminalTriangles !== void 0 && terminalTriangles < previousTriangles) {
|
|
14062
|
+
const terminal = terminalCardLevel(
|
|
14063
|
+
cards,
|
|
14064
|
+
placed,
|
|
14065
|
+
lod0Cells,
|
|
14066
|
+
lod0Area,
|
|
14067
|
+
cellSize,
|
|
14068
|
+
terminalTriangles,
|
|
14069
|
+
previousTriangles
|
|
14070
|
+
);
|
|
14071
|
+
if (terminal !== null) {
|
|
14072
|
+
const buffer = indicesOf(terminal.cards, indices);
|
|
14073
|
+
const triangles = Math.floor(buffer.length / TRIANGLE);
|
|
14074
|
+
if (triangles > 0 && triangles < previousTriangles) {
|
|
14075
|
+
const absoluteError = cellSize * terminal.factor;
|
|
14076
|
+
levels.push(terminal.summary);
|
|
14077
|
+
counts.push(triangles);
|
|
14078
|
+
absoluteErrors.push(absoluteError);
|
|
14079
|
+
errors.push(absoluteError / Math.max(scale3, 1e-6));
|
|
14080
|
+
buffers.push(buffer);
|
|
14081
|
+
previousTriangles = triangles;
|
|
14082
|
+
}
|
|
14083
|
+
}
|
|
14084
|
+
}
|
|
14085
|
+
return {
|
|
14086
|
+
absoluteErrors,
|
|
14087
|
+
counts,
|
|
14088
|
+
errors,
|
|
14089
|
+
indices: buffers,
|
|
14090
|
+
levels,
|
|
14091
|
+
lod0Cards: cards.length,
|
|
14092
|
+
lod0Triangles
|
|
14093
|
+
};
|
|
14094
|
+
}
|
|
14095
|
+
|
|
13374
14096
|
// src/lod/eligibility.ts
|
|
13375
14097
|
var SUPPORTED_ATTRIBUTES = /* @__PURE__ */ new Set([
|
|
13376
14098
|
"COLOR_0",
|
|
@@ -13380,13 +14102,16 @@ var SUPPORTED_ATTRIBUTES = /* @__PURE__ */ new Set([
|
|
|
13380
14102
|
"TEXCOORD_0",
|
|
13381
14103
|
"TEXCOORD_1"
|
|
13382
14104
|
]);
|
|
14105
|
+
function sharedByLevels(semantic) {
|
|
14106
|
+
return SUPPORTED_ATTRIBUTES.has(semantic) || semantic.startsWith("_");
|
|
14107
|
+
}
|
|
13383
14108
|
var DEFORMING_ATTRIBUTES = /* @__PURE__ */ new Set(["JOINTS_0", "WEIGHTS_0"]);
|
|
13384
14109
|
var TRIANGLES_MODE = 4;
|
|
13385
|
-
var
|
|
14110
|
+
var TRIANGLE2 = 3;
|
|
13386
14111
|
function triangleCount(primitive) {
|
|
13387
14112
|
const position = primitive.getAttribute("POSITION");
|
|
13388
14113
|
const drawn = primitive.getIndices()?.getCount() ?? position?.getCount() ?? 0;
|
|
13389
|
-
return Math.floor(drawn /
|
|
14114
|
+
return Math.floor(drawn / TRIANGLE2);
|
|
13390
14115
|
}
|
|
13391
14116
|
function finitePositions(primitive) {
|
|
13392
14117
|
const position = primitive.getAttribute("POSITION");
|
|
@@ -13405,7 +14130,7 @@ function validIndices(primitive) {
|
|
|
13405
14130
|
const indices = primitive.getIndices();
|
|
13406
14131
|
if (indices === null) return position !== null && position.getCount() > 0;
|
|
13407
14132
|
const array = indices.getArray();
|
|
13408
|
-
if (array === null || array.length === 0 || array.length %
|
|
14133
|
+
if (array === null || array.length === 0 || array.length % TRIANGLE2 !== 0) return false;
|
|
13409
14134
|
const vertices = position?.getCount() ?? 0;
|
|
13410
14135
|
for (let index = 0; index < array.length; index += 1) {
|
|
13411
14136
|
const value = array[index];
|
|
@@ -13415,7 +14140,8 @@ function validIndices(primitive) {
|
|
|
13415
14140
|
}
|
|
13416
14141
|
function unsupportedMaterial(material) {
|
|
13417
14142
|
if (material === null) return false;
|
|
13418
|
-
|
|
14143
|
+
const alphaMode = material.getAlphaMode();
|
|
14144
|
+
if (alphaMode !== "OPAQUE" && alphaMode !== "MASK") return true;
|
|
13419
14145
|
for (const extension of material.listExtensions()) {
|
|
13420
14146
|
const name = extension.extensionName.toLowerCase();
|
|
13421
14147
|
if (name.includes("transmission") || name.includes("displacement") || name.includes("volume"))
|
|
@@ -13461,8 +14187,7 @@ function classifyPrimitive(primitive, flags) {
|
|
|
13461
14187
|
const semantics = primitive.listSemantics();
|
|
13462
14188
|
for (const semantic of semantics) {
|
|
13463
14189
|
if (DEFORMING_ATTRIBUTES.has(semantic)) return { eligible: false, reason: "deforming" };
|
|
13464
|
-
if (!
|
|
13465
|
-
return { eligible: false, reason: "unsupported-attributes" };
|
|
14190
|
+
if (!sharedByLevels(semantic)) return { eligible: false, reason: "unsupported-attributes" };
|
|
13466
14191
|
}
|
|
13467
14192
|
if (!semantics.includes("POSITION")) return { eligible: false, reason: "unsupported-topology" };
|
|
13468
14193
|
if (!finitePositions(primitive) || !validIndices(primitive))
|
|
@@ -13483,8 +14208,7 @@ function classifyJoinCandidate(primitive, flags) {
|
|
|
13483
14208
|
const semantics = primitive.listSemantics();
|
|
13484
14209
|
for (const semantic of semantics) {
|
|
13485
14210
|
if (DEFORMING_ATTRIBUTES.has(semantic)) return { eligible: false, reason: "deforming" };
|
|
13486
|
-
if (!
|
|
13487
|
-
return { eligible: false, reason: "unsupported-attributes" };
|
|
14211
|
+
if (!sharedByLevels(semantic)) return { eligible: false, reason: "unsupported-attributes" };
|
|
13488
14212
|
}
|
|
13489
14213
|
if (!semantics.includes("POSITION")) return { eligible: false, reason: "unsupported-topology" };
|
|
13490
14214
|
if (!finitePositions(primitive) || !validIndices(primitive))
|
|
@@ -13497,12 +14221,18 @@ function authoredLodName(name) {
|
|
|
13497
14221
|
|
|
13498
14222
|
// src/lod/generate.ts
|
|
13499
14223
|
var LOD_GENERATOR = "threenative-discrete-lod";
|
|
13500
|
-
var LOD_GENERATOR_VERSION =
|
|
14224
|
+
var LOD_GENERATOR_VERSION = 3;
|
|
13501
14225
|
var LOD_ARTIFACT_SCHEMA_VERSION = 1;
|
|
13502
14226
|
var LOD_TOOLCHAIN = "meshoptimizer@1.1.1";
|
|
13503
14227
|
var LOD_ERROR_TARGETS = [2e-3, 6e-3, 0.02, 0.06];
|
|
13504
14228
|
var LOD_MIN_SAVING = 0.2;
|
|
13505
14229
|
var DEFAULT_LOD_MAX_LEVELS = 4;
|
|
14230
|
+
var LOD_TERMINAL_MIN_TRIANGLES = 1500;
|
|
14231
|
+
var LOD_TERMINAL_KEEP_RATIO = 0.05;
|
|
14232
|
+
function terminalLevelTriangles(lod0Triangles) {
|
|
14233
|
+
return Math.max(LOD_TERMINAL_MIN_TRIANGLES, Math.ceil(LOD_TERMINAL_KEEP_RATIO * lod0Triangles));
|
|
14234
|
+
}
|
|
14235
|
+
var TERMINAL_TARGET_ERROR = 1e9;
|
|
13506
14236
|
var DEFAULT_LOD_MIN_TRIANGLES = 128;
|
|
13507
14237
|
var DEFAULT_LOD_MIN_TRIANGLES_SCOPE = "asset";
|
|
13508
14238
|
var DEFAULT_LOD_HYSTERESIS = 0.15;
|
|
@@ -13587,8 +14317,10 @@ function resolveLodPolicy(lod, asset, legacy = {}) {
|
|
|
13587
14317
|
}
|
|
13588
14318
|
}
|
|
13589
14319
|
if (!enabled && reasons.length === 0) reasons.push("disabled");
|
|
14320
|
+
const cutout = !globalOff && (assetBlock?.cutout ?? project?.cutout ?? true);
|
|
13590
14321
|
const generationFingerprint = lodFingerprint({
|
|
13591
14322
|
algorithm: `${LOD_GENERATOR}/${String(LOD_GENERATOR_VERSION)}`,
|
|
14323
|
+
cutout,
|
|
13592
14324
|
enabled,
|
|
13593
14325
|
errorTargets: generation.errorTargets,
|
|
13594
14326
|
join: generation.join,
|
|
@@ -13600,6 +14332,7 @@ function resolveLodPolicy(lod, asset, legacy = {}) {
|
|
|
13600
14332
|
toolchain: LOD_TOOLCHAIN
|
|
13601
14333
|
});
|
|
13602
14334
|
return {
|
|
14335
|
+
cutout,
|
|
13603
14336
|
diagnostics,
|
|
13604
14337
|
enabled,
|
|
13605
14338
|
fingerprint: {
|
|
@@ -13669,7 +14402,7 @@ function sourceIndices(primitive, vertexCount) {
|
|
|
13669
14402
|
return Uint32Array.from(indices.getArray());
|
|
13670
14403
|
}
|
|
13671
14404
|
function packAttributes(primitive, vertexCount) {
|
|
13672
|
-
const semantics = primitive.listSemantics().filter((semantic) => semantic !== "POSITION");
|
|
14405
|
+
const semantics = primitive.listSemantics().filter((semantic) => semantic !== "POSITION" && !semantic.startsWith("_"));
|
|
13673
14406
|
const accessors = semantics.map((semantic) => ({ accessor: primitive.getAttribute(semantic), semantic })).filter(
|
|
13674
14407
|
(entry) => entry.accessor !== null
|
|
13675
14408
|
);
|
|
@@ -13725,6 +14458,16 @@ async function generateChain(primitive, maxLevels, errorTargets, minSaving) {
|
|
|
13725
14458
|
if (kept.length === 0) return null;
|
|
13726
14459
|
const keptSet = new Set(kept);
|
|
13727
14460
|
const chain = candidates.filter((candidate) => keptSet.has(candidate));
|
|
14461
|
+
const previous = chain[chain.length - 1];
|
|
14462
|
+
const terminal = terminalLevel(
|
|
14463
|
+
positions,
|
|
14464
|
+
indices,
|
|
14465
|
+
attributes,
|
|
14466
|
+
terminalLevelTriangles(lod0Triangles),
|
|
14467
|
+
previous.triangles,
|
|
14468
|
+
previous.error
|
|
14469
|
+
);
|
|
14470
|
+
if (terminal !== null) chain.push(terminal);
|
|
13728
14471
|
return {
|
|
13729
14472
|
absoluteErrors: chain.map((level) => level.error * scale3),
|
|
13730
14473
|
baselineTriangles: lod0Triangles,
|
|
@@ -13735,6 +14478,158 @@ async function generateChain(primitive, maxLevels, errorTargets, minSaving) {
|
|
|
13735
14478
|
lod0Triangles
|
|
13736
14479
|
};
|
|
13737
14480
|
}
|
|
14481
|
+
function weldMesh(positions, indices, attributes) {
|
|
14482
|
+
const vertexCount = Math.floor(positions.length / 3);
|
|
14483
|
+
const seen = /* @__PURE__ */ new Map();
|
|
14484
|
+
const remap = new Uint32Array(vertexCount);
|
|
14485
|
+
const origin = [];
|
|
14486
|
+
for (let vertex = 0; vertex < vertexCount; vertex += 1) {
|
|
14487
|
+
const key = `${positions[vertex * 3]},${positions[vertex * 3 + 1]},${positions[vertex * 3 + 2]}`;
|
|
14488
|
+
const compact = seen.get(key);
|
|
14489
|
+
if (compact === void 0) {
|
|
14490
|
+
seen.set(key, origin.length);
|
|
14491
|
+
remap[vertex] = origin.length;
|
|
14492
|
+
origin.push(vertex);
|
|
14493
|
+
} else remap[vertex] = compact;
|
|
14494
|
+
}
|
|
14495
|
+
const compactPositions = new Float32Array(origin.length * 3);
|
|
14496
|
+
const compactAttributes = new Float32Array(origin.length * attributes.stride);
|
|
14497
|
+
for (let compact = 0; compact < origin.length; compact += 1) {
|
|
14498
|
+
const source = origin[compact];
|
|
14499
|
+
for (let axis = 0; axis < 3; axis += 1)
|
|
14500
|
+
compactPositions[compact * 3 + axis] = positions[source * 3 + axis];
|
|
14501
|
+
for (let slot = 0; slot < attributes.stride; slot += 1)
|
|
14502
|
+
compactAttributes[compact * attributes.stride + slot] = attributes.data[source * attributes.stride + slot];
|
|
14503
|
+
}
|
|
14504
|
+
const compactIndices = new Uint32Array(indices.length);
|
|
14505
|
+
for (let index = 0; index < indices.length; index += 1)
|
|
14506
|
+
compactIndices[index] = remap[indices[index]];
|
|
14507
|
+
return {
|
|
14508
|
+
attributes: compactAttributes,
|
|
14509
|
+
indices: compactIndices,
|
|
14510
|
+
origin: Uint32Array.from(origin),
|
|
14511
|
+
positions: compactPositions
|
|
14512
|
+
};
|
|
14513
|
+
}
|
|
14514
|
+
function terminalLevel(positions, indices, attributes, terminalTriangles, previousTriangles, previousError) {
|
|
14515
|
+
if (terminalTriangles >= previousTriangles) return null;
|
|
14516
|
+
const target = terminalTriangles * 3;
|
|
14517
|
+
if (attributes.stride > 0) {
|
|
14518
|
+
const [pruned, pruneError] = MeshoptSimplifier.simplifyWithAttributes(
|
|
14519
|
+
indices,
|
|
14520
|
+
positions,
|
|
14521
|
+
3,
|
|
14522
|
+
attributes.data,
|
|
14523
|
+
attributes.stride,
|
|
14524
|
+
attributes.weights,
|
|
14525
|
+
null,
|
|
14526
|
+
target,
|
|
14527
|
+
TERMINAL_TARGET_ERROR,
|
|
14528
|
+
["Prune"]
|
|
14529
|
+
);
|
|
14530
|
+
const triangles2 = Math.floor(pruned.length / 3);
|
|
14531
|
+
if (triangles2 > 0 && triangles2 <= terminalTriangles && triangles2 < previousTriangles)
|
|
14532
|
+
return {
|
|
14533
|
+
error: Math.max(pruneError, previousError),
|
|
14534
|
+
indices: Uint32Array.from(pruned),
|
|
14535
|
+
triangles: triangles2
|
|
14536
|
+
};
|
|
14537
|
+
}
|
|
14538
|
+
const welded = weldMesh(positions, indices, attributes);
|
|
14539
|
+
let compacted;
|
|
14540
|
+
let weldedError;
|
|
14541
|
+
if (attributes.stride === 0) {
|
|
14542
|
+
[compacted, weldedError] = MeshoptSimplifier.simplify(
|
|
14543
|
+
welded.indices,
|
|
14544
|
+
welded.positions,
|
|
14545
|
+
3,
|
|
14546
|
+
target,
|
|
14547
|
+
TERMINAL_TARGET_ERROR,
|
|
14548
|
+
[]
|
|
14549
|
+
);
|
|
14550
|
+
} else {
|
|
14551
|
+
[compacted, weldedError] = MeshoptSimplifier.simplifyWithAttributes(
|
|
14552
|
+
welded.indices,
|
|
14553
|
+
welded.positions,
|
|
14554
|
+
3,
|
|
14555
|
+
welded.attributes,
|
|
14556
|
+
attributes.stride,
|
|
14557
|
+
new Array(attributes.stride).fill(0),
|
|
14558
|
+
null,
|
|
14559
|
+
target,
|
|
14560
|
+
TERMINAL_TARGET_ERROR,
|
|
14561
|
+
[]
|
|
14562
|
+
);
|
|
14563
|
+
}
|
|
14564
|
+
const triangles = Math.floor(compacted.length / 3);
|
|
14565
|
+
if (triangles <= 0 || triangles >= previousTriangles) return null;
|
|
14566
|
+
const mapped = new Uint32Array(compacted.length);
|
|
14567
|
+
for (let index = 0; index < compacted.length; index += 1)
|
|
14568
|
+
mapped[index] = welded.origin[compacted[index]];
|
|
14569
|
+
return {
|
|
14570
|
+
error: Math.max(weldedError, previousError),
|
|
14571
|
+
indices: mapped,
|
|
14572
|
+
triangles
|
|
14573
|
+
};
|
|
14574
|
+
}
|
|
14575
|
+
function generateTerminalChain(primitive) {
|
|
14576
|
+
const position = primitive.getAttribute("POSITION");
|
|
14577
|
+
if (position === null) return null;
|
|
14578
|
+
const positions = positionsOf(primitive);
|
|
14579
|
+
if (positions === null) return null;
|
|
14580
|
+
const vertexCount = position.getCount();
|
|
14581
|
+
const indices = sourceIndices(primitive, vertexCount);
|
|
14582
|
+
const lod0Triangles = Math.floor(indices.length / 3);
|
|
14583
|
+
if (lod0Triangles <= 0) return null;
|
|
14584
|
+
const scale3 = MeshoptSimplifier.getScale(positions, 3);
|
|
14585
|
+
const attributes = packAttributes(primitive, vertexCount);
|
|
14586
|
+
const terminal = terminalLevel(
|
|
14587
|
+
positions,
|
|
14588
|
+
indices,
|
|
14589
|
+
attributes,
|
|
14590
|
+
terminalLevelTriangles(lod0Triangles),
|
|
14591
|
+
lod0Triangles,
|
|
14592
|
+
0
|
|
14593
|
+
);
|
|
14594
|
+
if (terminal === null) return null;
|
|
14595
|
+
return {
|
|
14596
|
+
absoluteErrors: [terminal.error * scale3],
|
|
14597
|
+
baselineTriangles: lod0Triangles,
|
|
14598
|
+
counts: [terminal.triangles],
|
|
14599
|
+
errorScale: scale3,
|
|
14600
|
+
errors: [terminal.error],
|
|
14601
|
+
indices: [terminal.indices],
|
|
14602
|
+
lod0Triangles
|
|
14603
|
+
};
|
|
14604
|
+
}
|
|
14605
|
+
function generateCardsChain(primitive, maxLevels, minSaving) {
|
|
14606
|
+
if (primitive.getMaterial()?.getAlphaMode() !== "MASK") return null;
|
|
14607
|
+
const positions = positionsOf(primitive);
|
|
14608
|
+
if (positions === null) return null;
|
|
14609
|
+
const vertexCount = Math.floor(positions.length / 3);
|
|
14610
|
+
const indices = sourceIndices(primitive, vertexCount);
|
|
14611
|
+
const scale3 = MeshoptSimplifier.getScale(positions, 3);
|
|
14612
|
+
const chain = generateCardChain(
|
|
14613
|
+
positions,
|
|
14614
|
+
indices,
|
|
14615
|
+
maxLevels,
|
|
14616
|
+
minSaving,
|
|
14617
|
+
scale3,
|
|
14618
|
+
void 0,
|
|
14619
|
+
terminalLevelTriangles(Math.floor(indices.length / 3))
|
|
14620
|
+
);
|
|
14621
|
+
if (chain === null) return null;
|
|
14622
|
+
return {
|
|
14623
|
+
absoluteErrors: [...chain.absoluteErrors],
|
|
14624
|
+
baselineTriangles: chain.lod0Triangles,
|
|
14625
|
+
cardLevels: chain.levels,
|
|
14626
|
+
counts: [...chain.counts],
|
|
14627
|
+
errorScale: scale3,
|
|
14628
|
+
errors: [...chain.errors],
|
|
14629
|
+
indices: [...chain.indices],
|
|
14630
|
+
lod0Triangles: chain.lod0Triangles
|
|
14631
|
+
};
|
|
14632
|
+
}
|
|
13738
14633
|
function reachableNodes(document) {
|
|
13739
14634
|
const visited = /* @__PURE__ */ new Set();
|
|
13740
14635
|
const nodes = [];
|
|
@@ -13770,9 +14665,10 @@ function meshFlags(document, animated) {
|
|
|
13770
14665
|
}
|
|
13771
14666
|
return flags;
|
|
13772
14667
|
}
|
|
13773
|
-
function emptySummary(policy, generatedSeconds) {
|
|
14668
|
+
function emptySummary(policy, generatedSeconds, cutout = { converted: [], kept: [] }) {
|
|
13774
14669
|
return {
|
|
13775
14670
|
byteOverhead: 0,
|
|
14671
|
+
cutout,
|
|
13776
14672
|
diagnostics: [...policy.diagnostics],
|
|
13777
14673
|
enabled: policy.enabled,
|
|
13778
14674
|
errorTargets: [...policy.generation.errorTargets],
|
|
@@ -13856,6 +14752,10 @@ async function buildJoinedRungs(document, policy, reason) {
|
|
|
13856
14752
|
for (const node of nodes) {
|
|
13857
14753
|
const mesh = node.getMesh();
|
|
13858
14754
|
if (mesh === null || hasMeshBelow.has(node)) continue;
|
|
14755
|
+
if (node.getExtension("EXT_mesh_gpu_instancing") !== null) {
|
|
14756
|
+
reason("boundary-unsafe");
|
|
14757
|
+
continue;
|
|
14758
|
+
}
|
|
13859
14759
|
if ((meshRefs.get(mesh) ?? 0) > 1) {
|
|
13860
14760
|
reason("boundary-unsafe");
|
|
13861
14761
|
continue;
|
|
@@ -14006,10 +14906,11 @@ async function generateDiscreteLod(document, lod, logicalPath, sourceDigest, leg
|
|
|
14006
14906
|
const started = now();
|
|
14007
14907
|
const policy = resolveLodPolicy(lod, logicalPath, legacy);
|
|
14008
14908
|
const fingerprint = policy.fingerprint.generation;
|
|
14009
|
-
|
|
14909
|
+
const cutout = policy.cutout ? convertFoliageCutout(document) : { converted: [], kept: [] };
|
|
14910
|
+
if (!policy.enabled) return emptySummary(policy, (now() - started) / 1e3, cutout);
|
|
14010
14911
|
const joinRequested = policy.generation.join;
|
|
14011
14912
|
const discrete = policy.generation.maxLevels > 1;
|
|
14012
|
-
if (!discrete && !joinRequested) return emptySummary(policy, (now() - started) / 1e3);
|
|
14913
|
+
if (!discrete && !joinRequested) return emptySummary(policy, (now() - started) / 1e3, cutout);
|
|
14013
14914
|
await MeshoptSimplifier.ready;
|
|
14014
14915
|
const animated = animatedNodes(document);
|
|
14015
14916
|
const flags = meshFlags(document, animated);
|
|
@@ -14044,12 +14945,19 @@ async function generateDiscreteLod(document, lod, logicalPath, sourceDigest, leg
|
|
|
14044
14945
|
}
|
|
14045
14946
|
const before = triangleCount(primitive);
|
|
14046
14947
|
trianglesBefore += before;
|
|
14047
|
-
|
|
14948
|
+
let chain = await generateChain(
|
|
14048
14949
|
primitive,
|
|
14049
14950
|
policy.generation.maxLevels,
|
|
14050
14951
|
policy.generation.errorTargets,
|
|
14051
14952
|
policy.generation.minSaving
|
|
14052
14953
|
);
|
|
14954
|
+
const thinned = chain === null ? generateCardsChain(primitive, policy.generation.maxLevels, policy.generation.minSaving) : null;
|
|
14955
|
+
chain ??= thinned;
|
|
14956
|
+
let strategy = thinned === null ? "discrete" : "cards";
|
|
14957
|
+
if (chain === null) {
|
|
14958
|
+
chain = generateTerminalChain(primitive);
|
|
14959
|
+
strategy = "discrete";
|
|
14960
|
+
}
|
|
14053
14961
|
if (chain === null) {
|
|
14054
14962
|
reasons.add("insufficient-reduction");
|
|
14055
14963
|
skipped.push({
|
|
@@ -14061,7 +14969,10 @@ async function generateDiscreteLod(document, lod, logicalPath, sourceDigest, leg
|
|
|
14061
14969
|
continue;
|
|
14062
14970
|
}
|
|
14063
14971
|
extension ??= document.createExtension(TNDiscreteLod).setRequired(false);
|
|
14064
|
-
const property = attachDiscreteLod(document, extension, primitive,
|
|
14972
|
+
const property = attachDiscreteLod(document, extension, primitive, {
|
|
14973
|
+
...chain,
|
|
14974
|
+
strategy
|
|
14975
|
+
});
|
|
14065
14976
|
byteOverhead += discreteLodBytes(property);
|
|
14066
14977
|
levels = Math.max(levels, chain.counts.length);
|
|
14067
14978
|
const finest = chain.counts[chain.counts.length - 1] ?? before;
|
|
@@ -14074,7 +14985,8 @@ async function generateDiscreteLod(document, lod, logicalPath, sourceDigest, leg
|
|
|
14074
14985
|
})),
|
|
14075
14986
|
mesh: mesh.getName(),
|
|
14076
14987
|
primitive: primitiveIndex,
|
|
14077
|
-
|
|
14988
|
+
...chain.cardLevels === void 0 ? {} : { cardLevels: chain.cardLevels },
|
|
14989
|
+
strategy,
|
|
14078
14990
|
trianglesBefore: before
|
|
14079
14991
|
});
|
|
14080
14992
|
}
|
|
@@ -14092,6 +15004,7 @@ async function generateDiscreteLod(document, lod, logicalPath, sourceDigest, leg
|
|
|
14092
15004
|
extension?.setMetadata(metadata);
|
|
14093
15005
|
return {
|
|
14094
15006
|
byteOverhead,
|
|
15007
|
+
cutout,
|
|
14095
15008
|
diagnostics: [...policy.diagnostics],
|
|
14096
15009
|
enabled: true,
|
|
14097
15010
|
errorTargets: [...policy.generation.errorTargets],
|
|
@@ -14735,6 +15648,7 @@ ${result.stderr.trim().split("\n").slice(-12).join("\n")}`;
|
|
|
14735
15648
|
}
|
|
14736
15649
|
function blenderImportPass(options = {}) {
|
|
14737
15650
|
return {
|
|
15651
|
+
appliesTo: ["model"],
|
|
14738
15652
|
configuration: { extensions: [...BLENDER_SOURCE_EXTENSIONS].sort() },
|
|
14739
15653
|
name: BLENDER_IMPORT_PASS,
|
|
14740
15654
|
async apply(input, logicalPath) {
|
|
@@ -15300,8 +16214,8 @@ async function BASIS(moduleArg = {}) {
|
|
|
15300
16214
|
async function instantiateArrayBuffer(binaryFile, imports) {
|
|
15301
16215
|
try {
|
|
15302
16216
|
var binary = await getWasmBinary(binaryFile);
|
|
15303
|
-
var
|
|
15304
|
-
return
|
|
16217
|
+
var instance2 = await WebAssembly.instantiate(binary, imports);
|
|
16218
|
+
return instance2;
|
|
15305
16219
|
} catch (reason) {
|
|
15306
16220
|
err(`failed to asynchronously prepare wasm: ${reason}`);
|
|
15307
16221
|
abort(reason);
|
|
@@ -15324,8 +16238,8 @@ async function BASIS(moduleArg = {}) {
|
|
|
15324
16238
|
return { a: wasmImports };
|
|
15325
16239
|
}
|
|
15326
16240
|
async function createWasm() {
|
|
15327
|
-
function receiveInstance(
|
|
15328
|
-
wasmExports =
|
|
16241
|
+
function receiveInstance(instance2, module) {
|
|
16242
|
+
wasmExports = instance2.exports;
|
|
15329
16243
|
wasmMemory = wasmExports["_"];
|
|
15330
16244
|
updateMemoryViews();
|
|
15331
16245
|
wasmTable = wasmExports["ca"];
|
|
@@ -19370,6 +20284,7 @@ async function compressEmbeddedTextures(document, logicalPath, options = {}, rec
|
|
|
19370
20284
|
const root = document.getRoot();
|
|
19371
20285
|
const textures = root.listTextures();
|
|
19372
20286
|
if (textures.length === 0) return void 0;
|
|
20287
|
+
const decoderFree = options.decoderFree === true;
|
|
19373
20288
|
const maxSize = options.maxSize ?? DEFAULT_MAX_SIZE;
|
|
19374
20289
|
const quality = Math.min(255, Math.max(1, Math.round(options.quality ?? DEFAULT_ETC1S_QUALITY)));
|
|
19375
20290
|
const keys = textureKeys(root);
|
|
@@ -19389,6 +20304,16 @@ async function compressEmbeddedTextures(document, logicalPath, options = {}, rec
|
|
|
19389
20304
|
`TN_ASSETS_MODEL_TEXTURE_MISSING: '${logicalPath}' declares texture '${key}' with no image data.`
|
|
19390
20305
|
);
|
|
19391
20306
|
}
|
|
20307
|
+
const recalledFree = decoderFree ? recalled.get(index) : void 0;
|
|
20308
|
+
if (recalledFree !== void 0) {
|
|
20309
|
+
const shape = imageShape(image, texture.getMimeType());
|
|
20310
|
+
bytesBefore += recalledFree.sourceBytes;
|
|
20311
|
+
bytesAfter += image.byteLength;
|
|
20312
|
+
gpuBytesBefore += gpuBytes(shape.width, shape.height, "none");
|
|
20313
|
+
gpuBytesAfter += gpuBytes(shape.width, shape.height, "none");
|
|
20314
|
+
formats[key] = "none";
|
|
20315
|
+
continue;
|
|
20316
|
+
}
|
|
19392
20317
|
if (texture.getMimeType() === "image/ktx2") {
|
|
19393
20318
|
const shape = imageShape(image, "image/ktx2");
|
|
19394
20319
|
const fromStore = recalled.get(index);
|
|
@@ -19430,6 +20355,32 @@ async function compressEmbeddedTextures(document, logicalPath, options = {}, rec
|
|
|
19430
20355
|
formats[key] = "none";
|
|
19431
20356
|
continue;
|
|
19432
20357
|
}
|
|
20358
|
+
if (decoderFree) {
|
|
20359
|
+
const target2 = cappedSize(decoded.width, decoded.height, maxSize);
|
|
20360
|
+
if (target2.width === decoded.width && target2.height === decoded.height) {
|
|
20361
|
+
bytesAfter += image.byteLength;
|
|
20362
|
+
gpuBytesAfter += gpuBytes(decoded.width, decoded.height, "none");
|
|
20363
|
+
formats[key] = "none";
|
|
20364
|
+
continue;
|
|
20365
|
+
}
|
|
20366
|
+
const data2 = resampleRgba(
|
|
20367
|
+
decoded.data,
|
|
20368
|
+
decoded.width,
|
|
20369
|
+
decoded.height,
|
|
20370
|
+
target2.width,
|
|
20371
|
+
target2.height,
|
|
20372
|
+
getTextureColorSpace(texture) === "srgb"
|
|
20373
|
+
);
|
|
20374
|
+
const png = new PNG({ height: target2.height, width: target2.width });
|
|
20375
|
+
png.data = Buffer.from(data2);
|
|
20376
|
+
const encoded2 = PNG.sync.write(png);
|
|
20377
|
+
texture.setImage(encoded2).setMimeType("image/png");
|
|
20378
|
+
bytesAfter += encoded2.byteLength;
|
|
20379
|
+
gpuBytesAfter += gpuBytes(target2.width, target2.height, "none");
|
|
20380
|
+
formats[key] = "none";
|
|
20381
|
+
resized += 1;
|
|
20382
|
+
continue;
|
|
20383
|
+
}
|
|
19433
20384
|
const target = cappedSize(decoded.width, decoded.height, maxSize);
|
|
19434
20385
|
const srgb = getTextureColorSpace(texture) === "srgb";
|
|
19435
20386
|
let data = decoded.data;
|
|
@@ -19525,6 +20476,7 @@ async function encodeLinearRgbaKtx2(data, width, height) {
|
|
|
19525
20476
|
}
|
|
19526
20477
|
function texturePass(options = {}) {
|
|
19527
20478
|
return {
|
|
20479
|
+
appliesTo: ["texture"],
|
|
19528
20480
|
configuration: {
|
|
19529
20481
|
encoder: KTX2_ENCODER_VERSION,
|
|
19530
20482
|
keepSmallerSource: true,
|
|
@@ -19577,6 +20529,65 @@ function texturePass(options = {}) {
|
|
|
19577
20529
|
name: "ktx2"
|
|
19578
20530
|
};
|
|
19579
20531
|
}
|
|
20532
|
+
function textureResizePass(options) {
|
|
20533
|
+
const { maxSize } = options;
|
|
20534
|
+
return {
|
|
20535
|
+
appliesTo: ["texture"],
|
|
20536
|
+
// Part of the compile cache key: a different cap — or a different set of excluded globs — must
|
|
20537
|
+
// not re-serve the previous output.
|
|
20538
|
+
configuration: { maxSize, overrides: options.overrides ?? [], resample: "png" },
|
|
20539
|
+
name: "texture-resize",
|
|
20540
|
+
apply: async (input, logicalPath) => {
|
|
20541
|
+
if (classify2(logicalPath) !== "texture") return input;
|
|
20542
|
+
if (matchingOverride(logicalPath, options.overrides)?.codec === "none") return input;
|
|
20543
|
+
const stats = textureStats(input);
|
|
20544
|
+
let decoded;
|
|
20545
|
+
if (stats.width <= 0 || stats.height <= 0) {
|
|
20546
|
+
if (!isPngOrJpeg(input)) return input;
|
|
20547
|
+
decoded = await decodeForResize(input, logicalPath);
|
|
20548
|
+
}
|
|
20549
|
+
const width = decoded?.width ?? stats.width;
|
|
20550
|
+
const height = decoded?.height ?? stats.height;
|
|
20551
|
+
const target = cappedSize(width, height, maxSize);
|
|
20552
|
+
if (target.width === width && target.height === height) return input;
|
|
20553
|
+
const source = decoded ?? await decodeForResize(input, logicalPath);
|
|
20554
|
+
const data = resampleRgba(
|
|
20555
|
+
source.data,
|
|
20556
|
+
source.width,
|
|
20557
|
+
source.height,
|
|
20558
|
+
target.width,
|
|
20559
|
+
target.height,
|
|
20560
|
+
!NORMAL_MAP_BASENAME.test(baseNameOf(logicalPath))
|
|
20561
|
+
);
|
|
20562
|
+
const png = new PNG({ height: target.height, width: target.width });
|
|
20563
|
+
png.data = Buffer.from(data);
|
|
20564
|
+
return {
|
|
20565
|
+
buffer: PNG.sync.write(png),
|
|
20566
|
+
entry: { resizedFrom: `${String(width)}x${String(height)}` },
|
|
20567
|
+
// Emitted as PNG whatever the source extension was: the manifest records the logical
|
|
20568
|
+
// path unchanged and the served output name carries the bytes' real container, so a
|
|
20569
|
+
// `.jpg` logical path resolves to a `.png` output the loader sniffs by content.
|
|
20570
|
+
outputExtension: ".png"
|
|
20571
|
+
};
|
|
20572
|
+
}
|
|
20573
|
+
};
|
|
20574
|
+
}
|
|
20575
|
+
function isPngOrJpeg(bytes) {
|
|
20576
|
+
if (bytes.length >= 3 && bytes[0] === 255 && bytes[1] === 216 && bytes[2] === 255) {
|
|
20577
|
+
return true;
|
|
20578
|
+
}
|
|
20579
|
+
return bytes.length >= 8 && bytes[0] === 137 && bytes[1] === 80 && bytes[2] === 78 && bytes[3] === 71 && bytes[4] === 13 && bytes[5] === 10 && bytes[6] === 26 && bytes[7] === 10;
|
|
20580
|
+
}
|
|
20581
|
+
async function decodeForResize(input, logicalPath) {
|
|
20582
|
+
try {
|
|
20583
|
+
return await decodeImageBytes(input, logicalPath);
|
|
20584
|
+
} catch (error) {
|
|
20585
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
20586
|
+
throw new Error(
|
|
20587
|
+
`TN_ASSETS_TEXTURE_UNDECODABLE: '${logicalPath}' could not be decoded to apply its size cap: ${detail}`
|
|
20588
|
+
);
|
|
20589
|
+
}
|
|
20590
|
+
}
|
|
19580
20591
|
function resizeForEncoding(decoded, normalMap, maxSize) {
|
|
19581
20592
|
const target = maxSize === void 0 ? { height: decoded.height, width: decoded.width } : cappedSize(decoded.width, decoded.height, Math.floor(maxSize / 4) * 4);
|
|
19582
20593
|
const resized = target.width !== decoded.width || target.height !== decoded.height;
|
|
@@ -19610,8 +20621,8 @@ function rgbaHasAlpha2(rgba) {
|
|
|
19610
20621
|
function chooseCodec2(logicalPath, alpha, options) {
|
|
19611
20622
|
const normalMap = NORMAL_MAP_BASENAME.test(baseNameOf(logicalPath));
|
|
19612
20623
|
const fallbackQuality = options.quality ?? DEFAULT_ETC1S_QUALITY2;
|
|
19613
|
-
|
|
19614
|
-
|
|
20624
|
+
const override = matchingOverride(logicalPath, options.overrides);
|
|
20625
|
+
if (override !== void 0) {
|
|
19615
20626
|
return {
|
|
19616
20627
|
codec: override.codec,
|
|
19617
20628
|
explicit: true,
|
|
@@ -19626,6 +20637,9 @@ function chooseCodec2(logicalPath, alpha, options) {
|
|
|
19626
20637
|
quality: clampQuality(fallbackQuality)
|
|
19627
20638
|
};
|
|
19628
20639
|
}
|
|
20640
|
+
function matchingOverride(logicalPath, overrides) {
|
|
20641
|
+
return overrides?.find((override) => globMatch(override.glob, logicalPath));
|
|
20642
|
+
}
|
|
19629
20643
|
function encodeSettingsFor(choice) {
|
|
19630
20644
|
if (choice.codec === "none") return {};
|
|
19631
20645
|
if (choice.codec === "uastc") {
|
|
@@ -19711,7 +20725,7 @@ function chartCount(atlas) {
|
|
|
19711
20725
|
parent.set(value, root);
|
|
19712
20726
|
return root;
|
|
19713
20727
|
};
|
|
19714
|
-
const
|
|
20728
|
+
const join2 = (left, right) => {
|
|
19715
20729
|
const leftRoot = rootOf(left);
|
|
19716
20730
|
const rightRoot = rootOf(right);
|
|
19717
20731
|
parent.set(leftRoot, rightRoot);
|
|
@@ -19724,8 +20738,8 @@ function chartCount(atlas) {
|
|
|
19724
20738
|
parent.set(first, parent.get(first) ?? first);
|
|
19725
20739
|
parent.set(second, parent.get(second) ?? second);
|
|
19726
20740
|
parent.set(third, parent.get(third) ?? third);
|
|
19727
|
-
|
|
19728
|
-
|
|
20741
|
+
join2(first, second);
|
|
20742
|
+
join2(second, third);
|
|
19729
20743
|
}
|
|
19730
20744
|
charts += new Set([...parent.keys()].map(rootOf)).size;
|
|
19731
20745
|
}
|
|
@@ -19848,6 +20862,7 @@ function lightmapPass(options) {
|
|
|
19848
20862
|
const atlasSize = positiveInteger(options.atlasSize, "atlasSize");
|
|
19849
20863
|
const padding = positiveInteger(options.padding, "padding");
|
|
19850
20864
|
return {
|
|
20865
|
+
appliesTo: ["model"],
|
|
19851
20866
|
cacheKey: JSON.stringify({ atlasSize, padding }),
|
|
19852
20867
|
name: "lightmap-uv2",
|
|
19853
20868
|
async apply(input, logicalPath) {
|
|
@@ -20347,7 +21362,7 @@ async function bakeVirtualGeometry(document, options, now = () => Date.now()) {
|
|
|
20347
21362
|
for (const primitive of mesh.listPrimitives()) {
|
|
20348
21363
|
const indices = primitive.getIndices();
|
|
20349
21364
|
const positions = positionsOf2(primitive);
|
|
20350
|
-
if (indices === null || positions === null || primitive.getMode() !== 4) {
|
|
21365
|
+
if (indices === null || positions === null || primitive.getMode() !== 4 || primitive.getAttribute("JOINTS_0") !== null) {
|
|
20351
21366
|
skipped += 1;
|
|
20352
21367
|
continue;
|
|
20353
21368
|
}
|
|
@@ -20379,6 +21394,454 @@ async function bakeVirtualGeometry(document, options, now = () => Date.now()) {
|
|
|
20379
21394
|
stopReason
|
|
20380
21395
|
};
|
|
20381
21396
|
}
|
|
21397
|
+
var DEFAULT_PROTECTED_PATTERN = "propeller|aileron|rudder|flap|elevator|gear|wheel|canopy|hook|crew|gunner|threenativepivot|torpedo|wingport|wingstarboard|cockpit controls";
|
|
21398
|
+
var EXT_MESH_GPU_INSTANCING2 = "EXT_mesh_gpu_instancing";
|
|
21399
|
+
function asBatch(node) {
|
|
21400
|
+
return node.getExtension(EXT_MESH_GPU_INSTANCING2);
|
|
21401
|
+
}
|
|
21402
|
+
function sceneNodes(root) {
|
|
21403
|
+
const nodes = [];
|
|
21404
|
+
const seen = /* @__PURE__ */ new Set();
|
|
21405
|
+
for (const scene of root.listScenes()) {
|
|
21406
|
+
scene.traverse((node) => {
|
|
21407
|
+
if (seen.has(node)) return;
|
|
21408
|
+
seen.add(node);
|
|
21409
|
+
nodes.push(node);
|
|
21410
|
+
});
|
|
21411
|
+
}
|
|
21412
|
+
return nodes;
|
|
21413
|
+
}
|
|
21414
|
+
function countPrimitives(nodes) {
|
|
21415
|
+
let primitives = 0;
|
|
21416
|
+
for (const node of nodes) {
|
|
21417
|
+
const mesh = node.getMesh();
|
|
21418
|
+
if (mesh !== null) primitives += mesh.listPrimitives().length;
|
|
21419
|
+
}
|
|
21420
|
+
return primitives;
|
|
21421
|
+
}
|
|
21422
|
+
function countCompactNodes(document) {
|
|
21423
|
+
return sceneNodes(document.getRoot()).length;
|
|
21424
|
+
}
|
|
21425
|
+
function countCompactPrimitives(document) {
|
|
21426
|
+
return countPrimitives(sceneNodes(document.getRoot()));
|
|
21427
|
+
}
|
|
21428
|
+
function sceneNodeNames(document) {
|
|
21429
|
+
return sceneNodes(document.getRoot()).filter((node) => node.getMesh() !== null || node.listChildren().length > 0).map((node) => node.getName()).filter((name) => name !== "").sort();
|
|
21430
|
+
}
|
|
21431
|
+
function removedNames(before, after) {
|
|
21432
|
+
const counts = /* @__PURE__ */ new Map();
|
|
21433
|
+
for (const name of after) counts.set(name, (counts.get(name) ?? 0) + 1);
|
|
21434
|
+
const removed = [];
|
|
21435
|
+
for (const name of before) {
|
|
21436
|
+
const remaining = counts.get(name) ?? 0;
|
|
21437
|
+
if (remaining > 0) counts.set(name, remaining - 1);
|
|
21438
|
+
else removed.push(name);
|
|
21439
|
+
}
|
|
21440
|
+
return removed.sort();
|
|
21441
|
+
}
|
|
21442
|
+
function buildProtectedSet(document, options = {}) {
|
|
21443
|
+
const root = document.getRoot();
|
|
21444
|
+
const rules = /* @__PURE__ */ new Map();
|
|
21445
|
+
const claim = (node, rule) => {
|
|
21446
|
+
if (!rules.has(node)) rules.set(node, rule);
|
|
21447
|
+
};
|
|
21448
|
+
const allowList = new Set(options.protectedNames ?? []);
|
|
21449
|
+
for (const node of sceneNodes(root)) {
|
|
21450
|
+
const name = node.getName();
|
|
21451
|
+
if (name !== "" && allowList.has(name)) claim(node, "allow-list");
|
|
21452
|
+
}
|
|
21453
|
+
for (const skin of root.listSkins()) {
|
|
21454
|
+
for (const joint of skin.listJoints()) claim(joint, "skin-joint");
|
|
21455
|
+
}
|
|
21456
|
+
for (const animation of root.listAnimations()) {
|
|
21457
|
+
for (const channel of animation.listChannels()) {
|
|
21458
|
+
const target = channel.getTargetNode();
|
|
21459
|
+
if (target !== null) claim(target, "animation-target");
|
|
21460
|
+
}
|
|
21461
|
+
}
|
|
21462
|
+
const pattern = new RegExp(options.protectedPattern ?? DEFAULT_PROTECTED_PATTERN, "iu");
|
|
21463
|
+
for (const node of sceneNodes(root)) {
|
|
21464
|
+
const name = node.getName();
|
|
21465
|
+
if (name !== "" && pattern.test(name)) claim(node, "regex");
|
|
21466
|
+
}
|
|
21467
|
+
for (const animation of root.listAnimations()) {
|
|
21468
|
+
for (const channel of animation.listChannels()) {
|
|
21469
|
+
const target = channel.getTargetNode();
|
|
21470
|
+
if (target === null) continue;
|
|
21471
|
+
let ancestor = target.getParentNode();
|
|
21472
|
+
while (ancestor !== null) {
|
|
21473
|
+
claim(ancestor, "animation-ancestor");
|
|
21474
|
+
ancestor = ancestor.getParentNode();
|
|
21475
|
+
}
|
|
21476
|
+
}
|
|
21477
|
+
}
|
|
21478
|
+
const summary = [];
|
|
21479
|
+
for (const [node, rule] of rules) {
|
|
21480
|
+
summary.push({ name: node.getName() === "" ? "(unnamed)" : node.getName(), rule });
|
|
21481
|
+
}
|
|
21482
|
+
summary.sort(
|
|
21483
|
+
(left, right) => left.name.localeCompare(right.name) || left.rule.localeCompare(right.rule)
|
|
21484
|
+
);
|
|
21485
|
+
return { nodes: rules, summary };
|
|
21486
|
+
}
|
|
21487
|
+
function detachProtectedMeshes(document, protectedNodes) {
|
|
21488
|
+
const keep = keepInPlaceNodes(document, protectedNodes);
|
|
21489
|
+
const detached = [];
|
|
21490
|
+
for (const node of keep) {
|
|
21491
|
+
const mesh = node.getMesh();
|
|
21492
|
+
if (mesh === null || !isShared(mesh, node)) continue;
|
|
21493
|
+
const clone = deepCloneMesh(document, mesh);
|
|
21494
|
+
node.setMesh(clone);
|
|
21495
|
+
detached.push({ clone, node, original: mesh });
|
|
21496
|
+
}
|
|
21497
|
+
return detached;
|
|
21498
|
+
}
|
|
21499
|
+
function restoreDetachedMeshes(detached) {
|
|
21500
|
+
for (const { clone, node, original } of detached) {
|
|
21501
|
+
if (node.getMesh() === clone) node.setMesh(original);
|
|
21502
|
+
disposeMeshDeep(clone);
|
|
21503
|
+
}
|
|
21504
|
+
}
|
|
21505
|
+
function disposeMeshDeep(mesh) {
|
|
21506
|
+
for (const primitive of mesh.listPrimitives()) {
|
|
21507
|
+
for (const semantic of primitive.listSemantics()) {
|
|
21508
|
+
const attribute = primitive.getAttribute(semantic);
|
|
21509
|
+
primitive.setAttribute(semantic, null);
|
|
21510
|
+
attribute?.dispose();
|
|
21511
|
+
}
|
|
21512
|
+
const indices = primitive.getIndices();
|
|
21513
|
+
primitive.setIndices(null);
|
|
21514
|
+
indices?.dispose();
|
|
21515
|
+
for (const target of primitive.listTargets()) {
|
|
21516
|
+
for (const semantic of target.listSemantics()) {
|
|
21517
|
+
const attribute = target.getAttribute(semantic);
|
|
21518
|
+
target.setAttribute(semantic, null);
|
|
21519
|
+
attribute?.dispose();
|
|
21520
|
+
}
|
|
21521
|
+
target.dispose();
|
|
21522
|
+
}
|
|
21523
|
+
primitive.dispose();
|
|
21524
|
+
}
|
|
21525
|
+
mesh.dispose();
|
|
21526
|
+
}
|
|
21527
|
+
function isShared(mesh, node) {
|
|
21528
|
+
return mesh.listParents().some((parent) => parent !== node && parent.propertyType === PropertyType.NODE);
|
|
21529
|
+
}
|
|
21530
|
+
function isTrsExact(node) {
|
|
21531
|
+
const world = node.getWorldMatrix();
|
|
21532
|
+
const composed = composeTrsMatrix(
|
|
21533
|
+
node.getWorldTranslation(),
|
|
21534
|
+
node.getWorldRotation(),
|
|
21535
|
+
node.getWorldScale()
|
|
21536
|
+
);
|
|
21537
|
+
for (let index = 0; index < 16; index += 1) {
|
|
21538
|
+
const expected = world[index] ?? 0;
|
|
21539
|
+
const actual = composed[index] ?? 0;
|
|
21540
|
+
if (Math.abs(expected - actual) > 1e-4 * Math.max(1, Math.abs(expected))) return false;
|
|
21541
|
+
}
|
|
21542
|
+
return true;
|
|
21543
|
+
}
|
|
21544
|
+
function composeTrsMatrix(translation, rotation, scale3) {
|
|
21545
|
+
const translationVector = [translation[0] ?? 0, translation[1] ?? 0, translation[2] ?? 0];
|
|
21546
|
+
const rotationVector = [
|
|
21547
|
+
rotation[0] ?? 0,
|
|
21548
|
+
rotation[1] ?? 0,
|
|
21549
|
+
rotation[2] ?? 0,
|
|
21550
|
+
rotation[3] ?? 1
|
|
21551
|
+
];
|
|
21552
|
+
const scaleVector = [scale3[0] ?? 1, scale3[1] ?? 1, scale3[2] ?? 1];
|
|
21553
|
+
const out = [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1];
|
|
21554
|
+
MathUtils.compose(translationVector, rotationVector, scaleVector, out);
|
|
21555
|
+
return [...out];
|
|
21556
|
+
}
|
|
21557
|
+
function keepInPlaceNodes(document, protectedNodes) {
|
|
21558
|
+
const keepInPlace = new Set(protectedNodes.keys());
|
|
21559
|
+
const spreading = /* @__PURE__ */ new Set();
|
|
21560
|
+
for (const [node, rule] of protectedNodes) {
|
|
21561
|
+
if (rule !== "animation-ancestor") spreading.add(node);
|
|
21562
|
+
}
|
|
21563
|
+
for (const scene of document.getRoot().listScenes()) {
|
|
21564
|
+
scene.traverse((node) => {
|
|
21565
|
+
const parent = node.getParentNode();
|
|
21566
|
+
if (parent === null) {
|
|
21567
|
+
if (!isTrsExact(node)) {
|
|
21568
|
+
keepInPlace.add(node);
|
|
21569
|
+
spreading.add(node);
|
|
21570
|
+
}
|
|
21571
|
+
return;
|
|
21572
|
+
}
|
|
21573
|
+
if (spreading.has(parent) || !isTrsExact(parent) || !isTrsExact(node)) {
|
|
21574
|
+
keepInPlace.add(node);
|
|
21575
|
+
spreading.add(node);
|
|
21576
|
+
}
|
|
21577
|
+
});
|
|
21578
|
+
}
|
|
21579
|
+
return keepInPlace;
|
|
21580
|
+
}
|
|
21581
|
+
function flattenProtected(document, protectedNodes) {
|
|
21582
|
+
const keepInPlace = keepInPlaceNodes(document, protectedNodes);
|
|
21583
|
+
let reparented = 0;
|
|
21584
|
+
for (const scene of document.getRoot().listScenes()) {
|
|
21585
|
+
scene.traverse((node) => {
|
|
21586
|
+
if (keepInPlace.has(node) || node.getParentNode() === null) return;
|
|
21587
|
+
clearNodeParent(node);
|
|
21588
|
+
reparented += 1;
|
|
21589
|
+
});
|
|
21590
|
+
}
|
|
21591
|
+
return reparented;
|
|
21592
|
+
}
|
|
21593
|
+
function deepCloneMesh(document, mesh) {
|
|
21594
|
+
const clone = document.createMesh(mesh.getName());
|
|
21595
|
+
for (const primitive of mesh.listPrimitives()) {
|
|
21596
|
+
const copy = document.createPrimitive();
|
|
21597
|
+
for (const semantic of primitive.listSemantics()) {
|
|
21598
|
+
const attribute = primitive.getAttribute(semantic);
|
|
21599
|
+
if (attribute !== null) copy.setAttribute(semantic, attribute.clone());
|
|
21600
|
+
}
|
|
21601
|
+
const indices = primitive.getIndices();
|
|
21602
|
+
if (indices !== null) copy.setIndices(indices.clone());
|
|
21603
|
+
const material = primitive.getMaterial();
|
|
21604
|
+
if (material !== null) copy.setMaterial(material);
|
|
21605
|
+
for (const target of primitive.listTargets()) {
|
|
21606
|
+
const targetCopy = document.createPrimitiveTarget();
|
|
21607
|
+
for (const semantic of target.listSemantics()) {
|
|
21608
|
+
const attribute = target.getAttribute(semantic);
|
|
21609
|
+
if (attribute !== null) targetCopy.setAttribute(semantic, attribute.clone());
|
|
21610
|
+
}
|
|
21611
|
+
copy.addTarget(targetCopy);
|
|
21612
|
+
}
|
|
21613
|
+
clone.addPrimitive(copy);
|
|
21614
|
+
}
|
|
21615
|
+
return clone;
|
|
21616
|
+
}
|
|
21617
|
+
function instanceCount(batch) {
|
|
21618
|
+
for (const semantic of batch.listSemantics()) {
|
|
21619
|
+
const attribute = batch.getAttribute(semantic);
|
|
21620
|
+
if (attribute !== null) return attribute.getCount();
|
|
21621
|
+
}
|
|
21622
|
+
return 0;
|
|
21623
|
+
}
|
|
21624
|
+
async function compactModel(document, options = {}) {
|
|
21625
|
+
const root = document.getRoot();
|
|
21626
|
+
const namesBefore = sceneNodeNames(document);
|
|
21627
|
+
const nodesBefore = sceneNodes(root).length;
|
|
21628
|
+
const primitivesBefore = countPrimitives(sceneNodes(root));
|
|
21629
|
+
const { nodes: protectedNodes, summary: protectedSummary } = buildProtectedSet(document, options);
|
|
21630
|
+
const flattenEnabled = options.flatten ?? true;
|
|
21631
|
+
const joinEnabled = options.join ?? true;
|
|
21632
|
+
const instanceOption = options.instance ?? true;
|
|
21633
|
+
const instanceEnabled = instanceOption !== false;
|
|
21634
|
+
const instanceMin = typeof instanceOption === "object" && instanceOption.min !== void 0 ? instanceOption.min : 2;
|
|
21635
|
+
const reparented = flattenEnabled ? flattenProtected(document, protectedNodes) : 0;
|
|
21636
|
+
const sharedBefore = /* @__PURE__ */ new Set();
|
|
21637
|
+
for (const node of sceneNodes(root)) {
|
|
21638
|
+
const mesh = node.getMesh();
|
|
21639
|
+
if (mesh !== null && isShared(mesh, node)) sharedBefore.add(node);
|
|
21640
|
+
}
|
|
21641
|
+
let detached = [];
|
|
21642
|
+
if (instanceEnabled && root.listAnimations().length === 0) {
|
|
21643
|
+
detached = detachProtectedMeshes(document, protectedNodes);
|
|
21644
|
+
}
|
|
21645
|
+
let instanceSummary = {
|
|
21646
|
+
batches: 0,
|
|
21647
|
+
enabled: instanceEnabled,
|
|
21648
|
+
instances: 0
|
|
21649
|
+
};
|
|
21650
|
+
if (instanceEnabled) {
|
|
21651
|
+
await instance({ min: instanceMin })(document);
|
|
21652
|
+
let batches = 0;
|
|
21653
|
+
let instances = 0;
|
|
21654
|
+
for (const node of sceneNodes(root)) {
|
|
21655
|
+
const batch = asBatch(node);
|
|
21656
|
+
if (batch === null) continue;
|
|
21657
|
+
batches += 1;
|
|
21658
|
+
instances += instanceCount(batch);
|
|
21659
|
+
}
|
|
21660
|
+
const reason = batches > 0 ? void 0 : root.listAnimations().length > 0 ? "animated model; EXT_mesh_gpu_instancing is not supported for animated models" : `no mesh was shared by >=${String(instanceMin)} nodes`;
|
|
21661
|
+
instanceSummary = {
|
|
21662
|
+
batches,
|
|
21663
|
+
enabled: true,
|
|
21664
|
+
instances,
|
|
21665
|
+
...reason === void 0 ? {} : { reason }
|
|
21666
|
+
};
|
|
21667
|
+
}
|
|
21668
|
+
restoreDetachedMeshes(detached);
|
|
21669
|
+
const primitivesBeforeJoin = countPrimitives(sceneNodes(root));
|
|
21670
|
+
if (joinEnabled) {
|
|
21671
|
+
const sourcePrimitives = collectPrimitives(root);
|
|
21672
|
+
const exclusions = joinExclusions(root);
|
|
21673
|
+
await join({
|
|
21674
|
+
cleanup: false,
|
|
21675
|
+
filter: (node) => !protectedNodes.has(node) && !sharedBefore.has(node) && !exclusions.has(node)
|
|
21676
|
+
})(document);
|
|
21677
|
+
for (const primitive of sourcePrimitives) {
|
|
21678
|
+
if (primitive.listParents().some((parent) => parent.propertyType !== PropertyType.ROOT)) {
|
|
21679
|
+
continue;
|
|
21680
|
+
}
|
|
21681
|
+
const accessors = accessorsOfPrimitive(primitive);
|
|
21682
|
+
primitive.dispose();
|
|
21683
|
+
for (const accessor of accessors) {
|
|
21684
|
+
if (accessor.listParents().every((parent) => parent.propertyType === PropertyType.ROOT)) {
|
|
21685
|
+
accessor.dispose();
|
|
21686
|
+
}
|
|
21687
|
+
}
|
|
21688
|
+
}
|
|
21689
|
+
for (const mesh of root.listMeshes()) {
|
|
21690
|
+
if (mesh.listPrimitives().length === 0) mesh.dispose();
|
|
21691
|
+
}
|
|
21692
|
+
}
|
|
21693
|
+
const primitivesAfterJoin = countPrimitives(sceneNodes(root));
|
|
21694
|
+
const nodesAfter = sceneNodes(root).length;
|
|
21695
|
+
const summary = {
|
|
21696
|
+
flatten: { enabled: flattenEnabled, reparented },
|
|
21697
|
+
instance: instanceSummary,
|
|
21698
|
+
join: {
|
|
21699
|
+
enabled: joinEnabled,
|
|
21700
|
+
primitivesAfter: primitivesAfterJoin,
|
|
21701
|
+
primitivesBefore: primitivesBeforeJoin
|
|
21702
|
+
},
|
|
21703
|
+
nodesAfter,
|
|
21704
|
+
nodesBefore,
|
|
21705
|
+
primitivesAfter: primitivesAfterJoin,
|
|
21706
|
+
primitivesBefore,
|
|
21707
|
+
removed: removedNames(namesBefore, sceneNodeNames(document)),
|
|
21708
|
+
protected: protectedSummary
|
|
21709
|
+
};
|
|
21710
|
+
return summary;
|
|
21711
|
+
}
|
|
21712
|
+
function collectPrimitives(root) {
|
|
21713
|
+
return root.listMeshes().flatMap((mesh) => mesh.listPrimitives());
|
|
21714
|
+
}
|
|
21715
|
+
function primitiveSignature(primitive) {
|
|
21716
|
+
if (primitive.listTargets().length > 0) return null;
|
|
21717
|
+
const position = primitive.getAttribute("POSITION");
|
|
21718
|
+
if (position === null) return null;
|
|
21719
|
+
const hash = createHash("sha1");
|
|
21720
|
+
const min2 = position.getMin([0, 0, 0]);
|
|
21721
|
+
const max2 = position.getMax([0, 0, 0]);
|
|
21722
|
+
const extent = Math.max(
|
|
21723
|
+
(max2[0] ?? 0) - (min2[0] ?? 0),
|
|
21724
|
+
(max2[1] ?? 0) - (min2[1] ?? 0),
|
|
21725
|
+
(max2[2] ?? 0) - (min2[2] ?? 0),
|
|
21726
|
+
1e-9
|
|
21727
|
+
);
|
|
21728
|
+
for (const semantic of [...primitive.listSemantics()].sort()) {
|
|
21729
|
+
const attribute = primitive.getAttribute(semantic);
|
|
21730
|
+
if (attribute === null) continue;
|
|
21731
|
+
hash.update(semantic);
|
|
21732
|
+
hash.update(":");
|
|
21733
|
+
const array = attribute.getArray();
|
|
21734
|
+
const stride = attribute.getElementSize();
|
|
21735
|
+
const normalized = attribute.getNormalized();
|
|
21736
|
+
for (let index = 0; index < attribute.getCount(); index += 1) {
|
|
21737
|
+
for (let axis = 0; axis < stride; axis += 1) {
|
|
21738
|
+
let value = array[index * stride + axis] ?? 0;
|
|
21739
|
+
if (semantic === "POSITION" && !normalized && axis < 3) {
|
|
21740
|
+
value = (value - (min2[axis] ?? 0)) / extent;
|
|
21741
|
+
}
|
|
21742
|
+
hash.update(Math.round(value * 1e4).toString());
|
|
21743
|
+
hash.update(",");
|
|
21744
|
+
}
|
|
21745
|
+
}
|
|
21746
|
+
}
|
|
21747
|
+
const indices = primitive.getIndices();
|
|
21748
|
+
if (indices !== null) {
|
|
21749
|
+
const array = indices.getArray();
|
|
21750
|
+
hash.update("indices:");
|
|
21751
|
+
for (let index = 0; index < indices.getCount(); index += 1) {
|
|
21752
|
+
hash.update((array[index] ?? 0).toString());
|
|
21753
|
+
hash.update(",");
|
|
21754
|
+
}
|
|
21755
|
+
}
|
|
21756
|
+
return hash.digest("hex");
|
|
21757
|
+
}
|
|
21758
|
+
function meshShapeSignature(mesh) {
|
|
21759
|
+
const signatures = [];
|
|
21760
|
+
for (const primitive of mesh.listPrimitives()) {
|
|
21761
|
+
const signature = primitiveSignature(primitive);
|
|
21762
|
+
if (signature === null) return null;
|
|
21763
|
+
signatures.push(signature);
|
|
21764
|
+
}
|
|
21765
|
+
signatures.sort();
|
|
21766
|
+
return signatures.join("|");
|
|
21767
|
+
}
|
|
21768
|
+
function joinExclusions(root) {
|
|
21769
|
+
const excluded = /* @__PURE__ */ new Set();
|
|
21770
|
+
const signatures = /* @__PURE__ */ new Map();
|
|
21771
|
+
const byNode = /* @__PURE__ */ new Map();
|
|
21772
|
+
for (const node of sceneNodes(root)) {
|
|
21773
|
+
if (listNodeScenes(node).length > 1) excluded.add(node);
|
|
21774
|
+
const mesh = node.getMesh();
|
|
21775
|
+
if (mesh === null) continue;
|
|
21776
|
+
const signature = meshShapeSignature(mesh);
|
|
21777
|
+
if (signature === null) continue;
|
|
21778
|
+
byNode.set(node, signature);
|
|
21779
|
+
signatures.set(signature, (signatures.get(signature) ?? 0) + 1);
|
|
21780
|
+
}
|
|
21781
|
+
for (const [node, signature] of byNode) {
|
|
21782
|
+
if ((signatures.get(signature) ?? 0) > 1) excluded.add(node);
|
|
21783
|
+
}
|
|
21784
|
+
return excluded;
|
|
21785
|
+
}
|
|
21786
|
+
function accessorsOfPrimitive(primitive) {
|
|
21787
|
+
const accessors = [];
|
|
21788
|
+
for (const semantic of primitive.listSemantics()) {
|
|
21789
|
+
const attribute = primitive.getAttribute(semantic);
|
|
21790
|
+
if (attribute !== null) accessors.push(attribute);
|
|
21791
|
+
}
|
|
21792
|
+
const indices = primitive.getIndices();
|
|
21793
|
+
if (indices !== null) accessors.push(indices);
|
|
21794
|
+
for (const target of primitive.listTargets()) {
|
|
21795
|
+
for (const semantic of target.listSemantics()) {
|
|
21796
|
+
const attribute = target.getAttribute(semantic);
|
|
21797
|
+
if (attribute !== null) accessors.push(attribute);
|
|
21798
|
+
}
|
|
21799
|
+
}
|
|
21800
|
+
return accessors;
|
|
21801
|
+
}
|
|
21802
|
+
function compactRequested(options) {
|
|
21803
|
+
if (options === false) return false;
|
|
21804
|
+
if (options === void 0 || options === true) return true;
|
|
21805
|
+
return options.flatten !== false || options.join !== false || options.instance !== false;
|
|
21806
|
+
}
|
|
21807
|
+
var COMPACT_VERSION = 8;
|
|
21808
|
+
function resolveCompactOptions(options) {
|
|
21809
|
+
if (options === void 0 || options === true) {
|
|
21810
|
+
return {
|
|
21811
|
+
flatten: true,
|
|
21812
|
+
instance: { min: 2 },
|
|
21813
|
+
join: true,
|
|
21814
|
+
protectedNames: [],
|
|
21815
|
+
protectedPattern: DEFAULT_PROTECTED_PATTERN,
|
|
21816
|
+
version: COMPACT_VERSION
|
|
21817
|
+
};
|
|
21818
|
+
}
|
|
21819
|
+
if (options === false) {
|
|
21820
|
+
return {
|
|
21821
|
+
flatten: false,
|
|
21822
|
+
instance: false,
|
|
21823
|
+
join: false,
|
|
21824
|
+
protectedNames: [],
|
|
21825
|
+
protectedPattern: DEFAULT_PROTECTED_PATTERN,
|
|
21826
|
+
version: COMPACT_VERSION
|
|
21827
|
+
};
|
|
21828
|
+
}
|
|
21829
|
+
const instance2 = options.instance;
|
|
21830
|
+
const min2 = typeof instance2 === "object" ? instance2.min ?? 2 : 2;
|
|
21831
|
+
if (!Number.isSafeInteger(min2) || min2 < 2) {
|
|
21832
|
+
throw new Error(
|
|
21833
|
+
"TN_ASSETS_COMPACT_INVALID: assets.models.compact.instance.min must be an integer of at least 2."
|
|
21834
|
+
);
|
|
21835
|
+
}
|
|
21836
|
+
return {
|
|
21837
|
+
flatten: options.flatten !== false,
|
|
21838
|
+
instance: instance2 === false ? false : { min: min2 },
|
|
21839
|
+
join: options.join !== false,
|
|
21840
|
+
protectedNames: options.protectedNames ?? [],
|
|
21841
|
+
protectedPattern: options.protectedPattern ?? DEFAULT_PROTECTED_PATTERN,
|
|
21842
|
+
version: COMPACT_VERSION
|
|
21843
|
+
};
|
|
21844
|
+
}
|
|
20382
21845
|
var SHARED_IMAGES_DIRECTORY = "shared/images";
|
|
20383
21846
|
var KEY_LENGTH = 16;
|
|
20384
21847
|
var EXTENSION_BY_MIME = {
|
|
@@ -20587,6 +22050,8 @@ async function readSharedGlb(io, buffer, resolve2) {
|
|
|
20587
22050
|
// src/passes/model.ts
|
|
20588
22051
|
var DRACO_EXTENSION2 = "KHR_draco_mesh_compression";
|
|
20589
22052
|
var EXT_MESHOPT_EXTENSION = "EXT_meshopt_compression";
|
|
22053
|
+
var MATERIAL_MERGE_VERSION = 1;
|
|
22054
|
+
var MODEL_DEDUPE_VERSION = 1;
|
|
20590
22055
|
var BBOX_TOLERANCE = 1e-3;
|
|
20591
22056
|
var SIMPLIFY_BBOX_TOLERANCE = 0.01;
|
|
20592
22057
|
var SIMPLIFY_RATIO_FLOOR = 0.5;
|
|
@@ -20624,6 +22089,27 @@ function primitiveTriangles(primitive) {
|
|
|
20624
22089
|
const drawn = primitive.getIndices()?.getCount() ?? primitive.getAttribute("POSITION")?.getCount() ?? 0;
|
|
20625
22090
|
return Math.floor(drawn / 3);
|
|
20626
22091
|
}
|
|
22092
|
+
function instanceCount2(batch) {
|
|
22093
|
+
for (const semantic of batch.listSemantics()) {
|
|
22094
|
+
const attribute = batch.getAttribute(semantic);
|
|
22095
|
+
if (attribute !== null) return attribute.getCount();
|
|
22096
|
+
}
|
|
22097
|
+
return 0;
|
|
22098
|
+
}
|
|
22099
|
+
function instanceMatrix(batch, index) {
|
|
22100
|
+
return composeTrsMatrix(
|
|
22101
|
+
readElement(batch.getAttribute("TRANSLATION"), index, [0, 0, 0]),
|
|
22102
|
+
readElement(batch.getAttribute("ROTATION"), index, [0, 0, 0, 1]),
|
|
22103
|
+
readElement(batch.getAttribute("SCALE"), index, [1, 1, 1])
|
|
22104
|
+
);
|
|
22105
|
+
}
|
|
22106
|
+
function readElement(accessor, index, fallback) {
|
|
22107
|
+
if (accessor === null) return [...fallback];
|
|
22108
|
+
const array = accessor.getArray();
|
|
22109
|
+
const size = accessor.getElementSize();
|
|
22110
|
+
const base = index * size;
|
|
22111
|
+
return fallback.map((_, axis) => array[base + axis] ?? 0);
|
|
22112
|
+
}
|
|
20627
22113
|
function decodeElement(accessor, index) {
|
|
20628
22114
|
const array = accessor.getArray();
|
|
20629
22115
|
const base = index * accessor.getElementSize();
|
|
@@ -20721,36 +22207,43 @@ function reachableStats(root) {
|
|
|
20721
22207
|
if (skin !== null) skins.add(skin);
|
|
20722
22208
|
const mesh = node.getMesh();
|
|
20723
22209
|
if (mesh !== null) {
|
|
20724
|
-
const
|
|
22210
|
+
const batch = node.getExtension("EXT_mesh_gpu_instancing");
|
|
22211
|
+
const instanceMatrices = batch === null ? [node.getWorldMatrix()] : Array.from(
|
|
22212
|
+
{ length: instanceCount2(batch) },
|
|
22213
|
+
(_, index) => multiplyMatrices(node.getWorldMatrix(), instanceMatrix(batch, index))
|
|
22214
|
+
);
|
|
22215
|
+
const skin2 = node.getSkin();
|
|
20725
22216
|
let jointMatrices;
|
|
20726
|
-
if (
|
|
20727
|
-
const inverseBinds =
|
|
20728
|
-
jointMatrices =
|
|
22217
|
+
if (skin2 !== null) {
|
|
22218
|
+
const inverseBinds = skin2.getInverseBindMatrices();
|
|
22219
|
+
jointMatrices = skin2.listJoints().map((joint, index) => {
|
|
20729
22220
|
const jointWorld = joint.getWorldMatrix();
|
|
20730
22221
|
const ibm = inverseBinds?.getElement(index, [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) ?? IDENTITY_MATRIX2;
|
|
20731
22222
|
return multiplyMatrices(jointWorld, ibm);
|
|
20732
22223
|
});
|
|
20733
22224
|
}
|
|
20734
|
-
for (const
|
|
20735
|
-
|
|
20736
|
-
|
|
20737
|
-
|
|
20738
|
-
|
|
20739
|
-
|
|
20740
|
-
|
|
20741
|
-
|
|
20742
|
-
|
|
20743
|
-
|
|
20744
|
-
|
|
20745
|
-
|
|
20746
|
-
|
|
20747
|
-
|
|
20748
|
-
|
|
20749
|
-
|
|
20750
|
-
|
|
20751
|
-
|
|
20752
|
-
|
|
20753
|
-
|
|
22225
|
+
for (const matrix of instanceMatrices) {
|
|
22226
|
+
for (const primitive of mesh.listPrimitives()) {
|
|
22227
|
+
triangles += primitiveTriangles(primitive);
|
|
22228
|
+
const position = primitive.getAttribute("POSITION");
|
|
22229
|
+
if (position === null) continue;
|
|
22230
|
+
vertices += position.getCount();
|
|
22231
|
+
for (let index = 0; index < position.getCount(); index += 1) {
|
|
22232
|
+
const [wx, wy, wz] = evaluateVertex(
|
|
22233
|
+
position,
|
|
22234
|
+
index,
|
|
22235
|
+
primitive.getAttribute("JOINTS_0"),
|
|
22236
|
+
primitive.getAttribute("WEIGHTS_0"),
|
|
22237
|
+
skin2 !== null ? jointMatrices : void 0,
|
|
22238
|
+
matrix
|
|
22239
|
+
);
|
|
22240
|
+
minX = Math.min(minX, wx);
|
|
22241
|
+
minY = Math.min(minY, wy);
|
|
22242
|
+
minZ = Math.min(minZ, wz);
|
|
22243
|
+
maxX = Math.max(maxX, wx);
|
|
22244
|
+
maxY = Math.max(maxY, wy);
|
|
22245
|
+
maxZ = Math.max(maxZ, wz);
|
|
22246
|
+
}
|
|
20754
22247
|
}
|
|
20755
22248
|
}
|
|
20756
22249
|
}
|
|
@@ -20934,6 +22427,7 @@ function lodCacheKey(lod) {
|
|
|
20934
22427
|
}
|
|
20935
22428
|
function modelPass(options = {}) {
|
|
20936
22429
|
return {
|
|
22430
|
+
appliesTo: ["model"],
|
|
20937
22431
|
configuration: {
|
|
20938
22432
|
passes: {
|
|
20939
22433
|
dedup: options.passes?.dedup ?? true,
|
|
@@ -20951,8 +22445,18 @@ function modelPass(options = {}) {
|
|
|
20951
22445
|
// Part of the compile cache key: change the cap or a codec and stale outputs must not
|
|
20952
22446
|
// be re-served.
|
|
20953
22447
|
simplify: options.simplify ?? null,
|
|
22448
|
+
// The resolved compaction policy: a sub-pass switch, allow-list or regex edit changes the
|
|
22449
|
+
// output, so it must change the cache key.
|
|
22450
|
+
compact: resolveCompactOptions(options.compact),
|
|
20954
22451
|
// Generation-only identity; runtime budget edits must not invalidate baked geometry.
|
|
20955
22452
|
lod: lodCacheKey(options.lod),
|
|
22453
|
+
// The material merge is unconditional and lossless, so the version string is the whole knob:
|
|
22454
|
+
// it moves when the signature does, which invalidates every stale output at once.
|
|
22455
|
+
materials: MATERIAL_MERGE_VERSION,
|
|
22456
|
+
// Unconditional and lossless for the same reason: the cook shares one output between
|
|
22457
|
+
// content-identical sources, so a bake published before this key existed named one output
|
|
22458
|
+
// per copy and must not be re-served as if it had deduped.
|
|
22459
|
+
dedupe: MODEL_DEDUPE_VERSION,
|
|
20956
22460
|
// `"none"` and "absent" are different cache keys on purpose: absent bakes with defaults.
|
|
20957
22461
|
virtual: options.virtual === "none" ? "none" : {
|
|
20958
22462
|
...options.virtual === void 0 ? {} : options.virtual,
|
|
@@ -20960,6 +22464,7 @@ function modelPass(options = {}) {
|
|
|
20960
22464
|
},
|
|
20961
22465
|
sharedImages: options.sharedImages !== void 0,
|
|
20962
22466
|
textures: options.textures === "none" ? "none" : {
|
|
22467
|
+
decoderFree: options.textures?.decoderFree ?? false,
|
|
20963
22468
|
encoder: KTX2_ENCODER_VERSION,
|
|
20964
22469
|
maxSize: options.textures?.maxSize ?? null,
|
|
20965
22470
|
keepSmallerSource: true,
|
|
@@ -20970,7 +22475,7 @@ function modelPass(options = {}) {
|
|
|
20970
22475
|
name: "model",
|
|
20971
22476
|
// A mobile compile replaces these two decoder-backed sub-passes in the effective options;
|
|
20972
22477
|
// geometry rewrites and shared-image emission remain plain glTF and stay enabled there.
|
|
20973
|
-
needsRuntimeDecoder: (options.passes?.meshopt ?? true) || options.textures !== "none",
|
|
22478
|
+
needsRuntimeDecoder: (options.passes?.meshopt ?? true) || options.textures !== "none" && options.textures?.decoderFree !== true,
|
|
20974
22479
|
apply: async (input, logicalPath) => {
|
|
20975
22480
|
if (classify2(logicalPath) !== "model") return input;
|
|
20976
22481
|
const passes2 = options.passes ?? {};
|
|
@@ -20984,7 +22489,7 @@ function modelPass(options = {}) {
|
|
|
20984
22489
|
const textureOptions = options.textures === "none" ? void 0 : options.textures ?? {};
|
|
20985
22490
|
const virtualOptions = options.virtual === "none" ? void 0 : options.virtual ?? {};
|
|
20986
22491
|
const lodOptions = options.lod === void 0 || options.lod === false || options.lod === "none" ? void 0 : options.lod;
|
|
20987
|
-
const geometryActive = options.vertexLayout !== void 0 || Object.values(enabled).some(Boolean) || options.simplify !== void 0 || lodOptions !== void 0 || virtualOptions !== void 0;
|
|
22492
|
+
const geometryActive = options.vertexLayout !== void 0 || Object.values(enabled).some(Boolean) || options.simplify !== void 0 || lodOptions !== void 0 || virtualOptions !== void 0 || compactRequested(options.compact);
|
|
20988
22493
|
if (!geometryActive && textureOptions === void 0) return input;
|
|
20989
22494
|
const document = await readDocument2(input, logicalPath);
|
|
20990
22495
|
if (!geometryActive && document.getRoot().listTextures().length === 0) return input;
|
|
@@ -20995,8 +22500,21 @@ function modelPass(options = {}) {
|
|
|
20995
22500
|
const source = reachableStats(document.getRoot());
|
|
20996
22501
|
const jointFloor = jointComponentSizes(document.getRoot());
|
|
20997
22502
|
if (enabled.dedup) await dedup()(document);
|
|
20998
|
-
|
|
22503
|
+
const namesBeforeCompact = compactRequested(options.compact) ? sceneNodeNames(document) : void 0;
|
|
22504
|
+
let compact = compactRequested(options.compact) ? await compactModel(document, resolveCompactOptions(options.compact)) : void 0;
|
|
22505
|
+
if (enabled.prune) {
|
|
20999
22506
|
await prune({ keepAttributes: options.preserveLightmapUv === true })(document);
|
|
22507
|
+
if (compact !== void 0 && namesBeforeCompact !== void 0) {
|
|
22508
|
+
const nodes = countCompactNodes(document);
|
|
22509
|
+
compact = {
|
|
22510
|
+
...compact,
|
|
22511
|
+
nodesAfter: nodes,
|
|
22512
|
+
primitivesAfter: countCompactPrimitives(document),
|
|
22513
|
+
removed: removedNames(namesBeforeCompact, sceneNodeNames(document))
|
|
22514
|
+
};
|
|
22515
|
+
}
|
|
22516
|
+
}
|
|
22517
|
+
const materials = mergeIdenticalMaterials(document);
|
|
21000
22518
|
if (options.simplify !== void 0) {
|
|
21001
22519
|
await MeshoptSimplifier.ready;
|
|
21002
22520
|
await simplify({
|
|
@@ -21063,9 +22581,11 @@ function modelPass(options = {}) {
|
|
|
21063
22581
|
assertNoTextureDrift(sourceTextures, textureBindings(verified), logicalPath);
|
|
21064
22582
|
if (lod !== void 0 && lod.generated > 0) validateDiscreteLod(verified);
|
|
21065
22583
|
const entry = {
|
|
22584
|
+
...compact === void 0 ? {} : { compact },
|
|
21066
22585
|
...embeddedTextures === void 0 ? {} : { embeddedTextures },
|
|
21067
22586
|
extensions: [...extensions].sort(),
|
|
21068
22587
|
...lod === void 0 ? {} : { lod },
|
|
22588
|
+
materials,
|
|
21069
22589
|
...options.simplify === void 0 ? {} : {
|
|
21070
22590
|
simplify: {
|
|
21071
22591
|
achievedRatio: source.triangles === 0 ? 1 : output.triangles / source.triangles,
|
|
@@ -21097,6 +22617,7 @@ function sharedSettings(texture, textureOptions) {
|
|
|
21097
22617
|
colorSpace: getTextureColorSpace(texture),
|
|
21098
22618
|
slots: [...listTextureSlots(texture)].sort(),
|
|
21099
22619
|
textures: textureOptions === void 0 ? "none" : {
|
|
22620
|
+
decoderFree: textureOptions.decoderFree ?? false,
|
|
21100
22621
|
encoder: KTX2_ENCODER_VERSION,
|
|
21101
22622
|
keepSmallerSource: true,
|
|
21102
22623
|
maxSize: textureOptions.maxSize ?? null,
|
|
@@ -21431,6 +22952,7 @@ function serialFor(logicalPath) {
|
|
|
21431
22952
|
// src/passes/audio.ts
|
|
21432
22953
|
function audioPass(options = {}) {
|
|
21433
22954
|
return {
|
|
22955
|
+
appliesTo: ["audio"],
|
|
21434
22956
|
configuration: {
|
|
21435
22957
|
normalise: options.normalise ?? "ceiling",
|
|
21436
22958
|
overrides: options.overrides ?? [],
|
|
@@ -21653,6 +23175,8 @@ function buildInstances(specs, root) {
|
|
|
21653
23175
|
});
|
|
21654
23176
|
case "texture":
|
|
21655
23177
|
return texturePass(spec.options);
|
|
23178
|
+
case "texture-resize":
|
|
23179
|
+
return textureResizePass(spec.options);
|
|
21656
23180
|
}
|
|
21657
23181
|
});
|
|
21658
23182
|
}
|