@needle-tools/gltf-build-pipeline 1.5.0-alpha.5 → 1.5.0-alpha.6

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 CHANGED
@@ -4,6 +4,9 @@ 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.5.0-alpha.6] - 2023-05-15
8
+ - fix: If not further specified then automatically use UASTC for textures that are in either `normalTexture` or `metallicOcclusionTexture` slots
9
+
7
10
  ## [1.5.0-alpha.5] - 2023-05-04
8
11
  - fix: NEEDLE_opaque should handle if VRM texture index is negative
9
12
  - change: exit with code 1 if any file packing fails
@@ -1,7 +1,7 @@
1
1
  import { Extension, ExtensionProperty, PropertyType, ReaderContext, WriterContext } from "@gltf-transform/core";
2
2
  export declare const EXTENSION_NAME = "NEEDLE_compression_texture";
3
3
  export declare interface NEEDLE_compression_texture_schema {
4
- mode: string;
4
+ mode: "none" | "auto" | "webp" | "UASTC" | "ETC1S";
5
5
  slot: string;
6
6
  /** quality in range of 0 - 1 */
7
7
  quality: number;
@@ -1,6 +1,7 @@
1
1
  import { Document, Texture } from '@gltf-transform/core';
2
2
  import { NEEDLE_compression_texture_schema } from '../extensions/index.js';
3
3
  export interface NeedleTransformContext {
4
+ file: string;
4
5
  debug?: boolean;
5
6
  useCache: boolean;
6
7
  noToktx: boolean;
@@ -7,13 +7,13 @@ import path, { basename } from 'path';
7
7
  import { existsSync, readFileSync, rmSync, statSync, writeFileSync } from 'fs';
8
8
  import { addMetaToCache, addToCache, getHash, getKeyWithHash, tryGetFromCache, tryGetMetaFromCache } from '../cache/index.js';
9
9
  import sharp from "sharp";
10
- import { foreachGLTF, generateGuid, writeNodeIO } from '../utils/index.js';
10
+ import { detectTextureCompressionMode, foreachGLTF, generateGuid, writeNodeIO } from '../utils/index.js';
11
11
  import { needle_asset, writeNeedleAsset } from './needle_asset.js';
12
12
  import { MeshoptDecoder, MeshoptSimplifier } from 'meshoptimizer';
13
13
  import { compressPrimitive } from './needle_mesh_transform.js';
14
14
  import { calculateMeshDensity, getMeshInformation } from "../utils/mesh.js";
15
15
  export async function make_progressive(opts) {
16
- console.log("INFO: Run progressive transform: " + opts.path);
16
+ console.log(`INFO: Run progressive transform: ${opts.path}`);
17
17
  const file = opts.path;
18
18
  return foreachGLTF(file, async (f) => {
19
19
  // Don't re-process files that are already LODs
@@ -82,6 +82,7 @@ async function onProcess(file, opts) {
82
82
  }
83
83
  else
84
84
  console.warn("WARN: Progressive meshes are not supported for VRM files yet");
85
+ console.log("→ Transforming: " + file);
85
86
  await document.transform(...transforms);
86
87
  let filename = file;
87
88
  if (opts.debug === true) {
@@ -93,6 +94,7 @@ async function onProcess(file, opts) {
93
94
  filename = filename + ".debug." + extension;
94
95
  }
95
96
  await writeNodeIO(io, filename, document);
97
+ console.log("← Transforming done: " + filename);
96
98
  }
97
99
  }
98
100
  catch (err) {
@@ -169,7 +171,7 @@ export function make_progressive_textures(filePath, _options = TEXTURE_RESIZE_DE
169
171
  continue;
170
172
  }
171
173
  const textureGuid = settings.guid?.length ? settings.guid : generateGuid(path + "_texture_" + index.toString());
172
- const outPath = createTextureLod(doc, textureLodBasePath, 0, texture, slots, textureGuid, textureIndex);
174
+ const outPath = createTextureLod(doc, filePath, textureLodBasePath, 0, texture, slots, textureGuid, textureIndex);
173
175
  if (!outPath) {
174
176
  continue;
175
177
  }
@@ -205,7 +207,7 @@ export function make_progressive_textures(filePath, _options = TEXTURE_RESIZE_DE
205
207
  logger.debug(`Progressive transform finished in ${((Date.now() - startTime) / 1000).toFixed(1)} sec.`);
206
208
  });
207
209
  }
208
- function createTextureLod(doc, basePath, level, texture, slots, guid, textureIndex) {
210
+ function createTextureLod(doc, file, basePath, level, texture, slots, guid, textureIndex) {
209
211
  // const imageBytes = texture.getImage();
210
212
  // if (imageBytes) {
211
213
  // // write bytes
@@ -256,6 +258,13 @@ function createTextureLod(doc, basePath, level, texture, slots, guid, textureInd
256
258
  console.log("> Pass compression extension to new texture");
257
259
  const newExt = newDoc.createExtension(NEEDLE_compression_texture);
258
260
  const def = compressionSettings.getExtensionDefinition();
261
+ def.mode = detectTextureCompressionMode(slots);
262
+ def.slot = slots[0];
263
+ // for VRMS we just use webp
264
+ // if (file?.toLocaleLowerCase().endsWith(".vrm")) {
265
+ // def.mode = "webp";
266
+ // def.quality = 70;
267
+ // }
259
268
  const newProp = newExt.create(def);
260
269
  newTexture.setExtension(NEEDLE_compression_texture.EXTENSION_NAME, newProp);
261
270
  }
@@ -125,21 +125,41 @@ async function processTexture(texture, index, document, logger, context) {
125
125
  }
126
126
  }
127
127
  else {
128
+ let mimetype = "image/ktx2";
129
+ const isVRM = context.file?.toLowerCase().endsWith(".vrm");
130
+ // TODO: need to make this into a function
131
+ // const webpThreshold = 256; // this is the default threshold in progressive textures (needle_progressive.ts)
132
+ // console.log(context.file, isVRM);
133
+ // // if we're dealing with a VRM and the texture size is < 128x128 we can use webp (this is e.g. the case for progressive textures)
134
+ // if (isVRM) {
135
+ // const size = texture.getSize();
136
+ // console.log(isVRM, size)
137
+ // if (size && size[0] <= webpThreshold && size[1] <= webpThreshold) {
138
+ // mimetype = "image/webp";
139
+ // }
140
+ // }
128
141
  // If we have a texture cached we can use it
129
142
  // the default compression is toktx so we can safely use that for now
130
143
  if (cached) {
131
- texture.setImage(cached).setMimeType("image/ktx2");
144
+ texture.setImage(cached).setMimeType(mimetype);
132
145
  return;
133
146
  }
134
147
  await resizeTextureIfNecessary(texture);
135
- if (canRunToktx) {
148
+ if (mimeType === "image/webp") {
149
+ successFullyProcessed = await context.webPContext.process(index, texture, null);
150
+ }
151
+ else if (canRunToktx) {
136
152
  // if no specific compression settings are provided we just use the toktx default settings
137
153
  // if we ever change what is used for default compression we need to teach the cache to save the mime type
138
- successFullyProcessed = await context.toktxContext.process(index, texture, null);
139
- // don't add textures to the cache when toktx doesnt run
154
+ // For VRMs we use ETC1S always
155
+ let options = null;
156
+ if (isVRM) {
157
+ options = { mode: "ETC1S" };
158
+ }
159
+ successFullyProcessed = await context.toktxContext.process(index, texture, options);
140
160
  }
141
161
  else {
142
- console.log("toktx is disallowed. Skipping texture compression.");
162
+ console.log("Toktx not found: Skipping texture compression.");
143
163
  }
144
164
  }
145
165
  if (context.useCache && successFullyProcessed && key) {
@@ -20,6 +20,6 @@ export declare class NeedleToktxContext implements INeedleTextureTransformContex
20
20
  private _prepareFailed;
21
21
  constructor(options: NeedleToktxOptions);
22
22
  prepare(document: Document): Promise<boolean>;
23
- process(index: number, texture: Texture, settings: NEEDLE_compression_texture_schema | null): Promise<boolean>;
23
+ process(index: number, texture: Texture, settings: Pick<NEEDLE_compression_texture_schema, "mode"> | null): Promise<boolean>;
24
24
  finalize(document: Document): Promise<void>;
25
25
  }
@@ -113,17 +113,23 @@ export class NeedleToktxContext {
113
113
  const outPath = tmpName + '.ktx2';
114
114
  // const inBytes = image.byteLength;
115
115
  await fs.writeFile(inPath, Buffer.from(image));
116
+ let mode = Mode.ETC1S;
117
+ if (settings) {
118
+ mode = settings.mode === "ETC1S" ? Mode.ETC1S : Mode.UASTC;
119
+ }
120
+ else if (slots) {
121
+ const useUASTC = slots.includes("normalTexture") || slots.includes("metallicRoughnessTexture");
122
+ mode = useUASTC ? Mode.UASTC : Mode.ETC1S;
123
+ }
124
+ const defaults = mode === Mode.ETC1S ? ETC1S_DEFAULTS : UASTC_DEFAULTS;
116
125
  //@ts-ignore
117
- const defaults = (!settings || settings.mode === "ETC1S") ? ETC1S_DEFAULTS : UASTC_DEFAULTS;
118
- defaults.mode = (!settings || settings.mode === "ETC1S") ? Mode.ETC1S : Mode.UASTC;
119
- //@ts-ignore
120
- const toktxParams = createParams(slots, channels, size, logger, this.numTextures, { ...defaults }, this.version);
126
+ const toktxParams = createParams(slots, channels, size, logger, this.numTextures, { ...defaults, mode }, this.version);
121
127
  const params = [
122
128
  ...toktxParams,
123
129
  outPath,
124
130
  inPath,
125
131
  ];
126
- logger.debug(`${prefix}: Spawning → toktx ${params.join(' ')}`);
132
+ logger.debug(`${prefix}: Spawning → toktx ${mode.toUpperCase()} ${params.join(' ')}`);
127
133
  try {
128
134
  // COMPRESS: Run `toktx` CLI tool.
129
135
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
@@ -0,0 +1,2 @@
1
+ export declare type TextureCompressionMode = "webp" | "ETC1S" | "UASTC";
2
+ export declare function detectTextureCompressionMode(slots: string[]): "ETC1S" | "UASTC";
@@ -0,0 +1,6 @@
1
+ export function detectTextureCompressionMode(slots) {
2
+ if (slots.includes("normalTexture") || slots.includes("metallicRoughnessTexture")) {
3
+ return "UASTC";
4
+ }
5
+ return "ETC1S";
6
+ }
@@ -1,3 +1,4 @@
1
+ export * from "./compression_utils.js";
1
2
  export * from "./fileutils.js";
2
3
  export * from "./version.js";
3
4
  export * from "./mesh.js";
@@ -1,3 +1,4 @@
1
+ export * from "./compression_utils.js";
1
2
  export * from "./fileutils.js";
2
3
  export * from "./version.js";
3
4
  export * from "./mesh.js";
package/pack-gltf.mjs CHANGED
@@ -60,11 +60,9 @@ if (debug) {
60
60
 
61
61
  let allowCaching = true;
62
62
  if (args.includes("--no-caching")) {
63
- console.log("Caching is disabled")
64
63
  allowCaching = false;
65
64
  }
66
- else if (showHelp)
67
- console.log("Caching is enabled, use \"--no-caching\" to disable it");
65
+ else console.log("Use --no-caching to disable caching");
68
66
 
69
67
  const noToktx = args.includes("no-toktx");
70
68
  if (noToktx)
@@ -106,10 +104,11 @@ async function packFile(file) {
106
104
 
107
105
  if (packedFiles.has(file)) return;
108
106
  packedFiles.add(file);
109
- console.log("→ Begin packing: " + file);
110
107
 
111
108
  try {
112
109
  const prevSizeInMB = statSync(file).size / 1024 / 1024;
110
+ console.log(`→ Compressing ${prevSizeInMB.toFixed(1)} MB: ${file}`);
111
+
113
112
  const startTime = Date.now();
114
113
  const io = new NodeIO()
115
114
  .registerExtensions(ALL_EXTENSIONS)
@@ -130,6 +129,7 @@ async function packFile(file) {
130
129
  await document.transform(
131
130
  needle_asset(),
132
131
  needle_texture_transform({
132
+ file,
133
133
  useCache: allowCaching,
134
134
  noToktx: noToktx,
135
135
  debug: debug,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@needle-tools/gltf-build-pipeline",
3
- "version": "1.5.0-alpha.5",
3
+ "version": "1.5.0-alpha.6",
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",