Merge remote-tracking branch 'origin/feature/layout-comparison-version' into main-dev
This commit is contained in:
@@ -1,243 +0,0 @@
|
||||
import * as THREE from "three";
|
||||
import { useEffect, useMemo, useRef, useCallback } from "react";
|
||||
import { useThree } from "@react-three/fiber";
|
||||
import { useHeatMapStore } from "../../../store/simulation/useHeatMapStore";
|
||||
import * as CONSTANTS from "../../../types/world/worldConstants";
|
||||
import { usePlayButtonStore } from "../../../store/ui/usePlayButtonStore";
|
||||
import { useSceneContext } from "../../../modules/scene/sceneContext";
|
||||
|
||||
const RADIUS = 0.0025;
|
||||
const OPACITY = 0.8;
|
||||
const GROWTH_RATE = 20.0;
|
||||
|
||||
const BakedHeatMap = () => {
|
||||
const { materialStore } = useSceneContext();
|
||||
const { bakedPoints } = useHeatMapStore();
|
||||
const materialRef = useRef<THREE.ShaderMaterial>(null);
|
||||
const meshRef = useRef<THREE.Mesh>(null);
|
||||
const { gl } = useThree();
|
||||
const { isPlaying } = usePlayButtonStore();
|
||||
const height = CONSTANTS.gridConfig.size;
|
||||
const width = CONSTANTS.gridConfig.size;
|
||||
const { materialHistory, materials } = materialStore();
|
||||
|
||||
const createPointTexture = useCallback(
|
||||
(filteredPoints: typeof bakedPoints) => {
|
||||
if (filteredPoints.length === 0) return null;
|
||||
|
||||
const data = new Float32Array(filteredPoints.length * 4);
|
||||
filteredPoints.forEach((p, i) => {
|
||||
const index = i * 4;
|
||||
data[index] = (p.points.x + width / 2) / width;
|
||||
data[index + 1] = (p.points.y + height / 2) / height;
|
||||
data[index + 2] = 0.3;
|
||||
data[index + 3] = 0.0;
|
||||
});
|
||||
|
||||
const texture = new THREE.DataTexture(
|
||||
data,
|
||||
filteredPoints.length,
|
||||
1,
|
||||
THREE.RGBAFormat,
|
||||
THREE.FloatType
|
||||
);
|
||||
texture.needsUpdate = true;
|
||||
return texture;
|
||||
},
|
||||
[width, height]
|
||||
);
|
||||
|
||||
const uniformsRef = useRef({
|
||||
u_points: { value: null as THREE.DataTexture | null },
|
||||
u_count: { value: 0 },
|
||||
u_radius: { value: RADIUS },
|
||||
u_opacity: { value: OPACITY },
|
||||
u_growthRate: { value: GROWTH_RATE },
|
||||
});
|
||||
|
||||
const renderHeatmapToImage = useCallback(
|
||||
(type: string) => {
|
||||
if (!meshRef.current) return null;
|
||||
|
||||
const filteredPoints = bakedPoints.filter((p) => p.type === type);
|
||||
if (filteredPoints.length === 0) return null;
|
||||
|
||||
const pointTexture = createPointTexture(filteredPoints);
|
||||
if (!pointTexture) return null;
|
||||
|
||||
uniformsRef.current.u_points.value = pointTexture;
|
||||
uniformsRef.current.u_count.value = filteredPoints.length;
|
||||
|
||||
const exportCamera = new THREE.OrthographicCamera(
|
||||
width / -2,
|
||||
width / 2,
|
||||
height / 2,
|
||||
height / -2,
|
||||
0.1,
|
||||
10
|
||||
);
|
||||
exportCamera.position.set(0, 1, 0);
|
||||
exportCamera.lookAt(0, 0, 0);
|
||||
|
||||
const renderTarget = new THREE.WebGLRenderTarget(1024, 1024, {
|
||||
format: THREE.RGBAFormat,
|
||||
type: THREE.UnsignedByteType,
|
||||
});
|
||||
|
||||
const tempScene = new THREE.Scene();
|
||||
tempScene.add(meshRef.current);
|
||||
|
||||
gl.setRenderTarget(renderTarget);
|
||||
gl.render(tempScene, exportCamera);
|
||||
gl.setRenderTarget(null);
|
||||
|
||||
const pixels = new Uint8Array(1024 * 1024 * 4);
|
||||
gl.readRenderTargetPixels(renderTarget, 0, 0, 1024, 1024, pixels);
|
||||
|
||||
const canvas = document.createElement("canvas");
|
||||
canvas.width = 1024;
|
||||
canvas.height = 1024;
|
||||
const ctx = canvas.getContext("2d");
|
||||
|
||||
if (ctx) {
|
||||
const imageData = ctx.createImageData(1024, 1024);
|
||||
imageData.data.set(pixels);
|
||||
ctx.putImageData(imageData, 0, 0);
|
||||
return canvas.toDataURL("image/png");
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
[gl, width, height, bakedPoints, createPointTexture]
|
||||
);
|
||||
|
||||
// const downloadImage = (base64: string, filename: string) => {
|
||||
// const link = document.createElement("a");
|
||||
// link.href = base64;
|
||||
// link.download = filename;
|
||||
// document.body.appendChild(link);
|
||||
// link.click();
|
||||
// document.body.removeChild(link);
|
||||
// };
|
||||
|
||||
const exportHeatmapAsPNG = useCallback(() => {
|
||||
const types = ["human", "vehicle"];
|
||||
|
||||
const result = types.map((type) => {
|
||||
const image = renderHeatmapToImage(type);
|
||||
// console.log("image: ", type, image);
|
||||
if (image) {
|
||||
// downloadImage(image, `${type}-heatmap.png`);
|
||||
}
|
||||
return { type, image };
|
||||
});
|
||||
|
||||
// console.log("Exported Heatmaps:", result);
|
||||
return result;
|
||||
}, [renderHeatmapToImage]);
|
||||
|
||||
useEffect(() => {
|
||||
if (materials.length === 0 && materialHistory.length >= 0) {
|
||||
// console.log("SimulationCompleted");
|
||||
const getImage = exportHeatmapAsPNG();
|
||||
// console.log("getImage: ", getImage);
|
||||
}
|
||||
}, [isPlaying, materials, materialHistory]);
|
||||
|
||||
const pointTexture = useMemo(
|
||||
() => createPointTexture(bakedPoints),
|
||||
[bakedPoints, createPointTexture]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
uniformsRef.current.u_points.value = pointTexture;
|
||||
uniformsRef.current.u_count.value = bakedPoints.length;
|
||||
}, [pointTexture, bakedPoints.length]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<mesh ref={meshRef} rotation={[Math.PI / 2, 0, 0]} position={[0, 0.025, 0]}>
|
||||
<planeGeometry args={[width, height]} />
|
||||
<shaderMaterial
|
||||
ref={materialRef}
|
||||
transparent
|
||||
depthWrite={false}
|
||||
blending={THREE.AdditiveBlending}
|
||||
uniforms={uniformsRef.current}
|
||||
side={THREE.DoubleSide}
|
||||
vertexShader={`
|
||||
varying vec2 vUv;
|
||||
void main() {
|
||||
vUv = uv;
|
||||
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
|
||||
}
|
||||
`}
|
||||
fragmentShader={`
|
||||
uniform sampler2D u_points;
|
||||
precision highp float;
|
||||
uniform int u_count;
|
||||
uniform float u_radius;
|
||||
uniform float u_opacity;
|
||||
uniform float u_growthRate;
|
||||
varying vec2 vUv;
|
||||
|
||||
float gauss(float dist, float radius) {
|
||||
return exp(-pow(dist / radius, 2.0));
|
||||
}
|
||||
|
||||
void main() {
|
||||
float intensity = 0.0;
|
||||
|
||||
for (int i = 0; i < 10000; i++) {
|
||||
if (i >= u_count) break;
|
||||
float fi = float(i) + 0.5;
|
||||
float u = fi / float(u_count);
|
||||
|
||||
vec4 point = texture2D(u_points, vec2(u, 0.5));
|
||||
vec2 pos = point.rg;
|
||||
float strength = point.b;
|
||||
|
||||
float d = distance(vUv, pos);
|
||||
intensity += strength * gauss(d, u_radius);
|
||||
}
|
||||
|
||||
float normalized = clamp(intensity / max(u_growthRate, 0.0001), 0.0, 1.0);
|
||||
|
||||
vec3 color = vec3(0.0);
|
||||
if (normalized < 0.33) {
|
||||
color = mix(vec3(0.0, 0.0, 1.0), vec3(0.0, 1.0, 0.0), normalized / 0.33);
|
||||
} else if (normalized < 0.66) {
|
||||
color = mix(vec3(0.0, 1.0, 0.0), vec3(1.0, 1.0, 0.0), (normalized - 0.33) / 0.33);
|
||||
} else {
|
||||
color = mix(vec3(1.0, 1.0, 0.0), vec3(1.0, 0.0, 0.0), (normalized - 0.66) / 0.34);
|
||||
}
|
||||
|
||||
gl_FragColor = vec4(color, normalized * u_opacity);
|
||||
}
|
||||
`}
|
||||
/>
|
||||
</mesh>
|
||||
|
||||
{/* <Html>
|
||||
<button
|
||||
style={{
|
||||
position: "absolute",
|
||||
top: "10px",
|
||||
left: "10px",
|
||||
padding: "8px 12px",
|
||||
background: "#333",
|
||||
color: "white",
|
||||
border: "none",
|
||||
cursor: "pointer",
|
||||
zIndex: 10,
|
||||
}}
|
||||
onClick={exportHeatmapAsPNG}
|
||||
>
|
||||
Export Heatmap
|
||||
</button>
|
||||
</Html> */}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default BakedHeatMap;
|
||||
@@ -1,14 +1,11 @@
|
||||
import RealTimeHeatMap from "./realTime/realTimeHeatMap";
|
||||
import BakedHeatMap from "./baked/bakedHeatMap";
|
||||
import { useIsComparing } from "../../store/builder/store";
|
||||
import HeatMapRenderer from "./heatMapRenderer";
|
||||
|
||||
function HeatMap() {
|
||||
return (
|
||||
<>
|
||||
<RealTimeHeatMap />
|
||||
const { isComparing } = useIsComparing();
|
||||
|
||||
<BakedHeatMap />
|
||||
</>
|
||||
);
|
||||
return <>{isComparing ? <HeatMapRenderer /> : <RealTimeHeatMap />}</>;
|
||||
}
|
||||
|
||||
export default HeatMap;
|
||||
|
||||
68
app/src/components/heatMapGenerator/heatMapRenderer.tsx
Normal file
68
app/src/components/heatMapGenerator/heatMapRenderer.tsx
Normal file
@@ -0,0 +1,68 @@
|
||||
import React, { useMemo } from "react";
|
||||
import { useSceneContext } from "../../modules/scene/sceneContext";
|
||||
import { useSimulationManager } from "../../store/rough/useSimulationManagerStore";
|
||||
import { useSimulationState } from "../../store/simulation/useSimulationStore";
|
||||
import HeatmapPreview from "./heatmapPreview";
|
||||
|
||||
const HeatMapRenderer = () => {
|
||||
const { versionStore, layout } = useSceneContext();
|
||||
const { selectedVersion } = versionStore();
|
||||
const { simulationRecords } = useSimulationManager();
|
||||
const { mainScene, comparisonScene } = useSimulationState();
|
||||
|
||||
const { mainSceneHeatmaps, comparisonSceneHeatmaps } = useMemo(() => {
|
||||
const getHeatmaps = (scene: any) => {
|
||||
const heatmaps: Array<{ image: string | Blob; type: string }> = [];
|
||||
if (!scene) return heatmaps;
|
||||
|
||||
simulationRecords.forEach((project) =>
|
||||
project.versions.forEach((version) =>
|
||||
version.products.forEach((product) =>
|
||||
product.simulateData.forEach((simulateDataItem) => {
|
||||
const isTargetScene =
|
||||
product.productId === scene.product.productUuid &&
|
||||
version.versionId === scene.version.versionUuid &&
|
||||
selectedVersion?.versionId;
|
||||
if (!isTargetScene) return;
|
||||
|
||||
product.heatMaps?.forEach((heatMap) => {
|
||||
if (heatMap.type !== simulateDataItem.type) return;
|
||||
const img = heatMap.image;
|
||||
|
||||
if (typeof img === "string") {
|
||||
if (/\.(png|jpg)$/i.test(img)) {
|
||||
heatmaps.push({ image: img, type: heatMap.type });
|
||||
}
|
||||
} else if (img instanceof Blob) {
|
||||
heatmaps.push({ image: img, type: heatMap.type });
|
||||
}
|
||||
});
|
||||
})
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
return heatmaps;
|
||||
};
|
||||
|
||||
return {
|
||||
mainSceneHeatmaps: getHeatmaps(mainScene),
|
||||
comparisonSceneHeatmaps: getHeatmaps(comparisonScene),
|
||||
};
|
||||
}, [simulationRecords, mainScene, comparisonScene, selectedVersion]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{layout === "Main Layout" &&
|
||||
mainSceneHeatmaps.map((heatMap, idx) => (
|
||||
<HeatmapPreview key={`main-${idx}`} image={heatMap.image} type={heatMap.type} />
|
||||
))}
|
||||
{layout === "Comparison Layout" &&
|
||||
comparisonSceneHeatmaps.map((heatMap, idx) => (
|
||||
<HeatmapPreview key={`comp-${idx}`} image={heatMap.image} type={heatMap.type} />
|
||||
))}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default HeatMapRenderer;
|
||||
@@ -1,6 +1,7 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import { TextureLoader, Texture, DoubleSide } from "three";
|
||||
import * as CONSTANTS from "../../types/world/worldConstants";
|
||||
import { useHeatmapTypeStore } from "../../store/builder/store";
|
||||
|
||||
interface HeatmapPreviewProps {
|
||||
image: string | Blob | null;
|
||||
@@ -9,6 +10,7 @@ interface HeatmapPreviewProps {
|
||||
|
||||
const HeatmapPreview: React.FC<HeatmapPreviewProps> = ({ image, type }) => {
|
||||
const [texture, setTexture] = useState<Texture | null>(null);
|
||||
const { selectedTypes } = useHeatmapTypeStore();
|
||||
|
||||
const width = CONSTANTS.gridConfig.size;
|
||||
const height = CONSTANTS.gridConfig.size;
|
||||
@@ -64,16 +66,16 @@ const HeatmapPreview: React.FC<HeatmapPreviewProps> = ({ image, type }) => {
|
||||
|
||||
return (
|
||||
<>
|
||||
{type === "human" && (
|
||||
{type === "human" && selectedTypes.human && (
|
||||
<mesh rotation={[Math.PI / 2, 0, 0]} position={[0, 0.035, 0]}>
|
||||
<planeGeometry args={[width, height]} />
|
||||
<meshStandardMaterial map={texture} transparent side={DoubleSide} />
|
||||
<meshStandardMaterial map={texture.clone()} transparent side={DoubleSide} />
|
||||
</mesh>
|
||||
)}
|
||||
{type === "vehicle" && (
|
||||
{type === "vehicle" && selectedTypes.vehicle && (
|
||||
<mesh rotation={[Math.PI / 2, 0, 0]} position={[0, 0.025, 0]}>
|
||||
<planeGeometry args={[width, height]} />
|
||||
<meshStandardMaterial map={texture} transparent side={DoubleSide} />
|
||||
<meshStandardMaterial map={texture.clone()} transparent side={DoubleSide} />
|
||||
</mesh>
|
||||
)}
|
||||
</>
|
||||
|
||||
@@ -33,16 +33,8 @@ const RealTimeHeatMap = () => {
|
||||
const debugModeMap = { solid: 0, grayscale: 1, normal: 2 } as const;
|
||||
const { productStore } = useSceneContext();
|
||||
const { getProductById, products, selectedProduct } = productStore();
|
||||
const {
|
||||
bakedPoints,
|
||||
hasHuman,
|
||||
hasVehicle,
|
||||
monitoringHuman,
|
||||
monitoringVehicle,
|
||||
addMonitoringHuman,
|
||||
addMonitoringVehicle,
|
||||
addBakedPoint,
|
||||
} = useHeatMapStore();
|
||||
const { monitoringHuman, monitoringVehicle, clearBakedPoints, addBakedPoint } =
|
||||
useHeatMapStore();
|
||||
const { isPlaying } = usePlayButtonStore();
|
||||
const { isReset } = useResetButtonStore();
|
||||
const { isPaused } = usePauseButtonStore();
|
||||
@@ -65,13 +57,14 @@ const RealTimeHeatMap = () => {
|
||||
u_growthRate: { value: GROWTH_TIME_MULTIPLIER },
|
||||
});
|
||||
|
||||
// useEffect(() => {
|
||||
// if (isReset || !isPlaying) {
|
||||
// setPoints([]);
|
||||
// lastFrameTime.current = null;
|
||||
// lastUpdateTime.current = 0;
|
||||
// }
|
||||
// }, [isReset, isPlaying]);
|
||||
useEffect(() => {
|
||||
if (isReset || !isPlaying) {
|
||||
clearBakedPoints();
|
||||
setPoints([]);
|
||||
lastFrameTime.current = null;
|
||||
lastUpdateTime.current = 0;
|
||||
}
|
||||
}, [isReset, isPlaying]);
|
||||
|
||||
// Added human or vehicle
|
||||
// useEffect(() => {
|
||||
|
||||
@@ -3,6 +3,8 @@ import {
|
||||
useLoadingProgress,
|
||||
useIsComparing,
|
||||
useCreateNewWindow,
|
||||
useLimitDistance,
|
||||
useRenderDistance,
|
||||
} from "../../../store/builder/store";
|
||||
import { useSimulationState } from "../../../store/simulation/useSimulationStore";
|
||||
import { usePlayButtonStore } from "../../../store/ui/usePlayButtonStore";
|
||||
@@ -17,7 +19,8 @@ import { useParams } from "react-router-dom";
|
||||
import { validateSimulationDataApi } from "../../../services/simulation/comparison/validateSimulationDataApi";
|
||||
import { calculateSimulationData } from "./functions/calculateSimulationData";
|
||||
import NewWindowScene from "../../ui/compareVersion/NewWindowScene";
|
||||
import Button from "../../ui/compareVersion/Button";
|
||||
import ComparisonToolbar from "../../ui/compareVersion/ComparisonToolbar";
|
||||
import { findEnvironment } from "../../../services/factoryBuilder/environment/findEnvironment";
|
||||
type AssetData = {
|
||||
activeTime: number;
|
||||
idleTime: number;
|
||||
@@ -56,7 +59,7 @@ function ComparisonScene() {
|
||||
const { productStore, versionStore } = useSceneContext();
|
||||
const { versionHistory, selectedVersion, setSelectedVersion } = versionStore();
|
||||
const { products, selectedProduct } = productStore();
|
||||
const { isComparing } = useIsComparing();
|
||||
const { isComparing, setIsComparing } = useIsComparing();
|
||||
const { activeModule } = useModuleStore();
|
||||
const { mainScene, comparisonScene, setComparisonState } = useSimulationState();
|
||||
const { loadingProgress } = useLoadingProgress();
|
||||
@@ -65,8 +68,10 @@ function ComparisonScene() {
|
||||
const { setCompareProductsData } = useCompareProductDataStore();
|
||||
const [shouldShowComparisonResult, setShouldShowComparisonResult] = useState(false);
|
||||
const { addSimulationRecord } = useSimulationManager();
|
||||
const { createNewWindow } = useCreateNewWindow();
|
||||
|
||||
const { createNewWindow, setCreateNewWindow } = useCreateNewWindow();
|
||||
const { clearComparisonState } = useSimulationState();
|
||||
const { setRenderDistance } = useRenderDistance();
|
||||
const { setLimitDistance } = useLimitDistance();
|
||||
const handleSelectVersion = (option: string) => {
|
||||
const version = versionHistory.find((version) => version.versionName === option);
|
||||
if (version) {
|
||||
@@ -178,6 +183,17 @@ function ComparisonScene() {
|
||||
simulationRecords,
|
||||
]);
|
||||
|
||||
const handleExit = () => {
|
||||
setIsComparing(false);
|
||||
setCreateNewWindow(false);
|
||||
clearComparisonState();
|
||||
if (!projectId) return;
|
||||
findEnvironment(projectId).then((data) => {
|
||||
if (!data) return;
|
||||
setRenderDistance(data.renderDistance);
|
||||
setLimitDistance(data.limitDistance);
|
||||
});
|
||||
};
|
||||
return (
|
||||
<>
|
||||
{isComparing && activeModule === "simulation" && selectedProduct && (
|
||||
@@ -208,9 +224,10 @@ function ComparisonScene() {
|
||||
zIndex: 10,
|
||||
}}
|
||||
>
|
||||
<Button />
|
||||
<ComparisonToolbar />
|
||||
</div>
|
||||
)}
|
||||
|
||||
<CompareLayOut />
|
||||
|
||||
{createNewWindow && <NewWindowScene />}
|
||||
|
||||
29
app/src/components/layout/scenes/functions/handleExit.ts
Normal file
29
app/src/components/layout/scenes/functions/handleExit.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import { findEnvironment } from "../../../../services/factoryBuilder/environment/findEnvironment";
|
||||
import {
|
||||
useCreateNewWindow,
|
||||
useIsComparing,
|
||||
useLimitDistance,
|
||||
useRenderDistance,
|
||||
} from "../../../../store/builder/store";
|
||||
import { useSimulationState } from "../../../../store/simulation/useSimulationStore";
|
||||
|
||||
export function handleExit(projectId?: string) {
|
||||
const setIsComparing = useIsComparing.getState().setIsComparing;
|
||||
const setCreateNewWindow = useCreateNewWindow.getState().setCreateNewWindow;
|
||||
const setRenderDistance = useRenderDistance.getState().setRenderDistance;
|
||||
const setLimitDistance = useLimitDistance.getState().setLimitDistance;
|
||||
const clearComparisonState = useSimulationState.getState().clearComparisonState;
|
||||
// Reset UI comparison state
|
||||
setIsComparing(false);
|
||||
setCreateNewWindow(false);
|
||||
clearComparisonState();
|
||||
|
||||
if (!projectId) return;
|
||||
|
||||
// 🔹 Call async part separately (no async/await in this function)
|
||||
findEnvironment(projectId).then((data) => {
|
||||
if (!data) return;
|
||||
setRenderDistance(data.renderDistance);
|
||||
setLimitDistance(data.limitDistance);
|
||||
});
|
||||
}
|
||||
@@ -217,16 +217,6 @@ export const RenderInNewWindow: React.FC<NewWindowProps> = ({
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const handleResize = () => {
|
||||
childWindowRef.current?.dispatchEvent(new Event("resize"));
|
||||
};
|
||||
childWindowRef.current?.addEventListener("resize", handleResize);
|
||||
return () => {
|
||||
childWindowRef.current?.removeEventListener("resize", handleResize);
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const w = childWindowRef.current;
|
||||
if (w && !w.closed) {
|
||||
|
||||
@@ -19,6 +19,7 @@ import useRestStates from "../../../hooks/useResetStates";
|
||||
import { getVersionHistoryApi } from "../../../services/builder/versionControl/getVersionHistoryApi";
|
||||
import { validateSimulationDataApi } from "../../../services/simulation/comparison/validateSimulationDataApi";
|
||||
import { useSimulationManager } from "../../../store/rough/useSimulationManagerStore";
|
||||
import { handleExit } from "../../layout/scenes/functions/handleExit";
|
||||
|
||||
const CompareLayOut = () => {
|
||||
const { clearComparisonState, comparisonScene, setComparisonState } = useSimulationState();
|
||||
@@ -46,9 +47,7 @@ const CompareLayOut = () => {
|
||||
const { limitDistance, setLimitDistance } = useLimitDistance();
|
||||
const { simulationRecords } = useSimulationManager();
|
||||
|
||||
useEffect(() => {
|
||||
// console.log("simulationRecords: ", simulationRecords);
|
||||
}, [simulationRecords]);
|
||||
useEffect(() => {}, [simulationRecords]);
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
resetStates();
|
||||
@@ -225,7 +224,7 @@ const CompareLayOut = () => {
|
||||
</Suspense>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<button onClick={() => handleExit(projectId)}>Exit</button>
|
||||
{width !== "0px" &&
|
||||
!selectedVersion?.versionId && ( // Show only if no layout selected
|
||||
<div className="chooseLayout-wrapper">
|
||||
|
||||
@@ -1,35 +1,16 @@
|
||||
import React, { useState } from "react";
|
||||
import { useParams } from "react-router-dom";
|
||||
import {
|
||||
useCreateNewWindow,
|
||||
useHeatmapTypeStore,
|
||||
useIsComparing,
|
||||
useLimitDistance,
|
||||
useRenderDistance,
|
||||
} from "../../../store/builder/store";
|
||||
import { useSimulationState } from "../../../store/simulation/useSimulationStore";
|
||||
import { useParams } from "react-router-dom";
|
||||
import { findEnvironment } from "../../../services/builder/environment/findEnvironment";
|
||||
import { handleExit } from "../../layout/scenes/functions/handleExit";
|
||||
|
||||
const Button = () => {
|
||||
const { isComparing, setIsComparing } = useIsComparing();
|
||||
const ComparisonToolbar = () => {
|
||||
const { isComparing } = useIsComparing();
|
||||
const { createNewWindow, setCreateNewWindow } = useCreateNewWindow();
|
||||
const { clearComparisonState } = useSimulationState();
|
||||
const { projectId } = useParams();
|
||||
const { setRenderDistance } = useRenderDistance();
|
||||
const { setLimitDistance } = useLimitDistance();
|
||||
const { selectedTypes, toggleType } = useHeatmapTypeStore();
|
||||
|
||||
const handleExit = () => {
|
||||
setIsComparing(false);
|
||||
setCreateNewWindow(false);
|
||||
clearComparisonState();
|
||||
if (!projectId) return;
|
||||
findEnvironment(projectId).then((data) => {
|
||||
if (!data) return;
|
||||
setRenderDistance(data.renderDistance);
|
||||
setLimitDistance(data.limitDistance);
|
||||
});
|
||||
};
|
||||
const { projectId } = useParams();
|
||||
|
||||
const handleOpenInNewWindow = () => {
|
||||
setCreateNewWindow(true);
|
||||
@@ -46,7 +27,7 @@ const Button = () => {
|
||||
gap: "10px",
|
||||
}}
|
||||
>
|
||||
{isComparing && <button onClick={handleExit}>Exit</button>}
|
||||
{isComparing && <button onClick={() => handleExit(projectId)}>Exit</button>}
|
||||
{isComparing && !createNewWindow && (
|
||||
<button onClick={handleOpenInNewWindow}>Open in New Window</button>
|
||||
)}
|
||||
@@ -57,7 +38,7 @@ const Button = () => {
|
||||
checked={selectedTypes.vehicle}
|
||||
onChange={() => toggleType("vehicle")}
|
||||
/>
|
||||
Vehicle
|
||||
<p>Vehicle</p>
|
||||
</label>
|
||||
|
||||
<label>
|
||||
@@ -66,10 +47,10 @@ const Button = () => {
|
||||
checked={selectedTypes.human}
|
||||
onChange={() => toggleType("human")}
|
||||
/>
|
||||
Human
|
||||
<p>Human</p>
|
||||
</label>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Button;
|
||||
export default ComparisonToolbar;
|
||||
@@ -1,10 +1,10 @@
|
||||
import { Suspense } from "react";
|
||||
import { useCreateNewWindow, useLoadingProgress } from "../../../store/builder/store";
|
||||
import { RenderInNewWindow } from "../../templates/CreateNewWindow";
|
||||
import { useSceneContext } from "../../../modules/scene/sceneContext";
|
||||
import { useCreateNewWindow, useLoadingProgress } from "../../../store/builder/store";
|
||||
import Scene from "../../../modules/scene/scene";
|
||||
import ComparisonResult from "./ComparisonResult";
|
||||
import Button from "./Button";
|
||||
import ComparisonToolbar from "./ComparisonToolbar";
|
||||
import LoadingPage from "../../templates/LoadingPage";
|
||||
|
||||
const NewWindowScene = () => {
|
||||
@@ -22,7 +22,6 @@ const NewWindowScene = () => {
|
||||
title="3D Viewer"
|
||||
onClose={() => setCreateNewWindow(false)}
|
||||
>
|
||||
{/* Wait a tick to access child window */}
|
||||
<Scene layout="Comparison Layout" />
|
||||
{loadingProgress > 0 && (
|
||||
<LoadingPage
|
||||
@@ -39,7 +38,7 @@ const NewWindowScene = () => {
|
||||
zIndex: 1000,
|
||||
}}
|
||||
>
|
||||
<Button />
|
||||
<ComparisonToolbar />
|
||||
</div>
|
||||
{!loadingProgress && <ComparisonResult />}
|
||||
</RenderInNewWindow>
|
||||
|
||||
@@ -1,29 +1,23 @@
|
||||
import { useEffect, useMemo } from "react";
|
||||
import { Canvas } from "@react-three/fiber";
|
||||
import { useParams } from "react-router-dom";
|
||||
import { KeyboardControls } from "@react-three/drei";
|
||||
|
||||
import { useSceneContext } from "./sceneContext";
|
||||
import useModuleStore from "../../store/ui/useModuleStore";
|
||||
import { useCreateNewWindow, useLoadingProgress } from "../../store/builder/store";
|
||||
import { useSocketStore } from "../../store/socket/useSocketStore";
|
||||
|
||||
import Builder from "../builder/builder";
|
||||
import Visualization from "../visualization/visualization";
|
||||
import Setup from "./setup/setup";
|
||||
import Simulation from "../simulation/simulation";
|
||||
import Collaboration from "../collaboration/collaboration";
|
||||
import useModuleStore from "../../store/ui/useModuleStore";
|
||||
import { useParams } from "react-router-dom";
|
||||
|
||||
import { getUserData } from "../../functions/getUserData";
|
||||
import {
|
||||
useCreateNewWindow,
|
||||
useHeatmapTypeStore,
|
||||
useIsComparing,
|
||||
useLoadingProgress,
|
||||
} from "../../store/builder/store";
|
||||
import { useSocketStore } from "../../store/socket/useSocketStore";
|
||||
import { Color, SRGBColorSpace } from "three";
|
||||
import { compressImage } from "../../utils/compressImage";
|
||||
import { ALPHA_ORG } from "../../pages/Dashboard";
|
||||
import HeatmapPreview from "../../components/heatMapGenerator/heatmapPreview";
|
||||
import { useSimulationManager } from "../../store/rough/useSimulationManagerStore";
|
||||
import { useSimulationState } from "../../store/simulation/useSimulationStore";
|
||||
|
||||
export default function Scene({
|
||||
layout,
|
||||
@@ -40,7 +34,7 @@ export default function Scene({
|
||||
],
|
||||
[]
|
||||
);
|
||||
const { assetStore, layoutType, versionStore } = useSceneContext();
|
||||
const { assetStore, layoutType } = useSceneContext();
|
||||
const { assets } = assetStore();
|
||||
const { userId, organization } = getUserData();
|
||||
const { projectId } = useParams();
|
||||
@@ -48,11 +42,7 @@ export default function Scene({
|
||||
const { activeModule } = useModuleStore();
|
||||
const { loadingProgress } = useLoadingProgress();
|
||||
const { setWindowRendered } = useCreateNewWindow();
|
||||
const { simulationRecords } = useSimulationManager();
|
||||
const { isComparing } = useIsComparing();
|
||||
const { mainScene, comparisonScene } = useSimulationState();
|
||||
const { selectedVersion } = versionStore();
|
||||
const { selectedTypes } = useHeatmapTypeStore();
|
||||
|
||||
useEffect(() => {
|
||||
if (!projectId || loadingProgress !== 0) return;
|
||||
const canvas = document.getElementById("sceneCanvas")?.getElementsByTagName("canvas")[0];
|
||||
@@ -74,83 +64,7 @@ export default function Scene({
|
||||
});
|
||||
// eslint-disable-next-line
|
||||
}, [activeModule, assets, loadingProgress, projectId, layoutType]);
|
||||
// Prepare heatmaps per scene
|
||||
|
||||
const mainSceneHeatmaps = useMemo(() => {
|
||||
const heatmaps: Array<{ image: string | Blob; type: string }> = [];
|
||||
simulationRecords.forEach((project) =>
|
||||
project.versions.forEach((version) =>
|
||||
version.products.forEach((product) =>
|
||||
product.simulateData.forEach((simulateDataItem) => {
|
||||
const isMainScene =
|
||||
product.productId === mainScene.product.productUuid &&
|
||||
version.versionId === mainScene.version.versionUuid &&
|
||||
selectedVersion?.versionId;
|
||||
if (!isMainScene) return;
|
||||
|
||||
product.heatMaps?.forEach((heatMap) => {
|
||||
if (heatMap.type !== simulateDataItem.type) return;
|
||||
|
||||
// Filter by selected types
|
||||
if (
|
||||
(heatMap.type === "vehicle" && !selectedTypes.vehicle) ||
|
||||
(heatMap.type === "human" && !selectedTypes.human)
|
||||
)
|
||||
return;
|
||||
|
||||
const img = heatMap.image;
|
||||
if (typeof img === "string") {
|
||||
if (/\.(png|jpg)$/i.test(img)) {
|
||||
heatmaps.push({ image: img, type: heatMap.type });
|
||||
}
|
||||
} else if (img instanceof Blob) {
|
||||
heatmaps.push({ image: img, type: heatMap.type });
|
||||
}
|
||||
});
|
||||
})
|
||||
)
|
||||
)
|
||||
);
|
||||
return heatmaps;
|
||||
}, [simulationRecords, mainScene, selectedVersion, selectedTypes]);
|
||||
|
||||
const comparisonSceneHeatmaps = useMemo(() => {
|
||||
const heatmaps: Array<{ image: string | Blob; type: string }> = [];
|
||||
simulationRecords.forEach((project) =>
|
||||
project.versions.forEach((version) =>
|
||||
version.products.forEach((product) =>
|
||||
product.simulateData.forEach((simulateDataItem) => {
|
||||
const isComparisonScene =
|
||||
product.productId === comparisonScene?.product.productUuid &&
|
||||
version.versionId === comparisonScene?.version.versionUuid &&
|
||||
selectedVersion?.versionId;
|
||||
if (!isComparisonScene) return;
|
||||
|
||||
product.heatMaps?.forEach((heatMap) => {
|
||||
if (heatMap.type !== simulateDataItem.type) return;
|
||||
|
||||
// Filter by selected types
|
||||
if (
|
||||
(heatMap.type === "vehicle" && !selectedTypes.vehicle) ||
|
||||
(heatMap.type === "human" && !selectedTypes.human)
|
||||
)
|
||||
return;
|
||||
|
||||
const img = heatMap.image;
|
||||
if (typeof img === "string") {
|
||||
if (/\.(png|jpg)$/i.test(img)) {
|
||||
heatmaps.push({ image: img, type: heatMap.type });
|
||||
}
|
||||
} else if (img instanceof Blob) {
|
||||
heatmaps.push({ image: img, type: heatMap.type });
|
||||
}
|
||||
});
|
||||
})
|
||||
)
|
||||
)
|
||||
);
|
||||
return heatmaps;
|
||||
}, [simulationRecords, comparisonScene, selectedVersion, selectedTypes]);
|
||||
return (
|
||||
<KeyboardControls map={map}>
|
||||
<Canvas
|
||||
@@ -180,27 +94,6 @@ export default function Scene({
|
||||
<Setup />
|
||||
<Collaboration />
|
||||
<Builder />
|
||||
{isComparing && (selectedTypes.vehicle || selectedTypes.human) && (
|
||||
<>
|
||||
{layout === "Main Layout" &&
|
||||
mainSceneHeatmaps.map((heatMap, idx) => (
|
||||
<HeatmapPreview
|
||||
key={`main-${idx}`}
|
||||
image={heatMap.image}
|
||||
type={heatMap.type}
|
||||
/>
|
||||
))}
|
||||
{layout === "Comparison Layout" &&
|
||||
comparisonSceneHeatmaps.map((heatMap, idx) => (
|
||||
<HeatmapPreview
|
||||
key={`comp-${idx}`}
|
||||
image={heatMap.image}
|
||||
type={heatMap.type}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
|
||||
<Simulation />
|
||||
<Visualization />
|
||||
</Canvas>
|
||||
|
||||
@@ -15,14 +15,12 @@ import useModuleStore from "../../store/ui/useModuleStore";
|
||||
import SimulationAnalysis from "./analysis/simulationAnalysis";
|
||||
import { useSceneContext } from "../scene/sceneContext";
|
||||
import HeatMap from "../../components/heatMapGenerator/heatMap";
|
||||
import { useIsComparing } from "../../store/builder/store";
|
||||
|
||||
function Simulation() {
|
||||
const { activeModule } = useModuleStore();
|
||||
const { eventStore, productStore } = useSceneContext();
|
||||
const { events } = eventStore();
|
||||
const { products } = productStore();
|
||||
const { isComparing } = useIsComparing();
|
||||
|
||||
useEffect(() => {
|
||||
// console.log('events: ', events);
|
||||
@@ -62,7 +60,7 @@ function Simulation() {
|
||||
|
||||
<SimulationAnalysis />
|
||||
|
||||
{!isComparing && <HeatMap />}
|
||||
<HeatMap />
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { WebGLRenderer } from "three";
|
||||
import { Buffer } from "buffer";
|
||||
import { exportHeatmapAsPNG } from "../../../../components/heatMapGenerator/functions/exportHeatmapAsPNG";
|
||||
import { exportHeatmapAsPNG } from "./exportHeatmapAsPNG";
|
||||
|
||||
// Types
|
||||
interface BakedPoint {
|
||||
@@ -32,6 +32,7 @@ interface GenerateHeatmapOutputParams {
|
||||
gl: WebGLRenderer;
|
||||
width: number;
|
||||
height: number;
|
||||
version: string;
|
||||
outputType?: "url" | "file" | "blob";
|
||||
download?: boolean;
|
||||
}
|
||||
@@ -40,6 +41,7 @@ export async function generateHeatmapOutput({
|
||||
bakedPoints,
|
||||
gl,
|
||||
width,
|
||||
version,
|
||||
height,
|
||||
outputType = "file",
|
||||
download = false,
|
||||
@@ -48,7 +50,6 @@ export async function generateHeatmapOutput({
|
||||
|
||||
const fileResults = await Promise.all(
|
||||
bakedResult.map(async (item) => {
|
||||
console.log("item: ", item);
|
||||
|
||||
let imageBlob: Blob;
|
||||
|
||||
@@ -62,7 +63,7 @@ export async function generateHeatmapOutput({
|
||||
imageBlob = item.image;
|
||||
}
|
||||
|
||||
const fileName = `${item.type}-heatmap.png`;
|
||||
const fileName = `${item.type}-${version}-heatmap.png`;
|
||||
|
||||
// Convert Blob -> ArrayBuffer -> Buffer
|
||||
const arrayBuffer = await imageBlob.arrayBuffer();
|
||||
|
||||
@@ -90,7 +90,7 @@ const SimulationHandler = () => {
|
||||
async function handleSaveSimulationData(data: any) {
|
||||
try {
|
||||
await saveSimulationDataApi(data);
|
||||
console.log("Simulation data saved successfully");
|
||||
// console.log("Simulation data saved successfully");
|
||||
setIsPlaying(false);
|
||||
} catch (err) {
|
||||
console.error("Failed to save simulation data:", err);
|
||||
@@ -169,8 +169,10 @@ const SimulationHandler = () => {
|
||||
gl,
|
||||
width,
|
||||
height,
|
||||
version: selectedVersion.versionId,
|
||||
download: true,
|
||||
}).then((bakedResult) => {
|
||||
console.log("bakedResult: ", bakedResult);
|
||||
heatMapImageApi(
|
||||
projectId || "",
|
||||
selectedVersion?.versionId || "",
|
||||
|
||||
@@ -20,6 +20,7 @@ function Simulator() {
|
||||
const { selectedVersion } = versionStore();
|
||||
const { setIsComparing } = useIsComparing();
|
||||
const { addSimulationRecords } = useSimulationManager();
|
||||
|
||||
useEffect(() => {
|
||||
if (!isPlaying || isReset || !selectedProduct.productUuid) return;
|
||||
|
||||
@@ -74,11 +75,7 @@ function Simulator() {
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [selectedProduct, projectId]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<SimulationHandler />
|
||||
</>
|
||||
);
|
||||
return <SimulationHandler />;
|
||||
}
|
||||
|
||||
export default Simulator;
|
||||
|
||||
@@ -487,8 +487,8 @@ interface HeatmapTypeState {
|
||||
}
|
||||
export const useHeatmapTypeStore = create<HeatmapTypeState>((set) => ({
|
||||
selectedTypes: {
|
||||
vehicle: false,
|
||||
human: false,
|
||||
vehicle: true,
|
||||
human: true,
|
||||
},
|
||||
toggleType: (type) =>
|
||||
set((state) => ({
|
||||
|
||||
@@ -7,12 +7,12 @@ interface SimulationUsageRecord {
|
||||
type: "roboticArm" | "vehicle" | "transfer" | "storageUnit" | "crane" | "human" | "machine";
|
||||
assetId: string;
|
||||
}
|
||||
|
||||
type image = string | Blob | null;
|
||||
// Product → holds multiple usage records
|
||||
interface ProductSimulation {
|
||||
productId: string;
|
||||
simulateData: SimulationUsageRecord[];
|
||||
heatMaps?: { type: string; image: string | Blob | null }[];
|
||||
heatMaps?: { type: string; image: image }[];
|
||||
}
|
||||
|
||||
// Version → holds multiple products
|
||||
@@ -29,7 +29,6 @@ interface ProjectSimulation {
|
||||
|
||||
interface SimulationManagerStore {
|
||||
simulationRecords: ProjectSimulation[];
|
||||
setSimulationRecords: (simulateData: ProjectSimulation[]) => void;
|
||||
|
||||
addSimulationRecord: (
|
||||
projectId: string | undefined,
|
||||
@@ -42,7 +41,7 @@ interface SimulationManagerStore {
|
||||
versionId: string,
|
||||
productId: string,
|
||||
records: SimulationUsageRecord[],
|
||||
heatMaps?: { type: string; image: string | Blob | null }[]
|
||||
heatMaps?: { type: string; image: image }[]
|
||||
) => void;
|
||||
resetProductRecords: (
|
||||
projectId: string | undefined,
|
||||
@@ -126,12 +125,12 @@ export const useSimulationManager = create<SimulationManagerStore>((set, get) =>
|
||||
|
||||
return { simulationRecords: projects };
|
||||
}),
|
||||
addSimulationRecords: (
|
||||
addSimulationRecords: (
|
||||
projectId: string | undefined,
|
||||
versionId: string,
|
||||
productId: string,
|
||||
records: SimulationUsageRecord[],
|
||||
heatMaps?: { type: string; image: string | Blob | null }[]
|
||||
heatMaps?: { type: string; image: image }[]
|
||||
) =>
|
||||
set((state) => {
|
||||
const projects = [...state.simulationRecords];
|
||||
@@ -199,60 +198,6 @@ export const useSimulationManager = create<SimulationManagerStore>((set, get) =>
|
||||
return { simulationRecords: projects };
|
||||
}),
|
||||
|
||||
// addSimulationRecords: (projectId, versionId, productId, records) =>
|
||||
// set((state) => {
|
||||
// const projects = state.simulationRecords.map((project) => {
|
||||
// if (project.projectId !== projectId) return project;
|
||||
|
||||
// return {
|
||||
// ...project,
|
||||
// versions: project.versions.map((version) => {
|
||||
// if (version.versionId !== versionId) return version;
|
||||
|
||||
// return {
|
||||
// ...version,
|
||||
// products: version.products.map((product) =>
|
||||
// product.productId === productId
|
||||
// ? {
|
||||
// ...product,
|
||||
// simulateData: [...product.simulateData, ...records],
|
||||
// }
|
||||
// : product
|
||||
// ),
|
||||
// };
|
||||
// }),
|
||||
// };
|
||||
// });
|
||||
|
||||
// // same creation logic for new project/version/product
|
||||
// if (!state.simulationRecords.find((p) => p.projectId === projectId)) {
|
||||
// projects.push({
|
||||
// projectId,
|
||||
// versions: [
|
||||
// {
|
||||
// versionId,
|
||||
// products: [{ productId, simulateData: [...records] }],
|
||||
// },
|
||||
// ],
|
||||
// });
|
||||
// } else {
|
||||
// const project = projects.find((p) => p.projectId === projectId)!;
|
||||
// if (!project.versions.find((v) => v.versionId === versionId)) {
|
||||
// project.versions.push({
|
||||
// versionId,
|
||||
// products: [{ productId, simulateData: [...records] }],
|
||||
// });
|
||||
// } else {
|
||||
// const version = project.versions.find((v) => v.versionId === versionId)!;
|
||||
// if (!version.products.find((p) => p.productId === productId)) {
|
||||
// version.products.push({ productId, simulateData: [...records] });
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// return { simulationRecords: projects };
|
||||
// }),
|
||||
|
||||
resetProductRecords: (projectId, versionId, productId) =>
|
||||
set((state) => {
|
||||
const projects = state.simulationRecords.map((project) => {
|
||||
@@ -277,9 +222,7 @@ export const useSimulationManager = create<SimulationManagerStore>((set, get) =>
|
||||
|
||||
return { simulationRecords: projects };
|
||||
}),
|
||||
setSimulationRecords: (simulateData) => {
|
||||
set({ simulationRecords: simulateData });
|
||||
},
|
||||
|
||||
getProjectById: (projectId: string | undefined) => {
|
||||
return get().simulationRecords.find((p: ProjectSimulation) => p.projectId === projectId);
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user