@laplace.live/persona-sdk 0.12.0 → 0.14.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.
@@ -21,6 +21,9 @@ export const EFFECT_SPECS = {
21
21
  saturation: { default: 0, min: -1, max: 1, step: 0.01 },
22
22
  brightness: { default: 0, min: -1, max: 1, step: 0.01 },
23
23
  contrast: { default: 0, min: -1, max: 1, step: 0.01 },
24
+ // Unity PPv2's white-balance range, so a Unity-authored grade arrives as-is.
25
+ temperature: { default: 0, min: -100, max: 100, step: 1 },
26
+ tint: { default: 0, min: -100, max: 100, step: 1 },
24
27
  },
25
28
  bloom: {
26
29
  // Past ×3 the halo overwhelms the source pixels and everything reads as haze.
@@ -1,4 +1,4 @@
1
- import type { Attach, ModelFormat, MToonTuning, ObjectContent, ObjectSpace, SceneLight, SceneLightType, ScreenPlacement, VrmPlacement } from '../wire/types.ts';
1
+ import type { AssetRef, Attach, EnvironmentLook, ModelFormat, MToonTuning, ObjectContent, ObjectSpace, SceneEnvironment, SceneLight, SceneLightType, ScreenPlacement, VrmPlacement } from '../wire/types.ts';
2
2
  export declare function clamp(v: number, min: number, max: number): number;
3
3
  /** Clamp to the unit interval. */
4
4
  export declare function clamp01(v: number): number;
@@ -37,15 +37,69 @@ export declare function sceneLightShadowRadiusMax(type: SceneLightType): number;
37
37
  export declare const SCENE_FOG_DENSITY_MAX = 0.5;
38
38
  /** ×4 is +2 stops — enough to rescue an AgX-dimmed avatar without turning the slider to mush. */
39
39
  export declare const SCENE_EXPOSURE_MAX = 4;
40
+ export declare const MTOON_NORMAL_SCALE_MAX = 2;
40
41
  export declare const MTOON_RIM_MAX = 2;
42
+ /** ×0 flattens the Fresnel to a flood fill, ×4 tightens a default power-5 rim to a hairline. */
43
+ export declare const MTOON_RIM_FRESNEL_POWER_MAX = 4;
44
+ /** ×4 carries a quarter-strength authored `matcapFactor` back to the spec's full 1.0. */
45
+ export declare const MTOON_MATCAP_MAX = 4;
41
46
  export declare const MTOON_OUTLINE_WIDTH_MAX = 2;
42
47
  /** HDR headroom: past ×1 the point is pushing emissive parts over the bloom threshold. */
43
48
  export declare const MTOON_EMISSIVE_MAX = 4;
49
+ export declare const MTOON_UV_ANIMATION_MAX = 4;
44
50
  export declare function defaultMToonTuning(): MToonTuning;
45
51
  export declare const DEFAULT_LIGHT_AZIMUTH_DEG = 45;
46
52
  export declare const DEFAULT_LIGHT_ELEVATION_DEG: number;
47
53
  /** A fresh light of `type`, at the defaults that read sensibly for that type. */
48
54
  export declare function defaultSceneLightOf(type: SceneLightType): SceneLight;
55
+ /**
56
+ * Where a scene's own lights drop once a set lights the stage — Warudo's
57
+ * `CalculateIdealDirectionalLightIntensity`; past it the two light sets clip MToon to white.
58
+ */
59
+ export declare const ENVIRONMENT_IDEAL_LIGHT_INTENSITY = 0.25;
60
+ /**
61
+ * Warudo's Apply Environment Lighting: route the set's lights at everything and cap each
62
+ * shading light at {@link ENVIRONMENT_IDEAL_LIGHT_INTENSITY}; ambient, a flat floor, stays.
63
+ * @returns New `environment` and `lights` values; the inputs are untouched.
64
+ */
65
+ export declare function applyEnvironmentLighting(env: SceneEnvironment, lights: readonly SceneLight[]): {
66
+ environment: SceneEnvironment;
67
+ lights: SceneLight[];
68
+ };
69
+ /**
70
+ * Fold a set's {@link EnvironmentLook} into a scene's environment: its fog, its
71
+ * ambient level and the four post settings it names, leaving every other effect —
72
+ * and the user's lights, map and placement — untouched.
73
+ *
74
+ * Enabling an effect here is enough for it to render: `effectLayers` is re-derived
75
+ * from what the result switches on, so a look lands on the live stage rather than
76
+ * waiting for main's healing pass to come back around.
77
+ * @returns A new environment; the input is untouched.
78
+ */
79
+ export declare function applyEnvironmentLook(env: SceneEnvironment, look: EnvironmentLook): SceneEnvironment;
80
+ /**
81
+ * Whether `env` still holds `reference`'s values in every field an
82
+ * {@link EnvironmentLook} would overwrite — so applying one takes nothing away.
83
+ *
84
+ * The guard on adopting a set's look unasked. Applying it is otherwise deliberate
85
+ * because it overwrites fog and post the user may have tuned; when none of it has
86
+ * been touched, there is nothing to protect and a room that ships a look should
87
+ * simply arrive wearing it.
88
+ */
89
+ export declare function environmentLookIsUntouched(env: SceneEnvironment, reference: SceneEnvironment): boolean;
90
+ /**
91
+ * Point an environment at one asset, or at none.
92
+ *
93
+ * {@link SceneEnvironment.iblAssetId} and {@link SceneEnvironment.modelAssetId} are a
94
+ * single slot — a scene wears an equirect map or a 3D set — so this clears whichever
95
+ * the pick is not. `bakeFromModel` goes with the set: it outlives the id it names, and
96
+ * it wins over a picked map, so a stale one would leave a fresh map lighting nothing.
97
+ * Light overrides index the set's own lamps, so a different set — or none — drops them.
98
+ * `modelLook` stays put: the stage replaces it as the next set parses, and the desktop
99
+ * reads the outgoing one as what the scene wears now when deciding to adopt the next.
100
+ * @returns A new environment; the input is untouched.
101
+ */
102
+ export declare function environmentWithAsset(env: SceneEnvironment, asset: Pick<AssetRef, 'id' | 'kind'> | null): SceneEnvironment;
49
103
  export declare const PLACE_2D_SCALE_MIN = 0.01;
50
104
  export declare const PLACE_2D_SCALE_MAX = 50;
51
105
  export declare const PLACE_3D_SCALE_MIN = 0.01;
@@ -1,3 +1,4 @@
1
+ import { effectLayerKeys } from "./effect-schema.js";
1
2
  export function clamp(v, min, max) {
2
3
  return Math.min(max, Math.max(min, v));
3
4
  }
@@ -51,12 +52,33 @@ export function sceneLightShadowRadiusMax(type) {
51
52
  export const SCENE_FOG_DENSITY_MAX = 0.5;
52
53
  /** ×4 is +2 stops — enough to rescue an AgX-dimmed avatar without turning the slider to mush. */
53
54
  export const SCENE_EXPOSURE_MAX = 4;
55
+ export const MTOON_NORMAL_SCALE_MAX = 2;
54
56
  export const MTOON_RIM_MAX = 2;
57
+ /** ×0 flattens the Fresnel to a flood fill, ×4 tightens a default power-5 rim to a hairline. */
58
+ export const MTOON_RIM_FRESNEL_POWER_MAX = 4;
59
+ /** ×4 carries a quarter-strength authored `matcapFactor` back to the spec's full 1.0. */
60
+ export const MTOON_MATCAP_MAX = 4;
55
61
  export const MTOON_OUTLINE_WIDTH_MAX = 2;
56
62
  /** HDR headroom: past ×1 the point is pushing emissive parts over the bloom threshold. */
57
63
  export const MTOON_EMISSIVE_MAX = 4;
64
+ export const MTOON_UV_ANIMATION_MAX = 4;
58
65
  export function defaultMToonTuning() {
59
- return { shade: 1, shadingShift: 0, shadingToony: 0, giEqualization: 0, rim: 1, outlineWidth: 1, emissive: 1 };
66
+ return {
67
+ shade: 1,
68
+ shadingShift: 0,
69
+ shadingToony: 0,
70
+ giEqualization: 0,
71
+ normalScale: 1,
72
+ rim: 1,
73
+ rimLift: 0,
74
+ rimFresnelPower: 1,
75
+ rimLightingMix: 0,
76
+ matcap: 1,
77
+ outlineWidth: 1,
78
+ outlineLightingMix: 0,
79
+ emissive: 1,
80
+ uvAnimation: 1,
81
+ };
60
82
  }
61
83
  // Ranges for toggle-style effect params live in effect-schema.ts (EFFECT_SPECS).
62
84
  // Angles at which lightDirection reproduces (1,1,1).normalize() — the app's original hardcoded light.
@@ -82,6 +104,86 @@ export function defaultSceneLightOf(type) {
82
104
  shadowRadius: 6,
83
105
  };
84
106
  }
107
+ /**
108
+ * Where a scene's own lights drop once a set lights the stage — Warudo's
109
+ * `CalculateIdealDirectionalLightIntensity`; past it the two light sets clip MToon to white.
110
+ */
111
+ export const ENVIRONMENT_IDEAL_LIGHT_INTENSITY = 0.25;
112
+ /**
113
+ * Warudo's Apply Environment Lighting: route the set's lights at everything and cap each
114
+ * shading light at {@link ENVIRONMENT_IDEAL_LIGHT_INTENSITY}; ambient, a flat floor, stays.
115
+ * @returns New `environment` and `lights` values; the inputs are untouched.
116
+ */
117
+ export function applyEnvironmentLighting(env, lights) {
118
+ return {
119
+ environment: { ...env, modelLightsEnabled: true, lightsAffectCharacters: true, lightsAffectProps: true },
120
+ lights: lights.map(l => l.type === 'ambient' ? { ...l } : { ...l, intensity: Math.min(l.intensity, ENVIRONMENT_IDEAL_LIGHT_INTENSITY) }),
121
+ };
122
+ }
123
+ /**
124
+ * Fold a set's {@link EnvironmentLook} into a scene's environment: its fog, its
125
+ * ambient level and the four post settings it names, leaving every other effect —
126
+ * and the user's lights, map and placement — untouched.
127
+ *
128
+ * Enabling an effect here is enough for it to render: `effectLayers` is re-derived
129
+ * from what the result switches on, so a look lands on the live stage rather than
130
+ * waiting for main's healing pass to come back around.
131
+ * @returns A new environment; the input is untouched.
132
+ */
133
+ export function applyEnvironmentLook(env, look) {
134
+ const effects = {
135
+ ...env.effects,
136
+ toneMapping: look.toneMapping,
137
+ exposure: look.exposure,
138
+ bloom: { ...look.bloom },
139
+ color: { ...look.color },
140
+ };
141
+ return {
142
+ ...env,
143
+ iblIntensity: look.iblIntensity,
144
+ fog: { ...look.fog },
145
+ effects,
146
+ effectLayers: effectLayerKeys(env.effectLayers, effects),
147
+ };
148
+ }
149
+ /**
150
+ * Whether `env` still holds `reference`'s values in every field an
151
+ * {@link EnvironmentLook} would overwrite — so applying one takes nothing away.
152
+ *
153
+ * The guard on adopting a set's look unasked. Applying it is otherwise deliberate
154
+ * because it overwrites fog and post the user may have tuned; when none of it has
155
+ * been touched, there is nothing to protect and a room that ships a look should
156
+ * simply arrive wearing it.
157
+ */
158
+ export function environmentLookIsUntouched(env, reference) {
159
+ // Compared through the same shape `applyEnvironmentLook` writes, so a field
160
+ // added to the look is covered here without a second edit.
161
+ const looked = (e) => JSON.stringify([e.fog, e.iblIntensity, e.effects.toneMapping, e.effects.exposure, e.effects.bloom, e.effects.color]);
162
+ return looked(env) === looked(reference);
163
+ }
164
+ /**
165
+ * Point an environment at one asset, or at none.
166
+ *
167
+ * {@link SceneEnvironment.iblAssetId} and {@link SceneEnvironment.modelAssetId} are a
168
+ * single slot — a scene wears an equirect map or a 3D set — so this clears whichever
169
+ * the pick is not. `bakeFromModel` goes with the set: it outlives the id it names, and
170
+ * it wins over a picked map, so a stale one would leave a fresh map lighting nothing.
171
+ * Light overrides index the set's own lamps, so a different set — or none — drops them.
172
+ * `modelLook` stays put: the stage replaces it as the next set parses, and the desktop
173
+ * reads the outgoing one as what the scene wears now when deciding to adopt the next.
174
+ * @returns A new environment; the input is untouched.
175
+ */
176
+ export function environmentWithAsset(env, asset) {
177
+ const isSet = asset?.kind === 'prop';
178
+ const modelAssetId = isSet ? asset.id : null;
179
+ return {
180
+ ...env,
181
+ iblAssetId: asset !== null && !isSet ? asset.id : null,
182
+ modelAssetId,
183
+ bakeFromModel: isSet && env.bakeFromModel,
184
+ modelLightOverrides: modelAssetId === env.modelAssetId ? env.modelLightOverrides : {},
185
+ };
186
+ }
85
187
  export const PLACE_2D_SCALE_MIN = 0.01;
86
188
  export const PLACE_2D_SCALE_MAX = 50;
87
189
  export const PLACE_3D_SCALE_MIN = 0.01;
@@ -92,7 +92,8 @@ export interface Place3D extends VrmPlacement {
92
92
  /**
93
93
  * Per-instance MToon fine-tuning, layered over each material's authored values —
94
94
  * offsets add, multipliers scale, so materials keep their relative differences.
95
- * All-neutral values (1, 0, 0, 0, 1, 1, 1) render the model exactly as authored.
95
+ * Neutral values — every multiplier 1, every offset 0 render the model exactly
96
+ * as authored; `defaultMToonTuning()` is that neutral set.
96
97
  */
97
98
  export interface MToonTuning {
98
99
  /** 0..1 strength of the authored shading: 1 keeps it, 0 lifts every shade color to its lit color. */
@@ -103,12 +104,26 @@ export interface MToonTuning {
103
104
  shadingToony: number;
104
105
  /** -1..1 added to authored GI equalization: how evenly ambient/IBL light wraps the model. */
105
106
  giEqualization: number;
107
+ /** Multiplier on normal-map strength; 0 flattens the surface detail away. */
108
+ normalScale: number;
106
109
  /** Multiplier on the parametric rim color. */
107
110
  rim: number;
111
+ /** -1..1 added to authored rim lift: how far the rim wraps in off the silhouette. */
112
+ rimLift: number;
113
+ /** Multiplier on the rim's Fresnel power; higher pulls the band tighter to the edge. */
114
+ rimFresnelPower: number;
115
+ /** -1..1 added to authored rim lighting mix: 0 renders rim and matcap flat, 1 ties them to the scene lights. */
116
+ rimLightingMix: number;
117
+ /** Multiplier on the matcap factor — the sphere-add reflection. No-op on materials without a matcap. */
118
+ matcap: number;
108
119
  /** Multiplier on outline width. */
109
120
  outlineWidth: number;
121
+ /** -1..1 added to authored outline lighting mix: 0 keeps the outline its own color, 1 tints it with lighting. */
122
+ outlineLightingMix: number;
110
123
  /** Multiplier on emissive intensity; >1 pairs with bloom. */
111
124
  emissive: number;
125
+ /** Multiplier on UV scroll and rotation speeds; 0 freezes an animated texture. */
126
+ uvAnimation: number;
112
127
  }
113
128
  export interface SceneModelItem {
114
129
  kind: 'model';
@@ -242,8 +257,10 @@ export interface OrbitTransform {
242
257
  azimuth: number;
243
258
  elevation: number;
244
259
  distance: number;
260
+ /** Pivot the camera orbits and looks at. Panning moves it along the camera's own axes, so it leaves the XY plane. */
245
261
  targetX: number;
246
262
  targetY: number;
263
+ targetZ: number;
247
264
  }
248
265
  /** Scene-level VRM camera. `orbit: null` = never framed — the first VRM load frames it from model height. */
249
266
  export interface SceneCamera {
@@ -339,6 +356,10 @@ export interface SceneColorGrade {
339
356
  saturation: number;
340
357
  brightness: number;
341
358
  contrast: number;
359
+ /** White balance along blue↔amber, −100..100. Positive is warmer. */
360
+ temperature: number;
361
+ /** White balance along green↔magenta, −100..100 — the axis `temperature` leaves alone. */
362
+ tint: number;
342
363
  }
343
364
  /** Lens fringing that grows toward frame edges (Unity PPv2's curve, the one VTube Studio wraps). */
344
365
  export interface SceneChromaticAberration {
@@ -573,14 +594,59 @@ export interface SceneCustomEffect {
573
594
  params: Record<string, number | boolean | string>;
574
595
  }
575
596
  /**
576
- * Image-based lighting for the 3D stage: an environment map, whether to show it
577
- * behind the scene, the scene-global fog, and post-processing.
597
+ * A 3D set's own suggested look the fog and post settings it was authored against,
598
+ * read from its root `LAPLACE_environment` extension and held on the scene for every client.
599
+ */
600
+ export interface EnvironmentLook {
601
+ fog: SceneFog;
602
+ /**
603
+ * How strong the room's own ambient should be, in {@link SceneEnvironment.iblIntensity}'s
604
+ * units — the magnitude its environment map was calibrated against.
605
+ */
606
+ iblIntensity: number;
607
+ toneMapping: SceneToneMapping;
608
+ /** Scene exposure, in {@link SceneEffects.exposure}'s units. */
609
+ exposure: number;
610
+ bloom: SceneBloom;
611
+ color: SceneColorGrade;
612
+ }
613
+ /**
614
+ * The world the 3D stage sits in: one environment — a map, or a 3D set the avatar
615
+ * stands inside — the scene-global fog, and post-processing.
616
+ *
617
+ * The two asset ids are a single slot the editors write through
618
+ * {@link environmentWithAsset}: at most one is ever set.
578
619
  */
579
620
  export interface SceneEnvironment {
580
621
  /** Equirectangular `.hdr` or image driving the environment lighting, or null. */
581
622
  iblAssetId: string | null;
582
623
  iblIntensity: number;
624
+ /**
625
+ * How much of the environment's diffuse light reaches MToon avatars, 0..1. PBR
626
+ * meshes always take it in full; this is the toon-side dial, since MToon 1.0
627
+ * has no IBL term of its own and the app adds one.
628
+ */
629
+ iblAvatarAmount: number;
583
630
  showSkybox: boolean;
631
+ /** A `prop` asset (glb/gltf) rendered as the set the scene sits in, or null. */
632
+ modelAssetId: string | null;
633
+ /** Where that set sits, in the same units as an object's `place3d`. */
634
+ modelPlace: Place3D;
635
+ /**
636
+ * Bake the set's geometry into the environment lighting. It wins over
637
+ * {@link SceneEnvironment.iblAssetId}, so it only means anything with a set.
638
+ */
639
+ bakeFromModel: boolean;
640
+ /** Whether lights baked into the set render at all. */
641
+ modelLightsEnabled: boolean;
642
+ /** Whether the set's own lights reach avatars. Off means avatars keep only the scene's lights. */
643
+ lightsAffectCharacters: boolean;
644
+ /** Whether the set's own lights reach props. */
645
+ lightsAffectProps: boolean;
646
+ /** Overrides for the set's baked lights, keyed by traversal index — same shape as an object's. */
647
+ modelLightOverrides: Record<string, ObjectLightOverride>;
648
+ /** The set's suggested look, captured when the model was picked; null when it carries none. */
649
+ modelLook: EnvironmentLook | null;
584
650
  fog: SceneFog;
585
651
  effects: SceneEffects;
586
652
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@laplace.live/persona-sdk",
3
- "version": "0.12.0",
3
+ "version": "0.14.0",
4
4
  "description": "TypeScript SDK and wire schema for the LAPLACE Persona plugin API",
5
5
  "license": "MIT",
6
6
  "type": "module",