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