Skip to content

Ray geometry

gem.ray.Ray(startVector,dirVector) retains caller-owned Vector references. Construction records the original direction magnitude as distance and normalizes the caller's direction in place. Ordinary geometry uses Vector3 positions and nonzero directions. The existing zero-direction normalization error and wider malformed/nonfinite policies are unchanged.

The geometric position at distance d is start + dir*d. The stored .end field is separate intersection placeholder/state, initialized to a zero Vector3. It is not automatically set to start + dir*distance.

Copying and transforms

duplicate() returns a new Ray with independent start, direction and end Vectors/component lists and the exact stored distance. It does not invoke the constructor or re-normalize stored direction. Modifying either copy's Vector fields cannot modify the other's storage.

rotateUsingQuaternion(quat1) rotates start and direction about the coordinate origin using the established Hamilton sandwich q*(0,v)*conjugate(q). Inputs must be unit rotation quaternions in [w,x,y,z] order. Start/direction remain Vector3 values, direction is normalized, and distance is retained. The supplied quaternion and previously referenced input Vectors are preserved.

roateUsingMatrix(matrix) retains its historical spelling and Matrix3 rotation behavior, including rotation about the coordinate origin and direction normalization. Proper rotation matrices use the established row-vector convention; distance is retained. No new method name or Matrix4 rotation overload is added.

translate(matrix) supports pure Matrix4 translations on Vector3 geometry using local homogeneous positions [x,y,z,1] and directions [x,y,z,0]. It returns spatial Vector3 fields, changes origin, and preserves direction and distance. No perspective divide or general Matrix*Vector promotion is introduced. Existing matching-dimension multiplication remains the fallback; this does not establish scale/shear/projective ray semantics.

All transform methods mutate the receiver and retain their None returns. They replace transformed start/direction fields rather than mutate the previously referenced Vectors. Duplication and transforms preserve matrix or quaternion inputs. Public field edits remain caller-managed.

from gem.ray import Ray
from gem.vector import Vector
from gem.matrix import Matrix
from gem.quaternion import quat_from_axis_angle

origin = Vector(3, [1, 2, 3])
displacement = Vector(3, [0, 0, 5])
r = Ray(origin, displacement)  # distance=5; displacement becomes [0,0,1]
copy = r.duplicate()
r.translate(Matrix(4).translate(Vector(3, [2, -3, 4])))
# r.start=[3,-1,7], r.dir=[0,0,1], distance=5; origin remains [1,2,3].
r.rotateUsingQuaternion(quat_from_axis_angle([0, 0, 1], 90))
# r.start is approximately [1,3,7]; copy retains its original geometry.
# r.end remains the zero intersection placeholder throughout.

Intersection state remains unresolved

Duplication copies .end exactly. Construction and all transforms retain its historical semantics: transforms leave the existing object, component storage and values unchanged, whether zero or nonzero. A zero Vector might mean an unset placeholder or an actual hit at the origin; there is no validity flag. A nonzero value alone likewise does not establish a valid hit. No value-based inference or new intersection behavior is introduced.

A future API decision must define validity, hit ownership and whether hit coordinates transform with geometry. Applications using .end as a hit position must currently manage that state explicitly. Derived geometric tips can be computed independently and must not be confused with .end. Scale, shear, projective transforms and broader numerical policies remain outside this contract.