@needle-tools/gltf-build-pipeline 1.3.1 → 1.4.1-beta
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 +8 -0
- package/dist/extensions/NEEDLE_mesh_compression/NEEDLE_mesh_compression.d.ts +3 -2
- package/dist/extensions/NEEDLE_mesh_compression/NEEDLE_mesh_compression.js +26 -0
- package/dist/extensions/NEEDLE_opaque/NEEDLE_opaque.js +1 -1
- package/dist/extensions/NEEDLE_opaque/index.js +1 -1
- package/dist/extensions/NEEDLE_progressive/NEEDLE_progressive.d.ts +4 -4
- package/dist/extensions/NEEDLE_progressive/NEEDLE_progressive.js +42 -24
- package/dist/extensions/NEEDLE_progressive_mesh_settings/index.d.ts +20 -0
- package/dist/extensions/NEEDLE_progressive_mesh_settings/index.js +44 -0
- package/dist/extensions/index.d.ts +1 -0
- package/dist/extensions/index.js +1 -0
- package/dist/transforms/needle_mesh_transform.d.ts +3 -2
- package/dist/transforms/needle_mesh_transform.js +2 -1
- package/dist/transforms/needle_progressive.d.ts +1 -0
- package/dist/transforms/needle_progressive.js +208 -20
- package/dist/utils/fileutils.js +1 -1
- package/package.json +4 -4
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,14 @@ 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
|
+
## [1.4.1-beta] - 2023-03-13
|
|
8
|
+
- change: update gltf-transform to `^3.10.0`
|
|
9
|
+
- change: progressive mesh transform does now set ratio for LODs (see [simplifier issue 661 on github](https://github.com/zeux/meshoptimizer/issues/661))
|
|
10
|
+
- fix: issue where progressive meshes were not generated if a mesh was multiple times in the glTF
|
|
11
|
+
|
|
12
|
+
## [1.4.0-beta] - 2023-03-12
|
|
13
|
+
- add: progressive mesh transform to automatically generate mesh LOD levels (currently we only generate one low poly version)
|
|
14
|
+
|
|
7
15
|
## [1.3.1] - 2023-10-20
|
|
8
16
|
- fix: meshopt with khr_animation_2 extension was not correctly processed: animated node and target were not passed to gltf-transform which caused a wrong processing of meshopt. We now pass this information to the gltf-transform AnimationChannel object
|
|
9
17
|
- change: bump gltf-transform to 3.7.3
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Extension, ExtensionProperty, PropertyType, ReaderContext } from "@gltf-transform/core";
|
|
1
|
+
import { Extension, ExtensionProperty, PropertyType, ReaderContext, WriterContext } from "@gltf-transform/core";
|
|
2
2
|
export declare class NEEDLE_mesh_compression_model_root {
|
|
3
3
|
compression: "none" | "draco" | "meshopt";
|
|
4
4
|
}
|
|
@@ -13,7 +13,7 @@ export declare class NEEDLE_mesh_compression extends Extension {
|
|
|
13
13
|
extensionName: string;
|
|
14
14
|
create(model: NEEDLE_mesh_compression_model | NEEDLE_mesh_compression_model_root): NEEDLE_mesh_compression_property;
|
|
15
15
|
read(context: ReaderContext): this;
|
|
16
|
-
write(ctx:
|
|
16
|
+
write(ctx: WriterContext): this;
|
|
17
17
|
}
|
|
18
18
|
export declare class NEEDLE_mesh_compression_property extends ExtensionProperty {
|
|
19
19
|
static EXTENSION_NAME: string;
|
|
@@ -22,4 +22,5 @@ export declare class NEEDLE_mesh_compression_property extends ExtensionProperty
|
|
|
22
22
|
propertyType: string;
|
|
23
23
|
init(): void;
|
|
24
24
|
model: NEEDLE_mesh_compression_model | NEEDLE_mesh_compression_model_root;
|
|
25
|
+
copy(other: this, resolve?: (property: ExtensionProperty) => ExtensionProperty): this;
|
|
25
26
|
}
|
|
@@ -44,6 +44,28 @@ export class NEEDLE_mesh_compression extends Extension {
|
|
|
44
44
|
return this;
|
|
45
45
|
}
|
|
46
46
|
write(ctx) {
|
|
47
|
+
const root = this.document.getRoot();
|
|
48
|
+
const rootExtension = root.getExtension(EXTENSION_NAME);
|
|
49
|
+
if (rootExtension) {
|
|
50
|
+
ctx.jsonDoc.json.extensions = ctx.jsonDoc.json.extensions || {};
|
|
51
|
+
ctx.jsonDoc.json.extensions[EXTENSION_NAME] = rootExtension.model;
|
|
52
|
+
}
|
|
53
|
+
for (const mesh of root.listMeshes()) {
|
|
54
|
+
const index = ctx.meshIndexMap.get(mesh);
|
|
55
|
+
const meshDef = ctx.jsonDoc.json.meshes[index];
|
|
56
|
+
const primitives = mesh.listPrimitives();
|
|
57
|
+
for (let i = 0; i < primitives.length; i++) {
|
|
58
|
+
const primitive = primitives[i];
|
|
59
|
+
const ext = primitive.getExtension(EXTENSION_NAME);
|
|
60
|
+
if (ext) {
|
|
61
|
+
const prim = meshDef.primitives[i];
|
|
62
|
+
if (prim) {
|
|
63
|
+
prim.extensions = meshDef.extensions || {};
|
|
64
|
+
prim.extensions[EXTENSION_NAME] = ext.model;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
47
69
|
return this;
|
|
48
70
|
}
|
|
49
71
|
}
|
|
@@ -54,4 +76,8 @@ export class NEEDLE_mesh_compression_property extends ExtensionProperty {
|
|
|
54
76
|
propertyType = EXTENSION_NAME;
|
|
55
77
|
init() { }
|
|
56
78
|
model;
|
|
79
|
+
copy(other, resolve = null) {
|
|
80
|
+
this.model = { ...other.model };
|
|
81
|
+
return super.copy(other, resolve);
|
|
82
|
+
}
|
|
57
83
|
}
|
|
@@ -415,7 +415,7 @@ class TextureHandler {
|
|
|
415
415
|
}
|
|
416
416
|
});
|
|
417
417
|
if (foundTexture === false) {
|
|
418
|
-
if (this.key !== "EXT_texture_exr"
|
|
418
|
+
if (this.key !== "EXT_texture_exr" && this.debug)
|
|
419
419
|
console.warn("WARN: failed to re-assign extension:", this.key, this.index, this.tryGetSourceIndex(this.textureDefinition), textureDefs.length);
|
|
420
420
|
}
|
|
421
421
|
// this.document
|
|
@@ -13,6 +13,6 @@ export const ALL_EXTENSIONS = [
|
|
|
13
13
|
createOpaqueExtension("NEEDLE_lightmaps", PropertyType.ROOT, { ...opaqueOptions }),
|
|
14
14
|
createOpaqueExtension("NEEDLE_lighting_settings", PropertyType.ROOT, { ...opaqueOptions }),
|
|
15
15
|
createOpaqueExtension("NEEDLE_render_objects", PropertyType.ROOT, { ...opaqueOptions }),
|
|
16
|
-
createOpaqueExtension("NEEDLE_progressive", [PropertyType.TEXTURE, PropertyType.MATERIAL], { ...opaqueOptions }),
|
|
16
|
+
createOpaqueExtension("NEEDLE_progressive", [PropertyType.TEXTURE, PropertyType.MATERIAL, PropertyType.MESH], { ...opaqueOptions }),
|
|
17
17
|
createOpaqueExtension("EXT_texture_exr", [PropertyType.TEXTURE], { ...opaqueOptions }),
|
|
18
18
|
];
|
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
import { Extension, ExtensionProperty, PropertyType, ReaderContext, WriterContext } from "@gltf-transform/core";
|
|
2
|
-
export declare const
|
|
1
|
+
import { Extension, ExtensionProperty, Graph, IProperty, Property, PropertyType, ReaderContext, WriterContext } from "@gltf-transform/core";
|
|
2
|
+
export declare const NEEDLE_PROGRESSIVE_EXTENSION_NAME = "NEEDLE_progressive";
|
|
3
3
|
export declare interface IProgressiveTextureInfo {
|
|
4
|
-
uri?: string;
|
|
5
4
|
guid: string;
|
|
6
|
-
|
|
5
|
+
uri?: string | Array<string>;
|
|
7
6
|
}
|
|
8
7
|
export declare function generateGuid(): string;
|
|
9
8
|
export declare class NEEDLE_progressive extends Extension {
|
|
10
9
|
static EXTENSION_NAME: string;
|
|
11
10
|
extensionName: string;
|
|
11
|
+
static create(graph: Graph<Property<IProperty>>, data: IProgressiveTextureInfo): NEEDLE_progressive_texture_property;
|
|
12
12
|
create(data: IProgressiveTextureInfo): NEEDLE_progressive_texture_property;
|
|
13
13
|
read(_context: ReaderContext): this;
|
|
14
14
|
write(context: WriterContext): this;
|
|
@@ -1,52 +1,70 @@
|
|
|
1
1
|
import { Extension, ExtensionProperty, PropertyType, uuid } from "@gltf-transform/core";
|
|
2
2
|
// TODO: rename to NEEDLE_progressive_texture
|
|
3
|
-
export const
|
|
3
|
+
export const NEEDLE_PROGRESSIVE_EXTENSION_NAME = "NEEDLE_progressive";
|
|
4
4
|
export function generateGuid() {
|
|
5
5
|
// TODO: replace with same uuid package as in needle engine
|
|
6
6
|
return uuid();
|
|
7
7
|
}
|
|
8
8
|
export class NEEDLE_progressive extends Extension {
|
|
9
|
-
static EXTENSION_NAME =
|
|
10
|
-
extensionName =
|
|
11
|
-
create(data) {
|
|
12
|
-
const ext = new NEEDLE_progressive_texture_property(
|
|
9
|
+
static EXTENSION_NAME = NEEDLE_PROGRESSIVE_EXTENSION_NAME;
|
|
10
|
+
extensionName = NEEDLE_PROGRESSIVE_EXTENSION_NAME;
|
|
11
|
+
static create(graph, data) {
|
|
12
|
+
const ext = new NEEDLE_progressive_texture_property(graph);
|
|
13
13
|
ext.model = data;
|
|
14
14
|
return ext;
|
|
15
15
|
}
|
|
16
|
+
create(data) {
|
|
17
|
+
return NEEDLE_progressive.create(this.document.getGraph(), data);
|
|
18
|
+
}
|
|
16
19
|
read(_context) {
|
|
17
20
|
// console.log("READ " + NEEDLE_PROGRESSIVE_TEXTURE_EXTENSION_NAME);
|
|
18
21
|
return this;
|
|
19
22
|
}
|
|
20
23
|
write(context) {
|
|
21
24
|
// console.log("WRITE " + NEEDLE_PROGRESSIVE_TEXTURE_EXTENSION_NAME);
|
|
22
|
-
const textureDefinitions = context.jsonDoc.json.textures;
|
|
23
|
-
if (!textureDefinitions)
|
|
24
|
-
return;
|
|
25
25
|
const root = this.document.getRoot();
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
def.
|
|
26
|
+
const textureDefinitions = context.jsonDoc.json.textures;
|
|
27
|
+
if (textureDefinitions.length) {
|
|
28
|
+
const textures = root.listTextures();
|
|
29
|
+
// we use the texture image map to find the texture definition to write the extension to
|
|
30
|
+
const textureImageIndexMap = context.imageIndexMap;
|
|
31
|
+
for (const tex of textures) {
|
|
32
|
+
const prop = tex.getExtension(NEEDLE_PROGRESSIVE_EXTENSION_NAME);
|
|
33
|
+
if (prop && prop.model) {
|
|
34
|
+
// get the image this texture is using
|
|
35
|
+
const imageIndex = textureImageIndexMap.get(tex);
|
|
36
|
+
// find the texture definition that uses this image and add the extension
|
|
37
|
+
for (const def of textureDefinitions) {
|
|
38
|
+
if (def.source === imageIndex) {
|
|
39
|
+
def.extensions = def.extensions || {};
|
|
40
|
+
def.extensions[NEEDLE_PROGRESSIVE_EXTENSION_NAME] = prop.model;
|
|
41
|
+
}
|
|
39
42
|
}
|
|
40
43
|
}
|
|
41
44
|
}
|
|
42
45
|
}
|
|
46
|
+
const meshDefinitions = context.jsonDoc.json.meshes;
|
|
47
|
+
if (meshDefinitions?.length) {
|
|
48
|
+
const meshIndexMap = context.meshIndexMap;
|
|
49
|
+
const meshes = root.listMeshes();
|
|
50
|
+
for (let i = 0; i < meshes.length; i++) {
|
|
51
|
+
const mesh = meshes[i];
|
|
52
|
+
const meshIndex = meshIndexMap.get(mesh);
|
|
53
|
+
const meshExt = mesh.getExtension(NEEDLE_PROGRESSIVE_EXTENSION_NAME);
|
|
54
|
+
if (meshExt && meshExt.model) {
|
|
55
|
+
const meshDef = meshDefinitions[meshIndex];
|
|
56
|
+
meshDef.extensions = meshDef.extensions || {};
|
|
57
|
+
meshDef.extensions[NEEDLE_PROGRESSIVE_EXTENSION_NAME] = meshExt.model;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
43
61
|
return this;
|
|
44
62
|
}
|
|
45
63
|
}
|
|
46
64
|
export class NEEDLE_progressive_texture_property extends ExtensionProperty {
|
|
47
|
-
static EXTENSION_NAME =
|
|
48
|
-
extensionName =
|
|
49
|
-
parentTypes = [PropertyType.TEXTURE];
|
|
65
|
+
static EXTENSION_NAME = NEEDLE_PROGRESSIVE_EXTENSION_NAME;
|
|
66
|
+
extensionName = NEEDLE_PROGRESSIVE_EXTENSION_NAME;
|
|
67
|
+
parentTypes = [PropertyType.TEXTURE, PropertyType.MESH];
|
|
50
68
|
propertyType = "progressive_texture";
|
|
51
69
|
init() { }
|
|
52
70
|
model;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { Extension, ExtensionProperty, PropertyType, ReaderContext, WriterContext } from "@gltf-transform/core";
|
|
2
|
+
export declare interface IProgressiveMeshSettings {
|
|
3
|
+
guid: string;
|
|
4
|
+
}
|
|
5
|
+
export declare class NEEDLE_progressive_mesh_settings extends Extension {
|
|
6
|
+
static EXTENSION_NAME: string;
|
|
7
|
+
extensionName: string;
|
|
8
|
+
read(context: ReaderContext): this;
|
|
9
|
+
write(context: WriterContext): this;
|
|
10
|
+
}
|
|
11
|
+
export declare class NEEDLE_progressive_mesh_settings_property extends ExtensionProperty implements IProgressiveMeshSettings {
|
|
12
|
+
static EXTENSION_NAME: string;
|
|
13
|
+
readonly extensionName: string;
|
|
14
|
+
parentTypes: PropertyType[];
|
|
15
|
+
propertyType: string;
|
|
16
|
+
init(): void;
|
|
17
|
+
get isValid(): boolean;
|
|
18
|
+
maxSize: number;
|
|
19
|
+
guid: string;
|
|
20
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { Extension, ExtensionProperty, PropertyType } from "@gltf-transform/core";
|
|
2
|
+
const EXTENSION_NAME = "NEEDLE_progressive_mesh_settings";
|
|
3
|
+
export class NEEDLE_progressive_mesh_settings extends Extension {
|
|
4
|
+
static EXTENSION_NAME = EXTENSION_NAME;
|
|
5
|
+
extensionName = EXTENSION_NAME;
|
|
6
|
+
read(context) {
|
|
7
|
+
const definitions = context.jsonDoc.json.meshes;
|
|
8
|
+
if (!definitions)
|
|
9
|
+
return this;
|
|
10
|
+
const root = this.document.getRoot();
|
|
11
|
+
const meshes = root.listMeshes();
|
|
12
|
+
for (let i = 0; i < definitions.length; i++) {
|
|
13
|
+
const def = definitions[i];
|
|
14
|
+
if (def.extensions && def.extensions[EXTENSION_NAME]) {
|
|
15
|
+
const ext = def.extensions[EXTENSION_NAME];
|
|
16
|
+
const prop = new NEEDLE_progressive_mesh_settings_property(this.document.getGraph(), "");
|
|
17
|
+
Object.assign(prop, ext);
|
|
18
|
+
let index = i;
|
|
19
|
+
const mesh = meshes[index];
|
|
20
|
+
if (mesh)
|
|
21
|
+
mesh.setExtension(EXTENSION_NAME, prop);
|
|
22
|
+
else {
|
|
23
|
+
console.warn(`WARN: Progressive mesh at index ${i} was not found in the glTF file. This might result in falsely loaded textures for ${ext.guid} / name \"${def.name}\"`);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return this;
|
|
28
|
+
}
|
|
29
|
+
write(context) {
|
|
30
|
+
return this;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
export class NEEDLE_progressive_mesh_settings_property extends ExtensionProperty {
|
|
34
|
+
static EXTENSION_NAME = EXTENSION_NAME;
|
|
35
|
+
extensionName = EXTENSION_NAME;
|
|
36
|
+
parentTypes = [PropertyType.MESH];
|
|
37
|
+
propertyType = EXTENSION_NAME;
|
|
38
|
+
init() { }
|
|
39
|
+
get isValid() {
|
|
40
|
+
return this.maxSize !== undefined && this.maxSize > 0;
|
|
41
|
+
}
|
|
42
|
+
maxSize;
|
|
43
|
+
guid;
|
|
44
|
+
}
|
|
@@ -3,4 +3,5 @@ export * from "./NEEDLE_mesh_compression/index.js";
|
|
|
3
3
|
export * from "./NEEDLE_compression_texture/index.js";
|
|
4
4
|
export * from "./NEEDLE_progressive/index.js";
|
|
5
5
|
export * from "./NEEDLE_progressive_texture_settings/index.js";
|
|
6
|
+
export * from "./NEEDLE_progressive_mesh_settings/index.js";
|
|
6
7
|
export declare function registerExtensions(node: any): void;
|
package/dist/extensions/index.js
CHANGED
|
@@ -4,6 +4,7 @@ export * from "./NEEDLE_mesh_compression/index.js";
|
|
|
4
4
|
export * from "./NEEDLE_compression_texture/index.js";
|
|
5
5
|
export * from "./NEEDLE_progressive/index.js";
|
|
6
6
|
export * from "./NEEDLE_progressive_texture_settings/index.js";
|
|
7
|
+
export * from "./NEEDLE_progressive_mesh_settings/index.js";
|
|
7
8
|
import { ALL_EXTENSIONS } from '@gltf-transform/extensions';
|
|
8
9
|
import { ALL_EXTENSIONS as NEEDLE_EXTENSIONS } from "./NEEDLE_opaque/index.js";
|
|
9
10
|
import { NEEDLE_progressive } from './NEEDLE_progressive/index.js';
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
-
import { Transform } from "@gltf-transform/core";
|
|
2
|
-
import { SimplifyOptions } from "@gltf-transform/functions";
|
|
1
|
+
import { Transform, Document, Primitive, Mesh } from "@gltf-transform/core";
|
|
2
|
+
import { SimplifyOptions, WeldOptions } from "@gltf-transform/functions";
|
|
3
3
|
export declare const needle_mesh_transform: (_options: SimplifyOptions) => Transform;
|
|
4
|
+
export declare function compressPrimitive(document: Document, mesh: Mesh, prim: Primitive, weldOptions: Required<WeldOptions>, meshOptions: Required<SimplifyOptions>): void;
|
|
@@ -64,6 +64,7 @@ async function simplifierTransform(document, _context, _options) {
|
|
|
64
64
|
const simplifyOpts = { ...options };
|
|
65
65
|
if (!ext) {
|
|
66
66
|
// If no extension exists on the primitive dont apply any simplification
|
|
67
|
+
// console.log(`[${NAME}]: Skipping primitive simplification of mesh "${mesh.getName()}": No mesh compression extension found.`);
|
|
67
68
|
continue;
|
|
68
69
|
}
|
|
69
70
|
if (mesh_compression) {
|
|
@@ -87,7 +88,7 @@ async function simplifierTransform(document, _context, _options) {
|
|
|
87
88
|
}
|
|
88
89
|
return true;
|
|
89
90
|
}
|
|
90
|
-
function compressPrimitive(document, mesh, prim, weldOptions, meshOptions) {
|
|
91
|
+
export function compressPrimitive(document, mesh, prim, weldOptions, meshOptions) {
|
|
91
92
|
const logger = document.getLogger();
|
|
92
93
|
logger.debug(`[${NAME}]: Welding primitive of mesh "${mesh.getName()}" → tolerance: ${weldOptions.tolerance}`);
|
|
93
94
|
weldPrimitive(document, prim, weldOptions);
|
|
@@ -31,4 +31,5 @@ export declare enum TextureResizeFilter {
|
|
|
31
31
|
}
|
|
32
32
|
export declare const TEXTURE_RESIZE_DEFAULTS: TextureResizeOptions;
|
|
33
33
|
export declare function resizeTextureToMax(texture: Texture, maxSize: number, logger: ILogger): Promise<void>;
|
|
34
|
+
export declare function make_progressive_meshes(filePath: string, _options: any, opts?: ProgressiveOptions): Transform;
|
|
34
35
|
export {};
|
|
@@ -1,32 +1,40 @@
|
|
|
1
1
|
import { BufferUtils, Document, NodeIO, PropertyType } from '@gltf-transform/core';
|
|
2
|
-
import { listTextureSlots } from '@gltf-transform/functions';
|
|
3
|
-
import {
|
|
4
|
-
import { createOpaqueExtension, NEEDLE_compression_texture, NEEDLE_mesh_compression, NEEDLE_progressive_texture_settings, registerExtensions as registerDefaultExtensions } from '../extensions/index.js';
|
|
2
|
+
import { SIMPLIFY_DEFAULTS, WELD_DEFAULTS, listTextureSlots } from '@gltf-transform/functions';
|
|
3
|
+
import { NEEDLE_PROGRESSIVE_EXTENSION_NAME, NEEDLE_progressive, generateGuid } from "../extensions/NEEDLE_progressive/index.js";
|
|
4
|
+
import { createOpaqueExtension, NEEDLE_compression_texture, NEEDLE_mesh_compression, NEEDLE_progressive_mesh_settings, NEEDLE_progressive_texture_settings, registerExtensions as registerDefaultExtensions } from '../extensions/index.js';
|
|
5
5
|
import path from 'path';
|
|
6
6
|
import { existsSync } from 'fs';
|
|
7
7
|
import { addToCache, getKey, tryGetFromCache } from '../cache/index.js';
|
|
8
8
|
import sharp from "sharp";
|
|
9
9
|
import { foreachGLTF } from '../utils/index.js';
|
|
10
10
|
import { needle_asset } from './needle_asset.js';
|
|
11
|
-
import { MeshoptDecoder } from 'meshoptimizer';
|
|
11
|
+
import { MeshoptDecoder, MeshoptSimplifier } from 'meshoptimizer';
|
|
12
|
+
import { compressPrimitive } from './needle_mesh_transform.js';
|
|
13
|
+
export async function make_progressive(opts) {
|
|
14
|
+
console.log("INFO: Run progressive transform: " + opts.path);
|
|
15
|
+
const file = opts.path;
|
|
16
|
+
return foreachGLTF(file, async (f) => {
|
|
17
|
+
// Don't re-process files that are already LODs
|
|
18
|
+
if (isMeshLOD(f) || isImageLOD(f)) {
|
|
19
|
+
console.log(`Progressive Transform: Ignore existing LOD file at ${f}`);
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
await process(f, opts);
|
|
23
|
+
});
|
|
24
|
+
}
|
|
12
25
|
function registerExtensions(io) {
|
|
13
26
|
registerDefaultExtensions(io);
|
|
14
27
|
io.registerExtensions([
|
|
15
28
|
NEEDLE_progressive,
|
|
16
29
|
NEEDLE_progressive_texture_settings,
|
|
30
|
+
NEEDLE_progressive_mesh_settings,
|
|
31
|
+
NEEDLE_mesh_compression,
|
|
17
32
|
//@ts-ignore
|
|
18
33
|
createOpaqueExtension(NEEDLE_compression_texture.EXTENSION_NAME, PropertyType.TEXTURE),
|
|
19
34
|
//@ts-ignore
|
|
20
|
-
createOpaqueExtension(NEEDLE_mesh_compression.EXTENSION_NAME, [PropertyType.ROOT, PropertyType.MESH, PropertyType.PRIMITIVE])
|
|
35
|
+
// createOpaqueExtension(NEEDLE_mesh_compression.EXTENSION_NAME, [PropertyType.ROOT, PropertyType.MESH, PropertyType.PRIMITIVE]),
|
|
21
36
|
]);
|
|
22
37
|
}
|
|
23
|
-
export async function make_progressive(opts) {
|
|
24
|
-
console.log("INFO: Run progressive transform: " + opts.path);
|
|
25
|
-
const file = opts.path;
|
|
26
|
-
return foreachGLTF(file, async (f) => {
|
|
27
|
-
await process(f, opts);
|
|
28
|
-
});
|
|
29
|
-
}
|
|
30
38
|
async function process(file, opts) {
|
|
31
39
|
if (!file.endsWith(".glb") && !file.endsWith(".gltf")) {
|
|
32
40
|
console.log("ERR: file is not gltf/glb");
|
|
@@ -59,7 +67,7 @@ async function process(file, opts) {
|
|
|
59
67
|
}
|
|
60
68
|
//@ts-ignore
|
|
61
69
|
document.getLogger().verbosity = 0;
|
|
62
|
-
await document.transform(needle_asset(), make_progressive_textures(file, { size: [256, 256] }, opts));
|
|
70
|
+
await document.transform(needle_asset(), make_progressive_textures(file, { size: [256, 256] }, opts), make_progressive_meshes(file, {}, opts));
|
|
63
71
|
let filename = file;
|
|
64
72
|
if (opts.debug === true) {
|
|
65
73
|
// get extension
|
|
@@ -84,7 +92,7 @@ export function createTransform(name, fn) {
|
|
|
84
92
|
Object.defineProperty(fn, 'name', { value: name });
|
|
85
93
|
return fn;
|
|
86
94
|
}
|
|
87
|
-
const NAME = '
|
|
95
|
+
const NAME = 'NEEDLE_progressive';
|
|
88
96
|
export function make_progressive_textures(filePath, _options = TEXTURE_RESIZE_DEFAULTS, opts) {
|
|
89
97
|
console.log("Make progressive: " + filePath);
|
|
90
98
|
const startTime = Date.now();
|
|
@@ -124,7 +132,7 @@ export function make_progressive_textures(filePath, _options = TEXTURE_RESIZE_DE
|
|
|
124
132
|
}
|
|
125
133
|
options.size[0] = settings.maxSize;
|
|
126
134
|
options.size[1] = settings.maxSize;
|
|
127
|
-
const existingExtension = texture.getExtension(
|
|
135
|
+
const existingExtension = texture.getExtension(NEEDLE_PROGRESSIVE_EXTENSION_NAME);
|
|
128
136
|
if (existingExtension !== undefined && existingExtension !== null) {
|
|
129
137
|
logger.debug(`${NAME}: Skipping, texture is already progressive ${textureIndex}`);
|
|
130
138
|
continue;
|
|
@@ -141,10 +149,9 @@ export function make_progressive_textures(filePath, _options = TEXTURE_RESIZE_DE
|
|
|
141
149
|
}
|
|
142
150
|
const relName = path.relative(directory, outPath);
|
|
143
151
|
// TODO: this path should be added to the NEEDLE dependencies extension
|
|
144
|
-
texture.setExtension(
|
|
145
|
-
uri: relName,
|
|
152
|
+
texture.setExtension(NEEDLE_PROGRESSIVE_EXTENSION_NAME, progressiveExt.create({
|
|
146
153
|
guid: textureGuid,
|
|
147
|
-
|
|
154
|
+
uri: relName,
|
|
148
155
|
}));
|
|
149
156
|
const key = getKey(texture);
|
|
150
157
|
if (opts.useCache) {
|
|
@@ -219,14 +226,16 @@ function createTextureLod(basePath, level, texture, slots, guid, textureIndex) {
|
|
|
219
226
|
}
|
|
220
227
|
// add progressive texture extension
|
|
221
228
|
const progressiveExt = newDoc.createExtension(NEEDLE_progressive);
|
|
222
|
-
newTexture.setExtension(
|
|
229
|
+
newTexture.setExtension(NEEDLE_PROGRESSIVE_EXTENSION_NAME, progressiveExt.create({
|
|
223
230
|
guid: guid,
|
|
224
231
|
}));
|
|
225
232
|
const newIo = new NodeIO();
|
|
226
233
|
registerExtensions(newIo);
|
|
227
234
|
const ext = ".glb";
|
|
228
235
|
const imageName = "image_" + level + "_" + guid;
|
|
229
|
-
const
|
|
236
|
+
const filename = imageName + ext;
|
|
237
|
+
const outName = basePath + filename;
|
|
238
|
+
console.log(`INFO: Write texture LOD ${level} to \"${filename}\"`);
|
|
230
239
|
newIo.write(outName, newDoc);
|
|
231
240
|
return outName;
|
|
232
241
|
}
|
|
@@ -280,3 +289,182 @@ async function resizeTexture(options, texture, logger, uri, slots) {
|
|
|
280
289
|
.toBuffer();
|
|
281
290
|
texture.setImage(BufferUtils.toView(buffer));
|
|
282
291
|
}
|
|
292
|
+
export function make_progressive_meshes(filePath, _options, opts) {
|
|
293
|
+
const directory = path.dirname(filePath);
|
|
294
|
+
// const fileNameWithoutExtension = path.basename(filePath, path.extname(filePath));
|
|
295
|
+
const outputDirectory = directory + "/";
|
|
296
|
+
return createTransform("PROGRESSIVE_MESHES", async (doc, _context) => {
|
|
297
|
+
if (isMeshLOD(filePath)) {
|
|
298
|
+
console.log("Progressive Mesh Transform: skip lod at " + filePath);
|
|
299
|
+
return Promise.resolve();
|
|
300
|
+
}
|
|
301
|
+
const logger = doc.getLogger();
|
|
302
|
+
const filename = path.basename(filePath);
|
|
303
|
+
console.log("> Progressive Mesh Transform: " + filename);
|
|
304
|
+
let index = 0;
|
|
305
|
+
// the lowest LOD (last entry) will be saved in the original file
|
|
306
|
+
const LODS = [
|
|
307
|
+
{ error: 0 },
|
|
308
|
+
{ ratio: 0.5, error: 1 },
|
|
309
|
+
{ ratio: 0.25, error: 1 },
|
|
310
|
+
];
|
|
311
|
+
/** Map of visited GUID -> extension */
|
|
312
|
+
const visited = new Map();
|
|
313
|
+
const meshes = doc.getRoot().listMeshes();
|
|
314
|
+
for (const mesh of meshes) {
|
|
315
|
+
// TODO: skip meshes that are already below a certain threshold (E.g. just 4 vertices)
|
|
316
|
+
const settings = mesh.getExtension(NEEDLE_progressive_mesh_settings.EXTENSION_NAME);
|
|
317
|
+
if (!settings) {
|
|
318
|
+
logger.debug(`${NAME}: no ${NEEDLE_progressive_mesh_settings.EXTENSION_NAME} found for mesh at index ${index++}`);
|
|
319
|
+
continue;
|
|
320
|
+
}
|
|
321
|
+
const progressiveExt = doc.createExtension(NEEDLE_progressive);
|
|
322
|
+
if (!progressiveExt || !progressiveExt.create) {
|
|
323
|
+
logger.debug(`${NAME}: Skipping because progressive extension is not available. ${index++}`);
|
|
324
|
+
continue;
|
|
325
|
+
}
|
|
326
|
+
// check if we have already produced LODs for this mesh
|
|
327
|
+
let needsToGenerateLODs = true;
|
|
328
|
+
const generatedLODs = new Array();
|
|
329
|
+
if (visited.has(settings.guid)) {
|
|
330
|
+
// we can not just set the extension here...
|
|
331
|
+
logger.debug(`${NAME}: Mesh has already been processed ${index} - will use previously generated LODs`);
|
|
332
|
+
needsToGenerateLODs = false;
|
|
333
|
+
const existingPaths = visited.get(settings.guid).paths;
|
|
334
|
+
generatedLODs.push(...existingPaths);
|
|
335
|
+
}
|
|
336
|
+
if (needsToGenerateLODs) {
|
|
337
|
+
// produce LODS, skip the last level because it will be saved in the original file
|
|
338
|
+
for (let level = 0; level < LODS.length - 1; level++) {
|
|
339
|
+
const lod = LODS[level];
|
|
340
|
+
console.log(`${NAME}: Generate LOD ${level}`);
|
|
341
|
+
const guid = settings.guid;
|
|
342
|
+
const lodPath = createMeshLod(doc, outputDirectory, level, mesh, guid, lod);
|
|
343
|
+
generatedLODs.push(lodPath);
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
for (const prim of mesh.listPrimitives()) {
|
|
347
|
+
const lastLod = LODS[LODS.length - 1];
|
|
348
|
+
console.log(`${NAME}: ${filename} LOD ${LODS.length - 1}`);
|
|
349
|
+
simplifyMesh(doc, mesh, prim, lastLod);
|
|
350
|
+
}
|
|
351
|
+
const model = progressiveExt.create({
|
|
352
|
+
guid: settings.guid,
|
|
353
|
+
uri: generatedLODs.map(l => l),
|
|
354
|
+
});
|
|
355
|
+
mesh.setExtension(NEEDLE_PROGRESSIVE_EXTENSION_NAME, model);
|
|
356
|
+
index++;
|
|
357
|
+
if (!visited.has(settings.guid)) {
|
|
358
|
+
visited.set(settings.guid, {
|
|
359
|
+
model: model,
|
|
360
|
+
paths: generatedLODs,
|
|
361
|
+
});
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
console.log("< Progressive Mesh Transform: finished");
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
/**
|
|
368
|
+
* @param basePath the base path to save the lod files
|
|
369
|
+
* @param level: 0 = original, 1 = first lod, 2 = second lod, ...
|
|
370
|
+
* @param mesh the mesh
|
|
371
|
+
* @param guid a unique identifier for the mesh
|
|
372
|
+
* @param error the error to use for simplification. Higher means more simplification
|
|
373
|
+
*/
|
|
374
|
+
function createMeshLod(doc, basePath, level, mesh, guid, lodLevel) {
|
|
375
|
+
const newDoc = new Document();
|
|
376
|
+
const newScene = newDoc.createScene();
|
|
377
|
+
newDoc.createBuffer();
|
|
378
|
+
const newNode = newDoc.createNode();
|
|
379
|
+
newScene.addChild(newNode);
|
|
380
|
+
newNode.setName("node_" + level + "_" + guid);
|
|
381
|
+
newNode.setTranslation([0, 0, 0]);
|
|
382
|
+
const newMesh = newDoc.createMesh();
|
|
383
|
+
newNode.setMesh(newMesh);
|
|
384
|
+
const primitives = mesh.listPrimitives();
|
|
385
|
+
for (const prim of primitives) {
|
|
386
|
+
const newMat = newDoc.createMaterial();
|
|
387
|
+
const newPrim = newDoc.createPrimitive();
|
|
388
|
+
newMesh.addPrimitive(newPrim);
|
|
389
|
+
newPrim.setName(prim.getName());
|
|
390
|
+
newPrim.setMode(prim.getMode());
|
|
391
|
+
newPrim.setMaterial(newMat);
|
|
392
|
+
const semantics = prim.listSemantics();
|
|
393
|
+
for (let i = 0; i < semantics.length; i++) {
|
|
394
|
+
const attr = prim.getAttribute(semantics[i]);
|
|
395
|
+
if (attr) {
|
|
396
|
+
const newAttr = newDoc.createAccessor();
|
|
397
|
+
newAttr.setArray(attr.getArray());
|
|
398
|
+
newAttr.setType(attr.getType());
|
|
399
|
+
newPrim.setAttribute(semantics[i], newAttr);
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
const indices = prim.getIndices();
|
|
403
|
+
if (indices) {
|
|
404
|
+
const newIndices = newDoc.createAccessor();
|
|
405
|
+
newIndices.setArray(indices.getArray());
|
|
406
|
+
newIndices.setType(indices.getType());
|
|
407
|
+
newPrim.setIndices(newIndices);
|
|
408
|
+
}
|
|
409
|
+
const extensions = prim.listExtensions();
|
|
410
|
+
for (const ext of extensions) {
|
|
411
|
+
const PropertyClass = ext.constructor;
|
|
412
|
+
const newProp = new PropertyClass(newDoc.getGraph());
|
|
413
|
+
const copy = newProp.copy(ext);
|
|
414
|
+
const name = ext.extensionName;
|
|
415
|
+
console.log(`${NAME}: Copy primitive ${name} extension`);
|
|
416
|
+
newPrim.setExtension(name, copy);
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
if (lodLevel.error != undefined || lodLevel.ratio != undefined) {
|
|
420
|
+
for (const prim of newMesh.listPrimitives()) {
|
|
421
|
+
simplifyMesh(newDoc, newMesh, prim, lodLevel);
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
const progressiveExt = newDoc.createExtension(NEEDLE_progressive);
|
|
425
|
+
if (!progressiveExt || !progressiveExt.create) {
|
|
426
|
+
console.error("Failed to create progressive extension");
|
|
427
|
+
return;
|
|
428
|
+
}
|
|
429
|
+
newMesh.setExtension(NEEDLE_PROGRESSIVE_EXTENSION_NAME, progressiveExt.create({
|
|
430
|
+
guid: guid,
|
|
431
|
+
}));
|
|
432
|
+
// we need to compress the mesh with the same compression method as the original file
|
|
433
|
+
const root = doc.getRoot();
|
|
434
|
+
const compressionExt = root.getExtension(NEEDLE_mesh_compression.EXTENSION_NAME);
|
|
435
|
+
if (compressionExt) {
|
|
436
|
+
const newExt = newDoc.createExtension(NEEDLE_mesh_compression);
|
|
437
|
+
const newProp = newExt.create(compressionExt.model);
|
|
438
|
+
console.log(`${NAME}: Pass compression format to mesh LOD ${level} → ${JSON.stringify(compressionExt.model)}`);
|
|
439
|
+
newDoc.getRoot().setExtension(NEEDLE_mesh_compression.EXTENSION_NAME, newProp);
|
|
440
|
+
}
|
|
441
|
+
const newIo = new NodeIO();
|
|
442
|
+
registerExtensions(newIo);
|
|
443
|
+
const ext = ".glb";
|
|
444
|
+
const name = "mesh_lod_" + level + "_" + guid;
|
|
445
|
+
const filename = name + ext;
|
|
446
|
+
const outName = basePath + filename;
|
|
447
|
+
console.log(`${NAME}: Write mesh LOD ${level} to \"${filename}\" with: ${JSON.stringify(lodLevel)}`);
|
|
448
|
+
newIo.write(outName, newDoc);
|
|
449
|
+
return filename;
|
|
450
|
+
}
|
|
451
|
+
function simplifyMesh(doc, mesh, prim, lodinfo) {
|
|
452
|
+
const weldOptions = {
|
|
453
|
+
...WELD_DEFAULTS,
|
|
454
|
+
overwrite: true,
|
|
455
|
+
};
|
|
456
|
+
const simplifyOptions = {
|
|
457
|
+
...SIMPLIFY_DEFAULTS, simplifier: MeshoptSimplifier,
|
|
458
|
+
};
|
|
459
|
+
if (lodinfo.error != undefined)
|
|
460
|
+
simplifyOptions.error = lodinfo.error;
|
|
461
|
+
if (lodinfo.ratio != undefined)
|
|
462
|
+
simplifyOptions.ratio = lodinfo.ratio;
|
|
463
|
+
compressPrimitive(doc, mesh, prim, weldOptions, simplifyOptions);
|
|
464
|
+
}
|
|
465
|
+
function isMeshLOD(filepath) {
|
|
466
|
+
return filepath.includes("mesh_lod_");
|
|
467
|
+
}
|
|
468
|
+
function isImageLOD(filepath) {
|
|
469
|
+
return filepath.includes("image_");
|
|
470
|
+
}
|
package/dist/utils/fileutils.js
CHANGED
|
@@ -62,7 +62,7 @@ async function testEntry(entry, callback, context) {
|
|
|
62
62
|
// Try resolve potential relative path
|
|
63
63
|
const newPath = context.baseDirectory + "/" + entry;
|
|
64
64
|
if (existsSync(newPath) === false) {
|
|
65
|
-
console.log(`INFO: Resolved path failed: ${entry} -> ${newPath}`);
|
|
65
|
+
console.log(`INFO: Resolved path failed: ${entry} -> ${newPath} (doesnt exist or is locked by another process)`);
|
|
66
66
|
return;
|
|
67
67
|
}
|
|
68
68
|
else {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@needle-tools/gltf-build-pipeline",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.1-beta",
|
|
4
4
|
"description": "Pipeline and tools for optimizing gltf files using gltf-transform and compression settings within glTF extensions",
|
|
5
5
|
"main": "index.mjs",
|
|
6
6
|
"type": "module",
|
|
@@ -16,9 +16,9 @@
|
|
|
16
16
|
"compile": "tsc || exit 1"
|
|
17
17
|
},
|
|
18
18
|
"dependencies": {
|
|
19
|
-
"@gltf-transform/core": "^3.
|
|
20
|
-
"@gltf-transform/extensions": "^3.
|
|
21
|
-
"@gltf-transform/functions": "^3.
|
|
19
|
+
"@gltf-transform/core": "^3.10.0",
|
|
20
|
+
"@gltf-transform/extensions": "^3.10.0",
|
|
21
|
+
"@gltf-transform/functions": "^3.10.0",
|
|
22
22
|
"command-exists": "^1.2.9",
|
|
23
23
|
"command-line-args": "^5.2.1",
|
|
24
24
|
"draco3dgltf": "^1.5.3",
|