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