2011-12-10

Cylinder

Wszystko jest bardzo podobne do stożka. Bierzemy kod dla stożka i modyfikujemy go. Uwzględniamy inne równanie powierzchni, normalnej. To, że mamy dwie podstawy. To, że w lokalnym układzie współrzędnych cylinder nie jest obrócony o 180 stopni.

Równanie cylindra w lokalnym układzie współrzędnych:

$X^2+Z^2=R^2$

W naszym przypadku skalujemy stożek odpowiednio tak by H=1 i R = 1.

Podstawiając do $X^2+Z^2=1$ parametryczne równanie linii:

$P = START + DIR \cdot t$
$x = x_s + x_d t$
$y = y_s + y_d t$
$z = z_s + z_d t$

,otrzymujemy równanie kwadratowe $At^2 + Bt + C = 0$ o współczynnikach:

$A = x_d^2 + z_d^2 = DIR \cdot DIR - y_d^2 = 1 - y_d^2$
$B = 2(x_d x_s + z_d z_s) = 2 \cdot START \cdot DIR - 2 y_d y_s$
$C = x_s^2 + z_s^2 - 1 = START \cdot START - y_s^2 - 1$

Jego rozwiązanie wyznaczy nam dystans do powierzchni bocznej walca. Interesuje nas minimalne rozwiązanie większe od Constants.MINIMAL_DISTANT dla którego współrzędna Y punktu intersekcji spełnia warunek $Constants.MINIMAL\_DISTANT < Y < 1$.

Normalna walca wyliczona z gradientu ma postać: $[x, 0, z]$

Pełny kod klasy cylindra:
public class CylinderObject : RenderableObject
{
    [YAXNode("Radius")]
    private double m_radius;

    [YAXNode("Height")]
    private double m_height;

    public CylinderObject(Vector3 a_right, Vector3 a_up) :
        base(a_right, a_up)
    {
        Update(UpdateFlags.All);
        Closed = true;
    }

    public Material BottomMaterial
    {
        get
        {
            return Materials[1];
        }
        set
        {
            Materials[1] = value;
        }
    }

    public Material TopMaterial
    {
        get
        {
            return Materials[2];
        }
        set
        {
            Materials[2] = value;
        }
    }

    public double Radius
    {
        get
        {
            return m_radius;
        }
        set
        {
            m_radius = value;
            Update(UpdateFlags.BoundBox | UpdateFlags.Matrices);
        }
    }

    public double Height
    {
        get
        {
            return m_height;
        }
        set
        {
            m_height = value;
            Update(UpdateFlags.BoundBox | UpdateFlags.Matrices);
        }
    }

    protected override Matrix4 GetLocalToWorldMatrix()
    {
        return Matrix4.CreateTranslation(Pos) *
                new Matrix4(Right, Up, Forward) *
                Matrix4.CreateTranslation(0, -Height / 2, 0) *
                Matrix4.CreateScale(Scale.Scale(LocalScale));
    }

    public override Intersection GetIntersection(Intersection 
        a_source_ray_intersection, Ray a_ray)
    {
        bool back_hit_expected = false;

        if ((a_source_ray_intersection != null) && 
            a_source_ray_intersection.SceneObject == this)
        {
            Debug.Assert((a_ray.RaySurfaceSide == RaySurfaceSide.RefractedSide) ||
                            (a_ray.RaySurfaceSide == RaySurfaceSide.ReflectedSide));
            if (a_source_ray_intersection.BackHit ^ (a_ray.RaySurfaceSide == 
                RaySurfaceSide.ReflectedSide))
            {
                return Scene.NoIntersection;
            }
            else
            {
                if (OneSide)
                    return Scene.NoIntersection;
                else
                    back_hit_expected = true;
            }
        }

        Vector3 local_dir = default(Vector3);
        Vector3 local_start = default(Vector3);

        if (!TransformToLocalToBoundBox(a_ray, ref local_dir, ref local_start))
            return Scene.NoIntersection;

        Vector3 local_pos = default(Vector3);
        double dist = Double.PositiveInfinity;

        double top_dist = (1 - local_start.Y) / local_dir.Y;
        bool top_hit = false;
        bool bottom_hit = false;

        if (top_dist > Constants.MINIMAL_DISTANT)
        {
            local_pos = local_start + local_dir * top_dist;

            if (local_pos.X * local_pos.X + local_pos.Z * local_pos.Z < 1)
            {
                top_hit = true;
                dist = top_dist;
            }
        }

        double bottom_dist = -local_start.Y / local_dir.Y;

        if ((bottom_dist > Constants.MINIMAL_DISTANT) && (bottom_dist < dist))
        {
            Vector3 bottom_pos = local_start + local_dir * bottom_dist;

            if (bottom_pos.X * bottom_pos.X + bottom_pos.Z * bottom_pos.Z < 1)
            {
                top_hit = false;
                bottom_hit = true;
                dist = bottom_dist;
                local_pos = bottom_pos;
            }
        }

        PolynomialSolverAlgebraic solver = new PolynomialSolverAlgebraic(
            new Polynomial(
                1 - local_dir.Y * local_dir.Y, 
                2 * (local_dir.X * local_start.X + local_dir.Z * local_start.Z),
                local_start.X * local_start.X + local_start.Z * local_start.Z - 1));

        double dist1 = default(Double), dist2 = default(Double);
        int roots = solver.SolveAlgebraicQuadratic(ref dist1, ref dist2);

        if ((roots >= 1) && (dist1 > Constants.MINIMAL_DISTANT) && (dist1 < dist))
        {
            Vector3 local_pos2 = local_start + local_dir * dist1;

            if ((local_pos2.Y >= Constants.MINIMAL_DISTANT) && (local_pos2.Y < 1))
            {
                top_hit = false;
                bottom_hit = false;
                dist = dist1;
                local_pos = local_pos2;
            }
        }

        if ((roots == 2) && (dist2 > Constants.MINIMAL_DISTANT) && (dist2 < dist))
        {
            Vector3 local_pos2 = local_start + local_dir * dist2;

            if ((local_pos2.Y >= Constants.MINIMAL_DISTANT) && (local_pos2.Y < 1))
            {
                top_hit = false;
                bottom_hit = false;
                dist = dist2;
                local_pos = local_pos2;
            }
        }

        if (dist == Double.PositiveInfinity)
            return Scene.NoIntersection;

        bool back_hit;

        if (back_hit_expected)
            back_hit = true;
        else
        {
            if (bottom_hit)
                back_hit = local_start.Y > 0;
            else if (top_hit)
                back_hit = local_start.Y < 1;
            else
            {
                back_hit = local_start.X * local_start.X + local_start.Z * 
                           local_start.Z < 1;
            }
        }

        if (back_hit && OneSide)
            return Scene.NoIntersection;

        Vector3 world_pos = LocalToWorld * local_pos;
        dist = (world_pos - a_ray.Start).Length;

        return new Intersection()
        {
            PrevIntersection = a_source_ray_intersection,
            SceneObject = this,
            SourceRay = a_ray,
            Dist = dist,
            Scene = Scene,
            BackHit = back_hit,
            Pos = world_pos,
            LocalPos = local_pos,
            Material = top_hit ? TopMaterial : bottom_hit ? BottomMaterial : 
                DefaultMaterial
        };
    }

