_c4d_matrix

Demonstrates working with matrices.

_c4d_matrix.py

"""Demonstrates working with matrices.

Topics:
    - Instantiation and access of matrices
    - Arithmetic operations with matrices
    - Inversion, normalization, tensor, and scale
    - Arithmetic operations between matrices and vectors
    - Constructing matrices from vectors
    - Randomization of matrices

Related:
    math/_c4d_vector.py
    math/_mxutils_random.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


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.
    """
    print(f"\n--- Instantiation and Access -----------------------------------------------------\n")

    # Instantiate the identity matrix.
    identity: c4d.Matrix = c4d.Matrix()
    print(f"{identity = }")

    # Matrices are often also constructed with c4d.utils helper functions.

    # A matrix representing a translation by 100 units along the X axis.
    move: c4d.Matrix = c4d.utils.MatrixMove(c4d.Vector(100, 0, 0))
    print(f"\n{move = }")

    # A matrix representing a rotation by 90 degrees around the Z axis.
    rotate: c4d.Matrix = c4d.utils.MatrixRotY(c4d.utils.DegToRad(90))
    print(f"{rotate = }")

    # A matrix representing a scaling by a factor of 2 along all axes.
    scale: c4d.Matrix = c4d.utils.MatrixScale(c4d.Vector(2, 2, 2))
    print(f"{scale = }")

    # Matrices can also be manually constructed.

    # A matrix that only sets the offset component.
    offset: c4d.Matrix = c4d.Matrix(off=c4d.Vector(1, 2, 3))
    print(f"\n{offset = }")

    # A matrix that sets the three frame components (the basis vectors) v1, v2, and v3. v1 is the
    # x-axis, v2 is the y-axis, and v3 is the z-axis. In this case we manually define the identity 
    # frame.
    frame: c4d.Matrix = c4d.Matrix(
        v1=c4d.Vector(1, 0, 0),
        v2=c4d.Vector(0, 1, 0),
        v3=c4d.Vector(0, 0, 1)
    )
    print(f"{frame = }")

    # Or everything together: translation, rotation, and scaling.
    combined: c4d.Matrix = c4d.Matrix(
        v1=c4d.Vector(2, 0, 0),
        v2=c4d.Vector(0, 2, 0),
        v3=c4d.Vector(0, 0, 2),
        off=c4d.Vector(1, 2, 3)
    )

    # Accessing the individual components of the combined matrix.
    print(f"\n{combined = }")
    print(f"{combined.off = }")
    print(f"{combined.v1 = }")
    print(f"{combined.v2 = }")
    print(f"{combined.v3 = }")

    print(f"\n--- Arithmetic Operations between two Matrices -----------------------------------\n")

    a: c4d.Matrix = c4d.Matrix(c4d.utils.MatrixMove(c4d.Vector(1, 2, 3)))
    b: c4d.Matrix = c4d.Matrix(c4d.utils.MatrixRotY(c4d.utils.DegToRad(30)))

    print(f"{a = }")
    print(f"{b = }")

    # Matrix addition and subtraction.
    print(f"\n{a + b = }")
    print(f"{b + a = }")
    print(f"{a - b = }")
    print(f"{b - a = }")

    # Matrix multiplication (this is not commutative, i.e., a * b != b * a in general).
    print(f"{a * b = }")
    print(f"{b * a = }") # not the same as a * b

    # Practical example: combining a translation and a rotation.
    transform: c4d.Matrix = (c4d.utils.MatrixMove(c4d.Vector(10, 0, 0)) * 
                             c4d.utils.MatrixRotY(c4d.utils.DegToRad(90)))

    # Divide a matrix by a scalar (not commonly used, but possible). Only the form matrix / scalar is 
    # supported, not scalar / matrix.
    print(f"{a / 2 = }")

    print(f"\n--- Inversion, Normalization, Tensor, and Scale ----------------------------------\n")

    # A matrix can be inverted with the ~ operator.
    print(f"{~a = }")

    # A matrix can be normalized to remove any scaling.
    print(f"{a.GetNormalized() = }")

    # The tensor of a matrix can be accessed via the .GetTensorMatrix() method.
    print(f"{a.GetTensorMatrix() = }")
    
    # The scale of a matrix can be accessed via the .GetScale() method.
    print(f"{scale.GetScale() = }")

    print(f"\n--- Arithmetic Operations between Matrices and Vectors ---------------------------\n")

    # Multiplying a matrix by a vector applies the transformation to the vector. Both sides are
    # supported in both orders: matrix * vector and vector * matrix.
    v: c4d.Vector = c4d.Vector(0, 1, 0)

    print(f"{transform * v = }")
    print(f"{v * transform = }")

    print(f"\n--- Constructing Matrices --------------------------------------------------------\n")

    # A common task is to construct a matrix from either one or two vectors. Shown is here the case
    # of constructing a matrix from a single vector (and an up vector), where the input vector 
    # defines the z-axis of the resulting matrix. This can for example be useful when you have a
    # surface or spline normal and what to construct a frame/transform on it.

    p: c4d.Vector = c4d.Vector(1, 2, 3)      # Example input vector representing the z-axis
    up: c4d.Vector = c4d.Vector(0, 1, 0)     # Example up vector.
    epsilon: float = 1e-6                    

    # Make sure our up-vector is not parallel or anti-parallel to the input vector. When this is the
    # case, we slightly perturb the up vector to avoid numerical issues.
    up = up if 1. - abs(p * up) > epsilon else c4d.Vector(epsilon, 1, 0)

    z: c4d.Vector = p.GetNormalized()            # Normalize the input vector representing the z-axis
    x: c4d.Vector = up.Cross(z).GetNormalized()  # Compute the x-axis as the cross product of up and z
    y: c4d.Vector = z.Cross(x).GetNormalized()   # Compute the y-axis as the cross product of z and x

    # Construct the matrix from the orthonormal basis vectors x, y, and z.
    mg: c4d.Matrix = c4d.Matrix(v1=x, v2=y, v3=z)
    print(f"{mg = }")

    print(f"\n--- Randomization ----------------------------------------------------------------\n")

    # mxutils.Random offers a wide range of matrix randomization functions, see _mxutils_random.py
    # example for more details.
    print(f"{mxutils.g_random.HashNumberToMatrix(42) = }")
    print(f"{mxutils.g_random.HashVectorToMatrix(c4d.Vector(1, 2, 3)) = }")
    print(f"{mxutils.g_random.RandomMatrix() = }")


if __name__ == "__main__":
    c4d.ClearPythonConsole()
    main()