From ead9f100c43dccf16133f95a49d998993471e814 Mon Sep 17 00:00:00 2001 From: Gomathi9520 Date: Thu, 4 Sep 2025 17:37:46 +0530 Subject: [PATCH] refactor HeatMap component for improved performance and clarity --- app/src/components/HeatMap/HeatMap.tsx | 252 +++++++++++++++---------- 1 file changed, 151 insertions(+), 101 deletions(-) diff --git a/app/src/components/HeatMap/HeatMap.tsx b/app/src/components/HeatMap/HeatMap.tsx index e2c7c74..6344c67 100644 --- a/app/src/components/HeatMap/HeatMap.tsx +++ b/app/src/components/HeatMap/HeatMap.tsx @@ -5,114 +5,170 @@ import { useProductContext } from "../../modules/simulation/products/productCont import { useSceneContext } from "../../modules/scene/sceneContext"; import * as CONSTANTS from "../../types/world/worldConstants"; import { determineExecutionMachineSequences } from "../../modules/simulation/simulator/functions/determineExecutionMachineSequences"; +import { + useAnimationPlaySpeed, + usePauseButtonStore, + usePlayButtonStore, + useResetButtonStore, +} from "../../store/usePlayButtonStore"; -const MAX_POINTS = 100; +const DECAY_RATE = 0.01; +const RADIUS = 0.005; +const OPACITY = 0.8; +const UPDATE_INTERVAL = 0.1; + +interface HeatPoint { + x: number; + y: number; + strength: number; + lastUpdated: number; +} const HeatMap = () => { const isUsingBBOX = false; const height = CONSTANTS.gridConfig.size; const width = CONSTANTS.gridConfig.size; - const radius = 0.005; - const opacity = 0.8; const debugMode: "solid" | "grayscale" | "normal" = "normal"; - const debugModeMap = { solid: 0, grayscale: 1, normal: 2, } as const; + const debugMode: "solid" | "grayscale" | "normal" = "normal"; + + const debugModeMap = { solid: 0, grayscale: 1, normal: 2 } as const; + const { productStore } = useSceneContext(); const { getProductById, products } = productStore(); const { selectedProductStore } = useProductContext(); const { selectedProduct } = selectedProductStore(); + const { isPlaying } = usePlayButtonStore(); + const { isReset } = useResetButtonStore(); + const { isPaused } = usePauseButtonStore(); + const { speed } = useAnimationPlaySpeed(); const { scene } = useThree(); + const [events, setEvents] = useState([]); - const [points, setPoints] = useState<{ x: number; y: number; value: number; }[]>([]); + const [points, setPoints] = useState([]); const materialRef = useRef(null); + + const lastFrameTime = useRef(null); + const lastUpdateTime = useRef(0); + const uniformsRef = useRef({ u_points: { value: null as THREE.DataTexture | null }, u_count: { value: 0 }, - u_radius: { value: radius }, - u_opacity: { value: opacity }, + u_radius: { value: RADIUS }, + u_opacity: { value: OPACITY }, u_debugMode: { value: debugModeMap[debugMode] }, }); + useEffect(() => { + if (isReset || !isPlaying) { + setPoints([]); + lastFrameTime.current = null; + lastUpdateTime.current = 0; + } + }, [isReset, isPlaying]); + useEffect(() => { const selectedProductData = getProductById(selectedProduct.productUuid); - const events: EventsSchema[] = []; + const newEvents: EventsSchema[] = []; if (selectedProductData) { - determineExecutionMachineSequences([selectedProductData]).then( - (sequences) => { - sequences.forEach((sequence) => { - sequence.forEach((event) => { - if (event.type === 'human' || event.type === 'vehicle') { - events.push(event); - } - }) + determineExecutionMachineSequences([selectedProductData]).then((sequences) => { + sequences.forEach((sequence) => { + sequence.forEach((event) => { + if (event.type === "human" || event.type === "vehicle") { + newEvents.push(event); + } }); - setEvents(events); - } - ); + }); + setEvents(newEvents); + }); } - }, [selectedProduct, products]) + }, [selectedProduct, products]); - useFrame(() => { - if (!materialRef.current || !scene) return; + useFrame((state) => { + if (!scene || !isPlaying || isReset) return; - const heatmapPoints: { x: number; y: number; value: number }[] = []; + const now = state.clock.elapsedTime; + + if (isPaused || speed === 0) { + lastFrameTime.current = now; + return; + } + + let delta = 0; + if (lastFrameTime.current !== null) { + delta = now - lastFrameTime.current; + } + lastFrameTime.current = now; + + if (delta <= 0) return; + + const updateInterval = UPDATE_INTERVAL / Math.max(speed, 0.1); + + if (now - lastUpdateTime.current < updateInterval) return; + lastUpdateTime.current = now; + + const scaledDelta = delta * speed; + + let updatedPoints = [...points]; events.forEach((event) => { - const model = scene.getObjectByProperty('uuid', event.modelUuid) as THREE.Object3D; + const model = scene.getObjectByProperty("uuid", event.modelUuid) as THREE.Object3D; if (!model) return; - if (isUsingBBOX) { - const box = new THREE.Box3().setFromObject(model); + const pos = isUsingBBOX + ? (() => { + const box = new THREE.Box3().setFromObject(model); + const { min, max } = box; + return new THREE.Vector3((min.x + max.x) / 2, 0, (min.z + max.z) / 2); + })() + : model.position; - const { min, max } = box; - - const corners = [ - new THREE.Vector3(min.x, 0, min.z), - new THREE.Vector3(min.x, 0, max.z), - new THREE.Vector3(max.x, 0, min.z), - new THREE.Vector3(max.x, 0, max.z), - ]; - - corners.forEach((corner) => { - heatmapPoints.push({ - x: corner.x, - y: corner.z, - value: 1.0, - }); - }); - } else { - heatmapPoints.push({ - x: model.position.x, - y: model.position.z, - value: 1.0, - }); - } + updatedPoints.push({ + x: pos.x, + y: pos.z, + strength: 0.3, + lastUpdated: now, + }); }); - setPoints(heatmapPoints); + updatedPoints = updatedPoints + .map((p) => ({ + ...p, + strength: Math.max(0, p.strength - DECAY_RATE * scaledDelta), + })) + .filter((p) => p.strength > 0.01); + + setPoints(updatedPoints); }); const pointTexture = useMemo(() => { if (points.length === 0) return null; - const data = new Float32Array(MAX_POINTS * 4); + + const data = new Float32Array(points.length * 4); points.forEach((p, i) => { const index = i * 4; data[index] = (p.x + width / 2) / width; data[index + 1] = (p.y + height / 2) / height; - data[index + 2] = p.value; + data[index + 2] = p.strength; data[index + 3] = 0.0; }); - const texture = new THREE.DataTexture(data, MAX_POINTS, 1, THREE.RGBAFormat, THREE.FloatType); + const texture = new THREE.DataTexture( + data, + points.length, + 1, + THREE.RGBAFormat, + THREE.FloatType + ); texture.needsUpdate = true; return texture; }, [points, width, height]); useEffect(() => { - uniformsRef.current.u_radius.value = radius; - uniformsRef.current.u_opacity.value = opacity; + uniformsRef.current.u_radius.value = RADIUS; + uniformsRef.current.u_opacity.value = OPACITY; uniformsRef.current.u_debugMode.value = debugModeMap[debugMode]; - }, [radius, opacity, debugMode, debugModeMap]); + }, [RADIUS, OPACITY, debugMode]); useEffect(() => { uniformsRef.current.u_points.value = pointTexture; @@ -120,10 +176,7 @@ const HeatMap = () => { }, [pointTexture, points.length]); return ( - + { vertexShader={` varying vec2 vUv; void main() { - vUv = uv; - gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); + vUv = uv; + gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); } `} fragmentShader={` @@ -148,51 +201,48 @@ const HeatMap = () => { uniform int u_debugMode; varying vec2 vUv; - const int MAX_POINTS = ${MAX_POINTS}; - float gauss(float dist, float radius) { - return exp(-pow(dist / radius, 2.0)); + return exp(-pow(dist / radius, 2.0)); } void main() { - // Debug mode 0: solid red for plane visibility - if (u_debugMode == 0) { - gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0); - return; - } - - float intensity = 0.0; - - for (int i = 0; i < MAX_POINTS; i++) { - if (i >= u_count) break; - float fi = float(i) + 0.5; - float u = fi / float(MAX_POINTS); - - vec4 point = texture2D(u_points, vec2(u, 0.5)); - vec2 pos = point.rg; - float value = point.b; - - float d = distance(vUv, pos); - intensity += value * gauss(d, u_radius); - } - - // Debug mode 1: grayscale - if (u_debugMode == 1) { - gl_FragColor = vec4(vec3(intensity), 1.0); - return; - } - - // Debug mode 2: heatmap color - vec3 color = vec3(0.0); - if (intensity > 0.0) { - if (intensity < 0.5) { - color = mix(vec3(0.0, 0.0, 1.0), vec3(0.0, 1.0, 0.0), intensity * 2.0); - } else { - color = mix(vec3(0.0, 1.0, 0.0), vec3(1.0, 0.0, 0.0), (intensity - 0.5) * 2.0); + if (u_debugMode == 0) { + gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0); + return; } - } - gl_FragColor = vec4(color, intensity * u_opacity); + float intensity = 0.0; + + for (int i = 0; i < 10000; i++) { + if (i >= u_count) break; // dynamically stop looping + 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); + } + + if (u_debugMode == 1) { + gl_FragColor = vec4(vec3(intensity), 1.0); + return; + } + + vec3 color = vec3(0.0); + float normalized = clamp(intensity / 4.0, 0.0, 1.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); } `} />