Seongsu-CELL

Botanical Cultural Complex Proposal, Seongsu-dong

period2024.03–2024.06 · follow-up development 2026.02
location89 Yeonmujang-gil, Seongdong-gu, Seoul, South Korea
projectIndividual work · fourth-year architectural design studio
statusUnbuilt academic proposal · follow-up design development
goalSpatial organization of a botanical cultural complex using 3D Voronoi cells
resultSeed-point arrangement rules, optimization model, and 3D Power Diagram component

Seongsu-CELL is an individual project exploring how 3D Voronoi cells can organize a botanical cultural complex. In February 2026, the project added a custom Power Diagram to reconsider the spatial and structural hierarchy of those cells. The 2024 proposal developed its scheme through seed-point arrangement rules and an annual solar-radiation search. It left unresolved how to adjust cell sizes and floor plates for different programs, and how to translate complex boundaries into structural and construction logic. The follow-up work revisited these problems by controlling cell sizes through weights.

GARLIC.jpeg
cell04.jpg

Cellular Tissue and Three Programmatic Layers

The repeated divisions observed in a garlic cross-section provided the initial visual reference. The spatial organization drew from the interaction between cell arrangements, the roles of different tissues, and nutrient-transport pathways within a dicot stem.

The project translated this relationship into three programmatic layers. Core cells accommodate vertical circulation and services. Garden cells form the main interior spaces. Perimeter cells correspond to the epidermal tissue and accommodate visitor circulation and event spaces. The design translated the division of roles between tissues into a spatial principle; it did not attempt a literal biological replica.

cell01.jpeg
cell02.jpeg

Seed-Point Arrangements for Architectural Space

Freely placed seed points generate formally varied cells, but they do not reliably secure minimum floor area, usable floor slopes, or floor-to-floor height. The study first compared how distances and arrangements between seed points affected these three conditions.

voros.png
DIA_01.jpeg
BPA 01 02.jpeg
DIA_02.jpeg

The left diagram shows the sectional rule, which aligns seed points vertically according to each floor level. Fixing their heights and vertical stacks established regular floors and cell heights. The right diagram shows the radial rule, which positions seed points within three concentric bands. The spacing between bands secured minimum floor area, while different movement ranges adjusted the relative sizes of the core, garden, and perimeter cells. The center model combines both rules.

When plan positions differed from floor to floor, the vertical stacks became misaligned and the floor plates tilted. It was possible to retain only plates within an allowable slope, but adjusting every combination manually was slow and lacked a consistent selection criterion. This condition was therefore included in the Galapagos search.


Spiral Organization and Performance Search

BPA 02 01.jpeg
BPA 02 02.jpeg
BPA 02 03.jpeg

The left model shows the initial configuration based on the vertical and radial rules. In the center model, the garden and perimeter cells wrap around the core and rise in two-storey increments. This stage established the visitor route that connects the spaces through ramps and stairs. The right model shows the result of using Galapagos to fine-tune the seed points in plan while preserving these relationships.

scriptureWB.jpeg

Cell generation, floor-plate evaluation, and performance analysis were assembled within a single Grasshopper definition. The seed-point heights, three programmatic layers, and spiral arrangement remained fixed. Only the plan positions of the seed points varied within their radial bands.

For each option, the system checked floor area and normal vectors to identify slabs that met the minimum-area and allowable-slope criteria. It then combined this validity result with the annual solar radiation received by the valid floor plates into a single fitness value for Galapagos to maximize. The search sought seed-point arrangements that provided both usable floors and solar performance.


Two Peaks Across 132 Generations

GAL_01.jpeg
GAL_02.jpeg
GAL_03.jpeg

Rapid convergence on a single peak was not the sole objective. To compare spatial possibilities emerging at different peaks, the input resolution was reduced to widen the search intervals. A logarithmic transformation and digit truncation were applied to the fitness value so that minor differences within the high-scoring range would not dominate the search.

