Initializing 3D Canvas...

Dynamic Relaxation

2 min read1 page

How did architects design beautiful, swooping fabric roofs before computers existed? They built physical models using pantyhose or soap film. These materials naturally stretch into the most efficient shape possible, called a Minimal Surface. We can simulate this in a computer using a physics engine.

Geometry to Physics:

1. Dynamic Relaxation is an algorithm that pretends a 3D mesh is made of rubber bands. 2. Every single Edge in our polygon mesh is converted into a physical Spring. 3. Every single Vertex in our mesh is converted into a heavy Node. 4. By default, we set the "Rest Length" of every spring to exactly 0. This means every edge wants to shrink until it disappears!
python
1# Pretending Edges are Springs
2def initialize_mesh_network(mesh):
3 """Converting geometry into a physics simulation."""
4
5 # 1. Every Vertex becomes a Node (with mass and velocity)
6 for vertex in mesh.vertices:
7 vertex.velocity = Vector(0, 0, 0)
8 vertex.mass = 1.0
9
10 # 2. Every Edge becomes a Spring
11 for edge in mesh.edges:
12 edge.rest_length = 0.0 # We want it to shrink as small as possible!
13 edge.stiffness = 0.5
14
15 return mesh
Spring Tension (Low / High / Max)
0.00
3 min read1 page

To run our physics simulation, we must calculate the exact physical Force exerted by every single spring in the network. For this, we use a 350-year-old math formula: Hooke's Law.

Hooke's Law:

1. Look at a single edge (Spring). 2. Because we set the Rest Length to 0, the spring is currently stretched out. It wants to snap back together. 3. Hooke's Law states that the Force is equal to the Stiffness multiplied by the Stretch distance. 4. The longer the edge is, the harder it pulls! 5. This generates a 3D Force Vector: an arrow pointing along the edge, showing exactly how hard Node A is being pulled toward Node B.
python
1# Hooke's Law (F = kx)
2def calculate_spring_force(edge):
3 """How hard is the rubber band pulling back?"""
4
5 # 1. Look at the two Nodes connected by this Spring
6 node_A = edge.v1
7 node_B = edge.v2
8
9 # 2. Get the vector pointing from A to B
10 vector = node_B.position - node_A.position
11
12 # 3. How long is the spring right now?
13 current_length = length(vector)
14
15 # 4. How much is it stretched? (Remember, our rest_length is 0)
16 stretch = current_length - edge.rest_length
17
18 # 5. Calculate Force! (F = Stiffness * Stretch)
19 force_magnitude = edge.stiffness * stretch
20
21 # 6. Apply that force along the vector direction
22 force_vector = normalize(vector) * force_magnitude
23
24 return force_vector
Stretch Distance
3.00
2 min read1 page

A single Node (vertex) in our mesh might be connected to 4, 5, or 6 different Springs. This means it is being pulled in 6 different directions at the exact same time! To figure out where the Node will actually go, we must add all those forces together.

Vector Addition:

1. Look at a single Node. 2. Calculate the Force Vector (arrow) coming from Spring 1, and add it to the Node. 3. Calculate the arrow for Spring 2, and add it to the total. 4. Do this for all connected springs. 5. The mathematical sum of all those 3D arrows results in a single, final Resultant Force Vector. This arrow dictates the exact path the Node will take on the next frame!
python
1# Accumulating Forces
2def calculate_node_forces(mesh):
3 """Adding up all the tug-of-war forces."""
4
5 # 1. Reset all forces to 0 at the start of the frame
6 for node in mesh.vertices:
7 node.current_force = Vector(0, 0, 0)
8
9 # 2. Go through every spring in the mesh
10 for edge in mesh.edges:
11
12 # Calculate Hooke's Law for this spring
13 f_vector = calculate_spring_force(edge)
14
15 # 3. Apply the force to BOTH nodes!
16 # Node A gets pulled towards B
17 edge.v1.current_force += f_vector
18
19 # Node B gets pulled towards A (Newton's 3rd Law: Equal and opposite)
20 edge.v2.current_force -= f_vector
Forces (Individual / Resultant)
0.00
2 min read1 page

If we just applied spring forces and let the simulation run, our 3D mesh would bounce around endlessly like a jelly cube in zero gravity. It would never settle down into a clean shape!

Simulating Air Resistance:

