refactor HeatMap component for improved performance and clarity

This commit is contained in:
2025-09-04 17:37:46 +05:30
parent 64e730efa6
commit ead9f100c4

View File

@@ -5,114 +5,170 @@ import { useProductContext } from "../../modules/simulation/products/productCont
import { useSceneContext } from "../../modules/scene/sceneContext"; import { useSceneContext } from "../../modules/scene/sceneContext";
import * as CONSTANTS from "../../types/world/worldConstants"; import * as CONSTANTS from "../../types/world/worldConstants";
import { determineExecutionMachineSequences } from "../../modules/simulation/simulator/functions/determineExecutionMachineSequences"; 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 HeatMap = () => {
const isUsingBBOX = false; const isUsingBBOX = false;
const height = CONSTANTS.gridConfig.size; const height = CONSTANTS.gridConfig.size;
const width = CONSTANTS.gridConfig.size; const width = CONSTANTS.gridConfig.size;
const radius = 0.005; const debugMode: "solid" | "grayscale" | "normal" = "normal";
const opacity = 0.8; const debugMode: "solid" | "grayscale" | "normal" = "normal";
const debugModeMap = { solid: 0, grayscale: 1, normal: 2, } as const; const debugModeMap = { solid: 0, grayscale: 1, normal: 2 } as const;
const { productStore } = useSceneContext(); const { productStore } = useSceneContext();
const { getProductById, products } = productStore(); const { getProductById, products } = productStore();
const { selectedProductStore } = useProductContext(); const { selectedProductStore } = useProductContext();
const { selectedProduct } = selectedProductStore(); const { selectedProduct } = selectedProductStore();
const { isPlaying } = usePlayButtonStore();
const { isReset } = useResetButtonStore();
const { isPaused } = usePauseButtonStore();
const { speed } = useAnimationPlaySpeed();
const { scene } = useThree(); const { scene } = useThree();
const [events, setEvents] = useState<EventsSchema[]>([]); const [events, setEvents] = useState<EventsSchema[]>([]);
const [points, setPoints] = useState<{ x: number; y: number; value: number; }[]>([]); const [points, setPoints] = useState<HeatPoint[]>([]);
const materialRef = useRef<THREE.ShaderMaterial>(null); const materialRef = useRef<THREE.ShaderMaterial>(null);
const lastFrameTime = useRef<number | null>(null);
const lastUpdateTime = useRef<number>(0);
const uniformsRef = useRef({ const uniformsRef = useRef({
u_points: { value: null as THREE.DataTexture | null }, u_points: { value: null as THREE.DataTexture | null },
u_count: { value: 0 }, u_count: { value: 0 },
u_radius: { value: radius }, u_radius: { value: RADIUS },
u_opacity: { value: opacity }, u_opacity: { value: OPACITY },
u_debugMode: { value: debugModeMap[debugMode] }, u_debugMode: { value: debugModeMap[debugMode] },
}); });
useEffect(() => {
if (isReset || !isPlaying) {
setPoints([]);
lastFrameTime.current = null;
lastUpdateTime.current = 0;
}
}, [isReset, isPlaying]);
useEffect(() => { useEffect(() => {
const selectedProductData = getProductById(selectedProduct.productUuid); const selectedProductData = getProductById(selectedProduct.productUuid);
const events: EventsSchema[] = []; const newEvents: EventsSchema[] = [];
if (selectedProductData) { if (selectedProductData) {
determineExecutionMachineSequences([selectedProductData]).then( determineExecutionMachineSequences([selectedProductData]).then((sequences) => {
(sequences) => { sequences.forEach((sequence) => {
sequences.forEach((sequence) => { sequence.forEach((event) => {
sequence.forEach((event) => { if (event.type === "human" || event.type === "vehicle") {
if (event.type === 'human' || event.type === 'vehicle') { newEvents.push(event);
events.push(event); }
}
})
}); });
setEvents(events); });
} setEvents(newEvents);
); });
} }
}, [selectedProduct, products]) }, [selectedProduct, products]);
useFrame(() => { useFrame((state) => {
if (!materialRef.current || !scene) return; 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) => { 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 (!model) return;
if (isUsingBBOX) { const pos = isUsingBBOX
const box = new THREE.Box3().setFromObject(model); ? (() => {
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; updatedPoints.push({
x: pos.x,
const corners = [ y: pos.z,
new THREE.Vector3(min.x, 0, min.z), strength: 0.3,
new THREE.Vector3(min.x, 0, max.z), lastUpdated: now,
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,
});
}
}); });
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(() => { const pointTexture = useMemo(() => {
if (points.length === 0) return null; if (points.length === 0) return null;
const data = new Float32Array(MAX_POINTS * 4);
const data = new Float32Array(points.length * 4);
points.forEach((p, i) => { points.forEach((p, i) => {
const index = i * 4; const index = i * 4;
data[index] = (p.x + width / 2) / width; data[index] = (p.x + width / 2) / width;
data[index + 1] = (p.y + height / 2) / height; data[index + 1] = (p.y + height / 2) / height;
data[index + 2] = p.value; data[index + 2] = p.strength;
data[index + 3] = 0.0; 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; texture.needsUpdate = true;
return texture; return texture;
}, [points, width, height]); }, [points, width, height]);
useEffect(() => { useEffect(() => {
uniformsRef.current.u_radius.value = radius; uniformsRef.current.u_radius.value = RADIUS;
uniformsRef.current.u_opacity.value = opacity; uniformsRef.current.u_opacity.value = OPACITY;
uniformsRef.current.u_debugMode.value = debugModeMap[debugMode]; uniformsRef.current.u_debugMode.value = debugModeMap[debugMode];
}, [radius, opacity, debugMode, debugModeMap]); }, [RADIUS, OPACITY, debugMode]);
useEffect(() => { useEffect(() => {
uniformsRef.current.u_points.value = pointTexture; uniformsRef.current.u_points.value = pointTexture;
@@ -120,10 +176,7 @@ const HeatMap = () => {
}, [pointTexture, points.length]); }, [pointTexture, points.length]);
return ( return (
<mesh <mesh rotation={[Math.PI / 2, 0, 0]} position={[0, 0.025, 0]}>
rotation={[Math.PI / 2, 0, 0]}
position={[0, 0.025, 0]}
>
<planeGeometry args={[width, height]} /> <planeGeometry args={[width, height]} />
<shaderMaterial <shaderMaterial
ref={materialRef} ref={materialRef}
@@ -135,8 +188,8 @@ const HeatMap = () => {
vertexShader={` vertexShader={`
varying vec2 vUv; varying vec2 vUv;
void main() { void main() {
vUv = uv; vUv = uv;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
} }
`} `}
fragmentShader={` fragmentShader={`
@@ -148,51 +201,48 @@ const HeatMap = () => {
uniform int u_debugMode; uniform int u_debugMode;
varying vec2 vUv; varying vec2 vUv;
const int MAX_POINTS = ${MAX_POINTS};
float gauss(float dist, float radius) { float gauss(float dist, float radius) {
return exp(-pow(dist / radius, 2.0)); return exp(-pow(dist / radius, 2.0));
} }
void main() { void main() {
// Debug mode 0: solid red for plane visibility if (u_debugMode == 0) {
if (u_debugMode == 0) { gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0); return;
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);
} }
}
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);
} }
`} `}
/> />