UI: dispay selected cube base
This commit is contained in:
@@ -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
|
||||||
"margin-bottom": "10px",
|
style={{
|
||||||
"font-family": "monospace",
|
"margin-bottom": "10px",
|
||||||
"font-size": "12px",
|
"font-family": "monospace",
|
||||||
"background-color": "#f5f5f5",
|
"font-size": "12px",
|
||||||
"padding": "8px",
|
"background-color": "#f5f5f5",
|
||||||
"border-radius": "4px",
|
padding: "8px",
|
||||||
"border": "1px solid #ddd"
|
"border-radius": "4px",
|
||||||
}}>
|
border: "1px solid #ddd",
|
||||||
<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>
|
||||||
|
<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>
|
||||||
|
|||||||
Reference in New Issue
Block a user