@needle-tools/gltf-build-pipeline 2.6.0-alpha.1 โ 2.7.0-alpha.1
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/CHANGELOG.md +10 -0
- package/dist/extensions/NEEDLE_opaque/NEEDLE_opaque.js +64 -8
- package/dist/scripts/pack-gltf.js +3 -2
- package/dist/transforms/needle_mesh_transform.d.ts +1 -1
- package/dist/transforms/needle_mesh_transform.js +34 -10
- package/dist/transforms/needle_progressive.js +34 -5
- package/dist/utils/mesh.d.ts +2 -2
- package/dist/utils/mesh.js +29 -12
- package/dist/utils/nodeio.js +6 -0
- package/dist/utils/version.gen.d.ts +1 -0
- package/dist/utils/version.gen.js +1 -0
- package/dist/utils/version.js +2 -3
- package/package.json +4 -4
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,16 @@ All notable changes to this package will be documented in this file.
|
|
|
4
4
|
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
|
|
5
5
|
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
|
|
6
6
|
|
|
7
|
+
## [2.7.0-alpha.1] - 2024-01-13
|
|
8
|
+
- fix: handle LOD omitting and remove deleted files from progressive transform results list
|
|
9
|
+
|
|
10
|
+
## [2.7.0-alpha] - 2024-01-13
|
|
11
|
+
- add: VRM mesh simplification support
|
|
12
|
+
- fix: issue where the output directory was not created if it didn't exist yet
|
|
13
|
+
- fix: try resolve deduplicated meshes
|
|
14
|
+
- change: update gltf-transform to 4.1.1
|
|
15
|
+
- change: modify mesh LOD settings for more vertices
|
|
16
|
+
|
|
7
17
|
## [2.6.0-alpha] - 2024-12-16
|
|
8
18
|
- add: create placeholder textures for missing jpg, png, webp, gif, tif on disc
|
|
9
19
|
- fix: compressing a gltf file to a different output directory does not falsely delete old external textures on disc in the input directory
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Extension, ExtensionProperty, PropertyType, TextureChannel } from '@gltf-transform/core';
|
|
1
|
+
import { Extension, ExtensionProperty, PropertyType, Node, TextureChannel } from '@gltf-transform/core';
|
|
2
2
|
import { getTexture, tryGetInArray, writeExtension } from '../utils.js';
|
|
3
3
|
import { isLightmap } from '../../utils/texture.js';
|
|
4
4
|
const ALL_PROPERTY_TYPES = [];
|
|
@@ -369,7 +369,7 @@ export function createOpaqueExtension(name, types, opts) {
|
|
|
369
369
|
writeExtension(result, EXTENSION_NAME, this.ext);
|
|
370
370
|
if (debugLog) {
|
|
371
371
|
// console.log("WRITE", EXTENSION_NAME, "to", this.gltfField, "at index", index);
|
|
372
|
-
console.log(result);
|
|
372
|
+
// console.log("WRITE RESULT", result);
|
|
373
373
|
}
|
|
374
374
|
}
|
|
375
375
|
}
|
|
@@ -646,6 +646,26 @@ class JsonPointerHandler {
|
|
|
646
646
|
pointer?.register(obj, key, fullPath);
|
|
647
647
|
}
|
|
648
648
|
}
|
|
649
|
+
if (value.startsWith("/meshes/")) {
|
|
650
|
+
if (this.debug)
|
|
651
|
+
console.log("Read pointer", value);
|
|
652
|
+
const index = parseInt(value.substring("/meshes/".length));
|
|
653
|
+
const object = context.meshes[index];
|
|
654
|
+
if (object) {
|
|
655
|
+
let pointer = this.map.get(value);
|
|
656
|
+
if (!pointer) {
|
|
657
|
+
const definition = context.jsonDoc.json.meshes[index];
|
|
658
|
+
const res = new MeshIndexResolver(this.document, object, definition, prop);
|
|
659
|
+
res.debug = this.debug;
|
|
660
|
+
res.init(context);
|
|
661
|
+
pointer = new JsonPointer("/meshes/", res, value);
|
|
662
|
+
pointer.debug = this.debug;
|
|
663
|
+
this.map.set(value, pointer);
|
|
664
|
+
prop.setReference(value, object);
|
|
665
|
+
}
|
|
666
|
+
pointer?.register(obj, key, fullPath);
|
|
667
|
+
}
|
|
668
|
+
}
|
|
649
669
|
}
|
|
650
670
|
break;
|
|
651
671
|
case 'number':
|
|
@@ -770,14 +790,15 @@ class JsonPointer {
|
|
|
770
790
|
else {
|
|
771
791
|
this.newPointer = index;
|
|
772
792
|
}
|
|
773
|
-
if (this.debug)
|
|
774
|
-
console.log(`RESOLVED pointer ${this.oldPointer} to โ ${this.newPointer}`);
|
|
775
793
|
return true;
|
|
776
794
|
}
|
|
777
795
|
else {
|
|
778
796
|
// If the pointer could not be resolved during write we need to log an error
|
|
779
|
-
if (step === "write")
|
|
780
|
-
console.log(
|
|
797
|
+
if (step === "write") {
|
|
798
|
+
console.log(`ERR: ๐งจ FAILED to resolve pointer \"${this.oldPointer}\" used by ${this._debugInfo.join(", ")}`);
|
|
799
|
+
if (this.debug)
|
|
800
|
+
debugger;
|
|
801
|
+
}
|
|
781
802
|
}
|
|
782
803
|
return false;
|
|
783
804
|
}
|
|
@@ -790,7 +811,7 @@ class JsonPointer {
|
|
|
790
811
|
const obj = this._objects[i];
|
|
791
812
|
const prop = this._props[i];
|
|
792
813
|
if (this.debug) {
|
|
793
|
-
console.log(`< Resolved pointer ${this._debugInfo[i]}: ${this.newPointer}`);
|
|
814
|
+
console.log(`< Resolved pointer ${this._debugInfo[i]}: ${this.oldPointer} โ ${this.newPointer}`);
|
|
794
815
|
}
|
|
795
816
|
obj[prop] = this.newPointer;
|
|
796
817
|
}
|
|
@@ -802,10 +823,13 @@ class PointerResolver {
|
|
|
802
823
|
inputObject;
|
|
803
824
|
/* the original json definition */
|
|
804
825
|
inputDefinition;
|
|
805
|
-
|
|
826
|
+
/** The graph property this pointer belongs to */
|
|
827
|
+
property;
|
|
828
|
+
constructor(doc, obj, def, prop) {
|
|
806
829
|
this.document = doc;
|
|
807
830
|
this.inputObject = obj;
|
|
808
831
|
this.inputDefinition = def;
|
|
832
|
+
this.property = prop;
|
|
809
833
|
}
|
|
810
834
|
debug = false;
|
|
811
835
|
}
|
|
@@ -913,3 +937,35 @@ class NodeIndexResolver extends PointerResolver {
|
|
|
913
937
|
return context.nodeIndexMap.get(this.inputObject);
|
|
914
938
|
}
|
|
915
939
|
}
|
|
940
|
+
class MeshIndexResolver extends PointerResolver {
|
|
941
|
+
init(context) {
|
|
942
|
+
}
|
|
943
|
+
resolve(context) {
|
|
944
|
+
let index = context.meshIndexMap.get(this.inputObject);
|
|
945
|
+
if (index >= 0) {
|
|
946
|
+
return index;
|
|
947
|
+
}
|
|
948
|
+
let obj = null;
|
|
949
|
+
if (this.inputObject.isDisposed()) {
|
|
950
|
+
// If the mesh was deduplicated we try to get the mesh from a parent node
|
|
951
|
+
// This is a HACK, we need to get the reference to the mesh this one was deduplicated with since the parent node potentially has many children
|
|
952
|
+
const parents = this.property?.listParents();
|
|
953
|
+
for (const parent of parents) {
|
|
954
|
+
if (parent instanceof Node) {
|
|
955
|
+
obj = parent.getMesh();
|
|
956
|
+
if (obj) {
|
|
957
|
+
console.log(`INFO: resolved mesh \"${this.inputObject.getName()}\" from parent node \"${parent.getName()}\"`);
|
|
958
|
+
break;
|
|
959
|
+
}
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
if (obj.isDisposed()) {
|
|
963
|
+
console.warn("WARN: mesh is disposed", this.inputObject.getName());
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
if (!obj) {
|
|
967
|
+
return undefined;
|
|
968
|
+
}
|
|
969
|
+
return context.meshIndexMap.get(obj);
|
|
970
|
+
}
|
|
971
|
+
}
|
|
@@ -81,10 +81,11 @@ export async function packGLTF(inputFile, outputFile, options) {
|
|
|
81
81
|
},
|
|
82
82
|
config,
|
|
83
83
|
}),
|
|
84
|
-
needle_mesh_transform({ file: inputFile, config }
|
|
84
|
+
needle_mesh_transform({ file: inputFile, config }),
|
|
85
85
|
needle_animation_transform({}),
|
|
86
86
|
resample(),
|
|
87
87
|
// Can not dedup "materials" because progressive textures will then assigned to the wrong material instance
|
|
88
|
+
// HACK: keepUniqueNames is enabled to fix https://linear.app/needle/issue/NE-6288
|
|
88
89
|
dedup({ propertyTypes: [PropertyType.MESH, PropertyType.MATERIAL] }),
|
|
89
90
|
// prune(),
|
|
90
91
|
];
|
|
@@ -102,7 +103,7 @@ export async function packGLTF(inputFile, outputFile, options) {
|
|
|
102
103
|
transforms.push(join({ keepNamed: false }));
|
|
103
104
|
}
|
|
104
105
|
await document.transform(...transforms);
|
|
105
|
-
logger.info(
|
|
106
|
+
logger.info(`โ Writing to ${outputFile}`);
|
|
106
107
|
await writeNodeIO(io, outputFile, document);
|
|
107
108
|
if (useCache && cacheKey != undefined && existsSync(outputFile))
|
|
108
109
|
addToCache(cacheKey, readFileSync(outputFile));
|
|
@@ -10,6 +10,6 @@ declare type MeshTransformContext = {
|
|
|
10
10
|
* Which compression type for a model should be used. This should be respected when creating LOD meshes and the appropriate extension should be added to the LOD mesh so it receives the same mesh compression as the parent mesh
|
|
11
11
|
*/
|
|
12
12
|
export declare function determineMeshCompression(document: Document, filePath: string): NEEDLE_mesh_compression_model_root;
|
|
13
|
-
export declare const needle_mesh_transform: (context: MeshTransformContext
|
|
13
|
+
export declare const needle_mesh_transform: (context: MeshTransformContext) => Transform;
|
|
14
14
|
export declare function compressPrimitive(document: Document, mesh: Mesh, prim: Primitive, index: number, weldOptions: Required<WeldOptions>, meshOptions: Required<SimplifyOptions>): Promise<Primitive>;
|
|
15
15
|
export {};
|
|
@@ -3,6 +3,7 @@ import { meshopt, simplifyPrimitive, SIMPLIFY_DEFAULTS, WELD_DEFAULTS, draco } f
|
|
|
3
3
|
import { MeshoptEncoder, MeshoptSimplifier } from "meshoptimizer";
|
|
4
4
|
import { NEEDLE_mesh_compression, NEEDLE_mesh_compression_model_root } from "../extensions/index.js";
|
|
5
5
|
import { weldPrimitive } from "../functions/weld.3.js";
|
|
6
|
+
import { calculateMeshDensity } from "../utils/mesh.js";
|
|
6
7
|
const NAME = 'needle_mesh_transform';
|
|
7
8
|
/**
|
|
8
9
|
* Which compression type for a model should be used. This should be respected when creating LOD meshes and the appropriate extension should be added to the LOD mesh so it receives the same mesh compression as the parent mesh
|
|
@@ -18,7 +19,7 @@ export function determineMeshCompression(document, filePath) {
|
|
|
18
19
|
return rootModel;
|
|
19
20
|
}
|
|
20
21
|
}
|
|
21
|
-
if (filePath.endsWith(".vrm")) {
|
|
22
|
+
if (filePath?.toLowerCase().endsWith(".vrm")) {
|
|
22
23
|
const hasBlendshapes = document.getRoot().listMeshes().some((mesh) => mesh.listPrimitives().some((prim) => prim.listTargets().length > 0));
|
|
23
24
|
if (hasBlendshapes) {
|
|
24
25
|
return new NEEDLE_mesh_compression_model_root("meshopt");
|
|
@@ -57,10 +58,10 @@ export function determineMeshCompression(document, filePath) {
|
|
|
57
58
|
// }
|
|
58
59
|
return new NEEDLE_mesh_compression_model_root("draco");
|
|
59
60
|
}
|
|
60
|
-
export const needle_mesh_transform = function (context
|
|
61
|
+
export const needle_mesh_transform = function (context) {
|
|
61
62
|
return async (document, _context) => {
|
|
62
63
|
// TODO: needs to be moved into NEEDLE_progressive
|
|
63
|
-
await simplifierTransform(document, _context,
|
|
64
|
+
await simplifierTransform(document, _context, context);
|
|
64
65
|
await applyMeshCompression(document, _context, context);
|
|
65
66
|
};
|
|
66
67
|
};
|
|
@@ -88,21 +89,28 @@ async function applyMeshCompression(document, transform_context, context) {
|
|
|
88
89
|
document.getLogger().info(`[${NAME}]: โ will apply draco mesh compression (default)`);
|
|
89
90
|
return draco()(document, transform_context);
|
|
90
91
|
}
|
|
91
|
-
async function simplifierTransform(document, _context,
|
|
92
|
+
async function simplifierTransform(document, _context, meshContext) {
|
|
92
93
|
const simplifier = MeshoptSimplifier;
|
|
93
94
|
if (!simplifier) {
|
|
94
95
|
console.log(`WARN: [${NAME}] simplifier dependency required โ install "meshoptimizer".`);
|
|
95
96
|
return false;
|
|
96
97
|
}
|
|
97
98
|
const logger = document.getLogger();
|
|
98
|
-
const
|
|
99
|
-
|
|
100
|
-
|
|
99
|
+
const isVRM = meshContext.file?.toLowerCase().endsWith(".vrm");
|
|
100
|
+
const options = {
|
|
101
|
+
...SIMPLIFY_DEFAULTS,
|
|
102
|
+
simplifier: simplifier,
|
|
103
|
+
ratio: 0.75,
|
|
104
|
+
error: 0.0005,
|
|
105
|
+
};
|
|
101
106
|
const weldOptions = {
|
|
102
107
|
...WELD_DEFAULTS,
|
|
103
|
-
tolerance: 0.00000000001,
|
|
104
108
|
overwrite: true,
|
|
105
109
|
};
|
|
110
|
+
// let meshInformation: MeshInformation | null = null;
|
|
111
|
+
// if (isVRM) {
|
|
112
|
+
// meshInformation = getMeshInformation(document);
|
|
113
|
+
// }
|
|
106
114
|
for (const mesh of document.getRoot().listMeshes()) {
|
|
107
115
|
let i = 0;
|
|
108
116
|
for (const prim of mesh.listPrimitives()) {
|
|
@@ -119,9 +127,25 @@ async function simplifierTransform(document, _context, _options) {
|
|
|
119
127
|
const ext = prim.getExtension(NEEDLE_mesh_compression.EXTENSION_NAME);
|
|
120
128
|
const mesh_compression = ext?.model;
|
|
121
129
|
const simplifyOpts = { ...options };
|
|
122
|
-
if (
|
|
130
|
+
if (isVRM) {
|
|
131
|
+
const density = calculateMeshDensity(prim);
|
|
132
|
+
if (density > 0) {
|
|
133
|
+
const targetDensity = 2_000;
|
|
134
|
+
const t = (1 / density) * targetDensity;
|
|
135
|
+
simplifyOpts.ratio = Math.min(Math.max(.1, t), .95);
|
|
136
|
+
simplifyOpts.error = 15 / density;
|
|
137
|
+
simplifyOpts.lockBorder = true;
|
|
138
|
+
}
|
|
139
|
+
else {
|
|
140
|
+
simplifyOpts.ratio = 0.8;
|
|
141
|
+
simplifyOpts.error = 0.001;
|
|
142
|
+
simplifyOpts.lockBorder = true;
|
|
143
|
+
}
|
|
144
|
+
logger.info(`${mesh.getName()}: Density: ${density.toFixed(2)}, Ratio: ${simplifyOpts.ratio.toFixed(2)}, Error: ${simplifyOpts.error}`);
|
|
145
|
+
}
|
|
146
|
+
if (!ext && !isVRM) {
|
|
123
147
|
// If no extension exists on the primitive dont apply any simplification
|
|
124
|
-
//
|
|
148
|
+
// AND if the file is not a VRM file
|
|
125
149
|
continue;
|
|
126
150
|
}
|
|
127
151
|
if (mesh_compression) {
|
|
@@ -9,7 +9,7 @@ import { addMetaToCache, addToCache, getHash, getKey, tryGetFromCache, tryGetMet
|
|
|
9
9
|
import sharp from "sharp";
|
|
10
10
|
import { detectTextureCompressionMode, ensureIsDirectory, foreachGLTF, generateGuid, getOutputPath, isLightmap, setLightmapName, ioTryReadWithMissingResources, validateImageMimeType, writeNodeIO } from '../utils/index.js';
|
|
11
11
|
import { needle_asset, writeNeedleAsset } from './needle_asset.js';
|
|
12
|
-
import { MeshoptDecoder, MeshoptSimplifier } from 'meshoptimizer';
|
|
12
|
+
import { MeshoptDecoder, MeshoptEncoder, MeshoptSimplifier } from 'meshoptimizer';
|
|
13
13
|
import { compressPrimitive, determineMeshCompression } from './needle_mesh_transform.js';
|
|
14
14
|
import { calculateMeshDensity, getMeshInformation } from "../utils/mesh.js";
|
|
15
15
|
export async function make_progressive(opts) {
|
|
@@ -29,6 +29,19 @@ export async function make_progressive(opts) {
|
|
|
29
29
|
if (!res)
|
|
30
30
|
success = false;
|
|
31
31
|
});
|
|
32
|
+
if (success) {
|
|
33
|
+
// Make sure all results exist
|
|
34
|
+
// It's possible that files were deleted while omitting LODs - we want to remove them from the results list
|
|
35
|
+
const resultsCount = opts.results.length;
|
|
36
|
+
for (let i = opts.results.length - 1; i >= 0; i--) {
|
|
37
|
+
if (!existsSync(opts.results[i])) {
|
|
38
|
+
opts.results.splice(i, 1);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
const resultsChanged = resultsCount !== opts.results.length;
|
|
42
|
+
if (opts.debug)
|
|
43
|
+
console.log(`Progressive Transform: ${opts.results.length} files processed. ${resultsChanged ? "Some results were removed" : ""}`);
|
|
44
|
+
}
|
|
32
45
|
return success;
|
|
33
46
|
}
|
|
34
47
|
function registerExtensions(io) {
|
|
@@ -58,6 +71,7 @@ async function onProcess(file, opts) {
|
|
|
58
71
|
registerExtensions(io);
|
|
59
72
|
io.registerDependencies({
|
|
60
73
|
'meshopt.decoder': MeshoptDecoder,
|
|
74
|
+
'meshopt.encoder': MeshoptEncoder,
|
|
61
75
|
'draco3d.decoder': await draco3d.createDecoderModule(),
|
|
62
76
|
'draco3d.encoder': await draco3d.createEncoderModule(),
|
|
63
77
|
});
|
|
@@ -533,7 +547,7 @@ export function make_progressive_meshes(filePath, _options, opts) {
|
|
|
533
547
|
// const t = l / MAX_LODS;
|
|
534
548
|
const latest = LODS[LODS.length - 1];
|
|
535
549
|
const ratio = latest.ratio * .5;
|
|
536
|
-
let error = Math.min(.013 * (l + 1), 0.
|
|
550
|
+
let error = Math.min(.013 * (l + 1), 0.003);
|
|
537
551
|
if (opts.config.usecase === "product") {
|
|
538
552
|
error *= .5;
|
|
539
553
|
}
|
|
@@ -544,6 +558,7 @@ export function make_progressive_meshes(filePath, _options, opts) {
|
|
|
544
558
|
// toleranceNormal: 0.1 + t * 0.1,
|
|
545
559
|
});
|
|
546
560
|
}
|
|
561
|
+
logger.debug(`[${NAME}]: Generating mesh LODs:\n${JSON.stringify(LODS, undefined, 2)}`);
|
|
547
562
|
/** Map of visited GUID -> extension */
|
|
548
563
|
const visited = new Map();
|
|
549
564
|
const root = doc.getRoot();
|
|
@@ -659,7 +674,7 @@ export function make_progressive_meshes(filePath, _options, opts) {
|
|
|
659
674
|
if (shouldOmitLevel) {
|
|
660
675
|
logger.debug(`[${NAME}] Omitting LOD ${level} for \"${mesh.getName()}\" at \"${lodInfo.path}\"`);
|
|
661
676
|
generatedLODInfos.splice(i, 1);
|
|
662
|
-
|
|
677
|
+
deleteFile(fullPath, opts);
|
|
663
678
|
i--;
|
|
664
679
|
}
|
|
665
680
|
else {
|
|
@@ -697,7 +712,7 @@ export function make_progressive_meshes(filePath, _options, opts) {
|
|
|
697
712
|
logger.info(`[${NAME}]: Flag mesh LOD ${generatedLODInfos.length} (embedded): vertex count difference below threshold (removed ${difference} vertices, ${(-diff01 * 100).toFixed(2)} %) โ will remove all LODs. \"${mesh.getName()}\"`);
|
|
698
713
|
try {
|
|
699
714
|
for (const lodInfo of generatedLODInfos) {
|
|
700
|
-
|
|
715
|
+
deleteFile(outputDirectory + "/" + lodInfo.path, opts);
|
|
701
716
|
}
|
|
702
717
|
}
|
|
703
718
|
catch (err) {
|
|
@@ -742,6 +757,9 @@ export function make_progressive_meshes(filePath, _options, opts) {
|
|
|
742
757
|
}
|
|
743
758
|
});
|
|
744
759
|
}
|
|
760
|
+
function deleteFile(filePath, _context) {
|
|
761
|
+
rmSync(filePath);
|
|
762
|
+
}
|
|
745
763
|
function clonePrimitiveData(prim, logger) {
|
|
746
764
|
const semantics = prim.listSemantics();
|
|
747
765
|
if (semantics.some(s => s.includes("JOINTS"))) {
|
|
@@ -842,6 +860,8 @@ async function createMeshLod(inputFileName, doc, basePath, level, mesh, meshInfo
|
|
|
842
860
|
const newAttr = newDoc.createAccessor();
|
|
843
861
|
newAttr.setArray(attr.getArray());
|
|
844
862
|
newAttr.setType(attr.getType());
|
|
863
|
+
newAttr.setSparse(attr.getSparse());
|
|
864
|
+
newAttr.setNormalized(attr.getNormalized());
|
|
845
865
|
newPrim.setAttribute(semantics[i], newAttr);
|
|
846
866
|
}
|
|
847
867
|
else {
|
|
@@ -853,20 +873,29 @@ async function createMeshLod(inputFileName, doc, basePath, level, mesh, meshInfo
|
|
|
853
873
|
const newIndices = newDoc.createAccessor();
|
|
854
874
|
newIndices.setArray(indices.getArray());
|
|
855
875
|
newIndices.setType(indices.getType());
|
|
876
|
+
newIndices.setSparse(indices.getSparse());
|
|
877
|
+
newIndices.setNormalized(indices.getNormalized());
|
|
856
878
|
newPrim.setIndices(newIndices);
|
|
857
879
|
}
|
|
858
880
|
const targets = prim.listTargets();
|
|
859
881
|
for (const target of targets) {
|
|
860
882
|
const newTarget = newDoc.createPrimitiveTarget(target.getName());
|
|
861
883
|
newPrim.addTarget(newTarget);
|
|
862
|
-
|
|
884
|
+
// const attributes = target.listAttributes();
|
|
885
|
+
const semantics = target.listSemantics();
|
|
886
|
+
for (const attr of semantics) {
|
|
863
887
|
const accessor = target.getAttribute(attr);
|
|
864
888
|
if (accessor) {
|
|
865
889
|
const newAccessor = newDoc.createAccessor();
|
|
866
890
|
newAccessor.setArray(accessor.getArray());
|
|
867
891
|
newAccessor.setType(accessor.getType());
|
|
892
|
+
newAccessor.setSparse(accessor.getSparse());
|
|
893
|
+
newAccessor.setNormalized(accessor.getNormalized());
|
|
868
894
|
newTarget.setAttribute(attr, newAccessor);
|
|
869
895
|
}
|
|
896
|
+
else {
|
|
897
|
+
console.error(`[${NAME}] Missing target attribute ${attr} in mesh LOD ${level}`);
|
|
898
|
+
}
|
|
870
899
|
}
|
|
871
900
|
}
|
|
872
901
|
;
|
package/dist/utils/mesh.d.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { Mesh, Primitive } from "@gltf-transform/core";
|
|
1
|
+
import { Document, Mesh, Primitive } from "@gltf-transform/core";
|
|
2
2
|
export declare type MeshInformation = {
|
|
3
3
|
readonly maxVertexCount: number;
|
|
4
4
|
readonly totalVertexCount: number;
|
|
5
5
|
readonly totalIndexCount: number;
|
|
6
6
|
};
|
|
7
|
-
export declare function getMeshInformation(mesh: Mesh): MeshInformation;
|
|
7
|
+
export declare function getMeshInformation(mesh: Mesh | Document): MeshInformation;
|
|
8
8
|
/**
|
|
9
9
|
* Calculate the density of a mesh.
|
|
10
10
|
* @param prim - The primitive to calculate the density for.
|
package/dist/utils/mesh.js
CHANGED
|
@@ -1,18 +1,35 @@
|
|
|
1
|
+
import { Mesh } from "@gltf-transform/core";
|
|
1
2
|
export function getMeshInformation(mesh) {
|
|
2
3
|
let maxVertexCount = 0;
|
|
3
4
|
let totalIndexCount = 0;
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
5
|
+
let totalVertexCount = 0;
|
|
6
|
+
if (mesh instanceof Mesh) {
|
|
7
|
+
totalVertexCount = mesh.listPrimitives().reduce((acc, prim) => {
|
|
8
|
+
const indices = prim.getIndices()?.getCount() || 0;
|
|
9
|
+
totalIndexCount += indices;
|
|
10
|
+
const vertices = prim.getAttribute('POSITION')?.getCount() || 0;
|
|
11
|
+
maxVertexCount = Math.max(maxVertexCount, vertices);
|
|
12
|
+
return acc + vertices;
|
|
13
|
+
}, 0);
|
|
14
|
+
return {
|
|
15
|
+
maxVertexCount,
|
|
16
|
+
totalVertexCount,
|
|
17
|
+
totalIndexCount,
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
else {
|
|
21
|
+
mesh.getRoot().listMeshes().forEach(mesh => {
|
|
22
|
+
const info = getMeshInformation(mesh);
|
|
23
|
+
maxVertexCount = Math.max(maxVertexCount, info.maxVertexCount);
|
|
24
|
+
totalVertexCount += info.totalVertexCount;
|
|
25
|
+
totalIndexCount += info.totalIndexCount;
|
|
26
|
+
});
|
|
27
|
+
return {
|
|
28
|
+
maxVertexCount,
|
|
29
|
+
totalVertexCount,
|
|
30
|
+
totalIndexCount,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
16
33
|
}
|
|
17
34
|
/**
|
|
18
35
|
* Calculate the density of a mesh.
|
package/dist/utils/nodeio.js
CHANGED
|
@@ -1,9 +1,15 @@
|
|
|
1
|
+
import { existsSync, mkdirSync } from "fs";
|
|
1
2
|
import { writeFile } from "fs/promises";
|
|
3
|
+
import path from "path";
|
|
2
4
|
import sharp from "sharp";
|
|
3
5
|
/**
|
|
4
6
|
* Handle writing glTf, GLB and VRM files
|
|
5
7
|
*/
|
|
6
8
|
export async function writeNodeIO(nodeio, filename, document) {
|
|
9
|
+
const directoryPath = path.dirname(filename);
|
|
10
|
+
if (!existsSync(directoryPath)) {
|
|
11
|
+
mkdirSync(directoryPath);
|
|
12
|
+
}
|
|
7
13
|
// see https://github.com/donmccurdy/glTF-Transform/issues/1390
|
|
8
14
|
if (filename.endsWith(".vrm")) {
|
|
9
15
|
const bin = await nodeio.writeBinary(document);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const version = "2.7.0-alpha.1";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const version = "2.7.0-alpha.1";
|
package/dist/utils/version.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@needle-tools/gltf-build-pipeline",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.7.0-alpha.1",
|
|
4
4
|
"description": "Pipeline and tools for optimizing gltf files using gltf-transform and compression settings within glTF extensions",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -47,9 +47,9 @@
|
|
|
47
47
|
},
|
|
48
48
|
"dependencies": {
|
|
49
49
|
"@donmccurdy/caporal": "~0.0.10",
|
|
50
|
-
"@gltf-transform/core": "^4.
|
|
51
|
-
"@gltf-transform/extensions": "^4.
|
|
52
|
-
"@gltf-transform/functions": "^4.
|
|
50
|
+
"@gltf-transform/core": "^4.1.1",
|
|
51
|
+
"@gltf-transform/extensions": "^4.1.1",
|
|
52
|
+
"@gltf-transform/functions": "^4.1.1",
|
|
53
53
|
"command-exists": "^1.2.9",
|
|
54
54
|
"command-line-args": "^5.2.1",
|
|
55
55
|
"draco3dgltf": "^1.5.3",
|