rodecomp/Source/Contents/Ground/GroundGeometry.cpp
2026-10-27 10:21:57 +01:00

330 lines
13 KiB
C++

#include "Contents/Ground/GroundGeometry.h"
#include "Render/Textures/Texture.h"
#include "Render/Textures/TextureCache.h"
#include "Resource/World/GroundResource.h"
#include "Utility/Math/ScalarConversion.h"
#include <bit>
#include <cmath>
#include <cstring>
namespace {
struct CellNormals {
float values[4][3]{};
};
// INLINED
float InverseLength(float squared) {
const float length = std::sqrt(squared);
return 1.0f / length;
}
// INLINED
void Normalize(float* vector, bool yFirst) {
const float first = yFirst ? vector[1] * vector[1] + vector[0] * vector[0] : vector[0] * vector[0] + vector[1] * vector[1];
const float inverse = InverseLength(first + vector[2] * vector[2]);
vector[0] = inverse * vector[0];
vector[1] = inverse * vector[1];
vector[2] = inverse * vector[2];
}
// INLINED
void Add(float* result, const float* value) {
result[0] = result[0] + value[0];
result[1] = result[1] + value[1];
result[2] = result[2] + value[2];
}
// INLINED
std::uint32_t
LitColor(const float* normal, bool normalize, bool side, const float* direction, const float* diffuse, const float* ambient, const GroundQuad* source) {
float vector[3] = {normal[0], normal[1], normal[2]};
if (normalize) {
Normalize(vector, false);
}
const float dot = side ? (direction[1] * vector[1] + vector[0] * direction[0]) + direction[2] * vector[2]
: (vector[1] * direction[1] + vector[0] * direction[0]) + vector[2] * direction[2];
const float amount = dot > 0.0f ? dot : 0.0f;
const std::uint32_t color = source ? source->color : 0xFFFFFFFFu;
std::uint32_t result = 0xFF000000u;
for (unsigned channel = 0; channel < 3; ++channel) {
const float illumination = amount * diffuse[channel] + ambient[channel];
const float light = illumination < 1.0f ? illumination : 1.0f;
const unsigned shift = 16 - channel * 8;
const float scaled = static_cast<float>((color >> shift) & 255u) * light;
const unsigned value = static_cast<unsigned>(TruncateToInt(scaled)) & 255u;
result |= value << shift;
}
return result;
}
}
// OFFSET: 0x00ABD640
// FUNCTION: fn_17546
GroundGeometry::GroundGeometry() : cellSize(10.0f), loaded(0) {
}
// INLINED
GroundGeometry::~GroundGeometry() {
this->ClearLightmaps();
}
// OFFSET: 0x00AC3010
// FUNCTION: fn_17595
void GroundGeometry::ClearGeometry() {
this->tiles.clear();
this->lighting.clear();
this->cells.clear();
}
// OFFSET: 0x00AC0200
// FUNCTION: fn_17563
void GroundGeometry::Reset(int columns, int rows) {
this->ClearGeometry();
this->width = columns;
this->height = rows;
this->cells.resize(static_cast<unsigned>(columns) * static_cast<unsigned>(rows));
this->lighting.resize(4u * static_cast<unsigned>(columns) * static_cast<unsigned>(rows), {1.0f, 1.0f, 1.0f});
this->unknown_005C = 0;
this->unknown_0058 = 0;
}
// INLINED
GroundSpatial GroundGeometry::Spatial() const {
return {this->width, this->height, this->cellSize, this->cells};
}
// OFFSET: 0x00ABDEF0
// FUNCTION: fn_17556
void GroundGeometry::Build(CGndRes& resource, TextureCache& textures, const float* lightDirection, const float* diffuse, const float* ambient) {
this->loaded = resource.loaded;
this->Reset(resource.width, resource.height);
std::vector<CellNormals> normals(this->cells.size());
this->cellSize = resource.cellSize;
this->BuildLightmaps(resource, textures);
this->tiles.resize(static_cast<unsigned>(resource.tileCount));
for (int row = 0; row < this->height; ++row) {
for (int column = 0; column < this->width; ++column) {
const unsigned index = static_cast<unsigned>(row) * static_cast<unsigned>(this->width) + static_cast<unsigned>(column);
GroundCell& cell = this->cells[index];
const unsigned char* record = resource.cells + 28u * index;
std::memcpy(cell.heights, record, sizeof(cell.heights));
const float left = static_cast<float>(column - this->width / 2) * this->cellSize;
const float right = static_cast<float>(column - this->width / 2 + 1) * this->cellSize;
const float top = static_cast<float>(row - this->height / 2) * this->cellSize;
const float bottom = static_cast<float>(row - this->height / 2 + 1) * this->cellSize;
const float corners[4][3] = {{left, 0, top}, {right, 0, top}, {left, 0, bottom}, {right, 0, bottom}};
std::memcpy(cell.corners, corners, sizeof(corners));
int tileIndices[3];
std::memcpy(tileIndices, record + 16, sizeof(tileIndices));
for (unsigned face = 0; face < 3; ++face) {
if ((face == 1 && row >= this->height - 1) || (face == 2 && column >= this->width - 1)) {
continue;
}
const int tileIndex = tileIndices[face];
if (tileIndex < 0 || tileIndex >= resource.tileCount) {
continue;
}
GroundQuad& tile = this->tiles[tileIndex];
const unsigned char* tileBytes = resource.tiles + 40u * static_cast<unsigned>(tileIndex);
std::memcpy(&tile.color, tileBytes + 36, sizeof(tile.color));
std::int16_t textureIndex;
std::uint16_t lightmapIndex;
std::memcpy(&textureIndex, tileBytes + 32, sizeof(textureIndex));
std::memcpy(&lightmapIndex, tileBytes + 34, sizeof(lightmapIndex));
tile.texture = textures.Load(resource.textureNames[textureIndex], 0, false, true, false);
float positions[4][3];
if (face == 0) {
for (unsigned corner = 0; corner < 4; ++corner) {
positions[corner][0] = cell.corners[corner][0];
positions[corner][1] = cell.heights[corner];
positions[corner][2] = cell.corners[corner][2];
}
} else if (face == 1) {
float next[4];
std::memcpy(next, resource.cells + 28u * (index + this->width), sizeof(next));
const float values[4][3] = {{left, cell.heights[2], bottom}, {right, cell.heights[3], bottom}, {left, next[0], bottom}, {right, next[1], bottom}};
std::memcpy(positions, values, sizeof(positions));
normals[index].values[2][2] = next[0] <= cell.heights[2] ? 1.0f : -1.0f;
} else {
float next[4];
std::memcpy(next, resource.cells + 28u * (index + 1), sizeof(next));
const float values[4][3] = {{right, cell.heights[3], bottom}, {right, cell.heights[1], top}, {right, next[2], bottom}, {right, next[0], top}};
std::memcpy(positions, values, sizeof(positions));
normals[index].values[3][0] = next[0] <= cell.heights[2] ? 1.0f : -1.0f;
}
const float scaleU = static_cast<float>(tile.texture->Width()) / static_cast<float>(tile.texture->TextureWidth());
const float scaleV = static_cast<float>(tile.texture->Height()) / static_cast<float>(tile.texture->TextureHeight());
GroundLightmap& lightmap = this->lightmaps[lightmapIndex];
tile.lightmap = &lightmap;
tile.lightmapTexture = lightmap.texture;
for (unsigned corner = 0; corner < 4; ++corner) {
std::memcpy(tile.vertices[corner], positions[corner], sizeof(positions[corner]));
float u;
float v;
std::memcpy(&u, tileBytes + corner * 4, sizeof(u));
std::memcpy(&v, tileBytes + 16 + corner * 4, sizeof(v));
tile.vertices[corner][4] = scaleU * u;
tile.vertices[corner][5] = scaleV * v;
tile.vertices[corner][6] = lightmap.uv[corner][0];
tile.vertices[corner][7] = lightmap.uv[corner][1];
}
if (face == 0) {
for (unsigned channel = 0; channel < 3; ++channel) {
const unsigned value = 2u * lightmap.color[channel];
cell.color[channel] = static_cast<unsigned char>(value > 255 ? 255 : value);
}
float* first = normals[index].values[0];
float* second = normals[index].values[1];
const float ab = cell.heights[2] - cell.heights[0];
const float ac = cell.heights[2] - cell.heights[1];
first[0] = ac * (this->cellSize - 0.0f) - (this->cellSize - 0.0f) * ab;
first[1] = (this->cellSize - 0.0f) * 0.0f - (this->cellSize - 0.0f) * (0.0f - this->cellSize);
first[2] = ab * (0.0f - this->cellSize) - ac * 0.0f;
Normalize(first, true);
const float db = cell.heights[3] - cell.heights[1];
const float dc = cell.heights[3] - cell.heights[2];
const float dz = this->cellSize - 0.0f;
const float dx = this->cellSize - 0.0f;
const float zero = this->cellSize - this->cellSize;
second[0] = db * zero - dx * dc;
second[1] = dz * dx - zero * zero;
second[2] = zero * dc - dz * db;
Normalize(second, true);
cell.top = &tile;
} else if (face == 1) {
cell.side2 = &tile;
} else {
cell.frame = 0xFFFFFFFFu;
cell.side1 = &tile;
}
}
}
}
for (int row = 0; row < this->height; ++row) {
for (int column = 0; column < this->width; ++column) {
const unsigned index = static_cast<unsigned>(row) * static_cast<unsigned>(this->width) + static_cast<unsigned>(column);
GroundCell& cell = this->cells[index];
const auto neighbor = [&](int x, int y) -> const GroundCell* {
return x >= 0 && x < this->width && y >= 0 && y < this->height
? &this->cells[static_cast<unsigned>(y) * static_cast<unsigned>(this->width) + static_cast<unsigned>(x)]
: nullptr;
};
const GroundCell* north = neighbor(column, row - 1);
const GroundCell* west = neighbor(column - 1, row);
const GroundCell* northwest = neighbor(column - 1, row - 1);
const GroundCell* northeast = neighbor(column + 1, row - 1);
const GroundCell* east = neighbor(column + 1, row);
const GroundCell* south = neighbor(column, row + 1);
const GroundCell* southwest = neighbor(column - 1, row + 1);
const GroundCell* southeast = neighbor(column + 1, row + 1);
float smooth[4][3]{};
const auto add = [&](unsigned corner, const GroundCell* source, unsigned first, unsigned count) {
const auto sourceIndex = static_cast<unsigned>(source - this->cells.data());
for (unsigned normal = first; normal < first + count; ++normal) {
Add(smooth[corner], normals[sourceIndex].values[normal]);
}
};
add(0, &cell, 0, 1);
if (north && north->heights[2] == cell.heights[0]) {
add(0, north, 0, 2);
}
if (west && west->heights[1] == cell.heights[0]) {
add(0, west, 0, 2);
}
if (northwest && northwest->heights[3] == cell.heights[0]) {
add(0, northwest, 1, 1);
}
add(1, &cell, 0, 2);
if (east && east->heights[0] == cell.heights[1]) {
add(1, east, 0, 1);
}
if (north && north->heights[3] == cell.heights[1]) {
add(1, north, 1, 1);
}
if (northeast && northeast->heights[2] == cell.heights[1]) {
add(1, northeast, 0, 2);
}
add(2, &cell, 0, 2);
if (west && west->heights[3] == cell.heights[2]) {
add(2, west, 1, 1);
}
if (south && south->heights[0] == cell.heights[2]) {
add(2, south, 0, 1);
}
if (southwest && southwest->heights[1] == cell.heights[2]) {
add(2, southwest, 0, 2);
}
add(3, &cell, 0, 1);
if (east && east->heights[2] == cell.heights[3]) {
add(3, east, 0, 2);
}
if (south && south->heights[1] == cell.heights[3]) {
add(3, south, 0, 2);
}
if (southeast && southeast->heights[0] == cell.heights[3]) {
add(3, southeast, 0, 1);
}
if (cell.top) {
const GroundCell* sources[4] = {&cell, east, south, southeast};
for (unsigned corner = 0; corner < 4; ++corner) {
const GroundQuad* source = sources[corner] ? sources[corner]->top : nullptr;
const auto color = LitColor(smooth[corner], true, false, lightDirection, diffuse, ambient, source);
cell.top->vertices[corner][3] = std::bit_cast<float>(color);
}
}
if (cell.side2) {
for (unsigned corner = 0; corner < 4; ++corner) {
const GroundCell* source = (corner & 1) ? southeast : south;
const auto color = LitColor(normals[index].values[2], false, true, lightDirection, diffuse, ambient, source ? source->top : nullptr);
cell.side2->vertices[corner][3] = std::bit_cast<float>(color);
}
}
if (cell.side1) {
for (unsigned corner = 0; corner < 4; ++corner) {
const GroundCell* source = (corner & 1) ? east : southeast;
const auto color = LitColor(normals[index].values[3], false, true, lightDirection, diffuse, ambient, source ? source->top : nullptr);
cell.side1->vertices[corner][3] = std::bit_cast<float>(color);
}
}
}
}
}