# 금문교 (Golden Gate Bridge, 샌프란시스코, 1937) — 실제 치수(미터)로 만든 현수교 모델.
# 주경간 1,280m, 측경간 343m, 수면 위 주탑 높이 227m 를 기준으로 했다(바닥 z=0 이 수면).
# 주탑은 두 기둥과 가로보(포털 스트럿)를 bmesh 상자로 쌓아 단계적으로 가늘어지는 모양과
# 세로 홈(필라스터)을 표현했고, 한 개를 만든 뒤 메시를 공유하는 복제로 두 번째 주탑을 세웠다.
# 주케이블은 포물선 위의 점을 따라 원형 단면을 쓸어 만들었고, 행어 로프는 15.24m 간격으로 배열했다.
# 보강 트러스는 7.62m 패널마다 수직재·사재·하부 수평 브레이싱을 수학적으로 배치했다.
# 색은 인터내셔널 오렌지 도장, 콘크리트 교각과 앵커리지, 아스팔트 노면으로 단순화했다.
from atelier import *

SHOT = {"azimuth": -26, "elevation": 7, "lens": 55, "light": "golden", "background": "#1e2a3a"}

# ---------------------------------------------------------------- 재질
ORANGE = mat('International Orange', '#b8402a', rough=0.55, metal=0.15)
CABLE = mat('Cable Orange', '#a83a26', rough=0.5, metal=0.2)
CONCRETE = mat('Pier Concrete', '#a59f93', rough=0.85)
CONCRETE_DK = mat('Weathered Concrete', '#7d786f', rough=0.9)
ASPHALT = mat('Asphalt', '#3a3a3d', rough=0.9)
WALK = mat('Sidewalk Concrete', '#8f8a82', rough=0.85)
LAMP = mat('Sodium Lamp', '#f3c98a', rough=0.3, emit='#ffb860', emit_strength=3.0)
BEACON = mat('Aviation Beacon', '#c42020', rough=0.3, emit='#ff2a1a', emit_strength=6.0)

# ---------------------------------------------------------------- 치수
TOWER_X = 640.0            # 주경간 1280m 의 절반
ANCHOR_X = 990.0           # 측경간 끝(앵커리지 파일론)
PANEL = 7.62               # 트러스 패널 25ft
NPANEL = 136
DECK_END = NPANEL * PANEL  # 1036.32
CABLE_Y = 13.7             # 케이블 간격 90ft 의 절반
TOWER_TOP = 227.0
SADDLE_Z = 225.0


def road_z(x):
    """노면 높이: 가운데가 살짝 솟은 캠버, 앵커리지 위는 평평."""
    xa = min(abs(x), ANCHOR_X)
    return 72.0 + 3.0 * (1.0 - (xa / 1100.0) ** 2)


CABLE_LOW = road_z(0) + 4.0


def cable_z(x):
    xa = abs(x)
    if xa <= TOWER_X:
        return CABLE_LOW + (SADDLE_Z - CABLE_LOW) * (xa / TOWER_X) ** 2
    t = (xa - TOWER_X) / (ANCHOR_X - TOWER_X)
    z_end = road_z(ANCHOR_X) + 12.0
    return SADDLE_Z + (z_end - SADDLE_Z) * t - 10.0 * 4 * t * (1 - t)


# ---------------------------------------------------------------- bmesh 도우미
def bm_box(bm, center, size):
    bmesh.ops.create_cube(bm, size=1.0, matrix=Matrix.LocRotScale(Vector(center), None, Vector(size)))


def bm_beam(bm, p0, p1, w, h):
    """p0→p1 을 잇는 직사각 단면 부재(w: 수평 폭, h: 높이 방향)."""
    p0, p1 = Vector(p0), Vector(p1)
    d = (p1 - p0).normalized()
    ref = Vector((0, 0, 1)) if abs(d.z) < 0.95 else Vector((1, 0, 0))
    s = d.cross(ref).normalized()
    u = s.cross(d).normalized()
    vs = []
    for p in (p0, p1):
        for a, b in ((-1, -1), (1, -1), (1, 1), (-1, 1)):
            vs.append(bm.verts.new(p + s * (a * w / 2) + u * (b * h / 2)))
    f = [(0, 1, 2, 3), (7, 6, 5, 4), (0, 4, 5, 1), (1, 5, 6, 2), (2, 6, 7, 3), (3, 7, 4, 0)]
    for q in f:
        bm.faces.new([vs[i] for i in q])


