# 해바라기 (Helianthus annuus) — 한 그루를 실제 크기(높이 약 1.9m)로 코드만으로 만든 모델.
# 꽃 가운데 원반의 관상화(작은 꽃) 2,000개는 황금각(137.5°) 엽서(phyllotaxis) 공식
# r = c·√n, θ = n·137.5° 로 하나씩 놓아, 해바라기 특유의 나선(시계·반시계 방향 34/55개)이 저절로 드러난다.
# 혀꽃잎·총포 조각·잎은 모두 같은 '잎사귀 격자' 함수(길이·폭 윤곽, 오목함, 처짐, 비틀림)로 bmesh 를 빚었고,
# 줄기와 잎자루는 경로를 따라 굵기가 줄어드는 관(평행 이동 프레임)으로, 꽃 뒷면 꽃받침은 회전체로 만들었다.
# 무작위 값은 random.seed 로 고정해 늘 같은 모양이 나온다.
import random
from atelier import *

random.seed(1883)

SHOT = {"azimuth": -72, "elevation": 10, "lens": 60, "light": "golden", "background": "#24303a"}

GOLDEN = math.radians(137.50776)

# ---------------------------------------------------------------- 재질
M_PETAL = mat("Ray_Petal_Yellow", "#f0b40c", rough=0.55)
M_PETAL2 = mat("Ray_Petal_Deep", "#e39a0a", rough=0.55)
M_DISC_BASE = mat("Disc_Receptacle_Dark", "#2a180c", rough=0.9)
M_FLORET = mat("Disc_Floret_Brown", "#4e2c13", rough=0.85)
M_FLORET_IN = mat("Disc_Floret_Center", "#5f5a22", rough=0.8)
M_FLORET_OUT = mat("Disc_Floret_Pollen", "#b8781c", rough=0.75)
M_STEM = mat("Stem_Green", "#5d8030", rough=0.65)
M_LEAF = mat("Leaf_Green", "#3e6a24", rough=0.6)
M_VEIN = mat("Leaf_Vein", "#7d9c48", rough=0.6)
M_BRACT = mat("Bract_Green", "#4b7428", rough=0.6)
M_SOIL = mat("Garden_Soil", "#3e2c21", rough=0.95)


# ---------------------------------------------------------------- 도우미
def obj_from_bm(name, bm, material, smooth_shade=True):
    me = bpy.data.meshes.new(name)
    bm.to_mesh(me)
    bm.free()
    ob = bpy.data.objects.new(name, me)
    link(ob)
    assign(ob, material)
    if smooth_shade:
        smooth(ob)
    return ob


def bezier(p0, p1, p2, p3, n):
    out = []
    for i in range(n + 1):
        t = i / n
        u = 1 - t
        out.append(p0 * u ** 3 + p1 * 3 * u * u * t + p2 * 3 * u * t * t + p3 * t ** 3)
    return out


def tube_into(bm, pts, radii, seg=12, rib=0.0, cap_start=True, cap_end=True):
    """경로(pts)를 따라 반지름(radii)이 변하는 관. 평행 이동 프레임으로 비틀림 없이 감는다."""
    n = len(pts)
    tans = []
    for i in range(n):
        a = pts[max(i - 1, 0)]
        b = pts[min(i + 1, n - 1)]
        tans.append((b - a).normalized())
    ref = Vector((1, 0, 0)) if abs(tans[0].x) < 0.9 else Vector((0, 1, 0))
    nrm = tans[0].cross(ref).normalized()
    rings = []
    for i in range(n):
        if i > 0:
            rot = tans[i - 1].rotation_difference(tans[i])
            nrm = (rot @ nrm).normalized()
        bin_ = tans[i].cross(nrm).normalized()
        ring = []
        for k in range(seg):
            a = 2 * math.pi * k / seg
            r = radii[i] * (1 + rib * math.cos(a * 8))
            ring.append(bm.verts.new(pts[i] + (nrm * math.cos(a) + bin_ * math.sin(a)) * r))
        rings.append(ring)
    for i in range(n - 1):
        for k in range(seg):
            bm.faces.new((rings[i][k], rings[i][(k + 1) % seg], rings[i + 1][(k + 1) % seg], rings[i + 1][k]))
    if cap_start:
        bm.faces.new(list(reversed(rings[0])))
    if cap_end:
        bm.faces.new(rings[-1])
    return rings


