Pokitto-Raycasting/general.hpp

444 lines
11 KiB
C++
Raw Normal View History

2018-09-08 14:30:03 +02:00
/**
2018-09-11 09:19:09 +02:00
General definitions common for Pokitto raycasting demos.
The demos use mode 13: 1 byte per pixel = 256 colors. Bitmaps (textures,
sprites, ...) are also in this format (use the provided python script to
convert png images).
2018-09-08 14:30:03 +02:00
author: Miloslav "drummyfish" Ciz
license: CC0 1.0
*/
#ifndef RAYCAST_DEMO_GENERAL_HPP
#define RAYCAST_DEMO_GENERAL_HPP
2018-09-11 13:37:09 +02:00
#include "stdio.h" // for debugging raycastlibg
2018-09-08 14:30:03 +02:00
2018-09-17 17:55:33 +02:00
#define RCL_VERTICAL_FOV RCL_UNITS_PER_SQUARE /* redefine camera vertical FOV:
RCL_UNITS_PER_SQUARE would normally mean
360 degrees, but it's not an actual
angle, just linear approximation, so
this is okay */
#define RCL_PIXEL_FUNCTION pixelFunc
2018-09-17 07:14:35 +02:00
/* ^ This has to be defined to the name of the function that will render
pixels. */
2018-09-08 14:30:03 +02:00
#include "raycastlib.h"
#include "Pokitto.h"
Pokitto::Core pokitto;
2018-09-08 14:55:14 +02:00
#ifndef FPS
#define FPS 30
#endif
2018-09-08 15:53:11 +02:00
#ifndef PLAYER_SPEED
2018-09-17 17:55:33 +02:00
#define PLAYER_SPEED (4 * RCL_UNITS_PER_SQUARE)
2018-09-08 15:53:11 +02:00
#endif
#ifndef PLAYER_ROTATION_SPEED
2018-09-17 17:55:33 +02:00
#define PLAYER_ROTATION_SPEED (RCL_UNITS_PER_SQUARE / 2)
2018-09-08 15:53:11 +02:00
#endif
2018-09-10 18:23:53 +02:00
#ifndef PLAYER_JUMP_SPEED
#define PLAYER_JUMP_SPEED 500
#endif
2018-09-10 19:28:36 +02:00
#ifndef HEAD_BOB_HEIGHT
#define HEAD_BOB_HEIGHT 100
#endif
#ifndef HEAD_BOB_STEP
#define HEAD_BOB_STEP 10
#endif
2018-09-08 15:53:11 +02:00
#ifndef GRAVITY_ACCELERATION
2018-09-17 17:55:33 +02:00
#define GRAVITY_ACCELERATION ((3 * RCL_UNITS_PER_SQUARE) / 2)
2018-09-08 15:53:11 +02:00
#endif
2018-09-08 14:30:03 +02:00
#define SCREEN_WIDTH 110
#define SCREEN_HEIGHT 88
#define MIDDLE_ROW (SCREEN_HEIGHT / 2)
#define MIDDLE_COLUMN (SCREEN_WIDTH / 2)
#ifndef SUBSAMPLE
#define SUBSAMPLE 2
#endif
#define SUBSAMPLED_WIDTH (SCREEN_WIDTH / SUBSAMPLE)
2018-09-11 09:19:09 +02:00
#define TRANSPARENT_COLOR 0b00000111 /// Transparent color for sprites and GUI.
2018-09-08 14:30:03 +02:00
2018-09-17 17:55:33 +02:00
RCL_Unit zBuffer[SUBSAMPLED_WIDTH]; ///< 1D z-buffer for visibility determination.
2018-09-08 14:30:03 +02:00
2018-09-18 15:28:43 +02:00
RCL_RayConstraints defaultConstraints;
2018-09-08 14:55:14 +02:00
unsigned short palette[256];
2018-09-12 07:55:04 +02:00
#ifdef POK_SIM
2018-09-18 18:09:19 +02:00
inline void putSubsampledPixel(int32_t x, int32_t y, uint8_t color)
2018-09-18 15:28:43 +02:00
{
2018-09-18 18:09:19 +02:00
pokitto.display.drawPixel(x * SUBSAMPLE,y,color);
pokitto.display.drawPixel(x * SUBSAMPLE + 1,y,color);
2018-09-18 15:28:43 +02:00
}
2018-09-12 07:55:04 +02:00
#else
2018-09-18 15:28:43 +02:00
// This code breaks the simulator.
2018-09-18 18:09:19 +02:00
inline void putSubsampledPixel(int32_t x, int32_t y, uint8_t color)
2018-09-18 15:28:43 +02:00
{
uint8_t *buf = pokitto.display.screenbuffer;
2018-09-18 18:09:19 +02:00
buf += x * SUBSAMPLE;
buf += y * SCREEN_WIDTH;
2018-09-18 15:28:43 +02:00
for (uint8_t i = 0; i < SUBSAMPLE - 1; ++i)
*buf++ = color;
*buf = color;
}
2018-09-12 07:55:04 +02:00
#endif
2018-09-08 14:30:03 +02:00
/**
Gets (the index of) color by specified RGB components.
@param r red, 3 bits (0 to 7)
@param g green, 3 bits (0 to 7)
@param b blue, 2 bits (0 to 3)
@return palette index of the color
*/
inline uint8_t rgbToIndex(uint8_t r, uint8_t g, uint8_t b)
{
return (r & 0b00000111) | ((g & 0b00000111) << 3) | ((b & 0b00000011) << 6);
}
2018-09-08 14:55:14 +02:00
/**
Inits and loads a general 256 color palette.
*/
void initPalette()
{
for (uint8_t r = 0; r < 8; ++r)
for (uint8_t g = 0; g < 8; ++g)
for (uint8_t b = 0; b < 4; ++b)
palette[rgbToIndex(r,g,b)] =
pokitto.display.RGBto565(36 * r, 36 * g, 85 * b);
pokitto.display.load565Palette(palette);
}
2018-09-08 14:30:03 +02:00
/**
Adds given intensity to a color.
@param color input color
@param intensity intensity to add, 3 bit (0 to 7)
@return new color
*/
2018-09-10 11:26:20 +02:00
inline uint8_t addIntensity(uint8_t color, int16_t intensity)
2018-09-08 14:30:03 +02:00
{
uint8_t r = color & 0b00000111;
uint8_t g = (color & 0b00111000) >> 3;
uint8_t b = (color & 0b11000000) >> 6;
if (intensity >= 0)
{
r += intensity;
r = r > 7 ? 7 : r;
g += intensity;
g = g > 7 ? 7 : g;
b += intensity / 2;
b = b > 3 ? 3 : b;
}
else
{
intensity *= -1;
r = (intensity > r) ? 0 : r - intensity;
g = (intensity > g) ? 0 : g - intensity;
intensity /= 2;
b = intensity > b ? 0 : b - intensity;
}
return rgbToIndex(r,g,b);
}
2018-09-10 11:26:20 +02:00
inline uint8_t addRGB(uint8_t color, int16_t red, int16_t green, int16_t blue)
{
int8_t r = color & 0b00000111;
int8_t g = (color & 0b00111000) >> 3;
int8_t b = (color & 0b11000000) >> 6;
2018-09-17 17:55:33 +02:00
r = RCL_clamp(r + red,0,7);
g = RCL_clamp(g + green,0,7);
b = RCL_clamp(b + blue,0,3);
2018-09-10 11:26:20 +02:00
return rgbToIndex(r,g,b);
}
2018-09-25 10:04:16 +02:00
/**
Inits a color gradient from darkest (0, -7 intensity) to brightest
(14, +7 intensity). Index 7 will have the base color.
*/
void initGradient(unsigned char dest[15], unsigned char baseColor)
{
for (int8_t i = 0; i < 15; ++i)
dest[i] = addIntensity(baseColor, i - 7);
}
2018-09-08 14:30:03 +02:00
/**
Samples an image by normalized coordinates - each coordinate is in range
2018-09-17 17:55:33 +02:00
0 to RCL_UNITS_PER_SQUARE (from raycastlib).
2018-09-08 14:30:03 +02:00
*/
2018-09-17 17:55:33 +02:00
inline uint8_t sampleImage(const unsigned char *image, RCL_Unit x, RCL_Unit y)
2018-09-08 14:30:03 +02:00
{
// TODO: optimize
2018-09-17 17:55:33 +02:00
x = RCL_wrap(x,RCL_UNITS_PER_SQUARE);
y = RCL_wrap(y,RCL_UNITS_PER_SQUARE);
2018-09-08 14:30:03 +02:00
int32_t index =
2018-09-18 12:54:19 +02:00
(x / (RCL_UNITS_PER_SQUARE / TEXTURE_W)) * TEXTURE_H +
(y / (RCL_UNITS_PER_SQUARE / TEXTURE_W));
2018-09-08 14:30:03 +02:00
return image[2 + index];
}
2018-09-12 12:01:16 +02:00
/**
Draws a scaled sprite on screen in an optimized way. The sprite has to be
square in resolution.
*/
2018-09-17 17:55:33 +02:00
void inline drawSpriteSquare(const unsigned char *sprite, int16_t x, int16_t y, RCL_Unit depth, int16_t size)
2018-09-08 14:30:03 +02:00
{
2018-09-12 12:01:16 +02:00
if (size < 0 || size > 200 || // let's not mess up with the incoming array
sprite[0] != sprite[1]) // only draw square sprites
2018-09-11 10:33:47 +02:00
return;
int16_t samplingIndices[size];
// optimization: precompute the indices
2018-09-17 17:55:33 +02:00
for (RCL_Unit i = 0; i < size; ++i)
2018-09-12 12:01:16 +02:00
samplingIndices[i] = (i * sprite[0]) / size;
2018-09-08 14:30:03 +02:00
x -= size / 2;
y -= size / 2;
uint8_t c;
int16_t jTo = size - max(0,y + size - 88);
int16_t iTo = size - max(0,x + size - 110);
2018-09-17 17:55:33 +02:00
for (RCL_Unit i = max(-1 * x,0); i < iTo; ++i)
2018-09-08 14:30:03 +02:00
{
int16_t xPos = x + i;
if (zBuffer[xPos / SUBSAMPLE] <= depth)
continue;
2018-09-12 12:01:16 +02:00
int16_t columnLocation = 2 + samplingIndices[i] * sprite[0];
2018-09-17 17:55:33 +02:00
for (RCL_Unit j = max(-1 * y,0); j < jTo; ++j)
2018-09-08 14:30:03 +02:00
{
2018-09-12 12:01:16 +02:00
c = sprite[columnLocation + samplingIndices[j]];
2018-09-08 14:30:03 +02:00
if (c != TRANSPARENT_COLOR)
pokitto.display.drawPixel(xPos,y + j,c);
}
}
}
2018-09-11 09:19:09 +02:00
/// Faster than drawSprite.
2018-09-10 10:03:47 +02:00
void drawImage(const unsigned char *image, int16_t x, int16_t y)
{
// TODO: optimize
for (int16_t i = 0; i < image[0]; ++i)
{
int16_t xPos = x + i;
int16_t column = 2 + i * image[1];
for (int16_t j = 0; j < image[1]; ++j)
{
char c = image[column + j];
if (c != TRANSPARENT_COLOR)
pokitto.display.drawPixel(xPos,y + j,image[column + j]);
}
}
}
2018-09-08 14:55:14 +02:00
class Player
{
public:
2018-09-17 17:55:33 +02:00
RCL_Camera mCamera;
RCL_Unit mVericalSpeed;
2018-09-09 16:39:08 +02:00
bool mRunning;
2018-09-17 17:55:33 +02:00
RCL_Unit mHeadBob;
2018-09-10 19:28:36 +02:00
bool mHeadBobUp;
2018-09-08 14:55:14 +02:00
Player()
{
2018-09-17 17:55:33 +02:00
RCL_initCamera(&mCamera);
2018-09-15 17:06:07 +02:00
2018-09-08 14:55:14 +02:00
mCamera.position.x = 0;
mCamera.position.y = 0;
mCamera.direction = 0;
2018-09-17 17:55:33 +02:00
mCamera.height = RCL_UNITS_PER_SQUARE * 3;
2018-09-08 14:55:14 +02:00
mCamera.resolution.x = SCREEN_WIDTH / SUBSAMPLE;
mCamera.resolution.y = SCREEN_HEIGHT;
mCamera.shear = 0;
mVericalSpeed = 0;
2018-09-09 16:39:08 +02:00
mRunning = false;
2018-09-10 19:28:36 +02:00
mHeadBob = 0;
mHeadBobUp = true;
2018-09-08 14:55:14 +02:00
}
2018-09-17 17:55:33 +02:00
void setPosition(RCL_Unit x, RCL_Unit y)
2018-09-08 14:55:14 +02:00
{
mCamera.position.x = x;
mCamera.position.y = y;
}
2018-09-17 17:55:33 +02:00
void setPosition(RCL_Unit x, RCL_Unit y, RCL_Unit z, RCL_Unit direction)
2018-09-12 07:55:04 +02:00
{
mCamera.position.x = x;
mCamera.position.y = y;
mCamera.height = z;
mCamera.direction = direction;
}
2018-09-08 14:55:14 +02:00
void setPositionSquare(int16_t squareX, int16_t squareY)
{
setPosition(
2018-09-17 17:55:33 +02:00
squareX * RCL_UNITS_PER_SQUARE + RCL_UNITS_PER_SQUARE / 2,
squareY * RCL_UNITS_PER_SQUARE + RCL_UNITS_PER_SQUARE / 2);
2018-09-08 14:55:14 +02:00
}
2018-09-10 18:23:53 +02:00
void update(int16_t moveDirection, bool strafe, int16_t turnDirection, bool jump,
2018-09-17 17:55:33 +02:00
int16_t shearDirection, RCL_ArrayFunction floorHeightFunction,
RCL_ArrayFunction ceilingHeightFunction, bool computeHeight, uint32_t dt)
2018-09-10 18:23:53 +02:00
{
2018-09-17 17:55:33 +02:00
RCL_Vector2D moveOffset;
2018-09-10 18:23:53 +02:00
moveOffset.x = 0;
moveOffset.y = 0;
if (moveDirection != 0)
{
int16_t horizontalStep = (dt * PLAYER_SPEED * (mRunning ? 2 : 1)) / 1000 *
(moveDirection > 0 ? 1 : -1);
2018-09-17 17:55:33 +02:00
moveOffset = RCL_angleToDirection(mCamera.direction + (strafe ? RCL_UNITS_PER_SQUARE / 4 : 0));
2018-09-10 18:23:53 +02:00
2018-09-17 17:55:33 +02:00
moveOffset.x = (moveOffset.x * horizontalStep) / RCL_UNITS_PER_SQUARE;
moveOffset.y = (moveOffset.y * horizontalStep) / RCL_UNITS_PER_SQUARE;
2018-09-10 19:28:36 +02:00
mHeadBob += mHeadBobUp ? HEAD_BOB_STEP : -HEAD_BOB_STEP;
if (mHeadBob > HEAD_BOB_HEIGHT)
mHeadBobUp = false;
else if (mHeadBob < -HEAD_BOB_HEIGHT)
mHeadBobUp = true;
2018-09-10 18:23:53 +02:00
}
2018-09-10 19:28:36 +02:00
else
mHeadBob /= 2;
2018-09-10 18:23:53 +02:00
if (turnDirection != 0)
{
int16_t rotationStep = (dt * PLAYER_ROTATION_SPEED) / 1000;
2018-09-17 17:55:33 +02:00
mCamera.direction = RCL_wrap(mCamera.direction + turnDirection * rotationStep,RCL_UNITS_PER_SQUARE);
2018-09-10 18:23:53 +02:00
}
2018-09-17 17:55:33 +02:00
RCL_Unit prevHeight = mCamera.height;
2018-09-10 18:23:53 +02:00
2018-09-17 17:55:33 +02:00
RCL_moveCameraWithCollision(&mCamera,moveOffset,mVericalSpeed,
2018-09-12 07:55:04 +02:00
floorHeightFunction, ceilingHeightFunction, computeHeight ? 1 : 0, 0);
2018-09-10 18:23:53 +02:00
2018-09-17 17:55:33 +02:00
RCL_Unit heightDiff = mCamera.height - prevHeight;
2018-09-10 18:23:53 +02:00
if (heightDiff == 0)
mVericalSpeed = 0; // hit floor/ceiling
if (jump && mVericalSpeed == 0)
{
2018-09-17 17:55:33 +02:00
int16_t camX = RCL_divRoundDown(mCamera.position.x,RCL_UNITS_PER_SQUARE);
int16_t camY = RCL_divRoundDown(mCamera.position.y,RCL_UNITS_PER_SQUARE);
2018-09-10 18:23:53 +02:00
2018-09-17 17:55:33 +02:00
if (mCamera.height - RCL_CAMERA_COLL_HEIGHT_BELOW -
2018-09-10 18:23:53 +02:00
floorHeightFunction(camX,camY) < 2)
mVericalSpeed = PLAYER_JUMP_SPEED; // jump
}
if (shearDirection != 0)
2018-09-17 17:55:33 +02:00
mCamera.shear = RCL_clamp(mCamera.shear + shearDirection * 10,
2018-09-10 18:23:53 +02:00
-1 * mCamera.resolution.y, mCamera.resolution.y);
else
mCamera.shear /= 2;
if (computeHeight)
mVericalSpeed -= (dt * GRAVITY_ACCELERATION) / 1000; // gravity
}
2018-09-08 14:55:14 +02:00
};
class Sprite
{
public:
2018-09-15 17:06:07 +02:00
const unsigned char *mImage;
2018-09-17 17:55:33 +02:00
RCL_Vector2D mPosition;
RCL_Unit mHeight;
RCL_Unit mPixelSize;
2018-09-15 17:06:07 +02:00
2018-09-17 17:55:33 +02:00
Sprite(const unsigned char *image, int16_t squareX, int16_t squareY, RCL_Unit z,
RCL_Unit pixelSize):
2018-09-08 14:55:14 +02:00
mImage(image),
mPixelSize(pixelSize)
{
2018-09-17 17:55:33 +02:00
mPosition.x = squareX * RCL_UNITS_PER_SQUARE + RCL_UNITS_PER_SQUARE / 2;
mPosition.y = squareY * RCL_UNITS_PER_SQUARE + RCL_UNITS_PER_SQUARE / 2;
mHeight = z * RCL_UNITS_PER_SQUARE + RCL_UNITS_PER_SQUARE / 2;
2018-09-08 14:55:14 +02:00
}
Sprite():
mImage(0), mHeight(0), mPixelSize(1)
{
mPosition.x = 0;
mPosition.y = 0;
}
};
2018-09-09 17:52:30 +02:00
void initGeneral()
{
pokitto.begin();
pokitto.setFrameRate(FPS);
pokitto.display.setFont(fontTiny);
pokitto.display.persistence = 1;
2018-09-18 15:28:43 +02:00
RCL_initRayConstraints(&defaultConstraints);
2018-09-09 17:52:30 +02:00
initPalette();
for (uint8_t i = 0; i < SUBSAMPLED_WIDTH; ++i)
zBuffer[i] = 0;
}
2018-09-11 09:19:09 +02:00
/**
Computes an average color of given texture.
*/
2018-09-10 08:03:09 +02:00
unsigned char computeAverageColor(const unsigned char *texture)
{
uint32_t sumR = 0;
uint32_t sumG = 0;
uint32_t sumB = 0;
uint32_t pixels = texture[0] * texture[1];
for (uint16_t i = 0; i < pixels; ++i)
{
sumR += texture[2 + i] & 0b00000111;
sumG += (texture[2 + i] & 0b00111000) >> 3;
sumB += (texture[2 + i] & 0b11000000) >> 6;
}
return rgbToIndex(sumR / pixels,sumG / pixels,sumB / pixels);
}
2018-09-08 14:30:03 +02:00
#endif