import { v3, quat } from './Runtime3D.js';

/* ────────────────────────────────────────────────────────────────
   3D Scene Editor — Sprint 5.3
   
   3D viewport interaction, gizmos, terrain, skybox tools
──────────────────────────────────────────────────────────────── */
function createGizmo3DState() {
    return {
        mode: 'translate',
        activeAxis: 'none',
        space: 'world',
        dragging: false,
        startPosition: v3.zero(),
        startRotation: quat.identity(),
        startScale: v3.one(),
    };
}
const DEFAULT_PLACEMENT_CONFIG = {
    snapEnabled: false,
    gridSize: 1,
    surfaceSnap: true,
    randomRotation: false,
};
function snapToGrid3D(pos, gridSize) {
    return {
        x: Math.round(pos.x / gridSize) * gridSize,
        y: Math.round(pos.y / gridSize) * gridSize,
        z: Math.round(pos.z / gridSize) * gridSize,
    };
}
function placeObject(world, primitive, position, config) {
    let pos = position;
    if (config.snapEnabled)
        pos = snapToGrid3D(pos, config.gridSize);
    const t = world.createTransform({ position: pos });
    world.addMesh(t.entityId, primitive);
    if (config.randomRotation) {
        t.rotation = quat.fromEuler(0, Math.random() * Math.PI * 2, 0);
    }
    return t.entityId;
}
const DEFAULT_TERRAIN_CONFIG = {
    width: 100,
    depth: 100,
    resolution: 64,
    maxHeight: 20,
};
class Terrain3D {
    constructor(config) {
        this.config = { ...DEFAULT_TERRAIN_CONFIG, ...config };
        const res = this.config.resolution;
        this.heightmap = new Float32Array(res * res);
    }
    getHeight(gridX, gridZ) {
        const res = this.config.resolution;
        const x = Math.max(0, Math.min(res - 1, gridX));
        const z = Math.max(0, Math.min(res - 1, gridZ));
        return this.heightmap[z * res + x];
    }
    setHeight(gridX, gridZ, h) {
        const res = this.config.resolution;
        if (gridX >= 0 && gridX < res && gridZ >= 0 && gridZ < res) {
            this.heightmap[gridZ * res + gridX] = Math.min(this.config.maxHeight, Math.max(0, h));
        }
    }
    /** Get interpolated height at world coordinates. */
    getHeightAtWorld(worldX, worldZ) {
        const { width, depth, resolution } = this.config;
        const gx = ((worldX + width / 2) / width) * (resolution - 1);
        const gz = ((worldZ + depth / 2) / depth) * (resolution - 1);
        const ix = Math.floor(gx);
        const iz = Math.floor(gz);
        const fx = gx - ix;
        const fz = gz - iz;
        const h00 = this.getHeight(ix, iz);
        const h10 = this.getHeight(ix + 1, iz);
        const h01 = this.getHeight(ix, iz + 1);
        const h11 = this.getHeight(ix + 1, iz + 1);
        const h0 = h00 + (h10 - h00) * fx;
        const h1 = h01 + (h11 - h01) * fx;
        return h0 + (h1 - h0) * fz;
    }
    /** Raise/lower terrain around a point. */
    brush(worldX, worldZ, radius, strength) {
        const { width, depth, resolution } = this.config;
        const gridRadius = (radius / width) * resolution;
        const cx = ((worldX + width / 2) / width) * (resolution - 1);
        const cz = ((worldZ + depth / 2) / depth) * (resolution - 1);
        const minX = Math.max(0, Math.floor(cx - gridRadius));
        const maxX = Math.min(resolution - 1, Math.ceil(cx + gridRadius));
        const minZ = Math.max(0, Math.floor(cz - gridRadius));
        const maxZ = Math.min(resolution - 1, Math.ceil(cz + gridRadius));
        for (let z = minZ; z <= maxZ; z++) {
            for (let x = minX; x <= maxX; x++) {
                const dx = x - cx;
                const dz = z - cz;
                const dist = Math.sqrt(dx * dx + dz * dz);
                if (dist <= gridRadius) {
                    const falloff = 1 - dist / gridRadius;
                    const current = this.heightmap[z * resolution + x];
                    this.heightmap[z * resolution + x] = Math.max(0, Math.min(this.config.maxHeight, current + strength * falloff));
                }
            }
        }
    }
    /** Flatten terrain around a point to a given height. */
    flatten(worldX, worldZ, radius, targetHeight) {
        const { width, depth, resolution } = this.config;
        const gridRadius = (radius / width) * resolution;
        const cx = ((worldX + width / 2) / width) * (resolution - 1);
        const cz = ((worldZ + depth / 2) / depth) * (resolution - 1);
        const minX = Math.max(0, Math.floor(cx - gridRadius));
        const maxX = Math.min(resolution - 1, Math.ceil(cx + gridRadius));
        const minZ = Math.max(0, Math.floor(cz - gridRadius));
        const maxZ = Math.min(resolution - 1, Math.ceil(cz + gridRadius));
        for (let z = minZ; z <= maxZ; z++) {
            for (let x = minX; x <= maxX; x++) {
                const dx = x - cx;
                const dz = z - cz;
                const dist = Math.sqrt(dx * dx + dz * dz);
                if (dist <= gridRadius) {
                    const falloff = 1 - dist / gridRadius;
                    const current = this.heightmap[z * resolution + x];
                    this.heightmap[z * resolution + x] = current + (targetHeight - current) * falloff;
                }
            }
        }
    }
    /** Export heightmap to a simple JSON-serialisable format. */
    serialize() {
        return {
            config: { ...this.config },
            heights: Array.from(this.heightmap),
        };
    }
    static deserialize(data) {
        const t = new Terrain3D(data.config);
        t.heightmap.set(data.heights);
        return t;
    }
}
const DEFAULT_SKYBOX = {
    type: 'color',
    color: { r: 0.4, g: 0.6, b: 0.9 },
};
function createBlendTree(rootName) {
    return {
        name: rootName,
        animationId: null,
        children: [],
        blendParameter: 'speed',
        threshold: 0,
    };
}
function addBlendTreeChild(parent, animationId, threshold, name) {
    const child = {
        name: name !== null && name !== void 0 ? name : `blend_${parent.children.length}`,
        animationId,
        children: [],
        blendParameter: parent.blendParameter,
        threshold,
    };
    parent.children.push(child);
    // Sort children by threshold ascending
    parent.children.sort((a, b) => a.threshold - b.threshold);
    return child;
}
/** Evaluate blend tree → returns pairs of [animId, weight]. */
function evaluateBlendTree(node, parameterValue) {
    const results = [];
    if (node.children.length === 0 && node.animationId) {
        results.push({ animationId: node.animationId, weight: 1 });
        return results;
    }
    // Find surrounding children
    let below = null;
    let above = null;
    for (const child of node.children) {
        if (child.threshold <= parameterValue)
            below = child;
        if (child.threshold >= parameterValue && !above)
            above = child;
    }
    if (!below && above) {
        if (above.animationId)
            results.push({ animationId: above.animationId, weight: 1 });
    }
    else if (below && !above) {
        if (below.animationId)
            results.push({ animationId: below.animationId, weight: 1 });
    }
    else if (below && above && below !== above) {
        const range = above.threshold - below.threshold;
        const t = range > 0 ? (parameterValue - below.threshold) / range : 0;
        if (below.animationId)
            results.push({ animationId: below.animationId, weight: 1 - t });
        if (above.animationId)
            results.push({ animationId: above.animationId, weight: t });
    }
    else if (below && below.animationId) {
        results.push({ animationId: below.animationId, weight: 1 });
    }
    return results;
}
/** Material library for managing PBR materials. */
class MaterialLibrary {
    constructor() {
        this.materials = new Map();
    }
    add(mat) {
        this.materials.set(mat.id, mat);
    }
    get(id) {
        return this.materials.get(id);
    }
    remove(id) {
        return this.materials.delete(id);
    }
    all() {
        return Array.from(this.materials.values());
    }
    /** Duplicate a material with a new id. */
    clone(sourceId, newName) {
        const src = this.materials.get(sourceId);
        if (!src)
            return null;
        const cloned = {
            ...src,
            id: `mat_${Date.now()}_${Math.random().toString(36).slice(2, 5)}`,
            name: newName !== null && newName !== void 0 ? newName : `${src.name}_copy`,
        };
        this.materials.set(cloned.id, cloned);
        return cloned;
    }
    /** Export all materials to JSON-serialisable array. */
    serialize() {
        return this.all();
    }
    load(materials) {
        for (const m of materials)
            this.materials.set(m.id, m);
    }
}
const DEFAULT_FPS_CONFIG = {
    moveSpeed: 7,
    lookSensitivity: 0.002,
    jumpForce: 8,
    gravity: 20,
    weaponSlots: 4,
    healthMax: 100,
    crosshairSize: 20,
};
const DEFAULT_THIRD_PERSON_CONFIG = {
    moveSpeed: 5,
    sprintMultiplier: 1.6,
    jumpForce: 8,
    gravity: 20,
    cameraDistance: 5,
    cameraPitchMin: -0.8,
    cameraPitchMax: 1.2,
    cameraSmoothing: 0.1,
    lockOnRadius: 15,
};
const DEFAULT_RACING_3D_CONFIG = {
    maxSpeed: 200,
    acceleration: 40,
    braking: 60,
    turnSpeed: 2.5,
    driftFactor: 0.85,
    gravity: 20,
    cameraFollowDistance: 8,
    cameraHeight: 3,
};
const DEFAULT_FLIGHT_SIM_CONFIG = {
    maxThrust: 100,
    liftCoefficient: 0.5,
    dragCoefficient: 0.02,
    rollSpeed: 2,
    pitchSpeed: 1.5,
    yawSpeed: 0.8,
    gravity: 9.81,
    stallAngle: 0.35,
};
const DEFAULT_HORROR_CONFIG = {
    walkSpeed: 3,
    sprintSpeed: 6,
    staminaMax: 100,
    staminaDrain: 15,
    staminaRegen: 8,
    flashlightRange: 20,
    flashlightAngle: Math.PI / 5,
    sanityMax: 100,
    sanityDecayRate: 1,
    ambientLightIntensity: 0.05,
};

export { DEFAULT_FLIGHT_SIM_CONFIG, DEFAULT_FPS_CONFIG, DEFAULT_HORROR_CONFIG, DEFAULT_PLACEMENT_CONFIG, DEFAULT_RACING_3D_CONFIG, DEFAULT_SKYBOX, DEFAULT_TERRAIN_CONFIG, DEFAULT_THIRD_PERSON_CONFIG, MaterialLibrary, Terrain3D, addBlendTreeChild, createBlendTree, createGizmo3DState, evaluateBlendTree, placeObject, snapToGrid3D };
//# sourceMappingURL=SceneEditor3D.js.map
