refactor HeatMap component for improved performance and clarity
This commit is contained in:
@@ -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<EventsSchema[]>([]);
|
||||
const [points, setPoints] = useState<{ x: number; y: number; value: number; }[]>([]);
|
||||
const [points, setPoints] = useState<HeatPoint[]>([]);
|
||||
const materialRef = useRef<THREE.ShaderMaterial>(null);
|
||||
|
||||
const lastFrameTime = useRef<number | null>(null);
|
||||
const lastUpdateTime = useRef<number>(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 (
|
||||
<mesh
|
||||
rotation={[Math.PI / 2, 0, 0]}
|
||||
position={[0, 0.025, 0]}
|
||||
>
|
||||
<mesh rotation={[Math.PI / 2, 0, 0]} position={[0, 0.025, 0]}>
|
||||
<planeGeometry args={[width, height]} />
|
||||
<shaderMaterial
|
||||
ref={materialRef}
|
||||
@@ -135,8 +188,8 @@ const HeatMap = () => {
|
||||
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);
|
||||
}
|
||||
`}
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user