|
|
|
#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::vector<Ball>balls;
|
|
|
|
std::vector<Line>lines;
|
|
|
|
Ball*selectedBall;
|
|
|
|
Line*selectedLine;
|
|
|
|
std::vector<std::pair<Ball*,Ball*>>collidingPairs;
|
|
|
|
std::vector<Ball*>fakeBalls;
|
|
|
|
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<<selectedLine->startPos<<"/"<<selectedLine->endPos<<std::endl;
|
|
|
|
}
|
|
|
|
if(selectedBall!=nullptr){
|
|
|
|
selectedBall->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;i<simulationUpdates;i++){
|
|
|
|
for(Ball&b:balls){
|
|
|
|
b.simTimeRemaining=simElapsedTime;
|
|
|
|
}
|
|
|
|
for(int j=0;j<maxSimulationSteps;j++){
|
|
|
|
for(Ball&b:balls){
|
|
|
|
if(b.simTimeRemaining>0){
|
|
|
|
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)<stable){
|
|
|
|
b.vel.x=0;
|
|
|
|
b.vel.y=0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for(Ball&b:balls){
|
|
|
|
float deltaTime=b.simTimeRemaining;
|
|
|
|
for(Line&l:lines){
|
|
|
|
if(!l.oneWay||l.oneWay&&b.vel.y<0){
|
|
|
|
vf2d line1={l.endPos.x-l.startPos.x,l.endPos.y-l.startPos.y};
|
|
|
|
vf2d line2={b.pos.x-l.startPos.x,b.pos.y-l.startPos.y};
|
|
|
|
float edgeLength=line1.x*line1.x+line1.y*line1.y;
|
|
|
|
float t=std::max(0.f,std::min(edgeLength,(line1.x*line2.x+line1.y*line2.y)))/edgeLength;
|
|
|
|
vf2d closestPoint={l.startPos.x+t*line1.x,l.startPos.y+t*line1.y};
|
|
|
|
float dist=sqrtf((b.pos.x-closestPoint.x)*(b.pos.x-closestPoint.x)+(b.pos.y-closestPoint.y)*(b.pos.y-closestPoint.y));
|
|
|
|
if(dist<=b.radius+l.radius){
|
|
|
|
Ball*fakeBall=new Ball();
|
|
|
|
fakeBall->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<Ball*,Ball*>&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;
|
|
|
|
}
|