Start block shapes

This commit is contained in:
Miloslav Ciz 2024-07-22 01:16:16 +02:00
parent 84163d0d3c
commit 9e3bf000cf
8 changed files with 217 additions and 67 deletions

View file

@ -1,3 +1,10 @@
- sky images could be just composed of 4x4 normal images? then we only need
one type of image
- map actually in ASCII format? how will humans edit it?
- make a simple rendering setting:
- will exclude images and only draw solid colors, let's say only 16, so that
memory usage is reduced, CPU rendering is relieved, executable is smaller
AND frontend doesn't have to use RGB565 (it can basically choose any mode).
- background sky rendering efficiently:
- first draw the solid color top and bottom
- then render the 3D scene
@ -42,7 +49,7 @@ TOTAL SIZE OF TEXTURES:
- possibility of simple procedural textures to save space! <-- SOUNDS NICE
- possibility to turn off textures completely <-- MUST HAVE
- how to map textures to blocks?
- figured out nice procedural mapping in Blender :)
- figured out nice procedural mapping in Blender :) <-- DONE
- PROBABLY LIKE THIS:
- background textures: 128x128 with image-specific 256 color palette,
pixel access shouldn't be as time critical here (128x128/256 looks better
@ -54,6 +61,20 @@ TOTAL SIZE OF TEXTURES:
CAUSE BACKGROUND WON'T BE A 3D MODEL
- Track model has to be preprocessed, unseen walls must be removed else would
be too slow. <-- OF COURSE
- Removing unseen triangles:
- make a routine that does the cleaning, call it several times during the
model construction (waiting till the very end could run out of memory
with too many vertices/triangles, also processing so many of them could
even make it slower), it will work like this:
- go through all triangles, mark each covered or not (we can't remove
them right away, they need to stay to see if they cover other tris)
- for each triangle go through all again, mark the checked tri covered if:
- the other tri has the same vertices (trivial case), OR
- non-trivial case (consider e.g. two covering quads but split
differently) not so easy, maybe good enough: if the tris share two
vertices AND they're in the same plane (EZ to check for axis aligned)
AND the other tri has a neighbor tri that has the non-shared vertex?
- remove all marked tris AND vertices that become unused
- ====== Track/map format: list of map blocks. =======
- one block record:
- type: 5 bits?

View file

@ -2,6 +2,20 @@
#define _LCR_ASSETS_H
#include <stdint.h>
#include "map.h"
// TODO: images should be 64x64 indexed, sky will be composed of 2x2 normal images
static const uint8_t map1[] =
{
LCR_MAP_MAGIC_NUMBER,
0,
10,
10,
LCR_MAP_BLOCK(LCR_BLOCK_FULL,32,32,32,LCR_BLOCK_MATERIAL_CONCRETE,0),
LCR_MAP_BLOCK(LCR_BLOCK_FULL,33,32,32,LCR_BLOCK_MATERIAL_CONCRETE,0),
LCR_MAP_TERMINATOR
};
#define LCR_SKY_IMAGE_SIZE 128

View file

@ -16,6 +16,6 @@
(LCR_SETTING_FREE_CAMERA_TURN_SPEED / LCR_SETTING_FPS)
/** Maximum number of triangles of a block shape. */
#define LCR_MAP_BLOCK_SHAPE_MAX_TRIANGLES 32
#define LCR_MAP_BLOCK_SHAPE_MAX_BYTES 64
#endif

View file

@ -45,9 +45,9 @@ void LCR_sleep(uint16_t timeMs)
usleep(timeMs * 1000);
}
void LCR_drawPixel(unsigned int x, unsigned int y, uint_fast16_t color)
void LCR_drawPixel(unsigned long index, uint16_t color)
{
screen[y * LCR_SETTING_RESOLUTION_X + x] = color;
screen[index] = color;
}
int main(int argc, char *argv[])

53
game.h
View file