The Galapagos Inbreeding setting was also adjusted so that mating would not concentrate only among genomes with similar gene values. This left room for different genetic combinations to participate and made it possible to observe local optima that might otherwise be obscured by a single family of solutions. The setting did not guarantee diversity, but it allowed forms outside the performance ranking to remain available as design alternatives.

The 132-generation record showed two distinct peaks. One local optimum first appeared in Generation 15, disappeared from the population, and reappeared in Generation 50. Another lineage improved after Generation 25 and held the highest fitness from Generation 73 onward.

OPT_01.jpeg
OPT_02.jpeg
OPT_03.jpeg

The solutions converging on the highest peak distributed valid floors and solar performance evenly. The local optimum at the other peak scored lower but produced more pronounced mid-level projections and a clearer spatial hierarchy. The final proposal developed from the latter form.

palns.png
TEMain.jpeg

The Gap Between Generated Cells and Architecture

The original proposal constrained Voronoi irregularity through arrangement rules and performance criteria, but it could not control the scale of each programmatic cell independently. Cell sizes were determined indirectly by the distances between seed points, while changes in their plan positions altered floor slopes.

Voronoi edges defined spatial boundaries but did not guarantee stable load paths or repeatable joints. The original proposal concluded as a studio project without resolving how to translate its branching frames into rational members and connections.

The 2026 follow-up began by treating cell size as an independent parameter and connecting the spatial and structural hierarchies.


A 3D Power Diagram for Weighted Cells

In February 2026, a 3D Power Diagram was implemented in Python for use in Grasshopper, allowing each seed point to receive a different size parameter.

PD_py_01.jpeg
PD_py_02.jpeg
PD_py_03.jpeg
PD_py_04.jpeg

For an ordinary Voronoi diagram, Delaunay Triangulation determines neighboring relationships from seed-point positions alone. A Power Diagram uses Regular Triangulation, a weighted Delaunay subdivision that accounts for both position and weight. The size parameter can therefore adjust the relative scale and adjacency of cells within the same seed-point arrangement.

The component squares the input size parameter R and uses it as the power weight. Each seed point is lifted to the four-dimensional coordinate (x, y, z, x²+y²+z²−R²), and the lower convex hull provides the tetrahedral connections of the Regular Triangulation. The power centers of these tetrahedra become the cell vertices, which are assembled and clipped by the specified boundary to produce 3D cell meshes.

The component retains the input-output workflow of the existing 3D Voronoi component and adds the size-parameter list. This allowed it to connect to the existing Grasshopper definition without rebuilding the workflow.


Spatial Hierarchy Through Weighting

001.jpeg
002.jpeg
012.jpeg

The left model is the follow-up baseline, generated after rearranging the seed points but before applying differentiated weights. The center model applies weights according to program and floor level. The right model distinguishes the resulting spatial and structural hierarchies.

Cells within the same radial layer and floor shared a weight range, while the core, garden, and perimeter cells received different ranges. The seed-point heights remained fixed, and only these weights became Galapagos variables. The ranges preserved the three-layer programmatic hierarchy under every combination, and the result with the highest annual-solar-radiation fitness was used for the development model.

Adjusting cell sizes without moving the seed points in plan kept the floor plates generally horizontal. It also preserved the original spiral arrangement of the garden and perimeter spaces around the core. The core and perimeter cells form the structural framework and accommodate service and event spaces, while the larger cells between them contain the botanical garden. The three-layer relationship derived from the plant stem became explicit as a programmatic and spatial hierarchy.

slb+str.gif

Three Spatial Layers

slb_01.jpg
slb_02.jpg
slb_03.jpg

Core cells accommodate the elevator halls and service circulation at every level. The garden cells form the double-height main spaces, with their floors lowered below the surrounding circulation to provide sufficient soil depth. Perimeter cells contain single-storey visitor and interactive circulation as well as event spaces.

