# 아토미움 (Atomium, 브뤼셀, 1958)
# 철 결정의 체심입방 단위세포를 1650억 배로 키운 구조물. 지름 18m 구 9개를
# 정육면체 모서리 12개 + 중심-꼭짓점 대각선 8개, 모두 20개의 관으로 잇고,
# 정육면체를 꼭짓점 하나로 세워 몸체 대각선이 수직이 되게 했다.
# 구는 매끈한 UV 구 위에 측지 삼각 분할(icosphere)의 이음매를 bmesh 와이어프레임으로 얹어
# 2006년 리노베이션의 스테인리스 삼각 패널을 표현했고, 관·구·패널은 linked 복제로 메시를 공유한다.
# 아래쪽 세 구를 받치는 A자형 이각 지지대 3쌍, 바닥 구 아래 엘리베이터 기둥과 입구 파빌리온까지 만들었다.
from atelier import *

SHOT = {"azimuth": -78, "elevation": 8, "lens": 50, "light": "day", "background": "#34404f"}

# ---- 치수 (미터) ----
R_S = 9.0            # 구 반지름 (지름 18m)
R_T = 1.65           # 관 반지름 (지름 3.3m)
EDGE = 41.0          # 정육면체 모서리(구 중심 간)
HALF_D = EDGE * math.sqrt(3) / 2      # 중심-꼭짓점 거리
RHO = EDGE * math.sqrt(2 / 3)         # 고리 꼭짓점의 수평 반지름
Z_BOT = 102.0 - R_S - 2 * HALF_D      # 맨 아래 구 중심 높이 (꼭대기 102m)
Z_C = Z_BOT + HALF_D                  # 가운데 구 중심

# ---- 재질 ----
M_STEEL = mat('StainlessPanel', '#cdd2d7', rough=0.2, metal=1.0)
M_SEAM = mat('PanelSeam', '#5f666e', rough=0.45, metal=1.0)
M_TUBE = mat('TubeSteel', '#b9bfc5', rough=0.24, metal=1.0)
M_COLLAR = mat('CollarSteel', '#9da4ab', rough=0.3, metal=1.0)
M_WIN = mat('PortholeGlass', '#1d2832', rough=0.06, metal=0.2)
M_BAND = mat('PanoramaGlass', '#27384a', rough=0.05, metal=0.3)
M_LEG = mat('BipodSteel', '#8f969d', rough=0.42, metal=0.7)
M_CONC = mat('Concrete', '#a8a397', rough=0.88)
M_PAV = mat('PavilionGlass', '#3b4c5a', rough=0.08, metal=0.3)
M_PAVF = mat('PavilionFrame', '#5d636a', rough=0.5, metal=0.6)

Z = Vector((0, 0, 1))


def orient(ob, a, b):
    """Z 축 기준으로 만든 오브젝트를 a→b 방향으로 세운다."""
    d = (Vector(b) - Vector(a)).normalized()
    ob.rotation_mode = 'QUATERNION'
    ob.rotation_quaternion = Z.rotation_difference(d)
    ob.location = Vector(a)
    return ob


# ---- 꼭짓점(구 중심) 9개 ----
C = Vector((0, 0, Z_C))
TOP = C + Vector((0, 0, HALF_D))
BOT = C - Vector((0, 0, HALF_D))
UP = [C + Vector((RHO * math.cos(math.radians(a)), RHO * math.sin(math.radians(a)), HALF_D / 3)) for a in (30, 150, 270)]
LO = [C + Vector((RHO * math.cos(math.radians(a)), RHO * math.sin(math.radians(a)), -HALF_D / 3)) for a in (90, 210, 330)]
NODES = {'Top': TOP, 'Center': C, 'Bottom': BOT}
for i, p in enumerate(UP):
    NODES['Upper%d' % (i + 1)] = p
for i, p in enumerate(LO):
    NODES['Lower%d' % (i + 1)] = p

EDGES = [('Top', 'Upper%d' % (i + 1)) for i in range(3)] + [('Bottom', 'Lower%d' % (i + 1)) for i in range(3)]
for i, u in enumerate(UP):
    for j, l in enumerate(LO):
        if abs((u - l).length - EDGE) < 0.5:
            EDGES.append(('Upper%d' % (i + 1), 'Lower%d' % (j + 1)))
DIAGS = [('Center', k) for k in NODES if k != 'Center']


