@plasius/renderer 1.0.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 (79) hide show
  1. package/CHANGELOG.md +59 -0
  2. package/CODE_OF_CONDUCT.md +79 -0
  3. package/CONTRIBUTORS.md +27 -0
  4. package/LICENSE +203 -0
  5. package/README.md +70 -0
  6. package/SECURITY.md +17 -0
  7. package/dist/adaptivedpr.d.ts +2 -0
  8. package/dist/adaptivedpr.d.ts.map +1 -0
  9. package/dist/adaptivedpr.js +65 -0
  10. package/dist/camera/cameraRigProfile.d.ts +12 -0
  11. package/dist/camera/cameraRigProfile.d.ts.map +1 -0
  12. package/dist/camera/cameraRigProfile.js +18 -0
  13. package/dist/camera/managedCameraController.d.ts +49 -0
  14. package/dist/camera/managedCameraController.d.ts.map +1 -0
  15. package/dist/camera/managedCameraController.js +271 -0
  16. package/dist/index.d.ts +4 -0
  17. package/dist/index.d.ts.map +1 -0
  18. package/dist/index.js +3 -0
  19. package/dist/landscape.d.ts +2 -0
  20. package/dist/landscape.d.ts.map +1 -0
  21. package/dist/landscape.js +120 -0
  22. package/dist/player/player.d.ts +8 -0
  23. package/dist/player/player.d.ts.map +1 -0
  24. package/dist/player/player.js +203 -0
  25. package/dist/player/playerstore.d.ts +205 -0
  26. package/dist/player/playerstore.d.ts.map +1 -0
  27. package/dist/player/playerstore.js +500 -0
  28. package/dist/renderStateProvider.d.ts +57 -0
  29. package/dist/renderStateProvider.d.ts.map +1 -0
  30. package/dist/renderStateProvider.js +50 -0
  31. package/dist/renderer.d.ts +9 -0
  32. package/dist/renderer.d.ts.map +1 -0
  33. package/dist/renderer.js +165 -0
  34. package/dist/scene.d.ts +7 -0
  35. package/dist/scene.d.ts.map +1 -0
  36. package/dist/scene.js +10 -0
  37. package/dist/shaders/fragment/landscapeFragmentShader.js +141 -0
  38. package/dist/shaders/landscapeShader.d.ts +13 -0
  39. package/dist/shaders/landscapeShader.d.ts.map +1 -0
  40. package/dist/shaders/landscapeShader.js +25 -0
  41. package/dist/shaders/vertex/landscapeVertexShader.js +67 -0
  42. package/dist/styles/renderer.module.css +90 -0
  43. package/dist/worldSpaceCompositor.d.ts +50 -0
  44. package/dist/worldSpaceCompositor.d.ts.map +1 -0
  45. package/dist/worldSpaceCompositor.js +159 -0
  46. package/dist/xr/rendererXrBridge.d.ts +12 -0
  47. package/dist/xr/rendererXrBridge.d.ts.map +1 -0
  48. package/dist/xr/rendererXrBridge.js +17 -0
  49. package/docs/adrs/adr-0001-renderer-package-scope.md +21 -0
  50. package/docs/adrs/adr-0002-public-repo-governance.md +24 -0
  51. package/docs/adrs/adr-0003-world-space-compositor-contracts.md +34 -0
  52. package/docs/adrs/adr-template.md +35 -0
  53. package/docs/design/0001-public-package-scope.md +18 -0
  54. package/docs/tdrs/index.md +3 -0
  55. package/docs/tdrs/tdr-0001-renderer-public-package-standards-alignment.md +19 -0
  56. package/legal/CLA-REGISTRY.csv +1 -0
  57. package/legal/CLA.md +22 -0
  58. package/legal/CORPORATE_CLA.md +57 -0
  59. package/legal/INDIVIDUAL_CLA.md +91 -0
  60. package/package.json +117 -0
  61. package/src/adaptivedpr.tsx +74 -0
  62. package/src/camera/cameraRigProfile.ts +29 -0
  63. package/src/camera/managedCameraController.tsx +401 -0
  64. package/src/global.d.ts +10 -0
  65. package/src/index.ts +3 -0
  66. package/src/landscape.tsx +321 -0
  67. package/src/player/player.tsx +257 -0
  68. package/src/player/playerstore.tsx +733 -0
  69. package/src/renderStateProvider.tsx +121 -0
  70. package/src/renderer.tsx +294 -0
  71. package/src/scene.tsx +42 -0
  72. package/src/shaders/fragment/landscapeFragmentShader.d.ts +4 -0
  73. package/src/shaders/fragment/landscapeFragmentShader.js +141 -0
  74. package/src/shaders/landscapeShader.tsx +39 -0
  75. package/src/shaders/vertex/landscapeVertexShader.d.ts +4 -0
  76. package/src/shaders/vertex/landscapeVertexShader.js +67 -0
  77. package/src/styles/renderer.module.css +90 -0
  78. package/src/worldSpaceCompositor.ts +265 -0
  79. package/src/xr/rendererXrBridge.ts +44 -0
