create-definedmotion 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (98) hide show
  1. package/bin/index.js +3 -0
  2. package/package.json +37 -0
  3. package/src/cli.js +100 -0
  4. package/template/.editorconfig +9 -0
  5. package/template/.prettierignore +6 -0
  6. package/template/.prettierrc.yaml +10 -0
  7. package/template/_gitignore +10 -0
  8. package/template/build/entitlements.mac.plist +12 -0
  9. package/template/build/icon.icns +0 -0
  10. package/template/build/icon.ico +0 -0
  11. package/template/build/icon.png +0 -0
  12. package/template/electron-builder.yml +43 -0
  13. package/template/electron.vite.config.ts +50 -0
  14. package/template/eslint.config.mjs +24 -0
  15. package/template/package-lock.json +10299 -0
  16. package/template/package.json +64 -0
  17. package/template/resources/icon.png +0 -0
  18. package/template/src/assets/audio/fadeSound.mp3 +0 -0
  19. package/template/src/assets/audio/fadeSound2.mp3 +0 -0
  20. package/template/src/assets/audio/interstellar.mp3 +0 -0
  21. package/template/src/assets/audio/keyboard1.mp3 +0 -0
  22. package/template/src/assets/audio/keyboard2.mp3 +0 -0
  23. package/template/src/assets/audio/keyboard3.mp3 +0 -0
  24. package/template/src/assets/audio/tick_sound.mp3 +0 -0
  25. package/template/src/assets/base.css +67 -0
  26. package/template/src/assets/electron.svg +10 -0
  27. package/template/src/assets/fonts/Geo-Regular.woff +0 -0
  28. package/template/src/assets/fonts/Montserrat-Italic-VariableFont_wght.woff2 +0 -0
  29. package/template/src/assets/fonts/Montserrat-Medium.ttf +0 -0
  30. package/template/src/assets/fonts/Montserrat-Medium.woff +0 -0
  31. package/template/src/assets/fonts/Montserrat-VariableFont_wght.woff2 +0 -0
  32. package/template/src/assets/fonts/glitch.ttf +0 -0
  33. package/template/src/assets/hdri/indoor1.hdr +0 -0
  34. package/template/src/assets/hdri/metro1.hdr +0 -0
  35. package/template/src/assets/hdri/outdoor1.hdr +0 -0
  36. package/template/src/assets/hdri/photo-studio1.hdr +0 -0
  37. package/template/src/assets/hdri/photo-studio2.hdr +0 -0
  38. package/template/src/assets/hdri/photo-studio3.hdr +0 -0
  39. package/template/src/assets/objects/keyboardScene/ibm-keyboard.glb +0 -0
  40. package/template/src/assets/wavy-lines.svg +25 -0
  41. package/template/src/entry.ts +20 -0
  42. package/template/src/example_scenes/alternativesScene.ts +88 -0
  43. package/template/src/example_scenes/dependencyScene.ts +116 -0
  44. package/template/src/example_scenes/fourierMachineScene.ts +108 -0
  45. package/template/src/example_scenes/fourierSeriesScene.ts +678 -0
  46. package/template/src/example_scenes/keyboardScene.ts +447 -0
  47. package/template/src/example_scenes/surfaceScene.ts +88 -0
  48. package/template/src/example_scenes/tutorials/easy1.ts +59 -0
  49. package/template/src/example_scenes/tutorials/easy2.ts +141 -0
  50. package/template/src/example_scenes/tutorials/easy3.ts +133 -0
  51. package/template/src/example_scenes/tutorials/medium1.ts +154 -0
  52. package/template/src/example_scenes/vectorField.ts +209 -0
  53. package/template/src/example_scenes/visulizingFunctions.ts +246 -0
  54. package/template/src/main/index.ts +101 -0
  55. package/template/src/main/rendering.ts +219 -0
  56. package/template/src/main/storage.ts +35 -0
  57. package/template/src/preload/index.d.ts +8 -0
  58. package/template/src/preload/index.ts +36 -0
  59. package/template/src/renderer/index.html +17 -0
  60. package/template/src/renderer/src/App.svelte +130 -0
  61. package/template/src/renderer/src/app.css +24 -0
  62. package/template/src/renderer/src/env.d.ts +2 -0
  63. package/template/src/renderer/src/lib/animation/animations.ts +214 -0
  64. package/template/src/renderer/src/lib/animation/captureCanvas.ts +85 -0
  65. package/template/src/renderer/src/lib/animation/helpers.ts +7 -0
  66. package/template/src/renderer/src/lib/animation/interpolations.ts +155 -0
  67. package/template/src/renderer/src/lib/animation/protocols.ts +79 -0
  68. package/template/src/renderer/src/lib/audio/loader.ts +104 -0
  69. package/template/src/renderer/src/lib/fonts/Roboto_Regular.json +1 -0
  70. package/template/src/renderer/src/lib/fonts/montserrat-medium.json +1 -0
  71. package/template/src/renderer/src/lib/fonts/montserrat.json +1 -0
  72. package/template/src/renderer/src/lib/general/helpers.ts +77 -0
  73. package/template/src/renderer/src/lib/general/onDestory.ts +10 -0
  74. package/template/src/renderer/src/lib/mathHelpers/vectors.ts +18 -0
  75. package/template/src/renderer/src/lib/rendering/bumpMaps/noise.ts +84 -0
  76. package/template/src/renderer/src/lib/rendering/helpers.ts +35 -0
  77. package/template/src/renderer/src/lib/rendering/lighting3d.ts +387 -0
  78. package/template/src/renderer/src/lib/rendering/materials.ts +6 -0
  79. package/template/src/renderer/src/lib/rendering/objects/import.ts +148 -0
  80. package/template/src/renderer/src/lib/rendering/objects2d.ts +489 -0
  81. package/template/src/renderer/src/lib/rendering/objects3d.ts +89 -0
  82. package/template/src/renderer/src/lib/rendering/protocols.ts +21 -0
  83. package/template/src/renderer/src/lib/rendering/setup.ts +71 -0
  84. package/template/src/renderer/src/lib/rendering/svg/drawing.ts +213 -0
  85. package/template/src/renderer/src/lib/rendering/svg/parsing.ts +717 -0
  86. package/template/src/renderer/src/lib/rendering/svg/rastered.ts +42 -0
  87. package/template/src/renderer/src/lib/rendering/svgObjects.ts +1137 -0
  88. package/template/src/renderer/src/lib/scene/helpers.ts +89 -0
  89. package/template/src/renderer/src/lib/scene/sceneClass.ts +648 -0
  90. package/template/src/renderer/src/lib/shaders/background_gradient/frag.glsl +12 -0
  91. package/template/src/renderer/src/lib/shaders/background_gradient/vert.glsl +6 -0
  92. package/template/src/renderer/src/lib/shaders/hdri_blur/frag.glsl +45 -0
  93. package/template/src/renderer/src/lib/shaders/hdri_blur/vert.glsl +5 -0
  94. package/template/src/renderer/src/main.ts +9 -0
  95. package/template/svelte.config.mjs +7 -0
  96. package/template/tsconfig.json +4 -0
  97. package/template/tsconfig.node.json +10 -0
  98. package/template/tsconfig.web.json +32 -0
