diff --git a/QuarkPhysics/qaabb.h b/QuarkPhysics/qaabb.h index 8558f36..7c133df 100644 --- a/QuarkPhysics/qaabb.h +++ b/QuarkPhysics/qaabb.h @@ -85,15 +85,11 @@ class QAABB return (minPos+maxPos)*0.5f; } - bool isContain(QAABB &otherAABB)const{ - bool result=true; - result=result && minPos.x<=otherAABB.minPos.x; - result=result && minPos.y<=otherAABB.minPos.y; - result=result && maxPos.x>=otherAABB.maxPos.x; - result=result && maxPos.y>=otherAABB.maxPos.y; - - return result; - + bool isContain(const QAABB& otherAABB) const { + return (minPos.x <= otherAABB.minPos.x) && + (minPos.y <= otherAABB.minPos.y) && + (maxPos.x >= otherAABB.maxPos.x) && + (maxPos.y >= otherAABB.maxPos.y); } static QAABB Combine( QAABB &b1, QAABB &b2){ QAABB output=QAABB(); @@ -116,11 +112,15 @@ class QAABB return QAABB(minPos-amountVec,maxPos+amountVec); } - bool isCollidingWith(QAABB otherAABB) const{ - if( minPos.x<=otherAABB.maxPos.x && maxPos.x>=otherAABB.minPos.x && minPos.y<=otherAABB.maxPos.y && maxPos.y>=otherAABB.minPos.y ){ - return true; - } - return false; + + QAABB FattedWithRate(float rate)const{ + QVector ratedSize=size*rate*0.5f; + return QAABB(minPos-ratedSize,maxPos+ratedSize); + } + + bool isCollidingWith(const QAABB& otherAABB) const { + return maxPos.x >= otherAABB.minPos.x && minPos.x <= otherAABB.maxPos.x && + maxPos.y >= otherAABB.minPos.y && minPos.y <= otherAABB.maxPos.y; } diff --git a/QuarkPhysics/qbody.h b/QuarkPhysics/qbody.h index 08a96c7..3fa38f0 100644 --- a/QuarkPhysics/qbody.h +++ b/QuarkPhysics/qbody.h @@ -74,6 +74,7 @@ class QBody{ float rotation=0.0f; float prevRotation=0.0f; QAABB aabb; + QAABB spatialContainerAABB; QAABB fattedAABB; Modes mode=QBody::Modes::DYNAMIC; bool inertiaNeedsUpdate=true; @@ -610,6 +611,7 @@ class QBody{ friend class QManifold; friend class QParticle; friend class QJoint; + friend class QBroadPhase; protected: vector _meshes=vector(); diff --git a/QuarkPhysics/qbroadphase.cpp b/QuarkPhysics/qbroadphase.cpp index b21586c..4ceb280 100644 --- a/QuarkPhysics/qbroadphase.cpp +++ b/QuarkPhysics/qbroadphase.cpp @@ -26,120 +26,179 @@ **************************************************************************************/ #include "qbroadphase.h" -#include #include #include +#include "qworld.h" +#include "qaabb.h" -QBroadPhase::~QBroadPhase() -{ +QBroadPhase::QBroadPhase(float cellSize) : cellSize(cellSize) {} -} +void QBroadPhase::insert(QBody *body, const QAABB& aabb) { + std::vector cellKeys = getCellKeys(aabb); -QBroadPhase *QBroadPhase::Add(QBody* body) -{ - QAABB aabb=body->GetAABB(); - if(body->GetFattedAABB().isContain(aabb)){ - return this; - } + for (int key : cellKeys) { + hashTable[key].push_back(body); + } + +} - RemoveBodyFromCells(body->GetFattedAABB(),body); +void QBroadPhase::update(QBody *body, const QAABB& newAABB) { + + //Debug Test Bounding Boxes + /* body->GetWorld()->gizmos.push_back( new QGizmoRect(body->spatialContainerAABB) ); + body->GetWorld()->gizmos.push_back( new QGizmoRect(newAABB) ); */ - float fatFactor=5.0f; - if(body->GetSimulationModel()==QBody::SimulationModels::RIGID_BODY){ - fatFactor+=(body->GetPosition()-body->GetPreviousPosition()).Length(); - } - body->GetFattedAABB()=body->GetAABB().Fatted(fatFactor ); - QVector min = body->GetFattedAABB().GetMin(); - min *= inverseCellSize; - int minX = std::floor(min.x); - int minY= std::floor(min.y); + if(body->spatialContainerAABB.isContain(newAABB) ){ + return; + } + auto fatAABB=newAABB.FattedWithRate(1.2f); + - QVector max = body->GetFattedAABB().GetMax(); - max *= inverseCellSize; - int maxX = std::floor(max.x); - int maxY= std::floor(max.y); + remove(body, body->spatialContainerAABB); + + insert(body, fatAABB); - for (int x = minX; x <= maxX; x++){ - for (int y = minY; y <= maxY; y++) - { - int cell=hashFunction(x,y); + body->spatialContainerAABB=fatAABB; +} - collisionGroups[cell].insert(body); - } - } +void QBroadPhase::remove(QBody *body, const QAABB& aabb) { + std::vector cellKeys = getCellKeys(aabb); - isBodyAdded=true; - return this; + for (int key : cellKeys) { + auto& cell = hashTable[key]; + auto it=find(cell.begin(),cell.end(),body); + if(it!=cell.end() ){ + cell.erase(it); + } + } } +void QBroadPhase::ApplySweepAndPruneToCells() +{ + for (auto& cellPair : hashTable) { + vector &cell = cellPair.second; + sort(cell.begin(),cell.end(),QWorld::SortBodies); + } +} +void QBroadPhase::GetPotentialCollisions(QBody *body, unordered_set &collection) +{ + + + if(body->GetEnabled()==false ) + return; + + std::vector cellKeys = getCellKeys(body->spatialContainerAABB); + + for (size_t i = 0; i < cellKeys.size(); ++i){ + auto &cell = hashTable[cellKeys[i]]; + + auto itA = cell.begin(); + while (itA != cell.end() && *itA != body) { + ++itA; + } + + for (auto itB = std::next(itA); itB != cell.end(); ++itB) { + QBody *otherBody=*itB; + + if(otherBody->GetEnabled()==false ) + continue; + + if( QBody::CanCollide(body,otherBody)==false){ + continue; + } + body->GetWorld()->debugAABBTestCount+=1; + if(body->GetAABB().GetMax().x >= otherBody->GetAABB().GetMin().x){ + if( body->GetAABB().GetMin().y <= otherBody->GetAABB().GetMax().y && + body->GetAABB().GetMax().y >= otherBody->GetAABB().GetMin().y) { + collection.insert(otherBody); + } + + }else{ + break; + } + + + } + + + } + +} +vector< vector > QBroadPhase::GetBodiesFromCells() { + vector< vector > result; + // Hash tablosundaki her hücreyi dolaşıyoruz. + for (const auto& cellPair : hashTable) { + const vector& cell = cellPair.second; + std::vector uniqueBodies(cell.begin(), cell.end()); + result.push_back(uniqueBodies); + } -QBroadPhase *QBroadPhase::Clear() -{ - collisionGroups.clear(); - return this; + return result; } -std::unordered_set,QBroadPhase::pairHash,QBroadPhase::pairEqual>* QBroadPhase::GetPairs() -{ -// if(isBodyAdded==false) -// return &pairList; - - pairList.clear(); - - /* for(auto const &[key, value] : collisionGroups) { - for(auto ita=value.begin();ita!=value.end();++ita) { - auto bodyA=*ita; - for(auto itb=next(ita);itb!=value.end();++itb) { - auto bodyB=*itb; - auto pair = minmax(bodyA, bodyB); - pairList.insert(pair); - - } - } - } */ - isBodyAdded=false; - return &pairList; - -} +void QBroadPhase::GetAllPairs( unordered_set,QBroadPhase::bodyPairHash,bodyPairEqual > &pairs){ -void QBroadPhase::RemoveBodyFromCells(QAABB referenceAABB, QBody *body) -{ - QVector min = referenceAABB.GetMin(); - min *= inverseCellSize; - int minX = std::floor(min.x); - int minY= std::floor(min.y); - - - QVector max = referenceAABB.GetMax(); - max *= inverseCellSize; - int maxX = std::floor(max.x); - int maxY= std::floor(max.y); - - for (int x = minX; x <= maxX; x++){ - for (int y = minY; y <= maxY; y++) - { - int cell=hashFunction(x,y); - auto groupIt=collisionGroups.find(cell); - if(groupIt==collisionGroups.end()) + + + + for (auto& cellPair : hashTable) { + vector &cell = cellPair.second; + + for (auto itA = cell.begin(); itA != cell.end(); ++itA) { + QBody *body=*itA; + if(body->GetEnabled()==false ) continue; - unordered_set *group=&groupIt->second; - //auto it=std::find(group->begin(),group->end(),body); - auto it=group->find(body); - if(it!=group->end()){ - group->erase(it); - } + for (auto itB = itA+1; itB != cell.end(); ++itB) { + QBody *otherBody=*itB; + + if(otherBody->GetEnabled()==false ) + continue; + + /* if( QBody::CanCollide(body,otherBody)==false){ + continue; + } */ + + body->GetWorld()->debugAABBTestCount+=1; + if(body->GetAABB().isCollidingWith(otherBody->GetAABB()) ){ + pairs.insert(pair{body,otherBody}); + } + + + } + } + + + + + } + + +} + +std::vector QBroadPhase::getCellKeys(QAABB aabb) { + std::vector cellKeys; - } - } + int minCellX = static_cast(aabb.GetMin().x / cellSize); + int minCellY = static_cast(aabb.GetMin().y / cellSize); + int maxCellX = static_cast(aabb.GetMax().x / cellSize); + int maxCellY = static_cast(aabb.GetMax().y / cellSize); + // AABB'nin kapsadığı hücreleri buluyoruz. + for (int cellX = minCellX; cellX <= maxCellX; ++cellX) { + for (int cellY = minCellY; cellY <= maxCellY; ++cellY) { + int key = cellX | (cellY << 16); + cellKeys.push_back(key); + } + } + return cellKeys; } diff --git a/QuarkPhysics/qbroadphase.h b/QuarkPhysics/qbroadphase.h index a701585..37a62f2 100644 --- a/QuarkPhysics/qbroadphase.h +++ b/QuarkPhysics/qbroadphase.h @@ -31,59 +31,64 @@ #include #include #include +#include "qaabb.h" #include "qbody.h" -#include "qvector.h" +#include "qmanifold.h" +#include "qcollision.h" -class QBroadPhase -{ - int hashFunction(int x, int y){ - return x+31*y; - } - QVector inverseCellSize; - struct pairHash { - size_t operator()(const std::pair &p) const { - return std::hash()(p.first) + std::hash()(p.second); + +class QBroadPhase { +public: + QBroadPhase(){}; + QBroadPhase(float cellSize); + + struct bodyPairHash { + size_t operator()(const std::pair& p) const { + std::size_t h1 = std::hash{}(p.first); + std::size_t h2 = std::hash{}(p.second); + return h1 ^ (h2 + 0x9e3779b9 + (h1 << 6) + (h1 >> 2)); } }; - struct pairEqual { - bool operator()(const std::pair &p1, const std::pair &p2) const { - return ( (p1.first == p2.first && p1.second == p2.second) ) ; - + struct bodyPairEqual { + bool operator()(const std::pair& p1, const std::pair& p2) const { + return (p1.first == p2.first && p1.second == p2.second) || + (p1.first == p2.second && p1.second == p2.first); } }; - void RemoveBodyFromCells(QAABB referenceAABB,QBody *body); - - bool isBodyAdded=false; -public: - QVector cellSize; - - unordered_map> collisionGroups; - QBroadPhase(QVector cellSize): cellSize(cellSize) { - inverseCellSize=QVector(1/cellSize.x,1/cellSize.y); - }; - ~QBroadPhase(); + + void insert(QBody *body, const QAABB& aabb); + void update(QBody *body, const QAABB& newAABB); + void remove(QBody *body, const QAABB& aabb); - QBroadPhase* Add(QBody *body); + vector > GetBodiesFromCells(); - QBroadPhase* Clear(); + void GetAllPairs(unordered_set,QBroadPhase::bodyPairHash,bodyPairEqual > &pairs); - std::unordered_set,pairHash,pairEqual> pairList; + void ApplySweepAndPruneToCells(); - std::unordered_set,pairHash,pairEqual>* GetPairs(); + void GetPotentialCollisions(QBody *body,unordered_set &collection); + - //Methods +private: + float cellSize; + std::unordered_map> hashTable; + std::vector getCellKeys(QAABB aabb); + + + }; + #endif // QBROADPHASE_H diff --git a/QuarkPhysics/qcollision.cpp b/QuarkPhysics/qcollision.cpp index e8af023..97e6ec2 100644 --- a/QuarkPhysics/qcollision.cpp +++ b/QuarkPhysics/qcollision.cpp @@ -36,84 +36,6 @@ -void QCollision::PolylineAndCircle(vector &polylineParticles, vector &circleParticles, vector &contacts) -{ - CircleAndCircle(polylineParticles,circleParticles,contacts); - - /* - A. Start the loop for all segments of polyline - B. Check the perpandicular distance between the circle position and the segment position - C. Check If the circle object is in the segment range. - D. If the segment distance of the circle position is smaller than the radius and the circleObj position is in the range of the segment, apply collision. - */ - - - for(int n=0;nGetGlobalPosition(); - QVector s2Pos=s2->GetGlobalPosition(); - - - QVector segVec=(s2Pos-s1Pos); - QVector unit=segVec.Normalized(); - QVector normal=unit.Perpendicular(); - - if(s1->GetRadius()>0.5 || s2->GetRadius()>0.5){ - if(s1->GetRadius()>0.5){ - s1Pos+=s1->GetRadius()*normal; - } - if(s2->GetRadius()>0.5){ - s2Pos+=s2->GetRadius()*normal; - } - segVec=s2Pos-s1Pos; - unit=segVec.Normalized(); - normal=unit.Perpendicular(); - } - - float len=segVec.Length(); - - - - QVector bridgeVec=cp->GetGlobalPosition()-s1Pos; - - QVector testVirtualSegment[2]; - - testVirtualSegment[0]=s1Pos; - testVirtualSegment[1]=s2Pos; - - - QVector testBridgeVec=cp->GetGlobalPosition()-testVirtualSegment[0]; - - float perpProj=testBridgeVec.Dot(normal); - - if(abs(perpProj)GetRadius()){ - float proj=bridgeVec.Dot(unit); - if(proj>=0 && proj<=len){ - int projSign=perpProj<0 ? -1:1; - float penetration=abs( (cp->GetRadius()*projSign)-perpProj); - QVector contactPosition=cp->GetGlobalPosition()-(cp->GetRadius()*projSign*normal); - - QCollision::Contact contact(cp,contactPosition,normal,penetration,vector{s1,s2}); - - contacts.push_back(contact); - - } - } - - - - } - } - - -} - - void QCollision::PolylineAndPolygon(vector &polylineParticles, vector &polygonParticles, vector &contacts) { @@ -141,33 +63,53 @@ void QCollision::PolylineAndPolygon(vector &polylineParticles, vecto QParticle *p=polygonParticles[i]; QParticle *np=polygonParticles[ni]; + QVector fromPrev=p->GetGlobalPosition()-pp->GetGlobalPosition(); + QVector toNext=np->GetGlobalPosition()-p->GetGlobalPosition(); + QVector prevToNextVec=np->GetGlobalPosition()-pp->GetGlobalPosition(); QVector bridgeVec=p->GetGlobalPosition()-pp->GetGlobalPosition(); QVector prevToNextPerpVec=prevToNextVec.Perpendicular(); + QVector centerPos=(pp->GetGlobalPosition()+np->GetGlobalPosition() )*0.5f; - //We don't want to check from concave corners - /*if(bridgeVec.Dot(prevToNextPerpVec)<0){ - bisectorList.push_back(QVector::Zero() ); - continue; - }*/ + QVector toCenterPos=centerPos-p->GetGlobalPosition(); + + + + QVector bisectorUnit=prevToNextPerpVec.Normalized(); - QVector bisectorRay=-bisectorUnit*QWorld::MAX_WORLD_SIZE; + + if(fromPrev.Dot(prevToNextPerpVec)<0.0f ){ + if( toCenterPos.Dot(bisectorUnit)<0.0f ){ + bisectorUnit*=-1; + } + }else{ + if( toCenterPos.Dot(bisectorUnit)>0.0f ){ + bisectorUnit*=-1; + } + + } + + QVector bisectorRay=-bisectorUnit*QWorld::MAX_WORLD_SIZE; + + int sia=ni; // segment index a QVector bisectorVector=QVector::Zero(); if(polylineParticles==polygonParticles){ + float rayLength=abs(bridgeVec.Dot(prevToNextPerpVec.Normalized() ) )*0.5f; bisectorVector=-bisectorUnit*rayLength; }else{ float minDistance=QWorld::MAX_WORLD_SIZE; + bool findedIntersection=false; while(sia!=pi){ int sib=(sia+1)%polygonParticles.size(); //segment index b QVector intersectionPoint=LineIntersectionLine(p->GetGlobalPosition(),p->GetGlobalPosition()+bisectorRay,polygonParticles[sia]->GetGlobalPosition(),polygonParticles[sib]->GetGlobalPosition()); @@ -178,6 +120,7 @@ void QCollision::PolylineAndPolygon(vector &polylineParticles, vecto bisectorVector=findedVec*0.5f; minDistance=distance; } + findedIntersection=true; } sia=sib; } @@ -185,8 +128,10 @@ void QCollision::PolylineAndPolygon(vector &polylineParticles, vecto + + bisectorList.push_back(bisectorVector ); - //p->GetOwnerMesh()->GetOwnerBody()->GetWorld()->gizmos.push_back(new QGizmoLine(p->GetGlobalPosition(),p->GetGlobalPosition()+bisectorVector,true) ); + p->GetOwnerMesh()->GetOwnerBody()->GetWorld()->gizmos.push_back(new QGizmoLine(p->GetGlobalPosition(),p->GetGlobalPosition()+bisectorVector,true) ); /* Notes: Bisector vector is working well now but it doesn't looks true. A different method is followed here. @@ -200,6 +145,8 @@ void QCollision::PolylineAndPolygon(vector &polylineParticles, vecto for(int i=0;i &polylineParticles, vecto } +void QCollision::CircleAndPolyline(vector &circleParticles, vector &polylineParticles, vector &contacts) +{ + + /* The algorithm detects collisions between one or more particles and a polyline. + It utilizes point-polygon tests, ray casting, and circle-line collision tests if conditions require. + + A. A loop is performed for each circle particle and subjected to testing. + B. If the circular particle belongs to a collection with 3 or more elements, a ray vector is prepared towards the angle bisector using its edges. + C. Collision tests are applied between the circle particle and the nearest polyline particle to find the colliding edge. + C-1. If the circle particle belongs to a collection with 3 or more elements, a ray intersection test is applied from the particle to the two edges + of the polyline to find the reference surface. + C-2. If the circle particle belongs to a collection with fewer than 3 points, the closest edge is determined using a vertical projection. + D. If the circle particle is not within the polyline and the radius value is greater than 0.5, a collision edge is queried with a distance + test between the circle diameter and the edges of the nearest particle. + */ + + + + //A. A loop is performed for each circle particle and subjected to testing. + for (size_t ia=0;ia > nearestSides; + + int collidedSideIndex=-1; + + + //Checking whether the cirlce particle is in the polyline. + if (PointInPolygonWN(pA->GetGlobalPosition(),polylineParticles) && circleParticles!=polylineParticles) { + + //B. If the circular particle belongs to a collection with 3 or more elements, a ray vector is prepared towards the angle bisector using its edges. + QVector rayEndPoint=QVector::Zero(); + QVector rayUnit; + if(circleParticles.size()>=3){ + QParticle *prevParticle=circleParticles[ (ia-1+circleParticles.size() )%circleParticles.size() ]; + QParticle *nextParticle=circleParticles[ (ia+1 )%circleParticles.size() ]; + rayUnit=(nextParticle->GetGlobalPosition()-prevParticle->GetGlobalPosition() ).Normalized().Perpendicular(); + rayEndPoint=pA->GetGlobalPosition()-rayUnit*QWorld::MAX_WORLD_SIZE; + + } + + //C. Collision tests are applied between the circle particle and the nearest polyline particle to find the colliding edge. + + int ni=FindNearestParticleOfPolygon(pA,polylineParticles); + QParticle *pB=polylineParticles[ni]; + + + + nearestSides.push_back(vector{ polylineParticles[ (ni-1+polylineParticles.size() )%polylineParticles.size() ], pB} ); + nearestSides.push_back(vector{ pB, polylineParticles[ (ni+1)%polylineParticles.size() ]} ); + + bool isCollided=true; + + float penetration=0; + + QVector normal; + + float minDistance=-QWorld::MAX_WORLD_SIZE; + + for( int n=0;nGetGlobalPosition()-sA->GetGlobalPosition(); + QVector sideNormal=sideVec.Normalized().Perpendicular(); + + QVector sAPos=sA->GetGlobalPosition(); + QVector sBPos=sB->GetGlobalPosition(); + + + if(sA->GetRadius()+sB->GetRadius()>1.0 ){ + if(sA->GetRadius()>0.5){ + sAPos+=sA->GetRadius()*sideNormal; + } + if(sB->GetRadius()>0.5){ + sBPos+=sB->GetRadius()*sideNormal; + } + + sideVec=sBPos-sAPos; + sideNormal=sideVec.Normalized().Perpendicular(); + } + + if (circleParticles.size()>=3 ){ + /*C-1. If the circle particle belongs to a collection with 3 or more elements, a ray intersection test is applied from the particle to the two edges + of the polyline to find the reference surface.*/ + + QVector intersection=LineIntersectionLine(rayEndPoint,pA->GetGlobalPosition(),sAPos,sBPos); + + if(intersection.isNaN()==false ){ + float radius=pA->GetRadius(); + QVector bridgeVec=pA->GetGlobalPosition()-sAPos; + float dist=bridgeVec.Dot( sideNormal ); + minDistance=dist; + normal=sideNormal; + penetration=dist-radius; + collidedSideIndex=n; + }else{ + /* pA->GetOwnerMesh()->GetOwnerBody()->GetWorld()->gizmos.push_back(new QGizmoLine(pA->GetGlobalPosition(),pA->GetGlobalPosition()-rayUnit*64,true ) ); + pA->GetOwnerMesh()->GetOwnerBody()->GetWorld()->gizmos.push_back(new QGizmoLine(sAPos,sBPos,false ) ); */ + } + }else{ + //C-2. If the circle particle belongs to a collection with fewer than 3 points, the closest edge is determined using a vertical projection. + + QVector bridgeVec=pA->GetGlobalPosition()-sAPos; + + float dist=bridgeVec.Dot( sideNormal ); + + float radius=pA->GetRadius(); + if (dist>minDistance && dist &particlesA,vector &particlesB,vector &contacts){ + + + + } + + + if (collidedSideIndex==-1){ + continue; + } + + + + //pA->GetOwnerMesh()->GetOwnerBody()->GetWorld()->gizmos.push_back(new QGizmoCircle(pA->GetGlobalPosition(),5.0f ) ); + + QCollision::Contact contact(pA,pA->GetGlobalPosition(),normal,-penetration,vector{nearestSides[collidedSideIndex][0],nearestSides[collidedSideIndex][1]}); + contacts.push_back(contact); + + + }else{ + /* D. If the circle particle is not within the polyline and the radius value is greater than 0.5, a collision edge is queried with a distance + test between the circle diameter and the edges of the nearest particle. */ + if(pA->GetRadius()>0.5){ + + vector circles={pA}; + + + + int ni=FindNearestParticleOfPolygon(pA,polylineParticles); + QParticle *pB=polylineParticles[ni]; + + + + + nearestSides.push_back(vector{ polylineParticles[ (ni-1+polylineParticles.size() )%polylineParticles.size() ], pB} ); + nearestSides.push_back(vector{ pB, polylineParticles[ (ni+1)%polylineParticles.size() ]} ); + + for(size_t is=0;isGetGlobalPosition(); + QVector s2Pos=s2->GetGlobalPosition(); + + + QVector segVec=(s2Pos-s1Pos); + QVector unit=segVec.Normalized(); + QVector normal=unit.Perpendicular(); + + if(s1->GetRadius()>0.5 || s2->GetRadius()>0.5){ + if(s1->GetRadius()>0.5){ + s1Pos+=s1->GetRadius()*normal; + } + if(s2->GetRadius()>0.5){ + s2Pos+=s2->GetRadius()*normal; + } + segVec=s2Pos-s1Pos; + unit=segVec.Normalized(); + normal=unit.Perpendicular(); + } + + float len=segVec.Length(); + + + + QVector bridgeVec=pA->GetGlobalPosition()-s1Pos; + + QVector testVirtualSegment[2]; + + testVirtualSegment[0]=s1Pos; + testVirtualSegment[1]=s2Pos; + + + QVector testBridgeVec=pA->GetGlobalPosition()-testVirtualSegment[0]; + + float perpProj=testBridgeVec.Dot(normal); + + if(abs(perpProj)GetRadius()){ + float proj=bridgeVec.Dot(unit); + if(proj>=0 && proj<=len){ + int projSign=perpProj<0 ? -1:1; + float penetration=abs( (pA->GetRadius()*projSign)-perpProj); + QVector contactPosition=pA->GetGlobalPosition()-(pA->GetRadius()*projSign*normal); + + QCollision::Contact contact(pA,contactPosition,normal,penetration,vector{s1,s2}); + + contacts.push_back(contact); + + } + } + + } + + } + } + + + + + + } + +} + +void QCollision::CircleAndCircle(vector &particlesA,vector &particlesB,vector &contacts,float specifiedRadius){ /* A. Start the loop for all points of particlesA @@ -288,6 +456,11 @@ void QCollision::CircleAndCircle(vector &particlesA,vectorGetRadius(); float radiusB=pB->GetRadius(); + if(specifiedRadius!=0.0){ + radiusA=specifiedRadius; + radiusB=specifiedRadius; + } + //D. If the distance is less than radius of these points, create a new collision data if(positionalPenetration QCollision::FindNearestSideOfPolygon(const QVector point, vector polygonParticles,bool checkSideRange) + { + + + pair res(-1,-1); + + //A. Get a nearest points of polygonParticles + int polygonSize=polygonParticles.size(); + // A. Get nearest point of polygonObject + float minDistance=-QWorld::MAX_WORLD_SIZE; + + + + for(int pi=0; piGetGlobalPosition(); + QVector sideVec=(np->GetGlobalPosition()-p->GetGlobalPosition()); + QVector sidePerp=sideVec.Perpendicular(); + + if(checkSideRange){ + QVector sideUnit=sideVec.Normalized(); + float proj=bridgeVec.Dot( sideUnit ); + if( proj<0 || proj>sideVec.Length() ){ + continue; + } + } + + float dist=bridgeVec.Dot( sidePerp ); + + + + if(dist>minDistance && dist <0){ + res.first=pi; + res.second=npi; + minDistance=dist; + } + } + + + return res; + } + + int QCollision::FindNearestParticleOfPolygon(QParticle* particle, vector polygonParticles) + { + int res=0; + float minDistance=QWorld::MAX_WORLD_SIZE; + + for(size_t i=0;iGetGlobalPosition()-p->GetGlobalPosition()).Length(); + if(dist polygonParticles) + { + //Winding number algorithm for the point in polygon operations + const QVector ray=QVector(QWorld::MAX_WORLD_SIZE,0.0f); + const QVector rayPerp=QVector::Down(); + + int windingNumber=0; + + size_t polygonSize=polygonParticles.size(); + + for (size_t i=0;iGetGlobalPosition(); + QVector s2; + if(i+1==polygonSize){ + s2=polygonParticles[0]->GetGlobalPosition(); + }else{ + s2=polygonParticles[ i+1 ]->GetGlobalPosition(); + } + //Broadphase: Checking whether the point is in the range of y positions of the side + if( point.y<=s1.y != point.y<=s2.y ){ + QVector sideVec=s2-s1; + QVector sideVecPerp=sideVec.Perpendicular(); + QVector s1ToPoint=s1-point; + + float t=s1ToPoint.Dot( sideVecPerp )/ray.Dot(sideVecPerp ); + float u=(-s1ToPoint).Dot(rayPerp )/ sideVec.Dot(rayPerp); + + //Checking intersection between the ray and the side vector + if( (t>=0.0 && t<=1.0) && (u>=0.0 && t<=1.0) ){ + + if(sideVec.y<0 ) + windingNumber-=1; + else + windingNumber+=1; + } + + } + } + + return windingNumber!=0; + } + QVector QCollision::LineIntersectionLine(QVector d1A, QVector d1B, QVector d2A, QVector d2B) { QVector v1=d1B-d1A; @@ -638,23 +917,27 @@ void QCollision::ClipContactParticles(QParticle *referenceParticles[], QParticle QVector v2=d2B-d2A; - QVector v1Normal=v1.Perpendicular(); - QVector v2Normal=v2.Perpendicular(); QVector vb1=d2A-d1A; float t= vb1.Dot( v2Normal ) / v1.Dot( v2Normal) ; + if(t<0 || t>1 || t==0){ + return QVector::NaN(); + } + + QVector v1Normal=v1.Perpendicular(); + float u= -vb1.Dot(v1Normal) / v2.Dot (v1Normal); - if(t<0 || t>1 || u<0 || u>1 || t==0){ + if(u<0 || u>1){ return QVector::NaN(); } - QVector result2=d1A+t*v1; + QVector result=d1A+t*v1; - return result2; + return result; } bool QCollision::PointInPolygon(QVector &point, vector &polygon) @@ -692,8 +975,8 @@ void QCollision::ClipContactParticles(QParticle *referenceParticles[], QParticle bool c = false; for(i = 0, j = nvert - 1; i < nvert; j = i++) { - if( ( (polygon[i]->GetGlobalPosition().y >= point.y ) != (polygon[j]->GetGlobalPosition().y >= point.y) ) && - (point.x <= (polygon[j]->GetGlobalPosition().x - polygon[i]->GetGlobalPosition().x) * (point.y - polygon[i]->GetGlobalPosition().y) / (polygon[j]->GetGlobalPosition().y - polygon[i]->GetGlobalPosition().y) + polygon[i]->GetGlobalPosition().x) + if( ( (point.y < polygon[i]->GetGlobalPosition().y ) != ( point.y< polygon[j]->GetGlobalPosition().y) ) && + (point.x < (polygon[j]->GetGlobalPosition().x - polygon[i]->GetGlobalPosition().x) * (point.y - polygon[i]->GetGlobalPosition().y) / (polygon[j]->GetGlobalPosition().y - polygon[i]->GetGlobalPosition().y) + polygon[i]->GetGlobalPosition().x) ) c = !c; } diff --git a/QuarkPhysics/qcollision.h b/QuarkPhysics/qcollision.h index 46c75f4..9e6c205 100644 --- a/QuarkPhysics/qcollision.h +++ b/QuarkPhysics/qcollision.h @@ -115,24 +115,22 @@ class QCollision * @param particlesB Another collection of particles representing one or more circles, each having a radius. * @param contacts A collection where collision contact information will be stored. */ - static void CircleAndCircle(vector &particlesA,vector &particlesB,vector &contacts); + static void CircleAndCircle(vector &particlesA,vector &particlesB,vector &contacts, float specifiedRadius=0.0f); /** Checks collisions between polyline and polygon. - * @param particlesA A collection of particles that make up a polyline. - * @param particlesB Another collection of particles that make up a polygon. + * @param polylineParticles A collection of particles that make up a polyline. + * @param polygonParticles Another collection of particles that make up a polygon. * @param contacts A collection where collision contact information will be stored. * \note To check collisions between two polylines, call this method twice with the polylines swapped as arguments. * \note "Polyline" is commonly used to define polygons for softbody objects. In contrast to a solid and filled polygon , you can think of a polyline as a polygon shape formed by a connected and knotted rope. If a polygon-shaped rope is deformed, it may not always remain a polygon, it can overlap and its corners can merge. Collision testing for a polyline is performed based on these assumptions */ static void PolylineAndPolygon(vector &polylineParticles,vector &polygonParticles,vector &contacts); - /** Checks collisions between polyline and circle(s). - * @param polylineParticles A collection of particles that make up a polyline. + + /** Checks collisions between circles and polyline. * @param circleParticles A collection of particles representing one or more circles, each having a radius. + * @param polylineParticles A collection of particles that make up a polyline. * @param contacts A collection where collision contact information will be stored. - * \note "Polyline" is commonly used to define polygons for softbody objects. In contrast to a solid and filled polygon , you can think of a polyline as a polygon shape formed by a connected and knotted rope. If a polygon-shaped rope is deformed, it may not always remain a polygon, it can overlap and its corners can merge. Collision testing for a polyline is performed based on these assumptions */ - static void PolylineAndCircle(vector &polylineParticles,vector &circleParticles,vector &contacts); - - static void PolyLineSelfCollision(vector polylineParticles,vector &contacts); + static void CircleAndPolyline(vector &circleParticles,vector &polylineParticles,vector &contacts); //Geometry Helper Methods @@ -156,7 +154,7 @@ class QCollision * @return Returns true if the point is inside the polygon, false otherwise. */ static bool PointInPolygon(QVector &point, vector &polygon ); - /** Checks whether a specified point is inside the specified polygon. + /** Checks whether a specified point is inside the specified particle polygon. * @param point A point to check. * @param polygon A collection of particles that make up a polygon. * @return Returns true if the point is inside the polygon, false otherwise. @@ -167,6 +165,9 @@ class QCollision //Collision Helper Methods static void ClipContactParticles(QParticle *referenceParticles[], QParticle *incidentParticles[], vector &contacts ); static Project ProjectToAxis(QVector &normal,vector &polygon); + static pair FindNearestSideOfPolygon(const QVector point, vector polygonParticles,bool checkSideRange=false); + static int FindNearestParticleOfPolygon(QParticle * particle, vector polygonParticles); + static bool PointInPolygonWN(const QVector point, vector polygonParticles); diff --git a/QuarkPhysics/qgizmos.h b/QuarkPhysics/qgizmos.h index 88f21b4..a8a50d8 100644 --- a/QuarkPhysics/qgizmos.h +++ b/QuarkPhysics/qgizmos.h @@ -28,13 +28,15 @@ #ifndef QGIZMOS_H #define QGIZMOS_H #include "qvector.h" +#include "qaabb.h" class QGizmo { public : enum GizmoTypes{ Circle, - Line + Line, + Rectangle }; protected: GizmoTypes gizmoType=GizmoTypes::Circle; @@ -73,6 +75,16 @@ class QGizmoLine:public QGizmo{ } }; +class QGizmoRect:public QGizmo{ + public: + QAABB rect; + + QGizmoRect( QAABB rectangle ){ + rect=rectangle; + gizmoType=GizmoTypes::Rectangle; + } +}; + #endif // QGIZMOS_H diff --git a/QuarkPhysics/qmesh.cpp b/QuarkPhysics/qmesh.cpp index 2e9292a..2391d12 100644 --- a/QuarkPhysics/qmesh.cpp +++ b/QuarkPhysics/qmesh.cpp @@ -49,7 +49,9 @@ void QMesh::UpdateCollisionBehavior() { if(ownerBody==nullptr) return; - if(closedPolygons.size()>0){ + + + if(GetPolygonParticleCount()>0){ if(ownerBody->simulationModel==QBody::SimulationModels::RIGID_BODY){ collisionBehavior=CollisionBehaviors::POLYGONS; }else{ @@ -63,8 +65,6 @@ void QMesh::UpdateCollisionBehavior() - - QMesh *QMesh::AddParticle(QParticle *particle){ particles.push_back(particle); particles.back()->SetOwnerMesh(this); @@ -113,9 +113,265 @@ QParticle *QMesh::GetParticleAt(int index){ return particles[index]; } +QMesh *QMesh::SetPolygon(vector polygonParticles) +{ + polygon=polygonParticles; + + subConvexPolygonsNeedsUpdate=true; + + collisionBehaviorNeedsUpdate=true; + + return this; +} + +QMesh *QMesh::AddParticleToPolygon(QParticle *particle, int position) +{ + if (position==-1) { + polygon.push_back(particle); + }else{ + polygon.insert(polygon.begin()+position,particle); + } + subConvexPolygonsNeedsUpdate=true; + + collisionBehaviorNeedsUpdate=true; + + return this; +} + +QMesh *QMesh::RemoveParticleFromPolygon(QParticle *particle) +{ + int index=-1; + for (int i=0;i polygonParticles) +{ + int polygonParticleCount=GetPolygonParticleCount(); + for (size_t i=0;iGetGlobalPosition(); + QVector p2=GetParticleFromPolygon( i )->GetGlobalPosition(); + QVector p3=GetParticleFromPolygon( (i+1)%polygonParticleCount )->GetGlobalPosition(); + if (CheckIsReflex(p1,p2,p3) ) + return true; + } + return false; +} + +bool QMesh::CheckIsReflex(QVector pA, QVector pB, QVector pC) +{ + if ( (pB-pA).Dot( (pC-pA).Perpendicular() )<-1.0f ) + return true; + return false; +} + +bool QMesh::CheckIsReflex(int indexA, int indexB, int indexC, vector polygonParticles) +{ + return CheckIsReflex( polygonParticles[indexA]->GetGlobalPosition(),polygonParticles[indexB]->GetGlobalPosition(),polygonParticles[indexC]->GetGlobalPosition() ); +} + +void QMesh::TriangulatePolygon(vector &polygonParticles, vector> &triangles) +{ + vector indexList; + for (int i=0;i3 ){ + + for (int i=0;iGetPosition(); // previous particle position + QVector cp=polygonParticles[ci]->GetPosition(); // current particle position + QVector np=polygonParticles[ni]->GetPosition(); // next particle position + + + //Checking whether the vertice is ear + if ( CheckIsReflex(pp,cp,np) ) + continue; + + QVector cp2npPerp=(np-cp).Perpendicular(); + QVector np2ppPerp=(pp-np).Perpendicular(); + QVector pp2cpPerp=(cp-pp).Perpendicular(); + + // Checking other vertices are in the triangle + bool isThereAVertice=false; + for (int n=0;nGetPosition(); // test particle position + + float d1=(tp-cp).Dot( cp2npPerp ); + float d2=(tp-np).Dot( np2ppPerp ); + float d3=(tp-pp).Dot( pp2cpPerp ); + + if ( (d1>0 && d2>0 && d3>0 ) || (d1<0 && d2<0 && d3<0) ){ + isThereAVertice=true; + break; + } + + } + + //Adding a new triangle and removing ear from the polygon + if (isThereAVertice==false){ + vector triangle={pi,ci,ni}; + triangles.push_back ( triangle); + indexList.erase(indexList.begin()+i); + break; + } + + } + } + + //Adding the final three points as a triangle to the triangles + vector lastTriangle; + for (int i=0;i &polygonParticles, vector> &polygons) +{ + + vector> subPolygons; + TriangulatePolygon(polygonParticles,subPolygons); + + + + int ia=0; + + //Per all subPolygons + while (ia!=subPolygons.size()){ + cout<<"started subpolygons loop ia is:"< poly; + for(size_t n=0;n polygon) { - closedPolygons.push_back(polygon); + subConvexPolygons.push_back(polygon); if(ownerBody!=nullptr){ if (ownerBody->mode==QBody::Modes::STATIC) ownerBody->UpdateMeshTransforms(); @@ -128,7 +384,7 @@ QMesh *QMesh::AddClosedPolygon(vector polygon) QMesh *QMesh::RemoveClosedPolygonAt(int index) { - closedPolygons.erase(closedPolygons.begin()+index); + subConvexPolygons.erase(subConvexPolygons.begin()+index); if(ownerBody!=nullptr){ if (ownerBody->mode==QBody::Modes::STATIC) ownerBody->UpdateMeshTransforms(); @@ -143,8 +399,8 @@ QMesh *QMesh::RemoveClosedPolygonAt(int index) QMesh *QMesh::RemoveMatchingClosedPolygons(QParticle *particle) { int i=0; - while(i &polygon=closedPolygons[i]; + while(i &polygon=subConvexPolygons[i]; bool matched=false; int n=0; while(nAddParticle( particle ); } - //Adding closed polygons + //Adding polygon + vector polygonFromData; if(enablePolygons){ - for(int i=0;i polygonIndexes=data.closedPolygonList[i]; - vector polygon; - for(int n=0;nparticles[ polygonIndexes[n] ] ); - } - res->closedPolygons.push_back(polygon); + for(int i=0;iparticles[ data.polygon[i] ] ); } } + if (polygonFromData.size()>0 ) + res->SetPolygon(polygonFromData); + //Adding springs @@ -309,10 +566,13 @@ vector QMesh::GetMeshDatasFromJsonData(std::string &jsonBasedDa for (auto spring:internalSprings){ meshData.internalSpringList.push_back(pair(spring[0],spring[1]) ); } - auto polygons=mesh["polygons"]; - for (auto polygon:polygons){ - meshData.closedPolygonList.push_back(polygon); + + vector polygonParticleIndexes=(vector)mesh["polygon"]; + if (polygonParticleIndexes.size()>0) { + meshData.polygon=polygonParticleIndexes; } + + meshData.position=QVector(mesh["position"][0],mesh["position"][1] ); meshData.rotation=mesh["rotation"]; //Convert degrees to radians @@ -370,11 +630,10 @@ QMesh::MeshData QMesh::GenerateRectangleMeshData(QVector size,QVector centerPosi res.particleRadValues={particleRadius,particleRadius,particleRadius,particleRadius}; res.particleInternalValues={false,false,false,false}; - vector polygon; + for(int i=0;i(0,1)); @@ -470,12 +729,11 @@ QMesh::MeshData QMesh::GenerateRectangleMeshData(QVector size,QVector centerPosi res.springList=orderedSprings; } - //Creating a closed polygon with boundary springs + //Creating a polygon with boundary springs vector polygon; for(int i=0;i(i,(i+1)%sideCount) ); } - res.closedPolygonList.push_back(polygon); + if(polarGrid<0) return res; diff --git a/QuarkPhysics/qmesh.h b/QuarkPhysics/qmesh.h index 12be8b2..2dd0ffd 100644 --- a/QuarkPhysics/qmesh.h +++ b/QuarkPhysics/qmesh.h @@ -60,7 +60,8 @@ struct QMesh float rotation=0.0f; float globalRotation=0.0f; vector springs=vector(); - vector> closedPolygons=vector>(); + vector polygon=vector(); + vector> subConvexPolygons=vector>(); float circumference=0.0f; QBody *ownerBody=nullptr; CollisionBehaviors collisionBehavior=CollisionBehaviors::CIRCLES; @@ -70,6 +71,15 @@ struct QMesh //Helper Methods void UpdateCollisionBehavior(); + //Polygon Methods + void UpdateSubConvexPolygons(); + bool CheckIsPolygonConcave(vector polygonParticles); + static bool CheckIsReflex(QVector pA,QVector pB, QVector pC); + static bool CheckIsReflex(int indexA,int indexB, int indexC, vector polygonParticles); + static void TriangulatePolygon(vector &polygonParticles,vector> &triangles); + static void DecompositePolygon(vector &polygonParticles,vector> &polygons); + bool subConvexPolygonsNeedsUpdate=false; + public: /** The data struct of the mesh. */ @@ -90,11 +100,11 @@ struct QMesh * */ vector> internalSpringList; /** The polygon collection containing the index collection of the polygons. - * Closed polygons are important to define polygon colliders of the mesh. + * Polygons are important to define polygon colliders of the mesh. * The particle orders should be clockwise. * The integer values define the indices of particles in the particlePositions collection. * */ - vector > closedPolygonList; + vector polygon; /** The position of the mesh */ QVector position=QVector::Zero(); @@ -108,6 +118,8 @@ struct QMesh friend class QBody; friend class QRigidBody; friend class QSoftBody; + friend class QRaycast; + friend class QCollision; /** Creates a mesh. */ QMesh(); @@ -134,8 +146,9 @@ struct QMesh /** Returns the total area of the mesh with local positions of particles */ float GetInitialArea(){ float res=0.0f; - for(auto poly:closedPolygons){ - res+=GetPolygonArea(poly,true); + for(size_t n=0;nGetRadius()>0.5f){ @@ -147,8 +160,8 @@ struct QMesh /** Returns the total polygon area of the mesh with local positions of particles */ float GetInitialPolygonsArea(){ float res=0.0f; - for(auto poly:closedPolygons){ - res+=GetPolygonArea(poly,true); + for(size_t n=0;nGetRadius()>0.5f){ @@ -169,8 +182,8 @@ struct QMesh /** Returns total polygon area of the mesh with global positions of particles */ float GetPolygonsArea(){ float res=0.0f; - for(auto poly:closedPolygons){ - res+=GetPolygonArea(poly); + for(size_t n=0;n polygonParticles); + + /** Adds a particle of the mesh to the polygon. If you want to add a particle to the end of the polygon, set the position value as -1. + * @param particle A pointer to a particle + * @param position A position index. Default value is -1, indicating the end of the polygon. + * @return QMesh* A pointer to mesh itself. + */ + + QMesh * AddParticleToPolygon(QParticle * particle, int position=-1); + + /** Removes a particle from to the polygon. + * @param particle A pointer to a particle + * @return QMesh* A pointer to mesh itself. + * @note A polygon requires at least 3 particles. Please check the number of particles in the polygon before removing any particle from it. + */ + + QMesh *RemoveParticleFromPolygon(QParticle * particle); + + /** Removes a particle from to the polygon at the specified index. + * @param index An index value + * @return QMesh* A pointer to mesh itself. + * @note A polygon requires at least 3 particles. Please check the number of particles in the polygon before removing any particle from it. + */ + + QMesh * RemoveParticleFromPolygonAt(int index); + + /** Removes the polygon from the mesh. + * @return QMesh* A pointer to mesh itself. + */ + + QMesh * RemovePolygon(); + + /** Returns the total particle count of the polygon */ + + int GetPolygonParticleCount(); + + /** Returns a particle of the polygon at the specified index + * @param index An index value + * @return QParticle* A pointer to the particle. + */ + + QParticle *GetParticleFromPolygon(int index); + + + + + + /** Adds a polygon to the mesh * @param polygon A particle pointers collection of the polygon. @@ -280,14 +350,22 @@ struct QMesh /** Returns the total polygon count in the mesh. */ - int GetClosedPolygonCount(){ - return closedPolygons.size(); + int GetSubConvexPolygonCount(){ + if (subConvexPolygonsNeedsUpdate==true){ + UpdateSubConvexPolygons(); + subConvexPolygonsNeedsUpdate=false; + } + return subConvexPolygons.size(); } /** Returns polygon at the specified index * @param index The index of the polygon to get. */ - vector &GetClosedPolygonAt(int index){ - return closedPolygons[index]; + vector &GetSubConvexPolygonAt(int index){ + if (subConvexPolygonsNeedsUpdate==true){ + UpdateSubConvexPolygons(); + subConvexPolygonsNeedsUpdate=false; + } + return subConvexPolygons[index]; } /** Removes the polygons that contain the specified particle. * @param particle A particle to be matched. diff --git a/QuarkPhysics/qraycast.cpp b/QuarkPhysics/qraycast.cpp index 7b9e9af..65a5fe3 100644 --- a/QuarkPhysics/qraycast.cpp +++ b/QuarkPhysics/qraycast.cpp @@ -217,29 +217,24 @@ void QRaycast::RaycastToPolygon(QBody *body, QMesh *mesh, QVector rayPosition, Q QVector nearContactNormal=QVector::Zero(); - for(int n=0;nGetClosedPolygonCount();n++){ - vector &polygon=mesh->GetClosedPolygonAt(n); - for(int i=0;ipolygon.size();i++){ - QParticle *p=polygon[i]; - QParticle *np=polygon[(i+1)%polygon.size() ]; + QParticle *p=mesh->polygon[i]; + QParticle *np=mesh->polygon[(i+1)%mesh->polygon.size() ]; - QVector intersection=QCollision::LineIntersectionLine(p->GetGlobalPosition(),np->GetGlobalPosition(),rayStart,rayEnd); - if(intersection.isNaN())continue; + QVector intersection=QCollision::LineIntersectionLine(p->GetGlobalPosition(),np->GetGlobalPosition(),rayStart,rayEnd); + if(intersection.isNaN())continue; - float distance=(intersection-rayStart).Length(); - if(distance>nearDistance)continue; + float distance=(intersection-rayStart).Length(); + if(distance>nearDistance)continue; - nearDistance=distance; - nearContactPosition=intersection; - nearContactNormal=(np->GetGlobalPosition()-p->GetGlobalPosition()).Normalized().Perpendicular(); - contactFound=true; - - - - - } + nearDistance=distance; + nearContactPosition=intersection; + nearContactNormal=(np->GetGlobalPosition()-p->GetGlobalPosition()).Normalized().Perpendicular(); + contactFound=true; } + if(contactFound==false)return; diff --git a/QuarkPhysics/qsoftbody.cpp b/QuarkPhysics/qsoftbody.cpp index c365f3e..dac96b2 100644 --- a/QuarkPhysics/qsoftbody.cpp +++ b/QuarkPhysics/qsoftbody.cpp @@ -68,6 +68,8 @@ void QSoftBody::Update() } } + + //Integrate Velocities for(int i=0;i<_meshes.size();i++){ QMesh *mesh=_meshes[i]; @@ -84,6 +86,8 @@ void QSoftBody::Update() } } + + if(enableAreaPreserving){ PreserveAreas(); @@ -95,7 +99,6 @@ void QSoftBody::Update() } */ - UpdateAABB(); @@ -103,6 +106,8 @@ void QSoftBody::Update() void QSoftBody::PreserveAreas() { + + //Time scale feature float ts=1.0f; @@ -132,26 +137,25 @@ void QSoftBody::PreserveAreas() float pressure=(deltaArea/circumference)*areaPreservingRigidity; - for(int i=0;iGetClosedPolygonCount();i++){ - vector &polygon=mesh->GetClosedPolygonAt(i); - QVector volumeForces[polygon.size()]; - for(int n=0;nGetSpringAt(i); - QParticle *pp=polygon[ (n-1+polygon.size())%polygon.size() ]; - QParticle *np=polygon[ (n+1)%polygon.size() ]; - QVector vec=np->GetGlobalPosition()-pp->GetGlobalPosition(); - volumeForces[n]=pressure*(vec.Perpendicular().Normalized())*ts; - } + + + QVector volumeForces[mesh->polygon.size()]; + for(int n=0;npolygon.size();n++){ + QParticle *pp=mesh->polygon[ (n-1+mesh->polygon.size())%mesh->polygon.size() ]; + QParticle *np=mesh->polygon[ (n+1)%mesh->polygon.size() ]; + QVector vec=np->GetGlobalPosition()-pp->GetGlobalPosition(); + volumeForces[n]=pressure*(vec.Perpendicular().Normalized())*ts; + } - for(int n=0;nGetGlobalPosition()+pp->GetGlobalPosition())*0.5f; - QParticle::ApplyForceToParticleSegment(pp,np,volumeForces[n],centerPos); - //GetWorld()->GetGizmos()->push_back( new QGizmoLine( centerPos,centerPos+volumeForces[n]*30,true ) ); + for(int n=0;npolygon.size();n++){ + QParticle *pp=mesh->polygon[ (n-1+mesh->polygon.size())%mesh->polygon.size() ]; + QParticle *np=mesh->polygon[ (n+1)%mesh->polygon.size() ]; + QVector centerPos=(np->GetGlobalPosition()+pp->GetGlobalPosition())*0.5f; + QParticle::ApplyForceToParticleSegment(pp,np,volumeForces[n],centerPos); + //GetWorld()->GetGizmos()->push_back( new QGizmoLine( centerPos,centerPos+volumeForces[n]*30,true ) ); - } } + } @@ -222,10 +226,7 @@ void QSoftBody::ApplyShapeMatching() vector particles; if(mesh->GetCollisionBehavior()==QMesh::CollisionBehaviors::POLYLINE ){ - for(int n=0;nclosedPolygons.size();n++ ){ - vector polygon=mesh->closedPolygons[n]; - particles.insert(particles.end(),polygon.begin(),polygon.end() ); - } + particles=mesh->polygon; }else{ particles=mesh->particles; } diff --git a/QuarkPhysics/qsoftbody.h b/QuarkPhysics/qsoftbody.h index 33650a3..06c9e42 100644 --- a/QuarkPhysics/qsoftbody.h +++ b/QuarkPhysics/qsoftbody.h @@ -29,7 +29,7 @@ #define QSOFTBODY_H #include "qbody.h" #include -/** @brief QSoftBody is a body type that defines deformable, soft objects in the physics world. Mass-spring model is used for simulation dynamics in soft bodies. In the mass-spring model, there are particles with mass that can move individually and interact with the physics world, and these particles can be connected to each other with spring constraints. Additionally, with some user-configurable options specific to the simulation, particles can be subjected to constraints obtained from some calculations. For example, you can add a constraint that ensures particles remain faithful to their initially defined local positions using the "shape matching" option. You can apply a constraint that gives the feeling that the closed polygons are filled with gas and maintains their area using the "area preserving" option. You can use options that allow particles to collide with each other with a specific radius, and create objects called PBD (Position Based Dynamics). QSoftBody objects inherently require a more flexible configuration than other body types and contain many options. +/** @brief QSoftBody is a body type that defines deformable, soft objects in the physics world. Mass-spring model is used for simulation dynamics in soft bodies. In the mass-spring model, there are particles with mass that can move individually and interact with the physics world, and these particles can be connected to each other with spring constraints. Additionally, with some user-configurable options specific to the simulation, particles can be subjected to constraints obtained from some calculations. For example, you can add a constraint that ensures particles remain faithful to their initially defined local positions using the "shape matching" option. You can apply a constraint that gives the feeling that the polygon are filled with gas and maintains their area using the "area preserving" option. You can use options that allow particles to collide with each other with a specific radius, and create objects called PBD (Position Based Dynamics). QSoftBody objects inherently require a more flexible configuration than other body types and contain many options. */ class QSoftBody : public QBody { @@ -47,7 +47,10 @@ class QSoftBody : public QBody float circumference=0.0f; bool enableAreaStability=false; bool enablePassivationOfInternalSprings=false; + + bool enableSelfCollisions=false; + float selfCollisionParticleRadius=0.0f; bool enableShapeMatching=false; float shapeMatchingRate=0.4f; @@ -100,7 +103,7 @@ class QSoftBody : public QBody areaPreservingRigidity=value; return this; } - /** Sets whether the area preserving option is enabled of the body. If the area preserving option is enabled, the total area of closed polygons are calculated at each physics step, and forces are applied to the particles that define the boundaries of the polygon to achieve a total area that can be determined by the user. If the user does not specify, when this option is enabled, the target area is calculated based on the original positions of the closed polygon particles, in other words, the total area of undeformed polygons are set as the target area. + /** Sets whether the area preserving option is enabled of the body. If the area preserving option is enabled, the total area of the polygon are calculated at each physics step, and forces are applied to the particles that define the boundaries of the polygon to achieve a total area that can be determined by the user. If the user does not specify, when this option is enabled, the target area is calculated based on the original positions of the polygon particles, in other words, the total area of undeformed polygons are set as the target area. * @param value A value to set. * @return A pointer to the body itself. */ @@ -129,6 +132,15 @@ class QSoftBody : public QBody return this; } + /** Sets a specified particle radius value for self particle collisions. If set to 0, particles will collide with their radius. Default value is 0. + * @param value A value to set. + * @return A pointer to the body itself. + */ + QSoftBody *SetSelfCollisionsSpecifiedRadius(float value){ + selfCollisionParticleRadius=value; + return this; + } + /** Sets whether to passivate the internal spring connections of the soft body. If this option is enabled, the internal springs are more passive in the simulation, which can be useful for soft bodies where the internal springs and particle connections only provide UV and other data based on the movement of the soft body. * @param value A value to set. * @return A pointer to the body itself. @@ -221,7 +233,7 @@ class QSoftBody : public QBody float GetAreaPreservingRigidity(){ return areaPreservingRigidity; }; - /** Returns whether the area preserving option is enabled of the body. If the area preserving option is enabled, the total area of closed polygons are calculated at each physics step, and forces are applied to the particles that define the boundaries of the polygon to achieve a total area that can be determined by the user. If the user does not specify, when this option is enabled, the target area is calculated based on the original positions of the closed polygon particles, in other words, the total area of undeformed polygons are set as the target area. */ + /** Returns whether the area preserving option is enabled of the body. If the area preserving option is enabled, the total area of the polygon are calculated at each physics step, and forces are applied to the particles that define the boundaries of the polygon to achieve a total area that can be determined by the user. If the user does not specify, when this option is enabled, the target area is calculated based on the original positions of the polygon particles, in other words, the total area of undeformed polygons are set as the target area. */ bool GetAreaPreservingEnabled(){ return enableAreaPreserving; } @@ -233,6 +245,10 @@ class QSoftBody : public QBody bool GetSelfCollisionsEnabled(){ return enableSelfCollisions; } + /** Returns the specified particle radius value for self particle collisions. If the value is 0.0, particles will collide with their radius. Default value is 0.0. */ + float GetSelfCollisionsSpecifiedRadius(){ + return selfCollisionParticleRadius; + } /** Returns whether to passivate the internal spring connections of the soft body. If this option is enabled, the internal springs are more passive in the simulation, which can be useful for soft bodies where the internal springs and particle connections only provide UV and other data based on the movement of the soft body. */ bool GetPassivationOfInternalSpringsEnabled(){ return enablePassivationOfInternalSprings; @@ -241,6 +257,7 @@ class QSoftBody : public QBody bool GetShapeMatchingEnabled(){ return enableShapeMatching; } + /** Returns the rate value to apply the shape matching to the body. */ float GetShapeMatchingRate(){ diff --git a/QuarkPhysics/qworld.cpp b/QuarkPhysics/qworld.cpp index e99787d..89338dd 100644 --- a/QuarkPhysics/qworld.cpp +++ b/QuarkPhysics/qworld.cpp @@ -40,7 +40,7 @@ using namespace std; QWorld::QWorld(){ - + broadPhase=QBroadPhase(spatialHashingSize); } @@ -88,9 +88,22 @@ void QWorld::Update(){ debugCollisionTestCount=0; //cout<,QBroadPhase::bodyPairHash,QBroadPhase::bodyPairEqual> pairs; + //Preparing Updated Broadphasevariables + if (enableBroadphase){ + if (enableSpatialHashing){ + for (auto body:bodies){ + broadPhase.update(body,body->GetAABB()); + } + broadPhase.GetAllPairs(pairs); + }else{ + sort(bodies.begin(),bodies.end(),SortBodies); + } + + } + + for(unsigned int n=0;nUpdateAABB(); } + + + manifolds.clear(); + + if(enableBroadphase){ -// for(unsigned int i=0;iUpdateAABB(); -// broadPhase.Add(bodies[i]); -// } -// auto collisionPairs=broadPhase.GetPairs(); -// for(auto pair=collisionPairs->begin();pair!=collisionPairs->end();pair++){ -// QBody *bodyA=pair->first; -// QBody *bodyB=pair->second; -// Collide(bodyA,bodyB); -// } + + if(enableSpatialHashing){ + for (auto pair: pairs){ + vector contacts=GetCollisions(pair.first,pair.second); + if(contacts.size()>0){ + QManifold manifold(pair.first,pair.second); + manifold.contacts=contacts; + manifolds.push_back(manifold); + } + } + + + }else{ + + size_t bodiesSize=bodies.size(); + for(unsigned int i=0;iGetEnabled()==false ) + continue; - if(body->GetEnabled()==false ) - continue; + bool seperated=false; + for(unsigned int q=i+1;qGetEnabled()==false ) + continue; - if(otherBody->GetEnabled()==false ) - continue; + if( QBody::CanCollide(body,otherBody)==false){ + continue; + } - if( QBody::CanCollide(body,otherBody)==false){ - continue; - } + debugAABBTestCount+=1; + if(body->GetAABB().GetMax().x >= otherBody->GetAABB().GetMin().x){ + if( body->GetAABB().GetMin().y <= otherBody->GetAABB().GetMax().y && + body->GetAABB().GetMax().y >= otherBody->GetAABB().GetMin().y) { + vector contacts=GetCollisions(body,otherBody); + if(contacts.size()>0){ + QManifold manifold(body,otherBody); + manifold.contacts=contacts; + manifolds.push_back(manifold); + } - if(body->GetAABB().GetMax().x >= otherBody->GetAABB().GetMin().x){ - if( body->GetAABB().GetMin().y <= otherBody->GetAABB().GetMax().y && - body->GetAABB().GetMax().y >= otherBody->GetAABB().GetMin().y) { - debugAABBTestCount+=1; - vector contacts=GetCollisions(body,otherBody); - if(contacts.size()>0){ - QManifold manifold(body,otherBody); - manifold.contacts=contacts; - manifolds.push_back(manifold); } - + }else{ + break; } - - }else{ - break; } - } + } + } + + + + }else{ //Brute Force Collision @@ -205,7 +231,7 @@ void QWorld::Update(){ QMesh *meshB=sBody->GetMeshAt(mb); vector contacts; //Self Particle Collisions - QCollision::CircleAndCircle(meshA->particles,meshB->particles,contacts); + QCollision::CircleAndCircle(meshA->particles,meshB->particles,contacts,sBody->GetSelfCollisionsSpecifiedRadius()); if(contacts.size()>0){ QManifold manifold(sBody,sBody); manifold.contacts=contacts; @@ -214,19 +240,17 @@ void QWorld::Update(){ contacts.clear(); //Polyline Collisions if(QMesh::CheckCollisionBehaviors(meshA,meshB,QMesh::CollisionBehaviors::POLYLINE,QMesh::CollisionBehaviors::POLYLINE)){ //Self Polyline Collisions - for (int pA=0;pAGetClosedPolygonCount();pA++){ - for(int pB=0;pBGetClosedPolygonCount();pB++){ - QCollision::PolylineAndPolygon(meshA->closedPolygons[pA],meshB->closedPolygons[pB],contacts); - } - + if(sBody->GetAreaPreservingEnabled()==true){ + //QCollision::CircleAndPolyline(meshA->polygon,meshB->polygon,contacts); + }else{ + QCollision::PolylineAndPolygon(meshA->polygon,meshB->polygon,contacts); } + }else if(QMesh::CheckCollisionBehaviors(meshA,meshB,QMesh::CollisionBehaviors::CIRCLES,QMesh::CollisionBehaviors::POLYLINE) ){ //Self Polyline-Particle Collisions QMesh * circleMesh=meshA->GetCollisionBehavior()==QMesh::CollisionBehaviors::CIRCLES ? meshA:meshB; QMesh * polylineMesh=circleMesh==meshA ? meshB:meshA; - for(int p=0;pGetClosedPolygonCount();p++){ - QCollision::PolylineAndCircle( polylineMesh->GetClosedPolygonAt(p),circleMesh->particles,contacts); - } + QCollision::CircleAndPolyline( polylineMesh->polygon,circleMesh->particles,contacts); } @@ -243,10 +267,13 @@ void QWorld::Update(){ } + + } + } @@ -272,8 +299,8 @@ void QWorld::Update(){ } - //cout<<"Total Broad Phase Test Count: "< QWorld::GetBodiesHitByPoint(QVector point, int maxBodyCount, boo } } if(mesh->collisionBehavior==QMesh::POLYGONS || mesh->collisionBehavior==QMesh::POLYLINE){ - for(int i=0;iGetClosedPolygonCount();i++){ - vector polygon=mesh->GetClosedPolygonAt(i); + for(int i=0;iGetSubConvexPolygonCount();i++){ + vector polygon=mesh->GetSubConvexPolygonAt(i); if( QCollision::PointInPolygon(point,polygon) ){ res.push_back(body); break; @@ -554,7 +581,7 @@ bool QWorld::CollideWithWorld(QBody *body){ if(manifoldList.size()==0) return false; - cout< QWorld::GetCollisions(QBody *bodyA, QBody *bodyB){ - for(int sa=0;sasize();++sa){ + for(int sa=0;sasize();sa++){ QMesh *meshA=meshesA->at(sa); - for(int sb=0;sbsize();++sb){ + for(int sb=0;sbsize();sb++){ QMesh *meshB=meshesB->at(sb); bodyA->GetWorld()->debugCollisionTestCount+=1; if(QMesh::CheckCollisionBehaviors(meshA,meshB,QMesh::POLYGONS, QMesh::POLYGONS )){ - for(int a=0;aclosedPolygons.size();a++){ - for(int b=0;bclosedPolygons.size();b++){ - QCollision::PolygonAndPolygon(meshA->closedPolygons[a],meshB->closedPolygons[b],contactList); + for(int a=0;aGetSubConvexPolygonCount();a++){ + for(int b=0;bGetSubConvexPolygonCount();b++){ + QCollision::PolygonAndPolygon(meshA->GetSubConvexPolygonAt(a),meshB->GetSubConvexPolygonAt(b),contactList); } } }else if(QMesh::CheckCollisionBehaviors(meshA,meshB,QMesh::CIRCLES, QMesh::POLYGONS )){ QMesh *circleMesh=meshA->collisionBehavior==QMesh::CIRCLES ? meshA:meshB; QMesh *polygonMesh=&circleMesh->particles==&meshA->particles ? meshB :meshA; - for(int j=0;jGetClosedPolygonCount();j++){ - QCollision::CircleAndPolygon(circleMesh->particles,polygonMesh->GetClosedPolygonAt(j),contactList); + for(int j=0;jGetSubConvexPolygonCount();j++){ + QCollision::CircleAndPolygon(circleMesh->particles,polygonMesh->GetSubConvexPolygonAt(j),contactList); } }else if(QMesh::CheckCollisionBehaviors(meshA,meshB,QMesh::CIRCLES, QMesh::CIRCLES )){ @@ -846,28 +873,35 @@ vector QWorld::GetCollisions(QBody *bodyA, QBody *bodyB){ }else if(QMesh::CheckCollisionBehaviors(meshA,meshB,QMesh::POLYLINE, QMesh::POLYGONS )){ QMesh *polylineMesh=meshA->collisionBehavior==QMesh::POLYLINE ? meshA:meshB; QMesh *polygonMesh=meshA->collisionBehavior==QMesh::POLYGONS ? meshA:meshB; - for(int a=0;aclosedPolygons.size();a++){ - for(int b=0;bclosedPolygons.size();b++){ - QCollision::CircleAndPolygon(polylineMesh->GetClosedPolygonAt(a),polygonMesh->GetClosedPolygonAt(b),contactList); - QCollision::PolylineAndPolygon(polylineMesh->GetClosedPolygonAt(a),polygonMesh->GetClosedPolygonAt(b),contactList); - } + for(int b=0;bGetSubConvexPolygonCount();b++){ + //QCollision::PolygonAndPolygon(polylineMesh->subConvexPolygons[a],polygonMesh->subConvexPolygons[b],contactList); + QCollision::CircleAndPolygon(polylineMesh->polygon,polygonMesh->GetSubConvexPolygonAt(b),contactList); + QCollision::PolylineAndPolygon(polylineMesh->polygon,polygonMesh->GetSubConvexPolygonAt(b),contactList); } - }else if(QMesh::CheckCollisionBehaviors(meshA,meshB,QMesh::POLYLINE, QMesh::POLYLINE )){ - for(int a=0;aclosedPolygons.size();a++){ - for(int b=0;bclosedPolygons.size();b++){ - QCollision::PolylineAndPolygon(meshA->GetClosedPolygonAt(a),meshB->GetClosedPolygonAt(b),contactList); - QCollision::PolylineAndPolygon(meshB->GetClosedPolygonAt(b),meshA->GetClosedPolygonAt(a),contactList); + + + if(bodyA->simulationModel!=QBody::SimulationModels::RIGID_BODY && bodyB->simulationModel!=QBody::SimulationModels::RIGID_BODY){ + QSoftBody *sBodyA=static_cast(bodyA); + QSoftBody *sBodyB=static_cast(bodyB); + if (sBodyA!=nullptr && sBodyB!=nullptr){ + if (sBodyA->GetAreaPreservingEnabled()==true && sBodyB->GetAreaPreservingEnabled()==true ){ + QCollision::CircleAndCircle(meshA->polygon,meshB->polygon,contactList); + QCollision::CircleAndPolyline(meshA->polygon,meshB->polygon,contactList); + QCollision::CircleAndPolyline(meshB->polygon,meshA->polygon,contactList); + }else{ + QCollision::PolylineAndPolygon(meshA->polygon,meshB->polygon,contactList); + QCollision::PolylineAndPolygon(meshB->polygon,meshA->polygon,contactList); + } } } - + }else if(QMesh::CheckCollisionBehaviors(meshA,meshB,QMesh::POLYLINE, QMesh::CIRCLES )){ QMesh *circleMesh=meshA->collisionBehavior==QMesh::CIRCLES ? meshA:meshB; QMesh *polylineMesh=meshA->collisionBehavior==QMesh::POLYLINE ? meshA:meshB; - for(int j=0;jclosedPolygons.size();j++){ - QCollision::PolylineAndCircle(polylineMesh->GetClosedPolygonAt(j),circleMesh->particles,contactList); - } + QCollision::CircleAndCircle(circleMesh->particles,polylineMesh->polygon,contactList); + QCollision::CircleAndPolyline(circleMesh->particles,polylineMesh->polygon,contactList); } @@ -903,7 +937,7 @@ void QWorld::CreateIslands(QBody *body, vector &bodyList, vectorGetAABB(); - if (body != otherBody && body->GetAABB().isCollidingWith(otherAABB) ) + if (body != otherBody && body->GetAABB().isCollidingWith(otherAABB) && QBody::CanCollide(body,otherBody) ) { // If there is a collision, visit to otherBody CreateIslands(otherBody,bodyList, island); diff --git a/QuarkPhysics/qworld.h b/QuarkPhysics/qworld.h index 33deb68..157dcd5 100644 --- a/QuarkPhysics/qworld.h +++ b/QuarkPhysics/qworld.h @@ -50,17 +50,20 @@ using namespace std; class QWorld{ struct bodyPairHash { - size_t operator()(const std::pair &p) const { - return std::hash()(p.first) + std::hash()(p.second); + size_t operator()(const std::pair& p) const { + std::size_t h1 = std::hash{}(p.first); + std::size_t h2 = std::hash{}(p.second); + return h1 ^ (h2 + 0x9e3779b9 + (h1 << 6) + (h1 >> 2)); } }; struct bodyPairEqual { - bool operator()(const std::pair &p1, const std::pair &p2) const { - return ( (p1.first == p2.first && p1.second == p2.second) ) ; - + bool operator()(const std::pair& p1, const std::pair& p2) const { + return (p1.first == p2.first && p1.second == p2.second) || + (p1.first == p2.second && p1.second == p2.first); } }; + protected: //Collections vector bodies=vector(); @@ -76,7 +79,7 @@ class QWorld{ vector > sleepingIslands=vector >(); unordered_set, bodyPairHash,bodyPairEqual> collisionExceptions; - QBroadPhase broadPhase=QBroadPhase(QVector(128.0f,128.0f)); + QBroadPhase broadPhase; vector manifolds; @@ -89,6 +92,8 @@ class QWorld{ bool enableBroadphase=true; int iteration=4; float timeScale=1.0f; + bool enableSpatialHashing=true; + float spatialHashingSize=256.0f; //Sleeping @@ -128,16 +133,39 @@ class QWorld{ QVector GetGravity(){ return gravity; } + + /** Returns whether sleep mode will be applied to dynamic objects. */ - bool GetEnableSleeping(){ + bool GetSleepingEnabled(){ return enableSleeping; } + /** Returns the sleeping position tolerance. If the sleeping option is active, objects will go to sleep as long as they do not exceed this position change limit within a certain step amount. + */ + float GetSleepingPositionTolerance(){ + return sleepingPositionTolerance; + } + /** Returns the sleeping rotation tolerance. If the sleeping option is active, objects will go to sleep as long as they do not exceed this rotation change limit within a certain step amount. + */ + float GetSleepingRotationTolerance(){ + return sleepingRotationTolerance; + } /** Returns whether broad phase optimization will be applied to the collisions. */ - bool GetEnableBroadphase(){ + bool GetBroadphaseEnabled(){ return enableBroadphase; } + + /** Returns whether spatial hashing feature is enabled in the broad phase. The Spatial hashing operation can improves performance for big scenes. */ + bool GetSpatialHashingEnabled(){ + return enableSpatialHashing; + } + + /** Returns cell size value of the spatial hashing. */ + float GetSpatialHashingCellSize(){ + return spatialHashingSize; + } + /** Returns the iteration count per step of physics in the world. * The Iteration count determines the stability level of the simulation. */ @@ -165,18 +193,49 @@ class QWorld{ return this; } /** Sets whether sleep mode will be applied to dynamic objects. - * @param value True or false. + * @param value A value to set */ - QWorld *SetEnableSleeping(bool value){ + QWorld *SetSleepingEnabled(bool value){ enableSleeping=value; return this; } + /** Sets the sleeping position tolerance. If the sleeping option is active, objects will go to sleep as long as they do not exceed this position change limit within a certain step amount. + * @param value A value to set + */ + QWorld *SetSleepingPositionTolerance(float value){ + sleepingPositionTolerance=value; + return this; + } + /** Sets the sleeping rotation tolerance. If the sleeping option is active, objects will go to sleep as long as they do not exceed this rotation change limit within a certain step amount. + * @param value A value to set + */ + QWorld *SetSleepingRotationTolerance(float value){ + sleepingRotationTolerance=value; + return this; + } /** Sets whether broad phase optimization will be applied to the collisions. */ - QWorld *SetEnableBroadphase(bool value){ + QWorld *SetBroadphaseEnabled(bool value){ enableBroadphase=value; return this; } + + /** Sets whether spatial hashing feature is enabled in the broad phase. The Spatial hashing operation can improves performance for big scenes. + * @param value A value to set + */ + QWorld *SetSpatialHashingEnabled(bool value){ + enableSpatialHashing=value; + return this; + } + + /** Sets cell sizes for spatial hashing. + * @param value A value to set + */ + QWorld *SetSpatialHashingCellSize(float value){ + spatialHashingSize=value; + return this; + } + /** Sets the iteration count per step of physics in the world. * Iteration count determines stability level of the simulation. * @param value The number of iterations per step. @@ -202,6 +261,8 @@ class QWorld{ enabled=value; return this; } + + //Methods @@ -444,6 +505,8 @@ class QWorld{ friend class QCollision; friend class QManifold; + friend class QSoftBody; + friend class QBroadPhase; diff --git a/examples/examplescenebenchmarkboxes2.cpp b/examples/examplescenebenchmarkboxes2.cpp index c4b860f..5bd421d 100644 --- a/examples/examplescenebenchmarkboxes2.cpp +++ b/examples/examplescenebenchmarkboxes2.cpp @@ -32,7 +32,7 @@ ExampleSceneBenchmarkBoxes2::ExampleSceneBenchmarkBoxes2(QVector sceneSize):QExa world->SetGravity(QVector(0.0f,0.1f)); spawnRectSize=16.0f; spawnCircleRadius=8.0f; - world->SetEnableSleeping(true); + world->SetSleepingEnabled(false); //BOXES EXAMPLE QBody *floor=new QBody(); floor->AddMesh(QMesh::CreateWithRect(QVector(5000,64),QVector(0.0f,0.0f) ) )->SetPosition(QVector(512.0f,550.0f)); diff --git a/examples/examplesceneblobs.cpp b/examples/examplesceneblobs.cpp index e365d86..7da11c4 100644 --- a/examples/examplesceneblobs.cpp +++ b/examples/examplesceneblobs.cpp @@ -35,6 +35,8 @@ ExampleSceneBlobs::ExampleSceneBlobs(QVector sceneSize):QExampleScene(sceneSize) { //world->SetEnableSleeping(false); + world->SetIterationCount(10); + // Floor and walls QBody *floor=new QBody(); floor->AddMesh(QMesh::CreateWithRect(QVector(960,64),QVector(0.0f,0.0f) ) )->SetPosition(QVector(512.0f,550.0f)); @@ -58,9 +60,9 @@ void ExampleSceneBlobs::OnMousePressed(QVector mousePosition) //QExampleScene::OnMousePressed(mousePosition); //Adding pressure volume model soft body via gridded polygon mesh QSoftBody *griddedPressuredBody=new QSoftBody(); - griddedPressuredBody->AddMesh( QMesh::CreateWithPolygon(64,32,QVector::Zero(),-1 ) ); + griddedPressuredBody->AddMesh( QMesh::CreateWithPolygon(64,16,QVector::Zero(),-1 ,true,true) ); griddedPressuredBody->SetRigidity(0.5f)->SetPosition(mousePosition)->SetMass(0.5f); - griddedPressuredBody->SetAreaPreservingEnabled(true); + griddedPressuredBody->SetAreaPreservingEnabled(true)->SetAreaPreservingRate(0.5)->SetSelfCollisionsEnabled(true)->SetSelfCollisionsSpecifiedRadius(10.0f); world->AddBody(griddedPressuredBody); } diff --git a/examples/examplescenefrictions.cpp b/examples/examplescenefrictions.cpp index ed38687..fc462cb 100644 --- a/examples/examplescenefrictions.cpp +++ b/examples/examplescenefrictions.cpp @@ -29,7 +29,7 @@ ExampleSceneFrictions::ExampleSceneFrictions(QVector sceneSize):QExampleScene(sceneSize){ - world->SetEnableSleeping(false); + world->SetSleepingEnabled(false); //Adding Floor QRigidBody *floor=new QRigidBody(); floor->AddMesh(QMesh::CreateWithRect(QVector(3000,64),QVector(0.0f,0.0f) ) )->SetPosition(QVector(512.0f,550.0f)); diff --git a/examples/examplescenejoints.cpp b/examples/examplescenejoints.cpp index e499a0c..7117b91 100644 --- a/examples/examplescenejoints.cpp +++ b/examples/examplescenejoints.cpp @@ -30,7 +30,7 @@ ExampleSceneJoints::ExampleSceneJoints(QVector sceneSize):QExampleScene(sceneSize) { //world->gravity=QVector::Zero(); - world->SetEnableSleeping(false); + world->SetSleepingEnabled(false); PinJointSample(QVector(200,200) ); SpringDistanceJointSample(QVector(400,200) ); GrooveJointSample(QVector(600,200) ); diff --git a/examples/examplesceneplatformer.cpp b/examples/examplesceneplatformer.cpp index 3031365..c574b12 100644 --- a/examples/examplesceneplatformer.cpp +++ b/examples/examplesceneplatformer.cpp @@ -33,7 +33,7 @@ ExampleScenePlatformer::ExampleScenePlatformer(QVector sceneSize):QExampleScene( { //Creating platforms and player // - world->SetEnableSleeping(false); + world->SetSleepingEnabled(false); //Adding Floors QRigidBody *floor; floor=new QRigidBody(); diff --git a/examples/examplescenesoftbodies.cpp b/examples/examplescenesoftbodies.cpp index 7678e21..153384c 100644 --- a/examples/examplescenesoftbodies.cpp +++ b/examples/examplescenesoftbodies.cpp @@ -30,13 +30,15 @@ ExampleSceneSoftBodies::ExampleSceneSoftBodies(QVector sceneSize) :QExampleScene(sceneSize) { - world->SetEnableSleeping(false); + world->SetSleepingEnabled(false); //world->SetTimeScale(0.1f); //Adding Floor QRigidBody *floor=new QRigidBody(); floor->AddMesh(QMesh::CreateWithRect(QVector(3000,64),QVector(0.0f,0.0f) ) )->SetPosition(QVector(512.0f,550.0f)); floor->SetMode(QBody::Modes::STATIC); world->AddBody(floor); + //world->SetGravity(QVector::Zero() ); + @@ -98,6 +100,18 @@ ExampleSceneSoftBodies::ExampleSceneSoftBodies(QVector sceneSize) :QExampleScene world->AddBody(griddedPressuredBody); + //Adding mass-spring model soft body without polar grids + QSoftBody *boxA=new QSoftBody(); + boxA->AddMesh( QMesh::CreateWithRect(QVector(64,64) ) ); + boxA->SetRigidity(0.1f)->SetPosition(QVector(350,-200))->SetMass(0.5f); + world->AddBody(boxA); + + QSoftBody *boxB=new QSoftBody(); + boxB->AddMesh( QMesh::CreateWithRect(QVector(64,64) ) ); + boxB->SetRigidity(0.1f)->SetPosition(QVector(100,-200))->SetMass(0.5f); + world->AddBody(boxB); + + //It's about self collision tests. /* QSoftBody *wordBody=new QSoftBody(); wordBody->SetPosition(QVector(400,200) ); diff --git a/main.cpp b/main.cpp index 1ffcd9f..ead9725 100644 --- a/main.cpp +++ b/main.cpp @@ -136,16 +136,15 @@ void CalculateFPS(){ void RenderFPS(QWorld *world){ - - //Draw FPS sf::Text text; text.setFont(*font); int fpsInt=floor(fps); string str="Press 1...9 key to navigate between example scenes."; - str+="\n FPS:"+to_string(fpsInt)+"(World Step "+ to_string(worldStepMs)+" ms)" + " | object count:"+ to_string(world->GetBodyCount()); - str+="\n Broadphase:" + to_string(scene->world->GetEnableBroadphase())+" | Sleeping Mode:"+to_string(scene->world->GetEnableSleeping()); + str+="\n FPS:"+to_string(fpsInt)+"(World Step "+ to_string(worldStepMs/1000)+" ms)" + " | object count:"+ to_string(world->GetBodyCount()); + str+="\n Broadphase:" + to_string(scene->world->GetBroadphaseEnabled())+" | Sleeping Mode:"+to_string(scene->world->GetSleepingEnabled()); str+="\n Iteration Count:"+to_string(scene->world->GetIterationCount()); + str+="\n Spatial Hashing Enabled:"+to_string(scene->world->GetSpatialHashingEnabled()); text.setString( str); text.setCharacterSize(12); @@ -259,7 +258,7 @@ int main() window->setFramerateLimit(60); window->setKeyRepeatEnabled(false); - LoadExampleScene(1); + LoadExampleScene(8); while (window->isOpen()) { @@ -310,6 +309,10 @@ int main() } scene->OnKeyReleased(event.key.code); + if(event.key.code==sf::Keyboard::S){ + scene->world->SetSpatialHashingEnabled(scene->world->GetSpatialHashingEnabled()==true ? false:true ); + } + // if(event.key.code==sf::Keyboard::H){ // showColliders=showColliders==true ? false:true; // }else if(event.key.code==sf::Keyboard::Left){ @@ -325,12 +328,12 @@ int main() // scene->world->SetEnableBroadphase(enableBroadphase); // }else if(event.key.code==sf::Keyboard::S){ // bool enableSleeping=scene->world->GetEnableSleeping()==false ? true:false; +// } // scene->world->SetEnableSleeping(enableSleeping); // }else if(event.key.code==sf::Keyboard::C){ // showCollisions=showCollisions==false ? true:false; // }else if(event.key.code==sf::Keyboard::F){ // showSpatialBoundingBox=showSpatialBoundingBox==true ? false:true; -// } } diff --git a/qphysicsrenderer.cpp b/qphysicsrenderer.cpp index f4c59be..75acd19 100644 --- a/qphysicsrenderer.cpp +++ b/qphysicsrenderer.cpp @@ -79,7 +79,8 @@ void QPhysicsRenderer::RenderColliders(QWorld *world, sf::RenderWindow *window) col=QPhysicsRenderer::COLOR_BODY_DYNAMIC_SLEEPING; } col=body->GetMode()==QBody::STATIC ? QPhysicsRenderer::COLOR_BODY_STATIC:col; - + sf::Color colHalf=col; + colHalf.a=50; if(body->GetBodyType()==QBody::BodyTypes::RIGID){ QRigidBody *rbody=static_cast(body); @@ -146,21 +147,33 @@ void QPhysicsRenderer::RenderColliders(QWorld *world, sf::RenderWindow *window) window->draw(springLine,2,sf::Lines); } } - //Draw Polygons - for(int p=0;pGetClosedPolygonCount();p++){ - vector *polygon=&mesh->GetClosedPolygonAt(p); - sf::ConvexShape polygonShape(polygon->size()); + //Draw Sub Convex Polygons + if (mesh->GetSubConvexPolygonCount()>1 ){ + for(int p=0;pGetSubConvexPolygonCount();p++){ + vector *polygon=&mesh->GetSubConvexPolygonAt(p); - vector vertices; + vector vertices; - for(int t=0;tsize();t++){ - QParticle *particle=polygon->at(t); - QVector particlePos=particle->GetGlobalPosition(); - vertices.push_back(sf::Vertex(sf::Vector2f(particlePos.x,particlePos.y),col ) ); + for(int t=0;tsize();t++){ + QParticle *particle=polygon->at(t); + QVector particlePos=particle->GetGlobalPosition(); + vertices.push_back(sf::Vertex(sf::Vector2f(particlePos.x,particlePos.y),colHalf ) ); + } + vertices.push_back( vertices[0]); + window->draw(&vertices[0],vertices.size(),sf::LineStrip); } - vertices.push_back( vertices[0]); - window->draw(&vertices[0],vertices.size(),sf::LineStrip); + + } + //Draw Polygon + vector polygonVertices; + + for(int t=0;tGetPolygonParticleCount();t++){ + QParticle *particle=mesh->GetParticleFromPolygon(t); + QVector particlePos=particle->GetGlobalPosition(); + polygonVertices.push_back(sf::Vertex(sf::Vector2f(particlePos.x,particlePos.y),col ) ); } + polygonVertices.push_back( polygonVertices[0]); + window->draw(&polygonVertices[0],polygonVertices.size(),sf::LineStrip); @@ -293,6 +306,14 @@ void QPhysicsRenderer::RenderPhysicsGizmos(QWorld *world, sf::RenderWindow *wind window->draw(lineA,2,sf::Lines); window->draw(lineB,2,sf::Lines); } + }else if (gizmo->GetGizmoType()==QGizmo::Rectangle ){ + QGizmoRect *gizmoRect=static_cast(gizmo); + sf::RectangleShape rectShape(sf::Vector2f(gizmoRect->rect.GetSize().x,gizmoRect->rect.GetSize().y ) ); + rectShape.setFillColor(sf::Color::Transparent); + rectShape.setOutlineColor(col); + rectShape.setOutlineThickness(1.0); + rectShape.setPosition(sf::Vector2f (gizmoRect->rect.GetMin().x,gizmoRect->rect.GetMin().y ) ); + window->draw(rectShape); } } } diff --git a/resources/mesh_files/soft_test.qmesh b/resources/mesh_files/soft_test.qmesh deleted file mode 100644 index 0f75e29..0000000 --- a/resources/mesh_files/soft_test.qmesh +++ /dev/null @@ -1 +0,0 @@ -{"meshes":[{"name":"Mesh_0","position":[0,0],"rotation":0,"particles":[{"position":[-64.000008,-80],"radius":0,"is_internal":false},{"position":[64,-80],"radius":0,"is_internal":false},{"position":[-64,80],"radius":0,"is_internal":false},{"position":[64,80],"radius":0,"is_internal":false},{"position":[64,-32],"radius":0,"is_internal":false},{"position":[32,-32],"radius":0,"is_internal":false},{"position":[32,-48],"radius":0,"is_internal":false},{"position":[0,-48],"radius":0,"is_internal":false},{"position":[-32,-48],"radius":0,"is_internal":false},{"position":[-32,-16],"radius":0,"is_internal":false},{"position":[32,-79.999992],"radius":0,"is_internal":false},{"position":[0,-80],"radius":0,"is_internal":false},{"position":[-32,-79.999992],"radius":0,"is_internal":false},{"position":[64,-48],"radius":0,"is_internal":false},{"position":[-64,-48],"radius":0,"is_internal":false},{"position":[-64,-16],"radius":0,"is_internal":false},{"position":[-64,16],"radius":0,"is_internal":false},{"position":[-64,16],"radius":0,"is_internal":false},{"position":[-32,16],"radius":0,"is_internal":false},{"position":[0,15.999999],"radius":0,"is_internal":false},{"position":[32,16],"radius":0,"is_internal":false},{"position":[0,-16],"radius":0,"is_internal":false},{"position":[32,-16],"radius":0,"is_internal":false},{"position":[63.999985,-16],"radius":0,"is_internal":false},{"position":[63.999985,15.999999],"radius":0,"is_internal":false},{"position":[64,48],"radius":0,"is_internal":false},{"position":[32,48.000004],"radius":0,"is_internal":false},{"position":[0,48.000004],"radius":0,"is_internal":false},{"position":[-32,48],"radius":0,"is_internal":false},{"position":[-64,48],"radius":0,"is_internal":false},{"position":[-32,79.999992],"radius":0,"is_internal":false},{"position":[0,80],"radius":0,"is_internal":false},{"position":[32,79.999992],"radius":0,"is_internal":false},{"position":[-64,32],"radius":0,"is_internal":false},{"position":[-32.000008,32],"radius":0,"is_internal":false}],"springs":[[5,4],[4,13],[13,1],[1,10],[10,11],[11,12],[12,0],[5,6],[6,7],[7,8],[8,9],[0,14],[14,15],[15,16],[16,18],[19,20],[18,19],[9,21],[21,22],[22,23],[23,24],[24,25],[25,3],[3,32],[32,31],[31,30],[30,2],[2,29],[29,33],[33,34],[34,28],[28,27],[27,26],[26,20],[5,13],[4,6],[6,1],[13,10],[6,13],[6,10],[11,7],[12,8],[10,7],[11,6],[8,11],[7,12],[14,8],[12,14],[0,8],[14,9],[8,15],[15,18],[9,16],[15,9],[9,18],[18,21],[9,19],[19,21],[21,20],[19,22],[22,20],[20,24],[23,20],[22,24],[24,26],[20,25],[26,32],[32,25],[26,25],[3,26],[26,31],[27,32],[27,31],[31,28],[30,27],[28,30],[30,29],[29,28],[28,2],[29,34],[28,33],[5,1],[4,10],[33,30],[34,2]],"internal_springs":[],"polygons":[[0,12,11,10,1,13,6,7,8,14],[6,13,4,5],[14,8,9,18,16,15],[9,21,22,23,24,20,19,18],[20,24,25,3,32,26],[29,28,27,26,32,31,30,2],[33,34,28,29]]}],"reference_image_location":"","reference_image_position":[0,0],"reference_image_alpha":0.117647,"grid_size":[16,16],"snap_to_grid":true} \ No newline at end of file diff --git a/resources/mesh_files/word_a.qmesh b/resources/mesh_files/word_a.qmesh index de7a07e..80b0b46 100644 --- a/resources/mesh_files/word_a.qmesh +++ b/resources/mesh_files/word_a.qmesh @@ -1 +1,585 @@ -{"meshes":[{"name":"Mesh_0","position":[0,0],"rotation":0,"particles":[{"position":[48,-80],"radius":0,"is_internal":false},{"position":[0,-80.000023],"radius":0,"is_internal":false},{"position":[32,-80.000023],"radius":0,"is_internal":false},{"position":[0.000031,-48.000031],"radius":0,"is_internal":false},{"position":[32,-48.000004],"radius":0,"is_internal":false},{"position":[-32,-80.000023],"radius":0,"is_internal":false},{"position":[64,-48.000031],"radius":0,"is_internal":false},{"position":[-32,-48.000031],"radius":0,"is_internal":false},{"position":[-64.000015,-64],"radius":0,"is_internal":false},{"position":[-64,-32],"radius":0,"is_internal":false},{"position":[64,-15.999999],"radius":0,"is_internal":false},{"position":[32,-15.999999],"radius":0,"is_internal":false},{"position":[64,16],"radius":0,"is_internal":false},{"position":[32,16],"radius":0,"is_internal":false},{"position":[-48,80],"radius":0,"is_internal":false},{"position":[48,80],"radius":0,"is_internal":false},{"position":[64,47.999985],"radius":0,"is_internal":false},{"position":[32,47.999985],"radius":0,"is_internal":false},{"position":[32,80],"radius":0,"is_internal":false},{"position":[0,80],"radius":0,"is_internal":false},{"position":[-32,80],"radius":0,"is_internal":false},{"position":[0.000031,47.999985],"radius":0,"is_internal":false},{"position":[-32,47.999985],"radius":0,"is_internal":false},{"position":[-64,47.999985],"radius":0,"is_internal":false},{"position":[-64,16],"radius":0,"is_internal":false},{"position":[-32,16],"radius":0,"is_internal":false},{"position":[-48,-16],"radius":0,"is_internal":false},{"position":[16,-16],"radius":0,"is_internal":false},{"position":[16,16],"radius":0,"is_internal":false},{"position":[-32,-15.999999],"radius":0,"is_internal":false}],"springs":[[9,8],[8,5],[5,1],[1,2],[2,0],[0,6],[6,10],[10,12],[12,16],[16,15],[15,18],[18,19],[19,20],[20,14],[14,23],[24,23],[24,26],[26,29],[29,27],[27,28],[28,25],[25,22],[22,21],[21,17],[17,13],[13,11],[11,4],[4,3],[3,7],[7,9]],"internal_springs":[[9,5],[7,8],[5,7],[7,1],[3,5],[1,3],[3,2],[4,1],[2,4],[4,6],[0,4],[2,6],[4,10],[11,10],[11,6],[11,12],[10,13],[13,12],[12,17],[13,16],[17,16],[17,18],[16,18],[15,17],[21,19],[19,17],[18,21],[21,20],[19,22],[22,20],[23,22],[22,14],[20,23],[23,25],[22,24],[24,25],[25,29],[26,25],[29,24],[25,27],[28,29]],"polygons":[[8,5,1,2,0,6,10,12,16,15,18,19,20,14,23,24,26,29,27,28,25,22,21,17,13,11,4,3,7,9]]}],"reference_image_location":"","reference_image_position":[0,0],"reference_image_alpha":0.117647,"grid_size":[16,16],"snap_to_grid":true} \ No newline at end of file +{ + "meshes": [ + { + "name": "Mesh_0", + "position": [ + 0, + 0 + ], + "rotation": 0, + "particles": [ + { + "position": [ + 48, + -80 + ], + "radius": 0, + "is_internal": false + }, + { + "position": [ + 0, + -80.000023 + ], + "radius": 0, + "is_internal": false + }, + { + "position": [ + 32, + -80.000023 + ], + "radius": 0, + "is_internal": false + }, + { + "position": [ + 0.000031, + -48.000031 + ], + "radius": 0, + "is_internal": false + }, + { + "position": [ + 32, + -48.000004 + ], + "radius": 0, + "is_internal": false + }, + { + "position": [ + -32, + -80.000023 + ], + "radius": 0, + "is_internal": false + }, + { + "position": [ + 64, + -48.000031 + ], + "radius": 0, + "is_internal": false + }, + { + "position": [ + -32, + -48.000031 + ], + "radius": 0, + "is_internal": false + }, + { + "position": [ + -64.000015, + -64 + ], + "radius": 0, + "is_internal": false + }, + { + "position": [ + -64, + -32 + ], + "radius": 0, + "is_internal": false + }, + { + "position": [ + 64, + -15.999999 + ], + "radius": 0, + "is_internal": false + }, + { + "position": [ + 32, + -15.999999 + ], + "radius": 0, + "is_internal": false + }, + { + "position": [ + 64, + 16 + ], + "radius": 0, + "is_internal": false + }, + { + "position": [ + 32, + 16 + ], + "radius": 0, + "is_internal": false + }, + { + "position": [ + -48, + 80 + ], + "radius": 0, + "is_internal": false + }, + { + "position": [ + 48, + 80 + ], + "radius": 0, + "is_internal": false + }, + { + "position": [ + 64, + 47.999985 + ], + "radius": 0, + "is_internal": false + }, + { + "position": [ + 32, + 47.999985 + ], + "radius": 0, + "is_internal": false + }, + { + "position": [ + 32, + 80 + ], + "radius": 0, + "is_internal": false + }, + { + "position": [ + 0, + 80 + ], + "radius": 0, + "is_internal": false + }, + { + "position": [ + -32, + 80 + ], + "radius": 0, + "is_internal": false + }, + { + "position": [ + 0.000031, + 47.999985 + ], + "radius": 0, + "is_internal": false + }, + { + "position": [ + -32, + 47.999985 + ], + "radius": 0, + "is_internal": false + }, + { + "position": [ + -64, + 47.999985 + ], + "radius": 0, + "is_internal": false + }, + { + "position": [ + -64, + 16 + ], + "radius": 0, + "is_internal": false + }, + { + "position": [ + -32, + 16 + ], + "radius": 0, + "is_internal": false + }, + { + "position": [ + -48, + -16 + ], + "radius": 0, + "is_internal": false + }, + { + "position": [ + 16, + -16 + ], + "radius": 0, + "is_internal": false + }, + { + "position": [ + 16, + 16 + ], + "radius": 0, + "is_internal": false + }, + { + "position": [ + -32, + -15.999999 + ], + "radius": 0, + "is_internal": false + } + ], + "springs": [ + [ + 9, + 8 + ], + [ + 8, + 5 + ], + [ + 5, + 1 + ], + [ + 1, + 2 + ], + [ + 2, + 0 + ], + [ + 0, + 6 + ], + [ + 6, + 10 + ], + [ + 10, + 12 + ], + [ + 12, + 16 + ], + [ + 16, + 15 + ], + [ + 15, + 18 + ], + [ + 18, + 19 + ], + [ + 19, + 20 + ], + [ + 20, + 14 + ], + [ + 14, + 23 + ], + [ + 24, + 23 + ], + [ + 24, + 26 + ], + [ + 26, + 29 + ], + [ + 29, + 27 + ], + [ + 27, + 28 + ], + [ + 28, + 25 + ], + [ + 25, + 22 + ], + [ + 22, + 21 + ], + [ + 21, + 17 + ], + [ + 17, + 13 + ], + [ + 13, + 11 + ], + [ + 11, + 4 + ], + [ + 4, + 3 + ], + [ + 3, + 7 + ], + [ + 7, + 9 + ] + ], + "internal_springs": [ + [ + 9, + 5 + ], + [ + 7, + 8 + ], + [ + 5, + 7 + ], + [ + 7, + 1 + ], + [ + 3, + 5 + ], + [ + 1, + 3 + ], + [ + 3, + 2 + ], + [ + 4, + 1 + ], + [ + 2, + 4 + ], + [ + 4, + 6 + ], + [ + 0, + 4 + ], + [ + 2, + 6 + ], + [ + 4, + 10 + ], + [ + 11, + 10 + ], + [ + 11, + 6 + ], + [ + 11, + 12 + ], + [ + 10, + 13 + ], + [ + 13, + 12 + ], + [ + 12, + 17 + ], + [ + 13, + 16 + ], + [ + 17, + 16 + ], + [ + 17, + 18 + ], + [ + 16, + 18 + ], + [ + 15, + 17 + ], + [ + 21, + 19 + ], + [ + 19, + 17 + ], + [ + 18, + 21 + ], + [ + 21, + 20 + ], + [ + 19, + 22 + ], + [ + 22, + 20 + ], + [ + 23, + 22 + ], + [ + 22, + 14 + ], + [ + 20, + 23 + ], + [ + 23, + 25 + ], + [ + 22, + 24 + ], + [ + 24, + 25 + ], + [ + 25, + 29 + ], + [ + 26, + 25 + ], + [ + 29, + 24 + ], + [ + 25, + 27 + ], + [ + 28, + 29 + ] + ], + "polygon": [ + 8, + 5, + 1, + 2, + 0, + 6, + 10, + 12, + 16, + 15, + 18, + 19, + 20, + 14, + 23, + 24, + 26, + 29, + 27, + 28, + 25, + 22, + 21, + 17, + 13, + 11, + 4, + 3, + 7, + 9 + ] + } + ], + "reference_image_location": "", + "reference_image_position": [ + 0, + 0 + ], + "reference_image_alpha": 0.117647, + "grid_size": [ + 16, + 16 + ], + "snap_to_grid": true +} \ No newline at end of file diff --git a/resources/mesh_files/word_k.qmesh b/resources/mesh_files/word_k.qmesh index 61774dc..1796408 100644 --- a/resources/mesh_files/word_k.qmesh +++ b/resources/mesh_files/word_k.qmesh @@ -1 +1,564 @@ -{"meshes":[{"name":"Mesh_0","position":[0,0],"rotation":0,"particles":[{"position":[-64,-80.000008],"radius":0,"is_internal":false},{"position":[-64.000061,-47.999992],"radius":0,"is_internal":false},{"position":[-64,-15.999994],"radius":0,"is_internal":false},{"position":[-64,15.999997],"radius":0,"is_internal":false},{"position":[-64.000061,48],"radius":0,"is_internal":false},{"position":[-64,79.999969],"radius":0,"is_internal":false},{"position":[-32,-80],"radius":0,"is_internal":false},{"position":[-32,-47.999992],"radius":0,"is_internal":false},{"position":[-32,-15.999994],"radius":0,"is_internal":false},{"position":[-32,15.999997],"radius":0,"is_internal":false},{"position":[-32,48],"radius":0,"is_internal":false},{"position":[-32,79.999969],"radius":0,"is_internal":false},{"position":[32.000015,-80],"radius":0,"is_internal":false},{"position":[63.999878,-63.999989],"radius":0,"is_internal":false},{"position":[-16,-31.999996],"radius":0,"is_internal":false},{"position":[0.000001,0.000001],"radius":0,"is_internal":false},{"position":[-16,32],"radius":0,"is_internal":false},{"position":[63.999878,64],"radius":0,"is_internal":false},{"position":[32.000076,80],"radius":0,"is_internal":false},{"position":[16,-15.999994],"radius":0,"is_internal":false},{"position":[32.000015,-31.999996],"radius":0,"is_internal":false},{"position":[48,-47.999992],"radius":0,"is_internal":false},{"position":[0.000001,-47.999992],"radius":0,"is_internal":false},{"position":[16,-63.999989],"radius":0,"is_internal":false},{"position":[16,15.999997],"radius":0,"is_internal":false},{"position":[0.000001,48],"radius":0,"is_internal":false},{"position":[32.000015,32],"radius":0,"is_internal":false},{"position":[16,64],"radius":0,"is_internal":false},{"position":[48,48],"radius":0,"is_internal":false}],"springs":[[0,1],[1,2],[2,3],[3,4],[4,5],[5,11],[11,10],[9,10],[9,16],[0,6],[6,7],[7,8],[8,14],[14,22],[22,23],[23,12],[13,21],[21,20],[20,19],[19,15],[16,25],[25,27],[27,18],[17,28],[28,26],[26,24],[24,15]],"internal_springs":[[1,7],[2,8],[3,9],[8,9],[4,10],[6,1],[0,7],[7,2],[1,8],[8,3],[2,9],[9,4],[3,10],[10,5],[4,11],[15,8],[15,9],[14,19],[22,20],[23,21],[12,13],[16,24],[25,26],[27,28],[18,17],[8,19],[14,15],[14,20],[19,22],[22,21],[20,23],[23,13],[21,12],[9,24],[15,16],[16,26],[24,25],[25,28],[26,27],[27,17],[28,18]],"polygons":[[5,4,3,2,1,0,6,7,8,14,22,23,12,13,21,20,19,15,24,26,28,17,18,27,25,16,9,10,11]]}],"reference_image_location":"","reference_image_position":[0,0],"reference_image_alpha":0.117647,"grid_size":[16,16],"snap_to_grid":true} \ No newline at end of file +{ + "meshes": [ + { + "name": "Mesh_0", + "position": [ + 0, + 0 + ], + "rotation": 0, + "particles": [ + { + "position": [ + -64, + -80.000008 + ], + "radius": 0, + "is_internal": false + }, + { + "position": [ + -64.000061, + -47.999992 + ], + "radius": 0, + "is_internal": false + }, + { + "position": [ + -64, + -15.999994 + ], + "radius": 0, + "is_internal": false + }, + { + "position": [ + -64, + 15.999997 + ], + "radius": 0, + "is_internal": false + }, + { + "position": [ + -64.000061, + 48 + ], + "radius": 0, + "is_internal": false + }, + { + "position": [ + -64, + 79.999969 + ], + "radius": 0, + "is_internal": false + }, + { + "position": [ + -32, + -80 + ], + "radius": 0, + "is_internal": false + }, + { + "position": [ + -32, + -47.999992 + ], + "radius": 0, + "is_internal": false + }, + { + "position": [ + -32, + -15.999994 + ], + "radius": 0, + "is_internal": false + }, + { + "position": [ + -32, + 15.999997 + ], + "radius": 0, + "is_internal": false + }, + { + "position": [ + -32, + 48 + ], + "radius": 0, + "is_internal": false + }, + { + "position": [ + -32, + 79.999969 + ], + "radius": 0, + "is_internal": false + }, + { + "position": [ + 32.000015, + -80 + ], + "radius": 0, + "is_internal": false + }, + { + "position": [ + 63.999878, + -63.999989 + ], + "radius": 0, + "is_internal": false + }, + { + "position": [ + -16, + -31.999996 + ], + "radius": 0, + "is_internal": false + }, + { + "position": [ + 0.000001, + 0.000001 + ], + "radius": 0, + "is_internal": false + }, + { + "position": [ + -16, + 32 + ], + "radius": 0, + "is_internal": false + }, + { + "position": [ + 63.999878, + 64 + ], + "radius": 0, + "is_internal": false + }, + { + "position": [ + 32.000076, + 80 + ], + "radius": 0, + "is_internal": false + }, + { + "position": [ + 16, + -15.999994 + ], + "radius": 0, + "is_internal": false + }, + { + "position": [ + 32.000015, + -31.999996 + ], + "radius": 0, + "is_internal": false + }, + { + "position": [ + 48, + -47.999992 + ], + "radius": 0, + "is_internal": false + }, + { + "position": [ + 0.000001, + -47.999992 + ], + "radius": 0, + "is_internal": false + }, + { + "position": [ + 16, + -63.999989 + ], + "radius": 0, + "is_internal": false + }, + { + "position": [ + 16, + 15.999997 + ], + "radius": 0, + "is_internal": false + }, + { + "position": [ + 0.000001, + 48 + ], + "radius": 0, + "is_internal": false + }, + { + "position": [ + 32.000015, + 32 + ], + "radius": 0, + "is_internal": false + }, + { + "position": [ + 16, + 64 + ], + "radius": 0, + "is_internal": false + }, + { + "position": [ + 48, + 48 + ], + "radius": 0, + "is_internal": false + } + ], + "springs": [ + [ + 0, + 1 + ], + [ + 1, + 2 + ], + [ + 2, + 3 + ], + [ + 3, + 4 + ], + [ + 4, + 5 + ], + [ + 5, + 11 + ], + [ + 11, + 10 + ], + [ + 9, + 10 + ], + [ + 9, + 16 + ], + [ + 0, + 6 + ], + [ + 6, + 7 + ], + [ + 7, + 8 + ], + [ + 8, + 14 + ], + [ + 14, + 22 + ], + [ + 22, + 23 + ], + [ + 23, + 12 + ], + [ + 13, + 21 + ], + [ + 21, + 20 + ], + [ + 20, + 19 + ], + [ + 19, + 15 + ], + [ + 16, + 25 + ], + [ + 25, + 27 + ], + [ + 27, + 18 + ], + [ + 17, + 28 + ], + [ + 28, + 26 + ], + [ + 26, + 24 + ], + [ + 24, + 15 + ] + ], + "internal_springs": [ + [ + 1, + 7 + ], + [ + 2, + 8 + ], + [ + 3, + 9 + ], + [ + 8, + 9 + ], + [ + 4, + 10 + ], + [ + 6, + 1 + ], + [ + 0, + 7 + ], + [ + 7, + 2 + ], + [ + 1, + 8 + ], + [ + 8, + 3 + ], + [ + 2, + 9 + ], + [ + 9, + 4 + ], + [ + 3, + 10 + ], + [ + 10, + 5 + ], + [ + 4, + 11 + ], + [ + 15, + 8 + ], + [ + 15, + 9 + ], + [ + 14, + 19 + ], + [ + 22, + 20 + ], + [ + 23, + 21 + ], + [ + 12, + 13 + ], + [ + 16, + 24 + ], + [ + 25, + 26 + ], + [ + 27, + 28 + ], + [ + 18, + 17 + ], + [ + 8, + 19 + ], + [ + 14, + 15 + ], + [ + 14, + 20 + ], + [ + 19, + 22 + ], + [ + 22, + 21 + ], + [ + 20, + 23 + ], + [ + 23, + 13 + ], + [ + 21, + 12 + ], + [ + 9, + 24 + ], + [ + 15, + 16 + ], + [ + 16, + 26 + ], + [ + 24, + 25 + ], + [ + 25, + 28 + ], + [ + 26, + 27 + ], + [ + 27, + 17 + ], + [ + 28, + 18 + ] + ], + "polygon": [ + 5, + 4, + 3, + 2, + 1, + 0, + 6, + 7, + 8, + 14, + 22, + 23, + 12, + 13, + 21, + 20, + 19, + 15, + 24, + 26, + 28, + 17, + 18, + 27, + 25, + 16, + 9, + 10, + 11 + ] + } + ], + "reference_image_location": "", + "reference_image_position": [ + 0, + 0 + ], + "reference_image_alpha": 0.117647, + "grid_size": [ + 16, + 16 + ], + "snap_to_grid": true +} \ No newline at end of file diff --git a/resources/mesh_files/word_q.qmesh b/resources/mesh_files/word_q.qmesh index 4f19cd1..18d651f 100644 --- a/resources/mesh_files/word_q.qmesh +++ b/resources/mesh_files/word_q.qmesh @@ -1 +1,572 @@ -{"meshes":[{"name":"Mesh_0","position":[0,0],"rotation":0,"particles":[{"position":[-47.999992,-80],"radius":0,"is_internal":false},{"position":[48,-80.000015],"radius":0,"is_internal":false},{"position":[-32.000004,-48.000015],"radius":0,"is_internal":true},{"position":[-32,15.999996],"radius":0,"is_internal":false},{"position":[-31.999996,-15.999999],"radius":0,"is_internal":false},{"position":[64,-16],"radius":0,"is_internal":false},{"position":[63.999996,32],"radius":0,"is_internal":false},{"position":[63.999996,-47.999996],"radius":0,"is_internal":false},{"position":[-63.999996,-47.999996],"radius":0,"is_internal":false},{"position":[-64.000008,-15.999995],"radius":0,"is_internal":false},{"position":[-64,15.999993],"radius":0,"is_internal":false},{"position":[-64.000008,47.999992],"radius":0,"is_internal":false},{"position":[-31.999996,47.999996],"radius":0,"is_internal":false},{"position":[-48.000008,79.999992],"radius":0,"is_internal":false},{"position":[-31.999996,80.000008],"radius":0,"is_internal":false},{"position":[-0.000002,80],"radius":0,"is_internal":false},{"position":[32,80.000008],"radius":0,"is_internal":false},{"position":[64,48],"radius":0,"is_internal":false},{"position":[47.999996,96],"radius":0,"is_internal":false},{"position":[79.999992,96.000008],"radius":0,"is_internal":false},{"position":[-32,-80],"radius":0,"is_internal":false},{"position":[-0.000003,-80],"radius":0,"is_internal":false},{"position":[-0.000002,-48],"radius":0,"is_internal":false},{"position":[32,-48.000008],"radius":0,"is_internal":false},{"position":[32.000004,-80],"radius":0,"is_internal":false},{"position":[-0,48],"radius":0,"is_internal":false},{"position":[32,47.999996],"radius":0,"is_internal":false},{"position":[32,-15.999999],"radius":0,"is_internal":false},{"position":[31.999996,32],"radius":0,"is_internal":false}],"springs":[[13,11],[11,10],[10,9],[9,8],[8,0],[1,7],[7,5],[5,6],[16,15],[15,14],[14,13],[2,4],[4,3],[3,12],[17,19],[19,18],[18,16],[0,20],[20,21],[2,22],[21,24],[22,23],[9,9],[12,25],[25,26],[23,27],[27,28],[28,6],[24,1],[26,17]],"internal_springs":[[28,5],[6,27],[27,5],[27,7],[5,23],[23,7],[23,24],[1,23],[7,24],[22,24],[23,21],[21,22],[22,20],[20,2],[2,21],[2,8],[0,2],[8,20],[9,4],[9,2],[4,8],[9,3],[10,4],[3,10],[11,3],[12,10],[11,12],[12,14],[14,11],[12,13],[14,25],[12,15],[15,25],[25,16],[15,26],[26,16],[16,17],[16,19],[17,18],[26,19]],"polygons":[[13,11,10,9,8,0,20,21,24,1,7,5,6,28,27,23,22,2,4,3,12,25,26,17,19,18,16,15,14]]}],"reference_image_location":"","reference_image_position":[0,0],"reference_image_alpha":0.117647,"grid_size":[16,16],"snap_to_grid":true} \ No newline at end of file +{ + "meshes": [ + { + "name": "Mesh_0", + "position": [ + 0, + 0 + ], + "rotation": 0, + "particles": [ + { + "position": [ + -47.999992, + -80 + ], + "radius": 0, + "is_internal": false + }, + { + "position": [ + 48, + -80.000015 + ], + "radius": 0, + "is_internal": false + }, + { + "position": [ + -32.000004, + -48.000015 + ], + "radius": 0, + "is_internal": true + }, + { + "position": [ + -32, + 15.999996 + ], + "radius": 0, + "is_internal": false + }, + { + "position": [ + -31.999996, + -15.999999 + ], + "radius": 0, + "is_internal": false + }, + { + "position": [ + 64, + -16 + ], + "radius": 0, + "is_internal": false + }, + { + "position": [ + 63.999996, + 32 + ], + "radius": 0, + "is_internal": false + }, + { + "position": [ + 63.999996, + -47.999996 + ], + "radius": 0, + "is_internal": false + }, + { + "position": [ + -63.999996, + -47.999996 + ], + "radius": 0, + "is_internal": false + }, + { + "position": [ + -64.000008, + -15.999995 + ], + "radius": 0, + "is_internal": false + }, + { + "position": [ + -64, + 15.999993 + ], + "radius": 0, + "is_internal": false + }, + { + "position": [ + -64.000008, + 47.999992 + ], + "radius": 0, + "is_internal": false + }, + { + "position": [ + -31.999996, + 47.999996 + ], + "radius": 0, + "is_internal": false + }, + { + "position": [ + -48.000008, + 79.999992 + ], + "radius": 0, + "is_internal": false + }, + { + "position": [ + -31.999996, + 80.000008 + ], + "radius": 0, + "is_internal": false + }, + { + "position": [ + -0.000002, + 80 + ], + "radius": 0, + "is_internal": false + }, + { + "position": [ + 32, + 80.000008 + ], + "radius": 0, + "is_internal": false + }, + { + "position": [ + 64, + 48 + ], + "radius": 0, + "is_internal": false + }, + { + "position": [ + 47.999996, + 96 + ], + "radius": 0, + "is_internal": false + }, + { + "position": [ + 79.999992, + 96.000008 + ], + "radius": 0, + "is_internal": false + }, + { + "position": [ + -32, + -80 + ], + "radius": 0, + "is_internal": false + }, + { + "position": [ + -0.000003, + -80 + ], + "radius": 0, + "is_internal": false + }, + { + "position": [ + -0.000002, + -48 + ], + "radius": 0, + "is_internal": false + }, + { + "position": [ + 32, + -48.000008 + ], + "radius": 0, + "is_internal": false + }, + { + "position": [ + 32.000004, + -80 + ], + "radius": 0, + "is_internal": false + }, + { + "position": [ + -0, + 48 + ], + "radius": 0, + "is_internal": false + }, + { + "position": [ + 32, + 47.999996 + ], + "radius": 0, + "is_internal": false + }, + { + "position": [ + 32, + -15.999999 + ], + "radius": 0, + "is_internal": false + }, + { + "position": [ + 31.999996, + 32 + ], + "radius": 0, + "is_internal": false + } + ], + "springs": [ + [ + 13, + 11 + ], + [ + 11, + 10 + ], + [ + 10, + 9 + ], + [ + 9, + 8 + ], + [ + 8, + 0 + ], + [ + 1, + 7 + ], + [ + 7, + 5 + ], + [ + 5, + 6 + ], + [ + 16, + 15 + ], + [ + 15, + 14 + ], + [ + 14, + 13 + ], + [ + 2, + 4 + ], + [ + 4, + 3 + ], + [ + 3, + 12 + ], + [ + 17, + 19 + ], + [ + 19, + 18 + ], + [ + 18, + 16 + ], + [ + 0, + 20 + ], + [ + 20, + 21 + ], + [ + 2, + 22 + ], + [ + 21, + 24 + ], + [ + 22, + 23 + ], + [ + 9, + 9 + ], + [ + 12, + 25 + ], + [ + 25, + 26 + ], + [ + 23, + 27 + ], + [ + 27, + 28 + ], + [ + 28, + 6 + ], + [ + 24, + 1 + ], + [ + 26, + 17 + ] + ], + "internal_springs": [ + [ + 28, + 5 + ], + [ + 6, + 27 + ], + [ + 27, + 5 + ], + [ + 27, + 7 + ], + [ + 5, + 23 + ], + [ + 23, + 7 + ], + [ + 23, + 24 + ], + [ + 1, + 23 + ], + [ + 7, + 24 + ], + [ + 22, + 24 + ], + [ + 23, + 21 + ], + [ + 21, + 22 + ], + [ + 22, + 20 + ], + [ + 20, + 2 + ], + [ + 2, + 21 + ], + [ + 2, + 8 + ], + [ + 0, + 2 + ], + [ + 8, + 20 + ], + [ + 9, + 4 + ], + [ + 9, + 2 + ], + [ + 4, + 8 + ], + [ + 9, + 3 + ], + [ + 10, + 4 + ], + [ + 3, + 10 + ], + [ + 11, + 3 + ], + [ + 12, + 10 + ], + [ + 11, + 12 + ], + [ + 12, + 14 + ], + [ + 14, + 11 + ], + [ + 12, + 13 + ], + [ + 14, + 25 + ], + [ + 12, + 15 + ], + [ + 15, + 25 + ], + [ + 25, + 16 + ], + [ + 15, + 26 + ], + [ + 26, + 16 + ], + [ + 16, + 17 + ], + [ + 16, + 19 + ], + [ + 17, + 18 + ], + [ + 26, + 19 + ] + ], + "polygon": [ + 13, + 11, + 10, + 9, + 8, + 0, + 20, + 21, + 24, + 1, + 7, + 5, + 6, + 28, + 27, + 23, + 22, + 2, + 4, + 3, + 12, + 25, + 26, + 17, + 19, + 18, + 16, + 15, + 14 + ] + } + ], + "reference_image_location": "", + "reference_image_position": [ + 0, + 0 + ], + "reference_image_alpha": 0.117647, + "grid_size": [ + 16, + 16 + ], + "snap_to_grid": true +} \ No newline at end of file diff --git a/resources/mesh_files/word_r.qmesh b/resources/mesh_files/word_r.qmesh index 3dfb647..1d1e6c0 100644 --- a/resources/mesh_files/word_r.qmesh +++ b/resources/mesh_files/word_r.qmesh @@ -1 +1,382 @@ -{"meshes":[{"name":"Mesh_0","position":[0,0],"rotation":0,"particles":[{"position":[-64,80],"radius":0,"is_internal":false},{"position":[-64,48.000004],"radius":0,"is_internal":false},{"position":[-63.999969,15.999998],"radius":0,"is_internal":false},{"position":[-63.999969,-15.999996],"radius":0,"is_internal":false},{"position":[-64,-47.999996],"radius":0,"is_internal":false},{"position":[-64,-80],"radius":0,"is_internal":false},{"position":[-32,-80],"radius":0,"is_internal":false},{"position":[-32,-64],"radius":0,"is_internal":false},{"position":[-0.000002,-80],"radius":0,"is_internal":false},{"position":[31.999968,-80],"radius":0,"is_internal":false},{"position":[64,-64],"radius":0,"is_internal":false},{"position":[64,-31.999996],"radius":0,"is_internal":false},{"position":[31.999937,-47.999996],"radius":0,"is_internal":false},{"position":[-0.000002,-47.999996],"radius":0,"is_internal":false},{"position":[-32.000031,-31.999992],"radius":0,"is_internal":false},{"position":[-32.000031,-15.999996],"radius":0,"is_internal":false},{"position":[-32.000031,48.000004],"radius":0,"is_internal":false},{"position":[-32,80],"radius":0,"is_internal":false},{"position":[-32.000031,16.000006],"radius":0,"is_internal":false}],"springs":[[17,0],[1,0],[1,2],[2,3],[3,4],[4,5],[5,6],[6,7],[7,8],[8,9],[9,10],[10,11],[11,12],[12,13],[13,14],[14,15],[16,17],[16,18],[18,15]],"internal_springs":[[0,16],[17,1],[1,16],[1,18],[2,16],[2,18],[2,15],[3,18],[3,15],[3,14],[15,4],[14,7],[4,7],[14,5],[4,6],[3,7],[14,8],[7,13],[8,13],[13,9],[12,8],[12,9],[12,10],[9,11],[10,13],[11,8]],"polygons":[[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,18,16,17]]}],"reference_image_location":"","reference_image_position":[0,0],"reference_image_alpha":0.117647,"grid_size":[16,16],"snap_to_grid":true} \ No newline at end of file +{ + "meshes": [ + { + "name": "Mesh_0", + "position": [ + 0, + 0 + ], + "rotation": 0, + "particles": [ + { + "position": [ + -64, + 80 + ], + "radius": 0, + "is_internal": false + }, + { + "position": [ + -64, + 48.000004 + ], + "radius": 0, + "is_internal": false + }, + { + "position": [ + -63.999969, + 15.999998 + ], + "radius": 0, + "is_internal": false + }, + { + "position": [ + -63.999969, + -15.999996 + ], + "radius": 0, + "is_internal": false + }, + { + "position": [ + -64, + -47.999996 + ], + "radius": 0, + "is_internal": false + }, + { + "position": [ + -64, + -80 + ], + "radius": 0, + "is_internal": false + }, + { + "position": [ + -32, + -80 + ], + "radius": 0, + "is_internal": false + }, + { + "position": [ + -32, + -64 + ], + "radius": 0, + "is_internal": false + }, + { + "position": [ + -0.000002, + -80 + ], + "radius": 0, + "is_internal": false + }, + { + "position": [ + 31.999968, + -80 + ], + "radius": 0, + "is_internal": false + }, + { + "position": [ + 64, + -64 + ], + "radius": 0, + "is_internal": false + }, + { + "position": [ + 64, + -31.999996 + ], + "radius": 0, + "is_internal": false + }, + { + "position": [ + 31.999937, + -47.999996 + ], + "radius": 0, + "is_internal": false + }, + { + "position": [ + -0.000002, + -47.999996 + ], + "radius": 0, + "is_internal": false + }, + { + "position": [ + -32.000031, + -31.999992 + ], + "radius": 0, + "is_internal": false + }, + { + "position": [ + -32.000031, + -15.999996 + ], + "radius": 0, + "is_internal": false + }, + { + "position": [ + -32.000031, + 48.000004 + ], + "radius": 0, + "is_internal": false + }, + { + "position": [ + -32, + 80 + ], + "radius": 0, + "is_internal": false + }, + { + "position": [ + -32.000031, + 16.000006 + ], + "radius": 0, + "is_internal": false + } + ], + "springs": [ + [ + 17, + 0 + ], + [ + 1, + 0 + ], + [ + 1, + 2 + ], + [ + 2, + 3 + ], + [ + 3, + 4 + ], + [ + 4, + 5 + ], + [ + 5, + 6 + ], + [ + 6, + 7 + ], + [ + 7, + 8 + ], + [ + 8, + 9 + ], + [ + 9, + 10 + ], + [ + 10, + 11 + ], + [ + 11, + 12 + ], + [ + 12, + 13 + ], + [ + 13, + 14 + ], + [ + 14, + 15 + ], + [ + 16, + 17 + ], + [ + 16, + 18 + ], + [ + 18, + 15 + ] + ], + "internal_springs": [ + [ + 0, + 16 + ], + [ + 17, + 1 + ], + [ + 1, + 16 + ], + [ + 1, + 18 + ], + [ + 2, + 16 + ], + [ + 2, + 18 + ], + [ + 2, + 15 + ], + [ + 3, + 18 + ], + [ + 3, + 15 + ], + [ + 3, + 14 + ], + [ + 15, + 4 + ], + [ + 14, + 7 + ], + [ + 4, + 7 + ], + [ + 14, + 5 + ], + [ + 4, + 6 + ], + [ + 3, + 7 + ], + [ + 14, + 8 + ], + [ + 7, + 13 + ], + [ + 8, + 13 + ], + [ + 13, + 9 + ], + [ + 12, + 8 + ], + [ + 12, + 9 + ], + [ + 12, + 10 + ], + [ + 9, + 11 + ], + [ + 10, + 13 + ], + [ + 11, + 8 + ] + ], + "polygon": [ + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 18, + 16, + 17 + ] + } + ], + "reference_image_location": "", + "reference_image_position": [ + 0, + 0 + ], + "reference_image_alpha": 0.117647, + "grid_size": [ + 16, + 16 + ], + "snap_to_grid": true +} \ No newline at end of file diff --git a/resources/mesh_files/word_u.qmesh b/resources/mesh_files/word_u.qmesh index 3cf3b56..4fba538 100644 --- a/resources/mesh_files/word_u.qmesh +++ b/resources/mesh_files/word_u.qmesh @@ -1 +1 @@ -{"meshes":[{"name":"Mesh_0","position":[0,0],"rotation":0,"particles":[{"position":[-63.999985,-80.000008],"radius":0,"is_internal":false},{"position":[64.000015,-80.000015],"radius":0,"is_internal":false},{"position":[-31.999985,-80.000008],"radius":0,"is_internal":false},{"position":[32.000015,-80.000008],"radius":0,"is_internal":false},{"position":[-47.999985,80.000008],"radius":0,"is_internal":false},{"position":[-31.999985,79.999992],"radius":0,"is_internal":false},{"position":[32.000015,79.999992],"radius":0,"is_internal":false},{"position":[48.000019,80.000008],"radius":0,"is_internal":false},{"position":[0.000015,80],"radius":0,"is_internal":false},{"position":[0.000015,48.000008],"radius":0,"is_internal":false},{"position":[32.000015,48.000008],"radius":0,"is_internal":false},{"position":[-31.999985,48],"radius":0,"is_internal":false},{"position":[-31.999985,16],"radius":0,"is_internal":false},{"position":[-31.999985,-15.999999],"radius":0,"is_internal":false},{"position":[-31.999985,-48],"radius":0,"is_internal":false},{"position":[-63.999985,-48],"radius":0,"is_internal":false},{"position":[-63.999985,-15.999999],"radius":0,"is_internal":false},{"position":[-63.999985,16],"radius":0,"is_internal":false},{"position":[-63.999985,48],"radius":0,"is_internal":false},{"position":[32.000015,-48],"radius":0,"is_internal":false},{"position":[64.000015,-48],"radius":0,"is_internal":false},{"position":[32.000015,-15.999999],"radius":0,"is_internal":false},{"position":[64,-15.999999],"radius":0,"is_internal":false},{"position":[32.000015,16],"radius":0,"is_internal":false},{"position":[64,15.999998],"radius":0,"is_internal":false},{"position":[64.000015,48],"radius":0,"is_internal":false}],"springs":[[4,18],[18,17],[17,16],[16,15],[0,2],[2,14],[0,15],[14,13],[13,12],[12,11],[11,9],[9,10],[23,21],[10,23],[21,19],[19,3],[3,1],[1,20],[20,22],[22,22],[22,24],[24,25],[25,7],[7,6],[6,8],[8,5],[5,4]],"internal_springs":[[2,15],[0,14],[14,16],[15,13],[13,17],[16,12],[12,18],[17,11],[11,5],[18,11],[11,4],[5,18],[5,9],[11,8],[9,6],[8,10],[10,25],[6,10],[10,7],[6,25],[24,10],[23,25],[23,24],[23,22],[21,24],[21,22],[21,20],[19,22],[19,20],[19,1],[3,20],[15,14],[16,13],[17,12]],"polygons":[[3,1,20,22,24,25,7,6,8,5,4,18,17,16,15,0,2,14,13,12,11,9,10,23,21,19]]}],"reference_image_location":"","reference_image_position":[0,0],"reference_image_alpha":0.117647,"grid_size":[16,16],"snap_to_grid":true} \ No newline at end of file +{"meshes":[{"name":"Mesh_0","position":[0,0],"rotation":0,"particles":[{"position":[-63.999985,-80.000008],"radius":0,"is_internal":false},{"position":[64.000015,-80.000015],"radius":0,"is_internal":false},{"position":[-32,-80],"radius":0,"is_internal":false},{"position":[32.000015,-80.000008],"radius":0,"is_internal":false},{"position":[-47.999985,80.000008],"radius":0,"is_internal":false},{"position":[-31.999985,79.999992],"radius":0,"is_internal":false},{"position":[32.000015,79.999992],"radius":0,"is_internal":false},{"position":[48.000019,80.000008],"radius":0,"is_internal":false},{"position":[0.000015,80],"radius":0,"is_internal":false},{"position":[0.000015,48.000008],"radius":0,"is_internal":false},{"position":[32.000015,48.000008],"radius":0,"is_internal":false},{"position":[-31.999985,48],"radius":0,"is_internal":false},{"position":[-31.999985,16],"radius":0,"is_internal":false},{"position":[-31.999985,-15.999999],"radius":0,"is_internal":false},{"position":[-31.999985,-48],"radius":0,"is_internal":false},{"position":[-63.999985,-48],"radius":0,"is_internal":false},{"position":[-63.999985,-15.999999],"radius":0,"is_internal":false},{"position":[-63.999985,16],"radius":0,"is_internal":false},{"position":[-63.999985,48],"radius":0,"is_internal":false},{"position":[32.000015,-48],"radius":0,"is_internal":false},{"position":[64.000015,-48],"radius":0,"is_internal":false},{"position":[32.000015,-15.999999],"radius":0,"is_internal":false},{"position":[64,-15.999999],"radius":0,"is_internal":false},{"position":[32.000015,16],"radius":0,"is_internal":false},{"position":[64,15.999998],"radius":0,"is_internal":false},{"position":[64.000015,48],"radius":0,"is_internal":false}],"springs":[[4,18],[18,17],[17,16],[16,15],[0,2],[2,14],[0,15],[14,13],[13,12],[12,11],[11,9],[9,10],[23,21],[10,23],[21,19],[19,3],[3,1],[1,20],[20,22],[22,22],[22,24],[24,25],[25,7],[7,6],[6,8],[8,5],[5,4]],"internal_springs":[[2,15],[0,14],[14,16],[15,13],[13,17],[16,12],[12,18],[17,11],[11,5],[18,11],[11,4],[5,18],[5,9],[11,8],[9,6],[8,10],[10,25],[6,10],[10,7],[6,25],[24,10],[23,25],[23,24],[23,22],[21,24],[21,22],[21,20],[19,22],[19,20],[19,1],[3,20],[15,14],[16,13],[17,12]],"polygon":[0,2,14,13,12,11,9,10,23,21,19,3,1,20,22,24,25,7,6,8,5,4,18,17,16,15]}],"reference_image_location":"","reference_image_position":[0,0],"reference_image_alpha":0.117647,"grid_size":[16,16],"snap_to_grid":true} \ No newline at end of file diff --git a/resources/robotoFont.hpp.gch b/resources/robotoFont.hpp.gch index 28156bd..178fc29 100644 Binary files a/resources/robotoFont.hpp.gch and b/resources/robotoFont.hpp.gch differ