view projects/tank_wars/render.cpp @ 10:49f22e1246b2

Tank wars!
author andrewm
date Thu, 13 Nov 2014 15:58:08 +0100
parents
children 06f93bef7dd2
line wrap: on
line source
/*
 * render.cpp
 *
 *  Created on: Oct 24, 2014
 *      Author: parallels
 */


#include "../../include/RTAudio.h"
#include "../../include/Utilities.h"
#include "game.h"
#include <rtdk.h>
#include <cmath>
#include <cstdlib>
#include <time.h>

float gFrequency;
float gPhase;
float gInverseSampleRate;
int gNumChannels;

int gInputTank1Angle = 0;		// Inputs for the cannon angles
int gInputTank2Angle = 1;
int gInputLauncher = 2;			// Input for launcher FSR

int gOutputX = 0;				// Outputs for the scope
int gOutputY = 1;
int gOutputPlayer1LED = 2;
int gOutputPlayer2LED = 3;

int gGameFramesPerSecond = 60;	// How often the physics are updated
int gGameFrameInterval;			// ...and in frames
int gSamplesUntilNextFrame;		// Counter until next update
int gSamplesSinceFinish = 0;	// How long since somebody won?
bool gGameShouldRestart = false;// Whether we need to reinitiliase the game

// Counter for overall number of samples that have elapsed
unsigned int gSampleCounter = 0;

// 1st-order filter and peak detector for launcher input
float gLauncherLastSample = 0;
float gLauncherFilterPole = 0.8;
float gLauncherPeakValue = 0;
float gLauncherPeakFilterPole = 0.999;
float gLauncherNoiseThreshold = 0.01 * MATRIX_MAX;
float gLauncherMinimumPeak = 0.1 * MATRIX_MAX;
bool gLauncherTriggered = false;

// Screen update rate; affects buffer size. Actual contents of buffer
// may be smaller than this
int gScreenWidth = 512;
int gScreenHeight = 512;
int gScreenFramesPerSecond = 25;

// Double buffer for rendering screen. Each is an interleaved buffer
// of XY data.
float *gScreenBuffer1, *gScreenBuffer2;
float *gScreenBufferWrite, *gScreenBufferRead;
int gScreenBufferMaxLength;				// What is the total buffer allocated?
int gScreenBufferReadLength;			// How long is the read buffer?
int gScreenBufferWriteLength;			// How long is the write (next) buffer?
int gScreenBufferReadPointer;			// Where are we in the read buffer now?
int gScreenBufferNextUpdateLocation;	// When should we render the next buffer?
bool gScreenNextBufferReady;			// Is the next buffer ready to go?

// Auxiliary (low-priority) task for updating the screen
AuxiliaryTask gScreenUpdateTask;

void screen_update();

// initialise_render() is called once before the audio rendering starts.
// Use it to perform any initialisation and allocation which is dependent
// on the period size or sample rate.
//
// userData holds an opaque pointer to a data structure that was passed
// in from the call to initAudio().
//
// Return true on success; returning false halts the program.

bool initialise_render(int numChannels, int numMatrixFramesPerPeriod,
					   int numAudioFramesPerPeriod, float matrixSampleRate,
					   float audioSampleRate, void *userData)
{
	srandom(time(NULL));

	// Verify we are running with matrix enabled
	if(numMatrixFramesPerPeriod*2 != numAudioFramesPerPeriod) {
		rt_printf("Error: this example needs the matrix enabled, running at half audio rate\n");
		return false;
	}

	// Initialise the screen buffers
	gScreenBufferMaxLength = 2 * matrixSampleRate / gScreenFramesPerSecond;
	gScreenBuffer1 = new float[gScreenBufferMaxLength];
	gScreenBuffer2 = new float[gScreenBufferMaxLength];
	if(gScreenBuffer1 == 0 || gScreenBuffer2 == 0) {
		rt_printf("Error initialising screen buffers\n");
		return false;
	}

	gScreenBufferRead = gScreenBuffer1;
	gScreenBufferWrite = gScreenBuffer2;
	gScreenBufferReadLength = gScreenBufferWriteLength = 0;
	gScreenBufferReadPointer = 0;
	gScreenBufferNextUpdateLocation = 0;
	gScreenNextBufferReady = false;

	// Initialise the game
	setupGame(gScreenWidth, gScreenHeight);
	gGameFrameInterval = matrixSampleRate / gGameFramesPerSecond;
	gSamplesUntilNextFrame = gGameFrameInterval;

	// Initialise auxiliary tasks
	if((gScreenUpdateTask = createAuxiliaryTaskLoop(&screen_update, 90,
													"beaglert-screen-update")) == 0)
		return false;

	return true;
}

// Swap buffers on the screen
void swap_buffers()
{
	if(gScreenBufferRead == gScreenBuffer1) {
		gScreenBufferRead = gScreenBuffer2;
		gScreenBufferWrite = gScreenBuffer1;
	}
	else {
		gScreenBufferRead = gScreenBuffer1;
		gScreenBufferWrite = gScreenBuffer2;
	}

	gScreenBufferReadLength = gScreenBufferWriteLength;
	gScreenBufferReadPointer = 0;

	// Schedule next update for 3/4 of the way through the buffer
	gScreenBufferNextUpdateLocation = gScreenBufferReadLength * 0.75;
	gScreenNextBufferReady = false;
}