Core cells occur at every level, while the garden and perimeter spaces connect in two-storey increments. A spiral segment beginning at Level 1 meets the next segment at Level 3, which then continues to Level 5. Ramps and stairs connect these segments. The intermediate core and perimeter cells act as mezzanines adjoining the double-height garden spaces, providing access and views from different elevations.

interial_shot.jpeg
power_main_01.jpeg

Structural Planning from Spatial Hierarchy

str_01.jpg
str_02.jpg
str_03.jpg

Weights controlled both cell size and directionality. Reducing the weights of the perimeter cells while increasing those of adjacent cells narrowed the perimeter cells horizontally and preserved their vertical extent. This produced elongated cells with a strong vertical anisotropy around the perimeter. The vertically elongated cells simplify load paths and translate the arrangement of the plant stem’s epidermal cells into structural form.

The structure was organized in three tiers. Primary vertical members follow the axes of the core and perimeter cells. Secondary vertical members support the double-height garden cells and supplement the intended load path for planting loads. Horizontal ties connect the vertical members to control lateral deformation and torsion, directing loads toward the primary members at the core and perimeter.

A separate Galapagos search simplified the perimeter frame. Nearby vertices and edges within a set tolerance were merged, while the heights and weights of the perimeter seed points became variables. The search maintained the allowable range of floor slopes while minimizing the remaining edge count, aiming to reduce the number of members and increase repetition.


Development Model and Remaining Work

The weighted cells address the original proposal’s difficulty in controlling spatial scale and horizontal floor plates. The current result organizes programmatic and structural hierarchies within the same model.

Structural analysis and member calculations have not yet been conducted. Member sizing and joint design, together with the integration of drainage and building services into the planted spaces, remain unresolved. Converting the Voronoi-generated form into buildable architecture remains the next task for Seongsu-CELL.


Power Diagram - Grasshopper python component Script

