You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
I'm working on a custom "cloth" implementation. I have pretty much everything working except for a couple things:
When I update the number of segments within the plane/cloth using the UI, the y direction works perfectly, but when changing the x segment count, the first row which is initialized using type={isInFirstRow ? 'kinematicPosition' : 'dynamic'} would stay static if I put the property directly on the <Rigidbody /> component. I ended up removing that prop from the <Rigidbody /> and putting that logic into an effect to make it work properly using: currVertex.raw().setBodyType(isInFirstRow ? 2 : 0);
Now that I have that updating BETTER (still not perfect), it only updates correctly as I lower the amount. If I increase the number of x segments, the top row does not increase and the physics becomes erratic. You can also see that the spheres are orange (representing type of 'kinematicPosition') in the top row, but also start to wrap down to the lower rows if I start increases the segments.
I had a feeling that the reason that both of the previous issues were happening because I am using the index of the array item for the key on the root React component when I iterate over the array of rigidbodies. I changed this from:
but as soon as I do that, the rigidbodies actually disappear from my screen when using <Debug />. Here is my full file:
importtype{MeshProps}from'@react-three/fiber';import{useFrame}from'@react-three/fiber';importtype{RigidBodyApi}from'@react-three/rapier';import{Debug,RigidBody,useSphericalJoint}from'@react-three/rapier';import{Fragment,useCallback,useEffect,useMemo,useRef}from'react';importtype{Mesh}from'three';import{CollisionGroups}from'@app/constants';importtype{Vector3Object}from'@app/types';import{getCollisionGroups,useElementArrayRef}from'@app/utils';import{SphericalJoint}from'./SphericalJoint';interfaceClothPropsextendsOmit<MeshProps,'geometry'>{width: number;height: number;xSegments: number;ySegments: number;mass: number;windForce?: Vector3Object;}exportfunctionCloth(props: ClothProps): JSX.Element{const{ children, width, height, xSegments, ySegments, ...rest}=props;const{ clothRef, clothRbVertices }=useCloth(props);return(<group><Debug/><meshref={clothRef}receiveShadowcastShadow{...rest}><planeGeometryargs={[width,height,xSegments,ySegments]}/>{clothRbVertices}{children}</mesh></group>);}constclothColliderGroups=getCollisionGroups(CollisionGroups.Cloth,~CollisionGroups.Cloth);functionuseCloth({ xSegments, ySegments, mass, width, height, windForce }: ClothProps){constclothRef=useRef<Mesh>(null);constxVertexCount=xSegments+1;constyVertexCount=ySegments+1;constfaceWidth=width/xSegments;constfaceHeight=height/ySegments;consttotalVertices=xVertexCount*yVertexCount;const{initialized: clothRbVerticesInitialized,refs: clothRbVerticesRefs,
refFunction,}=useElementArrayRef<RigidBodyApi>(totalVertices,[totalVertices]);useEffect(()=>{if(!clothRbVerticesInitialized)return;// initialize physics rb positions to match the plane mesh's vertices positionsclothRbVerticesRefs.current.forEach((currVertex,i)=>{if(currVertex!=null&&clothRef.current!=null){const{position: pos}=clothRef.current.geometry.attributes;currVertex.setTranslation({x: pos.getX(i),y: pos.getY(i),z: pos.getZ(i)});constisInFirstRow=i<xVertexCount;// 2 is 'kinematicPosition' and 0 is 'dynamic'. Doing this here instead of on the component as props because it isn't updating properly therecurrVertex.raw().setBodyType(isInFirstRow ? 2 : 0);}});},[clothRbVerticesInitialized,clothRbVerticesRefs,xVertexCount]);useFrame(()=>{if(!clothRbVerticesInitialized)return;clothRbVerticesRefs.current.forEach((currentRb,i)=>{if(currentRb!=null&&clothRef.current!=null){if(windForce!=null){currentRb.resetForces();const{ x, y, z }=windForce;if(x!==0||y!==0||z!=0){constrandValue=Math.random();currentRb.addForce({x: x*randValue,y: y*randValue,z: z*randValue,});}}const{ x, y, z }=currentRb.translation();clothRef.current.geometry.attributes.position.setXYZ(i,x,y,z);clothRef.current.geometry.attributes.position.needsUpdate=true;}});});constgetJointToNextVertex=useCallback((i: number)=>{if(!clothRbVerticesInitialized)returnnull;constcurrVertex=clothRbVerticesRefs.current[i];constnextVertex=clothRbVerticesRefs.current[i+1];if(currVertex!=null&&nextVertex!=null&&(i+1)%xVertexCount!==0){constcurrVertexAnchorPos={// the attaching joint should be the right side of the facex: faceWidth/2,y: 0,z: 0,};constnextVertexAnchorPos={// the attaching joint should be the left side of the facex: -faceWidth/2,y: 0,z: 0,};return(<SphericalJointbody1={currVertex}body2={nextVertex}body1Anchor={currVertexAnchorPos}body2Anchor={nextVertexAnchorPos}/>);}returnnull;},[clothRbVerticesInitialized,clothRbVerticesRefs,faceWidth,xVertexCount]);constgetJointToBelowVertex=useCallback((i: number)=>{if(!clothRbVerticesInitialized)return;constcurrVertex=clothRbVerticesRefs.current[i];constbelowVertex=clothRbVerticesRefs.current[i+xVertexCount];if(currVertex!=null&&belowVertex!=null){constcurrVertexAnchorPos={x: 0,// the attaching joint should be the bottom side of the facey: -faceHeight/2,z: 0,};constbelowVertexAnchorPos={x: 0,// the attaching joint should be the top side of the facey: faceHeight/2,z: 0,};return(<SphericalJointbody1={currVertex}body2={belowVertex}body1Anchor={currVertexAnchorPos}body2Anchor={belowVertexAnchorPos}/>);}returnnull;},[clothRbVerticesInitialized,clothRbVerticesRefs,faceHeight,xVertexCount]);constclothRbVertices=useMemo(()=>clothRbVerticesRefs.current.map((item,i)=>{return(<Fragmentkey={item?.handle ? `${item.handle}` : i}><RigidBodyref={(ref)=>refFunction(ref,i)}colliders="ball"collisionGroups={clothColliderGroups}includeInvisiblemass={mass/totalVertices}// TODO: test out higher linear dampening, high mass, with higher forceslinearDamping={0.8}><meshvisible={false}><sphereGeometryargs={[0.1]}/><meshBasicMaterialwireframe/></mesh></RigidBody>{getJointToNextVertex(i)}{getJointToBelowVertex(i)}</Fragment>);}),[clothRbVerticesRefs,getJointToBelowVertex,getJointToNextVertex,mass,refFunction,totalVertices,]);return{ clothRef, clothRbVertices };}exportfunctionuseElementArrayRef<RefType=unknown>(length: number,deps: DependencyList=emptyArray): {refs: React.MutableRefObject<(RefType|null)[]>;refFunction: (ref: RefType|null,refIndex: number)=>void;initialized: boolean;}{constrefs=useRef<(RefType|null)[]>(Array(length).fill(null));constinitializedRefCount=useRef(0);const[initialized,setInitialized]=useState(false);useEffect(()=>{if(refs.current.length!==length){refs.current=Array(length).fill(null);setInitialized(false);initializedRefCount.current=0;}// eslint-disable-next-line react-hooks/exhaustive-deps},[length, ...deps]);constrefFunction=useCallback((ref: RefType|null,refIndex: number)=>{if(!initialized&&initializedRefCount.current<refs.current.length){refs.current[refIndex]=ref;if(ref!=null){initializedRefCount.current++;}if(initializedRefCount.current===refs.current.length){setInitialized(true);}}},// eslint-disable-next-line react-hooks/exhaustive-deps[initialized, ...deps]);return{ refs, refFunction, initialized };}exportconstSphericalJoint=memo(functionSphericalJoint({
body1,
body2,
body1Anchor,
body2Anchor,}: {body1: RigidBodyApi;body2: RigidBodyApi;body1Anchor: Vector3Object;body2Anchor: Vector3Object;}){const{x: x1,y: y1,z: z1}=body1Anchor;const{x: x2,y: y2,z: z2}=body2Anchor;constaRef=useRef(body1);constbRef=useRef(body2);useSphericalJoint(aRef,bRef,[[x1,y1,z1],[x2,y2,z2],]);returnnull;});
Here is a video displaying the behavior:
Screen.Recording.2022-11-30.at.7.54.20.PM.mov
As a side note, would it be better for performance to create this component using InstancedMeshes and InstancedRigidBodies if I want to have multiple cloths which can be affected by physics in the world at once?
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
-
I'm working on a custom "cloth" implementation. I have pretty much everything working except for a couple things:
When I update the number of segments within the plane/cloth using the UI, the y direction works perfectly, but when changing the x segment count, the first row which is initialized using
type={isInFirstRow ? 'kinematicPosition' : 'dynamic'}
would stay static if I put the property directly on the<Rigidbody />
component. I ended up removing that prop from the<Rigidbody />
and putting that logic into an effect to make it work properly using:currVertex.raw().setBodyType(isInFirstRow ? 2 : 0);
Now that I have that updating BETTER (still not perfect), it only updates correctly as I lower the amount. If I increase the number of x segments, the top row does not increase and the physics becomes erratic. You can also see that the spheres are orange (representing type of 'kinematicPosition') in the top row, but also start to wrap down to the lower rows if I start increases the segments.
I had a feeling that the reason that both of the previous issues were happening because I am using the index of the array item for the key on the root React component when I iterate over the array of rigidbodies. I changed this from:
to this:
but as soon as I do that, the rigidbodies actually disappear from my screen when using
<Debug />
. Here is my full file:Here is a video displaying the behavior:
Screen.Recording.2022-11-30.at.7.54.20.PM.mov
As a side note, would it be better for performance to create this component using InstancedMeshes and InstancedRigidBodies if I want to have multiple cloths which can be affected by physics in the world at once?
Beta Was this translation helpful? Give feedback.
All reactions