Processing math: 100%

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:

X2+Z2=R2

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

Podstawiając do X2+Z2=1 parametryczne równanie linii:

P=START+DIRt
x=xs+xdt
y=ys+ydt
z=zs+zdt

,otrzymujemy równanie kwadratowe At2+Bt+C=0 o współczynnikach:

A=x2d+z2d=DIRDIRy2d=1y2d
B=2(xdxs+zdzs)=2STARTDIR2ydys
C=x2s+z2s1=STARTSTARTy2s1

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