Files
mercury/benchmarks/progs/icfp2000/trans.m
Julien Fischer 84e2536618 Fix minor compilation problems in extras and benchmarks.
These are due to:

- differences between module and file names.
- redundant imports
- the recent change from math.domain_error -> exception.domain_error.

benchmarks/*/*.m:
extras/*/*.m:
    As above.
2020-05-15 16:00:41 +10:00

1016 lines
36 KiB
Mathematica

% ---------------------------------------------------------------------------- %
% trans.m
% Ralph Becket <rbeck@microsoft.com>
% Sun Aug 27 11:22:32 2000
% vim: ts=4 sw=4 et tw=0 wm=0 ff=unix
%
% This module computes the composition of the various transformations
% and their inverses. Also handles intersection between lines and
% transformed geometric primitives.
%
% Specialised composition operators are supplied for each transformation.
%
% There are six transformation matrices and their inverses:
%
% Mt(dx,dy,dz) = ( 1 0 0 dx) Mt-1(dx,dy,dz) = ( 1 0 0 -dx)
% ( 0 1 0 dy) ( 0 1 0 -dy)
% ( 0 0 1 dz) ( 0 0 1 -dz)
% ( 0 0 0 1) ( 0 0 0 1)
%
% Ms(sx,sy,sz) = ( sx 0 0 0) Ms-1(sx,sy,sz) = (1/sx 0 0 0)
% ( 0 sy 0 0) ( 0 1/sy 0 0)
% ( 0 0 sz 0) ( 0 0 1/sz 0)
% ( 0 0 0 1) ( 0 0 0 1)
%
% Mu(s) = ( s 0 0 0) Mu-1(s) = ( 1/s 0 0 0)
% ( 0 s 0 0) ( 0 1/s 0 0)
% ( 0 0 s 0) ( 0 0 1/s 0)
% ( 0 0 0 1) ( 0 0 0 1)
%
% Mrx(a) = ( 1 0 0 0) Mrx-1(a) = ( 1 0 0 0)
% ( 0 ca -sa 0) ( 0 ca sa 0)
% ( 0 sa ca 0) ( 0 -sa ca 0)
% ( 0 0 0 1) ( 0 0 0 1)
%
% Mry(a) = ( ca 0 sa 0) Mry-1(a) = ( ca 0 -sa 0)
% ( 0 1 0 0) ( 0 1 0 0)
% ( -sa 0 ca 0) ( sa 0 ca 0)
% ( 0 0 0 1) ( 0 0 0 1)
%
% Mrz(a) = ( ca -sa 0 0) Mrz-1(a) = ( ca sa 0 0)
% ( sa ca 0 0) ( -sa ca 0 0)
% ( 0 0 1 0) ( 0 0 1 0)
% ( 0 0 0 1) ( 0 0 0 1)
%
% Where ca = cos(a) and sa = sin(a).
%
% NOTE: any zero scaling factors will result in an exception being raised.
%
% ---------------------------------------------------------------------------- %
:- module trans.
:- interface.
:- import_module float, io, list.
:- import_module vector, eval.
:- type surface_coordinates
---> surface_coordinates(
face :: int,
surface_u :: real,
surface_v :: real
).
% The type of transformation matrices (contains both the
% forward transformation M and its inverse W).
%
:- type trans.
% Apply a transformation matrix to points, vectors and normals.
%
% NOTE: when transforming a normal, one has to use
% the transformation matrices the other way around -
% that is given an object -> world space transformation M
% and its inverse W, world space normals have to be
% transformed into object space using Mt, not W (and vice
% versa). (Mt is the transpose of M). This does work,
% surprisingly, and handles the case where M includes
% arbitrary scaling which does not preserve angles.
%
:- func point_to_object_space(trans, point) = point.
:- func vector_to_object_space(trans, point) = point.
:- func normal_to_object_space(trans, point) = point.
:- func point_to_world_space(trans, point) = point.
:- func vector_to_world_space(trans, point) = point.
:- func normal_to_world_space(trans, point) = point.
% The identity transformation (i.e. the unit matrix - start here!)
%
:- func identity = trans.
% Composition of the various transformations and their matrices.
%
% compose_transformation(Arg, ..., Transformation0) = Transformation.
% Args = dx, dy, dz
%
:- func compose_translate(float, float, float, trans) = trans.
% Args = sx, sy, sz
%
:- func compose_scale(float, float, float, trans) = trans.
% Arg = s
%
:- func compose_uscale(float, trans) = trans.
% Arg = angle (degrees)
%
:- func compose_rotatex(float, trans) = trans.
% Arg = angle (degrees)
%
:- func compose_rotatey(float, trans) = trans.
% Arg = angle (degrees)
%
:- func compose_rotatez(float, trans) = trans.
:- type intersection
---> intersection(
object_id :: object_id,
intersection_point :: point,
surface_normal :: vector,
surface_coordinates :: surface_coordinates,
surface :: surface
).
:- type intersections == list(intersection).
% Given a line (a point P and a direction vector D) decide whether it
% intersects with a particular geometric primitive with id Id under a given
% transformation M (the inverse transformation W must also be supplied).
% If the line does intersect, calculate the point of intersection POI,
% the texture coordinates TC, and the surface normal for the primitive at
% that point. N is the unit surface normal at the point of intersection.
%
% intersects_shape(Id, M, W, P, D, POI, TC, N).
% intersects_shape(MandW, P, D, POI, TC, N).
%
:- pred intersects_plane(object_id, trans, point, vector, surface,
intersections).
:- mode intersects_plane(in, in, in, in, in, out) is det.
:- pred intersects_sphere(object_id, trans, point, vector, surface,
intersections).
:- mode intersects_sphere(in, in, in, in, in, out) is det.
:- pred intersects_cube(object_id, trans, point, vector, surface,
intersections).
:- mode intersects_cube(in, in, in, in, in, out) is det.
:- pred intersects_cylinder(object_id, trans, point, vector, surface,
intersections).
:- mode intersects_cylinder(in, in, in, in, in, out) is det.
:- pred intersects_cone(object_id, trans, point, vector, surface,
intersections).
:- mode intersects_cone(in, in, in, in, in, out) is det.
% Decide whether we're inside a given object.
%
:- pred inside_sphere(point, trans).
:- mode inside_sphere(in, in) is semidet.
:- pred inside_plane(point, trans).
:- mode inside_plane(in, in) is semidet.
:- pred inside_cube(point, trans).
:- mode inside_cube(in, in) is semidet.
:- pred inside_cylinder(point, trans).
:- mode inside_cylinder(in, in) is semidet.
:- pred inside_cone(point, trans).
:- mode inside_cone(in, in) is semidet.
% Print out a transformation.
%
:- pred show_trans(trans, io__state, io__state).
:- mode show_trans(in, di, uo) is det.
% ---------------------------------------------------------------------------- %
% ---------------------------------------------------------------------------- %
:- implementation.
:- import_module exception, string, math, std_util.
% NOTE 1: points and vectors are represented using the point/3 type
% defined in eval.m. For the purposes, a point (x, y, z) is
% treated as a column vector (x, y, z, 1) while a vector (x, y, z)
% is treated as a column vector (x, y, z, 0), the only difference
% being that vectors are invariant under translation whereas points
% are not.
%
% NOTE 2: when computing a composition M, we also compute its
% inverse, W. The idea is to transform world space into object
% space via W, find the point of intersection, and transform it
% back into world space via M. Hence, if M = M3.M2.M1.I then
% W = I.W1.W2.W3.
%
% NOTE 3: it turns out that the last row of a matrix is always
% (0 0 0 1) and so we don't need to represent it.
%
:- type trans
---> trans(matrix, matrix). % ObjToWorldSpace - WorldToObjSpace.
:- type matrix
---> matrix(
float, float, float, float,
float, float, float, float,
float, float, float, float
% 0, 0, 0, 1
).
:- func unit_matrix = matrix.
:- func transform_point(matrix, point) = point.
:- func transform_vector(matrix, vector) = vector.
:- func transform_normal(matrix, vector) = vector.
:- func degrees_to_radians(float) = float.
% ---------------------------------------------------------------------------- %
transform_point(M, P0) = P :-
M = matrix(M11, M12, M13, M14,
M21, M22, M23, M24,
M31, M32, M33, M34),
P0 = point(X, Y, Z),
P = point(M11*X + M12*Y + M13*Z + M14,
M21*X + M22*Y + M23*Z + M24,
M31*X + M32*Y + M33*Z + M34).
% ---------------------------------------------------------------------------- %
transform_vector(M, V0) = V :-
M = matrix(M11, M12, M13, _M14,
M21, M22, M23, _M24,
M31, M32, M33, _M34),
V0 = point(X, Y, Z),
V = point(M11*X + M12*Y + M13*Z,
M21*X + M22*Y + M23*Z,
M31*X + M32*Y + M33*Z).
% ---------------------------------------------------------------------------- %
% Here, we multiply by the transpose of the matrix.
%
transform_normal(M, N0) = N :-
M = matrix(M11, M12, M13, _M14,
M21, M22, M23, _M24,
M31, M32, M33, _M34),
N0 = point(X, Y, Z),
N = point(M11*X + M21*Y + M31*Z,
M12*X + M22*Y + M32*Z,
M13*X + M23*Y + M33*Z).
% ---------------------------------------------------------------------------- %
point_to_object_space(trans(_M, W), P) = transform_point(W, P).
vector_to_object_space(trans(_M, W), P) = transform_vector(W, P).
normal_to_object_space(trans(M, _W), P) = transform_normal(M, P).
% ---------------------------------------------------------------------------- %
point_to_world_space(trans(M, _W), P) = transform_point(M, P).
vector_to_world_space(trans(M, _W), P) = transform_vector(M, P).
normal_to_world_space(trans(_M, W), P) = transform_normal(W, P).
% ---------------------------------------------------------------------------- %
identity = trans(I, I) :-
I = unit_matrix.
% ---------------------------------------------------------------------------- %
unit_matrix = matrix(1.0, 0.0, 0.0, 0.0,
0.0, 1.0, 0.0, 0.0,
0.0, 0.0, 1.0, 0.0).
% ---------------------------------------------------------------------------- %
compose_translate(Dx, Dy, Dz, trans(M0, W0)) = trans(M, W) :-
M0 = matrix(M11, M12, M13, M14,
M21, M22, M23, M24,
M31, M32, M33, M34),
M = matrix(M11, M12, M13, M14 + Dx,
M21, M22, M23, M24 + Dy,
M31, M32, M33, M34 + Dz),
W0 = matrix(W11, W12, W13, W14,
W21, W22, W23, W24,
W31, W32, W33, W34),
W = matrix(W11, W12, W13, W14 - Dx * W11 - Dy * W12 - Dz * W13,
W21, W22, W23, W24 - Dx * W21 - Dy * W22 - Dz * W23,
W31, W32, W33, W34 - Dx * W31 - Dy * W32 - Dz * W33).
% ---------------------------------------------------------------------------- %
compose_scale(Sx, Sy, Sz, trans(M0, W0)) = trans(M, W) :-
( if ( Sx = 0.0 ; Sy = 0.0 ; Sz = 0.0 ) then
throw("trans: compose_scale/7: zero scaling factor")
else
M0 = matrix(M11, M12, M13, M14,
M21, M22, M23, M24,
M31, M32, M33, M34),
M = matrix(Sx * M11, Sx * M12, Sx * M13, Sx * M14,
Sy * M21, Sy * M22, Sy * M23, Sy * M24,
Sz * M31, Sz * M32, Sz * M33, Sz * M34),
W0 = matrix(W11, W12, W13, W14,
W21, W22, W23, W24,
W31, W32, W33, W34),
W = matrix(W11 / Sx, W12 / Sy, W13 / Sz, W14,
W21 / Sx, W22 / Sy, W23 / Sz, W24,
W31 / Sx, W32 / Sy, W33 / Sz, W34)
).
% ---------------------------------------------------------------------------- %
compose_uscale(S, trans(M0, W0)) = trans(M, W) :-
( if S = 0.0 then
throw("trans: compose_uscale/7: zero scaling factor")
else
M0 = matrix(M11, M12, M13, M14,
M21, M22, M23, M24,
M31, M32, M33, M34),
M = matrix(S * M11, S * M12, S * M13, S * M14,
S * M21, S * M22, S * M23, S * M24,
S * M31, S * M32, S * M33, S * M34),
W0 = matrix(W11, W12, W13, W14,
W21, W22, W23, W24,
W31, W32, W33, W34),
W = matrix(W11 / S, W12 / S, W13 / S, W14,
W21 / S, W22 / S, W23 / S, W24,
W31 / S, W32 / S, W33 / S, W34)
).
% ---------------------------------------------------------------------------- %
compose_rotatex(Rx, trans(M0, W0)) = trans(M, W) :-
RRx = degrees_to_radians(Rx),
C = cos(RRx),
S = sin(RRx),
M0 = matrix(M11, M12, M13, M14,
M21, M22, M23, M24,
M31, M32, M33, M34),
M = matrix(M11 , M12 , M13 , M14 ,
C*M21 - S*M31, C*M22 - S*M32, C*M23 - S*M33, C*M24 - S*M34,
S*M21 + C*M31, S*M22 + C*M32, S*M23 + C*M33, S*M24 + C*M34),
W0 = matrix(W11, W12, W13, W14,
W21, W22, W23, W24,
W31, W32, W33, W34),
W = matrix(W11, C*W12 - S*W13, S*W12 + C*W13, W14,
W21, C*W22 - S*W23, S*W22 + C*W23, W24,
W31, C*W32 - S*W33, S*W32 + C*W33, W34).
% ---------------------------------------------------------------------------- %
compose_rotatey(Ry, trans(M0, W0)) = trans(M, W) :-
RRy = degrees_to_radians(Ry),
C = cos(RRy),
S = sin(RRy),
M0 = matrix(M11, M12, M13, M14,
M21, M22, M23, M24,
M31, M32, M33, M34),
M = matrix(C*M11 + S*M31, C*M12 + S*M32, C*M13 + S*M33, C*M14 + S*M34,
M21 , M22 , M23 , M24 ,
C*M31 - S*M11, C*M32 - S*M12, C*M33 - S*M13, C*M34 - S*M14),
W0 = matrix(W11, W12, W13, W14,
W21, W22, W23, W24,
W31, W32, W33, W34),
W = matrix(C*W11 + S*W13, W12, -S*W11 + C*W13, W14,
C*W21 + S*W23, W22, -S*W21 + C*W23, W24,
C*W31 + S*W33, W32, -S*W31 + C*W33, W34).
% ---------------------------------------------------------------------------- %
compose_rotatez(Rz, trans(M0, W0)) = trans(M, W) :-
RRz = degrees_to_radians(Rz),
C = cos(RRz),
S = sin(RRz),
M0 = matrix(M11, M12, M13, M14,
M21, M22, M23, M24,
M31, M32, M33, M34),
M = matrix(C*M11 - S*M21, C*M12 - S*M22, C*M13 - S*M23, C*M14 - S*M24,
S*M11 + C*M21, S*M12 + C*M22, S*M13 + C*M23, S*M14 + C*M24,
M31 , M32 , M33 , M34 ),
W0 = matrix(W11, W12, W13, W14,
W21, W22, W23, W24,
W31, W32, W33, W34),
W = matrix(-S*W12 + C*W11, S*W11 + C*W12, W13, W14,
-S*W22 + C*W21, S*W21 + C*W22, W23, W24,
-S*W32 + C*W31, S*W31 + C*W32, W33, W34).
% ---------------------------------------------------------------------------- %
degrees_to_radians(Theta) = Theta * pi / 180.0.
% ---------------------------------------------------------------------------- %
show_trans(trans(M, W)) -->
{ M = matrix(M11, M12, M13, M14,
M21, M22, M23, M24,
M31, M32, M33, M34) },
{ W = matrix(W11, W12, W13, W14,
W21, W22, W23, W24,
W31, W32, W33, W34) },
io__format("object -> world space world -> object space\n", []),
io__format("(%4.1f %4.1f %4.1f %4.1f) ", [f(M11),f(M12),f(M13),f(M14)]),
io__format("(%4.1f %4.1f %4.1f %4.1f)\n", [f(W11),f(W12),f(W13),f(W14)]),
io__format("(%4.1f %4.1f %4.1f %4.1f) ", [f(M21),f(M22),f(M23),f(M24)]),
io__format("(%4.1f %4.1f %4.1f %4.1f)\n", [f(W21),f(W22),f(W23),f(W24)]),
io__format("(%4.1f %4.1f %4.1f %4.1f) ", [f(M31),f(M32),f(M33),f(M34)]),
io__format("(%4.1f %4.1f %4.1f %4.1f)\n", [f(W31),f(W32),f(W33),f(W34)]),
io__format("( 0.0 0.0 0.0 1.0) ( 0.0 0.0 0.0 1.0)\n", []).
% ---------------------------------------------------------------------------- %
% INTERSECTION WITH THE PLANE y =< 0
%
% T - transformation
% P, D - point and direction vector defining a line
% POI - point of intersection with the plane
% TC - texture coordinates
% N - unit surface normal
%
% NOTE: recall that P and D are in world space, so we transform
% them via W (the inverse of the transformation M that has been
% applied to the plane y =< 0) into object space where we can
% work more easily. If we find a point of intersection, we have
% to translate it back into world space via the transformation M.
%
intersects_plane(Id, T, P, D, Surface, IntersectionResults) :-
% The plane is defined by y =< 0.
% First, translate the world space line into object space.
%
point(X, Y, Z) = point_to_object_space(T, P),
point(Dx, Dy, Dz) = vector_to_object_space(T, D),
% The plane is defined by y =< 0, therefore we need to find
% k s.t. Y + kDy = 0. If Dy = 0 then the line does not intersect the
% plane. Otherwise the POI in object space is (X + kDx, 0, Z + kDz)
% which we then translate back into world space. Note that these are
% also the texture coordinates.
(
Dy \= 0.0, % The ray must not parallel the plane.
K = -Y / Dy,
K > 0.0 % The ray must point at the plane.
->
POI = point_to_world_space(T, point(X + K * Dx, 0.0, Z + K * Dz)),
TC = surface_coordinates(0, X + K * Dx, Z + K * Dz),
% The normal to the plane is the y axis.
%
N = unit(vector_to_world_space(T, point(0.0, 1.0, 0.0))),
IntersectionResults = [intersection(Id, POI, N, TC, Surface)]
;
IntersectionResults = []
).
% ---------------------------------------------------------------------------- %
% T - transformation
% P, D - point and direction vector defining a line
% POI - point of intersection with the plane
% TC - texture coordinates
% N - unit surface normal
%
% NOTE: recall that P and D are in world space, so we transform
% them via W (the inverse of the transformation M that has been
% applied to the unit origin sphere) into object space where we can
% work more easily. If we find a point of intersection, we have
% to translate it back into world space via the transformation M.
%
% This algorithm is based on the web page
% http://www.cs.fit.edu/wds/classes/adv-graphics/raytrace/raytrace.html
%
intersects_sphere(Id, T, P, D, Surface, IntersectionResults) :-
% The sphere is defined by x^2 + y^2 + z^2 =< 1.
% First, translate the world space line into object space.
%
point(X, Y, Z) = point_to_object_space(T, P),
point(Dx, Dy, Dz) = unit(vector_to_object_space(T, D)),
% If SqrLoc < 1 then the line originates from within the sphere.
%
SqrLoc = (X * X) + (Y * Y) + (Z * Z),
Tca = (-X * Dx + -Y * Dy + -Z * Dz),
% If SqrLoc > 1 and Tca < 0 then the ray is pointing away from
% the sphere and not emanating from within it.
%
( not ( SqrLoc > 1.0, Tca < 0.0 ) ->
SqrTca = Tca * Tca,
SqrD = SqrLoc - SqrTca,
SqrThc = 1.0 - SqrD,
% If the half-chord distance is negative then the ray does not
% intersect with the sphere.
%
( SqrThc >= 0.0 ->
% Finally, we compute the point of intersection with the sphere.
% If SqrLoc >= 1.0 we are outside the sphere, otherwise
% we are inside the sphere.
%
Thc = sqrt(SqrThc),
DistToSurface1 = Tca - Thc,
DistToSurface2 = Tca + Thc,
intersects_sphere_2(Id, DistToSurface1, T, X, Y, Z, Dx, Dy, Dz,
Surface, IntersectionResult1),
intersects_sphere_2(Id, DistToSurface2, T, X, Y, Z, Dx, Dy, Dz,
Surface, IntersectionResult2),
IntersectionResults = [IntersectionResult1, IntersectionResult2]
;
IntersectionResults = []
)
;
IntersectionResults = []
).
:- pred intersects_sphere_2(object_id, real, trans, real, real, real, real,
real, real, surface, intersection).
:- mode intersects_sphere_2(in, in, in, in, in, in, in, in, in, in, out) is det.
intersects_sphere_2(Id, DistToSurface, T, X, Y, Z, Dx, Dy, Dz, Surface,
Intersection) :-
IX = X + DistToSurface * Dx,
IY = Y + DistToSurface * Dy,
IZ = Z + DistToSurface * Dz,
POI = point_to_world_space(T, point(IX, IY, IZ)),
V = 0.5 * (IY + 1.0),
% SqrIY = IY * IY,
% U = positive_asin(IX / sqrt(1.0 - SqrIY)) / (pi + pi),
U = calc_u(IX, IZ),
TC = surface_coordinates(0, U, V),
% Since the sphere is lying at the origin (after transformation), the
% surface normal is simply the vector from the center of the sphere in
% the direction of the point of intersection.
%
N = unit(normal_to_world_space(T, point(IX, IY, IZ))),
Intersection = intersection(Id, POI, N, TC, Surface).
% ---------------------------------------------------------------------------- %
:- type face_intersection % Face, IX, IY, IZ.
---> fi(int, float, float, float).
:- type face_intersections == list(face_intersection).
% T - transformation
% P, D - point and direction vector defining a line
% POI - point of intersection with the plane
% TC - texture coordinates
% N - unit surface normal
%
% NOTE: recall that P and D are in world space, so we transform
% them via W (the inverse of the transformation M that has been
% applied to the unit origin sphere) into object space where we can
% work more easily. If we find a point of intersection, we have
% to translate it back into world space via the transformation M.
%
% This algorithm is based on the web page
% http://www.cs.fit.edu/wds/classes/adv-graphics/raytrace/raytrace.html
%
intersects_cube(Id, T, P, D, Surface, IntersectionResults) :-
% The cube is defined by 0 =< x,y,z =< 1.
% First, translate the world space line into object space.
%
point(X, Y, Z) = point_to_object_space(T, P),
point(Dx, Dy, Dz) = vector_to_object_space(T, D),
% The cube has six faces defined by the planes
% z = 0 (face 0)
% z = 1 (face 1)
% x = 0 (face 2)
% x = 1 (face 3)
% y = 1 (face 4)
% y = 0 (face 5)
%
% We need to find the nearest point of intersection
% if there is one.
%
Face0Intersection = z_face_intersection(0, 0.0, X, Y, Z, Dx, Dy, Dz),
Face1Intersection = z_face_intersection(1, 1.0, X, Y, Z, Dx, Dy, Dz),
Face2Intersection = x_face_intersection(2, 0.0, X, Y, Z, Dx, Dy, Dz),
Face3Intersection = x_face_intersection(3, 1.0, X, Y, Z, Dx, Dy, Dz),
Face4Intersection = y_face_intersection(4, 1.0, X, Y, Z, Dx, Dy, Dz),
Face5Intersection = y_face_intersection(5, 0.0, X, Y, Z, Dx, Dy, Dz),
FaceIntersections = list__condense([Face0Intersection,
Face1Intersection, Face2Intersection, Face3Intersection,
Face4Intersection, Face5Intersection]),
IntersectionResults = list__map(process_cube_intersections(Id, T, Surface),
FaceIntersections).
:- func process_cube_intersections(object_id, trans, surface,
face_intersection) = intersection.
process_cube_intersections(Id, T, Surface, fi(Face, IX, IY, IZ)) =
Intersection :-
( if Face = 0 then U = IX, V = IY, NX = 0.0, NY = 0.0, NZ = -1.0
else if Face = 1 then U = IX, V = IY, NX = 0.0, NY = 0.0, NZ = 1.0
else if Face = 2 then U = IZ, V = IY, NX = -1.0, NY = 0.0, NZ = 0.0
else if Face = 3 then U = IZ, V = IY, NX = 1.0, NY = 0.0, NZ = 0.0
else if Face = 4 then U = IX, V = IZ, NX = 0.0, NY = 1.0, NZ = 0.0
else if Face = 5 then U = IX, V = IZ, NX = 0.0, NY = -1.0, NZ = 0.0
else throw("trans: intersects_cube/6: not a face!")
),
POI = point_to_world_space(T, point(IX, IY, IZ)),
TC = surface_coordinates(Face, U, V),
N = unit(normal_to_world_space(T, point(NX, NY, NZ))),
Intersection = intersection(Id, POI, N, TC, Surface).
% ---------------------------------------------------------------------------- %
% There is definitely a wittier way to accomplish this but my
% brain is too small to encompass it.
:- func z_face_intersection(int,float,float,float,float,float,float,float) =
face_intersections.
z_face_intersection(Face, Side, X, Y, Z, Dx, Dy, Dz) = MaybeIntersection :-
( if Dz = 0.0 then
MaybeIntersection = [] % Parallel to the face.
else
K = (Side - Z) / Dz,
( if K < 0.0 then
MaybeIntersection = [] % Pointing away from the face.
else
IX = X + K * Dx,
IY = Y + K * Dy,
( if ( 0.0 =< IX, IX =< 1.0, 0.0 =< IY, IY =< 1.0 ) then
MaybeIntersection = [fi(Face, IX, IY, Side)]
else
MaybeIntersection = []
)
)
).
% ---------------------------------------------------------------------------- %
:- func x_face_intersection(int,float,float,float,float,float,float,float) =
face_intersections.
x_face_intersection(Face, Side, X, Y, Z, Dx, Dy, Dz) = MaybeIntersection :-
( if Dx = 0.0 then
MaybeIntersection = [] % Parallel to the face.
else
K = (Side - X) / Dx,
( if K < 0.0 then
MaybeIntersection = [] % Pointing away from the face.
else
IY = Y + K * Dy,
IZ = Z + K * Dz,
( if ( 0.0 =< IZ, IZ =< 1.0, 0.0 =< IY, IY =< 1.0 ) then
MaybeIntersection = [fi(Face, Side, IY, IZ)]
else
MaybeIntersection = []
)
)
).
% ---------------------------------------------------------------------------- %
:- func y_face_intersection(int,float,float,float,float,float,float,float) =
face_intersections.
y_face_intersection(Face, Side, X, Y, Z, Dx, Dy, Dz) = MaybeIntersection :-
( if Dy = 0.0 then
MaybeIntersection = [] % Parallel to the face.
else
K = (Side - Y) / Dy,
( if K < 0.0 then
MaybeIntersection = [] % Pointing away from the face.
else
IX = X + K * Dx,
IZ = Z + K * Dz,
( if ( 0.0 =< IZ, IZ =< 1.0, 0.0 =< IX, IX =< 1.0 ) then
MaybeIntersection = [fi(Face, IX, Side, IZ)]
else
MaybeIntersection = []
)
)
).
% ---------------------------------------------------------------------------- %
% INTERSECTION WITH THE PLANE y =< 0
%
% T - transformation
% P, D - point and direction vector defining a line
% POI - point of intersection with the plane
% TC - texture coordinates
% N - unit surface normal
%
% NOTE: recall that P and D are in world space, so we transform
% them via W (the inverse of the transformation M that has been
% applied to the plane y =< 0) into object space where we can
% work more easily. If we find a point of intersection, we have
% to translate it back into world space via the transformation M.
%
intersects_cylinder(Id, T, P, D, Surface, IntersectionResults) :-
% The cylinder is defined by 0 =< y =< 1, x^2 + z^2 =< 1.
% First, translate the world space line into object space.
%
point(X, Y, Z) = point_to_object_space(T, P),
point(Dx, Dy, Dz) = vector_to_object_space(T, D),
% The three surfaces are
% x^2 + z^2 = 1 (face 0)
% y = 1 (face 1)
% y = 0 (face 2)
%
Face0Intersections = cylinder_intersection(0, X, Y, Z, Dx, Dy, Dz),
Face1Intersections = disc_intersection(1, 1.0, X, Y, Z, Dx, Dy, Dz),
Face2Intersections = disc_intersection(2, 0.0, X, Y, Z, Dx, Dy, Dz),
FaceIntersections = list__condense([Face0Intersections,
Face1Intersections, Face2Intersections]),
IntersectionResults = list__map(process_cylinder_intersections(Id, T,
Surface), FaceIntersections).
:- func process_cylinder_intersections(object_id, trans, surface,
face_intersection) = intersection.
process_cylinder_intersections(Id, T, Surface, fi(Face, IX, IY, IZ)) =
Intersection :-
( if Face = 0 then
% U = positive_asin(IX) / (pi + pi),
U = calc_u(IX, IZ),
V = IY,
NX = IX, NY = 0.0, NZ = IZ
else if Face = 1 then
U = 0.5*IX+0.5, V = 0.5*IZ+0.5, NX = 0.0, NY = 1.0, NZ = 0.0
else if Face = 2 then
U = 0.5*IX+0.5, V = 0.5*IZ+0.5, NX = 0.0, NY = -1.0, NZ = 0.0
else
throw("trans: intersects_cylinder/6: not a face!")
),
POI = point_to_world_space(T, point(IX, IY, IZ)),
TC = surface_coordinates(Face, U, V),
N = unit(normal_to_world_space(T, point(NX, NY, NZ))),
Intersection = intersection(Id, POI, N, TC, Surface).
% ---------------------------------------------------------------------------- %
intersects_cone(Id, T, P, D, Surface, IntersectionResults) :-
% The cone is defined by 0 =< y =< 1, x^2 + z^2 - y^2 =< 1.
% First, translate the world space line into object space.
%
point(X, Y, Z) = point_to_object_space(T, P),
point(Dx, Dy, Dz) = vector_to_object_space(T, D),
% The two surfaces are
% x^2 + z^2 - y^2 = 1 (face 0)
% y = 1 (face 1)
%
Face0Intersections = cone_intersection(0, X, Y, Z, Dx, Dy, Dz),
Face1Intersections = disc_intersection(1, 1.0, X, Y, Z, Dx, Dy, Dz),
FaceIntersections = list__append(Face1Intersections, Face0Intersections),
IntersectionResults = list__map(process_cone_intersections(Id, T, Surface),
FaceIntersections).
:- func process_cone_intersections(object_id, trans, surface,
face_intersection) = intersection.
process_cone_intersections(Id, T, Surface, fi(Face, IX, IY, IZ)) =
Intersection :-
( if Face = 0 then
% IXIY0 = IX / IY,
% IXIY = (if IXIY0 > 1.0 then 1.0 else if IXIY0 < -1.0 then -1.0
% else IXIY0),
% U = positive_asin(IXIY) / (pi + pi), V = IY,
U = calc_u(IX, IZ), V = IY,
NX = IX, NY = -sqrt(IX*IX + IZ*IZ), NZ = IZ
else if Face = 1 then
U = 0.5*IX+0.5, V = 0.5*IZ+0.5, NX = 0.0, NY = 1.0, NZ = 0.0
else
throw("trans: intersects_cylinder/6: not a face!")
),
POI = point_to_world_space(T, point(IX, IY, IZ)),
TC = surface_coordinates(Face, U, V),
N = unit(normal_to_world_space(T, point(NX, NY, NZ))),
Intersection = intersection(Id, POI, N, TC, Surface).
% ---------------------------------------------------------------------------- %
:- func cylinder_intersection(int, float, float, float, float, float, float) =
face_intersections.
cylinder_intersection(Face, X, Y, Z, Dx, Dy, Dz) =
solve_quadratic(Face, X, Y, Z, Dx, Dy, Dz, A, B, C)
:-
A = (Dx * Dx) + (Dz * Dz),
B = 2.0 * ((X * Dx) + (Z * Dz)),
C = (X * X) + (Z * Z) - 1.0.
% ---------------------------------------------------------------------------- %
:- func cone_intersection(int, float, float, float, float, float, float) =
face_intersections.
cone_intersection(Face, X, Y, Z, Dx, Dy, Dz) =
solve_quadratic(Face, X, Y, Z, Dx, Dy, Dz, A, B, C)
:-
A = (Dx * Dx) + (Dz * Dz) - (Dy * Dy),
B = 2.0 * ((X * Dx) + (Z * Dz) - (Y * Dy)),
C = (X * X) + (Z * Z) - (Y * Y).
% ---------------------------------------------------------------------------- %
:- func solve_quadratic(int, float, float, float, float, float, float, float,
float, float) = face_intersections.
solve_quadratic(Face, X, Y, Z, Dx, Dy, Dz, A, B, C) = Intersections :-
% Optimized quadratic solution from "Numerical Recipes in Pascal".
%
BB4AC = B * B - 4.0 * A * C,
( if BB4AC < 0.0 then
Intersections = []
else
SgnB = ( if B < 0.0 then -1.0 else 1.0 ),
Q = -0.5 * (B + SgnB * sqrt(BB4AC)),
K1 = Q / A,
K2 = C / Q,
maybe_add_intersection(K1, Face, X, Y, Z, Dx, Dy, Dz, [],
Intersections0),
maybe_add_intersection(K2, Face, X, Y, Z, Dx, Dy, Dz, Intersections0,
Intersections)
).
:- pred maybe_add_intersection(float::in, int::in, float::in, float::in,
float::in, float::in, float::in, float::in, face_intersections::in,
face_intersections::out) is det.
maybe_add_intersection(K, Face, X, Y, Z, Dx, Dy, Dz, Intersections0,
Intersections) :-
(
K >= 0.0,
IY = Y + K * Dy,
0.0 =< IY,
IY =< 1.0
->
IX = X + K * Dx,
IZ = Z + K * Dz,
Intersections = [fi(Face, IX, IY, IZ) | Intersections0]
;
Intersections = Intersections0
).
% ---------------------------------------------------------------------------- %
:- func disc_intersection(int,float,float,float,float,float,float,float) =
face_intersections.
disc_intersection(Face, Side, X, Y, Z, Dx, Dy, Dz) = MaybeIntersection :-
( if Dy = 0.0 then
MaybeIntersection = [] % Parallel to the disc.
else
K = (Side - Y) / Dy,
( if K < 0.0 then
MaybeIntersection = [] % Pointing away from the disc.
else
IX = X + K * Dx,
IZ = Z + K * Dz,
( if (IX * IX) + (IZ * IZ) =< 1.0 then
MaybeIntersection = [fi(Face, IX, Side, IZ)]
else
MaybeIntersection = []
)
)
).
% ---------------------------------------------------------------------------- %
inside_sphere(Point0, Trans) :-
Point = point_to_object_space(Trans, Point0),
mag2(Point) =< 1.0.
% ---------------------------------------------------------------------------- %
inside_plane(Point, Trans) :-
point(_X, Y, _Z) = point_to_object_space(Trans, Point),
Y =< 0.0.
% ---------------------------------------------------------------------------- %
inside_cube(Point, Trans) :-
point(X, Y, Z) = point_to_object_space(Trans, Point),
X >= 0.0,
Y >= 0.0,
Z >= 0.0,
X =< 1.0,
Y =< 1.0,
Z =< 1.0.
% ---------------------------------------------------------------------------- %
inside_cylinder(Point, Trans) :-
point(X, Y, Z) = point_to_object_space(Trans, Point),
Y >= 0.0,
Y =< 1.0,
X*X + Z*Z =< 1.0.
% ---------------------------------------------------------------------------- %
inside_cone(Point, Trans) :-
point(X, Y, Z) = point_to_object_space(Trans, Point),
Y >= 0.0,
Y =< 1.0,
X*X + Z*Z - Y*Y =< 0.0.
% ---------------------------------------------------------------------------- %
% Given X and Z, find U in [0, 1].
%
% From section 3.6, we have that
% X = sqrt(1-y*y) * sin(2 * pi * U)
% and Z = sqrt(1-y*y) * cos(2 * pi * U)
%
% therefore X / Z = tan(2 * pi * U)
% therefore U = atan(X / Z) / (2 * pi)
%
% Atan2 returns an angle in the range (-pi, pi), but we need angles
% in the range (0, 2*pi). We therefore leave the segment (0, pi)
% unchanged, and translate the segment (-pi, 0) by a full rotation.
%
:- func calc_u(float, float) = float.
calc_u(X, Z) = U :-
U0 = atan2(X, Z) / (pi + pi),
% -0.5 =< U0 =< 0.5
U = ( if U0 > 0.0 then U0 else 1.0 + U0 ).
% 0.0 =< U =< 1.0
% asin(X) returns a value in [-pi, pi]; we want one that
% returns a value in [0, 2pi].
%
:- func positive_asin(float) = float.
positive_asin(X) = PosAsinX :-
AsinX = asin(X),
PosAsinX = ( if AsinX < 0.0 then pi + pi - AsinX else AsinX ).
% ---------------------------------------------------------------------------- %
% ---------------------------------------------------------------------------- %