[Rod Stephens Books]
Index Books Python Examples About Rod Contact
[Mastodon] [Bluesky] [Facebook]
[Build Your Own Python Action Arcade!]

[Build Your Own Ray Tracer With Python]

[Beginning Database Design Solutions, Second Edition]

[Beginning Software Engineering, Second Edition]

[Essential Algorithms, Second Edition]

[The Modern C# Challenge]

[WPF 3d, Three-Dimensional Graphics with WPF and C#]

[The C# Helper Top 100]

[Interview Puzzles Dissected]

Title: Draw a rotated ray-traced beveled cube in Python

[A ray-traced rotated beveled cube inside reflective walls drawn in Python]

My previous post Draw a ray-traced beveled cube in Python showed how to use techniques described in my book Build Your Own Ray Tracer With Python to draw a ray traced beveled cube. Unfortunately those techniques require you to create shapes exactly as you want to draw them. For example, you can't rotate a shape after you define it. You can (at least sometimes) transform the shape's points as you use them to define the shape, but that can be messy. In fact, at the end of my book I mention transformations as a topic that you might like to add to the ray tracer.

This post shows how to achieve one kind of transformation. It shows how you can rotate a mesh object after defining it.

Rotating Meshes

The RtMesh class represents a collection of triangles that are all drawn with the same material. Transforming the points that define the triangles is relatively easy. The important thing to realize is that, if you rotate a triangle, you also rotate its associated vectors. The ray tracer uses two vectors called ab and ac to determine whether a ray intersects the triangle. It also uses a normal vector to determine how a ray strikes the triangle so it can determine the color of the point of intersection. (See the book for details.)

The RtMesh class holds the following geometry-related lists and dictionaries.

  • vertices—The triangles' vertices
  • triangle_indices—The indexes of the vertices that make up the triangles
  • triangle_normals—The normal vectors of the triangles, needed to determine color
  • ab_vectors—Vectors used to test for ray-triangle intersection
  • ac_vectors—Vectors used to test for ray-triangle intersection
  • shared_point_indices—Indices of vertices used for point sharing to make an object smooth
  • is_smoothTrue if the mesh is smooth
  • vertex_normals—Normals at the vertices used to calculate colors if the object is smooth
  • texture_coords—Coordinates for applying a texture to the triangles, for example, to make them look like they're made out of brick or wood
[If you rotate a polygon's vertices, you also need to rotate its normal]

If you rotate the vertices, you don't need to change the indices, but you must rotate the vectors in the same way you rotate the vertices. For example, the picture on the right shows a rectangle and its surface normal before and after rotation. When you rotate the rectangle, you must also rotate its normal so it remains perpendicular to the rectangle.

To rotate an entire mesh, we need to rotate these lists:

  • vertices
  • triangle_normals
  • ab_vectors
  • ac_vectors
  • vertex_normals

Rotating Vectors

Fortunately, rotating a mesh's vectors is easy. The vertices and vectors are stored as Pygame Vector3 objects and that class has a rotate method that does the trick.

The following code shows a rotate method in the RtMesh class that rotates the mesh's vertices and vector around an axis.

def rotate(self, degrees, axis): '''Rotate the vertices and normals.''' self.vertices = [vertex.rotate(degrees, axis) for vertex in self.vertices] self.triangle_normals = [normal.rotate(degrees, axis) for normal in self.triangle_normals] self.ab_vectors = [vector.rotate(degrees, axis) for vector in self.ab_vectors] self.ac_vectors = [vector.rotate(degrees, axis) for vector in self.ac_vectors] self.vertex_normals = [normal.rotate(degrees, axis) for normal in self.vertex_normals]

To rotate the vertices, the code uses a list comprehension that loops through the vertices list and calls each point's rotate method.

The code uses the same kind of comprehension to rotate the mesh's vectors.

That's all there is to it!

Other Transformations

Translating a mesh should be even easier. Just translate the vertices. A translation moves the shape but doesn't change its orientation so its vectors don't need to be changed.

Unfortunately, scaling a shape isn't as simple. For example, imagine a mesh that holds a sphere made up of triangles. If you scale it so it becomes an ellipsoid, its surface normals change in non-obvious ways. I think the relationship isn't too complicated, but I haven't had time to look at the problem closely.

And none of these transformations work for shapes that aren't represented by polygons. For example, spheres and cylinders aren't defined by lists of points and vectors, so transforming them requires a different approach. Note that the new approach isn't necessarily hard. For example, you can rotate a cylinder by rotating its defining end points. That's doable but this example doesn't show how to do it.

Python Code

This example uses the code from my previous post to build a beveled cube. It then uses the following code to rotate it three times.

# Rotate around the Z axis. axis = Vector3(0, 0, 1) face_cube.rotate(45, axis) edge_cube.rotate(45, axis) corner_cube.rotate(45, axis) # Rotate around the X axis. axis = Vector3(1, 0, 0) face_cube.rotate(45, axis) edge_cube.rotate(45, axis) corner_cube.rotate(45, axis) # Rotate around the Y axis. axis = Vector3(0, 1, 0) face_cube.rotate(30, axis) edge_cube.rotate(30, axis) corner_cube.rotate(30, axis)

This code first rotates the cube by 45° around the Z axis, then by 45° around the X axis, and finally 30° around the Y axis, resulting in the picture at the top of this post.

Conclusion

[Build Your Own Ray Tracer With Python] Transforming arbitrary shapes in arbitrary ways could be difficult, but rotating a mesh made up of triangles isn't too hard. Translating a mesh would also be easy. Scaling a mesh would be a bit different, but in the worst case you could simply scale its points and then rebuild its triangle_normals, ab_vectors, ac_vectors, and vertex_normals lists by regenerating the vectors from the vertices.

See my book Build Your Own Ray Tracer With Python for more information about how the ray tracer works.

To see an overview of the book, a gallery of sample pictures, and code downloads, CLICK HERE.

To see the book on Amazon, CLICK HERE.

Download the example to experiment with it and to see additional details.

© 2025 Rocky Mountain Computer Consulting, Inc. All rights reserved.