separate the function exportpngheatmap
This commit is contained in:
@@ -3,12 +3,14 @@ import { useEffect, useMemo, useRef, useCallback } from "react";
|
|||||||
import { useThree } from "@react-three/fiber";
|
import { useThree } from "@react-three/fiber";
|
||||||
import { useHeatMapStore } from "../../../store/simulation/useHeatMapStore";
|
import { useHeatMapStore } from "../../../store/simulation/useHeatMapStore";
|
||||||
import * as CONSTANTS from "../../../types/world/worldConstants";
|
import * as CONSTANTS from "../../../types/world/worldConstants";
|
||||||
|
import { exportHeatmapAsPNG } from "../functions/exportHeatmapAsPNG";
|
||||||
|
|
||||||
|
// Constants
|
||||||
const RADIUS = 0.0025;
|
const RADIUS = 0.0025;
|
||||||
const OPACITY = 0.8;
|
const OPACITY = 0.8;
|
||||||
const GROWTH_RATE = 20.0;
|
const GROWTH_RATE = 20.0;
|
||||||
|
|
||||||
|
// 🔹 React Component
|
||||||
const BakedHeatMap = () => {
|
const BakedHeatMap = () => {
|
||||||
const { bakedPoints } = useHeatMapStore();
|
const { bakedPoints } = useHeatMapStore();
|
||||||
const materialRef = useRef<THREE.ShaderMaterial>(null);
|
const materialRef = useRef<THREE.ShaderMaterial>(null);
|
||||||
@@ -18,178 +20,109 @@ const BakedHeatMap = () => {
|
|||||||
const height = CONSTANTS.gridConfig.size;
|
const height = CONSTANTS.gridConfig.size;
|
||||||
const width = CONSTANTS.gridConfig.size;
|
const width = CONSTANTS.gridConfig.size;
|
||||||
|
|
||||||
const createPointTexture = useCallback(
|
const pointTexture = useMemo(() => {
|
||||||
(filteredPoints: typeof bakedPoints) => {
|
if (bakedPoints.length === 0) return null;
|
||||||
if (filteredPoints.length === 0) return null;
|
const data = new Float32Array(bakedPoints.length * 4);
|
||||||
|
bakedPoints.forEach((p, i) => {
|
||||||
const data = new Float32Array(filteredPoints.length * 4);
|
const index = i * 4;
|
||||||
filteredPoints.forEach((p, i) => {
|
data[index] = (p.points.x + width / 2) / width;
|
||||||
const index = i * 4;
|
data[index + 1] = (p.points.y + height / 2) / height;
|
||||||
data[index] = (p.points.x + width / 2) / width;
|
data[index + 2] = 0.3;
|
||||||
data[index + 1] = (p.points.y + height / 2) / height;
|
data[index + 3] = 0.0;
|
||||||
data[index + 2] = 0.3;
|
});
|
||||||
data[index + 3] = 0.0;
|
const texture = new THREE.DataTexture(data, bakedPoints.length, 1, THREE.RGBAFormat, THREE.FloatType);
|
||||||
});
|
texture.needsUpdate = true;
|
||||||
|
return texture;
|
||||||
const texture = new THREE.DataTexture(data, filteredPoints.length, 1, THREE.RGBAFormat, THREE.FloatType);
|
}, [bakedPoints, width, height]);
|
||||||
texture.needsUpdate = true;
|
|
||||||
return texture;
|
|
||||||
},
|
|
||||||
[width, height]
|
|
||||||
);
|
|
||||||
|
|
||||||
const uniformsRef = useRef({
|
const uniformsRef = useRef({
|
||||||
u_points: { value: null as THREE.DataTexture | null },
|
u_points: { value: pointTexture },
|
||||||
u_count: { value: 0 },
|
u_count: { value: bakedPoints.length },
|
||||||
u_radius: { value: RADIUS },
|
u_radius: { value: RADIUS },
|
||||||
u_opacity: { value: OPACITY },
|
u_opacity: { value: OPACITY },
|
||||||
u_growthRate: { value: GROWTH_RATE },
|
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);
|
|
||||||
if (image) {
|
|
||||||
downloadImage(image, `${type}-heatmap.png`);
|
|
||||||
}
|
|
||||||
return { type, image };
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log("Exported Heatmaps:", result);
|
|
||||||
return result;
|
|
||||||
}, [renderHeatmapToImage]);
|
|
||||||
|
|
||||||
const pointTexture = useMemo(() => createPointTexture(bakedPoints), [bakedPoints, createPointTexture]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
uniformsRef.current.u_points.value = pointTexture;
|
uniformsRef.current.u_points.value = pointTexture;
|
||||||
uniformsRef.current.u_count.value = bakedPoints.length;
|
uniformsRef.current.u_count.value = bakedPoints.length;
|
||||||
}, [pointTexture, bakedPoints.length]);
|
}, [pointTexture, bakedPoints.length]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (meshRef.current) {
|
||||||
|
exportHeatmapAsPNG({
|
||||||
|
bakedPoints,
|
||||||
|
gl,
|
||||||
|
width: width,
|
||||||
|
height: height,
|
||||||
|
mesh: meshRef.current,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<></>
|
||||||
<mesh ref={meshRef} rotation={[Math.PI / 2, 0, 0]} position={[0, 0.025, 0]}>
|
// <mesh ref={meshRef} 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}
|
||||||
transparent
|
// transparent
|
||||||
depthWrite={false}
|
// depthWrite={false}
|
||||||
blending={THREE.AdditiveBlending}
|
// blending={THREE.AdditiveBlending}
|
||||||
uniforms={uniformsRef.current}
|
// uniforms={uniformsRef.current}
|
||||||
side={THREE.DoubleSide}
|
// side={THREE.DoubleSide}
|
||||||
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={`
|
||||||
uniform sampler2D u_points;
|
// uniform sampler2D u_points;
|
||||||
precision highp float;
|
// precision highp float;
|
||||||
uniform int u_count;
|
// uniform int u_count;
|
||||||
uniform float u_radius;
|
// uniform float u_radius;
|
||||||
uniform float u_opacity;
|
// uniform float u_opacity;
|
||||||
uniform float u_growthRate;
|
// uniform float u_growthRate;
|
||||||
varying vec2 vUv;
|
// varying vec2 vUv;
|
||||||
|
|
||||||
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() {
|
||||||
float intensity = 0.0;
|
// float intensity = 0.0;
|
||||||
|
|
||||||
for (int i = 0; i < 10000; i++) {
|
// for (int i = 0; i < 10000; i++) {
|
||||||
if (i >= u_count) break;
|
// if (i >= u_count) break;
|
||||||
float fi = float(i) + 0.5;
|
// float fi = float(i) + 0.5;
|
||||||
float u = fi / float(u_count);
|
// float u = fi / float(u_count);
|
||||||
|
|
||||||
vec4 point = texture2D(u_points, vec2(u, 0.5));
|
// vec4 point = texture2D(u_points, vec2(u, 0.5));
|
||||||
vec2 pos = point.rg;
|
// vec2 pos = point.rg;
|
||||||
float strength = point.b;
|
// float strength = point.b;
|
||||||
|
|
||||||
float d = distance(vUv, pos);
|
// float d = distance(vUv, pos);
|
||||||
intensity += strength * gauss(d, u_radius);
|
// intensity += strength * gauss(d, u_radius);
|
||||||
}
|
// }
|
||||||
|
|
||||||
float normalized = clamp(intensity / max(u_growthRate, 0.0001), 0.0, 1.0);
|
// float normalized = clamp(intensity / max(u_growthRate, 0.0001), 0.0, 1.0);
|
||||||
|
|
||||||
vec3 color = vec3(0.0);
|
// vec3 color = vec3(0.0);
|
||||||
if (normalized < 0.33) {
|
// if (normalized < 0.33) {
|
||||||
color = mix(vec3(0.0, 0.0, 1.0), vec3(0.0, 1.0, 0.0), 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) {
|
// } 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);
|
// color = mix(vec3(0.0, 1.0, 0.0), vec3(1.0, 1.0, 0.0), (normalized - 0.33) / 0.33);
|
||||||
} else {
|
// } else {
|
||||||
color = mix(vec3(1.0, 1.0, 0.0), vec3(1.0, 0.0, 0.0), (normalized - 0.66) / 0.34);
|
// 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);
|
// gl_FragColor = vec4(color, normalized * u_opacity);
|
||||||
}
|
// }
|
||||||
`}
|
// `}
|
||||||
/>
|
// />
|
||||||
</mesh>
|
// </mesh>
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,94 @@
|
|||||||
|
import * as THREE from "three";
|
||||||
|
|
||||||
|
const RADIUS = 0.0025;
|
||||||
|
const OPACITY = 0.8;
|
||||||
|
const GROWTH_RATE = 20.0;
|
||||||
|
|
||||||
|
export function exportHeatmapAsPNG({ bakedPoints, gl, width, height, mesh }: { bakedPoints: any[]; gl: THREE.WebGLRenderer; width: number; height: number; mesh: THREE.Mesh }) {
|
||||||
|
const createPointTexture = (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;
|
||||||
|
};
|
||||||
|
|
||||||
|
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 renderHeatmapToImage = (type: string) => {
|
||||||
|
const filteredPoints = bakedPoints.filter((p) => p.type === type);
|
||||||
|
if (filteredPoints.length === 0) return null;
|
||||||
|
|
||||||
|
const pointTexture = createPointTexture(filteredPoints);
|
||||||
|
if (!pointTexture) return null;
|
||||||
|
|
||||||
|
const uniforms = {
|
||||||
|
u_points: { value: pointTexture },
|
||||||
|
u_count: { value: filteredPoints.length },
|
||||||
|
u_radius: { value: RADIUS },
|
||||||
|
u_opacity: { value: OPACITY },
|
||||||
|
u_growthRate: { value: GROWTH_RATE },
|
||||||
|
};
|
||||||
|
|
||||||
|
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(mesh);
|
||||||
|
|
||||||
|
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;
|
||||||
|
};
|
||||||
|
|
||||||
|
const types = ["human", "vehicle"];
|
||||||
|
const result = types.map((type) => {
|
||||||
|
const image = renderHeatmapToImage(type);
|
||||||
|
if (image) {
|
||||||
|
downloadImage(image, `${type}-heatmap.png`);
|
||||||
|
}
|
||||||
|
return { type, image };
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log("Exported Heatmaps:", result);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
@@ -8,6 +8,6 @@ export const saveSimulationData = ({ key, data }: SimulationData) => {
|
|||||||
};
|
};
|
||||||
export const getSimulationData = ({ key }: SimulationData) => {
|
export const getSimulationData = ({ key }: SimulationData) => {
|
||||||
const data = localStorage.getItem(key);
|
const data = localStorage.getItem(key);
|
||||||
console.log("data: ", JSON.parse(data || "{}"));
|
// console.log("data: ", JSON.parse(data || "{}"));
|
||||||
};
|
};
|
||||||
export const clearSimulationData = ({ key, data }: SimulationData) => {};
|
export const clearSimulationData = ({ key, data }: SimulationData) => {};
|
||||||
|
|||||||
Reference in New Issue
Block a user