Particle Cannon

3d particle cannon with physics using OpenGL and C++.

to build 👉 g++ -framework OpenGL -framework GLUT -o cannon cannon.cpp
to run 👉 ./cannon

Modes

  • Q: Quit
  • R: Reset
  • F: Manual Firing
  • O: Single Shot
  • K: Spin
  • M: Friction
  • L: Lighting
  • C: Culling
  • B: Bouncy

Code

using namespace std;
#ifdef __APPLE_CC__
#include <GLUT/glut.h>
#else
#include <GL/glut.h>
#endif
#include <stdio.h>
#include <stdlib.h>
#include <vector>
#include <iostream>
#include <math.h>
#include <sys/types.h>
#include <sys/timeb.h>
#include <time.h>

// Cameron Cronheimer 
// Aman Braich 
// g++ -framework OpenGL -framework GLUT -o cannon cannon.cpp
// ./cannon

int redrawRate = 60;

bool stream = true;
bool spray, randomSpeed, light, friction, size, spin, cull, bouncy, lightShow;

int leftEdge = -20;
int rightEdge = 20;

// the particle object
struct particle
{
	// position
	double x, y, z;
	// acceleration
	double ax, ay, az;
	double dax, day, daz;
	// velocity
	double vx, vy, vz;
};

vector<particle> particles; // particles models

void createParticle()
{
	particle p; // particle object reference

	p.x = 0;
	p.y = 6; //starting pos (tip of the cannon)
	p.z = 0;

	// default speed (velocity)
	// "velocity is the direction the particle is going"
	p.vx = -10 + (rand() % 20); // -10 to 10
	p.vz = -10 + (rand() % 20);
	p.vy = 35; // constant y

	// acceleration
	float gravity = 60; // high gravity so we can have a high velocity

	// spin mode
	// simulates spinning by adjusting acceleration
	if (spin)
	{
		p.ax += -180 + (rand() % 360); // offset spin + increment thats updated in the loop
		p.ay = -gravity;
		p.az += -180 + (rand() % 360);
	}
	else
	{
		p.ax = 0;
		p.ay = -gravity;
		p.az = 0;
	}

	// random randomSpeed
	if (randomSpeed)
	{
		p.vx *= -10 + (rand() % 20); // -10 to 10
		p.vz *= -10 + (rand() % 20); // -10 to 10
	}

	particles.push_back(p);
}

void initDisplay(void)
{
	glMatrixMode(GL_MODELVIEW);
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();

	gluPerspective(60.0f, 1.0f, 1.0f, 100.0f); // perspective camera 60
	gluLookAt(35, 50, 35, 0, 0, 0, 0, 1, 0);   // move camera
}

void draw()
{
	initDisplay();

	glClear(GL_COLOR_BUFFER_BIT);

	// lighting
	if (light)
	{
		glEnable(GL_LIGHTING);
		glEnable(GL_LIGHT0);
	}
	else
	{
		glDisable(GL_LIGHTING);
		glDisable(GL_LIGHT0);
	}

	// culling
	if (cull)
	{
		glEnable(GL_CULL_FACE);
		glCullFace(GL_BACK);
		glFrontFace(GL_CW);
	}
	else
	{
		glDisable(GL_CULL_FACE);
	}

	// the floor
	glBegin(GL_POLYGON);
	glNormal3d(0, 0, 1);
	glColor3f(0.0, 0.5, 0.0); // green floor
	glVertex3f(leftEdge, 1, leftEdge);
	glVertex3f(leftEdge, 1, rightEdge);
	glVertex3f(rightEdge, 1, rightEdge);
	glVertex3f(rightEdge, 1, leftEdge);
	glEnd();

	glColor3f(0.58f, 0.25f, 0.15f); // volcano colour
	glPushMatrix();					// saves
	glTranslatef(0, 1.0f, 0);
	glRotatef(270.0f, 1.0f, 0, 0);
	glutSolidCone(5, 6, 20, 20); // volcano
	glPopMatrix();

	for (int i = 0; i < particles.size(); i++)
	{

		particle p = particles[i];
		glPushMatrix();
		glTranslatef(p.x, p.y, p.z);
		glutSolidSphere(0.5f, 20.0f, 20.0f); // lava ball

		if (lightShow)
		{
			// random colors
			glColor3f((float)rand() / (float)RAND_MAX, (float)rand() / (float)RAND_MAX, (float)rand() / (float)RAND_MAX);
		}
		else
		{
			// lava colour
			glColor3f(0.8f, 0, 0);
		}

		glPopMatrix();
	}

	glutSwapBuffers();
	glFlush();

	// default stream
	if (stream)
	{
		createParticle();
	}
}

