diff --git a/packages/client-core/i18n/en/editor.json b/packages/client-core/i18n/en/editor.json
index ed3f3e972c..bb3fd3bfc8 100755
--- a/packages/client-core/i18n/en/editor.json
+++ b/packages/client-core/i18n/en/editor.json
@@ -282,6 +282,7 @@
"textureFormat": "Image Format"
},
"lods": {
+ "label": "Level of Detail",
"serialize": "Serialize",
"generate": "Generate LODs"
}
diff --git a/packages/client-core/src/components/ConferenceMode/ConferenceModeParticipant.tsx b/packages/client-core/src/components/ConferenceMode/ConferenceModeParticipant.tsx
index 3f837f9689..90ace4c356 100644
--- a/packages/client-core/src/components/ConferenceMode/ConferenceModeParticipant.tsx
+++ b/packages/client-core/src/components/ConferenceMode/ConferenceModeParticipant.tsx
@@ -1,5 +1,6 @@
import classNames from 'classnames'
import React from 'react'
+import { useTranslation } from 'react-i18next'
import { getAvatarURLForUser } from '@etherealengine/client-core/src/user/components/UserMenu/util'
import { PeerID } from '@etherealengine/common/src/interfaces/PeerID'
@@ -16,6 +17,8 @@ interface Props {
type: 'cam' | 'screen'
}
+const { t } = useTranslation()
+
const ConferenceModeParticipant = ({ peerID, type }: Props): JSX.Element => {
const {
user,
@@ -34,7 +37,6 @@ const ConferenceModeParticipant = ({ peerID, type }: Props): JSX.Element => {
audioProducerPaused,
videoProducerGlobalMute,
audioProducerGlobalMute,
- t,
toggleAudio,
toggleVideo,
adjustVolume,
diff --git a/packages/editor/src/components/inputs/TexturePreviewInput.tsx b/packages/editor/src/components/inputs/TexturePreviewInput.tsx
index badc26fba0..acb079cfcb 100644
--- a/packages/editor/src/components/inputs/TexturePreviewInput.tsx
+++ b/packages/editor/src/components/inputs/TexturePreviewInput.tsx
@@ -5,6 +5,7 @@ import { AssetLoader } from '@etherealengine/engine/src/assets/classes/AssetLoad
import { ImageFileTypes, VideoFileTypes } from '@etherealengine/engine/src/assets/constants/fileTypes'
import { AssetClass } from '@etherealengine/engine/src/assets/enum/AssetClass'
import { useHookstate } from '@etherealengine/hyperflux'
+import Button from '@etherealengine/ui/src/Button'
import { Stack } from '@mui/material'
@@ -97,6 +98,19 @@ export default function TexturePreviewInput({
/>
>
)}
+ {value && (
+ <>
+
+
+
+ >
+ )}
)
diff --git a/packages/editor/src/components/materials/MaterialEditor.tsx b/packages/editor/src/components/materials/MaterialEditor.tsx
index b883fc1d0f..13afbf0c62 100644
--- a/packages/editor/src/components/materials/MaterialEditor.tsx
+++ b/packages/editor/src/components/materials/MaterialEditor.tsx
@@ -9,8 +9,8 @@ import {
materialFromId,
prototypeFromId
} from '@etherealengine/engine/src/renderer/materials/functions/MaterialLibraryFunctions'
-import { useMaterialLibrary } from '@etherealengine/engine/src/renderer/materials/MaterialLibrary'
-import { useState } from '@etherealengine/hyperflux'
+import { MaterialLibraryState } from '@etherealengine/engine/src/renderer/materials/MaterialLibrary'
+import { getMutableState, none, useState } from '@etherealengine/hyperflux'
import { Box, Divider, Stack } from '@mui/material'
@@ -23,11 +23,11 @@ import StringInput from '../inputs/StringInput'
export default function MaterialEditor({ material, ...rest }: { ['material']: Material }) {
if (material === undefined) return <>>
- const materialComponent = useState(materialFromId(material.uuid))
- const prototypeComponent = useState(prototypeFromId(materialComponent.prototype.value))
+ const materialLibrary = useState(getMutableState(MaterialLibraryState))
+ const materialComponent = materialLibrary.materials[material.uuid]
+ const prototypeComponent = materialLibrary.prototypes[materialComponent.prototype.value]
const loadingData = useState(false)
const selectionState = accessSelectionState()
- const materialLibrary = useMaterialLibrary()
const prototypes = Object.values(materialLibrary.prototypes.value).map((prototype) => ({
label: prototype.prototypeId,
value: prototype.prototypeId
@@ -54,11 +54,22 @@ export default function MaterialEditor({ material, ...rest }: { ['material']: Ma
return result
}, [materialComponent.parameters, materialComponent.material.uuid, materialComponent.prototype])
- const clearThumbs = async () => {
+ const checkThumbs = useCallback(async () => {
+ thumbnails.promised && (await thumbnails.promise)
+ const thumbnailVals = thumbnails.value
+ Object.entries(thumbnailVals).map(([k, v]) => {
+ if (!material[k]) {
+ URL.revokeObjectURL(v)
+ thumbnails[k].set(none)
+ }
+ })
+ }, [])
+
+ const clearThumbs = useCallback(async () => {
thumbnails.promised && (await thumbnails.promise)
Object.values(thumbnails.value).map(URL.revokeObjectURL)
thumbnails.set({})
- }
+ }, [])
useEffect(() => {
loadingData.set(true)
@@ -70,6 +81,10 @@ export default function MaterialEditor({ material, ...rest }: { ['material']: Ma
})
}, [materialComponent.prototype, materialComponent.material])
+ useEffect(() => {
+ checkThumbs()
+ }, [materialComponent.parameters])
+
return (
@@ -96,7 +111,7 @@ export default function MaterialEditor({ material, ...rest }: { ['material']: Ma
- {!loadingData.get() && (
+ {!loadingData.value && (
async (val) => {
let prop
if (prototypeComponent.arguments.value[k].type === 'texture' && typeof val === 'string') {
@@ -132,24 +147,9 @@ export default function MaterialEditor({ material, ...rest }: { ['material']: Ma
)
materialComponent.parameters[k].set(prop)
}}
- defaults={loadingData.get() ? {} : prototypeComponent.arguments.value}
+ defaults={loadingData.value ? {} : prototypeComponent.arguments.value}
thumbnails={thumbnails.value}
/>
- {/*
- */}
)
}
diff --git a/packages/editor/src/components/properties/ModelNodeEditor.tsx b/packages/editor/src/components/properties/ModelNodeEditor.tsx
index 863301432e..385185448d 100755
--- a/packages/editor/src/components/properties/ModelNodeEditor.tsx
+++ b/packages/editor/src/components/properties/ModelNodeEditor.tsx
@@ -22,7 +22,7 @@ import { useState } from '@etherealengine/hyperflux'
import ViewInArIcon from '@mui/icons-material/ViewInAr'
import exportGLTF from '../../functions/exportGLTF'
-import { createLODsFromModel } from '../../functions/lodsFromModel'
+import { convertToScaffold, createLODsFromModel } from '../../functions/lodsFromModel'
import { LODsFromModelParameters } from '../../functions/lodsFromModel'
import { StaticResourceService } from '../../services/StaticResourceService'
import BooleanInput from '../inputs/BooleanInput'
@@ -30,6 +30,7 @@ import { Button, PropertiesPanelButton } from '../inputs/Button'
import InputGroup from '../inputs/InputGroup'
import ModelInput from '../inputs/ModelInput'
import SelectInput from '../inputs/SelectInput'
+import CollapsibleBlock from '../layout/CollapsibleBlock'
import Well from '../layout/Well'
import ModelTransformProperties from './ModelTransformProperties'
import NodeEditor from './NodeEditor'
@@ -160,19 +161,24 @@ export const ModelNodeEditor: EditorComponentType = (props) => {
-
-
-
LODs
+
+
+
+
LODs
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
+
modelComponent.src.set(val)} />
{!exporting.value && modelComponent.src.value && (
diff --git a/packages/editor/src/components/properties/ModelTransformProperties.tsx b/packages/editor/src/components/properties/ModelTransformProperties.tsx
index 7c81ea2c19..aa1b426008 100644
--- a/packages/editor/src/components/properties/ModelTransformProperties.tsx
+++ b/packages/editor/src/components/properties/ModelTransformProperties.tsx
@@ -9,6 +9,7 @@ import {
ImageTransformParameters,
ModelTransformParameters
} from '@etherealengine/engine/src/assets/classes/ModelTransform'
+import { Engine } from '@etherealengine/engine/src/ecs/classes/Engine'
import { Entity } from '@etherealengine/engine/src/ecs/classes/Entity'
import {
ComponentType,
@@ -217,8 +218,8 @@ export default function ModelTransformProperties({
(modelState: State>) => async () => {
transforming.set(true)
const modelSrc = modelState.src.value
- const nuPath = await API.instance.client.service('model-transform').create({
- path: modelSrc,
+ const nuPath = await Engine.instance.api.service('model-transform').create({
+ src: modelSrc,
transformParameters: transformParms.value
})
transformHistory.set([modelSrc, ...transformHistory.value])
@@ -266,8 +267,8 @@ export default function ModelTransformProperties({
console.log('saved baked model')
//perform gltf transform
console.log('transforming model at ' + bakedPath + '...')
- const transformedPath = await API.instance.client.service('model-transform').create({
- path: bakedPath,
+ const transformedPath = await Engine.instance.api.service('model-transform').create({
+ src: bakedPath,
transformParameters: transformParms.value
})
console.log('transformed model into ' + transformedPath)
diff --git a/packages/editor/src/components/properties/Object3DNodeEditor.tsx b/packages/editor/src/components/properties/Object3DNodeEditor.tsx
index 757d88fd9b..d6702c657c 100755
--- a/packages/editor/src/components/properties/Object3DNodeEditor.tsx
+++ b/packages/editor/src/components/properties/Object3DNodeEditor.tsx
@@ -12,14 +12,14 @@ import { Engine } from '@etherealengine/engine/src/ecs/classes/Engine'
import { hasComponent } from '@etherealengine/engine/src/ecs/functions/ComponentFunctions'
import { EntityTreeComponent } from '@etherealengine/engine/src/ecs/functions/EntityTree'
import { materialFromId } from '@etherealengine/engine/src/renderer/materials/functions/MaterialLibraryFunctions'
-import { getMaterialLibrary } from '@etherealengine/engine/src/renderer/materials/MaterialLibrary'
+import { MaterialLibraryState } from '@etherealengine/engine/src/renderer/materials/MaterialLibrary'
import {
addObjectToGroup,
Object3DWithEntity,
removeObjectFromGroup
} from '@etherealengine/engine/src/scene/components/GroupComponent'
import { TransformSpace } from '@etherealengine/engine/src/scene/constants/transformConstants'
-import { dispatchAction, useHookstate } from '@etherealengine/hyperflux'
+import { dispatchAction, getState, useHookstate } from '@etherealengine/hyperflux'
import { Divider } from '@mui/material'
@@ -46,7 +46,6 @@ type Object3DProps = {
obj3d: Object3D
multiEdit: boolean
}
-
/**
* Object3DNodeEditor component used to provide the editor view to customize Object3D properties inside a model.
*
@@ -57,7 +56,7 @@ export const Object3DNodeEditor = (props: Object3DProps) => {
console.log(props)
const scene: Scene = Engine.instance.scene
const selectionState = accessSelectionState()
- const materialLibrary = getMaterialLibrary()
+ const materialLibrary = getState(MaterialLibraryState)
const obj3d: Object3D = props.obj3d as any
const mesh = obj3d as Mesh
const instancedMesh = obj3d as InstancedMesh
@@ -272,7 +271,7 @@ export const Object3DNodeEditor = (props: Object3DProps) => {
{
- if (materialLibrary.materials[nuId].value) {
+ if (materialLibrary.materials[nuId]) {
if (Array.isArray(mesh.material)) {
mesh.material[currentMaterialId.value] = materialFromId('' + nuId).material
} else {
@@ -315,11 +314,11 @@ export const Object3DNodeEditor = (props: Object3DProps) => {
{
- let transform = new Matrix4()
+ const transform = new Matrix4()
instancedMesh.getMatrixAt(i, transform)
- let position = new Vector3()
- let rotation = new Quaternion()
- let scale = new Vector3()
+ const position = new Vector3()
+ const rotation = new Quaternion()
+ const scale = new Vector3()
transform.decompose(position, rotation, scale)
const euler = new Euler()
diff --git a/packages/editor/src/functions/lodsFromModel.ts b/packages/editor/src/functions/lodsFromModel.ts
index a76ba0493a..122de2197b 100644
--- a/packages/editor/src/functions/lodsFromModel.ts
+++ b/packages/editor/src/functions/lodsFromModel.ts
@@ -1,4 +1,4 @@
-import { InstancedMesh, Mesh } from 'three'
+import { BufferGeometry, InstancedMesh, Mesh, MeshBasicMaterial } from 'three'
import createGLTFExporter from '@etherealengine/engine/src/assets/functions/createGLTFExporter'
import { pathResolver } from '@etherealengine/engine/src/assets/functions/pathResolver'
@@ -6,10 +6,16 @@ import { Entity } from '@etherealengine/engine/src/ecs/classes/Entity'
import {
addComponent,
getComponent,
- getMutableComponent
+ getMutableComponent,
+ setComponent
} from '@etherealengine/engine/src/ecs/functions/ComponentFunctions'
import { createEntity, removeEntity } from '@etherealengine/engine/src/ecs/functions/EntityFunctions'
import { addEntityNodeChild } from '@etherealengine/engine/src/ecs/functions/EntityTree'
+import { SourceType } from '@etherealengine/engine/src/renderer/materials/components/MaterialSource'
+import {
+ removeMaterialSource,
+ unregisterMaterial
+} from '@etherealengine/engine/src/renderer/materials/functions/MaterialLibraryFunctions'
import { LODComponent, LODLevel } from '@etherealengine/engine/src/scene/components/LODComponent'
import { ModelComponent } from '@etherealengine/engine/src/scene/components/ModelComponent'
import { NameComponent } from '@etherealengine/engine/src/scene/components/NameComponent'
@@ -19,6 +25,7 @@ import iterateObject3D from '@etherealengine/engine/src/scene/util/iterateObject
import { State } from '@etherealengine/hyperflux'
import { uploadProjectFiles } from './assetFunctions'
+import exportGLTF from './exportGLTF'
export type LODsFromModelParameters = {
serialize: boolean
@@ -52,7 +59,7 @@ export async function createLODsFromModel(
const mesh = meshes[i]
const lodEntity = createEntity()
addEntityNodeChild(lodEntity, entity)
- addComponent(lodEntity, LODComponent, {
+ setComponent(lodEntity, LODComponent, {
target: entity,
lodPath: mesh.userData['lodPath'],
levels: [
@@ -65,7 +72,7 @@ export async function createLODsFromModel(
],
instanced: mesh instanceof InstancedMesh
})
- addComponent(lodEntity, NameComponent, mesh.name)
+ setComponent(lodEntity, NameComponent, mesh.name)
processLoadedLODLevel(lodEntity, 0, mesh)
if (options.serialize) {
const lodComponent = getMutableComponent(lodEntity, LODComponent)
@@ -86,13 +93,13 @@ export async function serializeLOD(
) {
const mesh = getFirstMesh(level.model.value!)!
//clone the mesh and remove its world matrix so it can be exported
- const toExport = mesh.clone()
+ //also convert instanced meshes into singleton version as instance matrix data is stored in the scaffold
+ const toExport = mesh instanceof InstancedMesh ? new Mesh(mesh.geometry, mesh.material) : mesh.clone()
toExport.removeFromParent()
toExport.position.set(0, 0, 0)
toExport.rotation.set(0, 0, 0)
toExport.scale.set(1, 1, 1)
toExport.updateMatrixWorld()
- toExport.updateMatrix()
const [, , projectName, fileName] = pathResolver().exec(rootSrc)!
//create a new filename for the lod
const nuRelativePath = fileName.replace(/\.[^.]*$/, `_${mesh.name}.gltf`)
@@ -110,3 +117,28 @@ export async function serializeLOD(
const urls = await Promise.all(uploadProjectFiles(projectName, [file]).promises)
level.src.set(urls[0][0])
}
+
+export function convertToScaffold(entity: Entity) {
+ const modelComponent = getComponent(entity, ModelComponent)
+ modelComponent.scene &&
+ iterateObject3D(modelComponent.scene, (mesh: Mesh | InstancedMesh) => {
+ if (!mesh?.isMesh) return
+ mesh.geometry = new BufferGeometry()
+ mesh.material = new MeshBasicMaterial()
+ })
+ const scaffoldPath = modelComponent.src.replace(/(\.[^.]*$)/, '_scaffold$1')
+ exportGLTF(entity, scaffoldPath).then(() => {
+ getMutableComponent(entity, ModelComponent).src.set(scaffoldPath)
+ LODComponent.lodsByEntity[entity].value?.map((lodEntity) => {
+ const lodComponent = getMutableComponent(lodEntity, LODComponent)
+ lodComponent.levels.map((level) => {
+ level.model.set(null)
+ removeMaterialSource({
+ path: level.src.value,
+ type: SourceType.MODEL
+ })
+ level.loaded.set(false)
+ })
+ })
+ })
+}
diff --git a/packages/engine/src/assets/exporters/gltf/GLTFExporter.js b/packages/engine/src/assets/exporters/gltf/GLTFExporter.js
index 8a88a10ebf..ce9545665e 100644
--- a/packages/engine/src/assets/exporters/gltf/GLTFExporter.js
+++ b/packages/engine/src/assets/exporters/gltf/GLTFExporter.js
@@ -3,6 +3,7 @@ import {
ClampToEdgeWrapping,
Color,
DoubleSide,
+ Group,
InterpolateDiscrete,
InterpolateLinear,
LinearEncoding,
@@ -21,9 +22,11 @@ import {
Scene,
Source,
sRGBEncoding,
- Vector3
+ Vector3,
+ Texture
} from 'three';
+import createReadableTexture from '@etherealengine/engine/src/assets/functions/createReadableTexture'
/**
* The KHR_mesh_quantization extension allows these extra attribute component types
@@ -1219,8 +1222,8 @@ export class GLTFWriter {
resolve({})
}))
} else {
- const canvas = getCanvas();
-
+ const canvas = getCanvas();
+
canvas.width = Math.min( image.width, options.maxTextureSize );
canvas.height = Math.min( image.height, options.maxTextureSize );
@@ -1235,7 +1238,7 @@ export class GLTFWriter {
if ( image.data !== undefined ) { // THREE.DataTexture
if ( format !== RGBAFormat ) {
-
+//pass this since basisU extension may be present
}
else
@@ -1263,23 +1266,24 @@ export class GLTFWriter {
ctx.drawImage( image, 0, 0, canvas.width, canvas.height );
}
+
+
if ( options.binary === true ) {
pending.push(
-
- getToBlobPromise( canvas, mimeType )
+ //createReadableTexture(new Texture(image), {flipY: true, canvas: true}).then((texture) =>
+ getToBlobPromise( /*texture.image*/canvas, mimeType )
.then( blob => writer.processBufferViewImage( blob ) )
.then( bufferViewIndex => {
imageDef.bufferView = bufferViewIndex;
} )
-
- );
-
+ )
+ //)
} else {
-
+/*
if ( canvas.toDataURL !== undefined ) {
imageDef.uri = canvas.toDataURL( mimeType );
@@ -1299,7 +1303,7 @@ export class GLTFWriter {
);
}
-
+*/
}
}
} else {
@@ -2310,7 +2314,10 @@ export class GLTFWriter {
for ( let i = 0; i < input.length; i ++ ) {
- if ( input[ i ] instanceof Scene ) {
+ if (
+ input[ i ] instanceof Scene
+ || input[ i ] instanceof Group
+ ) {
this.processScene( input[ i ] );
@@ -2321,7 +2328,7 @@ export class GLTFWriter {
}
}
-
+
if ( objectsWithoutScene.length > 0 ) this.processObjects( objectsWithoutScene );
for ( let i = 0; i < this.skins.length; ++ i ) {
diff --git a/packages/engine/src/assets/exporters/gltf/extensions/BasisuExporterExtension.ts b/packages/engine/src/assets/exporters/gltf/extensions/BasisuExporterExtension.ts
index 78a3681a7b..f514c1bbde 100644
--- a/packages/engine/src/assets/exporters/gltf/extensions/BasisuExporterExtension.ts
+++ b/packages/engine/src/assets/exporters/gltf/extensions/BasisuExporterExtension.ts
@@ -1,5 +1,5 @@
import { ImageDataType } from '@loaders.gl/images'
-import { CompressedTexture, Texture } from 'three'
+import { CompressedTexture, LinearEncoding, Texture } from 'three'
import { dispatchAction } from '@etherealengine/hyperflux'
import { KTX2Encoder } from '@etherealengine/xrui/core/textures/KTX2Encoder'
@@ -12,7 +12,7 @@ import { ExporterExtension } from './ExporterExtension'
const hashCanvas = document.createElement('canvas')
-export function getImageHash(image: HTMLImageElement | HTMLCanvasElement, size = 8) {
+function getImageHash(image: HTMLImageElement | HTMLCanvasElement, size = 8) {
hashCanvas.width = size
hashCanvas.height = size
const context = hashCanvas.getContext('2d')!
@@ -38,7 +38,7 @@ export function getImageHash(image: HTMLImageElement | HTMLCanvasElement, size =
return hash
}
-export function stageHash(pHash: string | number, bits = 256): string {
+function stageHash(pHash: string | number, bits = 256): string {
const binaryHash = (typeof pHash === 'number' ? pHash : parseInt(pHash, 16)).toString(2).padStart(bits, '0')
return binaryHash
}
@@ -59,15 +59,13 @@ export default class BasisuExporterExtension extends ExporterExtension implement
writeTexture(_texture: CompressedTexture, textureDef) {
if (!_texture?.isCompressedTexture) return
const writer = this.writer
-
+ _texture.encoding = LinearEncoding
writer.pending.push(
new Promise((resolve) => {
createReadableTexture(_texture, { canvas: true, flipY: true }).then((texture: Texture) => {
textureDef.sampler = this.sampler
const image: HTMLCanvasElement = texture.source.data
- const ktx2write = new KTX2Encoder()
-
const imageDef: any = {
width: image.width,
height: image.height,
@@ -85,10 +83,10 @@ export default class BasisuExporterExtension extends ExporterExtension implement
textureDef.sampler = this.sampler
const stagedHash = stageHash(imgId)
const similarImages = this.lshIndex.query(stagedHash, 5)
-
- similarImages.length && (imageDef.uri = this.imageIdCache[similarImages[0]])
+ const ktx2Encoder = new KTX2Encoder()
+ similarImages.length && ((imageDef.uri = this.imageIdCache[similarImages[0]]) || resolve(null))
!similarImages.length &&
- ktx2write.encode(imgData, false, 2, false, false).then(async (arrayBuffer) => {
+ ktx2Encoder.encode(imgData, false, 2, false, false).then(async (arrayBuffer) => {
if (writer.options.embedImages) {
const bufferIdx = writer.processBuffer(arrayBuffer)
@@ -110,28 +108,23 @@ export default class BasisuExporterExtension extends ExporterExtension implement
imageDef.uri = relativeURI
this.imageIdCache[stagedHash] = relativeURI
this.lshIndex.add(stagedHash, stagedHash)
- writer.pending.push(
- new Promise((resolve) => {
- const saveParms = {
- name: `${imgId}`,
- byteLength: arrayBuffer.byteLength,
- uri: `${projectSpaceURI}/${imgId}.ktx2`,
- buffer: arrayBuffer
- }
- dispatchAction(
- BufferHandlerExtension.saveBuffer({
- saveParms,
- projectName,
- modelName: `${imgId}`
- })
- )
- resolve(null)
+ const saveParms = {
+ name: `${imgId}`,
+ byteLength: arrayBuffer.byteLength,
+ uri: `${projectSpaceURI}/${imgId}.ktx2`,
+ buffer: arrayBuffer
+ }
+ dispatchAction(
+ BufferHandlerExtension.saveBuffer({
+ saveParms,
+ projectName,
+ modelName: `${imgId}`
})
)
}
+ ktx2Encoder.pool.dispose()
resolve(null)
})
- resolve(null)
})
})
)
diff --git a/packages/engine/src/assets/exporters/gltf/extensions/BufferHandlerExtension.ts b/packages/engine/src/assets/exporters/gltf/extensions/BufferHandlerExtension.ts
index 3550451e82..7706d4da3e 100644
--- a/packages/engine/src/assets/exporters/gltf/extensions/BufferHandlerExtension.ts
+++ b/packages/engine/src/assets/exporters/gltf/extensions/BufferHandlerExtension.ts
@@ -1,10 +1,11 @@
import { sha3_256 } from 'js-sha3'
-import { Event, LoaderUtils, Object3D } from 'three'
+import { Event, LoaderUtils, Mesh, Object3D } from 'three'
import { generateUUID } from 'three/src/math/MathUtils'
import matches, { Validator } from 'ts-matches'
import { defineAction, dispatchAction } from '@etherealengine/hyperflux'
+import iterateObject3D from '../../../../scene/util/iterateObject3D'
import { AssetLoader } from '../../../classes/AssetLoader'
import { getFileName, getProjectName, getRelativeURI, modelResourcesPath } from '../../../functions/pathResolver'
import { GLTFExporterPlugin, GLTFWriter } from '../GLTFExporter'
@@ -53,6 +54,17 @@ export default class BufferHandlerExtension extends ExporterExtension implements
this.projectName = getProjectName(writer.options.path!)
this.modelName = getRelativeURI(writer.options.path!)
this.resourceURI = writer.options.resourceURI ?? null
+ const inputs = Array.isArray(input) ? input : [input]
+ inputs.forEach((input) =>
+ iterateObject3D(input, (child: Mesh) => {
+ if (child?.isMesh) {
+ const materials = Array.isArray(child.material) ? child.material : [child.material]
+ materials.forEach((material) => {
+ console.log(material)
+ })
+ }
+ })
+ )
dispatchAction(
BufferHandlerExtension.beginModelExport({
projectName: this.projectName,
diff --git a/packages/engine/src/assets/exporters/gltf/extensions/EEECSExporterExtension.ts b/packages/engine/src/assets/exporters/gltf/extensions/EEECSExporterExtension.ts
index e689ee7ba6..ffffd51cb4 100644
--- a/packages/engine/src/assets/exporters/gltf/extensions/EEECSExporterExtension.ts
+++ b/packages/engine/src/assets/exporters/gltf/extensions/EEECSExporterExtension.ts
@@ -26,21 +26,19 @@ export class EEECSExporterExtension extends ExporterExtension implements GLTFExp
const gltfLoaded = getComponent(entity, GLTFLoadedComponent)
const data = new Array<[string, any]>()
for (const field of gltfLoaded) {
- switch (field) {
- case 'entity':
- const name = getComponent(entity, NameComponent)
- data.push(['xrengine.entity', name])
- break
- default:
- const component = ComponentMap.get(field)!
- if (!component?.toJSON) {
- console.error(`[EEECSExporter]: Component ${field} does not have a toJSON method`)
- continue
- }
- const compData = component.toJSON(entity, getMutableComponent(entity, component))
- for (const [field, value] of Object.entries(compData)) {
- data.push([`xrengine.${component.name}.${field}`, value])
- }
+ if (field === 'entity') {
+ const name = getComponent(entity, NameComponent)
+ data.push(['xrengine.entity', name])
+ } else {
+ const component = ComponentMap.get(field)!
+ if (!component?.toJSON) {
+ console.error(`[EEECSExporter]: Component ${field} does not have a toJSON method`)
+ continue
+ }
+ const compData = component.toJSON(entity, getMutableComponent(entity, component))
+ for (const [field, value] of Object.entries(compData)) {
+ data.push([`xrengine.${component.name}.${field}`, value])
+ }
}
}
nodeDef.extensions = nodeDef.extensions ?? {}
diff --git a/packages/engine/src/assets/exporters/gltf/extensions/ImageProcessingExtension.ts b/packages/engine/src/assets/exporters/gltf/extensions/ImageProcessingExtension.ts
new file mode 100644
index 0000000000..ee694db9fa
--- /dev/null
+++ b/packages/engine/src/assets/exporters/gltf/extensions/ImageProcessingExtension.ts
@@ -0,0 +1,57 @@
+import { CompressedTexture, Event, Material, Mesh, Object3D, Texture } from 'three'
+
+import iterateObject3D from '../../../../scene/util/iterateObject3D'
+import createReadableTexture from '../../../functions/createReadableTexture'
+import { GLTFExporterPlugin, GLTFWriter } from '../GLTFExporter'
+import { ExporterExtension } from './ExporterExtension'
+
+export default class ImageProcessingExtension extends ExporterExtension implements GLTFExporterPlugin {
+ writer: GLTFWriter
+ originalImages: {
+ material: Material
+ field: string
+ texture: Texture
+ }[]
+
+ constructor(writer: GLTFWriter) {
+ super(writer)
+ this.writer = writer
+ this.originalImages = []
+ }
+
+ beforeParse(input: Object3D | Object3D[]) {
+ const writer = this.writer
+ const inputs = Array.isArray(input) ? input : [input]
+ inputs.forEach((input) =>
+ iterateObject3D(input, (child: Mesh) => {
+ if (child?.isMesh) {
+ const materials = Array.isArray(child.material) ? child.material : [child.material]
+ materials.forEach((material) => {
+ Object.entries(material)
+ .filter(([_, value]) => value instanceof Texture && !(value as CompressedTexture).isCompressedTexture)
+ .forEach(([k, texture]) => {
+ writer.pending.push(
+ createReadableTexture(texture, { flipY: true }).then((nuTexture) => {
+ this.originalImages.push({
+ material,
+ field: k,
+ texture
+ })
+ material[k] = nuTexture
+ })
+ )
+ })
+ })
+ }
+ })
+ )
+ }
+
+ afterParse(input: Object3D | Object3D[]) {
+ this.originalImages.forEach(({ material, field, texture }) => {
+ URL.revokeObjectURL(material[field].image.src)
+ material[field] = texture
+ })
+ this.originalImages = []
+ }
+}
diff --git a/packages/engine/src/assets/functions/createGLTFExporter.ts b/packages/engine/src/assets/functions/createGLTFExporter.ts
index 8806e1c32a..7735a9f7a9 100644
--- a/packages/engine/src/assets/functions/createGLTFExporter.ts
+++ b/packages/engine/src/assets/functions/createGLTFExporter.ts
@@ -3,6 +3,7 @@ import BufferHandlerExtension from '../exporters/gltf/extensions/BufferHandlerEx
import { EEECSExporterExtension } from '../exporters/gltf/extensions/EEECSExporterExtension'
import EEMaterialExporterExtension from '../exporters/gltf/extensions/EEMaterialExporterExtension'
import GPUInstancingExporterExtension from '../exporters/gltf/extensions/GPUInstancingExporterExtension'
+import ImageProcessingExtension from '../exporters/gltf/extensions/ImageProcessingExtension'
import ResourceIDExtension from '../exporters/gltf/extensions/ResourceIDExtension'
import URLResolutionExtension from '../exporters/gltf/extensions/URLResolutionExtension'
import { GLTFExporter, GLTFWriter } from '../exporters/gltf/GLTFExporter'
@@ -11,14 +12,12 @@ export default function createGLTFExporter() {
const exporter = new GLTFExporter()
const extensions = [
- //LightmapExporterExtension,
GPUInstancingExporterExtension,
- //BasisuExporterExtension,
EEMaterialExporterExtension,
- //BufferHandlerExtension,
URLResolutionExtension,
EEECSExporterExtension,
- ResourceIDExtension
+ ResourceIDExtension,
+ ImageProcessingExtension
]
extensions.forEach((extension) => exporter.register((writer) => new extension(writer)))
diff --git a/packages/engine/src/assets/functions/createReadableTexture.ts b/packages/engine/src/assets/functions/createReadableTexture.ts
index f61dba52b1..a126d6e4b0 100644
--- a/packages/engine/src/assets/functions/createReadableTexture.ts
+++ b/packages/engine/src/assets/functions/createReadableTexture.ts
@@ -1,19 +1,17 @@
import {
Camera,
CubeTexture,
+ LinearEncoding,
Mesh,
PerspectiveCamera,
PlaneGeometry,
- RepeatWrapping,
Scene,
ShaderMaterial,
Texture,
Uniform,
- Vector2,
Vector4,
WebGLRenderer,
- WebGLRenderTarget,
- Wrapping
+ WebGLRenderTarget
} from 'three'
export type BlitTextureOptions = {
@@ -150,7 +148,6 @@ export default async function createReadableTexture(
if (typeof map.source?.data?.src === 'string' && !/ktx2$/.test(map.source.data.src)) {
return options?.url ? map.source.data.src : map
}
-
const temporaryRenderer = getTemporaryRenderer()
blitTexture(map, options)
const result = await new Promise((resolve) =>
@@ -160,7 +157,7 @@ export default async function createReadableTexture(
if (!result) throw new Error('Error creating blob')
const image = new Image(map.image.width, map.image.height)
image.src = URL.createObjectURL(result)
- await new Promise(async (resolve) => {
+ await new Promise((resolve) => {
image.onload = () => resolve()
})
let finalTexture: Texture
diff --git a/packages/engine/src/assets/functions/exportModelGLTF.ts b/packages/engine/src/assets/functions/exportModelGLTF.ts
index 131e340325..c7c2b71b6f 100644
--- a/packages/engine/src/assets/functions/exportModelGLTF.ts
+++ b/packages/engine/src/assets/functions/exportModelGLTF.ts
@@ -1,9 +1,6 @@
-import { Matrix4 } from 'three'
-
import { Entity } from '../../ecs/classes/Entity'
import { getComponent } from '../../ecs/functions/ComponentFunctions'
import { ModelComponent } from '../../scene/components/ModelComponent'
-import { LocalTransformComponent, TransformComponent } from '../../transform/components/TransformComponent'
import createGLTFExporter from './createGLTFExporter'
export default async function exportModelGLTF(
@@ -18,13 +15,9 @@ export default async function exportModelGLTF(
const scene = getComponent(entity, ModelComponent).scene!
const exporter = createGLTFExporter()
const gltf: ArrayBuffer = await new Promise((resolve) => {
- const rootMatrix = scene.matrix.clone()
- const inverseRoot = rootMatrix.clone().invert()
- scene.children.map((child) => child.applyMatrix4(inverseRoot))
exporter.parse(
scene,
(gltf: ArrayBuffer) => {
- scene.children.map((child) => child.applyMatrix4(rootMatrix))
resolve(gltf)
},
(error) => {
diff --git a/packages/engine/src/assets/loaders/usdz/USDZLoader.js b/packages/engine/src/assets/loaders/usdz/USDZLoader.js
index 48137cd209..ee74f17b00 100644
--- a/packages/engine/src/assets/loaders/usdz/USDZLoader.js
+++ b/packages/engine/src/assets/loaders/usdz/USDZLoader.js
@@ -387,7 +387,7 @@ class USDZLoader extends Loader {
let geometry = new BufferGeometry();
let indices = []
function parseArray( text) {
- return JSON.parse( text.replaceAll('(', '').replaceAll(')','').replaceAll(/nan|inf/, '0.0').replaceAll('inf', '0.0'))
+ return JSON.parse( text.replaceAll(/[()]*/g,'').replaceAll(/nan|inf/g, '0.0'))
}
if ( 'int[] faceVertexIndices' in data && typeof data['int[] faceVertexIndices'] === 'string' ) {
@@ -820,6 +820,7 @@ class USDZLoader extends Loader {
const frontier = Object.keys(root).map(field => ["", root, field])
while (frontier.length > 0) {
const [path, context, name] = frontier.pop()
+ console.log("path", path, "context", context, "name", name)
const nameContext = context[name]
if ( registryRegex.test( name ) ) {
const domain = registryRegex.exec( name )[1]
diff --git a/packages/engine/src/avatar/AvatarLoadingSystem.ts b/packages/engine/src/avatar/AvatarLoadingSystem.ts
index 797b7c9e08..bf75a1d733 100644
--- a/packages/engine/src/avatar/AvatarLoadingSystem.ts
+++ b/packages/engine/src/avatar/AvatarLoadingSystem.ts
@@ -94,12 +94,11 @@ const execute = () => {
for (const entity of effectQuery.enter()) {
const effectComponent = getComponent(entity, AvatarEffectComponent)
const sourceTransform = getComponent(effectComponent.sourceEntity, TransformComponent)
- setTransformComponent(
- entity,
- sourceTransform.position.clone(),
- sourceTransform.rotation.clone(),
- sourceTransform.scale.clone()
- )
+ setComponent(entity, TransformComponent, {
+ position: sourceTransform.position.clone(),
+ rotation: sourceTransform.rotation.clone(),
+ scale: sourceTransform.scale.clone()
+ })
const transform = getComponent(entity, TransformComponent)
setComponent(entity, VisibleComponent, true)
/**
@@ -124,7 +123,7 @@ const execute = () => {
const ray = light.clone()
ray.position.y -= 2 * ray.geometry.boundingSphere?.radius! * Math.random()
- var a = (2 * Math.PI * i) / n,
+ const a = (2 * Math.PI * i) / n,
r = R * Math.random()
ray.position.x += r * Math.cos(a)
ray.position.z += r * Math.sin(a)
@@ -190,8 +189,8 @@ const execute = () => {
if (pillar !== null && plate !== null) {
plate['material'].opacity = opacityMultiplier * (0.7 + 0.5 * Math.sin((Date.now() % 6283) * 5e-3))
if (pillar !== undefined && plate !== undefined) {
- for (var i = 0, n = pillar.children.length; i < n; i++) {
- var ray = pillar.children[i]
+ for (let i = 0, n = pillar.children.length; i < n; i++) {
+ const ray = pillar.children[i]
ray.position.y += 2 * delta
ray.scale.y = lightScale(ray.position.y, ray['geometry'].boundingSphere.radius)
ray['material'].opacity = lightOpacity(ray.position.y, ray['geometry'].boundingSphere.radius)
diff --git a/packages/engine/src/renderer/materials/constants/DefaultArgs.ts b/packages/engine/src/renderer/materials/constants/DefaultArgs.ts
index 961316f636..7d211f4d38 100644
--- a/packages/engine/src/renderer/materials/constants/DefaultArgs.ts
+++ b/packages/engine/src/renderer/materials/constants/DefaultArgs.ts
@@ -9,7 +9,7 @@ export const Vec2Arg = { default: [1, 1], type: 'vec2' }
export const Vec3Arg = { default: [1, 1, 1], type: 'vec3' }
export const ColorArg = { default: new Color(), type: 'color' }
-export const TextureArg = { default: undefined, type: 'texture' }
+export const TextureArg = { default: null, type: 'texture' }
export const SelectArg = { default: '', options: [], type: 'select' }
export const StringArg = { default: '', type: 'string' }
@@ -31,6 +31,7 @@ export function getDefaultType(value) {
if ((value as Color).isColor) {
return 'color'
}
+ return ''
//todo: vectors, selects, objects
default:
return ''
diff --git a/packages/engine/src/renderer/materials/functions/MaterialLibraryFunctions.ts b/packages/engine/src/renderer/materials/functions/MaterialLibraryFunctions.ts
index 957f8a5d2d..96e40f2b52 100644
--- a/packages/engine/src/renderer/materials/functions/MaterialLibraryFunctions.ts
+++ b/packages/engine/src/renderer/materials/functions/MaterialLibraryFunctions.ts
@@ -41,7 +41,7 @@ export function formatMaterialArgs(args, defaultArgs: any = undefined) {
}
const tex = v as Texture
if (tex?.isTexture) {
- if (tex.source.data != undefined) {
+ if (tex.source.data !== undefined) {
return [k, v]
}
return [k, undefined]
@@ -105,7 +105,7 @@ export function materialIdToPrototype(matId: string): MaterialPrototypeComponent
return prototypeFromId(materialFromId(matId).prototype)
}
-export function materialToDefaultArgs(material: Material): Object {
+export function materialToDefaultArgs(material: Material): object {
return materialIdToDefaultArgs(material.uuid)
}
diff --git a/packages/engine/src/scene/components/ModelComponent.ts b/packages/engine/src/scene/components/ModelComponent.ts
index 9118342254..3038455e73 100644
--- a/packages/engine/src/scene/components/ModelComponent.ts
+++ b/packages/engine/src/scene/components/ModelComponent.ts
@@ -3,7 +3,7 @@ import { Mesh, Scene } from 'three'
import { EntityUUID } from '@etherealengine/common/src/interfaces/EntityUUID'
import { StaticResourceInterface } from '@etherealengine/common/src/interfaces/StaticResourceInterface'
-import { getState } from '@etherealengine/hyperflux'
+import { getState, none } from '@etherealengine/hyperflux'
import { AssetLoader } from '../../assets/classes/AssetLoader'
import { EngineState } from '../../ecs/classes/EngineState'
@@ -85,6 +85,7 @@ export const ModelComponent = defineComponent({
removeObjectFromGroup(entity, component.scene.value)
component.scene.set(null)
}
+ LODComponent.lodsByEntity[entity].value && LODComponent.lodsByEntity[entity].set(none)
removeMaterialSource({ type: SourceType.MODEL, path: component.src.value })
},
diff --git a/packages/engine/src/scene/functions/loaders/LODFunctions.ts b/packages/engine/src/scene/functions/loaders/LODFunctions.ts
index b09d3a957e..8f97f502a6 100644
--- a/packages/engine/src/scene/functions/loaders/LODFunctions.ts
+++ b/packages/engine/src/scene/functions/loaders/LODFunctions.ts
@@ -5,7 +5,7 @@ import { NO_PROXY } from '@etherealengine/hyperflux'
import { addOBCPlugin } from '../../../common/functions/OnBeforeCompilePlugin'
import { Entity } from '../../../ecs/classes/Entity'
-import { ComponentType, getMutableComponent } from '../../../ecs/functions/ComponentFunctions'
+import { ComponentType, getComponent, getMutableComponent } from '../../../ecs/functions/ComponentFunctions'
import { Object3DWithEntity } from '../../components/GroupComponent'
import { LODComponent } from '../../components/LODComponent'
import { ModelComponent } from '../../components/ModelComponent'
@@ -22,17 +22,17 @@ export function processLoadedLODLevel(entity: Entity, index: number, mesh: Mesh)
console.warn('trying to process an empty model file')
return
}
- const component = getMutableComponent(entity, LODComponent)
- const targetModel = getMutableComponent(component.target.value, ModelComponent)
- const level = component.levels[index]
+ const lodComponentState = getMutableComponent(entity, LODComponent)
+ const lodComponent = getComponent(entity, LODComponent)
+ const targetModel = getMutableComponent(lodComponent.target, ModelComponent)
+ const level = lodComponentState.levels[index]
- const lodComponent = getMutableComponent(entity, LODComponent)
- let loadedModel: Object3D | null = lodComponent.levels.find((level) => level.loaded.value)?.model.value ?? null
+ let loadedModel: Object3D | null = lodComponent.levels.find((level) => level.loaded)?.model ?? null
function addPlugin(mesh: Mesh) {
delete mesh.geometry.attributes['lodIndex']
delete mesh.geometry.attributes['_lodIndex']
- mesh.geometry.setAttribute('lodIndex', component.instanceLevels.get(NO_PROXY))
+ mesh.geometry.setAttribute('lodIndex', lodComponentState.instanceLevels.get(NO_PROXY))
const materials: Material[] = Array.isArray(mesh.material) ? mesh.material : [mesh.material]
materials.forEach((material) => {
addOBCPlugin(material, {
@@ -64,8 +64,8 @@ export function processLoadedLODLevel(entity: Entity, index: number, mesh: Mesh)
if (mesh instanceof InstancedMesh) {
mesh.instanceMatrix.setUsage(DynamicDrawUsage)
mesh.instanceMatrix.needsUpdate = true
- if (component.instanced.value) {
- if (component.instanceMatrix.value.array.length === 0) {
+ if (lodComponent.instanced) {
+ if (lodComponent.instanceMatrix.array.length === 0) {
const transforms = new Float32Array(mesh.count * 16)
const matrix = new Matrix4()
for (let i = 0; i < mesh.count; i++) {
@@ -74,21 +74,21 @@ export function processLoadedLODLevel(entity: Entity, index: number, mesh: Mesh)
transforms[i * 16 + j] = matrix.elements[j]
}
}
- component.instanceMatrix.set(new InstancedBufferAttribute(transforms, 16))
- component.instanceLevels.set(new InstancedBufferAttribute(new Uint8Array(mesh.count), 1))
+ lodComponentState.instanceMatrix.set(new InstancedBufferAttribute(transforms, 16))
+ lodComponentState.instanceLevels.set(new InstancedBufferAttribute(new Uint8Array(mesh.count), 1))
} else {
- mesh.instanceMatrix = component.instanceMatrix.value
+ mesh.instanceMatrix = lodComponent.instanceMatrix
}
}
- } else if (component.instanced.value) {
- const instancedModel = new InstancedMesh(mesh.geometry, mesh.material, component.instanceMatrix.value.count)
- instancedModel.instanceMatrix = component.instanceMatrix.get(NO_PROXY)
+ } else if (lodComponent.instanced) {
+ const instancedModel = new InstancedMesh(mesh.geometry, mesh.material, lodComponent.instanceMatrix.count)
+ instancedModel.instanceMatrix = lodComponent.instanceMatrix
loadedMesh = instancedModel
}
let removeLoaded = () => {}
if (!loadedModel) {
- loadedModel = objectFromLodPath(targetModel.value, component.lodPath.value)
+ loadedModel = objectFromLodPath(targetModel.value, lodComponent.lodPath)
removeLoaded = () => {
loadedModel?.removeFromParent()
}
@@ -97,21 +97,22 @@ export function processLoadedLODLevel(entity: Entity, index: number, mesh: Mesh)
loadedMesh instanceof InstancedMesh && addPlugin(loadedMesh)
level.model.set(loadedMesh)
- if (component.instanced.value) {
- if (component.instanceMatrix.value.array.length === 0) {
- component.instanceMatrix.set(new InstancedBufferAttribute(new Float32Array([...loadedMesh.matrix.elements]), 16))
- component.instanceLevels.set(new InstancedBufferAttribute(new Uint8Array([index]), 1))
- }
+ if (lodComponent.instanceMatrix.array.length === 0) {
+ lodComponentState.instanceMatrix.set(
+ new InstancedBufferAttribute(new Float32Array([...loadedMesh.matrix.elements]), 16)
+ )
+ lodComponentState.instanceLevels.set(new InstancedBufferAttribute(new Uint8Array([index]), 1))
}
+ if (loadedMesh !== loadedModel) {
+ loadedModel.parent?.add(loadedMesh)
+ loadedMesh.name = loadedModel.name
+ loadedMesh.position.copy(loadedModel.position)
+ loadedMesh.quaternion.copy(loadedModel.quaternion)
+ loadedMesh.scale.copy(loadedModel.scale)
+ loadedMesh.updateMatrixWorld(true)
- loadedModel.parent?.add(loadedMesh)
- loadedMesh.name = loadedModel.name
- loadedMesh.position.copy(loadedModel.position)
- loadedMesh.quaternion.copy(loadedModel.quaternion)
- loadedMesh.scale.copy(loadedModel.scale)
- loadedMesh.updateMatrixWorld(true)
-
- removeLoaded()
+ removeLoaded()
+ }
}
export type LODPath = OpaqueType<'LODPath'> & string
diff --git a/packages/engine/src/scene/systems/LODSystem.ts b/packages/engine/src/scene/systems/LODSystem.ts
index e97939a7b1..8971098563 100644
--- a/packages/engine/src/scene/systems/LODSystem.ts
+++ b/packages/engine/src/scene/systems/LODSystem.ts
@@ -17,7 +17,13 @@ const lodQuery = defineQuery([LODComponent])
const updateFrequency = 0.5
let lastUpdate = 0
-function updateLOD(entity, currentLevel, lodComponent: State, lodDistances, position) {
+function updateLOD(
+ index: number,
+ currentLevel: number,
+ lodComponent: State,
+ lodDistances: number[],
+ position: Vector3
+) {
const heuristic = lodComponent.lodHeuristic.value
if (['DISTANCE', 'SCENE_SCALE'].includes(heuristic)) {
const cameraPosition = Engine.instance.camera.position
@@ -26,13 +32,13 @@ function updateLOD(entity, currentLevel, lodComponent: State,
if (distance < lodDistances[j] || j === lodDistances.length - 1) {
const instanceLevels = lodComponent.instanceLevels.get(NO_PROXY)
if (currentLevel !== j) {
- instanceLevels.setX(entity, j)
- return j
+ instanceLevels.setX(index, j)
}
- break
+ return j
}
}
} else if (heuristic === 'MANUAL') {
+ return 0
//todo: implement manual LOD setting
} else {
throw Error('Invalid LOD heuristic')
@@ -69,34 +75,46 @@ function execute() {
}
lodComponent.instanceLevels.get(NO_PROXY).needsUpdate = true
const levelsToUnload: State[] = []
+ const levelsToLoad: [number, State][] = []
for (let i = 0; i < lodComponent.levels.length; i++) {
const level = lodComponent.levels[i]
if (referencedLods.has(i)) {
- if (!level.loaded.value) {
- if (level.src.value) {
- AssetLoader.load(level.src.value, {}, (loadedScene: GLTF) => {
- const mesh = getFirstMesh(loadedScene.scene)
- mesh && processLoadedLODLevel(entity, i, mesh)
- while (levelsToUnload.length > 0) {
- const levelToUnload = levelsToUnload.pop()
- levelToUnload?.loaded.set(false)
- levelToUnload?.model.get(NO_PROXY)?.removeFromParent()
- levelToUnload?.model.set(null)
- }
+ levelsToLoad.push([i, level])
+ } else {
+ if (!lodComponent.instanced.value && level.loaded.value) {
+ levelsToUnload.push(level)
+ }
+ }
+ }
+ const loadPromises: Promise[] = []
+ while (levelsToLoad.length > 0) {
+ const [i, level] = levelsToLoad.pop()!
+ if (!level.loaded.value) {
+ if (level.src.value) {
+ loadPromises.push(
+ new Promise((resolve) => {
+ AssetLoader.load(level.src.value, {}, (loadedScene: GLTF) => {
+ const mesh = getFirstMesh(loadedScene.scene)
+ mesh && processLoadedLODLevel(entity, i, mesh)
+ resolve()
+ })
})
- } else {
- !level.src.value &&
- processLoadedLODLevel(entity, i, objectFromLodPath(modelComponent, lodComponent.lodPath.value) as Mesh)
- }
- level.loaded.set(true)
+ )
} else {
- if (!lodComponent.instanced.value && level.loaded.value) {
- levelsToUnload.push(level)
- }
+ processLoadedLODLevel(entity, i, objectFromLodPath(modelComponent, lodComponent.lodPath.value) as Mesh)
}
+ level.loaded.set(true)
}
- referencedLods.clear()
}
+ Promise.all(loadPromises).then(() => {
+ while (levelsToUnload.length > 0) {
+ const levelToUnload = levelsToUnload.pop()
+ levelToUnload?.loaded.set(false)
+ levelToUnload?.model.get(NO_PROXY)?.removeFromParent()
+ levelToUnload?.model.set(null)
+ }
+ })
+ referencedLods.clear()
}
}
diff --git a/packages/server-core/src/assets/model-transform/model-transform.class.ts b/packages/server-core/src/assets/model-transform/model-transform.class.ts
index 38f58dea8b..a9cd181053 100644
--- a/packages/server-core/src/assets/model-transform/model-transform.class.ts
+++ b/packages/server-core/src/assets/model-transform/model-transform.class.ts
@@ -8,8 +8,9 @@ import { Application } from '@etherealengine/server-core/declarations'
import { transformModel } from './model-transform.helpers'
interface CreateParams {
- path: string
+ src: string
transformParameters: ModelTransformParameters
+ dst?: string
}
interface GetParams {
@@ -31,7 +32,7 @@ export class ModelTransform implements ServiceMethods {
}
processPath(inPath: string): string {
- const pathData = /.*projects\/([\w\d\s\-_]+)\/assets\/([\w\d\s\-_\\\/]+).glb$/.exec(inPath)
+ const pathData = /.*projects\/([\w\d\s\-_]+)\/assets\/([\w\d\s\-_\\/]+).glb$/.exec(inPath)
if (!pathData) throw Error('could not extract path data')
const [_, projectName, fileName] = pathData
return path.join(this.rootPath, `${projectName}/assets/${fileName}`)
@@ -58,9 +59,10 @@ export class ModelTransform implements ServiceMethods {
async create(createParams: CreateParams, params?: Params): Promise {
try {
const transformParms = createParams.transformParameters
- const commonPath = this.processPath(createParams.path)
+ const commonPath = this.processPath(createParams.src)
const inPath = `${commonPath}.glb`
- const outPath = `${commonPath}-transformed.glb`
+ let outPath = createParams.dst ? commonPath.replace(/[^/]+$/, createParams.dst) : `${commonPath}-transformed.glb`
+ !outPath.endsWith('.glb') && (outPath += '.glb')
return await transformModel(this.app, { src: inPath, dst: outPath, parms: transformParms })
} catch (e) {
console.error('error transforming model')
diff --git a/packages/server-core/src/media/model/model-upload.helper.ts b/packages/server-core/src/media/model/model-upload.helper.ts
index b1ad99c187..2bab448c70 100644
--- a/packages/server-core/src/media/model/model-upload.helper.ts
+++ b/packages/server-core/src/media/model/model-upload.helper.ts
@@ -2,12 +2,10 @@ import { createHash } from 'crypto'
import fs from 'fs'
import fetch from 'node-fetch'
import { Op } from 'sequelize'
-import { Readable } from 'stream'
import { CommonKnownContentTypes } from '@etherealengine/common/src/utils/CommonKnownContentTypes'
import { Application } from '../../../declarations'
-import config from '../../appconfig'
import logger from '../../ServerLogger'
import { addGenericAssetToS3AndStaticResources } from '../upload-asset/upload-asset.service'
@@ -78,8 +76,7 @@ export const modelUpload = async (app: Application, data) => {
]
}
})
- if (!config.server.cloneProjectStaticResources || (existingResource && existingModel))
- return app.service('model').get(existingModel.id)
+ if (existingResource && existingModel) return app.service('model').get(existingModel.id)
else {
let file, body
if (data.url) {
diff --git a/scripts/setup-ip.sh b/scripts/setup-ip.sh
index 15aa4306cf..e2a0780696 100755
--- a/scripts/setup-ip.sh
+++ b/scripts/setup-ip.sh
@@ -1,3 +1,3 @@
-LOCAL_IP=$(ip addr show eth0 | grep -oP '(?<=inet\s)\d+(\.\d+){3}')
+LOCAL_IP=$(ip addr show tailscale0 | grep -oP '(?<=inet\s)\d+(\.\d+){3}')
cp .env.local.default .env.local
sed -i "s/localhost/${LOCAL_IP}/g" .env.local