def blade_into(bm, matrix, L, W, width_fn, nu=12, nv=6, cup=0.0, droop=0.0, lift=0.0, twist=0.0,
               fold=0.0, notch=0.0, t0=0.0, serr=0.0, serr_n=0):
    """잎·꽃잎 한 장. 로컬 +X 가 길이, Y 가 폭, +Z 가 앞면. 좌우 반쪽을 따로 만든 뒤 가운데맥에서 붙인다.
    width_fn(t)→0..1 폭 윤곽, cup: 가장자리 들림, droop/lift: 길이 방향 휨, twist: 끝 비틀림(rad),
    fold: 가운데맥 V 접힘, notch/t0: 심장형 잎 밑동의 오목한 홈."""
    grid = []
    for side in (-1, 1):
        rows = []
        for i in range(nu + 1):
            t = t0 + (1 - t0) * i / nu
            w = W * width_fn(t)
            if serr and serr_n:
                ph = (t * serr_n) % 1.0
                w *= 1 + serr * (ph - 0.5)
            s_in = 0.0
            if t < 0:
                s_in = notch * W * (t / t0)
            row = []
            for j in range(nv + 1):
                s = j / nv
                y = side * (s_in + s * max(w - s_in, 0.0))
                x = t * L
                yn = y / max(W, 1e-6)
                z = cup * W * yn * yn - droop * L * t * t + lift * L * t - fold * abs(y)
                ang = twist * max(t, 0)
                yy = y * math.cos(ang) - z * math.sin(ang)
                zz = y * math.sin(ang) + z * math.cos(ang)
                row.append(bm.verts.new(matrix @ Vector((x, yy, zz))))
            rows.append(row)
        grid.append((side, rows))
    for side, rows in grid:
        for i in range(nu):
            for j in range(nv):
                q = (rows[i][j], rows[i + 1][j], rows[i + 1][j + 1], rows[i][j + 1])
                if side < 0:
                    q = tuple(reversed(q))
                bm.faces.new(q)


# ---------------------------------------------------------------- 꽃머리 (로컬: 원반 앞면이 +Z)
RD = 0.150            # 원반 반지름 (지름 30cm)
DOME = 0.014          # 원반의 볼록한 정도


def dome_z(r):
    return DOME * (1 - (r / RD) ** 2)


head_parts = []

# 원반 바탕(관상화 아래의 짙은 면)
prof = [(0.0, dome_z(0) - 0.002)]
for i in range(1, 13):
    r = RD * 1.03 * i / 12
    prof.append((r, dome_z(min(r, RD)) - 0.002 - (0.004 if r > RD else 0)))
disc = lathe("Disc_Base", prof, material=M_DISC_BASE, seg=96)
head_parts.append(disc)

# 관상화 — 황금각 나선 배치
N_FLORETS = 2000
c = RD / math.sqrt(N_FLORETS)
bms = {"in": bmesh.new(), "mid": bmesh.new(), "out": bmesh.new()}
for n in range(1, N_FLORETS + 1):
    r = c * math.sqrt(n)
    th = n * GOLDEN
    x, y = r * math.cos(th), r * math.sin(th)
    z = dome_z(r) - 0.002
    nrm = Vector((2 * DOME * x / RD ** 2, 2 * DOME * y / RD ** 2, 1)).normalized()
    rho = r / RD
    if rho < 0.22:
        key, h, s = "in", c * 1.0, c * 0.80
    elif rho < 0.86:
        key, h, s = "mid", c * (1.3 + 0.5 * rho), c * 0.84
    else:
        key, h, s = "out", c * 2.2, c * 0.70
    m = (Matrix.Translation(Vector((x, y, z)) + nrm * (h / 2))
         @ Vector((0, 0, 1)).rotation_difference(nrm).to_matrix().to_4x4()
         @ Matrix.Rotation(th, 4, 'Z'))
    bmesh.ops.create_cone(bms[key], cap_ends=True, segments=6, radius1=s, radius2=s * 0.6, depth=h, matrix=m)
