@zkmake/three-cameras 0.0.0 → 0.2.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.
package/README.md CHANGED
@@ -7,7 +7,13 @@ reads each one's position and projection live, and draws a camera's frustum in t
7
7
 
8
8
  Pick a camera and you control it: edit its position, rotation and lens by typing or scrubbing, look
9
9
  through it in place of the app's main view, copy its setup as code, and save views to fly back to.
10
- Keyframed camera moves with curve and graph editors are next.
10
+ Animate cameras on a keyframe timeline: key a camera, retime and ease its keys, and play the move
11
+ back in the running scene, shaping them in a graph editor and an ease curve editor, with the
12
+ motion path drawn in the scene.
13
+
14
+ It's one panel, anchored across the bottom of the screen: the camera list as the timeline's left
15
+ sidebar, each camera's row beside its key lane. Collapsed, it's a small widget with the picked
16
+ camera (its kind, name, and whether it's live or looked through); click it to expand.
11
17
 
12
18
  ```sh
13
19
  bun add -d @zkmake/three-cameras # or npm i -D / yarn add -D / pnpm add -D
@@ -70,6 +76,50 @@ so a picture-in-picture inset or a render target keeps its own camera. The camer
70
76
  view's aspect for the frame and put back after, and the app's camera is left where it was. A banner
71
77
  shows what you're looking through, with a way back.
72
78
 
79
+ ## The timeline
80
+
81
+ The timeline animates cameras with keyframes. Its left column is the camera list: each camera's row
82
+ sits beside its lane, and a row opened into its controls stretches its lane to match.
83
+
84
+ - **Key** the picked camera as it is now at the playhead, or double-click its lane to key it there.
85
+ A key holds its position, rotation and lens.
86
+ - **Drag** a key to retime it (snaps to 0.1 s; Shift for free). Click one to inspect it: its time,
87
+ its **ease** into the next key (ease in-out, linear, ease in, ease out, or hold), **Update** to
88
+ re-key it from the camera as it is now, and **Delete**.
89
+ - **Play**, pause (Space), scrub the ruler, loop, and set the length. The camera travels a smooth
90
+ curve through its keys, turns evenly between them, and its lens eases too.
91
+ - Once you play or scrub, tracked cameras are held to their tracks right before each frame, so a
92
+ track wins over an app that moves the camera itself. Edit a camera and it's let go, so the edit
93
+ sticks until you key it; **stop** lets every camera go back to the app.
94
+ - Look through a camera while it plays to see the shot. Tracks are kept in localStorage, by camera
95
+ name. Pass `timeline: false` for the camera list alone.
96
+
97
+ ### Shaping a move
98
+
99
+ - **Ease curve**: the key inspector draws the move from the picked key to the next as a curve of
100
+ progress over time, with two handles (CSS `cubic-bezier` terms). Drag a handle to reshape it: a
101
+ preset becomes a custom curve from the preset's shape. Handles go above 1 or below 0 for
102
+ overshoot and anticipation.
103
+ - **Graph**: switch the timeline to Graph for the picked camera's channels over time: position x,
104
+ y, z, rotation x, y, z (degrees), fov or zoom, near and far, drawn as the track plays them. Drag
105
+ a key's dot up or down to change that value (Shift for fine), or sideways to retime the key. Click
106
+ between keys to pick that segment's ease; double-click to add a key. Channels are stretched to
107
+ their own ranges; "values" puts them on one scale.
108
+
109
+ ### Motion paths in the scene
110
+
111
+ A camera that moves shows its path in the scene by itself, while it moves (cameras in the scene;
112
+ your own view camera is left out):
113
+
114
+ - **Keyed** cameras show their track, an amber curve with a white dot per key. **Drag a dot** in the
115
+ scene to move that key: it slides on a plane facing you, Shift for straight up or down. The path
116
+ and the move reshape as you drag, and the press doesn't reach orbit controls.
117
+ - **App-driven** cameras (moved by your code, not keys) show where they've been over the last 8
118
+ seconds, a light blue line. Your code drives that move, so it can't be dragged; **Bake to keys**
119
+ in the camera's controls turns it into keys (one where the path bends, linear timing), and it's a
120
+ track to edit like any other. Played, the track takes over from the app.
121
+ - The path toggle on a camera's row shows or hides its path by hand.
122
+
73
123
  ## How it finds cameras
74
124
 
75
125
  - **The scene graph:** anything with `isCamera` under `scene`. A `CubeCamera` is one row; its six face
@@ -83,25 +133,28 @@ panel's wrapper stays in place and just passes through.
83
133
 
84
134
  ## Options
85
135
 
86
- | Option | Default | What it does |
87
- | ------------------ | ----------------------------------- | --------------------------------------------------------------------------------- |
88
- | `scene` | required | Where to look for cameras, and where frustum helpers go |
89
- | `renderer` | none | Watched for the cameras it draws with; without it nothing is "live" |
90
- | `cameras` | `[]` | `{ name, camera }` to list whether or not the scene holds them yet |
91
- | `invalidate` | none | Ask for frames after an edit, a toggle, or during a move (render-on-demand loops) |
92
- | `store` | localStorage, by `storageKey` | Where saved views live: `{ load(), save(views) }` |
93
- | `storageKey` | `"three-cameras"` | localStorage prefix for the dock and compact state; `null` for none |
94
- | `defaultPlacement` | `{ edge: "right", align: "start" }` | Where it docks on a first visit |
95
- | `theme` | `"system"` | `"dark"`, `"light"` or `"system"` |
96
- | `compact` | `false` | Start compact, showing the brand row and the count |
97
- | `container` | `document.body` | Where to mount |
136
+ | Option | Default | What it does |
137
+ | ------------------ | ----------------------------------- | ------------------------------------------------------------------------------------------------ |
138
+ | `scene` | required | Where to look for cameras, and where frustum helpers go |
139
+ | `renderer` | none | Watched for the cameras it draws with; without it nothing is "live" |
140
+ | `cameras` | `[]` | `{ name, camera }` to list whether or not the scene holds them yet |
141
+ | `invalidate` | none | Ask for frames after an edit, a toggle, or during a move (render-on-demand loops) |
142
+ | `store` | localStorage, by `storageKey` | Where saved views live: `{ load(), save(views) }` |
143
+ | `trackStore` | localStorage, by `storageKey` | Where keyframe tracks live: `{ load(), save({ duration, tracks }) }` |
144
+ | `timeline` | `true` | The timeline, with the camera list as its sidebar, across the bottom; `false` for the list alone |
145
+ | `storageKey` | `"three-cameras"` | localStorage prefix for the dock and compact state; `null` for none |
146
+ | `defaultPlacement` | `{ edge: "right", align: "start" }` | Where the list alone docks on a first visit (with the timeline it spans the bottom) |
147
+ | `theme` | `"system"` | `"dark"`, `"light"` or `"system"` |
148
+ | `compact` | `false` | Start compact, showing the brand row and the count |
149
+ | `container` | `document.body` | Where to mount |
98
150
 
99
151
  ## The engine
100
152
 
101
153
  `CameraLab` is the panel's engine, with no DOM, for scripting or a panel of your own:
102
154
 
103
155
  ```ts
104
- import { CameraLab } from "@zkmake/three-cameras";
156
+ import { CameraLab, writeChannel } from "@zkmake/three-cameras";
157
+ import { Vector3 } from "three";
105
158
  import { createCameraPanel } from "@zkmake/three-cameras/ui";
106
159
 
107
160
  const lab = new CameraLab({ scene, renderer });
@@ -115,13 +168,25 @@ lab.setHelper("dolly", true);
115
168
  const home = lab.saveView("dolly", "home");
116
169
  lab.goToView("dolly", home.id, { duration: 800 });
117
170
 
171
+ lab.addKey("dolly", { time: 0 }); // the camera as it is now
172
+ lab.addKey("dolly", { time: 4, ease: "ease-out" });
173
+ lab.play(); // pause(), seek(2), stop(), setLoop(false), setDuration(12)
174
+
175
+ const [first] = lab.keys("dolly");
176
+ lab.updateKey("dolly", first.id, { bezier: [0.3, 1.4, 0.6, 1] }); // overshoot
177
+ lab.updateKey("dolly", first.id, { pose: writeChannel(first.pose, "fov", 30) });
178
+ lab.setTrail("dolly", true); // or false, or "auto" (the default)
179
+ lab.bakeMotion("dolly"); // its recorded move as keys
180
+ lab.moveKeyTo("dolly", home.id, new Vector3(0, 3, 8)); // a key to a world point
181
+
118
182
  tab.append(createCameraPanel(lab).element); // the bare panel, for a host with its own tabs
119
183
  ```
120
184
 
121
185
  ## The dev-panel frame
122
186
 
123
- It docks in three-meter's `mountDevPanel`, so it drags, snaps to an edge, compacts and dims exactly
124
- like the three-meter HUD and the three-textures panel, with its brand label on top.
187
+ It sits in three-meter's `mountDevPanel`, so it compacts and dims like the three-meter HUD and the
188
+ three-textures panel, with its brand label on top. With the timeline it's anchored across the
189
+ bottom (no drag grip); the list alone drags and snaps to an edge like the others.
125
190
 
126
191
  ## License
127
192