def bm_tube(bm, pts, r, seg=12, caps=True):
    pts = [Vector(p) for p in pts]
    rings = []
    for i, p in enumerate(pts):
        t = (pts[min(i + 1, len(pts) - 1)] - pts[max(i - 1, 0)]).normalized()
        ref = Vector((0, 1, 0)) if abs(t.y) < 0.9 else Vector((1, 0, 0))
        n1 = t.cross(ref).normalized()
        n2 = t.cross(n1).normalized()
        rings.append([bm.verts.new(p + (n1 * math.cos(2 * math.pi * k / seg) + n2 * math.sin(2 * math.pi * k / seg)) * r)
                      for k in range(seg)])
    for a, b in zip(rings, rings[1:]):
        for k in range(seg):
            bm.faces.new((a[k], a[(k + 1) % seg], b[(k + 1) % seg], b[k]))
    if caps:
        bm.faces.new(rings[0])
        bm.faces.new(list(reversed(rings[-1])))


def bm_sweep(bm, xs, sec, zf=road_z):
    """YZ 단면(sec=[(y, dz)...])을 노면 곡선을 따라 X 로 쓸기."""
    rings = [[bm.verts.new((x, y, zf(x) + dz)) for y, dz in sec] for x in xs]
    n = len(sec)
    for a, b in zip(rings, rings[1:]):
        for j in range(n):
            bm.faces.new((a[j], b[j], b[(j + 1) % n], a[(j + 1) % n]))
    bm.faces.new(list(reversed(rings[0])))
    bm.faces.new(rings[-1])


def rect(y0, y1, z0, z1):
    return [(y0, z0), (y1, z0), (y1, z1), (y0, z1)]


def finish(name, bm, material, loc=(0, 0, 0)):
    bmesh.ops.recalc_face_normals(bm, faces=bm.faces)
    me = bpy.data.meshes.new(name)
    bm.to_mesh(me)
    bm.free()
    ob = bpy.data.objects.new(name, me)
    link(ob)
    ob.location = Vector(loc)
    return assign(ob, material)


# ---------------------------------------------------------------- 주탑
PIER_TOP = 13.0
LEG_BASE = 14.5
# (z0, z1, sx[교축 방향], sy[교폭 방향]) — 스트럿 높이마다 단을 줄인다
LEG_SEGS = [(LEG_BASE, 65.7, 16.0, 10.0), (65.7, 124.0, 14.0, 8.6), (124.0, 159.0, 12.6, 7.9),
            (159.0, 190.0, 11.2, 7.2), (190.0, 222.0, 10.0, 6.5)]
STRUTS = [(116.0, 124.0), (152.0, 159.0), (184.0, 190.0), (212.0, 222.0)]


def leg_size(z):
    for z0, z1, sx, sy in LEG_SEGS:
        if z0 <= z <= z1:
            return sx, sy
    return LEG_SEGS[-1][2], LEG_SEGS[-1][3]