head_parts.append(obj_from_bm("Disc_Florets_Center", bms["in"], M_FLORET_IN, False))
head_parts.append(obj_from_bm("Disc_Florets_Spiral", bms["mid"], M_FLORET, False))
head_parts.append(obj_from_bm("Disc_Florets_Open_Ring", bms["out"], M_FLORET_OUT, False))

# 꽃 뒷면 꽃받침(녹색 그릇)
back_prof = [(0.014, -0.095), (0.035, -0.091), (0.065, -0.082), (0.095, -0.068), (0.12, -0.050),
             (0.139, -0.032), (0.151, -0.018), (RD * 1.035, -0.008), (RD * 1.03, dome_z(RD) - 0.004)]
receptacle = lathe("Receptacle_Back", back_prof, material=M_BRACT, seg=96)
head_parts.append(receptacle)


def petal_w(t):
    return 0.32 * (1 - t) + 0.75 * math.sin(math.pi * t ** 0.72) ** 0.9 if t < 1 else 0.0


# 혀꽃(노란 꽃잎) 두 겹
bm_p1, bm_p2 = bmesh.new(), bmesh.new()
for row, (count, r0, z0, tilt, Lb, bm_t) in enumerate([(32, RD * 0.96, -0.006, 2, 0.125, bm_p1),
                                                       (32, RD * 0.92, -0.002, 12, 0.110, bm_p2)]):
    for i in range(count):
        th = 2 * math.pi * (i + 0.5 * row) / count + random.uniform(-0.04, 0.04)
        L = Lb * random.uniform(0.88, 1.1)
        W = 0.023 * random.uniform(0.9, 1.1)
        tl = math.radians(tilt + random.uniform(-6, 6))
        m = (Matrix.Rotation(th, 4, 'Z') @ Matrix.Translation((r0, 0, z0))
             @ Matrix.Rotation(-tl, 4, 'Y'))
        blade_into(bm_t, m, L, W, petal_w, nu=10, nv=3, cup=0.35, droop=random.uniform(0.05, 0.22),
                   twist=random.uniform(-0.35, 0.35), fold=0.15)
p_out = obj_from_bm("Ray_Florets_Outer", bm_p1, M_PETAL)
p_in = obj_from_bm("Ray_Florets_Inner", bm_p2, M_PETAL2)
for p in (p_out, p_in):
    solidify(p, 0.0012)
head_parts += [p_out, p_in]


def bract_w(t):
    return 0.85 * math.sin(math.pi * min(t, 1) ** 0.8) ** 0.8 + 0.25 * (1 - t)


# 총포 조각(꽃 뒷면을 비늘처럼 덮은 뾰족한 녹색 잎조각) — 그릇 면을 따라 눕히고 끝은 바깥으로 젖힌다
def bowl_at(r):
    for (r0, z0), (r1, z1) in zip(back_prof, back_prof[1:]):
        if r0 <= r <= r1:
            a = (r - r0) / (r1 - r0)
            return z0 + (z1 - z0) * a, Vector((r1 - r0, 0, z1 - z0)).normalized()
    return back_prof[-1][1], Vector((0, 0, 1))


bm_b = bmesh.new()
N_BRACT = 110
for k in range(N_BRACT):
    th = k * GOLDEN
    f = k / (N_BRACT - 1)
    rr = 0.055 + 0.088 * f ** 0.7
    zz, tang = bowl_at(rr)
    out_n = Vector((tang.z, 0, -tang.x))           # 그릇 바깥쪽 법선
    pos = Vector((rr, 0, zz)) + out_n * 0.0015
    basis = Matrix((tang, out_n.cross(tang), out_n)).transposed().to_4x4()
    m = Matrix.Rotation(th, 4, 'Z') @ Matrix.Translation(pos) @ basis
    L = (0.040 + 0.040 * f) * random.uniform(0.85, 1.15)
    blade_into(bm_b, m, L, 0.012 + 0.006 * f, bract_w, nu=7, nv=2, cup=-0.15,
               droop=-(0.10 + 0.45 * f), twist=random.uniform(-0.2, 0.2), fold=0.15)