1#r: numpy
2#r: scipy
3import numpy as np
4from scipy.spatial import ConvexHull
5import Rhino.Geometry as rg
6import System
7import System.Threading.Tasks as tasks
8
9'''
10= 3D Weighted Voronoi Algorithm - Power Diagram =
11- Rhino8 Grasshopper, Python 3 Script Component -
12
13[Component Input]
14- P (List Access, Point3d): Voronoi Seed Points
15- R (List Access, float): Weights of Seed Points
16- B (Item Access, Brep): Voronoi Boundary
17
18[Component Output]
19- M (Mesh): Voronoi Cells
20- D (Curve): Regular Triangulation
21
22Last edit by. HelixHo 02/16/2026
23'''
24
25### Global Tolerance
26tol = 0.001
27
28### Parallel Operations ProcessorCount
29options = tasks.ParallelOptions()
30options.MaxDegreeOfParallelism = System.Environment.ProcessorCount - 2
31
32###### Calculate simplex 3D Volume (simplex validation)
33def get_simplex_volume_3d(pts_4d):
34    # pts_4d: [[x,y,z,w], [x,y,z,w], [x,y,z,w], [x,y,z,w]] (4x4 array)
35    pts_3d = pts_4d[:, :3] 
36    v1 = pts_3d[1] - pts_3d[0]
37    v2 = pts_3d[2] - pts_3d[0]
38    v3 = pts_3d[3] - pts_3d[0]
39    matrix = np.array([v1, v2, v3])
40    
41    return abs(np.linalg.det(matrix)) / 6.0
42
43###### Calculate Power Center (vertices of Weighted Voronoi)
44# ∣∣X-P_i∣∣^2 − w_i = ∣∣X-P_j∣∣^2 − w_j
45# (x-x_i)^2 + (y-y_i)^2 + (z-z_i)^2 - w_i = (x-x_j)^2 + (y-y_j)^2 + (z-z_j)^2 - w_j
46# 2x(x_j-x_i) + 2y(y_j-y_i) + 2z(z_j-z_i) = (x_j^2 + y_j^2 + z_j^2) - (x_i^2 + y_i^2 + z_i^2) - (w_j-w_i)
47# 2X⋅(P_i-P_j) = ∣∣P_i∣∣^2 − ∣∣P_j∣∣^2 − (w_i−w_j)
48def solve_power_center(pts, weights):
49    A, B = [], []
50    p0, w0 = pts[0], weights[0]
51    
52    for i in range(1, 4):
53        pi, wi = pts[i], weights[i]
54        A.append([2 * (pi[j] - p0[j]) for j in range(3)])
55        B.append(np.sum(pi**2) - np.sum(p0**2) - (wi - w0))
56    
57    try:
58        res = np.linalg.solve(np.array(A, dtype=float), np.array(B, dtype=float))
59        return rg.Point3d(float(res[0]), float(res[1]), float(res[2]))
60    except:
61        return None
62
63###### Parallel Operations Section: Individual Task Functions
64# Generate Voronoi Cell (polyhedron)
65def construct_single_cell(i, simplices, p_centers, B_mesh, move_back, tol):
66    try:
67        # i: Index of Seed Point (from. Input: P)
68        # Vertex extraction
69        sim_indices = [idx for idx, sim in enumerate(simplices) if i in sim]
70        if len(sim_indices) < 4: return None
71        v_pts = [p_centers[idx] for idx in sim_indices if p_centers[idx] is not None]
72        if len(v_pts) < 4: return None
73        
74        # Vertex integration stabilization (rounding to 7th digit)
75        snapped = [rg.Point3d(round(v.X, 7), round(v.Y, 7), round(v.Z, 7)) for v in v_pts]
76        unique_v = list(dict.fromkeys(snapped))
77        if len(unique_v) < 4: return None
78
79        # Create Meshes
80        temp_mesh = rg.Mesh.CreateConvexHull3D(unique_v, tol, tol)
81        if not temp_mesh: return None
82        cell_mesh = temp_mesh[0]
83
84        # Trim with Voronoi border (from. Input: B)
85        intersections = rg.Mesh.CreateBooleanIntersection([cell_mesh], [B_mesh])
86        
87        if intersections and len(intersections) > 0:
88            res_mesh = intersections[0]
89            res_mesh.Transform(move_back)
90            return res_mesh
91
92    except Exception as e:
93        return None
94    return None
95
96###### Generate Bounding Seed Points
97def get_26_anchors(bbox, scale=5.0):
98    big_box = bbox
99    big_box.Inflate(bbox.Diagonal.Length * scale)
100    
101    corners = []
102    for x in [0, 0.5, 1]:
103        for y in [0, 0.5, 1]:
104            for z in [0, 0.5, 1]:
105                if x == 0.5 and y == 0.5 and z == 0.5: continue
106                corners.append(big_box.PointAt(x, y, z))
107    return corners
108
109############################################################
110### MAIN ### 3D Weighted Voronoi (POWER DIAGRAM) Algorithm
111############################################################
112def main():
113    if not P or not R or not B: 
114        return [], None
115    
116    ### --- 1. Data Set
117    W = [r**2 for r in R]
118
119    anchor_pts = get_26_anchors(rg.BoundingBox(P), 2.0)
120    total_pts = P + anchor_pts
121    total_ws = W + [0.0] * len(anchor_pts)
122
123    ## Move closer to World Origin
124    pts_center = rg.BoundingBox(total_pts).Center
125    move_back = rg.Transform.Translation(rg.Vector3d(pts_center))
126    P_local = [p - pts_center for p in total_pts]
127
128    move_matrix = rg.Transform.Translation(-rg.Vector3d(pts_center))
129    B_local = B.DuplicateBrep()
130    B_local.Transform(move_matrix)
131
132    ## Convert to numpy.array
133    np_pts = np.array([[p.X, p.Y, p.Z] for p in P_local], dtype=float)
134    np_ws = np.array(total_ws, dtype=float)
135    
136    ## Normalize Input Data
137    max_coord = np.max(np.abs(np_pts))
138    if max_coord == 0:
139        max_coord = 1.0
140    np_pts_norm = np_pts / max_coord
141    np_ws_norm = np_ws / (max_coord**2)
142
143    ### --- 2. 4D Lifting & Convex Hull
144    # 4th Coord = x^2+y^2+z+2-w, w=r^2
145    lifted_coord = np.sum(np_pts_norm**2, axis=1) - np_ws_norm
146    lifted = np.column_stack((np_pts_norm, lifted_coord))
147    
148    hull = ConvexHull(lifted, qhull_options='QJ')
149    
150    ## Select Lower Hull & Simplex Validation
151    # hull.equations: numpy.list(A B C D E) of Ax+By+Cz+Dw+E = 0
152    lower_hull_indices = []
153    for i, eq in enumerate(hull.equations):
154        if eq[3] < -1e-7:
155            pts_4d = lifted[hull.simplices[i]]
156            vol = get_simplex_volume_3d(pts_4d)
157            if abs(vol) > 1e-7:
158                lower_hull_indices.append(i)
159
160    # hull.simplices: tetrahedron on 4D
161    # simplices: 3D Regular Triangulation (tetrahedron)
162    simplices = [hull.simplices[idx] for idx in lower_hull_indices]
163
164    ### --- 3. Get Power Center of each simplices
165    p_centers = []
166    for sim in simplices:
167        center_norm = solve_power_center(np_pts_norm[sim], np_ws_norm[sim])
168        # Return to original coord
169        if center_norm is not None:
170            p_centers.append(center_norm * max_coord)
171        else:
172            p_centers.append(None)
173    
174    ### --- 4. Generate Voronoi Cell
175    ## Convert Voronoi border(Brep) to Mesh (from. Input: B)
176    mp = rg.MeshingParameters.Default
177    raw_meshes = rg.Mesh.CreateFromBrep(B_local, mp)
178    final_bd_mesh = rg.Mesh()
179    for m in raw_meshes:
180        final_bd_mesh.Append(m)
181    final_bd_mesh.Vertices.CombineIdentical(True, True)
182    final_bd_mesh.Compact()
183    final_bd_mesh.Normals.ComputeNormals()
184
185    ## Parallel Operations (Generate Voronoi Cell)
186    num_pts = len(P)
187    result_cells = [None] * num_pts
188
189    def parallel_body(i):
190        result_cells[i] = construct_single_cell(i, simplices, p_centers, final_bd_mesh, move_back, tol)
191
192    tasks.Parallel.For(0, num_pts, options, parallel_body)
193
194    ### --- 5. Extract 3D Regular Triangulation (WireFrame)
195    d_lines = []
196    edges = set()
197
198    num_input_pts = len(P)
199
200    ## Possible edge combinations from a tetrahedron (6 total)
201    # (0,1), (0,2), (0,3), (1,2), (1,3), (2,3)
202    for sim in simplices:
203        combinations = [
204            (sim[0], sim[1]), (sim[0], sim[2]), (sim[0], sim[3]),
205            (sim[1], sim[2]), (sim[1], sim[3]), (sim[2], sim[3])
206        ]
207        
208        for start, end in combinations:
209            s_idx = int(start)
210            e_idx = int(end)
211            if s_idx < num_input_pts and e_idx < num_input_pts:
212                edge = tuple(sorted((s_idx, e_idx)))
213                edges.add(edge)
214
215    ## Convert Edge to Rhino Line
216    for start_idx, end_idx in edges:
217        line = rg.Line(total_pts[start_idx], total_pts[end_idx])
218        d_lines.append(line)
219
220    return result_cells, d_lines
221
222############################################################
223### EXECUTE ### 
224############################################################
225
226M, D = main()