Skip to content
This repository was archived by the owner on Aug 21, 2024. It is now read-only.

Commit 5ae5e74

Browse files
authored
Batched glTF-Transform (#7976)
* add dst and resourceURI properties to transform * fix resource uri routing * modularize GLTFTransformProperties into ui element * add batch transform to lod process fix bad image addressing logic * fix CI/CD errors * force power of two dimensions for ktx2 compression add localization for model transform * clean up model export
1 parent 1c00211 commit 5ae5e74

File tree

15 files changed

+570
-388
lines changed

15 files changed

+570
-388
lines changed

packages/client-core/i18n/en/editor.json

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -279,7 +279,22 @@
279279
"useDraco": "Use DRACO Mesh Compression",
280280
"useMeshopt": "Use Meshoptimizer",
281281
"useQuantization": "Use Mesh Quantization",
282-
"textureFormat": "Image Format"
282+
"textureFormat": "Image Format",
283+
"modelFormat": "Model Format",
284+
"resourceUri": "Resource URI",
285+
"dst": "File Name",
286+
"resampleAnimations": "Resample Animations",
287+
"maxTextureSize": "Max Texture Size",
288+
"flipY": "Flip Y",
289+
"linear": "Linear Color Space",
290+
"textureCompressionType": "Texture Compression Type",
291+
"ktx2Quality": "Compression Quality",
292+
"weldVertices": "Weld Vertices",
293+
"weldThreshold": "Weld Threshold",
294+
"removeDuplicates": "Remove Duplicates",
295+
"pruneUnused": "Prune Unused",
296+
"reorder": "Reorder",
297+
"resourceOverrides": "Resource Overrides"
283298
},
284299
"lods": {
285300
"label": "Level of Detail",

packages/editor/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
"react": "18.2.0"
2929
},
3030
"dependencies": {
31+
"@dimforge/rapier3d-compat": "0.11.2",
3132
"@etherealengine/client-core": "^1.2.1",
3233
"@etherealengine/common": "^1.2.1",
3334
"@etherealengine/engine": "^1.2.1",

packages/editor/src/components/assets/AssetPreviewPanels/ModelPreviewPanel.tsx

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,22 @@
11
import React, { useEffect, useRef } from 'react'
2+
import { Mesh } from 'three'
23

34
import LoadingView from '@etherealengine/client-core/src/common/components/LoadingView'
45
import { useRender3DPanelSystem } from '@etherealengine/client-core/src/user/components/Panel3D/useRender3DPanelSystem'
56
import { loadAvatarModelAsset } from '@etherealengine/engine/src/avatar/functions/avatarFunctions'
6-
import { useHookstate } from '@etherealengine/hyperflux'
7+
import { SourceType } from '@etherealengine/engine/src/renderer/materials/components/MaterialSource'
8+
import {
9+
removeMaterialSource,
10+
unregisterMaterial
11+
} from '@etherealengine/engine/src/renderer/materials/functions/MaterialLibraryFunctions'
12+
import { State, useHookstate } from '@etherealengine/hyperflux'
713

814
import styles from '../styles.module.scss'
915

1016
export const ModelPreviewPanel = (props) => {
1117
const url = props.resourceProps.resourceUrl
1218
const loading = useHookstate(true)
19+
1320
const error = useHookstate('')
1421
const panelRef = useRef() as React.MutableRefObject<HTMLDivElement>
1522

@@ -34,6 +41,14 @@ export const ModelPreviewPanel = (props) => {
3441
}
3542

3643
loadModel()
44+
45+
return () => {
46+
const sceneVal = scene.value
47+
const avatar = sceneVal.children.find((child) => child.name === 'avatar')
48+
if (avatar) {
49+
removeMaterialSource({ type: SourceType.MODEL, path: avatar.userData['src'] })
50+
}
51+
}
3752
}, [])
3853

