Tuesday, January 14, 2025

Transforming Normals in Computer Graphics...

In computer graphics, normals are vectors perpendicular to surfaces that are critical for rendering and lighting calculations. Transforming normals correctly is essential to ensure that the visual appearance of objects (such as shading, reflections, and lighting) remains accurate after transformations like scaling, rotation, or translation.


Challenges in Normal Transformation

  1. Non-Uniform Scaling:

    • Normals need special care under non-uniform scaling (e.g., scaling an object more in one direction than another) because simply applying the transformation matrix to the normal may not preserve its perpendicularity to the surface.
  2. Translation:

    • Normals represent direction and not position, so translation has no effect on them and must not be applied.
  3. Maintaining Unit Length:

    • Normals are typically normalized to unit length (magnitude = 1) for accurate lighting calculations. Transformations may distort their magnitude, requiring renormalization.

Mathematics Behind Normal Transformation

Standard Transformation

For a vertex vv, the transformation is done using the model matrix MM:

v=Mvv' = M \cdot v

However, directly applying MM to normals is incorrect in cases involving non-uniform scaling. This is because normals must remain perpendicular to the surface even after transformations.

Correct Normal Transformation

Normals are transformed using the inverse transpose of the model matrix MM:

n=(M1)Tnn' = (M^{-1})^T \cdot n

Here:

  • M1M^{-1}: Inverse of the transformation matrix.
  • (M1)T(M^{-1})^T: Transpose of the inverse matrix.

Why Use the Inverse Transpose?

  1. Inverse:

    • Ensures that the transformed normal remains perpendicular to the surface.
    • Compensates for non-uniform scaling distortions.
  2. Transpose:

    • Handles the directionality of the normal vector.
  3. Avoid Translation:

    • Translation components of MM do not affect the normal.

Steps to Transform a Normal

  1. Extract the transformation matrix MM used for the object's geometry.
  2. Compute the inverse transpose of MM: N=(M1)TN = (M^{-1})^T
  3. Apply NN to the normal nn to get the transformed normal nn': n=Nnn' = N \cdot n
  4. Normalize nn' to ensure it is a unit vector: n=nnn' = \frac{n'}{||n'||}

Special Cases

  1. Uniform Scaling:

    • If scaling is uniform, the same factor is applied in all directions, and the normals are directly proportional to the transformation.
    • In this case, you can simply apply the transformation matrix and renormalize the result.
  2. Rotation:

    • Rotations preserve angles and perpendicularity, so the normal can be directly transformed using the rotation matrix.
  3. Translation:

    • Normals are not affected by translation.

Example

Given:

  • A normal vector: n=[1,0,0]n = [1, 0, 0]
  • A transformation matrix MM:

M=[2000030000100001]M = \begin{bmatrix} 2 & 0 & 0 & 0 \\ 0 & 3 & 0 & 0 \\ 0 & 0 & 1 & 0 \\ 0 & 0 & 0 & 1 \end{bmatrix}

(Non-uniform scaling)

Steps:

  1. Compute M1M^{-1}:

M1=[120000130000100001]M^{-1} = \begin{bmatrix} \frac{1}{2} & 0 & 0 & 0 \\ 0 & \frac{1}{3} & 0 & 0 \\ 0 & 0 & 1 & 0 \\ 0 & 0 & 0 & 1 \end{bmatrix}

  1. Compute (M1)T(M^{-1})^T:

(M1)T=[120000130000100001](M^{-1})^T = \begin{bmatrix} \frac{1}{2} & 0 & 0 & 0 \\ 0 & \frac{1}{3} & 0 & 0 \\ 0 & 0 & 1 & 0 \\ 0 & 0 & 0 & 1 \end{bmatrix}

  1. Apply (M1)T(M^{-1})^T to nn:

n=[120000130000100001][1000]=[12000]n' = \begin{bmatrix} \frac{1}{2} & 0 & 0 & 0 \\ 0 & \frac{1}{3} & 0 & 0 \\ 0 & 0 & 1 & 0 \\ 0 & 0 & 0 & 1 \end{bmatrix} \begin{bmatrix} 1 \\ 0 \\ 0 \\ 0 \end{bmatrix} = \begin{bmatrix} \frac{1}{2} \\ 0 \\ 0 \\ 0 \end{bmatrix}

  1. Normalize nn':

