[READ-ONLY] Mirror of https://github.com/flo-bit/shapecraft.
flo-bit.dev/shapecraft/
5.1 kB
122 lines
1import { sphere } from '../../../primitives'
2import { merge } from '../../../ops'
3import { setup, trunk, blade } from '../../../build'
4import type { Mesh } from '../../../core/mesh'
5import type { Vec3 } from '../../../core/types'
6import type { OptionSchema, OptionInput } from '../../../core/schema'
7
8export const flowerSchema = {
9 seed: { type: 'integer', default: 1, min: 1, max: 100, label: 'Seed' },
10 height: { type: 'range', default: 0.5, min: 0.15, max: 1.2, step: 0.05, label: 'Stem Height' },
11 stemRadius: { type: 'range', default: 0.012,min: 0.004,max: 0.03, step: 0.002, label: 'Stem Radius' },
12 lean: { type: 'range', default: 0.12, min: 0, max: 0.4, step: 0.02, label: 'Lean' },
13 petals: { type: 'integer', default: 9, min: 3, max: 18, label: 'Petals' },
14 petalLength: { type: 'range', default: 0.13, min: 0.05, max: 0.3, step: 0.01, label: 'Petal Length' },
15 petalWidth: { type: 'range', default: 0.06, min: 0.02, max: 0.14, step: 0.005, label: 'Petal Width' },
16 petalLift: { type: 'range', default: 0.45, min: 0, max: 1.2, step: 0.05, label: 'Petal Lift' },
17 centerSize: { type: 'range', default: 0.045,min: 0.02, max: 0.1, step: 0.005, label: 'Center Size' },
18 leaves: { type: 'integer', default: 2, min: 0, max: 4, label: 'Leaves' },
19 leafLength: { type: 'range', default: 0.16, min: 0.05, max: 0.35, step: 0.01, label: 'Leaf Length' },
20 petalColor: { type: 'color', default: '#e0556b', label: 'Petal Color' },
21 centerColor: { type: 'color', default: '#f0c040', label: 'Center Color' },
22 stemColors: { type: 'color-array', default: ['#2f6a1e', '#3f8526'], min: 1, max: 4, label: 'Stem Colors' },
23} satisfies OptionSchema
24
25export type FlowerOptions = Partial<OptionInput<typeof flowerSchema>> & { preset?: string }
26
27export const flowerPresets: Record<string, Partial<FlowerOptions>> = {
28 default: {},
29 daisy: { petals: 14, petalColor: '#f3f0ee', centerColor: '#f0b830', petalWidth: 0.04, petalLength: 0.16 },
30 tulip: { petals: 6, petalLift: 1.0, petalColor: '#d23a52', petalWidth: 0.1, petalLength: 0.18, centerSize: 0.03 },
31 poppy: { petals: 5, petalColor: '#d8392b', petalWidth: 0.12, petalLift: 0.6 },
32 dandelion: { petals: 18, petalColor: '#f2cf33', petalWidth: 0.025, petalLength: 0.1, petalLift: 0.7 },
33}
34
35function norm(x: number, y: number, z: number): Vec3 {
36 const l = Math.hypot(x, y, z) || 1
37 return [x / l, y / l, z / l]
38}
39
40export function flower(options: FlowerOptions = {}): Mesh {
41 const { o, rng } = setup(flowerSchema, options, flowerPresets)
42 const shapeRng = rng.stream('shape')
43
44 const leanAngle = shapeRng() * Math.PI * 2
45 const leanX = Math.cos(leanAngle) * o.lean
46 const leanZ = Math.sin(leanAngle) * o.lean
47
48 const parts: Mesh[] = []
49
50 // Stem.
51 parts.push(trunk({
52 height: o.height,
53 baseRadius: o.stemRadius,
54 topRadius: o.stemRadius * 0.7,
55 taper: 1,
56 lean: [leanX, leanZ],
57 noiseSeed: shapeRng.seed(),
58 noiseScale: 6,
59 noiseAmount: 0.06,
60 segments: 5,
61 heightSegments: 4,
62 colors: o.stemColors,
63 }))
64
65 const head: Vec3 = [leanX, o.height, leanZ]
66
67 // Flower center.
68 parts.push(
69 sphere({ radius: o.centerSize, widthSegments: 6, heightSegments: 4 })
70 .scale(1, 0.6, 1)
71 .translate(head[0], head[1], head[2])
72 .vertexColor(o.centerColor),
73 )
74
75 // Petals arranged in a ring, lifting up into a cup.
76 for (let i = 0; i < o.petals; i++) {
77 const angle = (i / o.petals) * Math.PI * 2 + (shapeRng() - 0.5) * 0.15
78 const ca = Math.cos(angle), sa = Math.sin(angle)
79 const dir = norm(ca, o.petalLift * 1.4, sa)
80 const start: Vec3 = [head[0] + ca * o.centerSize, head[1], head[2] + sa * o.centerSize]
81 const segs = 3
82 const path: Vec3[] = []
83 for (let j = 0; j <= segs; j++) {
84 const t = j / segs
85 path.push([
86 start[0] + dir[0] * o.petalLength * t,
87 start[1] + dir[1] * o.petalLength * t,
88 start[2] + dir[2] * o.petalLength * t,
89 ])
90 }
91 parts.push(
92 blade(path, { width: (t) => o.petalWidth * Math.sin(Math.min(1, t) * Math.PI) })
93 .vertexColor(o.petalColor),
94 )
95 }
96
97 // Leaves partway up the stem.
98 for (let i = 0; i < o.leaves; i++) {
99 const t0 = 0.3 + (i / Math.max(1, o.leaves)) * 0.4
100 const angle = shapeRng() * Math.PI * 2
101 const ca = Math.cos(angle), sa = Math.sin(angle)
102 const dir = norm(ca, 0.35, sa)
103 const base: Vec3 = [leanX * t0 * t0, o.height * t0, leanZ * t0 * t0]
104 const segs = 3
105 const path: Vec3[] = []
106 for (let j = 0; j <= segs; j++) {
107 const t = j / segs
108 const droop = 0.25 * o.leafLength * t * t
109 path.push([
110 base[0] + dir[0] * o.leafLength * t,
111 base[1] + dir[1] * o.leafLength * t - droop,
112 base[2] + dir[2] * o.leafLength * t,
113 ])
114 }
115 parts.push(
116 blade(path, { width: (t) => o.leafLength * 0.3 * Math.sin(Math.min(1, t) * Math.PI) })
117 .vertexColor(o.stemColors[o.stemColors.length - 1]),
118 )
119 }
120
121 return merge(...parts)
122}