diff --git a/frontend/apps/editor3d/app/src/components/reusable/UploadModal3D/utilities/loadPreviewOnCanvas.ts b/frontend/apps/editor3d/app/src/components/reusable/UploadModal3D/utilities/loadPreviewOnCanvas.ts index b4de63ba2d..1f8ef100d7 100644 --- a/frontend/apps/editor3d/app/src/components/reusable/UploadModal3D/utilities/loadPreviewOnCanvas.ts +++ b/frontend/apps/editor3d/app/src/components/reusable/UploadModal3D/utilities/loadPreviewOnCanvas.ts @@ -13,6 +13,10 @@ interface LoaderInterface { statusCallback: (statusObject: { type: string; message?: string }) => void; } +interface PreviewReturn { + renderer: THREE.WebGLRenderer; +} + export const loadPreviewOnCanvas = ({ file, canvas, @@ -21,7 +25,7 @@ export const loadPreviewOnCanvas = ({ file: File; canvas: HTMLCanvasElement; statusCallback: (error: { type: string; message?: string }) => void; -}) => { +}): PreviewReturn => { // Setup of scene, camera, and renderer in the canvas const scene = new THREE.Scene(); @@ -74,8 +78,12 @@ export const loadPreviewOnCanvas = ({ file.arrayBuffer().then((arrayBuffer) => { splatMesh = new SplatMesh({ fileBytes: arrayBuffer, fileType: SplatFileType.SPZ, - onLoad: () => { scene.add(splatMesh) } + onLoad: () => { scene.add(splatMesh!) } }); + + if (file.name.split(".")[0].endsWith("ceramic")) { + splatMesh.rotateX(Math.PI); + } }).catch((loaderError) => { statusCallback({ type: "SPLAT Loader Error", @@ -100,6 +108,8 @@ export const loadPreviewOnCanvas = ({ splatMesh?.rotateY(0.01); }; renderer.setAnimationLoop(animate); + + return { renderer }; }; const glbLoader = ({ diff --git a/frontend/apps/editor3d/app/src/components/reusable/UploadModalSplat/UploadFilesSplat.tsx b/frontend/apps/editor3d/app/src/components/reusable/UploadModalSplat/UploadFilesSplat.tsx index a4a832903b..9a9228d871 100644 --- a/frontend/apps/editor3d/app/src/components/reusable/UploadModalSplat/UploadFilesSplat.tsx +++ b/frontend/apps/editor3d/app/src/components/reusable/UploadModalSplat/UploadFilesSplat.tsx @@ -7,6 +7,7 @@ import { FilterEngineCategories, MediaFileAnimationType, UploaderStates } from " import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { faCube } from "@fortawesome/pro-solid-svg-icons"; import { loadPreviewOnCanvas, snapshotCanvasAsThumbnail } from "../UploadModal3D/utilities"; +import { WebGLRenderer } from "three"; interface Props { title: string; @@ -19,7 +20,7 @@ interface Props { }; onClose: () => void; onUploadProgress: (newState: UploaderState) => void; - onLocalBytes: (bytes: ArrayBuffer) => void; + onLocalBytes: (bytes: ArrayBuffer, shouldFlip) => void; } export const UploadFilesSplat = ({ @@ -73,8 +74,10 @@ export const UploadFilesSplat = ({ // Use the filename as the title const title = assetFile.value.name.split(".")[0]; + const shouldFlip = title.endsWith("ceramic"); + assetFile.value.arrayBuffer().then((arrayBuffer) => { - onLocalBytes(arrayBuffer); + onLocalBytes(arrayBuffer, shouldFlip); }).catch((error) => { console.error("Error reading file as ArrayBuffer:", error); }); @@ -102,14 +105,21 @@ export const UploadFilesSplat = ({ }, [fileSubtypes]); useEffect(() => { + let renderer: WebGLRenderer | null = null; + if (canvasRef.current && assetFile.value) { - loadPreviewOnCanvas({ + renderer = loadPreviewOnCanvas({ file: assetFile.value, canvas: canvasRef.current, statusCallback: (statusObject: { type: string; message?: string }) => { setPreviewStatus(statusObject); }, - }); + }).renderer; + } + + return () => { + renderer?.setAnimationLoop(null); + renderer?.dispose(); } }, [assetFile.value]); diff --git a/frontend/apps/editor3d/app/src/components/reusable/UploadModalSplat/UploadModalSplat.tsx b/frontend/apps/editor3d/app/src/components/reusable/UploadModalSplat/UploadModalSplat.tsx index 35151c908a..e8f7ab7ee1 100644 --- a/frontend/apps/editor3d/app/src/components/reusable/UploadModalSplat/UploadModalSplat.tsx +++ b/frontend/apps/editor3d/app/src/components/reusable/UploadModalSplat/UploadModalSplat.tsx @@ -18,7 +18,7 @@ import { UploadFilesSplat } from "./UploadFilesSplat"; interface Props { onClose: () => void; - onSuccess: (splatArrayBuffer: ArrayBuffer) => void; + onSuccess: (splatArrayBuffer: ArrayBuffer, shouldFlip: boolean) => void; isOpen: boolean; title: string; titleIcon: IconDefinition; @@ -77,7 +77,7 @@ export function UploadModalSplat(props: Props) { }} onClose={onClose} onUploadProgress={updateUploaderState} - onLocalBytes={(buffer) => { onSuccess(buffer); }} + onLocalBytes={(buffer, shouldFlip) => { onSuccess(buffer, shouldFlip); }} /> ); diff --git a/frontend/apps/editor3d/app/src/pages/PageEnigma/Editor/timeline.ts b/frontend/apps/editor3d/app/src/pages/PageEnigma/Editor/timeline.ts index 28c79e93c4..7e35604743 100644 --- a/frontend/apps/editor3d/app/src/pages/PageEnigma/Editor/timeline.ts +++ b/frontend/apps/editor3d/app/src/pages/PageEnigma/Editor/timeline.ts @@ -402,8 +402,13 @@ export class TimeLine { // This method is for local dev testing of objects // and bypasses the queue system, that's kinda redundant anyway - public addLocalSplat(fileBytes: ArrayBuffer | Uint8Array) { + public addLocalSplat(fileBytes: ArrayBuffer | Uint8Array, flipVertical: boolean = false) { const splatMesh = new SplatMesh({ fileBytes: fileBytes }); + + if (flipVertical) { + splatMesh.rotateX(Math.PI); + } + this.editorEngine.sceneManager?.scene.scene.add(splatMesh); } diff --git a/frontend/apps/editor3d/app/src/pages/PageEnigma/comps/Controls3D/Controls3D.tsx b/frontend/apps/editor3d/app/src/pages/PageEnigma/comps/Controls3D/Controls3D.tsx index f64a9e5c46..31898ad82d 100644 --- a/frontend/apps/editor3d/app/src/pages/PageEnigma/comps/Controls3D/Controls3D.tsx +++ b/frontend/apps/editor3d/app/src/pages/PageEnigma/comps/Controls3D/Controls3D.tsx @@ -410,9 +410,9 @@ export const Controls3D = () => { setUploadSplatIsShowing(false)} - onSuccess={(buffer) => { + onSuccess={(buffer, shouldFlip) => { setUploadSplatIsShowing(false) - editorEngine?.timeline.addLocalSplat(buffer); + editorEngine?.timeline.addLocalSplat(buffer, shouldFlip); }} title="Upload an spz file" titleIcon={faCube}