From 04573b86dd8dc5776cae0497491ec5748f5d7835 Mon Sep 17 00:00:00 2001 From: Gomathi9520 Date: Wed, 30 Jul 2025 18:16:01 +0530 Subject: [PATCH] plane geometry added with physics --- .../asset/models/model/conveyorCollider.tsx | 12 +- .../builder/asset/models/model/model.tsx | 23 ++- .../asset/models/model/ribbonCollider.tsx | 182 ++++++++++++++++++ .../physics/colliders/colliderCreator.tsx | 40 +++- .../colliderInstance/colliderInstance.tsx | 7 +- app/src/modules/scene/physics/curvedPlane.tsx | 115 ----------- .../modules/scene/physics/materialSpawner.tsx | 10 +- .../scene/physics/physicsSimulator.tsx | 14 +- app/src/modules/scene/scene.tsx | 2 +- app/src/modules/scene/setup/setup.tsx | 4 +- .../secondaryCamera/secondaryCamera.tsx | 6 +- app/src/pages/Project.tsx | 2 +- 12 files changed, 263 insertions(+), 154 deletions(-) create mode 100644 app/src/modules/builder/asset/models/model/ribbonCollider.tsx delete mode 100644 app/src/modules/scene/physics/curvedPlane.tsx diff --git a/app/src/modules/builder/asset/models/model/conveyorCollider.tsx b/app/src/modules/builder/asset/models/model/conveyorCollider.tsx index 6cdb29a..26e4a29 100644 --- a/app/src/modules/builder/asset/models/model/conveyorCollider.tsx +++ b/app/src/modules/builder/asset/models/model/conveyorCollider.tsx @@ -1,14 +1,11 @@ import * as THREE from 'three'; -import { CollisionPayload, RapierRigidBody, RigidBody } from '@react-three/rapier'; +import { CollisionPayload, RigidBody } from '@react-three/rapier'; import { useEffect, useRef, useState } from 'react'; import { useFrame } from '@react-three/fiber'; -import CurvedPlane from '../../../../scene/physics/curvedPlane'; -function ConveyorCollider({ boundingBox, asset, modelName, conveyorPlaneSize, scene }: { +function ConveyorCollider({ boundingBox, asset, conveyorPlaneSize }: { boundingBox: THREE.Box3 | null, asset: Asset, - modelName: string, - scene: THREE.Scene, conveyorPlaneSize: [number, number] | null, }) { const conveyorRef = useRef(null); @@ -18,9 +15,9 @@ function ConveyorCollider({ boundingBox, asset, modelName, conveyorPlaneSize, sc useEffect(() => { if (!boundingBox || !conveyorPlaneSize) return; const [width, depth] = conveyorPlaneSize; - if (width < depth) { //z-axis conveyor + if (width < depth) { conveyorDirection.current.set(0, 0, 1); - } else {//x-axis conveyor + } else { conveyorDirection.current.set(1, 0, 0); } const rotation = new THREE.Euler().fromArray(asset.rotation || [0, 0, 0]); @@ -123,7 +120,6 @@ function ConveyorCollider({ boundingBox, asset, modelName, conveyorPlaneSize, sc colliders="cuboid" > - {/* */} - - - + > */} + + {/* */} @@ -474,10 +476,13 @@ function Model({ asset, isRendered }: { readonly asset: Asset, isRendered: boole )} - */} + + diff --git a/app/src/modules/builder/asset/models/model/ribbonCollider.tsx b/app/src/modules/builder/asset/models/model/ribbonCollider.tsx new file mode 100644 index 0000000..16fd83f --- /dev/null +++ b/app/src/modules/builder/asset/models/model/ribbonCollider.tsx @@ -0,0 +1,182 @@ +import * as THREE from 'three'; +import { CollisionPayload, RigidBody } from '@react-three/rapier'; +import { useEffect, useMemo, useRef, useState } from 'react'; +import { useFrame } from '@react-three/fiber'; +import { useSceneContext } from '../../../../scene/sceneContext'; +import { useProductContext } from '../../../../simulation/products/productContext'; + +function RibbonCollider({ boundingBox, asset, conveyorPlaneSize }: { + boundingBox: THREE.Box3 | null, + asset: Asset, + conveyorPlaneSize: [number, number] | null, +}) { + const { productStore } = useSceneContext(); + const { getEventByModelUuid } = productStore(); + const { selectedProductStore } = useProductContext(); + const { selectedProduct } = selectedProductStore(); + const conveyorRef = useRef(null); + const [objectsOnConveyor, setObjectsOnConveyor] = useState>(new Set()); + const conveyorDirection = useRef(new THREE.Vector3()); + const conveyorSpeed = 2; + const [geometryKey, setGeometryKey] = useState(0); + + const event = getEventByModelUuid( + selectedProduct.productUuid, + asset.modelUuid + ) as ConveyorEventSchema | undefined; + + 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(() => { + if (!event?.points || event.points.length < 2) return; + + const curve = new THREE.CatmullRomCurve3( + event.points.map(p => new THREE.Vector3(...p.position)) + ); + + const assetPos = new THREE.Vector3(...(asset.position || [0, 0, 0])); + const assetRot = new THREE.Euler(...(asset.rotation || [0, 0, 0])); + const assetQuat = new THREE.Quaternion().setFromEuler(assetRot); + const inverseAssetQuat = assetQuat.clone().invert(); + + objectsOnConveyor.forEach(rigidBody => { + if (!rigidBody) return; + + const worldPos = new THREE.Vector3().copy(rigidBody.translation()); + + const localPos = worldPos.clone().sub(assetPos).applyQuaternion(inverseAssetQuat); + + const curvePoints = curve.getPoints(100); + let closestIndex = 0; + let minDist = Infinity; + + for (let i = 0; i < curvePoints.length; i++) { + const dist = curvePoints[i].distanceToSquared(localPos); + if (dist < minDist) { + minDist = dist; + closestIndex = i; + } + } + + const point = curvePoints[closestIndex]; + const prev = curvePoints[closestIndex - 1] || point; + const next = curvePoints[closestIndex + 1] || point; + + const tangentLocal = new THREE.Vector3().subVectors(next, prev).normalize(); + const sideLocal = new THREE.Vector3().crossVectors(tangentLocal, new THREE.Vector3(0, 1, 0)).normalize(); + + const relative = new THREE.Vector3().subVectors(localPos, point); + const sideOffset = relative.dot(sideLocal); + + const centeringStrength = 10; + const centeringForceLocal = sideLocal.clone().multiplyScalar(-sideOffset * centeringStrength); + const forwardForceLocal = tangentLocal.clone().multiplyScalar(conveyorSpeed); + + const totalForceLocal = forwardForceLocal.add(centeringForceLocal); + + const totalForceWorld = totalForceLocal.applyQuaternion(assetQuat); + + rigidBody.setAngvel({ x: 0, y: 0, z: 0 }, true); + rigidBody.setLinvel(totalForceWorld, true); + }); + }); + + const geometry = useMemo(() => { + if (asset.eventData?.type !== 'Conveyor' || !conveyorPlaneSize) return null; + const width = 1; + const segments = 30; + const vertices: number[] = []; + const indices: number[] = []; + + if (!event || !event.points || event.points.length < 2) return null; + + const points = event.points.map(p => new THREE.Vector3(p.position[0], p.position[1], p.position[2])); + + if (points.length < 2) return null; + + const curve = new THREE.CatmullRomCurve3(points); + const curvePoints = curve.getPoints((points.length - 1) * segments); + + for (let i = 0; i < curvePoints.length; i++) { + const point = curvePoints[i]; + const prev = curvePoints[i - 1] || curvePoints[i]; + const next = curvePoints[i + 1] || curvePoints[i]; + + const tangent = new THREE.Vector3().subVectors(next, prev).normalize(); + const normal = new THREE.Vector3().crossVectors(tangent, new THREE.Vector3(0, 1, 0)).normalize(); + + const left = new THREE.Vector3().copy(point).addScaledVector(normal, -width / 2); + const right = new THREE.Vector3().copy(point).addScaledVector(normal, width / 2); + + vertices.push(...left.toArray()); + vertices.push(...right.toArray()); + } + + const totalSegments = curvePoints.length - 1; + for (let i = 0; i < totalSegments; i++) { + const base = i * 2; + indices.push(base, base + 1, base + 2); + indices.push(base + 1, base + 3, base + 2); + } + + const ribbonGeometry = new THREE.BufferGeometry(); + ribbonGeometry.setAttribute('position', new THREE.Float32BufferAttribute(vertices, 3)); + ribbonGeometry.setIndex(indices); + ribbonGeometry.computeVertexNormals(); + setGeometryKey((k) => k + 1); + return ribbonGeometry; + }, [asset.eventData, event]); + + return ( + <> + {asset.eventData?.type === 'Conveyor' && conveyorPlaneSize && geometry && ( + + + + + + )} + + ); +} + +export default RibbonCollider; \ No newline at end of file diff --git a/app/src/modules/scene/physics/colliders/colliderCreator.tsx b/app/src/modules/scene/physics/colliders/colliderCreator.tsx index e4aa3e5..c0b55b0 100644 --- a/app/src/modules/scene/physics/colliders/colliderCreator.tsx +++ b/app/src/modules/scene/physics/colliders/colliderCreator.tsx @@ -1,15 +1,19 @@ -import { useEffect, useState } from 'react'; +import { useEffect, useRef, useState } from 'react'; import { useThree } from '@react-three/fiber'; import ColliderInstance from './colliderInstance/colliderInstance'; +import { useToggleView } from '../../../../store/builder/store'; function ColliderCreator() { const { camera, gl, scene, raycaster, pointer } = useThree(); const [colliders, setColliders] = useState< - { id: string; position: [number, number, number]; colliderType: 'Default material' | 'Material 1' | 'Material 2' | 'Material 3' }[] + { id: string; position: [number, number, number]; rotation: [number, number, number]; colliderType: 'Default material' | 'Material 1' | 'Material 2' | 'Material 3' }[] >([]); + const drag = useRef(false); + const isLeftMouseDown = useRef(false); + const { toggleView } = useToggleView(); const handleCtrlClick = (e: MouseEvent) => { - if (!e.ctrlKey) return; + if (!e.ctrlKey || drag.current || toggleView) return; raycaster.setFromCamera(pointer, camera); @@ -25,6 +29,7 @@ function ColliderCreator() { { id: Date.now().toString(), position: spawnPosition, + rotation: [0, 0, 0], colliderType: 'Default material', } ]); @@ -33,22 +38,49 @@ function ColliderCreator() { useEffect(() => { const canvas = gl.domElement; + + const onMouseDown = (evt: any) => { + if (evt.button === 0) { + isLeftMouseDown.current = true; + drag.current = false; + } + }; + + const onMouseUp = (evt: any) => { + if (evt.button === 0) { + isLeftMouseDown.current = false; + } + }; + + const onMouseMove = () => { + if (isLeftMouseDown) { + drag.current = true; + } + }; + canvas.addEventListener('click', handleCtrlClick); + canvas.addEventListener('mousedown', onMouseDown); + canvas.addEventListener('mouseup', onMouseUp); + canvas.addEventListener('mousemove', onMouseMove); return () => { canvas.removeEventListener('click', handleCtrlClick); + canvas.removeEventListener('mousedown', onMouseDown); + canvas.removeEventListener('mouseup', onMouseUp); + canvas.removeEventListener('mousemove', onMouseMove); }; }, [colliders, camera]); return ( <> - {colliders.map(({ id, position }) => ( + {colliders.map(({ id, position, rotation }) => ( ))} diff --git a/app/src/modules/scene/physics/colliders/colliderInstance/colliderInstance.tsx b/app/src/modules/scene/physics/colliders/colliderInstance/colliderInstance.tsx index bcf1165..c7eb34e 100644 --- a/app/src/modules/scene/physics/colliders/colliderInstance/colliderInstance.tsx +++ b/app/src/modules/scene/physics/colliders/colliderInstance/colliderInstance.tsx @@ -4,11 +4,12 @@ import { CollisionPayload, RapierRigidBody, RigidBody } from '@react-three/rapie import { useEffect, useRef, useState } from 'react' import * as THREE from 'three'; -function ColliderInstance({ id, colliders, setColliders, position }: { +function ColliderInstance({ id, colliders, setColliders, position, rotation }: { id: string; - colliders: { id: string; position: [number, number, number]; colliderType: 'Default material' | 'Material 1' | 'Material 2' | 'Material 3' }[]; - setColliders: React.Dispatch>; + colliders: { id: string; position: [number, number, number]; rotation: [number, number, number]; colliderType: 'Default material' | 'Material 1' | 'Material 2' | 'Material 3' }[]; + setColliders: React.Dispatch>; position: [number, number, number]; + rotation: [number, number, number]; }) { const { camera, gl, pointer, controls } = useThree(); const [draggedId, setDraggedId] = useState(null); diff --git a/app/src/modules/scene/physics/curvedPlane.tsx b/app/src/modules/scene/physics/curvedPlane.tsx deleted file mode 100644 index a51d924..0000000 --- a/app/src/modules/scene/physics/curvedPlane.tsx +++ /dev/null @@ -1,115 +0,0 @@ -import { useThree } from '@react-three/fiber'; -import * as THREE from 'three'; -export default function CurvedPlane({ modelName, scene }: { modelName: string, scene: THREE.Scene }) { - // console.log('scene: ', scene);n - // console.log('modelName: ', modelName); - - function computeCircleFromPoints(p1: THREE.Vector2, p2: THREE.Vector2, p3: THREE.Vector2) { - const temp = p2.clone().sub(p1); - const temp2 = p3.clone().sub(p1); - const cross = temp.x * temp2.y - temp.y * temp2.x; - if (Math.abs(cross) < 1e-10) return null; // colinear - - const A = p1.lengthSq(); - const B = p2.lengthSq(); - const C = p3.lengthSq(); - - const D = 2 * (p1.x * (p2.y - p3.y) + p2.x * (p3.y - p1.y) + p3.x * (p1.y - p2.y)); - - const centerX = (A * (p2.y - p3.y) + B * (p3.y - p1.y) + C * (p1.y - p2.y)) / D; - const centerY = (A * (p3.x - p2.x) + B * (p1.x - p3.x) + C * (p2.x - p1.x)) / D; - - const center = new THREE.Vector2(centerX, centerY); - const radius = center.distanceTo(p1); - return { center, radius }; - } - function findFirstMesh(object: THREE.Object3D): THREE.Mesh | null { - if ((object as THREE.Mesh).isMesh) return object as THREE.Mesh; - - for (const child of object.children) { - const result = findFirstMesh(child); - if (result) return result; - } - - return null; - } - - const parentObject = scene.getObjectByName(modelName); // this is the group - if (!parentObject) return null; - - const mesh = findFirstMesh(parentObject); - console.log('mesh: ', mesh); - if (!mesh) { - console.warn('No mesh found inside group'); - return null; - } - - const geometry = mesh.geometry as THREE.BufferGeometry; - const positions = geometry?.attributes?.position; - - const points: THREE.Vector2[] = []; - for (let i = 0; i < positions.count; i += 20) { // sample every 20th vertex - const x = positions.getX(i); - const z = positions.getZ(i); // assuming Y is up - points.push(new THREE.Vector2(x, z)); - } - - console.log('points: ', points); - // Use three points to estimate curve - const p1 = points[0]; - const p2 = points[Math.floor(points.length / 2)]; - const p3 = points[points.length - 1]; - - const result = computeCircleFromPoints(p1, p2, p3); - if (!result) return null; - - const { center, radius } = result; - const angle = p1.clone().sub(center).angleTo(p3.clone().sub(center)); - - // Estimate width: take 2 points close to p2 but on either side - const width = 3; // placeholder, calculate from mesh later if needed - - const shape = new THREE.Shape(); - const segments = 32; - - const innerRadius = radius; - const outerRadius = radius + width; - - for (let i = 0; i <= segments; i++) { - const t = (angle * i) / segments; - const x = Math.cos(t) * outerRadius; - const y = Math.sin(t) * outerRadius; - if (i === 0) shape.moveTo(x, y); - else shape.lineTo(x, y); - } - - for (let i = segments; i >= 0; i--) { - const t = (angle * i) / segments; - const x = Math.cos(t) * innerRadius; - const y = Math.sin(t) * innerRadius; - shape.lineTo(x, y); - } - const helper = new THREE.BoxHelper(mesh, 0xffff00); - scene.add(helper); - - return ( - - - - - ); -} - diff --git a/app/src/modules/scene/physics/materialSpawner.tsx b/app/src/modules/scene/physics/materialSpawner.tsx index 80b524d..022d738 100644 --- a/app/src/modules/scene/physics/materialSpawner.tsx +++ b/app/src/modules/scene/physics/materialSpawner.tsx @@ -10,7 +10,7 @@ import { generateUniqueId } from '../../../functions/generateUniqueId'; type MaterialSpawnerProps = { position: [number, number, number]; spawnInterval: number; - spawnCount?: number; + spawnCount: number; }; function MaterialSpawner({ position, spawnInterval, spawnCount }: MaterialSpawnerProps) { @@ -177,11 +177,15 @@ function MaterialSpawner({ position, spawnInterval, spawnCount }: MaterialSpawne setSpawningPaused(prev => !prev); }; + const handleBoxContextMenu = () => { + }; + return ( <> @@ -192,12 +196,12 @@ function MaterialSpawner({ position, spawnInterval, spawnCount }: MaterialSpawne key={id} ref={ref} position={position} - colliders="cuboid" + colliders="hull" angularDamping={0.5} linearDamping={0.5} restitution={0.1} userData={{ materialType, materialUuid: id }} - // onSleep={() => handleSleep(id)} + onSleep={() => handleSleep(id)} > - {/* */} + spawnCount={5} + /> + + ) } diff --git a/app/src/modules/scene/scene.tsx b/app/src/modules/scene/scene.tsx index 4e760bf..ac1af61 100644 --- a/app/src/modules/scene/scene.tsx +++ b/app/src/modules/scene/scene.tsx @@ -74,8 +74,8 @@ export default function Scene({ layout }: { readonly layout: 'Main Layout' | 'Co - {/* */} + diff --git a/app/src/modules/scene/setup/setup.tsx b/app/src/modules/scene/setup/setup.tsx index 59d5cb5..9e06d3f 100644 --- a/app/src/modules/scene/setup/setup.tsx +++ b/app/src/modules/scene/setup/setup.tsx @@ -5,6 +5,7 @@ import Controls from '../controls/controls'; import { Environment } from '@react-three/drei' import background from "../../../assets/textures/hdr/mudroadpuresky2k.hdr"; + import SecondaryCamera from '../../secondaryCamera/secondaryCamera'; function Setup() { @@ -21,7 +22,8 @@ function Setup() { {/* */} - + + {/* */} ) } diff --git a/app/src/modules/secondaryCamera/secondaryCamera.tsx b/app/src/modules/secondaryCamera/secondaryCamera.tsx index 570a772..25466ec 100644 --- a/app/src/modules/secondaryCamera/secondaryCamera.tsx +++ b/app/src/modules/secondaryCamera/secondaryCamera.tsx @@ -15,11 +15,11 @@ type CameraData = { target: [number, number, number]; }; -function SecondaryCameraView() { +function SecondaryCamera() { const cameraRef = useRef(null); const helperRef = useRef(null); const rendererRef = useRef(null); - const dummyMeshRef = useRef(null); + const dummyMeshRef = useRef(null); const { scene, size, controls } = useThree(); const { secondaryCameraData, setSecondaryCameraData, updateSecondaryCameraData } = useSecondaryCameraData(); const { selectedSecondaryCamera, setSelectedSecondaryCamera } = useSecondaryCameraState(); @@ -166,4 +166,4 @@ function SecondaryCameraView() { ); } -export default SecondaryCameraView; +export default SecondaryCamera; diff --git a/app/src/pages/Project.tsx b/app/src/pages/Project.tsx index 7319c03..a0486b2 100644 --- a/app/src/pages/Project.tsx +++ b/app/src/pages/Project.tsx @@ -134,7 +134,7 @@ const Project: React.FC = () => { - + {/* */} {selectedUser && } {isLogListVisible && (