3954
return (
Lines changed: 286 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,286 @@
1+
import { t } from 'i18next'
2+
import React, { useCallback } from 'react'
3+
4+
import {
5+
ImageTransformParameters,
6+
ModelTransformParameters
7+
} from '@etherealengine/engine/src/assets/classes/ModelTransform'
8+
import { State } from '@etherealengine/hyperflux'
9+
10+
import BooleanInput from '../inputs/BooleanInput'
11+
import InputGroup from '../inputs/InputGroup'
12+
import NumericInput from '../inputs/NumericInput'
13+
import NumericInputGroup from '../inputs/NumericInputGroup'
14+
import ParameterInput from '../inputs/ParameterInput'
15+
import SelectInput from '../inputs/SelectInput'
16+
import StringInput from '../inputs/StringInput'
17+
import CollapsibleBlock from '../layout/CollapsibleBlock'
18+
import PaginatedList from '../layout/PaginatedList'
19+
20+
export default function GLTFTransformProperties({
21+
transformParms,
22+
onChange
23+
}: {
24+
transformParms: State<ModelTransformParameters>
25+
onChange: (transformParms: ModelTransformParameters) => void
26+
}) {
27+
const onChangeTransformParm = useCallback((scope: State<any>) => {
28+
return (value: typeof scope.value) => {
29+
scope.set(value)
30+
onChange(JSON.parse(JSON.stringify(transformParms.value)))
31+
}
32+
}, [])
33+
34+
const onChangeParameter = useCallback(
35+
(scope: State<any>, key: string) => (val: any) => {
36+
scope[key].set(val)
37+
onChange(JSON.parse(JSON.stringify(transformParms.value)))
38+
},
39+
[]
40+
)
41+
42+
return (
43+
<CollapsibleBlock label="glTF-Transform">
44+
<div
45+
style={{
46+
margin: '2rem',
47+
padding: '1rem',
48+
color: 'var(--textColor)'
49+
}}
50+
>
51+
<InputGroup name="dst" label={t('editor:properties.model.transform.dst')}>
52+
<StringInput value={transformParms.dst.value} onChange={onChangeTransformParm(transformParms.dst)} />
53+
</InputGroup>
54+
<InputGroup name="resource uri" label={t('editor:properties.model.transform.resourceUri')}>
55+
<StringInput
56+
value={transformParms.resourceUri.value}
57+
onChange={onChangeTransformParm(transformParms.resourceUri)}
58+
/>
59+
</InputGroup>
60+
</div>
61+
<hr />
62+
<div
63+
style={{
64+
margin: '2rem',
65+
padding: '1rem',
66+
color: 'var(--textColor)'
67+
}}
68+
>
69+
<InputGroup name="Model Format" label={t('editor:properties.model.transform.modelFormat')}>
70+
<SelectInput
71+
value={transformParms.modelFormat.value}
72+
onChange={onChangeTransformParm(transformParms.modelFormat)}
73+
options={[
74+
{ label: 'glB', value: 'glb' },
75+
{ label: 'glTF', value: 'gltf' }
76+
]}
77+
/>
78+
</InputGroup>
79+
<InputGroup name="Remove Duplicates" label={t('editor:properties.model.transform.removeDuplicates')}>
80+
<BooleanInput value={transformParms.dedup.value} onChange={onChangeTransformParm(transformParms.dedup)} />
81+
</InputGroup>
82+
<InputGroup name="Prune Unused" label={t('editor:properties.model.transform.pruneUnused')}>
83+
<BooleanInput value={transformParms.prune.value} onChange={onChangeTransformParm(transformParms.prune)} />
84+
</InputGroup>
85+
<InputGroup name="Reorder" label={t('editor:properties.model.transform.reorder')}>
86+
<BooleanInput value={transformParms.reorder.value} onChange={onChangeTransformParm(transformParms.reorder)} />
87+
</InputGroup>
88+
<InputGroup name="Weld Vertices" label={t('editor:properties.model.transform.weldVertices')}>
89+
<BooleanInput
90+
value={transformParms.weld.enabled.value}
91+
onChange={onChangeTransformParm(transformParms.weld.enabled)}
92+
/>
93+
</InputGroup>
94+
{transformParms.weld.enabled.value && (
95+
<>
96+
<NumericInputGroup
97+
name="Weld Threshold"
98+
label={t('editor:properties.model.transform.weldThreshold')}
99+
value={transformParms.weld.tolerance.value}
100+
onChange={onChangeTransformParm(transformParms.weld.tolerance)}
101+
min={0}
102+
max={1}
103+
/>
104+
</>
105+
)}
106+
<InputGroup name="Resample Animations" label={t('editor:properties.model.transform.resampleAnimations')}>
107+
<BooleanInput
108+
value={transformParms.resample.value}
109+
onChange={onChangeTransformParm(transformParms.resample)}
110+
/>
111+
</InputGroup>
112+
<InputGroup name="Use DRACO Compression" label={t('editor:properties.model.transform.useDraco')}>
113+
<BooleanInput
114+
value={transformParms.dracoCompression.enabled.value}
115+
onChange={onChangeTransformParm(transformParms.dracoCompression.enabled)}
116+
/>
117+
</InputGroup>
118+
{transformParms.dracoCompression.enabled.value && (
119+
<>
120+
<ParameterInput
121+
entity={`${transformParms}-draco-compression`}
122+
values={transformParms.dracoCompression.options.value}
123+
onChange={onChangeParameter.bind({}, transformParms.dracoCompression.options)}
124+
/>
125+
</>
126+
)}
127+
<InputGroup name="Texture Format" label={t('editor:properties.model.transform.textureFormat')}>
128+
<SelectInput
129+
value={transformParms.textureFormat.value}
130+
onChange={onChangeTransformParm(transformParms.textureFormat)}
131+
options={[
132+
{ label: 'Default', value: 'default' },
133+
{ label: 'JPG', value: 'jpg' },
134+
{ label: 'KTX2', value: 'ktx2' },
135+
{ label: 'PNG', value: 'png' },
136+
{ label: 'WebP', value: 'webp' }
137+
]}
138+
/>
139+
</InputGroup>
140+
<NumericInputGroup
141+
name="Max Texture Size"
142+
label={t('editor:properties.model.transform.maxTextureSize')}
143+
value={transformParms.maxTextureSize.value}
144+
onChange={onChangeTransformParm(transformParms.maxTextureSize)}
145+
max={4096}
146+
min={64}
147+
/>
148+
<InputGroup name="Flip Y" label={t('editor:properties.model.transform.flipY')}>
149+
<BooleanInput value={transformParms.flipY.value} onChange={onChangeTransformParm(transformParms.flipY)} />
150+
</InputGroup>
151+
<InputGroup name="Linear" label={t('editor:properties.model.transform.linear')}>
152+
<BooleanInput value={transformParms.linear.value} onChange={onChangeTransformParm(transformParms.linear)} />
153+
</InputGroup>
154+
{transformParms.textureFormat.value === 'ktx2' && (
155+
<>
156+
<InputGroup
157+
name="Texture Compression Type"
158+
label={t('editor:properties.model.transform.textureCompressionType')}
159+
>
160+
<SelectInput
161+
value={transformParms.textureCompressionType.value}
162+
onChange={onChangeTransformParm(transformParms.textureCompressionType)}
163+
options={[
164+
{ label: 'UASTC', value: 'uastc' },
165+
{ label: 'ETC1', value: 'etc1' }
166+
]}
167+
/>
168+
</InputGroup>
169+
<NumericInputGroup
170+
name="KTX2 Quality"
171+
label={t('editor:properties.model.transform.ktx2Quality')}
172+
value={transformParms.textureCompressionQuality.value}
173+
onChange={onChangeTransformParm(transformParms.textureCompressionQuality)}
174+
max={255}
175+
min={1}
176+
smallStep={1}
177+
mediumStep={1}
178+
largeStep={2}
179+
/>
180+
</>
181+
)}
182+
<CollapsibleBlock label={t('editor:properties.model.transform.resourceOverrides')}>
183+
<PaginatedList
184+
list={transformParms.resources.images}
185+
element={(image: State<ImageTransformParameters>) => {
186+
return (
187+
<>
188+
<div style={{ width: '100%' }}>
189+
<InputGroup name="Resource" label={image.resourceId.value}>
190+
<BooleanInput value={image.enabled.value} onChange={onChangeTransformParm(image.enabled)} />
191+
</InputGroup>
192+
</div>
193+
{image.enabled.value && (
194+
<div style={{ width: '100%' }}>
195+
<BooleanInput
196+
value={image.parameters.textureFormat.enabled.value}
197+
onChange={onChangeTransformParm(image.parameters.textureFormat.enabled)}
198+
/>
199+
<InputGroup name="Texture Format" label={t('editor:properties.model.transform.textureFormat')}>
200+
{image.parameters.textureFormat.enabled.value && (
201+
<SelectInput
202+
value={image.parameters.textureFormat.parameters.value}
203+
onChange={onChangeTransformParm(image.parameters.textureFormat)}
204+
options={[
205+
{ label: 'Default', value: 'default' },
206+
{ label: 'JPG', value: 'jpg' },
207+
{ label: 'KTX2', value: 'ktx2' },
208+
{ label: 'PNG', value: 'png' },
209+
{ label: 'WebP', value: 'webp' }
210+
]}
211+
/>
212+
)}
213+
</InputGroup>
214+
<BooleanInput
215+
value={image.parameters.maxTextureSize.enabled.value}
216+
onChange={onChangeTransformParm(image.parameters.maxTextureSize.enabled)}
217+
/>
218+
<InputGroup name="Max Texture Size" label={t('editor:properties.model.transform.maxTextureSize')}>
219+
{image.parameters.maxTextureSize.enabled.value && (
220+
<NumericInput
221+
value={image.parameters.maxTextureSize.parameters.value}
222+
onChange={onChangeTransformParm(image.parameters.maxTextureSize)}
223+
max={4096}
224+
min={64}
225+
/>
226+
)}
227+
</InputGroup>
228+
<BooleanInput
229+
value={image.parameters.textureCompressionType.enabled.value}
230+
onChange={onChangeTransformParm(image.parameters.textureCompressionType.enabled)}
231+
/>
232+
<InputGroup
233+
name="Texture Compression Type"
234+
label={t('editor:properties.model.transform.textureCompressionType')}
235+
>
236+
{image.parameters.textureCompressionType.enabled.value && (
237+
<SelectInput
238+
value={image.parameters.textureCompressionType.parameters.value}
239+
onChange={onChangeTransformParm(image.parameters.textureCompressionType)}
240+
options={[
241+
{ label: 'UASTC', value: 'uastc' },
242+
{ label: 'ETC1', value: 'etc1' }
243+
]}
244+
/>
245+
)}
246+
</InputGroup>
247+
<BooleanInput
248+
value={image.parameters.textureCompressionQuality.enabled.value}
249+
onChange={onChangeTransformParm(image.parameters.textureCompressionQuality.enabled)}
250+
/>
251+
<InputGroup name="KTX2 Quality" label={t('editor:properties.model.transform.ktx2Quality')}>
252+
{image.parameters.textureCompressionQuality.enabled.value && (
253+
<NumericInput
254+
value={image.parameters.textureCompressionQuality.parameters.value}
255+
onChange={onChangeTransformParm(image.parameters.textureCompressionQuality)}
256+
max={255}
257+
min={1}
258+
smallStep={1}
259+
mediumStep={1}
260+
largeStep={2}
261+
/>
262+
)}
263+
</InputGroup>
264+
<BooleanInput
265+
value={image.parameters.flipY.enabled.value}
266+
onChange={onChangeTransformParm(image.parameters.flipY.enabled)}
267+
/>
268+
<InputGroup name="Flip Y" label={t('editor:properties.model.transform.flipY')}>
269+
{image.parameters.flipY.enabled.value && (
270+
<BooleanInput
271+
value={image.parameters.flipY.parameters.value}
272+
onChange={onChangeTransformParm(image.parameters.flipY)}
273+
/>
274+
)}
275+
</InputGroup>
276+
</div>
277+
)}
278+
</>
279+
)
280+
}}
281+
/>
282+
</CollapsibleBlock>
283+
</div>
284+
</CollapsibleBlock>
285+
)
286+
}

0 commit comments

Comments
 (0)