1. To make the mesh settle down (relax), we must slowly drain the kinetic energy out of the system. 2. We do this by applying a Damping Force to every Node. 3. The math is incredibly simple: look at which way the Node is currently moving, and push in the exact opposite direction. 4. This acts just like air resistance or friction. The mesh will still bounce, but the bounces will get smaller and smaller until it freezes in the perfect Minimal Surface shape.
python
1# Adding Friction
2def apply_damping(node, damping_factor):
3 """Stopping the simulation from exploding."""
4
5 # 1. We look at the node's current velocity (how fast is it moving?)
6 velocity = node.velocity
7
8 # 2. We calculate a force pushing exactly in the OPPOSITE direction
9 # The faster the node is moving, the harder we push back!
10 damping_force = -velocity * damping_factor
11
12 # 3. Add this friction force to the node's total force
13 node.current_force += damping_force
Damping (None / Medium / High)
0.00
2 min read1 page

We have calculated all the forces (Springs + Damping). Now we need to actually move the vertices! To do this, we step forward in time using Numerical Integration.

Moving Forward in Time:

1. Time Step: We pretend the simulation runs in tiny slices of time (e.g., 0.01 seconds per frame). 2. Acceleration: We use Newton's Second Law (F = ma) to convert the Force we calculated into Acceleration. 3. Velocity: We add that Acceleration to the Node's current speed. 4. Position: Finally, we move the Node based on its new speed. 5. This is called the Forward Euler method. It is the core heartbeat of every physics engine, from video games to bridge engineering software.
python
1# Physics Integration (Moving Forward in Time)
2def euler_integration(node, time_step):
3 """Newton's Second Law: F = ma"""
4
5 # 1. Acceleration = Force / Mass
6 acceleration = node.current_force / node.mass
7
8 # 2. Update Velocity (How fast is it going now?)
9 # Velocity = Old Velocity + (Acceleration * Time)
10 node.velocity = node.velocity + (acceleration * time_step)
11
12 # 3. Update Position (Where is it now?)
13 # Position = Old Position + (Velocity * Time)
14 node.position = node.position + (node.velocity * time_step)
Time Step Size (Small / Medium / Explode)
1.00
2 min read1 page

If we turn on the physics simulation right now, every single spring will pull inward. The entire mesh will instantly collapse into a microscopic dot in the center of the screen!

Nailing it down:

1. To create a roof or a tent, the fabric must be tied down to the ground. 2. In code, we find all the vertices on the outer edge (Boundary) of the mesh. 3. We label these vertices as Anchors. 4. Inside our physics loop, right before we update positions, we force the velocity and forces of all Anchors to exactly 0. 5. The internal springs pull desperately trying to shrink, but the Anchors refuse to move. The resulting tension creates the Minimal Surface!
python
1# Freezing the Edges
2def apply_anchor_constraints(mesh):
3 """Nailing the corners to the floor."""
4
5 for vertex in mesh.vertices:
6
7 # 1. Is this vertex on the outer edge of the mesh?
8 if vertex.is_boundary:
9
10 # 2. If yes, it is an Anchor!
11 # We instantly set its Velocity to 0
12 vertex.velocity = Vector(0, 0, 0)
13
14 # 3. We instantly set its Force to 0
15 vertex.current_force = Vector(0, 0, 0)
16
17 # (Now the physics engine cannot move it, no matter how hard
18 # the internal springs pull on it!)
State (Initial / Pulling / Relaxed)
0.00
2 min read1 page

When we put all the pieces together in a loop, we have created a custom physics engine specifically designed to find the optimal geometry for tension structures.

Architectural Applications:

1. We start with a completely flat, dense grid of polygons. 2. We anchor a few points in the air (the masts), and anchor the outer edges to the ground. 3. We hit Play on the physics loop. 4. The internal springs pull tight. The flat grid smoothly curves upward into a Hyperbolic Paraboloid. 5. Once the forces balance out to zero, the mesh stops moving. The resulting shape is perfectly optimized for structural tension, identical to the famous roof of the Munich Olympic Stadium!
python
1# The Physics Engine Loop
2def solve_dynamic_relaxation(mesh, max_iterations=500):
3
4 # 1. Setup the Simulation
5 mesh = initialize_mesh_network(mesh)
6
7 # 2. Run the Physics Engine!
8 for i in range(max_iterations):
9
10 # Calculate Hooke's Law Forces
11 calculate_node_forces(mesh)
12
13 # Apply air friction
14 apply_damping(mesh, damping_factor=0.9)
15
16 # Freeze the outer edges
17 apply_anchor_constraints(mesh)
18
19 # Move forward in time (F=ma)
20 euler_integration(mesh, time_step=0.01)
21
22 return mesh
Physics Loop Time
0.00