Penrose-like sferne kupole: matematička konstrukcija i wireframe

Penrose-like sferne kupole: matematička konstrukcija i wireframe

Penrose-like kupole kombinuju kvazi-periodične tilinge sa zakrivljenim površinama, omogućavajući izuzetno estetske i interesantne 3D strukture. U ovoj sekciji opisujemo kako se Penrose-like raspored može projektovati na sferu ili hemisferu i kako se generiše wireframe mreža.

1. Matematika sfere

Kao i kod obične sfere, svaka tačka površine sa poluprečnikom \(R\) određena je sfernim koordinatama \((\theta, \phi)\):

\[ x = R \sin \theta \cos \phi, \quad y = R \sin \theta \sin \phi, \quad z = R \cos \theta \]

Za hemisferu koristimo \(\theta \in [0, \pi/2]\).

2. Kvazi-periodični Penrose tiling u 2D

Penrose tiling se sastoji od \( \text{thick} \) i \( \text{thin} \) rombova koji popunjavaju ravnu površinu kvazi-periodičnim rasporedom. Svaki romb se može iterativno subdividovati:

Subdivizija thick romba:

\[ \text{thick } (A,B,C,D) \rightarrow \{(A,B,P,D)_{\text{thick}}, (P,B,C,D)_{\text{thin}}\}, \quad P = A + \frac{C-A}{\phi} \]

Subdivizija thin romba:

\[ \text{thin } (A,B,C,D) \rightarrow \{(A,B,C,Q)_{\text{thin}}, (A,Q,C,D)_{\text{thick}}\}, \quad Q = B + \frac{D-B}{\phi} \]

Iteracijom ove procedure dobijamo kvazi-periodičan disk 2D tačaka.

3. Mapiranje 2D tilinga na sferu

Da bismo Penrose-like disk projektovali na površinu sfere, koristimo uniformno mapiranje radijalnog rastojanja na polarni ugao:

\[ \theta = \frac{r}{r_{\max}} \cdot \frac{\pi}{2}, \quad r = \sqrt{x^2 + y^2} \]

Azimut \(\phi\) ostaje isti kao u 2D disku:

\[ \phi = \arctan2(y, x) \]

3D koordinate dobijamo:

\[ x’ = R \sin\theta \cos\phi, \quad y’ = R \sin\theta \sin\phi, \quad z’ = R \cos\theta \]

Na ovaj način, Penrose-like tačke se ravnomerno raspoređuju na hemisfernoj površini.

4. Wireframe veza rombova

Da bi mreža bila prikazana šipkama, svaka tačka romba povezuje se sa susednim tačkama u rombu (horizontalno i vertikalno), formirajući trokutasti wireframe:

  • Svaka ivica romba postaje linija u 3D prostoru
  • Rezultat je kvazi-periodični 3D wireframe na hemisferi

5. Primene i vizualizacija

  • Arhitektura: kvazi-periodične kupole
  • Wireframe prikaz za vizualizaciju i 3D štampu
  • Estetski Penrose-like rasporedi na zakrivljenim površinama

Ova metoda kombinuje **matematičku preciznost** Penrose tilinga sa glatkom **sfernom geometrijom**, omogućavajući vizuelno interesantne strukture i praktičnu primenu u 3D modeliranju.


Programski kod za penrose_dome_wireframe.py:


#penrose_dome_wireframe.py
## Instalacija potrebnih zavisnosti: pip install numpy trimesh scipy


#!/usr/bin/env python3
"""
penrose_dome_wireframe.py
Generiše hemisferičnu kupolu (gornju polovinu sfere) sa Penrose-like šipkama.
"""

import numpy as np
import trimesh
from math import sin, cos, pi
from scipy.spatial import cKDTree

# -------------------- 1. Tačke na hemisferi --------------------

def fibonacci_hemisphere(samples=400, radius=1.0):
    """Ravnomerno raspoređene tačke po gornjoj hemisferi (y > 0)."""
    points = []
    offset = 1.0 / samples
    increment = pi * (3.0 - np.sqrt(5.0))  # zlatni ugao

    for i in range(samples):
        y = (i * offset)  # samo gornja polovina (0 do 1)
        y = 1 - 2 * y
        if y < 0:  # preskoči donju hemisferu
            continue
        r = np.sqrt(1 - y * y)
        phi = i * increment
        x = cos(phi) * r
        z = sin(phi) * r
        points.append((x * radius, y * radius, z * radius))
    return np.array(points)

# -------------------- 2. Povezivanje tačaka --------------------

def connect_nearest(points, k=4):
    """Povezuje svaku tačku sa k najbližih suseda (Penrose-like mreža)."""
    tree = cKDTree(points)
    edges = set()
    for i, p in enumerate(points):
        _, idxs = tree.query(p, k=k+1)
        for j in idxs[1:]:
            edge = tuple(sorted((i, j)))
            edges.add(edge)
    return list(edges)

# -------------------- 3. Kreiranje šipki --------------------

def make_cylinder(p1, p2, radius=0.01, sections=12):
    """Formira cilindar između dve tačke p1 i p2."""
    from trimesh.creation import cylinder
    p1 = np.array(p1)
    p2 = np.array(p2)
    vec = p2 - p1
    length = np.linalg.norm(vec)
    if length < 1e-8:
        return None

    cyl = cylinder(radius=radius, height=length, sections=sections)

    # Orijentacija duž vektora
    z_axis = np.array([0, 0, 1])
    axis = np.cross(z_axis, vec)
    angle = np.arccos(np.dot(z_axis, vec) / length)
    if np.linalg.norm(axis) > 1e-8:
        cyl.apply_transform(trimesh.transformations.rotation_matrix(angle, axis))

    # Pozicioniranje u sredinu
    mid = (p1 + p2) / 2
    cyl.apply_translation(mid)
    return cyl

