From 7e9bc802e6a82bcc59832b3139e6213c6bcc88fc Mon Sep 17 00:00:00 2001 From: Michael Estes Date: Thu, 6 Jun 2024 03:29:24 -0700 Subject: [PATCH] IR-2225 Asset Loading Tech Debt (#10296) * Move asset type enums to common * Asset type mapping * Asset loader cleanup * Test fix * Leverage useImmediateEffect better for resource hooks * debug * Texture loader * License * Texture scaling more generic, last cleanup * Remove comment --------- Co-authored-by: Josh Field --- .../UserMenu/menus/AvatarCreatorMenu.tsx | 4 +- packages/common/src/constants/AssetType.ts | 149 ++++++++++ .../components/assets/AssetsPreviewPanel.tsx | 22 +- .../components/inputs/TexturePreviewInput.tsx | 8 +- .../components/properties/ModelNodeEditor.tsx | 4 +- .../properties/VariantNodeEditor.tsx | 4 +- .../properties/three.quarks/BehaviorInput.tsx | 5 +- packages/editor/src/constants/AssetTypes.ts | 23 +- .../src/assets/classes/AssetLoader.test.ts | 21 +- .../engine/src/assets/classes/AssetLoader.ts | 273 +++--------------- .../engine/src/assets/constants/fileTypes.tsx | 76 ++--- packages/engine/src/assets/enum/AssetClass.ts | 41 --- packages/engine/src/assets/enum/AssetType.ts | 58 ---- .../functions/resourceLoaderFunctions.ts | 48 ++- .../assets/functions/resourceLoaderHooks.ts | 41 +-- .../src/assets/loaders/base/FileLoader.ts | 4 +- .../engine/src/assets/loaders/base/Loader.ts | 12 +- .../src/assets/loaders/gltf/GLTFLoader.ts | 2 +- .../assets/loaders/texture/TextureLoader.ts | 110 +++++++ .../engine/src/audio/systems/MediaSystem.ts | 7 +- .../avatar/functions/XRControllerFunctions.ts | 10 +- .../src/scene/components/ImageComponent.ts | 4 +- .../components/ParticleSystemComponent.ts | 8 +- .../src/scene/components/UVOL2Component.ts | 4 +- packages/engine/src/scene/constants/Util.ts | 4 +- .../materials/functions/LoadVideoTexture.ts | 4 +- .../src/projects/project/project-helper.ts | 16 +- .../spatial/src/resources/ResourceState.ts | 85 ++++-- .../editor/input/Behavior/index.tsx | 5 +- .../components/editor/input/Texture/index.tsx | 8 +- 30 files changed, 554 insertions(+), 506 deletions(-) create mode 100644 packages/common/src/constants/AssetType.ts delete mode 100755 packages/engine/src/assets/enum/AssetClass.ts delete mode 100755 packages/engine/src/assets/enum/AssetType.ts create mode 100644 packages/engine/src/assets/loaders/texture/TextureLoader.ts diff --git a/packages/client-core/src/user/components/UserMenu/menus/AvatarCreatorMenu.tsx b/packages/client-core/src/user/components/UserMenu/menus/AvatarCreatorMenu.tsx index 4360ab9f8e..6ac935f3aa 100755 --- a/packages/client-core/src/user/components/UserMenu/menus/AvatarCreatorMenu.tsx +++ b/packages/client-core/src/user/components/UserMenu/menus/AvatarCreatorMenu.tsx @@ -31,9 +31,9 @@ import InputText from '@etherealengine/client-core/src/common/components/InputTe import Menu from '@etherealengine/client-core/src/common/components/Menu' import { getCanvasBlob } from '@etherealengine/client-core/src/common/utils' import config from '@etherealengine/common/src/config' +import { AssetExt } from '@etherealengine/common/src/constants/AssetType' import { THUMBNAIL_HEIGHT, THUMBNAIL_WIDTH } from '@etherealengine/common/src/constants/AvatarConstants' import { AssetLoader } from '@etherealengine/engine/src/assets/classes/AssetLoader' -import { AssetType } from '@etherealengine/engine/src/assets/enum/AssetType' import { isAvaturn } from '@etherealengine/engine/src/avatar/functions/avatarFunctions' import Box from '@etherealengine/ui/src/primitives/mui/Box' import Icon from '@etherealengine/ui/src/primitives/mui/Icon' @@ -94,7 +94,7 @@ const AvatarCreatorMenu = (selectedSdk: string) => () => { setAvatarName(avatarIdRegexExec ? avatarIdRegexExec[1] : generateAvatarId()) try { - const assetType = AssetLoader.getAssetType(url) ?? isAvaturn(url) ? AssetType.glB : null + const assetType = AssetLoader.getAssetType(url) ?? isAvaturn(url) ? AssetExt.GLB : null if (assetType) { const res = await fetch(url) const data = await res.blob() diff --git a/packages/common/src/constants/AssetType.ts b/packages/common/src/constants/AssetType.ts new file mode 100644 index 0000000000..48bda39117 --- /dev/null +++ b/packages/common/src/constants/AssetType.ts @@ -0,0 +1,149 @@ +/* +CPAL-1.0 License + +The contents of this file are subject to the Common Public Attribution License +Version 1.0. (the "License"); you may not use this file except in compliance +with the License. You may obtain a copy of the License at +https://github.com/EtherealEngine/etherealengine/blob/dev/LICENSE. +The License is based on the Mozilla Public License Version 1.1, but Sections 14 +and 15 have been added to cover use of software over a computer network and +provide for limited attribution for the Original Developer. In addition, +Exhibit A has been modified to be consistent with Exhibit B. + +Software distributed under the License is distributed on an "AS IS" basis, +WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the +specific language governing rights and limitations under the License. + +The Original Code is Ethereal Engine. + +The Original Developer is the Initial Developer. The Initial Developer of the +Original Code is the Ethereal Engine team. + +All portions of the code written by the Ethereal Engine team are Copyright © 2021-2023 +Ethereal Engine. All Rights Reserved. +*/ + +/** List of Asset Types. */ +export enum AssetType { + Model = 'Model', + Material = 'Material', + Image = 'Image', + Video = 'Video', + Audio = 'Audio', + Prefab = 'Prefab', + Lookdev = 'Lookdev', + Volumetric = 'Volumetric', + Unknown = 'unknown' +} + +/** List of Asset Extensions. */ +export enum AssetExt { + GLB = 'glb', + GLTF = 'gltf', + VRM = 'vrm', + FBX = 'fbx', + OBJ = 'obj', + USDZ = 'usdz', + BIN = 'bin', + + PNG = 'png', + JPEG = 'jpeg', + TGA = 'tga', + KTX2 = 'ktx2', + DDS = 'dds', + + MP4 = 'mp4', + AVI = 'avi', + WEBM = 'webm', + MKV = 'mkv', + MOV = 'mov', + M3U8 = 'm3u8', + MPD = 'mpd', + + MP3 = 'mp3', + WAV = 'wav', + OGG = 'ogg', + M4A = 'm4a', + FLAC = 'flac', + AAC = 'acc', + + MAT = 'material', + + UVOL = 'uvol', + JSON = 'json' +} + +export const AssetExtToAssetType = (assetExt: AssetExt | undefined): AssetType => { + switch (assetExt) { + // Model + case AssetExt.GLB: + case AssetExt.GLTF: + case AssetExt.VRM: + case AssetExt.FBX: + case AssetExt.OBJ: + case AssetExt.USDZ: + return AssetType.Model + + // Image + case AssetExt.PNG: + case AssetExt.JPEG: + case AssetExt.TGA: + case AssetExt.KTX2: + case AssetExt.DDS: + return AssetType.Image + + // Video + case AssetExt.MP4: + case AssetExt.AVI: + case AssetExt.WEBM: + case AssetExt.MKV: + case AssetExt.MOV: + case AssetExt.M3U8: + case AssetExt.MPD: + return AssetType.Video + + // Audio + case AssetExt.MP3: + case AssetExt.WAV: + case AssetExt.OGG: + case AssetExt.M4A: + case AssetExt.FLAC: + case AssetExt.AAC: + return AssetType.Audio + + // Material + case AssetExt.MAT: + return AssetType.Material + + // Volumetric + case AssetExt.JSON: + case AssetExt.UVOL: + return AssetType.Volumetric + + default: + return AssetType.Unknown + } +} + +export const FileExtToAssetExt = (fileExt: string): AssetExt | undefined => { + fileExt = fileExt.toLowerCase() + if (fileExt === 'jpg') return AssetExt.JPEG + return fileExt +} + +export const FileToAssetType = (fileName: string): AssetType => { + fileName = fileName.toLowerCase() + const split = fileName.split('.') + const ext = split.pop() + if (!ext) return AssetType.Unknown + if (ext === 'gltf') { + const prev = split.pop() + if (prev) { + if (prev === 'material') return AssetType.Material + else if (prev === 'lookdev') return AssetType.Lookdev + else if (prev === 'prefab') return AssetType.Prefab + } + } + + return AssetExtToAssetType(FileExtToAssetExt(ext)) +} diff --git a/packages/editor/src/components/assets/AssetsPreviewPanel.tsx b/packages/editor/src/components/assets/AssetsPreviewPanel.tsx index 794e3a3b14..a319c22454 100644 --- a/packages/editor/src/components/assets/AssetsPreviewPanel.tsx +++ b/packages/editor/src/components/assets/AssetsPreviewPanel.tsx @@ -25,11 +25,11 @@ Ethereal Engine. All Rights Reserved. */ import React, { useImperativeHandle } from 'react' -import { AssetLoader } from '@etherealengine/engine/src/assets/classes/AssetLoader' -import { AssetType } from '@etherealengine/engine/src/assets/enum/AssetType' +import { AssetExt } from '@etherealengine/common/src/constants/AssetType' import { NO_PROXY, useHookstate } from '@etherealengine/hyperflux' import createReadableTexture from '@etherealengine/spatial/src/renderer/functions/createReadableTexture' +import { getTextureAsync } from '@etherealengine/engine/src/assets/functions/resourceLoaderHooks' import { AudioPreviewPanel } from './AssetPreviewPanels/AudioPreviewPanel' import { ImagePreviewPanel } from './AssetPreviewPanels/ImagePreviewPanel' import { JsonPreviewPanel } from './AssetPreviewPanels/JsonPreviewPanel' @@ -75,9 +75,11 @@ export const AssetsPreviewPanel = React.forwardRef(({ hideHeading, previewPanelP const onSelectionChanged = async (props: AssetSelectionChangePropsType) => { thumbnail.value && URL.revokeObjectURL(thumbnail.value) if (/ktx2$/.test(props.resourceUrl)) { - const texture = await AssetLoader.loadAsync(props.resourceUrl) - thumbnail.set((await createReadableTexture(texture, { url: true })) as string) - texture.dispose() + const [texture, unload] = await getTextureAsync(props.resourceUrl) + if (texture) { + thumbnail.set((await createReadableTexture(texture, { url: true })) as string) + unload() + } } else { thumbnail.set('') } @@ -90,13 +92,13 @@ export const AssetsPreviewPanel = React.forwardRef(({ hideHeading, previewPanelP case 'model/gltf': case 'model/gltf-binary': case 'model/glb': - case AssetType.VRM: + case AssetExt.VRM: case 'model/vrm': - case AssetType.glB: - case AssetType.glTF: + case AssetExt.GLB: + case AssetExt.GLTF: case 'gltf-binary': - case AssetType.USDZ: - case AssetType.FBX: + case AssetExt.USDZ: + case AssetExt.FBX: const modelPreviewPanel = { PreviewSource: ModelPreviewPanel, resourceProps: { resourceUrl: props.resourceUrl, name: props.name, size: props.size } diff --git a/packages/editor/src/components/inputs/TexturePreviewInput.tsx b/packages/editor/src/components/inputs/TexturePreviewInput.tsx index 7e91a9c44a..409b5ce11c 100644 --- a/packages/editor/src/components/inputs/TexturePreviewInput.tsx +++ b/packages/editor/src/components/inputs/TexturePreviewInput.tsx @@ -27,9 +27,9 @@ import { Stack } from '@mui/material' import React, { Fragment, useEffect } from 'react' import { ColorSpace, DisplayP3ColorSpace, LinearSRGBColorSpace, SRGBColorSpace, Texture, Vector2 } from 'three' +import { AssetType } from '@etherealengine/common/src/constants/AssetType' import { AssetLoader } from '@etherealengine/engine/src/assets/classes/AssetLoader' 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/primitives/mui/Button' @@ -75,7 +75,7 @@ export default function TexturePreviewInput({ } const { preview } = rest const validSrcValue = - typeof value === 'string' && [AssetClass.Image, AssetClass.Video].includes(AssetLoader.getAssetClass(value)) + typeof value === 'string' && [AssetType.Image, AssetType.Video].includes(AssetLoader.getAssetClass(value)) const srcState = useHookstate(value) const texture = srcState.value as Texture @@ -109,10 +109,10 @@ export default function TexturePreviewInput({ {showPreview && ( {(typeof preview === 'string' || - (typeof value === 'string' && AssetLoader.getAssetClass(value) === AssetClass.Image)) && ( + (typeof value === 'string' && AssetLoader.getAssetClass(value) === AssetType.Image)) && ( )} - {typeof value === 'string' && AssetLoader.getAssetClass(value) === AssetClass.Video && ( + {typeof value === 'string' && AssetLoader.getAssetClass(value) === AssetType.Video && ( diff --git a/packages/editor/src/components/properties/ModelNodeEditor.tsx b/packages/editor/src/components/properties/ModelNodeEditor.tsx index f9b4126916..de1010ca7c 100755 --- a/packages/editor/src/components/properties/ModelNodeEditor.tsx +++ b/packages/editor/src/components/properties/ModelNodeEditor.tsx @@ -34,7 +34,7 @@ import config from '@etherealengine/common/src/config' import { pathJoin } from '@etherealengine/common/src/utils/miscUtils' import { useComponent } from '@etherealengine/ecs/src/ComponentFunctions' import { pathResolver } from '@etherealengine/engine/src/assets/functions/pathResolver' -import { updateResource } from '@etherealengine/engine/src/assets/functions/resourceLoaderFunctions' +import { updateModelResource } from '@etherealengine/engine/src/assets/functions/resourceLoaderFunctions' import { recursiveHipsLookup } from '@etherealengine/engine/src/avatar/AvatarBoneMatching' import { getEntityErrors } from '@etherealengine/engine/src/scene/components/ErrorComponent' import { ModelComponent } from '@etherealengine/engine/src/scene/components/ModelComponent' @@ -131,7 +131,7 @@ export const ModelNodeEditor: EditorComponentType = (props) => { value={modelComponent.src.value} onRelease={(src) => { if (src !== modelComponent.src.value) commitProperty(ModelComponent, 'src')(src) - else updateResource(src) + else updateModelResource(src) }} /> {errors?.LOADING_ERROR || diff --git a/packages/editor/src/components/properties/VariantNodeEditor.tsx b/packages/editor/src/components/properties/VariantNodeEditor.tsx index 21823f8aeb..b4b489d9ed 100644 --- a/packages/editor/src/components/properties/VariantNodeEditor.tsx +++ b/packages/editor/src/components/properties/VariantNodeEditor.tsx @@ -27,10 +27,10 @@ import DeblurIcon from '@mui/icons-material/Deblur' import React, { useEffect } from 'react' import { useTranslation } from 'react-i18next' +import { AssetExt } from '@etherealengine/common/src/constants/AssetType' import { getOptionalMutableComponent, useComponent } from '@etherealengine/ecs/src/ComponentFunctions' import { Entity, UndefinedEntity } from '@etherealengine/ecs/src/Entity' import { AssetLoader } from '@etherealengine/engine/src/assets/classes/AssetLoader' -import { AssetType } from '@etherealengine/engine/src/assets/enum/AssetType' import { loadResource } from '@etherealengine/engine/src/assets/functions/resourceLoaderFunctions' import { ModelComponent } from '@etherealengine/engine/src/scene/components/ModelComponent' import { Heuristic, VariantComponent, VariantLevel } from '@etherealengine/engine/src/scene/components/VariantComponent' @@ -268,7 +268,7 @@ export const BudgetVariantNodeEditor = (props: { lastSrc.set(src) const assetType = AssetLoader.getAssetType(src) - if (assetType !== AssetType.glB && assetType !== AssetType.glTF) return + if (assetType !== AssetExt.GLB && assetType !== AssetExt.GLTF) return const controller = new AbortController() buildBudgetVariantMetadata(level.value, controller.signal, (maxTextureSize: number, vertexCount: number) => { diff --git a/packages/editor/src/components/properties/three.quarks/BehaviorInput.tsx b/packages/editor/src/components/properties/three.quarks/BehaviorInput.tsx index d473e78349..4a1f354d83 100644 --- a/packages/editor/src/components/properties/three.quarks/BehaviorInput.tsx +++ b/packages/editor/src/components/properties/three.quarks/BehaviorInput.tsx @@ -26,7 +26,6 @@ Ethereal Engine. All Rights Reserved. import React, { useCallback } from 'react' import { Texture, Vector2, Vector3 } from 'three' -import { AssetLoader } from '@etherealengine/engine/src/assets/classes/AssetLoader' import { ApplyForceBehaviorJSON, ApplySequencesJSON, @@ -52,6 +51,7 @@ import { import { State } from '@etherealengine/hyperflux' import createReadableTexture from '@etherealengine/spatial/src/renderer/functions/createReadableTexture' +import { getTextureAsync } from '@etherealengine/engine/src/assets/functions/resourceLoaderHooks' import BooleanInput from '../../inputs/BooleanInput' import { Button } from '../../inputs/Button' import InputGroup from '../../inputs/InputGroup' @@ -344,7 +344,8 @@ export default function BehaviorInput({ (scope: State) => { const thisOnChange = onChange(scope.src) return (src: string) => { - AssetLoader.load(src, (texture: Texture) => { + getTextureAsync(src).then(([texture]) => { + if (!texture) return createReadableTexture(texture, { canvas: true, flipY: true }).then((readableTexture: Texture) => { const canvas = readableTexture.image as HTMLCanvasElement const ctx = canvas.getContext('2d')! diff --git a/packages/editor/src/constants/AssetTypes.ts b/packages/editor/src/constants/AssetTypes.ts index 4d5a538ee9..38719059e0 100644 --- a/packages/editor/src/constants/AssetTypes.ts +++ b/packages/editor/src/constants/AssetTypes.ts @@ -23,10 +23,9 @@ All portions of the code written by the Ethereal Engine team are Copyright © 20 Ethereal Engine. All Rights Reserved. */ +import { AssetExt } from '@etherealengine/common/src/constants/AssetType' import { NativeTypes } from 'react-dnd-html5-backend' -import { AssetType } from '@etherealengine/engine/src/assets/enum/AssetType' - /** * ItemTypes object containing types of items used. * @@ -35,24 +34,24 @@ import { AssetType } from '@etherealengine/engine/src/assets/enum/AssetType' export const ItemTypes = { File: NativeTypes.FILE, Folder: 'folder', - Audios: [AssetType.MP3, 'mpeg', 'audio/mpeg'], - Images: [AssetType.PNG, AssetType.JPEG, 'jpg', 'gif', AssetType.KTX2, 'image/png', 'image/jpeg', 'image/ktx2'], + Audios: [AssetExt.MP3, 'mpeg', 'audio/mpeg'], + Images: [AssetExt.PNG, AssetExt.JPEG, 'jpg', 'gif', AssetExt.KTX2, 'image/png', 'image/jpeg', 'image/ktx2'], Models: [ - AssetType.glB, + AssetExt.GLB, 'model/glb', - AssetType.glTF, + AssetExt.GLTF, 'model/gltf', - AssetType.FBX, + AssetExt.FBX, 'model/fbx', - AssetType.USDZ, + AssetExt.USDZ, 'model/usdz', - AssetType.VRM, + AssetExt.VRM, 'model/vrm' ], - Scripts: ['tsx', AssetType.TS, 'jsx', 'js', AssetType.Script], - Videos: [AssetType.MP4, AssetType.M3U8, 'video/mp4', AssetType.MKV], + Scripts: ['tsx', 'ts', 'jsx', 'js', 'script'], + Videos: [AssetExt.MP4, AssetExt.M3U8, 'video/mp4', AssetExt.MKV], Volumetrics: ['manifest'], - Text: [AssetType.PlainText, 'txt'], + Text: ['text', 'txt'], ECS: ['scene.json'], Node: 'Node', Material: 'Material', diff --git a/packages/engine/src/assets/classes/AssetLoader.test.ts b/packages/engine/src/assets/classes/AssetLoader.test.ts index e05abf3e9e..91a9ef207d 100644 --- a/packages/engine/src/assets/classes/AssetLoader.test.ts +++ b/packages/engine/src/assets/classes/AssetLoader.test.ts @@ -32,8 +32,7 @@ import '../../EngineModule' import { destroyEngine } from '@etherealengine/ecs/src/Engine' import { createEngine } from '@etherealengine/spatial/src/initializeEngine' -import { AssetClass } from '../enum/AssetClass' -import { AssetType } from '../enum/AssetType' +import { AssetExt, AssetType } from '@etherealengine/common/src/constants/AssetType' import { AssetLoader } from './AssetLoader' /** @@ -52,31 +51,31 @@ describe('AssetLoader', async () => { it('should work for gltf asset', async () => { const url = 'www.test.com/file.gltf' const type = AssetLoader.getAssetType(url) - assert.equal(type, AssetType.glTF) + assert.equal(type, AssetExt.GLTF) }) it('should work for fbx asset', async () => { const url = 'www.test.com/file.fbx' const type = AssetLoader.getAssetType(url) - assert.equal(type, AssetType.FBX) + assert.equal(type, AssetExt.FBX) }) it('should work for vrm asset', async () => { const url = 'www.test.com/file.vrm' const type = AssetLoader.getAssetType(url) - assert.equal(type, AssetType.VRM) + assert.equal(type, AssetExt.VRM) }) it('should work for png asset', async () => { const url = 'www.test.com/file.png' const type = AssetLoader.getAssetType(url) - assert.equal(type, AssetType.PNG) + assert.equal(type, AssetExt.PNG) }) it('should work for jpeg asset', async () => { const url = 'www.test.com/file.jpeg' const type = AssetLoader.getAssetType(url) - assert.equal(type, AssetType.JPEG) + assert.equal(type, AssetExt.JPEG) }) }) @@ -84,19 +83,19 @@ describe('AssetLoader', async () => { it('should work for model asset', async () => { const url = 'www.test.com/file.gltf' const type = AssetLoader.getAssetClass(url) - assert.equal(type, AssetClass.Model) + assert.equal(type, AssetType.Model) }) it('should work for image asset', async () => { const url = 'www.test.com/file.png' const type = AssetLoader.getAssetClass(url) - assert.equal(type, AssetClass.Image) + assert.equal(type, AssetType.Image) }) it('should work for unsupported asset', async () => { const url = 'www.test.com/file.pdf' const type = AssetLoader.getAssetClass(url) - assert.equal(type, AssetClass.Unknown) + assert.equal(type, AssetType.Unknown) }) }) @@ -112,7 +111,7 @@ describe('AssetLoader', async () => { }) it('should give error for empty url', async () => { - AssetLoader.load('', undefined, undefined, (err) => { + AssetLoader.loadAsset('', undefined, undefined, (err) => { assert.notEqual(err, null) }) }) diff --git a/packages/engine/src/assets/classes/AssetLoader.ts b/packages/engine/src/assets/classes/AssetLoader.ts index 20e5a5c27c..544529f865 100644 --- a/packages/engine/src/assets/classes/AssetLoader.ts +++ b/packages/engine/src/assets/classes/AssetLoader.ts @@ -23,20 +23,18 @@ All portions of the code written by the Ethereal Engine team are Copyright © 20 Ethereal Engine. All Rights Reserved. */ -import { AudioLoader, Material, RepeatWrapping, Texture, TextureLoader } from 'three' +import { AudioLoader } from 'three' import { getState } from '@etherealengine/hyperflux' import { isAbsolutePath } from '@etherealengine/spatial/src/common/functions/isAbsolutePath' -import { iOS } from '@etherealengine/spatial/src/common/functions/isMobile' import { EngineState } from '@etherealengine/spatial/src/EngineState' +import { AssetExt, AssetType, FileExtToAssetExt, FileToAssetType } from '@etherealengine/common/src/constants/AssetType' import loadVideoTexture from '../../scene/materials/functions/LoadVideoTexture' -import { AssetClass } from '../enum/AssetClass' -import { AssetType } from '../enum/AssetType' import { FileLoader } from '../loaders/base/FileLoader' import { DDSLoader } from '../loaders/dds/DDSLoader' import { FBXLoader } from '../loaders/fbx/FBXLoader' -import { GLTF } from '../loaders/gltf/GLTFLoader' +import { TextureLoader } from '../loaders/texture/TextureLoader' import { TGALoader } from '../loaders/tga/TGALoader' import { USDZLoader } from '../loaders/usdz/USDZLoader' import { AssetLoaderState } from '../state/AssetLoaderState' @@ -46,56 +44,10 @@ import { AssetLoaderState } from '../state/AssetLoaderState' * @param assetFileName Name of the Asset file. * @returns Asset type of the file. */ -const getAssetType = (assetFileName: string): AssetType => { - assetFileName = assetFileName.toLowerCase() - const suffix = assetFileName.split('.').pop() - switch (suffix) { - case 'gltf': - return AssetType.glTF - case 'glb': - return AssetType.glB - case 'usdz': - return AssetType.USDZ - case 'fbx': - return AssetType.FBX - case 'vrm': - return AssetType.VRM - case 'tga': - return AssetType.TGA - case 'ktx2': - return AssetType.KTX2 - case 'ddx': - return AssetType.DDS - case 'png': - return AssetType.PNG - case 'jpg': - case 'jpeg': - return AssetType.JPEG - case 'mp3': - return AssetType.MP3 - case 'wav': - return AssetType.WAV - case 'aac': - return AssetType.AAC - case 'ogg': - return AssetType.OGG - case 'm4a': - return AssetType.M4A - case 'mp4': - return AssetType.MP4 - case 'mkv': - return AssetType.MKV - case 'm3u8': - return AssetType.M3U8 - case 'material': - return AssetType.MAT - case 'json': - return AssetType.JSON - case 'bin': - return AssetType.BIN - default: - return AssetType.UNKNOWN - } +const getAssetType = (assetFileName: string): AssetExt => { + const ext = assetFileName.split('.').pop() + if (!ext) return undefined! + return FileExtToAssetExt(ext)! } /** @@ -103,200 +55,71 @@ const getAssetType = (assetFileName: string): AssetType => { * @param assetFileName Name of the Asset file. * @returns Asset class of the file. */ -const getAssetClass = (assetFileName: string): AssetClass => { - assetFileName = assetFileName.toLowerCase() - if (/\.(gltf|glb|vrm|fbx|obj|usdz)$/.test(assetFileName)) { - if (/\.(material.gltf)$/.test(assetFileName)) { - console.log('Material asset') - return AssetClass.Material - } else if (/\.(lookdev.gltf)$/.test(assetFileName)) { - console.log('Lookdev asset') - return AssetClass.Lookdev - } - if (/\.(prefab.gltf)$/.test(assetFileName)) { - console.log('prefab asset') - return AssetClass.Prefab - } - return AssetClass.Model - } else if (/\.(png|jpg|jpeg|tga|ktx2|dds)$/.test(assetFileName)) { - return AssetClass.Image - } else if (/\.(mp4|avi|webm|mkv|mov|m3u8|mpd)$/.test(assetFileName)) { - return AssetClass.Video - } else if (/\.(mp3|ogg|m4a|flac|wav)$/.test(assetFileName)) { - return AssetClass.Audio - } else if (/\.(drcs|uvol|manifest)$/.test(assetFileName)) { - return AssetClass.Volumetric - } else { - return AssetClass.Unknown - } +const getAssetClass = (assetFileName: string): AssetType => { + return FileToAssetType(assetFileName) } -//@ts-ignore -const ddsLoader = () => new DDSLoader() -const fbxLoader = () => new FBXLoader() -const textureLoader = () => new TextureLoader() -const fileLoader = () => new FileLoader() -const audioLoader = () => new AudioLoader() -const tgaLoader = () => new TGALoader() -const videoLoader = () => ({ load: loadVideoTexture }) -const ktx2Loader = () => ({ - load: (src, onLoad, onProgress, onError, signal?) => { - const gltfLoader = getState(AssetLoaderState).gltfLoader - gltfLoader.ktx2Loader!.load( - src, - (texture) => { - // console.log('KTX2Loader loaded texture', texture) - texture.source.data.src = src - onLoad(texture) - }, - onProgress, - onError - ) - } -}) -const usdzLoader = () => new USDZLoader() - -export const getLoader = (assetType: AssetType) => { +export const getLoader = (assetType: AssetExt) => { switch (assetType) { - case AssetType.KTX2: - return ktx2Loader() - case AssetType.DDS: - return ddsLoader() - case AssetType.glTF: - case AssetType.glB: - case AssetType.VRM: + case AssetExt.KTX2: + return getState(AssetLoaderState).gltfLoader.ktx2Loader! + case AssetExt.DDS: + return new DDSLoader() + case AssetExt.GLTF: + case AssetExt.GLB: + case AssetExt.VRM: return getState(AssetLoaderState).gltfLoader - case AssetType.USDZ: - return usdzLoader() - case AssetType.FBX: - return fbxLoader() - case AssetType.TGA: - return tgaLoader() - case AssetType.PNG: - case AssetType.JPEG: - return textureLoader() - case AssetType.AAC: - case AssetType.MP3: - case AssetType.OGG: - case AssetType.M4A: - return audioLoader() - case AssetType.MP4: - case AssetType.MKV: - case AssetType.M3U8: - return videoLoader() + case AssetExt.USDZ: + return new USDZLoader() + case AssetExt.FBX: + return new FBXLoader() + case AssetExt.TGA: + return new TGALoader() + case AssetExt.PNG: + case AssetExt.JPEG: + return new TextureLoader() + case AssetExt.AAC: + case AssetExt.MP3: + case AssetExt.OGG: + case AssetExt.M4A: + return new AudioLoader() + case AssetExt.MP4: + case AssetExt.MKV: + case AssetExt.M3U8: + return { load: loadVideoTexture } default: - return fileLoader() - } -} - -const assetLoadCallback = (url: string, assetType: AssetType, onLoad: (response: any) => void) => async (asset) => { - const assetClass = AssetLoader.getAssetClass(url) - if (assetClass === AssetClass.Model) { - const notGLTF = [AssetType.FBX, AssetType.USDZ].includes(assetType) - if (notGLTF) { - asset = { scene: asset } - } else if (assetType === AssetType.VRM) { - asset = asset.userData.vrm - } - - if (asset.scene && !asset.scene.userData) asset.scene.userData = {} - if (asset.scene.userData) asset.scene.userData.type = assetType - if (asset.userData) asset.userData.type = assetType - else asset.userData = { type: assetType } + return new FileLoader() } - if (assetClass === AssetClass.Material) { - const material = asset as Material - material.userData.type = assetType - } - if ([AssetClass.Image, AssetClass.Video].includes(assetClass)) { - const texture = asset as Texture - texture.wrapS = RepeatWrapping - texture.wrapT = RepeatWrapping - } - - const gltf = asset as GLTF - if (gltf.parser) delete asset.parser - - onLoad(asset) } const getAbsolutePath = (url) => (isAbsolutePath(url) ? url : getState(EngineState).publicPath + url) -const load = async ( - _url: string, - onLoad = (response: any) => {}, - onProgress = (request: ProgressEvent) => {}, - onError = (event: ErrorEvent | Error) => {}, +const loadAsset = async ( + url: string, + onLoad: (response: T) => void = () => {}, + onProgress: (request: ProgressEvent) => void = () => {}, + onError: (event: ErrorEvent | Error) => void = () => {}, signal?: AbortSignal ) => { - if (!_url) { + if (!url) { onError(new Error('URL is empty')) return } - let url = getAbsolutePath(_url) - - const assetType = AssetLoader.getAssetType(url) - const loader = getLoader(assetType) - if (iOS && (assetType === AssetType.PNG || assetType === AssetType.JPEG)) { - const img = new Image() - img.crossOrigin = 'anonymous' //browser will yell without this - img.src = url - await img.decode() //new way to wait for image to load - // Initialize the canvas and it's size - const canvas = document.createElement('canvas') //dead dom elements? Remove after Three loads them - const ctx = canvas.getContext('2d') - - // Set width and height - const originalWidth = img.width - const originalHeight = img.height - - let resizingFactor = 1 - if (originalWidth >= originalHeight) { - if (originalWidth > 1024) { - resizingFactor = 1024 / originalWidth - } - } else { - if (originalHeight > 1024) { - resizingFactor = 1024 / originalHeight - } - } - - const canvasWidth = originalWidth * resizingFactor - const canvasHeight = originalHeight * resizingFactor - - canvas.width = canvasWidth - canvas.height = canvasHeight - - // Draw image and export to a data-uri - ctx?.drawImage(img, 0, 0, canvasWidth, canvasHeight) - const dataURI = canvas.toDataURL() - - // Do something with the result, like overwrite original - url = dataURI - } - - const callback = assetLoadCallback(url, assetType, onLoad) + url = getAbsolutePath(url) + const assetExt = AssetLoader.getAssetType(url) + const loader = getLoader(assetExt) try { - return loader.load(url, callback, onProgress, onError, signal) + return loader.load(url, onLoad, onProgress, onError, signal) } catch (error) { onError(error) } } -const loadAsync = async (url: string, onProgress = (request: ProgressEvent) => {}) => { - return new Promise((resolve, reject) => { - load(url, resolve, onProgress, reject) - }) -} - export const AssetLoader = { - loaders: new Map(), getAbsolutePath, getAssetType, getAssetClass, - /** @deprecated Use hooks from resourceHooks.ts instead **/ - load, - /** @deprecated Use hooks from resourceHooks.ts instead **/ - loadAsync + /** @deprecated Use resourceLoaderHooks instead */ + loadAsset } diff --git a/packages/engine/src/assets/constants/fileTypes.tsx b/packages/engine/src/assets/constants/fileTypes.tsx index 336a03c91a..d81ae55366 100755 --- a/packages/engine/src/assets/constants/fileTypes.tsx +++ b/packages/engine/src/assets/constants/fileTypes.tsx @@ -23,7 +23,7 @@ All portions of the code written by the Ethereal Engine team are Copyright © 20 Ethereal Engine. All Rights Reserved. */ -import { AssetType } from '../enum/AssetType' +import { AssetExt } from '@etherealengine/common/src/constants/AssetType' // array containing audio file type export const AudioFileTypes = ['.mp3', '.mpeg', 'audio/mpeg', '.ogg'] @@ -63,45 +63,45 @@ export const AllFileTypes = [ export const AcceptsAllFileTypes = AllFileTypes.join(',') export const MimeTypeToAssetType = { - 'audio/mpeg': AssetType.MP3, - 'video/mp4': AssetType.MP4, - 'image/png': AssetType.PNG, - 'image/jpeg': AssetType.JPEG, - 'image/ktx2': AssetType.KTX2, - 'model/gltf-binary': AssetType.glB, - 'model/gltf+json': AssetType.glTF, - 'model/vrm': AssetType.VRM, - 'model/vrml': AssetType.VRM -} as Record + 'audio/mpeg': AssetExt.MP3, + 'video/mp4': AssetExt.MP4, + 'image/png': AssetExt.PNG, + 'image/jpeg': AssetExt.JPEG, + 'image/ktx2': AssetExt.KTX2, + 'model/gltf-binary': AssetExt.GLB, + 'model/gltf+json': AssetExt.GLTF, + 'model/vrm': AssetExt.VRM, + 'model/vrml': AssetExt.VRM +} as Record export const AssetTypeToMimeType = { - [AssetType.MP3]: 'audio/mpeg', - [AssetType.MP4]: 'video/mp4', - [AssetType.PNG]: 'image/png', - [AssetType.JPEG]: 'image/jpeg', - [AssetType.KTX2]: 'image/ktx2', - [AssetType.glB]: 'model/gltf-binary', - [AssetType.glTF]: 'model/gltf+json' -} as Record + [AssetExt.MP3]: 'audio/mpeg', + [AssetExt.MP4]: 'video/mp4', + [AssetExt.PNG]: 'image/png', + [AssetExt.JPEG]: 'image/jpeg', + [AssetExt.KTX2]: 'image/ktx2', + [AssetExt.GLB]: 'model/gltf-binary', + [AssetExt.GLTF]: 'model/gltf+json' +} as Record export const ExtensionToAssetType = { - gltf: AssetType.glTF, - glb: AssetType.glB, - usdz: AssetType.USDZ, - fbx: AssetType.FBX, - vrm: AssetType.VRM, - tga: AssetType.TGA, - ktx2: AssetType.KTX2, - ddx: AssetType.DDS, - png: AssetType.PNG, - jpg: AssetType.JPEG, - jpeg: AssetType.JPEG, - mp3: AssetType.MP3, - aac: AssetType.AAC, - ogg: AssetType.OGG, - m4a: AssetType.M4A, - mp4: AssetType.MP4, - mkv: AssetType.MKV, - m3u8: AssetType.M3U8, - material: AssetType.MAT + gltf: AssetExt.GLTF, + glb: AssetExt.GLB, + usdz: AssetExt.USDZ, + fbx: AssetExt.FBX, + vrm: AssetExt.VRM, + tga: AssetExt.TGA, + ktx2: AssetExt.KTX2, + ddx: AssetExt.DDS, + png: AssetExt.PNG, + jpg: AssetExt.JPEG, + jpeg: AssetExt.JPEG, + mp3: AssetExt.MP3, + aac: AssetExt.AAC, + ogg: AssetExt.OGG, + m4a: AssetExt.M4A, + mp4: AssetExt.MP4, + mkv: AssetExt.MKV, + m3u8: AssetExt.M3U8, + material: AssetExt.MAT } diff --git a/packages/engine/src/assets/enum/AssetClass.ts b/packages/engine/src/assets/enum/AssetClass.ts deleted file mode 100755 index 9c8ace8710..0000000000 --- a/packages/engine/src/assets/enum/AssetClass.ts +++ /dev/null @@ -1,41 +0,0 @@ -/* -CPAL-1.0 License - -The contents of this file are subject to the Common Public Attribution License -Version 1.0. (the "License"); you may not use this file except in compliance -with the License. You may obtain a copy of the License at -https://github.com/EtherealEngine/etherealengine/blob/dev/LICENSE. -The License is based on the Mozilla Public License Version 1.1, but Sections 14 -and 15 have been added to cover use of software over a computer network and -provide for limited attribution for the Original Developer. In addition, -Exhibit A has been modified to be consistent with Exhibit B. - -Software distributed under the License is distributed on an "AS IS" basis, -WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the -specific language governing rights and limitations under the License. - -The Original Code is Ethereal Engine. - -The Original Developer is the Initial Developer. The Initial Developer of the -Original Code is the Ethereal Engine team. - -All portions of the code written by the Ethereal Engine team are Copyright © 2021-2023 -Ethereal Engine. All Rights Reserved. -*/ - -/** List of Asset Classes. */ -export enum AssetClass { - Material = 'Material', - Lookdev = 'Lookdev', - Asset = 'Asset', - Model = 'Model', - Image = 'Image', - Video = 'Video', - Audio = 'Audio', - Document = 'Document', - Text = 'Text', - Script = 'Script', - Prefab = 'Prefab', - Unknown = 'unknown', - Volumetric = 'Volumetric' -} diff --git a/packages/engine/src/assets/enum/AssetType.ts b/packages/engine/src/assets/enum/AssetType.ts deleted file mode 100755 index c3fcc6542e..0000000000 --- a/packages/engine/src/assets/enum/AssetType.ts +++ /dev/null @@ -1,58 +0,0 @@ -/* -CPAL-1.0 License - -The contents of this file are subject to the Common Public Attribution License -Version 1.0. (the "License"); you may not use this file except in compliance -with the License. You may obtain a copy of the License at -https://github.com/EtherealEngine/etherealengine/blob/dev/LICENSE. -The License is based on the Mozilla Public License Version 1.1, but Sections 14 -and 15 have been added to cover use of software over a computer network and -provide for limited attribution for the Original Developer. In addition, -Exhibit A has been modified to be consistent with Exhibit B. - -Software distributed under the License is distributed on an "AS IS" basis, -WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the -specific language governing rights and limitations under the License. - -The Original Code is Ethereal Engine. - -The Original Developer is the Initial Developer. The Initial Developer of the -Original Code is the Ethereal Engine team. - -All portions of the code written by the Ethereal Engine team are Copyright © 2021-2023 -Ethereal Engine. All Rights Reserved. -*/ - -/** List of Asset Types. */ -export enum AssetType { - glB = 'glb', - glTF = 'gltf', - FBX = 'fbx', - OBJ = 'obj', - VRM = 'vrm', - PNG = 'png', - JPEG = 'jpeg', - TGA = 'tga', - MP4 = 'mp4', - TS = 'ts', - MKV = 'mkv', - AVI = 'avi', - MP3 = 'mp3', - WAV = 'wav', - OGG = 'ogg', - M4A = 'm4a', - AAC = 'acc', - CSV = 'csv', - PlainText = 'text', - JSON = 'json', - DOC = 'doc', - XLS = 'xls', - Script = 'script', - DDS = 'dds', - KTX2 = 'ktx2', - USDZ = 'usdz', - M3U8 = 'm3u8', - MAT = 'material', - BIN = 'binary', - UNKNOWN = 'unknown' -} diff --git a/packages/engine/src/assets/functions/resourceLoaderFunctions.ts b/packages/engine/src/assets/functions/resourceLoaderFunctions.ts index 6939a87f51..b1f8574960 100644 --- a/packages/engine/src/assets/functions/resourceLoaderFunctions.ts +++ b/packages/engine/src/assets/functions/resourceLoaderFunctions.ts @@ -69,7 +69,7 @@ export const loadResource = ( const resource = resources[url] callbacks.onStart(resource) ResourceState.debugLog('ResourceManager:load Loading resource: ' + url + ' for entity: ' + entity) - AssetLoader.load( + AssetLoader.loadAsset( url, (response: T) => { if (!resource || !resource.value) { @@ -100,30 +100,54 @@ export const loadResource = ( ) } -export const updateResource = (id: string) => { +/** + * + * Updates a model's resource without the url changing + * Removes the model from the resource state and reloads + * + * @param url the url of the asset to update + * @returns + */ +export const updateModelResource = (url: string) => { const resourceState = getMutableState(ResourceState) const resources = resourceState.nested('resources') - const resource = resources[id] + const resource = resources[url] if (!resource.value) { - console.warn('ResourceManager:update No resource found to update for id: ' + id) + console.warn('resourceLoaderFunctions:updateResource No resource found to update for url: ' + url) return } const onLoads = resource.onLoads.get(NO_PROXY) if (!onLoads) { - console.warn('ResourceManager:update No callbacks found to update for id: ' + id) + console.warn('resourceLoaderFunctions:updateResource No callbacks found to update for url: ' + url) + return + } + + const onLoadArr = Object.values(onLoads) + const entities = resource.references.get(NO_PROXY) as Entity[] + if (onLoadArr.length !== entities.length) { + console.warn( + 'resourceLoaderFunctions:updateResource There should be one loaded callback for every reference, url: ' + url + ) return } - ResourceState.debugLog('ResourceManager:update Updating asset for id: ' + id) - for (const [_, onLoad] of Object.entries(onLoads)) { - AssetLoader.load( - id, + ResourceState.debugLog('resourceLoaderFunctions:updateResource Updating asset for url: ' + url) + const resourceType = resource.type.value + ResourceManager.__unsafeRemoveResource(url) + for (let i = 0; i < onLoadArr.length; i++) { + const onLoad = onLoadArr[i] + loadResource( + url, + resourceType, + entities[i], (response: ResourceAssetType) => { - resource.asset.set(response) onLoad(response) }, - (request) => {}, - (error) => {} + () => {}, + (error) => { + console.error('resourceLoaderFunctions:updateResource error updating resource for url: ' + url, error) + }, + new AbortController().signal ) } } diff --git a/packages/engine/src/assets/functions/resourceLoaderHooks.ts b/packages/engine/src/assets/functions/resourceLoaderHooks.ts index 35ad75a49a..7b4cdc5a48 100644 --- a/packages/engine/src/assets/functions/resourceLoaderHooks.ts +++ b/packages/engine/src/assets/functions/resourceLoaderHooks.ts @@ -43,7 +43,6 @@ function useLoader( //Called when the asset url is changed, mostly useful for editor functions when changing an asset onUnload: (url: string) => void = (url: string) => {} ): [T | null, ErrorEvent | Error | null, ProgressEvent | null, () => void] { - const urlState = useHookstate(url) const value = useHookstate(null) const error = useHookstate(null) const progress = useHookstate | null>(null) @@ -58,41 +57,30 @@ function useLoader( }, []) useImmediateEffect(() => { - const controller = new AbortController() - if (url !== urlState.value) { - if (urlState.value) { - const oldUrl = urlState.value - ResourceManager.unload(oldUrl, entity, uuid.value) - value.set(null) - progress.set(null) - error.set(null) - onUnload(oldUrl) - } - urlState.set(url) - } - - if (!url) return + const _url = url + if (!_url) return let completed = false if (entity) { - ResourcePendingComponent.setResource(entity, url, 0, 0) + ResourcePendingComponent.setResource(entity, _url, 0, 0) } + const controller = new AbortController() loadResource( - url, + _url, resourceType, entity, (response) => { completed = true value.set(response) if (entity) { - ResourcePendingComponent.removeResource(entity, url) + ResourcePendingComponent.removeResource(entity, _url) } }, (request) => { progress.set(request) if (entity) { - ResourcePendingComponent.setResource(entity, url, request.loaded, request.total) + ResourcePendingComponent.setResource(entity, _url, request.loaded, request.total) } }, (err) => { @@ -101,7 +89,7 @@ function useLoader( completed = true error.set(err) if (entity) { - ResourcePendingComponent.removeResource(entity, url) + ResourcePendingComponent.removeResource(entity, _url) } }, controller.signal, @@ -113,6 +101,12 @@ function useLoader( controller.abort( `resourceHooks:useLoader Component loading ${resourceType} at url ${url} for entity ${entity} was unmounted` ) + + ResourceManager.unload(_url, entity, uuid.value) + value.set(null) + progress.set(null) + error.set(null) + onUnload(_url) } }, [url]) @@ -316,3 +310,10 @@ export async function getTextureAsync( ): Promise<[Texture | null, () => void, ErrorEvent | Error | null]> { return getLoader(url, ResourceType.Texture, entity) } + +export async function getAudioAsync( + url: string, + entity?: Entity +): Promise<[AudioBuffer | null, () => void, ErrorEvent | Error | null]> { + return getLoader(url, ResourceType.Audio, entity) +} diff --git a/packages/engine/src/assets/loaders/base/FileLoader.ts b/packages/engine/src/assets/loaders/base/FileLoader.ts index 1de8582227..37948372a5 100644 --- a/packages/engine/src/assets/loaders/base/FileLoader.ts +++ b/packages/engine/src/assets/loaders/base/FileLoader.ts @@ -37,7 +37,7 @@ class HttpError extends Error { } } -class FileLoader extends Loader { +class FileLoader extends Loader { mimeType: undefined | any responseType: undefined | string @@ -45,7 +45,7 @@ class FileLoader extends Loader { super(manager) } - load( + override load( url: string, onLoad: (data: TData) => void, onProgress?: (event: ProgressEvent) => void, diff --git a/packages/engine/src/assets/loaders/base/Loader.ts b/packages/engine/src/assets/loaders/base/Loader.ts index 4a1f493893..da58992509 100644 --- a/packages/engine/src/assets/loaders/base/Loader.ts +++ b/packages/engine/src/assets/loaders/base/Loader.ts @@ -25,7 +25,17 @@ Ethereal Engine. All Rights Reserved. import { DefaultLoadingManager, LoadingManager } from 'three' -class Loader { +interface Load { + load: ( + url: TUrl, + onLoad: (data: TData) => void, + onProgress?: (event: ProgressEvent) => void, + onError?: (err: unknown) => void, + signal?: AbortSignal + ) => void +} + +class Loader implements Load { static DEFAULT_MATERIAL_NAME = '__DEFAULT' manager: LoadingManager diff --git a/packages/engine/src/assets/loaders/gltf/GLTFLoader.ts b/packages/engine/src/assets/loaders/gltf/GLTFLoader.ts index aa7eccdfd3..0ff0f17778 100755 --- a/packages/engine/src/assets/loaders/gltf/GLTFLoader.ts +++ b/packages/engine/src/assets/loaders/gltf/GLTFLoader.ts @@ -360,7 +360,7 @@ export interface GLTF { extensions?: any extras?: any } - parser: GLTFParser + parser?: GLTFParser userData: any } diff --git a/packages/engine/src/assets/loaders/texture/TextureLoader.ts b/packages/engine/src/assets/loaders/texture/TextureLoader.ts new file mode 100644 index 0000000000..4e00d8b61c --- /dev/null +++ b/packages/engine/src/assets/loaders/texture/TextureLoader.ts @@ -0,0 +1,110 @@ +/* +CPAL-1.0 License + +The contents of this file are subject to the Common Public Attribution License +Version 1.0. (the "License"); you may not use this file except in compliance +with the License. You may obtain a copy of the License at +https://github.com/EtherealEngine/etherealengine/blob/dev/LICENSE. +The License is based on the Mozilla Public License Version 1.1, but Sections 14 +and 15 have been added to cover use of software over a computer network and +provide for limited attribution for the Original Developer. In addition, +Exhibit A has been modified to be consistent with Exhibit B. + +Software distributed under the License is distributed on an "AS IS" basis, +WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the +specific language governing rights and limitations under the License. + +The Original Code is Ethereal Engine. + +The Original Developer is the Initial Developer. The Initial Developer of the +Original Code is the Ethereal Engine team. + +All portions of the code written by the Ethereal Engine team are Copyright © 2021-2023 +Ethereal Engine. All Rights Reserved. +*/ + +import { iOS } from '@etherealengine/spatial/src/common/functions/isMobile' +import { ImageLoader, LoadingManager, Texture } from 'three' +import { Loader } from '../base/Loader' + +const iOSMaxResolution = 1024 + +/** @todo make this accessible for performance scaling */ +const getScaledTextureURI = async (src: string, maxResolution: number): Promise<[string, HTMLCanvasElement]> => { + return new Promise(async (resolve) => { + const img = new Image() + img.crossOrigin = 'anonymous' //browser will yell without this + img.src = src + await img.decode() //new way to wait for image to load + // Initialize the canvas and it's size + const canvas = document.createElement('canvas') //dead dom elements? Remove after Three loads them + const ctx = canvas.getContext('2d') + + // Set width and height + const originalWidth = img.width + const originalHeight = img.height + + let resizingFactor = 1 + if (originalWidth >= originalHeight) { + if (originalWidth > maxResolution) { + resizingFactor = maxResolution / originalWidth + } + } else { + if (originalHeight > maxResolution) { + resizingFactor = maxResolution / originalHeight + } + } + + const canvasWidth = originalWidth * resizingFactor + const canvasHeight = originalHeight * resizingFactor + + canvas.width = canvasWidth + canvas.height = canvasHeight + + // Draw image and export to a data-uri + ctx?.drawImage(img, 0, 0, canvasWidth, canvasHeight) + const dataURI = canvas.toDataURL() + + // Do something with the result, like overwrite original + resolve([dataURI, canvas]) + }) +} + +class TextureLoader extends Loader { + maxResolution: number | undefined + + constructor(maxResolution?: number, manager?: LoadingManager) { + super(manager) + if (maxResolution) this.maxResolution = maxResolution + else if (iOS) this.maxResolution = iOSMaxResolution + } + + override async load( + url: string, + onLoad: (loadedTexture: Texture) => void, + onProgress?: (event: ProgressEvent) => void, + onError?: (err: unknown) => void, + signal?: AbortSignal + ) { + let canvas: HTMLCanvasElement | undefined = undefined + if (this.maxResolution) { + ;[url, canvas] = await getScaledTextureURI(url, this.maxResolution) + } + + const texture = new Texture() + const loader = new ImageLoader(this.manager).setCrossOrigin(this.crossOrigin).setPath(this.path) + loader.load( + url, + (image) => { + texture.image = image + texture.needsUpdate = true + if (canvas) canvas.remove() + onLoad(texture) + }, + onProgress, + onError + ) + } +} + +export { TextureLoader } diff --git a/packages/engine/src/audio/systems/MediaSystem.ts b/packages/engine/src/audio/systems/MediaSystem.ts index 26b3670b34..29c8944208 100755 --- a/packages/engine/src/audio/systems/MediaSystem.ts +++ b/packages/engine/src/audio/systems/MediaSystem.ts @@ -35,7 +35,7 @@ import { getState } from '@etherealengine/hyperflux' import { StandardCallbacks, setCallback } from '@etherealengine/spatial/src/common/CallbackComponent' import { MeshComponent } from '@etherealengine/spatial/src/renderer/components/MeshComponent' -import { AssetLoader } from '../../assets/classes/AssetLoader' +import { getAudioAsync } from '../../assets/functions/resourceLoaderHooks' import { MediaComponent } from '../../scene/components/MediaComponent' import { VideoComponent, VideoTexturePriorityQueueState } from '../../scene/components/VideoComponent' import { AudioState, useAudioState } from '../AudioState' @@ -61,7 +61,7 @@ export class AudioEffectPlayer { bufferMap = {} as { [path: string]: AudioBuffer } loadBuffer = async (path: string) => { - const buffer = await AssetLoader.loadAsync(path) + const [buffer] = await getAudioAsync(path) return buffer } @@ -85,7 +85,8 @@ export class AudioEffectPlayer { if (!this.bufferMap[sound]) { // create buffer if doesn't exist - this.bufferMap[sound] = await AudioEffectPlayer?.instance?.loadBuffer(sound) + const [buffer] = await getAudioAsync(sound) + if (buffer) this.bufferMap[sound] = buffer } const source = getState(AudioState).audioContext.createBufferSource() diff --git a/packages/engine/src/avatar/functions/XRControllerFunctions.ts b/packages/engine/src/avatar/functions/XRControllerFunctions.ts index 641a500d83..34e0df4bf2 100644 --- a/packages/engine/src/avatar/functions/XRControllerFunctions.ts +++ b/packages/engine/src/avatar/functions/XRControllerFunctions.ts @@ -34,7 +34,7 @@ import { NameComponent } from '@etherealengine/spatial/src/common/NameComponent' import { addObjectToGroup, removeObjectFromGroup } from '@etherealengine/spatial/src/renderer/components/GroupComponent' import { EntityTreeComponent } from '@etherealengine/spatial/src/transform/components/EntityTree' -import { AssetLoader } from '../../assets/classes/AssetLoader' +import { getGLTFAsync } from '../../assets/functions/resourceLoaderHooks' import { SkeletonUtils } from '../SkeletonUtils' import { AvatarControllerType, AvatarInputSettingsState } from '../state/AvatarInputSettingsState' import { XRHandMeshModel } from './XRHandMeshModel' @@ -44,7 +44,7 @@ export const initializeControllerModel = async (entity: Entity, handedness: stri const avatarInputControllerType = avatarInputState.controlType.value if (avatarInputControllerType !== AvatarControllerType.OculusQuest) return - const gltf = await AssetLoader.loadAsync( + const [gltf] = await getGLTFAsync( `${config.client.fileServer}/projects/default-project/assets/controllers/${handedness}_controller.glb` ) let handMesh = gltf?.scene?.children[0] @@ -84,10 +84,10 @@ export const initializeHandModel = async (entity: Entity, handedness: string) => // if is hands and 'none' type enabled (instead we use IK to move hands in avatar model) if (avatarInputControllerType === AvatarControllerType.None) return - const gltf = await AssetLoader.loadAsync( + const [gltf] = await getGLTFAsync( `${config.client.fileServer}/projects/default-project/assets/controllers/${handedness}.glb` ) - let handMesh = gltf?.scene?.children[0] + const handMesh = gltf?.scene?.children[0] const controller = new Group() controller.name = `controller-hand-model-${entity}` @@ -100,7 +100,7 @@ export const initializeHandModel = async (entity: Entity, handedness: string) => removeObjectFromGroup(controllerEntity, controller.userData.mesh) } - controller.userData.mesh = new XRHandMeshModel(entity, controller, handMesh, handedness) + controller.userData.mesh = new XRHandMeshModel(entity, controller, handMesh!, handedness) addObjectToGroup(controllerEntity, controller.userData.mesh) controller.userData.handedness = handedness diff --git a/packages/engine/src/scene/components/ImageComponent.ts b/packages/engine/src/scene/components/ImageComponent.ts index 9a383071bf..dad220650a 100644 --- a/packages/engine/src/scene/components/ImageComponent.ts +++ b/packages/engine/src/scene/components/ImageComponent.ts @@ -48,8 +48,8 @@ import { useEntityContext } from '@etherealengine/ecs/src/EntityFunctions' import { useMeshComponent } from '@etherealengine/spatial/src/renderer/components/MeshComponent' import { RendererComponent } from '@etherealengine/spatial/src/renderer/WebGLRendererSystem' +import { AssetType } from '@etherealengine/common/src/constants/AssetType' import { AssetLoader } from '../../assets/classes/AssetLoader' -import { AssetClass } from '../../assets/enum/AssetClass' import { useTexture } from '../../assets/functions/resourceLoaderHooks' import { ImageAlphaMode, ImageAlphaModeType, ImageProjection, ImageProjectionType } from '../classes/ImageUtils' import { addError, clearErrors } from '../functions/ErrorFunctions' @@ -163,7 +163,7 @@ export function ImageReactor() { } const assetType = AssetLoader.getAssetClass(image.source.value) - if (assetType !== AssetClass.Image) { + if (assetType !== AssetType.Image) { addError(entity, ImageComponent, `UNSUPPORTED_ASSET_CLASS`) } }, [image.source]) diff --git a/packages/engine/src/scene/components/ParticleSystemComponent.ts b/packages/engine/src/scene/components/ParticleSystemComponent.ts index aff97e73d6..28bc1a3cd7 100644 --- a/packages/engine/src/scene/components/ParticleSystemComponent.ts +++ b/packages/engine/src/scene/components/ParticleSystemComponent.ts @@ -71,8 +71,8 @@ import { useDisposable } from '@etherealengine/spatial/src/resources/resourceHoo import { EntityTreeComponent } from '@etherealengine/spatial/src/transform/components/EntityTree' import { TransformComponent } from '@etherealengine/spatial/src/transform/components/TransformComponent' +import { AssetType } from '@etherealengine/common/src/constants/AssetType' import { AssetLoader } from '../../assets/classes/AssetLoader' -import { AssetClass } from '../../assets/enum/AssetClass' import { useGLTF, useTexture } from '../../assets/functions/resourceLoaderHooks' import { GLTFComponent } from '../../gltf/GLTFComponent' import { GLTFSnapshotAction } from '../../gltf/GLTFDocumentState' @@ -941,15 +941,15 @@ export const ParticleSystemComponent = defineComponent({ const doLoadEmissionGeo = component.systemParameters.shape.type === 'mesh_surface' && - AssetLoader.getAssetClass(component.systemParameters.shape.mesh ?? '') === AssetClass.Model + AssetLoader.getAssetClass(component.systemParameters.shape.mesh ?? '') === AssetType.Model const doLoadInstancingGeo = component.systemParameters.instancingGeometry && - AssetLoader.getAssetClass(component.systemParameters.instancingGeometry) === AssetClass.Model + AssetLoader.getAssetClass(component.systemParameters.instancingGeometry) === AssetType.Model const doLoadTexture = component.systemParameters.texture && - AssetLoader.getAssetClass(component.systemParameters.texture) === AssetClass.Image + AssetLoader.getAssetClass(component.systemParameters.texture) === AssetType.Image const loadedEmissionGeo = (doLoadEmissionGeo && shapeMesh) || !doLoadEmissionGeo const loadedInstanceGeo = (doLoadInstancingGeo && geoDependency) || !doLoadInstancingGeo diff --git a/packages/engine/src/scene/components/UVOL2Component.ts b/packages/engine/src/scene/components/UVOL2Component.ts index c139fd53c6..223910ae95 100644 --- a/packages/engine/src/scene/components/UVOL2Component.ts +++ b/packages/engine/src/scene/components/UVOL2Component.ts @@ -67,8 +67,8 @@ import { isIPhone, isMobile } from '@etherealengine/spatial/src/common/functions import { addObjectToGroup, removeObjectFromGroup } from '@etherealengine/spatial/src/renderer/components/GroupComponent' import { isMobileXRHeadset } from '@etherealengine/spatial/src/xr/XRState' +import { AssetExt } from '@etherealengine/common/src/constants/AssetType' import { getLoader } from '../../assets/classes/AssetLoader' -import { AssetType } from '../../assets/enum/AssetType' import { GLTF } from '../../assets/loaders/gltf/GLTFLoader' import { AssetLoaderState } from '../../assets/state/AssetLoaderState' import { AudioState } from '../../audio/AudioState' @@ -407,7 +407,7 @@ const loadGLB = (url: string) => { const loadTexture = (url: string, repeat: Vector2, offset: Vector2) => { return new Promise<{ texture: CompressedTexture; fetchTime: number }>((resolve, reject) => { const startTime = performance.now() - getLoader(AssetType.KTX2).load( + getLoader(AssetExt.KTX2).load( url, (texture: CompressedTexture) => { texture.repeat.copy(repeat) diff --git a/packages/engine/src/scene/constants/Util.ts b/packages/engine/src/scene/constants/Util.ts index 22b0175b1f..39348cd7af 100644 --- a/packages/engine/src/scene/constants/Util.ts +++ b/packages/engine/src/scene/constants/Util.ts @@ -23,12 +23,10 @@ All portions of the code written by the Ethereal Engine team are Copyright © 20 Ethereal Engine. All Rights Reserved. */ -import { Color, CompressedTexture, CubeTexture, CubeTextureLoader, TextureLoader } from 'three' +import { Color, CompressedTexture, CubeTexture, CubeTextureLoader } from 'three' import { DDSLoader } from '../../assets/loaders/dds/DDSLoader' -export const textureLoader = new TextureLoader() - export const getRGBArray = (color: Color): Uint8Array => { const resolution = 64 // Min value required const size = resolution * resolution diff --git a/packages/engine/src/scene/materials/functions/LoadVideoTexture.ts b/packages/engine/src/scene/materials/functions/LoadVideoTexture.ts index 2cbb17e88c..02dcb189b3 100644 --- a/packages/engine/src/scene/materials/functions/LoadVideoTexture.ts +++ b/packages/engine/src/scene/materials/functions/LoadVideoTexture.ts @@ -23,7 +23,7 @@ All portions of the code written by the Ethereal Engine team are Copyright © 20 Ethereal Engine. All Rights Reserved. */ -import { VideoTexture } from 'three' +import { RepeatWrapping, VideoTexture } from 'three' import { isClient } from '@etherealengine/common/src/utils/getEnvironment' @@ -57,6 +57,8 @@ export default function loadVideoTexture(src, onLoad = (result) => {}) { el.currentTime = 1 if (!texture) console.error('texture is missing') + texture.wrapS = RepeatWrapping + texture.wrapT = RepeatWrapping el.addEventListener( 'loadeddata', () => { diff --git a/packages/server-core/src/projects/project/project-helper.ts b/packages/server-core/src/projects/project/project-helper.ts index 1dae89849e..c60781f430 100644 --- a/packages/server-core/src/projects/project/project-helper.ts +++ b/packages/server-core/src/projects/project/project-helper.ts @@ -44,6 +44,7 @@ import semver from 'semver' import { promisify } from 'util' import { v4 as uuidv4 } from 'uuid' +import { AssetType } from '@etherealengine/common/src/constants/AssetType' import { PUBLIC_SIGNED_REGEX } from '@etherealengine/common/src/constants/GitHubConstants' import { ManifestJson } from '@etherealengine/common/src/interfaces/ManifestJson' import { ProjectPackageJsonType } from '@etherealengine/common/src/interfaces/ProjectPackageJsonType' @@ -75,7 +76,6 @@ import { } from '@etherealengine/common/src/utils/fsHelperFunctions' import { processFileName } from '@etherealengine/common/src/utils/processFileName' import { AssetLoader } from '@etherealengine/engine/src/assets/classes/AssetLoader' -import { AssetClass } from '@etherealengine/engine/src/assets/enum/AssetClass' import { getState } from '@etherealengine/hyperflux' import { ProjectConfigInterface, ProjectEventHooks } from '@etherealengine/projects/ProjectConfigInterface' @@ -1830,13 +1830,13 @@ export const uploadLocalProjectToProvider = async ( if (!hasResourceDB) { //otherwise, upload the files into static resources individually const staticResourceClasses = [ - AssetClass.Audio, - AssetClass.Image, - AssetClass.Model, - AssetClass.Video, - AssetClass.Volumetric, - AssetClass.Material, - AssetClass.Prefab + AssetType.Audio, + AssetType.Image, + AssetType.Model, + AssetType.Video, + AssetType.Volumetric, + AssetType.Material, + AssetType.Prefab ] const thisFileClass = AssetLoader.getAssetClass(file) if (filePathRelative.startsWith('/assets/') && staticResourceClasses.includes(thisFileClass)) { diff --git a/packages/spatial/src/resources/ResourceState.ts b/packages/spatial/src/resources/ResourceState.ts index 9e5d029c39..38c522b06a 100644 --- a/packages/spatial/src/resources/ResourceState.ts +++ b/packages/spatial/src/resources/ResourceState.ts @@ -23,12 +23,23 @@ All portions of the code written by the Ethereal Engine team are Copyright © 20 Ethereal Engine. All Rights Reserved. */ -import { BufferAttribute, Cache, CompressedTexture, Material, Mesh, Object3D, Scene, SkinnedMesh, Texture } from 'three' +import { + BufferAttribute, + Cache, + CompressedTexture, + Material, + Mesh, + Object3D, + RepeatWrapping, + SkinnedMesh, + Texture +} from 'three' import { Engine, Entity, getOptionalComponent, UndefinedEntity } from '@etherealengine/ecs' import { defineState, getMutableState, getState, NO_PROXY, none, State } from '@etherealengine/hyperflux' import { removeObjectFromGroup } from '@etherealengine/spatial/src/renderer/components/GroupComponent' +import { GLTF } from '@etherealengine/engine/src/assets/loaders/gltf/GLTFLoader' import { Geometry } from '../common/constants/Geometry' import iterateObject3D from '../common/functions/iterateObject3D' import { PerformanceState } from '../renderer/PerformanceState' @@ -58,14 +69,9 @@ export enum ResourceType { Geometry = 'Geometry', Material = 'Material', Object3D = 'Object3D', + Audio = 'Audio', Unknown = 'Unknown' // ECSData = 'ECSData', - // Audio = 'Audio', -} - -type GLTF = { - scene: Scene - scenes: Scene[] } export type ResourceAssetType = @@ -77,6 +83,7 @@ export type ResourceAssetType = | Material[] | Mesh | DisposableObject + | AudioBuffer type BaseMetadata = { size?: number @@ -159,7 +166,9 @@ const getTotalVertexCount = () => { } const getRendererInfo = () => { - const renderer = getOptionalComponent(Engine.instance.viewerEntity, RendererComponent)?.renderer + const viewer = Engine?.instance?.viewerEntity as Entity | undefined + if (!viewer) return {} + const renderer = getOptionalComponent(viewer, RendererComponent)?.renderer if (!renderer) return {} return { memory: renderer.info.memory, @@ -185,7 +194,7 @@ const checkBudgets = () => { const resourceCallbacks = { [ResourceType.GLTF]: { onStart: (resource: State) => {}, - onLoad: (response: GLTF, resource: State, resourceState: State) => { + onLoad: (asset: GLTF, resource: State, resourceState: State) => { const resources = getMutableState(ResourceState).nested('resources') const geometryIDs = resource.assetRefs[ResourceType.Geometry] const metadata = resource.metadata as State @@ -209,6 +218,8 @@ const resourceCallbacks = { } metadata.textureWidths.set(textureWidths) } + + if (asset.parser) delete asset.parser }, onProgress: (request: ProgressEvent, resource: State) => { resource.metadata.size.set(request.total) @@ -225,31 +236,38 @@ const resourceCallbacks = { resource.metadata.merge({ onGPU: false }) }, onLoad: ( - response: Texture | CompressedTexture, + asset: Texture | CompressedTexture, resource: State, resourceState: State ) => { - response.onUpdate = () => { + asset.wrapS = RepeatWrapping + asset.wrapT = RepeatWrapping + asset.onUpdate = () => { if (resource && resource.value) resource.metadata.merge({ onGPU: true }) //@ts-ignore - response.onUpdate = null + asset.onUpdate = null } //Compressed texture size - if (response.mipmaps[0]) { + if (asset.mipmaps[0]) { let size = 0 - for (const mip of response.mipmaps) { + for (const mip of asset.mipmaps) { size += mip.data.byteLength } resource.metadata.size.set(size) // Non compressed texture size } else { - const height = response.image.height - const width = response.image.width + const height = asset.image.height + const width = asset.image.width const size = width * height * 4 resource.metadata.size.set(size) } - resource.metadata.merge({ textureWidth: response.image.width }) + if ((asset as CompressedTexture).isCompressedTexture) { + const id = resource.id.value + if (id.endsWith('ktx2')) asset.source.data.src = id + } + + resource.metadata.merge({ textureWidth: asset.image.width }) resourceState.totalBufferCount.set(resourceState.totalBufferCount.value + resource.metadata.size.value!) }, onProgress: (request: ProgressEvent, resource: State) => {}, @@ -266,7 +284,7 @@ const resourceCallbacks = { }, [ResourceType.Material]: { onStart: (resource: State) => {}, - onLoad: (response: Material, resource: State, resourceState: State) => {}, + onLoad: (asset: Material, resource: State, resourceState: State) => {}, onProgress: (request: ProgressEvent, resource: State) => {}, onError: (event: ErrorEvent | Error, resource: State) => {}, onUnload: ( @@ -279,23 +297,23 @@ const resourceCallbacks = { }, [ResourceType.Geometry]: { onStart: (resource: State) => {}, - onLoad: (response: Geometry, resource: State, resourceState: State) => { + onLoad: (asset: Geometry, resource: State, resourceState: State) => { // Estimated geometry size - const attributeKeys = Object.keys(response.attributes) - let needsUploaded = response.index ? attributeKeys.length + 1 : attributeKeys.length + const attributeKeys = Object.keys(asset.attributes) + let needsUploaded = asset.index ? attributeKeys.length + 1 : attributeKeys.length let size = 0 const checkUploaded = () => { if (needsUploaded == 0 && resource && resource.value) resource.metadata.merge({ onGPU: true }) } - response.index?.onUpload(() => { + asset.index?.onUpload(() => { needsUploaded -= 1 checkUploaded() }) for (const name of attributeKeys) { - const attr = response.getAttribute(name) as BufferAttribute + const attr = asset.getAttribute(name) as BufferAttribute size += attr.count * attr.itemSize * attr.array.BYTES_PER_ELEMENT if (typeof attr.onUpload === 'function') { attr.onUpload(() => { @@ -308,7 +326,7 @@ const resourceCallbacks = { } checkUploaded() - const indices = response.getIndex() + const indices = asset.getIndex() if (indices) { resource.metadata.merge({ vertexCount: indices.count }) size += indices.count * indices.itemSize * indices.array.BYTES_PER_ELEMENT @@ -323,7 +341,7 @@ const resourceCallbacks = { }, [ResourceType.Mesh]: { onStart: (resource: State) => {}, - onLoad: (response: Material, resource: State, resourceState: State) => {}, + onLoad: (asset: Mesh, resource: State, resourceState: State) => {}, onProgress: (request: ProgressEvent, resource: State) => {}, onError: (event: ErrorEvent | Error, resource: State) => {}, onUnload: (asset: Mesh, resource: State, resourceState: State) => { @@ -332,7 +350,7 @@ const resourceCallbacks = { }, [ResourceType.Object3D]: { onStart: (resource: State) => {}, - onLoad: (response: Material, resource: State, resourceState: State) => {}, + onLoad: (asset: Material, resource: State, resourceState: State) => {}, onProgress: (request: ProgressEvent, resource: State) => {}, onError: (event: ErrorEvent | Error, resource: State) => {}, onUnload: ( @@ -343,9 +361,16 @@ const resourceCallbacks = { tryUnloadObj(asset) } }, + [ResourceType.Audio]: { + onStart: (resource: State) => {}, + onLoad: (asset: AudioBuffer, resource: State, resourceState: State) => {}, + onProgress: (request: ProgressEvent, resource: State) => {}, + onError: (event: ErrorEvent | Error, resource: State) => {}, + onUnload: (asset: AudioBuffer, resource: State, resourceState: State) => {} + }, [ResourceType.Unknown]: { onStart: (resource: State) => {}, - onLoad: (response: Material, resource: State, resourceState: State) => {}, + onLoad: (asset: Material, resource: State, resourceState: State) => {}, onProgress: (request: ProgressEvent, resource: State) => {}, onError: (event: ErrorEvent | Error, resource: State) => {}, onUnload: ( @@ -360,7 +385,7 @@ const resourceCallbacks = { [key in ResourceType]: { onStart: (resource: State) => void onLoad: ( - response: ResourceAssetType, + asset: ResourceAssetType, resource: State, resourceState: State ) => void @@ -675,5 +700,7 @@ export const ResourceManager = { getTotalSizeOfResources, getTotalBufferSize, getTotalVertexCount - } + }, + /** Removes a resource even if it is still being referenced, needed for updating assets in the studio */ + __unsafeRemoveResource: removeResource } diff --git a/packages/ui/src/components/editor/input/Behavior/index.tsx b/packages/ui/src/components/editor/input/Behavior/index.tsx index 876e388d53..078c023e51 100644 --- a/packages/ui/src/components/editor/input/Behavior/index.tsx +++ b/packages/ui/src/components/editor/input/Behavior/index.tsx @@ -26,7 +26,7 @@ Ethereal Engine. All Rights Reserved. import React, { useCallback } from 'react' import { Texture, Vector2, Vector3 } from 'three' -import { AssetLoader } from '@etherealengine/engine/src/assets/classes/AssetLoader' +import { getTextureAsync } from '@etherealengine/engine/src/assets/functions/resourceLoaderHooks' import { ApplyForceBehaviorJSON, ApplySequencesJSON, @@ -352,7 +352,8 @@ export default function BehaviorInput({ (scope: State) => { const thisOnChange = onChange(scope.src) return (src: string) => { - AssetLoader.load(src, (texture: Texture) => { + getTextureAsync(src).then(([texture]) => { + if (!texture) return createReadableTexture(texture, { canvas: true, flipY: true }).then((readableTexture: Texture) => { const canvas = readableTexture.image as HTMLCanvasElement const ctx = canvas.getContext('2d')! diff --git a/packages/ui/src/components/editor/input/Texture/index.tsx b/packages/ui/src/components/editor/input/Texture/index.tsx index 67b7ab5553..a0719c273c 100644 --- a/packages/ui/src/components/editor/input/Texture/index.tsx +++ b/packages/ui/src/components/editor/input/Texture/index.tsx @@ -26,9 +26,9 @@ Ethereal Engine. All Rights Reserved. import React, { Fragment, useEffect } from 'react' import { ColorSpace, DisplayP3ColorSpace, LinearSRGBColorSpace, SRGBColorSpace, Texture, Vector2 } from 'three' +import { AssetType } from '@etherealengine/common/src/constants/AssetType' import { AssetLoader } from '@etherealengine/engine/src/assets/classes/AssetLoader' 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 { ItemTypes } from '@etherealengine/editor/src/constants/AssetTypes' @@ -64,7 +64,7 @@ export default function TexturePreviewInput({ }) { const { preview } = rest const validSrcValue = - typeof value === 'string' && [AssetClass.Image, AssetClass.Video].includes(AssetLoader.getAssetClass(value)) + typeof value === 'string' && [AssetType.Image, AssetType.Video].includes(AssetLoader.getAssetClass(value)) const srcState = useHookstate(value) const texture = srcState.value as Texture @@ -99,7 +99,7 @@ export default function TexturePreviewInput({
{(typeof preview === 'string' || - (typeof value === 'string' && AssetLoader.getAssetClass(value) === AssetClass.Image)) && ( + (typeof value === 'string' && AssetLoader.getAssetClass(value) === AssetType.Image)) && ( )} - {typeof value === 'string' && AssetLoader.getAssetClass(value) === AssetClass.Video && ( + {typeof value === 'string' && AssetLoader.getAssetClass(value) === AssetType.Video && (