@multiplekex/shallot 0.2.3 → 0.2.5

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 (42) hide show
  1. package/package.json +1 -1
  2. package/src/extras/arrows/index.ts +3 -3
  3. package/src/extras/caustic.ts +37 -0
  4. package/src/extras/gradient/index.ts +63 -69
  5. package/src/extras/index.ts +3 -0
  6. package/src/extras/lines/index.ts +3 -3
  7. package/src/extras/skylab/index.ts +314 -0
  8. package/src/extras/text/font.ts +69 -14
  9. package/src/extras/text/index.ts +15 -7
  10. package/src/extras/text/sdf.ts +13 -2
  11. package/src/extras/water.ts +64 -0
  12. package/src/standard/defaults.ts +2 -0
  13. package/src/standard/index.ts +2 -0
  14. package/src/standard/raster/index.ts +517 -0
  15. package/src/standard/{render → raytracing}/bvh/blas.ts +3 -3
  16. package/src/standard/{render → raytracing}/bvh/tlas.ts +3 -0
  17. package/src/standard/{render → raytracing}/depth.ts +9 -9
  18. package/src/standard/raytracing/index.ts +380 -0
  19. package/src/standard/{render → raytracing}/instance.ts +3 -0
  20. package/src/standard/raytracing/shaders.ts +815 -0
  21. package/src/standard/{render → raytracing}/triangle.ts +1 -1
  22. package/src/standard/render/camera.ts +88 -80
  23. package/src/standard/render/index.ts +68 -208
  24. package/src/standard/render/indirect.ts +9 -10
  25. package/src/standard/render/mesh/index.ts +35 -166
  26. package/src/standard/render/overlay.ts +4 -4
  27. package/src/standard/render/pass.ts +1 -1
  28. package/src/standard/render/postprocess.ts +75 -50
  29. package/src/standard/render/scene.ts +28 -16
  30. package/src/standard/render/surface/compile.ts +6 -8
  31. package/src/standard/render/surface/noise.ts +15 -2
  32. package/src/standard/render/surface/shaders.ts +257 -0
  33. package/src/standard/render/surface/structs.ts +13 -6
  34. package/src/standard/render/forward/index.ts +0 -259
  35. package/src/standard/render/forward/raster.ts +0 -228
  36. package/src/standard/render/shaders.ts +0 -484
  37. package/src/standard/render/surface/wgsl.ts +0 -573
  38. /package/src/standard/{render → raytracing}/bvh/radix.ts +0 -0
  39. /package/src/standard/{render → raytracing}/bvh/structs.ts +0 -0
  40. /package/src/standard/{render → raytracing}/bvh/traverse.ts +0 -0
  41. /package/src/standard/{render → raytracing}/intersection.ts +0 -0
  42. /package/src/standard/{render → raytracing}/ray.ts +0 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@multiplekex/shallot",
3
- "version": "0.2.3",
3
+ "version": "0.2.5",
4
4
  "type": "module",
5
5
  "main": "./src/index.ts",
6
6
  "types": "./src/index.ts",
@@ -17,8 +17,8 @@ import {
17
17
  type Draw,
18
18
  type SharedPassContext,
19
19
  } from "../../standard/render";
20
- import { DEPTH_FORMAT } from "../../standard/render/scene";
21
- import { SCENE_STRUCT_WGSL } from "../../standard/render/shaders";
20
+ import { Z_FORMAT } from "../../standard/render/scene";
21
+ import { SCENE_STRUCT_WGSL } from "../../standard/render/surface/structs";
22
22
  import { Transform } from "../../standard/transforms";
23
23
  import { Line, Lines, LinesPlugin } from "../lines";
24
24
 
@@ -198,7 +198,7 @@ export function createArrowsPipeline(
198
198
  topology: "triangle-list",
199
199
  },
200
200
  depthStencil: {
201
- format: DEPTH_FORMAT,
201
+ format: Z_FORMAT,
202
202
  depthCompare: "less",
203
203
  depthWriteEnabled: false,
204
204
  },
