OverviewFilesCommitsIssuesDocsReleasesPeers

dc-terrain / src / dc_terrain_utils.cpp

138 lines · cpp ref:
#include "dc_terrain_utils.h"
#include "dc_mesher.h"

#include <godot_cpp/core/class_db.hpp>
#include <godot_cpp/variant/utility_functions.hpp>
#include <godot_cpp/classes/mesh.hpp>

#include <algorithm>
#include <cmath>

namespace godot {

void DCTerrainUtils::_bind_methods() {
    ClassDB::bind_static_method("DCTerrainUtils",
        D_METHOD("mesh_from_generator", "generator", "origin", "voxel_size", "grid_size"),
        &DCTerrainUtils::mesh_from_generator);
    ClassDB::bind_static_method("DCTerrainUtils",
        D_METHOD("mesh_from_generator_auto", "generator", "voxel_size"),
        &DCTerrainUtils::mesh_from_generator_auto);
}

Ref<ArrayMesh> DCTerrainUtils::mesh_from_generator(Ref<DCTerrainGenerator> generator,
                                                    Vector3 origin, float voxel_size, int grid_size) {
    Ref<ArrayMesh> arr_mesh;
    arr_mesh.instantiate();

    if (!generator.is_valid()) {
        UtilityFunctions::printerr("DCTerrainUtils: generator is null");
        return arr_mesh;
    }

    DCGenerator* gen = generator->get_native_generator();
    if (!gen) {
        UtilityFunctions::printerr("DCTerrainUtils: native generator is null");
        return arr_mesh;
    }

    float org[3] = { origin.x, origin.y, origin.z };

    DCMesherConfig config;
    config.auto_smooth = true;
    config.smooth_angle_deg = 30.0f;
    config.qef_bias = 0.01f;

    DCMeshData mesh = dc_mesh_from_generator(*gen, org, voxel_size, grid_size, config);

    if (mesh.positions.empty() || mesh.indices.empty()) {
        UtilityFunctions::print("DCTerrainUtils: generator produced empty mesh");
        return arr_mesh;
    }

    // Convert DCMeshData → Godot arrays
    int vert_count = (int)mesh.positions.size() / 3;
    int tri_count = (int)mesh.indices.size() / 3;

    PackedVector3Array vertices;
    vertices.resize(vert_count);
    for (int i = 0; i < vert_count; i++) {
        vertices[i] = Vector3(mesh.positions[i * 3],
                               mesh.positions[i * 3 + 1],
                               mesh.positions[i * 3 + 2]);
    }

    PackedVector3Array normals;
    if (!mesh.normals.empty()) {
        normals.resize(vert_count);
        for (int i = 0; i < vert_count; i++) {
            normals[i] = Vector3(mesh.normals[i * 3],
                                  mesh.normals[i * 3 + 1],
                                  mesh.normals[i * 3 + 2]);
        }
    }

    PackedInt32Array indices;
    indices.resize((int)mesh.indices.size());
    for (int i = 0; i < (int)mesh.indices.size(); i++) {
        indices[i] = mesh.indices[i];
    }

    Array arrays;
    arrays.resize(Mesh::ARRAY_MAX);
    arrays[Mesh::ARRAY_VERTEX] = vertices;
    if (!normals.is_empty()) {
        arrays[Mesh::ARRAY_NORMAL] = normals;
    }
    arrays[Mesh::ARRAY_INDEX] = indices;

    arr_mesh->add_surface_from_arrays(Mesh::PRIMITIVE_TRIANGLES, arrays);

    UtilityFunctions::print("DCTerrainUtils: meshed ", vert_count, " verts, ", tri_count, " tris");
    return arr_mesh;
}

Ref<ArrayMesh> DCTerrainUtils::mesh_from_generator_auto(Ref<DCTerrainGenerator> generator,
                                                         float voxel_size) {
    if (!generator.is_valid()) {
        UtilityFunctions::printerr("DCTerrainUtils: generator is null");
        Ref<ArrayMesh> empty;
        empty.instantiate();
        return empty;
    }

    DCGenerator* gen = generator->get_native_generator();
    if (!gen) {
        UtilityFunctions::printerr("DCTerrainUtils: native generator is null");
        Ref<ArrayMesh> empty;
        empty.instantiate();
        return empty;
    }

    float bmin[3], bmax[3];
    if (!gen->get_bounds(bmin, bmax)) {
        UtilityFunctions::printerr("DCTerrainUtils: generator has no bounds, use mesh_from_generator() with explicit origin/size");
        Ref<ArrayMesh> empty;
        empty.instantiate();
        return empty;
    }

    // Add 10% padding
    float extent[3] = { bmax[0] - bmin[0], bmax[1] - bmin[1], bmax[2] - bmin[2] };
    float max_extent = std::max({extent[0], extent[1], extent[2]});
    float pad = max_extent * 0.1f;

    Vector3 origin(bmin[0] - pad, bmin[1] - pad, bmin[2] - pad);
    int raw_size = (int)std::ceil((max_extent + 2 * pad) / voxel_size);
    // Round up to next power of 2 — octree requires power-of-2 grid size
    int grid_size = 1;
    while (grid_size < raw_size) grid_size <<= 1;
    grid_size = std::min(grid_size, 256);  // cap at 256 to avoid OOM

    UtilityFunctions::print("DCTerrainUtils: auto bounds, grid_size=", grid_size,
                             " voxel_size=", voxel_size);

    return mesh_from_generator(generator, origin, voxel_size, grid_size);
}

} // namespace godot