# 앵무조개(Nautilus pompilius) 껍데기 — 실제 크기(지름 약 20cm).
# 껍데기는 로그 나선(한 바퀴에 반지름이 약 3.1배)을 따라 단면을 쓸어 만든 한 장의 bmesh 곡면이다.
# 단면은 배 쪽이 둥글고 배꼽 쪽이 넓은 달걀꼴이며, 바깥 소용돌이가 안쪽 소용돌이를 완전히 감싸(포권) 배꼽이 닫힌다.
# 호랑이 줄무늬는 텍스처 없이 면마다 재질 번호를 수식(나선 각·단면 위치·고정 난수)으로 골라 칠했다.
# 입구 쪽에는 껍데기 두께, 진주층 안쪽 면, 마지막 격벽과 연실관 구멍, 검은 층(black layer)을 넣었다.
import random
from atelier import *

random.seed(7)

SHOT = {"azimuth": -50, "elevation": 14, "lens": 70, "light": "studio", "background": "#1e2a33"}

# ---------------------------------------------------------------- 재질
M_SHELL = mat('Shell_Porcelain', '#ece0c6', rough=0.28, coat=0.5)
M_STRIPE = mat('Stripe_RustBrown', '#a2552c', rough=0.3, coat=0.5)
M_STRIPE_DK = mat('Stripe_DarkUmber', '#6f311a', rough=0.3, coat=0.5)
M_NACRE = mat('Nacre_Pearl', '#ddd3cb', rough=0.16, metal=0.45)
M_BLACK = mat('Black_Layer', '#2e2622', rough=0.35)
M_CALLUS = mat('Umbilical_Callus', '#d9cdb8', rough=0.25, coat=0.4)
M_SIPHO = mat('Siphuncle_Dark', '#4a3a2e', rough=0.6)
MATS = [M_SHELL, M_STRIPE, M_STRIPE_DK, M_NACRE, M_BLACK, M_CALLUS, M_SIPHO]
SHELL, STRIPE, STRIPE_DK, NACRE, BLACK, CALLUS, SIPHO = range(7)

# ---------------------------------------------------------------- 나선 매개변수
W = 3.1                         # 소용돌이 확장률(한 바퀴당 반지름 배율)
B = math.log(W) / (2 * math.pi)
R0 = 0.128                      # 입구 배 쪽까지의 반지름(m) → 지름 ≈ 0.2m
TH = 0.0                        # 입구의 나선 각(기준)
C, A = 0.525, 0.475             # 단면 중심 / 반경 방향 반축 (R 배)
LH = 0.36                       # 옆 방향 반폭 (R 배)
T = 0.0016                      # 입구 껍데기 두께(m)
NPHI = 144
BODY = 2.1                      # 주거실(마지막 격벽~입구) 길이(rad)


def R(th):
    return R0 * math.exp(B * (th - TH))


def section(th, phi, inset=0.0, g=0.0):
    """나선 각 th, 단면 각 phi(0=배 쪽, π=배꼽 쪽) 의 점. inset 만큼 안쪽으로 줄인 면, g 는 성장선 부풀림."""
    r = R(th)
    a = (A * r - inset) * (1 + g)
    lh = (LH * r - inset) * (1 + g)
    cp, sp = math.cos(phi), math.sin(phi)
    rad = C * r + a * cp
    lat = lh * sp * (1.0 - 0.26 * cp) / 1.05
    return Vector((rad * math.cos(th), lat, rad * math.sin(th)))


def sec_center(th):
    r = R(th)
    return Vector((C * r * math.cos(th), 0.0, C * r * math.sin(th)))


def wrap(phi):
    return (phi + math.pi) % (2 * math.pi) - math.pi


def margin_shift(th, phi):
    """입구 가장자리 굴곡: 옆면은 앞으로, 배 쪽은 깔때기 홈(hyponomic sinus)으로 오목."""
    ramp = min(max((th - (TH - 0.35)) / 0.35, 0.0), 1.0)
    ramp = ramp * ramp * (3 - 2 * ramp)
    pw = wrap(phi)
    d = 0.09 * math.sin(phi) ** 2 * (1 - 0.3 * math.cos(phi)) - 0.13 * math.exp(-(pw / 0.42) ** 2)
    return th + ramp * d


def growth(th, phi):
    """성장선: 입구 가장자리 모양을 따라가는 잔물결(배꼽 쪽은 약하게)."""
    pw = wrap(phi)
    d = 0.09 * math.sin(phi) ** 2 - 0.13 * math.exp(-(pw / 0.42) ** 2)
    q = (th + d) * 26.0
    w = 0.5 + 0.5 * math.cos(q + 0.6 * math.sin(q * 0.37))
    return 0.0045 * w ** 6 * (0.4 + 0.6 * (1 + math.cos(phi)) / 2)


# 나선 각 표본: 감춰진 안쪽 소용돌이는 성기게, 보이는 바깥 소용돌이는 촘촘히
ths = []
t = TH - 6 * math.pi
while t < TH - 2.4 * math.pi:
    ths.append(t)
    t += 1 / 22
t = TH - 2.4 * math.pi
n_fine = int(2.4 * math.pi * 135)
for i in range(n_fine + 1):
    ths.append(TH - 2.4 * math.pi + 2.4 * math.pi * i / n_fine)
phis = [2 * math.pi * j / NPHI for j in range(NPHI)]

