#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 #include #include 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((color >> shift) & 255u) * light; const unsigned value = static_cast(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(columns) * static_cast(rows)); this->lighting.resize(4u * static_cast(columns) * static_cast(rows), {1.0f, 1.0f, 1.0f}); this->unknown_005C = 0; this->unknown_0058 = 0; } 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 normals(this->cells.size()); this->cellSize = resource.cellSize; this->BuildLightmaps(resource, textures); this->tiles.resize(static_cast(resource.tileCount)); for (int row = 0; row < this->height; ++row) { for (int column = 0; column < this->width; ++column) { const unsigned index = static_cast(row) * static_cast(this->width) + static_cast(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(column - this->width / 2) * this->cellSize; const float right = static_cast(column - this->width / 2 + 1) * this->cellSize; const float top = static_cast(row - this->height / 2) * this->cellSize; const float bottom = static_cast(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(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(tile.texture->Width()) / static_cast(tile.texture->TextureWidth()); const float scaleV = static_cast(tile.texture->Height()) / static_cast(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(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(row) * static_cast(this->width) + static_cast(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(y) * static_cast(this->width) + static_cast(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(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(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(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(color); } } } } }