From e633a0ad9eb658ea1e007a711c4e507d36e6fac5 Mon Sep 17 00:00:00 2001 From: Igorxp5 Date: Sat, 18 May 2024 14:51:59 -0300 Subject: [PATCH 01/11] removes rotation and translation (anchor) --- src/components/3D/Brick.jsx | 26 ++--------- src/components/3D/BrickCursor.jsx | 23 +++------- src/components/3D/BrickOutline.jsx | 16 +------ src/components/3D/Scene.jsx | 14 +----- src/components/UI/Dialog/DialogBox.jsx | 7 --- src/components/UI/Panel/Checkbox/index.jsx | 39 ----------------- src/components/UI/Panel/Checkbox/styles.css | 28 ------------ src/components/UI/Panel/Slider/index.jsx | 4 -- src/components/UI/Panel/index.jsx | 4 -- src/store/index.js | 10 ----- src/utils/constants.js | 1 - src/utils/hooks.js | 48 --------------------- 12 files changed, 12 insertions(+), 208 deletions(-) delete mode 100644 src/components/UI/Panel/Checkbox/index.jsx delete mode 100644 src/components/UI/Panel/Checkbox/styles.css diff --git a/src/components/3D/Brick.jsx b/src/components/3D/Brick.jsx index 512fecc..404b77d 100644 --- a/src/components/3D/Brick.jsx +++ b/src/components/3D/Brick.jsx @@ -16,8 +16,6 @@ export const Brick = ({ intersect, color = "#ff0000", dimensions = { x: 1, z: 1 }, - rotation = 0, - translation = { x: 0, z: 0 }, bricksBoundBox = { current: [] }, uID = "", onClick = () => {}, @@ -32,10 +30,8 @@ export const Brick = ({ }, [width, height, depth, dimensions]); const position = useMemo(() => { - const evenWidth = - rotation === 0 ? dimensions.x % 2 === 0 : dimensions.z % 2 === 0; - const evenDepth = - rotation === 0 ? dimensions.z % 2 === 0 : dimensions.x % 2 === 0; + const evenWidth = dimensions.x % 2 === 0; + const evenDepth = dimensions.z % 2 === 0; return new Vector3() .copy(intersect.point) @@ -50,7 +46,7 @@ export const Brick = ({ evenDepth ? base : base / 2 ) ); - }, [intersect, dimensions.x, dimensions.z, height, rotation]); + }, [intersect, dimensions.x, dimensions.z, height]); useEffect(() => { const brickBoundingBox = new Box3().setFromObject(brickRef.current); @@ -69,27 +65,15 @@ export const Brick = ({ }; }, [uID, bricksBoundBox]); - const compansate = { + const offset = { x: dimensions.x % 2 === 0 ? dimensions.x / 2 : (dimensions.x - 1) / 2, z: dimensions.z % 2 === 0 ? dimensions.z / 2 : (dimensions.z - 1) / 2, }; - const offset = { - x: - Math.sign(translation.x) < 0 - ? Math.max(translation.x, -compansate.x) - : Math.min(translation.x, compansate.x), - z: - Math.sign(translation.z) < 0 - ? Math.max(translation.z, -compansate.z) - : Math.min(translation.z, compansate.z), - }; - return ( <> { @@ -27,8 +25,8 @@ export const BrickCursor = forwardRef( const { height, width, depth } = getMeasurementsFromDimensions(dimensions); const position = useMemo(() => { - const evenWidth = rotation === 0 ? width % 2 === 0 : depth % 2 === 0; - const evenDepth = rotation === 0 ? depth % 2 === 0 : width % 2 === 0; + const evenWidth = width % 2 === 0; + const evenDepth = depth % 2 === 0; return new Vector3() .copy(intersect.point) @@ -43,29 +41,18 @@ export const BrickCursor = forwardRef( evenDepth ? base : base / 2 ) ); - }, [intersect, height, rotation, width, depth]); - - const compansateX = - dimensions.x % 2 === 0 ? dimensions.x / 2 : (dimensions.x - 1) / 2; - const compansateZ = - dimensions.z % 2 === 0 ? dimensions.z / 2 : (dimensions.z - 1) / 2; + }, [intersect, height, width, depth]); const offsetX = - Math.sign(translation.x) < 0 - ? Math.max(translation.x, -compansateX) - : Math.min(translation.x, compansateX); - + dimensions.x % 2 === 0 ? dimensions.x / 2 : (dimensions.x - 1) / 2; const offsetZ = - Math.sign(translation.z) < 0 - ? Math.max(translation.z, -compansateZ) - : Math.min(translation.z, compansateZ); + dimensions.z % 2 === 0 ? dimensions.z / 2 : (dimensions.z - 1) / 2; return ( <> { if (!ref.current) return; meshesData.forEach((meshData, i) => { - const compansate = { + const offset = { x: dimensions.x % 2 === 0 ? dimensions.x / 2 : (dimensions.x - 1) / 2, z: dimensions.z % 2 === 0 ? dimensions.z / 2 : (dimensions.z - 1) / 2, }; - const translation = meshData.translation; - - const offset = { - x: - Math.sign(translation.x) < 0 - ? Math.max(translation.x, -compansate.x) - : Math.min(translation.x, compansate.x), - z: - Math.sign(translation.z) < 0 - ? Math.max(translation.z, -compansate.z) - : Math.min(translation.z, compansate.z), - }; - - dummy.rotation.set(0, meshData.rotation, 0); dummy.position.set( meshData.position.x + (offset.x * width) / dimensions.x, Math.abs(meshData.position.y), diff --git a/src/components/3D/Scene.jsx b/src/components/3D/Scene.jsx index 065b371..c315bad 100644 --- a/src/components/3D/Scene.jsx +++ b/src/components/3D/Scene.jsx @@ -15,7 +15,6 @@ import { uID, getMeasurementsFromDimensions, base, - useAnchorShorcuts, minWorkSpaceSize, EDIT_MODE, } from "../../utils"; @@ -43,16 +42,11 @@ export const Scene = () => { const width = useStore((state) => state.width); const depth = useStore((state) => state.depth); - const anchorX = useStore((state) => state.anchorX); - const anchorZ = useStore((state) => state.anchorZ); - const rotate = useStore((state) => state.rotate); const color = useStore((state) => state.color); const room = useStore((state) => state.liveblocks.room); const self = useStore((state) => state.self); - useAnchorShorcuts(); - const addBrick = (e) => { e.stopPropagation(); @@ -103,9 +97,7 @@ export const Scene = () => { intersect: { point: e.point, face: e.face }, uID: uID(), dimensions: { x: width, z: depth }, - rotation: rotate ? Math.PI / 2 : 0, color: color, - translation: { x: anchorX, z: anchorZ }, }; setBricks((prevBricks) => [...prevBricks, brickData]); @@ -124,8 +116,8 @@ export const Scene = () => { z: depth, }); - const evenWidth = !rotate ? width % 2 === 0 : depth % 2 === 0; - const evenDepth = !rotate ? depth % 2 === 0 : width % 2 === 0; + const evenWidth = width % 2 === 0; + const evenDepth = depth % 2 === 0; mousePoint.set(e.point.x, Math.abs(e.point.y), e.point.z); normal.set(e.face.normal.x, Math.abs(e.face.normal.y), e.face.normal.z); @@ -215,9 +207,7 @@ export const Scene = () => { /> diff --git a/src/components/UI/Dialog/DialogBox.jsx b/src/components/UI/Dialog/DialogBox.jsx index 582dc17..47ca363 100644 --- a/src/components/UI/Dialog/DialogBox.jsx +++ b/src/components/UI/Dialog/DialogBox.jsx @@ -30,13 +30,6 @@ export const DialogBox = () => (
  • Use left side panel to change the properties of brick
  • -
  • - You change the pivot point of bricks using{" "} - A S{" "} - W and D{" "} - keys or You can use anchorX and{" "} - anchorZ option from the control panel -
  • To delete brick, select Edit option in control panel then select brick by clicking on brick or hold{" "} diff --git a/src/components/UI/Panel/Checkbox/index.jsx b/src/components/UI/Panel/Checkbox/index.jsx deleted file mode 100644 index e176d69..0000000 --- a/src/components/UI/Panel/Checkbox/index.jsx +++ /dev/null @@ -1,39 +0,0 @@ -/* eslint-disable react/prop-types */ -/* eslint-disable no-unused-vars */ -import React from "react"; -import * as CheckboxRadix from "@radix-ui/react-checkbox"; -import { CheckIcon } from "@radix-ui/react-icons"; -import "./styles.css"; -import { useStore } from "../../../../store"; - -const stateMap = { - rotate: "rotate", -}; - -const setterMap = { - rotate: "setRotate", -}; - -export const Checkbox = ({ label = "rotate" }) => { - const setValue = useStore((state) => state[setterMap[label]]); - - return ( -
    - - setValue(bool)} - > - - - - -
    - ); -}; diff --git a/src/components/UI/Panel/Checkbox/styles.css b/src/components/UI/Panel/Checkbox/styles.css deleted file mode 100644 index 9624c5e..0000000 --- a/src/components/UI/Panel/Checkbox/styles.css +++ /dev/null @@ -1,28 +0,0 @@ -@import "@radix-ui/colors/black-alpha.css"; -@import "@radix-ui/colors/violet.css"; - -/* reset */ -button { - all: unset; -} - -.CheckboxRoot { - background-color: var(--black-a7); - width: 20px; - height: 20px; - border-radius: 4px; - display: flex; - align-items: center; - justify-content: center; - box-shadow: 0 2px 10px var(--black-a7); -} -.CheckboxRoot:hover { - cursor: pointer; -} -.CheckboxRoot:focus { - box-shadow: 0 0 0 2px black; -} - -.CheckboxIndicator { - color: var(--violet-11); -} diff --git a/src/components/UI/Panel/Slider/index.jsx b/src/components/UI/Panel/Slider/index.jsx index 3698f5b..0f47753 100644 --- a/src/components/UI/Panel/Slider/index.jsx +++ b/src/components/UI/Panel/Slider/index.jsx @@ -8,15 +8,11 @@ import { useStore } from "../../../../store"; const stateMap = { width: "width", depth: "depth", - "anchor X": "anchorX", - "anchor Z": "anchorZ", }; const setterMap = { width: "setWidth", depth: "setDepth", - "anchor X": "setAnchorX", - "anchor Z": "setAnchorZ", }; export const SliderWithLabel = ({ diff --git a/src/components/UI/Panel/index.jsx b/src/components/UI/Panel/index.jsx index 8f365da..6aebf66 100644 --- a/src/components/UI/Panel/index.jsx +++ b/src/components/UI/Panel/index.jsx @@ -3,7 +3,6 @@ import React from "react"; import "./styles.css"; import { SliderWithLabel } from "./Slider"; -import { Checkbox } from "./Checkbox"; import { ColorInput } from "./ColorInput"; export const Panel = () => { @@ -11,9 +10,6 @@ export const Panel = () => {
    - - -
    ); diff --git a/src/store/index.js b/src/store/index.js index 3b0b15f..72858f0 100644 --- a/src/store/index.js +++ b/src/store/index.js @@ -1,7 +1,6 @@ import { create } from "zustand"; import { CREATE_MODE, - defaultAnchor, defaultWidth, generateSoftColors, uID, @@ -28,15 +27,6 @@ export const useStore = create( depth: defaultWidth, setDepth: (newDepth) => set({ depth: newDepth }), - anchorX: defaultAnchor, - setAnchorX: (newAnchorPoint) => set({ anchorX: newAnchorPoint }), - - anchorZ: defaultAnchor, - setAnchorZ: (newAnchorPoint) => set({ anchorZ: newAnchorPoint }), - - rotate: false, - setRotate: (bool) => set({ rotate: bool }), - color: "#ff0000", setColor: (newColor) => set({ color: newColor }), diff --git a/src/utils/constants.js b/src/utils/constants.js index 9ed463e..b756022 100644 --- a/src/utils/constants.js +++ b/src/utils/constants.js @@ -7,7 +7,6 @@ export const CREATE_MODE = "Create Mode"; export const EDIT_MODE = "Edit Mode"; export const defaultWidth = 1; -export const defaultAnchor = 0; export const bricks = [ { x: 1, z: 1 }, diff --git a/src/utils/hooks.js b/src/utils/hooks.js index 6132e51..197c306 100644 --- a/src/utils/hooks.js +++ b/src/utils/hooks.js @@ -1,52 +1,4 @@ import useKeyboardShortcut from "use-keyboard-shortcut"; -import { useStore } from "../store"; - -export const useAnchorShorcuts = () => { - const setAnchorX = useStore((state) => state.setAnchorX); - const setAnchorZ = useStore((state) => state.setAnchorZ); - - const anchorXPlus = () => { - setAnchorX(useStore.getState().anchorX + 1); - }; - - const anchorXMinus = () => { - setAnchorX(useStore.getState().anchorX - 1); - }; - - const anchorZPlus = () => { - setAnchorZ(useStore.getState().anchorZ + 1); - }; - - const anchorZMinus = () => { - setAnchorZ(useStore.getState().anchorZ - 1); - }; - - useKeyboardShortcut(["D"], anchorXPlus, { - overrideSystem: true, - ignoreInputFields: false, - repeatOnHold: false, - }); - - useKeyboardShortcut(["A"], anchorXMinus, { - overrideSystem: true, - ignoreInputFields: false, - repeatOnHold: false, - }); - - useKeyboardShortcut(["W"], anchorZPlus, { - overrideSystem: true, - ignoreInputFields: false, - repeatOnHold: false, - }); - - useKeyboardShortcut(["S"], anchorZMinus, { - overrideSystem: true, - ignoreInputFields: false, - repeatOnHold: false, - }); - - return null; -}; export const useDeleteShortcut = (selected, setBricks, onDelete) => { const deleteSelectedBricks = () => { From 86fd86e0279fe5f7feb559a92484266f18652bd8 Mon Sep 17 00:00:00 2001 From: Igorxp5 Date: Sat, 18 May 2024 22:27:27 -0300 Subject: [PATCH 02/11] simplifies positioning: add bricks from origin instead of center --- src/components/3D/Brick.jsx | 39 +------- src/components/3D/BrickCursor.jsx | 93 +++++------------ src/components/3D/BrickOutline.jsx | 24 ++--- src/components/3D/Others.jsx | 10 +- src/components/3D/Scene.jsx | 155 ++++++++++++----------------- src/store/index.js | 7 +- src/utils/constants.js | 36 ++----- src/utils/helpers.js | 48 ++++++--- 8 files changed, 154 insertions(+), 258 deletions(-) diff --git a/src/components/3D/Brick.jsx b/src/components/3D/Brick.jsx index 404b77d..ce7d428 100644 --- a/src/components/3D/Brick.jsx +++ b/src/components/3D/Brick.jsx @@ -6,14 +6,14 @@ import React, { useMemo, useEffect, useRef } from "react"; import { CSSToHex, getMeasurementsFromDimensions, - base, + getBoundBoxFromDimensions, createGeometry, } from "../../utils"; import { Vector3, Box3 } from "three"; import { motion } from "framer-motion-3d"; export const Brick = ({ - intersect, + position, color = "#ff0000", dimensions = { x: 1, z: 1 }, bricksBoundBox = { current: [] }, @@ -29,27 +29,8 @@ export const Brick = ({ return createGeometry({ width, height, depth, dimensions }); }, [width, height, depth, dimensions]); - const position = useMemo(() => { - const evenWidth = dimensions.x % 2 === 0; - const evenDepth = dimensions.z % 2 === 0; - - return new Vector3() - .copy(intersect.point) - .add(intersect.face.normal) - .divide(new Vector3(base, height, base)) - .floor() - .multiply(new Vector3(base, height, base)) - .add( - new Vector3( - evenWidth ? base : base / 2, - height / 2, - evenDepth ? base : base / 2 - ) - ); - }, [intersect, dimensions.x, dimensions.z, height]); - useEffect(() => { - const brickBoundingBox = new Box3().setFromObject(brickRef.current); + const brickBoundingBox = getBoundBoxFromDimensions(position, { width, height, depth }); bricksBoundBox.current.push({ uID, brickBoundingBox }); @@ -65,16 +46,11 @@ export const Brick = ({ }; }, [uID, bricksBoundBox]); - const offset = { - x: dimensions.x % 2 === 0 ? dimensions.x / 2 : (dimensions.x - 1) / 2, - z: dimensions.z % 2 === 0 ? dimensions.z / 2 : (dimensions.z - 1) / 2, - }; - return ( <> { - const mode = useStore((state) => state.mode); +export const BrickCursor = ({position, dimensions, visible}) => { + const { height, width, depth } = getMeasurementsFromDimensions(dimensions); - const visible = mode === CREATE_MODE; + const brickGeometry = useMemo(() => { + return createGeometry({ width, height, depth, dimensions, knobDim: 0}); + }, [width, height, depth, dimensions]); - const { height, width, depth } = getMeasurementsFromDimensions(dimensions); - - const position = useMemo(() => { - const evenWidth = width % 2 === 0; - const evenDepth = depth % 2 === 0; - - return new Vector3() - .copy(intersect.point) - .add(intersect.face.normal) - .divide(new Vector3(base, height, base)) - .floor() - .multiply(new Vector3(base, height, base)) - .add( - new Vector3( - evenWidth ? base : base / 2, - height / 2, - evenDepth ? base : base / 2 - ) - ); - }, [intersect, height, width, depth]); - - const offsetX = - dimensions.x % 2 === 0 ? dimensions.x / 2 : (dimensions.x - 1) / 2; - const offsetZ = - dimensions.z % 2 === 0 ? dimensions.z / 2 : (dimensions.z - 1) / 2; - - return ( - <> - + + - - - - - - - ); - } -); + + + + + ); +}; diff --git a/src/components/3D/BrickOutline.jsx b/src/components/3D/BrickOutline.jsx index 238a13f..7c832e9 100644 --- a/src/components/3D/BrickOutline.jsx +++ b/src/components/3D/BrickOutline.jsx @@ -7,8 +7,8 @@ import React, { useLayoutEffect, useMemo, useRef } from "react"; import { createGeometry, getMeasurementsFromDimensions, - knobSize, - outlineWidth, + KNOB_SIZE, + OUTLINE_WIDTH, } from "../../utils"; import { BackSide, Object3D } from "three"; import { useStore } from "../../store"; @@ -26,11 +26,11 @@ const OutlineMesh = ({ meshesData }) => { const outlineGeometry = useMemo(() => { return createGeometry({ - width: width + outlineWidth * 2, - height: height + outlineWidth * 2, - depth: depth + outlineWidth * 2, + width: width + OUTLINE_WIDTH * 2, + height: height + OUTLINE_WIDTH * 2, + depth: depth + OUTLINE_WIDTH * 2, dimensions, - knobDim: knobSize + outlineWidth, + knobDim: KNOB_SIZE + OUTLINE_WIDTH, }); }, [width, height, depth, dimensions]); @@ -38,16 +38,10 @@ const OutlineMesh = ({ meshesData }) => { if (!ref.current) return; meshesData.forEach((meshData, i) => { - const offset = { - x: dimensions.x % 2 === 0 ? dimensions.x / 2 : (dimensions.x - 1) / 2, - z: dimensions.z % 2 === 0 ? dimensions.z / 2 : (dimensions.z - 1) / 2, - }; - dummy.position.set( - meshData.position.x + (offset.x * width) / dimensions.x, - Math.abs(meshData.position.y), - meshData.position.z + (offset.z * width) / dimensions.z - ); + meshData.position.x - OUTLINE_WIDTH, + meshData.position.y - OUTLINE_WIDTH, + meshData.position.z - OUTLINE_WIDTH); dummy.updateMatrix(); ref.current.setMatrixAt(i, dummy.matrix); }); diff --git a/src/components/3D/Others.jsx b/src/components/3D/Others.jsx index aaa11e5..6494e84 100644 --- a/src/components/3D/Others.jsx +++ b/src/components/3D/Others.jsx @@ -2,6 +2,7 @@ /* eslint-disable no-unused-vars */ /* eslint-disable react/prop-types */ import { useStore } from "../../store"; +import { GRID_UNIT } from "../../utils"; import { memo, useRef, useEffect } from "react"; import { BorderPlane } from "./BorderPlane"; @@ -14,9 +15,9 @@ const Other = memo(function Other({ id, color }) { const unsubscribe = room.subscribe("event", ({ event }) => { if (event.type === id) { const d = event.data; - ref.current.container.position.x = d.x; - ref.current.container.position.z = d.z; - ref.current.container.position.y = d.y - 31 / 2; + ref.current.container.position.x = d.x + (GRID_UNIT.x * d.w) / 2; + ref.current.container.position.z = d.z + (GRID_UNIT.z * d.w) / 2; + ref.current.container.position.y = d.y; ref.current.plane.scale.set(d.w, d.d); ref.current.plane.material.uniforms.uSize.value.x = d.w; @@ -33,7 +34,7 @@ const Other = memo(function Other({ id, color }) { return ( <> - + ); }); @@ -44,7 +45,6 @@ export const Others = () => { return ( <> {others.map((user) => { - console.log(user); return user.presence?.self ? ( { const bricks = useStore((state) => state.bricks); const setBricks = useStore((state) => state.setBricks); const bricksBoundBox = useRef([]); - const brickCursorRef = useRef(); - const mode = useStore((state) => state.mode); const isEditMode = mode === EDIT_MODE; + const isCreateMode = mode === CREATE_MODE; const width = useStore((state) => state.width); const depth = useStore((state) => state.depth); const color = useStore((state) => state.color); + const [mouseIntersect, setMouseIntersect] = useState(new Vector3()); + const room = useStore((state) => state.liveblocks.room); const self = useStore((state) => state.self); - const addBrick = (e) => { - e.stopPropagation(); - - if (isEditMode) return; - - if (!e.face?.normal || !e.point) return; - - if (!brickCursorRef.current) return; - - if (!isDrag.current) { - const dimensions = getMeasurementsFromDimensions({ - x: width, - z: depth, - }); - const boundingBoxOfBrickToBeAdded = new Box3().setFromObject( - brickCursorRef.current - ); - - let canCreate = true; - - for (let index = 0; index < bricksBoundBox.current.length; index++) { - const brickBoundingBox = bricksBoundBox.current[index].brickBoundingBox; - const collision = - boundingBoxOfBrickToBeAdded.intersectsBox(brickBoundingBox); - - if (collision) { - const dx = Math.abs( - brickBoundingBox.max.x - boundingBoxOfBrickToBeAdded.max.x - ); - const dz = Math.abs( - brickBoundingBox.max.z - boundingBoxOfBrickToBeAdded.max.z - ); - const yIntsersect = - brickBoundingBox.max.y - 9 > boundingBoxOfBrickToBeAdded.min.y; - if ( - yIntsersect && - dx !== dimensions.width && - dz !== dimensions.depth - ) { - canCreate = false; - break; - } + const addBrick = () => { + const dimensions = getMeasurementsFromDimensions({x: width, z: depth}); + const boundingBoxOfBrickToBeAdded = getBoundBoxFromDimensions(mouseIntersect, dimensions); + + let canCreate = true; + + for (let index = 0; index < bricksBoundBox.current.length; index++) { + const brickBoundingBox = bricksBoundBox.current[index].brickBoundingBox; + const collision = + boundingBoxOfBrickToBeAdded.intersectsBox(brickBoundingBox); + + if (collision) { + const dx = Math.abs( + brickBoundingBox.max.x - boundingBoxOfBrickToBeAdded.max.x + ); + const dz = Math.abs( + brickBoundingBox.max.z - boundingBoxOfBrickToBeAdded.max.z + ); + const yIntsersect = + brickBoundingBox.max.y - 9 > boundingBoxOfBrickToBeAdded.min.y; + if ( + yIntsersect && + dx !== dimensions.width && + dz !== dimensions.depth + ) { + canCreate = false; + break; } } + } - if (canCreate) { - const brickData = { - intersect: { point: e.point, face: e.face }, - uID: uID(), - dimensions: { x: width, z: depth }, - color: color, - }; + if (canCreate) { + const brickData = { + position: mouseIntersect, + uID: uID(), + dimensions: { x: width, z: depth }, + color: color, + }; - setBricks((prevBricks) => [...prevBricks, brickData]); - } - } else { - isDrag.current = false; + setBricks((prevBricks) => [...prevBricks, brickData]); } }; const setBrickCursorPosition = (e) => { e.stopPropagation(); if (isEditMode) return; - if (!brickCursorRef.current) return; - const { height } = getMeasurementsFromDimensions({ - x: width, - z: depth, - }); - const evenWidth = width % 2 === 0; - const evenDepth = depth % 2 === 0; - - mousePoint.set(e.point.x, Math.abs(e.point.y), e.point.z); - normal.set(e.face.normal.x, Math.abs(e.face.normal.y), e.face.normal.z); - brickDimensions.set(base, height, base); - offsetVec.set( - evenWidth ? base : base / 2, - height / 2, - evenDepth ? base : base / 2 - ); - - brickCursorRef.current.position - .copy(mousePoint) - .add(normal) - .divide(brickDimensions) - .floor() - .multiply(brickDimensions) - .add(offsetVec); + const mousePosition = new Vector3() + .copy(e.point) + .add(e.face.normal) + .setY(Math.abs(e.point.y)); + + const normalizedMousePosition = normalizePositionToSceneGrid(mousePosition); + + setMouseIntersect(normalizedMousePosition); room.broadcastEvent({ type: self.id, data: { - x: brickCursorRef.current.position.x, - y: brickCursorRef.current.position.y, - z: brickCursorRef.current.position.z, + x: normalizedMousePosition.x, + y: normalizedMousePosition.y, + z: normalizedMousePosition.z, w: width, d: depth, }, @@ -149,7 +114,10 @@ export const Scene = () => { }; const onClick = (e) => { - if (!isEditMode) addBrick(e); + if (!isEditMode) { + if (!isDrag.current) addBrick(); + else isDrag.current = false; + } }; const mouseMove = (e) => { @@ -203,10 +171,11 @@ export const Scene = () => { diff --git a/src/store/index.js b/src/store/index.js index 72858f0..ddf668e 100644 --- a/src/store/index.js +++ b/src/store/index.js @@ -1,7 +1,8 @@ import { create } from "zustand"; import { CREATE_MODE, - defaultWidth, + DEFAULT_WIDTH, + DEFAULT_DEPTH, generateSoftColors, uID, } from "../utils"; @@ -21,10 +22,10 @@ export const useStore = create( mode: CREATE_MODE, setMode: (newMode) => set({ mode: newMode }), - width: defaultWidth, + width: DEFAULT_WIDTH, setWidth: (newWidth) => set({ width: newWidth }), - depth: defaultWidth, + depth: DEFAULT_DEPTH, setDepth: (newDepth) => set({ depth: newDepth }), color: "#ff0000", diff --git a/src/utils/constants.js b/src/utils/constants.js index b756022..c3a1555 100644 --- a/src/utils/constants.js +++ b/src/utils/constants.js @@ -1,36 +1,16 @@ -export const base = 25; -export const knobSize = 7; -export const outlineWidth = 1.3; -export const minWorkSpaceSize = 1000; +export const BASE = 25; +export const GRID_UNIT = {x: BASE, y: (BASE * 2) / 1.5, z: BASE }; +export const KNOB_SIZE = 7; +export const OUTLINE_WIDTH = 1.3; +export const MIN_WORKSPACE_SIZE = 1000; export const CREATE_MODE = "Create Mode"; export const EDIT_MODE = "Edit Mode"; -export const defaultWidth = 1; +export const DEFAULT_WIDTH = 1; +export const DEFAULT_DEPTH = 1; -export const bricks = [ - { x: 1, z: 1 }, - { x: 2, z: 1 }, - { x: 2, z: 2 }, - { x: 3, z: 1 }, - { x: 3, z: 2 }, - { x: 4, z: 1 }, - { x: 4, z: 2 }, -]; - -export const colors = [ - "#FF0000", - "#FF9800", - "#F0E100", - "#00DE00", - "#A1BC24", - "#0011CF", - "#FFFFFF", - "#000000", - "#652A0C", -]; - -export const cursorColors = [ +export const CURSOR_COLORS = [ "#FFA6B1", // Soft Pink "#FFB6C1", // Light Pink "#FFD1A8", // Peach diff --git a/src/utils/helpers.js b/src/utils/helpers.js index 6bfba2e..fa569d7 100644 --- a/src/utils/helpers.js +++ b/src/utils/helpers.js @@ -1,42 +1,66 @@ -import { base, knobSize } from "./constants"; -import { BoxGeometry, CylinderGeometry } from "three"; +import { BASE, GRID_UNIT, KNOB_SIZE } from "./constants"; +import { BoxGeometry, CylinderGeometry, Vector3, Box3 } from "three"; import { mergeGeometries } from "three/addons/utils/BufferGeometryUtils.js"; +export const SCENE_GRID_NORM = new Vector3(GRID_UNIT.x, GRID_UNIT.y, GRID_UNIT.z); + export function CSSToHex(cssColor) { return parseInt(`0x${cssColor.substring(1)}`, 16); } export function getMeasurementsFromDimensions({ x, y, z }) { return { - width: base * x, - height: base * y || (base * 2) / 1.5, - depth: base * z, + width: GRID_UNIT.x * x, + height: GRID_UNIT.y || GRID_UNIT.y * y, + depth: GRID_UNIT.z * z, }; } +export function normalizeVector3(vector, norm) { + return new Vector3() + .copy(vector) + .divide(norm) + .floor() + .multiply(norm); +} + +export function normalizePositionToSceneGrid(vector) { + return normalizeVector3(vector, SCENE_GRID_NORM); +} + export function mergeMeshes(geometries) { return mergeGeometries(geometries); } +export function getBoundBoxFromDimensions(position, {width, height, depth}) { + return new Box3( + position, + new Vector3() + .copy(position) + .add(new Vector3(width, height, depth)) + ); +} + export function createGeometry({ width, height, depth, dimensions, - knobDim = knobSize, + knobDim = KNOB_SIZE, }) { let geometries = []; - const cubeGeo = new BoxGeometry(width - 0.1, height - 0.1, depth - 0.1); - + const cubeGeo = new BoxGeometry(width - 0.1, height + 0.1, depth - 0.1); + const originTranslate = {x: width / 2, y: height / 2, z: depth / 2} + cubeGeo.translate(originTranslate.x, originTranslate.y, originTranslate.z); geometries.push(cubeGeo); for (let i = 0; i < dimensions.x; i++) { for (let j = 0; j < dimensions.z; j++) { const cylinder = new CylinderGeometry(knobDim, knobDim, knobDim, 20); - const x = base * i - ((dimensions.x - 1) * base) / 2; - const y = base / 1.5; - const z = base * j - ((dimensions.z - 1) * base) / 2; - cylinder.translate(x, y, z); + const x = GRID_UNIT.x * i - ((dimensions.x - 1) * GRID_UNIT.x) / 2; + const y = BASE / 1.5; + const z = GRID_UNIT.z * j - ((dimensions.z - 1) * GRID_UNIT.z) / 2; + cylinder.translate(originTranslate.x + x, originTranslate.y + y, originTranslate.z + z); geometries.push(cylinder); } } From 7e38ffddbfda758efb216a19bfec6b438a0eccb4 Mon Sep 17 00:00:00 2001 From: Igorxp5 Date: Sat, 18 May 2024 23:08:39 -0300 Subject: [PATCH 03/11] fixes: propagate edit brick color --- src/components/3D/ChangeColor.jsx | 38 ++++++++----------------------- 1 file changed, 9 insertions(+), 29 deletions(-) diff --git a/src/components/3D/ChangeColor.jsx b/src/components/3D/ChangeColor.jsx index fc1d4af..166476c 100644 --- a/src/components/3D/ChangeColor.jsx +++ b/src/components/3D/ChangeColor.jsx @@ -12,36 +12,16 @@ export const ChangeColor = ({ color }) => { const setBricks = useStore((state) => state.setBricks); - const prevColor = useRef(color); - - const deferredColor = useDeferredValue(color); - useEffect(() => { - if (selected.length < 1 || prevColor.current === deferredColor) return; - - setBricks((bricks) => { - const updatedBricks = []; - - bricks.forEach((brick) => { - const selectedClone = [...selected]; - const uID = brick.uID; - for (let i = 0; i < selectedClone.length; i++) { - const selectedUID = selectedClone[i]; - if (uID === selectedUID) { - brick.color = deferredColor; - selectedClone.splice(i, 1); - } - } - updatedBricks.push(brick); - }); - - return updatedBricks; - }); - - return () => { - prevColor.current = deferredColor; - }; - }, [deferredColor, selected, setBricks]); + if (selected.length > 0) { + setBricks((bricks) => + bricks.map((brick) => ({ + ...brick, + color: selected.includes(brick.uID) ? color : brick.color + })) + ); + } + }, [color]); return null; }; From 9ffea4290a8bf1f79c868084c4fbf062c8feefdd Mon Sep 17 00:00:00 2001 From: Igorxp5 Date: Sat, 18 May 2024 23:13:45 -0300 Subject: [PATCH 04/11] adds: Escape shortcut to unselect bricks --- src/components/3D/Select/index.jsx | 8 +++++++- src/utils/hooks.js | 8 ++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/components/3D/Select/index.jsx b/src/components/3D/Select/index.jsx index 3fbff40..aeec247 100644 --- a/src/components/3D/Select/index.jsx +++ b/src/components/3D/Select/index.jsx @@ -7,7 +7,7 @@ import { SelectionBox } from "three-stdlib"; import { useThree } from "@react-three/fiber"; import { shallow } from "zustand/shallow"; import { useStore } from "../../../store"; -import { EDIT_MODE } from "../../../utils"; +import { useEscapeShortcut, EDIT_MODE } from "../../../utils"; export function Select({ box, @@ -51,6 +51,12 @@ export function Select({ setSelectedBricks({}); }, []); + const onEscapePressed = () => { + setSelectedBricks({}); + }; + + useEscapeShortcut(onEscapePressed); + const ref = React.useRef(null); const selectionOverTimeoutId = React.useRef(null); diff --git a/src/utils/hooks.js b/src/utils/hooks.js index 197c306..891c660 100644 --- a/src/utils/hooks.js +++ b/src/utils/hooks.js @@ -31,6 +31,14 @@ export const useDeleteShortcut = (selected, setBricks, onDelete) => { return null; }; +export const useEscapeShortcut = (onPressed) => { + useKeyboardShortcut(["Escape"], onPressed, { + overrideSystem: true, + ignoreInputFields: false, + repeatOnHold: false, + }); +} + export const useUndoRedoShortcut = (undo, redo) => { useKeyboardShortcut(["Control", "Z"], undo, { overrideSystem: true, From 1c7d933e91ea1b44184f6bdfc4f6681f56a5659a Mon Sep 17 00:00:00 2001 From: Igorxp5 Date: Sun, 19 May 2024 00:17:21 -0300 Subject: [PATCH 05/11] adds move selection --- src/components/3D/Brick.jsx | 4 +- src/components/3D/MultiBrickCursor.jsx | 47 ++++++++++++++ src/components/3D/Scene.jsx | 88 ++++++++++++++++---------- src/components/3D/index.js | 1 + src/utils/helpers.js | 32 +++++++++- 5 files changed, 137 insertions(+), 35 deletions(-) create mode 100644 src/components/3D/MultiBrickCursor.jsx diff --git a/src/components/3D/Brick.jsx b/src/components/3D/Brick.jsx index ce7d428..7ce66b2 100644 --- a/src/components/3D/Brick.jsx +++ b/src/components/3D/Brick.jsx @@ -6,7 +6,7 @@ import React, { useMemo, useEffect, useRef } from "react"; import { CSSToHex, getMeasurementsFromDimensions, - getBoundBoxFromDimensions, + getBoundBoxFromMeasures, createGeometry, } from "../../utils"; import { Vector3, Box3 } from "three"; @@ -30,7 +30,7 @@ export const Brick = ({ }, [width, height, depth, dimensions]); useEffect(() => { - const brickBoundingBox = getBoundBoxFromDimensions(position, { width, height, depth }); + const brickBoundingBox = getBoundBoxFromMeasures(position, { width, height, depth }); bricksBoundBox.current.push({ uID, brickBoundingBox }); diff --git a/src/components/3D/MultiBrickCursor.jsx b/src/components/3D/MultiBrickCursor.jsx new file mode 100644 index 0000000..d1f0584 --- /dev/null +++ b/src/components/3D/MultiBrickCursor.jsx @@ -0,0 +1,47 @@ +/* eslint-disable react/no-unknown-property */ +/* eslint-disable no-unused-vars */ +/* eslint-disable react/prop-types */ +/* eslint-disable react/display-name */ +import React, { useMemo } from "react"; +import { Vector3 } from "three"; +import { getMeasurementsFromDimensions, createGeometry } from "../../utils"; + +export const MultiBrickCursor = ({position, anchor, bricks, visible = true}) => { + const meshes = useMemo(() => { + return bricks.map((brick) => { + const { height, width, depth } = getMeasurementsFromDimensions(brick.dimensions); + const brickGeometry = createGeometry({ width, height, depth, dimensions: brick.dimensions, knobDim: 0}); + + return { + uID: brick.uID, + relPosition: new Vector3().copy(brick.position).sub(anchor).toArray(), + geometry: brickGeometry + } + }) + }, [anchor, bricks]); + + return ( + <> + + {meshes.map((mesh) => { + return ( + + + + ); + })} + + + ); +}; diff --git a/src/components/3D/Scene.jsx b/src/components/3D/Scene.jsx index cd4a76d..03d9d4a 100644 --- a/src/components/3D/Scene.jsx +++ b/src/components/3D/Scene.jsx @@ -4,6 +4,7 @@ import { useEffect, useRef, useState, useMemo } from "react"; import { Brick, BrickCursor, + MultiBrickCursor, Lights, Workspace, BrickOutline, @@ -15,7 +16,8 @@ import { uID, getMeasurementsFromDimensions, normalizePositionToSceneGrid, - getBoundBoxFromDimensions, + getBoundBoxFromMeasures, + doBoundBoxCollideWithBoundBoxSet, MIN_WORKSPACE_SIZE, EDIT_MODE, CREATE_MODE, @@ -36,6 +38,19 @@ export const Scene = () => { const isEditMode = mode === EDIT_MODE; const isCreateMode = mode === CREATE_MODE; + const selectedBricks = useStore((state) => state.selectedBricks).map( + (sel) => sel.userData + ).filter((sel) => Object.keys(sel).length > 0); + + const selectedBricksAnchor = useMemo(() => { + return selectedBricks.reduce((acc, brick) => { + if(brick.position.x < acc.x) acc.setX(brick.position.x); + if(brick.position.y < acc.y) acc.setY(brick.position.y); + if(brick.position.z < acc.z) acc.setZ(brick.position.z); + return acc; + }, new Vector3(Infinity, Infinity, Infinity)); + }); + const width = useStore((state) => state.width); const depth = useStore((state) => state.depth); const color = useStore((state) => state.color); @@ -46,37 +61,11 @@ export const Scene = () => { const self = useStore((state) => state.self); const addBrick = () => { - const dimensions = getMeasurementsFromDimensions({x: width, z: depth}); - const boundingBoxOfBrickToBeAdded = getBoundBoxFromDimensions(mouseIntersect, dimensions); - - let canCreate = true; - - for (let index = 0; index < bricksBoundBox.current.length; index++) { - const brickBoundingBox = bricksBoundBox.current[index].brickBoundingBox; - const collision = - boundingBoxOfBrickToBeAdded.intersectsBox(brickBoundingBox); - - if (collision) { - const dx = Math.abs( - brickBoundingBox.max.x - boundingBoxOfBrickToBeAdded.max.x - ); - const dz = Math.abs( - brickBoundingBox.max.z - boundingBoxOfBrickToBeAdded.max.z - ); - const yIntsersect = - brickBoundingBox.max.y - 9 > boundingBoxOfBrickToBeAdded.min.y; - if ( - yIntsersect && - dx !== dimensions.width && - dz !== dimensions.depth - ) { - canCreate = false; - break; - } - } - } + const measures = getMeasurementsFromDimensions({x: width, z: depth}); + const boundingBoxOfBrick = getBoundBoxFromMeasures(mouseIntersect, measures); + const bricksBoundingBox = bricksBoundBox.current.map((bound) => bound.brickBoundingBox); - if (canCreate) { + if (!doBoundBoxCollideWithBoundBoxSet(boundingBoxOfBrick, bricksBoundingBox)) { const brickData = { position: mouseIntersect, uID: uID(), @@ -88,9 +77,37 @@ export const Scene = () => { } }; + const updateBrickPosition = () => { + const selected = selectedBricks.map((sel) => sel.uID); + const bricksBoundingBox = bricksBoundBox.current.map((bound) => bound.brickBoundingBox); + const selectedBricksNewPosition = {}; + let canMove = true; + for (let index = 0; index < selectedBricks.length; index++) { + const brick = selectedBricks[index]; + const measures = getMeasurementsFromDimensions(brick.dimensions); + const boundingBoxOfBrick = getBoundBoxFromMeasures(mouseIntersect, measures); + const newPosition = new Vector3() + .copy(mouseIntersect) + .add(brick.position) + .sub(selectedBricksAnchor); + selectedBricksNewPosition[brick.uID] = newPosition; + if (doBoundBoxCollideWithBoundBoxSet(boundingBoxOfBrick, bricksBoundingBox)) { + canMove = false; + break; + } + } + if (canMove) { + setBricks((bricks) => + bricks.map((brick) => ({ + ...brick, + position: selected.includes(brick.uID) ? selectedBricksNewPosition[brick.uID] : brick.position + })) + ); + } + } + const setBrickCursorPosition = (e) => { e.stopPropagation(); - if (isEditMode) return; const mousePosition = new Vector3() .copy(e.point) @@ -117,6 +134,8 @@ export const Scene = () => { if (!isEditMode) { if (!isDrag.current) addBrick(); else isDrag.current = false; + } else if (selectedBricks.length > 0) { + updateBrickPosition(); } }; @@ -178,6 +197,11 @@ export const Scene = () => { visible={isCreateMode} dimensions={{ x: width, z: depth }} /> + ); diff --git a/src/components/3D/index.js b/src/components/3D/index.js index 848f442..6fd374a 100644 --- a/src/components/3D/index.js +++ b/src/components/3D/index.js @@ -4,6 +4,7 @@ export { ControlsWrapper } from "./ControlsWrapper"; export { DeleteBrick } from "./DeleteBrick"; export { LegoRoom } from "./LegoRoom"; export { Lights } from "./Lights"; +export { MultiBrickCursor } from "./MultiBrickCursor"; export { Scene } from "./Scene"; export { Workspace } from "./Workspace"; export { Brick } from "./Brick"; diff --git a/src/utils/helpers.js b/src/utils/helpers.js index fa569d7..ce03fc5 100644 --- a/src/utils/helpers.js +++ b/src/utils/helpers.js @@ -32,7 +32,7 @@ export function mergeMeshes(geometries) { return mergeGeometries(geometries); } -export function getBoundBoxFromDimensions(position, {width, height, depth}) { +export function getBoundBoxFromMeasures(position, {width, height, depth}) { return new Box3( position, new Vector3() @@ -69,6 +69,36 @@ export function createGeometry({ return brickGeometry; } +export function doBoundBoxCollideWithBoundBoxSet(testBoundBox, targetBoxes) { + for (let index = 0; index < targetBoxes.length; index++) { + const boundingBox = targetBoxes[index]; + const collision = testBoundBox.intersectsBox(boundingBox); + + const size = testBoundBox.getSize(new Vector3()); + const width = size.x; + const depth = size.z; + + if (collision) { + const dx = Math.abs( + boundingBox.max.x - testBoundBox.max.x + ); + const dz = Math.abs( + boundingBox.max.z - testBoundBox.max.z + ); + const yIntsersect = + boundingBox.max.y - 9 > testBoundBox.min.y; + if ( + yIntsersect && + dx !== width && + dz !== depth + ) { + return true; + } + } + } + return false; +} + export function collisonXYZ(o1, o2) { if ( Math.abs(o1.position.x - o2.position.x) > From 6d1636273885a2942fa7c245b9ed41a7ae818454 Mon Sep 17 00:00:00 2001 From: Igorxp5 Date: Sun, 19 May 2024 00:44:48 -0300 Subject: [PATCH 06/11] removes unselect on pointer missed Click when bricks are selected it moves them. By removing unselect, the user has the feedback when they're trying to add a brick in invalid position. Select only by shift key. --- src/components/3D/Brick.jsx | 2 +- src/components/3D/Scene.jsx | 5 ++++- src/components/3D/Select/index.jsx | 8 ++------ src/components/UI/Dialog/DialogBox.jsx | 15 +++++++++++---- src/store/index.js | 9 +++++---- 5 files changed, 23 insertions(+), 16 deletions(-) diff --git a/src/components/3D/Brick.jsx b/src/components/3D/Brick.jsx index 7ce66b2..1419f5e 100644 --- a/src/components/3D/Brick.jsx +++ b/src/components/3D/Brick.jsx @@ -44,7 +44,7 @@ export const Brick = ({ } bricksBoundBox.current = newA; }; - }, [uID, bricksBoundBox]); + }, [uID, position, dimensions, bricksBoundBox]); return ( <> diff --git a/src/components/3D/Scene.jsx b/src/components/3D/Scene.jsx index 03d9d4a..de5796d 100644 --- a/src/components/3D/Scene.jsx +++ b/src/components/3D/Scene.jsx @@ -38,6 +38,7 @@ export const Scene = () => { const isEditMode = mode === EDIT_MODE; const isCreateMode = mode === CREATE_MODE; + const setSelectedBricks = useStore((state) => state.setSelectedBricks); const selectedBricks = useStore((state) => state.selectedBricks).map( (sel) => sel.userData ).filter((sel) => Object.keys(sel).length > 0); @@ -103,6 +104,7 @@ export const Scene = () => { position: selected.includes(brick.uID) ? selectedBricksNewPosition[brick.uID] : brick.position })) ); + setSelectedBricks({}); } } @@ -134,7 +136,7 @@ export const Scene = () => { if (!isEditMode) { if (!isDrag.current) addBrick(); else isDrag.current = false; - } else if (selectedBricks.length > 0) { + } else if (selectedBricks.length > 0 && !e.shiftKey) { updateBrickPosition(); } }; @@ -144,6 +146,7 @@ export const Scene = () => { }; const isDrag = useRef(false); + const isShiftKey = useRef(false); const timeoutID = useRef(null); useEffect(() => { diff --git a/src/components/3D/Select/index.jsx b/src/components/3D/Select/index.jsx index aeec247..75e0f7c 100644 --- a/src/components/3D/Select/index.jsx +++ b/src/components/3D/Select/index.jsx @@ -35,7 +35,8 @@ export function Select({ if (!enable) return; setSelectedBricks({ object: customFilter([e.object])[0], - shift: multiple && e.shiftKey, + multiple: multiple, + shift: e.shiftKey }); }, [enable, multiple] @@ -47,10 +48,6 @@ export function Select({ } }, [enable]); - const onPointerMissed = React.useCallback((e) => { - setSelectedBricks({}); - }, []); - const onEscapePressed = () => { setSelectedBricks({}); }; @@ -201,7 +198,6 @@ export function Select({ {children} diff --git a/src/components/UI/Dialog/DialogBox.jsx b/src/components/UI/Dialog/DialogBox.jsx index 47ca363..03707be 100644 --- a/src/components/UI/Dialog/DialogBox.jsx +++ b/src/components/UI/Dialog/DialogBox.jsx @@ -30,12 +30,19 @@ export const DialogBox = () => (
    • Use left side panel to change the properties of brick
    • +
    • + To replace brick, select Edit option in control + panel then hold{" "} + Shift, select one or multiple bricks + by clicking on them, release {" "} + Shift and click on the new place. +
    • To delete brick, select Edit option in control - panel then select brick by clicking on brick or hold{" "} - Shift to select multiple bricks and - use - Delete to delete selected bricks + panel then hold{" "} + Shift, select one or multiple bricks + by clicking on them and + Delete to delete selected bricks.
    • Top bar has two options. create Mode which let's you create diff --git a/src/store/index.js b/src/store/index.js index ddf668e..e14b857 100644 --- a/src/store/index.js +++ b/src/store/index.js @@ -32,19 +32,20 @@ export const useStore = create( setColor: (newColor) => set({ color: newColor }), selectedBricks: [], - setSelectedBricks: ({ object, shift }) => + setSelectedBricks: ({ object, multiple, shift }) => set((state) => { if (object === undefined) return { selectedBricks: [] }; else if (Array.isArray(object)) return { selectedBricks: object }; - else if (!shift) + else if (!multiple) return state.selectedBricks[0] === object ? { selectedBricks: [] } : { selectedBricks: [object] }; - else if (state.selectedBricks.includes(object)) + else if (shift && state.selectedBricks.includes(object)) return { selectedBricks: state.selectedBricks.filter((o) => o !== object), }; - else return { selectedBricks: [object, ...state.selectedBricks] }; + else if (shift) return { selectedBricks: [object, ...state.selectedBricks] }; + else return {}; }), bricks: [], From c692c96ea0c447a7707a34397b0848b32aef4bd5 Mon Sep 17 00:00:00 2001 From: Igorxp5 Date: Sun, 19 May 2024 19:25:51 -0300 Subject: [PATCH 07/11] reverts translation (anchor) removal --- src/components/3D/Scene.jsx | 34 +++++++++++++---- src/components/UI/Dialog/DialogBox.jsx | 7 ++++ src/components/UI/Dialog/dialogbox.css | 6 ++- src/components/UI/Panel/Slider/index.jsx | 5 +++ src/components/UI/Panel/index.jsx | 2 + src/store/index.js | 7 ++++ src/utils/constants.js | 1 + src/utils/hooks.js | 48 ++++++++++++++++++++++++ 8 files changed, 101 insertions(+), 9 deletions(-) diff --git a/src/components/3D/Scene.jsx b/src/components/3D/Scene.jsx index de5796d..215de23 100644 --- a/src/components/3D/Scene.jsx +++ b/src/components/3D/Scene.jsx @@ -14,10 +14,12 @@ import { import { Vector3 } from "three"; import { uID, + useAnchorShorcuts, getMeasurementsFromDimensions, normalizePositionToSceneGrid, getBoundBoxFromMeasures, doBoundBoxCollideWithBoundBoxSet, + GRID_UNIT, MIN_WORKSPACE_SIZE, EDIT_MODE, CREATE_MODE, @@ -54,13 +56,18 @@ export const Scene = () => { const width = useStore((state) => state.width); const depth = useStore((state) => state.depth); + const anchorX = useStore((state) => state.anchorX); + const anchorZ = useStore((state) => state.anchorZ); const color = useStore((state) => state.color); + const [rawMousePosition, setRawMousePosition] = useState(new Vector3()); const [mouseIntersect, setMouseIntersect] = useState(new Vector3()); const room = useStore((state) => state.liveblocks.room); const self = useStore((state) => state.self); + useAnchorShorcuts(); + const addBrick = () => { const measures = getMeasurementsFromDimensions({x: width, z: depth}); const boundingBoxOfBrick = getBoundBoxFromMeasures(mouseIntersect, measures); @@ -115,23 +122,37 @@ export const Scene = () => { .copy(e.point) .add(e.face.normal) .setY(Math.abs(e.point.y)); - - const normalizedMousePosition = normalizePositionToSceneGrid(mousePosition); - setMouseIntersect(normalizedMousePosition); + setRawMousePosition(mousePosition); + + updateMouseIntersectWithTranslation(); room.broadcastEvent({ type: self.id, data: { - x: normalizedMousePosition.x, - y: normalizedMousePosition.y, - z: normalizedMousePosition.z, + x: mouseIntersect.x, + y: mouseIntersect.y, + z: mouseIntersect.z, w: width, d: depth, }, }); + }; + const updateMouseIntersectWithTranslation = () => { + const normalizedMousePosition = normalizePositionToSceneGrid(rawMousePosition); + normalizedMousePosition.add( + new Vector3(anchorX * GRID_UNIT.x, 0, anchorZ * GRID_UNIT.z) + ); + + setMouseIntersect(normalizedMousePosition); + } + + useEffect(() => { + updateMouseIntersectWithTranslation(); + }, [anchorX, anchorZ]); + const onClick = (e) => { if (!isEditMode) { if (!isDrag.current) addBrick(); @@ -146,7 +167,6 @@ export const Scene = () => { }; const isDrag = useRef(false); - const isShiftKey = useRef(false); const timeoutID = useRef(null); useEffect(() => { diff --git a/src/components/UI/Dialog/DialogBox.jsx b/src/components/UI/Dialog/DialogBox.jsx index 03707be..6cb2dc6 100644 --- a/src/components/UI/Dialog/DialogBox.jsx +++ b/src/components/UI/Dialog/DialogBox.jsx @@ -30,6 +30,13 @@ export const DialogBox = () => (
      • Use left side panel to change the properties of brick
      • +
      • + You change the pivot point of bricks using{" "} + A S{" "} + W and D{" "} + keys or You can use anchorX and{" "} + anchorZ option from the control panel +
      • To replace brick, select Edit option in control panel then hold{" "} diff --git a/src/components/UI/Dialog/dialogbox.css b/src/components/UI/Dialog/dialogbox.css index a21f0a1..faccdca 100644 --- a/src/components/UI/Dialog/dialogbox.css +++ b/src/components/UI/Dialog/dialogbox.css @@ -22,7 +22,7 @@ transform: translate(-50%, -50%); width: 90vw; max-width: 550px; - max-height: 85vh; + height: 85vh; padding: 25px; animation: contentShow 150ms cubic-bezier(0.16, 1, 0.3, 1); z-index: 1000; @@ -39,10 +39,12 @@ } .DialogDescription { - margin: 10px 0 20px; + height: 100%; + padding-bottom: 25px; /* color: var(--mauve11); */ font-size: 15px; line-height: 1.5; + overflow-y: scroll; } .DialogDescription li { diff --git a/src/components/UI/Panel/Slider/index.jsx b/src/components/UI/Panel/Slider/index.jsx index 0f47753..a390e12 100644 --- a/src/components/UI/Panel/Slider/index.jsx +++ b/src/components/UI/Panel/Slider/index.jsx @@ -8,11 +8,15 @@ import { useStore } from "../../../../store"; const stateMap = { width: "width", depth: "depth", + "anchor X": "anchorX", + "anchor Z": "anchorZ", }; const setterMap = { width: "setWidth", depth: "setDepth", + "anchor X": "setAnchorX", + "anchor Z": "setAnchorZ", }; export const SliderWithLabel = ({ @@ -28,6 +32,7 @@ export const SliderWithLabel = ({ {
        + +
        ); diff --git a/src/store/index.js b/src/store/index.js index e14b857..c3d4102 100644 --- a/src/store/index.js +++ b/src/store/index.js @@ -3,6 +3,7 @@ import { CREATE_MODE, DEFAULT_WIDTH, DEFAULT_DEPTH, + DEFAULT_ANCHOR, generateSoftColors, uID, } from "../utils"; @@ -28,6 +29,12 @@ export const useStore = create( depth: DEFAULT_DEPTH, setDepth: (newDepth) => set({ depth: newDepth }), + anchorX: DEFAULT_ANCHOR, + setAnchorX: (newAnchorPoint) => set({ anchorX: newAnchorPoint }), + + anchorZ: DEFAULT_ANCHOR, + setAnchorZ: (newAnchorPoint) => set({ anchorZ: newAnchorPoint }), + color: "#ff0000", setColor: (newColor) => set({ color: newColor }), diff --git a/src/utils/constants.js b/src/utils/constants.js index c3a1555..8809d6c 100644 --- a/src/utils/constants.js +++ b/src/utils/constants.js @@ -9,6 +9,7 @@ export const EDIT_MODE = "Edit Mode"; export const DEFAULT_WIDTH = 1; export const DEFAULT_DEPTH = 1; +export const DEFAULT_ANCHOR = 0; export const CURSOR_COLORS = [ "#FFA6B1", // Soft Pink diff --git a/src/utils/hooks.js b/src/utils/hooks.js index 891c660..2674388 100644 --- a/src/utils/hooks.js +++ b/src/utils/hooks.js @@ -1,4 +1,52 @@ import useKeyboardShortcut from "use-keyboard-shortcut"; +import { useStore } from "../store"; + +export const useAnchorShorcuts = () => { + const setAnchorX = useStore((state) => state.setAnchorX); + const setAnchorZ = useStore((state) => state.setAnchorZ); + + const anchorXPlus = () => { + setAnchorX(useStore.getState().anchorX + 1); + }; + + const anchorXMinus = () => { + setAnchorX(useStore.getState().anchorX - 1); + }; + + const anchorZPlus = () => { + setAnchorZ(useStore.getState().anchorZ + 1); + }; + + const anchorZMinus = () => { + setAnchorZ(useStore.getState().anchorZ - 1); + }; + + useKeyboardShortcut(["D"], anchorXPlus, { + overrideSystem: true, + ignoreInputFields: false, + repeatOnHold: false, + }); + + useKeyboardShortcut(["A"], anchorXMinus, { + overrideSystem: true, + ignoreInputFields: false, + repeatOnHold: false, + }); + + useKeyboardShortcut(["W"], anchorZPlus, { + overrideSystem: true, + ignoreInputFields: false, + repeatOnHold: false, + }); + + useKeyboardShortcut(["S"], anchorZMinus, { + overrideSystem: true, + ignoreInputFields: false, + repeatOnHold: false, + }); + + return null; +}; export const useDeleteShortcut = (selected, setBricks, onDelete) => { const deleteSelectedBricks = () => { From 96b24b487562912fd68b29cc3c094587e866d711 Mon Sep 17 00:00:00 2001 From: Igorxp5 Date: Mon, 20 May 2024 09:15:30 -0300 Subject: [PATCH 08/11] adds select first brick without shift key --- src/components/3D/Scene.jsx | 4 +++- src/components/UI/Dialog/DialogBox.jsx | 15 +++++++-------- src/store/index.js | 2 +- 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/src/components/3D/Scene.jsx b/src/components/3D/Scene.jsx index 215de23..db69a34 100644 --- a/src/components/3D/Scene.jsx +++ b/src/components/3D/Scene.jsx @@ -111,7 +111,9 @@ export const Scene = () => { position: selected.includes(brick.uID) ? selectedBricksNewPosition[brick.uID] : brick.position })) ); - setSelectedBricks({}); + setTimeout(() => { + setSelectedBricks({}); + }, 5); // This timeout makes the Select component do not click underlying brick after the selected bricks has been moved } } diff --git a/src/components/UI/Dialog/DialogBox.jsx b/src/components/UI/Dialog/DialogBox.jsx index 6cb2dc6..d127b4d 100644 --- a/src/components/UI/Dialog/DialogBox.jsx +++ b/src/components/UI/Dialog/DialogBox.jsx @@ -39,17 +39,16 @@ export const DialogBox = () => (
      • To replace brick, select Edit option in control - panel then hold{" "} - Shift, select one or multiple bricks - by clicking on them, release {" "} - Shift and click on the new place. + panel then select brick by clicking on brick or hold{" "} + Shift to select multiple bricks + and click on the new place.
      • To delete brick, select Edit option in control - panel then hold{" "} - Shift, select one or multiple bricks - by clicking on them and - Delete to delete selected bricks. + panel then select brick by clicking on brick or hold{" "} + Shift to select multiple bricks and + use + Delete to delete selected bricks
      • Top bar has two options. create Mode which let's you create diff --git a/src/store/index.js b/src/store/index.js index c3d4102..d523a68 100644 --- a/src/store/index.js +++ b/src/store/index.js @@ -43,7 +43,7 @@ export const useStore = create( set((state) => { if (object === undefined) return { selectedBricks: [] }; else if (Array.isArray(object)) return { selectedBricks: object }; - else if (!multiple) + else if (!multiple || (!shift && state.selectedBricks.length === 0)) return state.selectedBricks[0] === object ? { selectedBricks: [] } : { selectedBricks: [object] }; From cfaa5af1d2d90ab71116ee453cd3df361d23039e Mon Sep 17 00:00:00 2001 From: Igorxp5 Date: Tue, 21 May 2024 08:45:16 -0300 Subject: [PATCH 09/11] reverts rotation removal --- src/components/3D/Scene.jsx | 11 +++++- src/components/UI/Panel/Checkbox/index.jsx | 42 +++++++++++++++++++++ src/components/UI/Panel/Checkbox/styles.css | 28 ++++++++++++++ src/components/UI/Panel/index.jsx | 2 + src/store/index.js | 3 ++ 5 files changed, 84 insertions(+), 2 deletions(-) create mode 100644 src/components/UI/Panel/Checkbox/index.jsx create mode 100644 src/components/UI/Panel/Checkbox/styles.css diff --git a/src/components/3D/Scene.jsx b/src/components/3D/Scene.jsx index db69a34..3a2623c 100644 --- a/src/components/3D/Scene.jsx +++ b/src/components/3D/Scene.jsx @@ -54,8 +54,11 @@ export const Scene = () => { }, new Vector3(Infinity, Infinity, Infinity)); }); - const width = useStore((state) => state.width); - const depth = useStore((state) => state.depth); + const rotate = useStore((state) => state.rotate); + const setRotate = useStore((state) => state.setRotate); + + const width = useStore((state) => !rotate ? state.width : state.depth); + const depth = useStore((state) => !rotate ? state.depth : state.width); const anchorX = useStore((state) => state.anchorX); const anchorZ = useStore((state) => state.anchorZ); const color = useStore((state) => state.color); @@ -155,6 +158,10 @@ export const Scene = () => { updateMouseIntersectWithTranslation(); }, [anchorX, anchorZ]); + useEffect(() => { + setRotate(false); + }, [mode]); + const onClick = (e) => { if (!isEditMode) { if (!isDrag.current) addBrick(); diff --git a/src/components/UI/Panel/Checkbox/index.jsx b/src/components/UI/Panel/Checkbox/index.jsx new file mode 100644 index 0000000..44b48d8 --- /dev/null +++ b/src/components/UI/Panel/Checkbox/index.jsx @@ -0,0 +1,42 @@ +/* eslint-disable react/prop-types */ +/* eslint-disable no-unused-vars */ +import React from "react"; +import * as CheckboxRadix from "@radix-ui/react-checkbox"; +import { CheckIcon } from "@radix-ui/react-icons"; +import "./styles.css"; +import { useStore } from "../../../../store"; + +const stateMap = { + rotate: "rotate", +}; + +const setterMap = { + rotate: "setRotate", +}; + +export const Checkbox = ({ label = "rotate" }) => { + const value = useStore((state) => state[stateMap[label]]); + const setValue = useStore((state) => state[setterMap[label]]); + + return ( +
        + + setValue(bool)} + > + + + + +
        + ); +}; diff --git a/src/components/UI/Panel/Checkbox/styles.css b/src/components/UI/Panel/Checkbox/styles.css new file mode 100644 index 0000000..9624c5e --- /dev/null +++ b/src/components/UI/Panel/Checkbox/styles.css @@ -0,0 +1,28 @@ +@import "@radix-ui/colors/black-alpha.css"; +@import "@radix-ui/colors/violet.css"; + +/* reset */ +button { + all: unset; +} + +.CheckboxRoot { + background-color: var(--black-a7); + width: 20px; + height: 20px; + border-radius: 4px; + display: flex; + align-items: center; + justify-content: center; + box-shadow: 0 2px 10px var(--black-a7); +} +.CheckboxRoot:hover { + cursor: pointer; +} +.CheckboxRoot:focus { + box-shadow: 0 0 0 2px black; +} + +.CheckboxIndicator { + color: var(--violet-11); +} diff --git a/src/components/UI/Panel/index.jsx b/src/components/UI/Panel/index.jsx index 5dca00b..8f365da 100644 --- a/src/components/UI/Panel/index.jsx +++ b/src/components/UI/Panel/index.jsx @@ -3,6 +3,7 @@ import React from "react"; import "./styles.css"; import { SliderWithLabel } from "./Slider"; +import { Checkbox } from "./Checkbox"; import { ColorInput } from "./ColorInput"; export const Panel = () => { @@ -12,6 +13,7 @@ export const Panel = () => { + ); diff --git a/src/store/index.js b/src/store/index.js index d523a68..09112f0 100644 --- a/src/store/index.js +++ b/src/store/index.js @@ -35,6 +35,9 @@ export const useStore = create( anchorZ: DEFAULT_ANCHOR, setAnchorZ: (newAnchorPoint) => set({ anchorZ: newAnchorPoint }), + rotate: false, + setRotate: (bool) => set({ rotate: bool }), + color: "#ff0000", setColor: (newColor) => set({ color: newColor }), From 3434fe62818fa983b40ede11122b9376c1c5c609 Mon Sep 17 00:00:00 2001 From: Igorxp5 Date: Sat, 25 May 2024 00:54:54 -0300 Subject: [PATCH 10/11] adds rotate multi-selection --- src/components/3D/MultiBrickCursor.jsx | 18 ++++++++++----- src/components/3D/Scene.jsx | 31 +++++++++++++++++--------- 2 files changed, 33 insertions(+), 16 deletions(-) diff --git a/src/components/3D/MultiBrickCursor.jsx b/src/components/3D/MultiBrickCursor.jsx index d1f0584..baae46a 100644 --- a/src/components/3D/MultiBrickCursor.jsx +++ b/src/components/3D/MultiBrickCursor.jsx @@ -6,19 +6,27 @@ import React, { useMemo } from "react"; import { Vector3 } from "three"; import { getMeasurementsFromDimensions, createGeometry } from "../../utils"; -export const MultiBrickCursor = ({position, anchor, bricks, visible = true}) => { +export const MultiBrickCursor = ({position, anchor, bricks, rotate = false, visible = true}) => { const meshes = useMemo(() => { return bricks.map((brick) => { - const { height, width, depth } = getMeasurementsFromDimensions(brick.dimensions); - const brickGeometry = createGeometry({ width, height, depth, dimensions: brick.dimensions, knobDim: 0}); + const dimensions = { + x: !rotate ? brick.dimensions.x : brick.dimensions.z, + z: !rotate ? brick.dimensions.z : brick.dimensions.x + } + const { height, width, depth } = getMeasurementsFromDimensions(dimensions); + const brickGeometry = createGeometry({ width, height, depth, dimensions: dimensions, knobDim: 0}); return { uID: brick.uID, - relPosition: new Vector3().copy(brick.position).sub(anchor).toArray(), + relPosition: new Vector3() + .copy(brick.position) + .sub(anchor) + .applyAxisAngle(new Vector3(0, 1, 0), rotate ? Math.PI / 2 : 0) + .toArray(), geometry: brickGeometry } }) - }, [anchor, bricks]); + }, [anchor, bricks, rotate]); return ( <> diff --git a/src/components/3D/Scene.jsx b/src/components/3D/Scene.jsx index 3a2623c..8da92ec 100644 --- a/src/components/3D/Scene.jsx +++ b/src/components/3D/Scene.jsx @@ -44,6 +44,7 @@ export const Scene = () => { const selectedBricks = useStore((state) => state.selectedBricks).map( (sel) => sel.userData ).filter((sel) => Object.keys(sel).length > 0); + const hasSelectedBricks = selectedBricks.length > 0; const selectedBricksAnchor = useMemo(() => { return selectedBricks.reduce((acc, brick) => { @@ -52,7 +53,7 @@ export const Scene = () => { if(brick.position.z < acc.z) acc.setZ(brick.position.z); return acc; }, new Vector3(Infinity, Infinity, Infinity)); - }); + }, [hasSelectedBricks]); const rotate = useStore((state) => state.rotate); const setRotate = useStore((state) => state.setRotate); @@ -91,27 +92,34 @@ export const Scene = () => { const updateBrickPosition = () => { const selected = selectedBricks.map((sel) => sel.uID); const bricksBoundingBox = bricksBoundBox.current.map((bound) => bound.brickBoundingBox); - const selectedBricksNewPosition = {}; + const selectedBricksNewProps = {}; let canMove = true; for (let index = 0; index < selectedBricks.length; index++) { const brick = selectedBricks[index]; - const measures = getMeasurementsFromDimensions(brick.dimensions); - const boundingBoxOfBrick = getBoundBoxFromMeasures(mouseIntersect, measures); - const newPosition = new Vector3() - .copy(mouseIntersect) + selectedBricksNewProps[brick.uID] = {}; + const dimensions = { + x: !rotate ? brick.dimensions.x : brick.dimensions.z, + z: !rotate ? brick.dimensions.z : brick.dimensions.x + } + const position = new Vector3() .add(brick.position) - .sub(selectedBricksAnchor); - selectedBricksNewPosition[brick.uID] = newPosition; + .sub(selectedBricksAnchor) + .applyAxisAngle(new Vector3(0, 1, 0), rotate ? Math.PI / 2 : 0) + .add(mouseIntersect); + const measures = getMeasurementsFromDimensions(dimensions); + const boundingBoxOfBrick = getBoundBoxFromMeasures(position, measures); if (doBoundBoxCollideWithBoundBoxSet(boundingBoxOfBrick, bricksBoundingBox)) { canMove = false; break; } + selectedBricksNewProps[brick.uID].position = position; + selectedBricksNewProps[brick.uID].dimensions = dimensions; } if (canMove) { setBricks((bricks) => bricks.map((brick) => ({ ...brick, - position: selected.includes(brick.uID) ? selectedBricksNewPosition[brick.uID] : brick.position + ...(selected.includes(brick.uID) ? selectedBricksNewProps[brick.uID] : {}) })) ); setTimeout(() => { @@ -160,13 +168,13 @@ export const Scene = () => { useEffect(() => { setRotate(false); - }, [mode]); + }, [mode, hasSelectedBricks]); const onClick = (e) => { if (!isEditMode) { if (!isDrag.current) addBrick(); else isDrag.current = false; - } else if (selectedBricks.length > 0 && !e.shiftKey) { + } else if (hasSelectedBricks && !e.shiftKey) { updateBrickPosition(); } }; @@ -232,6 +240,7 @@ export const Scene = () => { From e67274b77d9e4ab5288669f4f14d13ad8a419c2e Mon Sep 17 00:00:00 2001 From: Igorxp5 Date: Sat, 25 May 2024 02:03:05 -0300 Subject: [PATCH 11/11] reverts forwardRef for BrickCursor Scene and its children were being rendered every mouse move, causing render lag in some slower hardwares --- src/components/3D/BrickCursor.jsx | 58 ++++++++------ src/components/3D/MultiBrickCursor.jsx | 103 ++++++++++++++----------- src/components/3D/Scene.jsx | 63 +++++++++------ 3 files changed, 129 insertions(+), 95 deletions(-) diff --git a/src/components/3D/BrickCursor.jsx b/src/components/3D/BrickCursor.jsx index 9a02816..6301e61 100644 --- a/src/components/3D/BrickCursor.jsx +++ b/src/components/3D/BrickCursor.jsx @@ -2,32 +2,42 @@ /* eslint-disable no-unused-vars */ /* eslint-disable react/prop-types */ /* eslint-disable react/display-name */ -import React, { useMemo } from "react"; +import React, { forwardRef, useMemo } from "react"; import { getMeasurementsFromDimensions, createGeometry } from "../../utils"; +import { Vector3 } from "three"; -export const BrickCursor = ({position, dimensions, visible}) => { - const { height, width, depth } = getMeasurementsFromDimensions(dimensions); +export const BrickCursor = forwardRef( + ( + { + position = new Vector3(), + dimensions = { x: 1, z: 1 }, + visible = true + }, + ref + ) => { + const { height, width, depth } = getMeasurementsFromDimensions(dimensions); - const brickGeometry = useMemo(() => { - return createGeometry({ width, height, depth, dimensions, knobDim: 0}); - }, [width, height, depth, dimensions]); + const brickGeometry = useMemo(() => { + return createGeometry({ width, height, depth, dimensions, knobDim: 0}); + }, [width, height, depth, dimensions]); - return ( - <> - - + - - - - - ); -}; + + + + + + ); +}); diff --git a/src/components/3D/MultiBrickCursor.jsx b/src/components/3D/MultiBrickCursor.jsx index baae46a..8bda791 100644 --- a/src/components/3D/MultiBrickCursor.jsx +++ b/src/components/3D/MultiBrickCursor.jsx @@ -2,54 +2,65 @@ /* eslint-disable no-unused-vars */ /* eslint-disable react/prop-types */ /* eslint-disable react/display-name */ -import React, { useMemo } from "react"; +import React, { forwardRef, useMemo } from "react"; import { Vector3 } from "three"; import { getMeasurementsFromDimensions, createGeometry } from "../../utils"; -export const MultiBrickCursor = ({position, anchor, bricks, rotate = false, visible = true}) => { - const meshes = useMemo(() => { - return bricks.map((brick) => { - const dimensions = { - x: !rotate ? brick.dimensions.x : brick.dimensions.z, - z: !rotate ? brick.dimensions.z : brick.dimensions.x - } - const { height, width, depth } = getMeasurementsFromDimensions(dimensions); - const brickGeometry = createGeometry({ width, height, depth, dimensions: dimensions, knobDim: 0}); +export const MultiBrickCursor = forwardRef( + ( + { + position = new Vector3(), + anchor = new Vector3(), + bricks = [], + rotate = false, + visible = true + }, + ref + ) => { + const meshes = useMemo(() => { + return bricks.map((brick) => { + const dimensions = { + x: !rotate ? brick.dimensions.x : brick.dimensions.z, + z: !rotate ? brick.dimensions.z : brick.dimensions.x + } + const { height, width, depth } = getMeasurementsFromDimensions(dimensions); + const brickGeometry = createGeometry({ width, height, depth, dimensions: dimensions, knobDim: 0}); - return { - uID: brick.uID, - relPosition: new Vector3() - .copy(brick.position) - .sub(anchor) - .applyAxisAngle(new Vector3(0, 1, 0), rotate ? Math.PI / 2 : 0) - .toArray(), - geometry: brickGeometry - } - }) - }, [anchor, bricks, rotate]); + return { + uID: brick.uID, + relPosition: new Vector3() + .copy(brick.position) + .sub(anchor) + .applyAxisAngle(new Vector3(0, 1, 0), rotate ? Math.PI / 2 : 0) + .toArray(), + geometry: brickGeometry + } + }) + }, [anchor, bricks, rotate]); - return ( - <> - - {meshes.map((mesh) => { - return ( - - - - ); - })} - - - ); -}; + return ( + <> + + {meshes.map((mesh) => { + return ( + + + + ); + })} + + + ); +}); diff --git a/src/components/3D/Scene.jsx b/src/components/3D/Scene.jsx index 8da92ec..47fa42c 100644 --- a/src/components/3D/Scene.jsx +++ b/src/components/3D/Scene.jsx @@ -35,6 +35,9 @@ export const Scene = () => { const bricksBoundBox = useRef([]); + const brickCursorRef = useRef(); + const multiBrickCursorRef = useRef(); + const mode = useStore((state) => state.mode); const isEditMode = mode === EDIT_MODE; @@ -64,9 +67,6 @@ export const Scene = () => { const anchorZ = useStore((state) => state.anchorZ); const color = useStore((state) => state.color); - const [rawMousePosition, setRawMousePosition] = useState(new Vector3()); - const [mouseIntersect, setMouseIntersect] = useState(new Vector3()); - const room = useStore((state) => state.liveblocks.room); const self = useStore((state) => state.self); @@ -74,12 +74,12 @@ export const Scene = () => { const addBrick = () => { const measures = getMeasurementsFromDimensions({x: width, z: depth}); - const boundingBoxOfBrick = getBoundBoxFromMeasures(mouseIntersect, measures); + const boundingBoxOfBrick = getBoundBoxFromMeasures(brickCursorRef.current.position, measures); const bricksBoundingBox = bricksBoundBox.current.map((bound) => bound.brickBoundingBox); if (!doBoundBoxCollideWithBoundBoxSet(boundingBoxOfBrick, bricksBoundingBox)) { const brickData = { - position: mouseIntersect, + position: new Vector3().copy(brickCursorRef.current.position), uID: uID(), dimensions: { x: width, z: depth }, color: color, @@ -105,7 +105,7 @@ export const Scene = () => { .add(brick.position) .sub(selectedBricksAnchor) .applyAxisAngle(new Vector3(0, 1, 0), rotate ? Math.PI / 2 : 0) - .add(mouseIntersect); + .add(multiBrickCursorRef.current.position); const measures = getMeasurementsFromDimensions(dimensions); const boundingBoxOfBrick = getBoundBoxFromMeasures(position, measures); if (doBoundBoxCollideWithBoundBoxSet(boundingBoxOfBrick, bricksBoundingBox)) { @@ -131,21 +131,33 @@ export const Scene = () => { const setBrickCursorPosition = (e) => { e.stopPropagation(); - const mousePosition = new Vector3() + const mousePosition = normalizePositionToSceneGrid( + new Vector3() .copy(e.point) .add(e.face.normal) - .setY(Math.abs(e.point.y)); + .setY(Math.abs(e.point.y)) + ); - setRawMousePosition(mousePosition); + const translatedXZMousePosition = new Vector3() + .copy(mousePosition) + .add(new Vector3(anchorX * GRID_UNIT.x, 0, anchorZ * GRID_UNIT.z)); - updateMouseIntersectWithTranslation(); + if (isCreateMode && brickCursorRef.current) { + brickCursorRef.current.userData.mousePosition = mousePosition; + brickCursorRef.current.position.copy(translatedXZMousePosition); + } + + if (isEditMode && multiBrickCursorRef.current) { + multiBrickCursorRef.current.userData.mousePosition = mousePosition; + multiBrickCursorRef.current.position.copy(translatedXZMousePosition); + } room.broadcastEvent({ type: self.id, data: { - x: mouseIntersect.x, - y: mouseIntersect.y, - z: mouseIntersect.z, + x: translatedXZMousePosition.x, + y: translatedXZMousePosition.y, + z: translatedXZMousePosition.z, w: width, d: depth, }, @@ -153,17 +165,18 @@ export const Scene = () => { }; - const updateMouseIntersectWithTranslation = () => { - const normalizedMousePosition = normalizePositionToSceneGrid(rawMousePosition); - normalizedMousePosition.add( - new Vector3(anchorX * GRID_UNIT.x, 0, anchorZ * GRID_UNIT.z) - ); - - setMouseIntersect(normalizedMousePosition); - } - useEffect(() => { - updateMouseIntersectWithTranslation(); + if (isCreateMode && brickCursorRef.current && brickCursorRef.current.userData.mousePosition) { + brickCursorRef.current.position + .copy(brickCursorRef.current.userData.mousePosition) + .add(new Vector3(anchorX * GRID_UNIT.x, 0, anchorZ * GRID_UNIT.z)); + } + + if (isEditMode && multiBrickCursorRef.current && multiBrickCursorRef.current.userData.mousePosition) { + multiBrickCursorRef.current.position + .copy(multiBrickCursorRef.current.userData.mousePosition) + .add(new Vector3(anchorX * GRID_UNIT.x, 0, anchorZ * GRID_UNIT.z)); + } }, [anchorX, anchorZ]); useEffect(() => { @@ -233,13 +246,13 @@ export const Scene = () => { workspaceSize={MIN_WORKSPACE_SIZE} />