package org.sunflow.core.primitive;

import org.sunflow.SunflowAPI;
import org.sunflow.core.IntersectionState;
import org.sunflow.core.ParameterList;
import org.sunflow.core.PrimitiveList;
import org.sunflow.core.Ray;
import org.sunflow.core.ShadingState;
import org.sunflow.core.ParameterList.FloatParameter;
import org.sunflow.math.BoundingBox;
import org.sunflow.math.Matrix4;
import org.sunflow.math.OrthoNormalBasis;
import org.sunflow.math.Point3;
import org.sunflow.math.Solvers;
import org.sunflow.math.Vector3;

public class ParticleSurface implements PrimitiveList {
    private float[] particles;
    private float r, r2;
    private int n;

    public ParticleSurface() {
        particles = null;
        r = r2 = 1;
        n = 0;
    }

    public int getNumPrimitives() {
        return n;
    }

    public float getPrimitiveBound(int primID, int i) {
        float c = particles[primID * 3 + (i >>> 1)];
        return (i & 1) == 0 ? c - r : c + r;
    }

    public BoundingBox getWorldBounds(Matrix4 o2w) {
        BoundingBox bounds = new BoundingBox();
        for (int i = 0, i3 = 0; i < n; i++, i3 += 3)
            bounds.include(particles[i3], particles[i3 + 1], particles[i3 + 2]);
        bounds.include(bounds.getMinimum().x - r, bounds.getMinimum().y - r, bounds.getMinimum().z - r);
        bounds.include(bounds.getMaximum().x + r, bounds.getMaximum().y + r, bounds.getMaximum().z + r);
        return o2w == null ? bounds : o2w.transform(bounds);
    }

    public void intersectPrimitive(Ray r, int primID, IntersectionState state) {
        int i3 = primID * 3;
        float ocx = r.ox - particles[i3 + 0];
        float ocy = r.oy - particles[i3 + 1];
        float ocz = r.oz - particles[i3 + 2];
        float qa = r.dx * r.dx + r.dy * r.dy + r.dz * r.dz;
        float qb = 2 * ((r.dx * ocx) + (r.dy * ocy) + (r.dz * ocz));
        float qc = ((ocx * ocx) + (ocy * ocy) + (ocz * ocz)) - r2;
        double[] t = Solvers.solveQuadric(qa, qb, qc);
        if (t != null) {
            // early rejection
            if (t[0] >= r.getMax() || t[1] <= r.getMin())
                return;
            if (t[0] > r.getMin())
                r.setMax((float) t[0]);
            else
                r.setMax((float) t[1]);
            state.setIntersection(primID);
        }
    }

    public void prepareShadingState(ShadingState state) {
        state.init();
        state.getRay().getPoint(state.getPoint());
        Point3 localPoint = state.transformWorldToObject(state.getPoint());

        localPoint.x -= particles[3 * state.getPrimitiveID() + 0];
        localPoint.y -= particles[3 * state.getPrimitiveID() + 1];
        localPoint.z -= particles[3 * state.getPrimitiveID() + 2];

        state.getNormal().set(localPoint.x, localPoint.y, localPoint.z);
        state.getNormal().normalize();

        state.setShader(state.getInstance().getShader(0));
        state.setModifier(state.getInstance().getModifier(0));
        // into object space
        Vector3 worldNormal = state.transformNormalObjectToWorld(state.getNormal());
        state.getNormal().set(worldNormal);
        state.getNormal().normalize();
        state.getGeoNormal().set(state.getNormal());
        state.setBasis(OrthoNormalBasis.makeFromW(state.getNormal()));
    }

    public boolean update(ParameterList pl, SunflowAPI api) {
        FloatParameter p = pl.getPointArray("particles");
        if (p != null)
            particles = p.data;
        r = pl.getFloat("radius", r);
        r2 = r * r;
        n = pl.getInt("num", n);
        return particles != null && n <= (particles.length / 3);
    }

    public PrimitiveList getBakingPrimitives() {
        return null;
    }
}