/* ────────────────────────────────────────────────────────────────
   EPIC E6 — nice2dev-physics (Advanced Physics)
   
   Fluid simulation, soft body, destruction, rope/chain,
   cloth simulation
──────────────────────────────────────────────────────────────── */
const DEFAULT_FLUID_CONFIG = {
    particleCount: 1000,
    particleRadius: 0.5,
    restDensity: 1000,
    stiffness: 200,
    viscosity: 0.01,
    gravity: { x: 0, y: -9.81 },
    boundaryMin: { x: -10, y: -10 },
    boundaryMax: { x: 10, y: 10 },
    smoothingRadius: 1.5,
    timeStep: 1 / 60,
    surfaceTension: 0.1,
    colorMode: 'velocity',
    renderMode: 'particles',
};
function createFluidParticles(config) {
    const particles = [];
    const cols = Math.ceil(Math.sqrt(config.particleCount));
    const spacing = config.particleRadius * 2;
    const startX = (config.boundaryMin.x + config.boundaryMax.x) / 2 - (cols * spacing) / 2;
    const startY = (config.boundaryMin.y + config.boundaryMax.y) / 2;
    for (let i = 0; i < config.particleCount; i++) {
        const col = i % cols;
        const row = Math.floor(i / cols);
        particles.push({
            position: { x: startX + col * spacing, y: startY + row * spacing },
            velocity: { x: 0, y: 0 },
            acceleration: { x: 0, y: 0 },
            density: config.restDensity,
            pressure: 0,
            mass: 1,
        });
    }
    return particles;
}
/** SPH kernel (poly6). */
function sphKernel(r, h) {
    if (r > h)
        return 0;
    const h2 = h * h;
    const diff = h2 - r * r;
    return (315 / (64 * Math.PI * Math.pow(h, 9))) * diff * diff * diff;
}
/** SPH pressure kernel gradient (spiky). */
function sphPressureGrad(r, h) {
    if (r > h || r < 0.0001)
        return 0;
    const diff = h - r;
    return -(45 / (Math.PI * Math.pow(h, 6))) * diff * diff;
}
/** SPH viscosity laplacian. */
function sphViscosityLap(r, h) {
    if (r > h)
        return 0;
    return (45 / (Math.PI * Math.pow(h, 6))) * (h - r);
}
function stepFluid(particles, config) {
    const h = config.smoothingRadius;
    // Compute densities
    for (const pi of particles) {
        pi.density = 0;
        for (const pj of particles) {
            const dx = pi.position.x - pj.position.x;
            const dy = pi.position.y - pj.position.y;
            const r = Math.sqrt(dx * dx + dy * dy);
            pi.density += pj.mass * sphKernel(r, h);
        }
        pi.pressure = config.stiffness * (pi.density - config.restDensity);
    }
    // Compute forces
    for (const pi of particles) {
        let fx = 0, fy = 0;
        for (const pj of particles) {
            if (pi === pj)
                continue;
            const dx = pi.position.x - pj.position.x;
            const dy = pi.position.y - pj.position.y;
            const r = Math.sqrt(dx * dx + dy * dy);
            if (r < h && r > 0.0001) {
                // Pressure force
                const pressureScale = -pj.mass * (pi.pressure + pj.pressure) / (2 * pj.density) * sphPressureGrad(r, h);
                fx += pressureScale * (dx / r);
                fy += pressureScale * (dy / r);
                // Viscosity force
                const viscScale = config.viscosity * pj.mass / pj.density * sphViscosityLap(r, h);
                fx += viscScale * (pj.velocity.x - pi.velocity.x);
                fy += viscScale * (pj.velocity.y - pi.velocity.y);
            }
        }
        pi.acceleration.x = fx / pi.density + config.gravity.x;
        pi.acceleration.y = fy / pi.density + config.gravity.y;
    }
    // Integrate (symplectic Euler)
    const dt = config.timeStep;
    for (const p of particles) {
        p.velocity.x += p.acceleration.x * dt;
        p.velocity.y += p.acceleration.y * dt;
        p.position.x += p.velocity.x * dt;
        p.position.y += p.velocity.y * dt;
        // Boundary enforcement
        if (p.position.x < config.boundaryMin.x) {
            p.position.x = config.boundaryMin.x;
            p.velocity.x *= -0.5;
        }
        if (p.position.x > config.boundaryMax.x) {
            p.position.x = config.boundaryMax.x;
            p.velocity.x *= -0.5;
        }
        if (p.position.y < config.boundaryMin.y) {
            p.position.y = config.boundaryMin.y;
            p.velocity.y *= -0.5;
        }
        if (p.position.y > config.boundaryMax.y) {
            p.position.y = config.boundaryMax.y;
            p.velocity.y *= -0.5;
        }
    }
}
const DEFAULT_SOFT_BODY = {
    stiffness: 500,
    damping: 5,
    gravity: { x: 0, y: -9.81 },
    pressure: 100,
    friction: 0.3,
    maxStretch: 1.5,
    iterations: 4,
};
function createSoftBodyCircle(center, radius, segments, config) {
    const nodes = [];
    const springs = [];
    // Center node
    nodes.push({
        position: { ...center },
        prevPosition: { ...center },
        velocity: { x: 0, y: 0 },
        mass: 1,
        pinned: false,
    });
    // Perimeter nodes
    for (let i = 0; i < segments; i++) {
        const angle = (i / segments) * Math.PI * 2;
        const px = center.x + Math.cos(angle) * radius;
        const py = center.y + Math.sin(angle) * radius;
        nodes.push({
            position: { x: px, y: py },
            prevPosition: { x: px, y: py },
            velocity: { x: 0, y: 0 },
            mass: 1,
            pinned: false,
        });
    }
    // Connect perimeter to center
    for (let i = 1; i <= segments; i++) {
        const dx = nodes[i].position.x - nodes[0].position.x;
        const dy = nodes[i].position.y - nodes[0].position.y;
        springs.push({
            nodeA: 0, nodeB: i,
            restLength: Math.sqrt(dx * dx + dy * dy),
            stiffness: config.stiffness,
            damping: config.damping,
        });
    }
    // Connect perimeter ring
    for (let i = 1; i <= segments; i++) {
        const j = i < segments ? i + 1 : 1;
        const dx = nodes[i].position.x - nodes[j].position.x;
        const dy = nodes[i].position.y - nodes[j].position.y;
        springs.push({
            nodeA: i, nodeB: j,
            restLength: Math.sqrt(dx * dx + dy * dy),
            stiffness: config.stiffness,
            damping: config.damping,
        });
    }
    return { nodes, springs, config };
}
function stepSoftBody(body, dt) {
    const { nodes, springs, config } = body;
    // Apply gravity
    for (const node of nodes) {
        if (node.pinned)
            continue;
        node.velocity.x += config.gravity.x * dt;
        node.velocity.y += config.gravity.y * dt;
    }
    // Solve springs (iterative)
    for (let iter = 0; iter < config.iterations; iter++) {
        for (const spring of springs) {
            const a = nodes[spring.nodeA];
            const b = nodes[spring.nodeB];
            const dx = b.position.x - a.position.x;
            const dy = b.position.y - a.position.y;
            const dist = Math.sqrt(dx * dx + dy * dy) || 0.001;
            const diff = (dist - spring.restLength) / dist;
            const clampedDiff = Math.max(-config.maxStretch + 1, Math.min(config.maxStretch - 1, diff));
            if (!a.pinned) {
                a.position.x += dx * clampedDiff * 0.5;
                a.position.y += dy * clampedDiff * 0.5;
            }
            if (!b.pinned) {
                b.position.x -= dx * clampedDiff * 0.5;
                b.position.y -= dy * clampedDiff * 0.5;
            }
        }
    }
    // Update velocities from position change (Verlet-like)
    for (const node of nodes) {
        if (node.pinned)
            continue;
        node.position.x += node.velocity.x * dt;
        node.position.y += node.velocity.y * dt;
        node.velocity.x *= (1 - config.damping * dt);
        node.velocity.y *= (1 - config.damping * dt);
    }
}
const DEFAULT_DESTRUCTION = {
    maxFragments: 12,
    minFragmentSize: 4,
    explosionForce: 200,
    debrisLifetime: 5,
    cascadeChance: 0.3,
    cascadeForceMultiplier: 0.6,
    particlesPerBreak: 20,
};
function shatterObject(obj, impactPoint, force, config) {
    const fragments = [];
    const count = Math.min(config.maxFragments, Math.max(2, Math.floor(force / 50)));
    for (let i = 0; i < count; i++) {
        const fw = Math.max(config.minFragmentSize, obj.width / count * (0.5 + Math.random()));
        const fh = Math.max(config.minFragmentSize, obj.height / count * (0.5 + Math.random()));
        const angle = (i / count) * Math.PI * 2 + (Math.random() - 0.5) * 0.5;
        const dist = Math.random() * Math.max(obj.width, obj.height) * 0.5;
        const fx = obj.position.x + Math.cos(angle) * dist;
        const fy = obj.position.y + Math.sin(angle) * dist;
        const dx = fx - impactPoint.x;
        const dy = fy - impactPoint.y;
        const d = Math.sqrt(dx * dx + dy * dy) || 1;
        const ejectForce = config.explosionForce * (0.5 + Math.random() * 0.5);
        fragments.push({
            position: { x: fx, y: fy },
            velocity: { x: (dx / d) * ejectForce, y: (dy / d) * ejectForce },
            rotation: Math.random() * Math.PI * 2,
            angularVelocity: (Math.random() - 0.5) * 10,
            width: fw,
            height: fh,
            lifetime: config.debrisLifetime * (0.5 + Math.random() * 0.5),
        });
    }
    return fragments;
}
const DEFAULT_ROPE_CONFIG = {
    segmentCount: 20,
    segmentLength: 5,
    gravity: { x: 0, y: -9.81 },
    damping: 0.99,
    iterations: 8,
    stiffness: 1,
    breakForce: 0,
    collisionRadius: 1,
};
function createRope(startPos, endPos, config) {
    const points = [];
    for (let i = 0; i <= config.segmentCount; i++) {
        const t = i / config.segmentCount;
        const x = startPos.x + (endPos.x - startPos.x) * t;
        const y = startPos.y + (endPos.y - startPos.y) * t;
        points.push({
            position: { x, y },
            prevPosition: { x, y },
            pinned: i === 0 || i === config.segmentCount,
        });
    }
    return { points, config, broken: false, breakIndex: -1 };
}
function stepRope(rope, dt) {
    const { points, config } = rope;
    // Verlet integration
    for (const p of points) {
        if (p.pinned)
            continue;
        const vx = (p.position.x - p.prevPosition.x) * config.damping;
        const vy = (p.position.y - p.prevPosition.y) * config.damping;
        p.prevPosition.x = p.position.x;
        p.prevPosition.y = p.position.y;
        p.position.x += vx + config.gravity.x * dt * dt;
        p.position.y += vy + config.gravity.y * dt * dt;
    }
    // Constraint solving
    for (let iter = 0; iter < config.iterations; iter++) {
        for (let i = 0; i < points.length - 1; i++) {
            if (rope.broken && i === rope.breakIndex)
                continue;
            const a = points[i];
            const b = points[i + 1];
            const dx = b.position.x - a.position.x;
            const dy = b.position.y - a.position.y;
            const dist = Math.sqrt(dx * dx + dy * dy) || 0.001;
            const diff = (config.segmentLength - dist) / dist * config.stiffness;
            const offsetX = dx * diff * 0.5;
            const offsetY = dy * diff * 0.5;
            if (!a.pinned) {
                a.position.x -= offsetX;
                a.position.y -= offsetY;
            }
            if (!b.pinned) {
                b.position.x += offsetX;
                b.position.y += offsetY;
            }
            // Break check
            if (config.breakForce > 0 && !rope.broken) {
                const tension = Math.abs(dist - config.segmentLength) * config.stiffness;
                if (tension > config.breakForce) {
                    rope.broken = true;
                    rope.breakIndex = i;
                }
            }
        }
    }
}
const DEFAULT_CLOTH = {
    width: 100,
    height: 100,
    resolutionX: 20,
    resolutionY: 20,
    gravity: { x: 0, y: -9.81 },
    damping: 0.98,
    stiffness: 1,
    tearThreshold: 0,
    windForce: { x: 2, y: 0 },
    windTurbulence: 0.3,
    iterations: 6,
    pinnedRow: 'top',
};
function createCloth(originX, originY, config) {
    const nodes = [];
    const links = [];
    const cols = config.resolutionX;
    const rows = config.resolutionY;
    const spacingX = config.width / (cols - 1);
    const spacingY = config.height / (rows - 1);
    for (let r = 0; r < rows; r++) {
        for (let c = 0; c < cols; c++) {
            const x = originX + c * spacingX;
            const y = originY - r * spacingY;
            const pinned = (config.pinnedRow === 'top' && r === 0) ||
                (config.pinnedRow === 'bottom' && r === rows - 1) ||
                (config.pinnedRow === 'both' && (r === 0 || r === rows - 1));
            nodes.push({ position: { x, y }, prevPosition: { x, y }, pinned });
        }
    }
    // Horizontal links
    for (let r = 0; r < rows; r++) {
        for (let c = 0; c < cols - 1; c++) {
            links.push({ a: r * cols + c, b: r * cols + c + 1, restLength: spacingX, torn: false });
        }
    }
    // Vertical links
    for (let r = 0; r < rows - 1; r++) {
        for (let c = 0; c < cols; c++) {
            links.push({ a: r * cols + c, b: (r + 1) * cols + c, restLength: spacingY, torn: false });
        }
    }
    return { nodes, links, config, cols, rows };
}
function stepCloth(cloth, dt, time) {
    const { nodes, links, config } = cloth;
    // Wind with turbulence
    const windX = config.windForce.x + Math.sin(time * 3) * config.windTurbulence;
    const windY = config.windForce.y + Math.cos(time * 2.7) * config.windTurbulence * 0.5;
    // Verlet integration
    for (const node of nodes) {
        if (node.pinned)
            continue;
        const vx = (node.position.x - node.prevPosition.x) * config.damping;
        const vy = (node.position.y - node.prevPosition.y) * config.damping;
        node.prevPosition.x = node.position.x;
        node.prevPosition.y = node.position.y;
        node.position.x += vx + (config.gravity.x + windX) * dt * dt;
        node.position.y += vy + (config.gravity.y + windY) * dt * dt;
    }
    // Constraint solving
    for (let iter = 0; iter < config.iterations; iter++) {
        for (const link of links) {
            if (link.torn)
                continue;
            const a = nodes[link.a];
            const b = nodes[link.b];
            const dx = b.position.x - a.position.x;
            const dy = b.position.y - a.position.y;
            const dist = Math.sqrt(dx * dx + dy * dy) || 0.001;
            const diff = (link.restLength - dist) / dist * config.stiffness;
            const ox = dx * diff * 0.5;
            const oy = dy * diff * 0.5;
            if (!a.pinned) {
                a.position.x -= ox;
                a.position.y -= oy;
            }
            if (!b.pinned) {
                b.position.x += ox;
                b.position.y += oy;
            }
            // Tear check
            if (config.tearThreshold > 0 && dist > link.restLength * config.tearThreshold) {
                link.torn = true;
            }
        }
    }
}

export { DEFAULT_CLOTH, DEFAULT_DESTRUCTION, DEFAULT_FLUID_CONFIG, DEFAULT_ROPE_CONFIG, DEFAULT_SOFT_BODY, createCloth, createFluidParticles, createRope, createSoftBodyCircle, shatterObject, stepCloth, stepFluid, stepRope, stepSoftBody };
//# sourceMappingURL=PhysicsAdvanced.js.map
