mirror of
https://github.com/Mercury-Language/mercury.git
synced 2026-04-25 06:14:18 +00:00
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.
1016 lines
36 KiB
Mathematica
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 ).
|
|
|
|
% ---------------------------------------------------------------------------- %
|
|
% ---------------------------------------------------------------------------- %
|