# 줄무늬: 줄마다 고정 난수(길이·폭·흔들림)
N_STRIPES_PER_TURN = 19
stripe_rand = {}


def srand(n):
    if n not in stripe_rand:
        stripe_rand[n] = (random.uniform(-0.25, 0.3), random.uniform(0.8, 1.15), random.uniform(0, 6.28))
    return stripe_rand[n]


def outer_color(th, phi):
    u = math.cos(phi)                       # +1 배 쪽, -1 배꼽 쪽
    dlt = TH - th                           # 입구로부터의 각 거리
    # 안쪽 소용돌이 가운데 주거실 안에 드러나는 배 쪽: 검은 층과 진주층
    if dlt > 2 * math.pi - 0.12 and u > 0.15:
        return BLACK if dlt < 2 * math.pi + 0.45 else NACRE
    if u < -0.93:
        return CALLUS
    # 줄무늬
    k = N_STRIPES_PER_TURN / (2 * math.pi)
    s = (u + 1) / 2                         # 0 배꼽 → 1 배
    p0 = th * k + 1.5 * s ** 1.3
    n = math.floor(p0)
    ext, wid, ph = srand(n)
    p = p0 + 0.07 * math.sin(9 * s + ph) + 0.05 * math.sin(23 * s + 2 * ph)
    n = math.floor(p)
    ext, wid, ph = srand(n)
    width = (0.55 - 0.12 * s) * wid
    fade = min(max((dlt - 0.55) / 2.2, 0.0), 1.0)    # 입구 쪽으로 갈수록 줄이 짧아진다
    reach = -0.35 + 1.75 * fade + ext
    if dlt < 0.55 or s > reach:
        return SHELL
    # 끝부분은 가늘어지며 사라짐
    taper = min(1.0, (reach - s) / 0.18)
    if (p - n) < width * (0.35 + 0.65 * taper):
        return STRIPE_DK if s < 0.28 else STRIPE
    return SHELL


# ---------------------------------------------------------------- 껍데기 메시
bm = bmesh.new()
outer = []
for th in ths:
    outer.append([bm.verts.new(section(margin_shift(th, ph), ph, g=growth(th, ph))) for ph in phis])
faces_out = []
for i in range(len(ths) - 1):
    thm = 0.5 * (ths[i] + ths[i + 1])
    for j in range(NPHI):
        j2 = (j + 1) % NPHI
        f = bm.faces.new((outer[i][j], outer[i][j2], outer[i + 1][j2], outer[i + 1][j]))
        f.material_index = outer_color(thm, (j + 0.5) * 2 * math.pi / NPHI)
        faces_out.append(f)

# 안쪽(진주층) 면: 마지막 격벽부터 입구까지
i_sept = next(i for i, th in enumerate(ths) if th >= TH - BODY)
inner = []
for th in ths[i_sept:]:
    inner.append([bm.verts.new(section(margin_shift(th, ph), ph, inset=T)) for ph in phis])
faces_in = []
for i in range(len(inner) - 1):
    for j in range(NPHI):
        j2 = (j + 1) % NPHI
        f = bm.faces.new((inner[i][j], inner[i + 1][j], inner[i + 1][j2], inner[i][j2]))
        f.material_index = NACRE
        faces_in.append(f)

# 입구 테두리(두께)
for j in range(NPHI):
    j2 = (j + 1) % NPHI
    f = bm.faces.new((outer[-1][j], outer[-1][j2], inner[-1][j2], inner[-1][j]))
    f.material_index = SHELL

# 마지막 격벽: 입구 쪽으로 오목한 접시, 가운데 연실관 구멍
th_s = ths[i_sept]
cen = sec_center(th_s)
tan = Vector((-math.sin(th_s), 0, math.cos(th_s))) + B * Vector((math.cos(th_s), 0, math.sin(th_s)))
tan.normalize()
depth = 0.32 * A * R(th_s)
K = 14
prev = inner[0]
for k in range(1, K + 1):
    s = 1.0 - k / K * 0.94
    ring = []
    for j, ph in enumerate(phis):
        p0 = inner[0][j].co
        ring.append(bm.verts.new(cen + (p0 - cen) * s - tan * depth * (1 - s * s)))
    for j in range(NPHI):
        j2 = (j + 1) % NPHI
        f = bm.faces.new((prev[j], prev[j2], ring[j2], ring[j]))
        f.material_index = SIPHO if s < 0.14 else NACRE
    prev = ring
fc = bm.faces.new(list(reversed(prev)))
fc.material_index = SIPHO

me = bpy.data.meshes.new('Nautilus_Shell')
bm.to_mesh(me)
bm.free()
shell = bpy.data.objects.new('Nautilus_Shell', me)
link(shell)
for m in MATS:
    me.materials.append(m)
smooth(shell)

# 배꼽을 덮는 굳은살(callus): 양옆 오목한 자리를 매끈하게 메운다
callus = sphere('Umbilical_Callus', r=1.0, seg=32, rings=16, scale=(0.0165, 0.0124, 0.0165), material=M_CALLUS)

# 살아 있을 때처럼 세운다: 입구는 아래, 입은 +X 쪽을 본다. 나선면은 XZ 평면(옆면이 -Y)
# 회전은 메시에 직접 굽는다(바운딩 박스가 실제 형태에 딱 맞도록)
POSE = Matrix.Rotation(math.radians(76), 4, 'Y')
for ob in (shell, callus):
    ob.data.transform(POSE)
    ob.data.update()
