[READ-ONLY] Mirror of https://github.com/flo-bit/shapecraft.
flo-bit.dev/shapecraft/
3.7 kB
137 lines
1import PatternsDemo from './components/PatternsDemo.astro'
2
3# Patterns
4
5<PatternsDemo />
6
7## Generator functions
8
9A generator is just a function that returns a Mesh:
10
11```ts
12function tree(options?: { height?: number; seed?: number }): Mesh {
13 const rand = createRng(options?.seed ?? 1)
14 const trunk = cylinder({ radius: 0.05, height: height * 0.4 })
15 .translate(0, height * 0.2, 0)
16 .vertexColor([0.35, 0.22, 0.1])
17 const canopy = icosphere({ radius: 0.5 })
18 .translate(0, height * 0.6, 0)
19 .faceColor(...)
20 return merge(trunk, canopy)
21}
22```
23
24## Seeded randomness
25
26Use `createRng` (wraps alea PRNG) for deterministic generation:
27
28```ts
29import { createRng } from 'shapecraft'
30
31const rand = createRng(42) // seed → deterministic sequence
32rand() // 0-1
33rand() // next value
34
35// Derive sub-seeds for noise, jitter, etc.
36function subSeed() { return Math.floor(rand() * 2147483647) }
37const noise = new UberNoise({ seed: subSeed() })
38mesh.jitter(0.05, { seed: subSeed() })
39```
40
41**Important:** always consume `rand()` / `subSeed()` calls in the same order regardless of options, so changing one parameter doesn't shift the entire random sequence.
42
43## Option schemas & presets
44
45Define configurable generators with typed schemas:
46
47```ts
48import { resolveOptions, type OptionSchema } from 'shapecraft'
49
50const treeSchema = {
51 height: { type: 'range', default: 2.5, min: 0.5, max: 6, step: 0.1 },
52 seed: { type: 'integer', default: 1, min: 1, max: 100 },
53 lean: { type: 'range', default: [0.1, 0.4], min: 0, max: 1 }, // randomized range
54 showCanopy: { type: 'boolean', default: true },
55 colors: { type: 'color-array', default: ['#1a5a10', '#2a7518'] },
56} satisfies OptionSchema
57
58const presets = {
59 default: {},
60 autumn: { colors: ['#c44422', '#d48825'] },
61 winter: { colors: ['#4a4a4a'], snowColors: ['#e8e8f0'] },
62}
63
64function tree(options = {}) {
65 const rand = createRng(options.seed ?? 1)
66 const o = resolveOptions(treeSchema, options, presets, rand)
67 // o.height is a number, o.lean is resolved from [min,max] via rand
68}
69```
70
71Numeric defaults can be `[min, max]` — resolved deterministically from the seed. The editor shows the midpoint.
72
73## Scatter points
74
75```ts
76import { scatterOnSphere } from 'shapecraft'
77
78// N random points on a sphere surface
79const points = scatterOnSphere(5, seed, {
80 radius: 1,
81 polarMin: Math.PI * 0.3, // avoid top
82 polarMax: Math.PI * 0.7, // avoid bottom
83})
84// → [[x,y,z], [x,y,z], ...]
85```
86
87## Terrain workflow
88
89```ts
90const terrain = plane({ size: 20, segments: 100 })
91 .displaceNoise(fbm({ seed: 42, scale: 0.2, octaves: 5 }), 3)
92 .vertexColor(heightGradient([
93 [0, [0.2, 0.5, 0.1]],
94 [1.5, [0.5, 0.35, 0.2]],
95 [3, [1, 1, 1]],
96 ]))
97```
98
99## Organic shapes with icosphere
100
101```ts
102// Subdivide → spherize → displace radially (no gaps)
103icosphere({ radius: r, subdivisions: 0 })
104 .subdivideAdaptive(edgeLen)
105 .spherize(r)
106 .displaceNoise(noise, r * 0.5, { direction: 'radial' })
107 .jitter(r * 0.04, { seed })
108```
109
110Use `direction: 'radial'` instead of `'normal'` on non-indexed geometry to avoid gaps between faces.
111
112## Branches & tubes
113
114```ts
115import { tube } from 'shapecraft'
116
117// Tapered branch along a curved path
118const path = [[0,0,0], [0.1,0.5,0], [0.3,1,0.1], [0.2,1.5,0]]
119const branch = tube(path, (t) => 0.05 * (1 - t * 0.8), 5)
120```
121
122## Leaves with loft + thicken
123
124```ts
125import { loft, thicken } from 'shapecraft'
126
127const leaf = loft({
128 path: leafPath,
129 shape: (t) => {
130 const w = 0.2 * Math.sin(t * Math.PI) // wide in middle
131 return [[-w, 0], [0, w*0.2], [w, 0]]
132 },
133 closedShape: false,
134 up: [0, 1, 0],
135})
136const solidLeaf = thicken(leaf, 0.01)
137```