diff 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 diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/projects/tank_wars/render.cpp	Thu Nov 13 15:58:08 2014 +0100
@@ -0,0 +1,275 @@
+/*
+ * 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();
+}