_c4d_geraycollider¶
Demonstrates ray casting.
_c4d_geraycollider.py¶
"""Demonstrates ray casting.
This example demonstrates how to use the GeRayCollider to perform ray casting against a polygon object.
You can run the example multiple times in a row, it will generate different rays each time.
Topics:
- Initializing and using the GeRayCollider
- Generating rays and checking for intersections
- Visualizing ray intersections in the scene
Related:
math/_c4d_matrix.py
"""
__author__ = "Ferdinand Hoppe"
__copyright__ = "Copyright (C) 2026 MAXON Computer GmbH"
__license__ = "Apache-2.0 License"
__version__ = "2027.0.0"
__date__ = "07/09/2026"
import c4d
import mxutils
import pprint
doc: c4d.documents.BaseDocument # The active Cinema 4D document.
op: c4d.BaseObject | None # The currently selected object in the Cinema 4D document, can be None.
def main() -> None:
"""Runs the example.
"""
# --- Setup ------------------------------------------------------------------------------------
# Flush the scene, and create a tours object at origin. Also give the tours a Phong and display
# tag to make it more clear which faces are hit by the ray collider.
doc.Flush()
torus: c4d.BaseObject = mxutils.CheckType(c4d.BaseObject(c4d.Otorus))
torus[c4d.ID_BASEOBJECT_XRAY] = True
doc.InsertObject(torus)
mxutils.CheckType(torus.MakeTag(c4d.Tphong))
tag: c4d.BaseTag = mxutils.CheckType(torus.MakeTag(c4d.Tdisplay))
tag[c4d.DISPLAYTAG_AFFECT_DISPLAYMODE] = True
tag[c4d.DISPLAYTAG_SDISPLAYMODE] = c4d.DISPLAYTAG_SDISPLAY_GOURAUD_WIRE
# Create a helper null object to organize the generated ray visualizations.
root: c4d.BaseObject = mxutils.CheckType(c4d.BaseObject(c4d.Onull))
root.SetName("rays")
root.ChangeNBit(c4d.NBIT_OM1_FOLD, c4d.NBITCONTROL_SET)
doc.InsertObject(root)
# We need a polygon object to raycast against. Our torus is a generator and has no cache yet, as
# we just inserted it into the scene. So, we have to evaluate the document state ('execute the
# passes') to build the cache.
if not doc.ExecutePasses(
bt=None, animation=False, expressions=False, caches=True, flags=c4d.BUILDFLAGS_NONE):
raise RuntimeError("Failed to execute passes on the document.")
# Now we can get the cache of the object which is a polygon object in this case. Note that not
# all generators necessarily directly produce a polygon object cache, and instead might also
# return complex hierarchies of objects instead. You can then also use a modelling command
# such as current state to object to collapse the whole cache into a singular polygon object
# (instead of getting the cache directly).
cache: c4d.PolygonObject = mxutils.CheckType(torus.GetCache(), c4d.PolygonObject)
# --- Ray Casting ------------------------------------------------------------------------------
# Now we can initialize our ray collider and then construct a base vector for generating multiple
# rays. We make the base vector 50% larger than the largest dimension of the bounding box.
caster: c4d.utils.GeRayCollider = c4d.utils.GeRayCollider()
caster.Init(cache)
bbox: c4d.Vector = cache.GetRad()
base: c4d.Vector = c4d.Vector(max(bbox.x, bbox.y, bbox.z) * 1.5, 0, 0)
# Now we construct multiple rays and directions based on the base ray.
count: int = 5
rays: list[tuple[c4d.Vector, c4d.Vector]] = []
for _ in range(count):
r1: c4d.Matrix = mxutils.g_random.RandomMatrix(
rotationInterval=(c4d.Vector(), c4d.Vector(c4d.utils.DegToRad(360))))
r2: c4d.Matrix = mxutils.g_random.RandomMatrix(
rotationInterval=(c4d.Vector(), c4d.Vector(c4d.utils.DegToRad(25))))
# Append the ray start and direction. The start is the base ray rotated by a matrix in the
# rotation interval [0°, 360°]. The direction is the inverse of the start vector rotated
# by a matrix in the rotation interval [0°, 25°] (so that the example is more interesting
# and we can also have misses).
rays.append((r1 * base , r2 * -(r1 * base)))
# Carry out the intersections for all the rays we generated.
maxDistance: float = base.GetLength() * 2
for i, (start, direction) in enumerate(rays):
# This generates the the intersections for the i-th ray.
caster.Intersect(start, direction, maxDistance)
# Retrieve all intersections for the i-th ray.
intersections: list[dict] = [
caster.GetIntersection(j) for j in range(caster.GetIntersectionCount())]
ExampleHelper.VisualizeRay(start, direction, maxDistance, intersections, root,
f"Ray.{i}", i / count)
# Print one example intersection set.
# {
# 'backface': True, // True if backface hit
# 'barrycoords': Vector(0.038, 0.542, 0.42), // Barycentric coordinates of hit
# 'distance': 1.601394977155644, // Distance from ray origin to hit
# 'f_normal': Vector(356.937, 111.378, 434.929), // Face normal (Not normalized)
# 'face_id': 64, // Face ID of hit
# 'hitpos': Vector(92.81, 0.723, 116.76), // Hit position in ray space
# 's_normal': Vector(356.937, 111.378, 434.929), // Phong normal of hit or f_normal
# 'tri_face_id': -65 // If first half of quad or triangle
# // face_id + 1, else -(face_id + 1)
# }
pprint.pprint(intersections)
c4d.EventAdd()
class ExampleHelper:
"""Provides helper function to generate ray visualizations.
"""
@staticmethod
def VisualizeRay(r: c4d.Vector, dir: c4d.Vector, maxDistance: float, intersections: list[dict],
root: c4d.BaseObject, name: str, t: float) -> None:
"""Visualize a ray with its intersections.
"""
# Compute the start, end, and midpoint of the ray.
start: c4d.Vector = r
end: c4d.Vector = start + dir.GetNormalized() * maxDistance
mid: c4d.Vector = (start + end) * 0.5
# Build a frame for the ray direction. See math/_c4d_vector.py when you are lost as to what
# is being done here.
epsilon: float = 1e-6
up: c4d.Vector = c4d.Vector(0, 1, 0)
up = up if 1. - abs(dir * c4d.Vector(0, 1, 0)) > epsilon else c4d.Vector(epsilon, 1, 0)
fz: c4d.Vector = dir.GetNormalized()
fx: c4d.Vector = up.Cross(fz).GetNormalized()
fy: c4d.Vector = fz.Cross(fx).GetNormalized()
# Build the cylinder representing the ray.
color: c4d.Vector = c4d.utils.MixVec(c4d.Vector(1, 0, 0), c4d.Vector(0, 1, 0), t)
cyliner: c4d.BaseObject = mxutils.CheckType(c4d.BaseObject(c4d.Ocylinder))
cyliner[c4d.PRIM_CYLINDER_RADIUS] = 1
cyliner[c4d.PRIM_CYLINDER_HEIGHT] = maxDistance
cyliner[c4d.PRIM_AXIS] = c4d.PRIM_AXIS_ZP
cyliner[c4d.ID_BASEOBJECT_COLOR] = color
cyliner[c4d.ID_BASEOBJECT_USECOLOR] = c4d.ID_BASEOBJECT_USECOLOR_ALWAYS
cyliner.SetMg(c4d.Matrix(off=mid, v1=fx, v2=fy, v3=fz))
cyliner.SetName(name)
cyliner.InsertUnderLast(root)
# Build the cone representing the ray's endpoint.
cone: c4d.BaseObject = mxutils.CheckType(c4d.BaseObject(c4d.Ocone))
cone[c4d.PRIM_CONE_BRAD] = 5
cone[c4d.PRIM_CONE_HEIGHT] = 10
cone[c4d.PRIM_AXIS] = c4d.PRIM_AXIS_ZP
cone[c4d.ID_BASEOBJECT_COLOR] = color * 0.5
cone[c4d.ID_BASEOBJECT_USECOLOR] = c4d.ID_BASEOBJECT_USECOLOR_ALWAYS
cone.InsertUnderLast(cyliner)
cone.SetName(name + ".direction")
cone.SetMg(c4d.Matrix(off=end, v1=fx, v2=fy, v3=fz))
# Build spheres at the intersection points.
for data in intersections:
isBackFace: bool = data.get("backface", False)
hitPos: c4d.Vector = data.get("hitpos", c4d.Vector())
sphere: c4d.BaseObject = mxutils.CheckType(c4d.BaseObject(c4d.Osphere))
sphere[c4d.PRIM_SPHERE_RAD] = 5
sphere[c4d.ID_BASEOBJECT_COLOR] = color * 0.5 * (0.25 if isBackFace else 1.0)
sphere[c4d.ID_BASEOBJECT_USECOLOR] = c4d.ID_BASEOBJECT_USECOLOR_ALWAYS
sphere.InsertUnderLast(cyliner)
sphere.SetName(name + ".hit" + (".in" if isBackFace else ".out"))
sphere.SetMg(c4d.Matrix(off=hitPos, v1=fx, v2=fy, v3=fz))
if __name__ == "__main__":
c4d.ClearPythonConsole()
main()