n=(12)2+02+02=12||n'|| = \sqrt{\left(\frac{1}{2}\right)^2 + 0^2 + 0^2} = \frac{1}{2} n=12/12=[1,0,0]n' = \frac{1}{2} / \frac{1}{2} = [1, 0, 0]


Applications

  1. Shading and Lighting: Normals are critical for calculating how light interacts with a surface (e.g., diffuse and specular reflection).

  2. Normal Mapping: Techniques like normal mapping use altered normals to simulate surface details without changing the geometry.

  3. Rendering Pipelines: Correct normal transformations are necessary for accurate rendering of objects under transformations.


Conclusion

Transforming normals in computer graphics is a key operation for ensuring visual accuracy in rendering. The use of the inverse transpose matrix for transforming normals, particularly under non-uniform scaling, ensures that they remain perpendicular to the surface and consistent with the object's geometry.

Here's the source code for experimentation of normal transformation in freeCAD...



import FreeCAD
import FreeCADGui
from PySide2 import QtWidgets, QtGui, QtCore
import Part


class NormalTransformationApp(QtWidgets.QWidget):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Normal Transformation in FreeCAD")
        self.setGeometry(200, 200, 400, 300)
        self.layout = QtWidgets.QVBoxLayout()

        # Create Buttons
        self.create_cube_btn = QtWidgets.QPushButton("Create Cube")
        self.apply_scaling_btn = QtWidgets.QPushButton("Apply Non-Uniform Scaling")
        self.reset_scaling_btn = QtWidgets.QPushButton("Reset Scaling")
        self.show_normals_btn = QtWidgets.QPushButton("Show Normals")

        # Add Buttons to Layout
        self.layout.addWidget(self.create_cube_btn)
        self.layout.addWidget(self.apply_scaling_btn)
        self.layout.addWidget(self.reset_scaling_btn)
        self.layout.addWidget(self.show_normals_btn)

        self.setLayout(self.layout)

        # Connect Buttons to Functions
        self.create_cube_btn.clicked.connect(self.create_cube)
        self.apply_scaling_btn.clicked.connect(self.apply_scaling)
        self.reset_scaling_btn.clicked.connect(self.reset_scaling)
        self.show_normals_btn.clicked.connect(self.show_normals)

    def create_cube(self):
        """Creates a cube in the FreeCAD document."""
        doc = FreeCAD.ActiveDocument
        if not doc:
            doc = FreeCAD.newDocument("NormalTransformationDemo")
        cube = doc.addObject("Part::Box", "Cube")
        cube.Length = 10
        cube.Width = 10
        cube.Height = 10
        cube.Placement = FreeCAD.Placement(FreeCAD.Vector(0, 0, 0), FreeCAD.Rotation(0, 0, 0, 1))
        doc.recompute()
        FreeCADGui.Selection.addSelection(cube)

    def apply_scaling(self):
        """Applies non-uniform scaling to the selected object."""
        selected_objects = FreeCADGui.Selection.getSelection()
        if not selected_objects:
            QtWidgets.QMessageBox.warning(self, "Warning", "No object selected.")
            return
        obj = selected_objects[0]
        scale_matrix = FreeCAD.Matrix()
        scale_matrix.A11 = 2  # Scale X by 2
        scale_matrix.A22 = 0.5  # Scale Y by 0.5
        scale_matrix.A33 = 1  # Scale Z by 1
        obj.Placement = obj.Placement.multiply(FreeCAD.Placement(scale_matrix))
        FreeCAD.ActiveDocument.recompute()

    def reset_scaling(self):
        """Resets the scaling of the selected object."""
        selected_objects = FreeCADGui.Selection.getSelection()
        if not selected_objects:
            QtWidgets.QMessageBox.warning(self, "Warning", "No object selected.")
            return
        obj = selected_objects[0]
        obj.Placement = FreeCAD.Placement(FreeCAD.Vector(0, 0, 0), FreeCAD.Rotation(0, 0, 0, 1))
        FreeCAD.ActiveDocument.recompute()

    def show_normals(self):
        """Calculates and displays normals for the faces of the selected object."""
        selected_objects = FreeCADGui.Selection.getSelection()
        if not selected_objects:
            QtWidgets.QMessageBox.warning(self, "Warning", "No object selected.")
            return
        obj = selected_objects[0]
        if not hasattr(obj, "Shape"):
            QtWidgets.QMessageBox.warning(self, "Warning", "Selected object has no shape.")
            return
        shape = obj.Shape
        normals = []
        for face in shape.Faces:
            normal = face.normalAt(0.5, 0.5)  # Normal at the center of the face
            normals.append(normal)
            # Create a visual line for the normal
            center = face.CenterOfMass
            normal_line = Part.LineSegment(center, center + normal * 5)
            normal_obj = FreeCAD.ActiveDocument.addObject("Part::Feature", "Normal")
            normal_obj.Shape = normal_line.toShape()
        FreeCAD.ActiveDocument.recompute()
        QtWidgets.QMessageBox.information(self, "Normals", f"Calculated {len(normals)} normals.")


