diff --git a/package-lock.json b/package-lock.json index 23509de..f521db8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,8 @@ "name": "lego-builder", "version": "0.1.0", "dependencies": { + "@liveblocks/client": "^1.0.1", + "@liveblocks/zustand": "^1.0.1", "@react-three/drei": "^9.57.0", "@react-three/fiber": "^8.11.6", "@testing-library/jest-dom": "^5.16.5", @@ -18,7 +20,9 @@ "react-dom": "^18.2.0", "react-scripts": "5.0.1", "three": "^0.150.1", - "web-vitals": "^2.1.4" + "use-keyboard-shortcut": "^1.1.6", + "web-vitals": "^2.1.4", + "zustand": "^4.3.6" } }, "node_modules/@adobe/css-tools": { @@ -3015,6 +3019,31 @@ "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz", "integrity": "sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A==" }, + "node_modules/@liveblocks/client": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@liveblocks/client/-/client-1.0.1.tgz", + "integrity": "sha512-lU+A+v1bbvI6H/pOjo93G8LAf1HYM2yqZc4D/LCI6b/JiZT/3bMrJ2X8Or4Yc5DVmIK3T5Rumk2za9bSdcjBQQ==", + "dependencies": { + "@liveblocks/core": "1.0.1" + } + }, + "node_modules/@liveblocks/core": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@liveblocks/core/-/core-1.0.1.tgz", + "integrity": "sha512-IwtVtp1ogmVJLW7KSmkI7J+8+pPg0FMNdFV71oCPAugmlZRxUer3MvdIBOW4ULFBgoBJOzAmcjWRDFsl3K7ADA==" + }, + "node_modules/@liveblocks/zustand": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@liveblocks/zustand/-/zustand-1.0.1.tgz", + "integrity": "sha512-cAXMS5r9wRa+NFAnJ4IusV/azRxcVB9zWtc99WSCXNq2uhxwlrsG6z+kDNR8LemoDHweSVkpb/c3Hi8FDFnv6Q==", + "dependencies": { + "@liveblocks/client": "1.0.1", + "@liveblocks/core": "1.0.1" + }, + "peerDependencies": { + "zustand": "^4.1.3" + } + }, "node_modules/@nicolo-ribaudo/eslint-scope-5-internals": { "version": "5.1.1-v1", "resolved": "https://registry.npmjs.org/@nicolo-ribaudo/eslint-scope-5-internals/-/eslint-scope-5-internals-5.1.1-v1.tgz", @@ -3290,6 +3319,22 @@ } } }, + "node_modules/@react-three/drei/node_modules/zustand": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-3.7.2.tgz", + "integrity": "sha512-PIJDIZKtokhof+9+60cpockVOq05sJzHCriyvaLBmEJixseQ1a5Kdov6fWZfWOu5SK9c+FhH1jU0tntLxRJYMA==", + "engines": { + "node": ">=12.7.0" + }, + "peerDependencies": { + "react": ">=16.8" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + } + } + }, "node_modules/@react-three/fiber": { "version": "8.11.7", "resolved": "https://registry.npmjs.org/@react-three/fiber/-/fiber-8.11.7.tgz", @@ -3339,6 +3384,22 @@ "loose-envify": "^1.1.0" } }, + "node_modules/@react-three/fiber/node_modules/zustand": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-3.7.2.tgz", + "integrity": "sha512-PIJDIZKtokhof+9+60cpockVOq05sJzHCriyvaLBmEJixseQ1a5Kdov6fWZfWOu5SK9c+FhH1jU0tntLxRJYMA==", + "engines": { + "node": ">=12.7.0" + }, + "peerDependencies": { + "react": ">=16.8" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + } + } + }, "node_modules/@rollup/plugin-babel": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz", @@ -12520,6 +12581,22 @@ "react": "^16.8 || ^17.0" } }, + "node_modules/leva/node_modules/zustand": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-3.7.2.tgz", + "integrity": "sha512-PIJDIZKtokhof+9+60cpockVOq05sJzHCriyvaLBmEJixseQ1a5Kdov6fWZfWOu5SK9c+FhH1jU0tntLxRJYMA==", + "engines": { + "node": ">=12.7.0" + }, + "peerDependencies": { + "react": ">=16.8" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + } + } + }, "node_modules/leven": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", @@ -17292,6 +17369,23 @@ "requires-port": "^1.0.0" } }, + "node_modules/use-keyboard-shortcut": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/use-keyboard-shortcut/-/use-keyboard-shortcut-1.1.6.tgz", + "integrity": "sha512-tOKjR7eKXQIfAgFMwhelpWSRTrwE1GaB8CE/47ohtcVCKxUdn7UbfNNhiigTkATpUVoPu+5tRPbHrwjIBgTYoQ==", + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/use-sync-external-store": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz", + "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -18335,16 +18429,23 @@ "integrity": "sha512-DCo0oxvcvOTGP/f5FA6tz2Z6wF+FIcEApSTu0zV5sQgn9hoT5lZ9YRAKUraxt9oP7l4e8TnNdi8IZTCX6WCkwA==" }, "node_modules/zustand": { - "version": "3.7.2", - "resolved": "https://registry.npmjs.org/zustand/-/zustand-3.7.2.tgz", - "integrity": "sha512-PIJDIZKtokhof+9+60cpockVOq05sJzHCriyvaLBmEJixseQ1a5Kdov6fWZfWOu5SK9c+FhH1jU0tntLxRJYMA==", + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-4.3.6.tgz", + "integrity": "sha512-6J5zDxjxLE+yukC2XZWf/IyWVKnXT9b9HUv09VJ/bwGCpKNcaTqp7Ws28Xr8jnbvnZcdRaidztAPsXFBIqufiw==", + "dependencies": { + "use-sync-external-store": "1.2.0" + }, "engines": { "node": ">=12.7.0" }, "peerDependencies": { + "immer": ">=9.0", "react": ">=16.8" }, "peerDependenciesMeta": { + "immer": { + "optional": true + }, "react": { "optional": true } @@ -20379,6 +20480,28 @@ "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz", "integrity": "sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A==" }, + "@liveblocks/client": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@liveblocks/client/-/client-1.0.1.tgz", + "integrity": "sha512-lU+A+v1bbvI6H/pOjo93G8LAf1HYM2yqZc4D/LCI6b/JiZT/3bMrJ2X8Or4Yc5DVmIK3T5Rumk2za9bSdcjBQQ==", + "requires": { + "@liveblocks/core": "1.0.1" + } + }, + "@liveblocks/core": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@liveblocks/core/-/core-1.0.1.tgz", + "integrity": "sha512-IwtVtp1ogmVJLW7KSmkI7J+8+pPg0FMNdFV71oCPAugmlZRxUer3MvdIBOW4ULFBgoBJOzAmcjWRDFsl3K7ADA==" + }, + "@liveblocks/zustand": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@liveblocks/zustand/-/zustand-1.0.1.tgz", + "integrity": "sha512-cAXMS5r9wRa+NFAnJ4IusV/azRxcVB9zWtc99WSCXNq2uhxwlrsG6z+kDNR8LemoDHweSVkpb/c3Hi8FDFnv6Q==", + "requires": { + "@liveblocks/client": "1.0.1", + "@liveblocks/core": "1.0.1" + } + }, "@nicolo-ribaudo/eslint-scope-5-internals": { "version": "5.1.1-v1", "resolved": "https://registry.npmjs.org/@nicolo-ribaudo/eslint-scope-5-internals/-/eslint-scope-5-internals-5.1.1-v1.tgz", @@ -20570,6 +20693,14 @@ "troika-three-text": "^0.47.1", "utility-types": "^3.10.0", "zustand": "^3.5.13" + }, + "dependencies": { + "zustand": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-3.7.2.tgz", + "integrity": "sha512-PIJDIZKtokhof+9+60cpockVOq05sJzHCriyvaLBmEJixseQ1a5Kdov6fWZfWOu5SK9c+FhH1jU0tntLxRJYMA==", + "requires": {} + } } }, "@react-three/fiber": { @@ -20594,6 +20725,12 @@ "requires": { "loose-envify": "^1.1.0" } + }, + "zustand": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-3.7.2.tgz", + "integrity": "sha512-PIJDIZKtokhof+9+60cpockVOq05sJzHCriyvaLBmEJixseQ1a5Kdov6fWZfWOu5SK9c+FhH1jU0tntLxRJYMA==", + "requires": {} } } }, @@ -27356,6 +27493,12 @@ } } } + }, + "zustand": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-3.7.2.tgz", + "integrity": "sha512-PIJDIZKtokhof+9+60cpockVOq05sJzHCriyvaLBmEJixseQ1a5Kdov6fWZfWOu5SK9c+FhH1jU0tntLxRJYMA==", + "requires": {} } } }, @@ -30667,6 +30810,18 @@ "requires-port": "^1.0.0" } }, + "use-keyboard-shortcut": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/use-keyboard-shortcut/-/use-keyboard-shortcut-1.1.6.tgz", + "integrity": "sha512-tOKjR7eKXQIfAgFMwhelpWSRTrwE1GaB8CE/47ohtcVCKxUdn7UbfNNhiigTkATpUVoPu+5tRPbHrwjIBgTYoQ==", + "requires": {} + }, + "use-sync-external-store": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz", + "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==", + "requires": {} + }, "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -31481,10 +31636,12 @@ "integrity": "sha512-DCo0oxvcvOTGP/f5FA6tz2Z6wF+FIcEApSTu0zV5sQgn9hoT5lZ9YRAKUraxt9oP7l4e8TnNdi8IZTCX6WCkwA==" }, "zustand": { - "version": "3.7.2", - "resolved": "https://registry.npmjs.org/zustand/-/zustand-3.7.2.tgz", - "integrity": "sha512-PIJDIZKtokhof+9+60cpockVOq05sJzHCriyvaLBmEJixseQ1a5Kdov6fWZfWOu5SK9c+FhH1jU0tntLxRJYMA==", - "requires": {} + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-4.3.6.tgz", + "integrity": "sha512-6J5zDxjxLE+yukC2XZWf/IyWVKnXT9b9HUv09VJ/bwGCpKNcaTqp7Ws28Xr8jnbvnZcdRaidztAPsXFBIqufiw==", + "requires": { + "use-sync-external-store": "1.2.0" + } } } } diff --git a/package.json b/package.json index c4d7677..c05a704 100644 --- a/package.json +++ b/package.json @@ -3,17 +3,21 @@ "version": "0.1.0", "private": true, "dependencies": { + "@liveblocks/client": "^1.0.1", + "@liveblocks/zustand": "^1.0.1", + "@react-three/drei": "^9.57.0", + "@react-three/fiber": "^8.11.6", "@testing-library/jest-dom": "^5.16.5", "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", + "leva": "^0.9.34", "react": "^18.2.0", "react-dom": "^18.2.0", "react-scripts": "5.0.1", - "web-vitals": "^2.1.4", - "@react-three/drei": "^9.57.0", - "@react-three/fiber": "^8.11.6", "three": "^0.150.1", - "leva": "^0.9.34" + "use-keyboard-shortcut": "^1.1.6", + "web-vitals": "^2.1.4", + "zustand": "^4.3.6" }, "scripts": { "start": "react-scripts start", diff --git a/src/App.js b/src/App.js index f22d6da..4dded16 100644 --- a/src/App.js +++ b/src/App.js @@ -5,9 +5,9 @@ import { Suspense } from "react"; import { LinearToneMapping } from "three"; import { InfoPanel, Scene } from "./components"; -function App() { +const LegoRoom = () => { return ( -
+ <> @@ -30,6 +30,14 @@ function App() { + + ); +}; + +function App() { + return ( +
+
); } diff --git a/src/components/Brick.jsx b/src/components/Brick.jsx index 260e504..f2f6a63 100644 --- a/src/components/Brick.jsx +++ b/src/components/Brick.jsx @@ -12,8 +12,8 @@ export const Brick = ({ color = "#ff0000", dimensions = { x: 1, z: 1 }, rotation = 0, - translation = 0, - addBrick = () => {}, + translation = { x: 0, z: 0 }, + onClick = () => {}, bricksBoundBox = { current: [] }, uID = "", mouseMove = () => {}, @@ -54,28 +54,53 @@ export const Brick = ({ boundingBoxRef.push({ uID, brickBoundingBox }); return () => { - boundingBoxRef.filter((brick) => brick.uID !== uID); + boundingBoxRef.filter((brick) => { + return brick.uID !== uID; + }); }; - }, [uID, bricksBoundBox]); + }, []); + + 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; + + const offsetX = + Math.sign(translation.x) < 0 + ? Math.max(translation.x, -compansateX) + : Math.min(translation.x, compansateX); + + const offsetZ = + Math.sign(translation.z) < 0 + ? Math.max(translation.z, -compansateZ) + : Math.min(translation.z, compansateZ); return ( <> - - - + + + + ); }; diff --git a/src/components/BrickCursor.jsx b/src/components/BrickCursor.jsx index efe6706..12196df 100644 --- a/src/components/BrickCursor.jsx +++ b/src/components/BrickCursor.jsx @@ -1,4 +1,3 @@ -import { useThree } from "@react-three/fiber"; import React, { forwardRef, useMemo } from "react"; import { Vector3 } from "three"; import { getMeasurementsFromDimensions, base } from "../utils"; @@ -12,7 +11,7 @@ export const BrickCursor = forwardRef( }, dimensions = { x: 1, z: 1 }, rotation = 0, - translation = 0, + translation = { x: 0, z: 0 }, }, ref ) => { @@ -37,18 +36,43 @@ export const BrickCursor = forwardRef( ); }, [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; + + const offsetX = + Math.sign(translation.x) < 0 + ? Math.max(translation.x, -compansateX) + : Math.min(translation.x, compansateX); + + const offsetZ = + Math.sign(translation.z) < 0 + ? Math.max(translation.z, -compansateZ) + : Math.min(translation.z, compansateZ); + return ( <> - - - - + + + + + ); } diff --git a/src/components/InfoPanel.jsx b/src/components/InfoPanel.jsx index 39e725e..1b831d4 100644 --- a/src/components/InfoPanel.jsx +++ b/src/components/InfoPanel.jsx @@ -13,7 +13,9 @@ export const InfoPanel = () => { width: "200px", }} > - Use panel on right top corner to customize the Lego Bricks Properties + Use panel on right top corner to customize the Lego Bricks Properties Use +
+ "AWSD" to chnage the anchor points of bricks
); }; diff --git a/src/components/Lights.jsx b/src/components/Lights.jsx index 13e28d7..cbbd382 100644 --- a/src/components/Lights.jsx +++ b/src/components/Lights.jsx @@ -1,17 +1,48 @@ import React from "react"; +// import { useControls } from "leva"; export const Lights = () => { + // const [{ left, bottom, far, near, zoom }, set] = useControls(() => ({ + // left: { + // value: 1, + // min: 1, + // max: 500, + // step: 1, + // }, + // bottom: { + // value: 1, + // min: 1, + // max: 500, + // step: 1, + // }, + // far: { + // value: 1, + // min: 1, + // max: 50000, + // step: 1, + // }, + // near: { + // value: 0.01, + // min: 0.01, + // max: 20, + // step: 0.1, + // }, + // zoom: { + // value: 0, + // min: 0, + // max: 2, + // step: 0.1, + // }, + // })); + return ( <> diff --git a/src/components/Scene.jsx b/src/components/Scene.jsx index 79bde87..4373bc4 100644 --- a/src/components/Scene.jsx +++ b/src/components/Scene.jsx @@ -1,7 +1,12 @@ -import React, { useEffect, useState, useRef } from "react"; -import { Shadow, Brick, BrickCursor, Lights, Workspace } from "."; +import React, { useEffect, useRef, useState } from "react"; +import { Brick, BrickCursor, Lights, Workspace } from "."; import { Vector3, Box3 } from "three"; -import { uID, getMeasurementsFromDimensions, base } from "../utils"; +import { + uID, + getMeasurementsFromDimensions, + base, + useAnchorShorcuts, +} from "../utils"; import { useControls } from "leva"; let t; @@ -13,22 +18,38 @@ export const Scene = () => { const brickCursorRef = useRef(); - const { width, depth, rotate, color } = useControls({ - width: { - value: 1, - min: 1, - max: 5, - step: 1, - }, - depth: { - value: 1, - min: 1, - max: 5, - step: 1, - }, - rotate: false, - color: "#f00", - }); + const [{ width, depth, rotate, color, anchorX, anchorZ }, set] = useControls( + () => ({ + width: { + value: 1, + min: 1, + max: 5, + step: 1, + }, + depth: { + value: 1, + min: 1, + max: 5, + step: 1, + }, + rotate: false, + color: "#ff0", + anchorX: { + value: 0, + min: -2, + max: 2, + step: 1, + }, + anchorZ: { + value: 0, + min: -2, + max: 2, + step: 1, + }, + }) + ); + + useAnchorShorcuts(anchorX, anchorZ, set); const addBrick = (e) => { e.stopPropagation(); @@ -53,7 +74,7 @@ export const Scene = () => { const collision = boundingBoxOfBrickToBeAdded.intersectsBox(brickBoundingBox); - console.log(collision); + console.log(collision, brickBoundingBox, boundingBoxOfBrickToBeAdded); if (collision) { const dx = Math.abs( @@ -82,16 +103,17 @@ export const Scene = () => { dimensions: { x: width, z: depth }, rotation: rotate ? Math.PI / 2 : 0, color: color, + translation: { x: anchorX, z: anchorZ }, }; - setBricks((prevState) => [...prevState, brickData]); + setBricks((prevBricks) => [...prevBricks, brickData]); } } else { isDrag.current = false; } }; - const mouseMove = (e) => { + const setBrickCursorPosition = (e) => { e.stopPropagation(); if (!brickCursorRef.current) return; @@ -120,6 +142,14 @@ export const Scene = () => { ); }; + const onClick = (e) => { + addBrick(e); + }; + + const mouseMove = (e) => { + setBrickCursorPosition(e); + }; + const isDrag = useRef(false); useEffect(() => { @@ -152,22 +182,23 @@ export const Scene = () => { key={b.uID} dimensions={b.dimensions} intersect={b.intersect} - addBrick={addBrick} + onClick={onClick} rotation={b.rotation} bricksBoundBox={bricksBoundBox} uID={b.uID} mouseMove={mouseMove} color={b.color} + translation={b.translation} /> ); })} - - + ); diff --git a/src/components/Workspace.jsx b/src/components/Workspace.jsx index 20005bb..1afa2c7 100644 --- a/src/components/Workspace.jsx +++ b/src/components/Workspace.jsx @@ -1,17 +1,25 @@ import React from "react"; -export const Workspace = ({ addBrick = () => {}, mouseMove = () => {} }) => { +export const Workspace = ({ onClick = () => {}, mouseMove = () => {} }) => { return ( <> - + + {/* + + + */} ); }; diff --git a/src/store/index.js b/src/store/index.js new file mode 100644 index 0000000..e69de29 diff --git a/src/utils/hooks.js b/src/utils/hooks.js new file mode 100644 index 0000000..1311e6e --- /dev/null +++ b/src/utils/hooks.js @@ -0,0 +1,45 @@ +import useKeyboardShortcut from "use-keyboard-shortcut"; + +export const useAnchorShorcuts = (anchorX, anchorZ, set) => { + const anchorXPlus = () => { + set({ anchorX: anchorX + 1 }); + }; + + const anchorXMinus = () => { + set({ anchorX: anchorX - 1 }); + }; + + const anchorZPlus = () => { + set({ anchorZ: anchorZ - 1 }); + }; + + const anchorZMinus = () => { + set({ anchorZ: 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; +}; diff --git a/src/utils/index.js b/src/utils/index.js index 035c458..c9a72e2 100644 --- a/src/utils/index.js +++ b/src/utils/index.js @@ -1,2 +1,3 @@ export * from "./helpers"; export * from "./constants"; +export * from "./hooks";