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