def show_app():
    """Displays the application within FreeCAD."""
    main_window = FreeCADGui.getMainWindow()
    dock_widget = QtWidgets.QDockWidget("Normal Transformation", main_window)
    dock_widget.setWidget(NormalTransformationApp())
    main_window.addDockWidget(QtCore.Qt.RightDockWidgetArea, dock_widget)


show_app()


Saturday, January 11, 2025

चरैवेति..... चरैवेति... CHARAIVETI... CHARAIVETI - my journey through the wilderness of Computer Graphics - Rotational transformation...


Source Code - python script...


import FreeCAD
import FreeCADGui
import Part
from PySide2.QtWidgets import QApplication, QVBoxLayout, QLabel, QLineEdit, QPushButton, QWidget


class RotationApp(QWidget):
    def __init__(self):
        super().__init__()
        self.initUI()

    def initUI(self):
        self.setWindowTitle("Rotation in Computer Graphics")
        self.setGeometry(100, 100, 300, 250)

        layout = QVBoxLayout()

        # Instruction label
        self.label_info = QLabel("Select an object or create one for rotation:")
        layout.addWidget(self.label_info)

        # Button to create a default cube
        self.create_button = QPushButton("Create Cube")
        self.create_button.clicked.connect(self.create_cube)
        layout.addWidget(self.create_button)

        # Input fields for rotation angles
        self.label_x = QLabel("Rotation Angle (X-axis):")
        layout.addWidget(self.label_x)
        self.input_x = QLineEdit("0")
        layout.addWidget(self.input_x)

        self.label_y = QLabel("Rotation Angle (Y-axis):")
        layout.addWidget(self.label_y)
        self.input_y = QLineEdit("0")
        layout.addWidget(self.input_y)

        self.label_z = QLabel("Rotation Angle (Z-axis):")
        layout.addWidget(self.label_z)
        self.input_z = QLineEdit("0")
        layout.addWidget(self.input_z)

        # Buttons
        self.apply_button = QPushButton("Apply Rotation")
        self.apply_button.clicked.connect(self.apply_rotation)
        layout.addWidget(self.apply_button)

        self.reset_button = QPushButton("Reset Rotation")
        self.reset_button.clicked.connect(self.reset_rotation)
        layout.addWidget(self.reset_button)

        self.setLayout(layout)

    def create_cube(self):
        """Create a default cube if no object exists."""
        doc = FreeCAD.ActiveDocument
        if not doc:
            doc = FreeCAD.newDocument("RotationDemo")

        cube = Part.makeBox(10, 10, 10)
        obj = doc.addObject("Part::Feature", "Cube")
        obj.Shape = cube
        doc.recompute()
        FreeCADGui.Selection.clearSelection()
        FreeCADGui.Selection.addSelection(obj)
        FreeCAD.Console.PrintMessage("Cube created and selected.\n")

    def get_selected_object(self):
        """Get the currently selected object in FreeCAD."""
        selected = FreeCADGui.Selection.getSelection()
        if len(selected) == 0:
            FreeCAD.Console.PrintMessage("No object selected!\n")
            return None
        return selected[0]

    def apply_rotation(self):
        """Apply rotation to the selected object."""
        obj = self.get_selected_object()
        if not obj:
            return

        try:
            # Get rotation angles from input fields
            angle_x = float(self.input_x.text())
            angle_y = float(self.input_y.text())
            angle_z = float(self.input_z.text())
        except ValueError:
            FreeCAD.Console.PrintMessage("Invalid input! Please enter numeric values.\n")
            return

        from math import radians
        angle_x = radians(angle_x)
        angle_y = radians(angle_y)
        angle_z = radians(angle_z)

        # Create rotation matrices
        rotation_x = FreeCAD.Rotation(FreeCAD.Vector(1, 0, 0), angle_x)
        rotation_y = FreeCAD.Rotation(FreeCAD.Vector(0, 1, 0), angle_y)
        rotation_z = FreeCAD.Rotation(FreeCAD.Vector(0, 0, 1), angle_z)

        # Combine rotations
        combined_rotation = rotation_x.multiply(rotation_y).multiply(rotation_z)

        # Update object's Placement
        current_placement = obj.Placement
        new_rotation = combined_rotation.multiply(current_placement.Rotation)
        obj.Placement = FreeCAD.Placement(current_placement.Base, new_rotation)

        FreeCAD.ActiveDocument.recompute()
        FreeCAD.Console.PrintMessage(
            f"Applied rotation: X={self.input_x.text()}°, Y={self.input_y.text()}°, Z={self.input_z.text()}°\n"
        )

    def reset_rotation(self):
        """Reset the rotation of the selected object."""
        obj = self.get_selected_object()
        if not obj:
            return

        obj.Placement = FreeCAD.Placement(obj.Placement.Base, FreeCAD.Rotation())
        FreeCAD.ActiveDocument.recompute()
        FreeCAD.Console.PrintMessage("Rotation reset to default.\n")


