# Garden: a deck under a pergola, a gravel path worked out along a curve, a pond cut into the lawn
# with boulders round it, raised beds, trees and hedges, a picket fence with a gate.
from math import *
units("cm")

grass, gravel, timber, dark_timber, soil = "#86b463", "#d8cfbf", "#b07d52", "#7a5638", "#6e4f36"
W, D = 1600, 1100

def spline(pts, n=6):
    out = []
    for i in range(len(pts) - 1):
        p0, p1, p2, p3 = pts[max(0, i - 1)], pts[i], pts[i + 1], pts[min(len(pts) - 1, i + 2)]
        for j in range(n):
            t = j / n
            out.append(tuple(0.5 * (2 * p1[k] + (p2[k] - p0[k]) * t + (2 * p0[k] - 5 * p1[k] + 4 * p2[k] - p3[k]) * t * t + (3 * p1[k] - p0[k] - 3 * p2[k] + p3[k]) * t * t * t) for k in range(2)))
    out.append(pts[-1])
    return out

# ---------- the plot, the pond cut into it ----------
site = box(W, D, 60, at=(0, 0, -60), name="plot", color="#d9d2c5")
pond_pts = [(980, 330), (1120, 290), (1270, 330), (1330, 450), (1270, 560), (1120, 580), (1010, 520), (940, 430)]
site = site - curve(pond_pts, offset=-45).pull(45)
site.paint("#d9d2c5")
site.top.paint(grass)
water = curve(pond_pts, offset=-45).pull(37, name="water", color="#4f9fcf")
boulders = []
for k, (x, y) in enumerate(pond_pts):
    b = sphere(26 + 9 * (k % 3), at=(x + 14 * cos(k), y + 14 * sin(k), 0), name="boulder", color=["stone", "#a9a49a", "#bdb6aa"][k % 3], sides=14)
    b.scale((1.2, 1, 0.55))
    boulders.append(b)

# ---------- the path: a gravel band of even width along a smooth centre line ----------
centre = spline([(780, 14), (765, 160), (620, 330), (470, 470), (420, 640), (450, 760)], 8)
left, right = [], []
for i in range(len(centre)):
    a = centre[max(0, i - 1)]
    b = centre[min(len(centre) - 1, i + 1)]
    dx, dy = b[0] - a[0], b[1] - a[1]
    l = hypot(dx, dy)
    nx, ny = -dy / l * 55, dx / l * 55
    left.append((centre[i][0] + nx, centre[i][1] + ny))
    right.append((centre[i][0] - nx, centre[i][1] - ny))
path = prism(left + right[::-1], 2, name="path", color=gravel)
lamps = []
for i in (6, 18, 30):
    x, y = right[i]
    post = cylinder(4, 70, at=(x - 20, y, 2), name="lamp post", color="#2f3033", sides=12)
    light = cylinder(7, 12, at=(x - 20, y, 72), name="lamp", color="#fff3d1", sides=16)
    hat = cone(10, 2, 6, at=(x - 20, y, 84), name="lamp cap", color="#2f3033", sides=16)
    lamps.append(group([post, light, hat], name="garden lamp"))

# ---------- the deck and the pergola over it ----------
boards = [box(560, 13.5, 6, at=(150, 680 + k * 14.5, 0), color=timber) for k in range(27)]
posts, beams, rafters = [], [], []
for x in (170, 690):
    for y in (690, 1050):
        posts.append(box(14, 14, 260, at=(x - 7, y - 7, 6), color=dark_timber))
for y in (683, 1043):
    beams.append(box(640, 14, 24, at=(130, y, 266), color=dark_timber))
for k in range(14):
    rafters.append(box(6, 420, 18, at=(140 + k * 46, 660, 290), color=dark_timber))