void loop(int val)
{

	float bounceVal;

	if (bouncy)
	{
		bounceVal = 0.08f;
	}
	else
	{
		bounceVal = 0.02f;
	}

	for (int i = 0; i < particles.size(); i++)
	{

		if (particles[i].y > -100)
		{
			// adjust all particles speed for bouncing
			particles[i].vx += particles[i].ax * bounceVal;
			particles[i].x += particles[i].vx * bounceVal;

			particles[i].vy += particles[i].ay * bounceVal;
			particles[i].y += particles[i].vy * bounceVal;

			particles[i].vz += particles[i].az * bounceVal;
			particles[i].z += particles[i].vz * bounceVal;

			// if the particle is in range of the platform to bounce
			if (particles[i].y < 0 && particles[i].x <= rightEdge && particles[i].z <= rightEdge && particles[i].x >= leftEdge && particles[i].z >= leftEdge)
			{
				particles[i].y = 0;
				particles[i].vy = -particles[i].vy * 0.8f; // "0.8f" slows slows down bouncing so doesnt bounce same height
			}

			// spin
			if (spin)
			{
				// keep particle rot between -360 and 360
				if (particles[i].ax >= -360 && particles[i].ax <= 360)
				{
					particles[i].ax += rand() % 1 + -1; // dax
				}
				// keep particle rot between -360 and 360
				if (particles[i].az >= -360 && particles[i].az <= 360)
				{
					particles[i].az += rand() % 1 + -1; // daz
				}
			}
			// friction
			if (friction)
			{
				if (particles[i].y == 0)
				{
					particles[i].vx *= 0.95f;
					particles[i].vz *= 0.95f;
				}
			}
		}
		else // if particle y is below -100 delete (abyss)
		{
			particles.erase(particles.begin() + i);
		}
	}

	glutPostRedisplay();
	glutTimerFunc(1000.0f / redrawRate, loop, 0);
}

// reset everything
void reset(void)
{
	randomSpeed = false;
	light = false;
	cull = false;
	friction = false;
	spin = false;
	bouncy = false;
	lightShow = false;
	particles.clear();
}

// keyboard events
void keyboard(unsigned char key, int x, int y)
{
	switch (key)
	{
	case 0x1B:
	case 'q':
		// quit
		exit(0);
		break;
	case 'r':
		// reset
		cout << "\nReset";
		stream = true;
		reset();
		break;

	case 'f':
		// manual burst mode
		cout << "\nBurst";
		if (stream)
		{
			stream = false;
			reset(); // clear up the enviroment
		}
		// 20 particles
		for (int i = 0; i < 20; i++)
		{
			createParticle();
		}

		break;
	case 'o':
		// single shot mode
		cout << "\nSingle Shot";
		if (stream)
		{
			stream = false;
			reset(); // clear up the enviroment
		}
		createParticle();
		break;

	case 's':
		// random speed spread mode
		cout << "\nRandom Speed Mode";
		randomSpeed = true;
		break;

	case 'k':
		// random spin mode
		spin = true;
		stream = true;
		break;
	case 'm':
		// friction mode
		stream = true;
		friction = true;
		cout << "\nFriction Mode";
		break;

	case 'l':
		// lighting mode
		cout << "\nLighting Mode";
		light = true;
		break;
	case 'c':
		// culling mode
		cout << "\nCulling Mode";
		cull = true;
		break;
	case 'b':
		// bounce mode
		bouncy = true;
		break;
	case 'h':
		// lightshow mode
		lightShow = true;
		break;
	}

} //keyboard
void info(void)
{
	printf("----------MODES----------\n");
	printf("Q: Quit\n");
	printf("R: Reset\n");
	printf("F: Manual Firing\n");
	printf("O: Single Shot\n");
	printf("K: Spin\n");
	printf("M: Friction\n");
	printf("L: Lighting\n");
	printf("C: Culling\n");
	printf("B: Bouncy\n");
	printf("H: Light Mode\n");
}

int main(int argc, char **argv)
{
	// start
	glutInit(&argc, argv);
	glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE);
	glutInitWindowSize(720, 720);
	glutInitWindowPosition(50, 50);
	glutCreateWindow("Particle Volcano");

	// print menu
	info();

	glClearColor(1.0f, 1.0f, 1.0f, 0); // white background

	glutKeyboardFunc(keyboard);
	glutDisplayFunc(draw);

	glutTimerFunc(1000.0f / redrawRate, loop, 0);
	glutMainLoop();

	return 0;
}

Screenshots