# -------------------- 4. Generisanje kupole --------------------

def generate_penrose_dome(samples=400, k=3, dome_radius=1.0, thickness=0.02, color=(0.8, 0.4, 0.1)):
    """Generiše Penrose-like kupolu sa cilindarskim šipkama."""
    points = fibonacci_hemisphere(samples, radius=dome_radius)
    edges = connect_nearest(points, k=k)

    cylinders = []
    for (i, j) in edges:
        cyl = make_cylinder(points[i], points[j], radius=thickness)
        if cyl is not None:
            cylinders.append(cyl)

    mesh = trimesh.util.concatenate(cylinders)
    mesh.visual.vertex_colors = np.tile(np.array(color) * 255, (len(mesh.vertices), 1))
    return mesh

# -------------------- 5. Glavni deo programa --------------------

if __name__ == "__main__":
    dome_mesh = generate_penrose_dome(
        samples=600,       # broj tačaka po kupoli
        k=3,               # broj povezanih suseda
        dome_radius=4.0,   # poluprečnik kupole
        thickness=0.03,    # debljina šipki
        color=(0.9, 0.7, 0.1)  # topla zlatna boja
    )

    dome_mesh.export("penrose_dome_wireframe.obj")
    dome_mesh.export("penrose_dome_wireframe.ply")
    print("Generisano: penrose_dome_wireframe.obj i .ply (kupola sa šipkama).")


Programski kod za penrose_sphere_wireframe.py:


#penrose_sphere_wireframe.py
# Instalacija potrebnih zavisnosti:pip install numpy trimesh


#!/usr/bin/env python3
"""
penrose_sphere_wireframe.py
Stvara sferu sa Penrose-like mrežom šipki (cilindara) po površini.
"""

import numpy as np
import trimesh
from math import sin, cos, pi

def fibonacci_sphere(samples=500, radius=1.0):
    """Distribuiše tačke kvazi-jednako po sferi koristeći Fibonacci raspored."""
    points = []
    offset = 2.0 / samples
    increment = pi * (3.0 - np.sqrt(5.0))  # zlatni ugao

    for i in range(samples):
        y = ((i * offset) - 1) + (offset / 2)
        r = np.sqrt(1 - y * y)
        phi = i * increment
        x = cos(phi) * r
        z = sin(phi) * r
        points.append((x * radius, y * radius, z * radius))  # skalirano po radiusu
    return np.array(points)

from scipy.spatial import cKDTree

def connect_nearest(points, k=4):
    """Povezuje svaku tačku sa k najbližih suseda."""
    tree = cKDTree(points)
    edges = set()
    for i, p in enumerate(points):
        _, idxs = tree.query(p, k=k+1)
        for j in idxs[1:]:
            edge = tuple(sorted((i, j)))
            edges.add(edge)
    return list(edges)

def make_cylinder(p1, p2, radius=0.01, sections=12):
    """Formira cilindar između dve tačke p1 i p2."""
    from trimesh.creation import cylinder
    p1 = np.array(p1)
    p2 = np.array(p2)
    vec = p2 - p1
    length = np.linalg.norm(vec)
    if length < 1e-8:
        return None

    cyl = cylinder(radius=radius, height=length, sections=sections)

    # orijentacija
    z_axis = np.array([0, 0, 1])
    axis = np.cross(z_axis, vec)
    angle = np.arccos(np.dot(z_axis, vec) / length)
    if np.linalg.norm(axis) > 1e-8:
        cyl.apply_transform(trimesh.transformations.rotation_matrix(angle, axis))

    # pozicija
    mid = (p1 + p2) / 2
    cyl.apply_translation(mid)
    return cyl

def generate_penrose_sphere(samples=400, k=3, sphere_radius=1.0, thickness=0.015, color=(0.8, 0.5, 0.2)):
    """Generiše sferu sa šipkama kao cilindrima."""
    points = fibonacci_sphere(samples, radius=sphere_radius)
    edges = connect_nearest(points, k=k)

    cylinders = []
    for (i, j) in edges:
        cyl = make_cylinder(points[i], points[j], radius=thickness)
        if cyl is not None:
            cylinders.append(cyl)

    mesh = trimesh.util.concatenate(cylinders)
    mesh.visual.vertex_colors = np.tile(np.array(color) * 255, (len(mesh.vertices), 1))
    return mesh

if __name__ == "__main__":
    sphere_mesh = generate_penrose_sphere(
        samples=600,     # broj tačaka
        k=3,             # broj suseda
        sphere_radius=4, # <<< OVDE SE DEFINIŠE POLUPREČNIK SFERE
        thickness=0.03,  # debljina šipki
        color=(0.9, 0.6, 0.1)
    )

    sphere_mesh.export("penrose_sphere_wireframe.obj")
    sphere_mesh.export("penrose_sphere_wireframe.ply")
    print("Generisano: penrose_sphere_wireframe.obj i .ply sa cilindrima (šipkama).")

By Abel

Leave a Reply

Your email address will not be published. Required fields are marked *