def build_tower(name):
    bm = bmesh.new()
    for sgn in (-1, 1):
        yc = sgn * CABLE_Y
        for z0, z1, sx, sy in LEG_SEGS:
            h = z1 - z0
            bm_box(bm, (0, yc, z0 - 0.05 + (h + 0.05) / 2), (sx, sy, h + 0.05))
            # 세로 필라스터(셀 구조가 드러나는 홈 무늬)
            pz0, pz1 = z0 + 0.3, z1 - 0.6
            ph = pz1 - pz0
            for fx in (-1, 1):
                for oy in (-sy / 2 + 0.8, 0.0, sy / 2 - 0.8):
                    bm_box(bm, (fx * (sx / 2 + 0.12), yc + oy, pz0 + ph / 2), (0.5, 1.3, ph))
            for fy in (-1, 1):
                for ox in (-sx / 2 + 0.9, -sx / 6, sx / 6, sx / 2 - 0.9):
                    bm_box(bm, (ox, yc + fy * (sy / 2 + 0.12), pz0 + ph / 2), (1.3, 0.5, ph))
        # 꼭대기 새들 하우징: 상자 + 반원통 지붕
        bm_box(bm, (0, yc, 223.0), (9.0, 6.0, 2.1))
        bm_tube(bm, [(-4.5, yc, 224.0), (4.5, yc, 224.0)], 2.9, seg=20)
    # 포털 스트럿(노면 위 4단)
    for zb, zt in STRUTS:
        sx, sy = leg_size(zb + 0.1)
        yi = CABLE_Y - sy / 2 + 0.3
        depth = sx * 0.62
        h = zt - zb
        bm_box(bm, (0, 0, zb + h / 2), (depth, 2 * yi, h))
        # 위·아래 띠 몰딩
        for zz in (zb + 0.45, zt - 0.45):
            bm_box(bm, (0, 0, zz), (depth + 0.7, 2 * yi, 0.9))
        # 앞뒤 면의 세로 리브(아르데코 장식)
        nrib = 9
        for i in range(nrib):
            y = -yi + 2 * yi * (i + 0.5) / nrib
            for fx in (-1, 1):
                bm_box(bm, (fx * (depth / 2 + 0.15), y, zb + h / 2), (0.5, 0.7, h - 1.8))
    # 노면 아래 스트럿과 X 브레이싱
    sx, sy = leg_size(60)
    yi = CABLE_Y - sy / 2 + 0.5
    bm_box(bm, (0, 0, 61.7), (sx * 0.6, 2 * yi, 8.0))
    bm_box(bm, (0, 0, 61.7), (sx * 0.6 + 0.8, 2 * yi, 1.0))
    for sgn in (-1, 1):
        bm_beam(bm, (0, sgn * (yi + 1.2), LEG_BASE + 4.0), (0, -sgn * (yi + 1.2), 58.5), sx * 0.45, 2.6)
    # 기둥 밑 받침
    for sgn in (-1, 1):
        bm_box(bm, (0, sgn * CABLE_Y, PIER_TOP + 0.75), (18.5, 12.5, 1.6))
    return finish(name, bm, ORANGE)


def build_tower_beacons(name):
    bm = bmesh.new()
    for sgn in (-1, 1):
        bmesh.ops.create_uvsphere(bm, u_segments=10, v_segments=6, radius=0.7,
                                  matrix=Matrix.Translation((0, sgn * CABLE_Y, 226.9)))
    ob = finish(name, bm, BEACON)
    return smooth(ob)


def build_pier(name):
    bm = bmesh.new()
    # 교폭 방향으로 긴 둥근 직사각형 교각, 위로 약간 좁아진다
    pts = []
    rx, half = 15.0, 18.0
    for k in range(13):
        a = -math.pi / 2 + math.pi * k / 12
        pts.append((rx * math.cos(a), half + rx * math.sin(a)))
    for k in range(13):
        a = math.pi / 2 + math.pi * k / 12
        pts.append((rx * math.cos(a), -half + rx * math.sin(a)))
    bot = [bm.verts.new((x * 1.06, y * 1.03, 0)) for x, y in pts]
    top = [bm.verts.new((x, y, PIER_TOP)) for x, y in pts]
    bm.faces.new(list(reversed(bot)))
    bm.faces.new(top)
    n = len(pts)
    for i in range(n):
        bm.faces.new((bot[i], bot[(i + 1) % n], top[(i + 1) % n], top[i]))
    # 수면 근처의 넓은 기단
    bmesh.ops.create_cone(bm, cap_ends=True, segments=40, radius1=1, radius2=1, depth=2.5,
                          matrix=Matrix.LocRotScale((0, 0, 1.25), None, (18.5, 36.0, 1)))
    return finish(name, bm, CONCRETE)


