@rydr/game-sdk 8.11.0 → 8.12.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 +26 -2
- package/dist/client/PlatformClient.d.ts.map +1 -1
- package/dist/client/PlatformClient.js +14 -2
- package/dist/client/PlatformClient.js.map +1 -1
- package/dist/client/Room.d.ts +9 -4
- package/dist/client/Room.d.ts.map +1 -1
- package/dist/client/Room.js +8 -5
- package/dist/client/Room.js.map +1 -1
- package/dist/host/PlatformHost.d.ts +4 -2
- package/dist/host/PlatformHost.d.ts.map +1 -1
- package/dist/host/PlatformHost.js +5 -3
- package/dist/host/PlatformHost.js.map +1 -1
- package/dist/protocol/messages.d.ts +11 -4
- package/dist/protocol/messages.d.ts.map +1 -1
- package/dist/protocol/room.d.ts +26 -0
- package/dist/protocol/room.d.ts.map +1 -1
- package/dist/protocol/version.d.ts +2 -2
- package/dist/protocol/version.d.ts.map +1 -1
- package/dist/protocol/version.js +23 -2
- package/dist/protocol/version.js.map +1 -1
- package/dist/three/README.md +111 -0
- package/dist/three/character/index.d.ts +14 -0
- package/dist/three/character/index.d.ts.map +1 -0
- package/dist/three/character/index.js +14 -0
- package/dist/three/character/index.js.map +1 -0
- package/dist/three/character/kit.d.ts +74 -0
- package/dist/three/character/kit.d.ts.map +1 -0
- package/dist/three/character/kit.js +254 -0
- package/dist/three/character/kit.js.map +1 -0
- package/dist/three/character/riders.d.ts +20 -0
- package/dist/three/character/riders.d.ts.map +1 -0
- package/dist/three/character/riders.js +383 -0
- package/dist/three/character/riders.js.map +1 -0
- package/dist/three/character/skeleton.d.ts +105 -0
- package/dist/three/character/skeleton.d.ts.map +1 -0
- package/dist/three/character/skeleton.js +57 -0
- package/dist/three/character/skeleton.js.map +1 -0
- package/dist/three/controller/glyph-texture.d.ts +29 -0
- package/dist/three/controller/glyph-texture.d.ts.map +1 -0
- package/dist/three/controller/glyph-texture.js +68 -0
- package/dist/three/controller/glyph-texture.js.map +1 -0
- package/dist/three/controller/index.d.ts +12 -0
- package/dist/three/controller/index.d.ts.map +1 -0
- package/dist/three/controller/index.js +12 -0
- package/dist/three/controller/index.js.map +1 -0
- package/dist/three/controller/joycon-designs.d.ts +49 -0
- package/dist/three/controller/joycon-designs.d.ts.map +1 -0
- package/dist/three/controller/joycon-designs.js +742 -0
- package/dist/three/controller/joycon-designs.js.map +1 -0
- package/dist/three/controller/joycon-shoulder-mesh.d.ts +41 -0
- package/dist/three/controller/joycon-shoulder-mesh.d.ts.map +1 -0
- package/dist/three/controller/joycon-shoulder-mesh.js +527 -0
- package/dist/three/controller/joycon-shoulder-mesh.js.map +1 -0
- package/dist/three/controller/joycon-spec.d.ts +233 -0
- package/dist/three/controller/joycon-spec.d.ts.map +1 -0
- package/dist/three/controller/joycon-spec.js +285 -0
- package/dist/three/controller/joycon-spec.js.map +1 -0
- package/dist/three/controller/three-pad-renderer.d.ts +81 -0
- package/dist/three/controller/three-pad-renderer.d.ts.map +1 -0
- package/dist/three/controller/three-pad-renderer.js +433 -0
- package/dist/three/controller/three-pad-renderer.js.map +1 -0
- package/dist/three/index.d.ts +2 -0
- package/dist/three/index.d.ts.map +1 -1
- package/dist/three/index.js +2 -0
- package/dist/three/index.js.map +1 -1
- package/dist/three/rider/cyclist-pose.d.ts +62 -0
- package/dist/three/rider/cyclist-pose.d.ts.map +1 -0
- package/dist/three/rider/cyclist-pose.js +173 -0
- package/dist/three/rider/cyclist-pose.js.map +1 -0
- package/dist/three/rider/index.d.ts +8 -0
- package/dist/three/rider/index.d.ts.map +1 -0
- package/dist/three/rider/index.js +8 -0
- package/dist/three/rider/index.js.map +1 -0
- package/dist/three/rider/three-rider-rig.d.ts +80 -0
- package/dist/three/rider/three-rider-rig.d.ts.map +1 -0
- package/dist/three/rider/three-rider-rig.js +418 -0
- package/dist/three/rider/three-rider-rig.js.map +1 -0
- package/dist/ui/README.md +153 -1
- package/dist/ui/controller/combo-demo.d.ts +9 -0
- package/dist/ui/controller/combo-demo.d.ts.map +1 -1
- package/dist/ui/controller/combo-demo.js +1 -0
- package/dist/ui/controller/combo-demo.js.map +1 -1
- package/dist/ui/controller/control-spotlight.d.ts +10 -1
- package/dist/ui/controller/control-spotlight.d.ts.map +1 -1
- package/dist/ui/controller/control-spotlight.js +42 -42
- package/dist/ui/controller/control-spotlight.js.map +1 -1
- package/dist/ui/controller/control-trainer.d.ts +14 -0
- package/dist/ui/controller/control-trainer.d.ts.map +1 -1
- package/dist/ui/controller/control-trainer.js +6 -0
- package/dist/ui/controller/control-trainer.js.map +1 -1
- package/dist/ui/controller/controller-map.d.ts +9 -0
- package/dist/ui/controller/controller-map.d.ts.map +1 -1
- package/dist/ui/controller/controller-map.js +8 -6
- package/dist/ui/controller/controller-map.js.map +1 -1
- package/dist/ui/controller/controller-pad.d.ts +35 -2
- package/dist/ui/controller/controller-pad.d.ts.map +1 -1
- package/dist/ui/controller/controller-pad.js +47 -7
- package/dist/ui/controller/controller-pad.js.map +1 -1
- package/dist/ui/controller/index.d.ts +1 -0
- package/dist/ui/controller/index.d.ts.map +1 -1
- package/dist/ui/controller/index.js +1 -0
- package/dist/ui/controller/index.js.map +1 -1
- package/dist/ui/controller/renderer.d.ts +118 -0
- package/dist/ui/controller/renderer.d.ts.map +1 -0
- package/dist/ui/controller/renderer.js +93 -0
- package/dist/ui/controller/renderer.js.map +1 -0
- package/dist/ui/controller/styles.js +30 -0
- package/dist/ui/controller/styles.js.map +1 -1
- package/dist/ui/index.d.ts +6 -0
- package/dist/ui/index.d.ts.map +1 -1
- package/dist/ui/index.js +6 -0
- package/dist/ui/index.js.map +1 -1
- package/dist/ui/rider-rig.d.ts +315 -0
- package/dist/ui/rider-rig.d.ts.map +1 -0
- package/dist/ui/rider-rig.js +1000 -0
- package/dist/ui/rider-rig.js.map +1 -0
- package/dist/ui/showcase/index.d.ts +11 -0
- package/dist/ui/showcase/index.d.ts.map +1 -1
- package/dist/ui/showcase/index.js +109 -1
- package/dist/ui/showcase/index.js.map +1 -1
- package/package.json +8 -4
|
@@ -0,0 +1,418 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* THREE RIDER RIG — the rider-on-a-trainer drawing, in 3D.
|
|
3
|
+
*
|
|
4
|
+
* A second {@link RigRenderer} for `mountRiderRig`, exactly as `createThreePadRenderer` is a second
|
|
5
|
+
* renderer for the controller views. The component is unchanged: it still owns the two EMAs, the
|
|
6
|
+
* crank, the effort, the %FTP zone and the glass tooltip, and it still hands over one
|
|
7
|
+
* {@link RigPose} per frame. All this file does is draw that pose with meshes instead of paths.
|
|
8
|
+
*
|
|
9
|
+
* ```ts
|
|
10
|
+
* import { createThreeRiderRig } from "@rydr/game-sdk/three";
|
|
11
|
+
* import { mountRiderRig } from "@rydr/game-sdk/ui";
|
|
12
|
+
*
|
|
13
|
+
* mountRiderRig(panel, { design: "solid", ftp: 240, renderer: createThreeRiderRig });
|
|
14
|
+
* ```
|
|
15
|
+
*
|
|
16
|
+
* ## What is shared, and what that buys
|
|
17
|
+
*
|
|
18
|
+
* **The joints arrive already solved.** `RigPose` carries the hip, shoulder, head, elbow, both knees
|
|
19
|
+
* and both pedals as points in the flat 200×200 drawing space — so the two-bone IK, the crank
|
|
20
|
+
* kinematics, the out-of-the-saddle rise and the posture fold all stay in `rider-rig.ts` and run
|
|
21
|
+
* once. This file re-derives nothing, which is the whole point of the seam: two renderers cannot
|
|
22
|
+
* drift into two different gaits, and a fix to the pedalling fixes both.
|
|
23
|
+
*
|
|
24
|
+
* So the mapping is the only geometry here: {@link toWorld} takes a flat point to metres. The drawing
|
|
25
|
+
* space is scaled off the ONE wheel that is left — a 700c wheel is 0.68 m and 52 units across, giving
|
|
26
|
+
* ~1.05 m of wheelbase and a rider whose head sits at ~1.7 m. Those are a real bike's numbers, which
|
|
27
|
+
* matters because in 3D a wrong scale reads as a toy the moment perspective is applied.
|
|
28
|
+
*
|
|
29
|
+
* **The tooltip needs nothing from us.** It is an HTML box the component positions over the drawing,
|
|
30
|
+
* so it sits over this canvas exactly as it sits over the flat `<svg>` — no 3D projection, no
|
|
31
|
+
* re-layout, no second implementation. That is a payoff from building it in CSS.
|
|
32
|
+
*
|
|
33
|
+
* ## What only 3D can add, and what it costs
|
|
34
|
+
*
|
|
35
|
+
* The flat drawing is a strict side view, so the bike is a silhouette. Here the camera sits off-axis,
|
|
36
|
+
* which is what makes the trainer read as a machine with a body rather than an outline: you see the
|
|
37
|
+
* housing's depth, the flywheel as a disc rather than a circle, and the rider's far leg genuinely
|
|
38
|
+
* behind the near one instead of merely darker.
|
|
39
|
+
*
|
|
40
|
+
* The cost is a WebGL context. **This renderer draws on demand — one frame per `setPose`, no
|
|
41
|
+
* animation loop of its own** — because a game calling `rig.tick(dt)` already has a loop, and a
|
|
42
|
+
* decorative rig must not open a second one. It is still a second context beside the game's own
|
|
43
|
+
* scene, so prefer the flat renderer in-game and reach for this one on the screens where the picture
|
|
44
|
+
* IS the content: onboarding, a store page, a "how this works" panel.
|
|
45
|
+
*
|
|
46
|
+
* Requires the optional `three` peer dependency, like everything else in `@rydr/game-sdk/three`.
|
|
47
|
+
*/
|
|
48
|
+
import * as THREE from "three";
|
|
49
|
+
import { RIG_GEOMETRY as G } from "../../ui/rider-rig.js";
|
|
50
|
+
import { characterById, disposeTree } from "../character/index.js";
|
|
51
|
+
import { cyclistOverrides, mountCyclist, poseCyclist, tickPose } from "./cyclist-pose.js";
|
|
52
|
+
/**
|
|
53
|
+
* Flat units → metres.
|
|
54
|
+
*
|
|
55
|
+
* Fixed by the front wheel: 52 units across, 0.68 m for a 700c. Everything else in the drawing was
|
|
56
|
+
* proportioned off that wheel, so this one number makes the whole rig life-sized.
|
|
57
|
+
*/
|
|
58
|
+
const S = 0.68 / (G.WHEEL_R * 2);
|
|
59
|
+
/** Where the near limbs, the frame and the far limbs sit across the bike's centre line. */
|
|
60
|
+
const Z_NEAR = 0.1;
|
|
61
|
+
const Z_FRAME = 0;
|
|
62
|
+
const Z_FAR = -0.1;
|
|
63
|
+
const UP = new THREE.Vector3(0, 1, 0);
|
|
64
|
+
/** Vertical field of view. Narrow enough that the perspective reads as a lens, not a fisheye. */
|
|
65
|
+
const FOV = 32;
|
|
66
|
+
/** A flat drawing point in world metres, at a given depth. */
|
|
67
|
+
function toWorld(p, z) {
|
|
68
|
+
return new THREE.Vector3((p.x - G.VB_W / 2) * S, (G.GROUND - p.y) * S, z);
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* One palette per design, so `design` means the same thing in 3D as it does flat.
|
|
72
|
+
*
|
|
73
|
+
* `neon` is the one that changes materially rather than just in hue: its parts are emissive, which is
|
|
74
|
+
* how a glowing outline survives being lit. Faking it with a bright diffuse colour would go grey the
|
|
75
|
+
* moment the key light falls off.
|
|
76
|
+
*/
|
|
77
|
+
const PALETTES = {
|
|
78
|
+
solid: {
|
|
79
|
+
bg: 0x0b0f17,
|
|
80
|
+
frame: 0x8ea3c4,
|
|
81
|
+
metal: 0xb9c7de,
|
|
82
|
+
tyre: 0x2b3346,
|
|
83
|
+
housing: 0x44536f,
|
|
84
|
+
fly: 0x566b90,
|
|
85
|
+
jersey: 0xff7a45,
|
|
86
|
+
legs: 0xf0b429,
|
|
87
|
+
legsFar: 0xa8791b,
|
|
88
|
+
skin: 0xffd9a8,
|
|
89
|
+
emissive: 0x000000,
|
|
90
|
+
glow: 0,
|
|
91
|
+
},
|
|
92
|
+
blueprint: {
|
|
93
|
+
bg: 0x081620,
|
|
94
|
+
frame: 0x7fd4f0,
|
|
95
|
+
metal: 0xa9e6fa,
|
|
96
|
+
tyre: 0x2b5f77,
|
|
97
|
+
housing: 0x1b4356,
|
|
98
|
+
fly: 0x4fa3c7,
|
|
99
|
+
jersey: 0xffffff,
|
|
100
|
+
legs: 0xfef9c3,
|
|
101
|
+
legsFar: 0x8a7f4a,
|
|
102
|
+
skin: 0xe8f6ff,
|
|
103
|
+
emissive: 0x1d5f7a,
|
|
104
|
+
glow: 0.25,
|
|
105
|
+
},
|
|
106
|
+
neon: {
|
|
107
|
+
bg: 0x0a0418,
|
|
108
|
+
frame: 0x22d3ee,
|
|
109
|
+
metal: 0xa5f3fc,
|
|
110
|
+
tyre: 0xc026d3,
|
|
111
|
+
housing: 0x2f1152,
|
|
112
|
+
fly: 0xe879f9,
|
|
113
|
+
jersey: 0xfb7185,
|
|
114
|
+
legs: 0xfde047,
|
|
115
|
+
legsFar: 0xa16207,
|
|
116
|
+
skin: 0xfda4af,
|
|
117
|
+
emissive: 0xffffff,
|
|
118
|
+
glow: 0.55,
|
|
119
|
+
},
|
|
120
|
+
};
|
|
121
|
+
/**
|
|
122
|
+
* Which rider variant each design wears — `cel` for all three, and that is a decision rather than an
|
|
123
|
+
* oversight.
|
|
124
|
+
*
|
|
125
|
+
* Matching each design to its nearest variant was the obvious move and it was tried: `facet` for
|
|
126
|
+
* `blueprint`, `neon` for `neon`. On screen at this size it fails. `facet`'s cold palette sinks into
|
|
127
|
+
* the blueprint's own teal until the rider and the machine are one shape, and `neon`'s polished chrome
|
|
128
|
+
* has almost no diffuse response, so under this lighting it reads as a black mass with a few
|
|
129
|
+
* highlights — the exact opposite of what a rider needs to be, which is the most legible thing in the
|
|
130
|
+
* picture.
|
|
131
|
+
*
|
|
132
|
+
* `cel` is the one that holds: its own blurb says it "reads at thumbnail size and never goes muddy",
|
|
133
|
+
* and that is what the job is. So the DESIGN paints the bike, the trainer and the tooltip; the RIDER
|
|
134
|
+
* stays the same person across all three. Anyone who wants the others can pass `character` — they are
|
|
135
|
+
* all still exported, `clay` and `knight` included.
|
|
136
|
+
*/
|
|
137
|
+
const VARIANT = {
|
|
138
|
+
solid: "cel",
|
|
139
|
+
blueprint: "cel",
|
|
140
|
+
neon: "cel",
|
|
141
|
+
};
|
|
142
|
+
/** A unit-height cylinder, so a segment is placed by scaling Y and pointing it. */
|
|
143
|
+
const SEG_GEOM = new THREE.CylinderGeometry(1, 1, 1, 12);
|
|
144
|
+
/**
|
|
145
|
+
* Point an existing segment mesh from `a` to `b`.
|
|
146
|
+
*
|
|
147
|
+
* The mesh is a unit cylinder, so its length is a Y scale and its direction a quaternion — no
|
|
148
|
+
* geometry is rebuilt per frame, which is the 3D equivalent of the flat renderer never re-rendering
|
|
149
|
+
* its SVG.
|
|
150
|
+
*/
|
|
151
|
+
function setSegment(mesh, a, b, radius) {
|
|
152
|
+
const dir = new THREE.Vector3().subVectors(b, a);
|
|
153
|
+
const len = dir.length() || 1e-6;
|
|
154
|
+
mesh.position.copy(a).addScaledVector(dir, 0.5);
|
|
155
|
+
mesh.scale.set(radius, len, radius);
|
|
156
|
+
mesh.quaternion.setFromUnitVectors(UP, dir.divideScalar(len));
|
|
157
|
+
}
|
|
158
|
+
function matFor(color, p, opts = {}) {
|
|
159
|
+
return new THREE.MeshStandardMaterial({
|
|
160
|
+
color,
|
|
161
|
+
roughness: opts.rough ?? 0.55,
|
|
162
|
+
metalness: opts.metal ?? 0.15,
|
|
163
|
+
emissive: p.glow ? color : 0x000000,
|
|
164
|
+
emissiveIntensity: p.glow,
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
const VIEW_DIR = {
|
|
168
|
+
// A hair of elevation, not zero: dead level reads as a technical elevation rather than a photograph.
|
|
169
|
+
profile: [0, 0.07, 1],
|
|
170
|
+
"three-quarter": [0.6, 0.4, 1],
|
|
171
|
+
};
|
|
172
|
+
/**
|
|
173
|
+
* Build a renderer factory with options.
|
|
174
|
+
*
|
|
175
|
+
* `RigRendererFactory` takes only the design, so anything else has to be closed over — hence a factory
|
|
176
|
+
* of a factory rather than one more argument. {@link createThreeRiderRig} is this with the defaults,
|
|
177
|
+
* and is what you pass when you have nothing to configure.
|
|
178
|
+
*/
|
|
179
|
+
export function threeRiderRig(opts = {}) {
|
|
180
|
+
return ({ design }) => buildRenderer(design, opts);
|
|
181
|
+
}
|
|
182
|
+
/** The 3D renderer with its defaults — pass it straight to `mountRiderRig`'s `renderer` option. */
|
|
183
|
+
export const createThreeRiderRig = ({ design }) => buildRenderer(design, {});
|
|
184
|
+
function buildRenderer(design, opts) {
|
|
185
|
+
const p = PALETTES[design];
|
|
186
|
+
const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
|
|
187
|
+
renderer.setPixelRatio(Math.min(window.devicePixelRatio || 1, 2));
|
|
188
|
+
renderer.domElement.style.display = "block";
|
|
189
|
+
renderer.domElement.style.width = "100%";
|
|
190
|
+
renderer.domElement.style.height = "auto";
|
|
191
|
+
const scene = new THREE.Scene();
|
|
192
|
+
const camera = new THREE.PerspectiveCamera(FOV, 1, 0.05, 40);
|
|
193
|
+
/**
|
|
194
|
+
* The camera is FITTED, not placed by hand.
|
|
195
|
+
*
|
|
196
|
+
* Hand-placed, it framed the rig at one scale and cropped the rider's head the moment anything
|
|
197
|
+
* moved — and the bounds cannot be read off the meshes either, because the limbs are unit cylinders
|
|
198
|
+
* that only take their real positions on the first `setPose`. So it is fitted to the DRAWING's own
|
|
199
|
+
* bounds, which are known up front and are the same bounds the flat renderer works to. Change the
|
|
200
|
+
* scale or the geometry and the framing follows.
|
|
201
|
+
*/
|
|
202
|
+
// The drawing occupies x 20→190 and y 32 (the rider's crown) → GROUND, plus the rig's own depth.
|
|
203
|
+
const bounds = new THREE.Box3()
|
|
204
|
+
.expandByPoint(toWorld({ x: 20, y: 32 }, -0.3))
|
|
205
|
+
.expandByPoint(toWorld({ x: 190, y: G.GROUND }, 0.3));
|
|
206
|
+
const centre = bounds.getCenter(new THREE.Vector3());
|
|
207
|
+
const radius = bounds.getSize(new THREE.Vector3()).length() / 2;
|
|
208
|
+
// Off-axis and slightly above: enough perspective for the housing to have depth and for the far leg
|
|
209
|
+
// to sit genuinely behind the near one, without losing the readable side-on silhouette.
|
|
210
|
+
const dir = new THREE.Vector3(...VIEW_DIR[opts.view ?? "profile"]).normalize();
|
|
211
|
+
camera.position.copy(centre).addScaledVector(dir, (radius / Math.sin((FOV / 2) * (Math.PI / 180))) * 0.86);
|
|
212
|
+
camera.lookAt(centre);
|
|
213
|
+
// `lookAt` sets the rotation but NOT `matrixWorld`, and `Vector3.project` reads
|
|
214
|
+
// `camera.matrixWorldInverse`. Without this the very first `anchor()` — which runs before anything
|
|
215
|
+
// has rendered — projects against an identity matrix and reports a point off the canvas entirely.
|
|
216
|
+
camera.updateMatrixWorld();
|
|
217
|
+
scene.add(new THREE.HemisphereLight(0xdfe9ff, p.bg, p.glow ? 0.5 : 0.85));
|
|
218
|
+
const key = new THREE.DirectionalLight(0xffffff, p.glow ? 0.7 : 1.35);
|
|
219
|
+
key.position.set(2.2, 3.2, 2.6);
|
|
220
|
+
scene.add(key);
|
|
221
|
+
const rim = new THREE.DirectionalLight(0x9ec7ff, 0.5);
|
|
222
|
+
rim.position.set(-2.4, 1.4, -1.8);
|
|
223
|
+
scene.add(rim);
|
|
224
|
+
const materials = [];
|
|
225
|
+
const geometries = [];
|
|
226
|
+
const mat = (color, o) => {
|
|
227
|
+
const m = matFor(color, p, o);
|
|
228
|
+
materials.push(m);
|
|
229
|
+
return m;
|
|
230
|
+
};
|
|
231
|
+
const geom = (g) => {
|
|
232
|
+
geometries.push(g);
|
|
233
|
+
return g;
|
|
234
|
+
};
|
|
235
|
+
const rig = new THREE.Group();
|
|
236
|
+
scene.add(rig);
|
|
237
|
+
/* ── the trainer ──────────────────────────────────────────────────────── */
|
|
238
|
+
// Extruded from the SAME outline the flat renderer strokes, so the two silhouettes agree; the depth
|
|
239
|
+
// is the only thing 3D adds. Reproducing the shape by hand here would let the two drift.
|
|
240
|
+
const body = new THREE.Shape();
|
|
241
|
+
const outline = [
|
|
242
|
+
[34, G.GROUND],
|
|
243
|
+
[44, 160],
|
|
244
|
+
[62, 154],
|
|
245
|
+
[G.AXLE.x, G.AXLE.y - 3],
|
|
246
|
+
[90, 157],
|
|
247
|
+
[96, 161],
|
|
248
|
+
[104, G.GROUND],
|
|
249
|
+
];
|
|
250
|
+
outline.forEach(([x, y], i) => {
|
|
251
|
+
const w = toWorld({ x, y }, 0);
|
|
252
|
+
if (i === 0)
|
|
253
|
+
body.moveTo(w.x, w.y);
|
|
254
|
+
else
|
|
255
|
+
body.lineTo(w.x, w.y);
|
|
256
|
+
});
|
|
257
|
+
body.closePath();
|
|
258
|
+
const housing = new THREE.Mesh(geom(new THREE.ExtrudeGeometry(body, { depth: 0.3, bevelEnabled: true, bevelSize: 0.012, bevelThickness: 0.012, bevelSegments: 2 })), mat(p.housing, { rough: 0.42, metal: 0.25 }));
|
|
259
|
+
housing.position.z = -0.15;
|
|
260
|
+
rig.add(housing);
|
|
261
|
+
const discGeom = geom(new THREE.CylinderGeometry(G.FLY.r * S, G.FLY.r * S, 0.085, 28));
|
|
262
|
+
const flywheel = new THREE.Mesh(discGeom, mat(p.fly, { rough: 0.35, metal: 0.6 }));
|
|
263
|
+
flywheel.rotation.x = Math.PI / 2;
|
|
264
|
+
flywheel.position.copy(toWorld(G.FLY, 0.17));
|
|
265
|
+
rig.add(flywheel);
|
|
266
|
+
// A pair of spokes so the spin is legible on a disc that would otherwise look static.
|
|
267
|
+
const flySpokes = new THREE.Group();
|
|
268
|
+
const vaneMat = mat(p.housing, { rough: 0.5, metal: 0.3 });
|
|
269
|
+
for (let i = 0; i < 2; i++) {
|
|
270
|
+
const vane = new THREE.Mesh(geom(new THREE.BoxGeometry(G.FLY.r * S * 1.55, 0.02, 0.03)), vaneMat);
|
|
271
|
+
vane.rotation.z = (i * Math.PI) / 2;
|
|
272
|
+
flySpokes.add(vane);
|
|
273
|
+
}
|
|
274
|
+
flySpokes.position.copy(toWorld(G.FLY, 0.22));
|
|
275
|
+
rig.add(flySpokes);
|
|
276
|
+
const cassette = new THREE.Mesh(geom(new THREE.CylinderGeometry(G.CASSETTE_R * S, G.CASSETTE_R * S, 0.05, 18)), mat(p.metal, { rough: 0.3, metal: 0.7 }));
|
|
277
|
+
cassette.rotation.x = Math.PI / 2;
|
|
278
|
+
cassette.position.copy(toWorld(G.AXLE, 0.06));
|
|
279
|
+
rig.add(cassette);
|
|
280
|
+
/* ── the bike ─────────────────────────────────────────────────────────── */
|
|
281
|
+
const frameMat = mat(p.frame, { rough: 0.35, metal: 0.5 });
|
|
282
|
+
const tube = (a, b, r = 0.017, z = Z_FRAME) => {
|
|
283
|
+
const m = new THREE.Mesh(SEG_GEOM, frameMat);
|
|
284
|
+
setSegment(m, toWorld(a, z), toWorld(b, z), r);
|
|
285
|
+
rig.add(m);
|
|
286
|
+
};
|
|
287
|
+
tube(G.AXLE, G.SEAT_CLUSTER);
|
|
288
|
+
tube(G.SEAT_CLUSTER, G.HEADTUBE_TOP);
|
|
289
|
+
tube(G.HEADTUBE_TOP, G.HEADTUBE_BOT, 0.02);
|
|
290
|
+
tube(G.HEADTUBE_BOT, G.BB);
|
|
291
|
+
tube(G.BB, G.AXLE);
|
|
292
|
+
tube(G.SEAT_CLUSTER, G.BB);
|
|
293
|
+
tube(G.SEAT_CLUSTER, G.SADDLE, 0.012);
|
|
294
|
+
/**
|
|
295
|
+
* The fork is TWO blades, one either side of the wheel — not one tube down the middle.
|
|
296
|
+
*
|
|
297
|
+
* Drawn as a single centred tube it ran straight through the rim and out the far side, because the
|
|
298
|
+
* crown sits well outside a 0.34 m wheel and a straight line from there to the hub has to cross it.
|
|
299
|
+
* In the flat drawing that reads as a fork passing behind the wheel; in 3D, with solid geometry, it
|
|
300
|
+
* reads as the frame skewering it. A real fork straddles the wheel, and straddling it is also what
|
|
301
|
+
* removes the intersection.
|
|
302
|
+
*/
|
|
303
|
+
const FORK_Z = 0.045;
|
|
304
|
+
tube(G.HEADTUBE_BOT, G.FRONT, 0.012, FORK_Z);
|
|
305
|
+
tube(G.HEADTUBE_BOT, G.FRONT, 0.012, -FORK_Z);
|
|
306
|
+
// The stem, out to the bars.
|
|
307
|
+
tube(G.HEADTUBE_TOP, G.BAR, 0.013);
|
|
308
|
+
const metalMat = mat(p.metal, { rough: 0.3, metal: 0.6 });
|
|
309
|
+
const saddle = new THREE.Mesh(geom(new THREE.BoxGeometry(0.16, 0.03, 0.09)), metalMat);
|
|
310
|
+
saddle.position.copy(toWorld(G.SADDLE, Z_FRAME));
|
|
311
|
+
saddle.rotation.z = -0.06;
|
|
312
|
+
rig.add(saddle);
|
|
313
|
+
const bars = new THREE.Mesh(geom(new THREE.CylinderGeometry(0.014, 0.014, 0.34, 10)), metalMat);
|
|
314
|
+
bars.rotation.x = Math.PI / 2;
|
|
315
|
+
bars.position.copy(toWorld(G.BAR, Z_FRAME));
|
|
316
|
+
rig.add(bars);
|
|
317
|
+
// The front wheel — the only one left, and it never turns: parked on the floor, driven by nothing.
|
|
318
|
+
const wheel = new THREE.Group();
|
|
319
|
+
const tyre = new THREE.Mesh(geom(new THREE.TorusGeometry(G.WHEEL_R * S, 0.017, 10, 44)), mat(p.tyre, { rough: 0.85, metal: 0.05 }));
|
|
320
|
+
wheel.add(tyre);
|
|
321
|
+
const spokeGeom = geom(new THREE.CylinderGeometry(0.0035, 0.0035, G.WHEEL_R * S * 2, 6));
|
|
322
|
+
for (let i = 0; i < 6; i++) {
|
|
323
|
+
const sp = new THREE.Mesh(spokeGeom, metalMat);
|
|
324
|
+
sp.rotation.z = (i * Math.PI) / 6;
|
|
325
|
+
wheel.add(sp);
|
|
326
|
+
}
|
|
327
|
+
const hub = new THREE.Mesh(geom(new THREE.CylinderGeometry(0.022, 0.022, 0.07, 12)), metalMat);
|
|
328
|
+
hub.rotation.x = Math.PI / 2;
|
|
329
|
+
wheel.add(hub);
|
|
330
|
+
wheel.position.copy(toWorld(G.FRONT, Z_FRAME));
|
|
331
|
+
rig.add(wheel);
|
|
332
|
+
/* ── the crank ────────────────────────────────────────────────────────── */
|
|
333
|
+
const crank = new THREE.Group();
|
|
334
|
+
crank.position.copy(toWorld(G.BB, 0));
|
|
335
|
+
rig.add(crank);
|
|
336
|
+
const chainring = new THREE.Mesh(geom(new THREE.CylinderGeometry(G.CHAINRING_R * S, G.CHAINRING_R * S, 0.012, 22)), metalMat);
|
|
337
|
+
chainring.rotation.x = Math.PI / 2;
|
|
338
|
+
chainring.position.z = 0.06;
|
|
339
|
+
crank.add(chainring);
|
|
340
|
+
for (const side of [1, -1]) {
|
|
341
|
+
const arm = new THREE.Mesh(geom(new THREE.BoxGeometry(G.CRANK_R * S, 0.02, 0.016)), metalMat);
|
|
342
|
+
arm.position.set((side * G.CRANK_R * S) / 2, 0, side * 0.1);
|
|
343
|
+
crank.add(arm);
|
|
344
|
+
const pedal = new THREE.Mesh(geom(new THREE.BoxGeometry(0.075, 0.014, 0.05)), mat(p.metal));
|
|
345
|
+
pedal.position.set(side * G.CRANK_R * S, 0, side * 0.1);
|
|
346
|
+
crank.add(pedal);
|
|
347
|
+
}
|
|
348
|
+
/* ── the rider ────────────────────────────────────────────────────────── */
|
|
349
|
+
// A real procedural character from `../character/`, not a stack of cylinders. It is built to the
|
|
350
|
+
// DRAWING's own dimensions (see `cyclistProportions`) so the knees the flat IK solved are knees this
|
|
351
|
+
// skeleton can actually reach — a rotation can aim a limb but it cannot stretch one.
|
|
352
|
+
const variant = characterById(opts.character ?? VARIANT[design]);
|
|
353
|
+
const rider = variant.build(cyclistOverrides(S));
|
|
354
|
+
const riderMount = mountCyclist(rider.object, S);
|
|
355
|
+
rig.add(riderMount);
|
|
356
|
+
/* ── the floor ────────────────────────────────────────────────────────── */
|
|
357
|
+
const floor = new THREE.Mesh(geom(new THREE.PlaneGeometry(6, 6)), new THREE.MeshStandardMaterial({ color: p.bg, roughness: 1, metalness: 0 }));
|
|
358
|
+
floor.rotation.x = -Math.PI / 2;
|
|
359
|
+
scene.add(floor);
|
|
360
|
+
materials.push(floor.material);
|
|
361
|
+
let w = 0;
|
|
362
|
+
let h = 0;
|
|
363
|
+
/** Seconds since mount — what a variant's own flourishes (a cape, a glow) advance against. */
|
|
364
|
+
let clock = 0;
|
|
365
|
+
function resize() {
|
|
366
|
+
const parent = renderer.domElement.parentElement;
|
|
367
|
+
const next = parent?.clientWidth ?? 0;
|
|
368
|
+
if (!next || next === w)
|
|
369
|
+
return;
|
|
370
|
+
w = next;
|
|
371
|
+
h = next; // the flat drawing is square, and the tooltip is anchored in % of that box
|
|
372
|
+
renderer.setSize(w, h, false);
|
|
373
|
+
renderer.domElement.style.height = `${h}px`;
|
|
374
|
+
camera.aspect = w / h;
|
|
375
|
+
camera.updateProjectionMatrix();
|
|
376
|
+
}
|
|
377
|
+
const anchorWorld = toWorld(G.TRAINER_ANCHOR, 0);
|
|
378
|
+
return {
|
|
379
|
+
node: renderer.domElement,
|
|
380
|
+
/**
|
|
381
|
+
* The anchor, PROJECTED. The flat renderer can answer with a fixed percentage because it is an
|
|
382
|
+
* orthographic side view; here the same point lands wherever the camera puts it, so it has to go
|
|
383
|
+
* through the projection matrix or the tooltip ends up across the middle of the machine.
|
|
384
|
+
*/
|
|
385
|
+
anchor() {
|
|
386
|
+
camera.updateMatrixWorld();
|
|
387
|
+
const ndc = anchorWorld.clone().project(camera);
|
|
388
|
+
return { x: (ndc.x + 1) / 2, y: (1 - ndc.y) / 2 };
|
|
389
|
+
},
|
|
390
|
+
setPose(pose) {
|
|
391
|
+
resize();
|
|
392
|
+
clock += 1 / 60;
|
|
393
|
+
if (!w)
|
|
394
|
+
return; // not laid out yet; the next tick will size and draw
|
|
395
|
+
crank.rotation.z = -pose.crankAngle * (Math.PI / 180);
|
|
396
|
+
cassette.rotation.y = pose.cassetteAngle * (Math.PI / 180);
|
|
397
|
+
flySpokes.rotation.z = -pose.flyAngle * (Math.PI / 180);
|
|
398
|
+
// The rider: one call. Every joint it needs was solved by the component, so nothing about the
|
|
399
|
+
// gait is decided here — see `cyclist-pose.ts`.
|
|
400
|
+
poseCyclist(rider.joints, pose, S);
|
|
401
|
+
rider.tick?.(tickPose(pose, clock), 1 / 60);
|
|
402
|
+
renderer.render(scene, camera);
|
|
403
|
+
},
|
|
404
|
+
dispose() {
|
|
405
|
+
// A WebGL context is not garbage: it has to be handed back, along with everything uploaded to
|
|
406
|
+
// it, or a page that mounts a few of these leaks contexts until the browser drops the oldest.
|
|
407
|
+
disposeTree(riderMount);
|
|
408
|
+
SEG_GEOM.dispose();
|
|
409
|
+
for (const g of geometries)
|
|
410
|
+
g.dispose();
|
|
411
|
+
for (const m of materials)
|
|
412
|
+
m.dispose();
|
|
413
|
+
renderer.dispose();
|
|
414
|
+
renderer.domElement.remove();
|
|
415
|
+
},
|
|
416
|
+
};
|
|
417
|
+
}
|
|
418
|
+
//# sourceMappingURL=three-rider-rig.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"three-rider-rig.js","sourceRoot":"","sources":["../../../src/three/rider/three-rider-rig.ts"],"names":[],"mappings":"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"}
|
package/dist/ui/README.md
CHANGED
|
@@ -49,6 +49,7 @@ feed its `{x, y, onScreen}` into `setScreenPos`/`setVisible` — don't compute p
|
|
|
49
49
|
| `mountRarityCard(host, opts)` / `buildRarityCard(opts)` | fn | A generic rarity-tinted item/creature card. |
|
|
50
50
|
| `createActionMarker(host, opts)` | fn | **"There is something to do THERE"** — a card pinned to a point in the world (you `place(x, y)` it every frame with the point projected to screen), the keycap joining it once the rider is close enough (`setNear`), and a caret clamping it to the border with an arrow while the point is off screen. Two independent axes: `tone` is MOTION (`hint` still · `action` breathing · `urgent` hammering), `color` is SUBJECT (`red`/`danger`, `green`/`success`, `cyan`/`accent`, … — see `MARKER_COLORS`). |
|
|
51
51
|
| `createPowerRace(opts)` | fn | A timed **power-vs-deadline race** overlay — the "push NOW or lose it" beat. A charge bar (∫power dt) races a clock; first to 100% wins. `start()` then `setPower(w)`+`tick(dt)` per frame; `won`/`lost` via `onResolve`. Recolour with `theme`, relabel with `text`. **Timings:** `introS` is the wind-up before the bars go live (default 2s — pass `0` for a widget the rider OPENED by pressing a button, so watts count from frame 1), `durationS` the race itself, `hold: { stampDelayS, hideAfterS }` what happens after it resolves (either can be `false`). `setTarget({ ftp })` re-sizes the effort target in place — no rebuild when a rider retunes their FTP mid-ride. |
|
|
52
|
+
| `mountRiderRig(host, opts)` | fn | **A rider pedalling on their direct-drive trainer, with their watts in a glass tooltip leaving the machine** (bubble + pointer, the number above the bar, every label inside the glass) — the one drawing here whose subject is the BIKE rather than the pad. No rear wheel (it comes off; the frame bolts to the trainer's axle and the chain drives its cassette), and the bike is STILL — it's clamped to a fixed machine, so the frame never sways and the front wheel never turns. What a game puts on an onboarding beat, a "how this works" panel or an effort-based pause screen to say that the trainer is half of the controller. `setPower(w)` + `setCadence(rpm)` + `tick(dt)` per frame. **Two separate engines:** cadence turns the legs, power fills the gauge and folds the posture — never drive both from watts (see below). `design` picks one of three art directions (`solid` / `blueprint` / `neon`); the geometry and the animation are shared. NOT `mountControlTrainer`, which is a lesson. |
|
|
52
53
|
| `mountHeatVignette(host, opts)` | fn | **Screen-edge heat vignette alone** — the peripheral amber→orange→red glow that escalates with heat. Take this when your game draws its own heat bar. `setHeat(heat01, locked)` per frame. |
|
|
53
54
|
| `mountOverheatGlitch(host, opts)` | fn | **Lockout glitch overlay alone** — one-shot RGB chromatic burst on entry, then scanlines + red noise for the lockout. `setLocked(bool)` per frame. |
|
|
54
55
|
| `mountOverheatHudBar(host, opts)` | fn | **The whole overheat package**: centred inferno bar + label + COOLING DOWN countdown, composing the vignette and glitch above. One `update(heat01, locked, lockoutRemaining)` per frame. |
|
|
@@ -68,7 +69,9 @@ Types: `DiamondButton` (`"DIAMOND_UP"\|"DIAMOND_DOWN"\|"DIAMOND_LEFT"\|"DIAMOND_
|
|
|
68
69
|
`LabeledDiamond`, `ActionDiamond`, `Rarity`, `RarityCard`, `OptionMenu`, `OptionMenuItem`, `OptionMenuOptions`,
|
|
69
70
|
`ControlAction`, `ComboToken`, `StickDir`, `PadInputSource`, `PadBrandKey`, `PadPart`, `PadLayout`,
|
|
70
71
|
`ControllerPad`, `ControllerMap`, `ComboDemo`, `InputChip`, `ComboStrip`,
|
|
71
|
-
`ActionMarker`, `ActionMarkerOptions`, `ActionMarkerTone`, `ActionMarkerColor`, `ActionMarkerRole
|
|
72
|
+
`ActionMarker`, `ActionMarkerOptions`, `ActionMarkerTone`, `ActionMarkerColor`, `ActionMarkerRole`,
|
|
73
|
+
`RiderRig`, `RiderRigOptions`, `RiderRigDesign`, `RigPose`, `RigPoint`, `RigRenderer`,
|
|
74
|
+
`RigRendererFactory`
|
|
72
75
|
(+ the `MARKER_COLORS` / `MARKER_ROLE_COLORS` tables).
|
|
73
76
|
|
|
74
77
|
---
|
|
@@ -262,6 +265,15 @@ where the caption sits in a card — but write it for what the combination *can'
|
|
|
262
265
|
controls lights all of them, but the bubble draws no chips; use `mountComboDemo` or `mountControllerMap`
|
|
263
266
|
to spell a combination out.
|
|
264
267
|
|
|
268
|
+
**Two looks, chosen by the drawing underneath.** Over the flat SVG pad the caption is the opaque white
|
|
269
|
+
bubble with a solid caret — one known tone under it, and a bubble that sits beside the drawing more
|
|
270
|
+
than on it. Over a pad drawn in volume (`renderer: createThreePadRenderer`) it becomes bare white type
|
|
271
|
+
with its own dark shadow and no pointer: an opaque slab on a shaded shell reads as a sticker and hides
|
|
272
|
+
the control it names, and with the control lit under the caption there is nothing for a caret to
|
|
273
|
+
disambiguate. The spotlight picks this from `PadRenderer.perspective` and marks its root
|
|
274
|
+
`.on-3d`, so a host that wants something else overrides those rules in CSS rather than passing an
|
|
275
|
+
option.
|
|
276
|
+
|
|
265
277
|
### Teaching a control — `mountControlTrainer`
|
|
266
278
|
|
|
267
279
|
For a lesson the rider has to *perform*, not just watch. Built for paired controls: one concept, two
|
|
@@ -309,6 +321,146 @@ rotation moves on, so "you did it" lands before "now this one".
|
|
|
309
321
|
|
|
310
322
|
---
|
|
311
323
|
|
|
324
|
+
## The rider on the trainer (`mountRiderRig`)
|
|
325
|
+
|
|
326
|
+
Everything else in this kit teaches the rider about the **pad**. A game built only from those pieces
|
|
327
|
+
tells a new player that the controller is the thing in their hands — and it isn't, or not only. This
|
|
328
|
+
is the picture that says so.
|
|
329
|
+
|
|
330
|
+
```ts
|
|
331
|
+
const rig = mountRiderRig(panel, { design: "solid", ftp: session.identity.ftp });
|
|
332
|
+
|
|
333
|
+
// each frame:
|
|
334
|
+
rig.setPower(session.hardware.current.power); // raw, not smoothedPower — see below
|
|
335
|
+
rig.setCadence(session.hardware.current.cadence);
|
|
336
|
+
rig.tick(dt);
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
### No rear wheel, and a bike that doesn't move
|
|
340
|
+
|
|
341
|
+
This is a **direct-drive** trainer, the kind RYDR runs on: the rear wheel comes **off**, the bike's
|
|
342
|
+
dropouts bolt onto the trainer's own axle, and the chain drives the trainer's cassette directly —
|
|
343
|
+
that junction is where the connection to the platform physically happens. A wheel-on trainer (a tyre
|
|
344
|
+
pressed onto a spinning roller) would be a picture of a different, older machine, and would quietly
|
|
345
|
+
teach a newcomer that the trainer is something the bike sits on top of rather than part of the
|
|
346
|
+
drivetrain.
|
|
347
|
+
|
|
348
|
+
That also settles what may move. **The bike is still.** It is bolted to a fixed machine: the frame
|
|
349
|
+
does not sway, and the front wheel — parked on the floor, driven by nothing — does not turn. The only
|
|
350
|
+
things that rotate are the ones the rider's legs actually drive: the crank, the cassette, and the
|
|
351
|
+
flywheel. A frame that rocked or a front wheel that spun would be describing a bike going somewhere,
|
|
352
|
+
which is the one thing this bike is not doing.
|
|
353
|
+
|
|
354
|
+
### ⚠️ Not `mountControlTrainer`
|
|
355
|
+
|
|
356
|
+
English "trainer" means both the *turbo trainer* and the *coach*, and this kit has one of each:
|
|
357
|
+
|
|
358
|
+
| | what it is |
|
|
359
|
+
| --- | --- |
|
|
360
|
+
| `mountControlTrainer` | the **coach** — a guided lesson that watches the rider perform a combination |
|
|
361
|
+
| `mountRiderRig` | the **turbo trainer** — the physical machine, drawn, pedalled, with a watts gauge |
|
|
362
|
+
|
|
363
|
+
They share no code. If you want to teach a control, you want the other one.
|
|
364
|
+
|
|
365
|
+
### Two engines, and why they must stay separate
|
|
366
|
+
|
|
367
|
+
**Cadence turns the legs. Power fills the gauge.** Nothing crosses over:
|
|
368
|
+
|
|
369
|
+
- `setCadence(rpm)` drives the crank, the legs, the rear wheel and the flywheel — the *movement*.
|
|
370
|
+
- `setPower(watts)` drives the gauge and the rider's posture (the torso folding onto the bars, and a
|
|
371
|
+
rock out of the saddle past ~110% FTP) — the *effort*.
|
|
372
|
+
|
|
373
|
+
Driving both from watts would be less code and would teach the opposite of the truth: that a rider
|
|
374
|
+
spinning fast against no resistance is doing something. Two riders at 90 rpm, one at 100 W and one at
|
|
375
|
+
300 W, have identical legs and nothing else in common — and which of them the game is reading is
|
|
376
|
+
exactly what a newcomer has to understand. If you have no cadence, pass a plausible constant (or
|
|
377
|
+
`idleCadence`) rather than feeding watts in twice.
|
|
378
|
+
|
|
379
|
+
### The gauge is FTP-relative, and it leaves the machine
|
|
380
|
+
|
|
381
|
+
The gauge is a **glass tooltip leaving the machine**: a compact bubble with a pointer into the
|
|
382
|
+
trainer's shoulder, the watts printed large at the top and the bar below them. The drawing is inline
|
|
383
|
+
SVG; the tooltip is an **HTML element over it** — the kit's own split, the same one `mountControllerMap`
|
|
384
|
+
makes when it draws its callout cards in HTML and only the lines between them in SVG. Boxes are CSS,
|
|
385
|
+
shapes are SVG. That is what gives the panel real `backdrop-filter` glass, and it means its FTP label
|
|
386
|
+
is centred on its tick by CSS rather than by a hand-computed coordinate. The bar's orientation
|
|
387
|
+
drives the shape of the box — a landscape bubble where it fills left → right, a portrait one where it
|
|
388
|
+
fills bottom → top.
|
|
389
|
+
|
|
390
|
+
Two reasons, neither decorative. Where the number comes from is part of what the picture says — these
|
|
391
|
+
watts are measured by *that* machine, not floating beside the bike. And the bubble gives every label
|
|
392
|
+
somewhere to live: the number, its unit and the FTP mark all sit **inside** the glass, so none of them
|
|
393
|
+
is ever printed over the frame, the rider or the trainer.
|
|
394
|
+
|
|
395
|
+
The scale runs `0 → 1.5 × ftp`, with the rider's threshold marked at two thirds height and headroom
|
|
396
|
+
above it for a sprint. Pass `ftp` (from `session.identity.ftp`) and never hard-code a raw-watt scale —
|
|
397
|
+
the same rule as `createPowerRace`. Zone colours are computed from %FTP and are **local to this
|
|
398
|
+
component**: they are deliberately not `@rydr/game-sdk/difficulty`'s ramp, which colours the rider's
|
|
399
|
+
chosen *calibration level* and is a different quantity that happens to be measured in watts too.
|
|
400
|
+
|
|
401
|
+
Feed it **raw `power`**. It keeps both numbers: the readout shows the true instantaneous value while
|
|
402
|
+
the bar follows an internal EMA so it climbs smoothly between the trainer's sparse (~1–4 Hz) updates.
|
|
403
|
+
That is the `power` / `smoothedPower` split from `HardwareSnapshot`, resolved here once so no caller
|
|
404
|
+
has to hold two of them.
|
|
405
|
+
|
|
406
|
+
### Drawn flat, or in 3D
|
|
407
|
+
|
|
408
|
+
`mountRiderRig` never draws the rig itself. It owns the state — the two EMAs, the crank, the effort,
|
|
409
|
+
the %FTP zone — and the tooltip, and hands one solved `RigPose` per frame to a **renderer**. The
|
|
410
|
+
default is the flat SVG drawing; pass `renderer: createThreeRiderRig` from `@rydr/game-sdk/three` to
|
|
411
|
+
get the same rig in 3D, with everything else unchanged:
|
|
412
|
+
|
|
413
|
+
```ts
|
|
414
|
+
import { createThreeRiderRig } from "@rydr/game-sdk/three";
|
|
415
|
+
|
|
416
|
+
mountRiderRig(panel, { design: "solid", ftp: 240, renderer: createThreeRiderRig });
|
|
417
|
+
```
|
|
418
|
+
|
|
419
|
+
The `ui/showcase` catalog can show both: pass `riderRigRenderer` to `mountUiShowcase` and its rider-rig
|
|
420
|
+
section grows a **Draw in 3D** button. It has to come from the HOST — the catalog is part of `/ui`,
|
|
421
|
+
which must not depend on `three` — so omitting it leaves the button disabled rather than absent.
|
|
422
|
+
|
|
423
|
+
The pose is solved **here**, once — the leg IK, the crank kinematics, the posture fold, the rise out
|
|
424
|
+
of the saddle — so a renderer receives finished joint positions and re-derives nothing. Two renderers
|
|
425
|
+
cannot drift into two different gaits, and a fix to the pedalling fixes both. `@rydr/game-sdk/ui` has
|
|
426
|
+
no dependency on `three`: the 3D renderer lives in `@rydr/game-sdk/three`, where `three` is already an
|
|
427
|
+
optional peer dependency. Types: `RigPose`, `RigPoint`, `RigRenderer`, `RigRendererFactory`, and
|
|
428
|
+
`RIG_GEOMETRY` for a renderer that needs the drawing's own coordinates.
|
|
429
|
+
|
|
430
|
+
### Placing the tooltip
|
|
431
|
+
|
|
432
|
+
The tooltip sits where each layout's default puts it, and `tip` overrides any part of that:
|
|
433
|
+
|
|
434
|
+
```ts
|
|
435
|
+
mountRiderRig(panel, { design: "solid", ftp: 240, tip: { x: 0.32, y: 0.695, arrow: 6.2 } });
|
|
436
|
+
```
|
|
437
|
+
|
|
438
|
+
`x`/`y` are fractions of the drawing's box (y from the top) marking where the pointer's tip lands;
|
|
439
|
+
`arrow` is how far along the tooltip's own bottom edge the pointer hangs, in em. `RiderRig.setTipPlacement()`
|
|
440
|
+
moves it after mounting.
|
|
441
|
+
|
|
442
|
+
Those numbers are meant to be found **by eye**, not derived: `npm run rig3d` carries a dev panel that
|
|
443
|
+
drives `setTipPlacement` live and prints the exact `tip` option to paste back. The two layouts anchor
|
|
444
|
+
differently, so it tunes them separately.
|
|
445
|
+
|
|
446
|
+
### Three designs
|
|
447
|
+
|
|
448
|
+
`design` is paint only — the geometry, the joint solving and the timings are shared, and the numeric
|
|
449
|
+
readout sits in the same place in all three so the rider always knows where to look:
|
|
450
|
+
|
|
451
|
+
Every gauge is a **continuous** fill; what changes is the paint and the tooltip's orientation.
|
|
452
|
+
|
|
453
|
+
- **`solid`** (default) — flat opaque silhouette; a landscape tooltip. Most legible.
|
|
454
|
+
- **`blueprint`** — thin technical linework; a **portrait** tooltip whose bar runs vertically and
|
|
455
|
+
which shows **no figure at all** — just the `WATTS` title and a tall bar marked at FTP. On that
|
|
456
|
+
variant the bar is the reading: a rider glancing at a trainer screen reads the fill long before
|
|
457
|
+
they read three digits, and a number beside it competes with what it annotates.
|
|
458
|
+
- **`neon`** — glowing outline; a landscape tooltip whose fill carries a bright leading edge. The
|
|
459
|
+
arcade one.
|
|
460
|
+
|
|
461
|
+
See all three side by side, drivable across the whole scale, in the `ui/showcase` catalog
|
|
462
|
+
(`npm run showcase` → "Rider on the trainer").
|
|
463
|
+
|
|
312
464
|
## Wiring to real input
|
|
313
465
|
|
|
314
466
|
Any keycap/card reflects real presses if you pass it something with `onButton` — your platform
|
|
@@ -32,6 +32,7 @@
|
|
|
32
32
|
*/
|
|
33
33
|
import type { GlyphSet } from "../../protocol/glyphs.js";
|
|
34
34
|
import { type ControllerPad, type PadInputSource } from "./controller-pad.js";
|
|
35
|
+
import type { PadRendererFactory } from "./renderer.js";
|
|
35
36
|
import type { ControlAction } from "./tokens.js";
|
|
36
37
|
import type { PadBrandKey } from "./types.js";
|
|
37
38
|
export interface ComboDemoOptions {
|
|
@@ -66,6 +67,14 @@ export interface ComboDemoOptions {
|
|
|
66
67
|
speed?: number;
|
|
67
68
|
/** Called each time the loop completes a full cycle. */
|
|
68
69
|
onCycle?: () => void;
|
|
70
|
+
/**
|
|
71
|
+
* What DRAWS the pad. Default: the flat SVG drawing.
|
|
72
|
+
*
|
|
73
|
+
* Pass `createThreePadRenderer` from `@rydr/game-sdk/three` to render the Joy-Con in 3D — this view
|
|
74
|
+
* works against it unchanged, because it never draws the controller itself. See
|
|
75
|
+
* `ui/controller/renderer.ts`.
|
|
76
|
+
*/
|
|
77
|
+
renderer?: PadRendererFactory;
|
|
69
78
|
}
|
|
70
79
|
export interface ComboDemo {
|
|
71
80
|
readonly root: HTMLDivElement;
|