vine = [sphere(28 + 8 * (k % 3), at=(160 + k * 70, 690 + 60 * (k % 2), 315), name="vine", color=["#5f8f44", "#4f7d3a", "#6a9a4c"][k % 3], sides=12) for k in range(8)]
# a table and two benches under it
table = box(180, 90, 5, at=(340, 820, 72), name="table top", color=timber)
table.fillet(1.5, edges="top")
legs = [box(8, 8, 72, at=(x, y, 6 - 0), color=dark_timber) for x in (350, 502) for y in (828, 894)]
benches = []
for y in (760, 935):
    seat = box(180, 35, 5, at=(340, y, 44), color=timber)
    seat.fillet(1.5, edges="top")
    benches += [seat] + [box(6, 30, 38, at=(x, y + 2, 6), color=dark_timber) for x in (350, 504)]

# ---------- raised beds with soil and plants ----------
beds = []
for k, (x, y) in enumerate([(120, 120), (120, 330), (120, 540)]):
    bed = box(300, 120, 45, at=(x, y, 0), color=dark_timber)
    bed.shell(6, open="top")
    earth = box(288, 108, 34, at=(x + 6, y + 6, 0), color=soil)
    plants = []
    for i in range(6):
        px, py = x + 30 + i * 48, y + 35 + 50 * (i % 2)
        if k == 1:
            plants.append(cone(18, 0, 55, at=(px, py, 34), name="plant", color="#4f7d3a", sides=10))
        else:
            plants.append(sphere(20 + 4 * (i % 2), at=(px, py, 50), name="plant", color=["#6a9a4c", "#d4a73c", "#e481b0"][k % 3] if i % 3 == 0 else "#5f8f44", sides=12))
    beds.append(group([bed, earth] + plants, name=f"bed {k + 1}"))

# ---------- trees, conifers, hedges ----------
def tree(x, y, s, name):
    trunk = cone(14 * s, 9 * s, 260 * s, at=(x, y, 0), name="trunk", color="#6b4a3a", sides=12)
    crown = [sphere(r * s, at=(x + dx * s, y + dy * s, dz * s), name="crown", color=c, sides=16)
             for dx, dy, dz, r, c in [(0, 0, 300, 120, "#4f7d3a"), (60, 30, 360, 95, "#5f8f44"), (-55, -25, 380, 90, "#457235"), (10, -40, 440, 75, "#6a9a4c")]]
    return group([trunk] + crown, name=name)
tree(1350, 900, 1.15, "tree 1")
tree(1480, 110, 0.7, "tree 2")
for k, (x, y) in enumerate([(880, 980), (1000, 1010), (1110, 990)]):
    tiers = [cone(55 - 12 * t, 8, 110 - 10 * t, at=(x, y, 30 + 70 * t), name="conifer", color="#3f6b35" if t % 2 else "#4a7a3e", sides=12) for t in range(3)]
    tiers.append(cylinder(8, 30, at=(x, y, 0), name="trunk", color="#6b4a3a", sides=8))
    group(tiers, name=f"conifer {k + 1}")
hedges = [box(W - 60, 70, 140, at=(30, D - 75, 0), name="hedge", color="#3f6b35"), box(70, D - 200, 140, at=(W - 75, 100, 0), name="hedge", color="#3f6b35")]
for h in hedges:
    h.fillet(30, edges="top")

# ---------- a picket fence with a gate along the front ----------
def picket(x, y):
    # a pointed board, standing
    return prism([(x, 0), (x + 8, 0), (x + 8, 88), (x + 4, 95), (x, 88)], 3, plane="xz", offset=y, name="picket", color="#f4f2ec")
fence = []
for x in range(10, W - 10, 18):
    if 700 < x < 860:
        continue                                  # the gate's gap
    fence.append(picket(x, 6))
for z in (25, 70):
    fence += [box(690, 3, 8, at=(10, 9, z), color="#f4f2ec"), box(W - 870, 3, 8, at=(860, 9, z), color="#f4f2ec")]
gate = []
for x in range(712, 850, 18):
    gate.append(picket(x, 2))
gate += [box(150, 3, 8, at=(705, 5, z), color="#f4f2ec") for z in (25, 70)]

group([site, water] + boulders, name="plot")
group(boards + posts + beams + rafters + vine, name="pergola and deck")
group([table] + legs + benches, name="table and benches")
group(fence + [group(gate, name="gate")], name="fence")
group([path] + lamps, name="path")
group(hedges, name="hedges")
label("pond", at=(1180, 250, 160), to=(1140, 440, -8))
