UI: Cubes improve memory usage
This commit is contained in:
@@ -15,10 +15,20 @@ export function CubeScene() {
|
|||||||
let camera: THREE.PerspectiveCamera;
|
let camera: THREE.PerspectiveCamera;
|
||||||
let renderer: THREE.WebGLRenderer;
|
let renderer: THREE.WebGLRenderer;
|
||||||
let raycaster: THREE.Raycaster;
|
let raycaster: THREE.Raycaster;
|
||||||
let controls: any; // OrbitControls type
|
|
||||||
let meshMap = new Map<string, THREE.Mesh>();
|
let meshMap = new Map<string, THREE.Mesh>();
|
||||||
let baseMap = new Map<string, THREE.Mesh>(); // Map for cube bases
|
let baseMap = new Map<string, THREE.Mesh>(); // Map for cube bases
|
||||||
|
|
||||||
|
let sharedCubeGeometry: THREE.BoxGeometry;
|
||||||
|
let sharedBaseGeometry: THREE.BoxGeometry;
|
||||||
|
|
||||||
|
// Used for development purposes
|
||||||
|
// Vite does hot-reload but we need to ensure the animation loop doesn't run multiple times
|
||||||
|
// This flag prevents multiple animation loops from running simultaneously
|
||||||
|
// It is set to true when the component mounts and false when it unmounts
|
||||||
|
let isAnimating = false; // Flag to prevent multiple loops
|
||||||
|
let frameCount = 0;
|
||||||
|
|
||||||
const [cubes, setCubes] = createSignal<CubeData[]>([]);
|
const [cubes, setCubes] = createSignal<CubeData[]>([]);
|
||||||
const [selectedIds, setSelectedIds] = createSignal<Set<string>>(new Set());
|
const [selectedIds, setSelectedIds] = createSignal<Set<string>>(new Set());
|
||||||
const [cameraInfo, setCameraInfo] = createSignal({
|
const [cameraInfo, setCameraInfo] = createSignal({
|
||||||
@@ -66,9 +76,8 @@ export function CubeScene() {
|
|||||||
|
|
||||||
// Create white base for cube
|
// Create white base for cube
|
||||||
function createCubeBase(cube_pos: [number, number, number]) {
|
function createCubeBase(cube_pos: [number, number, number]) {
|
||||||
const baseGeometry = new THREE.BoxGeometry(1.2, 0.05, 1.2); // 1.2 times cube size, thin height
|
|
||||||
const baseMaterials = createBaseMaterials();
|
const baseMaterials = createBaseMaterials();
|
||||||
const base = new THREE.Mesh(baseGeometry, baseMaterials);
|
const base = new THREE.Mesh(sharedBaseGeometry, baseMaterials);
|
||||||
// tranlate_y = - cube_height / 2 - base_height / 2
|
// tranlate_y = - cube_height / 2 - base_height / 2
|
||||||
base.position.set(cube_pos[0], cube_pos[1] - 0.5 - 0.025, cube_pos[2]); // Position below cube
|
base.position.set(cube_pos[0], cube_pos[1] - 0.5 - 0.025, cube_pos[2]); // Position below cube
|
||||||
base.receiveShadow = true;
|
base.receiveShadow = true;
|
||||||
@@ -88,14 +97,35 @@ export function CubeScene() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function deleteCube(id: string) {
|
function deleteCube(id: string) {
|
||||||
setCubes((prev) => prev.filter((c) => c.id !== id));
|
// Remove cube mesh
|
||||||
const mesh = meshMap.get(id);
|
const mesh = meshMap.get(id);
|
||||||
if (mesh) {
|
if (mesh) {
|
||||||
scene.remove(mesh);
|
scene.remove(mesh);
|
||||||
mesh.geometry.dispose();
|
mesh.geometry.dispose();
|
||||||
(mesh.material as THREE.Material).dispose();
|
// Dispose materials properly
|
||||||
|
if (Array.isArray(mesh.material)) {
|
||||||
|
mesh.material.forEach((material) => material.dispose());
|
||||||
|
} else {
|
||||||
|
mesh.material.dispose();
|
||||||
|
}
|
||||||
meshMap.delete(id);
|
meshMap.delete(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Remove base mesh - THIS WAS MISSING!
|
||||||
|
const base = baseMap.get(id);
|
||||||
|
if (base) {
|
||||||
|
scene.remove(base);
|
||||||
|
base.geometry.dispose();
|
||||||
|
// Dispose base materials properly
|
||||||
|
if (Array.isArray(base.material)) {
|
||||||
|
base.material.forEach((material) => material.dispose());
|
||||||
|
} else {
|
||||||
|
base.material.dispose();
|
||||||
|
}
|
||||||
|
baseMap.delete(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
setCubes((prev) => prev.filter((c) => c.id !== id));
|
||||||
}
|
}
|
||||||
|
|
||||||
function toggleSelection(id: string) {
|
function toggleSelection(id: string) {
|
||||||
@@ -137,6 +167,18 @@ export function CubeScene() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function logMemoryUsage() {
|
||||||
|
if (renderer && renderer.info) {
|
||||||
|
console.log("Three.js Memory:", {
|
||||||
|
geometries: renderer.info.memory.geometries,
|
||||||
|
textures: renderer.info.memory.textures,
|
||||||
|
programs: renderer.info.programs?.length || 0,
|
||||||
|
calls: renderer.info.render.calls,
|
||||||
|
triangles: renderer.info.render.triangles,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
// Scene setup
|
// Scene setup
|
||||||
scene = new THREE.Scene();
|
scene = new THREE.Scene();
|
||||||
@@ -196,6 +238,11 @@ export function CubeScene() {
|
|||||||
floor.position.y = 0; // Keep at ground level for reference
|
floor.position.y = 0; // Keep at ground level for reference
|
||||||
scene.add(floor);
|
scene.add(floor);
|
||||||
|
|
||||||
|
// Shared geometries for cubes and bases
|
||||||
|
// This allows us to reuse the same geometry for all cubes and bases
|
||||||
|
sharedCubeGeometry = new THREE.BoxGeometry(1, 1, 1);
|
||||||
|
sharedBaseGeometry = new THREE.BoxGeometry(1.2, 0.05, 1.2);
|
||||||
|
|
||||||
// Basic OrbitControls implementation (simplified)
|
// Basic OrbitControls implementation (simplified)
|
||||||
let isDragging = false;
|
let isDragging = false;
|
||||||
let previousMousePosition = { x: 0, y: 0 };
|
let previousMousePosition = { x: 0, y: 0 };
|
||||||
@@ -288,11 +335,18 @@ export function CubeScene() {
|
|||||||
|
|
||||||
renderer.domElement.addEventListener("click", onClick);
|
renderer.domElement.addEventListener("click", onClick);
|
||||||
|
|
||||||
// Animation loop
|
|
||||||
const animate = () => {
|
const animate = () => {
|
||||||
|
if (!isAnimating) return; // Exit if component is unmounted
|
||||||
|
|
||||||
requestAnimationFrame(animate);
|
requestAnimationFrame(animate);
|
||||||
|
|
||||||
|
frameCount++;
|
||||||
renderer.render(scene, camera);
|
renderer.render(scene, camera);
|
||||||
|
|
||||||
|
// Uncomment for memory debugging:
|
||||||
|
if (frameCount % 60 === 0) logMemoryUsage(); // Log every 60 frames
|
||||||
};
|
};
|
||||||
|
isAnimating = true;
|
||||||
animate();
|
animate();
|
||||||
|
|
||||||
// Handle window resize
|
// Handle window resize
|
||||||
@@ -305,12 +359,18 @@ export function CubeScene() {
|
|||||||
|
|
||||||
// Cleanup function
|
// Cleanup function
|
||||||
onCleanup(() => {
|
onCleanup(() => {
|
||||||
|
// Stop animation loop
|
||||||
|
isAnimating = false;
|
||||||
renderer.domElement.removeEventListener("mousedown", onMouseDown);
|
renderer.domElement.removeEventListener("mousedown", onMouseDown);
|
||||||
renderer.domElement.removeEventListener("mouseup", onMouseUp);
|
renderer.domElement.removeEventListener("mouseup", onMouseUp);
|
||||||
renderer.domElement.removeEventListener("mousemove", onMouseMove);
|
renderer.domElement.removeEventListener("mousemove", onMouseMove);
|
||||||
renderer.domElement.removeEventListener("wheel", onWheel);
|
renderer.domElement.removeEventListener("wheel", onWheel);
|
||||||
renderer.domElement.removeEventListener("click", onClick);
|
renderer.domElement.removeEventListener("click", onClick);
|
||||||
window.removeEventListener("resize", handleResize);
|
window.removeEventListener("resize", handleResize);
|
||||||
|
|
||||||
|
if (container) {
|
||||||
|
container.innerHTML = "";
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -322,9 +382,8 @@ export function CubeScene() {
|
|||||||
cubes().forEach((cube) => {
|
cubes().forEach((cube) => {
|
||||||
if (!meshMap.has(cube.id)) {
|
if (!meshMap.has(cube.id)) {
|
||||||
// Create cube mesh
|
// Create cube mesh
|
||||||
const cubeGeometry = new THREE.BoxGeometry(1, 1, 1);
|
|
||||||
const cubeMaterials = createCubeMaterials();
|
const cubeMaterials = createCubeMaterials();
|
||||||
const mesh = new THREE.Mesh(cubeGeometry, cubeMaterials);
|
const mesh = new THREE.Mesh(sharedCubeGeometry, cubeMaterials);
|
||||||
mesh.castShadow = true;
|
mesh.castShadow = true;
|
||||||
mesh.receiveShadow = true;
|
mesh.receiveShadow = true;
|
||||||
mesh.position.set(...cube.position);
|
mesh.position.set(...cube.position);
|
||||||
@@ -356,9 +415,7 @@ export function CubeScene() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
onCleanup(() => {
|
onCleanup(() => {
|
||||||
renderer?.dispose();
|
|
||||||
for (const mesh of meshMap.values()) {
|
for (const mesh of meshMap.values()) {
|
||||||
mesh.geometry.dispose();
|
|
||||||
// Handle both single material and material array
|
// Handle both single material and material array
|
||||||
if (Array.isArray(mesh.material)) {
|
if (Array.isArray(mesh.material)) {
|
||||||
mesh.material.forEach((material) => material.dispose());
|
mesh.material.forEach((material) => material.dispose());
|
||||||
@@ -367,8 +424,8 @@ export function CubeScene() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
meshMap.clear();
|
meshMap.clear();
|
||||||
|
|
||||||
for (const mesh of baseMap.values()) {
|
for (const mesh of baseMap.values()) {
|
||||||
mesh.geometry.dispose();
|
|
||||||
// Handle both single material and material array
|
// Handle both single material and material array
|
||||||
if (Array.isArray(mesh.material)) {
|
if (Array.isArray(mesh.material)) {
|
||||||
mesh.material.forEach((material) => material.dispose());
|
mesh.material.forEach((material) => material.dispose());
|
||||||
@@ -377,6 +434,12 @@ export function CubeScene() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
baseMap.clear();
|
baseMap.clear();
|
||||||
|
|
||||||
|
// Dispose shared geometries
|
||||||
|
sharedCubeGeometry?.dispose();
|
||||||
|
sharedBaseGeometry?.dispose();
|
||||||
|
|
||||||
|
renderer?.dispose();
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
Reference in New Issue
Block a user