OverviewFilesCommitsIssuesDocsReleasesPeers

dc-terrain / src / dc_collision_sdf.h

70 lines · c ref:
#ifndef DC_COLLISION_SDF_H
#define DC_COLLISION_SDF_H

#include <godot_cpp/variant/vector3.hpp>
#include "dc_generator.h"

#include <cmath>

namespace godot {

// ---------------------------------------------------------------------------
// DCCollisionSDF — static SDF collision queries against a DCGenerator.
// Extracted from DCTerrain's get_distance / get_surface_normal / get_capsule_push.
// These are pure density field queries with no per-frame state.
// ---------------------------------------------------------------------------
class DCCollisionSDF {
public:
    // Returns signed distance to terrain surface (positive = outside solid, negative = inside).
    // Uses SDF convention: negate density (density is positive inside solid).
    static float get_distance(DCGenerator* generator, const Vector3& position) {
        return -generator->evaluate(position.x, position.y, position.z, 0.0f);
    }

    // Returns outward-pointing surface normal at position via central differences.
    // h: step size for gradient computation (typically voxel_size * 0.01).
    static Vector3 get_surface_normal(DCGenerator* generator, const Vector3& position, float h) {
        float x = position.x, y = position.y, z = position.z;

        float gx = generator->evaluate(x + h, y, z, 0.0f) - generator->evaluate(x - h, y, z, 0.0f);
        float gy = generator->evaluate(x, y + h, z, 0.0f) - generator->evaluate(x, y - h, z, 0.0f);
        float gz = generator->evaluate(x, y, z + h, 0.0f) - generator->evaluate(x, y, z - h, 0.0f);

        // Gradient points toward increasing density (into solid).
        // Negate to get outward-pointing surface normal (away from solid).
        float len = std::sqrt(gx * gx + gy * gy + gz * gz);
        if (len < 1e-8f) return Vector3(0, 1, 0);  // degenerate: default to up
        float inv = -1.0f / len;
        return Vector3(gx * inv, gy * inv, gz * inv);
    }

    // Capsule collision query: returns push-out vector if capsule intersects terrain.
    // Returns zero vector if no collision. Tests bottom, center, and top of capsule.
    static Vector3 get_capsule_push(DCGenerator* generator, const Vector3& position,
                                     float radius, float height, float voxel_size) {
        float half_h = height * 0.5f;
        Vector3 points[3] = {
            Vector3(position.x, position.y - half_h + radius, position.z),  // bottom
            position,                                                         // center
            Vector3(position.x, position.y + half_h - radius, position.z),  // top
        };

        float h = voxel_size * 0.01f;
        Vector3 total_push(0, 0, 0);
        for (int i = 0; i < 3; i++) {
            float dist = get_distance(generator, points[i]);
            if (dist < radius) {
                // Penetrating — push out along surface normal
                Vector3 normal = get_surface_normal(generator, points[i], h);
                float penetration = radius - dist;
                total_push += normal * penetration;
            }
        }
        return total_push;
    }
};

} // namespace godot

#endif // DC_COLLISION_SDF_H