Merge remote-tracking branch 'origin/v2' into v2-ui
This commit is contained in:
@@ -3,7 +3,6 @@ import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader';
|
|||||||
import gsap from 'gsap';
|
import gsap from 'gsap';
|
||||||
import * as THREE from 'three';
|
import * as THREE from 'three';
|
||||||
import * as CONSTANTS from '../../../types/world/worldConstants';
|
import * as CONSTANTS from '../../../types/world/worldConstants';
|
||||||
import { toast } from 'react-toastify';
|
|
||||||
import * as Types from "../../../types/world/worldTypes";
|
import * as Types from "../../../types/world/worldTypes";
|
||||||
import { initializeDB, retrieveGLTF, storeGLTF } from '../../../utils/indexDB/idbUtils';
|
import { initializeDB, retrieveGLTF, storeGLTF } from '../../../utils/indexDB/idbUtils';
|
||||||
import { getCamera } from '../../../services/factoryBuilder/camera/getCameraApi';
|
import { getCamera } from '../../../services/factoryBuilder/camera/getCameraApi';
|
||||||
@@ -91,7 +90,7 @@ async function loadInitialFloorItems(
|
|||||||
},
|
},
|
||||||
undefined,
|
undefined,
|
||||||
(error) => {
|
(error) => {
|
||||||
toast.error(`[IndexedDB] Error loading ${item.modelName}:`);
|
echo.error(`[IndexedDB] Error loading ${item.modelName}:`);
|
||||||
URL.revokeObjectURL(blobUrl);
|
URL.revokeObjectURL(blobUrl);
|
||||||
resolve();
|
resolve();
|
||||||
}
|
}
|
||||||
@@ -111,7 +110,7 @@ async function loadInitialFloorItems(
|
|||||||
},
|
},
|
||||||
undefined,
|
undefined,
|
||||||
(error) => {
|
(error) => {
|
||||||
toast.error(`[Backend] Error loading ${item.modelName}:`);
|
echo.error(`[Backend] Error loading ${item.modelName}:`);
|
||||||
resolve();
|
resolve();
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ async function loadInitialWallItems(
|
|||||||
|
|
||||||
const loadedWallItems = await Promise.all(items.map(async (item: Types.WallItem) => {
|
const loadedWallItems = await Promise.all(items.map(async (item: Types.WallItem) => {
|
||||||
// Check THREE.js cache first
|
// Check THREE.js cache first
|
||||||
const cachedModel = THREE.Cache.get(item.modelName!);
|
const cachedModel = THREE.Cache.get(item.modelfileID!);
|
||||||
if (cachedModel) {
|
if (cachedModel) {
|
||||||
return processModel(cachedModel, item);
|
return processModel(cachedModel, item);
|
||||||
}
|
}
|
||||||
@@ -45,7 +45,7 @@ async function loadInitialWallItems(
|
|||||||
return new Promise<Types.WallItem>((resolve) => {
|
return new Promise<Types.WallItem>((resolve) => {
|
||||||
loader.load(blobUrl, (gltf) => {
|
loader.load(blobUrl, (gltf) => {
|
||||||
URL.revokeObjectURL(blobUrl);
|
URL.revokeObjectURL(blobUrl);
|
||||||
THREE.Cache.add(item.modelName!, gltf);
|
THREE.Cache.add(item.modelfileID!, gltf);
|
||||||
resolve(processModel(gltf, item));
|
resolve(processModel(gltf, item));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -58,8 +58,8 @@ async function loadInitialWallItems(
|
|||||||
try {
|
try {
|
||||||
// Cache the model
|
// Cache the model
|
||||||
const modelBlob = await fetch(modelUrl).then((res) => res.blob());
|
const modelBlob = await fetch(modelUrl).then((res) => res.blob());
|
||||||
await storeGLTF(item.modelName!, modelBlob);
|
await storeGLTF(item.modelfileID!, modelBlob);
|
||||||
THREE.Cache.add(item.modelName!, gltf);
|
THREE.Cache.add(item.modelfileID!, gltf);
|
||||||
resolve(processModel(gltf, item));
|
resolve(processModel(gltf, item));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to cache model:', error);
|
console.error('Failed to cache model:', error);
|
||||||
|
|||||||
125
app/src/modules/builder/assetGroup/assetsGroup.tsx
Normal file
125
app/src/modules/builder/assetGroup/assetsGroup.tsx
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
import * as THREE from "three"
|
||||||
|
import { useEffect } from 'react'
|
||||||
|
import { getFloorAssets } from '../../../services/factoryBuilder/assest/floorAsset/getFloorItemsApi';
|
||||||
|
import { useLoadingProgress } from '../../../store/builder/store';
|
||||||
|
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader";
|
||||||
|
import { DRACOLoader } from "three/examples/jsm/loaders/DRACOLoader";
|
||||||
|
import { FloorItems } from "../../../types/world/worldTypes";
|
||||||
|
import { useAssetsStore } from "../../../store/builder/useAssetStore";
|
||||||
|
import Models from "./models/models";
|
||||||
|
import { useGLTF } from "@react-three/drei";
|
||||||
|
|
||||||
|
const gltfLoaderWorker = new Worker(
|
||||||
|
new URL(
|
||||||
|
"../../../services/factoryBuilder/webWorkers/gltfLoaderWorker.js",
|
||||||
|
import.meta.url
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
function AssetsGroup() {
|
||||||
|
const { setLoadingProgress } = useLoadingProgress();
|
||||||
|
const { setAssets } = useAssetsStore();
|
||||||
|
|
||||||
|
const loader = new GLTFLoader();
|
||||||
|
const dracoLoader = new DRACOLoader();
|
||||||
|
|
||||||
|
dracoLoader.setDecoderPath(
|
||||||
|
"https://cdn.jsdelivr.net/npm/three@0.160.0/examples/jsm/libs/draco/gltf/"
|
||||||
|
);
|
||||||
|
loader.setDRACOLoader(dracoLoader);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const email = localStorage.getItem("email");
|
||||||
|
const organization = email!.split("@")[1].split(".")[0];
|
||||||
|
|
||||||
|
let totalAssets = 0;
|
||||||
|
let loadedAssets = 0;
|
||||||
|
|
||||||
|
const updateLoadingProgress = (progress: number) => {
|
||||||
|
if (progress < 100) {
|
||||||
|
setLoadingProgress(progress);
|
||||||
|
} else if (progress === 100) {
|
||||||
|
setTimeout(() => {
|
||||||
|
setLoadingProgress(100);
|
||||||
|
setTimeout(() => {
|
||||||
|
setLoadingProgress(0);
|
||||||
|
}, 1500);
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
getFloorAssets(organization).then((data) => {
|
||||||
|
if (data.length > 0) {
|
||||||
|
const uniqueItems = (data as FloorItems).filter((item, index, self) => index === self.findIndex((t) => t.modelfileID === item.modelfileID));
|
||||||
|
totalAssets = uniqueItems.length;
|
||||||
|
if (totalAssets === 0) {
|
||||||
|
updateLoadingProgress(100);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
gltfLoaderWorker.postMessage({ floorItems: uniqueItems });
|
||||||
|
} else {
|
||||||
|
gltfLoaderWorker.postMessage({ floorItems: [] });
|
||||||
|
updateLoadingProgress(100);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
gltfLoaderWorker.onmessage = async (event) => {
|
||||||
|
if (event.data.message === "gltfLoaded" && event.data.modelBlob) {
|
||||||
|
const blobUrl = URL.createObjectURL(event.data.modelBlob);
|
||||||
|
loader.load(blobUrl, (gltf) => {
|
||||||
|
URL.revokeObjectURL(blobUrl);
|
||||||
|
THREE.Cache.remove(blobUrl);
|
||||||
|
THREE.Cache.add(event.data.modelID, gltf);
|
||||||
|
|
||||||
|
loadedAssets++;
|
||||||
|
const progress = Math.round((loadedAssets / totalAssets) * 100);
|
||||||
|
updateLoadingProgress(progress);
|
||||||
|
|
||||||
|
if (loadedAssets === totalAssets) {
|
||||||
|
const assets: Asset[] = [];
|
||||||
|
getFloorAssets(organization).then((data: FloorItems) => {
|
||||||
|
data.forEach((item) => {
|
||||||
|
if (item.eventData) {
|
||||||
|
assets.push({
|
||||||
|
modelUuid: item.modelUuid,
|
||||||
|
modelName: item.modelName,
|
||||||
|
assetId: item.modelfileID,
|
||||||
|
position: item.position,
|
||||||
|
rotation: [item.rotation.x, item.rotation.y, item.rotation.z],
|
||||||
|
isLocked: item.isLocked,
|
||||||
|
isCollidable: false,
|
||||||
|
isVisible: item.isVisible,
|
||||||
|
opacity: 1,
|
||||||
|
eventData: item.eventData
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
assets.push({
|
||||||
|
modelUuid: item.modelUuid,
|
||||||
|
modelName: item.modelName,
|
||||||
|
assetId: item.modelfileID,
|
||||||
|
position: item.position,
|
||||||
|
rotation: [item.rotation.x, item.rotation.y, item.rotation.z],
|
||||||
|
isLocked: item.isLocked,
|
||||||
|
isCollidable: false,
|
||||||
|
isVisible: item.isVisible,
|
||||||
|
opacity: 1,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
setAssets(assets);
|
||||||
|
})
|
||||||
|
updateLoadingProgress(100);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Models />
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AssetsGroup;
|
||||||
82
app/src/modules/builder/assetGroup/models/model/model.tsx
Normal file
82
app/src/modules/builder/assetGroup/models/model/model.tsx
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
import { Outlines } from '@react-three/drei';
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import { retrieveGLTF, storeGLTF } from '../../../../../utils/indexDB/idbUtils';
|
||||||
|
import { GLTF, GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader';
|
||||||
|
import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader';
|
||||||
|
import * as THREE from 'three';
|
||||||
|
|
||||||
|
function Model({ asset }: { asset: Asset }) {
|
||||||
|
const url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_MARKETPLACE_URL}`;
|
||||||
|
const [gltfScene, setGltfScene] = useState<GLTF | null>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const loader = new GLTFLoader();
|
||||||
|
const dracoLoader = new DRACOLoader();
|
||||||
|
|
||||||
|
dracoLoader.setDecoderPath('https://cdn.jsdelivr.net/npm/three@0.160.0/examples/jsm/libs/draco/gltf/');
|
||||||
|
loader.setDRACOLoader(dracoLoader);
|
||||||
|
const loadModel = async () => {
|
||||||
|
try {
|
||||||
|
const cachedModel = THREE.Cache.get(asset.assetId!);
|
||||||
|
if (cachedModel) {
|
||||||
|
setGltfScene(cachedModel);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check IndexedDB
|
||||||
|
const indexedDBModel = await retrieveGLTF(asset.assetId!);
|
||||||
|
if (indexedDBModel) {
|
||||||
|
const blobUrl = URL.createObjectURL(indexedDBModel);
|
||||||
|
loader.load(blobUrl, (gltf) => {
|
||||||
|
URL.revokeObjectURL(blobUrl);
|
||||||
|
THREE.Cache.remove(blobUrl);
|
||||||
|
THREE.Cache.add(asset.assetId!, gltf);
|
||||||
|
setGltfScene(gltf);
|
||||||
|
},
|
||||||
|
undefined,
|
||||||
|
(error) => {
|
||||||
|
echo.error(`[IndexedDB] Error loading ${asset.modelName}:`);
|
||||||
|
URL.revokeObjectURL(blobUrl);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch from Backend
|
||||||
|
const modelUrl = `${url_Backend_dwinzo}/api/v2/AssetFile/${asset.assetId!}`;
|
||||||
|
loader.load(modelUrl, async (gltf) => {
|
||||||
|
const modelBlob = await fetch(modelUrl).then((res) => res.blob());
|
||||||
|
await storeGLTF(asset.assetId!, modelBlob);
|
||||||
|
THREE.Cache.add(asset.assetId!, gltf);
|
||||||
|
setGltfScene(gltf);
|
||||||
|
},
|
||||||
|
undefined,
|
||||||
|
(error) => {
|
||||||
|
echo.error(`[Backend] Error loading ${asset.modelName}:`);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Failed to load model:", asset.assetId, err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
loadModel();
|
||||||
|
|
||||||
|
}, [asset.assetId]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{gltfScene &&
|
||||||
|
<group
|
||||||
|
position={asset.position}
|
||||||
|
rotation={asset.rotation}
|
||||||
|
visible={asset.isVisible}
|
||||||
|
>
|
||||||
|
<primitive object={gltfScene.scene.clone()} />
|
||||||
|
</group>
|
||||||
|
}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Model;
|
||||||
17
app/src/modules/builder/assetGroup/models/models.tsx
Normal file
17
app/src/modules/builder/assetGroup/models/models.tsx
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import { useAssetsStore } from '../../../../store/builder/useAssetStore';
|
||||||
|
import Model from './model/model';
|
||||||
|
|
||||||
|
function Models() {
|
||||||
|
const { assets } = useAssetsStore();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{assets.map((asset) =>
|
||||||
|
<Model key={asset.modelUuid} asset={asset} />
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Models
|
||||||
@@ -47,6 +47,7 @@ import MeasurementTool from "../scene/tools/measurementTool";
|
|||||||
import NavMesh from "../simulation/vehicle/navMesh/navMesh";
|
import NavMesh from "../simulation/vehicle/navMesh/navMesh";
|
||||||
import CalculateAreaGroup from "./groups/calculateAreaGroup";
|
import CalculateAreaGroup from "./groups/calculateAreaGroup";
|
||||||
import LayoutImage from "./layout/layoutImage";
|
import LayoutImage from "./layout/layoutImage";
|
||||||
|
import AssetsGroup from "./assetGroup/assetsGroup";
|
||||||
|
|
||||||
export default function Builder() {
|
export default function Builder() {
|
||||||
const state = useThree<Types.ThreeState>(); // Importing the state from the useThree hook, which contains the scene, camera, and other Three.js elements.
|
const state = useThree<Types.ThreeState>(); // Importing the state from the useThree hook, which contains the scene, camera, and other Three.js elements.
|
||||||
@@ -299,6 +300,8 @@ export default function Builder() {
|
|||||||
anglesnappedPoint={anglesnappedPoint}
|
anglesnappedPoint={anglesnappedPoint}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
{/* <AssetsGroup /> */}
|
||||||
|
|
||||||
<MeasurementTool />
|
<MeasurementTool />
|
||||||
|
|
||||||
<CalculateAreaGroup />
|
<CalculateAreaGroup />
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ export default async function assetManager(
|
|||||||
},
|
},
|
||||||
undefined,
|
undefined,
|
||||||
(error) => {
|
(error) => {
|
||||||
toast.error(`[IndexedDB] Error loading ${item.modelName}:`);
|
echo.error(`[IndexedDB] Error loading ${item.modelName}:`);
|
||||||
resolve();
|
resolve();
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@@ -97,7 +97,7 @@ export default async function assetManager(
|
|||||||
},
|
},
|
||||||
undefined,
|
undefined,
|
||||||
(error) => {
|
(error) => {
|
||||||
toast.error(`[Backend] Error loading ${item.modelName}:`);
|
echo.error(`[Backend] Error loading ${item.modelName}:`);
|
||||||
resolve();
|
resolve();
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ function updateDistanceText(
|
|||||||
const textMesh = text as THREE.Mesh;
|
const textMesh = text as THREE.Mesh;
|
||||||
if (textMesh.userData[0][1] === linePoints[0][1] && textMesh.userData[1][1] === linePoints[1][1]) {
|
if (textMesh.userData[0][1] === linePoints[0][1] && textMesh.userData[1][1] === linePoints[1][1]) {
|
||||||
textMesh.position.set(position.x, 1, position.z);
|
textMesh.position.set(position.x, 1, position.z);
|
||||||
const className = `Distance line-${textMesh.userData[0][1]}_${textMesh.userData[1][1]}_${linePoints[0][2]}`;
|
const className = `distance line-${textMesh.userData[0][1]}_${textMesh.userData[1][1]}_${linePoints[0][2]}`;
|
||||||
const element = document.getElementsByClassName(className)[0] as HTMLElement;
|
const element = document.getElementsByClassName(className)[0] as HTMLElement;
|
||||||
if (element) {
|
if (element) {
|
||||||
element.innerHTML = `${distance} m`;
|
element.innerHTML = `${distance} m`;
|
||||||
|
|||||||
@@ -104,7 +104,7 @@ const FloorItemsGroup = ({
|
|||||||
updateLoadingProgress(100);
|
updateLoadingProgress(100);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
gltfLoaderWorker.postMessage({ floorItems: data });
|
gltfLoaderWorker.postMessage({ floorItems: uniqueItems });
|
||||||
} else {
|
} else {
|
||||||
gltfLoaderWorker.postMessage({ floorItems: [] });
|
gltfLoaderWorker.postMessage({ floorItems: [] });
|
||||||
loadInitialFloorItems(
|
loadInitialFloorItems(
|
||||||
|
|||||||
221
app/src/store/builder/useAssetStore.ts
Normal file
221
app/src/store/builder/useAssetStore.ts
Normal file
@@ -0,0 +1,221 @@
|
|||||||
|
import { create } from 'zustand';
|
||||||
|
import { immer } from 'zustand/middleware/immer';
|
||||||
|
|
||||||
|
interface AssetsStore {
|
||||||
|
assets: Assets;
|
||||||
|
|
||||||
|
// Asset CRUD operations
|
||||||
|
addAsset: (asset: Asset) => void;
|
||||||
|
removeAsset: (modelUuid: string) => void;
|
||||||
|
updateAsset: (modelUuid: string, updates: Partial<Asset>) => void;
|
||||||
|
setAssets: (assets: Assets) => void;
|
||||||
|
|
||||||
|
// Asset properties
|
||||||
|
setPosition: (modelUuid: string, position: [number, number, number]) => void;
|
||||||
|
setRotation: (modelUuid: string, rotation: [number, number, number]) => void;
|
||||||
|
setLock: (modelUuid: string, isLocked: boolean) => void;
|
||||||
|
setCollision: (modelUuid: string, isCollidable: boolean) => void;
|
||||||
|
setVisibility: (modelUuid: string, isVisible: boolean) => void;
|
||||||
|
setOpacity: (modelUuid: string, opacity: number) => void;
|
||||||
|
|
||||||
|
// Animation controls
|
||||||
|
setAnimation: (modelUuid: string, animation: string) => void;
|
||||||
|
setCurrentAnimation: (modelUuid: string, current: string, isPlaying: boolean) => void;
|
||||||
|
addAnimation: (modelUuid: string, animation: string) => void;
|
||||||
|
removeAnimation: (modelUuid: string, animation: string) => void;
|
||||||
|
|
||||||
|
// Event data operations
|
||||||
|
addEventData: (modelUuid: string, eventData: Asset['eventData']) => void;
|
||||||
|
updateEventData: (modelUuid: string, updates: Partial<Asset['eventData']>) => void;
|
||||||
|
removeEventData: (modelUuid: string) => void;
|
||||||
|
|
||||||
|
// Helper functions
|
||||||
|
getAssetById: (modelUuid: string) => Asset | undefined;
|
||||||
|
getAssetByPointUuid: (pointUuid: string) => Asset | undefined;
|
||||||
|
hasAsset: (modelUuid: string) => boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useAssetsStore = create<AssetsStore>()(
|
||||||
|
immer((set, get) => ({
|
||||||
|
assets: [],
|
||||||
|
|
||||||
|
// Asset CRUD operations
|
||||||
|
addAsset: (asset) => {
|
||||||
|
set((state) => {
|
||||||
|
if (!state.assets.some(a => a.modelUuid === asset.modelUuid)) {
|
||||||
|
state.assets.push(asset);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
removeAsset: (modelUuid) => {
|
||||||
|
set((state) => {
|
||||||
|
state.assets = state.assets.filter(a => a.modelUuid !== modelUuid);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
updateAsset: (modelUuid, updates) => {
|
||||||
|
set((state) => {
|
||||||
|
const asset = state.assets.find(a => a.modelUuid === modelUuid);
|
||||||
|
if (asset) {
|
||||||
|
Object.assign(asset, updates);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
setAssets: (assets) => {
|
||||||
|
set((state) => {
|
||||||
|
state.assets = assets;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
// Asset properties
|
||||||
|
setPosition: (modelUuid, position) => {
|
||||||
|
set((state) => {
|
||||||
|
const asset = state.assets.find(a => a.modelUuid === modelUuid);
|
||||||
|
if (asset) {
|
||||||
|
asset.position = position;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
setRotation: (modelUuid, rotation) => {
|
||||||
|
set((state) => {
|
||||||
|
const asset = state.assets.find(a => a.modelUuid === modelUuid);
|
||||||
|
if (asset) {
|
||||||
|
asset.rotation = rotation;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
setLock: (modelUuid, isLocked) => {
|
||||||
|
set((state) => {
|
||||||
|
const asset = state.assets.find(a => a.modelUuid === modelUuid);
|
||||||
|
if (asset) {
|
||||||
|
asset.isLocked = isLocked;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
setCollision: (modelUuid, isCollidable) => {
|
||||||
|
set((state) => {
|
||||||
|
const asset = state.assets.find(a => a.modelUuid === modelUuid);
|
||||||
|
if (asset) {
|
||||||
|
asset.isCollidable = isCollidable;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
setVisibility: (modelUuid, isVisible) => {
|
||||||
|
set((state) => {
|
||||||
|
const asset = state.assets.find(a => a.modelUuid === modelUuid);
|
||||||
|
if (asset) {
|
||||||
|
asset.isVisible = isVisible;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
setOpacity: (modelUuid, opacity) => {
|
||||||
|
set((state) => {
|
||||||
|
const asset = state.assets.find(a => a.modelUuid === modelUuid);
|
||||||
|
if (asset) {
|
||||||
|
asset.opacity = opacity;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
// Animation controls
|
||||||
|
setAnimation: (modelUuid, animation) => {
|
||||||
|
set((state) => {
|
||||||
|
const asset = state.assets.find(a => a.modelUuid === modelUuid);
|
||||||
|
if (asset) {
|
||||||
|
if (!asset.animationState) {
|
||||||
|
asset.animationState = { current: animation, playing: false };
|
||||||
|
} else {
|
||||||
|
asset.animationState.current = animation;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
setCurrentAnimation: (modelUuid, current, isPlaying) => {
|
||||||
|
set((state) => {
|
||||||
|
const asset = state.assets.find(a => a.modelUuid === modelUuid);
|
||||||
|
if (asset?.animationState) {
|
||||||
|
asset.animationState.current = current;
|
||||||
|
asset.animationState.playing = isPlaying;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
addAnimation: (modelUuid, animation) => {
|
||||||
|
set((state) => {
|
||||||
|
const asset = state.assets.find(a => a.modelUuid === modelUuid);
|
||||||
|
if (asset) {
|
||||||
|
if (!asset.animations) {
|
||||||
|
asset.animations = [animation];
|
||||||
|
} else if (!asset.animations.includes(animation)) {
|
||||||
|
asset.animations.push(animation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
removeAnimation: (modelUuid, animation) => {
|
||||||
|
set((state) => {
|
||||||
|
const asset = state.assets.find(a => a.modelUuid === modelUuid);
|
||||||
|
if (asset?.animations) {
|
||||||
|
asset.animations = asset.animations.filter(a => a !== animation);
|
||||||
|
if (asset.animationState?.current === animation) {
|
||||||
|
asset.animationState.playing = false;
|
||||||
|
asset.animationState.current = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
// Event data operations
|
||||||
|
addEventData: (modelUuid, eventData) => {
|
||||||
|
set((state) => {
|
||||||
|
const asset = state.assets.find(a => a.modelUuid === modelUuid);
|
||||||
|
if (asset) {
|
||||||
|
asset.eventData = eventData;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
updateEventData: (modelUuid, updates) => {
|
||||||
|
set((state) => {
|
||||||
|
const asset = state.assets.find(a => a.modelUuid === modelUuid);
|
||||||
|
if (asset?.eventData) {
|
||||||
|
asset.eventData = { ...asset.eventData, ...updates };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
removeEventData: (modelUuid) => {
|
||||||
|
set((state) => {
|
||||||
|
const asset = state.assets.find(a => a.modelUuid === modelUuid);
|
||||||
|
if (asset) {
|
||||||
|
delete asset.eventData;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
// Helper functions
|
||||||
|
getAssetById: (modelUuid) => {
|
||||||
|
return get().assets.find(a => a.modelUuid === modelUuid);
|
||||||
|
},
|
||||||
|
|
||||||
|
getAssetByPointUuid: (pointUuid) => {
|
||||||
|
return get().assets.find(asset =>
|
||||||
|
asset.eventData?.point?.uuid === pointUuid ||
|
||||||
|
asset.eventData?.points?.some(p => p.uuid === pointUuid)
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
hasAsset: (modelUuid) => {
|
||||||
|
return get().assets.some(a => a.modelUuid === modelUuid);
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
);
|
||||||
31
app/src/types/builderTypes.d.ts
vendored
Normal file
31
app/src/types/builderTypes.d.ts
vendored
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
interface Asset {
|
||||||
|
modelUuid: string;
|
||||||
|
modelName: string;
|
||||||
|
assetId: string;
|
||||||
|
position: [number, number, number];
|
||||||
|
rotation: [number, number, number];
|
||||||
|
isLocked: boolean;
|
||||||
|
isCollidable: boolean;
|
||||||
|
isVisible: boolean;
|
||||||
|
opacity: number;
|
||||||
|
animations?: string[];
|
||||||
|
animationState?: {
|
||||||
|
current: string;
|
||||||
|
playing: boolean;
|
||||||
|
};
|
||||||
|
eventData?: {
|
||||||
|
type: string;
|
||||||
|
point?: {
|
||||||
|
uuid: string;
|
||||||
|
position: [number, number, number];
|
||||||
|
rotation: [number, number, number];
|
||||||
|
}
|
||||||
|
points?: {
|
||||||
|
uuid: string;
|
||||||
|
position: [number, number, number];
|
||||||
|
rotation: [number, number, number];
|
||||||
|
}[];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
type Assets = Asset[];
|
||||||
Reference in New Issue
Block a user