bracts = obj_from_bm("Involucral_Bracts", bm_b, M_BRACT)
solidify(bracts, 0.0015)
head_parts.append(bracts)

# 꽃머리 자세: 앞(-Y)을 보며 조금 위로, 살짝 옆으로 돌아 고개를 숙인 듯
HEAD_C = Vector((0.03, -0.30, 1.62))
HEAD_ROT = Euler((math.radians(72), 0, math.radians(-8)))
head = bpy.data.objects.new("Sunflower_Head", None)
link(head)
head.location = HEAD_C
head.rotation_euler = HEAD_ROT
for p in head_parts:
    p.parent = head
N_HEAD = HEAD_ROT.to_matrix() @ Vector((0, 0, 1))

# ---------------------------------------------------------------- 줄기
STEM_TOP = 1.34
stem_pts, stem_r = [], []
for i in range(71):
    z = STEM_TOP * i / 70
    sway = 0.03 * math.sin(z * 1.9) * (z / STEM_TOP)
    stem_pts.append(Vector((sway, 0.012 * math.sin(z * 2.7), z)))
    stem_r.append(0.024 - 0.010 * (z / STEM_TOP))
back = HEAD_C - N_HEAD * 0.080
p0 = stem_pts[-1]
neck = bezier(p0, p0 + Vector((0, 0.01, 0.16)), back - N_HEAD * 0.17, back, 26)[1:]
for i, p in enumerate(neck):
    t = (i + 1) / len(neck)
    stem_pts.append(p)
    stem_r.append(0.013 + 0.004 * t ** 3)
stem_pts[0] = stem_pts[0] - Vector((0, 0, 0.03))  # 흙 속까지 박힘
bm = bmesh.new()
tube_into(bm, stem_pts, stem_r, seg=16, rib=0.05)
stem = obj_from_bm("Stem", bm, M_STEM)


def stem_at(z):
    """수직 구간의 줄기 위치와 반지름."""
    f = min(max(z / STEM_TOP, 0), 1) * 70
    i = min(int(f), 69)
    a = f - i
    return stem_pts[i].lerp(stem_pts[i + 1], a), stem_r[i] + (stem_r[i + 1] - stem_r[i]) * a


# ---------------------------------------------------------------- 잎 (심장형, 어긋나기)
def leaf_w(t):
    if t < 0:
        return 0.55 * math.sqrt(max(0.0, 1 - (t / -0.16) ** 2))
    return (1 - t) ** 0.9 * (0.55 + 1.6 * t) / (1 + 1.6 * t * t)


