Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Functional Icosahedron

FSL is a powerful functional language that allows for procedural generation of complex geometry. This example demonstrates how to construct a Platonic solid (Icosahedron) using vector mathematics and functional programming patterns (Map/Reduce), rather than hardcoding tier definitions.

The Concept

An Icosahedron has 20 faces. The face normals correspond to the vertices of a Dodecahedron. Instead of manually calculating angles and indices for each face, we:

  1. Define the 20 vectors of a Dodecahedron mathematically.
  2. Convert these vectors into Faceting Machine coordinates (Index, Angle, Side).
  3. Execute the cuts using a loop.

This approach is resolution-independent. Since the angles involve the Golden Ratio ($\phi$), they are irrational and do not align perfectly with standard gears (like 96). To demonstrate the precision of FSL, we use a very high gear setting (360,000) to approximate these angles nearly perfectly.

The Code

// Functional Icosahedron Generator
// Demonstrates: Vector Geometry, Map/Reduce, and Dynamic Tiers

// --- Configuration ---
// We use a high gear to minimize rounding errors for irrational angles
gear = 360000
width = 1.0       // Distance from center to face

// Golden Ratio
phi = (1 + Math.sqrt(5)) / 2

// --- Helper Functions ---

// Flatten an array of arrays
flatten = (arr) => arr.reduce((acc, val) => concat(acc, val), [])

// Map a function over an array and flatten the result
flatMap = (arr, fn) => flatten(arr.map(fn))

// Generate 3 cyclic permutations of a vector [x, y, z]
permute = (v) => [
  [v[0], v[1], v[2]],
  [v[1], v[2], v[0]],
  [v[2], v[0], v[1]]
]

// Convert a Cartesian Vector to Machine Coordinates [Index, Angle, Side]
toMachine = (v) => {
  x = v[0]
  y = v[1]
  z = v[2]
  mag = Math.sqrt(x*x + y*y + z*z)

  // Azimuth (Index)
  // atan2 returns radians -PI to PI
  az_rad = Math.atan2(y, x)
  az_norm = az_rad < 0 ? az_rad + 2 * Math.PI : az_rad

  // Map to gear index
  idx_raw = (az_norm / (2 * Math.PI)) * gear
  idx = Math.round(idx_raw) % gear

  // Elevation (Angle)
  // angle from XY plane (Equator)
  lat_rad = Math.asin(z / mag)
  lat_deg = lat_rad * 180 / Math.PI

  // Convert to Faceting Angle (0 = Top/Pole, 90 = Girdle)
  // We mirror bottom hemisphere angles to match 0-90 range
  final_angle = 90 - Math.abs(lat_deg)

  // Determine side. Note: Use semicolons to avoid array indexing ambiguity.
  cutSide = cond(z >= 0, () => "up", () => "down");

  [idx, final_angle, cutSide]
}

// --- Geometry Generation ---

pm = [-1, 1] // Plus/Minus variations

// Group 1: Cube vertices (+-1, +-1, +-1)
// Cartesian product of pm, pm, pm
g1 = flatMap(pm, x =>
       flatMap(pm, y =>
         pm.map(z => [x, y, z])
       )
     )

// Group 2: Cyclic permutations of (0, +-1/phi, +-phi)
invPhi = 1 / phi
g2_base = flatMap(pm, y =>
            pm.map(z => [0, y * invPhi, z * phi])
          )

// Apply permutations (x,y,z) -> (y,z,x) -> (z,x,y)
g2 = flatMap(g2_base, v => permute(v))

// Combine all 20 vectors
vectors = concat(g1, g2)

// Convert to machine commands
commands = vectors.map(toMachine)

// Separate by side
pavilion = commands.filter(c => c[2] == "down")
crown = commands.filter(c => c[2] == "up")

// --- Execution ---

print("Generating Icosahedron...")
print("Total Faces: ", commands.length)

// 1. Cut Pavilion (Side defaults to Down)
print("Cutting Pavilion (", pavilion.length, " faces)")
setSide("down")
pavilion.forEach(cmd => {
  // cut(angle, index, tier_name, depth)
  cut(cmd[1], cmd[0], "Pavilion", width)
})

// 2. Cut Crown (Switch to Up)
print("Cutting Crown (", crown.length, " faces)")
setSide("up")
crown.forEach(cmd => {
  cut(cmd[1], cmd[0], "Crown", width)
})

print("Done!")

Explanation

  1. Vector Generation: We mathematically define the 20 normal vectors of an Icosahedron.
    • 8 from the corners of a cube: $(\pm 1, \pm 1, \pm 1)$
    • 12 from the "roof" permutations: $(0, \pm \frac{1}{\phi}, \pm \phi)$
  2. Coordinate Transformation: The toMachine function handles the complex math of converting a 3D vector into the Index (azimuth) and Angle (elevation) required by the faceting machine.
  3. Side Switching: We use setSide("up") and setSide("down") to control the orientation of the machine before executing the cuts using the low-level cut() function.

This script generates a highly precise Icosahedron by utilizing a high-resolution gear (360,000) to approximate the irrational angles inherent in the Golden Ratio geometry.