# Run the application
if __name__ == "__main__":
    if not FreeCADGui.activeWorkbench():
        FreeCADGui.showMainWindow()

    app = QApplication.instance()
    if app is None:
        app = QApplication([])

    window = RotationApp()
    window.show()



 Rotational transformation is a fundamental operation in computer graphics, enabling the manipulation of objects by rotating them around a fixed point or axis. This transformation is crucial in a wide array of applications, from 3D modeling and animation to virtual reality and game development. Understanding rotational transformations requires knowledge of geometric principles, trigonometric functions, and matrix algebra, which form the backbone of this operation.

What is Rotational Transformation?

Rotational transformation involves changing the orientation of an object while maintaining its shape and size. This operation pivots the object around a fixed point, often referred to as the center of rotation, or an axis in 3D space. The rotation can occur in two or three dimensions and is defined by:

  • Angle of Rotation: Specifies the degree of rotation, typically in degrees or radians.
  • Direction: Clockwise or counterclockwise in 2D, and along the X, Y, or Z-axis in 3D.

Rotational transformations preserve the object's geometric properties, such as distances and angles, making them rigid transformations.


Mathematical Representation

2D Rotation

In two dimensions, rotation around the origin is achieved using a rotation matrix. The new coordinates (x,y)(x', y') of a point after rotation by an angle θ\theta are derived as:

[xy]=[cosθsinθsinθcosθ][xy]

Where:

  • (x,y)(x, y) are the original coordinates.
  • (x,y)(x', y') are the transformed coordinates.
  • θ\theta is the angle of rotation.

3D Rotation

In three dimensions, rotation is more complex as it can occur around any of the principal axes. The rotation matrices for the X, Y, and Z-axes are:

  • X-axis:
[1000cosθsinθ0sinθcosθ]\begin{bmatrix} 1 & 0 & 0 \\ 0 & \cos \theta & -\sin \theta \\ 0 & \sin \theta & \cos \theta \end{bmatrix}
  • Y-axis:
[cosθ0sinθ010sinθ0cosθ]\begin{bmatrix} \cos \theta & 0 & \sin \theta \\ 0 & 1 & 0 \\ -\sin \theta & 0 & \cos \theta \end{bmatrix}
  • Z-axis:
[cosθsinθ0sinθcosθ0001]\begin{bmatrix} \cos \theta & -\sin \theta & 0 \\ \sin \theta & \cos \theta & 0 \\ 0 & 0 & 1 \end{bmatrix}

To achieve rotation around an arbitrary axis, more advanced techniques like quaternions or axis-angle representation are used.

Applications of Rotational Transformation

1. 3D Modeling and Animation

Rotation is essential for animating objects, creating realistic movements, and positioning objects in 3D scenes. For example, a car wheel's rotation is simulated using rotational transformation.

2. Virtual Reality and Augmented Reality

In VR and AR, objects must be rotated dynamically based on user input or device orientation to ensure an immersive experience.

3. Robotics

Rotational transformations are used in robotics to calculate the movement of joints and arms, enabling precise control of robotic mechanisms.

4. Game Development

Rotations are fundamental in game engines for camera movement, object rotations, and character animations.

5. Simulation and Visualization

In simulations, such as flight or driving simulators, rotational transformations are used to model realistic object behaviors.