@@ -0,0 +1,141 @@
1
+ // Tutorial 2 (easy2.ts)
2
+ // Goal for this animation:
3
+ // 1) Render a time-varying mathematical surface (z = f(x, y, t))
4
+ // 2) Add a glowing orb with a point light
5
+ // 3) Animate the camera on a smooth orbit while the surface deforms
6
+
7
+ import * as THREE from 'three'
8
+
9
+ import { AnimatedScene, HotReloadSetting, SpaceSetting } from '$renderer/lib/scene/sceneClass'
10
+
11
+ // If your helpers live elsewhere, tweak these paths:'
12
+ import { createFunctionSurface, updateFunctionSurface } from '$renderer/lib/rendering/objects3d'
13
+ import { addBackgroundGradient } from '$renderer/lib/rendering/lighting3d'
14
+
15
+ // ─────────────────────────────────────────────────────────────────────────────
16
+ // Step 0: Materials used by our surface and our glowing sphere
17
+ // MeshStandardMaterial gives us physically-based shading that reacts to lights.
18
+ // For the sphere, we use a strong emissive color so it "glows" even without light.
19
+ // ─────────────────────────────────────────────────────────────────────────────
20
+ const surfaceMaterial = new THREE.MeshStandardMaterial({
21
+ color: 0xffffff,
22
+ metalness: 0.8,
23
+ roughness: 0.1,
24
+ side: THREE.DoubleSide
25
+ }) as any // cast to any to satisfy TS if createFunctionSurface has a stricter type
26
+
27
+ const sphereMaterial = new THREE.MeshStandardMaterial({
28
+ color: 0x000000, // base color (almost irrelevant since emissive dominates)
29
+ emissive: 0xffffff, // self-illumination color
30
+ emissiveIntensity: 200 // strong glow
31
+ })
32
+
33
+ // ─────────────────────────────────────────────────────────────────────────────
34
+ // Step 1: A time-dependent function that returns a surface height function.
35
+ // We return a function (x, y) => z that changes smoothly over time.
36
+ // Try tweaking constants to see different wave behaviors.
37
+ // ─────────────────────────────────────────────────────────────────────────────
38
+ const sineTimeFunction = (time: number): ((x: number, y: number) => number) => {
39
+ return (x: number, y: number) =>
40
+ (5 * (Math.sin(x * 2 + time) * Math.cos(y * 2 + time))) /
41
+ (Math.pow(Math.abs(x) + Math.abs(y), 2) + 5) +
42
+ 3
43
+ }
44
+
45
+ // ─────────────────────────────────────────────────────────────────────────────
46
+ // Step 2: Export an AnimatedScene just like in tutorial 1, but in 3D.
47
+ // We use HotReloadSetting.BeginFromCurrent since it will be much faster during debug.
48
+ // Since this has accumulative effects (angle += 0.005) notice that the angle is not correct at hot reload.
49
+ // Regardless of our HotReloadSetting, the renders will always be correct
50
+ // ─────────────────────────────────────────────────────────────────────────────
51
+ export function tutorial_easy2(): AnimatedScene {
52
+ return new AnimatedScene(
53
+ 1500, // width (square clip)
54
+ 1500, // height
55
+ SpaceSetting.ThreeDim, // 3D scene
56
+ HotReloadSetting.BeginFromCurrent,
57
+ async (scene) => {
58
+ // ───────────────────────────────────────────────────────────────────────
59
+ // Step 3: Basic environment (background gradient)
60
+ // ───────────────────────────────────────────────────────────────────────
61
+ addBackgroundGradient({
62
+ scene,
63
+ topColor: 0x0c8ccd, // blue-ish
64
+ bottomColor: 0x000000, // black
65
+ lightingIntensity: 10
66
+ })
67
+
68
+ // We use three.js directly to create a grid and axes
69
+ const gridHelper = new THREE.GridHelper(20, 20)
70
+ const axesHelper = new THREE.AxesHelper(20)
71
+ scene.add(gridHelper, axesHelper)
72
+
73
+ // ───────────────────────────────────────────────────────────────────────
74
+ // Step 4: Create a function surface over a domain.
75
+ // We start with t = 0, then update it every frame in onEachTick.
76
+ // The returned object is a THREE.Mesh we can style like any other mesh.
77
+ // ───────────────────────────────────────────────────────────────────────
78
+ const DOMAIN: [number, number, number, number] = [-7, 7, -7, 7] // [xMin, xMax, yMin, yMax]
79
+ const surface = createFunctionSurface(sineTimeFunction(0), ...DOMAIN)
80
+ surface.material = surfaceMaterial
81
+ scene.add(surface)
82
+
83
+ // ───────────────────────────────────────────────────────────────────────
84
+ // Step 5: Add a glowing sphere with a point light.
85
+ // We put them in a Group so they move together if we want.
86
+ // The light’s position is kept in sync with the sphere.
87
+ // ───────────────────────────────────────────────────────────────────────
88
+ const sphere = new THREE.Mesh(new THREE.SphereGeometry(1, 32, 32), sphereMaterial)
89
+ const pointLight = new THREE.PointLight(0xffffff, 50) // (color, intensity)
90
+ pointLight.position.copy(sphere.position)
91
+
92
+ const orbGroup = new THREE.Group()
93
+ orbGroup.add(sphere, pointLight)
94
+ orbGroup.position.y = 6 // float above the surface a bit
95
+ scene.add(orbGroup)
96
+
97
+ // ───────────────────────────────────────────────────────────────────────
98
+ // Step 6: Camera setup + gentle orbit animation.
99
+ // We place the camera, measure its distance to the origin, and then
100
+ // slowly orbit around (0,0,0). The wobble changes radius over time.
101
+ // Thees numbers for the position is difficult to guess,
102
+ // the workflow is therefore to navigate the scene to where you want it and then copy the positions it prints.
103
+ // This avoids awkward value guessing for position and rotation
104
+ // ───────────────────────────────────────────────────────────────────────
105
+ scene.camera.position.set(3.889329, 7.895859, 10.51772)
106
+ scene.camera.rotation.set(-0.6027059, 0.3079325, 0.2056132)
107
+
108
+ const LOOK_AT = new THREE.Vector3(0, 0, 0)
109
+ const baseRadius = scene.camera.position.distanceTo(LOOK_AT)
110
+ let angle = 0
111
+
112
+ // ───────────────────────────────────────────────────────────────────────
113
+ // Step 7: Per-frame updates.
114
+ // - Update the surface with the current time (tick)
115
+ // - Keep the orb group floating
116
+ // - Orbit the camera and keep it looking at the center
117
+ // ───────────────────────────────────────────────────────────────────────
118
+ scene.onEachTick((tick) => {
119
+ const t = tick / 20
120
+ const f = sineTimeFunction(t)
121
+ updateFunctionSurface(surface, f, ...DOMAIN)
122
+
123
+ // Keep the orb hovering above the surface
124
+ orbGroup.position.y = 6
125
+
126
+ // Camera orbit with a subtle radius wobble
127
+ angle += 0.005
128
+ const wobble = (Math.sin(tick / 50) + 2) / 2 // ranges roughly [0.5, 1.5]
129
+ scene.camera.position.x = Math.sin(angle) * baseRadius * wobble
130
+ scene.camera.position.z = Math.cos(angle) * baseRadius * wobble
131
+ scene.camera.lookAt(LOOK_AT)
132
+ })
133
+
134
+ // ───────────────────────────────────────────────────────────────────────
135
+ // Step 8: Let the animation run for a while before finishing.
136
+ // This adds 10 seconds of "play time" to the scene’s schedule.
137
+ // ───────────────────────────────────────────────────────────────────────
138
+ scene.addWait(10_000)
139
+ }
140
+ )
141
+ }
@@ -0,0 +1,133 @@
1
+ // Tutorial 3 (easy3.ts)
2
+ // Goal for this animation:
3
+ // 1) Show a fullscreen-ish color card
4
+ // 2) Swap the headline text every few beats
5
+ // 3) Play a short tick sound on each swap
6
+
7
+ import * as THREE from 'three'
8
+
9
+ import { easeLinear } from '$renderer/lib/animation/interpolations'
10
+ import { createAnim } from '$renderer/lib/animation/protocols'
11
+ import { createFastText, createRectangle, updateText } from '$renderer/lib/rendering/objects2d'
12
+ import { AnimatedScene, HotReloadSetting, SpaceSetting } from '$renderer/lib/scene/sceneClass'
13
+
14
+ // Adjust this path if your assets are elsewhere:
15
+ import tickSound from '$assets/audio/tick_sound.mp3'
16
+
17
+ // ─────────────────────────────────────────────────────────────────────────────
18
+ // Step 0: Slides = headline alternatives (translated to English)
19
+ // ─────────────────────────────────────────────────────────────────────────────
20
+ const ALTERNATIVES = [
21
+ 'Introductory Mathematical Analysis',
22
+ 'Linear Algebra',
23
+ 'The Physicist’s Toolkit',
24
+ 'Mathematical Analysis, Continued',
25
+ 'Mechanics 1',
26
+ 'Programming Techniques and Numerical Analysis',
27
+ 'Multivariable Calculus',
28
+ 'Probability and Statistics',
29
+ 'Mechanics 2',
30
+ 'Complex Analysis',
31
+ 'Experimental Physics 1',
32
+ 'Electrical Circuits and Systems',
33
+ 'Vector Fields and Electromagnetic Field Theory',
34
+ 'Control Engineering (F)',
35
+ 'Bayesian Inference and Machine Learning',
36
+ 'Fourier Analysis',
37
+ 'Optics',
38
+ 'Continuum Mechanics',
39
+ 'Thermodynamics and Statistical Mechanics',
40
+ 'Quantum Physics',
41
+ 'Data Structures and Algorithms',
42
+ 'Experimental Physics 2',
43
+ 'Solid State Physics',
44
+ 'Subatomic Physics',
45
+ 'Environmental Physics',
46
+ 'Algorithms',
47
+ 'Logic for Computer Science',
48
+ 'Introduction to Data Science and AI',
49
+ 'Programming Languages',
50
+ 'Design of AI Systems',
51
+ 'Algorithms for Machine Learning and Inference',
52
+ 'Computational Methods for Large-Scale Data',
53
+ 'Compiler Construction'
54
+ ]
55
+
56
+ // Pleasant, varied card colors
57
+ const SLIDE_COLORS = [
58
+ '#3D6680',
59
+ '#2F6666',
60
+ '#2F3D66',
61
+ '#592659',
62
+ '#3D2F66',
63
+ '#2F665C',
64
+ '#5C662F',
65
+ '#66332F',
66
+ '#3D2F66',
67
+ '#2F4D66'
68
+ ]
69
+
70
+ // How long each slide stays on screen, measured in ticks/frames
71
+ const TICKS_PER_SLIDE = 300
72
+
73
+ // ─────────────────────────────────────────────────────────────────────────────
74
+ // Step 1: Export an AnimatedScene (2D, begin from current = fast hot reload)
75
+ // ─────────────────────────────────────────────────────────────────────────────
76
+ export function tutorial_easy3(): AnimatedScene {
77
+ return new AnimatedScene(
78
+ 1000,
79
+ 1000,
80
+ SpaceSetting.TwoDim,
81
+ HotReloadSetting.BeginFromCurrent,
82
+ async (scene) => {
83
+ // ───────────────────────────────────────────────────────────────────────
84
+ // Step 2: Create a background card and a headline text
85
+ // The rectangle acts like a slide background; we’ll change its color.
86
+ // ───────────────────────────────────────────────────────────────────────
87
+ const card = createRectangle(200, 200) // simple square card
88
+ const title = await createFastText('', 1.5) // scale = 1.5 ⇒ nice big headline
89
+ scene.add(card, title)
90
+
91
+ // Optional: center them if your renderer doesn’t already
92
+ card.position.set(0, 0, 0)
93
+ title.position.set(0, 0, 0)
94
+
95
+ // ───────────────────────────────────────────────────────────────────────
96
+ // Step 3: Load the tick SFX and register it once
97
+ // ───────────────────────────────────────────────────────────────────────
98
+ scene.registerAudio(tickSound)
99
+
100
+ // ───────────────────────────────────────────────────────────────────────
101
+ // Step 4: Build a simple “switcher” animation
102
+ // We map a linear 0→1 over (ALTERNATIVES.length * TICKS_PER_SLIDE) ticks.
103
+ // On each whole-index step we:
104
+ // - update the background color
105
+ // - update the headline text
106
+ // - play a tick sound
107
+ // ───────────────────────────────────────────────────────────────────────
108
+ let lastIndex = -1
109
+ const totalTicks = ALTERNATIVES.length * TICKS_PER_SLIDE
110
+
111
+ const switcher = createAnim(easeLinear(0, 1, totalTicks), (value) => {
112
+ // Convert 0..1 into a slide index. Modulo makes the final frame safe.
113
+ const i = Math.floor(value * ALTERNATIVES.length) % ALTERNATIVES.length
114
+ if (i === lastIndex) return
115
+ lastIndex = i
116
+
117
+ // Update color + text
118
+ const color = SLIDE_COLORS[i % SLIDE_COLORS.length]
119
+ ;(card.material as THREE.MeshBasicMaterial).color = new THREE.Color(color)
120
+ updateText(title, ALTERNATIVES[i])
121
+
122
+ // Click!
123
+ scene.playAudio(tickSound)
124
+ })
125
+
126
+ // Start the slide show
127
+ scene.addAnim(switcher)
128
+
129
+ // Let it run a bit after the last change (nice tail for render/export)
130
+ scene.addWait(1_000)
131
+ }
132
+ )
133
+ }
@@ -0,0 +1,154 @@
1
+ // Tutorial 3 (medium1.ts)
2
+ // Goal for this animation:
3
+ // 1) Light the scene with an HDRI (image-based lighting) + soft gradient background
4
+ // 2) Spawn a flock of spheres that wander smoothly with damped random acceleration
5
+ // 3) Draw dependency lines between neighbors to form a living chain
6
+ // 4) Orbit the camera around the action
7
+
8
+ import * as THREE from 'three'
9
+
10
+ import { AnimatedScene, HotReloadSetting, SpaceSetting } from '$renderer/lib/scene/sceneClass'
11
+
12
+ // If your helpers live elsewhere, tweak these paths:
13
+ import {
14
+ addBackgroundGradient,
15
+ addHDRI,
16
+ loadHDRIData,
17
+ HDRIs
18
+ } from '$renderer/lib/rendering/lighting3d'
19
+ import { setOpacity } from '$renderer/lib/animation/animations'
20
+ import { createLine } from '$renderer/lib/rendering/objects2d'
21
+
22
+ // ─────────────────────────────────────────────────────────────────────────────
23
+ // Step 0: Scene constants and materials
24
+ // ─────────────────────────────────────────────────────────────────────────────
25
+ const NODE_COUNT = 200
26
+ const BOUNDARY_RADIUS = 10
27
+ const ORBIT_SPEED = 0.01
28
+
29
+ const sphereMaterial = new THREE.MeshStandardMaterial({
30
+ color: 0xffffff,
31
+ roughness: 0.3,
32
+ metalness: 1.0
33
+ })
34
+
35
+ type Node = {
36
+ mesh: THREE.Mesh
37
+ velocity: THREE.Vector3
38
+ }
39
+
40
+ // ─────────────────────────────────────────────────────────────────────────────
41
+ // Step 1: Helper to make a moving node (sphere + velocity)
42
+ // ─────────────────────────────────────────────────────────────────────────────
43
+ function createNode(): Node {
44
+ const geometry = new THREE.SphereGeometry(0.15, 32, 32)
45
+ const mesh = new THREE.Mesh(geometry, sphereMaterial.clone())
46
+ // Start near the origin with a tiny random offset so lines aren’t degenerate
47
+ mesh.position.set(
48
+ THREE.MathUtils.randFloatSpread(1),
49
+ THREE.MathUtils.randFloatSpread(1),
50
+ THREE.MathUtils.randFloatSpread(1)
51
+ )
52
+ return { mesh, velocity: new THREE.Vector3() }
53
+ }
54
+
55
+ // ─────────────────────────────────────────────────────────────────────────────
56
+ // Step 2: Export an AnimatedScene (3D, hot-reload from current frame)
57
+ // We don’t need to replay long histories, so BeginFromCurrent makes iteration fast.
58
+ // ─────────────────────────────────────────────────────────────────────────────
59
+ export function tutorial_medium1(): AnimatedScene {
60
+ return new AnimatedScene(
61
+ 1080, // width (vertical clip like easy1)
62
+ 1920, // height
63
+ SpaceSetting.ThreeDim, // 3D scene
64
+ HotReloadSetting.BeginFromCurrent,
65
+ async (scene) => {
66
+ // ───────────────────────────────────────────────────────────────────────
67
+ // Step 3: Lighting & background
68
+ // HDRI gives nice reflections/IBL; gradient provides a subtle backdrop.
69
+ // ───────────────────────────────────────────────────────────────────────
70
+ const hdriData = await loadHDRIData(HDRIs.photoStudio1, 2, 0.5) // (lods, intensity normalization)
71
+ await addHDRI(scene, hdriData, 0.3) // envMap intensity
72
+
73
+ addBackgroundGradient({
74
+ scene,
75
+ topColor: 0xaa9775, // warm top
76
+ bottomColor: 0x483924, // rich brown base
77
+ backgroundOpacity: 0.7
78
+ })
79
+
80
+ // Optional helpers if you like visual anchors:
81
+ //scene.add(new THREE.GridHelper(30, 30))
82
+
83
+ // ───────────────────────────────────────────────────────────────────────
84
+ // Step 4: Create moving nodes and dependency lines (neighbor chain)
85
+ // We connect each node i to node (i+1) to visualize a simple dependency.
86
+ // ───────────────────────────────────────────────────────────────────────
87
+ const nodes: Node[] = Array.from({ length: NODE_COUNT }, createNode)
88
+ const lines = nodes.map(() => setOpacity(createLine(), 0.12)) // faint lines
89
+
90
+ scene.add(...nodes.map((n) => n.mesh), ...lines)
91
+
92
+ // ───────────────────────────────────────────────────────────────────────
93
+ // Step 5: Camera setup — start out on the +X axis and orbit around origin
94
+ // ───────────────────────────────────────────────────────────────────────
95
+ scene.camera.position.set(30, 20, 0)
96
+ const center = new THREE.Vector3(0, 0, 0)
97
+ const orbitRadius = scene.camera.position.distanceTo(center)
98
+ let angle = 0
99
+
100
+ // ───────────────────────────────────────────────────────────────────────
101
+ // Step 6: Per-frame simulation
102
+ // - Each node gets a tiny random acceleration (smooth wander)
103
+ // - Velocity is damped for stability
104
+ // - Soft boundary: gentle pull toward center if too far
105
+ // - Lines update to connect neighbors (i -> i+1)
106
+ // - Camera orbits and keeps looking at the center
107
+ // ───────────────────────────────────────────────────────────────────────
108
+ scene.onEachTick((tick) => {
109
+ // Physics update
110
+ for (const n of nodes) {
111
+ // Small random acceleration
112
+ const ax = (Math.random() - 0.5) * 0.05
113
+ const ay = (Math.random() - 0.5) * 0.05
114
+ const az = (Math.random() - 0.5) * 0.05
115
+ n.velocity.x += ax
116
+ n.velocity.y += ay
117
+ n.velocity.z += az
118
+
119
+ // Damping (retain ~95% of previous velocity)
120
+ n.velocity.multiplyScalar(0.95)
121
+
122
+ // Integrate
123
+ n.mesh.position.add(n.velocity)
124
+
125
+ // Soft boundary: if outside radius, nudge back toward origin
126
+ const len = n.mesh.position.length()
127
+ if (len > BOUNDARY_RADIUS) {
128
+ const pull = n.mesh.position.clone().negate().normalize().multiplyScalar(0.02)
129
+ n.velocity.add(pull)
130
+ }
131
+ }
132
+
133
+ // Update dependency lines (chain)
134
+ for (let i = 0; i < nodes.length; i++) {
135
+ const a = nodes[i].mesh.position
136
+ const b = nodes[(i + 1) % nodes.length].mesh.position // wrap to form a loop
137
+ // createLine() returns a handle with updatePositions(start, end)
138
+ lines[i].updatePositions(a, b)
139
+ }
140
+
141
+ // Camera orbit
142
+ angle += ORBIT_SPEED
143
+ scene.camera.position.x = Math.sin(angle) * orbitRadius
144
+ scene.camera.position.z = Math.cos(angle) * orbitRadius
145
+ scene.camera.lookAt(center)
146
+ })
147
+
148
+ // ───────────────────────────────────────────────────────────────────────
149
+ // Step 7: Let it play for 20 seconds before finishing
150
+ // ───────────────────────────────────────────────────────────────────────
151
+ scene.addWait(20_000)
152
+ }
153
+ )
154
+ }
@@ -0,0 +1,209 @@
1
+ import { linspace } from '../renderer/src/lib/mathHelpers/vectors'
2
+ import { COLORS } from '../renderer/src/lib/rendering/helpers'
3
+ import {
4
+ addBackgroundGradient,
5
+ addHDRI,
6
+ addSceneLighting,
7
+ HDRIs,
8
+ loadHDRIData
9
+ } from '../renderer/src/lib/rendering/lighting3d'
10
+ import { createFastText } from '../renderer/src/lib/rendering/objects2d'
11
+ import { AnimatedScene, HotReloadSetting, SpaceSetting } from '../renderer/src/lib/scene/sceneClass'
12
+ import * as THREE from 'three'
13
+
14
+ // 1. Define a Vector3 type
15
+ export interface Vector3 {
16
+ x: number
17
+ y: number
18
+ z: number
19
+ }
20
+
21
+ // 2. Define a type alias for any 3D vector field: a function that
22
+ // takes a point (x, y, z) and returns a vector at that point.
23
+ export type VectorField3D = (x: number, y: number, z: number, time: number) => Vector3
24
+
25
+ // 3. Example: a simple swirling field around the z‑axis
26
+ export const field: VectorField3D = (x, y, z, time) => {
27
+ const scale = 10
28
+ const target = new THREE.Vector3(
29
+ Math.sin(time),
30
+ Math.cos(time),
31
+ Math.sin(time) * Math.cos(time)
32
+ ).multiplyScalar(scale) // the center point to attract objects to
33
+ const position = new THREE.Vector3(x, y, z)
34
+ const direction = target.sub(position).normalize() // calculate the direction towards the target
35
+ const strength = 0.1 // adjust the strength of the attraction
36
+
37
+ return {
38
+ x: direction.x * strength,
39
+ y: direction.y * strength,
40
+ z: direction.z * strength
41
+ }
42
+ }
43
+
44
+ const hdriData = await loadHDRIData(HDRIs.outdoor1, 2, 1)
45
+
46
+ export const vectorFieldScene = (): AnimatedScene => {
47
+ return new AnimatedScene(
48
+ 1080,
49
+ 2160,
50
+ SpaceSetting.ThreeDim,
51
+ HotReloadSetting.BeginFromCurrent,
52
+ async (scene) => {
53
+ addSceneLighting(scene.scene)
54
+ await addHDRI(scene, hdriData, 1)
55
+ addBackgroundGradient({
56
+ scene,
57
+ topColor: '#00639d',
58
+ bottomColor: COLORS.black,
59
+ backgroundOpacity: 0.5
60
+ })
61
+ const max = 25
62
+ const xs = linspace(-max, max, 6)
63
+ const ys = [...xs]
64
+ const zs = [...xs]
65
+
66
+ const length = 2 // arrow length units
67
+ const color = 0xffffff // red
68
+
69
+ const arrows: (THREE.ArrowHelper | null)[][][] = Array.from({ length: xs.length }, () =>
70
+ Array.from({ length: ys.length }, () => Array.from({ length: zs.length }, () => null))
71
+ )
72
+
73
+ for (let i = 0; i < xs.length; i++) {
74
+ for (let j = 0; j < ys.length; j++) {
75
+ for (let k = 0; k < zs.length; k++) {
76
+ const x = xs[i],
77
+ y = ys[j],
78
+ z = zs[k]
79
+ const value = field(x, y, z, 0)
80
+ // 1. define direction, origin, length and color
81
+ const dir = new THREE.Vector3(value.x, value.y, value.z).normalize() // arrow points along +X
82
+ const origin = new THREE.Vector3(x, y, z) // from world‑origin
83
+
84
+ // 2. create helper
85
+ const arrowHelper = new THREE.ArrowHelper(dir, origin, length, color, 6, 1)
86
+
87
+ // remove the low‑res cone
88
+ arrowHelper.cone.geometry.dispose()
89
+
90
+ // create a new high‑res cone: (radiusTop, height, radialSegments, heightSegments)
91
+ const highResSegments = 32
92
+ const highResConeGeo = new THREE.ConeGeometry(0.15, 1, highResSegments, 1)
93
+
94
+ arrowHelper.cone.geometry = highResConeGeo
95
+
96
+ const coneMaterial = new THREE.MeshStandardMaterial({
97
+ color: 0xffffff,
98
+ metalness: 0.5,
99
+ roughness: 0.7,
100
+ transparent: true
101
+ })
102
+
103
+ const lineMaterial = new THREE.MeshStandardMaterial({
104
+ color: 0xffffff,
105
+ metalness: 0.5,
106
+ roughness: 0.7,
107
+ transparent: true
108
+ })
109
+
110
+ arrowHelper.line.material = lineMaterial
111
+ arrowHelper.cone.material = coneMaterial
112
+
113
+ arrowHelper.cone.castShadow
114
+
115
+ arrows[i][j][k] = arrowHelper
116
+
117
+ // 3. add to scene
118
+ scene.add(arrowHelper)
119
+ }
120
+ }
121
+ }
122
+
123
+ const textNode = await createFastText('Vector Field', 5)
124
+ textNode.position.y = max * 1.5
125
+ scene.add(textNode)
126
+
127
+ // const axesHelper = new THREE.AxesHelper(20)
128
+ // scene.add(axesHelper)
129
+
130
+ scene.camera.position.set(-116.443, 35.10142, 68.73468)
131
+ scene.camera.quaternion.set(-0.109989, -0.491824, -0.06279674, 0.8614338)
132
+
133
+ const geometry = new THREE.SphereGeometry(0.3, 32, 32)
134
+ const material = new THREE.MeshStandardMaterial({
135
+ color: 0x000000,
136
+ emissive: 0xffffff,
137
+ emissiveIntensity: 20.0
138
+ })
139
+
140
+ interface GroupData {
141
+ group: THREE.Group
142
+ acceleration: THREE.Vector3
143
+ velocity: THREE.Vector3
144
+ }
145
+
146
+ const groups: GroupData[] = []
147
+
148
+ Array(30)
149
+ .fill(0)
150
+ .forEach(() => {
151
+ const sphere = new THREE.Mesh(geometry, material)
152
+ const pointLight = new THREE.PointLight(0xffffff, 2000) // color, intensity, distance
153
+ pointLight.position.copy(sphere.position)
154
+ const group = new THREE.Group()
155
+ group.add(sphere, pointLight)
156
+
157
+ group.position.y = 8 * (Math.random() - 0.5)
158
+ group.position.z = 8 * (Math.random() - 0.5)
159
+
160
+ let velocity = new THREE.Vector3(0, 0, 0) // Initial velocity
161
+ let acceleration = new THREE.Vector3(0, 0, 0) // Initialize acceleration
162
+
163
+ groups.push({
164
+ group,
165
+ acceleration,
166
+ velocity
167
+ })
168
+
169
+ scene.add(group)
170
+ })
171
+
172
+ scene.onEachTick((tick, time) => {
173
+ textNode.lookAt(scene.camera.position)
174
+ const editedTime = time / 500
175
+
176
+ for (const groupData of groups) {
177
+ const fieldValue = field(
178
+ groupData.group.position.x,
179
+ groupData.group.position.y,
180
+ groupData.group.position.z,
181
+ editedTime
182
+ )
183
+ groupData.acceleration.set(fieldValue.x, fieldValue.y, fieldValue.z)
184
+
185
+ // Update velocity with acceleration (basic kinematic equation)
186
+ groupData.velocity.add(groupData.acceleration.clone().multiplyScalar(2)) // Adjust time factor for smoothness
187
+
188
+ // Update position of point light based on velocity
189
+ groupData.group.position.add(groupData.velocity)
190
+ }
191
+
192
+ for (let i = 0; i < xs.length; i++) {
193
+ for (let j = 0; j < ys.length; j++) {
194
+ for (let k = 0; k < zs.length; k++) {
195
+ const x = xs[i],
196
+ y = ys[j],
197
+ z = zs[k]
198
+ const v = field(x, y, z, editedTime)
199
+ const dir = new THREE.Vector3(v.x, v.y, v.z).normalize()
200
+ arrows[i][j][k]?.setDirection(dir)
201
+ }
202
+ }
203
+ }
204
+ })
205
+
206
+ scene.addWait(20000)
207
+ }
208
+ )
209
+ }