Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Model Transform in Cloud #8861

Merged
merged 28 commits into from
Nov 1, 2023
Merged
Show file tree
Hide file tree
Changes from 25 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
062b6d6
turn model transform into a job
dinomut1 Sep 20, 2023
964ab71
licensing
dinomut1 Sep 20, 2023
e9b55cb
Merge branch 'refactor-model-transform' of https://github.com/Etherea…
dinomut1 Sep 20, 2023
2ffd8eb
Merge branch 'dev' of https://github.com/EtherealEngine/etherealengin…
dinomut1 Sep 21, 2023
47c5575
checkpoint
dinomut1 Sep 22, 2023
767a69f
Merge branch 'dev' of https://github.com/EtherealEngine/etherealengin…
dinomut1 Oct 10, 2023
3e20043
class adjustment
dinomut1 Oct 12, 2023
6113d93
Merge branch 'dev' of https://github.com/EtherealEngine/etherealengin…
dinomut1 Oct 13, 2023
d07725c
move model transform into engine package
dinomut1 Oct 14, 2023
ca74be8
checkpoint
dinomut1 Oct 16, 2023
d76ecff
Merge branch 'dev' of https://github.com/EtherealEngine/etherealengin…
dinomut1 Oct 16, 2023
085863b
convert fs and path library calls to client code
dinomut1 Oct 17, 2023
1815253
Merge branch 'dev' of https://github.com/EtherealEngine/etherealengin…
dinomut1 Oct 19, 2023
165f0ba
debugging
dinomut1 Oct 19, 2023
9831147
Fix missing draco wasm files on client
dinomut1 Oct 20, 2023
565cf89
Merge branch 'dev' of https://github.com/EtherealEngine/etherealengin…
dinomut1 Oct 20, 2023
3ebe613
checkpoint
dinomut1 Oct 22, 2023
e1dd391
Merge branch 'refactor-model-transform' of https://github.com/Etherea…
dinomut1 Oct 24, 2023
24231ac
Merge branch 'dev' of https://github.com/EtherealEngine/etherealengin…
dinomut1 Oct 24, 2023
be5df2a
create UploadRequestSystem + State
dinomut1 Oct 24, 2023
17c7397
working baseline client-side model transform
dinomut1 Oct 24, 2023
daaf1e5
client-side KTX2 Compression and image resize
dinomut1 Oct 28, 2023
175ca50
Merge branch 'dev' of https://github.com/EtherealEngine/etherealengin…
dinomut1 Oct 28, 2023
da591e7
Merge branch 'dev' of https://github.com/EtherealEngine/etherealengin…
dinomut1 Oct 30, 2023
2ce1656
add basic client-side meshopt support
dinomut1 Oct 30, 2023
8d20959
change server-side code to use engine transformer
dinomut1 Nov 1, 2023
0174add
fix transformModel call
dinomut1 Nov 1, 2023
3dbcdac
Merge branch 'dev' into refactor-model-transform
speigg Nov 1, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
6 changes: 6 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,12 @@
"request": "launch",
"type": "node-terminal",
},
{
"command": "npm run dev-tabs",
"name": "npm run dev-tabs",
"request": "launch",
"type": "node-terminal",
},
{
"command": "npm run prepare-database",
"name": "npm run prepare-database",
Expand Down
Binary file added packages/client/public/draco_decoder_gltf.wasm
Binary file not shown.
Binary file added packages/client/public/draco_encoder.wasm
Binary file not shown.
3 changes: 2 additions & 1 deletion packages/common/src/utils/CommonKnownContentTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,8 @@ export const CommonKnownContentTypes = {
tsx: 'application/octet-stream',
ts: 'application/octet-stream',
js: 'application/octet-stream',
json: 'application/json'
json: 'application/json',
bin: 'application/octet-stream'
}

export const MimeTypeToExtension = {
Expand Down
36 changes: 36 additions & 0 deletions packages/common/src/utils/miscUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,3 +70,39 @@ export function arraysAreEqual(arr1: any[], arr2: any[]): boolean {

return true
}

export function pathJoin(...parts: string[]): string {
const separator = '/'

return parts
.map((part, index) => {
// If it's the first part, we only want to remove trailing slashes
if (index === 0) {
while (part.endsWith(separator)) {
part = part.substring(0, part.length - 1)
}
}
// If it's the last part, we only want to remove leading slashes
else if (index === parts.length - 1) {
while (part.startsWith(separator)) {
part = part.substring(1)
}
}
// For all other parts, remove leading and trailing slashes
else {
while (part.startsWith(separator)) {
part = part.substring(1)
}
while (part.endsWith(separator)) {
part = part.substring(0, part.length - 1)
}
}

return part
})
.join(separator)
}

export function baseName(path: string): string {
return path.split(/[\\/]/).pop()!
}
84 changes: 84 additions & 0 deletions packages/common/src/utils/objectToCommandLineArgs.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
/*
CPAL-1.0 License

The contents of this file are subject to the Common Public Attribution License
Version 1.0. (the "License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
https://github.com/EtherealEngine/etherealengine/blob/dev/LICENSE.
The License is based on the Mozilla Public License Version 1.1, but Sections 14
and 15 have been added to cover use of software over a computer network and
provide for limited attribution for the Original Developer. In addition,
Exhibit A has been modified to be consistent with Exhibit B.

Software distributed under the License is distributed on an "AS IS" basis,
WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the
specific language governing rights and limitations under the License.

The Original Code is Ethereal Engine.

The Original Developer is the Initial Developer. The Initial Developer of the
Original Code is the Ethereal Engine team.

All portions of the code written by the Ethereal Engine team are Copyright © 2021-2023
Ethereal Engine. All Rights Reserved.
*/

function castValue(value, type) {
switch (type.toLowerCase()) {
case 'number':
return parseFloat(value)
case 'boolean':
return value === 'true'
case 'string':
return value
default:
return value
}
}

export function objectToArgs(obj: any, prefix = '') {
const args: string[] = []

for (const [key, value] of Object.entries(obj)) {
let newPrefix = prefix ? `${prefix}_${key}` : `${key}`

if (Array.isArray(value)) {
value.forEach((item, index) => {
args.push(...objectToArgs(item, `${newPrefix}_${index}`))
})
} else if (value !== null && typeof value === 'object') {
args.push(...objectToArgs(value, newPrefix))
} else {
const type = typeof value
args.push(`--${newPrefix}_${type}`, String(value))
}
}

return args
}

export function argsToObject(args: string[]): any {
const obj: Record<string, any> = {}
for (let i = 0; i < args.length; i += 2) {
const arg = args[i]
const value = args[i + 1]

const keys: string[] = arg.slice(2).split('_')
const type = keys.pop()
const parsedKeys = keys.map((key, i) => (isNaN(Number.parseFloat(keys[i])) ? key.toLowerCase() : Number(key)))

let current = obj

parsedKeys.forEach((key, index) => {
if (index === keys.length - 1) {
current[key] = castValue(value, type)
} else {
if (current[key] === undefined) {
current[key] = typeof keys[index + 1] === 'number' ? [] : {}
}
current = current[key]
}
})
}
return obj
}
10 changes: 0 additions & 10 deletions packages/editor/src/components/assets/CompressionPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ Ethereal Engine. All Rights Reserved.
import { t } from 'i18next'
import React from 'react'

import { API } from '@etherealengine/client-core/src/API'
import Button from '@etherealengine/client-core/src/common/components/Button'
import Menu from '@etherealengine/client-core/src/common/components/Menu'
import { uploadToFeathersService } from '@etherealengine/client-core/src/util/upload'
Expand Down Expand Up @@ -123,15 +122,6 @@ export default function CompressionPanel({
openCompress.set(false)
}

/** @todo */
const compressContent = async () => {
const props = fileProperties.value
compressProperties.src.set(props.type === 'folder' ? `${props.url}/${props.key}` : props.url)
const compressedPath = await API.instance.client.service('ktx2-encode').create(compressProperties.value)
await onRefreshDirectory()
openCompress.set(false)
}

return (
<Menu
open={openCompress.value}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ import { getModelResources } from '@etherealengine/engine/src/scene/functions/lo
import { useHookstate } from '@etherealengine/hyperflux'
import { getMutableState, State } from '@etherealengine/hyperflux/functions/StateFunctions'

import { transformModel as clientSideTransformModel } from '@etherealengine/engine/src/assets/compression/ModelTransformFunctions'
import { modelTransformPath } from '@etherealengine/engine/src/schemas/assets/model-transform.schema'
import exportGLTF from '../../functions/exportGLTF'
import { SelectionState } from '../../services/SelectionServices'
import BooleanInput from '../inputs/BooleanInput'
Expand All @@ -58,8 +60,6 @@ import TexturePreviewInput from '../inputs/TexturePreviewInput'
import CollapsibleBlock from '../layout/CollapsibleBlock'
import GLTFTransformProperties from './GLTFTransformProperties'
import LightmapBakerProperties from './LightmapBakerProperties'

import { modelTransformPath } from '@etherealengine/engine/src/schemas/assets/model-transform.schema'
import './ModelTransformProperties.css'

export default function ModelTransformProperties({
Expand All @@ -73,9 +73,10 @@ export default function ModelTransformProperties({
const selectionState = useHookstate(getMutableState(SelectionState))
const transforming = useHookstate<boolean>(false)
const transformHistory = useHookstate<string[]>([])

const isClientside = useHookstate<boolean>(false)
const transformParms = useHookstate<ModelTransformParameters>({
...DefaultModelTransformParameters,
src: modelState.src.value,
modelFormat: modelState.src.value.endsWith('.gltf') ? 'gltf' : 'glb'
})

Expand Down Expand Up @@ -133,10 +134,12 @@ export default function ModelTransformProperties({
(modelState: State<ComponentType<typeof ModelComponent>>) => async () => {
transforming.set(true)
const modelSrc = modelState.src.value
const nuPath = await Engine.instance.api.service(modelTransformPath).create({
src: modelSrc,
transformParameters: transformParms.value
})
if (isClientside.value) {
await clientSideTransformModel(transformParms.value)
} else {
await Engine.instance.api.service(modelTransformPath).create(transformParms.value)
}
const nuPath = modelSrc.replace(/\.glb$/, '-transformed.glb')
transformHistory.set([modelSrc, ...transformHistory.value])
const [_, directoryToRefresh, fileName] = /.*\/(projects\/.*)\/([\w\d\s\-_.]*)$/.exec(nuPath)!
await FileBrowserService.fetchFiles(directoryToRefresh)
Expand Down Expand Up @@ -182,10 +185,7 @@ export default function ModelTransformProperties({
console.log('saved baked model')
//perform gltf transform
console.log('transforming model at ' + bakedPath + '...')
const transformedPath = await Engine.instance.api.service(modelTransformPath).create({
src: bakedPath,
transformParameters: transformParms.value
})
const transformedPath = await Engine.instance.api.service(modelTransformPath).create(transformParms.value)
console.log('transformed model into ' + transformedPath)
onChangeModel(transformedPath)
}
Expand All @@ -211,9 +211,19 @@ export default function ModelTransformProperties({
onChange={(transformParms: ModelTransformParameters) => {}}
/>
{!transforming.value && (
<button className="OptimizeButton button" onClick={onTransformModel(modelState)}>
Optimize
</button>
<>
<InputGroup name="Clientside Transform" label="Clientside Transform">
<BooleanInput
value={isClientside.value}
onChange={(val: boolean) => {
isClientside.set(val)
}}
/>
</InputGroup>
<button className="OptimizeButton button" onClick={onTransformModel(modelState)}>
Optimize
</button>
</>
)}
{transforming.value && <p>Transforming...</p>}
{transformHistory.length > 0 && <Button onClick={onUndoTransform}>Undo</Button>}
Expand Down
3 changes: 2 additions & 1 deletion packages/editor/src/pages/EditorPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ import { GizmoSystem } from '../systems/GizmoSystem'
import { ModelHandlingSystem } from '../systems/ModelHandlingSystem'

import { useDefaultLocationSystems } from '@etherealengine/client-core/src/world/useDefaultLocationSystems'
import { UploadRequestSystem } from '../systems/UploadRequestSystem'

// ensure all our systems are imported, #9077
const EditorSystemsReferenced = [useDefaultLocationSystems]
Expand All @@ -59,7 +60,7 @@ const editorSystems = () => {
startSystems([EditorFlyControlSystem, EditorControlSystem, EditorCameraSystem, GizmoSystem], {
before: PresentationSystemGroup
})
startSystems([ModelHandlingSystem], { with: SimulationSystemGroup })
startSystems([ModelHandlingSystem, UploadRequestSystem], { with: SimulationSystemGroup })

startSystems([EditorInstanceNetworkingSystem, ClientNetworkingSystem, RenderInfoSystem], {
after: PresentationSystemGroup
Expand Down
51 changes: 51 additions & 0 deletions packages/editor/src/systems/UploadRequestSystem.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/*
CPAL-1.0 License

The contents of this file are subject to the Common Public Attribution License
Version 1.0. (the "License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
https://github.com/EtherealEngine/etherealengine/blob/dev/LICENSE.
The License is based on the Mozilla Public License Version 1.1, but Sections 14
and 15 have been added to cover use of software over a computer network and
provide for limited attribution for the Original Developer. In addition,
Exhibit A has been modified to be consistent with Exhibit B.

Software distributed under the License is distributed on an "AS IS" basis,
WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the
specific language governing rights and limitations under the License.

The Original Code is Ethereal Engine.

The Original Developer is the Initial Developer. The Initial Developer of the
Original Code is the Ethereal Engine team.

All portions of the code written by the Ethereal Engine team are Copyright © 2021-2023
Ethereal Engine. All Rights Reserved.
*/

import { defineSystem } from '@etherealengine/engine/src/ecs/functions/SystemFunctions'
import { defineAction, getMutableState, useState } from '@etherealengine/hyperflux'
import { useEffect } from 'react'

import { UploadRequestState } from '@etherealengine/engine/src/assets/state/UploadRequestState'
import { uploadProjectFiles } from '../functions/assetFunctions'

const clearUploadQueueAction = defineAction({
type: 'ee.editor.clearUploadQueueAction'
})

export const UploadRequestSystem = defineSystem({
uuid: 'ee.editor.UploadRequestSystem',
reactor: () => {
const uploadRequestState = useState(getMutableState(UploadRequestState))
useEffect(() => {
const uploadRequests = uploadRequestState.queue.value
if (uploadRequests.length === 0) return
const uploadPromises = uploadRequests.map((uploadRequest) => {
return uploadProjectFiles(uploadRequest.projectName, [uploadRequest.file], true)
})
uploadRequestState.queue.set([])
}, [uploadRequestState.queue.length])
return null
}
})
8 changes: 8 additions & 0 deletions packages/engine/src/assets/classes/ModelTransform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,13 +101,15 @@ export type ResourceTransforms = {
}

export type ModelTransformParameters = ExtractedImageTransformParameters & {
src: string
dst: string
resourceUri: string
split: boolean
combineMaterials: boolean
instance: boolean
dedup: boolean
flatten: boolean

join: {
enabled: boolean
options: JoinOptions
Expand All @@ -117,27 +119,33 @@ export type ModelTransformParameters = ExtractedImageTransformParameters & {
enabled: boolean
options: PaletteOptions
}

prune: boolean
reorder: boolean
resample: boolean

weld: {
enabled: boolean
tolerance: number
}

meshoptCompression: {
enabled: boolean
options: GLTFPackOptions
}

dracoCompression: {
enabled: boolean
options: DracoOptions
}

modelFormat: 'glb' | 'gltf'

resources: ResourceTransforms
}

export const DefaultModelTransformParameters: ModelTransformParameters = {
src: '',
dst: '',
resourceUri: '',
modelFormat: 'gltf',
Expand Down