# Tea set: a teapot turned from one profile, its spout lofted through level sections, its handle
# swept along a curve; two cups on saucers and a sugar bowl on a wooden tray.
from math import *
units("mm")

def spline(pts, n=3):
    # a smooth line through the points (Catmull-Rom), n steps between each two
    out = []
    for i in range(len(pts) - 1):
        p0 = pts[max(0, i - 1)]
        p1 = pts[i]
        p2 = pts[i + 1]
        p3 = 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

glaze = "#2f6b68"
cream = "#efe7d6"

# ---------- the teapot ----------
# half its outline, turned about the vertical: a foot, a round belly, a short neck
belly = spline([(53, 3), (63, 14), (78, 35), (86, 60), (84, 85), (74, 105), (60, 120), (49, 129)])
body = polygon([(0, 0), (50, 0)] + belly + [(47, 132), (47, 138), (0, 138)], plane="xz").revolve(axis="z", name="pot", color=glaze)
body.shell(3, open="top")

# the spout: level sections rising out of the belly and narrowing; a bore through it, into the pot
def section(t, wall):
    # wide where it grows out of the belly (a flare, like a cast spout), narrow at the tip
    u = 1 - t
    rx = 8 + 8 * u + 6 * u ** 3
    ry = 7 + 7 * u + 5 * u ** 3
    return ellipse(58 + 94 * t ** 1.3, 0, rx - wall, ry - wall, offset=48 + 82 * t ** 0.85, sides=32)
steps = [0, 0.15, 0.35, 0.65, 1]
pot = body + loft([section(t, 0) for t in steps])
pot = pot - loft([section(t, 3) for t in steps])

# the handle: an oval section swept round a curve at the back, both ends sunk into the wall
path = [(-62, 0, 114), (-96, 0, 121), (-124, 0, 104), (-131, 0, 74), (-116, 0, 46), (-74, 0, 34)]
pot = pot + ellipse(0, 114, 7, 10, plane="yz", offset=-62, sides=24).follow(path, smooth=True)
pot.paint(glaze)

# the lid drops into the neck and rests on its rim; a cream knob on top
lid = polygon([(0, 126), (43.5, 126), (43.5, 138), (50, 138), (50, 140.5)] + spline([(47, 143), (38, 148), (22, 152.5), (0, 154)]), plane="xz").revolve(axis="z", name="lid", color=glaze)
knob = polygon([(0, 153.5), (6, 153.5)] + spline([(5.5, 157), (9.5, 162), (9, 167), (5, 170), (0, 171)]), plane="xz").revolve(axis="z", name="knob", color=cream, sides=40)
group([pot, group([lid, knob], name="lid")], name="teapot")

# ---------- cups on saucers ----------
def cup(n, x, y, turn):
    wall = spline([(27, 6), (33, 18), (38, 36), (40, 52), (41, 58)])
    c = polygon([(0, 0), (24, 0), (26, 3), (24, 6)] + wall + [(0, 58)], plane="xz").revolve(axis="z", name="cup", color=cream, sides=56)
    c.shell(2.5, open="top")
    c = c + ellipse(0, 46, 4, 5.5, plane="yz", offset=39, sides=20).follow([(39, 0, 46), (51, 0, 49), (59, 0, 40), (58, 0, 27), (48, 0, 19), (31.5, 0, 17)], smooth=True)
    c.paint(cream)
    c.rotate(turn, about=(0, 0, 0))
    c.move(x, y, 10)
    s = polygon([(0, 0), (34, 0), (36, 2), (31, 7)] + spline([(42, 8), (58, 9.5), (70, 12.5)]) + [(72, 14), (69, 14), (57, 11.5), (41, 10), (0, 10)], plane="xz").revolve(axis="z", name="saucer", color=cream, sides=56)
    s.move(x, y, 0)
    return group([c, s], name=f"cup {n}")

cup(1, 175, -70, -35)
cup(2, 215, 85, 20)

# ---------- the sugar bowl ----------
bowl = polygon([(0, 0), (26, 0), (28, 3)] + spline([(30, 7), (36, 20), (40, 34), (41, 44)]) + [(41.5, 47), (0, 47)], plane="xz").revolve(axis="z", name="sugar bowl", color=glaze, sides=56)
bowl.shell(2.5, open="top")
cover = polygon([(0, 46), (38.5, 46), (38.5, 47), (43, 47), (43, 49)] + spline([(39, 52), (26, 56.5), (0, 58)]), plane="xz").revolve(axis="z", name="sugar bowl lid", color=glaze, sides=56)
bead = sphere(7, at=(0, 0, 62), name="knob", color=cream, sides=28)
sugar = group([bowl, group([cover, bead], name="sugar bowl lid")], name="sugar bowl")
sugar.move(-30, -150, 0)

# ---------- the tray ----------
tray = box(600, 400, 22, at=(-250, -220, -22), name="tray", color="#8a5a36")
tray.fillet(30, edges="vertical")
well = box(570, 370, 22, at=(-235, -205, -10))
well.fillet(16, edges="vertical")
tray = tray - well
tray.fillet(3, edges="top")
tray.paint("#8a5a36")
group([tray], name="tray")
