Skip to content

Commit

Permalink
feat: console, re-usable skinned, gltf-t updates
Browse files Browse the repository at this point in the history
  • Loading branch information
drcmda committed Jul 13, 2024
1 parent d6c6f93 commit 21033c6
Show file tree
Hide file tree
Showing 6 changed files with 348 additions and 402 deletions.
3 changes: 2 additions & 1 deletion cli.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
#!/usr/bin/env node
'use strict'
import meow from 'meow'
import path from 'path'
import { fileURLToPath } from 'url'
import { dirname } from 'path'
import gltfjsx from './src/gltfjsx.js'
Expand Down Expand Up @@ -39,6 +38,7 @@ const cli = meow(
--weld Weld tolerance (default: 0.00005)
--ratio Simplifier ratio (default: 0)
--error Simplifier error threshold (default: 0.0001)
--console, -c Log JSX to console, won't produce a file
--debug, -D Debug output
`,
{
Expand Down Expand Up @@ -69,6 +69,7 @@ const cli = meow(
weld: { type: 'number', default: 0.0001 },
ratio: { type: 'number', default: 0.75 },
error: { type: 'number', default: 0.001 },
console: { type: 'boolean', shortFlag: 'c' },
debug: { type: 'boolean', shortFlag: 'D' },
},
}
Expand Down
17 changes: 9 additions & 8 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,24 +38,25 @@
"node": ">=16"
},
"dependencies": {
"@gltf-transform/core": "3.10.0",
"@gltf-transform/extensions": "3.10.0",
"@gltf-transform/functions": "3.10.0",
"@gltf-transform/core": "4.0.4",
"@gltf-transform/extensions": "4.0.4",
"@gltf-transform/functions": "4.0.4",
"@node-loader/babel": "^2.0.1",
"draco3dgltf": "^1.5.6",
"draco3dgltf": "^1.5.7",
"is-var-name": "^2.0.0",
"keyframe-resample": "^0.0.15",
"keyframe-resample": "^0.1.0",
"meow": "^12.1.1",
"meshoptimizer": "^0.20.0",
"meshoptimizer": "^0.21.0",
"prettier": "3.1.1",
"read-pkg-up": "^10.1.0",
"three": "0.159.0",
"three-stdlib": "^2.28.7"
},
"optionalDependencies": {
"jsdom": "^23.0.1",
"jsdom": "^24.1.0",
"jsdom-global": "^3.0.2",
"sharp": "0.32.6"
"libvips": "0.0.2",
"sharp": "^0.33.4"
},
"devDependencies": {
"@babel/core": "7.23.6",
Expand Down
7 changes: 5 additions & 2 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,23 +33,26 @@ Options
--types, -t Add Typescript definitions
--keepnames, -k Keep original names
--keepgroups, -K Keep (empty) groups, disable pruning
--bones, -b Lay out bones declaratively (default: false)
--meta, -m Include metadata (as userData)
--shadows, -s Let meshes cast and receive shadows
--shadows, s Let meshes cast and receive shadows
--printwidth, w Prettier printWidth (default: 120)
--precision, -p Number of fractional digits (default: 3)
--draco, -d Draco binary path
--root, -r Sets directory from which .gltf file is served
--instance, -i Instance re-occuring geometry
--instanceall, -I Instance every geometry (for cheaper re-use)
--exportdefault, -E Use default export
--transform, -T Transform the asset for the web (draco, prune, resize)
--resolution, -R Resolution for texture resizing (default: 1024)
--keepmeshes, -j Do not join compatible meshes
--keepmeshes, -j Do not join compatible meshes
--keepmaterials, -M Do not palette join materials
--format, -f Texture format (default: "webp")
--simplify, -S Mesh simplification (default: false)
--weld Weld tolerance (default: 0.00005)
--ratio Simplifier ratio (default: 0)
--error Simplifier error threshold (default: 0.0001)
--console, -c Log JSX to console, won't produce a file
--debug, -D Debug output
```

Expand Down
82 changes: 41 additions & 41 deletions src/gltfjsx.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,47 +43,47 @@ export default function (file, output, options) {
}

return new Promise((resolve, reject) => {
const stream = fs.createWriteStream(path.resolve(output))
stream.once('open', async (fd) => {
if (!fs.existsSync(file)) {
reject(file + ' does not exist.')
} else {
let size = ''
// Process GLTF
if (output && path.parse(output).ext === '.tsx') {
options.types = true
}

if (options.transform || options.instance || options.instanceall) {
const { name } = path.parse(file)
const outputDir = path.parse(path.resolve(output ?? file)).dir;
const transformOut = path.join(outputDir, name + '-transformed.glb')
await transform(file, transformOut, options)
const { size: sizeOriginal, sizeKB: sizeKBOriginal } = getFileSize(file)
const { size: sizeTransformed, sizeKB: sizeKBTransformed } = getFileSize(transformOut)
size = `${file} [${sizeOriginal}] > ${transformOut} [${sizeTransformed}] (${Math.round(
100 - (sizeKBTransformed / sizeKBOriginal) * 100
)}%)`
file = transformOut
}
resolve()

const filePath = getRelativeFilePath(file)
const data = fs.readFileSync(file)
const arrayBuffer = toArrayBuffer(data)
gltfLoader.parse(
arrayBuffer,
'',
async (gltf) => {
stream.write(await parse(gltf, { fileName: filePath, size, ...options }))
stream.end()
resolve()
},
(reason) => {
console.log(reason)
}
)
async function run(stream) {
let size = ''
// Process GLTF
if (output && path.parse(output).ext === '.tsx') options.types = true
if (options.transform || options.instance || options.instanceall) {
const { name } = path.parse(file)
const outputDir = path.parse(path.resolve(output ?? file)).dir
const transformOut = path.join(outputDir, name + '-transformed.glb')
await transform(file, transformOut, options)
const { size: sizeOriginal, sizeKB: sizeKBOriginal } = getFileSize(file)
const { size: sizeTransformed, sizeKB: sizeKBTransformed } = getFileSize(transformOut)
size = `${file} [${sizeOriginal}] > ${transformOut} [${sizeTransformed}] (${Math.round(
100 - (sizeKBTransformed / sizeKBOriginal) * 100
)}%)`
file = transformOut
}
})
const filePath = getRelativeFilePath(file)
const data = fs.readFileSync(file)
const arrayBuffer = toArrayBuffer(data)
gltfLoader.parse(
arrayBuffer,
'',
async (gltf) => {
const output = await parse(gltf, { fileName: filePath, size, ...options })
if (options.console) console.log(output)
else stream?.write(output)
stream?.end()
resolve()
},
(reason) => console.log(reason)
)
}

if (options.console) {
run()
} else {
const stream = fs.createWriteStream(path.resolve(output))
stream.once('open', async () => {
if (!fs.existsSync(file)) reject(file + ' does not exist.')
else run(stream)
})
}
})
}
51 changes: 33 additions & 18 deletions src/utils/parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -101,17 +101,16 @@ function parse(gltf, { fileName = 'model', ...options } = {}) {
if (animations.length) {
animationTypes = `\n
type ActionName = ${animations.map((clip, i) => `"${clip.name}"`).join(' | ')};
interface GLTFAction extends THREE.AnimationClip {\n
name: ActionName;\n
}`
interface GLTFAction extends THREE.AnimationClip { name: ActionName }\n`
}

const types = [...new Set([...meshes, ...bones].map((o) => getType(o)))]
const contextType = `type ContextType = Record<string, React.ForwardRefExoticComponent<
const contextType = hasInstances ? `\ntype ContextType = Record<string, React.ForwardRefExoticComponent<
${types.map((type) => `JSX.IntrinsicElements['${type}']`).join(' | ')}
>>`
>>\n` : ''

return `\ntype GLTFResult = GLTF & {
return `\n${animationTypes}\ntype GLTFResult = GLTF & {
nodes: {
${meshes.map(({ name, type }) => (isVarName(name) ? name : `['${name}']`) + ': THREE.' + type).join(',')}
${bones.map(({ name, type }) => (isVarName(name) ? name : `['${name}']`) + ': THREE.' + type).join(',')}
Expand All @@ -120,7 +119,7 @@ function parse(gltf, { fileName = 'model', ...options } = {}) {
${materials.map(({ name, type }) => (isVarName(name) ? name : `['${name}']`) + ': THREE.' + type).join(',')}
}
animations: GLTFAction[]
}\n${animationTypes}\n${contextType}`
}\n${contextType}`
}

function getType(obj) {
Expand Down Expand Up @@ -446,25 +445,31 @@ ${options.header ? options.header : 'Auto-generated by: https://github.com/pmndr
options.size ? `\nFiles: ${options.size}` : ''
}
${parseExtras(gltf.parser.json.asset && gltf.parser.json.asset.extras)}*/`
const hasPrimitives = scene.includes('<primitive')
const result = `${options.types ? `\nimport * as THREE from 'three'` : ''}
import React, { useRef ${hasInstances ? ', useMemo, useContext, createContext' : ''} } from 'react'
import React from 'react'${hasPrimitives ? '\nimport { useGraph } from "@react-three/fiber"' : ''}
import { useGLTF, ${hasInstances ? 'Merged, ' : ''} ${
scene.includes('PerspectiveCamera') ? 'PerspectiveCamera,' : ''
}
${scene.includes('OrthographicCamera') ? 'OrthographicCamera,' : ''}
${hasAnimations ? 'useAnimations' : ''} } from '@react-three/drei'
${options.types ? 'import { GLTF } from "three-stdlib"' : ''}
${
hasPrimitives || options.types
? `import { ${options.types ? 'GLTF,' : ''} ${hasPrimitives ? 'SkeletonUtils' : ''} } from "three-stdlib"`
: ''
}
${options.types ? printTypes(objects, animations) : ''}
${
hasInstances
? `
const context = createContext(${options.types ? '{} as ContextType' : ''})
const context = React.createContext(${options.types ? '{} as ContextType' : ''})
export function Instances({ children, ...props }${options.types ? ': JSX.IntrinsicElements["group"]' : ''}) {
const { nodes } = useGLTF('${url}'${options.draco ? `, ${JSON.stringify(options.draco)}` : ''})${
options.types ? ' as GLTFResult' : ''
}
const instances = useMemo(() => ({
const instances = React.useMemo(() => ({
${Object.values(duplicates.geometries)
.map((v) => `${v.name}: ${v.node}`)
.join(', ')}
Expand All @@ -484,13 +489,22 @@ ${parseExtras(gltf.parser.json.asset && gltf.parser.json.asset.extras)}*/`
export ${options.exportdefault ? 'default' : ''} function Model(props${
options.types ? ": JSX.IntrinsicElements['group']" : ''
}) {
${hasInstances ? 'const instances = useContext(context);' : ''} ${
hasAnimations ? `const group = ${options.types ? 'useRef<THREE.Group>()' : 'useRef()'};` : ''
${hasInstances ? 'const instances = React.useContext(context);' : ''} ${
hasAnimations ? `const group = ${options.types ? 'React.useRef<THREE.Group>()' : 'React.useRef()'};` : ''
} ${
!options.instanceall
? `const { nodes, materials${hasAnimations ? ', animations' : ''} } = useGLTF('${url}'${
options.draco ? `, ${JSON.stringify(options.draco)}` : ''
})${options.types ? ' as GLTFResult' : ''}`
? `const { ${
!hasPrimitives ? `nodes, materials${hasAnimations ? ', animations' : ''}` : 'scene'
}} = useGLTF('${url}'${options.draco ? `, ${JSON.stringify(options.draco)}` : ''})${
!hasPrimitives && options.types ? ' as GLTFResult' : ''
}${
hasPrimitives
? `\nconst clone = React.useMemo(() => SkeletonUtils.clone(scene), [scene])
const { nodes, materials${hasAnimations ? ', animations' : ''} } = useGraph(clone) ${
options.types ? ' as GLTFResult' : ''
}`
: ''
}`
: ''
} ${printAnimations(animations)}
return (
Expand All @@ -502,16 +516,17 @@ ${parseExtras(gltf.parser.json.asset && gltf.parser.json.asset.extras)}*/`
useGLTF.preload('${url}')`

console.log(header)
if (!options.console) console.log(header)
const output = header + '\n' + result
return prettier.format(output, {
const formatted = prettier.format(output, {
semi: false,
printWidth: options.printwidth || 1000,
singleQuote: true,
jsxBracketSameLine: true,
parser: 'babel-ts',
plugins: [babelParser],
})
return formatted
}

export default parse
Loading

0 comments on commit 21033c6

Please sign in to comment.