···
32
32
export default defineConfig({
33
33
vite: {
34
34
resolve: {
35
35
-
alias: {
36
36
-
$components: resolve("./src/components"),
37
37
-
$layouts: resolve("./src/layouts"),
38
38
-
$pages: resolve("./src/pages"),
39
39
-
$assets: resolve("./src/assets"),
40
40
-
$content: resolve("./src/content"),
41
41
-
},
35
35
+
alias: [
36
36
+
{ find: "$components", replacement: resolve("./src/components") },
37
37
+
{ find: "$layouts", replacement: resolve("./src/layouts") },
38
38
+
{ find: "$pages", replacement: resolve("./src/pages") },
39
39
+
{ find: "$assets", replacement: resolve("./src/assets") },
40
40
+
{ find: "$content", replacement: resolve("./src/content") },
41
41
+
{ find: "shapecraft/three", replacement: resolve("../src/three/index.ts") },
42
42
+
{ find: "shapecraft/noise", replacement: resolve("../src/noise/index.ts") },
43
43
+
{ find: "shapecraft/color", replacement: resolve("../src/color/index.ts") },
44
44
+
{ find: "shapecraft", replacement: resolve("../src/index.ts") },
45
45
+
],
42
46
},
43
47
define: {
44
48
__PUBLIC_CONFIG__: JSON.stringify(config),
···
4
4
5
5
<script>
6
6
async function init() {
7
7
-
const { setupScene } = await import('./scene-setup')
8
8
-
const { icosphere } = await import('../../src')
9
9
-
const { fbm } = await import('../../src/noise')
10
10
-
const { toThreeMesh } = await import('../../src/three')
7
7
+
const THREE = await import('three')
8
8
+
const { OrbitControls } = await import('three/examples/jsm/controls/OrbitControls.js')
9
9
+
const { icosphere } = await import('shapecraft')
10
10
+
const { fbm } = await import('shapecraft/noise')
11
11
+
const { toThreeMesh } = await import('shapecraft/three')
12
12
+
13
13
+
const canvas = document.getElementById('displace-demo') as HTMLCanvasElement
14
14
+
if (!canvas) return
11
15
12
12
-
const ctx = await setupScene('displace-demo', { cameraPos: [3, 2, 3], target: [0, 0.5, 0] })
13
13
-
if (!ctx) return
16
16
+
const renderer = new THREE.WebGLRenderer({ canvas, antialias: true })
17
17
+
renderer.setSize(canvas.clientWidth, canvas.clientHeight)
18
18
+
const scene = new THREE.Scene()
19
19
+
scene.background = new THREE.Color(0x2a2a3a)
20
20
+
const camera = new THREE.PerspectiveCamera(50, canvas.clientWidth / canvas.clientHeight, 0.1, 100)
21
21
+
camera.position.set(3, 2, 3)
22
22
+
const controls = new OrbitControls(camera, canvas)
23
23
+
controls.target.set(0, 0.5, 0)
24
24
+
controls.enableDamping = true
25
25
+
controls.update()
26
26
+
27
27
+
scene.add(new THREE.HemisphereLight(0x87ceeb, 0x3a5a2a, 0.6))
28
28
+
const sun = new THREE.DirectionalLight(0xfff4e0, 1.2)
29
29
+
sun.position.set(5, 8, 5)
30
30
+
scene.add(sun)
14
31
15
32
const noise = fbm({ seed: 42, scale: 2, octaves: 3 })
16
33
17
17
-
ctx.scene.add(toThreeMesh(
18
18
-
icosphere({ radius: 0.5, subdivisions: 2 })
19
19
-
.translate(-1.5, 0.5, 0).vertexColor('#4488cc')
34
34
+
scene.add(toThreeMesh(
35
35
+
icosphere({ radius: 0.5, subdivisions: 2 }).translate(-1.5, 0.5, 0).vertexColor('#4488cc')
20
36
))
21
21
-
ctx.scene.add(toThreeMesh(
37
37
+
scene.add(toThreeMesh(
22
38
icosphere({ radius: 0.5, subdivisions: 2 })
23
39
.displaceNoise(noise, 0.15, { direction: 'radial' })
24
40
.translate(0, 0.5, 0).vertexColor('#cc8844')
25
41
))
26
26
-
ctx.scene.add(toThreeMesh(
42
42
+
scene.add(toThreeMesh(
27
43
icosphere({ radius: 0.5, subdivisions: 2 })
28
28
-
.jitter(0.06, { seed: 7 })
29
29
-
.translate(1.5, 0.5, 0).vertexColor('#88cc44')
44
44
+
.jitter(0.06, { seed: 7 }).translate(1.5, 0.5, 0).vertexColor('#88cc44')
30
45
))
46
46
+
47
47
+
;(function animate() {
48
48
+
requestAnimationFrame(animate)
49
49
+
controls.update()
50
50
+
renderer.render(scene, camera)
51
51
+
})()
31
52
}
32
53
init()
33
54
</script>
···
4
4
5
5
<script>
6
6
async function init() {
7
7
-
const { setupScene } = await import('./scene-setup')
8
8
-
const { tube, loft, thicken } = await import('../../src')
9
9
-
const { toThreeMesh } = await import('../../src/three')
7
7
+
const THREE = await import('three')
8
8
+
const { OrbitControls } = await import('three/examples/jsm/controls/OrbitControls.js')
9
9
+
const { tube, loft, thicken } = await import('shapecraft')
10
10
+
const { toThreeMesh } = await import('shapecraft/three')
10
11
11
11
-
const ctx = await setupScene('loft-demo', { cameraPos: [3, 3, 5], target: [0, 1, 0] })
12
12
-
if (!ctx) return
12
12
+
const canvas = document.getElementById('loft-demo') as HTMLCanvasElement
13
13
+
if (!canvas) return
14
14
+
15
15
+
const renderer = new THREE.WebGLRenderer({ canvas, antialias: true })
16
16
+
renderer.setSize(canvas.clientWidth, canvas.clientHeight)
17
17
+
const scene = new THREE.Scene()
18
18
+
scene.background = new THREE.Color(0x2a2a3a)
19
19
+
const camera = new THREE.PerspectiveCamera(50, canvas.clientWidth / canvas.clientHeight, 0.1, 100)
20
20
+
camera.position.set(3, 3, 5)
21
21
+
const controls = new OrbitControls(camera, canvas)
22
22
+
controls.target.set(0, 1, 0)
23
23
+
controls.enableDamping = true
24
24
+
controls.update()
13
25
14
14
-
// Tapered branch
26
26
+
scene.add(new THREE.HemisphereLight(0x87ceeb, 0x3a5a2a, 0.6))
27
27
+
const sun = new THREE.DirectionalLight(0xfff4e0, 1.2)
28
28
+
sun.position.set(5, 8, 5)
29
29
+
scene.add(sun)
30
30
+
15
31
const branchPath = Array.from({ length: 10 }, (_, i) => {
16
32
const t = i / 9
17
33
return [Math.sin(t * 2) * 0.5, t * 2, Math.cos(t * 3) * 0.3] as [number, number, number]
18
34
})
19
19
-
ctx.scene.add(toThreeMesh(
20
20
-
tube(branchPath, (t) => 0.08 * (1 - t * 0.8), 5)
35
35
+
scene.add(toThreeMesh(
36
36
+
tube(branchPath, (t: number) => 0.08 * (1 - t * 0.8), 5)
21
37
.translate(-1.5, 0, 0).vertexColor('#8B6B4A')
22
38
))
23
39
24
24
-
// Lofted leaf
25
40
const leafPath = Array.from({ length: 8 }, (_, i) => {
26
41
const t = i / 7
27
42
return [0, t * 1.5 + 0.3, -t * 0.3] as [number, number, number]
28
43
})
29
44
const leaf = loft({
30
45
path: leafPath,
31
31
-
shape: (t) => {
46
46
+
shape: (t: number) => {
32
47
const w = 0.25 * Math.sin(t * Math.PI) * (1 - t * 0.3)
33
33
-
return [[w, 0.02], [0, 0.06], [-w, 0.02], [-w, -0.01], [0, -0.02], [w, -0.01]]
48
48
+
return [[w, 0.02], [0, 0.06], [-w, 0.02], [-w, -0.01], [0, -0.02], [w, -0.01]] as [number,number][]
34
49
},
35
35
-
closedShape: true,
36
36
-
closed: false,
37
37
-
up: [0, 0, 1],
50
50
+
closedShape: true, closed: false, up: [0, 0, 1],
38
51
}).vertexColor('#3a7a2a')
39
39
-
ctx.scene.add(toThreeMesh(thicken(leaf, 0.01)))
52
52
+
scene.add(toThreeMesh(thicken(leaf, 0.01)))
40
53
41
41
-
// Wavy trunk
42
54
const trunkPath = Array.from({ length: 15 }, (_, i) => {
43
55
const t = i / 14
44
56
return [1.5 + Math.sin(t * 4) * 0.15, t * 2, 0] as [number, number, number]
45
57
})
46
46
-
ctx.scene.add(toThreeMesh(
47
47
-
tube(trunkPath, (t) => {
58
58
+
scene.add(toThreeMesh(
59
59
+
tube(trunkPath, (t: number) => {
48
60
const taper = 0.1 * (1 - t * 0.5)
49
61
const wave = 1 + Math.sin(t * Math.PI * 10) * 0.12
50
62
return taper * wave
51
63
}, 5).vertexColor('#6a5030')
52
64
))
65
65
+
66
66
+
;(function animate() {
67
67
+
requestAnimationFrame(animate)
68
68
+
controls.update()
69
69
+
renderer.render(scene, camera)
70
70
+
})()
53
71
}
54
72
init()
55
73
</script>
···
4
4
5
5
<script>
6
6
async function init() {
7
7
-
const { setupScene } = await import('./scene-setup')
8
8
-
const { plane, icosphere, merge, tube, createRng } = await import('../../src')
9
9
-
const { fbm } = await import('../../src/noise')
10
10
-
const { heightGradient, normalGradient } = await import('../../src/color')
11
11
-
const { toThreeMesh } = await import('../../src/three')
7
7
+
const THREE = await import('three')
8
8
+
const { OrbitControls } = await import('three/examples/jsm/controls/OrbitControls.js')
9
9
+
const { plane, icosphere, merge, tube, createRng } = await import('shapecraft')
10
10
+
const { fbm } = await import('shapecraft/noise')
11
11
+
const { heightGradient, normalGradient } = await import('shapecraft/color')
12
12
+
const { toThreeMesh } = await import('shapecraft/three')
13
13
+
14
14
+
const canvas = document.getElementById('patterns-demo') as HTMLCanvasElement
15
15
+
if (!canvas) return
16
16
+
17
17
+
const renderer = new THREE.WebGLRenderer({ canvas, antialias: true })
18
18
+
renderer.setSize(canvas.clientWidth, canvas.clientHeight)
19
19
+
const scene = new THREE.Scene()
20
20
+
scene.background = new THREE.Color(0x2a2a3a)
21
21
+
const camera = new THREE.PerspectiveCamera(55, canvas.clientWidth / canvas.clientHeight, 0.1, 100)
22
22
+
camera.position.set(6, 5, 8)
23
23
+
const controls = new OrbitControls(camera, canvas)
24
24
+
controls.target.set(0, 0.5, 0)
25
25
+
controls.enableDamping = true
26
26
+
controls.update()
12
27
13
13
-
const ctx = await setupScene('patterns-demo', { cameraPos: [6, 5, 8], target: [0, 0.5, 0] })
14
14
-
if (!ctx) return
28
28
+
scene.add(new THREE.HemisphereLight(0x87ceeb, 0x3a5a2a, 0.6))
29
29
+
const sun = new THREE.DirectionalLight(0xfff4e0, 1.2)
30
30
+
sun.position.set(5, 8, 5)
31
31
+
scene.add(sun)
15
32
16
33
const terrainNoise = fbm({ seed: 7, scale: 0.2, octaves: 3 })
17
17
-
ctx.scene.add(toThreeMesh(
34
34
+
scene.add(toThreeMesh(
18
35
plane({ size: 12, segments: 40 })
19
36
.displaceNoise(terrainNoise, 0.5)
20
37
.vertexColor(heightGradient([
···
30
47
const z = (rand() - 0.5) * 8
31
48
const h = 1 + rand() * 1.5
32
49
const r = 0.3 + rand() * 0.3
33
33
-
34
50
const trunk = tube(
35
35
-
[[0,0,0],[0,h*0.4,0]], (t) => 0.04 * (1 - t * 0.5), 5
51
51
+
[[0,0,0],[0,h*0.4,0]], (t: number) => 0.04 * (1 - t * 0.5), 5
36
52
).vertexColor('#4a2815')
37
37
-
38
53
const canopy = icosphere({ radius: r, subdivisions: 1 })
39
54
.scale(1, 0.8, 1).jitter(r * 0.08, { seed: i })
40
55
.translate(0, h * 0.5, 0)
41
56
.faceColor(normalGradient([0.2, 0.5, 0.1], [0.1, 0.3, 0.05]))
42
42
-
43
57
const obj = toThreeMesh(merge(trunk, canopy))
44
58
obj.position.set(x, terrainNoise.get(x, z), z)
45
45
-
obj.castShadow = true
46
46
-
ctx.scene.add(obj)
59
59
+
scene.add(obj)
47
60
}
61
61
+
62
62
+
;(function animate() {
63
63
+
requestAnimationFrame(animate)
64
64
+
controls.update()
65
65
+
renderer.render(scene, camera)
66
66
+
})()
48
67
}
49
68
init()
50
69
</script>
···
4
4
5
5
<script>
6
6
async function init() {
7
7
-
const { setupScene } = await import('./scene-setup')
8
8
-
const { box, sphere, cylinder, cone, icosphere, torus } = await import('../../src')
9
9
-
const { toThreeMesh } = await import('../../src/three')
7
7
+
const THREE = await import('three')
8
8
+
const { OrbitControls } = await import('three/examples/jsm/controls/OrbitControls.js')
9
9
+
const { box, sphere, cylinder, cone, icosphere, torus } = await import('shapecraft')
10
10
+
const { toThreeMesh } = await import('shapecraft/three')
11
11
+
12
12
+
const canvas = document.getElementById('primitives-demo') as HTMLCanvasElement
13
13
+
if (!canvas) return
14
14
+
15
15
+
const renderer = new THREE.WebGLRenderer({ canvas, antialias: true })
16
16
+
renderer.setSize(canvas.clientWidth, canvas.clientHeight)
17
17
+
const scene = new THREE.Scene()
18
18
+
scene.background = new THREE.Color(0x2a2a3a)
19
19
+
const camera = new THREE.PerspectiveCamera(50, canvas.clientWidth / canvas.clientHeight, 0.1, 100)
20
20
+
camera.position.set(6, 4, 8)
21
21
+
const controls = new OrbitControls(camera, canvas)
22
22
+
controls.target.set(3, 0.5, 0)
23
23
+
controls.enableDamping = true
24
24
+
controls.update()
10
25
11
11
-
const ctx = await setupScene('primitives-demo', { cameraPos: [6, 4, 8], target: [3, 0.5, 0] })
12
12
-
if (!ctx) return
26
26
+
scene.add(new THREE.HemisphereLight(0x87ceeb, 0x3a5a2a, 0.6))
27
27
+
const sun = new THREE.DirectionalLight(0xfff4e0, 1.2)
28
28
+
sun.position.set(5, 8, 5)
29
29
+
scene.add(sun)
13
30
14
31
const primitives = [
15
32
box().vertexColor('#cc8844'),
···
20
37
torus().vertexColor('#aa44cc'),
21
38
]
22
39
primitives.forEach((m, i) => {
23
23
-
ctx.scene.add(toThreeMesh(m.translate(i * 1.5, 0.5, 0)))
40
40
+
scene.add(toThreeMesh(m.translate(i * 1.5, 0.5, 0)))
24
41
})
42
42
+
43
43
+
;(function animate() {
44
44
+
requestAnimationFrame(animate)
45
45
+
controls.update()
46
46
+
renderer.render(scene, camera)
47
47
+
})()
25
48
}
26
49
init()
27
50
</script>
···
4
4
5
5
<script>
6
6
async function init() {
7
7
-
const { setupScene } = await import('./scene-setup')
8
8
-
const { box, sphere, merge } = await import('../../src')
9
9
-
const { toThreeMesh } = await import('../../src/three')
7
7
+
const THREE = await import('three')
8
8
+
const { OrbitControls } = await import('three/examples/jsm/controls/OrbitControls.js')
9
9
+
const { box, sphere, merge } = await import('shapecraft')
10
10
+
const { toThreeMesh } = await import('shapecraft/three')
10
11
11
11
-
const ctx = await setupScene('quickstart-demo')
12
12
-
if (!ctx) return
12
12
+
const canvas = document.getElementById('quickstart-demo') as HTMLCanvasElement
13
13
+
if (!canvas) return
14
14
+
15
15
+
const renderer = new THREE.WebGLRenderer({ canvas, antialias: true })
16
16
+
renderer.setSize(canvas.clientWidth, canvas.clientHeight)
17
17
+
const scene = new THREE.Scene()
18
18
+
scene.background = new THREE.Color(0x2a2a3a)
19
19
+
const camera = new THREE.PerspectiveCamera(50, canvas.clientWidth / canvas.clientHeight, 0.1, 100)
20
20
+
camera.position.set(3, 2, 4)
21
21
+
const controls = new OrbitControls(camera, canvas)
22
22
+
controls.target.set(0, 0.5, 0)
23
23
+
controls.enableDamping = true
24
24
+
controls.update()
25
25
+
26
26
+
scene.add(new THREE.HemisphereLight(0x87ceeb, 0x3a5a2a, 0.6))
27
27
+
const sun = new THREE.DirectionalLight(0xfff4e0, 1.2)
28
28
+
sun.position.set(5, 8, 5)
29
29
+
scene.add(sun)
13
30
14
31
const b = box({ size: 1 }).vertexColor('#cc8844')
15
32
const s = sphere({ radius: 0.5 }).translate(2, 0, 0).vertexColor('#4488cc')
16
16
-
const combined = merge(b, s).translate(-1, 0.5, 0)
17
17
-
ctx.scene.add(toThreeMesh(combined))
33
33
+
scene.add(toThreeMesh(merge(b, s).translate(-1, 0.5, 0)))
34
34
+
35
35
+
;(function animate() {
36
36
+
requestAnimationFrame(animate)
37
37
+
controls.update()
38
38
+
renderer.render(scene, camera)
39
39
+
})()
18
40
}
19
41
init()
20
42
</script>
···
4
4
5
5
<script>
6
6
async function init() {
7
7
-
const { setupScene } = await import('./scene-setup')
8
8
-
const { icosphere } = await import('../../src')
9
9
-
const { toThreeMesh } = await import('../../src/three')
7
7
+
const THREE = await import('three')
8
8
+
const { OrbitControls } = await import('three/examples/jsm/controls/OrbitControls.js')
9
9
+
const { icosphere } = await import('shapecraft')
10
10
+
const { toThreeMesh } = await import('shapecraft/three')
10
11
11
11
-
const ctx = await setupScene('subdivide-demo', { cameraPos: [4, 2, 4], target: [0, 0.5, 0] })
12
12
-
if (!ctx) return
12
12
+
const canvas = document.getElementById('subdivide-demo') as HTMLCanvasElement
13
13
+
if (!canvas) return
13
14
14
14
-
ctx.scene.add(toThreeMesh(
15
15
-
icosphere({ radius: 0.5, subdivisions: 0 })
16
16
-
.translate(-2, 0.5, 0).vertexColor('#cc4444')
15
15
+
const renderer = new THREE.WebGLRenderer({ canvas, antialias: true })
16
16
+
renderer.setSize(canvas.clientWidth, canvas.clientHeight)
17
17
+
const scene = new THREE.Scene()
18
18
+
scene.background = new THREE.Color(0x2a2a3a)
19
19
+
const camera = new THREE.PerspectiveCamera(50, canvas.clientWidth / canvas.clientHeight, 0.1, 100)
20
20
+
camera.position.set(4, 2, 4)
21
21
+
const controls = new OrbitControls(camera, canvas)
22
22
+
controls.target.set(0, 0.5, 0)
23
23
+
controls.enableDamping = true
24
24
+
controls.update()
25
25
+
26
26
+
scene.add(new THREE.HemisphereLight(0x87ceeb, 0x3a5a2a, 0.6))
27
27
+
const sun = new THREE.DirectionalLight(0xfff4e0, 1.2)
28
28
+
sun.position.set(5, 8, 5)
29
29
+
scene.add(sun)
30
30
+
31
31
+
scene.add(toThreeMesh(
32
32
+
icosphere({ radius: 0.5, subdivisions: 0 }).translate(-2, 0.5, 0).vertexColor('#cc4444')
17
33
))
18
18
-
ctx.scene.add(toThreeMesh(
34
34
+
scene.add(toThreeMesh(
19
35
icosphere({ radius: 0.5, subdivisions: 0 })
20
36
.subdivideAdaptive(0.3).spherize(0.5)
21
37
.translate(0, 0.5, 0).vertexColor('#44cc44')
22
38
))
23
23
-
ctx.scene.add(toThreeMesh(
39
39
+
scene.add(toThreeMesh(
24
40
icosphere({ radius: 0.5, subdivisions: 0 })
25
41
.subdivideAdaptive(0.3).spherize(0.5)
26
42
.jitter(0.04, { seed: 3 })
27
43
.translate(2, 0.5, 0).vertexColor('#4444cc')
28
44
))
45
45
+
46
46
+
;(function animate() {
47
47
+
requestAnimationFrame(animate)
48
48
+
controls.update()
49
49
+
renderer.render(scene, camera)
50
50
+
})()
29
51
}
30
52
init()
31
53
</script>
···
1
1
-
export async function setupScene(canvasId: string, options?: {
2
2
-
cameraPos?: [number, number, number]
3
3
-
target?: [number, number, number]
4
4
-
background?: number
5
5
-
}) {
6
6
-
const canvas = document.getElementById(canvasId) as HTMLCanvasElement
7
7
-
if (!canvas) return null
8
8
-
9
9
-
const THREE = await import('three')
10
10
-
const { OrbitControls } = await import('three/examples/jsm/controls/OrbitControls.js')
11
11
-
12
12
-
const renderer = new THREE.WebGLRenderer({ canvas, antialias: true })
13
13
-
renderer.setSize(canvas.clientWidth, canvas.clientHeight)
14
14
-
renderer.setPixelRatio(window.devicePixelRatio)
15
15
-
renderer.toneMapping = THREE.ACESFilmicToneMapping
16
16
-
17
17
-
const scene = new THREE.Scene()
18
18
-
scene.background = new THREE.Color(options?.background ?? 0x2a2a3a)
19
19
-
20
20
-
const camera = new THREE.PerspectiveCamera(50, canvas.clientWidth / canvas.clientHeight, 0.1, 100)
21
21
-
const cp = options?.cameraPos ?? [3, 2, 4]
22
22
-
camera.position.set(cp[0], cp[1], cp[2])
23
23
-
24
24
-
const controls = new OrbitControls(camera, canvas)
25
25
-
const t = options?.target ?? [0, 0.5, 0]
26
26
-
controls.target.set(t[0], t[1], t[2])
27
27
-
controls.enableDamping = true
28
28
-
controls.update()
29
29
-
30
30
-
// Lights
31
31
-
scene.add(new THREE.HemisphereLight(0x87ceeb, 0x3a5a2a, 0.6))
32
32
-
const sun = new THREE.DirectionalLight(0xfff4e0, 1.2)
33
33
-
sun.position.set(5, 8, 5)
34
34
-
scene.add(sun)
35
35
-
36
36
-
37
37
-
function animate() {
38
38
-
requestAnimationFrame(animate)
39
39
-
controls.update()
40
40
-
renderer.render(scene, camera)
41
41
-
}
42
42
-
animate()
43
43
-
44
44
-
const ro = new ResizeObserver(() => {
45
45
-
camera.aspect = canvas.clientWidth / canvas.clientHeight
46
46
-
camera.updateProjectionMatrix()
47
47
-
renderer.setSize(canvas.clientWidth, canvas.clientHeight)
48
48
-
})
49
49
-
ro.observe(canvas)
50
50
-
51
51
-
return { scene, camera, renderer, controls }
52
52
-
}