@@ -0,0 +1,67 @@
1
+ import * as TSL from "three/tsl";
2
+
3
+ // Vertex shader
4
+ export const landscapeVertexShader = TSL.Fn((shader) => {
5
+ const position = shader.input.vec3("position");
6
+ const uv = shader.input.vec2("uv");
7
+ const time = shader.uniforms.float("time");
8
+
9
+ let vHeight = shader.output.float("vHeight");
10
+ const clipPosition = output.vec4("gl_Position");
11
+
12
+ const waveZ = TSL.mul(
13
+ TSL.sin(TSL.add(TSL.mul(position.x, 0.5), TSL.mul(time, 1.0))),
14
+ 1.0
15
+ );
16
+
17
+ const newPos = TSL.vec3(position.x, position.y, TSL.add(position.z, waveZ));
18
+
19
+ TSL.assign(vHeight, newPos.z);
20
+ TSL.assign(clipPosition, TSL.vec4(newPos, 1.0));
21
+ });
22
+ /*
23
+
24
+
25
+ // Vertex shader
26
+ export const landscapeVertexShader = TSL.Fn((shader) => {
27
+ const position = shader.input.vec3("position");
28
+ const uv = shader.input.vec2("uv");
29
+
30
+ const time = shader.uniforms.float("time");
31
+ const seed = shader.uniforms.float("seed");
32
+
33
+ let vUv = shader.output.vec2("vUv");
34
+ let vHeight = shader.output.float("vHeight");
35
+ let vPos = shader.output.vec3("vPos");
36
+
37
+ const clipPosition = output.vec4("gl_Position");
38
+
39
+ const random = (st) =>
40
+ fract(
41
+ mul(
42
+ sin(dot(st, vec2(add(12.9898, seed), add(78.233, seed)))),
43
+ 43758.5453123
44
+ )
45
+ );
46
+
47
+ assign(vUv, uv);
48
+ assign(vPos, position);
49
+
50
+ const rnd = random(position.xy);
51
+ const waveZ = add(
52
+ mul(
53
+ add(
54
+ sin(add(mul(position.x, 0.2), time)),
55
+ cos(add(mul(position.y, 0.2), time))
56
+ ),
57
+ 0.75
58
+ ),
59
+ sub(mul(rnd, 2.0), 1.0)
60
+ );
61
+
62
+ const newPos = vec3(position.x, position.y, add(position.z, waveZ));
63
+
64
+ assign(vHeight, newPos.z);
65
+ assign(clipPosition, vec4(newPos, 1.0));
66
+ });
67
+ */
@@ -0,0 +1,90 @@
1
+ .canvas {
2
+ width: 100%;
3
+ height: 100%;
4
+ min-width: 640px;
5
+ min-height: 480px;
6
+ top: 0px;
7
+ left: 0px;
8
+ display: block;
9
+ z-index: 0;
10
+ }
11
+
12
+ .vrButton {
13
+ top: 67px;
14
+ position: absolute;
15
+ right: 15px;
16
+ z-index: 1000;
17
+ background-color: rgba(225, 18, 222, 0.8);
18
+ border: none;
19
+ border-radius: 5px;
20
+ padding: 10px;
21
+ cursor: pointer;
22
+ transition: background-color 0.3s;
23
+ }
24
+
25
+ .vrButton .fullscreen {
26
+ top: 15px;
27
+ }
28
+
29
+ .vrButton:hover {
30
+ background-color: rgba(200, 10, 200, 0.8);
31
+ border: 2px solid #fff;
32
+ box-shadow: 0 0 10px rgba(255, 255, 255, 0.5);
33
+ }
34
+ .vrButton:active {
35
+ background-color: rgba(175, 5, 175, 0.8);
36
+ }
37
+ .vrButton:disabled {
38
+ background-color: rgba(150, 0, 150, 0.8);
39
+ cursor: not-allowed;
40
+ }
41
+ .vrButton:disabled:hover {
42
+ background-color: rgba(150, 0, 150, 0.8);
43
+ }
44
+ .vrButton:focus {
45
+ outline: none;
46
+ box-shadow: 0 0 0 2px rgba(225, 18, 222, 0.8);
47
+ }
48
+ .vrButton:focus-visible {
49
+ outline: none;
50
+ box-shadow: 0 0 0 2px rgba(225, 18, 222, 0.8);
51
+ }
52
+ .vrButton:focus:not(:focus-visible) {
53
+ box-shadow: none;
54
+ }
55
+
56
+ .performanceContainer {
57
+ position: absolute;
58
+ bottom: 15px;
59
+ left: 15px;
60
+ width: 80px;
61
+ height: 48px;
62
+ z-index: 1000;
63
+ background-color: rgba(220, 220, 220, 0.5);
64
+ padding: 10px;
65
+ border-radius: 5px;
66
+ }
67
+
68
+ .performanceContainer p {
69
+ margin: 0;
70
+ font-size: 14px;
71
+ }
72
+ .performanceContainer span {
73
+ font-weight: bold;
74
+ }
75
+ .performanceContainer .performanceText {
76
+ font-size: 16px;
77
+ color: #202020;
78
+ }
79
+ .performanceContainer .frameRateText {
80
+ font-size: 16px;
81
+ color: #202020;
82
+ }
83
+ /* Custom canvas classes for VR/AR mode switching */
84
+ .fixed {
85
+ position: fixed;
86
+ }
87
+
88
+ .absolute {
89
+ position: absolute;
90
+ }
@@ -0,0 +1,265 @@
1
+ export const WORLD_SPACE_SURFACE_LAYERS = [
2
+ "screen",
3
+ "overlay",
4
+ "alert",
5
+ ] as const;
6
+
7
+ export type WorldSpaceSurfaceLayer =
8
+ (typeof WORLD_SPACE_SURFACE_LAYERS)[number];
9
+
10
+ export const WORLD_SPACE_OCCLUSION_MODES = [
11
+ "world",
12
+ "overlay",
13
+ "always-visible",
14
+ ] as const;
15
+
16
+ export type WorldSpaceOcclusionMode =
17
+ (typeof WORLD_SPACE_OCCLUSION_MODES)[number];
18
+
19
+ export interface WorldSpaceOcclusionPolicy {
20
+ mode: WorldSpaceOcclusionMode;
21
+ depthTest: boolean;
22
+ depthWrite: boolean;
23
+ polygonOffset: boolean;
24
+ polygonOffsetFactor: number;
25
+ polygonOffsetUnits: number;
26
+ renderOrderOffset: number;
27
+ fadeWhenOccluded: boolean;
28
+ }
29
+
30
+ export interface WorldSpaceSurfaceInput {
31
+ id: string;
32
+ slot: string;
33
+ layer: WorldSpaceSurfaceLayer;
34
+ priority?: number;
35
+ occlusionMode?: WorldSpaceOcclusionMode;
36
+ exclusiveSlot?: boolean;
37
+ }
38
+
39
+ export interface ComposedWorldSpaceSurface extends WorldSpaceSurfaceInput {
40
+ exclusiveSlot: boolean;
41
+ occlusionMode: WorldSpaceOcclusionMode;
42
+ occlusionPolicy: WorldSpaceOcclusionPolicy;
43
+ renderOrder: number;
44
+ slotIndex: number;
45
+ }
46
+
47
+ export interface WorldSpaceCompositionCollision {
48
+ slot: string;
49
+ surfaceIds: string[];
50
+ reason: "exclusive-slot-conflict";
51
+ }
52
+
53
+ export interface WorldSpaceCompositionResult {
54
+ surfaces: ComposedWorldSpaceSurface[];
55
+ collisions: WorldSpaceCompositionCollision[];
56
+ }
57
+
58
+ export interface WorldSpaceCompositionOptions {
59
+ slotOrder?: string[];
60
+ startingRenderOrder?: number;
61
+ slotStep?: number;
62
+ priorityStep?: number;
63
+ }
64
+
65
+ const DEFAULT_LAYER_CONFIG: Record<
66
+ WorldSpaceSurfaceLayer,
67
+ {
68
+ baseRenderOrder: number;
69
+ defaultOcclusionMode: WorldSpaceOcclusionMode;
70
+ exclusiveSlot: boolean;
71
+ }
72
+ > = {
73
+ screen: {
74
+ baseRenderOrder: 100,
75
+ defaultOcclusionMode: "world",
76
+ exclusiveSlot: true,
77
+ },
78
+ overlay: {
79
+ baseRenderOrder: 200,
80
+ defaultOcclusionMode: "overlay",
81
+ exclusiveSlot: false,
82
+ },
83
+ alert: {
84
+ baseRenderOrder: 300,
85
+ defaultOcclusionMode: "always-visible",
86
+ exclusiveSlot: false,
87
+ },
88
+ };
89
+
90
+ const OCCLUSION_POLICIES: Record<
91
+ WorldSpaceOcclusionMode,
92
+ WorldSpaceOcclusionPolicy
93
+ > = {
94
+ world: {
95
+ mode: "world",
96
+ depthTest: true,
97
+ depthWrite: false,
98
+ polygonOffset: true,
99
+ polygonOffsetFactor: -1,
100
+ polygonOffsetUnits: -1,
101
+ renderOrderOffset: 0,
102
+ fadeWhenOccluded: false,
103
+ },
104
+ overlay: {
105
+ mode: "overlay",
106
+ depthTest: true,
107
+ depthWrite: false,
108
+ polygonOffset: true,
109
+ polygonOffsetFactor: -2,
110
+ polygonOffsetUnits: -2,
111
+ renderOrderOffset: 10,
112
+ fadeWhenOccluded: true,
113
+ },
114
+ "always-visible": {
115
+ mode: "always-visible",
116
+ depthTest: false,
117
+ depthWrite: false,
118
+ polygonOffset: false,
119
+ polygonOffsetFactor: 0,
120
+ polygonOffsetUnits: 0,
121
+ renderOrderOffset: 20,
122
+ fadeWhenOccluded: false,
123
+ },
124
+ };
125
+
126
+ function normalizePriority(value?: number): number {
127
+ if (!Number.isFinite(value)) {
128
+ return 0;
129
+ }
130
+
131
+ return Math.trunc(value as number);
132
+ }
133
+
134
+ function resolveSlotIndex(
135
+ slot: string,
136
+ options: WorldSpaceCompositionOptions & { slotIndex?: number }
137
+ ): number {
138
+ if (typeof options.slotIndex === "number") {
139
+ return options.slotIndex;
140
+ }
141
+
142
+ const preferredSlotOrder = options.slotOrder ?? [];
143
+ const preferredIndex = preferredSlotOrder.indexOf(slot);
144
+
145
+ if (preferredIndex >= 0) {
146
+ return preferredIndex;
147
+ }
148
+
149
+ return 0;
150
+ }
151
+
152
+ function buildSlotIndexMap(
153
+ surfaces: readonly WorldSpaceSurfaceInput[],
154
+ preferredSlotOrder: readonly string[]
155
+ ): Map<string, number> {
156
+ const slotIndexes = new Map<string, number>();
157
+ let nextIndex = 0;
158
+
159
+ for (const slot of preferredSlotOrder) {
160
+ if (!slotIndexes.has(slot)) {
161
+ slotIndexes.set(slot, nextIndex);
162
+ nextIndex += 1;
163
+ }
164
+ }
165
+
166
+ for (const surface of surfaces) {
167
+ if (!slotIndexes.has(surface.slot)) {
168
+ slotIndexes.set(surface.slot, nextIndex);
169
+ nextIndex += 1;
170
+ }
171
+ }
172
+
173
+ return slotIndexes;
174
+ }
175
+
176
+ export function resolveWorldSpaceOcclusionPolicy(
177
+ mode: WorldSpaceOcclusionMode
178
+ ): WorldSpaceOcclusionPolicy {
179
+ return { ...OCCLUSION_POLICIES[mode] };
180
+ }
181
+
182
+ export function resolveWorldSpaceRenderOrder(
183
+ surface: WorldSpaceSurfaceInput,
184
+ options: WorldSpaceCompositionOptions & { slotIndex?: number } = {}
185
+ ): number {
186
+ const layerConfig = DEFAULT_LAYER_CONFIG[surface.layer];
187
+ const occlusionMode =
188
+ surface.occlusionMode ?? layerConfig.defaultOcclusionMode;
189
+ const policy = OCCLUSION_POLICIES[occlusionMode];
190
+ const slotIndex = resolveSlotIndex(surface.slot, options);
191
+ const startingRenderOrder = options.startingRenderOrder ?? 1000;
192
+ const slotStep = options.slotStep ?? 1000;
193
+ const priorityStep = options.priorityStep ?? 10;
194
+
195
+ return (
196
+ startingRenderOrder +
197
+ slotIndex * slotStep +
198
+ layerConfig.baseRenderOrder +
199
+ normalizePriority(surface.priority) * priorityStep +
200
+ policy.renderOrderOffset
201
+ );
202
+ }
203
+
204
+ export function composeWorldSpaceSurfaces(
205
+ surfaces: readonly WorldSpaceSurfaceInput[],
206
+ options: WorldSpaceCompositionOptions = {}
207
+ ): WorldSpaceCompositionResult {
208
+ const slotIndexes = buildSlotIndexMap(surfaces, options.slotOrder ?? []);
209
+
210
+ const composedSurfaces = surfaces
211
+ .map<ComposedWorldSpaceSurface>((surface) => {
212
+ const layerConfig = DEFAULT_LAYER_CONFIG[surface.layer];
213
+ const occlusionMode =
214
+ surface.occlusionMode ?? layerConfig.defaultOcclusionMode;
215
+ const slotIndex = slotIndexes.get(surface.slot) ?? 0;
216
+
217
+ return {
218
+ ...surface,
219
+ exclusiveSlot: surface.exclusiveSlot ?? layerConfig.exclusiveSlot,
220
+ occlusionMode,
221
+ occlusionPolicy: resolveWorldSpaceOcclusionPolicy(occlusionMode),
222
+ renderOrder: resolveWorldSpaceRenderOrder(surface, {
223
+ ...options,
224
+ slotIndex,
225
+ }),
226
+ slotIndex,
227
+ };
228
+ })
229
+ .sort((left, right) => {
230
+ if (left.renderOrder !== right.renderOrder) {
231
+ return left.renderOrder - right.renderOrder;
232
+ }
233
+
234
+ return left.id.localeCompare(right.id);
235
+ });
236
+
237
+ const exclusiveSurfaceIdsBySlot = new Map<string, string[]>();
238
+
239
+ for (const surface of composedSurfaces) {
240
+ if (!surface.exclusiveSlot) {
241
+ continue;
242
+ }
243
+
244
+ const slotEntries = exclusiveSurfaceIdsBySlot.get(surface.slot) ?? [];
245
+ slotEntries.push(surface.id);
246
+ exclusiveSurfaceIdsBySlot.set(surface.slot, slotEntries);
247
+ }
248
+
249
+ const collisions: WorldSpaceCompositionCollision[] = [];
250
+
251
+ for (const [slot, surfaceIds] of exclusiveSurfaceIdsBySlot) {
252
+ if (surfaceIds.length > 1) {
253
+ collisions.push({
254
+ slot,
255
+ surfaceIds,
256
+ reason: "exclusive-slot-conflict",
257
+ });
258
+ }
259
+ }
260
+
261
+ return {
262
+ surfaces: composedSurfaces,
263
+ collisions,
264
+ };
265
+ }
@@ -0,0 +1,44 @@
1
+ import {
2
+ defaultVrSessionInit,
3
+ mergeXrSessionInit,
4
+ } from "@plasius/gpu-xr";
5
+
6
+ type XrReferenceSpaceType =
7
+ | "viewer"
8
+ | "local"
9
+ | "local-floor"
10
+ | "bounded-floor"
11
+ | "unbounded";
12
+
13
+ export interface XrEnabledRenderer {
14
+ xr?: {
15
+ enabled: boolean;
16
+ setReferenceSpaceType?: (type: XrReferenceSpaceType) => void;
17
+ setSession?: (session: XRSession) => Promise<void> | void;
18
+ };
19
+ }
20
+
21
+ export const rendererVrSessionInit = mergeXrSessionInit(defaultVrSessionInit, {
22
+ requiredFeatures: ["local-floor"],
23
+ optionalFeatures: ["depth-sensing", "hit-test", "hand-tracking", "layers"],
24
+ });
25
+
26
+ export async function bindSessionToRenderer(
27
+ renderer: XrEnabledRenderer | null,
28
+ session: XRSession,
29
+ referenceSpaceType: XrReferenceSpaceType = "local-floor"
30
+ ): Promise<boolean> {
31
+ const xr = renderer?.xr;
32
+ if (!xr) {
33
+ return false;
34
+ }
35
+
36
+ xr.enabled = true;
37
+ xr.setReferenceSpaceType?.(referenceSpaceType);
38
+
39
+ if (typeof xr.setSession === "function") {
40
+ await xr.setSession(session);
41
+ }
42
+
43
+ return true;
44
+ }