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

Tank wars!
author andrewm
date Thu, 13 Nov 2014 15:58:08 +0100
parents
children 06f93bef7dd2
rev   line source
andrewm@10 1 /*
andrewm@10 2 * render.cpp
andrewm@10 3 *
andrewm@10 4 * Created on: Oct 24, 2014
andrewm@10 5 * Author: parallels
andrewm@10 6 */
andrewm@10 7
andrewm@10 8
andrewm@10 9 #include "../../include/RTAudio.h"
andrewm@10 10 #include "../../include/Utilities.h"
andrewm@10 11 #include "game.h"
andrewm@10 12 #include <rtdk.h>
andrewm@10 13 #include <cmath>
andrewm@10 14 #include <cstdlib>
andrewm@10 15 #include <time.h>
andrewm@10 16
andrewm@10 17 float gFrequency;
andrewm@10 18 float gPhase;
andrewm@10 19 float gInverseSampleRate;
andrewm@10 20 int gNumChannels;
andrewm@10 21
andrewm@10 22 int gInputTank1Angle = 0; // Inputs for the cannon angles
andrewm@10 23 int gInputTank2Angle = 1;
andrewm@10 24 int gInputLauncher = 2; // Input for launcher FSR
andrewm@10 25
andrewm@10 26 int gOutputX = 0; // Outputs for the scope
andrewm@10 27 int gOutputY = 1;
andrewm@10 28 int gOutputPlayer1LED = 2;
andrewm@10 29 int gOutputPlayer2LED = 3;
andrewm@10 30
andrewm@10 31 int gGameFramesPerSecond = 60; // How often the physics are updated
andrewm@10 32 int gGameFrameInterval; // ...and in frames
andrewm@10 33 int gSamplesUntilNextFrame; // Counter until next update
andrewm@10 34 int gSamplesSinceFinish = 0; // How long since somebody won?
andrewm@10 35 bool gGameShouldRestart = false;// Whether we need to reinitiliase the game
andrewm@10 36
andrewm@10 37 // Counter for overall number of samples that have elapsed
andrewm@10 38 unsigned int gSampleCounter = 0;
andrewm@10 39
andrewm@10 40 // 1st-order filter and peak detector for launcher input
andrewm@10 41 float gLauncherLastSample = 0;
andrewm@10 42 float gLauncherFilterPole = 0.8;
andrewm@10 43 float gLauncherPeakValue = 0;
andrewm@10 44 float gLauncherPeakFilterPole = 0.999;
andrewm@10 45 float gLauncherNoiseThreshold = 0.01 * MATRIX_MAX;
andrewm@10 46 float gLauncherMinimumPeak = 0.1 * MATRIX_MAX;
andrewm@10 47 bool gLauncherTriggered = false;
andrewm@10 48
andrewm@10 49 // Screen update rate; affects buffer size. Actual contents of buffer
andrewm@10 50 // may be smaller than this
andrewm@10 51 int gScreenWidth = 512;
andrewm@10 52 int gScreenHeight = 512;
andrewm@10 53 int gScreenFramesPerSecond = 25;
andrewm@10 54
andrewm@10 55 // Double buffer for rendering screen. Each is an interleaved buffer
andrewm@10 56 // of XY data.
andrewm@10 57 float *gScreenBuffer1, *gScreenBuffer2;
andrewm@10 58 float *gScreenBufferWrite, *gScreenBufferRead;
andrewm@10 59 int gScreenBufferMaxLength; // What is the total buffer allocated?
andrewm@10 60 int gScreenBufferReadLength; // How long is the read buffer?
andrewm@10 61 int gScreenBufferWriteLength; // How long is the write (next) buffer?
andrewm@10 62 int gScreenBufferReadPointer; // Where are we in the read buffer now?
andrewm@10 63 int gScreenBufferNextUpdateLocation; // When should we render the next buffer?
andrewm@10 64 bool gScreenNextBufferReady; // Is the next buffer ready to go?
andrewm@10 65
andrewm@10 66 // Auxiliary (low-priority) task for updating the screen
andrewm@10 67 AuxiliaryTask gScreenUpdateTask;
andrewm@10 68
andrewm@10 69 void screen_update();
andrewm@10 70
andrewm@10 71 // initialise_render() is called once before the audio rendering starts.
andrewm@10 72 // Use it to perform any initialisation and allocation which is dependent
andrewm@10 73 // on the period size or sample rate.
andrewm@10 74 //
andrewm@10 75 // userData holds an opaque pointer to a data structure that was passed
andrewm@10 76 // in from the call to initAudio().
andrewm@10 77 //
andrewm@10 78 // Return true on success; returning false halts the program.
andrewm@10 79
andrewm@10 80 bool initialise_render(int numChannels, int numMatrixFramesPerPeriod,
andrewm@10 81 int numAudioFramesPerPeriod, float matrixSampleRate,
andrewm@10 82 float audioSampleRate, void *userData)
andrewm@10 83 {
andrewm@10 84 srandom(time(NULL));
andrewm@10 85
andrewm@10 86 // Verify we are running with matrix enabled
andrewm@10 87 if(numMatrixFramesPerPeriod*2 != numAudioFramesPerPeriod) {
andrewm@10 88 rt_printf("Error: this example needs the matrix enabled, running at half audio rate\n");
andrewm@10 89 return false;
andrewm@10 90 }
andrewm@10 91
andrewm@10 92 // Initialise the screen buffers
andrewm@10 93 gScreenBufferMaxLength = 2 * matrixSampleRate / gScreenFramesPerSecond;
andrewm@10 94 gScreenBuffer1 = new float[gScreenBufferMaxLength];
andrewm@10 95 gScreenBuffer2 = new float[gScreenBufferMaxLength];
andrewm@10 96 if(gScreenBuffer1 == 0 || gScreenBuffer2 == 0) {
andrewm@10 97 rt_printf("Error initialising screen buffers\n");
andrewm@10 98 return false;
andrewm@10 99 }
andrewm@10 100
andrewm@10 101 gScreenBufferRead = gScreenBuffer1;
andrewm@10 102 gScreenBufferWrite = gScreenBuffer2;
andrewm@10 103 gScreenBufferReadLength = gScreenBufferWriteLength = 0;
andrewm@10 104 gScreenBufferReadPointer = 0;
andrewm@10 105 gScreenBufferNextUpdateLocation = 0;
andrewm@10 106 gScreenNextBufferReady = false;
andrewm@10 107
andrewm@10 108 // Initialise the game
andrewm@10 109 setupGame(gScreenWidth, gScreenHeight);
andrewm@10 110 gGameFrameInterval = matrixSampleRate / gGameFramesPerSecond;
andrewm@10 111 gSamplesUntilNextFrame = gGameFrameInterval;
andrewm@10 112
andrewm@10 113 // Initialise auxiliary tasks
andrewm@10 114 if((gScreenUpdateTask = createAuxiliaryTaskLoop(&screen_update, 90,
andrewm@10 115 "beaglert-screen-update")) == 0)
andrewm@10 116 return false;
andrewm@10 117
andrewm@10 118 return true;
andrewm@10 119 }
andrewm@10 120
andrewm@10 121 // Swap buffers on the screen
andrewm@10 122 void swap_buffers()
andrewm@10 123 {
andrewm@10 124 if(gScreenBufferRead == gScreenBuffer1) {
andrewm@10 125 gScreenBufferRead = gScreenBuffer2;
andrewm@10 126 gScreenBufferWrite = gScreenBuffer1;
andrewm@10 127 }
andrewm@10 128 else {
andrewm@10 129 gScreenBufferRead = gScreenBuffer1;
andrewm@10 130 gScreenBufferWrite = gScreenBuffer2;
andrewm@10 131 }
andrewm@10 132
andrewm@10 133 gScreenBufferReadLength = gScreenBufferWriteLength;
andrewm@10 134 gScreenBufferReadPointer = 0;
andrewm@10 135
andrewm@10 136 // Schedule next update for 3/4 of the way through the buffer
andrewm@10 137 gScreenBufferNextUpdateLocation = gScreenBufferReadLength * 0.75;
andrewm@10 138 gScreenNextBufferReady = false;
andrewm@10 139 }
andrewm@10 140
andrewm@10 141 // render() is called regularly at the highest priority by the audio engine.
andrewm@10 142 // Input and output are given from the audio hardware and the other
andrewm@10 143 // ADCs and DACs (if available). If only audio is available, numMatrixFrames
andrewm@10 144 // will be 0.
andrewm@10 145
andrewm@10 146 void render(int numMatrixFrames, int numAudioFrames, float *audioIn, float *audioOut,
andrewm@10 147 uint16_t *matrixIn, uint16_t *matrixOut)
andrewm@10 148 {
andrewm@10 149 for(int n = 0; n < numMatrixFrames; n++) {
andrewm@10 150 // First-order lowpass filter to remove noise on launch FSR
andrewm@10 151 float rawSample = analogRead(gInputLauncher, n);
andrewm@10 152 float launchSample = gLauncherFilterPole * gLauncherLastSample +
andrewm@10 153 (1.0f - gLauncherFilterPole) * rawSample;
andrewm@10 154 gLauncherLastSample = launchSample;
andrewm@10 155
andrewm@10 156 // Peak-detect on launch signal
andrewm@10 157 if(launchSample >= gLauncherPeakValue) {
andrewm@10 158 gLauncherPeakValue = launchSample;
andrewm@10 159 gLauncherTriggered = false;
andrewm@10 160 }
andrewm@10 161 else {
andrewm@10 162 if(gLauncherPeakValue - launchSample > gLauncherNoiseThreshold && !gLauncherTriggered) {
andrewm@10 163 // Detected a peak; is it big enough overall?
andrewm@10 164 if(gLauncherPeakValue >= gLauncherMinimumPeak) {
andrewm@10 165 gLauncherTriggered = true;
andrewm@10 166 // Peak detected-- fire!!
andrewm@10 167 // Set both cannon strengths but only one will
andrewm@10 168 // fire depending on whose turn it is
andrewm@10 169 float strength = map(gLauncherPeakValue,
andrewm@10 170 gLauncherMinimumPeak, MATRIX_MAX,
andrewm@10 171 0.5f, 10.0f);
andrewm@10 172 setTank1CannonStrength(strength);
andrewm@10 173 setTank2CannonStrength(strength);
andrewm@10 174 fireProjectile();
andrewm@10 175 }
andrewm@10 176 }
andrewm@10 177
andrewm@10 178 gLauncherPeakValue *= gLauncherPeakFilterPole;
andrewm@10 179 }
andrewm@10 180
andrewm@10 181 if(--gSamplesUntilNextFrame <= 0) {
andrewm@10 182 // Update game physics and cannon angles
andrewm@10 183 gSamplesUntilNextFrame = gGameFrameInterval;
andrewm@10 184
andrewm@10 185 setTank1CannonAngle(map(analogRead(gInputTank1Angle, n),
andrewm@10 186 0, MATRIX_MAX, M_PI, 0));
andrewm@10 187 setTank2CannonAngle(map(analogRead(gInputTank2Angle, n),
andrewm@10 188 0, MATRIX_MAX, M_PI, 0));
andrewm@10 189 nextGameFrame();
andrewm@10 190 }
andrewm@10 191
andrewm@10 192 if(gScreenBufferReadPointer >= gScreenBufferReadLength - 1
andrewm@10 193 && gScreenNextBufferReady) {
andrewm@10 194 // Got to the end; swap buffers
andrewm@10 195 swap_buffers();
andrewm@10 196 }
andrewm@10 197
andrewm@10 198 // Push current screen buffer to the matrix output
andrewm@10 199 if(gScreenBufferReadPointer < gScreenBufferReadLength - 1) {
andrewm@10 200 float x = gScreenBufferRead[gScreenBufferReadPointer++];
andrewm@10 201 float y = gScreenBufferRead[gScreenBufferReadPointer++];
andrewm@10 202
andrewm@10 203 // Rescale screen coordinates to matrix ranges; invert the Y
andrewm@10 204 // coordinate to go from normal screen coordinates to scope coordinates
andrewm@10 205 analogWrite(gOutputX, n, constrain(map(x, 0, gScreenWidth, 0, MATRIX_MAX), 0, MATRIX_MAX));
andrewm@10 206 analogWrite(gOutputY, n, constrain(map(y, 0, gScreenHeight, MATRIX_MAX, 0), 0, MATRIX_MAX));
andrewm@10 207 }
andrewm@10 208 else {
andrewm@10 209 // Still not ready! Write 0 until something happens
andrewm@10 210 analogWrite(gOutputX, n, 0);
andrewm@10 211 analogWrite(gOutputY, n, 0);
andrewm@10 212 }
andrewm@10 213
andrewm@10 214 if(gameStatusWinner() != 0) {
andrewm@10 215 // Blink one LED to show who won
andrewm@10 216 // Blink both LEDs when projectile is in motion
andrewm@10 217 uint16_t val = (gSampleCounter % 4000 > 2000) ? MATRIX_MAX : 0;
andrewm@10 218 analogWrite(gOutputPlayer1LED, n, gameStatusWinner() == 1 ? val : 0);
andrewm@10 219 analogWrite(gOutputPlayer2LED, n, gameStatusWinner() == 2 ? val : 0);
andrewm@10 220
andrewm@10 221 // After 5 seconds, restart the game
andrewm@10 222 gSamplesSinceFinish++;
andrewm@10 223 if(gSamplesSinceFinish > 22050*5)
andrewm@10 224 gGameShouldRestart = true;
andrewm@10 225 }
andrewm@10 226 else if(gameStatusProjectileInMotion()) {
andrewm@10 227 // Blink both LEDs when projectile is in motion
andrewm@10 228 uint16_t val = (gSampleCounter % 2000 > 1000) ? MATRIX_MAX : 0;
andrewm@10 229 analogWrite(gOutputPlayer1LED, n, val);
andrewm@10 230 analogWrite(gOutputPlayer2LED, n, val);
andrewm@10 231 }
andrewm@10 232 else if(gameStatusPlayer1Turn()) {
andrewm@10 233 analogWrite(gOutputPlayer1LED, n, MATRIX_MAX);
andrewm@10 234 analogWrite(gOutputPlayer2LED, n, 0);
andrewm@10 235 }
andrewm@10 236 else {
andrewm@10 237 analogWrite(gOutputPlayer2LED, n, MATRIX_MAX);
andrewm@10 238 analogWrite(gOutputPlayer1LED, n, 0);
andrewm@10 239 }
andrewm@10 240
andrewm@10 241 // Check if we have reached the point where we should next update
andrewm@10 242 if(gScreenBufferReadPointer >= gScreenBufferNextUpdateLocation &&
andrewm@10 243 !gScreenNextBufferReady) {
andrewm@10 244 // Update the screen at lower priority than the audio thread
andrewm@10 245 scheduleAuxiliaryTask(gScreenUpdateTask);
andrewm@10 246 }
andrewm@10 247
andrewm@10 248 gSampleCounter++;
andrewm@10 249 }
andrewm@10 250 }
andrewm@10 251
andrewm@10 252 void screen_update()
andrewm@10 253 {
andrewm@10 254 // If we should restart, reinitialise the game
andrewm@10 255 if(gGameShouldRestart) {
andrewm@10 256 restartGame();
andrewm@10 257 gGameShouldRestart = false;
andrewm@10 258 gSamplesSinceFinish = 0;
andrewm@10 259 }
andrewm@10 260
andrewm@10 261 // Render the game based on the current state
andrewm@10 262 gScreenBufferWriteLength = drawGame(gScreenBufferWrite, gScreenBufferMaxLength);
andrewm@10 263
andrewm@10 264 // Flag it as ready to go
andrewm@10 265 gScreenNextBufferReady = true;
andrewm@10 266 }
andrewm@10 267
andrewm@10 268 // cleanup_render() is called once at the end, after the audio has stopped.
andrewm@10 269 // Release any resources that were allocated in initialise_render().
andrewm@10 270
andrewm@10 271 void cleanup_render()
andrewm@10 272 {
andrewm@10 273 // Clean up the game state
andrewm@10 274 cleanupGame();
andrewm@10 275 }