ui/cubes: align with design
This commit is contained in:
15
pkgs/clan-app/ui/src/scene/cubes.css
Normal file
15
pkgs/clan-app/ui/src/scene/cubes.css
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
.cubes-scene-container {
|
||||||
|
width: 100%;
|
||||||
|
height: 100vh;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolbar-container {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 10%;
|
||||||
|
width: 100%;
|
||||||
|
z-index: 10;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
@@ -6,8 +6,14 @@ import {
|
|||||||
createMemo,
|
createMemo,
|
||||||
on,
|
on,
|
||||||
} from "solid-js";
|
} from "solid-js";
|
||||||
|
import "./cubes.css";
|
||||||
|
|
||||||
import * as THREE from "three";
|
import * as THREE from "three";
|
||||||
|
|
||||||
|
import { Toolbar } from "../components/Toolbar/Toolbar";
|
||||||
|
import { ToolbarButton } from "../components/Toolbar/ToolbarButton";
|
||||||
|
import { Divider } from "../components/Divider/Divider";
|
||||||
|
|
||||||
function garbageCollectGroup(group: THREE.Group) {
|
function garbageCollectGroup(group: THREE.Group) {
|
||||||
for (const child of group.children) {
|
for (const child of group.children) {
|
||||||
if (child instanceof THREE.Mesh) {
|
if (child instanceof THREE.Mesh) {
|
||||||
@@ -75,7 +81,7 @@ export function CubeScene() {
|
|||||||
|
|
||||||
const [ids, setIds] = createSignal<string[]>([]);
|
const [ids, setIds] = createSignal<string[]>([]);
|
||||||
const [positionMode, setPositionMode] = createSignal<"grid" | "circle">(
|
const [positionMode, setPositionMode] = createSignal<"grid" | "circle">(
|
||||||
"circle",
|
"grid",
|
||||||
);
|
);
|
||||||
const [nextBasePosition, setNextPosition] =
|
const [nextBasePosition, setNextPosition] =
|
||||||
createSignal<THREE.Vector3 | null>(null);
|
createSignal<THREE.Vector3 | null>(null);
|
||||||
@@ -104,15 +110,26 @@ export function CubeScene() {
|
|||||||
const GRID_SIZE = 2;
|
const GRID_SIZE = 2;
|
||||||
const CUBE_SPACING = 2;
|
const CUBE_SPACING = 2;
|
||||||
|
|
||||||
const BASE_SIZE = 1; // Height of the cube above the ground
|
const BASE_SIZE = 0.9; // Height of the cube above the ground
|
||||||
const CUBE_SIZE = BASE_SIZE / 1.2; // Height of the cube above the ground
|
const CUBE_SIZE = BASE_SIZE / 1.5; //
|
||||||
const BASE_HEIGHT = 0.05; // Height of the cube above the ground
|
const BASE_HEIGHT = 0.05; // Height of the cube above the ground
|
||||||
const CUBE_Y = 0 + CUBE_SIZE / 2 + BASE_HEIGHT / 2; // Y position of the cube above the ground
|
const CUBE_Y = 0 + CUBE_SIZE / 2 + BASE_HEIGHT / 2; // Y position of the cube above the ground
|
||||||
|
const CUBE_SEGMENT_HEIGHT = CUBE_SIZE / 1;
|
||||||
|
|
||||||
const FLOOR_COLOR = 0xcdd8d9;
|
const FLOOR_COLOR = 0xcdd8d9;
|
||||||
|
|
||||||
const BASE_COLOR = 0x9cbcff;
|
const CUBE_COLOR = 0xd7e0e1;
|
||||||
|
const CUBE_EMISSIVE = 0x303030; // Emissive color for cubes
|
||||||
|
|
||||||
|
const CUBE_SELECTED_COLOR = 0x4b6767;
|
||||||
|
|
||||||
|
const BASE_COLOR = 0xecfdff;
|
||||||
const BASE_EMISSIVE = 0x0c0c0c;
|
const BASE_EMISSIVE = 0x0c0c0c;
|
||||||
|
const BASE_SELECTED_COLOR = 0x69b0e3;
|
||||||
|
const BASE_SELECTED_EMISSIVE = 0x666666; // Emissive color for selected bases
|
||||||
|
|
||||||
|
const CREATE_BASE_COLOR = 0x636363;
|
||||||
|
const CREATE_BASE_EMISSIVE = 0xc5fad7;
|
||||||
|
|
||||||
function getGridPosition(
|
function getGridPosition(
|
||||||
id: string,
|
id: string,
|
||||||
@@ -191,29 +208,6 @@ export function CubeScene() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// Create multi-colored cube materials for different faces
|
|
||||||
function createCubeMaterials() {
|
|
||||||
const materials = [
|
|
||||||
new THREE.MeshLambertMaterial({ color: 0xb0c0cf, emissive: 0x303030 }), // Right face - medium
|
|
||||||
new THREE.MeshLambertMaterial({ color: 0xb0c0cf, emissive: 0x303030 }), // Left face - dark shadow
|
|
||||||
new THREE.MeshBasicMaterial({ color: 0xdce4e5 }), // Top face - light
|
|
||||||
new THREE.MeshLambertMaterial({ color: 0xb0c0cf, emissive: 0x303030 }), // Bottom face - dark shadow
|
|
||||||
new THREE.MeshLambertMaterial({ color: 0xb0c0cf, emissive: 0x303030 }), // Front face - medium
|
|
||||||
new THREE.MeshLambertMaterial({ color: 0xb0c0cf, emissive: 0x303030 }), // Back face - dark shadow
|
|
||||||
];
|
|
||||||
return materials;
|
|
||||||
}
|
|
||||||
|
|
||||||
function createBaseMaterials(opacity: number) {
|
|
||||||
return new THREE.MeshLambertMaterial({
|
|
||||||
color: BASE_COLOR,
|
|
||||||
emissive: BASE_EMISSIVE,
|
|
||||||
flatShading: true,
|
|
||||||
transparent: true,
|
|
||||||
opacity,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Animation helper function
|
// Animation helper function
|
||||||
function animateToPosition(
|
function animateToPosition(
|
||||||
thing: THREE.Object3D,
|
thing: THREE.Object3D,
|
||||||
@@ -254,25 +248,25 @@ export function CubeScene() {
|
|||||||
baseMesh.scale.setScalar(0);
|
baseMesh.scale.setScalar(0);
|
||||||
|
|
||||||
// Ensure materials are fully opaque
|
// Ensure materials are fully opaque
|
||||||
if (Array.isArray(mesh.material)) {
|
// if (Array.isArray(mesh.material)) {
|
||||||
mesh.material.forEach((material) => {
|
// mesh.material.forEach((material) => {
|
||||||
(material as THREE.MeshBasicMaterial).opacity = 1;
|
// (material as THREE.MeshBasicMaterial).opacity = 1;
|
||||||
material.transparent = false;
|
// material.transparent = false;
|
||||||
});
|
// });
|
||||||
} else {
|
// } else {
|
||||||
(mesh.material as THREE.MeshBasicMaterial).opacity = 1;
|
// (mesh.material as THREE.MeshBasicMaterial).opacity = 1;
|
||||||
mesh.material.transparent = false;
|
// mesh.material.transparent = false;
|
||||||
}
|
// }
|
||||||
|
|
||||||
if (Array.isArray(baseMesh.material)) {
|
// if (Array.isArray(baseMesh.material)) {
|
||||||
baseMesh.material.forEach((material) => {
|
// baseMesh.material.forEach((material) => {
|
||||||
(material as THREE.MeshBasicMaterial).opacity = 1;
|
// (material as THREE.MeshBasicMaterial).opacity = 1;
|
||||||
material.transparent = false;
|
// material.transparent = false;
|
||||||
});
|
// });
|
||||||
} else {
|
// } else {
|
||||||
(baseMesh.material as THREE.MeshBasicMaterial).opacity = 1;
|
// (baseMesh.material as THREE.MeshBasicMaterial).opacity = 1;
|
||||||
baseMesh.material.transparent = false;
|
// baseMesh.material.transparent = false;
|
||||||
}
|
// }
|
||||||
|
|
||||||
function animate() {
|
function animate() {
|
||||||
const elapsed = Date.now() - startTime;
|
const elapsed = Date.now() - startTime;
|
||||||
@@ -336,12 +330,19 @@ export function CubeScene() {
|
|||||||
|
|
||||||
function createCubeBase(
|
function createCubeBase(
|
||||||
cube_pos: [number, number, number],
|
cube_pos: [number, number, number],
|
||||||
opacity: number = 1,
|
opacity = 1,
|
||||||
|
color: THREE.ColorRepresentation = BASE_COLOR,
|
||||||
|
emissive: THREE.ColorRepresentation = BASE_EMISSIVE,
|
||||||
) {
|
) {
|
||||||
const baseMaterial = createBaseMaterials(opacity);
|
const baseMaterial = new THREE.MeshPhongMaterial({
|
||||||
|
opacity,
|
||||||
|
color,
|
||||||
|
emissive,
|
||||||
|
// flatShading: true,
|
||||||
|
transparent: true,
|
||||||
|
});
|
||||||
const base = new THREE.Mesh(sharedBaseGeometry, baseMaterial);
|
const base = new THREE.Mesh(sharedBaseGeometry, baseMaterial);
|
||||||
// tranlate_y = - cube_height / 2 - base_height / 2
|
base.position.set(cube_pos[0], BASE_HEIGHT / 2, cube_pos[2]);
|
||||||
base.position.set(cube_pos[0], BASE_HEIGHT / 2, cube_pos[2]); // Position Y=0 on the floor
|
|
||||||
base.receiveShadow = true;
|
base.receiveShadow = true;
|
||||||
return base;
|
return base;
|
||||||
}
|
}
|
||||||
@@ -419,27 +420,62 @@ export function CubeScene() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateMeshColors() {
|
function updateMeshColors(
|
||||||
for (const [id, group] of groupMap.entries()) {
|
selected: Set<string>,
|
||||||
const selected = selectedIds().has(id);
|
prev: Set<string> | undefined,
|
||||||
|
) {
|
||||||
|
for (const id of selected) {
|
||||||
|
const group = groupMap.get(id);
|
||||||
|
if (!group) {
|
||||||
|
console.warn(`UPDATE COLORS: Group not found for id: ${id}`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
const base = group.children.find((child) => child.name === "base");
|
const base = group.children.find((child) => child.name === "base");
|
||||||
|
|
||||||
if (!base || !(base instanceof THREE.Mesh)) {
|
if (!base || !(base instanceof THREE.Mesh)) {
|
||||||
// console.warn(`UPDATE COLORS: Base mesh not found for id: ${id}`);
|
console.warn(`UPDATE COLORS: Base mesh not found for id: ${id}`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const cube = group.children.find((child) => child.name === "cube");
|
||||||
|
if (!cube || !(cube instanceof THREE.Mesh)) {
|
||||||
|
console.warn(`UPDATE COLORS: Cube mesh not found for id: ${id}`);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const material = base.material as THREE.MeshLambertMaterial;
|
const baseMaterial = base.material as THREE.MeshPhongMaterial;
|
||||||
|
const cubeMaterial = cube.material as THREE.MeshPhongMaterial;
|
||||||
|
|
||||||
if (selected) {
|
baseMaterial.color.set(BASE_SELECTED_COLOR);
|
||||||
// When selected, make all faces red-ish but maintain the lighting difference
|
baseMaterial.emissive.set(BASE_SELECTED_EMISSIVE);
|
||||||
material.color.set(0xffffff);
|
|
||||||
material.emissive.set(0xff6666);
|
cubeMaterial.color.set(CUBE_SELECTED_COLOR);
|
||||||
} else {
|
|
||||||
// Normal colors - restore original face colors
|
|
||||||
material.color.set(BASE_COLOR);
|
|
||||||
material.emissive.set(BASE_EMISSIVE);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const deselected = Array.from(prev || []).filter((s) => !selected.has(s));
|
||||||
|
|
||||||
|
for (const id of deselected) {
|
||||||
|
const group = groupMap.get(id);
|
||||||
|
if (!group) {
|
||||||
|
console.warn(`UPDATE COLORS: Group not found for id: ${id}`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const base = group.children.find((child) => child.name === "base");
|
||||||
|
if (!base || !(base instanceof THREE.Mesh)) {
|
||||||
|
console.warn(`UPDATE COLORS: Base mesh not found for id: ${id}`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const cube = group.children.find((child) => child.name === "cube");
|
||||||
|
if (!cube || !(cube instanceof THREE.Mesh)) {
|
||||||
|
console.warn(`UPDATE COLORS: Cube mesh not found for id: ${id}`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const baseMaterial = base.material as THREE.MeshPhongMaterial;
|
||||||
|
const cubeMaterial = cube.material as THREE.MeshPhongMaterial;
|
||||||
|
|
||||||
|
baseMaterial.color.set(BASE_COLOR);
|
||||||
|
baseMaterial.emissive.set(BASE_EMISSIVE);
|
||||||
|
|
||||||
|
cubeMaterial.color.set(CUBE_COLOR);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -455,7 +491,7 @@ export function CubeScene() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const initialCameraPosition = { x: 2.8, y: 3.6, z: -2 };
|
const initialCameraPosition = { x: 2.8, y: 4, z: -2 };
|
||||||
const initialSphericalCameraPosition = new THREE.Spherical();
|
const initialSphericalCameraPosition = new THREE.Spherical();
|
||||||
initialSphericalCameraPosition.setFromVector3(
|
initialSphericalCameraPosition.setFromVector3(
|
||||||
new THREE.Vector3(
|
new THREE.Vector3(
|
||||||
@@ -479,8 +515,8 @@ export function CubeScene() {
|
|||||||
// Create a fullscreen quad with a gradient shader
|
// Create a fullscreen quad with a gradient shader
|
||||||
// TODO: Recalculate gradient depending on container size
|
// TODO: Recalculate gradient depending on container size
|
||||||
const uniforms = {
|
const uniforms = {
|
||||||
colorTop: { value: new THREE.Color("#E6EAEA") }, // Top color
|
colorTop: { value: new THREE.Color("#edf1f1") }, // Top color
|
||||||
colorBottom: { value: new THREE.Color("#C5D1D2") }, // Bottom color
|
colorBottom: { value: new THREE.Color("#e3e7e7") }, // Bottom color
|
||||||
resolution: {
|
resolution: {
|
||||||
value: new THREE.Vector2(window.innerWidth, window.innerHeight),
|
value: new THREE.Vector2(window.innerWidth, window.innerHeight),
|
||||||
},
|
},
|
||||||
@@ -530,18 +566,21 @@ export function CubeScene() {
|
|||||||
container.appendChild(renderer.domElement);
|
container.appendChild(renderer.domElement);
|
||||||
|
|
||||||
// Lighting
|
// Lighting
|
||||||
const ambientLight = new THREE.AmbientLight(0xffffff, 0.5); // Bright
|
const ambientLight = new THREE.AmbientLight(0xffffff, 1.5);
|
||||||
scene.add(ambientLight);
|
scene.add(ambientLight);
|
||||||
|
|
||||||
const directionalLight = new THREE.DirectionalLight(0xffffff, 0.9);
|
const directionalLight = new THREE.DirectionalLight(0xffffff, 2);
|
||||||
|
|
||||||
|
// scene.add(new THREE.DirectionalLightHelper(directionalLight));
|
||||||
|
// scene.add(new THREE.CameraHelper(directionalLight.shadow.camera));
|
||||||
|
// scene.add(new THREE.CameraHelper(camera));
|
||||||
const lightPos = new THREE.Spherical(
|
const lightPos = new THREE.Spherical(
|
||||||
100,
|
100,
|
||||||
initialSphericalCameraPosition.phi,
|
initialSphericalCameraPosition.phi,
|
||||||
initialSphericalCameraPosition.theta,
|
initialSphericalCameraPosition.theta - Math.PI / 2,
|
||||||
);
|
);
|
||||||
lightPos.theta = initialSphericalCameraPosition.theta - Math.PI / 2; // 90 degrees offset
|
|
||||||
directionalLight.position.setFromSpherical(lightPos);
|
directionalLight.position.setFromSpherical(lightPos);
|
||||||
|
directionalLight.target.position.set(0, 0, 0); // Point light at the center
|
||||||
|
|
||||||
// initialSphericalCameraPosition
|
// initialSphericalCameraPosition
|
||||||
directionalLight.castShadow = true;
|
directionalLight.castShadow = true;
|
||||||
@@ -558,14 +597,14 @@ export function CubeScene() {
|
|||||||
directionalLight.shadow.radius = 1; // Hard shadows (low radius)
|
directionalLight.shadow.radius = 1; // Hard shadows (low radius)
|
||||||
directionalLight.shadow.blurSamples = 4; // Fewer samples for harder edges
|
directionalLight.shadow.blurSamples = 4; // Fewer samples for harder edges
|
||||||
scene.add(directionalLight);
|
scene.add(directionalLight);
|
||||||
|
scene.add(directionalLight.target);
|
||||||
|
|
||||||
// Floor/Ground - Make it invisible but keep it for reference
|
// Floor/Ground - Make it invisible but keep it for reference
|
||||||
const floorGeometry = new THREE.PlaneGeometry(1000, 1000);
|
const floorGeometry = new THREE.PlaneGeometry(1000, 1000);
|
||||||
const floorMaterial = new THREE.MeshBasicMaterial({
|
const floorMaterial = new THREE.MeshBasicMaterial({
|
||||||
color: FLOOR_COLOR,
|
color: FLOOR_COLOR,
|
||||||
transparent: true,
|
transparent: true,
|
||||||
opacity: 0.1, // Make completely invisible
|
opacity: 0.0, // Make completely invisible
|
||||||
// visible: false, // Also hide it completely
|
|
||||||
});
|
});
|
||||||
floor = new THREE.Mesh(floorGeometry, floorMaterial);
|
floor = new THREE.Mesh(floorGeometry, floorMaterial);
|
||||||
floor.rotation.x = -Math.PI / 2;
|
floor.rotation.x = -Math.PI / 2;
|
||||||
@@ -582,7 +621,11 @@ export function CubeScene() {
|
|||||||
|
|
||||||
// Shared geometries for cubes and bases
|
// Shared geometries for cubes and bases
|
||||||
// This allows us to reuse the same geometry for all cubes and bases
|
// This allows us to reuse the same geometry for all cubes and bases
|
||||||
sharedCubeGeometry = new THREE.BoxGeometry(CUBE_SIZE, CUBE_SIZE, CUBE_SIZE);
|
sharedCubeGeometry = new THREE.BoxGeometry(
|
||||||
|
CUBE_SIZE,
|
||||||
|
CUBE_SEGMENT_HEIGHT,
|
||||||
|
CUBE_SIZE,
|
||||||
|
);
|
||||||
sharedBaseGeometry = new THREE.BoxGeometry(
|
sharedBaseGeometry = new THREE.BoxGeometry(
|
||||||
BASE_SIZE,
|
BASE_SIZE,
|
||||||
BASE_HEIGHT,
|
BASE_HEIGHT,
|
||||||
@@ -651,7 +694,9 @@ export function CubeScene() {
|
|||||||
// Create initial base mesh if it doesn't exist
|
// Create initial base mesh if it doesn't exist
|
||||||
initBase = createCubeBase(
|
initBase = createCubeBase(
|
||||||
[snapped.x, BASE_HEIGHT / 2, snapped.z],
|
[snapped.x, BASE_HEIGHT / 2, snapped.z],
|
||||||
0.5,
|
1,
|
||||||
|
CREATE_BASE_COLOR,
|
||||||
|
CREATE_BASE_EMISSIVE, // Emissive color
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
initBase.position.set(snapped.x, BASE_HEIGHT / 2, snapped.z);
|
initBase.position.set(snapped.x, BASE_HEIGHT / 2, snapped.z);
|
||||||
@@ -676,12 +721,12 @@ export function CubeScene() {
|
|||||||
// spherical.phi += deltaY * 0.01;
|
// spherical.phi += deltaY * 0.01;
|
||||||
// spherical.phi = Math.max(0.1, Math.min(Math.PI - 0.1, spherical.phi));
|
// spherical.phi = Math.max(0.1, Math.min(Math.PI - 0.1, spherical.phi));
|
||||||
|
|
||||||
const lightPos = new THREE.Spherical();
|
// const lightPos = new THREE.Spherical();
|
||||||
lightPos.setFromVector3(directionalLight.position);
|
// lightPos.setFromVector3(directionalLight.position);
|
||||||
lightPos.theta = spherical.theta - Math.PI / 2; // 90 degrees offset
|
// lightPos.theta = spherical.theta - Math.PI / 2; // 90 degrees offset
|
||||||
directionalLight.position.setFromSpherical(lightPos);
|
// directionalLight.position.setFromSpherical(lightPos);
|
||||||
|
|
||||||
directionalLight.lookAt(0, 0, 0);
|
// directionalLight.lookAt(0, 0, 0);
|
||||||
|
|
||||||
camera.position.setFromSpherical(spherical);
|
camera.position.setFromSpherical(spherical);
|
||||||
camera.lookAt(0, 0, 0);
|
camera.lookAt(0, 0, 0);
|
||||||
@@ -756,7 +801,7 @@ export function CubeScene() {
|
|||||||
const intersects = raycaster.intersectObjects(
|
const intersects = raycaster.intersectObjects(
|
||||||
Array.from(groupMap.values()),
|
Array.from(groupMap.values()),
|
||||||
);
|
);
|
||||||
|
console.log("Intersects:", intersects);
|
||||||
if (intersects.length > 0) {
|
if (intersects.length > 0) {
|
||||||
const id = intersects[0].object.userData.id;
|
const id = intersects[0].object.userData.id;
|
||||||
toggleSelection(id);
|
toggleSelection(id);
|
||||||
@@ -805,9 +850,12 @@ export function CubeScene() {
|
|||||||
|
|
||||||
if (initBase) {
|
if (initBase) {
|
||||||
initBase.geometry.dispose();
|
initBase.geometry.dispose();
|
||||||
// @ts-ignore: Not sure why this is needed
|
if (Array.isArray(initBase.material)) {
|
||||||
|
initBase.material.forEach((material) => material.dispose());
|
||||||
|
} else {
|
||||||
initBase.material.dispose();
|
initBase.material.dispose();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (container) {
|
if (container) {
|
||||||
container.innerHTML = "";
|
container.innerHTML = "";
|
||||||
@@ -839,12 +887,17 @@ export function CubeScene() {
|
|||||||
|
|
||||||
function createCube(
|
function createCube(
|
||||||
gridPosition: [number, number],
|
gridPosition: [number, number],
|
||||||
userData: { id: string; [key: string]: any },
|
userData: { id: string },
|
||||||
) {
|
) {
|
||||||
// Creates a cube, base, and other visuals
|
// Creates a cube, base, and other visuals
|
||||||
// Groups them together in the scene
|
// Groups them together in the scene
|
||||||
const cubeMaterials = createCubeMaterials();
|
const cubeMaterial = new THREE.MeshPhongMaterial({
|
||||||
const cubeMesh = new THREE.Mesh(sharedCubeGeometry, cubeMaterials);
|
color: CUBE_COLOR,
|
||||||
|
emissive: CUBE_EMISSIVE,
|
||||||
|
// specular: 0xffffff,
|
||||||
|
shininess: 100,
|
||||||
|
});
|
||||||
|
const cubeMesh = new THREE.Mesh(sharedCubeGeometry, cubeMaterial);
|
||||||
cubeMesh.castShadow = true;
|
cubeMesh.castShadow = true;
|
||||||
cubeMesh.receiveShadow = true;
|
cubeMesh.receiveShadow = true;
|
||||||
cubeMesh.userData = userData;
|
cubeMesh.userData = userData;
|
||||||
@@ -921,10 +974,16 @@ export function CubeScene() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
updateMeshColors();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
createEffect(
|
||||||
|
on(selectedIds, (curr, prev) => {
|
||||||
|
console.log("Selected cubes:", curr);
|
||||||
|
// Update colors of selected cubes
|
||||||
|
updateMeshColors(curr, prev);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
// Effect to clean up deleted cubes after animation
|
// Effect to clean up deleted cubes after animation
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
const deleting = deletingIds();
|
const deleting = deletingIds();
|
||||||
@@ -955,7 +1014,7 @@ export function CubeScene() {
|
|||||||
|
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
selectedIds(); // Track the signal
|
selectedIds(); // Track the signal
|
||||||
updateMeshColors();
|
// updateMeshColors();
|
||||||
});
|
});
|
||||||
|
|
||||||
onCleanup(() => {
|
onCleanup(() => {
|
||||||
@@ -981,7 +1040,12 @@ export function CubeScene() {
|
|||||||
|
|
||||||
if (!initBase) {
|
if (!initBase) {
|
||||||
// Create initial base mesh if it doesn't exist
|
// Create initial base mesh if it doesn't exist
|
||||||
initBase = createCubeBase([0, BASE_HEIGHT / 2, 0], 0.5);
|
initBase = createCubeBase(
|
||||||
|
[0, BASE_HEIGHT / 2, 0],
|
||||||
|
1,
|
||||||
|
CREATE_BASE_COLOR,
|
||||||
|
CREATE_BASE_EMISSIVE,
|
||||||
|
); // Emissive color
|
||||||
}
|
}
|
||||||
if (inside) {
|
if (inside) {
|
||||||
scene.add(initBase);
|
scene.add(initBase);
|
||||||
@@ -996,79 +1060,37 @@ export function CubeScene() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style="width: 100%; height: 100%; position: relative;">
|
<>
|
||||||
<div style={{ "margin-bottom": "10px" }}>
|
<div class="cubes-scene-container" ref={(el) => (container = el)} />
|
||||||
<span style={{ "margin-left": "10px" }}>
|
<div class="toolbar-container">
|
||||||
Selected: {selectedIds().size} cubes | Total: {ids().length} cubes
|
<Toolbar>
|
||||||
</span>
|
<ToolbarButton
|
||||||
<span>
|
name="new-machine"
|
||||||
{" | "}
|
icon="NewMachine"
|
||||||
World Mode: {worldMode()}
|
|
||||||
{" | "}
|
|
||||||
Position Mode: {positionMode()}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div
|
|
||||||
ref={(el) => (container = el)}
|
|
||||||
style={{
|
|
||||||
width: "100%",
|
|
||||||
height: "80vh",
|
|
||||||
cursor: "pointer",
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{/* Camera Information Display */}
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
"margin-bottom": "10px",
|
|
||||||
"font-family": "monospace",
|
|
||||||
"font-size": "12px",
|
|
||||||
"background-color": "#f5f5f5",
|
|
||||||
padding: "8px",
|
|
||||||
"border-radius": "4px",
|
|
||||||
border: "1px solid #ddd",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
position: "absolute",
|
|
||||||
bottom: "70px",
|
|
||||||
left: "0",
|
|
||||||
display: "flex",
|
|
||||||
"flex-direction": "row",
|
|
||||||
width: "100%",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<button
|
|
||||||
onClick={onAddClick}
|
|
||||||
onMouseEnter={onHover(true)}
|
onMouseEnter={onHover(true)}
|
||||||
onMouseLeave={onHover(false)}
|
onMouseLeave={onHover(false)}
|
||||||
>
|
onClick={onAddClick}
|
||||||
Add Cube
|
selected={worldMode() === "create"}
|
||||||
</button>
|
/>
|
||||||
<button onClick={() => deleteSelectedCubes(selectedIds())}>
|
<Divider orientation="vertical" />
|
||||||
Delete Selected
|
<ToolbarButton
|
||||||
</button>
|
name="modules"
|
||||||
<button onClick={() => setPositionMode("grid")}>
|
icon="Modules"
|
||||||
Grid Positioning
|
onClick={() => {
|
||||||
</button>
|
if (positionMode() === "grid") {
|
||||||
<button onClick={() => setPositionMode("circle")}>
|
setPositionMode("circle");
|
||||||
Circle Positioning
|
} else {
|
||||||
</button>
|
setPositionMode("grid");
|
||||||
</div>
|
}
|
||||||
<div>
|
}}
|
||||||
<strong>Camera Info:</strong>
|
/>
|
||||||
</div>
|
<ToolbarButton
|
||||||
<div>
|
name="delete"
|
||||||
Position: ({cameraInfo().position.x}, {cameraInfo().position.y},{" "}
|
icon="Trash"
|
||||||
{cameraInfo().position.z})
|
onClick={() => deleteSelectedCubes(selectedIds())}
|
||||||
</div>
|
/>
|
||||||
<div>
|
</Toolbar>
|
||||||
Spherical: radius={cameraInfo().spherical.radius}, θ=
|
|
||||||
{cameraInfo().spherical.theta}, φ={cameraInfo().spherical.phi}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user