bm_leaf, bm_pet, bm_vein = bmesh.new(), bmesh.new(), bmesh.new()
N_LEAVES = 14
for i in range(N_LEAVES):
    f = i / (N_LEAVES - 1)
    z = 0.16 + f * 1.08
    phi = math.radians(30) + i * GOLDEN
    base, sr = stem_at(z)
    d = Vector((math.cos(phi), math.sin(phi), 0))
    up = Vector((0, 0, 1))
    pl = 0.16 - 0.06 * f                       # 잎자루 길이
    L = (0.42 - 0.22 * f) * random.uniform(0.92, 1.05)   # 잎몸 길이
    W = L * 0.72
    q0 = base + d * sr * 0.4
    q3 = q0 + d * pl * 0.92 + up * pl * (0.55 - 0.2 * f)
    pet = bezier(q0, q0 + up * pl * 0.35 + d * pl * 0.1, q3 - d * pl * 0.3, q3, 10)
    tube_into(bm_pet, pet, [0.0055 - 0.0025 * k / 10 - 0.001 * f for k in range(11)], seg=8)
    pitch = math.radians(42 - 12 * f)
    m = (Matrix.Translation(q3) @ Matrix.Rotation(phi, 4, 'Z') @ Matrix.Rotation(-pitch, 4, 'Y')
         @ Matrix.Rotation(random.choice((-1, 1)) * random.uniform(0.35, 0.7), 4, "X"))
    droop = 0.95 - 0.35 * f
    blade_into(bm_leaf, m, L, W, leaf_w, nu=34, nv=6, t0=-0.16, notch=0.22, droop=droop,
               cup=-0.06, fold=0.30, serr=0.07, serr_n=16)
    def leaf_pt(t, y, lift_=0.0018):
        z = -0.06 * W * (y / W) ** 2 - droop * L * t * t - 0.30 * abs(y) + lift_
        return m @ Vector((t * L, y, z))
    # 가운데맥과 곁맥(잎 앞면 위로 살짝 돋음)
    mid = [leaf_pt(k / 12 * 0.92, 0.0) for k in range(13)]
    tube_into(bm_vein, mid, [0.0022 * (1 - k / 13) + 0.0006 for k in range(13)], seg=6)
    for tv in (0.04, 0.22, 0.42):
        for sd in (-1, 1):
            reach = W * leaf_w(min(tv + 0.25, 1.0)) * 0.75
            vp = [leaf_pt(tv + 0.25 * u, sd * reach * u ** 0.9) for u in [k / 6 for k in range(7)]]
            tube_into(bm_vein, vp, [0.0011 * (1 - k / 8) + 0.0003 for k in range(7)], seg=5)
leaves = obj_from_bm("Leaves", bm_leaf, M_LEAF)
solidify(leaves, 0.0012)
obj_from_bm("Petioles", bm_pet, M_STEM)
obj_from_bm("Leaf_Midribs", bm_vein, M_VEIN)

# ---------------------------------------------------------------- 흙 둔덕
def soil_h(r, th, R):
    q = min(r / R, 1.0)
    return 0.075 * (1 - q * q) ** 1.6 * (1 + 0.12 * math.sin(5 * th + 2 * q) + 0.06 * math.sin(11 * th + 1))


bm = bmesh.new()
NA, NR = 64, 10
rim = [0.27 * (1 + 0.07 * math.sin(3 * (2 * math.pi * k / NA) + 1) + 0.04 * math.sin(7 * (2 * math.pi * k / NA)))
       for k in range(NA)]
top = bm.verts.new((0, 0, soil_h(0, 0, 1)))
rings = []
for j in range(1, NR + 1):
    ring = []
    for k in range(NA):
        th = 2 * math.pi * k / NA
        r = rim[k] * j / NR
        ring.append(bm.verts.new((r * math.cos(th), r * math.sin(th), soil_h(r, th, rim[k]))))
    rings.append(ring)
for k in range(NA):
    bm.faces.new((top, rings[0][k], rings[0][(k + 1) % NA]))
for j in range(NR - 1):
    for k in range(NA):
        bm.faces.new((rings[j][k], rings[j + 1][k], rings[j + 1][(k + 1) % NA], rings[j][(k + 1) % NA]))
bm.faces.new(list(reversed(rings[-1])))
bmesh.ops.recalc_face_normals(bm, faces=bm.faces)
obj_from_bm("Soil_Mound", bm, M_SOIL)

# 흙덩이 몇 개(반쯤 묻힘)
bm = bmesh.new()
for k in range(16):
    th = random.uniform(0, 2 * math.pi)
    r = random.uniform(0.06, 0.24)
    s = random.uniform(0.010, 0.022)
    R = 0.27
    z = soil_h(r, th, R)
    mtx = (Matrix.Translation((r * math.cos(th), r * math.sin(th), z))
           @ Matrix.Rotation(random.uniform(0, 3), 4, 'Z')
           @ Matrix.Diagonal((s * random.uniform(1, 1.6), s, s * 0.7, 1)))
    bmesh.ops.create_icosphere(bm, subdivisions=1, radius=1.0, matrix=mtx)
obj_from_bm("Soil_Clods", bm, M_SOIL, smooth_shade=False)