    public override Vector3 GetNormal(Intersection a_intersection)
    {
        if (a_intersection.LocalPos.Y.AlmostEquals(1))
        {
            if (a_intersection.BackHit)
                return -Up;
            else
                return Up;
        }
        else if (a_intersection.LocalPos.Y.AlmostEquals(0))
        {
            if (a_intersection.BackHit)
                return Up;
            else
                return -Up;
        }
        else
        {
            if (a_intersection.BackHit)
            {
                return (LocalToWorldNormal *
                    new Vector3(-a_intersection.LocalPos.X, 0, 
                        -a_intersection.LocalPos.Z)).Normalized;
            }
            else
            {
                return (LocalToWorldNormal *
                    new Vector3(a_intersection.LocalPos.X, 0, 
                        a_intersection.LocalPos.Z)).Normalized;
            }
        }
    }

    public override string ToString()
    {
        return String.Format("Cone: {0}", Name);
    }

    public override Vector3 GetUVW(Intersection a_intersection)
    {
        Vector3 uvw = base.GetUVW(a_intersection);

        return new Vector3(uvw.X / 2 + 0.5, 1 - uvw.Y, uvw.Z / 2 + 0.5);
    }

    public override Vector2 GetUV(Intersection a_intersection)
    {
        if (a_intersection.UVW.Y.AlmostEquals(0) || 
            a_intersection.UVW.Y.AlmostEquals(1))
        {
            return new Vector2(a_intersection.UVW.X, a_intersection.UVW.Z);
        }
        else
        {
            double u = Math.Atan2(a_intersection.LocalPos.X, 
                a_intersection.LocalPos.Z) / 2 / Constants.PI;

            if (a_intersection.LocalPos.X < 0)
                u = u + 1;

            return new Vector2(u, a_intersection.UVW.Y);
        }
    }

    public override void GetTangents(Intersection a_intersection,
        out Vector3 a_tangent_x, out Vector3 a_tangent_y)
    {
        if ((a_intersection.LocalPos.Y.AlmostEquals(1)) ||
            (a_intersection.LocalPos.Y.AlmostEquals(0)))
        {
            a_tangent_x = Right;
            a_tangent_y = Forward;
        }
        else
        {
            if (a_intersection.BackHit)
            {
                a_tangent_x = Vector3.CrossProduct(a_intersection.Normal, 
                    Up).Normalized;
                a_tangent_y = -Up;
            }
            else
            {
                a_tangent_x = Vector3.CrossProduct(Up, 
                    a_intersection.Normal).Normalized;
                a_tangent_y = Up;
            }
        }
    }

    protected override Vector3 LocalScale
    {
        get
        {
            return new Vector3(Radius, Height, Radius);
        }
    }

    protected override AABB GetLocalBoundBox()
    {
        return new AABB(new Vector3(-Radius, 0, -Radius),
                        new Vector3(Radius, Height, Radius));
    }

    public override void ScaleAbsolute(double a_scale)
    {
        Radius *= a_scale;
        Height *= a_scale;

        base.ScaleAbsolute(a_scale);
    }
}

Brak komentarzy:

Prześlij komentarz