@@ -0,0 +1,37 @@
1
+ import { surface } from "../standard/render/surface";
2
+
3
+ export const Caustic = surface({
4
+ fragment: `
5
+ let uv = (*surface).worldPos.xz;
6
+ let t = scene.time * 0.5;
7
+
8
+ // Fine caustic network
9
+ let f1 = sin(uv.x * 4.0 + t * 1.3 + sin(uv.y * 3.0 + t * 0.9) * 0.6);
10
+ let f2 = sin(uv.y * 5.0 - t * 1.1 + sin(uv.x * 3.5 - t * 0.7) * 0.5);
11
+ let f3 = sin((uv.x + uv.y) * 3.5 + t * 0.8);
12
+ let f4 = sin((uv.x - uv.y) * 4.0 - t * 1.2);
13
+ let f5 = sin(uv.x * 6.0 - uv.y * 2.5 + t * 1.5);
14
+ let f6 = sin(uv.y * 6.5 + uv.x * 2.0 - t * 1.0);
15
+
16
+ // Medium detail layer
17
+ let m1 = sin(uv.x * 2.5 + t * 0.7 + sin(uv.y * 1.5 + t * 0.5) * 0.4);
18
+ let m2 = sin(uv.y * 2.8 - t * 0.6 + sin(uv.x * 2.0 - t * 0.4) * 0.3);
19
+
20
+ // Sharp caustic lines using high power
21
+ let c1 = pow(max(0.0, 1.0 - abs(f1)), 12.0);
22
+ let c2 = pow(max(0.0, 1.0 - abs(f2)), 12.0);
23
+ let c3 = pow(max(0.0, 1.0 - abs(f3)), 10.0);
24
+ let c4 = pow(max(0.0, 1.0 - abs(f4)), 10.0);
25
+ let c5 = pow(max(0.0, 1.0 - abs(f5)), 14.0);
26
+ let c6 = pow(max(0.0, 1.0 - abs(f6)), 14.0);
27
+ let c7 = pow(max(0.0, 1.0 - abs(m1)), 8.0);
28
+ let c8 = pow(max(0.0, 1.0 - abs(m2)), 8.0);
29
+
30
+ // Combine layers with variation
31
+ let fine = (c1 + c2 + c3 + c4 + c5 + c6) * 0.12;
32
+ let medium = (c7 + c8) * 0.15;
33
+ let caustic = fine + medium;
34
+
35
+ let light = scene.sunColor.rgb * caustic * 0.3;
36
+ (*surface).baseColor += light;`,
37
+ });
@@ -443,8 +443,8 @@ export interface Gradients {
443
443
  compositeBindGroupLayout: GPUBindGroupLayout | null;
444
444
  compositeUniformBuffer: GPUBuffer | null;
445
445
  gradientAngle: number;
446
- gradientColor1: [number, number, number, number];
447
- gradientColor2: [number, number, number, number];
446
+ gradientColor1: Float32Array;
447
+ gradientColor2: Float32Array;
448
448
  textureSeed: number;
449
449
  textureEnabled: boolean;
450
450
  fineNoise: number;
@@ -453,8 +453,8 @@ export interface Gradients {
453
453
  largeScale: number;
454
454
  fiberIntensity: number;
455
455
  textureOpacity: number;
456
- overlayColor: [number, number, number, number];
457
- bubbleUniformData: Float32Array | null;
456
+ overlayColor: Float32Array;
457
+ bubbleUniformData: Float32Array;
458
458
  bubbleRadius: number;
459
459
  blurRadius: number;
460
460
  bubbleCount: number;
@@ -758,6 +758,8 @@ fn fs(input: VertexOutput) -> @location(0) vec4f {
758
758
  }
759
759
  `;
760
760
 
761
+ const compositeUniformData = new Float32Array(60);
762
+
761
763
  function createCompositeNode(res: Gradients): ComputeNode {
762
764
  return {
763
765
  id: "gradient",
@@ -766,7 +768,6 @@ function createCompositeNode(res: Gradients): ComputeNode {
766
768
 
767
769
  execute(ctx: ExecutionContext) {
768
770
  if (res.width === 0 || res.height === 0) return;
769
- if (!res.bubbleUniformData) return;
770
771
 
771
772
  if (!res.compositeUniformBuffer) {
772
773
  res.compositeUniformBuffer = ctx.device.createBuffer({
@@ -810,49 +811,47 @@ function createCompositeNode(res: Gradients): ComputeNode {
810
811
  });
811
812
  }
812
813
 
813
- const uniformData = new Float32Array(60);
814
-
815
- uniformData[0] = res.overlayColor[0];
816
- uniformData[1] = res.overlayColor[1];
817
- uniformData[2] = res.overlayColor[2];
818
- uniformData[3] = res.overlayColor[3];
819
- uniformData[4] = res.gradientColor1[0];
820
- uniformData[5] = res.gradientColor1[1];
821
- uniformData[6] = res.gradientColor1[2];
822
- uniformData[7] = res.gradientColor1[3];
823
- uniformData[8] = res.gradientColor2[0];
824
- uniformData[9] = res.gradientColor2[1];
825
- uniformData[10] = res.gradientColor2[2];
826
- uniformData[11] = res.gradientColor2[3];
814
+ compositeUniformData[0] = res.overlayColor[0];
815
+ compositeUniformData[1] = res.overlayColor[1];
816
+ compositeUniformData[2] = res.overlayColor[2];
817
+ compositeUniformData[3] = res.overlayColor[3];
818
+ compositeUniformData[4] = res.gradientColor1[0];
819
+ compositeUniformData[5] = res.gradientColor1[1];
820
+ compositeUniformData[6] = res.gradientColor1[2];
821
+ compositeUniformData[7] = res.gradientColor1[3];
822
+ compositeUniformData[8] = res.gradientColor2[0];
823
+ compositeUniformData[9] = res.gradientColor2[1];
824
+ compositeUniformData[10] = res.gradientColor2[2];
825
+ compositeUniformData[11] = res.gradientColor2[3];
827
826
 
828
827
  for (let i = 0; i < 4; i++) {
829
828
  const srcOffset = i * 8;
830
829
  const dstOffset = 12 + i * 8;
831
- uniformData[dstOffset + 0] = res.bubbleUniformData[srcOffset + 0];
832
- uniformData[dstOffset + 1] = res.bubbleUniformData[srcOffset + 1];
833
- uniformData[dstOffset + 4] = res.bubbleUniformData[srcOffset + 4];
834
- uniformData[dstOffset + 5] = res.bubbleUniformData[srcOffset + 5];
835
- uniformData[dstOffset + 6] = res.bubbleUniformData[srcOffset + 6];
836
- uniformData[dstOffset + 7] = res.bubbleUniformData[srcOffset + 7];
830
+ compositeUniformData[dstOffset + 0] = res.bubbleUniformData[srcOffset + 0];
831
+ compositeUniformData[dstOffset + 1] = res.bubbleUniformData[srcOffset + 1];
832
+ compositeUniformData[dstOffset + 4] = res.bubbleUniformData[srcOffset + 4];
833
+ compositeUniformData[dstOffset + 5] = res.bubbleUniformData[srcOffset + 5];
834
+ compositeUniformData[dstOffset + 6] = res.bubbleUniformData[srcOffset + 6];
835
+ compositeUniformData[dstOffset + 7] = res.bubbleUniformData[srcOffset + 7];
837
836
  }
838
837
 
839
- const u32View = new Uint32Array(uniformData.buffer);
838
+ const u32View = new Uint32Array(compositeUniformData.buffer);
840
839
  u32View[44] = res.textureSeed;
841
840
  u32View[45] = res.textureEnabled ? 1 : 0;
842
- uniformData[46] = res.fineNoise;
843
- uniformData[47] = res.mediumNoise;
844
- uniformData[48] = res.coarseNoise;
845
- uniformData[49] = res.largeScale;
846
- uniformData[50] = res.fiberIntensity;
847
- uniformData[51] = res.textureOpacity;
848
- uniformData[52] = res.width;
849
- uniformData[53] = res.height;
850
- uniformData[54] = res.gradientAngle;
851
- uniformData[55] = res.bubbleRadius;
852
- uniformData[56] = res.blurRadius;
841
+ compositeUniformData[46] = res.fineNoise;
842
+ compositeUniformData[47] = res.mediumNoise;
843
+ compositeUniformData[48] = res.coarseNoise;
844
+ compositeUniformData[49] = res.largeScale;
845
+ compositeUniformData[50] = res.fiberIntensity;
846
+ compositeUniformData[51] = res.textureOpacity;
847
+ compositeUniformData[52] = res.width;
848
+ compositeUniformData[53] = res.height;
849
+ compositeUniformData[54] = res.gradientAngle;
850
+ compositeUniformData[55] = res.bubbleRadius;
851
+ compositeUniformData[56] = res.blurRadius;
853
852
  u32View[57] = res.bubbleCount;
854
853
 
855
- ctx.queue.writeBuffer(res.compositeUniformBuffer, 0, uniformData);
854
+ ctx.queue.writeBuffer(res.compositeUniformBuffer, 0, compositeUniformData);
856
855
 
857
856
  const pass = ctx.encoder.beginRenderPass({
858
857
  colorAttachments: [
@@ -931,34 +930,28 @@ export const GradientSystem: System = {
931
930
  const color1 = parseColor(gradientColor1);
932
931
  const color2 = parseColor(gradientColor2);
933
932
  res.gradientAngle = angle;
934
- res.gradientColor1 = [
935
- color1[0] / 255,
936
- color1[1] / 255,
937
- color1[2] / 255,
938
- color1[3] / 255,
939
- ];
940
- res.gradientColor2 = [
941
- color2[0] / 255,
942
- color2[1] / 255,
943
- color2[2] / 255,
944
- color2[3] / 255,
945
- ];
933
+ res.gradientColor1[0] = color1[0] / 255;
934
+ res.gradientColor1[1] = color1[1] / 255;
935
+ res.gradientColor1[2] = color1[2] / 255;
936
+ res.gradientColor1[3] = color1[3] / 255;
937
+ res.gradientColor2[0] = color2[0] / 255;
938
+ res.gradientColor2[1] = color2[1] / 255;
939
+ res.gradientColor2[2] = color2[2] / 255;
940
+ res.gradientColor2[3] = color2[3] / 255;
946
941
 
947
942
  const bubbleSize = Gradient.bubbleSize[eid] ?? DEFAULT_BUBBLE_CONFIG.size;
948
943
  const bubbleBlur = Gradient.bubbleBlur[eid] ?? DEFAULT_BUBBLE_CONFIG.blur;
949
944
 
950
- const bubbleUniformData = new Float32Array(4 * 8);
951
945
  for (let i = 0; i < b.length; i++) {
952
946
  const offset = i * 8;
953
- bubbleUniformData[offset + 0] = b[i].x / 100;
954
- bubbleUniformData[offset + 1] = b[i].y / 100;
947
+ res.bubbleUniformData[offset + 0] = b[i].x / 100;
948
+ res.bubbleUniformData[offset + 1] = b[i].y / 100;
955
949
  const color = parseColor(b[i].color);
956
- bubbleUniformData[offset + 4] = color[0] / 255;
957
- bubbleUniformData[offset + 5] = color[1] / 255;
958
- bubbleUniformData[offset + 6] = color[2] / 255;
959
- bubbleUniformData[offset + 7] = color[3] / 255;
950
+ res.bubbleUniformData[offset + 4] = color[0] / 255;
951
+ res.bubbleUniformData[offset + 5] = color[1] / 255;
952
+ res.bubbleUniformData[offset + 6] = color[2] / 255;
953
+ res.bubbleUniformData[offset + 7] = color[3] / 255;
960
954
  }
961
- res.bubbleUniformData = bubbleUniformData;
962
955
  res.bubbleRadius = (bubbleSize / 100) * Math.min(res.width, res.height);
963
956
  res.blurRadius = (bubbleBlur / 100) * Math.min(res.width, res.height);
964
957
 
@@ -979,14 +972,15 @@ export const GradientSystem: System = {
979
972
  const overlayColor = parseColor(
980
973
  Gradient.overlayColor[eid] || DEFAULT_OVERLAY_CONFIG.color
981
974
  );
982
- res.overlayColor = [
983
- overlayColor[0] / 255,
984
- overlayColor[1] / 255,
985
- overlayColor[2] / 255,
986
- overlayColor[3] / 255,
987
- ];
975
+ res.overlayColor[0] = overlayColor[0] / 255;
976
+ res.overlayColor[1] = overlayColor[1] / 255;
977
+ res.overlayColor[2] = overlayColor[2] / 255;
978
+ res.overlayColor[3] = overlayColor[3] / 255;
988
979
  } else {
989
- res.overlayColor = [0, 0, 0, 0];
980
+ res.overlayColor[0] = 0;
981
+ res.overlayColor[1] = 0;
982
+ res.overlayColor[2] = 0;
983
+ res.overlayColor[3] = 0;
990
984
  }
991
985
  }
992
986
  },
@@ -1007,8 +1001,8 @@ export const GradientPlugin: Plugin = {
1007
1001
  compositeBindGroupLayout: null,
1008
1002
  compositeUniformBuffer: null,
1009
1003
  gradientAngle: 0,
1010
- gradientColor1: [0, 0, 0, 1],
1011
- gradientColor2: [0, 0, 0, 1],
1004
+ gradientColor1: new Float32Array([0, 0, 0, 1]),
1005
+ gradientColor2: new Float32Array([0, 0, 0, 1]),
1012
1006
  textureSeed: 0,
1013
1007
  textureEnabled: true,
1014
1008
  fineNoise: DEFAULT_TEXTURE_CONFIG.fineNoise,
@@ -1017,8 +1011,8 @@ export const GradientPlugin: Plugin = {
1017
1011
  largeScale: DEFAULT_TEXTURE_CONFIG.largeScale,
1018
1012
  fiberIntensity: DEFAULT_TEXTURE_CONFIG.fiberIntensity,
1019
1013
  textureOpacity: DEFAULT_TEXTURE_CONFIG.opacity,
1020
- overlayColor: [0, 0, 0, 0],
1021
- bubbleUniformData: null,
1014
+ overlayColor: new Float32Array(4),
1015
+ bubbleUniformData: new Float32Array(4 * 8),
1022
1016
  bubbleRadius: 0,
1023
1017
  blurRadius: 0,
1024
1018
  bubbleCount: 4,
@@ -3,3 +3,6 @@ export * from "./lines";
3
3
  export * from "./arrows";
4
4
  export * from "./text";
5
5
  export * from "./gradient";
6
+ export * from "./skylab";
7
+ export * from "./caustic";
8
+ export * from "./water";
@@ -17,8 +17,8 @@ import {
17
17
  type Draw,
18
18
  type SharedPassContext,
19
19
  } from "../../standard/render";
20
- import { DEPTH_FORMAT } from "../../standard/render/scene";
21
- import { SCENE_STRUCT_WGSL } from "../../standard/render/shaders";
20
+ import { Z_FORMAT } from "../../standard/render/scene";
21
+ import { SCENE_STRUCT_WGSL } from "../../standard/render/surface/structs";
22
22
  import { Transform } from "../../standard/transforms";
23
23
 
24
24
  export const LineData = {
@@ -240,7 +240,7 @@ export function createLinesPipeline(
240
240
  topology: "triangle-list",
241
241
  },
242
242
  depthStencil: {
243
- format: DEPTH_FORMAT,
243
+ format: Z_FORMAT,
244
244
  depthCompare: "less",
245
245
  depthWriteEnabled: false,
246
246
  },
@@ -0,0 +1,314 @@
1
+ import type { Plugin, State, System } from "../../core";
2
+ import { setTraits } from "../../core/component";
3
+ import {
4
+ Camera,
5
+ Sky,
6
+ Sun,
7
+ Stars,
8
+ Moon,
9
+ Haze,
10
+ Clouds,
11
+ AmbientLight,
12
+ DirectionalLight,
13
+ } from "../../standard/render";
14
+
15
+ export const Skylab = {
16
+ azimuth: [] as number[],
17
+ elevation: [] as number[],
18
+ };
19
+ setTraits(Skylab, { defaults: () => ({ azimuth: 37, elevation: 45 }) });
20
+
21
+ interface ColorRGB {
22
+ r: number;
23
+ g: number;
24
+ b: number;
25
+ }
26
+
27
+ interface GradientStop {
28
+ elevation: number;
29
+ sunColor: ColorRGB;
30
+ sunIntensity: number;
31
+ ambientColor: ColorRGB;
32
+ ambientIntensity: number;
33
+ zenith: ColorRGB;
34
+ horizon: ColorRGB;
35
+ sunGlow: number;
36
+ starsIntensity: number;
37
+ moonGlow: number;
38
+ moonElevationOffset: number;
39
+ hazeDensity: number;
40
+ hazeColor: ColorRGB;
41
+ cloudsColor: ColorRGB;
42
+ }
43
+
44
+ const STOPS: GradientStop[] = [
45
+ {
46
+ elevation: -90,
47
+ sunColor: { r: 0.25, g: 0.3, b: 0.55 },
48
+ sunIntensity: 0.15,
49
+ ambientColor: { r: 0.3, g: 0.25, b: 0.55 },
50
+ ambientIntensity: 0.5,
51
+ zenith: { r: 0.03, g: 0.02, b: 0.1 },
52
+ horizon: { r: 0.05, g: 0.06, b: 0.12 },
53
+ sunGlow: 0,
54
+ starsIntensity: 1.0,
55
+ moonGlow: 0.4,
56
+ moonElevationOffset: 180,
57
+ hazeDensity: 0.001,
58
+ hazeColor: { r: 0.05, g: 0.04, b: 0.12 },
59
+ cloudsColor: { r: 0.1, g: 0.1, b: 0.18 },
60
+ },
61
+ {
62
+ elevation: -6,
63
+ sunColor: { r: 0.6, g: 0.2, b: 0.3 },
64
+ sunIntensity: 0.1,
65
+ ambientColor: { r: 0.25, g: 0.18, b: 0.45 },
66
+ ambientIntensity: 0.5,
67
+ zenith: { r: 0.05, g: 0.02, b: 0.15 },
68
+ horizon: { r: 0.4, g: 0.15, b: 0.25 },
69
+ sunGlow: 0,
70
+ starsIntensity: 0.6,
71
+ moonGlow: 0.35,
72
+ moonElevationOffset: 160,
73
+ hazeDensity: 0.003,
74
+ hazeColor: { r: 0.25, g: 0.1, b: 0.2 },
75
+ cloudsColor: { r: 0.3, g: 0.12, b: 0.2 },
76
+ },
77
+ {
78
+ elevation: 0,
79
+ sunColor: { r: 1.0, g: 0.45, b: 0.15 },
80
+ sunIntensity: 0.4,
81
+ ambientColor: { r: 0.35, g: 0.2, b: 0.15 },
82
+ ambientIntensity: 0.5,
83
+ zenith: { r: 0.1, g: 0.08, b: 0.3 },
84
+ horizon: { r: 0.9, g: 0.4, b: 0.15 },
85
+ sunGlow: 1.2,
86
+ starsIntensity: 0.1,
87
+ moonGlow: 0,
88
+ moonElevationOffset: 140,
89
+ hazeDensity: 0.008,
90
+ hazeColor: { r: 0.8, g: 0.4, b: 0.15 },
91
+ cloudsColor: { r: 0.95, g: 0.5, b: 0.2 },
92
+ },
93
+ {
94
+ elevation: 8,
95
+ sunColor: { r: 1.0, g: 0.7, b: 0.4 },
96
+ sunIntensity: 0.7,
97
+ ambientColor: { r: 0.5, g: 0.4, b: 0.3 },
98
+ ambientIntensity: 0.7,
99
+ zenith: { r: 0.15, g: 0.25, b: 0.55 },
100
+ horizon: { r: 0.85, g: 0.6, b: 0.3 },
101
+ sunGlow: 0.8,
102
+ starsIntensity: 0,
103
+ moonGlow: 0,
104
+ moonElevationOffset: 120,
105
+ hazeDensity: 0.006,
106
+ hazeColor: { r: 0.7, g: 0.55, b: 0.35 },
107
+ cloudsColor: { r: 0.95, g: 0.75, b: 0.5 },
108
+ },
109
+ {
110
+ elevation: 20,
111
+ sunColor: { r: 1.0, g: 1.0, b: 1.0 },
112
+ sunIntensity: 0.75,
113
+ ambientColor: { r: 0.55, g: 0.52, b: 0.5 },
114
+ ambientIntensity: 1.0,
115
+ zenith: { r: 0.25, g: 0.48, b: 0.82 },
116
+ horizon: { r: 0.3, g: 0.6, b: 0.85 },
117
+ sunGlow: 0.5,
118
+ starsIntensity: 0,
119
+ moonGlow: 0,
120
+ moonElevationOffset: 90,
121
+ hazeDensity: 0.005,
122
+ hazeColor: { r: 0.3, g: 0.5, b: 0.82 },
123
+ cloudsColor: { r: 1.0, g: 1.0, b: 1.0 },
124
+ },
125
+ {
126
+ elevation: 50,
127
+ sunColor: { r: 1.0, g: 1.0, b: 1.0 },
128
+ sunIntensity: 0.81,
129
+ ambientColor: { r: 0.53, g: 0.536, b: 0.54 },
130
+ ambientIntensity: 1.0,
131
+ zenith: { r: 0.25, g: 0.47, b: 0.815 },
132
+ horizon: { r: 0.24, g: 0.595, b: 0.845 },
133
+ sunGlow: 0.4,
134
+ starsIntensity: 0,
135
+ moonGlow: 0,
136
+ moonElevationOffset: 60,
137
+ hazeDensity: 0.005,
138
+ hazeColor: { r: 0.24, g: 0.465, b: 0.815 },
139
+ cloudsColor: { r: 1.0, g: 1.0, b: 1.0 },
140
+ },
141
+ ];
142
+
143
+ function lerpColor(a: ColorRGB, b: ColorRGB, t: number): ColorRGB {
144
+ return {
145
+ r: a.r + (b.r - a.r) * t,
146
+ g: a.g + (b.g - a.g) * t,
147
+ b: a.b + (b.b - a.b) * t,
148
+ };
149
+ }
150
+
151
+ function lerp(a: number, b: number, t: number): number {
152
+ return a + (b - a) * t;
153
+ }
154
+
155
+ function packColor(c: ColorRGB): number {
156
+ return (
157
+ (Math.round(Math.min(1, Math.max(0, c.r)) * 255) << 16) |
158
+ (Math.round(Math.min(1, Math.max(0, c.g)) * 255) << 8) |
159
+ Math.round(Math.min(1, Math.max(0, c.b)) * 255)
160
+ );
161
+ }
162
+
163
+ export interface SkylabOutput {
164
+ sunColor: number;
165
+ sunIntensity: number;
166
+ ambientColor: number;
167
+ ambientIntensity: number;
168
+ zenith: number;
169
+ horizon: number;
170
+ sunGlow: number;
171
+ starsIntensity: number;
172
+ moonGlow: number;
173
+ moonAzimuth: number;
174
+ moonElevation: number;
175
+ hazeDensity: number;
176
+ hazeColor: number;
177
+ cloudsColor: number;
178
+ }
179
+
180
+ export function sampleGradient(elevationDegrees: number): SkylabOutput {
181
+ const el = Math.max(-90, Math.min(90, elevationDegrees));
182
+
183
+ let lo = 0;
184
+ let hi = STOPS.length - 1;
185
+ for (let i = 0; i < STOPS.length - 1; i++) {
186
+ if (el >= STOPS[i].elevation && el <= STOPS[i + 1].elevation) {
187
+ lo = i;
188
+ hi = i + 1;
189
+ break;
190
+ }
191
+ }
192
+
193
+ if (el <= STOPS[0].elevation) {
194
+ lo = 0;
195
+ hi = 0;
196
+ } else if (el >= STOPS[STOPS.length - 1].elevation) {
197
+ lo = STOPS.length - 1;
198
+ hi = STOPS.length - 1;
199
+ }
200
+
201
+ const a = STOPS[lo];
202
+ const b = STOPS[hi];
203
+ const range = b.elevation - a.elevation;
204
+ const t = range > 0 ? (el - a.elevation) / range : 0;
205
+
206
+ return {
207
+ sunColor: packColor(lerpColor(a.sunColor, b.sunColor, t)),
208
+ sunIntensity: lerp(a.sunIntensity, b.sunIntensity, t),
209
+ ambientColor: packColor(lerpColor(a.ambientColor, b.ambientColor, t)),
210
+ ambientIntensity: lerp(a.ambientIntensity, b.ambientIntensity, t),
211
+ zenith: packColor(lerpColor(a.zenith, b.zenith, t)),
212
+ horizon: packColor(lerpColor(a.horizon, b.horizon, t)),
213
+ sunGlow: lerp(a.sunGlow, b.sunGlow, t),
214
+ starsIntensity: lerp(a.starsIntensity, b.starsIntensity, t),
215
+ moonGlow: lerp(a.moonGlow, b.moonGlow, t),
216
+ moonAzimuth: 0,
217
+ moonElevation: lerp(a.moonElevationOffset, b.moonElevationOffset, t),
218
+ hazeDensity: lerp(a.hazeDensity, b.hazeDensity, t),
219
+ hazeColor: packColor(lerpColor(a.hazeColor, b.hazeColor, t)),
220
+ cloudsColor: packColor(lerpColor(a.cloudsColor, b.cloudsColor, t)),
221
+ };
222
+ }
223
+
224
+ export function directionToElevation(dx: number, dy: number, dz: number): number {
225
+ const len = Math.sqrt(dx * dx + dy * dy + dz * dz);
226
+ if (len < 0.0001) return 90;
227
+ return Math.asin(Math.min(1, Math.max(-1, -dy / len))) * (180 / Math.PI);
228
+ }
229
+
230
+ export function directionToAzimuth(dx: number, dy: number, dz: number): number {
231
+ const len = Math.sqrt(dx * dx + dy * dy + dz * dz);
232
+ if (len < 0.0001) return 0;
233
+ return (Math.atan2(-dx / len, -dz / len) * (180 / Math.PI) + 360) % 360;
234
+ }
235
+
236
+ export function toDirection(azimuthDeg: number, elevationDeg: number): [number, number, number] {
237
+ const az = (azimuthDeg * Math.PI) / 180;
238
+ const el = (elevationDeg * Math.PI) / 180;
239
+ const cosEl = Math.cos(el);
240
+ return [-Math.sin(az) * cosEl, -Math.sin(el), -Math.cos(az) * cosEl];
241
+ }
242
+
243
+ function moonDirection(sunAzimuth: number, sunElevation: number): [number, number, number] {
244
+ const moonAz = (sunAzimuth + 180) % 360;
245
+ return toDirection(moonAz, Math.abs(sunElevation));
246
+ }
247
+
248
+ function lightDirection(azimuth: number, elevation: number): [number, number, number] {
249
+ if (elevation >= 0) return toDirection(azimuth, elevation);
250
+ if (elevation <= -6) return moonDirection(azimuth, elevation);
251
+ const t = -elevation / 6;
252
+ const [sx, sy, sz] = toDirection(azimuth, elevation);
253
+ const [mx, my, mz] = moonDirection(azimuth, elevation);
254
+ return [sx + (mx - sx) * t, sy + (my - sy) * t, sz + (mz - sz) * t];
255
+ }
256
+
257
+ const SkylabSystem: System = {
258
+ group: "simulation",
259
+
260
+ update(state: State) {
261
+ for (const eid of state.query([Camera, Skylab])) {
262
+ if (!Camera.active[eid]) continue;
263
+
264
+ const azimuth = Skylab.azimuth[eid];
265
+ const elevation = Skylab.elevation[eid];
266
+ const [dirX, dirY, dirZ] = lightDirection(azimuth, elevation);
267
+ const output = sampleGradient(elevation);
268
+
269
+ for (const lightEid of state.query([DirectionalLight])) {
270
+ DirectionalLight.directionX[lightEid] = dirX;
271
+ DirectionalLight.directionY[lightEid] = dirY;
272
+ DirectionalLight.directionZ[lightEid] = dirZ;
273
+ DirectionalLight.color[lightEid] = output.sunColor;
274
+ DirectionalLight.intensity[lightEid] = output.sunIntensity;
275
+ break;
276
+ }
277
+
278
+ for (const lightEid of state.query([AmbientLight])) {
279
+ AmbientLight.color[lightEid] = output.ambientColor;
280
+ AmbientLight.intensity[lightEid] = output.ambientIntensity;
281
+ break;
282
+ }
283
+
284
+ if (!state.hasComponent(eid, Sky)) state.addComponent(eid, Sky);
285
+ Sky.zenith[eid] = output.zenith;
286
+ Sky.horizon[eid] = output.horizon;
287
+
288
+ if (!state.hasComponent(eid, Sun)) state.addComponent(eid, Sun);
289
+ Sun.glow[eid] = output.sunGlow;
290
+
291
+ if (!state.hasComponent(eid, Stars)) state.addComponent(eid, Stars);
292
+ Stars.intensity[eid] = output.starsIntensity;
293
+
294
+ if (!state.hasComponent(eid, Moon)) state.addComponent(eid, Moon);
295
+ Moon.glow[eid] = output.moonGlow;
296
+ Moon.azimuth[eid] = (azimuth + 180) % 360;
297
+ Moon.elevation[eid] = Math.abs(elevation);
298
+
299
+ if (!state.hasComponent(eid, Haze)) state.addComponent(eid, Haze);
300
+ Haze.density[eid] = output.hazeDensity;
301
+ Haze.color[eid] = output.hazeColor;
302
+
303
+ if (!state.hasComponent(eid, Clouds)) state.addComponent(eid, Clouds);
304
+ Clouds.color[eid] = output.cloudsColor;
305
+
306
+ break;
307
+ }
308
+ },
309
+ };
310
+
311
+ export const SkylabPlugin: Plugin = {
312
+ systems: [SkylabSystem],
313
+ components: { Skylab },
314
+ };