plane geometry added with physics
This commit is contained in:
@@ -1,14 +1,11 @@
|
|||||||
import * as THREE from 'three';
|
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 { useEffect, useRef, useState } from 'react';
|
||||||
import { useFrame } from '@react-three/fiber';
|
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,
|
boundingBox: THREE.Box3 | null,
|
||||||
asset: Asset,
|
asset: Asset,
|
||||||
modelName: string,
|
|
||||||
scene: THREE.Scene,
|
|
||||||
conveyorPlaneSize: [number, number] | null,
|
conveyorPlaneSize: [number, number] | null,
|
||||||
}) {
|
}) {
|
||||||
const conveyorRef = useRef<any>(null);
|
const conveyorRef = useRef<any>(null);
|
||||||
@@ -18,9 +15,9 @@ function ConveyorCollider({ boundingBox, asset, modelName, conveyorPlaneSize, sc
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!boundingBox || !conveyorPlaneSize) return;
|
if (!boundingBox || !conveyorPlaneSize) return;
|
||||||
const [width, depth] = conveyorPlaneSize;
|
const [width, depth] = conveyorPlaneSize;
|
||||||
if (width < depth) { //z-axis conveyor
|
if (width < depth) {
|
||||||
conveyorDirection.current.set(0, 0, 1);
|
conveyorDirection.current.set(0, 0, 1);
|
||||||
} else {//x-axis conveyor
|
} else {
|
||||||
conveyorDirection.current.set(1, 0, 0);
|
conveyorDirection.current.set(1, 0, 0);
|
||||||
}
|
}
|
||||||
const rotation = new THREE.Euler().fromArray(asset.rotation || [0, 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"
|
colliders="cuboid"
|
||||||
>
|
>
|
||||||
<mesh>
|
<mesh>
|
||||||
{/* <CurvedPlane modelName={modelName} scene={scene} /> */}
|
|
||||||
<planeGeometry args={conveyorPlaneSize} />
|
<planeGeometry args={conveyorPlaneSize} />
|
||||||
<meshBasicMaterial
|
<meshBasicMaterial
|
||||||
color="green"
|
color="green"
|
||||||
|
|||||||
@@ -19,10 +19,12 @@ import { useSceneContext } from '../../../../scene/sceneContext';
|
|||||||
import { useVersionContext } from '../../../version/versionContext';
|
import { useVersionContext } from '../../../version/versionContext';
|
||||||
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';
|
|
||||||
|
|
||||||
import { ModelAnimator } from './animator/modelAnimator';
|
import { ModelAnimator } from './animator/modelAnimator';
|
||||||
|
|
||||||
|
import ConveyorCollider from './conveyorCollider';
|
||||||
|
import RibbonCollider from './ribbonCollider';
|
||||||
|
|
||||||
function Model({ asset, isRendered }: { readonly asset: Asset, isRendered: boolean }) {
|
function Model({ asset, isRendered }: { readonly asset: Asset, isRendered: boolean }) {
|
||||||
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 { controls, gl, scene } = useThree();
|
const { controls, gl, scene } = useThree();
|
||||||
@@ -459,13 +461,13 @@ function Model({ asset, isRendered }: { readonly asset: Asset, isRendered: boole
|
|||||||
{isRendered ? (
|
{isRendered ? (
|
||||||
<>
|
<>
|
||||||
|
|
||||||
<RigidBody
|
{/* <RigidBody
|
||||||
type="fixed"
|
type="fixed"
|
||||||
colliders='cuboid'
|
colliders='hull'
|
||||||
ref={rigidBodyRef}
|
ref={rigidBodyRef}
|
||||||
>
|
> */}
|
||||||
<primitive object={gltfScene} />
|
<primitive object={gltfScene} />
|
||||||
</ RigidBody>
|
{/* </ RigidBody> */}
|
||||||
|
|
||||||
<ModelAnimator asset={asset} gltfScene={gltfScene} />
|
<ModelAnimator asset={asset} gltfScene={gltfScene} />
|
||||||
|
|
||||||
@@ -474,10 +476,13 @@ function Model({ asset, isRendered }: { readonly asset: Asset, isRendered: boole
|
|||||||
<AssetBoundingBox name='Asset Fallback' boundingBox={boundingBox} color='gray' lineWidth={2.5} />
|
<AssetBoundingBox name='Asset Fallback' boundingBox={boundingBox} color='gray' lineWidth={2.5} />
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<ConveyorCollider boundingBox={boundingBox}
|
{/* <ConveyorCollider boundingBox={boundingBox}
|
||||||
|
asset={asset}
|
||||||
|
conveyorPlaneSize={conveyorPlaneSize}
|
||||||
|
/> */}
|
||||||
|
|
||||||
|
<RibbonCollider boundingBox={boundingBox}
|
||||||
asset={asset}
|
asset={asset}
|
||||||
modelName={asset.modelName}
|
|
||||||
scene={scene}
|
|
||||||
conveyorPlaneSize={conveyorPlaneSize}
|
conveyorPlaneSize={conveyorPlaneSize}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|||||||
182
app/src/modules/builder/asset/models/model/ribbonCollider.tsx
Normal file
182
app/src/modules/builder/asset/models/model/ribbonCollider.tsx
Normal file
@@ -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<any>(null);
|
||||||
|
const [objectsOnConveyor, setObjectsOnConveyor] = useState<Set<any>>(new Set());
|
||||||
|
const conveyorDirection = useRef<THREE.Vector3>(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 && (
|
||||||
|
<RigidBody
|
||||||
|
key={geometryKey}
|
||||||
|
ref={conveyorRef}
|
||||||
|
type="fixed"
|
||||||
|
position={[0, 0.001, 0]}
|
||||||
|
userData={{ isConveyor: true }}
|
||||||
|
onCollisionEnter={handleMaterialEnter}
|
||||||
|
onCollisionExit={handleMaterialExit}
|
||||||
|
colliders="trimesh"
|
||||||
|
>
|
||||||
|
<mesh geometry={geometry} >
|
||||||
|
<meshStandardMaterial color="skyblue" side={THREE.DoubleSide} opacity={0.5} transparent />
|
||||||
|
</mesh>
|
||||||
|
</RigidBody>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default RibbonCollider;
|
||||||
@@ -1,15 +1,19 @@
|
|||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useRef, useState } from 'react';
|
||||||
import { useThree } from '@react-three/fiber';
|
import { useThree } from '@react-three/fiber';
|
||||||
import ColliderInstance from './colliderInstance/colliderInstance';
|
import ColliderInstance from './colliderInstance/colliderInstance';
|
||||||
|
import { useToggleView } from '../../../../store/builder/store';
|
||||||
|
|
||||||
function ColliderCreator() {
|
function ColliderCreator() {
|
||||||
const { camera, gl, scene, raycaster, pointer } = useThree();
|
const { camera, gl, scene, raycaster, pointer } = useThree();
|
||||||
const [colliders, setColliders] = useState<
|
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) => {
|
const handleCtrlClick = (e: MouseEvent) => {
|
||||||
if (!e.ctrlKey) return;
|
if (!e.ctrlKey || drag.current || toggleView) return;
|
||||||
|
|
||||||
raycaster.setFromCamera(pointer, camera);
|
raycaster.setFromCamera(pointer, camera);
|
||||||
|
|
||||||
@@ -25,6 +29,7 @@ function ColliderCreator() {
|
|||||||
{
|
{
|
||||||
id: Date.now().toString(),
|
id: Date.now().toString(),
|
||||||
position: spawnPosition,
|
position: spawnPosition,
|
||||||
|
rotation: [0, 0, 0],
|
||||||
colliderType: 'Default material',
|
colliderType: 'Default material',
|
||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
@@ -33,22 +38,49 @@ function ColliderCreator() {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const canvas = gl.domElement;
|
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('click', handleCtrlClick);
|
||||||
|
canvas.addEventListener('mousedown', onMouseDown);
|
||||||
|
canvas.addEventListener('mouseup', onMouseUp);
|
||||||
|
canvas.addEventListener('mousemove', onMouseMove);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
canvas.removeEventListener('click', handleCtrlClick);
|
canvas.removeEventListener('click', handleCtrlClick);
|
||||||
|
canvas.removeEventListener('mousedown', onMouseDown);
|
||||||
|
canvas.removeEventListener('mouseup', onMouseUp);
|
||||||
|
canvas.removeEventListener('mousemove', onMouseMove);
|
||||||
};
|
};
|
||||||
}, [colliders, camera]);
|
}, [colliders, camera]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{colliders.map(({ id, position }) => (
|
{colliders.map(({ id, position, rotation }) => (
|
||||||
<ColliderInstance
|
<ColliderInstance
|
||||||
key={id}
|
key={id}
|
||||||
id={id}
|
id={id}
|
||||||
colliders={colliders}
|
colliders={colliders}
|
||||||
setColliders={setColliders}
|
setColliders={setColliders}
|
||||||
position={position}
|
position={position}
|
||||||
|
rotation={rotation}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -4,11 +4,12 @@ import { CollisionPayload, RapierRigidBody, RigidBody } from '@react-three/rapie
|
|||||||
import { useEffect, useRef, useState } from 'react'
|
import { useEffect, useRef, useState } from 'react'
|
||||||
import * as THREE from 'three';
|
import * as THREE from 'three';
|
||||||
|
|
||||||
function ColliderInstance({ id, colliders, setColliders, position }: {
|
function ColliderInstance({ id, colliders, setColliders, position, rotation }: {
|
||||||
id: string;
|
id: string;
|
||||||
colliders: { id: string; position: [number, number, number]; colliderType: 'Default material' | 'Material 1' | 'Material 2' | 'Material 3' }[];
|
colliders: { id: string; position: [number, number, number]; rotation: [number, number, number]; colliderType: 'Default material' | 'Material 1' | 'Material 2' | 'Material 3' }[];
|
||||||
setColliders: React.Dispatch<React.SetStateAction<{ id: string; position: [number, number, number]; colliderType: 'Default material' | 'Material 1' | 'Material 2' | 'Material 3' }[]>>;
|
setColliders: React.Dispatch<React.SetStateAction<{ id: string; position: [number, number, number]; rotation: [number, number, number]; colliderType: 'Default material' | 'Material 1' | 'Material 2' | 'Material 3' }[]>>;
|
||||||
position: [number, number, number];
|
position: [number, number, number];
|
||||||
|
rotation: [number, number, number];
|
||||||
}) {
|
}) {
|
||||||
const { camera, gl, pointer, controls } = useThree();
|
const { camera, gl, pointer, controls } = useThree();
|
||||||
const [draggedId, setDraggedId] = useState<string | null>(null);
|
const [draggedId, setDraggedId] = useState<string | null>(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 (
|
|
||||||
<mesh
|
|
||||||
// position={[
|
|
||||||
// mesh.getWorldPosition(new THREE.Vector3()).x,
|
|
||||||
// mesh.getWorldPosition(new THREE.Vector3()).y,
|
|
||||||
// mesh.getWorldPosition(new THREE.Vector3()).z
|
|
||||||
// ]}
|
|
||||||
scale={mesh.getWorldScale(new THREE.Vector3())}
|
|
||||||
rotation={[0, 0, 0]}
|
|
||||||
>
|
|
||||||
<shapeGeometry args={[shape]} />
|
|
||||||
<meshBasicMaterial
|
|
||||||
color="green"
|
|
||||||
transparent
|
|
||||||
opacity={0.3}
|
|
||||||
side={THREE.DoubleSide}
|
|
||||||
/>
|
|
||||||
</mesh>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -10,7 +10,7 @@ import { generateUniqueId } from '../../../functions/generateUniqueId';
|
|||||||
type MaterialSpawnerProps = {
|
type MaterialSpawnerProps = {
|
||||||
position: [number, number, number];
|
position: [number, number, number];
|
||||||
spawnInterval: number;
|
spawnInterval: number;
|
||||||
spawnCount?: number;
|
spawnCount: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
function MaterialSpawner({ position, spawnInterval, spawnCount }: MaterialSpawnerProps) {
|
function MaterialSpawner({ position, spawnInterval, spawnCount }: MaterialSpawnerProps) {
|
||||||
@@ -177,11 +177,15 @@ function MaterialSpawner({ position, spawnInterval, spawnCount }: MaterialSpawne
|
|||||||
setSpawningPaused(prev => !prev);
|
setSpawningPaused(prev => !prev);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleBoxContextMenu = () => {
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<mesh
|
<mesh
|
||||||
position={position}
|
position={position}
|
||||||
onClick={handleBoxClick}
|
onClick={handleBoxClick}
|
||||||
|
onContextMenu={handleBoxContextMenu}
|
||||||
>
|
>
|
||||||
<boxGeometry args={[1, 1, 1]} />
|
<boxGeometry args={[1, 1, 1]} />
|
||||||
<meshStandardMaterial color={spawningPaused ? "red" : "white"} transparent opacity={0.2} />
|
<meshStandardMaterial color={spawningPaused ? "red" : "white"} transparent opacity={0.2} />
|
||||||
@@ -192,12 +196,12 @@ function MaterialSpawner({ position, spawnInterval, spawnCount }: MaterialSpawne
|
|||||||
key={id}
|
key={id}
|
||||||
ref={ref}
|
ref={ref}
|
||||||
position={position}
|
position={position}
|
||||||
colliders="cuboid"
|
colliders="hull"
|
||||||
angularDamping={0.5}
|
angularDamping={0.5}
|
||||||
linearDamping={0.5}
|
linearDamping={0.5}
|
||||||
restitution={0.1}
|
restitution={0.1}
|
||||||
userData={{ materialType, materialUuid: id }}
|
userData={{ materialType, materialUuid: id }}
|
||||||
// onSleep={() => handleSleep(id)}
|
onSleep={() => handleSleep(id)}
|
||||||
>
|
>
|
||||||
<MaterialModel
|
<MaterialModel
|
||||||
materialId={id}
|
materialId={id}
|
||||||
|
|||||||
@@ -1,26 +1,28 @@
|
|||||||
import React from 'react'
|
|
||||||
import MaterialSpawner from './materialSpawner'
|
import MaterialSpawner from './materialSpawner'
|
||||||
import ColliderCreator from './colliders/colliderCreator'
|
import ColliderCreator from './colliders/colliderCreator'
|
||||||
|
|
||||||
function PhysicsSimulator() {
|
function PhysicsSimulator() {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{/* <MaterialSpawner
|
|
||||||
|
<MaterialSpawner
|
||||||
position={[0, 3, 0]}
|
position={[0, 3, 0]}
|
||||||
spawnInterval={1000}
|
spawnInterval={1000}
|
||||||
spawnCount={15}
|
spawnCount={15}
|
||||||
/>
|
/>
|
||||||
<MaterialSpawner
|
<MaterialSpawner
|
||||||
position={[-21, 3, -8]}
|
position={[6, 3, 6]}
|
||||||
spawnInterval={1000}
|
spawnInterval={1000}
|
||||||
spawnCount={5}
|
spawnCount={5}
|
||||||
/>
|
/>
|
||||||
<MaterialSpawner
|
<MaterialSpawner
|
||||||
position={[-17, 3, 6]}
|
position={[6, 3, -6]}
|
||||||
spawnInterval={1000}
|
spawnInterval={1000}
|
||||||
spawnCount={50}
|
spawnCount={5}
|
||||||
/> */}
|
/>
|
||||||
|
|
||||||
<ColliderCreator />
|
<ColliderCreator />
|
||||||
|
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -74,8 +74,8 @@ export default function Scene({ layout }: { readonly layout: 'Main Layout' | 'Co
|
|||||||
<Setup />
|
<Setup />
|
||||||
<Collaboration />
|
<Collaboration />
|
||||||
<Physics gravity={[0, -9.81, 0]} allowedLinearError={50} numSolverIterations={50} debug >
|
<Physics gravity={[0, -9.81, 0]} allowedLinearError={50} numSolverIterations={50} debug >
|
||||||
{/* <Physics gravity={[0, -9.81, 0]} allowedLinearError={50} numSolverIterations={50} > */}
|
|
||||||
<Builder />
|
<Builder />
|
||||||
|
|
||||||
<Simulation />
|
<Simulation />
|
||||||
|
|
||||||
<PhysicsSimulator />
|
<PhysicsSimulator />
|
||||||
|
|||||||
@@ -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';
|
import SecondaryCamera from '../../secondaryCamera/secondaryCamera';
|
||||||
|
|
||||||
function Setup() {
|
function Setup() {
|
||||||
@@ -21,7 +22,8 @@ function Setup() {
|
|||||||
{/* <MovingClouds /> */}
|
{/* <MovingClouds /> */}
|
||||||
|
|
||||||
<Environment files={background} environmentIntensity={1.5} />
|
<Environment files={background} environmentIntensity={1.5} />
|
||||||
<SecondaryCamera/>
|
|
||||||
|
{/* <SecondaryCamera /> */}
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ type CameraData = {
|
|||||||
target: [number, number, number];
|
target: [number, number, number];
|
||||||
};
|
};
|
||||||
|
|
||||||
function SecondaryCameraView() {
|
function SecondaryCamera() {
|
||||||
const cameraRef = useRef<THREE.PerspectiveCamera | null>(null);
|
const cameraRef = useRef<THREE.PerspectiveCamera | null>(null);
|
||||||
const helperRef = useRef<THREE.CameraHelper | null>(null);
|
const helperRef = useRef<THREE.CameraHelper | null>(null);
|
||||||
const rendererRef = useRef<THREE.WebGLRenderer | null>(null);
|
const rendererRef = useRef<THREE.WebGLRenderer | null>(null);
|
||||||
@@ -166,4 +166,4 @@ function SecondaryCameraView() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default SecondaryCameraView;
|
export default SecondaryCamera;
|
||||||
|
|||||||
@@ -134,7 +134,7 @@ const Project: React.FC = () => {
|
|||||||
</VersionProvider>
|
</VersionProvider>
|
||||||
</SceneProvider>
|
</SceneProvider>
|
||||||
|
|
||||||
<SecondaryCanvas />
|
{/* <SecondaryCanvas /> */}
|
||||||
|
|
||||||
{selectedUser && <FollowPerson />}
|
{selectedUser && <FollowPerson />}
|
||||||
{isLogListVisible && (
|
{isLogListVisible && (
|
||||||
|
|||||||
Reference in New Issue
Block a user