Licar/renderer.h

1281 lines
35 KiB
C
Raw Normal View History

2023-09-16 22:52:03 +02:00
/**
3D renderer: implements 3D rendering.
*/
2023-09-13 20:51:07 +02:00
#ifndef _LCR_RENDERER_H
#define _LCR_RENDERER_H
2023-09-16 20:35:01 +02:00
#define S3L_RESOLUTION_X LCR_SETTING_RESOLUTION_X
#define S3L_RESOLUTION_Y LCR_SETTING_RESOLUTION_Y
#define S3L_PIXEL_FUNCTION LCR_pixelFunc3D
2024-07-29 17:42:40 +02:00
#define S3L_PERSPECTIVE_CORRECTION 2
2024-08-06 01:34:38 +02:00
#define S3L_NEAR_CROSS_STRATEGY 1
2024-07-29 17:42:40 +02:00
2024-07-30 02:47:42 +02:00
#define S3L_Z_BUFFER 1
2023-09-17 15:42:46 +02:00
2023-09-16 20:35:01 +02:00
#include "small3dlib.h"
2024-07-24 20:28:57 +02:00
/// Renderer specific unit, length of one map square.
2024-08-13 00:53:04 +02:00
#define LCR_RENDERER_UNIT (S3L_FRACTIONS_PER_UNIT / 2)
// ^ just S3L_FRACTIONS_PER_UNIT leaves some tris bugging
2024-07-23 19:57:29 +02:00
2024-08-13 00:53:04 +02:00
#define LCR_RENDERER_CHUNK_RESOLUTION 4 // do not change
2024-08-29 00:10:16 +02:00
#define LCR_RENDERER_LOD_BLOCKS 64 // do not change
2024-08-06 01:34:38 +02:00
#define LCR_RENDERER_CHUNK_SIZE_HORIZONTAL \
((LCR_MAP_SIZE_BLOCKS * LCR_RENDERER_UNIT) / LCR_RENDERER_CHUNK_RESOLUTION)
#define LCR_RENDERER_CHUNKS_TOTAL (LCR_RENDERER_CHUNK_RESOLUTION * \
LCR_RENDERER_CHUNK_RESOLUTION * LCR_RENDERER_CHUNK_RESOLUTION)
2024-08-30 01:04:07 +02:00
#define LCR_RENDERER_MODEL_COUNT 9
2024-08-13 00:53:04 +02:00
2024-07-30 02:47:42 +02:00
struct
2023-09-13 20:51:07 +02:00
{
2024-08-02 01:01:02 +02:00
S3L_Scene scene;
2024-08-14 15:19:54 +02:00
S3L_Model3D mapModel; ///< whole map model
2024-08-30 01:04:07 +02:00
S3L_Model3D *carModel;
2024-08-29 00:10:16 +02:00
/**
The scene model array.
0, 1, 2, 3, 4, 5, 6, 7: nearest map chunk models
2024-08-30 01:04:07 +02:00
8: car model
2024-08-29 00:10:16 +02:00
*/
S3L_Model3D models[LCR_RENDERER_MODEL_COUNT];
2024-08-06 01:34:38 +02:00
2024-09-01 14:06:24 +02:00
uint32_t frame;
2024-08-29 00:10:16 +02:00
uint8_t loadedChunks[8]; ///< numbers of loaded map chunks
2023-09-13 20:51:07 +02:00
2024-08-02 01:01:02 +02:00
S3L_Unit mapVerts[LCR_SETTING_MAX_MAP_VERTICES * 3];
S3L_Index mapTris[LCR_SETTING_MAX_MAP_TRIANGLES * 3];
2023-09-16 20:35:01 +02:00
2024-08-06 01:34:38 +02:00
S3L_Index chunkStarts[LCR_RENDERER_CHUNKS_TOTAL];
2024-08-01 21:41:21 +02:00
/**
Additional data for triangles. 4 higher bits hold direction (for lighting):
0 is floor, 1 is wall, 2 is wall (90 degrees). 4 lower bits hold the
texture index.
*/
uint8_t mapTriangleData[LCR_SETTING_MAX_MAP_TRIANGLES];
2024-08-29 00:10:16 +02:00
/**
8x8x8 3D grid of bits, each bit says (for each corresponding part of map)
whether there is an LOD block or not.
*/
uint8_t gridOfLODs[LCR_RENDERER_LOD_BLOCKS];
2024-08-13 00:53:04 +02:00
2024-07-30 02:47:42 +02:00
// pixel function precomputed values:
2024-08-02 01:01:02 +02:00
int previousTriID;
int triUVs[6];
2024-08-06 01:34:38 +02:00
uint8_t texSubsampleCount;
2024-09-01 14:06:24 +02:00
#if LCR_SETTING_ANIMATE_CAR
S3L_Unit wheelRotation;
S3L_Unit wheelTurn;
S3L_Unit wheelRotationCenters[4];
S3L_Unit animatedCarVerts[LCR_CAR_VERTEX_COUNT * 3];
#endif
2024-07-30 02:47:42 +02:00
} LCR_renderer;
2024-07-29 17:42:40 +02:00
2023-09-16 20:35:01 +02:00
void LCR_pixelFunc3D(S3L_PixelInfo *pixel)
{
2024-07-29 17:42:40 +02:00
// once we get a new triangle, we precompute things for it:
2024-08-30 01:04:07 +02:00
if (pixel->triangleID != LCR_renderer.previousTriID)
2024-07-29 17:42:40 +02:00
{
2024-08-30 01:04:07 +02:00
LCR_renderer.previousTriID = pixel->triangleID;
2024-09-01 14:06:24 +02:00
#if LCR_SETTING_TEXTURE_SUBSAMPLE != 0
LCR_renderer.texSubsampleCount = 0;
#endif
2024-08-30 01:04:07 +02:00
if (pixel->modelIndex == 8)
{
// car model
LCR_loadImage(LCR_IMAGE_CAR);
for (int i = 0; i < 6; ++i)
{
LCR_renderer.triUVs[i] =
(LCR_carUvs[2 * LCR_carTriangleUvs[
2024-09-01 14:06:24 +02:00
3 * pixel->triangleIndex + i / 2] + i % 2] * (LCR_IMAGE_SIZE + 1))
/ 512;
2024-08-30 01:04:07 +02:00
}
}
else
{
// map model
const S3L_Index *t =
LCR_renderer.models[pixel->modelIndex].triangles +
3 * pixel->triangleIndex;
S3L_Unit *v[3];
2024-07-29 17:42:40 +02:00
2024-08-30 01:04:07 +02:00
for (int i = 0; i < 3; ++i)
v[i] = LCR_renderer.mapVerts + 3 * t[i];
2024-08-06 01:34:38 +02:00
2024-08-30 01:04:07 +02:00
const uint8_t *triData =
LCR_renderer.mapTriangleData +
LCR_renderer.chunkStarts[LCR_renderer.loadedChunks[
pixel->modelIndex]];
2024-07-29 17:42:40 +02:00
2024-08-30 01:04:07 +02:00
uint8_t type = triData[pixel->triangleIndex] >> 4;
2024-07-29 17:42:40 +02:00
2024-08-30 01:04:07 +02:00
LCR_loadImage(triData[pixel->triangleIndex] & 0x0f);
2024-08-01 21:41:21 +02:00
2024-08-30 01:04:07 +02:00
if (type == 0) // floor?
{
if (v[0][1] != v[1][1] || v[1][1] != v[2][1]) // angled floor?
LCR_imageChangeBrightness(1);
2024-07-30 02:47:42 +02:00
2024-08-30 01:04:07 +02:00
for (int i = 0; i < 6; ++i)
LCR_renderer.triUVs[i] = ((
(v[i / 2][(i % 2) * 2]) *
LCR_IMAGE_SIZE) / LCR_RENDERER_UNIT);
}
else
2024-07-29 17:42:40 +02:00
{
2024-08-30 01:04:07 +02:00
if (type == 1)
LCR_imageChangeBrightness(0);
2024-07-29 17:42:40 +02:00
2024-08-30 01:04:07 +02:00
for (int i = 0; i < 6; ++i)
{
LCR_renderer.triUVs[i] = ((
(v[i / 2][i % 2 ? 1 : (type == 1 ? 2 : 0)]) *
LCR_IMAGE_SIZE) / LCR_RENDERER_UNIT);
if (i % 2)
LCR_renderer.triUVs[i] = LCR_IMAGE_SIZE -
LCR_renderer.triUVs[i];
}
2024-07-29 17:42:40 +02:00
}
2024-08-30 01:04:07 +02:00
// shift the UVs to the origin (prevent high values of UV coords)
for (int i = 0; i < 2; ++i)
{
uint8_t minCoord = LCR_renderer.triUVs[i] <
LCR_renderer.triUVs[2 + i] ? (0 + i) : (2 + i);
2024-08-01 21:41:21 +02:00
2024-08-30 01:04:07 +02:00
if (LCR_renderer.triUVs[4 + i] < LCR_renderer.triUVs[minCoord])
minCoord = 4 + i;
2024-08-01 21:41:21 +02:00
2024-08-30 01:04:07 +02:00
S3L_Unit shiftBy = LCR_renderer.triUVs[minCoord] % LCR_IMAGE_SIZE;
2024-08-01 21:41:21 +02:00
2024-08-30 01:04:07 +02:00
if (shiftBy < 0)
shiftBy += LCR_IMAGE_SIZE;
2024-08-01 21:41:21 +02:00
2024-08-30 01:04:07 +02:00
shiftBy -= LCR_renderer.triUVs[minCoord];
2024-08-01 21:41:21 +02:00
2024-08-30 01:04:07 +02:00
LCR_renderer.triUVs[i] += shiftBy;
LCR_renderer.triUVs[2 + i] += shiftBy;
LCR_renderer.triUVs[4 + i] += shiftBy;
}
2024-08-01 21:41:21 +02:00
}
2024-07-29 17:42:40 +02:00
}
2024-08-06 01:34:38 +02:00
uint16_t color;
2024-07-29 17:42:40 +02:00
2024-09-01 14:06:24 +02:00
2024-08-06 01:34:38 +02:00
#if LCR_SETTING_TEXTURE_SUBSAMPLE != 0
if (LCR_renderer.texSubsampleCount == 0)
{
#endif
int barycentric[3];
barycentric[0] = pixel->barycentric[0] / 8;
barycentric[1] = pixel->barycentric[1] / 8;
barycentric[2] = pixel->barycentric[2] / 8;
color = LCR_sampleImage(
(barycentric[0] * LCR_renderer.triUVs[0] +
barycentric[1] * LCR_renderer.triUVs[2] +
barycentric[2] * LCR_renderer.triUVs[4])
/ (S3L_FRACTIONS_PER_UNIT / 8),
(barycentric[0] * LCR_renderer.triUVs[1] +
barycentric[1] * LCR_renderer.triUVs[3] +
barycentric[2] * LCR_renderer.triUVs[5])
/ (S3L_FRACTIONS_PER_UNIT / 8));
#if LCR_SETTING_TEXTURE_SUBSAMPLE != 0
LCR_renderer.texSubsampleCount = LCR_SETTING_TEXTURE_SUBSAMPLE;
}
2024-07-29 17:42:40 +02:00
2024-08-06 01:34:38 +02:00
LCR_renderer.texSubsampleCount--;
#endif
2024-07-29 17:42:40 +02:00
LCR_drawPixelXYUnsafe(pixel->x,pixel->y,color);
2024-07-22 01:16:16 +02:00
}
2024-08-14 15:19:54 +02:00
S3L_Index _LCR_rendererAddMapVert(S3L_Unit x, S3L_Unit y, S3L_Unit z)
2024-07-22 01:16:16 +02:00
{
S3L_Index index = 0;
2024-08-02 01:01:02 +02:00
S3L_Unit *verts = LCR_renderer.mapVerts;
2024-07-22 01:16:16 +02:00
2024-08-14 15:19:54 +02:00
while (index < LCR_renderer.mapModel.vertexCount) // if exists, return index
2024-07-22 01:16:16 +02:00
{
2024-08-02 01:01:02 +02:00
if (verts[0] == x && verts[1] == y && verts[2] == z)
2024-07-22 01:16:16 +02:00
return index;
2024-08-02 01:01:02 +02:00
verts += 3;
2024-07-22 01:16:16 +02:00
index++;
}
// if it doesn't exist, add it
2024-08-14 15:19:54 +02:00
if (LCR_renderer.mapModel.vertexCount < LCR_SETTING_MAX_MAP_VERTICES)
2024-07-22 01:16:16 +02:00
{
2024-08-02 01:01:02 +02:00
*verts = x; verts++;
*verts = y; verts++;
*verts = z;
2024-08-14 15:19:54 +02:00
LCR_renderer.mapModel.vertexCount++;
return LCR_renderer.mapModel.vertexCount - 1;
2024-07-22 01:16:16 +02:00
}
2024-08-02 00:05:03 +02:00
LCR_log("couldn't add map vertex!");
2024-07-22 01:16:16 +02:00
return 0;
}
2024-08-14 15:19:54 +02:00
void _LCR_rendererAddMapTri(S3L_Index a, S3L_Index b, S3L_Index c, uint8_t mat)
2024-07-22 01:16:16 +02:00
{
2024-08-14 15:19:54 +02:00
if (LCR_renderer.mapModel.triangleCount < LCR_SETTING_MAX_MAP_TRIANGLES)
2024-07-22 01:16:16 +02:00
{
2024-08-01 21:41:21 +02:00
S3L_Index *t =
2024-08-14 15:19:54 +02:00
&(LCR_renderer.mapTris[LCR_renderer.mapModel.triangleCount * 3]);
2024-07-22 01:16:16 +02:00
2024-07-31 16:07:25 +02:00
*t = a; t++;
*t = b; t++;
2024-07-22 01:16:16 +02:00
*t = c;
2024-08-14 15:19:54 +02:00
LCR_renderer.mapTriangleData[LCR_renderer.mapModel.triangleCount] =
2024-08-01 21:41:21 +02:00
mat;
2024-08-14 15:19:54 +02:00
LCR_renderer.mapModel.triangleCount++;
2024-07-22 01:16:16 +02:00
}
2023-09-16 20:35:01 +02:00
}
2024-08-14 15:19:54 +02:00
void _LCR_rendererSwapMapTris(unsigned int index1, unsigned int index2)
2024-07-24 23:16:13 +02:00
{
2024-08-01 21:41:21 +02:00
uint8_t tmpMat;
S3L_Index tmpIndex,
2024-08-02 01:01:02 +02:00
*t1 = LCR_renderer.mapTris + index1 * 3,
*t2 = LCR_renderer.mapTris + index2 * 3;
2024-07-24 23:16:13 +02:00
for (int i = 0; i < 3; ++i)
{
2024-08-01 21:41:21 +02:00
tmpIndex = t1[i];
2024-07-24 23:16:13 +02:00
t1[i] = t2[i];
2024-08-01 21:41:21 +02:00
t2[i] = tmpIndex;
2024-07-24 23:16:13 +02:00
}
2024-08-01 21:41:21 +02:00
tmpMat = LCR_renderer.mapTriangleData[index1];
2024-08-02 01:01:02 +02:00
LCR_renderer.mapTriangleData[index1] = LCR_renderer.mapTriangleData[index2];
2024-08-01 21:41:21 +02:00
LCR_renderer.mapTriangleData[index2] = tmpMat;
2024-07-24 23:16:13 +02:00
}
2024-08-14 15:19:54 +02:00
int _LCR_rendererQuadCoversTri(const S3L_Unit quad[8], const S3L_Unit tri[6])
2024-07-29 17:42:40 +02:00
{
for (int i = 0; i < 3; ++i) // for each triangle point
{
int covered = 0;
for (int j = 0; j < 3; ++j) // for each quad subtriangle
{
uint8_t winds = 0;
for (int k = 0; k < 3; ++k) // for each subtriangle side
{
S3L_Unit w = // triangle winding
(quad[(2 * (j + ((k + 1) % 3))) % 8 + 1] -
quad[(2 * (j + k)) % 8 + 1]) *
(tri[2 * i] - quad[(2 * (j + (k + 1) % 3)) % 8]) -
(quad[(2 * (j + ((k + 1) % 3))) % 8] - quad[(2 * (j + k)) % 8])
* (tri[2 * i + 1] - quad[(2 * (j + (k + 1) % 3)) % 8 + 1]);
if (w > 0)
winds |= 1;
else if (w < 0)
winds |= 2;
}
if (winds != 3) // no opposite winds?
{
covered = 1;
break;
}
}
if (!covered)
return 0;
}
return 1;
}
2024-07-24 23:16:13 +02:00
/**
Checks whether two triangles (and potenrially their neighbors) cover each
other, in return values lowest bit means whether t1 is covered and the second
lowest bit means whether t2 is covered.
*/
2024-08-14 15:19:54 +02:00
uint8_t _LCR_rendererCheckMapTriCover(const S3L_Index *t1,
2024-07-24 23:16:13 +02:00
const S3L_Index *t2)
{
if ((t1[0] == t2[0] || t1[0] == t2[1] || t1[0] == t2[2]) &&
(t1[1] == t2[0] || t1[1] == t2[1] || t1[1] == t2[2]) &&
(t1[2] == t2[0] || t1[2] == t2[1] || t1[2] == t2[2]))
2024-07-28 02:00:36 +02:00
return 0x03;
2024-07-24 23:16:13 +02:00
2024-07-28 02:00:36 +02:00
uint8_t result = 0;
int plane = -1;
2024-08-02 00:05:03 +02:00
S3L_Unit *vertices[6];
2024-07-28 02:00:36 +02:00
for (int i = 0; i < 3; ++i)
2024-08-02 00:05:03 +02:00
{
2024-08-02 01:01:02 +02:00
vertices[i] = LCR_renderer.mapVerts + 3 * t1[i];
vertices[3 + i] = LCR_renderer.mapVerts + 3 * t2[i];
2024-08-02 00:05:03 +02:00
}
for (int i = 0; i < 3; ++i)
if (vertices[0][i] == vertices[1][i] && vertices[1][i] == vertices[2][i] &&
vertices[2][i] == vertices[3][i] && vertices[3][i] == vertices[4][i] &&
vertices[4][i] == vertices[5][i])
2024-07-28 02:00:36 +02:00
{
plane = i;
break;
}
if (plane >= 0) // both triangles in the same plane => then do more checks
{
2024-08-02 01:01:02 +02:00
if (S3L_abs(vertices[0][0] - vertices[3][0]) +
S3L_abs(vertices[0][1] - vertices[3][1]) +
S3L_abs(vertices[0][2] - vertices[3][2]) >
2 * LCR_RENDERER_UNIT)
return 0; // quick manhattan distance bailout condition
2024-07-28 02:00:36 +02:00
for (int j = 0; j < 2; ++j)
{
S3L_Unit points2D[14]; // tri1, quad (tri2 + 1 extra vert)
int coordX = plane == 0 ? 1 : 0,
coordY = plane == 2 ? 1 : 2;
for (int i = 0; i < 3; ++i)
{
2024-08-02 00:05:03 +02:00
points2D[i * 2] = vertices[i][coordX];
points2D[i * 2 + 1] = vertices[i][coordY];
points2D[6 + i * 2] = vertices[3 + i][coordX];
points2D[6 + i * 2 + 1] = vertices[3 + i][coordY];
2024-07-28 02:00:36 +02:00
}
points2D[12] = (4 * points2D[6] + 3 * points2D[8] + points2D[10]) / 8;
points2D[13] = (4 * points2D[7] + 3 * points2D[9] + points2D[11]) / 8;
// first: does the triangle alone cover the other one?
2024-08-14 15:19:54 +02:00
if (_LCR_rendererQuadCoversTri(points2D + 6,points2D))
2024-07-28 02:00:36 +02:00
result |= 1 << j;
else
{
// now check if this triangle along with a neighbor cover the other one
2024-08-02 01:01:02 +02:00
S3L_Index *t3 = LCR_renderer.mapTris;
2024-07-28 02:00:36 +02:00
2024-08-14 15:19:54 +02:00
for (int i = 0; i < LCR_renderer.mapModel.triangleCount; ++i)
2024-07-28 02:00:36 +02:00
{
uint8_t sharedVerts =
(t3[0] == t2[0] || t3[0] == t2[1] || t3[0] == t2[2]) |
((t3[1] == t2[0] || t3[1] == t2[1] || t3[1] == t2[2]) << 1) |
((t3[2] == t2[0] || t3[2] == t2[1] || t3[2] == t2[2]) << 2);
if (
t3 != t1 && t3 != t2 &&
(sharedVerts == 3 || sharedVerts == 5 || sharedVerts == 6) &&
2024-08-02 01:01:02 +02:00
LCR_renderer.mapVerts[3 * t3[0] + plane] ==
LCR_renderer.mapVerts[3 * t3[1] + plane] &&
LCR_renderer.mapVerts[3 * t3[1] + plane] ==
LCR_renderer.mapVerts[3 * t3[2] + plane] &&
LCR_renderer.mapVerts[3 * t3[0] + plane] ==
LCR_renderer.mapVerts[3 * t1[0] + plane]
2024-07-28 02:00:36 +02:00
)
{
// here shares exactly two vertices and is in the same plane
uint8_t freeVert =
sharedVerts == 3 ? 2 : (sharedVerts == 5 ? 1 : 0);
2024-08-02 01:01:02 +02:00
points2D[12] = LCR_renderer.mapVerts[3 * t3[freeVert] + coordX];
points2D[13] = LCR_renderer.mapVerts[3 * t3[freeVert] + coordY];
2024-07-28 02:00:36 +02:00
2024-08-14 15:19:54 +02:00
if (_LCR_rendererQuadCoversTri(points2D + 6,points2D))
2024-07-28 02:00:36 +02:00
{
result |= 1 << j;
break;
}
}
t3 += 3;
}
}
2024-08-02 00:05:03 +02:00
// now swap both triangles and do it all again:
2024-07-28 02:00:36 +02:00
const S3L_Index *tmp = t1;
t1 = t2;
t2 = tmp;
2024-08-02 00:05:03 +02:00
for (int i = 0; i < 3; ++i)
{
S3L_Unit *tmpCoord = vertices[i];
vertices[i] = vertices[3 + i];
vertices[3 + i] = tmpCoord;
}
2024-07-28 02:00:36 +02:00
}
}
return result;
}
2024-07-24 23:16:13 +02:00
/**
Removes map triangles that are covered by other triangles (and also vertices
that by this become unused). This makes the map model smaller, faster and
prevents bleeding through due to z-bugger imprecisions.
*/
2024-08-02 01:01:02 +02:00
void _LCR_cullHiddenMapTris(void)
2024-07-24 23:16:13 +02:00
{
2024-08-02 00:05:03 +02:00
LCR_log("culling invisible triangles");
2024-07-24 23:16:13 +02:00
int n = 0; // number of removed elements
int i = 0;
2024-08-02 01:01:02 +02:00
S3L_Index *t1 = LCR_renderer.mapTris, *t2;
2024-07-24 23:16:13 +02:00
/*
We'll be moving the covered triangles to the end of the array, then at the
2024-07-31 16:07:25 +02:00
end we'll just shorten the array by number of removed triangles.
2024-07-24 23:16:13 +02:00
*/
2024-08-14 15:19:54 +02:00
while (i < LCR_renderer.mapModel.triangleCount - n)
2024-07-24 23:16:13 +02:00
{
2024-07-31 16:07:25 +02:00
t2 = t1 + 3; // t2 is the the other triangle against which we check
2024-07-24 23:16:13 +02:00
int t1Covered = 0;
2024-08-14 15:19:54 +02:00
for (int j = i + 1; j < LCR_renderer.mapModel.triangleCount; ++j)
2024-07-24 23:16:13 +02:00
{
2024-08-14 15:19:54 +02:00
uint8_t cover = _LCR_rendererCheckMapTriCover(t1,t2);
2024-07-24 23:16:13 +02:00
t1Covered |= cover & 0x01;
if (cover & 0x02)
{
2024-08-14 15:19:54 +02:00
if (j < LCR_renderer.mapModel.triangleCount - n)
2024-07-24 23:16:13 +02:00
{
2024-08-14 15:19:54 +02:00
_LCR_rendererSwapMapTris(j,
LCR_renderer.mapModel.triangleCount - 1 - n);
2024-07-24 23:16:13 +02:00
n++;
}
}
2024-07-31 16:07:25 +02:00
t2 += 3; // check next triangle
2024-07-24 23:16:13 +02:00
}
if (t1Covered)
{
2024-08-14 15:19:54 +02:00
_LCR_rendererSwapMapTris(i,
LCR_renderer.mapModel.triangleCount - 1 - n);
2024-08-01 21:41:21 +02:00
2024-07-24 23:16:13 +02:00
n++;
2024-07-31 16:07:25 +02:00
// we stay at this position because we've swapped the triangle here
2024-07-24 23:16:13 +02:00
}
else
{
t1 += 3;
i++;
}
}
2024-08-14 15:19:54 +02:00
LCR_renderer.mapModel.triangleCount -= n; // cut off the removed triangles
2024-07-24 23:16:13 +02:00
// remove unused vertices:
i = 0;
2024-08-14 15:19:54 +02:00
while (i < LCR_renderer.mapModel.vertexCount)
2024-07-24 23:16:13 +02:00
{
int used = 0;
2024-08-14 15:19:54 +02:00
for (int j = 0; j < LCR_renderer.mapModel.triangleCount * 3; ++j)
2024-08-02 01:01:02 +02:00
if (LCR_renderer.mapTris[j] == i)
2024-07-24 23:16:13 +02:00
{
used = 1;
break;
}
if (used)
i++;
else
{
for (int j = 0; j < 3; ++j)
2024-08-02 01:01:02 +02:00
LCR_renderer.mapVerts[3 * i + j] =
2024-08-14 15:19:54 +02:00
LCR_renderer.mapVerts[(LCR_renderer.mapModel.vertexCount - 1) * 3 + j];
2024-07-24 23:16:13 +02:00
2024-08-14 15:19:54 +02:00
for (int j = 0; j < LCR_renderer.mapModel.triangleCount * 3; ++j)
if (LCR_renderer.mapTris[j] == LCR_renderer.mapModel.vertexCount - 1)
2024-08-02 01:01:02 +02:00
LCR_renderer.mapTris[j] = i;
2024-07-24 23:16:13 +02:00
2024-08-14 15:19:54 +02:00
LCR_renderer.mapModel.vertexCount--;
2024-07-24 23:16:13 +02:00
}
}
}
2024-08-06 01:34:38 +02:00
void _LCR_makeMapChunks(void)
{
LCR_log("making map chunks");
S3L_Index start = 0;
for (int chunkNo = 0; chunkNo < LCR_RENDERER_CHUNKS_TOTAL; ++chunkNo)
{
S3L_Unit chunkCorner[3];
const S3L_Index *tri = LCR_renderer.mapTris + 3 * start;
LCR_renderer.chunkStarts[chunkNo] = start;
chunkCorner[0] = (chunkNo & 0x03) * LCR_RENDERER_CHUNK_SIZE_HORIZONTAL;
chunkCorner[1] = ((chunkNo >> 2) & 0x03) * (LCR_RENDERER_CHUNK_SIZE_HORIZONTAL / 2);
chunkCorner[2] = ((chunkNo >> 4) & 0x03) * LCR_RENDERER_CHUNK_SIZE_HORIZONTAL;
chunkCorner[0] -= LCR_MAP_SIZE_BLOCKS * LCR_RENDERER_UNIT / 2;
chunkCorner[1] -= LCR_MAP_SIZE_BLOCKS * LCR_RENDERER_UNIT / 4;
chunkCorner[2] -= LCR_MAP_SIZE_BLOCKS * LCR_RENDERER_UNIT / 2;
2024-08-14 15:19:54 +02:00
for (int i = start; i < LCR_renderer.mapModel.triangleCount; ++i)
2024-08-06 01:34:38 +02:00
{
const S3L_Unit *v = LCR_renderer.mapVerts + 3 * tri[0];
if (v[0] >= chunkCorner[0] &&
v[0] < chunkCorner[0] + LCR_RENDERER_CHUNK_SIZE_HORIZONTAL &&
v[1] >= chunkCorner[1] &&
v[1] < chunkCorner[1] + (LCR_RENDERER_CHUNK_SIZE_HORIZONTAL / 2) &&
v[2] >= chunkCorner[2] &&
v[2] < chunkCorner[2] + LCR_RENDERER_CHUNK_SIZE_HORIZONTAL)
{
2024-08-14 15:19:54 +02:00
_LCR_rendererSwapMapTris(i,start);
2024-08-06 01:34:38 +02:00
start++;
}
tri += 3;
}
}
}
2024-07-24 20:28:57 +02:00
/**
2024-07-30 02:47:42 +02:00
Builds the internal 3D model of the currently loaded map. Returns 1 on
success, 0 otherwise (e.g. not enough space).
2024-07-24 20:28:57 +02:00
*/
2024-08-02 01:01:02 +02:00
uint8_t _LCR_buildMapModel(void)
2023-09-16 20:35:01 +02:00
{
2024-08-02 00:05:03 +02:00
LCR_log("building map model");
2024-07-22 01:16:16 +02:00
uint8_t blockShapeBytes[LCR_MAP_BLOCK_SHAPE_MAX_BYTES];
uint8_t blockShapeByteCount;
2024-08-14 15:19:54 +02:00
S3L_model3DInit(LCR_renderer.mapVerts,0,LCR_renderer.mapTris,0,
&LCR_renderer.mapModel);
2024-07-22 01:16:16 +02:00
2024-07-24 20:28:57 +02:00
for (int j = 0; j < LCR_currentMap.blockCount; ++j)
2024-07-22 01:16:16 +02:00
{
2024-08-02 00:05:03 +02:00
if ((j + 1) % LCR_SETTING_TRIANGLE_CULLING_PERIOD == 0)
2024-08-02 01:01:02 +02:00
_LCR_cullHiddenMapTris();
2024-08-02 00:05:03 +02:00
2024-08-01 21:41:21 +02:00
S3L_Unit originOffset = -1 * LCR_MAP_SIZE_BLOCKS / 2 * LCR_RENDERER_UNIT;
2024-08-02 01:01:02 +02:00
S3L_Index triIndices[3];
2024-08-01 21:41:21 +02:00
const uint8_t *block = LCR_currentMap.blocks + j * LCR_BLOCK_SIZE;
uint8_t
blockType = block[0],
edgeBits, // bottom, top, left, right, front, bottom
bx, by, bz, // block coords
vx, vy, vz, // vertex coords
vi = 0; // vertex index (0, 1 or 2)
2024-07-24 20:28:57 +02:00
2024-08-01 21:41:21 +02:00
LCR_mapBlockGetCoords(block,&bx,&by,&bz);
LCR_mapGetBlockShape(blockType,LCR_mapBlockGetTransform(block),
blockShapeBytes,&blockShapeByteCount);
2024-07-24 20:28:57 +02:00
for (int i = 0; i < blockShapeByteCount; ++i)
{
2024-08-01 21:41:21 +02:00
if (vi == 0)
edgeBits = (by == 0) |
((by == LCR_MAP_SIZE_BLOCKS - 1) << 1) |
((bx == 0) << 2) |
((bx == LCR_MAP_SIZE_BLOCKS - 1) << 3) |
((bz == 0) << 4) |
((bz == LCR_MAP_SIZE_BLOCKS - 1) << 5);
2024-07-24 20:28:57 +02:00
LCR_decodeMapBlockCoords(blockShapeBytes[i],&vx,&vy,&vz);
2024-08-01 21:41:21 +02:00
edgeBits &= (vy == 0) | ((vy == LCR_BLOCK_SHAPE_COORD_MAX) << 1) |
((vx == 0) << 2) | ((vx == LCR_BLOCK_SHAPE_COORD_MAX) << 3) |
((vz == 0) << 4) | ((vz == LCR_BLOCK_SHAPE_COORD_MAX) << 5);
2024-07-31 16:07:25 +02:00
2024-08-14 15:19:54 +02:00
triIndices[vi] = _LCR_rendererAddMapVert(
2024-08-01 21:41:21 +02:00
originOffset + (((S3L_Unit) bx) * LCR_RENDERER_UNIT) +
(LCR_RENDERER_UNIT * ((S3L_Unit) vx)) / LCR_BLOCK_SHAPE_COORD_MAX,
(originOffset + (((S3L_Unit) by) * LCR_RENDERER_UNIT)) / 2 +
(LCR_RENDERER_UNIT / 2 * ((S3L_Unit) vy)) / LCR_BLOCK_SHAPE_COORD_MAX,
originOffset + (((S3L_Unit) bz) * LCR_RENDERER_UNIT) +
(LCR_RENDERER_UNIT * ((S3L_Unit) vz)) / LCR_BLOCK_SHAPE_COORD_MAX);
2024-07-24 20:28:57 +02:00
if (vi < 2)
vi++;
else
{
2024-07-31 16:07:25 +02:00
// don't add triangles completely at the floor or ceiling of the map
2024-08-01 21:41:21 +02:00
if (!edgeBits)
{
2024-08-02 01:01:02 +02:00
#define VERT(n,c) LCR_renderer.mapVerts[3 * n + c]
2024-08-01 21:41:21 +02:00
uint8_t blockMat = LCR_mapBlockGetMaterial(block);
2024-08-02 01:01:02 +02:00
uint8_t triData =
(((VERT(triIndices[0],0) == VERT(triIndices[1],0)) &&
(VERT(triIndices[1],0) == VERT(triIndices[2],0))) << 4) |
(((VERT(triIndices[0],2) == VERT(triIndices[1],2)) &&
(VERT(triIndices[1],2) == VERT(triIndices[2],2))) << 5);
2024-08-01 21:41:21 +02:00
#undef VERT
2024-08-02 01:01:02 +02:00
if (triData & 0xf0) // wall?
2024-08-01 21:41:21 +02:00
{
2024-08-02 01:01:02 +02:00
triData |=
2024-08-01 21:41:21 +02:00
((blockMat == LCR_BLOCK_MATERIAL_CONCRETE) ||
(blockMat == LCR_BLOCK_MATERIAL_ICE) ||
(blockType == LCR_BLOCK_FULL_ACCEL) ||
2024-08-06 01:34:38 +02:00
(blockType == LCR_BLOCK_FULL_FAN)) ?
2024-08-01 21:41:21 +02:00
LCR_IMAGE_WALL_CONCRETE : LCR_IMAGE_WALL_WOOD;
}
else
{ // TODO: tidy this mess?
if (blockType == LCR_BLOCK_FULL_ACCEL)
2024-08-02 01:01:02 +02:00
triData |= LCR_IMAGE_GROUND_ACCEL;
2024-08-06 01:34:38 +02:00
else if (blockType == LCR_BLOCK_FULL_FAN)
triData |= LCR_IMAGE_GROUND_FAN;
2024-08-01 21:41:21 +02:00
else
switch (blockMat)
{
case LCR_BLOCK_MATERIAL_CONCRETE:
2024-08-02 01:01:02 +02:00
triData |= LCR_IMAGE_GROUND_CONCRETE;
2024-08-01 21:41:21 +02:00
break;
case LCR_BLOCK_MATERIAL_GRASS:
2024-08-02 01:01:02 +02:00
triData |= LCR_IMAGE_GROUND_GRASS;
2024-08-01 21:41:21 +02:00
break;
case LCR_BLOCK_MATERIAL_DIRT:
2024-08-02 01:01:02 +02:00
triData |= LCR_IMAGE_GROUND_DIRT;
2024-08-01 21:41:21 +02:00
break;
case LCR_BLOCK_MATERIAL_ICE:
2024-08-02 01:01:02 +02:00
triData |= LCR_IMAGE_GROUND_ICE;
2024-08-01 21:41:21 +02:00
break;
default:
break;
}
}
2024-07-31 16:07:25 +02:00
2024-08-14 15:19:54 +02:00
_LCR_rendererAddMapTri(triIndices[0],triIndices[1],triIndices[2],triData);
2024-08-02 01:01:02 +02:00
}
2024-08-01 21:41:21 +02:00
2024-07-24 20:28:57 +02:00
vi = 0;
}
}
2024-07-22 01:16:16 +02:00
}
2023-09-16 20:35:01 +02:00
2024-07-31 16:07:25 +02:00
// TODO: also cull the triangles in the loop by some N steps
2024-08-02 01:01:02 +02:00
_LCR_cullHiddenMapTris();
LCR_log("map model built");
2024-07-24 23:16:13 +02:00
2023-09-16 20:35:01 +02:00
return 1;
}
2024-08-14 15:19:54 +02:00
void _LCR_rendererComputeLOD(void)
2024-08-13 00:53:04 +02:00
{
LCR_log("computing LOD");
for (int i = 0; i < LCR_RENDERER_LOD_BLOCKS; ++i)
2024-08-29 00:10:16 +02:00
LCR_renderer.gridOfLODs[i] = 0;
2024-08-13 00:53:04 +02:00
for (int i = 0; i < LCR_currentMap.blockCount; ++i)
{
uint8_t x, y, z;
LCR_mapBlockGetCoords(LCR_currentMap.blocks + i * LCR_BLOCK_SIZE,&x,&y,&z);
x /= 8;
y /= 8;
z /= 8;
2024-08-29 00:10:16 +02:00
LCR_renderer.gridOfLODs[z * 8 + y] |= (0x01 << x);
2024-08-13 00:53:04 +02:00
}
}
2023-09-16 20:35:01 +02:00
uint8_t LCR_rendererInit(void)
{
2024-08-02 00:05:03 +02:00
LCR_log("initializing renderer");
2024-09-01 14:06:24 +02:00
LCR_renderer.frame = 0;
2024-08-02 01:01:02 +02:00
if (!_LCR_buildMapModel())
2023-09-16 20:35:01 +02:00
return 0;
2024-08-06 01:34:38 +02:00
_LCR_makeMapChunks();
2024-08-14 15:19:54 +02:00
_LCR_rendererComputeLOD();
2024-08-13 00:53:04 +02:00
2024-08-30 01:04:07 +02:00
LCR_renderer.carModel = LCR_renderer.models + 8;
S3L_model3DInit(
2024-09-01 14:06:24 +02:00
#if LCR_SETTING_ANIMATE_CAR
LCR_renderer.animatedCarVerts
#else
LCR_carVertices
#endif
,LCR_CAR_VERTEX_COUNT,
2024-08-30 01:04:07 +02:00
LCR_carTriangles,LCR_CAR_TRIANGLE_COUNT,
LCR_renderer.carModel);
2024-09-01 14:06:24 +02:00
#if LCR_SETTING_ANIMATE_CAR
for (int i = 0; i < LCR_CAR_VERTEX_COUNT * 3; ++i)
LCR_renderer.animatedCarVerts[i] = LCR_carVertices[i];
int count[2];
count[0] = 0;
count[1] = 0;
for (int i = 0; i < 4; ++i)
LCR_renderer.wheelRotationCenters[i] = 0;
for (int i = 0; i < LCR_CAR_VERTEX_COUNT; ++i)
if (LCR_carVertexTypes[i] > 0) // wheel?
{
uint8_t front = LCR_carVertexTypes[i] == 1;
LCR_renderer.wheelRotationCenters[0 + 2 * front] +=
LCR_carVertices[3 * i + 2];
LCR_renderer.wheelRotationCenters[1 + 2 * front] +=
LCR_carVertices[3 * i + 1];
count[front]++;
}
LCR_renderer.wheelRotationCenters[0] /= count[0];
LCR_renderer.wheelRotationCenters[1] /= count[0];
LCR_renderer.wheelRotationCenters[2] /= count[1];
LCR_renderer.wheelRotationCenters[3] /= count[1];
LCR_renderer.wheelRotation = 0;
LCR_renderer.wheelTurn = 0;
#endif
2024-08-30 01:04:07 +02:00
LCR_renderer.carModel->transform.translation.x -= 2 * LCR_RENDERER_UNIT;
LCR_renderer.carModel->transform.scale.x = LCR_RENDERER_UNIT / 3;
LCR_renderer.carModel->transform.scale.y =
LCR_renderer.carModel->transform.scale.x;
LCR_renderer.carModel->transform.scale.z =
LCR_renderer.carModel->transform.scale.x;
2024-08-06 01:34:38 +02:00
S3L_sceneInit(
2024-08-29 00:10:16 +02:00
LCR_renderer.models,LCR_RENDERER_MODEL_COUNT,&LCR_renderer.scene);
2023-09-16 20:35:01 +02:00
return 1;
}
2023-09-17 13:21:19 +02:00
void LCR_rendererMoveCamera(LCR_SpaceUnit forwRightUpOffset[3],
LCR_SpaceUnit yawPitchOffset[2])
{
S3L_Vec4 f, r, u;
2024-08-02 01:01:02 +02:00
S3L_rotationToDirections(LCR_renderer.scene.camera.transform.rotation,
2023-09-17 13:21:19 +02:00
S3L_FRACTIONS_PER_UNIT,&f,&r,&u);
2024-08-02 01:01:02 +02:00
LCR_renderer.scene.camera.transform.translation.x +=
2023-09-17 13:21:19 +02:00
((f.x * forwRightUpOffset[0] + r.x * forwRightUpOffset[1] +
u.x * forwRightUpOffset[2]) * S3L_FRACTIONS_PER_UNIT) / LCR_SQUARE_SIDE_LEN;
2024-08-02 01:01:02 +02:00
LCR_renderer.scene.camera.transform.translation.y +=
2023-09-17 13:21:19 +02:00
((f.y * forwRightUpOffset[0] + r.y * forwRightUpOffset[1] +
u.y * forwRightUpOffset[2]) * S3L_FRACTIONS_PER_UNIT) / LCR_SQUARE_SIDE_LEN;
2024-08-02 01:01:02 +02:00
LCR_renderer.scene.camera.transform.translation.z +=
2023-09-17 13:21:19 +02:00
((f.z * forwRightUpOffset[0] + r.z * forwRightUpOffset[1] +
u.z * forwRightUpOffset[2]) * S3L_FRACTIONS_PER_UNIT) / LCR_SQUARE_SIDE_LEN;
2024-08-02 01:01:02 +02:00
LCR_renderer.scene.camera.transform.rotation.y +=
2023-09-17 13:21:19 +02:00
(yawPitchOffset[0] * S3L_FRACTIONS_PER_UNIT) / LCR_SQUARE_SIDE_LEN;
2024-08-02 01:01:02 +02:00
LCR_renderer.scene.camera.transform.rotation.x +=
2023-09-17 13:21:19 +02:00
(yawPitchOffset[1] * S3L_FRACTIONS_PER_UNIT) / LCR_SQUARE_SIDE_LEN;
2024-08-02 01:01:02 +02:00
if (LCR_renderer.scene.camera.transform.rotation.x > S3L_FRACTIONS_PER_UNIT / 4)
LCR_renderer.scene.camera.transform.rotation.x = S3L_FRACTIONS_PER_UNIT / 4;
2023-09-16 22:52:03 +02:00
2024-08-02 01:01:02 +02:00
if (LCR_renderer.scene.camera.transform.rotation.x < -1 * S3L_FRACTIONS_PER_UNIT / 4)
LCR_renderer.scene.camera.transform.rotation.x = -1 * S3L_FRACTIONS_PER_UNIT / 4;
2024-08-06 01:34:38 +02:00
#define chk(o,c,l) \
if (LCR_renderer.scene.camera.transform.translation.c o l) \
LCR_renderer.scene.camera.transform.translation.c = l;
chk(<,x,-1 * LCR_MAP_SIZE_BLOCKS * LCR_RENDERER_UNIT / 2)
chk(>,x,LCR_MAP_SIZE_BLOCKS * LCR_RENDERER_UNIT / 2)
chk(<,y,-1 * LCR_MAP_SIZE_BLOCKS * LCR_RENDERER_UNIT / 4)
chk(>,y,LCR_MAP_SIZE_BLOCKS * LCR_RENDERER_UNIT / 4)
chk(<,z,-1 * LCR_MAP_SIZE_BLOCKS * LCR_RENDERER_UNIT / 2)
chk(>,z,LCR_MAP_SIZE_BLOCKS * LCR_RENDERER_UNIT / 2)
2023-09-16 22:52:03 +02:00
}
2024-08-13 00:53:04 +02:00
/**
Fast and safe rect drawing function (handles out of screen coords).
*/
void LCR_rendererDrawRect(int x, int y, unsigned int w, unsigned int h,
2024-08-13 20:49:52 +02:00
uint16_t color, int dither)
2024-08-13 00:53:04 +02:00
{
if (x >= LCR_EFFECTIVE_RESOLUTION_X || y >= LCR_EFFECTIVE_RESOLUTION_Y)
return;
if (x < 0)
{
if (-1 * x > ((int) w))
return;
w += x;
x = 0;
}
if (x + w > LCR_EFFECTIVE_RESOLUTION_X)
w = LCR_EFFECTIVE_RESOLUTION_X - x;
if (y < 0)
{
if (-1 * y > ((int) h))
return;
2024-08-13 20:49:52 +02:00
h += y;
2024-08-13 00:53:04 +02:00
y = 0;
}
if (y + h > LCR_EFFECTIVE_RESOLUTION_X)
h = LCR_EFFECTIVE_RESOLUTION_Y - y;
unsigned long index = y * LCR_EFFECTIVE_RESOLUTION_X + x;
2024-08-13 20:49:52 +02:00
if (dither)
2024-08-13 00:53:04 +02:00
{
2024-08-13 20:49:52 +02:00
uint8_t parity = (x % 2) == (y % 2);
for (unsigned int i = 0; i < h; ++i)
2024-08-13 00:53:04 +02:00
{
2024-08-13 20:49:52 +02:00
for (unsigned int j = ((i % 2) == parity); j < w; j += 2)
LCR_drawPixel(index + j,color);
index += LCR_EFFECTIVE_RESOLUTION_X;
}
2024-08-13 00:53:04 +02:00
}
2024-08-13 20:49:52 +02:00
else
for (unsigned int i = 0; i < h; ++i)
{
for (unsigned int j = 0; j < w; ++j)
{
LCR_drawPixel(index,color);
index++;
}
index += LCR_EFFECTIVE_RESOLUTION_X - w;
}
2024-08-13 00:53:04 +02:00
}
2024-08-14 15:19:54 +02:00
void _LCR_rendererDrawLODBlock(int blockX, int blockY, int blockZ, unsigned int size,
2024-08-29 15:38:44 +02:00
uint16_t color, uint8_t variability)
2024-08-13 00:53:04 +02:00
{
S3L_Vec4 p, r;
p.x = (blockX - LCR_MAP_SIZE_BLOCKS / 2) * LCR_RENDERER_UNIT
+ LCR_RENDERER_UNIT / 2;
p.y = (blockY - LCR_MAP_SIZE_BLOCKS / 2) * (LCR_RENDERER_UNIT / 2)
+ LCR_RENDERER_UNIT / 4;
p.z = (blockZ - LCR_MAP_SIZE_BLOCKS / 2) * LCR_RENDERER_UNIT
+ LCR_RENDERER_UNIT / 2;
p.w = size;
S3L_project3DPointToScreen(p,LCR_renderer.scene.camera,&r);
if (r.w > 0 && r.z > LCR_SETTING_LOD_DISTANCE * LCR_RENDERER_UNIT &&
r.w < LCR_EFFECTIVE_RESOLUTION_X)
{
2024-08-29 15:38:44 +02:00
switch (variability % 4)
{
case 0: r.w += r.w / 4; r.x += LCR_BLOCK_SIZE / 8; break;
case 1: r.w += r.w / 8; r.y -= LCR_BLOCK_SIZE / 16; break;
case 2: r.w += r.w / 4; break;
default: r.z += LCR_BLOCK_SIZE / 8; break;
}
if (variability % 8 < 5)
LCR_rendererDrawRect(r.x - r.w / 2,r.y - r.w / 2,r.w,r.w,color,1);
else
{
r.w /= 2;
LCR_rendererDrawRect(r.x - r.w / 2,r.y - r.w / 2,r.w,r.w,color,1);
r.w += r.w / 2;
LCR_rendererDrawRect(r.x - r.w / 8,r.y - r.w / 4,r.w,r.w,color,1);
}
2024-08-13 00:53:04 +02:00
}
}
2024-07-30 02:47:42 +02:00
/**
Draws background sky, offsets are in multiples of screen dimensions
(e.g. S3L_FRACTIONS_PER_UNIT / 2 for offsetH means half the screen width).
*/
void LCR_rendererDrawSky(int sky, S3L_Unit offsetH, S3L_Unit offsetV)
2023-09-16 20:35:01 +02:00
{
2024-07-30 02:47:42 +02:00
int anchorPoint[2], y;
unsigned long pixelIndex;
unsigned int topColor, bottomColor;
2023-09-16 20:35:01 +02:00
2024-07-30 21:47:50 +02:00
sky = 8 + 4 * sky;
LCR_loadImage(sky);
2024-07-30 02:47:42 +02:00
topColor = LCR_sampleImage(0,0);
2024-07-22 01:16:16 +02:00
2024-07-30 21:47:50 +02:00
LCR_loadImage(sky + 3);
2024-07-30 02:47:42 +02:00
bottomColor = LCR_sampleImage(LCR_IMAGE_SIZE - 1,LCR_IMAGE_SIZE - 1);
2024-07-22 01:16:16 +02:00
2024-07-30 02:47:42 +02:00
anchorPoint[0] = ((LCR_EFFECTIVE_RESOLUTION_X * offsetH)
/ S3L_FRACTIONS_PER_UNIT) %
(2 * LCR_IMAGE_SIZE * LCR_SETTING_SKY_SIZE);
2024-07-22 01:16:16 +02:00
2024-07-30 02:47:42 +02:00
if (anchorPoint[0] < 0)
anchorPoint[0] += 2 * LCR_IMAGE_SIZE * LCR_SETTING_SKY_SIZE;
2023-09-13 20:51:07 +02:00
2024-07-30 02:47:42 +02:00
anchorPoint[1] =
2024-07-30 21:47:50 +02:00
(LCR_EFFECTIVE_RESOLUTION_Y) / 3 - // 3: we place the center a bit more up
2024-07-30 02:47:42 +02:00
(LCR_EFFECTIVE_RESOLUTION_Y * offsetV) / S3L_FRACTIONS_PER_UNIT
- LCR_IMAGE_SIZE * LCR_SETTING_SKY_SIZE;
pixelIndex = 0;
y = anchorPoint[1] < 0 ? anchorPoint[1] : 0;
2023-09-13 20:51:07 +02:00
2024-07-30 02:47:42 +02:00
while (y < anchorPoint[1] && y < LCR_EFFECTIVE_RESOLUTION_Y) // top strip
{
for (int x = 0; x < LCR_EFFECTIVE_RESOLUTION_X; ++x)
{
LCR_drawPixel(pixelIndex,topColor);
pixelIndex++;
}
y++;
}
anchorPoint[1] += 2 * LCR_IMAGE_SIZE * LCR_SETTING_SKY_SIZE;
int linesLeft = 0;
int skyPart = 0;
while (y < anchorPoint[1] && y < LCR_EFFECTIVE_RESOLUTION_Y) // image strip
{
if (!linesLeft)
{
2024-07-30 21:47:50 +02:00
LCR_loadImage(sky + skyPart);
2024-07-30 02:47:42 +02:00
linesLeft = LCR_IMAGE_SIZE / 2;
skyPart++;
}
if (y >= 0)
{
for (int ix = 0; ix < 2 * LCR_IMAGE_SIZE * LCR_SETTING_SKY_SIZE;
ix += LCR_SETTING_SKY_SIZE)
{
unsigned int color = LCR_getNextImagePixel();
2024-07-30 21:47:50 +02:00
unsigned long startIndex = pixelIndex;
2024-07-30 02:47:42 +02:00
2024-07-30 21:47:50 +02:00
for (int k = 0; k < LCR_SETTING_SKY_SIZE; ++k)
{
if (y + k >= LCR_EFFECTIVE_RESOLUTION_Y)
break;
2024-07-30 02:47:42 +02:00
2024-07-30 21:47:50 +02:00
for (int j = 0; j < LCR_SETTING_SKY_SIZE; ++j)
{
int x = anchorPoint[0] + ix + j;
2024-07-30 02:47:42 +02:00
2024-07-30 21:47:50 +02:00
if (x >= 2 * LCR_IMAGE_SIZE * LCR_SETTING_SKY_SIZE)
x -= 2 * LCR_IMAGE_SIZE * LCR_SETTING_SKY_SIZE;
while (x < LCR_EFFECTIVE_RESOLUTION_X)
{
LCR_drawPixel(startIndex + x,color);
x += 2 * LCR_IMAGE_SIZE * LCR_SETTING_SKY_SIZE;
}
}
startIndex += LCR_EFFECTIVE_RESOLUTION_X;
2024-07-30 02:47:42 +02:00
}
}
pixelIndex += LCR_EFFECTIVE_RESOLUTION_X * LCR_SETTING_SKY_SIZE;
2024-07-30 21:47:50 +02:00
y += LCR_SETTING_SKY_SIZE;
2024-07-30 02:47:42 +02:00
}
else
2024-07-30 21:47:50 +02:00
{
2024-07-30 02:47:42 +02:00
for (int ix = 0; ix < 2 * LCR_IMAGE_SIZE; ++ix)
LCR_getNextImagePixel();
2024-07-30 21:47:50 +02:00
for (int i = 0; i < LCR_SETTING_SKY_SIZE; ++i)
{
if (y >= 0)
for (int x = 0; x < LCR_EFFECTIVE_RESOLUTION_X; ++x)
{
LCR_drawPixel(pixelIndex,topColor);
pixelIndex++;
}
y++;
}
}
2024-07-30 02:47:42 +02:00
linesLeft--;
}
while (y < 0) // can still be the case
y = 0;
while (y < LCR_EFFECTIVE_RESOLUTION_Y) // bottom strip
{
for (int x = 0; x < LCR_EFFECTIVE_RESOLUTION_X; ++x)
{
LCR_drawPixel(pixelIndex,bottomColor);
pixelIndex++;
}
y++;
}
2023-09-17 15:42:46 +02:00
}
2024-08-14 15:19:54 +02:00
void _LCR_rendererLoadMapChunk(uint8_t chunk, int8_t x, int8_t y, int8_t z)
2024-08-06 01:34:38 +02:00
{
2024-08-14 15:19:54 +02:00
LCR_renderer.models[chunk] = LCR_renderer.mapModel;
2024-08-06 01:34:38 +02:00
if (x < 0 || x >= LCR_RENDERER_CHUNK_RESOLUTION ||
y < 0 || y >= LCR_RENDERER_CHUNK_RESOLUTION ||
z < 0 || z >= LCR_RENDERER_CHUNK_RESOLUTION)
{
2024-08-14 15:19:54 +02:00
LCR_renderer.models[chunk].triangleCount = 0;
2024-08-06 01:34:38 +02:00
LCR_renderer.loadedChunks[chunk] = 0;
}
else
{
int blockNum = x | (y << 2) | (z << 4);
LCR_renderer.loadedChunks[chunk] = blockNum;
int triCount =
(blockNum == LCR_RENDERER_CHUNKS_TOTAL - 1 ?
2024-08-14 15:19:54 +02:00
(LCR_renderer.mapModel.triangleCount - 1) :
2024-08-06 01:34:38 +02:00
LCR_renderer.chunkStarts[blockNum + 1])
- LCR_renderer.chunkStarts[blockNum];
if (triCount < 0)
triCount = 0;
2024-08-14 15:19:54 +02:00
LCR_renderer.models[chunk].triangles =
2024-08-06 01:34:38 +02:00
LCR_renderer.mapTris + LCR_renderer.chunkStarts[blockNum] * 3;
2024-08-14 15:19:54 +02:00
LCR_renderer.models[chunk].triangleCount = triCount;
2024-08-06 01:34:38 +02:00
}
}
2024-08-14 15:19:54 +02:00
/**
Loads the map models with 8 chunks that are nearest to a certain point
towards which camera is looking.
*/
void _LCR_rendererLoadMapChunks(void)
2024-08-06 01:34:38 +02:00
{
int8_t camChunk[3], chunkOffsets[3];
S3L_Vec4 cp = LCR_renderer.scene.camera.transform.translation;
2024-08-13 00:53:04 +02:00
S3L_Vec4 cf;
S3L_rotationToDirections(LCR_renderer.scene.camera.transform.rotation,
LCR_RENDERER_CHUNK_SIZE_HORIZONTAL / 2,&cf,0,0);
2024-08-06 01:34:38 +02:00
2024-08-13 00:53:04 +02:00
cp.x += (LCR_MAP_SIZE_BLOCKS * LCR_RENDERER_UNIT) / 2;
cp.y += (LCR_MAP_SIZE_BLOCKS * LCR_RENDERER_UNIT) / 4;
cp.z += (LCR_MAP_SIZE_BLOCKS * LCR_RENDERER_UNIT) / 2;
2024-08-06 01:34:38 +02:00
2024-08-13 00:53:04 +02:00
cf.x += cp.x % LCR_RENDERER_CHUNK_SIZE_HORIZONTAL;
cf.y += cp.y % (LCR_RENDERER_CHUNK_SIZE_HORIZONTAL / 2);
cf.z += cp.z % LCR_RENDERER_CHUNK_SIZE_HORIZONTAL;
2024-08-06 01:34:38 +02:00
2024-08-13 00:53:04 +02:00
camChunk[0] = cp.x / LCR_RENDERER_CHUNK_SIZE_HORIZONTAL;
camChunk[1] = cp.y / (LCR_RENDERER_CHUNK_SIZE_HORIZONTAL / 2);
camChunk[2] = cp.z / LCR_RENDERER_CHUNK_SIZE_HORIZONTAL;
2024-08-06 01:34:38 +02:00
2024-08-13 00:53:04 +02:00
chunkOffsets[0] =
(cf.x >= (LCR_RENDERER_CHUNK_SIZE_HORIZONTAL / 2)) ? 1 : -1;
2024-08-06 01:34:38 +02:00
chunkOffsets[1] =
2024-08-13 00:53:04 +02:00
(cf.y >= (LCR_RENDERER_CHUNK_SIZE_HORIZONTAL / 4)) ? 1 : -1;
chunkOffsets[2] =
(cf.z >= (LCR_RENDERER_CHUNK_SIZE_HORIZONTAL / 2)) ? 1 : -1;
2024-08-06 01:34:38 +02:00
for (uint8_t i = 0; i < 8; ++i)
2024-08-14 15:19:54 +02:00
_LCR_rendererLoadMapChunk(i,
2024-08-06 01:34:38 +02:00
camChunk[0] + ((i & 0x01) ? chunkOffsets[0] : 0),
camChunk[1] + ((i & 0x02) ? chunkOffsets[1] : 0),
camChunk[2] + ((i & 0x04) ? chunkOffsets[2] : 0));
}
2024-08-14 15:19:54 +02:00
/**
Draws the LOD overlay.
*/
2024-08-13 00:53:04 +02:00
void LCR_rendererDrawLOD(void)
{
2024-08-14 15:19:54 +02:00
#if LCR_SETTING_LOD_DISTANCE < 64
2024-08-29 15:38:44 +02:00
int variability = 0;
2024-08-13 00:53:04 +02:00
for (unsigned int i = 0; i < LCR_RENDERER_LOD_BLOCKS; ++i)
2024-08-29 00:10:16 +02:00
if (LCR_renderer.gridOfLODs[i])
2024-08-13 00:53:04 +02:00
{
2024-08-29 00:10:16 +02:00
uint8_t byte = LCR_renderer.gridOfLODs[i];
2024-08-13 00:53:04 +02:00
unsigned int bx, by, bz;
bz = (i / 8) * 8 + 4;
by = (i % 8) * 8 + 4;
for (unsigned int j = 0; j < 8; ++j)
{
if (byte & 0x01)
{
2024-08-29 15:38:44 +02:00
variability = variability < 14 ? variability + 1 : 0;
2024-08-13 00:53:04 +02:00
bx = j * 8 + 4;
2024-08-14 15:19:54 +02:00
2024-08-29 15:38:44 +02:00
_LCR_rendererDrawLODBlock(bx,by,bz,(4 * LCR_EFFECTIVE_RESOLUTION_Y)
/ 3,LCR_SETTING_LOD_COLOR,variability);
2024-08-14 15:19:54 +02:00
}
2024-08-13 00:53:04 +02:00
byte >>= 1;
}
}
2024-08-14 15:19:54 +02:00
#endif
2024-08-13 00:53:04 +02:00
}
2024-08-29 00:10:16 +02:00
void LCR_drawLevelFloor(void)
{
#if LCR_SETTING_FLOOR_PARTICLE_SIZE != 0
#define _STEP ((LCR_MAP_SIZE_BLOCKS * LCR_RENDERER_UNIT) / LCR_SETTING_FLOOR_PARTICLE_RESOLUTION)
S3L_Vec4 floorPoint, projectedPoint;
floorPoint.z = -1 * (LCR_MAP_SIZE_BLOCKS / 2) * LCR_RENDERER_UNIT + _STEP / 2;
floorPoint.y = -1 *(LCR_MAP_SIZE_BLOCKS / 2) * (LCR_RENDERER_UNIT / 2);
floorPoint.w = LCR_SETTING_RESOLUTION_X / LCR_SETTING_FLOOR_PARTICLE_SIZE;
for (uint8_t j = 0; j < LCR_SETTING_FLOOR_PARTICLE_RESOLUTION; ++j)
{
floorPoint.x = -1 * (LCR_MAP_SIZE_BLOCKS / 2) * LCR_RENDERER_UNIT
+ _STEP / 2;
for (uint8_t i = 0; i < LCR_SETTING_FLOOR_PARTICLE_RESOLUTION; ++i)
{
S3L_project3DPointToScreen(floorPoint,LCR_renderer.scene.camera,
&projectedPoint);
if (projectedPoint.w > 0 && projectedPoint.w < LCR_EFFECTIVE_RESOLUTION_X)
LCR_rendererDrawRect(projectedPoint.x,projectedPoint.y,
projectedPoint.w,projectedPoint.w,LCR_SETTING_LOD_COLOR,1);
floorPoint.x += _STEP;
}
floorPoint.z += _STEP;
}
#undef _STEP
#endif
}
2024-09-01 14:06:24 +02:00
void _LCR_rendererAnimateCar(void)
{
for (int i = 0; i < LCR_CAR_VERTEX_COUNT; ++i)
if (LCR_carVertexTypes[i] > 0)
{
S3L_Unit s = S3L_sin(-1 * LCR_renderer.wheelRotation),
c = S3L_cos(-1 * LCR_renderer.wheelRotation);
S3L_Unit v[2], tmp;
unsigned int index = 3 * i;
uint8_t offset = (LCR_carVertexTypes[i] == 1) * 2;
v[0] = LCR_carVertices[index + 2] -
LCR_renderer.wheelRotationCenters[offset];
v[1] = LCR_carVertices[index + 1] -
LCR_renderer.wheelRotationCenters[offset + 1];
tmp = v[0];
v[0] = (v[0] * c - v[1] * s) / S3L_FRACTIONS_PER_UNIT;
v[1] = (tmp * s + v[1] * c) / S3L_FRACTIONS_PER_UNIT;
LCR_renderer.animatedCarVerts[index + 2] =
v[0] + LCR_renderer.wheelRotationCenters[offset];
LCR_renderer.animatedCarVerts[index + 1] =
v[1] + LCR_renderer.wheelRotationCenters[offset + 1];
if (LCR_carVertexTypes[i] > 1)
{
/* Turn front wheels; this is not real turning but just a fake by
skewing in Z and X directions. */
LCR_renderer.animatedCarVerts[index] = LCR_carVertices[index];
LCR_renderer.animatedCarVerts[index + 2] -=
(LCR_renderer.animatedCarVerts[index] * LCR_renderer.wheelTurn)
/ (8 * S3L_FRACTIONS_PER_UNIT);
LCR_renderer.animatedCarVerts[index] +=
((LCR_renderer.animatedCarVerts[index + 2] -
LCR_renderer.wheelRotationCenters[0]) * LCR_renderer.wheelTurn)
/ (2 * S3L_FRACTIONS_PER_UNIT);
}
}
}
2024-07-30 02:47:42 +02:00
void LCR_rendererDraw(void)
2023-09-17 15:42:46 +02:00
{
2024-08-02 01:01:02 +02:00
LCR_renderer.previousTriID = -1;
2024-07-30 02:47:42 +02:00
S3L_newFrame();
2024-08-14 15:19:54 +02:00
_LCR_rendererLoadMapChunks(); // TODO: call only once in a while?
2024-08-06 01:34:38 +02:00
2024-07-30 21:47:50 +02:00
LCR_rendererDrawSky(2,
2024-08-02 01:01:02 +02:00
LCR_renderer.scene.camera.transform.rotation.y,
-4 * LCR_renderer.scene.camera.transform.rotation.x);
2024-09-01 14:06:24 +02:00
LCR_renderer.wheelRotation += 5;
LCR_renderer.wheelTurn = S3L_sin(LCR_renderer.frame * 4);
_LCR_rendererAnimateCar();
2024-07-30 02:47:42 +02:00
2024-08-29 00:10:16 +02:00
LCR_drawLevelFloor();
2024-08-13 00:53:04 +02:00
LCR_rendererDrawLOD();
2024-08-02 01:01:02 +02:00
S3L_drawScene(LCR_renderer.scene);
2024-09-01 14:06:24 +02:00
LCR_renderer.frame++;
2023-09-17 15:42:46 +02:00
}
2023-09-13 20:51:07 +02:00
#endif // guard