// render() is called regularly at the highest priority by the audio engine.
// Input and output are given from the audio hardware and the other
// ADCs and DACs (if available). If only audio is available, numMatrixFrames
// will be 0.

void render(int numMatrixFrames, int numAudioFrames, float *audioIn, float *audioOut,
			uint16_t *matrixIn, uint16_t *matrixOut)
{
	for(int n = 0; n < numMatrixFrames; n++) {
		// First-order lowpass filter to remove noise on launch FSR
		float rawSample = analogRead(gInputLauncher, n);
		float launchSample = gLauncherFilterPole * gLauncherLastSample +
							(1.0f - gLauncherFilterPole) * rawSample;
		gLauncherLastSample = launchSample;

		// Peak-detect on launch signal
		if(launchSample >= gLauncherPeakValue) {
			gLauncherPeakValue = launchSample;
			gLauncherTriggered = false;
		}
		else {
			if(gLauncherPeakValue - launchSample > gLauncherNoiseThreshold && !gLauncherTriggered) {
				// Detected a peak; is it big enough overall?
				if(gLauncherPeakValue >= gLauncherMinimumPeak) {
					gLauncherTriggered = true;
					// Peak detected-- fire!!
					// Set both cannon strengths but only one will
					// fire depending on whose turn it is
					float strength = map(gLauncherPeakValue,
									     gLauncherMinimumPeak, MATRIX_MAX,
										 0.5f, 10.0f);
					setTank1CannonStrength(strength);
					setTank2CannonStrength(strength);
					fireProjectile();
				}
			}

			gLauncherPeakValue *= gLauncherPeakFilterPole;
		}

		if(--gSamplesUntilNextFrame <= 0) {
			// Update game physics and cannon angles
			gSamplesUntilNextFrame = gGameFrameInterval;

			setTank1CannonAngle(map(analogRead(gInputTank1Angle, n),
									0, MATRIX_MAX, M_PI, 0));
			setTank2CannonAngle(map(analogRead(gInputTank2Angle, n),
									0, MATRIX_MAX, M_PI, 0));
			nextGameFrame();
		}

		if(gScreenBufferReadPointer >= gScreenBufferReadLength - 1
			&& gScreenNextBufferReady) {
			// Got to the end; swap buffers
			swap_buffers();
		}

		// Push current screen buffer to the matrix output
		if(gScreenBufferReadPointer < gScreenBufferReadLength - 1) {
			float x = gScreenBufferRead[gScreenBufferReadPointer++];
			float y = gScreenBufferRead[gScreenBufferReadPointer++];

			// Rescale screen coordinates to matrix ranges; invert the Y
			// coordinate to go from normal screen coordinates to scope coordinates
			analogWrite(gOutputX, n, constrain(map(x, 0, gScreenWidth, 0, MATRIX_MAX), 0, MATRIX_MAX));
			analogWrite(gOutputY, n, constrain(map(y, 0, gScreenHeight, MATRIX_MAX, 0), 0, MATRIX_MAX));
		}
		else {
			// Still not ready! Write 0 until something happens
			analogWrite(gOutputX, n, 0);
			analogWrite(gOutputY, n, 0);
		}

		if(gameStatusWinner() != 0) {
			// Blink one LED to show who won
			// Blink both LEDs when projectile is in motion
			uint16_t val = (gSampleCounter % 4000 > 2000) ? MATRIX_MAX : 0;
			analogWrite(gOutputPlayer1LED, n, gameStatusWinner() == 1 ? val : 0);
			analogWrite(gOutputPlayer2LED, n, gameStatusWinner() == 2 ? val : 0);

			// After 5 seconds, restart the game
			gSamplesSinceFinish++;
			if(gSamplesSinceFinish > 22050*5)
				gGameShouldRestart = true;
		}
		else if(gameStatusProjectileInMotion()) {
			// Blink both LEDs when projectile is in motion
			uint16_t val = (gSampleCounter % 2000 > 1000) ? MATRIX_MAX : 0;
			analogWrite(gOutputPlayer1LED, n, val);
			analogWrite(gOutputPlayer2LED, n, val);
		}
		else if(gameStatusPlayer1Turn()) {
			analogWrite(gOutputPlayer1LED, n, MATRIX_MAX);
			analogWrite(gOutputPlayer2LED, n, 0);
		}
		else {
			analogWrite(gOutputPlayer2LED, n, MATRIX_MAX);
			analogWrite(gOutputPlayer1LED, n, 0);
		}

		// Check if we have reached the point where we should next update
		if(gScreenBufferReadPointer >= gScreenBufferNextUpdateLocation &&
		   !gScreenNextBufferReady) {
			// Update the screen at lower priority than the audio thread
			scheduleAuxiliaryTask(gScreenUpdateTask);
		}

		gSampleCounter++;
	}
}

void screen_update()
{
	// If we should restart, reinitialise the game
	if(gGameShouldRestart) {
		restartGame();
		gGameShouldRestart = false;
		gSamplesSinceFinish = 0;
	}

	// Render the game based on the current state
	gScreenBufferWriteLength = drawGame(gScreenBufferWrite, gScreenBufferMaxLength);

	// Flag it as ready to go
	gScreenNextBufferReady = true;
}

// cleanup_render() is called once at the end, after the audio has stopped.
// Release any resources that were allocated in initialise_render().

void cleanup_render()
{
	// Clean up the game state
	cleanupGame();
}