ui/render: optimize rendering, requestRenderIfNotRequested

This commit is contained in:
Johannes Kirschbauer
2025-07-18 18:15:30 +02:00
parent 2e577dbd1e
commit 174e66ef95

View File

@@ -77,13 +77,15 @@ export function CubeScene(props: {
let renderer: THREE.WebGLRenderer;
let floor: THREE.Mesh;
let controls: MapControls;
// Raycaster for clicking
let raycaster = new THREE.Raycaster();
let needsRender = false; // Flag to control rendering
// Create background scene
const bgScene = new THREE.Scene();
const bgCamera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0, 1);
let raycaster: THREE.Raycaster;
const groupMap = new Map<string, THREE.Group>();
const occupiedPositions = new Set<string>();
@@ -173,6 +175,26 @@ export function CubeScene(props: {
}
});
function requestRenderIfNotRequested() {
if (!needsRender) {
needsRender = true;
requestAnimationFrame(renderScene);
}
}
function renderScene() {
if (!isAnimating) return;
needsRender = false;
frameCount++;
renderer.autoClear = false;
renderer.render(bgScene, bgCamera);
controls.update(); // optional; see note below
renderer.render(scene, camera);
if (frameCount % 30 === 0) logMemoryUsage();
}
function getGridPosition(id: string): [number, number, number] {
// TODO: Detect collision with other cubes
const machine = props.sceneStore()[id];
@@ -400,15 +422,18 @@ export function CubeScene(props: {
cubeMaterial.color.set(CUBE_COLOR);
}
requestRenderIfNotRequested();
}
function logMemoryUsage() {
if (renderer && renderer.info) {
console.log("Three.js Memory:", {
console.debug("Three.js Memory:", {
frame: renderer.info.render.frame,
calls: renderer.info.render.calls,
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,
});
}
@@ -493,6 +518,7 @@ export function CubeScene(props: {
// Enable the context menu,
// TODO: disable in production
controls.mouseButtons.RIGHT = null;
controls.addEventListener("change", requestRenderIfNotRequested);
// Lighting
const ambientLight = new THREE.AmbientLight(0xffffff, 1.5);
@@ -588,120 +614,6 @@ export function CubeScene(props: {
// Initial camera info update
updateCameraInfo();
const onMouseDown = (event: MouseEvent) => {
isDragging = true;
previousMousePosition = { x: event.clientX, y: event.clientY };
};
const onMouseUp = () => {
isDragging = false;
};
const onMouseMove = (event: MouseEvent) => {
if (worldMode() === "create") {
if (isDragging) return;
const rect = renderer.domElement.getBoundingClientRect();
const mouse = new THREE.Vector2(
((event.clientX - rect.left) / rect.width) * 2 - 1,
-((event.clientY - rect.top) / rect.height) * 2 + 1,
);
raycaster.setFromCamera(mouse, camera);
const intersects = raycaster.intersectObject(floor);
if (intersects.length > 0) {
const point = intersects[0].point;
// Snap to grid
const snapped = new THREE.Vector3(
Math.round(point.x / GRID_SIZE) * GRID_SIZE,
0,
Math.round(point.z / GRID_SIZE) * GRID_SIZE,
);
if (!initBase) {
// Create initial base mesh if it doesn't exist
initBase = createCubeBase(
[snapped.x, 0, snapped.z],
1,
CREATE_BASE_COLOR,
CREATE_BASE_EMISSIVE, // Emissive color
);
} else {
initBase.position.set(snapped.x, 0, snapped.z);
}
scene.remove(initBase); // Remove any existing base mesh
scene.add(initBase);
setCursorPosition([snapped.x, snapped.z]); // Update next position for cube creation
}
// If in create mode, don't allow camera movement
return;
}
if (!isDragging) return;
const deltaX = event.clientX - previousMousePosition.x;
const deltaY = event.clientY - previousMousePosition.y;
// const deltaY = event.clientY - previousMousePosition.y;
if (positionMode() === "circle") {
const spherical = new THREE.Spherical();
spherical.setFromVector3(camera.position);
spherical.theta -= deltaX * 0.01;
// spherical.phi += deltaY * 0.01;
// spherical.phi = Math.max(0.1, Math.min(Math.PI - 0.1, spherical.phi));
// const lightPos = new THREE.Spherical();
// lightPos.setFromVector3(directionalLight.position);
// lightPos.theta = spherical.theta - Math.PI / 2; // 90 degrees offset
// directionalLight.position.setFromSpherical(lightPos);
// directionalLight.lookAt(0, 0, 0);
camera.position.setFromSpherical(spherical);
camera.lookAt(0, 0, 0);
} else {
const movementSpeed = 0.015;
// Get camera direction vectors
const cameraDirection = new THREE.Vector3();
camera.getWorldDirection(cameraDirection);
cameraDirection.y = 0; // Ignore vertical direction
const cameraRight = new THREE.Vector3();
cameraRight.crossVectors(camera.up, cameraDirection).normalize(); // Get right vector
// Move camera based on mouse deltas
camera.position.addScaledVector(cameraRight, deltaX * movementSpeed); // horizontal drag
camera.position.addScaledVector(
cameraDirection,
deltaY * movementSpeed,
); // vertical drag (forward/back)
}
updateCameraInfo();
previousMousePosition = { x: event.clientX, y: event.clientY };
};
const onWheel = (event: WheelEvent) => {
const spherical = new THREE.Spherical();
spherical.setFromVector3(camera.position);
event.preventDefault();
spherical.radius += event.deltaY * 0.01;
spherical.radius = Math.max(3, Math.min(10, spherical.radius)); // Clamp radius between 5 and 50
camera.position.setFromSpherical(spherical);
// camera.lookAt(0, 0, 0);
updateCameraInfo();
};
// Event listeners
// renderer.domElement.addEventListener("mousedown", onMouseDown);
// renderer.domElement.addEventListener("mouseup", onMouseUp);
// renderer.domElement.addEventListener("mousemove", onMouseMove);
// renderer.domElement.addEventListener("wheel", onWheel);
// Raycaster for clicking
raycaster = new THREE.Raycaster();
// Click handler:
// - Select/deselects a cube in "view" mode
// - Creates a new cube in "create" mode
@@ -742,31 +654,26 @@ export function CubeScene(props: {
renderer.domElement.addEventListener("click", onClick);
const animate = () => {
if (!isAnimating) return; // Exit if component is unmounted
requestAnimationFrame(animate);
frameCount++;
renderer.autoClear = false;
renderer.render(bgScene, bgCamera); // Render background scene
controls.update();
renderer.render(scene, camera);
// Uncomment for memory debugging:
if (frameCount % 300 === 0) logMemoryUsage(); // Log every 60 frames
};
isAnimating = true;
animate();
requestRenderIfNotRequested();
// Handle window resize
const handleResize = () => {
camera.aspect = container.clientWidth / container.clientHeight;
camera.updateProjectionMatrix();
renderer.setSize(container.clientWidth, container.clientHeight);
// Update background shader resolution
uniforms.resolution.value.set(
container.clientWidth,
container.clientHeight,
);
renderer.render(bgScene, bgCamera);
requestRenderIfNotRequested();
};
window.addEventListener("resize", handleResize);
// For debugging,
// TODO: Remove in production
@@ -782,11 +689,6 @@ export function CubeScene(props: {
onCleanup(() => {
// Stop animation loop
isAnimating = false;
// renderer.domElement.removeEventListener("mousedown", onMouseDown);
// renderer.domElement.removeEventListener("mouseup", onMouseUp);
// renderer.domElement.removeEventListener("mousemove", onMouseMove);
// renderer.domElement.removeEventListener("wheel", onWheel);
renderer.domElement.removeEventListener("click", onClick);
window.removeEventListener("resize", handleResize);