def build_fender(name):
    """남쪽 주탑을 둘러싼 타원형 방현벽(펜더)."""
    bm = bmesh.new()
    seg = 64
    ax, ay, t, h = 48.0, 72.0, 5.0, 9.0
    outer_b, outer_t, inner_b, inner_t = [], [], [], []
    for k in range(seg):
        a = 2 * math.pi * k / seg
        c, s = math.cos(a), math.sin(a)
        outer_b.append(bm.verts.new((ax * c, ay * s, 0)))
        outer_t.append(bm.verts.new((ax * c, ay * s, h)))
        inner_b.append(bm.verts.new(((ax - t) * c, (ay - t) * s, 0)))
        inner_t.append(bm.verts.new(((ax - t) * c, (ay - t) * s, h)))
    for k in range(seg):
        j = (k + 1) % seg
        bm.faces.new((outer_b[k], outer_b[j], outer_t[j], outer_t[k]))
        bm.faces.new((inner_b[j], inner_b[k], inner_t[k], inner_t[j]))
        bm.faces.new((outer_t[k], outer_t[j], inner_t[j], inner_t[k]))
        bm.faces.new((outer_b[j], outer_b[k], inner_b[k], inner_b[j]))
    return finish(name, bm, CONCRETE_DK)


# ---------------------------------------------------------------- 보강 트러스와 노면
xs = [-DECK_END + i * PANEL for i in range(2 * NPANEL + 1)]
ZR_TOP = 0.6     # 상현재 윗면(노면 위로 올라와 연석 역할)
TC = (-1.2, ZR_TOP)
BC = (-8.25, -7.05)


def build_truss():
    bm = bmesh.new()
    for sgn in (-1, 1):
        y = sgn * CABLE_Y
        bm_sweep(bm, xs, rect(y - 0.55, y + 0.55, *TC))
        bm_sweep(bm, xs, rect(y - 0.55, y + 0.55, *BC))
        for i, x in enumerate(xs):
            zr = road_z(x)
            bm_beam(bm, (x, y, zr + BC[1] - 0.1), (x, y, zr + TC[0] + 0.1), 0.6, 0.6)
            if i + 1 < len(xs):
                x2 = xs[i + 1]
                if i % 2 == 0:
                    a, b = (x, y, zr + BC[1]), (x2, y, road_z(x2) + TC[0])
                else:
                    a, b = (x, y, zr + TC[0]), (x2, y, road_z(x2) + BC[1])
                bm_beam(bm, a, b, 0.5, 0.55)
    for i, x in enumerate(xs):
        zr = road_z(x)
        # 가로보
        bm_beam(bm, (x, -CABLE_Y, zr - 1.15), (x, CABLE_Y, zr - 1.15), 0.7, 1.3)
        bm_beam(bm, (x, -CABLE_Y, zr + BC[0] + 0.6), (x, CABLE_Y, zr + BC[0] + 0.6), 0.5, 0.5)
        # 하부 수평 브레이싱(X 자)
        if i + 1 < len(xs):
            x2 = xs[i + 1]
            z1, z2 = zr + BC[0] + 0.6, road_z(x2) + BC[0] + 0.6
            bm_beam(bm, (x, -CABLE_Y, z1), (x2, CABLE_Y, z2), 0.45, 0.35)
            bm_beam(bm, (x, CABLE_Y, z1), (x2, -CABLE_Y, z2), 0.45, 0.35)
    # 난간: 상현재 위 기둥 + 손잡이
    rx = [x for x in xs]
    for sgn in (-1, 1):
        y = sgn * (CABLE_Y + 0.25)
        bm_sweep(bm, xs, rect(y - 0.1, y + 0.1, 1.2, 1.35))
        for i in range(len(rx) - 1):
            for f in (0.0, 0.5):
                x = rx[i] + f * PANEL
                bm_box(bm, (x, y, road_z(x) + 0.9), (0.15, 0.15, 0.7))
    return finish('Deck_StiffeningTruss', bm, ORANGE)


def build_road():
    bm = bmesh.new()
    bm_sweep(bm, xs, rect(-13.2, 13.2, -0.55, 0.0))
    return finish('Deck_Roadway', bm, ASPHALT)


def build_walks():
    bm = bmesh.new()
    for sgn in (-1, 1):
        y0, y1 = sorted((sgn * 9.45, sgn * 13.15))
        bm_sweep(bm, xs, rect(y0, y1, -0.1, 0.25))
    # 가운데 이동식 분리대
    bm_sweep(bm, xs, [(-0.3, -0.1), (0.3, -0.1), (0.15, 0.8), (-0.15, 0.8)])
    return finish('Deck_Sidewalks_Median', bm, WALK)


