@threlte/gltf 3.0.0 → 3.0.2

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/src/index.js CHANGED
@@ -1,82 +1,54 @@
1
1
  import 'jsdom-global'
2
- import fs from 'fs'
3
- import path from 'path'
4
- import transform from './utils/transform.js'
5
- // import { GLTFLoader, DRACOLoader } from 'three-stdlib'
2
+ import fs from 'node:fs/promises'
3
+ import path from 'node:path'
6
4
  import * as prettier from 'prettier'
7
- import THREE from 'three'
8
-
9
- global.THREE = THREE
10
-
11
- import './bin/GLTFLoader.js'
12
- import DracoLoader from './bin/DRACOLoader.js'
13
- THREE.DRACOLoader.getDecoderModule = () => {}
14
-
15
- import parse from './utils/parser.js'
16
-
17
- const gltfLoader = new THREE.GLTFLoader()
18
- gltfLoader.setDRACOLoader(new DracoLoader())
19
-
20
- function toArrayBuffer(buf) {
21
- var ab = new ArrayBuffer(buf.length)
22
- var view = new Uint8Array(ab)
23
- for (var i = 0; i < buf.length; ++i) view[i] = buf[i]
24
- return ab
25
- }
26
-
27
- export default function (file, output, options) {
28
- function getFilePath(file) {
29
- return `${options.root ?? '/'}${options.root ? path.basename(file) : path.normalize(file)}`
5
+ import { transform } from './utils/transform.js'
6
+ import { parse } from './utils/parser.js'
7
+ import { GLTFLoader } from './bin/GLTFLoader.js'
8
+ import { DRACOLoader } from './bin/DRACOLoader.js'
9
+
10
+ globalThis.self = globalThis
11
+
12
+ const gltfLoader = new GLTFLoader()
13
+ const draco = new DRACOLoader()
14
+ gltfLoader.setDRACOLoader(draco)
15
+
16
+ export default async function (file, output, options) {
17
+ try {
18
+ await fs.stat(file)
19
+ } catch {
20
+ throw new Error(`${file} does not exist.`)
30
21
  }
31
22
 
32
- return new Promise((resolve, reject) => {
33
- const stream = fs.createWriteStream(output)
34
- stream.once('open', async (fd) => {
35
- if (!fs.existsSync(file)) {
36
- reject(file + ' does not exist.')
37
- } else {
38
- // Process GLTF
39
- if (options.transform || options.instance || options.instanceall) {
40
- const { name } = path.parse(file)
41
- const transformOut = path.join(name + '-transformed.glb')
42
- await transform(file, transformOut, options)
43
- file = transformOut
44
- }
45
- resolve()
46
-
47
- const filePath = getFilePath(file)
48
- const data = fs.readFileSync(file)
49
- const arrayBuffer = toArrayBuffer(data)
23
+ // Process GLTF
24
+ if (options.transform || options.instance || options.instanceall) {
25
+ const { name } = path.parse(file)
26
+ const transformOut = path.join(`${name}-transformed.glb`)
27
+ await transform(file, transformOut, options)
28
+ file = transformOut
29
+ }
50
30
 
51
- gltfLoader.parse(
52
- arrayBuffer,
53
- '',
54
- async (gltf) => {
55
- const raw = parse(filePath, gltf, options)
56
- try {
57
- const prettiered = await prettier.format(raw, {
58
- singleQuote: true,
59
- trailingComma: 'none',
60
- semi: false,
61
- printWidth: 100,
62
- parser: 'svelte',
63
- plugins: ['prettier-plugin-svelte'],
64
- overrides: [{ files: '*.svelte', options: { parser: 'svelte' } }],
65
- singleAttributePerLine: true
66
- })
67
- stream.write(prettiered)
68
- stream.end()
69
- resolve()
70
- } catch (error) {
71
- console.error(error)
72
- stream.write(raw)
73
- stream.end()
74
- reject(error)
75
- }
76
- },
77
- reject
78
- )
31
+ const filePath = `${options.root ?? '/'}${options.root ? path.basename(file) : path.normalize(file)}`
32
+ const data = await fs.readFile(file)
33
+ const gltf = await gltfLoader.parseAsync(data.buffer)
34
+
35
+ const raw = parse(filePath, gltf, options)
36
+
37
+ const prettiered = await prettier.format(raw, {
38
+ singleQuote: true,
39
+ trailingComma: 'es5',
40
+ semi: false,
41
+ printWidth: 100,
42
+ parser: 'svelte',
43
+ plugins: ['prettier-plugin-svelte'],
44
+ overrides: [
45
+ {
46
+ files: '*.svelte',
47
+ options: { parser: 'svelte' }
79
48
  }
80
- })
49
+ ],
50
+ singleAttributePerLine: true
81
51
  })
52
+
53
+ await fs.writeFile(output, prettiered)
82
54
  }
@@ -1,5 +1,5 @@
1
1
  import parse from './parser'
2
2
  import transform from './transform'
3
- import GLTFStructureLoader from './glftLoader'
3
+ import { GLTFLoader as GLTFStructureLoader } from '../bin/GLTFLoader.js'
4
4
 
5
5
  export { transform, parse, GLTFStructureLoader }
@@ -1,4 +1,4 @@
1
- const isVarName = (str) => {
1
+ export function isVarName(str) {
2
2
  // eslint-disable-next-line no-misleading-character-class
3
3
  const regex = new RegExp(
4
4
  // eslint-disable-next-line no-misleading-character-class
@@ -6,5 +6,3 @@ const isVarName = (str) => {
6
6
  )
7
7
  return regex.test(str)
8
8
  }
9
-
10
- export default isVarName
@@ -1,5 +1,5 @@
1
- import THREE from 'three'
2
- import isVarName from './isVarName.js'
1
+ import * as THREE from 'three'
2
+ import { isVarName } from './isVarName.js'
3
3
 
4
4
  /**
5
5
  * @param {string} fileName
@@ -7,7 +7,12 @@ import isVarName from './isVarName.js'
7
7
  * @param {import('./Options.d.ts').Options} options
8
8
  * @returns {string}
9
9
  */
10
- function parse(fileName, gltf, options = {}) {
10
+ export function parse(fileName, gltf, options = {}) {
11
+ if (gltf.isObject3D) {
12
+ // Wrap scene in a GLTF Structure
13
+ gltf = { scene: gltf, animations: [], parser: { json: {} } }
14
+ }
15
+
11
16
  const url = fileName
12
17
  const animations = gltf.animations
13
18
  const hasAnimations = animations.length > 0
@@ -25,38 +30,40 @@ function parse(fileName, gltf, options = {}) {
25
30
 
26
31
  function uniqueName(attempt, index = 0) {
27
32
  const newAttempt = index > 0 ? attempt + index : attempt
28
- if (Object.values(duplicates.geometries).find(({ name }) => name === newAttempt) === undefined)
33
+ if (
34
+ Object.values(duplicates.geometries).find(({ name }) => name === newAttempt) === undefined
35
+ ) {
29
36
  return newAttempt
30
- else return uniqueName(attempt, index + 1)
37
+ } else {
38
+ return uniqueName(attempt, index + 1)
39
+ }
31
40
  }
32
41
 
33
42
  gltf.scene.traverse((child) => {
34
- if (child.isMesh) {
35
- if (child.material) {
36
- if (!duplicates.materials[child.material.name]) {
37
- duplicates.materials[child.material.name] = 1
38
- } else {
39
- duplicates.materials[child.material.name]++
40
- }
43
+ if (!child.isMesh) {
44
+ return
45
+ }
46
+
47
+ if (child.material) {
48
+ if (!duplicates.materials[child.material.name]) {
49
+ duplicates.materials[child.material.name] = 1
50
+ } else {
51
+ duplicates.materials[child.material.name]++
41
52
  }
42
53
  }
43
- })
44
54
 
45
- gltf.scene.traverse((child) => {
46
- if (child.isMesh) {
47
- if (child.geometry) {
48
- const key = child.geometry.uuid + child.material?.name ?? ''
49
- if (!duplicates.geometries[key]) {
50
- let name = (child.name || 'Part').replace(/[^a-zA-Z]/g, '')
51
- name = name.charAt(0).toUpperCase() + name.slice(1)
52
- duplicates.geometries[key] = {
53
- count: 1,
54
- name: uniqueName(name),
55
- node: 'nodes' + sanitizeName(child.name)
56
- }
57
- } else {
58
- duplicates.geometries[key].count++
55
+ if (child.geometry) {
56
+ const key = child.geometry.uuid + child.material?.name ?? ''
57
+ if (!duplicates.geometries[key]) {
58
+ let name = (child.name || 'Part').replace(/[^a-zA-Z]/g, '')
59
+ name = name.charAt(0).toUpperCase() + name.slice(1)
60
+ duplicates.geometries[key] = {
61
+ count: 1,
62
+ name: uniqueName(name),
63
+ node: 'nodes' + sanitizeName(child.name)
59
64
  }
65
+ } else {
66
+ duplicates.geometries[key].count++
60
67
  }
61
68
  }
62
69
  })
@@ -64,7 +71,9 @@ function parse(fileName, gltf, options = {}) {
64
71
  // Prune duplicate geometries
65
72
  for (let key of Object.keys(duplicates.geometries)) {
66
73
  const duplicate = duplicates.geometries[key]
67
- if (duplicate.count === 1) delete duplicates.geometries[key]
74
+ if (duplicate.count === 1) {
75
+ delete duplicates.geometries[key]
76
+ }
68
77
  }
69
78
 
70
79
  function sanitizeName(name) {
@@ -72,17 +81,17 @@ function parse(fileName, gltf, options = {}) {
72
81
  }
73
82
 
74
83
  const rNbr = (number) => {
75
- return parseFloat(number.toFixed(Math.round(options.precision || 2)))
84
+ return Number.parseFloat(number.toFixed(Math.round(options.precision || 2)))
76
85
  }
77
86
 
78
87
  const rDeg = (number) => {
79
- const abs = Math.abs(Math.round(parseFloat(number) * 100000))
88
+ const abs = Math.abs(Math.round(Number.parseFloat(number) * 100000))
80
89
  for (let i = 1; i <= 10; i++) {
81
- if (abs === Math.round(parseFloat(Math.PI / i) * 100000))
90
+ if (abs === Math.round(Number.parseFloat(Math.PI / i) * 100000))
82
91
  return `${number < 0 ? '-' : ''}Math.PI${i > 1 ? ' / ' + i : ''}`
83
92
  }
84
93
  for (let i = 1; i <= 10; i++) {
85
- if (abs === Math.round(parseFloat(Math.PI * i) * 100000))
94
+ if (abs === Math.round(Number.parseFloat(Math.PI * i) * 100000))
86
95
  return `${number < 0 ? '-' : ''}Math.PI${i > 1 ? ' * ' + i : ''}`
87
96
  }
88
97
  return rNbr(number)
@@ -125,10 +134,20 @@ function parse(fileName, gltf, options = {}) {
125
134
 
126
135
  function getType(obj) {
127
136
  let type = obj.type.charAt(0).toLowerCase() + obj.type.slice(1)
137
+
128
138
  // Turn object3d's into groups, it should be faster according to the threejs docs
129
- if (type === 'object3D') type = 'group'
130
- if (type === 'perspectiveCamera') type = 'PerspectiveCamera'
131
- if (type === 'orthographicCamera') type = 'OrthographicCamera'
139
+ if (type === 'object3D') {
140
+ return 'group'
141
+ }
142
+
143
+ if (type === 'perspectiveCamera') {
144
+ return 'PerspectiveCamera'
145
+ }
146
+
147
+ if (type === 'orthographicCamera') {
148
+ return 'OrthographicCamera'
149
+ }
150
+
132
151
  return type
133
152
  }
134
153
 
@@ -261,7 +280,6 @@ function parse(fileName, gltf, options = {}) {
261
280
  const firstProps = handleThrelteProps(first)
262
281
  const regex = /([a-z-A-Z]*)={([a-zA-Z0-9\.\[\]\-\,\ \/]*)}/g
263
282
  const keys1 = [...result.matchAll(regex)].map(([, match]) => match)
264
- const values1 = [...result.matchAll(regex)].map(([, , match]) => match)
265
283
  const keys2 = [...firstProps.matchAll(regex)].map(([, match]) => match)
266
284
 
267
285
  /** Double negative rotations
@@ -467,7 +485,7 @@ function parse(fileName, gltf, options = {}) {
467
485
  const scene = printThrelte(gltf.scene)
468
486
 
469
487
  const useGltfOptions = {
470
- draco: options.transform && options.draco ? (options.draco ? options.transform : true) : false
488
+ draco: options.transform || options.draco
471
489
  }
472
490
 
473
491
  const imports = `
@@ -489,7 +507,7 @@ function parse(fileName, gltf, options = {}) {
489
507
 
490
508
  const useGltf = `${options.suspense ? 'suspend(' : ''}useGltf${
491
509
  options.types ? '<GLTFResult>' : ''
492
- }('${url}'${useGltfOptions.draco ? `, { dracoLoader: useDraco(${typeof useGltfOptions.draco === 'string' ? `'${useGltfOptions.draco}'` : ''}) }` : ''})${
510
+ }('${url}'${useGltfOptions.draco ? `, { dracoLoader: useDraco(${typeof options.draco === 'string' ? `'${useGltfOptions.draco}'` : ''}) }` : ''})${
493
511
  options.suspense ? ')' : ''
494
512
  }`
495
513
 
@@ -575,5 +593,3 @@ ${
575
593
  </${hasAnimations ? 'T' : 'T.Group'}>
576
594
  `
577
595
  }
578
-
579
- export default parse
@@ -1,19 +1,25 @@
1
- import { NodeIO } from '@gltf-transform/core'
1
+ import { Logger, NodeIO } from '@gltf-transform/core'
2
2
  import {
3
+ reorder,
4
+ palette,
3
5
  simplify,
6
+ join,
4
7
  weld,
5
8
  dedup,
6
9
  resample,
7
10
  prune,
8
11
  textureCompress,
9
- draco
12
+ draco,
13
+ sparse,
14
+ flatten
10
15
  } from '@gltf-transform/functions'
11
16
  import { ALL_EXTENSIONS } from '@gltf-transform/extensions'
12
17
  import { MeshoptDecoder, MeshoptEncoder, MeshoptSimplifier } from 'meshoptimizer'
18
+ import { ready as resampleReady, resample as resampleWASM } from 'keyframe-resample'
13
19
  import draco3d from 'draco3dgltf'
14
20
  import sharp from 'sharp'
15
21
 
16
- async function transform(file, output, config = {}) {
22
+ export async function transform(file, output, config = {}) {
17
23
  await MeshoptDecoder.ready
18
24
  await MeshoptEncoder.ready
19
25
  const io = new NodeIO().registerExtensions(ALL_EXTENSIONS).registerDependencies({
@@ -22,27 +28,37 @@ async function transform(file, output, config = {}) {
22
28
  'meshopt.decoder': MeshoptDecoder,
23
29
  'meshopt.encoder': MeshoptEncoder
24
30
  })
31
+ if (config.console) {
32
+ io.setLogger(new Logger(Logger.Verbosity.ERROR))
33
+ } else {
34
+ io.setLogger(new Logger(Logger.Verbosity.WARN))
35
+ }
25
36
 
26
37
  const document = await io.read(file)
27
38
  const resolution = config.resolution ?? 1024
39
+ const normalResolution = Math.max(resolution, 2048)
28
40
 
29
- const functions = [
30
- // Losslessly resample animation frames.
31
- resample(),
41
+ const functions = []
42
+
43
+ if (!config.keepmaterials) {
44
+ functions.push(palette({ min: 5 }))
45
+ }
46
+
47
+ functions.push(
48
+ reorder({ encoder: MeshoptEncoder }),
32
49
  // Remove duplicate vertex or texture data, if any.
33
50
  dedup(),
34
- // Remove unused nodes, textures, or other data.
35
- prune(),
36
- // Resize and convert textures (using webp and sharp)
37
- textureCompress({ targetFormat: 'webp', encoder: sharp, resize: [resolution, resolution] }),
38
- // Add Draco compression.
39
- draco()
40
- ]
51
+ flatten()
52
+ )
53
+
54
+ if (!config.keepmeshes) {
55
+ functions.push(join())
56
+ }
57
+
58
+ functions.push(weld())
41
59
 
42
60
  if (config.simplify) {
43
61
  functions.push(
44
- // Weld vertices
45
- weld({ tolerance: config.weld ?? 0.0001 }),
46
62
  // Simplify meshes
47
63
  simplify({
48
64
  simplifier: MeshoptSimplifier,
@@ -52,9 +68,54 @@ async function transform(file, output, config = {}) {
52
68
  )
53
69
  }
54
70
 
71
+ functions.push(
72
+ // Losslessly resample animation frames.
73
+ resample({ ready: resampleReady, resample: resampleWASM }),
74
+ // Remove unused nodes, textures, or other data.
75
+ prune({ keepAttributes: false, keepLeaves: false }),
76
+ sparse()
77
+ )
78
+
79
+ if (config.degrade) {
80
+ // Custom per-file resolution
81
+ functions.push(
82
+ textureCompress({
83
+ encoder: sharp,
84
+ pattern: new RegExp(`^(?=${config.degrade}).*$`),
85
+ targetFormat: config.format,
86
+ resize: [degradeResolution, degradeResolution]
87
+ }),
88
+ textureCompress({
89
+ encoder: sharp,
90
+ pattern: new RegExp(`^(?!${config.degrade}).*$`),
91
+ targetFormat: config.format,
92
+ resize: [resolution, resolution]
93
+ })
94
+ )
95
+ } else {
96
+ // Keep normal maps near lossless
97
+ functions.push(
98
+ textureCompress({
99
+ slots: /^(?!normalTexture).*$/, // exclude normal maps
100
+ encoder: sharp,
101
+ targetFormat: config.format,
102
+ resize: [resolution, resolution]
103
+ }),
104
+ textureCompress({
105
+ slots: /^(?=normalTexture).*$/, // include normal maps
106
+ encoder: sharp,
107
+ targetFormat: 'jpeg',
108
+ resize: [normalResolution, normalResolution]
109
+ })
110
+ )
111
+ }
112
+
113
+ functions.push(
114
+ // Add Draco compression.
115
+ draco()
116
+ )
117
+
55
118
  await document.transform(...functions)
56
119
 
57
120
  await io.write(output, document)
58
121
  }
59
-
60
- export default transform
package/tsconfig.json ADDED
@@ -0,0 +1,30 @@
1
+ {
2
+ "compilerOptions": {
3
+ // ESM out, modern syntax in source
4
+ "target": "ES2020",
5
+ "module": "ESNext",
6
+ "moduleResolution": "Bundler",
7
+
8
+ // Stricter, modern TS defaults
9
+ "strict": true,
10
+ "noImplicitOverride": true,
11
+ "noUncheckedIndexedAccess": true,
12
+ "useDefineForClassFields": true,
13
+ "verbatimModuleSyntax": true,
14
+ "exactOptionalPropertyTypes": true,
15
+
16
+ // Type ergonomics
17
+ "skipLibCheck": true,
18
+ "resolveJsonModule": true,
19
+
20
+ "noEmit": true,
21
+ // Declarations: keep off in the main tsconfig; use a build tsconfig to emit types
22
+ "declarationMap": false,
23
+ "allowJs": true,
24
+
25
+ // Svelte types
26
+ "types": ["svelte"]
27
+ },
28
+ "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.js"],
29
+ "exclude": ["dist", "node_modules"]
30
+ }
@@ -1,4 +0,0 @@
1
- const THREE = (global.THREE = require('three'))
2
- require('../bin/GLTFLoader')
3
-
4
- module.exports = THREE.GLTFLoader