$X^2+Y^2=R^2$
Uzależnijmy jego promień od wysokości Y:
$X^2+Z^2=Y^2$
W ten sposób otrzymujemy równanie powierzchni, której częścią jest stożek. Jego środek znajduje się w środku układu współrzędnych. Jego podstawa niech znajduje się po dodatniej osi Y w odległości H od środka układu współrzędnych.
Dokonując transformacji promienia do lokalnego układu współrzędnych w którym leży nasz stożek skalujemy go także odpowiednio tak by $R=1$ i $H=1$.
Środek naszego stożka znajduje się w połowie jego wysokości.
Transformacja z lokalnego układu współrzędnych do układu współrzędnych świata ma postać:
protected override Vector3 LocalScale { get { return new Vector3(Radius, Height, Radius); } } protected override Matrix4 GetLocalToWorldMatrix() { return Matrix4.CreateTranslation(Pos) * new Matrix4(Right, Up, Forward) * Matrix4.CreateTranslation(0, Height / 2, 0) * Matrix4.CreateRotateX(180) * Matrix4.CreateScale(Scale.Scale(LocalScale)); }
W kolejności najpierw skalujemy stożek, później obracamy go o 180 stopni, tak by czubek znajdował się po dodatniej stronie osi Y. Następnie przesuwamy go tak by jego podstawa znajdowała się na osi XZ. I na końcu transformujemy go do układu współrzędnych świata (rotacja + translacja). Dzięki temu do obliczeń mamy stożek w postaci najprostszej i najszybszej do obliczeń (czubek w środku układu współrzędnych), zaś w układzie świata w postaci intuicyjnej leżącego podstawą na płaszczyźnie XZ.
Podstawiając do równania powierzchni tworzącej parametryczne równanie linii (DIR to wektor znormalizowany):
$P = START + DIR \cdot t$
$x = x_s + x_d t$
$y = y_s + y_d t$
$z = z_s + z_d t$
,otrzymyjemy równanie kwadratowe $At^2 + Bt + C = 0$ o współczynnikach:
$A = x_d^2 + z_d^2 - y_d^2 = DIR \cdot DIR - 2 y_d^2 = 1 - 2 y_d^2$
$B = 2(x_d x_s + z_d z_s - y_d y_s) = 2 \cdot START \cdot DIR - 4 y_d y_s$
$C = x_s^2 + z_s^2 - y_s^2 = START \cdot START - 2 y_s^2$
Jego rozwiązanie wyznaczy nam dystans do boków tworzących stożek. Interesuje nas minimalne rozwiąnie większe od
Constants.MINIMAL_DISTANT
dla którego współrzędna Y punktu intersekcji spełnia warunek $Constants.MINIMAL\_DISTANT < Y < 1$.Następnie znajdujemy odległość do płaszczyzny tworzącej stożek korzystając z wzoru na intersekcje promienia z płaszczyzną:
$D = \displaystyle\frac{1-START.Y}{DIR.Y}$
Jeśli odległość ta jest miejsza od poprzednio wyznaczonego dystansu do tworzących stożek możemy wyznaczyć punkt intersekcji i sprawdzić czy znajduje się on w promieniu podstawy:
$POS.X^2 + POS.Z^2 < 1$
Punkt intersekcji został wyznaczony. Teraz trzeba sprawdzić czy uderzenie nastąpiło od zewnątrz czy wewnątrz.
Ponieważ stożek jest bryłą zamkniętą i wypukłą, możemy mieć pewność że promień odbity od wnętrza uderzy we wnętrze stożka. Promień odbity od zewnątrz stożka na pewno w niego nie uderzy. Promień refrakcyjny wychodzący na zewnątrz także w stożek nie uderzy. Promień refrakcyjny wchodzący do stożka na pewno w niego uderzy od środka.
W pozostałych przypadkach, gdy punktem wyjścia nie jest poprzednie uderzenie w stożek musimy liczyć. Jeśli uderzenie nastąpiło w podstawę stożka to wystarczy sprawdzić wartość współrzędnej START.Y. Jeśli uderzenie nastąpiło w bok stożka to uderzenie nastąpiło wewnątrz jeśli $POS.X^2 + POS.Z^2 < POS.Z^2$. Tylko pod warunkiem, że punkt startu promienia znajdował się w płaszczyźnie stożka: $0 \le START.y \le 1$
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; double dist = Double.PositiveInfinity; PolynomialSolverAlgebraic solver = new PolynomialSolverAlgebraic( new Polynomial( 1 - 2 * local_dir.Y * local_dir.Y, 2 * (local_dir.X * local_start.X + local_dir.Z * local_start.Z - local_dir.Y * local_start.Y), local_start.X * local_start.X + local_start.Z * local_start.Z - local_start.Y * local_start.Y)); double dist1 = default(Double), dist2 = default(Double); int roots = solver.SolveAlgebraicQuadratic(ref dist1, ref dist2); if (roots == 0) return Scene.NoIntersection; if ((roots >= 1) && (dist1 > Constants.MINIMAL_DISTANT)) { double ly = local_start.Y + local_dir.Y * dist1; if ((ly >= Constants.MINIMAL_DISTANT) && (ly < 1)) dist = dist1; } if ((roots == 2) && (dist2 > Constants.MINIMAL_DISTANT) && (dist2 < dist)) { double ly = local_start.Y + local_dir.Y * dist2; if ((ly >= Constants.MINIMAL_DISTANT) && (ly < 1)) dist = dist2; } double base_dist = (1 - local_start.Y) / local_dir.Y; bool base_hit = false; if ((base_dist > Constants.MINIMAL_DISTANT) && (base_dist < dist)) { double lx = local_start.X + local_dir.X * base_dist; double lz = local_start.Z + local_dir.Z * base_dist; if (lx * lx + lz * lz < 1) { base_hit = true; dist = base_dist; } } if (dist == Double.PositiveInfinity) return Scene.NoIntersection; bool back_hit = false; if (back_hit_expected) back_hit = true; else { if (base_hit) back_hit = local_start.Y < 1; else { if ((local_start.Y > 0) && (local_start.Y < 1)) { back_hit = local_start.X * local_start.X + local_start.Z * local_start.Z < local_start.Y * local_start.Y; } } } if (back_hit && OneSide) return Scene.NoIntersection; Vector3 local_pos = local_start + local_dir * dist; 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 = base_hit ? BaseMaterial : DefaultMaterial }; }
Normalną w miejscu uderzenia wyznaczymy z gradientu funkcji:
$f(x,y,z)=X^2+Z^2-Y^2$
$\nabla f=[2X,2Z,-2Y]=[X,Z,-Y]$
Jeśli uderzenie nastąpiło od środka wartość normalnej bierzemy ze znakiem ujemnym.
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.BackHit) { return (LocalToWorldNormal * new Vector3(-a_intersection.LocalPos.X, a_intersection.LocalPos.Y, -a_intersection.LocalPos.Z)).Normalized; } else { return (LocalToWorldNormal * new Vector3(a_intersection.LocalPos.X, -a_intersection.LocalPos.Y, a_intersection.LocalPos.Z)).Normalized; } } }
Kod na wyliczenie współrzędnych UVW (0..1):
public override Vector3 GetUVW(Intersection a_intersection) { Vector3 uvw = base.GetUVW(a_intersection); return new Vector3(uvw.X / 2 + 0.5, uvw.Y, -uvw.Z / 2 + 0.5); }
Współrzędna Y jest odwracana tak by góra tesktury (Y=0) znajdowała się na czubku stożka.
Dla UV jeśli uderzenie nastąpiło w podstawę stożka mapujemy współrzędną wprost. Dla boku stożka nawijamy na niego teksturę. Tutaj kod jest podobny do kodu dla sfery.
public override Vector2 GetUV(Intersection a_intersection) { if (a_intersection.UVW.Y.AlmostEquals(0)) 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); } }Tekstura wokół osi stożka nawija się od dodatniej osi Z w prawo. No i pozostało nam wyliczanie stycznych do powierzchni potrzebnych do mapowania wypukłości.
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_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 = Vector3.CrossProduct(a_intersection.Normal, a_tangent_x).Normalized; } else { a_tangent_x = Vector3.CrossProduct(Up, a_intersection.Normal).Normalized; a_tangent_y = Vector3.CrossProduct(a_tangent_x, a_intersection.Normal).Normalized; } } }
Brak komentarzy:
Prześlij komentarz