# ---------------------------------------------------------------- 케이블과 행어
def cable_points(sgn_x):
    """한쪽 앵커리지에서 반대편 앵커리지까지의 케이블 중심선."""
    pts = []
    x = -ANCHOR_X - 7.0
    while x <= ANCHOR_X + 7.0 + 1e-6:
        xa = min(abs(x), ANCHOR_X)
        z = cable_z(xa) if abs(x) <= ANCHOR_X else cable_z(ANCHOR_X) - (abs(x) - ANCHOR_X) * 0.6
        pts.append((x, z))
        x += 5.0
    return pts


def suspender_xs():
    out = []
    k = 0.0
    xmax = ANCHOR_X - 12.0
    x = 0.0
    xsus = []
    while x < xmax:
        if abs(x - TOWER_X) > 10.0:
            xsus.append(x)
        x += 2 * PANEL
    for x in xsus:
        out.append(x)
        if x > 0:
            out.append(-x)
    return sorted(out)


def build_cables():
    bm = bmesh.new()
    prof = cable_points(1)
    for sgn in (-1, 1):
        y = sgn * CABLE_Y
        bm_tube(bm, [(x, y, z) for x, z in prof], 0.55, seg=14)
    return smooth(finish('Main_Cables', bm, CABLE))


def build_suspenders():
    bm = bmesh.new()
    for x in suspender_xs():
        zc = cable_z(x)
        zt = road_z(x) + ZR_TOP
        for sgn in (-1, 1):
            y = sgn * CABLE_Y
            # 한 점에 두 가닥씩
            for dx in (-0.3, 0.3):
                bm_tube(bm, [(x + dx, y, zt - 0.05), (x + dx, y, zc)], 0.12, seg=6)
            # 케이블 밴드
            xa = abs(x)
            dz = (cable_z(xa + 0.5) - cable_z(xa - 0.5)) * (1 if x >= 0 else -1)
            d = Vector((1.0, 0, dz)).normalized()
            c = Vector((x, y, zc))
            bm_tube(bm, [c - d * 0.9, c + d * 0.9], 0.78, seg=12)
    return finish('Suspender_Ropes', bm, CABLE)


# ---------------------------------------------------------------- 앵커리지와 파일론
ZA = road_z(ANCHOR_X)


def build_anchorage(name):
    """케이블을 묶어 두는 콘크리트 덩어리. 위로 갈수록 한 단씩 물러난다."""
    bm = bmesh.new()
    x0, x1 = -9.0, DECK_END - ANCHOR_X + 2.0
    top = ZA + BC[0] - 0.05
    L = x1 - x0
    cx = x0 + L / 2
    tiers = [(0.0, 10.0, L + 8.0, 58.0), (10.0, top - 9.0, L + 2.0, 52.0), (top - 9.0, top, L, 48.0)]
    for a, b, sx, sy in tiers:
        bm_box(bm, (cx, 0, (a + b) / 2 - 0.02), (sx, sy, b - a + 0.04))
    # 옆면의 세로 버트레스와 띠
    for sgn in (-1, 1):
        for i in range(5):
            x = x0 + 4.0 + (L - 8.0) * i / 4
            bm_box(bm, (x, sgn * 26.4, 10.0 + (top - 20.0) / 2), (3.2, 1.2, top - 20.0))
        bm_box(bm, (cx, sgn * 26.3, top - 10.0), (L + 3.0, 1.0, 1.6))
    for fx in (x0 - 1.0, x1 + 1.0):
        for i in range(5):
            y = -20 + 10 * i
            bm_box(bm, (fx + (0.4 if fx > cx else -0.4), y, 10.0 + (top - 20.0) / 2), (1.2, 3.2, top - 20.0))
        bm_box(bm, (fx, 0, top - 10.0), (1.0, 53.0, 1.6))
    return finish(name, bm, CONCRETE)


