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 { useHeatMapStore } from "../../../store/simulation/useHeatMapStore";
|
||||
import * as CONSTANTS from "../../../types/world/worldConstants";
|
||||
import { exportHeatmapAsPNG } from "../functions/exportHeatmapAsPNG";
|
||||
|
||||
|
||||
// Constants
|
||||
const RADIUS = 0.0025;
|
||||
const OPACITY = 0.8;
|
||||
const GROWTH_RATE = 20.0;
|
||||
|
||||
// 🔹 React Component
|
||||
const BakedHeatMap = () => {
|
||||
const { bakedPoints } = useHeatMapStore();
|
||||
const materialRef = useRef<THREE.ShaderMaterial>(null);
|
||||
@@ -18,178 +20,109 @@ const BakedHeatMap = () => {
|
||||
const height = CONSTANTS.gridConfig.size;
|
||||
const width = CONSTANTS.gridConfig.size;
|
||||
|
||||
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 pointTexture = useMemo(() => {
|
||||
if (bakedPoints.length === 0) return null;
|
||||
const data = new Float32Array(bakedPoints.length * 4);
|
||||
bakedPoints.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, bakedPoints.length, 1, THREE.RGBAFormat, THREE.FloatType);
|
||||
texture.needsUpdate = true;
|
||||
return texture;
|
||||
}, [bakedPoints, width, height]);
|
||||
|
||||
const uniformsRef = useRef({
|
||||
u_points: { value: null as THREE.DataTexture | null },
|
||||
u_count: { value: 0 },
|
||||
u_points: { value: pointTexture },
|
||||
u_count: { value: bakedPoints.length },
|
||||
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);
|
||||
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(() => {
|
||||
uniformsRef.current.u_points.value = pointTexture;
|
||||
uniformsRef.current.u_count.value = bakedPoints.length;
|
||||
}, [pointTexture, bakedPoints.length]);
|
||||
|
||||
useEffect(() => {
|
||||
if (meshRef.current) {
|
||||
exportHeatmapAsPNG({
|
||||
bakedPoints,
|
||||
gl,
|
||||
width: width,
|
||||
height: height,
|
||||
mesh: meshRef.current,
|
||||
});
|
||||
}
|
||||
}, []);
|
||||
|
||||
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;
|
||||
<></>
|
||||
// <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));
|
||||
}
|
||||
// float gauss(float dist, float radius) {
|
||||
// return exp(-pow(dist / radius, 2.0));
|
||||
// }
|
||||
|
||||
void main() {
|
||||
float intensity = 0.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);
|
||||
// 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;
|
||||
// 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 d = distance(vUv, pos);
|
||||
// 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);
|
||||
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);
|
||||
}
|
||||
// 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>
|
||||
</>
|
||||
// gl_FragColor = vec4(color, normalized * u_opacity);
|
||||
// }
|
||||
// `}
|
||||
// />
|
||||
// </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) => {
|
||||
const data = localStorage.getItem(key);
|
||||
console.log("data: ", JSON.parse(data || "{}"));
|
||||
// console.log("data: ", JSON.parse(data || "{}"));
|
||||
};
|
||||
export const clearSimulationData = ({ key, data }: SimulationData) => {};
|
||||
|
||||
Reference in New Issue
Block a user