1576 lines
44 KiB
C
1576 lines
44 KiB
C
/**
|
|
3D renderer: implements 3D rendering.
|
|
*/
|
|
|
|
#ifndef _LCR_RENDERER_H
|
|
#define _LCR_RENDERER_H
|
|
|
|
#define S3L_RESOLUTION_X LCR_SETTING_RESOLUTION_X
|
|
#define S3L_RESOLUTION_Y LCR_SETTING_RESOLUTION_Y
|
|
#define S3L_PIXEL_FUNCTION _LCR_pixelFunc3D
|
|
#define S3L_PERSPECTIVE_CORRECTION 2
|
|
#define S3L_NEAR_CROSS_STRATEGY 1
|
|
#define S3L_Z_BUFFER 1
|
|
|
|
#include "small3dlib.h"
|
|
|
|
/// Renderer specific unit, length of one map square.
|
|
#define LCR_RENDERER_UNIT (S3L_F / 2)
|
|
// NOTE: ^ S3L_F sometimes makes some triangles bug, S3L_F/2 seems to fix it
|
|
// but it's more jerky, maybe try to apply anti-overflow in S3L?
|
|
|
|
#define LCR_RENDERER_CHUNK_RESOLUTION 4 // do not change
|
|
#define LCR_RENDERER_LOD_BLOCKS 64 // do not change
|
|
|
|
#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)
|
|
|
|
#define LCR_RENDERER_MODEL_COUNT 10
|
|
#define LCR_RENDERER_CAR_SCALE (LCR_RENDERER_UNIT / 4)
|
|
|
|
/** For some reason the map model is a bit misaligned with physics world, this
|
|
kinda hotfixes it -- later try to discover source of this bug. TODO */
|
|
#define _LCR_MAP_MODEL_SCALE 1034
|
|
|
|
#define LCR_RENDERER_MAT_CP0 0x0f ///< material for untaken checkpoint
|
|
#define LCR_RENDERER_MAT_CP1 0x0e
|
|
#define LCR_RENDERER_MAT_FIN 0x0d
|
|
|
|
struct
|
|
{
|
|
S3L_Scene scene;
|
|
S3L_Model3D mapModel; ///< whole map model
|
|
S3L_Model3D *carModel;
|
|
S3L_Model3D *ghostModel;
|
|
|
|
/**
|
|
The scene model array.
|
|
0, 1, 2, 3, 4, 5, 6, 7: nearest map chunk models
|
|
8: car model
|
|
9: ghost model
|
|
*/
|
|
S3L_Model3D models[LCR_RENDERER_MODEL_COUNT];
|
|
|
|
uint32_t frame;
|
|
|
|
uint8_t loadedChunks[8]; ///< numbers of loaded map chunks
|
|
|
|
S3L_Unit mapVerts[LCR_SETTING_MAX_MAP_VERTICES * 3];
|
|
S3L_Index mapTris[LCR_SETTING_MAX_MAP_TRIANGLES * 3];
|
|
|
|
S3L_Index chunkStarts[LCR_RENDERER_CHUNKS_TOTAL];
|
|
|
|
/**
|
|
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];
|
|
|
|
/**
|
|
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];
|
|
|
|
#if LCR_ANIMATE_CAR
|
|
S3L_Unit wheelRotation;
|
|
S3L_Unit wheelSteer;
|
|
S3L_Unit wheelRotationCenters[4]; /**< back and front wheel XY centers */
|
|
S3L_Unit animatedCarVerts[LCR_CAR_VERTEX_COUNT * 3];
|
|
#endif
|
|
|
|
// pixel function precomputed values:
|
|
uint32_t previousTriID;
|
|
int triUVs[6];
|
|
int texSubsampleCount;
|
|
unsigned int flatAndTransparent; /**< If non-zero, transparent (dithered)
|
|
polygons will be drawn without texture,
|
|
with color stored in this variable. */
|
|
} LCR_renderer;
|
|
|
|
void LCR_rendererSetCarTransform(LCR_GameUnit position[3],
|
|
LCR_GameUnit rotation[3])
|
|
{
|
|
LCR_LOG2("setting car transform");
|
|
|
|
LCR_renderer.carModel->transform.translation.x =
|
|
(position[0] * LCR_RENDERER_UNIT) / LCR_GAME_UNIT;
|
|
LCR_renderer.carModel->transform.translation.y =
|
|
(position[1] * LCR_RENDERER_UNIT) / LCR_GAME_UNIT;
|
|
LCR_renderer.carModel->transform.translation.z =
|
|
(position[2] * LCR_RENDERER_UNIT) / LCR_GAME_UNIT;
|
|
|
|
LCR_renderer.carModel->transform.rotation.x = S3L_wrap((rotation[0] *
|
|
S3L_F) / LCR_GAME_UNIT,S3L_F);
|
|
LCR_renderer.carModel->transform.rotation.y = S3L_wrap((rotation[1] *
|
|
S3L_F) / LCR_GAME_UNIT,S3L_F);
|
|
LCR_renderer.carModel->transform.rotation.z = S3L_wrap((rotation[2] *
|
|
S3L_F) / LCR_GAME_UNIT,S3L_F);
|
|
}
|
|
|
|
void _LCR_pixelFunc3D(S3L_PixelInfo *pixel)
|
|
{
|
|
// once we get a new triangle, we precompute things for it:
|
|
if (pixel->triangleID != LCR_renderer.previousTriID)
|
|
{
|
|
LCR_renderer.previousTriID = pixel->triangleID;
|
|
LCR_renderer.flatAndTransparent = 0;
|
|
|
|
#if LCR_SETTING_TEXTURE_SUBSAMPLE != 0
|
|
LCR_renderer.texSubsampleCount = 0;
|
|
#endif
|
|
|
|
if (pixel->modelIndex == 9)
|
|
{
|
|
// car ghost model
|
|
LCR_renderer.flatAndTransparent = LCR_SETTING_GHOST_COLOR;
|
|
}
|
|
else 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[
|
|
3 * pixel->triangleIndex + i / 2] + i % 2] * (LCR_IMAGE_SIZE + 1))
|
|
/ 512;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// map model
|
|
const S3L_Index *t =
|
|
LCR_renderer.models[pixel->modelIndex].triangles +
|
|
3 * pixel->triangleIndex;
|
|
S3L_Unit *v[3];
|
|
|
|
for (int i = 0; i < 3; ++i)
|
|
v[i] = LCR_renderer.mapVerts + 3 * t[i];
|
|
|
|
const uint8_t *triData =
|
|
LCR_renderer.mapTriangleData +
|
|
LCR_renderer.chunkStarts[LCR_renderer.loadedChunks[
|
|
pixel->modelIndex]];
|
|
|
|
uint8_t type = triData[pixel->triangleIndex] >> 4;
|
|
uint8_t mat = triData[pixel->triangleIndex] & 0x0f;
|
|
|
|
switch (mat)
|
|
{
|
|
#define CL (type ? 0x8210 : 0x0000)
|
|
case LCR_RENDERER_MAT_CP0:
|
|
LCR_renderer.flatAndTransparent = LCR_SETTING_CHECKPOINT_0_COLOR | CL;
|
|
break;
|
|
|
|
case LCR_RENDERER_MAT_CP1:
|
|
LCR_renderer.flatAndTransparent = LCR_SETTING_CHECKPOINT_1_COLOR | CL;
|
|
break;
|
|
|
|
case LCR_RENDERER_MAT_FIN:
|
|
LCR_renderer.flatAndTransparent = LCR_SETTING_FINISH_COLOR | CL;
|
|
#undef CL
|
|
break;
|
|
|
|
default:
|
|
LCR_loadImage(mat);
|
|
|
|
if (type == 0) // floor?
|
|
{
|
|
if (v[0][1] != v[1][1] || v[1][1] != v[2][1]) // angled floor?
|
|
LCR_imageChangeBrightness(1);
|
|
|
|
for (int i = 0; i < 6; ++i)
|
|
LCR_renderer.triUVs[i] = ((
|
|
(v[i / 2][(i % 2) * 2]) *
|
|
LCR_IMAGE_SIZE) / LCR_RENDERER_UNIT);
|
|
}
|
|
else
|
|
{
|
|
if (type == 1)
|
|
LCR_imageChangeBrightness(0);
|
|
|
|
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];
|
|
}
|
|
}
|
|
|
|
// 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);
|
|
|
|
if (LCR_renderer.triUVs[4 + i] < LCR_renderer.triUVs[minCoord])
|
|
minCoord = 4 + i;
|
|
|
|
S3L_Unit shiftBy = LCR_renderer.triUVs[minCoord] % LCR_IMAGE_SIZE;
|
|
|
|
if (shiftBy < 0)
|
|
shiftBy += LCR_IMAGE_SIZE;
|
|
|
|
shiftBy -= LCR_renderer.triUVs[minCoord];
|
|
|
|
LCR_renderer.triUVs[i] += shiftBy;
|
|
LCR_renderer.triUVs[2 + i] += shiftBy;
|
|
LCR_renderer.triUVs[4 + i] += shiftBy;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (LCR_renderer.flatAndTransparent)
|
|
{
|
|
if (pixel->x % 2 == pixel->y % 2)
|
|
LCR_drawPixelXYUnsafe(pixel->x,pixel->y,LCR_renderer.flatAndTransparent);
|
|
else
|
|
S3L_zBufferWrite(pixel->x,pixel->y,S3L_MAX_DEPTH);
|
|
/* ^ Clear z-buffer if we don't draw the pixel. Without this further
|
|
geometry drawn later on won't be seen through transparent objects which
|
|
looks bad. With this "fix" glitches may still appear (wrong draw order)
|
|
but it generally looks better this way. */
|
|
|
|
return;
|
|
}
|
|
|
|
uint16_t color;
|
|
|
|
#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_F / 8),
|
|
(barycentric[0] * LCR_renderer.triUVs[1] +
|
|
barycentric[1] * LCR_renderer.triUVs[3] +
|
|
barycentric[2] * LCR_renderer.triUVs[5])
|
|
/ (S3L_F / 8));
|
|
|
|
#if LCR_SETTING_TEXTURE_SUBSAMPLE != 0
|
|
LCR_renderer.texSubsampleCount = LCR_SETTING_TEXTURE_SUBSAMPLE;
|
|
}
|
|
|
|
LCR_renderer.texSubsampleCount--;
|
|
#endif
|
|
|
|
LCR_drawPixelXYUnsafe(pixel->x,pixel->y,color);
|
|
}
|
|
|
|
S3L_Index _LCR_rendererAddMapVert(S3L_Unit x, S3L_Unit y, S3L_Unit z)
|
|
{
|
|
S3L_Index index = 0;
|
|
S3L_Unit *verts = LCR_renderer.mapVerts;
|
|
|
|
while (index < LCR_renderer.mapModel.vertexCount) // if exists, return index
|
|
{
|
|
if (verts[0] == x && verts[1] == y && verts[2] == z)
|
|
return index;
|
|
|
|
verts += 3;
|
|
index++;
|
|
}
|
|
|
|
// if it doesn't exist, add it
|
|
if (LCR_renderer.mapModel.vertexCount < LCR_SETTING_MAX_MAP_VERTICES)
|
|
{
|
|
*verts = x; verts++;
|
|
*verts = y; verts++;
|
|
*verts = z;
|
|
LCR_renderer.mapModel.vertexCount++;
|
|
return LCR_renderer.mapModel.vertexCount - 1;
|
|
}
|
|
|
|
LCR_LOG0("couldn't add map vertex!");
|
|
|
|
return 0;
|
|
}
|
|
|
|
void _LCR_rendererAddMapTri(S3L_Index a, S3L_Index b, S3L_Index c, uint8_t mat)
|
|
{
|
|
if (LCR_renderer.mapModel.triangleCount < LCR_SETTING_MAX_MAP_TRIANGLES)
|
|
{
|
|
S3L_Index *t =
|
|
&(LCR_renderer.mapTris[LCR_renderer.mapModel.triangleCount * 3]);
|
|
|
|
*t = a; t++;
|
|
*t = b; t++;
|
|
*t = c;
|
|
|
|
LCR_renderer.mapTriangleData[LCR_renderer.mapModel.triangleCount] =
|
|
mat;
|
|
|
|
LCR_renderer.mapModel.triangleCount++;
|
|
}
|
|
}
|
|
|
|
void _LCR_rendererSwapMapTris(unsigned int index1, unsigned int index2)
|
|
{
|
|
uint8_t tmpMat;
|
|
S3L_Index tmpIndex,
|
|
*t1 = LCR_renderer.mapTris + index1 * 3,
|
|
*t2 = LCR_renderer.mapTris + index2 * 3;
|
|
|
|
for (int i = 0; i < 3; ++i)
|
|
{
|
|
tmpIndex = t1[i];
|
|
t1[i] = t2[i];
|
|
t2[i] = tmpIndex;
|
|
}
|
|
|
|
tmpMat = LCR_renderer.mapTriangleData[index1];
|
|
LCR_renderer.mapTriangleData[index1] = LCR_renderer.mapTriangleData[index2];
|
|
LCR_renderer.mapTriangleData[index2] = tmpMat;
|
|
}
|
|
|
|
int _LCR_rendererQuadCoversTri(const S3L_Unit quad[8], const S3L_Unit tri[6])
|
|
{
|
|
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;
|
|
}
|
|
|
|
/**
|
|
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.
|
|
*/
|
|
uint8_t _LCR_rendererCheckMapTriCover(const S3L_Index *t1,
|
|
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]))
|
|
return 0x03;
|
|
|
|
uint8_t result = 0;
|
|
int plane = -1;
|
|
S3L_Unit *vertices[6];
|
|
|
|
for (int i = 0; i < 3; ++i)
|
|
{
|
|
vertices[i] = LCR_renderer.mapVerts + 3 * t1[i];
|
|
vertices[3 + i] = LCR_renderer.mapVerts + 3 * t2[i];
|
|
}
|
|
|
|
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])
|
|
{
|
|
plane = i;
|
|
break;
|
|
}
|
|
|
|
if (plane >= 0) // both triangles in the same plane => then do more checks
|
|
{
|
|
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
|
|
|
|
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)
|
|
{
|
|
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];
|
|
}
|
|
|
|
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?
|
|
if (_LCR_rendererQuadCoversTri(points2D + 6,points2D))
|
|
result |= 1 << j;
|
|
else
|
|
{
|
|
// now check if this triangle along with a neighbor cover the other one
|
|
|
|
S3L_Index *t3 = LCR_renderer.mapTris;
|
|
|
|
for (int i = 0; i < LCR_renderer.mapModel.triangleCount; ++i)
|
|
{
|
|
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) &&
|
|
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])
|
|
{
|
|
// here shares exactly two vertices and is in the same plane
|
|
|
|
uint8_t freeVert =
|
|
sharedVerts == 3 ? 2 : (sharedVerts == 5 ? 1 : 0);
|
|
|
|
points2D[12] = LCR_renderer.mapVerts[3 * t3[freeVert] + coordX];
|
|
points2D[13] = LCR_renderer.mapVerts[3 * t3[freeVert] + coordY];
|
|
|
|
if (_LCR_rendererQuadCoversTri(points2D + 6,points2D))
|
|
{
|
|
result |= 1 << j;
|
|
break;
|
|
}
|
|
}
|
|
|
|
t3 += 3;
|
|
}
|
|
}
|
|
|
|
// now swap both triangles and do it all again:
|
|
|
|
const S3L_Index *tmp = t1;
|
|
t1 = t2;
|
|
t2 = tmp;
|
|
|
|
for (int i = 0; i < 3; ++i)
|
|
{
|
|
S3L_Unit *tmpCoord = vertices[i];
|
|
vertices[i] = vertices[3 + i];
|
|
vertices[3 + i] = tmpCoord;
|
|
}
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
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.
|
|
*/
|
|
void _LCR_cullHiddenMapTris(void)
|
|
{
|
|
LCR_LOG1("culling invisible triangles");
|
|
|
|
int n = 0; // number of removed elements
|
|
int i = 0;
|
|
|
|
S3L_Index *t1 = LCR_renderer.mapTris, *t2;
|
|
|
|
/*
|
|
We'll be moving the covered triangles to the end of the array, then at the
|
|
end we'll just shorten the array by number of removed triangles.
|
|
*/
|
|
while (i < LCR_renderer.mapModel.triangleCount - n)
|
|
{
|
|
t2 = t1 + 3; // t2 is the the other triangle against which we check
|
|
|
|
int t1Covered = 0;
|
|
|
|
for (int j = i + 1; j < LCR_renderer.mapModel.triangleCount; ++j)
|
|
{
|
|
uint8_t cover = _LCR_rendererCheckMapTriCover(t1,t2);
|
|
|
|
t1Covered |= cover & 0x01;
|
|
|
|
if (cover & 0x02)
|
|
{
|
|
if (j < LCR_renderer.mapModel.triangleCount - n)
|
|
{
|
|
_LCR_rendererSwapMapTris(j,
|
|
LCR_renderer.mapModel.triangleCount - 1 - n);
|
|
|
|
n++;
|
|
}
|
|
}
|
|
|
|
t2 += 3; // check next triangle
|
|
}
|
|
|
|
if (t1Covered)
|
|
{
|
|
_LCR_rendererSwapMapTris(i,
|
|
LCR_renderer.mapModel.triangleCount - 1 - n);
|
|
|
|
n++;
|
|
// we stay at this position because we've swapped the triangle here
|
|
}
|
|
else
|
|
{
|
|
t1 += 3;
|
|
i++;
|
|
}
|
|
}
|
|
|
|
LCR_renderer.mapModel.triangleCount -= n; // cut off the removed triangles
|
|
|
|
// remove unused vertices:
|
|
|
|
i = 0;
|
|
|
|
while (i < LCR_renderer.mapModel.vertexCount)
|
|
{
|
|
int used = 0;
|
|
|
|
for (int j = 0; j < LCR_renderer.mapModel.triangleCount * 3; ++j)
|
|
if (LCR_renderer.mapTris[j] == i)
|
|
{
|
|
used = 1;
|
|
break;
|
|
}
|
|
|
|
if (used)
|
|
i++;
|
|
else
|
|
{
|
|
for (int j = 0; j < 3; ++j)
|
|
LCR_renderer.mapVerts[3 * i + j] =
|
|
LCR_renderer.mapVerts[(LCR_renderer.mapModel.vertexCount - 1) * 3 + j];
|
|
|
|
for (int j = 0; j < LCR_renderer.mapModel.triangleCount * 3; ++j)
|
|
if (LCR_renderer.mapTris[j] == LCR_renderer.mapModel.vertexCount - 1)
|
|
LCR_renderer.mapTris[j] = i;
|
|
|
|
LCR_renderer.mapModel.vertexCount--;
|
|
}
|
|
}
|
|
}
|
|
|
|
void _LCR_makeMapChunks(void)
|
|
{
|
|
LCR_LOG1("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;
|
|
|
|
for (int i = start; i < LCR_renderer.mapModel.triangleCount; ++i)
|
|
{
|
|
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)
|
|
{
|
|
_LCR_rendererSwapMapTris(i,start);
|
|
start++;
|
|
}
|
|
|
|
tri += 3;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
Builds the internal 3D model of the currently loaded map. Returns 1 on
|
|
success, 0 otherwise (e.g. not enough space).
|
|
*/
|
|
uint8_t _LCR_buildMapModel(void)
|
|
{
|
|
LCR_LOG1("building map model");
|
|
|
|
uint8_t blockShapeBytes[LCR_MAP_BLOCK_SHAPE_MAX_BYTES];
|
|
uint8_t blockShapeByteCount;
|
|
|
|
S3L_model3DInit(LCR_renderer.mapVerts,0,LCR_renderer.mapTris,0,
|
|
&LCR_renderer.mapModel);
|
|
|
|
LCR_renderer.mapModel.transform.scale.x =
|
|
(_LCR_MAP_MODEL_SCALE * S3L_F) / 1024;
|
|
LCR_renderer.mapModel.transform.scale.y
|
|
= LCR_renderer.mapModel.transform.scale.x;
|
|
LCR_renderer.mapModel.transform.scale.z
|
|
= LCR_renderer.mapModel.transform.scale.x;
|
|
|
|
for (int j = 0; j < LCR_currentMap.blockCount; ++j)
|
|
{
|
|
if ((j + 1) % LCR_SETTING_TRIANGLE_CULLING_PERIOD == 0)
|
|
_LCR_cullHiddenMapTris();
|
|
|
|
S3L_Unit originOffset = -1 * LCR_MAP_SIZE_BLOCKS / 2 * LCR_RENDERER_UNIT;
|
|
S3L_Index triIndices[3];
|
|
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)
|
|
|
|
LCR_mapBlockGetCoords(block,&bx,&by,&bz);
|
|
|
|
LCR_mapGetBlockShape(blockType,LCR_mapBlockGetTransform(block),
|
|
blockShapeBytes,&blockShapeByteCount);
|
|
|
|
for (int i = 0; i < blockShapeByteCount; ++i)
|
|
{
|
|
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);
|
|
|
|
LCR_decodeMapBlockCoords(blockShapeBytes[i],&vx,&vy,&vz);
|
|
|
|
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);
|
|
|
|
triIndices[vi] = _LCR_rendererAddMapVert(
|
|
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);
|
|
|
|
if (vi < 2)
|
|
vi++;
|
|
else
|
|
{
|
|
// don't add triangles completely at the floor or ceiling of the map
|
|
if (!edgeBits)
|
|
{
|
|
uint8_t triData;
|
|
|
|
if (blockType == LCR_BLOCK_CHECKPOINT_0)
|
|
triData = LCR_RENDERER_MAT_CP0 | ((i % 2) << 4);
|
|
else if (blockType == LCR_BLOCK_FINISH)
|
|
triData = LCR_RENDERER_MAT_FIN | ((i % 2) << 4);
|
|
else
|
|
{
|
|
uint8_t blockMat = LCR_mapBlockGetMaterial(block);
|
|
|
|
#define VERT(n,c) LCR_renderer.mapVerts[3 * n + c]
|
|
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);
|
|
#undef VERT
|
|
|
|
if (triData & 0xf0) // wall?
|
|
{
|
|
triData |=
|
|
((blockMat == LCR_BLOCK_MATERIAL_CONCRETE) ||
|
|
(blockMat == LCR_BLOCK_MATERIAL_ICE) ||
|
|
(blockType == LCR_BLOCK_FULL_ACCEL) ||
|
|
(blockType == LCR_BLOCK_FULL_FAN)) ?
|
|
LCR_IMAGE_WALL_CONCRETE : LCR_IMAGE_WALL_WOOD;
|
|
}
|
|
else
|
|
{ // TODO: tidy this mess?
|
|
if (blockType == LCR_BLOCK_FULL_ACCEL)
|
|
triData |= LCR_IMAGE_GROUND_ACCEL;
|
|
else if (blockType == LCR_BLOCK_FULL_FAN)
|
|
triData |= LCR_IMAGE_GROUND_FAN;
|
|
else
|
|
switch (blockMat)
|
|
{
|
|
case LCR_BLOCK_MATERIAL_CONCRETE:
|
|
triData |= LCR_IMAGE_GROUND_CONCRETE;
|
|
break;
|
|
|
|
case LCR_BLOCK_MATERIAL_GRASS:
|
|
triData |= LCR_IMAGE_GROUND_GRASS;
|
|
break;
|
|
|
|
case LCR_BLOCK_MATERIAL_DIRT:
|
|
triData |= LCR_IMAGE_GROUND_DIRT;
|
|
break;
|
|
|
|
case LCR_BLOCK_MATERIAL_ICE:
|
|
triData |= LCR_IMAGE_GROUND_ICE;
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
_LCR_rendererAddMapTri(triIndices[0],triIndices[1],triIndices[2],
|
|
triData);
|
|
}
|
|
|
|
vi = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
_LCR_cullHiddenMapTris();
|
|
|
|
LCR_LOG1("map model built");
|
|
|
|
return 1;
|
|
}
|
|
|
|
void _LCR_rendererComputeLOD(void)
|
|
{
|
|
LCR_LOG1("computing LOD");
|
|
|
|
for (int i = 0; i < LCR_RENDERER_LOD_BLOCKS; ++i)
|
|
LCR_renderer.gridOfLODs[i] = 0;
|
|
|
|
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;
|
|
|
|
LCR_renderer.gridOfLODs[z * 8 + y] |= (0x01 << x);
|
|
}
|
|
}
|
|
|
|
/**
|
|
Unmarks all checkpoints that were marked with LCR_rendererMarkTakenCP.
|
|
*/
|
|
void LCR_rendererUnmarkCPs(void)
|
|
{
|
|
for (int i = 0; i < LCR_renderer.mapModel.triangleCount; ++i)
|
|
if ((LCR_renderer.mapTriangleData[i] & 0x0f) == LCR_RENDERER_MAT_CP1)
|
|
LCR_renderer.mapTriangleData[i] = (LCR_renderer.mapTriangleData[i] & 0xf0)
|
|
| LCR_RENDERER_MAT_CP0;
|
|
}
|
|
|
|
/**
|
|
Marks checkpoint as taken, i.e. changes how a checkpoint will be drawn.
|
|
*/
|
|
void LCR_rendererMarkTakenCP(int x, int y, int z)
|
|
{
|
|
for (int i = 0; i < LCR_renderer.mapModel.triangleCount; ++i)
|
|
if ((LCR_renderer.mapTriangleData[i] & 0x0f) == LCR_RENDERER_MAT_CP0)
|
|
{
|
|
S3L_Unit point[3];
|
|
|
|
point[0] = 0;
|
|
point[1] = 0;
|
|
point[2] = 0;
|
|
|
|
for (int j = 0; j < 2; ++j)
|
|
for (int k = 0; k < 3; ++k)
|
|
point[k] += LCR_renderer.mapModel.vertices[
|
|
3 * LCR_renderer.mapModel.triangles[3 * i + j] + k] +
|
|
(LCR_MAP_SIZE_BLOCKS / 2) *
|
|
(k == 1 ? LCR_RENDERER_UNIT / 2 : LCR_RENDERER_UNIT);
|
|
|
|
point[0] /= 2;
|
|
point[1] /= 2;
|
|
point[2] /= 2;
|
|
|
|
if (point[0] / LCR_RENDERER_UNIT == x &&
|
|
point[1] / (LCR_RENDERER_UNIT / 2) == y &&
|
|
point[2] / LCR_RENDERER_UNIT == z)
|
|
LCR_renderer.mapTriangleData[i] = (LCR_renderer.mapTriangleData[i]
|
|
& 0xf0) | LCR_RENDERER_MAT_CP1;
|
|
}
|
|
}
|
|
|
|
uint8_t LCR_rendererLoadMap(void)
|
|
{
|
|
LCR_LOG0("loading map");
|
|
|
|
if (!_LCR_buildMapModel())
|
|
return 0;
|
|
|
|
_LCR_makeMapChunks();
|
|
_LCR_rendererComputeLOD();
|
|
|
|
return 1;
|
|
}
|
|
|
|
/**
|
|
Initializes renderer, only call once.
|
|
*/
|
|
uint8_t LCR_rendererInit(void)
|
|
{
|
|
LCR_LOG0("initializing renderer");
|
|
|
|
LCR_renderer.frame = 0;
|
|
|
|
LCR_renderer.carModel = LCR_renderer.models + 8;
|
|
LCR_renderer.ghostModel = LCR_renderer.models + 9;
|
|
|
|
S3L_model3DInit(
|
|
#if LCR_ANIMATE_CAR
|
|
LCR_renderer.animatedCarVerts
|
|
#else
|
|
LCR_carVertices
|
|
#endif
|
|
,LCR_CAR_VERTEX_COUNT,
|
|
LCR_carTriangles,LCR_CAR_TRIANGLE_COUNT,
|
|
LCR_renderer.carModel);
|
|
|
|
S3L_vec4Set(&(LCR_renderer.carModel->transform.scale),
|
|
LCR_RENDERER_CAR_SCALE,LCR_RENDERER_CAR_SCALE,LCR_RENDERER_CAR_SCALE,0);
|
|
|
|
S3L_model3DInit(LCR_carVertices,LCR_CAR_VERTEX_COUNT,LCR_carTriangles,
|
|
LCR_CAR_TRIANGLE_COUNT,LCR_renderer.ghostModel);
|
|
|
|
LCR_renderer.ghostModel->transform.scale =
|
|
LCR_renderer.carModel->transform.scale;
|
|
|
|
LCR_renderer.ghostModel->transform.translation.x -= LCR_GAME_UNIT / 4;
|
|
|
|
#if LCR_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.wheelSteer = 0;
|
|
#endif
|
|
|
|
LCR_LOG2("initializing 3D scene");
|
|
|
|
S3L_sceneInit(
|
|
LCR_renderer.models,LCR_RENDERER_MODEL_COUNT,&LCR_renderer.scene);
|
|
|
|
return 1;
|
|
}
|
|
|
|
void LCR_rendererGetCameraTransform(LCR_GameUnit position[3],
|
|
LCR_GameUnit rotation[3], LCR_GameUnit *fov)
|
|
{
|
|
position[0] = (LCR_renderer.scene.camera.transform.translation.x *
|
|
LCR_GAME_UNIT) / LCR_RENDERER_UNIT;
|
|
position[1] = (LCR_renderer.scene.camera.transform.translation.y *
|
|
LCR_GAME_UNIT) / LCR_RENDERER_UNIT;
|
|
position[2] = (LCR_renderer.scene.camera.transform.translation.z *
|
|
LCR_GAME_UNIT) / LCR_RENDERER_UNIT;
|
|
|
|
rotation[0] = (LCR_renderer.scene.camera.transform.rotation.x *
|
|
LCR_GAME_UNIT) / S3L_F;
|
|
rotation[1] = (LCR_renderer.scene.camera.transform.rotation.y *
|
|
LCR_GAME_UNIT) / S3L_F;
|
|
rotation[2] = (LCR_renderer.scene.camera.transform.rotation.z *
|
|
LCR_GAME_UNIT) / S3L_F;
|
|
|
|
*fov = (LCR_renderer.scene.camera.focalLength * LCR_GAME_UNIT)
|
|
/ S3L_F;
|
|
}
|
|
|
|
void LCR_rendererMoveCamera(LCR_GameUnit forwRightUpOffset[3],
|
|
LCR_GameUnit yawPitchOffset[2])
|
|
{
|
|
LCR_LOG2("moving camera");
|
|
|
|
S3L_Vec4 f, r, u;
|
|
|
|
S3L_rotationToDirections(LCR_renderer.scene.camera.transform.rotation,
|
|
S3L_F,&f,&r,&u);
|
|
|
|
LCR_renderer.scene.camera.transform.translation.x +=
|
|
((f.x * forwRightUpOffset[0] + r.x * forwRightUpOffset[1] +
|
|
u.x * forwRightUpOffset[2]) * S3L_F) / LCR_GAME_UNIT;
|
|
|
|
LCR_renderer.scene.camera.transform.translation.y +=
|
|
((f.y * forwRightUpOffset[0] + r.y * forwRightUpOffset[1] +
|
|
u.y * forwRightUpOffset[2]) * S3L_F) / LCR_GAME_UNIT;
|
|
|
|
LCR_renderer.scene.camera.transform.translation.z +=
|
|
((f.z * forwRightUpOffset[0] + r.z * forwRightUpOffset[1] +
|
|
u.z * forwRightUpOffset[2]) * S3L_F) / LCR_GAME_UNIT;
|
|
|
|
LCR_renderer.scene.camera.transform.rotation.y +=
|
|
(yawPitchOffset[0] * S3L_F) / LCR_GAME_UNIT;
|
|
|
|
LCR_renderer.scene.camera.transform.rotation.x +=
|
|
(yawPitchOffset[1] * S3L_F) / LCR_GAME_UNIT;
|
|
|
|
#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)
|
|
|
|
}
|
|
|
|
/**
|
|
Fast and safe rect drawing function (handles out of screen coords).
|
|
*/
|
|
void LCR_rendererDrawRect(int x, int y, unsigned int w, unsigned int h,
|
|
uint16_t color, int dither)
|
|
{
|
|
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;
|
|
|
|
h += y;
|
|
y = 0;
|
|
}
|
|
|
|
if (y + h > LCR_EFFECTIVE_RESOLUTION_Y)
|
|
h = LCR_EFFECTIVE_RESOLUTION_Y - y;
|
|
|
|
unsigned long index = y * LCR_EFFECTIVE_RESOLUTION_X + x;
|
|
|
|
if (dither)
|
|
{
|
|
uint8_t parity = (x % 2) == (y % 2);
|
|
|
|
for (unsigned int i = 0; i < h; ++i)
|
|
{
|
|
for (unsigned int j = ((i % 2) == parity); j < w; j += 2)
|
|
LCR_drawPixel(index + j,color);
|
|
|
|
index += LCR_EFFECTIVE_RESOLUTION_X;
|
|
}
|
|
}
|
|
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;
|
|
}
|
|
}
|
|
|
|
void _LCR_rendererDrawLODBlock(int blockX, int blockY, int blockZ, unsigned int size,
|
|
uint16_t color, uint8_t variability)
|
|
{
|
|
LCR_LOG2("drawing LOD block");
|
|
|
|
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)
|
|
{
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
Draws background sky, offsets are in multiples of screen dimensions
|
|
(e.g. S3L_F / 2 for offsetH means half the screen width).
|
|
*/
|
|
void LCR_rendererDrawSky(int sky, S3L_Unit offsetH, S3L_Unit offsetV)
|
|
{
|
|
LCR_LOG2("drawing sky");
|
|
|
|
int anchorPoint[2], y;
|
|
unsigned long pixelIndex;
|
|
unsigned int topColor, bottomColor;
|
|
|
|
sky = 8 + 4 * sky;
|
|
|
|
LCR_loadImage(sky);
|
|
topColor = LCR_sampleImage(0,0);
|
|
|
|
LCR_loadImage(sky + 3);
|
|
bottomColor = LCR_sampleImage(LCR_IMAGE_SIZE - 1,LCR_IMAGE_SIZE - 1);
|
|
|
|
anchorPoint[0] = ((LCR_EFFECTIVE_RESOLUTION_X * offsetH)
|
|
/ S3L_F) %
|
|
(2 * LCR_IMAGE_SIZE * LCR_SETTING_SKY_SIZE);
|
|
|
|
if (anchorPoint[0] < 0)
|
|
anchorPoint[0] += 2 * LCR_IMAGE_SIZE * LCR_SETTING_SKY_SIZE;
|
|
|
|
anchorPoint[1] =
|
|
(LCR_EFFECTIVE_RESOLUTION_Y) / 3 - // 3: we place the center a bit more up
|
|
(LCR_EFFECTIVE_RESOLUTION_Y * offsetV) / S3L_F
|
|
- LCR_IMAGE_SIZE * LCR_SETTING_SKY_SIZE;
|
|
|
|
pixelIndex = 0;
|
|
y = anchorPoint[1] < 0 ? anchorPoint[1] : 0;
|
|
|
|
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)
|
|
{
|
|
LCR_loadImage(sky + skyPart);
|
|
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();
|
|
unsigned long startIndex = pixelIndex;
|
|
|
|
for (int k = 0; k < LCR_SETTING_SKY_SIZE; ++k)
|
|
{
|
|
if (y + k >= LCR_EFFECTIVE_RESOLUTION_Y)
|
|
break;
|
|
|
|
for (int j = 0; j < LCR_SETTING_SKY_SIZE; ++j)
|
|
{
|
|
int x = anchorPoint[0] + ix + j;
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
pixelIndex += LCR_EFFECTIVE_RESOLUTION_X * LCR_SETTING_SKY_SIZE;
|
|
y += LCR_SETTING_SKY_SIZE;
|
|
}
|
|
else
|
|
{
|
|
for (int ix = 0; ix < 2 * LCR_IMAGE_SIZE; ++ix)
|
|
LCR_getNextImagePixel();
|
|
|
|
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++;
|
|
}
|
|
}
|
|
|
|
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++;
|
|
}
|
|
}
|
|
|
|
void _LCR_rendererLoadMapChunk(uint8_t chunk, int8_t x, int8_t y, int8_t z)
|
|
{
|
|
LCR_renderer.models[chunk] = LCR_renderer.mapModel;
|
|
|
|
if (x < 0 || x >= LCR_RENDERER_CHUNK_RESOLUTION ||
|
|
y < 0 || y >= LCR_RENDERER_CHUNK_RESOLUTION ||
|
|
z < 0 || z >= LCR_RENDERER_CHUNK_RESOLUTION)
|
|
{
|
|
LCR_renderer.models[chunk].triangleCount = 0;
|
|
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 ?
|
|
(LCR_renderer.mapModel.triangleCount - 1) :
|
|
LCR_renderer.chunkStarts[blockNum + 1])
|
|
- LCR_renderer.chunkStarts[blockNum];
|
|
|
|
if (triCount < 0)
|
|
triCount = 0;
|
|
|
|
LCR_renderer.models[chunk].triangles =
|
|
LCR_renderer.mapTris + LCR_renderer.chunkStarts[blockNum] * 3;
|
|
|
|
LCR_renderer.models[chunk].triangleCount = triCount;
|
|
}
|
|
}
|
|
|
|
/**
|
|
Serves for smoothing out angle change, e.g. that of camera rotation.
|
|
*/
|
|
S3L_Unit _LCR_rendererSmoothRot(S3L_Unit angleOld, S3L_Unit angleNew,
|
|
unsigned int amount)
|
|
{
|
|
/* We have to do the following angle correction -- even if keep angles in
|
|
correct range at the start of frame, subsequent steps may alter the rotations
|
|
and here we could end up with bad ranges again. */
|
|
S3L_Unit angleDiff = S3L_wrap(angleNew,S3L_F) - S3L_wrap(angleOld,S3L_F);
|
|
|
|
if (angleDiff == 0)
|
|
return angleNew;
|
|
|
|
S3L_Unit angleDiffAbs = S3L_abs(angleDiff);
|
|
|
|
if (angleDiffAbs > S3L_F / 2) // consider e.g. 350 degrees minus 1 degree
|
|
{
|
|
angleDiffAbs = S3L_F - angleDiffAbs;
|
|
angleDiff = (angleDiff > 0) ? -1 * angleDiffAbs : angleDiffAbs;
|
|
}
|
|
|
|
if (angleDiffAbs > (3 * S3L_F) / 8) // angle too big, rotate immediately
|
|
return angleNew;
|
|
|
|
return angleOld + (angleDiff / S3L_nonZero(amount));
|
|
}
|
|
|
|
/**
|
|
Loads the map models with 8 chunks that are nearest to a certain point
|
|
towards which camera is looking.
|
|
*/
|
|
void _LCR_rendererLoadMapChunks(void)
|
|
{
|
|
LCR_LOG2("loading map chunks");
|
|
|
|
int8_t camChunk[3], chunkOffsets[3];
|
|
S3L_Vec4 cp = LCR_renderer.scene.camera.transform.translation;
|
|
S3L_Vec4 cf;
|
|
|
|
S3L_rotationToDirections(LCR_renderer.scene.camera.transform.rotation,
|
|
LCR_RENDERER_CHUNK_SIZE_HORIZONTAL / 2,&cf,0,0);
|
|
|
|
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;
|
|
|
|
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;
|
|
|
|
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;
|
|
|
|
chunkOffsets[0] =
|
|
(cf.x >= (LCR_RENDERER_CHUNK_SIZE_HORIZONTAL / 2)) ? 1 : -1;
|
|
|
|
chunkOffsets[1] =
|
|
(cf.y >= (LCR_RENDERER_CHUNK_SIZE_HORIZONTAL / 4)) ? 1 : -1;
|
|
|
|
chunkOffsets[2] =
|
|
(cf.z >= (LCR_RENDERER_CHUNK_SIZE_HORIZONTAL / 2)) ? 1 : -1;
|
|
|
|
for (uint8_t i = 0; i < 8; ++i)
|
|
_LCR_rendererLoadMapChunk(i,
|
|
camChunk[0] + ((i & 0x01) ? chunkOffsets[0] : 0),
|
|
camChunk[1] + ((i & 0x02) ? chunkOffsets[1] : 0),
|
|
camChunk[2] + ((i & 0x04) ? chunkOffsets[2] : 0));
|
|
}
|
|
|
|
/**
|
|
Draws the LOD overlay.
|
|
*/
|
|
void LCR_rendererDrawLOD(void)
|
|
{
|
|
LCR_LOG2("drawing LOD");
|
|
|
|
#if LCR_SETTING_LOD_DISTANCE < 64
|
|
int variability = 0;
|
|
|
|
for (unsigned int i = 0; i < LCR_RENDERER_LOD_BLOCKS; ++i)
|
|
if (LCR_renderer.gridOfLODs[i])
|
|
{
|
|
uint8_t byte = LCR_renderer.gridOfLODs[i];
|
|
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)
|
|
{
|
|
variability = variability < 14 ? variability + 1 : 0;
|
|
bx = j * 8 + 4;
|
|
|
|
_LCR_rendererDrawLODBlock(bx,by,bz,(4 * LCR_EFFECTIVE_RESOLUTION_Y)
|
|
/ 3,LCR_SETTING_LOD_COLOR,variability);
|
|
}
|
|
|
|
byte >>= 1;
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void LCR_drawLevelFloor(void)
|
|
{
|
|
LCR_LOG2("drawing floor");
|
|
|
|
#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
|
|
}
|
|
|
|
#if LCR_ANIMATE_CAR
|
|
void _LCR_rendererAnimateCar(void)
|
|
{
|
|
LCR_LOG2("animating car");
|
|
|
|
for (int i = LCR_renderer.frame % LCR_SETTING_CAR_ANIMATION_SUBDIVIDE;
|
|
i < LCR_CAR_VERTEX_COUNT; i += LCR_SETTING_CAR_ANIMATION_SUBDIVIDE)
|
|
{
|
|
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_F;
|
|
v[1] = (tmp * s + v[1] * c) / S3L_F;
|
|
|
|
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.wheelSteer)
|
|
/ (8 * S3L_F);
|
|
|
|
LCR_renderer.animatedCarVerts[index] +=
|
|
((LCR_renderer.animatedCarVerts[index + 2] -
|
|
LCR_renderer.wheelRotationCenters[2]) * LCR_renderer.wheelSteer)
|
|
/ (2 * S3L_F);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
void LCR_rendererCameraFollow(void)
|
|
{
|
|
LCR_LOG2("following camera");
|
|
|
|
S3L_Transform3D transPrev = LCR_renderer.scene.camera.transform;
|
|
|
|
LCR_renderer.scene.camera.transform.translation.y =
|
|
S3L_clamp(
|
|
LCR_renderer.scene.camera.transform.translation.y,
|
|
LCR_renderer.carModel->transform.translation.y +
|
|
(LCR_SETTING_CAMERA_HEIGHT - LCR_SETTING_CAMERA_HEIGHT_BAND) *
|
|
LCR_RENDERER_UNIT / 8,
|
|
LCR_renderer.carModel->transform.translation.y +
|
|
(LCR_SETTING_CAMERA_HEIGHT + LCR_SETTING_CAMERA_HEIGHT_BAND) *
|
|
LCR_RENDERER_UNIT / 8);
|
|
|
|
S3L_Vec4 toCam = LCR_renderer.scene.camera.transform.translation;
|
|
|
|
S3L_vec3Sub(&toCam,LCR_renderer.carModel->transform.translation);
|
|
|
|
S3L_Unit horizontalDist = S3L_sqrt(toCam.x * toCam.x + toCam.z * toCam.z);
|
|
|
|
if (horizontalDist == 0)
|
|
{
|
|
toCam.z = 1;
|
|
horizontalDist = 1;
|
|
}
|
|
|
|
S3L_Unit horizontalDistNew =
|
|
S3L_clamp(horizontalDist,
|
|
(LCR_SETTING_CAMERA_DISTANCE - LCR_SETTING_CAMERA_DISTANCE_BAND)
|
|
* (LCR_RENDERER_UNIT / 4),
|
|
(LCR_SETTING_CAMERA_DISTANCE + LCR_SETTING_CAMERA_DISTANCE_BAND)
|
|
* (LCR_RENDERER_UNIT / 4));
|
|
|
|
if (horizontalDistNew != horizontalDist)
|
|
{
|
|
toCam.x = (toCam.x * horizontalDistNew) / horizontalDist;
|
|
toCam.z = (toCam.z * horizontalDistNew) / horizontalDist;
|
|
|
|
LCR_renderer.scene.camera.transform.translation.x =
|
|
LCR_renderer.carModel->transform.translation.x +
|
|
(toCam.x * horizontalDistNew) / horizontalDist;
|
|
|
|
LCR_renderer.scene.camera.transform.translation.z =
|
|
LCR_renderer.carModel->transform.translation.z +
|
|
(toCam.z * horizontalDistNew) / horizontalDist;
|
|
}
|
|
|
|
S3L_lookAt(LCR_renderer.carModel->transform.translation,
|
|
&(LCR_renderer.scene.camera.transform));
|
|
|
|
#if LCR_SETTING_SMOOTH_ANIMATIONS
|
|
// now average with previous transform to smooth the animation out:
|
|
|
|
S3L_vec3Add(&(LCR_renderer.scene.camera.transform.translation),
|
|
transPrev.translation);
|
|
|
|
LCR_renderer.scene.camera.transform.translation.x /= 2;
|
|
LCR_renderer.scene.camera.transform.translation.y /= 2;
|
|
LCR_renderer.scene.camera.transform.translation.z /= 2;
|
|
|
|
LCR_renderer.scene.camera.transform.rotation.x = _LCR_rendererSmoothRot(
|
|
transPrev.rotation.x,LCR_renderer.scene.camera.transform.rotation.x,8);
|
|
|
|
LCR_renderer.scene.camera.transform.rotation.y = _LCR_rendererSmoothRot(
|
|
transPrev.rotation.y,LCR_renderer.scene.camera.transform.rotation.y,6);
|
|
#endif
|
|
}
|
|
|
|
void LCR_rendererSetWheelState(LCR_GameUnit rotation, LCR_GameUnit steer)
|
|
{
|
|
#if LCR_ANIMATE_CAR
|
|
LCR_renderer.wheelRotation = rotation;
|
|
LCR_renderer.wheelSteer = steer;
|
|
#endif
|
|
}
|
|
|
|
void LCR_rendererDraw(void)
|
|
{
|
|
LCR_LOG2("rendering frame (start)");
|
|
|
|
// first make sure rotations are in correct range:
|
|
LCR_renderer.scene.camera.transform.rotation.y = S3L_wrap(
|
|
LCR_renderer.scene.camera.transform.rotation.y, S3L_F);
|
|
|
|
LCR_renderer.scene.camera.transform.rotation.x = S3L_clamp(
|
|
LCR_renderer.scene.camera.transform.rotation.x,-1 * S3L_F / 4,S3L_F / 4);
|
|
|
|
LCR_renderer.previousTriID = -1;
|
|
S3L_newFrame();
|
|
|
|
#if LCR_ANIMATE_CAR
|
|
_LCR_rendererAnimateCar();
|
|
#endif
|
|
|
|
_LCR_rendererLoadMapChunks(); // TODO: call only once in a while?
|
|
|
|
LCR_rendererDrawSky(LCR_currentMap.environment,
|
|
LCR_renderer.scene.camera.transform.rotation.y,
|
|
-4 * LCR_renderer.scene.camera.transform.rotation.x);
|
|
|
|
LCR_drawLevelFloor();
|
|
LCR_rendererDrawLOD();
|
|
|
|
LCR_LOG2("gonna render 3D scene");
|
|
S3L_drawScene(LCR_renderer.scene);
|
|
LCR_LOG2("rendering 3D scene done");
|
|
|
|
LCR_renderer.frame++;
|
|
|
|
LCR_LOG2("rendering frame (end)");
|
|
}
|
|
|
|
#endif // guard
|