def build_pylon(name):
    """측경간 끝의 아르데코 콘크리트 파일론(케이블이 들어가는 곳)."""
    bm = bmesh.new()
    z0 = ZA + BC[0] - 0.1
    tiers = [(z0, ZA + 24.0, 16.0, 9.0), (ZA + 24.0, ZA + 29.0, 13.5, 7.6), (ZA + 29.0, ZA + 33.0, 11.0, 6.2),
             (ZA + 33.0, ZA + 35.5, 8.0, 4.8)]
    yc = CABLE_Y + 1.2
    for a, b, sx, sy in tiers:
        bm_box(bm, (0, yc, (a + b) / 2 - 0.02), (sx, sy, b - a + 0.04))
    for ox in (-5.0, 0.0, 5.0):
        for fy in (-1, 1):
            bm_box(bm, (ox, yc + fy * 4.62, (z0 + ZA + 22.0) / 2), (1.4, 0.4, ZA + 22.0 - z0))
        for fx in (-1, 1):
            bm_box(bm, (fx * 8.12, yc + ox * 0.55, (z0 + ZA + 22.0) / 2), (0.4, 1.4, ZA + 22.0 - z0))
    return finish(name, bm, CONCRETE)


# ---------------------------------------------------------------- 가로등
def build_lamp():
    bm = bmesh.new()
    bm_tube(bm, [(0, 0, 0), (0, 0, 8.5)], 0.13, seg=8)
    bm_tube(bm, [(0, 0, 8.4), (0, -1.6, 8.9)], 0.08, seg=6)
    bm_box(bm, (0, 0, 0.3), (0.45, 0.45, 0.6))
    ob = finish('Street_Lamp', bm, ORANGE)
    bm2 = bmesh.new()
    bm_box(bm2, (0, -1.9, 8.8), (0.5, 0.9, 0.3))
    head = finish('Street_Lamp_Head', bm2, LAMP)
    return ob, head


# ---------------------------------------------------------------- 조립
tower_s = build_tower('Tower_South')
tower_s.location.x = -TOWER_X
tower_n = duplicate(tower_s, loc=(TOWER_X, 0, 0), name='Tower_North')
beacon_s = build_tower_beacons('Tower_South_Beacons')
beacon_s.location.x = -TOWER_X
duplicate(beacon_s, loc=(TOWER_X, 0, 0), name='Tower_North_Beacons')

pier_s = build_pier('Pier_South')
pier_s.location.x = -TOWER_X
duplicate(pier_s, loc=(TOWER_X, 0, 0), name='Pier_North')
fender = build_fender('Pier_South_Fender')
fender.location.x = -TOWER_X

build_truss()
build_road()
build_walks()
build_cables()
build_suspenders()

anc = build_anchorage('Anchorage_North')
anc.location.x = ANCHOR_X
duplicate(anc, loc=(-ANCHOR_X, 0, 0), rot=(0, 0, math.pi), name='Anchorage_South')
pyl = build_pylon('Pylon_North_East')
pyl.location.x = ANCHOR_X + 6.0
for nm, loc, rz in (('Pylon_North_West', (ANCHOR_X + 6.0, 0, 0), math.pi),
                    ('Pylon_South_East', (-ANCHOR_X - 6.0, 0, 0), 0.0),
                    ('Pylon_South_West', (-ANCHOR_X - 6.0, 0, 0), math.pi)):
    p = duplicate(pyl, loc=loc, name=nm)
    if rz:
        p.scale = (1, -1, 1)

pole, head = build_lamp()
lamp_x = []
x = -ANCHOR_X + 20.0
while x < ANCHOR_X - 15.0:
    if abs(abs(x) - TOWER_X) > 14.0:
        lamp_x.append(x)
    x += 6 * PANEL
first = True
for x in lamp_x:
    for sgn in (-1, 1):
        loc = (x + 1.9, sgn * (CABLE_Y + 0.25), road_z(x) + ZR_TOP)
        rot = (0, 0, 0 if sgn > 0 else math.pi)
        if first:
            pole.location, pole.rotation_euler = loc, rot
            head.location, head.rotation_euler = loc, rot
            first = False
        else:
            duplicate(pole, loc=loc, rot=rot)
            duplicate(head, loc=loc, rot=rot)

