feat: Integrate @react-three/rapier for physics simulation and add conveyor collider functionality
- Added @react-three/rapier to package.json for physics support. - Refactored AssetBoundingBox to utilize RigidBody for collision detection. - Implemented ConveyorCollider to manage object movement on conveyor belts. - Enhanced Model component to include rigid body references and bounding box calculations. - Updated Ground component to use RigidBody for ground physics. - Introduced Colliders component to manage material instances with physics interactions. - Created SecondaryCamera for enhanced camera management and editing capabilities. - Added secondary canvas for rendering secondary camera views. - Updated selection controls to utilize bounding boxes for asset selection.
This commit is contained in:
70
app/package-lock.json
generated
70
app/package-lock.json
generated
@@ -16,6 +16,7 @@
|
|||||||
"@react-three/drei": "^9.113.0",
|
"@react-three/drei": "^9.113.0",
|
||||||
"@react-three/fiber": "^8.17.7",
|
"@react-three/fiber": "^8.17.7",
|
||||||
"@react-three/postprocessing": "^2.16.3",
|
"@react-three/postprocessing": "^2.16.3",
|
||||||
|
"@react-three/rapier": "^1.5.0",
|
||||||
"@recast-navigation/core": "^0.39.0",
|
"@recast-navigation/core": "^0.39.0",
|
||||||
"@recast-navigation/three": "^0.39.0",
|
"@recast-navigation/three": "^0.39.0",
|
||||||
"@testing-library/jest-dom": "^5.17.0",
|
"@testing-library/jest-dom": "^5.17.0",
|
||||||
@@ -2026,7 +2027,7 @@
|
|||||||
"version": "0.8.1",
|
"version": "0.8.1",
|
||||||
"resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz",
|
"resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz",
|
||||||
"integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==",
|
"integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==",
|
||||||
"dev": true,
|
"devOptional": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@jridgewell/trace-mapping": "0.3.9"
|
"@jridgewell/trace-mapping": "0.3.9"
|
||||||
},
|
},
|
||||||
@@ -2038,7 +2039,7 @@
|
|||||||
"version": "0.3.9",
|
"version": "0.3.9",
|
||||||
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz",
|
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz",
|
||||||
"integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==",
|
"integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==",
|
||||||
"dev": true,
|
"devOptional": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@jridgewell/resolve-uri": "^3.0.3",
|
"@jridgewell/resolve-uri": "^3.0.3",
|
||||||
"@jridgewell/sourcemap-codec": "^1.4.10"
|
"@jridgewell/sourcemap-codec": "^1.4.10"
|
||||||
@@ -2371,6 +2372,12 @@
|
|||||||
"ms": "^2.1.1"
|
"ms": "^2.1.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@dimforge/rapier3d-compat": {
|
||||||
|
"version": "0.14.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@dimforge/rapier3d-compat/-/rapier3d-compat-0.14.0.tgz",
|
||||||
|
"integrity": "sha512-/uHrUzS+CRQ+NQrrJCEDUkhwHlNsAAexbNXgbN9sHY+GwR+SFFAFrxRr8Llf5/AJZzqiLANdQIfJ63Cw4gJVqw==",
|
||||||
|
"license": "Apache-2.0"
|
||||||
|
},
|
||||||
"node_modules/@dnd-kit/accessibility": {
|
"node_modules/@dnd-kit/accessibility": {
|
||||||
"version": "3.1.1",
|
"version": "3.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/@dnd-kit/accessibility/-/accessibility-3.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/@dnd-kit/accessibility/-/accessibility-3.1.1.tgz",
|
||||||
@@ -3804,6 +3811,21 @@
|
|||||||
"three": ">=0.144.0"
|
"three": ">=0.144.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@react-three/rapier": {
|
||||||
|
"version": "1.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@react-three/rapier/-/rapier-1.5.0.tgz",
|
||||||
|
"integrity": "sha512-gylk2KyCer9EoymFyTyc+g2IqyAq4mTbZgaHoSJi6gHoXlJsC2LVeN4jedvegvjUsXPExdE60wHjCPa+DS4iXw==",
|
||||||
|
"dependencies": {
|
||||||
|
"@dimforge/rapier3d-compat": "0.14.0",
|
||||||
|
"suspend-react": "^0.1.3",
|
||||||
|
"three-stdlib": "^2.29.4"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@react-three/fiber": ">=8.9.0",
|
||||||
|
"react": ">=18.0.0",
|
||||||
|
"three": ">=0.139.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@recast-navigation/core": {
|
"node_modules/@recast-navigation/core": {
|
||||||
"version": "0.39.0",
|
"version": "0.39.0",
|
||||||
"resolved": "https://registry.npmjs.org/@recast-navigation/core/-/core-0.39.0.tgz",
|
"resolved": "https://registry.npmjs.org/@recast-navigation/core/-/core-0.39.0.tgz",
|
||||||
@@ -4180,6 +4202,26 @@
|
|||||||
"url": "https://github.com/sponsors/gregberge"
|
"url": "https://github.com/sponsors/gregberge"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@testing-library/dom": {
|
||||||
|
"version": "10.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.0.tgz",
|
||||||
|
"integrity": "sha512-pemlzrSESWbdAloYml3bAJMEfNh1Z7EduzqPKprCH5S341frlpYnUEW0H72dLxa6IsYr+mPno20GiSm+h9dEdQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/code-frame": "^7.10.4",
|
||||||
|
"@babel/runtime": "^7.12.5",
|
||||||
|
"@types/aria-query": "^5.0.1",
|
||||||
|
"aria-query": "5.3.0",
|
||||||
|
"chalk": "^4.1.0",
|
||||||
|
"dom-accessibility-api": "^0.5.9",
|
||||||
|
"lz-string": "^1.5.0",
|
||||||
|
"pretty-format": "^27.0.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@testing-library/jest-dom": {
|
"node_modules/@testing-library/jest-dom": {
|
||||||
"version": "5.17.0",
|
"version": "5.17.0",
|
||||||
"resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-5.17.0.tgz",
|
"resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-5.17.0.tgz",
|
||||||
@@ -4291,25 +4333,25 @@
|
|||||||
"version": "1.0.11",
|
"version": "1.0.11",
|
||||||
"resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz",
|
"resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz",
|
||||||
"integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==",
|
"integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==",
|
||||||
"dev": true
|
"devOptional": true
|
||||||
},
|
},
|
||||||
"node_modules/@tsconfig/node12": {
|
"node_modules/@tsconfig/node12": {
|
||||||
"version": "1.0.11",
|
"version": "1.0.11",
|
||||||
"resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz",
|
"resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz",
|
||||||
"integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==",
|
"integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==",
|
||||||
"dev": true
|
"devOptional": true
|
||||||
},
|
},
|
||||||
"node_modules/@tsconfig/node14": {
|
"node_modules/@tsconfig/node14": {
|
||||||
"version": "1.0.3",
|
"version": "1.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz",
|
||||||
"integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==",
|
"integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==",
|
||||||
"dev": true
|
"devOptional": true
|
||||||
},
|
},
|
||||||
"node_modules/@tsconfig/node16": {
|
"node_modules/@tsconfig/node16": {
|
||||||
"version": "1.0.4",
|
"version": "1.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz",
|
||||||
"integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==",
|
"integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==",
|
||||||
"dev": true
|
"devOptional": true
|
||||||
},
|
},
|
||||||
"node_modules/@turf/along": {
|
"node_modules/@turf/along": {
|
||||||
"version": "7.2.0",
|
"version": "7.2.0",
|
||||||
@@ -9063,7 +9105,7 @@
|
|||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz",
|
||||||
"integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==",
|
"integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==",
|
||||||
"dev": true
|
"devOptional": true
|
||||||
},
|
},
|
||||||
"node_modules/cross-env": {
|
"node_modules/cross-env": {
|
||||||
"version": "7.0.3",
|
"version": "7.0.3",
|
||||||
@@ -9940,7 +9982,7 @@
|
|||||||
"version": "4.0.2",
|
"version": "4.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz",
|
||||||
"integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==",
|
"integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==",
|
||||||
"dev": true,
|
"devOptional": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.3.1"
|
"node": ">=0.3.1"
|
||||||
}
|
}
|
||||||
@@ -15324,7 +15366,7 @@
|
|||||||
"version": "1.3.6",
|
"version": "1.3.6",
|
||||||
"resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz",
|
"resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz",
|
||||||
"integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==",
|
"integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==",
|
||||||
"dev": true
|
"devOptional": true
|
||||||
},
|
},
|
||||||
"node_modules/makeerror": {
|
"node_modules/makeerror": {
|
||||||
"version": "1.0.12",
|
"version": "1.0.12",
|
||||||
@@ -20801,7 +20843,7 @@
|
|||||||
"version": "10.9.2",
|
"version": "10.9.2",
|
||||||
"resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz",
|
"resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz",
|
||||||
"integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==",
|
"integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==",
|
||||||
"dev": true,
|
"devOptional": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@cspotcode/source-map-support": "^0.8.0",
|
"@cspotcode/source-map-support": "^0.8.0",
|
||||||
"@tsconfig/node10": "^1.0.7",
|
"@tsconfig/node10": "^1.0.7",
|
||||||
@@ -20844,7 +20886,7 @@
|
|||||||
"version": "8.3.4",
|
"version": "8.3.4",
|
||||||
"resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz",
|
"resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz",
|
||||||
"integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==",
|
"integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==",
|
||||||
"dev": true,
|
"devOptional": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"acorn": "^8.11.0"
|
"acorn": "^8.11.0"
|
||||||
},
|
},
|
||||||
@@ -20856,7 +20898,7 @@
|
|||||||
"version": "4.1.3",
|
"version": "4.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz",
|
||||||
"integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==",
|
"integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==",
|
||||||
"dev": true
|
"devOptional": true
|
||||||
},
|
},
|
||||||
"node_modules/tsconfig-paths": {
|
"node_modules/tsconfig-paths": {
|
||||||
"version": "3.15.0",
|
"version": "3.15.0",
|
||||||
@@ -21352,7 +21394,7 @@
|
|||||||
"version": "3.0.1",
|
"version": "3.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz",
|
||||||
"integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==",
|
"integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==",
|
||||||
"dev": true
|
"devOptional": true
|
||||||
},
|
},
|
||||||
"node_modules/v8-to-istanbul": {
|
"node_modules/v8-to-istanbul": {
|
||||||
"version": "8.1.1",
|
"version": "8.1.1",
|
||||||
@@ -22411,7 +22453,7 @@
|
|||||||
"version": "3.1.1",
|
"version": "3.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz",
|
||||||
"integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==",
|
"integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==",
|
||||||
"dev": true,
|
"devOptional": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=6"
|
"node": ">=6"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
"@react-three/csg": "^3.2.0",
|
"@react-three/csg": "^3.2.0",
|
||||||
"@react-three/drei": "^9.113.0",
|
"@react-three/drei": "^9.113.0",
|
||||||
"@react-three/fiber": "^8.17.7",
|
"@react-three/fiber": "^8.17.7",
|
||||||
|
"@react-three/rapier": "^1.5.0",
|
||||||
"@react-three/postprocessing": "^2.16.3",
|
"@react-three/postprocessing": "^2.16.3",
|
||||||
"@recast-navigation/core": "^0.39.0",
|
"@recast-navigation/core": "^0.39.0",
|
||||||
"@recast-navigation/three": "^0.39.0",
|
"@recast-navigation/three": "^0.39.0",
|
||||||
|
|||||||
@@ -1,20 +1,26 @@
|
|||||||
import { Box3, BoxGeometry, EdgesGeometry, Vector3 } from "three";
|
import { Box3, BoxGeometry, EdgesGeometry, Vector3 } from "three";
|
||||||
|
import { RigidBody } from "@react-three/rapier";
|
||||||
|
import { useMemo } from "react";
|
||||||
|
|
||||||
export const AssetBoundingBox = ({ boundingBox }: { boundingBox: Box3 | null }) => {
|
export const AssetBoundingBox = ({ boundingBox }: { boundingBox: Box3 }) => {
|
||||||
if (!boundingBox) return null;
|
|
||||||
|
|
||||||
const size = boundingBox.getSize(new Vector3());
|
const size = boundingBox.getSize(new Vector3());
|
||||||
const center = boundingBox.getCenter(new Vector3());
|
const center = boundingBox.getCenter(new Vector3());
|
||||||
|
|
||||||
const boxGeometry = new BoxGeometry(size.x, size.y, size.z);
|
const boxGeometry = useMemo(() => new BoxGeometry(size.x, size.y, size.z), [size]);
|
||||||
const edges = new EdgesGeometry(boxGeometry);
|
const edges = useMemo(() => new EdgesGeometry(boxGeometry), [boxGeometry]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<group name='Asset FallBack'>
|
<RigidBody includeInvisible type="fixed" colliders="cuboid" position={center.toArray()} rotation={[0, 0, 0]}>
|
||||||
<lineSegments position={center}>
|
<lineSegments>
|
||||||
<bufferGeometry attach="geometry" {...edges} />
|
<bufferGeometry attach="geometry" {...edges} />
|
||||||
<lineBasicMaterial depthWrite={false} attach="material" color="gray" linewidth={1} />
|
<lineBasicMaterial depthWrite={false} attach="material" color="gray" linewidth={1} />
|
||||||
</lineSegments>
|
</lineSegments>
|
||||||
</group>
|
|
||||||
|
<mesh visible={false}>
|
||||||
|
<boxGeometry args={[size.x, size.y, size.z]} />
|
||||||
|
<meshStandardMaterial transparent opacity={0} />
|
||||||
|
</mesh>
|
||||||
|
</RigidBody>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
148
app/src/modules/builder/asset/models/model/conveyorCollider.tsx
Normal file
148
app/src/modules/builder/asset/models/model/conveyorCollider.tsx
Normal file
@@ -0,0 +1,148 @@
|
|||||||
|
import * as THREE from 'three';
|
||||||
|
import { CollisionPayload, RapierRigidBody, RigidBody } from '@react-three/rapier';
|
||||||
|
import { useEffect, useRef, useState } from 'react';
|
||||||
|
import { useFrame } from '@react-three/fiber';
|
||||||
|
|
||||||
|
function ConveyorCollider({ boundingBox, asset, conveyorPlaneSize, onReachEnd }: {
|
||||||
|
boundingBox: THREE.Box3 | null,
|
||||||
|
asset: Asset,
|
||||||
|
conveyorPlaneSize: [number, number] | null,
|
||||||
|
onReachEnd?: (rigidBody: RapierRigidBody) => void
|
||||||
|
}) {
|
||||||
|
const conveyorRef = useRef<any>(null);
|
||||||
|
const [objectsOnConveyor, setObjectsOnConveyor] = useState<Set<any>>(new Set());
|
||||||
|
const conveyorDirection = useRef<THREE.Vector3>(new THREE.Vector3());
|
||||||
|
const conveyorSpeed = 2;
|
||||||
|
const reached = useRef<Set<RapierRigidBody>>(new Set());
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!boundingBox || !conveyorPlaneSize) return;
|
||||||
|
|
||||||
|
const [width, depth] = conveyorPlaneSize;
|
||||||
|
if (width < depth) {
|
||||||
|
conveyorDirection.current.set(0, 0, 1);
|
||||||
|
} else {
|
||||||
|
conveyorDirection.current.set(1, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
const rotation = new THREE.Euler().fromArray(asset.rotation || [0, 0, 0]);
|
||||||
|
conveyorDirection.current.applyEuler(rotation);
|
||||||
|
}, [boundingBox, conveyorPlaneSize, asset.rotation]);
|
||||||
|
|
||||||
|
const handleMaterialEnter = (e: CollisionPayload) => {
|
||||||
|
if (e.other.rigidBody) {
|
||||||
|
setObjectsOnConveyor(prev => {
|
||||||
|
const newSet = new Set(prev);
|
||||||
|
newSet.add(e.other.rigidBody);
|
||||||
|
return newSet;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleMaterialExit = (e: CollisionPayload) => {
|
||||||
|
if (e.other.rigidBody) {
|
||||||
|
setObjectsOnConveyor(prev => {
|
||||||
|
const newSet = new Set(prev);
|
||||||
|
newSet.delete(e.other.rigidBody);
|
||||||
|
return newSet;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// useFrame(() => {
|
||||||
|
// const forward = conveyorDirection.current.clone().normalize();
|
||||||
|
// const side = new THREE.Vector3().crossVectors(forward, new THREE.Vector3(0, 1, 0)).normalize(); // perpendicular vector
|
||||||
|
|
||||||
|
// const force = forward.clone().multiplyScalar(conveyorSpeed);
|
||||||
|
|
||||||
|
// objectsOnConveyor.forEach(rigidBody => {
|
||||||
|
// if (rigidBody) {
|
||||||
|
// const position = rigidBody.translation();
|
||||||
|
|
||||||
|
// const centerLine = conveyorRef.current.translation();
|
||||||
|
// const relative = new THREE.Vector3().subVectors(position, centerLine);
|
||||||
|
// const sideOffset = relative.dot(side);
|
||||||
|
|
||||||
|
// const centeringStrength = 10;
|
||||||
|
// const centeringForce = side.clone().multiplyScalar(-sideOffset * centeringStrength);
|
||||||
|
|
||||||
|
// const totalForce = force.clone().add(centeringForce);
|
||||||
|
|
||||||
|
// rigidBody.setAngvel(new THREE.Vector3(0, 0, 0), true);
|
||||||
|
// rigidBody.setLinvel(totalForce, true);
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
// });
|
||||||
|
useFrame(() => {
|
||||||
|
if (
|
||||||
|
!boundingBox ||
|
||||||
|
!conveyorPlaneSize ||
|
||||||
|
!conveyorRef.current ||
|
||||||
|
typeof conveyorRef.current.translation !== 'function'
|
||||||
|
) return;
|
||||||
|
|
||||||
|
const forward = conveyorDirection.current.clone().normalize();
|
||||||
|
const side = new THREE.Vector3().crossVectors(forward, new THREE.Vector3(0, 1, 0)).normalize();
|
||||||
|
const force = forward.clone().multiplyScalar(conveyorSpeed);
|
||||||
|
const start = conveyorRef.current.translation();
|
||||||
|
const conveyorLength = Math.max(conveyorPlaneSize[0], conveyorPlaneSize[1]);
|
||||||
|
const halfLength = conveyorLength / 2;
|
||||||
|
|
||||||
|
objectsOnConveyor.forEach(rigidBody => {
|
||||||
|
if (!rigidBody) return;
|
||||||
|
|
||||||
|
const position = rigidBody.translation();
|
||||||
|
const relative = new THREE.Vector3().subVectors(position, start);
|
||||||
|
const forwardOffset = relative.dot(forward);
|
||||||
|
const sideOffset = relative.dot(side);
|
||||||
|
const atEnd = forwardOffset >= halfLength - 0.5;
|
||||||
|
|
||||||
|
if (!atEnd) {
|
||||||
|
const centeringStrength = 10;
|
||||||
|
const centeringForce = side.clone().multiplyScalar(-sideOffset * centeringStrength);
|
||||||
|
const totalForce = force.clone().add(centeringForce);
|
||||||
|
rigidBody.setAngvel(new THREE.Vector3(0, 0, 0), true);
|
||||||
|
rigidBody.setLinvel(totalForce, true);
|
||||||
|
} else {
|
||||||
|
rigidBody.setLinvel(new THREE.Vector3(0, 0, 0), true);
|
||||||
|
rigidBody.setAngvel(new THREE.Vector3(0, 0, 0), true);
|
||||||
|
|
||||||
|
if (!reached.current.has(rigidBody)) {
|
||||||
|
reached.current.add(rigidBody);
|
||||||
|
console.log("✅ Triggering spawn from conveyor");
|
||||||
|
onReachEnd?.(rigidBody); // ✅ trigger here
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{asset.eventData?.type === 'Conveyor' && conveyorPlaneSize && (
|
||||||
|
<RigidBody
|
||||||
|
ref={conveyorRef}
|
||||||
|
type="fixed"
|
||||||
|
position={[0, (boundingBox?.max.y ?? 0) + 0.1, 0]}
|
||||||
|
rotation={[Math.PI / 2, 0, 0]}
|
||||||
|
sensor
|
||||||
|
userData={{ isConveyor: true }}
|
||||||
|
onIntersectionEnter={handleMaterialEnter}
|
||||||
|
onIntersectionExit={handleMaterialExit}
|
||||||
|
colliders="cuboid"
|
||||||
|
>
|
||||||
|
<mesh>
|
||||||
|
<planeGeometry args={conveyorPlaneSize} />
|
||||||
|
<meshBasicMaterial
|
||||||
|
color="green"
|
||||||
|
transparent
|
||||||
|
opacity={0.3}
|
||||||
|
visible={false}
|
||||||
|
/>
|
||||||
|
</mesh>
|
||||||
|
</RigidBody>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ConveyorCollider;
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
import * as THREE from 'three';
|
import * as THREE from 'three';
|
||||||
|
import { SkeletonUtils } from 'three-stdlib';
|
||||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||||
import { retrieveGLTF, storeGLTF } from '../../../../../utils/indexDB/idbUtils';
|
import { retrieveGLTF, storeGLTF } from '../../../../../utils/indexDB/idbUtils';
|
||||||
|
import { RapierRigidBody, RigidBody } from '@react-three/rapier';
|
||||||
import { GLTF, GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader';
|
import { GLTF, GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader';
|
||||||
import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader';
|
import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader';
|
||||||
import { ThreeEvent, useFrame, useThree } from '@react-three/fiber';
|
import { ThreeEvent, useFrame, useThree } from '@react-three/fiber';
|
||||||
@@ -15,14 +17,14 @@ import { useParams } from 'react-router-dom';
|
|||||||
import { getUserData } from '../../../../../functions/getUserData';
|
import { getUserData } from '../../../../../functions/getUserData';
|
||||||
import { useSceneContext } from '../../../../scene/sceneContext';
|
import { useSceneContext } from '../../../../scene/sceneContext';
|
||||||
import { useVersionContext } from '../../../version/versionContext';
|
import { useVersionContext } from '../../../version/versionContext';
|
||||||
import { SkeletonUtils } from 'three-stdlib';
|
|
||||||
import { useAnimationPlaySpeed } from '../../../../../store/usePlayButtonStore';
|
import { useAnimationPlaySpeed } from '../../../../../store/usePlayButtonStore';
|
||||||
import { upsertProductOrEventApi } from '../../../../../services/simulation/products/UpsertProductOrEventApi';
|
import { upsertProductOrEventApi } from '../../../../../services/simulation/products/UpsertProductOrEventApi';
|
||||||
import { getAssetIksApi } from '../../../../../services/simulation/ik/getAssetIKs';
|
import { getAssetIksApi } from '../../../../../services/simulation/ik/getAssetIKs';
|
||||||
|
import ConveyorCollider from './conveyorCollider';
|
||||||
|
|
||||||
function Model({ asset }: { readonly asset: Asset }) {
|
function Model({ asset }: { readonly asset: Asset }) {
|
||||||
const url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_MARKETPLACE_URL}`;
|
const url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_MARKETPLACE_URL}`;
|
||||||
const { camera, controls, gl } = useThree();
|
const { camera, controls, gl, scene } = useThree();
|
||||||
const { activeTool } = useActiveTool();
|
const { activeTool } = useActiveTool();
|
||||||
const { toolMode } = useToolMode();
|
const { toolMode } = useToolMode();
|
||||||
const { toggleView } = useToggleView();
|
const { toggleView } = useToggleView();
|
||||||
@@ -50,7 +52,9 @@ function Model({ asset }: { readonly asset: Asset }) {
|
|||||||
const [isRendered, setIsRendered] = useState(false);
|
const [isRendered, setIsRendered] = useState(false);
|
||||||
const [gltfScene, setGltfScene] = useState<GLTF["scene"] | null>(null);
|
const [gltfScene, setGltfScene] = useState<GLTF["scene"] | null>(null);
|
||||||
const [boundingBox, setBoundingBox] = useState<THREE.Box3 | null>(null);
|
const [boundingBox, setBoundingBox] = useState<THREE.Box3 | null>(null);
|
||||||
|
const [conveyorPlaneSize, setConveyorPlaneSize] = useState<[number, number] | null>(null);
|
||||||
const groupRef = useRef<THREE.Group>(null);
|
const groupRef = useRef<THREE.Group>(null);
|
||||||
|
const rigidBodyRef = useRef<RapierRigidBody>(null);
|
||||||
const mixerRef = useRef<THREE.AnimationMixer>();
|
const mixerRef = useRef<THREE.AnimationMixer>();
|
||||||
const actions = useRef<{ [name: string]: THREE.AnimationAction }>({});
|
const actions = useRef<{ [name: string]: THREE.AnimationAction }>({});
|
||||||
const [previousAnimation, setPreviousAnimation] = useState<string | null>(null);
|
const [previousAnimation, setPreviousAnimation] = useState<string | null>(null);
|
||||||
@@ -181,6 +185,11 @@ function Model({ asset }: { readonly asset: Asset }) {
|
|||||||
const calculateBoundingBox = (scene: THREE.Object3D) => {
|
const calculateBoundingBox = (scene: THREE.Object3D) => {
|
||||||
const box = new THREE.Box3().setFromObject(scene);
|
const box = new THREE.Box3().setFromObject(scene);
|
||||||
setBoundingBox(box);
|
setBoundingBox(box);
|
||||||
|
|
||||||
|
if (asset.eventData?.type === 'Conveyor') {
|
||||||
|
const size = box.getSize(new THREE.Vector3());
|
||||||
|
setConveyorPlaneSize([size.x, size.z]);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
loadModel();
|
loadModel();
|
||||||
@@ -464,7 +473,7 @@ function Model({ asset }: { readonly asset: Asset }) {
|
|||||||
position={asset.position}
|
position={asset.position}
|
||||||
rotation={asset.rotation}
|
rotation={asset.rotation}
|
||||||
visible={asset.isVisible}
|
visible={asset.isVisible}
|
||||||
userData={{ ...asset, iks: ikData }}
|
userData={{ ...asset, iks: ikData, rigidBodyRef: rigidBodyRef.current, boundingBox: boundingBox }}
|
||||||
onDoubleClick={(e) => {
|
onDoubleClick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
if (!toggleView) {
|
if (!toggleView) {
|
||||||
@@ -495,11 +504,38 @@ function Model({ asset }: { readonly asset: Asset }) {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{gltfScene && (
|
{gltfScene && (
|
||||||
isRendered ? (
|
<>
|
||||||
<primitive object={gltfScene} />
|
{isRendered ? (
|
||||||
) : (
|
<>
|
||||||
<AssetBoundingBox boundingBox={boundingBox} />
|
<RigidBody
|
||||||
)
|
type="fixed"
|
||||||
|
colliders='cuboid'
|
||||||
|
ref={rigidBodyRef}
|
||||||
|
>
|
||||||
|
<primitive object={gltfScene} />
|
||||||
|
</ RigidBody>
|
||||||
|
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
{boundingBox &&
|
||||||
|
<AssetBoundingBox boundingBox={boundingBox} />
|
||||||
|
}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
<ConveyorCollider boundingBox={boundingBox}
|
||||||
|
asset={asset}
|
||||||
|
conveyorPlaneSize={conveyorPlaneSize}
|
||||||
|
onReachEnd={(rigidBody) => {
|
||||||
|
// Option A: Reset the same object
|
||||||
|
rigidBody.setTranslation({ x: 0, y: 10, z: 0 }, true);
|
||||||
|
rigidBody.setLinvel({ x: 0, y: 0, z: 0 }, true);
|
||||||
|
rigidBody.setAngvel({ x: 0, y: 0, z: 0 }, true);
|
||||||
|
rigidBody.wakeUp();
|
||||||
|
|
||||||
|
// Option B: You can also call a function that sets a state to "add a new rigid body"
|
||||||
|
}} />
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
</group>
|
</group>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -36,10 +36,8 @@ const BoundingBox = ({ boundingBoxRef, isPerAsset = true }: BoundingBoxProps) =>
|
|||||||
return selectedAssets.map((obj: THREE.Object3D) => {
|
return selectedAssets.map((obj: THREE.Object3D) => {
|
||||||
const position = obj.position;
|
const position = obj.position;
|
||||||
const rotation = obj.getWorldQuaternion(new THREE.Quaternion());
|
const rotation = obj.getWorldQuaternion(new THREE.Quaternion());
|
||||||
const clone = obj.clone();
|
|
||||||
clone.position.set(0, 0, 0);
|
const box: THREE.Box3 = obj.userData.boundingBox ?? new THREE.Box3().setFromObject(obj);
|
||||||
clone.rotation.set(0, 0, 0);
|
|
||||||
const box = new THREE.Box3().setFromObject(clone);
|
|
||||||
const size = new THREE.Vector3();
|
const size = new THREE.Vector3();
|
||||||
const center = new THREE.Vector3();
|
const center = new THREE.Vector3();
|
||||||
box.getSize(size);
|
box.getSize(size);
|
||||||
@@ -57,12 +55,17 @@ const BoundingBox = ({ boundingBoxRef, isPerAsset = true }: BoundingBoxProps) =>
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
const box = new THREE.Box3();
|
const unionBox = new THREE.Box3();
|
||||||
selectedAssets.forEach((obj: any) => box.expandByObject(obj.clone()));
|
|
||||||
|
selectedAssets.forEach((obj: any) => {
|
||||||
|
const localBox: THREE.Box3 = obj.userData.boundingBox ?? new THREE.Box3().setFromObject(obj);
|
||||||
|
unionBox.union(localBox);
|
||||||
|
});
|
||||||
|
|
||||||
const size = new THREE.Vector3();
|
const size = new THREE.Vector3();
|
||||||
const center = new THREE.Vector3();
|
const center = new THREE.Vector3();
|
||||||
box.getSize(size);
|
unionBox.getSize(size);
|
||||||
box.getCenter(center);
|
unionBox.getCenter(center);
|
||||||
|
|
||||||
const halfSize = size.clone().multiplyScalar(0.5);
|
const halfSize = size.clone().multiplyScalar(0.5);
|
||||||
const min = center.clone().sub(halfSize);
|
const min = center.clone().sub(halfSize);
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { RigidBody } from "@react-three/rapier";
|
||||||
import { useTileDistance, useToggleView } from "../../../store/builder/store";
|
import { useTileDistance, useToggleView } from "../../../store/builder/store";
|
||||||
import * as CONSTANTS from "../../../types/world/worldConstants";
|
import * as CONSTANTS from "../../../types/world/worldConstants";
|
||||||
|
|
||||||
@@ -6,7 +7,7 @@ const Ground = ({ plane }: any) => {
|
|||||||
const { planeValue, gridValue } = useTileDistance();
|
const { planeValue, gridValue } = useTileDistance();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<mesh name="Ground">
|
<>
|
||||||
<mesh
|
<mesh
|
||||||
name="Grid"
|
name="Grid"
|
||||||
position={!toggleView ? CONSTANTS.gridConfig.position3D : CONSTANTS.gridConfig.position2D}
|
position={!toggleView ? CONSTANTS.gridConfig.position3D : CONSTANTS.gridConfig.position2D}
|
||||||
@@ -20,17 +21,23 @@ const Ground = ({ plane }: any) => {
|
|||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
</mesh>
|
</mesh>
|
||||||
<mesh
|
|
||||||
ref={plane}
|
<RigidBody
|
||||||
rotation-x={CONSTANTS.planeConfig.rotation}
|
type="fixed"
|
||||||
position={!toggleView ? CONSTANTS.planeConfig.position3D : CONSTANTS.planeConfig.position2D}
|
colliders='cuboid'
|
||||||
name="Plane"
|
|
||||||
receiveShadow
|
|
||||||
>
|
>
|
||||||
<planeGeometry args={[planeValue.width, planeValue.height]} />
|
<mesh
|
||||||
<meshBasicMaterial color={CONSTANTS.planeConfig.color} />
|
ref={plane}
|
||||||
</mesh>
|
rotation-x={CONSTANTS.planeConfig.rotation}
|
||||||
</mesh>
|
position={!toggleView ? CONSTANTS.planeConfig.position3D : CONSTANTS.planeConfig.position2D}
|
||||||
|
name="Plane"
|
||||||
|
receiveShadow
|
||||||
|
>
|
||||||
|
<planeGeometry args={[planeValue.width, planeValue.height]} />
|
||||||
|
<meshBasicMaterial color={CONSTANTS.planeConfig.color} />
|
||||||
|
</mesh>
|
||||||
|
</RigidBody>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
112
app/src/modules/scene/physics/colliders.tsx
Normal file
112
app/src/modules/scene/physics/colliders.tsx
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
// import { RigidBody, RapierRigidBody } from '@react-three/rapier'
|
||||||
|
// import { useRef } from 'react';
|
||||||
|
// import { useLoadingProgress } from '../../../store/builder/store'
|
||||||
|
// import { MaterialModel } from '../../simulation/materials/instances/material/materialModel';
|
||||||
|
|
||||||
|
// function Colliders() {
|
||||||
|
// const { loadingProgress } = useLoadingProgress();
|
||||||
|
// const rigidBodyRef = useRef<RapierRigidBody>(null);
|
||||||
|
|
||||||
|
// const handleSleep = () => {
|
||||||
|
// console.log("slept");
|
||||||
|
// const body = rigidBodyRef.current;
|
||||||
|
// if (body) {
|
||||||
|
// body.setTranslation({ x: 0, y: 10, z: 0 }, true);
|
||||||
|
// body.setLinvel({ x: 0, y: 0, z: 0 }, true);
|
||||||
|
// body.setAngvel({ x: 0, y: 0, z: 0 }, true);
|
||||||
|
// body.wakeUp();
|
||||||
|
// }
|
||||||
|
// };
|
||||||
|
|
||||||
|
// return (
|
||||||
|
// <>
|
||||||
|
// {loadingProgress === 0 && (
|
||||||
|
// <RigidBody
|
||||||
|
// ref={rigidBodyRef}
|
||||||
|
// position={[0, 10, 0]}
|
||||||
|
// colliders="cuboid"
|
||||||
|
// angularDamping={5}
|
||||||
|
// linearDamping={1}
|
||||||
|
// restitution={0.1}
|
||||||
|
// onSleep={handleSleep}
|
||||||
|
// >
|
||||||
|
// <MaterialModel materialId='123' materialType={"Default material"} />
|
||||||
|
// </RigidBody>
|
||||||
|
// )}
|
||||||
|
// </>
|
||||||
|
// )
|
||||||
|
// }
|
||||||
|
|
||||||
|
// export default Colliders;
|
||||||
|
|
||||||
|
|
||||||
|
import { RigidBody, RapierRigidBody } from '@react-three/rapier';
|
||||||
|
import { useEffect, useRef, useState } from 'react';
|
||||||
|
import { useLoadingProgress } from '../../../store/builder/store';
|
||||||
|
import { MaterialModel } from '../../simulation/materials/instances/material/materialModel';
|
||||||
|
import { generateUniqueId } from '../../../functions/generateUniqueId';
|
||||||
|
import { useFrame } from '@react-three/fiber';
|
||||||
|
|
||||||
|
type MaterialInstance = {
|
||||||
|
id: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
function Colliders() {
|
||||||
|
const { loadingProgress } = useLoadingProgress();
|
||||||
|
const [materials, setMaterials] = useState<MaterialInstance[]>([]);
|
||||||
|
|
||||||
|
const spawnNewMaterial = () => {
|
||||||
|
setMaterials(prev => [...prev, { id: generateUniqueId() }]);
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (loadingProgress === 0 && materials.length === 0) {
|
||||||
|
spawnNewMaterial(); // spawn one initially
|
||||||
|
}
|
||||||
|
}, [loadingProgress]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{materials.map((mat) => (
|
||||||
|
<SingleMaterial
|
||||||
|
key={mat.id}
|
||||||
|
onReachEnd={spawnNewMaterial}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
export default Colliders;
|
||||||
|
|
||||||
|
function SingleMaterial({ onReachEnd }: { onReachEnd: () => void }) {
|
||||||
|
const rigidBodyRef = useRef<RapierRigidBody>(null);
|
||||||
|
const hasReachedEnd = useRef(false);
|
||||||
|
|
||||||
|
useFrame(() => {
|
||||||
|
const body = rigidBodyRef.current;
|
||||||
|
if (!body || hasReachedEnd.current) return;
|
||||||
|
|
||||||
|
const position = body.translation();
|
||||||
|
if (position && position.z > 5) {
|
||||||
|
console.log('✅ Reached end — triggering spawn');
|
||||||
|
hasReachedEnd.current = true;
|
||||||
|
body.setLinvel({ x: 0, y: 0, z: 0 }, true);
|
||||||
|
body.setAngvel({ x: 0, y: 0, z: 0 }, true);
|
||||||
|
onReachEnd();
|
||||||
|
body.setTranslation({ x: 0, y: -100, z: 0 }, true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<RigidBody
|
||||||
|
ref={rigidBodyRef}
|
||||||
|
position={[0, 10, 0]}
|
||||||
|
colliders="cuboid"
|
||||||
|
angularDamping={5}
|
||||||
|
linearDamping={1}
|
||||||
|
restitution={0.1}
|
||||||
|
>
|
||||||
|
<MaterialModel materialId='123' materialType='Default material' />
|
||||||
|
</RigidBody>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -14,6 +14,8 @@ import { getAllProjects } from "../../services/dashboard/getAllProjects";
|
|||||||
import { getUserData } from "../../functions/getUserData";
|
import { getUserData } from "../../functions/getUserData";
|
||||||
import { useLoadingProgress, useSocketStore } from "../../store/builder/store";
|
import { useLoadingProgress, useSocketStore } from "../../store/builder/store";
|
||||||
import { Color } from "three";
|
import { Color } from "three";
|
||||||
|
import { Physics } from "@react-three/rapier";
|
||||||
|
import Colliders from "./physics/colliders";
|
||||||
|
|
||||||
export default function Scene({ layout }: { readonly layout: 'Main Layout' | 'Comparison Layout' }) {
|
export default function Scene({ layout }: { readonly layout: 'Main Layout' | 'Comparison Layout' }) {
|
||||||
const map = useMemo(() => [
|
const map = useMemo(() => [
|
||||||
@@ -71,8 +73,12 @@ export default function Scene({ layout }: { readonly layout: 'Main Layout' | 'Co
|
|||||||
>
|
>
|
||||||
<Setup />
|
<Setup />
|
||||||
<Collaboration />
|
<Collaboration />
|
||||||
<Builder />
|
<Physics gravity={[0, -9.81, 0]} debug >
|
||||||
<Simulation />
|
<Builder />
|
||||||
|
<Simulation />
|
||||||
|
|
||||||
|
<Colliders />
|
||||||
|
</Physics>
|
||||||
<Visualization />
|
<Visualization />
|
||||||
</Canvas>
|
</Canvas>
|
||||||
</KeyboardControls>
|
</KeyboardControls>
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import Controls from '../controls/controls';
|
|||||||
import { Environment } from '@react-three/drei'
|
import { Environment } from '@react-three/drei'
|
||||||
|
|
||||||
import background from "../../../assets/textures/hdr/mudroadpuresky2k.hdr";
|
import background from "../../../assets/textures/hdr/mudroadpuresky2k.hdr";
|
||||||
|
import SecondaryCamera from '../../secondaryCamera/secondaryCamera';
|
||||||
|
|
||||||
function Setup() {
|
function Setup() {
|
||||||
return (
|
return (
|
||||||
@@ -20,6 +21,7 @@ function Setup() {
|
|||||||
{/* <MovingClouds /> */}
|
{/* <MovingClouds /> */}
|
||||||
|
|
||||||
<Environment files={background} environmentIntensity={1.5} />
|
<Environment files={background} environmentIntensity={1.5} />
|
||||||
|
<SecondaryCamera/>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
169
app/src/modules/secondaryCamera/secondaryCamera.tsx
Normal file
169
app/src/modules/secondaryCamera/secondaryCamera.tsx
Normal file
@@ -0,0 +1,169 @@
|
|||||||
|
import { useEffect, useRef } from 'react';
|
||||||
|
import { useThree, useFrame } from '@react-three/fiber';
|
||||||
|
import { CameraControls, Html, PerspectiveCamera, PivotControls } from '@react-three/drei';
|
||||||
|
import * as THREE from 'three';
|
||||||
|
import {
|
||||||
|
useSecondaryCameraData,
|
||||||
|
useSecondaryCameraEdit,
|
||||||
|
useSecondaryCameraState,
|
||||||
|
} from '../../store/builder/store';
|
||||||
|
|
||||||
|
type CameraData = {
|
||||||
|
id: any;
|
||||||
|
name: string;
|
||||||
|
position: [number, number, number];
|
||||||
|
target: [number, number, number];
|
||||||
|
};
|
||||||
|
|
||||||
|
function SecondaryCameraView() {
|
||||||
|
const cameraRef = useRef<THREE.PerspectiveCamera | null>(null);
|
||||||
|
const helperRef = useRef<THREE.CameraHelper | null>(null);
|
||||||
|
const rendererRef = useRef<THREE.WebGLRenderer | null>(null);
|
||||||
|
const dummyMeshRef = useRef<THREE.Mesh | null>(null);
|
||||||
|
const { scene, size, controls } = useThree();
|
||||||
|
const { secondaryCameraData, setSecondaryCameraData, updateSecondaryCameraData } = useSecondaryCameraData();
|
||||||
|
const { selectedSecondaryCamera, setSelectedSecondaryCamera } = useSecondaryCameraState();
|
||||||
|
const { secondaryCameraEdit } = useSecondaryCameraEdit();
|
||||||
|
|
||||||
|
// Sync main controls to selected camera
|
||||||
|
useEffect(() => {
|
||||||
|
if (selectedSecondaryCamera) {
|
||||||
|
(controls as CameraControls).setLookAt(
|
||||||
|
...selectedSecondaryCamera.position,
|
||||||
|
...selectedSecondaryCamera.target
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}, [selectedSecondaryCamera?.id]);
|
||||||
|
|
||||||
|
// Setup WebGLRenderer
|
||||||
|
useEffect(() => {
|
||||||
|
const secondaryCanvas = document.getElementById('secondary-canvas');
|
||||||
|
if (!secondaryCanvas) return;
|
||||||
|
|
||||||
|
const customRenderer = new THREE.WebGLRenderer({
|
||||||
|
canvas: secondaryCanvas,
|
||||||
|
antialias: true,
|
||||||
|
alpha: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
customRenderer.setSize(
|
||||||
|
secondaryCanvas.getBoundingClientRect().width,
|
||||||
|
secondaryCanvas.getBoundingClientRect().height
|
||||||
|
);
|
||||||
|
customRenderer.setPixelRatio(window.devicePixelRatio);
|
||||||
|
rendererRef.current = customRenderer;
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
customRenderer.dispose();
|
||||||
|
};
|
||||||
|
}, [size]);
|
||||||
|
|
||||||
|
// Camera helper
|
||||||
|
useEffect(() => {
|
||||||
|
if (selectedSecondaryCamera && cameraRef.current) {
|
||||||
|
const helper = new THREE.CameraHelper(cameraRef.current);
|
||||||
|
helperRef.current = helper;
|
||||||
|
scene.add(helper);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
scene.remove(helper);
|
||||||
|
helper.geometry.dispose();
|
||||||
|
(helper.material as any).dispose?.();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}, [scene, selectedSecondaryCamera?.id]);
|
||||||
|
|
||||||
|
// Render to secondary canvas
|
||||||
|
useFrame(() => {
|
||||||
|
if (rendererRef.current && cameraRef.current && selectedSecondaryCamera) {
|
||||||
|
rendererRef.current.render(scene, cameraRef.current);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Live update camera if editing enabled
|
||||||
|
useFrame(() => {
|
||||||
|
if (secondaryCameraEdit && selectedSecondaryCamera && cameraRef.current) {
|
||||||
|
const target = (controls as CameraControls).getTarget(new THREE.Vector3());
|
||||||
|
const position = (controls as CameraControls).getPosition(new THREE.Vector3());
|
||||||
|
|
||||||
|
updateSecondaryCameraData(selectedSecondaryCamera.id, {
|
||||||
|
position: position.toArray(),
|
||||||
|
target: target.toArray(),
|
||||||
|
});
|
||||||
|
|
||||||
|
setSelectedSecondaryCamera({
|
||||||
|
id: selectedSecondaryCamera.id,
|
||||||
|
position: position.toArray(),
|
||||||
|
target: target.toArray(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function handleCamera() {
|
||||||
|
const target = (controls as CameraControls).getTarget(new THREE.Vector3());
|
||||||
|
const position = (controls as CameraControls).getPosition(new THREE.Vector3());
|
||||||
|
|
||||||
|
const newCameraData: CameraData = {
|
||||||
|
id: secondaryCameraData.length + 1,
|
||||||
|
name: `Camera ${secondaryCameraData.length + 1}`,
|
||||||
|
position: position.toArray(),
|
||||||
|
target: target.toArray(),
|
||||||
|
};
|
||||||
|
|
||||||
|
setSecondaryCameraData([...secondaryCameraData, newCameraData]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Html
|
||||||
|
className='secondary-camera-controls'
|
||||||
|
style={{
|
||||||
|
zIndex: 10,
|
||||||
|
position: 'absolute',
|
||||||
|
bottom: 350,
|
||||||
|
right: 0,
|
||||||
|
border: '1px solid black',
|
||||||
|
backgroundColor: 'black',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<button onClick={handleCamera}>+Add Camera</button>
|
||||||
|
</Html>
|
||||||
|
|
||||||
|
{selectedSecondaryCamera && (
|
||||||
|
<PerspectiveCamera
|
||||||
|
ref={cameraRef}
|
||||||
|
position={selectedSecondaryCamera.position}
|
||||||
|
onUpdate={(self) => self.lookAt(...selectedSecondaryCamera.target)}
|
||||||
|
>
|
||||||
|
<PivotControls
|
||||||
|
anchor={[0, 0, 0]}
|
||||||
|
onDrag={() => {
|
||||||
|
if (dummyMeshRef.current) {
|
||||||
|
const worldPos = new THREE.Vector3();
|
||||||
|
dummyMeshRef.current.getWorldPosition(worldPos);
|
||||||
|
|
||||||
|
const updatedPos: [number, number, number] = worldPos.toArray() as [number, number, number];
|
||||||
|
|
||||||
|
updateSecondaryCameraData(selectedSecondaryCamera.id, {
|
||||||
|
position: updatedPos,
|
||||||
|
});
|
||||||
|
|
||||||
|
setSelectedSecondaryCamera({
|
||||||
|
...selectedSecondaryCamera,
|
||||||
|
position: updatedPos,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<mesh ref={dummyMeshRef}>
|
||||||
|
<sphereGeometry args={[0.1, 32, 32]} />
|
||||||
|
<meshBasicMaterial color="yellow" />
|
||||||
|
</mesh>
|
||||||
|
</PivotControls>
|
||||||
|
</PerspectiveCamera>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default SecondaryCameraView;
|
||||||
46
app/src/modules/secondaryCamera/secondaryCanvas.tsx
Normal file
46
app/src/modules/secondaryCamera/secondaryCanvas.tsx
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
import React, { useEffect } from 'react'
|
||||||
|
import { useSecondaryCameraData, useSecondaryCameraEdit, useSecondaryCameraState } from '../../store/builder/store'
|
||||||
|
|
||||||
|
type CameraData = {
|
||||||
|
id: any
|
||||||
|
name: string
|
||||||
|
position: [number, number, number]
|
||||||
|
target: [number, number, number]
|
||||||
|
}
|
||||||
|
|
||||||
|
function SecondaryCanvas() {
|
||||||
|
const { secondaryCameraData } = useSecondaryCameraData()
|
||||||
|
const { selectedSecondaryCamera, setSelectedSecondaryCamera } = useSecondaryCameraState();
|
||||||
|
const { setSecondaryCameraEdit, secondaryCameraEdit } = useSecondaryCameraEdit()
|
||||||
|
|
||||||
|
function handleSelectCamera(camera: any) {
|
||||||
|
const selectedCamera = camera
|
||||||
|
if (!selectedCamera || (selectedSecondaryCamera && selectedCamera.id === selectedSecondaryCamera.id)) {
|
||||||
|
setSelectedSecondaryCamera(null);
|
||||||
|
} else {
|
||||||
|
setSelectedSecondaryCamera({
|
||||||
|
id: selectedCamera.id,
|
||||||
|
position: selectedCamera.position,
|
||||||
|
target: selectedCamera.target
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{secondaryCameraData.map((camera: CameraData) => (
|
||||||
|
<div key={camera.id} className='secondary-camera-item' onClick={() => handleSelectCamera(camera)} style={{ zIndex: 10, position: 'absolute', bottom: 300 - (camera.id * 30), right: 0, border: '1px solid black', backgroundColor: 'black' }}>
|
||||||
|
<p>{camera.name}</p>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
<div onClick={() => { setSecondaryCameraEdit(!secondaryCameraEdit) }} style={{ zIndex: 10, position: 'absolute', bottom: 500, right: 0, border: '1px solid black', backgroundColor: secondaryCameraEdit ? 'black' : "white", color: !secondaryCameraEdit ? "black" : "white", padding: '10px' }}>
|
||||||
|
Edit
|
||||||
|
</div>
|
||||||
|
<div className='secondary-canvas-container' style={{ zIndex: 10, position: 'absolute', bottom: 0, right: 0, width: '300px', height: '200px' }}>
|
||||||
|
<canvas id='secondary-canvas' className='secondary-canvas' style={{ height: "100%", width: "100%", background: "gray" }}></canvas>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default SecondaryCanvas
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import { useEffect, useRef, useState } from 'react'
|
import { useEffect, useRef, useState } from 'react'
|
||||||
import { useFrame, useThree } from '@react-three/fiber';
|
import { useFrame, useThree } from '@react-three/fiber';
|
||||||
|
import { RapierRigidBody } from '@react-three/rapier';
|
||||||
import * as THREE from 'three';
|
import * as THREE from 'three';
|
||||||
import { Line } from '@react-three/drei';
|
import { Line } from '@react-three/drei';
|
||||||
import { useAnimationPlaySpeed, usePauseButtonStore, usePlayButtonStore, useResetButtonStore } from '../../../../../store/usePlayButtonStore';
|
import { useAnimationPlaySpeed, usePauseButtonStore, usePlayButtonStore, useResetButtonStore } from '../../../../../store/usePlayButtonStore';
|
||||||
@@ -28,6 +29,7 @@ function HumanAnimator({ path, handleCallBack, currentPhase, human, reset, start
|
|||||||
const [objectRotation, setObjectRotation] = useState<[number, number, number] | null>(human.point?.action?.pickUpPoint?.rotation || [0, 0, 0])
|
const [objectRotation, setObjectRotation] = useState<[number, number, number] | null>(human.point?.action?.pickUpPoint?.rotation || [0, 0, 0])
|
||||||
const [restRotation, setRestingRotation] = useState<boolean>(true);
|
const [restRotation, setRestingRotation] = useState<boolean>(true);
|
||||||
const [currentPath, setCurrentPath] = useState<[number, number, number][]>([]);
|
const [currentPath, setCurrentPath] = useState<[number, number, number][]>([]);
|
||||||
|
const rigidBodyRef = useRef<RapierRigidBody>(null);
|
||||||
const { scene } = useThree();
|
const { scene } = useThree();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ type ModelType = keyof typeof modelPaths;
|
|||||||
interface ModelProps extends React.ComponentProps<'group'> {
|
interface ModelProps extends React.ComponentProps<'group'> {
|
||||||
materialId: string;
|
materialId: string;
|
||||||
materialType: ModelType;
|
materialType: ModelType;
|
||||||
matRef: React.Ref<THREE.Group<THREE.Object3DEventMap>>
|
matRef?: React.Ref<THREE.Group<THREE.Object3DEventMap>>
|
||||||
}
|
}
|
||||||
|
|
||||||
export function MaterialModel({ materialId, materialType, matRef, ...props }: Readonly<ModelProps>) {
|
export function MaterialModel({ materialId, materialType, matRef, ...props }: Readonly<ModelProps>) {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { useEffect, useRef, useState } from 'react'
|
|||||||
import { useFrame, useThree } from '@react-three/fiber';
|
import { useFrame, useThree } from '@react-three/fiber';
|
||||||
import * as THREE from 'three';
|
import * as THREE from 'three';
|
||||||
import { Line } from '@react-three/drei';
|
import { Line } from '@react-three/drei';
|
||||||
|
import { RapierRigidBody } from '@react-three/rapier';
|
||||||
import { useAnimationPlaySpeed, usePauseButtonStore, usePlayButtonStore, useResetButtonStore } from '../../../../../store/usePlayButtonStore';
|
import { useAnimationPlaySpeed, usePauseButtonStore, usePlayButtonStore, useResetButtonStore } from '../../../../../store/usePlayButtonStore';
|
||||||
import { useSceneContext } from '../../../../scene/sceneContext';
|
import { useSceneContext } from '../../../../scene/sceneContext';
|
||||||
|
|
||||||
@@ -28,8 +29,15 @@ function VehicleAnimator({ path, handleCallBack, currentPhase, agvUuid, agvDetai
|
|||||||
const [objectRotation, setObjectRotation] = useState<{ x: number; y: number; z: number } | undefined>(agvDetail.point?.action?.pickUpPoint?.rotation || { x: 0, y: 0, z: 0 })
|
const [objectRotation, setObjectRotation] = useState<{ x: number; y: number; z: number } | undefined>(agvDetail.point?.action?.pickUpPoint?.rotation || { x: 0, y: 0, z: 0 })
|
||||||
const [restRotation, setRestingRotation] = useState<boolean>(true);
|
const [restRotation, setRestingRotation] = useState<boolean>(true);
|
||||||
const [currentPath, setCurrentPath] = useState<[number, number, number][]>([]);
|
const [currentPath, setCurrentPath] = useState<[number, number, number][]>([]);
|
||||||
|
const rigidBodyRef = useRef<RapierRigidBody>(null);
|
||||||
const { scene } = useThree();
|
const { scene } = useThree();
|
||||||
|
|
||||||
|
const object = scene.getObjectByProperty('uuid', agvUuid);
|
||||||
|
|
||||||
|
if (object?.userData.rigidBodyRef) {
|
||||||
|
(rigidBodyRef as React.MutableRefObject<RapierRigidBody | null>).current = object.userData.rigidBodyRef;
|
||||||
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (currentPhase === 'stationed-pickup' && path.length > 0) {
|
if (currentPhase === 'stationed-pickup' && path.length > 0) {
|
||||||
setCurrentPath(path);
|
setCurrentPath(path);
|
||||||
@@ -56,11 +64,17 @@ function VehicleAnimator({ path, handleCallBack, currentPhase, agvUuid, agvDetai
|
|||||||
progressRef.current = 0;
|
progressRef.current = 0;
|
||||||
setReset(false);
|
setReset(false);
|
||||||
setRestingRotation(true);
|
setRestingRotation(true);
|
||||||
const object = scene.getObjectByProperty('uuid', agvUuid);
|
|
||||||
const vehicle = getVehicleById(agvDetail.modelUuid);
|
const vehicle = getVehicleById(agvDetail.modelUuid);
|
||||||
if (object && vehicle) {
|
if (rigidBodyRef.current && vehicle) {
|
||||||
object.position.set(vehicle.position[0], vehicle.position[1], vehicle.position[2]);
|
rigidBodyRef.current.setTranslation(
|
||||||
object.rotation.set(vehicle.rotation[0], vehicle.rotation[1], vehicle.rotation[2]);
|
{ x: vehicle.position[0], y: vehicle.position[1], z: vehicle.position[2] },
|
||||||
|
true
|
||||||
|
);
|
||||||
|
rigidBodyRef.current.setRotation(
|
||||||
|
{ x: vehicle.rotation[0], y: vehicle.rotation[1], z: vehicle.rotation[2], w: 1 },
|
||||||
|
true
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [isReset, isPlaying])
|
}, [isReset, isPlaying])
|
||||||
@@ -72,8 +86,7 @@ function VehicleAnimator({ path, handleCallBack, currentPhase, agvUuid, agvDetai
|
|||||||
const delta = (now - lastTimeRef.current) / 1000;
|
const delta = (now - lastTimeRef.current) / 1000;
|
||||||
lastTimeRef.current = now;
|
lastTimeRef.current = now;
|
||||||
|
|
||||||
const object = scene.getObjectByProperty('uuid', agvUuid);
|
if (!rigidBodyRef.current || currentPath.length < 2) return;
|
||||||
if (!object || currentPath.length < 2) return;
|
|
||||||
if (isPaused) return;
|
if (isPaused) return;
|
||||||
|
|
||||||
let totalDistance = 0;
|
let totalDistance = 0;
|
||||||
@@ -100,21 +113,42 @@ function VehicleAnimator({ path, handleCallBack, currentPhase, agvUuid, agvDetai
|
|||||||
const end = new THREE.Vector3(...currentPath[index + 1]);
|
const end = new THREE.Vector3(...currentPath[index + 1]);
|
||||||
const segmentDistance = distances[index];
|
const segmentDistance = distances[index];
|
||||||
|
|
||||||
const targetQuaternion = new THREE.Quaternion().setFromRotationMatrix(new THREE.Matrix4().lookAt(start, end, new THREE.Vector3(0, 1, 0)));
|
const targetQuaternion = new THREE.Quaternion().setFromRotationMatrix(
|
||||||
|
new THREE.Matrix4().lookAt(start, end, new THREE.Vector3(0, 1, 0))
|
||||||
|
);
|
||||||
const y180 = new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0, 1, 0), Math.PI);
|
const y180 = new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0, 1, 0), Math.PI);
|
||||||
targetQuaternion.multiply(y180);
|
targetQuaternion.multiply(y180);
|
||||||
|
|
||||||
const angle = object.quaternion.angleTo(targetQuaternion);
|
const currentRotation = rigidBodyRef.current.rotation();
|
||||||
|
const currentQuaternion = new THREE.Quaternion(
|
||||||
|
currentRotation.x,
|
||||||
|
currentRotation.y,
|
||||||
|
currentRotation.z,
|
||||||
|
currentRotation.w
|
||||||
|
);
|
||||||
|
|
||||||
|
const angle = currentQuaternion.angleTo(targetQuaternion);
|
||||||
|
|
||||||
if (angle < 0.01) {
|
if (angle < 0.01) {
|
||||||
object.quaternion.copy(targetQuaternion);
|
rigidBodyRef.current.setRotation(
|
||||||
|
{ x: targetQuaternion.x, y: targetQuaternion.y, z: targetQuaternion.z, w: targetQuaternion.w },
|
||||||
|
true
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
const step = rotationSpeed * delta * speed * agvDetail.speed;
|
const step = rotationSpeed * delta * speed * agvDetail.speed;
|
||||||
const angle = object.quaternion.angleTo(targetQuaternion);
|
const angle = currentQuaternion.angleTo(targetQuaternion);
|
||||||
|
|
||||||
if (angle < step) {
|
if (angle < step) {
|
||||||
object.quaternion.copy(targetQuaternion);
|
rigidBodyRef.current.setRotation(
|
||||||
|
{ x: targetQuaternion.x, y: targetQuaternion.y, z: targetQuaternion.z, w: targetQuaternion.w },
|
||||||
|
true
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
object.quaternion.rotateTowards(targetQuaternion, step);
|
const newQuaternion = currentQuaternion.clone().rotateTowards(targetQuaternion, step);
|
||||||
|
rigidBodyRef.current.setRotation(
|
||||||
|
{ x: newQuaternion.x, y: newQuaternion.y, z: newQuaternion.z, w: newQuaternion.w },
|
||||||
|
true
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -124,30 +158,51 @@ function VehicleAnimator({ path, handleCallBack, currentPhase, agvUuid, agvDetai
|
|||||||
progressRef.current += delta * (speed * agvDetail.speed);
|
progressRef.current += delta * (speed * agvDetail.speed);
|
||||||
const t = (progressRef.current - accumulatedDistance) / segmentDistance;
|
const t = (progressRef.current - accumulatedDistance) / segmentDistance;
|
||||||
const position = start.clone().lerp(end, t);
|
const position = start.clone().lerp(end, t);
|
||||||
object.position.copy(position);
|
|
||||||
|
rigidBodyRef.current.setTranslation(
|
||||||
|
{ x: position.x, y: position.y, z: position.z },
|
||||||
|
true
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (progressRef.current >= totalDistance) {
|
if (progressRef.current >= totalDistance) {
|
||||||
if (restRotation && objectRotation) {
|
if (restRotation && objectRotation) {
|
||||||
const targetEuler = new THREE.Euler(0, objectRotation.y - agvDetail.point.action.steeringAngle, 0);
|
const targetEuler = new THREE.Euler(0, objectRotation.y - agvDetail.point.action.steeringAngle, 0);
|
||||||
|
|
||||||
const baseQuaternion = new THREE.Quaternion().setFromEuler(targetEuler);
|
const baseQuaternion = new THREE.Quaternion().setFromEuler(targetEuler);
|
||||||
const y180 = new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0, 1, 0), Math.PI);
|
const y180 = new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0, 1, 0), Math.PI);
|
||||||
const targetQuaternion = baseQuaternion.multiply(y180);
|
const targetQuaternion = baseQuaternion.multiply(y180);
|
||||||
|
|
||||||
const angle = object.quaternion.angleTo(targetQuaternion);
|
const currentRotation = rigidBodyRef.current.rotation();
|
||||||
|
const currentQuaternion = new THREE.Quaternion(
|
||||||
|
currentRotation.x,
|
||||||
|
currentRotation.y,
|
||||||
|
currentRotation.z,
|
||||||
|
currentRotation.w
|
||||||
|
);
|
||||||
|
|
||||||
|
const angle = currentQuaternion.angleTo(targetQuaternion);
|
||||||
if (angle < 0.01) {
|
if (angle < 0.01) {
|
||||||
object.quaternion.copy(targetQuaternion);
|
rigidBodyRef.current.setRotation(
|
||||||
|
{ x: targetQuaternion.x, y: targetQuaternion.y, z: targetQuaternion.z, w: targetQuaternion.w },
|
||||||
|
true
|
||||||
|
);
|
||||||
setRestingRotation(false);
|
setRestingRotation(false);
|
||||||
} else {
|
} else {
|
||||||
const step = rotationSpeed * delta * speed * agvDetail.speed;
|
const step = rotationSpeed * delta * speed * agvDetail.speed;
|
||||||
const angle = object.quaternion.angleTo(targetQuaternion);
|
const angle = currentQuaternion.angleTo(targetQuaternion);
|
||||||
|
|
||||||
if (angle < step) {
|
if (angle < step) {
|
||||||
object.quaternion.copy(targetQuaternion);
|
rigidBodyRef.current.setRotation(
|
||||||
|
{ x: targetQuaternion.x, y: targetQuaternion.y, z: targetQuaternion.z, w: targetQuaternion.w },
|
||||||
|
true
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
object.quaternion.rotateTowards(targetQuaternion, step);
|
const newQuaternion = currentQuaternion.clone().rotateTowards(targetQuaternion, step);
|
||||||
|
rigidBodyRef.current.setRotation(
|
||||||
|
{ x: newQuaternion.x, y: newQuaternion.y, z: newQuaternion.z, w: newQuaternion.w },
|
||||||
|
true
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ import { getVersionHistoryApi } from "../services/factoryBuilder/versionControl/
|
|||||||
import { useVersionHistoryStore } from "../store/builder/useVersionHistoryStore";
|
import { useVersionHistoryStore } from "../store/builder/useVersionHistoryStore";
|
||||||
import { VersionProvider } from "../modules/builder/version/versionContext";
|
import { VersionProvider } from "../modules/builder/version/versionContext";
|
||||||
import { sharedWithMeProjects } from "../services/dashboard/sharedWithMeProject";
|
import { sharedWithMeProjects } from "../services/dashboard/sharedWithMeProject";
|
||||||
|
import SecondaryCanvas from "../modules/secondaryCamera/secondaryCanvas";
|
||||||
|
|
||||||
const Project: React.FC = () => {
|
const Project: React.FC = () => {
|
||||||
let navigate = useNavigate();
|
let navigate = useNavigate();
|
||||||
@@ -140,6 +141,9 @@ const Project: React.FC = () => {
|
|||||||
<ComparisonSceneProvider />
|
<ComparisonSceneProvider />
|
||||||
</VersionProvider>
|
</VersionProvider>
|
||||||
</SceneProvider>
|
</SceneProvider>
|
||||||
|
|
||||||
|
<SecondaryCanvas />
|
||||||
|
|
||||||
{selectedUser && <FollowPerson />}
|
{selectedUser && <FollowPerson />}
|
||||||
{isLogListVisible && (
|
{isLogListVisible && (
|
||||||
<RenderOverlay>
|
<RenderOverlay>
|
||||||
|
|||||||
@@ -724,3 +724,49 @@ export const useSelectedComment = create<any>((set: any) => ({
|
|||||||
commentPositionState: null,
|
commentPositionState: null,
|
||||||
setCommentPositionState: (x: any) => set({ commentPositionState: x }),
|
setCommentPositionState: (x: any) => set({ commentPositionState: x }),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|
||||||
|
type CameraData = {
|
||||||
|
id: any;
|
||||||
|
name: string;
|
||||||
|
position: [number, number, number];
|
||||||
|
target: [number, number, number];
|
||||||
|
};
|
||||||
|
|
||||||
|
type SecondaryCameraStore = {
|
||||||
|
secondaryCameraData: CameraData[];
|
||||||
|
setSecondaryCameraData: (data: CameraData[]) => void;
|
||||||
|
updateSecondaryCameraData: (id: any, updatedData: Partial<CameraData>) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useSecondaryCameraData = create<SecondaryCameraStore>((set) => ({
|
||||||
|
secondaryCameraData: [],
|
||||||
|
setSecondaryCameraData: (data) => set({ secondaryCameraData: data }),
|
||||||
|
updateSecondaryCameraData: (id, updatedData) =>
|
||||||
|
set((state) => ({
|
||||||
|
secondaryCameraData: state.secondaryCameraData.map((camera) =>
|
||||||
|
camera.id === id ? { ...camera, ...updatedData } : camera
|
||||||
|
),
|
||||||
|
})),
|
||||||
|
}));
|
||||||
|
|
||||||
|
type SecondaryCamera = {
|
||||||
|
id: number;
|
||||||
|
position: [number, number, number];
|
||||||
|
target: [number, number, number];
|
||||||
|
};
|
||||||
|
|
||||||
|
type SecondaryCameraState = {
|
||||||
|
selectedSecondaryCamera: SecondaryCamera | null;
|
||||||
|
setSelectedSecondaryCamera: (camera: SecondaryCamera | null) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useSecondaryCameraState = create<SecondaryCameraState>((set) => ({
|
||||||
|
selectedSecondaryCamera: null,
|
||||||
|
setSelectedSecondaryCamera: (camera) => set({ selectedSecondaryCamera: camera }),
|
||||||
|
}));
|
||||||
|
|
||||||
|
export const useSecondaryCameraEdit = create<any>((set) => ({
|
||||||
|
secondaryCameraEdit: false,
|
||||||
|
setSecondaryCameraEdit: (x: boolean) => set({ secondaryCameraEdit: x }),
|
||||||
|
}));
|
||||||
Reference in New Issue
Block a user