# ---- 구: 매끈한 몸체 + 삼각 패널 이음매 + 둥근 창 ----
def make_sphere_parts():
    body = sphere('Sphere_Body', r=R_S, seg=72, rings=36, material=M_STEEL)

    bm = bmesh.new()
    bmesh.ops.create_icosphere(bm, subdivisions=3, radius=R_S + 0.03)
    centers = [(f.calc_center_median().copy()) for f in bm.faces]
    bmesh.ops.wireframe(bm, faces=bm.faces[:], thickness=0.13, offset=0.0, use_replace=True,
                        use_even_offset=True, use_relative_offset=False, use_boundary=True)
    seams = _bm_obj('Sphere_PanelSeams', bm, M_SEAM)

    # 둥근 창: 적도 위아래 띠의 패널 중앙
    bm = bmesh.new()
    for c in centers:
        n = c.normalized()
        lat = math.degrees(math.asin(max(-1, min(1, n.z))))
        lon = math.degrees(math.atan2(n.y, n.x))
        if -14 < lat < 10 and int((lon + 180) // 15) % 2 == 0:
            _disc(bm, n, R_S - 0.05, R_S + 0.06, 0.38, 16)
    wins = _bm_obj('Sphere_Portholes', bm, M_WIN)
    return body, seams, wins


def _bm_obj(name, bm, material):
    me = bpy.data.meshes.new(name)
    bm.to_mesh(me)
    bm.free()
    ob = bpy.data.objects.new(name, me)
    link(ob)
    return assign(ob, material)


def _disc(bm, n, r0, r1, rad, seg):
    """방향 n 을 축으로 하는 짧은 원기둥(창)."""
    q = Z.rotation_difference(n)
    ring0, ring1 = [], []
    for k in range(seg):
        a = 2 * math.pi * k / seg
        v = q @ Vector((rad * math.cos(a), rad * math.sin(a), 0))
        ring0.append(bm.verts.new(n * r0 + v))
        ring1.append(bm.verts.new(n * r1 + v))
    bm.faces.new(list(reversed(ring0)))
    bm.faces.new(ring1)
    for k in range(seg):
        bm.faces.new((ring0[k], ring0[(k + 1) % seg], ring1[(k + 1) % seg], ring1[k]))


body, seams, wins = make_sphere_parts()
used = set()


def _inst(ob):
    if ob.name in used:
        return duplicate(ob)
    used.add(ob.name)
    return ob


for name, p in NODES.items():
    parts = [(body, 'Body'), (seams, 'PanelSeams')]
    if name != 'Top':  # 맨 위 구는 둥근 창 대신 전망 유리띠
        parts.append((wins, 'Portholes'))
    for src, suf in parts:
        o = _inst(src)
        o.location = p
        o['label'] = 'Sphere_%s_%s' % (name, suf)
for ob in list(bpy.context.scene.objects):
    if 'label' in ob:
        ob.name = ob['label']
        del ob['label']

# 맨 위 구의 전망대 유리띠 (구면을 따라 도는 띠)
band_prof = []
for k in range(9):
    lat = math.radians(-6 + 20 * k / 8)
    band_prof.append(((R_S + 0.015) * math.cos(lat), (R_S + 0.015) * math.sin(lat)))
lathe('Sphere_Top_PanoramaWindows', band_prof, loc=TOP, material=M_BAND, seg=96)


# ---- 관(20개) + 이음 고리 ----
def tube_profile(length, pitch=4.0):
    """관 단면 프로필: 일정 간격마다 살짝 도드라진 패널 이음 띠."""
    prof = [(0.0, 0.0), (R_T, 0.0)]
    n = max(1, int(length / pitch))
    step = length / n
    for k in range(1, n):
        z = k * step
        prof += [(R_T, z - 0.16), (R_T, z - 0.1), (R_T + 0.025, z - 0.07), (R_T + 0.025, z + 0.07),
                 (R_T, z + 0.1), (R_T, z + 0.16)]
    prof += [(R_T, length), (0.0, length)]
    return prof


def tube_between(name, a, b, mesh_cache):
    d = (b - a)
    L = d.length
    u = d.normalized()
    inset = math.sqrt(R_S ** 2 - R_T ** 2) - 0.25
    p0 = a + u * inset
    length = L - 2 * inset
    key = round(length, 2)
    if key not in mesh_cache:
        t = lathe('Tube', tube_profile(length), material=M_TUBE, seg=40)
        mesh_cache[key] = t
        ob = t
    else:
        ob = duplicate(mesh_cache[key])
    ob.name = 'Tube_' + name
    orient(ob, p0, b)
    # 구와 만나는 고리 (양 끝)
    for end, (pa, sgn) in enumerate(((a, 1), (b, -1))):
        if 'collar' not in mesh_cache:
            cl = cylinder('Collar', r=R_T + 0.35, h=1.1, seg=40, material=M_COLLAR)
            smooth(cl)
            mesh_cache['collar'] = cl
            co = cl
        else:
            co = duplicate(mesh_cache['collar'])
        co.name = 'TubeCollar_%s_%d' % (name, end)
        start = pa + u * sgn * (math.sqrt(R_S ** 2 - (R_T + 0.35) ** 2) - 0.2)
        orient(co, start, start + u * sgn)
    return ob


cache = {}
for a, b in EDGES + DIAGS:
    tube_between('%s-%s' % (a, b), NODES[a], NODES[b], cache)

# ---- 바닥 구 아래 엘리베이터 기둥 ----
shaft_top = BOT.z - math.sqrt(R_S ** 2 - R_T ** 2) + 0.25
shaft = lathe('ElevatorShaft', tube_profile(shaft_top), material=M_TUBE, seg=40)

# ---- 이각 지지대 3쌍 (아래쪽 세 구를 받침) ----
def leg(name, foot, top, w0=3.0, d0=2.2, w1=1.9, d1=1.5):
    bm = bmesh.new()
    bot = [bm.verts.new(v) for v in ((-w0 / 2, -d0 / 2, 0), (w0 / 2, -d0 / 2, 0), (w0 / 2, d0 / 2, 0), (-w0 / 2, d0 / 2, 0))]
    L = (top - foot).length
    tp = [bm.verts.new(v) for v in ((-w1 / 2, -d1 / 2, L), (w1 / 2, -d1 / 2, L), (w1 / 2, d1 / 2, L), (-w1 / 2, d1 / 2, L))]
    bm.faces.new(list(reversed(bot)))
    bm.faces.new(tp)
    for k in range(4):
        bm.faces.new((bot[k], bot[(k + 1) % 4], tp[(k + 1) % 4], tp[k]))
    ob = _bm_obj(name, bm, M_LEG)
    orient(ob, foot, top)
    bevel(ob, 0.12, 2)
    return ob


for i, p in enumerate(LO):
    radial = Vector((p.x, p.y, 0)).normalized()
    tang = Vector((-radial.y, radial.x, 0))
    apex = Vector((p.x, p.y, 0)) + radial * 1.0 + Vector((0, 0, p.z - R_S + 0.6))
    for s in (-1, 1):
        foot = Vector((p.x, p.y, 0)) + radial * 5.5 + tang * s * 11.0
        leg('Bipod%d_Leg%s' % (i + 1, 'L' if s < 0 else 'R'), foot, apex)
        pad = box('Bipod%d_Footing%s' % (i + 1, 'L' if s < 0 else 'R'), size=(4.2, 4.2, 0.8), loc=(foot.x, foot.y, 0), base=True, material=M_CONC)
        pad.rotation_euler.z = math.atan2(radial.y, radial.x)
    # 지지대 꼭대기와 구 사이 받침
    sad = cylinder('Bipod%d_Saddle' % (i + 1), r=2.4, h=2.4, r2=1.6, seg=24, loc=(apex.x, apex.y, apex.z - 1.6), material=M_LEG)
    smooth(sad)

# ---- 입구 파빌리온 (바닥 구 아래) ----
pav_r = 7.5
cylinder('Pavilion_Plinth', r=pav_r + 1.0, h=0.6, seg=48, material=M_CONC)
cylinder('Pavilion_Glass', r=pav_r, h=4.2, seg=48, loc=(0, 0, 0.6), material=M_PAV)
cylinder('Pavilion_Roof', r=pav_r + 0.6, h=0.7, seg=48, loc=(0, 0, 4.8), material=M_PAVF)
mull = box('Pavilion_Mullion', size=(0.18, 0.25, 4.2), loc=(pav_r + 0.05, 0, 0.6), base=True, material=M_PAVF)
ring_of(mull, 24, pav_r + 0.05)
