@solidrt/2d 0.0.54 → 0.0.56
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/AGENTS.md +241 -57
- package/examples/README.md +22 -5
- package/examples/anim.tsx +86 -0
- package/examples/camera-probe.tsx +200 -0
- package/examples/camera.tsx +190 -0
- package/examples/parity.tsx +4 -4
- package/examples/pick.tsx +50 -33
- package/examples/tiles.tsx +19 -10
- package/package.json +3 -3
- package/src/animation.ts +178 -0
- package/src/camera-motion.ts +532 -0
- package/src/camera.ts +86 -0
- package/src/camera2d.ts +140 -0
- package/src/components.tsx +330 -76
- package/src/dispatch.ts +231 -0
- package/src/index.ts +30 -5
- package/src/layer.ts +454 -186
- package/src/oversample-math.ts +73 -0
- package/src/oversample.ts +39 -11
- package/src/records.ts +132 -27
- package/src/shaders.ts +29 -12
- package/src/tiles.ts +79 -24
package/src/camera2d.ts
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
// The 2d camera: Godot's Camera2D and Three's MapControls in one control
|
|
2
|
+
// over the layers' shared CameraUpdate - drag to pan with inertia, wheel
|
|
3
|
+
// and pinch to zoom about the pointer, eased glides, fit-to-rect, follow a
|
|
4
|
+
// moving point through a dead zone with damping, rotation about the pivot,
|
|
5
|
+
// world bounds - extracted so apps stop rebuilding the same hundred
|
|
6
|
+
// lines. The motion itself is camera-motion.ts, pure and
|
|
7
|
+
// headless-checked; this file adds the input and the reactive face.
|
|
8
|
+
//
|
|
9
|
+
// Input goes through core's merged transform recognizer (createTransform),
|
|
10
|
+
// the same path as @solidrt/3d's orbit camera: the drag participates in
|
|
11
|
+
// the gesture arena (a layer inside a scroller arbitrates instead of both
|
|
12
|
+
// panning), the slop is swallowed, and one delta arrives per frame. One
|
|
13
|
+
// finger or a mouse drag pans; two fingers pan by their focal point and
|
|
14
|
+
// zoom by their span about it; the wheel zooms directly through an eased
|
|
15
|
+
// glide. Taps are not the camera's business: the layer's dispatch
|
|
16
|
+
// synthesizes them (onTap on sprites, groups and the root), as the DOM
|
|
17
|
+
// does for the elements a map control sits on.
|
|
18
|
+
//
|
|
19
|
+
// Two ways in. `attach(layer)` listens at the layer's root, so the camera
|
|
20
|
+
// sees exactly the presses the sprites let through: a sprite that drags
|
|
21
|
+
// itself stops its down and the camera never pans it, a drag on empty
|
|
22
|
+
// space or on a sprite without a claim pans, a wheel anywhere zooms.
|
|
23
|
+
// `handlers` spread straight onto a leaf for the layers without a
|
|
24
|
+
// dispatch (a tile world on its own). Either way the pose reaches the
|
|
25
|
+
// layers synchronously on input, so a drag needs no frame loop and the
|
|
26
|
+
// next paint carries the new camera.
|
|
27
|
+
//
|
|
28
|
+
// Coordinates: everything arrives in the input element's own frame. The
|
|
29
|
+
// recognizer reports the pinch focal in the element's local pixels (layer
|
|
30
|
+
// pixels on the layer's leaf) and the drag deltas in its parent frame, and
|
|
31
|
+
// the wheel uses the event's localX/localY - so a leaf under a designSize
|
|
32
|
+
// fit or any scaled ancestor pans and zooms correctly.
|
|
33
|
+
//
|
|
34
|
+
// Pose is plain mutable state advanced by update(dt) from a frame loop;
|
|
35
|
+
// `active()` is the reactive gate for that loop (true while a glide,
|
|
36
|
+
// fling, fit or follow needs frames; false at rest, when the camera costs
|
|
37
|
+
// nothing), which is how <Camera2d> runs frames only while something
|
|
38
|
+
// moves. update() pushes the pose to the layers only when it changed (one
|
|
39
|
+
// setCamera per driven layer: a shared-params write, however many sprites
|
|
40
|
+
// exist) and reports that, so per-frame dependents (labels re-projected
|
|
41
|
+
// via projectCamera) can follow the camera without recomputing every
|
|
42
|
+
// frame.
|
|
43
|
+
|
|
44
|
+
import { createSignal, createTransform } from "@solidrt/core"
|
|
45
|
+
import type { PointerEvent, WheelEvent } from "@solidrt/core"
|
|
46
|
+
import { createCameraMotion } from "./camera-motion.ts"
|
|
47
|
+
import type { Camera2dMotion, Camera2dMotionOptions, Camera2dTarget } from "./camera-motion.ts"
|
|
48
|
+
import type { LayerPointerListener } from "./layer.ts"
|
|
49
|
+
|
|
50
|
+
export type Camera2dOptions = Camera2dMotionOptions
|
|
51
|
+
|
|
52
|
+
export type Camera2d = Camera2dMotion & {
|
|
53
|
+
/** Reactive: whether update(dt) needs frames (see Camera2dMotion.active).
|
|
54
|
+
* Track it to run a frame loop only while the camera moves. */
|
|
55
|
+
active: () => boolean
|
|
56
|
+
/** Spread onto the element that receives input where there is no sprite
|
|
57
|
+
* dispatch to attach to - a tile world's leaf. Layers with a dispatch
|
|
58
|
+
* take `attach`. */
|
|
59
|
+
handlers: {
|
|
60
|
+
onPointerDown(e: PointerEvent): void
|
|
61
|
+
onPointerMove(e: PointerEvent): void
|
|
62
|
+
onPointerUp(e: PointerEvent): void
|
|
63
|
+
onWheel(e: WheelEvent): void
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Take input from a layer's root (LayerBase.listen): the presses the
|
|
67
|
+
* sprites let through pan and pinch, every wheel zooms. A sprite that
|
|
68
|
+
* claims a press (stopPropagation on its down) keeps the camera out of
|
|
69
|
+
* that drag. Returns the detach. The layer must be one the camera
|
|
70
|
+
* drives, or its events arrive in a frame the camera does not show.
|
|
71
|
+
*/
|
|
72
|
+
attach(layer: { listen(listener: LayerPointerListener): () => void }): () => void
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Create a 2d camera driving `target`'s camera, where `target` is a sprite
|
|
77
|
+
* or record layer (or several: a layer and its overlay layer share one
|
|
78
|
+
* camera), or anything else with the layers' `setCamera`, such as a signal
|
|
79
|
+
* setter feeding `<TileLayer camera>`. The initial pose applies
|
|
80
|
+
* immediately: the world fitted when `world` is given and no `zoom`.
|
|
81
|
+
* Create it from an owned scope (a component body, like the orbit
|
|
82
|
+
* camera): the input recognizer registers its cleanup with the owner.
|
|
83
|
+
*/
|
|
84
|
+
export function createCamera2d(target: Camera2dTarget | Camera2dTarget[], options: Camera2dOptions): Camera2d {
|
|
85
|
+
// Written by the motion from wherever it is driven - handlers, frames,
|
|
86
|
+
// a set() in a component body - hence the owned-write opt-in.
|
|
87
|
+
let [active, setActive] = createSignal(false, { ownedWrite: true })
|
|
88
|
+
let motion = createCameraMotion(target, options, setActive)
|
|
89
|
+
|
|
90
|
+
let transform = createTransform({
|
|
91
|
+
onTransformMove: (t) => {
|
|
92
|
+
if (t.scale !== 1) motion.zoomAt(t.x, t.y, t.scale)
|
|
93
|
+
if (t.dx !== 0 || t.dy !== 0) motion.panBy(t.dx, t.dy)
|
|
94
|
+
},
|
|
95
|
+
onTransformEnd: () => motion.release(),
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
let handlers: Camera2d["handlers"] = {
|
|
99
|
+
onPointerDown(e) {
|
|
100
|
+
if (e.button != null && e.button !== 0) return
|
|
101
|
+
// A finger landing on a gliding view stops it where it is.
|
|
102
|
+
motion.interrupt()
|
|
103
|
+
transform.handlers.onPointerDown(e)
|
|
104
|
+
},
|
|
105
|
+
onPointerMove(e) {
|
|
106
|
+
transform.handlers.onPointerMove(e)
|
|
107
|
+
},
|
|
108
|
+
onPointerUp(e) {
|
|
109
|
+
transform.handlers.onPointerUp(e)
|
|
110
|
+
},
|
|
111
|
+
onWheel(e) {
|
|
112
|
+
motion.wheel(e.localX, e.localY, e.deltaY)
|
|
113
|
+
},
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return {
|
|
117
|
+
...motion,
|
|
118
|
+
active,
|
|
119
|
+
handlers,
|
|
120
|
+
attach(layer) {
|
|
121
|
+
// Input pushes the pose synchronously (update(0)), so a drag needs
|
|
122
|
+
// no frame loop and the next paint carries the new camera.
|
|
123
|
+
return layer.listen({
|
|
124
|
+
onPointerDown: e => {
|
|
125
|
+
handlers.onPointerDown(e.native)
|
|
126
|
+
motion.update(0)
|
|
127
|
+
},
|
|
128
|
+
onPointerMove: e => {
|
|
129
|
+
handlers.onPointerMove(e.native)
|
|
130
|
+
motion.update(0)
|
|
131
|
+
},
|
|
132
|
+
onPointerUp: e => handlers.onPointerUp(e.native),
|
|
133
|
+
onWheel: e => {
|
|
134
|
+
handlers.onWheel(e.native)
|
|
135
|
+
motion.update(0)
|
|
136
|
+
},
|
|
137
|
+
})
|
|
138
|
+
},
|
|
139
|
+
}
|
|
140
|
+
}
|