@ -44,7 +44,7 @@ void LCR_sleep(uint16_t timeMs);
is handled by the game internals and out-of-screen pixels will never be drawn.
The color value depends on game settings but is normally an RGB565 value.
*/
void LCR_drawPixel(unsigned int x, unsigned int y, uint_fast16_t color);
void LCR_drawPixel(unsigned long index, uint16_t color);
/**
Call this function in your frontend at the start of the program.
@ -69,52 +69,47 @@ uint8_t LCR_gameStep(uint32_t timeMs);
uint32_t LCR_nextRenderFrameTime;
void LCR_drawPixelUnsafe(unsigned int x, unsigned int y,
uint_fast16_t color);
void LCR_drawPixelXYUnsafe(unsigned int x, unsigned int y,
uint16_t color);
/**
Internal pixel drawing function that checks for out-of-screen coordinates. Use
this if the pixel can potentially lie of screen (however if you know it won't,
use the normal unsafe function in sake of performance).
*/
static inline void LCR_drawPixelSafe(unsigned int x, unsigned int y,
static inline void LCR_drawPixelXYSafe(unsigned int x, unsigned int y,
uint_fast16_t color);
#include "racing.h"
#include "assets.h"
#include "renderer.h"
uint8_t _LCR_keyStates[LCR_KEYS_TOTAL]; /**< Assures unchanging key states
uint8_t LCR_keyStates[LCR_KEYS_TOTAL]; /**< Assures unchanging key states
during a single frame. */
void LCR_drawPixelUnsafe(unsigned int x, unsigned int y,
uint_fast16_t color)
void LCR_drawPixelXYUnsafe(unsigned int x, unsigned int y,
uint16_t color)
{
#if LCR_SETTING_RESOLUTION_SUBDIVIDE == 1
LCR_drawPixel(x,y,color);
LCR_drawPixel(y * LCR_SETTING_RESOLUTION_X + x,color);
#else
x *= LCR_SETTING_RESOLUTION_SUBDIVIDE;
y *= LCR_SETTING_RESOLUTION_SUBDIVIDE;
unsigned int x2 = x + LCR_SETTING_RESOLUTION_SUBDIVIDE;
for (int yy = y; yy < y + LCR_SETTING_RESOLUTION_SUBDIVIDE; ++yy)
for (int xx = x; xx < x2; ++xx)
LCR_drawPixel(xx,yy,color);
// TODO
#endif
}
static inline void LCR_drawPixelSafe(unsigned int x, unsigned int y,
static inline void LCR_drawPixelXYSafe(unsigned int x, unsigned int y,
uint_fast16_t color)
{
if (x < LCR_EFFECTIVE_RESOLUTION_X && y < LCR_EFFECTIVE_RESOLUTION_Y)
LCR_drawPixelUnsafe(x,y,color);
LCR_drawPixelXYUnsafe(x,y,color);
}
void LCR_gameInit(void)
{
for (int i = 0; i < LCR_KEYS_TOTAL; ++i)
_LCR_keyStates[i] = 0;
LCR_keyStates[i] = 0;
LCR_mapLoad(map1);
LCR_rendererInit();
}
@ -126,7 +121,7 @@ void LCR_gameEnd(void)
uint8_t LCR_gameStep(uint32_t time)
{
for (int i = 0; i < LCR_KEYS_TOTAL; ++i)
_LCR_keyStates[i] = LCR_keyPressed(i);
LCR_keyStates[i] = LCR_keyPressed(i);
uint32_t sleep = 0;
@ -152,28 +147,28 @@ uint8_t LCR_gameStep(uint32_t time)
for (int i = 0; i < 5; ++i)
offsets[i] = 0;
if (_LCR_keyStates[LCR_KEY_A])
if (LCR_keyStates[LCR_KEY_A])
{
if (_LCR_keyStates[LCR_KEY_UP])
if (LCR_keyStates[LCR_KEY_UP])
offsets[4] = LCR_FREE_CAMERA_TURN_STEP;
else if (_LCR_keyStates[LCR_KEY_DOWN])
else if (LCR_keyStates[LCR_KEY_DOWN])
offsets[4] -= LCR_FREE_CAMERA_TURN_STEP;
if (_LCR_keyStates[LCR_KEY_RIGHT])
if (LCR_keyStates[LCR_KEY_RIGHT])
offsets[3] -= LCR_FREE_CAMERA_TURN_STEP;
else if (_LCR_keyStates[LCR_KEY_LEFT])
else if (LCR_keyStates[LCR_KEY_LEFT])
offsets[3] = LCR_FREE_CAMERA_TURN_STEP;
}
else
{
if (_LCR_keyStates[LCR_KEY_UP])
if (LCR_keyStates[LCR_KEY_UP])
offsets[0] = LCR_FREE_CAMERA_STEP;
else if (_LCR_keyStates[LCR_KEY_DOWN])
else if (LCR_keyStates[LCR_KEY_DOWN])
offsets[0] -= LCR_FREE_CAMERA_STEP;
if (_LCR_keyStates[LCR_KEY_RIGHT])
if (LCR_keyStates[LCR_KEY_RIGHT])
offsets[1] = LCR_FREE_CAMERA_STEP;
else if (_LCR_keyStates[LCR_KEY_LEFT])
else if (LCR_keyStates[LCR_KEY_LEFT])
offsets[1] -= LCR_FREE_CAMERA_STEP;
}

57
map.h
View file

@ -76,8 +76,8 @@
#define LCR_BLOCK_LEFT_FRONT 0x03 ///< filled left front quarter of block
#define LCR_BLOCK_BOTTOM_LEFT 0x04 ///< filled bottom left quarter of block
#define LCR_BLOCK_CHECKPOINT_NOT 0x10 ///< checkpoint, not taken
#define LCR_BLOCK_CHECKPOINT_YES 0x11 ///< checkpoint, taken
#define LCR_BLOCK_CHECKPOINT_0 0x10 ///< checkpoint, not taken
#define LCR_BLOCK_CHECKPOINT_1 0x11 ///< checkpoint, taken
#define LCR_BLOCK_FINISH 0x12 ///< finish
// special blocks:
@ -274,18 +274,57 @@ const uint8_t *LCR_mapGetBlockAt(uint8_t x, uint8_t y, uint8_t z)
return 0;
}
void _LCR_addBlockShapeByte(uint8_t *bytes, uint8_t *byteCount,
int x, int y, int z)
{
if (*byteCount >= LCR_MAP_BLOCK_SHAPE_MAX_BYTES)
return;
bytes[*byteCount] = (5 * 7) * z + 7 * y + x;
*byteCount += 1;
}
/**
Gets a shape of given map block type as a 3D model composed of triangles. The
model is returned as an array of 16 bit ints, each of which represents
X with lowest 5 bits, Y with next 5 bits and Z with next 5 bits (the
directions of axes are same as map X, Y, Z directions). Each coordinate can
go from 0 to 16; WATCH OUT, THIS IS 17 VALUES, NOT 16, so as to allow having
a mid coord (8), mid-mid coords (4, 12) and mid-mid-mid coords.
model is returned as an array of byte triplets (triangles), with each byte
representing one coordinate. These coordinates can be decoded with
LCR_decodeMapBlockCoords function.
*/
void LCR_mapGetBlockShape(uint8_t blockType, uint8_t transform,
uint16_t triangles[LCR_MAP_BLOCK_SHAPE_MAX_TRIANGLES * 3],
uint8_t *triangleCount)
uint8_t bytes[LCR_MAP_BLOCK_SHAPE_MAX_BYTES], uint8_t *byteCount)
{
/*
The coordinate format is following: byte B specifies coordinates X (0 to 6)
= B % 7, Y (vertical, 0 to 4) = (B / 7) % 5, Z (0 to 6) = B % 35.
*/
*byteCount = 0;
switch (blockType)
{
case LCR_BLOCK_FULL:
default:
_LCR_addBlockShapeByte(bytes,byteCount,0,0,1);
_LCR_addBlockShapeByte(bytes,byteCount,5,0,1);
_LCR_addBlockShapeByte(bytes,byteCount,0,1,3);
break;
}
}
/**
Decodes XYZ coordinates encoded in a byte returned by LCR_mapGetBlockShape.
Each coordinate will be in range 0 to 12 (including both). This unusual range
is intentional as it for example has an exact mid value.
*/
void LCR_decodeMapBlockCoords(uint8_t byte, uint8_t *x, uint8_t *y, uint8_t *z)
{
*x = (byte % 7) * 2;
*y = ((byte / 7) % 5) * 3;
*z = (byte % 35) * 2;
}
#endif // guard

View file

@ -23,45 +23,116 @@ struct LCR_Renderer
S3L_Scene LCR_scene3D;
S3L_Model3D LCR_models3D[3]; // TODO
S3L_Unit LCR_vertices3D[LCR_SETTING_MAX_VERTICES * 3];
S3L_Index LCR_triangles3D[LCR_SETTING_MAX_TRIANGLES * 3];
S3L_Model3D *LCR_mapModel;
S3L_Unit LCR_mapVertices[LCR_SETTING_MAX_MAP_VERTICES * 3];
S3L_Index LCR_mapTriangles[LCR_SETTING_MAX_MAP_TRIANGLES * 3];
void LCR_pixelFunc3D(S3L_PixelInfo *pixel)
{
LCR_drawPixelSafe(pixel->x,pixel->y,0xff00);
LCR_drawPixelXYSafe(pixel->x,pixel->y,0xff00);
}
S3L_Index _LCR_addMapVertex(S3L_Unit x, S3L_Unit y, S3L_Unit z)
{
S3L_Index index = 0;
S3L_Unit *vertices = LCR_mapVertices;
while (index < LCR_mapModel->vertexCount) // if exists, return vertex index
{
if (vertices[0] == x && vertices[1] == y && vertices[2] == z)
return index;
vertices += 3;
index++;
}
// if it doesn't exist, add it
if (LCR_mapModel->vertexCount < LCR_SETTING_MAX_MAP_VERTICES)
{
*vertices = x;
vertices++;
*vertices = y;
vertices++;
*vertices = z;
LCR_mapModel->vertexCount++;
return LCR_mapModel->vertexCount - 1;
}
// TODO: error print
return 0;
}
void _LCR_addMapTriangle(S3L_Index a, S3L_Index b, S3L_Index c)
{
if (LCR_mapModel->triangleCount < LCR_SETTING_MAX_MAP_TRIANGLES)
{
S3L_Index *t = &(LCR_mapModel->triangles[LCR_mapModel->triangleCount * 3]);
*t = a;
t++;
*t = b;
t++;
*t = c;
LCR_mapModel->triangleCount++;
}
}
/** Builds an internal 3D model of the currently loaded map. Returns 1 on
success, otherwise 0 (e.g. not enough space). */
uint8_t LCR_buildMapModel(void)
uint8_t _LCR_rendererBuildMapModel(void)
{
// TODO
uint8_t blockShapeBytes[LCR_MAP_BLOCK_SHAPE_MAX_BYTES];
uint8_t blockShapeByteCount;
LCR_vertices3D[0] = -2000;
LCR_vertices3D[1] = -100;
LCR_vertices3D[2] = 1000;
S3L_model3DInit(LCR_mapVertices,0,LCR_mapTriangles,0,LCR_mapModel);
LCR_vertices3D[3] = 400;
LCR_vertices3D[4] = -100;
LCR_vertices3D[5] = 2000;
LCR_mapGetBlockShape(0,0,blockShapeBytes,&blockShapeByteCount);
LCR_vertices3D[6] = 0;
LCR_vertices3D[7] = 400;
LCR_vertices3D[8] = 2000;
uint8_t vx, vy, vz, vi = 0;
uint8_t *bytes = blockShapeBytes;
S3L_Index triangleIndices[3];
LCR_triangles3D[0] = 0;
LCR_triangles3D[1] = 1;
LCR_triangles3D[2] = 2;
for (int i = 0; i < blockShapeByteCount; ++i)
{
LCR_decodeMapBlockCoords(blockShapeBytes[i],&vx,&vy,&vz);
printf("%d: %d %d %d\n",blockShapeBytes[i],vx,vy,vz);
triangleIndices[vi] = _LCR_addMapVertex(
(LCR_SQUARE_SIDE_LEN * ((LCR_SpaceUnit) vx)) / 12,
(LCR_SQUARE_SIDE_LEN / 2 * ((LCR_SpaceUnit) vy)) / 12,
(LCR_SQUARE_SIDE_LEN * ((LCR_SpaceUnit) vz)) / 12);
if (vi < 2)
vi++;
else
{
_LCR_addMapTriangle(
triangleIndices[0],triangleIndices[1],triangleIndices[2]);
vi = 0;
}
}
/*
a = _LCR_addMapVertex(-2000,-100,1000);
b = _LCR_addMapVertex(400,-100,2000);
c = _LCR_addMapVertex(0,400,2000);
_LCR_addMapTriangle(a,b,c);
*/
return 1;
}
uint8_t LCR_rendererInit(void)
{
if (!LCR_buildMapModel())
LCR_mapModel = LCR_models3D;
if (!_LCR_rendererBuildMapModel())
return 0;
S3L_model3DInit(LCR_vertices3D,3,LCR_triangles3D,1,LCR_models3D);
S3L_sceneInit(LCR_models3D,1,&LCR_scene3D);
return 1;
@ -108,6 +179,14 @@ void LCR_rendererDraw(void)
void LCR_drawBackground(int verticalOffset)
{
// TODO
for (int y = 0; y < LCR_EFFECTIVE_RESOLUTION_Y; ++y)
for (int x = 0; x < LCR_EFFECTIVE_RESOLUTION_X; ++x)
LCR_drawPixelXYUnsafe(x,y,0xffff);
/*
uint16_t color = LCR_skyImages[LCR_skyImages[256] & 0x00ff] ; // TODO
int limit = verticalOffset - LCR_SETTING_SKY_SIZE * LCR_SKY_IMAGE_SIZE / 2;
@ -128,11 +207,13 @@ void LCR_drawBackground(int verticalOffset)
for (int y = LCR_EFFECTIVE_RESOLUTION_Y - 1; y >= limit; --y)
for (int x = 0; x < LCR_EFFECTIVE_RESOLUTION_X; ++x)
LCR_drawPixelUnsafe(x,y,color);
*/
}
void LCR_drawSkyStrip(int verticalOffset, uint8_t horizontalOffset)
{
#if LCR_SETTING_SKY_SIZE != 0
/*
verticalOffset -= (LCR_SETTING_SKY_SIZE * LCR_SKY_IMAGE_SIZE) / 2;
int finalY = verticalOffset + LCR_SETTING_SKY_SIZE * LCR_SKY_IMAGE_SIZE;
@ -173,7 +254,7 @@ void LCR_drawSkyStrip(int verticalOffset, uint8_t horizontalOffset)
if (verticalOffset % LCR_SETTING_SKY_SIZE == 0)
skyLine += LCR_SKY_IMAGE_SIZE / 2;
}
*/
#endif
}

View file

@ -35,15 +35,15 @@
#define LCR_SETTING_SKY_ROLL_MULTIPLIER_H 4
#endif
#ifndef LCR_SETTING_MAX_VERTICES
#ifndef LCR_SETTING_MAX_MAP_VERTICES
/** Maximum number of vertices for 3D rendering. Lower number will decrease
RAM usage but will prevent larger maps from being loaded. */
#define LCR_SETTING_MAX_VERTICES 10000
#define LCR_SETTING_MAX_MAP_VERTICES 10000
#endif
#ifndef LCR_SETTING_MAX_TRIANGLES
/** Like LCR_SETTING_MAX_VERTICES but for the number of triangles. */
#define LCR_SETTING_MAX_TRIANGLES 10000
#ifndef LCR_SETTING_MAX_MAP_TRIANGLES
/** Like LCR_SETTING_MAX_MAP_VERTICES but for the number of triangles. */
#define LCR_SETTING_MAX_MAP_TRIANGLES 10000
#endif
#ifndef LCR_SETTING_SKY_SIZE