#include "pixelGameEngine.h" using namespace olc; #define WIDTH 640 #define HEIGHT 480 struct Ball{ vf2d pos={}; vf2d vel={}; vf2d acc={}; vf2d originalPos={}; float radius=0; float mass=0; float simTimeRemaining=0; }; struct Line{ vf2d startPos={}; vf2d endPos={}; float radius=0; float bounceFactor=0.8; float bumperLeft=0; float bumperRight=0; bool oneWay=false; }; const float PI=3.14159f; class MiniGolfPro : public olc::PixelGameEngine { public: std::vectorballs; std::vectorlines; Ball*selectedBall; Line*selectedLine; std::vector>collidingPairs; std::vectorfakeBalls; bool selectedLineStart=false; float friction=0.9f; float gravity=200.f; float limit=500.f; Line*leftFlipperLine,*rightFlipperLine; float leftFlipper=PI/4; float rightFlipper=3*PI/4; MiniGolfPro() { sAppName = "Mini Golf Pro"; } void AddBall(vf2d pos,float r){ Ball b; b.pos=pos; b.radius=r; b.mass=r*10; b.vel.y=rand()%1000-17000; balls.emplace_back(b); } void AddLine(vf2d pos1,vf2d pos2,float r,float bounceFactor,bool oneway=false){ Line l; l.startPos=pos1; l.endPos=pos2; l.radius=r; l.bounceFactor=bounceFactor; l.oneWay=oneway; lines.push_back(l); } void AddTriangle(vf2d center,float size,float radius,float bounceFactor,bool flip=false){ AddLine(center+vf2d{-size,size}*(flip?-1:1),center+vf2d{0,-size}*(flip?-1:1),radius,bounceFactor); AddLine(center+vf2d{0,-size}*(flip?-1:1),center+vf2d{size,size}*(flip?-1:1),radius,bounceFactor); AddLine(center+vf2d{size,size}*(flip?-1:1),center+vf2d{-size,size}*(flip?-1:1),radius,bounceFactor); } bool isLeft(Line line, vf2d point) { return (line.endPos.x - line.startPos.x)*(point.y - line.startPos.y) - (line.endPos.y - line.startPos.y)*(point.x - line.startPos.x) > 0; } public: bool OnUserCreate() override { AddBall({610,620},rand()%4+8); AddLine({0,630},{640,630},10,0.8); ConsoleCaptureStdOut(true); for(int x=0;x<320;x+=20){ AddLine({float(x+320),(1.f/18000)*std::abs(powf(float(x),2.6f))},{x+20+320.f,(1.f/18000)*std::abs(powf(float(x+20),2.6f))},10,0.4); if(x<260){ AddLine({float(-x+320),(1.f/18000)*std::abs(powf(float(x),2.6f))},{float(-x-20+320),(1.f/18000)*std::abs(powf(float(x+20),2.6f))},10,0.4); } } for(int x=0;x<260;x+=20){ AddLine({float(x+320),(1.f/12000)*std::abs(powf(float(x),2.6f))+60},{x+20+320.f,(1.f/12000)*std::abs(powf(float(x+20),2.6f))+60},10,0.4); if(x<150){ AddLine({float(-x+320),(1.f/12000)*std::abs(powf(float(x),2.6f))+60},{float(-x-20+320),(1.f/12000)*std::abs(powf(float(x+20),2.6f))+60},10,0.4); } } AddLine({640,190},{640,640},10,0.8); AddLine({580,220},{580,640},10,0.8); AddLine({60,100},{100,290},15,1.5); AddLine({110,290},{120,560},5,0.8); AddLine({158,392},{196,503},5,0.8); AddLine({196,503},{258,545},5,0.8); AddLine({161,104},{81,86},2,0.4,true); AddTriangle({250,220},40,10,1.5); AddTriangle({350,160},40,10,1.5,true); AddTriangle({450,220},40,10,1.5); AddLine({535,392},{497,503},5,0.8); AddLine({497,503},{435,545},5,0.8); AddLine({258,545},{258,545},8,0.4); AddLine({435,545},{435,545},8,0.4); leftFlipperLine=&lines[lines.size()-2]; rightFlipperLine=&lines[lines.size()-1]; return true; } bool OnUserUpdate(float fElapsedTime) override { auto DoCirclesOverlap = [](vf2d pos1, float r1, vf2d pos2, float r2){ return fabs((pos1.x - pos2.x)*(pos1.x - pos2.x) + (pos1.y - pos2.y)*(pos1.y - pos2.y)) <= (r1 + r2)*(r1 + r2); }; auto IsPointInCircle = [](vf2d pos, float r, vf2d point){ return fabs((pos.x - point.x)*(pos.x - point.x) + (pos.y - point.y)*(pos.y - point.y)) < (r * r); }; if (GetMouse(0).bPressed){ selectedBall = nullptr; for (Ball&b:balls){ if (IsPointInCircle(b.pos,b.radius,GetMousePos())) { selectedBall = &b; break; } } selectedLine=nullptr; for(Line&l:lines){ if(IsPointInCircle(l.startPos,l.radius,GetMousePos())){ selectedLine=&l; selectedLineStart=true; break; } if(IsPointInCircle(l.endPos,l.radius,GetMousePos())){ selectedLine=&l; selectedLineStart=false; break; } } } if(GetMouse(0).bHeld){ if(selectedLine!=nullptr){ if(selectedLineStart){ selectedLine->startPos=GetMousePos(); }else{ selectedLine->endPos=GetMousePos(); } } } if(GetMouse(0).bReleased){ if(selectedLine!=nullptr){ std::cout<startPos<<"/"<endPos<vel.x=5*((selectedBall->pos.x)-GetMouseX()); selectedBall->vel.y=5*((selectedBall->pos.y)-GetMouseY()); } selectedBall=nullptr; selectedLine=nullptr; } if(GetMouse(1).bHeld){ for(Ball&b:balls){ b.vel.x+=(GetMouseX()-b.pos.x)*0.01f; b.vel.y+=(GetMouseY()-b.pos.y)*0.01f; } } if(GetKey(F1).bPressed){ ConsoleShow(F1); } Clear(BLACK); float stable=0.05f; int simulationUpdates=4; int maxSimulationSteps=15; float simElapsedTime=fElapsedTime/(float)simulationUpdates; leftFlipperLine->endPos=vf2d{std::cosf(leftFlipper)*64,std::sinf(leftFlipper)*64}+leftFlipperLine->startPos; rightFlipperLine->endPos=vf2d{std::cosf(rightFlipper)*64,std::sinf(rightFlipper)*64}+rightFlipperLine->startPos; if(GetKey(SPACE).bPressed){ AddBall({610,620},rand()%4+8); } if(GetKey(A).bHeld){ leftFlipper=std::max(-PI/4,leftFlipper-8*PI*fElapsedTime); if(leftFlipper!=-PI/4){ leftFlipperLine->bounceFactor=2; } else { leftFlipperLine->bounceFactor=std::max(0.4f,leftFlipperLine->bounceFactor-2*fElapsedTime); } } else { leftFlipper=std::min(PI/4,leftFlipper+8*PI*fElapsedTime); leftFlipperLine->bounceFactor=0.4; } if(GetKey(L).bHeld){ rightFlipper=std::min(5*PI/4,rightFlipper+8*PI*fElapsedTime); if(rightFlipper!=5*PI/4){ rightFlipperLine->bounceFactor=2; } else { rightFlipperLine->bounceFactor=std::max(0.4f,rightFlipperLine->bounceFactor-2*fElapsedTime); } } else { rightFlipper=std::max(3*PI/4,rightFlipper-8*PI*fElapsedTime); rightFlipperLine->bounceFactor=0.4; } for(int i=0;i0){ b.originalPos.x=b.pos.x; b.originalPos.y=b.pos.y; b.acc.x=-b.vel.x*friction; b.acc.y=-b.vel.y*friction+gravity; //Gravity constant; b.vel.x+=b.acc.x*b.simTimeRemaining; b.vel.y+=b.acc.y*b.simTimeRemaining; b.pos.x+=b.vel.x*b.simTimeRemaining; b.pos.y+=b.vel.y*b.simTimeRemaining; if(b.pos.x<0){ b.pos.x+=ScreenWidth(); } if(b.pos.y<0){ b.pos.y+=ScreenHeight(); } if(b.pos.x>=ScreenWidth()){ b.pos.x-=ScreenWidth(); } if(b.pos.y>=ScreenHeight()){ b.pos.y-=ScreenHeight(); } if(fabs(b.vel.x*b.vel.x+b.vel.y*b.vel.y)radius=l.radius; fakeBall->mass=b.mass*l.bounceFactor; fakeBall->pos={closestPoint.x,closestPoint.y}; fakeBall->vel={std::min(limit,std::max(-limit,-b.vel.x)),std::min(limit,std::max(-limit,-b.vel.y))}; fakeBalls.push_back(fakeBall); collidingPairs.push_back({&b,fakeBall}); float overlap=1.1f*(dist-b.radius-fakeBall->radius); b.pos.x-=overlap*(b.pos.x-fakeBall->pos.x)/dist; b.pos.y-=overlap*(b.pos.y-fakeBall->pos.y)/dist; if(isLeft(l,b.pos)){ l.bumperLeft=1; } else { l.bumperRight=1; } } } } for(Ball&b2:balls){ if(&b!=&b2){ if(DoCirclesOverlap(b.pos,b.radius,b2.pos,b2.radius)){ collidingPairs.push_back({&b,&b2}); float dist=sqrtf((b.pos.x-b2.pos.x)*(b.pos.x-b2.pos.x)+(b.pos.y-b2.pos.y)*(b.pos.y-b2.pos.y)); float overlap=0.5f*(dist-b.radius-b2.radius); b.pos.x-=overlap*(b.pos.x-b2.pos.x)/dist; b.pos.y-=overlap*(b.pos.y-b2.pos.y)/dist; b2.pos.x+=overlap*(b.pos.x-b2.pos.x)/dist; b2.pos.y+=overlap*(b.pos.y-b2.pos.y)/dist; } } } float intendedSpeed=sqrtf(b.vel.x+b.vel.y*b.vel.y); float intendedDist=intendedSpeed*b.simTimeRemaining; float actualDist=sqrtf((b.pos.x-b.originalPos.x)*(b.pos.x-b.originalPos.x)+(b.pos.y-b.originalPos.y)*(b.pos.y-b.originalPos.y)); float actualTime=actualDist/intendedSpeed; b.simTimeRemaining=b.simTimeRemaining-actualTime; } float efficiency=1; for(std::pair&pair:collidingPairs){ Ball*b1=pair.first; Ball*b2=pair.second; float dist=sqrtf((b1->pos.x-b2->pos.x)*(b1->pos.x-b2->pos.x)+(b1->pos.y-b2->pos.y)*(b1->pos.y-b2->pos.y)); vf2d normal={(b2->pos.x-b1->pos.x)/dist,(b2->pos.y-b1->pos.y)/dist}; vf2d tangent={-normal.y,normal.x}; vf2d dpTangent={tangent.dot(b1->vel),tangent.dot(b2->vel)}; vf2d dpNormal={normal.dot(b1->vel),normal.dot(b2->vel)}; vf2d momentum={efficiency*(dpNormal.x*(b1->mass-b2->mass)+2.f*b2->mass*dpNormal.y)/(b1->mass+b2->mass),efficiency*(dpNormal.y*(b2->mass-b1->mass)+2.f*b1->mass*dpNormal.x)/(b1->mass+b2->mass)}; b1->vel.x=tangent.x*dpTangent.x+normal.x*momentum.x; b1->vel.y=tangent.y*dpTangent.x+normal.y*momentum.x; b2->vel.x=tangent.x*dpTangent.y+normal.x*momentum.y; b2->vel.y=tangent.y*dpTangent.y+normal.y*momentum.y; } collidingPairs.clear(); for(Ball*fb:fakeBalls){ delete fb; } fakeBalls.clear(); } } for(Ball&b:balls){ if(selectedBall==&b){ FillCircle(b.pos,b.radius,YELLOW); } else { FillCircle(b.pos,b.radius); } } for(Line&l:lines){ FillCircle(l.startPos,l.radius); FillCircle(l.endPos,l.radius); vf2d normal={-(l.endPos.y-l.startPos.y),l.endPos.x-l.startPos.x}; float dist=sqrt(normal.x*normal.x+normal.y*normal.y); normal/=dist; l.bumperLeft=std::max(0.f,l.bumperLeft-fElapsedTime); l.bumperRight=std::max(0.f,l.bumperRight-fElapsedTime); if(l.bumperLeft){ SetPixelMode(Pixel::ALPHA); for(int i=0;i<3;i++){ DrawLine(l.startPos.x+normal.x*(l.radius+i),l.startPos.y+normal.y*(l.radius+i),l.endPos.x+normal.x*(l.radius+i),l.endPos.y+normal.y*(l.radius+i),{255,0,0,uint8_t((4-i)/4.f*255)}); } SetPixelMode(Pixel::NORMAL); } else { DrawLine(l.startPos.x+normal.x*l.radius,l.startPos.y+normal.y*l.radius,l.endPos.x+normal.x*l.radius,l.endPos.y+normal.y*l.radius,l.oneWay?DARK_YELLOW:WHITE); } if(l.bumperRight){ SetPixelMode(Pixel::ALPHA); for(int i=0;i<3;i++){ DrawLine(l.startPos.x-normal.x*(l.radius+i),l.startPos.y-normal.y*(l.radius+i),l.endPos.x-normal.x*(l.radius+i),l.endPos.y-normal.y*(l.radius+i),{0,0,255,uint8_t((4-i)/4.f*255)}); } SetPixelMode(Pixel::NORMAL); } else { DrawLine(l.startPos.x-normal.x*l.radius,l.startPos.y-normal.y*l.radius,l.endPos.x-normal.x*l.radius,l.endPos.y-normal.y*l.radius,l.oneWay?DARK_YELLOW:WHITE); } } return true; } bool OnUserDestroy()override{ return true; } }; int main() { MiniGolfPro demo; if (demo.Construct(640, 640, 4, 4)) demo.Start(); return 0; }