Tesseract (Hypercube)#

A tesseract is a four-dimensional cube. A tesseract can be unfolded into eight cubes, Just as a cube can be unfolded into eight squares.

First, import some useful functions

import itertools

import numpy as np

import fury

Let’s define some variables and their descriptions:

Use wireframe = True to show wireframe like representation of the tesseract wireframe = False will render it with point actor on each vertex.

wireframe = False

# p_color: color of the point actor (default: (0, 0.5, 1, 1))
# e_color: color of the line actor (default: (1, 1, 1, 1))
# dtheta: change in `angle` on each iteration. It determines the "speed" of the
#        animation. Increase dtheta to increase speed of rotation, It may
#        result in less smoother rotation (default: 0.02)
# angle: defines the angle to be rotated to perform the animation, It changes
#        as we run the `callback` method later on. (initial value: 0)

p_color = np.array([0, 0.5, 1, 1])
e_color = np.array([1, 1, 1, 1])
dtheta = 0.02
angle = 0

Let’s define vertices for our 4D cube, verts4D contains the coordinates of our 4D tesseract.

verts3D = np.array(
    [
        [1, 1, 1],
        [1, -1, 1],
        [-1, -1, 1],
        [-1, 1, 1],
        [-1, 1, -1],
        [1, 1, -1],
        [1, -1, -1],
        [-1, -1, -1],
    ]
)

# We can use primitive.box alternatively to get the cube's 3-D vertices.

u = np.insert(verts3D, 3, 1, axis=1)
v = np.insert(verts3D, 3, -1, axis=1)
verts4D = np.append(u, v, axis=0)

We define a rotate4D function that takes 4D matrix as parameter and rotates it in XY plane (Z axis) and ZW plane (an imaginary axis), projects it to the 3D plane so that we can render it in a scene.

def rotate4D(verts4D):
    cos = np.cos(angle)
    sin = np.sin(angle)
    rotation4d_xy = np.array(
        [[cos, -sin, 0, 0], [sin, cos, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]]
    )
    rotation4d_zw = np.array(
        [[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, cos, -sin], [0, 0, sin, cos]]
    )
    distance = 2
    projected_marix = np.zeros((16, 3))
    for i, vert in enumerate(verts4D):
        rotated_3D = np.dot(rotation4d_xy, vert)
        rotated_3D = np.dot(rotation4d_zw, rotated_3D)
        w = 1 / (distance - rotated_3D[3])
        proj_mat4D = np.array([[w, 0, 0, 0], [0, w, 0, 0], [0, 0, w, 0]])

        projeced_mat3D = np.dot(proj_mat4D, rotated_3D)
        projected_marix[i] = projeced_mat3D  # vertices to be proj (3D)
    return projected_marix

Now, We have 4D points projected to 3D. Let’s define a function to connect lines.

def connect_points(verts3D):
    lines = np.array([])
    len_vert = len(verts3D)

    for i in range(len_vert - 1):
        if i < 8:
            lines = np.append(lines, [verts3D[i], verts3D[i + 8]])
        if i == 7:
            pass
        else:
            lines = np.append(lines, [verts3D[i], verts3D[i + 1]])
        if i % 4 == 0:
            lines = np.append(lines, [verts3D[i], verts3D[i + 3]])

    for i in range(3):
        lines = np.append(lines, [verts3D[i], verts3D[i + 5]])
        lines = np.append(lines, [verts3D[i + 8], verts3D[i + 5 + 8]])

    return np.reshape(lines, (-1, 2, 3))

Creating a scene object and configuring the camera’s position

scene = fury.window.Scene()
scene.set_camera(
    position=(0, 10, -1), focal_point=(0.0, 0.0, 0.0), view_up=(0.0, 0.0, 0.0)
)
showm = fury.window.ShowManager(scene=scene, size=(1920, 1080), order_transparent=True)

Creating vertices and points actors

verts3D = rotate4D(verts4D)
if not wireframe:
    points = fury.actor.point(verts3D, colors=p_color)
    point_verts = fury.utils.vertices_from_actor(points)
    no_vertices = len(point_verts) / 16
    initial_verts = point_verts.copy() - np.repeat(verts3D, no_vertices, axis=0)

    scene.add(points)

Connecting points with lines actor

lines = connect_points(verts3D)
edges = fury.actor.line(
    lines=lines, colors=e_color, lod=False, fake_tube=True, linewidth=4
)
lines_verts = fury.utils.vertices_from_actor(edges)
initial_lines = lines_verts.copy() - np.reshape(lines, (-1, 3))

scene.add(edges)

Initializing text box to display the name

tb = fury.ui.TextBlock2D(text="Tesseract", position=(900, 950), font_size=20)
showm.scene.add(tb)

Define a timer_callback in which we’ll update the vertices of point and lines actor using rotate4D.

counter = itertools.count()
end = 200


def timer_callback(_obj, _event):
    global verts3D, angle
    cnt = next(counter)
    verts3D = rotate4D(verts4D)
    if not wireframe:
        point_verts[:] = initial_verts + np.repeat(verts3D, no_vertices, axis=0)
        fury.utils.update_actor(points)

    lines = connect_points(verts3D)
    lines_verts[:] = initial_lines + np.reshape(lines, (-1, 3))
    fury.utils.update_actor(edges)

    showm.render()
    angle += dtheta

    if cnt == end:
        showm.exit()

Run every 20 milliseconds

showm.add_timer_callback(True, 20, timer_callback)
showm.start()
fury.window.record(scene=showm.scene, size=(600, 600), out_path="viz_tesseract.png")

Gallery generated by Sphinx-Gallery