Dynamic Relaxation
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# Pretending Edges are Springs2def initialize_mesh_network(mesh):3 """Converting geometry into a physics simulation."""45 # 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.0910 # 2. Every Edge becomes a Spring11 for edge in mesh.edges:12 edge.rest_length = 0.0 # We want it to shrink as small as possible!13 edge.stiffness = 0.51415 return mesh
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# Hooke's Law (F = kx)2def calculate_spring_force(edge):3 """How hard is the rubber band pulling back?"""45 # 1. Look at the two Nodes connected by this Spring6 node_A = edge.v17 node_B = edge.v289 # 2. Get the vector pointing from A to B10 vector = node_B.position - node_A.position1112 # 3. How long is the spring right now?13 current_length = length(vector)1415 # 4. How much is it stretched? (Remember, our rest_length is 0)16 stretch = current_length - edge.rest_length1718 # 5. Calculate Force! (F = Stiffness * Stretch)19 force_magnitude = edge.stiffness * stretch2021 # 6. Apply that force along the vector direction22 force_vector = normalize(vector) * force_magnitude2324 return force_vector
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# Accumulating Forces2def calculate_node_forces(mesh):3 """Adding up all the tug-of-war forces."""45 # 1. Reset all forces to 0 at the start of the frame6 for node in mesh.vertices:7 node.current_force = Vector(0, 0, 0)89 # 2. Go through every spring in the mesh10 for edge in mesh.edges:1112 # Calculate Hooke's Law for this spring13 f_vector = calculate_spring_force(edge)1415 # 3. Apply the force to BOTH nodes!16 # Node A gets pulled towards B17 edge.v1.current_force += f_vector1819 # Node B gets pulled towards A (Newton's 3rd Law: Equal and opposite)20 edge.v2.current_force -= f_vector
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# Adding Friction2def apply_damping(node, damping_factor):3 """Stopping the simulation from exploding."""45 # 1. We look at the node's current velocity (how fast is it moving?)6 velocity = node.velocity78 # 2. We calculate a force pushing exactly in the OPPOSITE direction9 # The faster the node is moving, the harder we push back!10 damping_force = -velocity * damping_factor1112 # 3. Add this friction force to the node's total force13 node.current_force += damping_force
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# Physics Integration (Moving Forward in Time)2def euler_integration(node, time_step):3 """Newton's Second Law: F = ma"""45 # 1. Acceleration = Force / Mass6 acceleration = node.current_force / node.mass78 # 2. Update Velocity (How fast is it going now?)9 # Velocity = Old Velocity + (Acceleration * Time)10 node.velocity = node.velocity + (acceleration * time_step)1112 # 3. Update Position (Where is it now?)13 # Position = Old Position + (Velocity * Time)14 node.position = node.position + (node.velocity * time_step)
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:
0. 5. The internal springs pull desperately trying to shrink, but the Anchors refuse to move. The resulting tension creates the Minimal Surface!1# Freezing the Edges2def apply_anchor_constraints(mesh):3 """Nailing the corners to the floor."""45 for vertex in mesh.vertices:67 # 1. Is this vertex on the outer edge of the mesh?8 if vertex.is_boundary:910 # 2. If yes, it is an Anchor!11 # We instantly set its Velocity to 012 vertex.velocity = Vector(0, 0, 0)1314 # 3. We instantly set its Force to 015 vertex.current_force = Vector(0, 0, 0)1617 # (Now the physics engine cannot move it, no matter how hard18 # the internal springs pull on it!)
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# The Physics Engine Loop2def solve_dynamic_relaxation(mesh, max_iterations=500):34 # 1. Setup the Simulation5 mesh = initialize_mesh_network(mesh)67 # 2. Run the Physics Engine!8 for i in range(max_iterations):910 # Calculate Hooke's Law Forces11 calculate_node_forces(mesh)1213 # Apply air friction14 apply_damping(mesh, damping_factor=0.9)1516 # Freeze the outer edges17 apply_anchor_constraints(mesh)1819 # Move forward in time (F=ma)20 euler_integration(mesh, time_step=0.01)2122 return mesh