UI: dispay selected cube base

This commit is contained in:
Johannes Kirschbauer
2025-07-08 22:12:52 +02:00
parent 0b2657cb0c
commit f555dd349e

View File

@@ -17,15 +17,13 @@ export function CubeScene() {
let raycaster: THREE.Raycaster; let raycaster: THREE.Raycaster;
let controls: any; // OrbitControls type 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 ambientLightMap = new Map<string, THREE.PointLight>(); // Map for pink ambient lights
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({
position: { x: 0, y: 0, z: 0 }, position: { x: 0, y: 0, z: 0 },
spherical: { radius: 0, theta: 0, phi: 0 } spherical: { radius: 0, theta: 0, phi: 0 },
}); });
// Grid configuration // Grid configuration
@@ -34,8 +32,11 @@ export function CubeScene() {
// Calculate grid position for a cube index with floating effect // Calculate grid position for a cube index with floating effect
function getGridPosition(index: number): [number, number, number] { function getGridPosition(index: number): [number, number, number] {
const x = (index % GRID_SIZE) * CUBE_SPACING - (GRID_SIZE * CUBE_SPACING) / 2; const x =
const z = Math.floor(index / GRID_SIZE) * CUBE_SPACING - (GRID_SIZE * CUBE_SPACING) / 2; (index % GRID_SIZE) * CUBE_SPACING - (GRID_SIZE * CUBE_SPACING) / 2;
const z =
Math.floor(index / GRID_SIZE) * CUBE_SPACING -
(GRID_SIZE * CUBE_SPACING) / 2;
return [x, 0.5, z]; return [x, 0.5, z];
} }
@@ -53,22 +54,23 @@ export function CubeScene() {
} }
function createBaseMaterials() { function createBaseMaterials() {
const materials = [ const materials = [
new THREE.MeshStandardMaterial({ color: 0xDCE4E5 }), // Right face - medium new THREE.MeshBasicMaterial({ color: 0xdce4e5 }), // Right face - medium
new THREE.MeshStandardMaterial({ color: 0xA4B3B5 }), // Left face - dark shadow new THREE.MeshBasicMaterial({ color: 0xa4b3b5 }), // Left face - dark shadow
new THREE.MeshStandardMaterial({ color: 0xF2F5F5 }), // Top face - light new THREE.MeshLambertMaterial({ color: 0xffffff, emissive: 0x303030 }), // Top face - light
new THREE.MeshStandardMaterial({ color: 0xA4B3B5 }), // Bottom face - dark shadow new THREE.MeshBasicMaterial({ color: 0xa4b3b5 }), // Bottom face - dark shadow
new THREE.MeshStandardMaterial({ color: 0xDCE4E5 }), // Front face - medium new THREE.MeshBasicMaterial({ color: 0xdce4e5 }), // Front face - medium
new THREE.MeshStandardMaterial({ color: 0xA4B3B5 }), // Back face - dark shadow new THREE.MeshBasicMaterial({ color: 0xa4b3b5 }), // Back face - dark shadow
]; ];
return materials; return materials;
} }
// Create white base for cube // Create white base for cube
function createCubeBase(position: [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 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(baseGeometry, baseMaterials);
base.position.set(position[0], position[1] - 0.55, position[2]); // Position below cube // 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.receiveShadow = true; base.receiveShadow = true;
return base; return base;
} }
@@ -105,26 +107,30 @@ export function CubeScene() {
} }
function updateMeshColors() { function updateMeshColors() {
for (const [id, mesh] of meshMap.entries()) { for (const [id, base] of baseMap.entries()) {
const selected = selectedIds().has(id); const selected = selectedIds().has(id);
const materials = mesh.material as THREE.Material[]; const materials = base.material as THREE.Material[];
if (selected) { if (selected) {
// When selected, make all faces red-ish but maintain the lighting difference // When selected, make all faces red-ish but maintain the lighting difference
materials.forEach((material, index) => { materials.forEach((material, index) => {
(material as THREE.MeshBasicMaterial).color.set( (material as THREE.MeshBasicMaterial).color.set(
index === 2 ? 0xff6666 : // Top face - lighter red index === 2
index === 0 || index === 4 ? 0xff4444 : // Front/right faces - medium red ? 0xff6666 // Top face - lighter red
0xcc2222 // Shadow faces - darker red : index === 0 || index === 4
? 0xdce4e5 // Front/right faces - keep
: 0xa4b3b5, // Shadow faces - keep
); );
}); });
} else { } else {
// Normal colors - restore original face colors // Normal colors - restore original face colors
materials.forEach((material, index) => { materials.forEach((material, index) => {
(material as THREE.MeshBasicMaterial).color.set( (material as THREE.MeshBasicMaterial).color.set(
index === 2 ? 0xdce4e5 : // Top face - light index === 2
index === 0 || index === 4 ? 0xb0c0c2 : // Front/right faces - medium ? 0xffffff // Top face - light
0x4d6a6b // Shadow faces - dark : index === 0 || index === 4
? 0xdce4e5 // Front/right faces - medium
: 0xa4b3b5, // Shadow faces - dark
); );
}); });
} }
@@ -141,9 +147,9 @@ export function CubeScene() {
75, 75,
container!.clientWidth / container!.clientHeight, container!.clientWidth / container!.clientHeight,
0.1, 0.1,
1000 1000,
); );
camera.position.set(10, 8, 10); camera.position.set(11, 8, -11);
camera.lookAt(0, 0, 0); camera.lookAt(0, 0, 0);
// Renderer setup // Renderer setup
@@ -153,24 +159,28 @@ export function CubeScene() {
renderer.shadowMap.type = THREE.PCFSoftShadowMap; renderer.shadowMap.type = THREE.PCFSoftShadowMap;
container.appendChild(renderer.domElement); container.appendChild(renderer.domElement);
// Lighting - Position directional light like the sun (very far away) // Lighting
const ambientLight = new THREE.AmbientLight(0x404040, 0.4); const ambientLight = new THREE.AmbientLight(0xffffff, 1.0); // Bright
scene.add(ambientLight); scene.add(ambientLight);
const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8); const directionalLight = new THREE.DirectionalLight(0xffffff, 1.0);
// Position the light very far away to simulate sun-like parallel rays // Position light at 30 degree angle (30 degrees from vertical)
directionalLight.position.set(50, 100, 50); // For 30 degree angle: tan(30°) = opposite/adjacent = x/y
// If y = 100, then x = 100 * tan(30°) = 100 * 0.577 = 57.7
directionalLight.position.set(57.7, 100, 57.7);
directionalLight.castShadow = true; directionalLight.castShadow = true;
// Configure shadow camera for a larger area with parallel shadows // Configure shadow camera for hard, crisp shadows
directionalLight.shadow.camera.left = -50; directionalLight.shadow.camera.left = -30;
directionalLight.shadow.camera.right = 50; directionalLight.shadow.camera.right = 30;
directionalLight.shadow.camera.top = 50; directionalLight.shadow.camera.top = 30;
directionalLight.shadow.camera.bottom = -50; directionalLight.shadow.camera.bottom = -30;
directionalLight.shadow.camera.near = 0.1; directionalLight.shadow.camera.near = 0.1;
directionalLight.shadow.camera.far = 200; directionalLight.shadow.camera.far = 200;
directionalLight.shadow.mapSize.width = 2048; directionalLight.shadow.mapSize.width = 4096; // Higher resolution for sharper shadows
directionalLight.shadow.mapSize.height = 2048; directionalLight.shadow.mapSize.height = 4096;
directionalLight.shadow.radius = 1; // Hard shadows (low radius)
directionalLight.shadow.blurSamples = 4; // Fewer samples for harder edges
scene.add(directionalLight); scene.add(directionalLight);
// Floor/Ground - Make it invisible but keep it for reference // Floor/Ground - Make it invisible but keep it for reference
@@ -179,7 +189,7 @@ export function CubeScene() {
color: 0xcccccc, color: 0xcccccc,
transparent: true, transparent: true,
opacity: 0, // Make completely invisible opacity: 0, // Make completely invisible
visible: false // Also hide it completely visible: false, // Also hide it completely
}); });
const floor = new THREE.Mesh(floorGeometry, floorMaterial); const floor = new THREE.Mesh(floorGeometry, floorMaterial);
floor.rotation.x = -Math.PI / 2; floor.rotation.x = -Math.PI / 2;
@@ -198,13 +208,13 @@ export function CubeScene() {
position: { position: {
x: Math.round(camera.position.x * 100) / 100, x: Math.round(camera.position.x * 100) / 100,
y: Math.round(camera.position.y * 100) / 100, y: Math.round(camera.position.y * 100) / 100,
z: Math.round(camera.position.z * 100) / 100 z: Math.round(camera.position.z * 100) / 100,
}, },
spherical: { spherical: {
radius: Math.round(spherical.radius * 100) / 100, radius: Math.round(spherical.radius * 100) / 100,
theta: Math.round(spherical.theta * 100) / 100, theta: Math.round(spherical.theta * 100) / 100,
phi: Math.round(spherical.phi * 100) / 100 phi: Math.round(spherical.phi * 100) / 100,
} },
}); });
}; };
@@ -247,10 +257,10 @@ export function CubeScene() {
}; };
// Event listeners // Event listeners
renderer.domElement.addEventListener('mousedown', onMouseDown); renderer.domElement.addEventListener("mousedown", onMouseDown);
renderer.domElement.addEventListener('mouseup', onMouseUp); renderer.domElement.addEventListener("mouseup", onMouseUp);
renderer.domElement.addEventListener('mousemove', onMouseMove); renderer.domElement.addEventListener("mousemove", onMouseMove);
renderer.domElement.addEventListener('wheel', onWheel); renderer.domElement.addEventListener("wheel", onWheel);
// Raycaster for clicking // Raycaster for clicking
raycaster = new THREE.Raycaster(); raycaster = new THREE.Raycaster();
@@ -262,12 +272,12 @@ export function CubeScene() {
const rect = renderer.domElement.getBoundingClientRect(); const rect = renderer.domElement.getBoundingClientRect();
const mouse = new THREE.Vector2( const mouse = new THREE.Vector2(
((event.clientX - rect.left) / rect.width) * 2 - 1, ((event.clientX - rect.left) / rect.width) * 2 - 1,
-((event.clientY - rect.top) / rect.height) * 2 + 1 -((event.clientY - rect.top) / rect.height) * 2 + 1,
); );
raycaster.setFromCamera(mouse, camera); raycaster.setFromCamera(mouse, camera);
const intersects = raycaster.intersectObjects( const intersects = raycaster.intersectObjects(
Array.from(meshMap.values()) Array.from(meshMap.values()),
); );
if (intersects.length > 0) { if (intersects.length > 0) {
@@ -276,7 +286,7 @@ export function CubeScene() {
} }
}; };
renderer.domElement.addEventListener('click', onClick); renderer.domElement.addEventListener("click", onClick);
// Animation loop // Animation loop
const animate = () => { const animate = () => {
@@ -291,16 +301,16 @@ export function CubeScene() {
camera.updateProjectionMatrix(); camera.updateProjectionMatrix();
renderer.setSize(container.clientWidth, container.clientHeight); renderer.setSize(container.clientWidth, container.clientHeight);
}; };
window.addEventListener('resize', handleResize); window.addEventListener("resize", handleResize);
// Cleanup function // Cleanup function
onCleanup(() => { onCleanup(() => {
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);
}); });
}); });
@@ -326,7 +336,7 @@ export function CubeScene() {
const base = createCubeBase(cube.position); const base = createCubeBase(cube.position);
base.userData.id = cube.id; base.userData.id = cube.id;
scene.add(base); scene.add(base);
// baseMap.set(cube.id, base); baseMap.set(cube.id, base);
} }
existing.delete(cube.id); existing.delete(cube.id);
}); });
@@ -351,12 +361,22 @@ export function CubeScene() {
mesh.geometry.dispose(); 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());
} else { } else {
mesh.material.dispose(); mesh.material.dispose();
} }
} }
meshMap.clear(); meshMap.clear();
for (const mesh of baseMap.values()) {
mesh.geometry.dispose();
// Handle both single material and material array
if (Array.isArray(mesh.material)) {
mesh.material.forEach((material) => material.dispose());
} else {
mesh.material.dispose();
}
}
baseMap.clear();
}); });
return ( return (
@@ -369,18 +389,28 @@ export function CubeScene() {
</div> </div>
{/* Camera Information Display */} {/* Camera Information Display */}
<div style={{ <div
style={{
"margin-bottom": "10px", "margin-bottom": "10px",
"font-family": "monospace", "font-family": "monospace",
"font-size": "12px", "font-size": "12px",
"background-color": "#f5f5f5", "background-color": "#f5f5f5",
"padding": "8px", padding: "8px",
"border-radius": "4px", "border-radius": "4px",
"border": "1px solid #ddd" border: "1px solid #ddd",
}}> }}
<div><strong>Camera Info:</strong></div> >
<div>Position: ({cameraInfo().position.x}, {cameraInfo().position.y}, {cameraInfo().position.z})</div> <div>
<div>Spherical: radius={cameraInfo().spherical.radius}, θ={cameraInfo().spherical.theta}, φ={cameraInfo().spherical.phi}</div> <strong>Camera Info:</strong>
</div>
<div>
Position: ({cameraInfo().position.x}, {cameraInfo().position.y},{" "}
{cameraInfo().position.z})
</div>
<div>
Spherical: radius={cameraInfo().spherical.radius}, θ=
{cameraInfo().spherical.theta}, φ={cameraInfo().spherical.phi}
</div>
</div> </div>
<div <div
@@ -389,7 +419,7 @@ export function CubeScene() {
width: "100%", width: "100%",
height: "500px", height: "500px",
border: "1px solid #ccc", border: "1px solid #ccc",
cursor: "grab" cursor: "grab",
}} }}
/> />
</div> </div>