@scenography/plugin-sky 0.2.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/dist/data-BY6HfW4z.mjs +21 -0
- package/dist/data-BY6HfW4z.mjs.map +1 -0
- package/dist/data-DM9PlHyL.cjs +32 -0
- package/dist/data-DM9PlHyL.cjs.map +1 -0
- package/dist/data.cjs +4 -0
- package/dist/data.d.cts +2 -0
- package/dist/data.d.mts +2 -0
- package/dist/data.mjs +2 -0
- package/dist/index-GwVhqXni.d.cts +37 -0
- package/dist/index-GwVhqXni.d.mts +37 -0
- package/dist/index.cjs +226 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +58 -0
- package/dist/index.d.mts +58 -0
- package/dist/index.mjs +222 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +52 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { isFiniteNumber, isNonNegativeNumber, isNumberInRange, propIs, validate } from "@scenography/scene";
|
|
2
|
+
//#region src/data/validate.ts
|
|
3
|
+
const validateSky = (sky) => validate([
|
|
4
|
+
propIs("time", isNumberInRange(0, 24), "time must be between 0 and 24."),
|
|
5
|
+
propIs("azimuth", isFiniteNumber, "azimuth must be a finite number."),
|
|
6
|
+
propIs("turbidity", isNonNegativeNumber, "turbidity must be a non-negative number."),
|
|
7
|
+
propIs("rayleigh", isNonNegativeNumber, "rayleigh must be a non-negative number."),
|
|
8
|
+
propIs("fog", isNonNegativeNumber, "fog must be a non-negative number.")
|
|
9
|
+
])(sky);
|
|
10
|
+
//#endregion
|
|
11
|
+
//#region src/data/plugin.ts
|
|
12
|
+
/** The data half of the sky plugin: owns the `sky` scene key (a singleton). */
|
|
13
|
+
const skyData = {
|
|
14
|
+
kind: "sky",
|
|
15
|
+
cardinality: "single",
|
|
16
|
+
validate: (sky) => validateSky(sky)
|
|
17
|
+
};
|
|
18
|
+
//#endregion
|
|
19
|
+
export { validateSky as n, skyData as t };
|
|
20
|
+
|
|
21
|
+
//# sourceMappingURL=data-BY6HfW4z.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"data-BY6HfW4z.mjs","names":[],"sources":["../src/data/validate.ts","../src/data/plugin.ts"],"sourcesContent":["import {\n isFiniteNumber,\n isNonNegativeNumber,\n isNumberInRange,\n propIs,\n validate,\n type ValidationResult,\n} from \"@scenography/scene\";\nimport type { Sky } from \"./sky\";\n\nexport const validateSky = (sky: Sky): ValidationResult =>\n validate<Sky>([\n propIs(\"time\", isNumberInRange(0, 24), \"time must be between 0 and 24.\"),\n propIs(\"azimuth\", isFiniteNumber, \"azimuth must be a finite number.\"),\n propIs(\"turbidity\", isNonNegativeNumber, \"turbidity must be a non-negative number.\"),\n propIs(\"rayleigh\", isNonNegativeNumber, \"rayleigh must be a non-negative number.\"),\n propIs(\"fog\", isNonNegativeNumber, \"fog must be a non-negative number.\"),\n ])(sky);\n","import type { DataPlugin } from \"@scenography/scene\";\n\nimport { validateSky } from \"./validate\";\nimport type { Sky } from \"./sky\";\n\n/** The data half of the sky plugin: owns the `sky` scene key (a singleton). */\nexport const skyData: DataPlugin<Sky> = {\n kind: \"sky\",\n cardinality: \"single\",\n validate: (sky) => validateSky(sky),\n};\n"],"mappings":";;AAUA,MAAa,eAAe,QAC1B,SAAc;CACZ,OAAO,QAAQ,gBAAgB,GAAG,EAAE,GAAG,gCAAgC;CACvE,OAAO,WAAW,gBAAgB,kCAAkC;CACpE,OAAO,aAAa,qBAAqB,0CAA0C;CACnF,OAAO,YAAY,qBAAqB,yCAAyC;CACjF,OAAO,OAAO,qBAAqB,oCAAoC;AACzE,CAAC,CAAC,CAAC,GAAG;;;;ACXR,MAAa,UAA2B;CACtC,MAAM;CACN,aAAa;CACb,WAAW,QAAQ,YAAY,GAAG;AACpC"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
let _scenography_scene = require("@scenography/scene");
|
|
2
|
+
//#region src/data/validate.ts
|
|
3
|
+
const validateSky = (sky) => (0, _scenography_scene.validate)([
|
|
4
|
+
(0, _scenography_scene.propIs)("time", (0, _scenography_scene.isNumberInRange)(0, 24), "time must be between 0 and 24."),
|
|
5
|
+
(0, _scenography_scene.propIs)("azimuth", _scenography_scene.isFiniteNumber, "azimuth must be a finite number."),
|
|
6
|
+
(0, _scenography_scene.propIs)("turbidity", _scenography_scene.isNonNegativeNumber, "turbidity must be a non-negative number."),
|
|
7
|
+
(0, _scenography_scene.propIs)("rayleigh", _scenography_scene.isNonNegativeNumber, "rayleigh must be a non-negative number."),
|
|
8
|
+
(0, _scenography_scene.propIs)("fog", _scenography_scene.isNonNegativeNumber, "fog must be a non-negative number.")
|
|
9
|
+
])(sky);
|
|
10
|
+
//#endregion
|
|
11
|
+
//#region src/data/plugin.ts
|
|
12
|
+
/** The data half of the sky plugin: owns the `sky` scene key (a singleton). */
|
|
13
|
+
const skyData = {
|
|
14
|
+
kind: "sky",
|
|
15
|
+
cardinality: "single",
|
|
16
|
+
validate: (sky) => validateSky(sky)
|
|
17
|
+
};
|
|
18
|
+
//#endregion
|
|
19
|
+
Object.defineProperty(exports, "skyData", {
|
|
20
|
+
enumerable: true,
|
|
21
|
+
get: function() {
|
|
22
|
+
return skyData;
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
Object.defineProperty(exports, "validateSky", {
|
|
26
|
+
enumerable: true,
|
|
27
|
+
get: function() {
|
|
28
|
+
return validateSky;
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
//# sourceMappingURL=data-DM9PlHyL.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"data-DM9PlHyL.cjs","names":["isFiniteNumber","isNonNegativeNumber"],"sources":["../src/data/validate.ts","../src/data/plugin.ts"],"sourcesContent":["import {\n isFiniteNumber,\n isNonNegativeNumber,\n isNumberInRange,\n propIs,\n validate,\n type ValidationResult,\n} from \"@scenography/scene\";\nimport type { Sky } from \"./sky\";\n\nexport const validateSky = (sky: Sky): ValidationResult =>\n validate<Sky>([\n propIs(\"time\", isNumberInRange(0, 24), \"time must be between 0 and 24.\"),\n propIs(\"azimuth\", isFiniteNumber, \"azimuth must be a finite number.\"),\n propIs(\"turbidity\", isNonNegativeNumber, \"turbidity must be a non-negative number.\"),\n propIs(\"rayleigh\", isNonNegativeNumber, \"rayleigh must be a non-negative number.\"),\n propIs(\"fog\", isNonNegativeNumber, \"fog must be a non-negative number.\"),\n ])(sky);\n","import type { DataPlugin } from \"@scenography/scene\";\n\nimport { validateSky } from \"./validate\";\nimport type { Sky } from \"./sky\";\n\n/** The data half of the sky plugin: owns the `sky` scene key (a singleton). */\nexport const skyData: DataPlugin<Sky> = {\n kind: \"sky\",\n cardinality: \"single\",\n validate: (sky) => validateSky(sky),\n};\n"],"mappings":";;AAUA,MAAa,eAAe,SAAA,GAAA,mBAAA,SAAA,CACZ;gCACL,SAAA,GAAA,mBAAA,gBAAA,CAAwB,GAAG,EAAE,GAAG,gCAAgC;gCAChE,WAAWA,mBAAAA,gBAAgB,kCAAkC;gCAC7D,aAAaC,mBAAAA,qBAAqB,0CAA0C;gCAC5E,YAAYA,mBAAAA,qBAAqB,yCAAyC;gCAC1E,OAAOA,mBAAAA,qBAAqB,oCAAoC;AACzE,CAAC,CAAC,CAAC,GAAG;;;;ACXR,MAAa,UAA2B;CACtC,MAAM;CACN,aAAa;CACb,WAAW,QAAQ,YAAY,GAAG;AACpC"}
|
package/dist/data.cjs
ADDED
package/dist/data.d.cts
ADDED
package/dist/data.d.mts
ADDED
package/dist/data.mjs
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { DataPlugin, ValidationResult } from "@scenography/scene";
|
|
2
|
+
|
|
3
|
+
//#region src/data/sky.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* A procedural sky driven by the time of day. The runtime renders an atmospheric
|
|
6
|
+
* sky dome, moves a sun light along its daily arc, and fades in stars at night —
|
|
7
|
+
* all from `time`. Always clear (no weather). Distinct from `Environment` (a
|
|
8
|
+
* static HDRI): a scene uses one or the other for its background.
|
|
9
|
+
*/
|
|
10
|
+
interface Sky {
|
|
11
|
+
/** Hour of day, `0`..`24` (`6` sunrise, `12` noon, `18` sunset, `0`/`24` midnight). Default `12`. */
|
|
12
|
+
time?: number;
|
|
13
|
+
/** Compass offset (degrees) that rotates the sun's daily arc — orient the scene to the sun. Default `0`. */
|
|
14
|
+
azimuth?: number;
|
|
15
|
+
/** Atmospheric haze; higher is hazier/whiter. Default `4`. */
|
|
16
|
+
turbidity?: number;
|
|
17
|
+
/** Rayleigh scattering; higher is a deeper blue. Default `2`. */
|
|
18
|
+
rayleigh?: number;
|
|
19
|
+
/** Exponential fog density tinted to the sky (fades the horizon). `0` = none. Default `0`. */
|
|
20
|
+
fog?: number;
|
|
21
|
+
}
|
|
22
|
+
declare module "@scenography/scene" {
|
|
23
|
+
interface Scene {
|
|
24
|
+
/** Procedural time-of-day sky — provided by `@scenography/plugin-sky`. */
|
|
25
|
+
sky?: Sky;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
//#endregion
|
|
29
|
+
//#region src/data/validate.d.ts
|
|
30
|
+
declare const validateSky: (sky: Sky) => ValidationResult;
|
|
31
|
+
//#endregion
|
|
32
|
+
//#region src/data/plugin.d.ts
|
|
33
|
+
/** The data half of the sky plugin: owns the `sky` scene key (a singleton). */
|
|
34
|
+
declare const skyData: DataPlugin<Sky>;
|
|
35
|
+
//#endregion
|
|
36
|
+
export { validateSky as n, Sky as r, skyData as t };
|
|
37
|
+
//# sourceMappingURL=index-GwVhqXni.d.cts.map
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { DataPlugin, ValidationResult } from "@scenography/scene";
|
|
2
|
+
|
|
3
|
+
//#region src/data/sky.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* A procedural sky driven by the time of day. The runtime renders an atmospheric
|
|
6
|
+
* sky dome, moves a sun light along its daily arc, and fades in stars at night —
|
|
7
|
+
* all from `time`. Always clear (no weather). Distinct from `Environment` (a
|
|
8
|
+
* static HDRI): a scene uses one or the other for its background.
|
|
9
|
+
*/
|
|
10
|
+
interface Sky {
|
|
11
|
+
/** Hour of day, `0`..`24` (`6` sunrise, `12` noon, `18` sunset, `0`/`24` midnight). Default `12`. */
|
|
12
|
+
time?: number;
|
|
13
|
+
/** Compass offset (degrees) that rotates the sun's daily arc — orient the scene to the sun. Default `0`. */
|
|
14
|
+
azimuth?: number;
|
|
15
|
+
/** Atmospheric haze; higher is hazier/whiter. Default `4`. */
|
|
16
|
+
turbidity?: number;
|
|
17
|
+
/** Rayleigh scattering; higher is a deeper blue. Default `2`. */
|
|
18
|
+
rayleigh?: number;
|
|
19
|
+
/** Exponential fog density tinted to the sky (fades the horizon). `0` = none. Default `0`. */
|
|
20
|
+
fog?: number;
|
|
21
|
+
}
|
|
22
|
+
declare module "@scenography/scene" {
|
|
23
|
+
interface Scene {
|
|
24
|
+
/** Procedural time-of-day sky — provided by `@scenography/plugin-sky`. */
|
|
25
|
+
sky?: Sky;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
//#endregion
|
|
29
|
+
//#region src/data/validate.d.ts
|
|
30
|
+
declare const validateSky: (sky: Sky) => ValidationResult;
|
|
31
|
+
//#endregion
|
|
32
|
+
//#region src/data/plugin.d.ts
|
|
33
|
+
/** The data half of the sky plugin: owns the `sky` scene key (a singleton). */
|
|
34
|
+
declare const skyData: DataPlugin<Sky>;
|
|
35
|
+
//#endregion
|
|
36
|
+
export { validateSky as n, Sky as r, skyData as t };
|
|
37
|
+
//# sourceMappingURL=index-GwVhqXni.d.mts.map
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
const require_data = require("./data-DM9PlHyL.cjs");
|
|
3
|
+
let three = require("three");
|
|
4
|
+
//#region src/SkyController.ts
|
|
5
|
+
const clamp01 = (x) => Math.min(Math.max(x, 0), 1);
|
|
6
|
+
const lerp = (a, b, t) => a + (b - a) * t;
|
|
7
|
+
const DOME = 4500;
|
|
8
|
+
const STAR_COUNT = 1500;
|
|
9
|
+
const SUN_DISTANCE = 2e3;
|
|
10
|
+
const MAX_ELEVATION = 60;
|
|
11
|
+
const ENV_THROTTLE_MS = 150;
|
|
12
|
+
const SUNSET = new three.Color("#ff7a3c");
|
|
13
|
+
const DAYLIGHT = new three.Color("#fff4e6");
|
|
14
|
+
const NIGHT_FOG = new three.Color("#0a1222");
|
|
15
|
+
const DAY_FOG = new three.Color("#bcd3e0");
|
|
16
|
+
/**
|
|
17
|
+
* Owns the procedural time-of-day sky: an atmospheric dome (three's `Sky`
|
|
18
|
+
* addon), a sun `DirectionalLight` riding its daily arc, a hemisphere fill, and a
|
|
19
|
+
* star field that fades in at night. `setTime(hour)` drives everything; the
|
|
20
|
+
* dome loads on demand (own chunk) and catches up to the latest time.
|
|
21
|
+
*/
|
|
22
|
+
var SkyController = class {
|
|
23
|
+
scene;
|
|
24
|
+
sun = new three.DirectionalLight(16777215, 0);
|
|
25
|
+
hemi = new three.HemisphereLight(8961023, 5917238, 0);
|
|
26
|
+
stars;
|
|
27
|
+
dome = null;
|
|
28
|
+
def = null;
|
|
29
|
+
time = 12;
|
|
30
|
+
azimuth = 0;
|
|
31
|
+
hash = null;
|
|
32
|
+
renderer;
|
|
33
|
+
pmrem;
|
|
34
|
+
envRT = null;
|
|
35
|
+
envDirty = false;
|
|
36
|
+
envTimer = 0;
|
|
37
|
+
constructor(scene, renderer) {
|
|
38
|
+
this.scene = scene;
|
|
39
|
+
this.renderer = renderer;
|
|
40
|
+
this.pmrem = new three.PMREMGenerator(renderer);
|
|
41
|
+
this.sun.castShadow = true;
|
|
42
|
+
this.sun.shadow.mapSize.set(2048, 2048);
|
|
43
|
+
const cam = this.sun.shadow.camera;
|
|
44
|
+
cam.left = cam.bottom = -1500;
|
|
45
|
+
cam.right = cam.top = 1500;
|
|
46
|
+
cam.near = 1;
|
|
47
|
+
cam.far = SUN_DISTANCE * 2;
|
|
48
|
+
this.stars = this.buildStars();
|
|
49
|
+
}
|
|
50
|
+
reconcile(def) {
|
|
51
|
+
const hash = def ? JSON.stringify(def) : null;
|
|
52
|
+
if (hash === this.hash) return;
|
|
53
|
+
this.hash = hash;
|
|
54
|
+
if (!def) {
|
|
55
|
+
this.clear();
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
if (!this.def) {
|
|
59
|
+
this.scene.add(this.sun, this.sun.target, this.hemi, this.stars);
|
|
60
|
+
this.ensureDome();
|
|
61
|
+
}
|
|
62
|
+
this.def = def;
|
|
63
|
+
this.azimuth = def.azimuth ?? 0;
|
|
64
|
+
this.applyAtmosphere();
|
|
65
|
+
this.setTime(def.time ?? 12);
|
|
66
|
+
}
|
|
67
|
+
/** Set the hour of day (0..24) and update sun, sky, fill and stars live. */
|
|
68
|
+
setTime(hour) {
|
|
69
|
+
this.time = hour;
|
|
70
|
+
const elevation = Math.sin((hour - 6) / 12 * Math.PI) * MAX_ELEVATION;
|
|
71
|
+
const azimuth = 90 + (hour - 6) / 12 * 180 + this.azimuth;
|
|
72
|
+
const dir = new three.Vector3().setFromSphericalCoords(1, three.MathUtils.degToRad(90 - elevation), three.MathUtils.degToRad(azimuth));
|
|
73
|
+
if (this.dome) this.dome.material.uniforms.sunPosition.value.copy(dir);
|
|
74
|
+
this.sun.position.copy(dir).multiplyScalar(SUN_DISTANCE);
|
|
75
|
+
const day = clamp01(elevation / 6);
|
|
76
|
+
this.sun.intensity = day * 3;
|
|
77
|
+
this.sun.color.copy(SUNSET).lerp(DAYLIGHT, clamp01(elevation / 20));
|
|
78
|
+
this.hemi.intensity = lerp(.04, .6, clamp01((elevation + 6) / 12));
|
|
79
|
+
const night = clamp01(-elevation / 6);
|
|
80
|
+
const starMat = this.stars.material;
|
|
81
|
+
starMat.opacity = night;
|
|
82
|
+
this.stars.visible = night > 0;
|
|
83
|
+
const fogDensity = this.def?.fog ?? 0;
|
|
84
|
+
if (fogDensity > 0) {
|
|
85
|
+
const fog = this.scene.fog instanceof three.FogExp2 ? this.scene.fog : new three.FogExp2(0, fogDensity);
|
|
86
|
+
fog.density = fogDensity;
|
|
87
|
+
fog.color.copy(NIGHT_FOG).lerp(DAY_FOG, clamp01((elevation + 4) / 16));
|
|
88
|
+
const lowSun = clamp01(1 - elevation / 12) * clamp01(elevation / 3 + .4);
|
|
89
|
+
fog.color.lerp(SUNSET, lowSun * .4);
|
|
90
|
+
this.scene.fog = fog;
|
|
91
|
+
} else if (this.scene.fog instanceof three.FogExp2) this.scene.fog = null;
|
|
92
|
+
this.envDirty = true;
|
|
93
|
+
}
|
|
94
|
+
/** Current hour of day. */
|
|
95
|
+
getTime() {
|
|
96
|
+
return this.time;
|
|
97
|
+
}
|
|
98
|
+
/** Set the sun's compass offset (degrees) and re-place the sun. */
|
|
99
|
+
setAzimuth(degrees) {
|
|
100
|
+
this.azimuth = degrees;
|
|
101
|
+
this.setTime(this.time);
|
|
102
|
+
}
|
|
103
|
+
/** Current sun compass offset (degrees). */
|
|
104
|
+
getAzimuth() {
|
|
105
|
+
return this.azimuth;
|
|
106
|
+
}
|
|
107
|
+
/** Advance throttled work (IBL regeneration). Call once per frame. */
|
|
108
|
+
update(deltaMs) {
|
|
109
|
+
if (!this.envDirty || !this.dome) return;
|
|
110
|
+
this.envTimer += deltaMs;
|
|
111
|
+
if (this.envTimer < ENV_THROTTLE_MS) return;
|
|
112
|
+
this.envTimer = 0;
|
|
113
|
+
this.envDirty = false;
|
|
114
|
+
this.regenerateEnvironment();
|
|
115
|
+
}
|
|
116
|
+
/** Render just the dome to a PMREM and use it as the scene's IBL. */
|
|
117
|
+
regenerateEnvironment() {
|
|
118
|
+
if (!this.dome) return;
|
|
119
|
+
this.scene.remove(this.dome);
|
|
120
|
+
const envScene = new three.Scene();
|
|
121
|
+
envScene.add(this.dome);
|
|
122
|
+
const rt = this.pmrem.fromScene(envScene);
|
|
123
|
+
envScene.remove(this.dome);
|
|
124
|
+
this.scene.add(this.dome);
|
|
125
|
+
this.envRT?.dispose();
|
|
126
|
+
this.envRT = rt;
|
|
127
|
+
this.scene.environment = rt.texture;
|
|
128
|
+
}
|
|
129
|
+
applyAtmosphere() {
|
|
130
|
+
if (!this.dome || !this.def) return;
|
|
131
|
+
const u = this.dome.material.uniforms;
|
|
132
|
+
u.turbidity.value = this.def.turbidity ?? 4;
|
|
133
|
+
u.rayleigh.value = this.def.rayleigh ?? 2;
|
|
134
|
+
u.mieCoefficient.value = .005;
|
|
135
|
+
u.mieDirectionalG.value = .8;
|
|
136
|
+
}
|
|
137
|
+
ensureDome() {
|
|
138
|
+
if (this.dome) return;
|
|
139
|
+
import("three/examples/jsm/objects/Sky.js").then(({ Sky }) => {
|
|
140
|
+
if (!this.def) return;
|
|
141
|
+
const dome = new Sky();
|
|
142
|
+
dome.scale.setScalar(DOME);
|
|
143
|
+
this.dome = dome;
|
|
144
|
+
this.scene.add(dome);
|
|
145
|
+
this.applyAtmosphere();
|
|
146
|
+
this.setTime(this.time);
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
buildStars() {
|
|
150
|
+
const positions = new Float32Array(STAR_COUNT * 3);
|
|
151
|
+
for (let i = 0; i < STAR_COUNT; i++) {
|
|
152
|
+
const theta = Math.random() * Math.PI * 2;
|
|
153
|
+
const phi = Math.acos(Math.random());
|
|
154
|
+
const r = DOME * .9;
|
|
155
|
+
positions[i * 3] = r * Math.sin(phi) * Math.cos(theta);
|
|
156
|
+
positions[i * 3 + 1] = r * Math.cos(phi);
|
|
157
|
+
positions[i * 3 + 2] = r * Math.sin(phi) * Math.sin(theta);
|
|
158
|
+
}
|
|
159
|
+
const geometry = new three.BufferGeometry();
|
|
160
|
+
geometry.setAttribute("position", new three.Float32BufferAttribute(positions, 3));
|
|
161
|
+
const stars = new three.Points(geometry, new three.PointsMaterial({
|
|
162
|
+
color: 16777215,
|
|
163
|
+
size: 12,
|
|
164
|
+
sizeAttenuation: true,
|
|
165
|
+
transparent: true,
|
|
166
|
+
opacity: 0,
|
|
167
|
+
depthWrite: false
|
|
168
|
+
}));
|
|
169
|
+
stars.visible = false;
|
|
170
|
+
return stars;
|
|
171
|
+
}
|
|
172
|
+
clear() {
|
|
173
|
+
if (!this.def) return;
|
|
174
|
+
this.scene.remove(this.sun, this.sun.target, this.hemi, this.stars);
|
|
175
|
+
if (this.dome) {
|
|
176
|
+
this.scene.remove(this.dome);
|
|
177
|
+
this.dome = null;
|
|
178
|
+
}
|
|
179
|
+
if (this.scene.fog instanceof three.FogExp2) this.scene.fog = null;
|
|
180
|
+
this.scene.environment = null;
|
|
181
|
+
this.envRT?.dispose();
|
|
182
|
+
this.envRT = null;
|
|
183
|
+
this.envDirty = false;
|
|
184
|
+
this.def = null;
|
|
185
|
+
}
|
|
186
|
+
dispose() {
|
|
187
|
+
this.clear();
|
|
188
|
+
this.stars.geometry.dispose();
|
|
189
|
+
this.stars.material.dispose();
|
|
190
|
+
this.pmrem.dispose();
|
|
191
|
+
this.hash = null;
|
|
192
|
+
}
|
|
193
|
+
};
|
|
194
|
+
//#endregion
|
|
195
|
+
//#region src/plugin.ts
|
|
196
|
+
/**
|
|
197
|
+
* Procedural time-of-day sky — a *system* plugin. It renders an atmospheric sky
|
|
198
|
+
* dome, moves a sun light along its arc, fades in stars at night and drives the
|
|
199
|
+
* image-based lighting, all from `scene.sky`. Exposes `ctx.sky` (setTime/…). It
|
|
200
|
+
* manages its own objects, so it takes the scene graph and renderer (for IBL).
|
|
201
|
+
*/
|
|
202
|
+
const sky = () => ({
|
|
203
|
+
name: "sky",
|
|
204
|
+
setup: (ctx) => {
|
|
205
|
+
const controller = new SkyController(ctx.scene3d, ctx.renderer);
|
|
206
|
+
controller.reconcile(ctx.scene.sky);
|
|
207
|
+
return {
|
|
208
|
+
update: (deltaMs) => controller.update(deltaMs),
|
|
209
|
+
setScene: (scene) => controller.reconcile(scene.sky),
|
|
210
|
+
dispose: () => controller.dispose(),
|
|
211
|
+
api: {
|
|
212
|
+
setTime: (hour) => controller.setTime(hour),
|
|
213
|
+
getTime: () => controller.getTime(),
|
|
214
|
+
setAzimuth: (degrees) => controller.setAzimuth(degrees),
|
|
215
|
+
getAzimuth: () => controller.getAzimuth()
|
|
216
|
+
}
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
});
|
|
220
|
+
//#endregion
|
|
221
|
+
exports.SkyController = SkyController;
|
|
222
|
+
exports.sky = sky;
|
|
223
|
+
exports.skyData = require_data.skyData;
|
|
224
|
+
exports.validateSky = require_data.validateSky;
|
|
225
|
+
|
|
226
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.cjs","names":["Color","DirectionalLight","HemisphereLight","PMREMGenerator","Vector3","MathUtils","FogExp2","Scene","BufferGeometry","Float32BufferAttribute","Points","PointsMaterial"],"sources":["../src/SkyController.ts","../src/plugin.ts"],"sourcesContent":["import {\n BufferGeometry,\n Color,\n DirectionalLight,\n Float32BufferAttribute,\n FogExp2,\n HemisphereLight,\n MathUtils,\n PMREMGenerator,\n Points,\n PointsMaterial,\n Scene,\n Vector3,\n type Object3D,\n type Scene as ThreeScene,\n type WebGLRenderer,\n type WebGLRenderTarget,\n} from \"three\";\nimport type { Sky as SkyDef } from \"./data\";\n\nconst clamp01 = (x: number): number => Math.min(Math.max(x, 0), 1);\nconst lerp = (a: number, b: number, t: number): number => a + (b - a) * t;\n\n// Sky dome radius (within the camera far plane) and star shell radius.\nconst DOME = 4500;\nconst STAR_COUNT = 1500;\nconst SUN_DISTANCE = 2000;\nconst MAX_ELEVATION = 60; // noon sun angle (less than zenith for longer shadows)\nconst ENV_THROTTLE_MS = 150; // min gap between IBL regenerations\n\nconst SUNSET = new Color(\"#ff7a3c\");\nconst DAYLIGHT = new Color(\"#fff4e6\");\nconst NIGHT_FOG = new Color(\"#0a1222\");\nconst DAY_FOG = new Color(\"#bcd3e0\");\n\n/** Minimal shape of the Sky addon's material uniforms we touch. */\ninterface SkyMaterial {\n uniforms: {\n turbidity: { value: number };\n rayleigh: { value: number };\n mieCoefficient: { value: number };\n mieDirectionalG: { value: number };\n sunPosition: { value: Vector3 };\n };\n}\n\n/**\n * Owns the procedural time-of-day sky: an atmospheric dome (three's `Sky`\n * addon), a sun `DirectionalLight` riding its daily arc, a hemisphere fill, and a\n * star field that fades in at night. `setTime(hour)` drives everything; the\n * dome loads on demand (own chunk) and catches up to the latest time.\n */\nexport class SkyController {\n private readonly scene: ThreeScene;\n private readonly sun = new DirectionalLight(0xffffff, 0);\n private readonly hemi = new HemisphereLight(0x88bbff, 0x5a4a36, 0);\n private readonly stars: Points;\n private dome: (Object3D & { material: SkyMaterial }) | null = null;\n private def: SkyDef | null = null;\n private time = 12;\n private azimuth = 0;\n private hash: string | null = null;\n\n // Image-based lighting: a PMREM of the sky dome, regenerated (throttled) when\n // the sky changes, so the water reflects the sky and PBR picks up its color.\n private readonly renderer: WebGLRenderer;\n private readonly pmrem: PMREMGenerator;\n private envRT: WebGLRenderTarget | null = null;\n private envDirty = false;\n private envTimer = 0;\n\n constructor(scene: ThreeScene, renderer: WebGLRenderer) {\n this.scene = scene;\n this.renderer = renderer;\n this.pmrem = new PMREMGenerator(renderer);\n this.sun.castShadow = true;\n this.sun.shadow.mapSize.set(2048, 2048);\n const cam = this.sun.shadow.camera;\n cam.left = cam.bottom = -1500;\n cam.right = cam.top = 1500;\n cam.near = 1;\n cam.far = SUN_DISTANCE * 2;\n this.stars = this.buildStars();\n }\n\n reconcile(def: SkyDef | undefined): void {\n const hash = def ? JSON.stringify(def) : null;\n if (hash === this.hash) return;\n this.hash = hash;\n\n if (!def) {\n this.clear();\n return;\n }\n if (!this.def) {\n this.scene.add(this.sun, this.sun.target, this.hemi, this.stars);\n this.ensureDome();\n }\n this.def = def;\n this.azimuth = def.azimuth ?? 0;\n this.applyAtmosphere();\n this.setTime(def.time ?? 12);\n }\n\n /** Set the hour of day (0..24) and update sun, sky, fill and stars live. */\n setTime(hour: number): void {\n this.time = hour;\n\n // Sun along its arc: elevation peaks at noon, dips below at night.\n const elevation = Math.sin(((hour - 6) / 12) * Math.PI) * MAX_ELEVATION;\n const azimuth = 90 + ((hour - 6) / 12) * 180 + this.azimuth;\n const dir = new Vector3().setFromSphericalCoords(\n 1,\n MathUtils.degToRad(90 - elevation),\n MathUtils.degToRad(azimuth),\n );\n\n if (this.dome) this.dome.material.uniforms.sunPosition.value.copy(dir);\n this.sun.position.copy(dir).multiplyScalar(SUN_DISTANCE);\n\n // Intensity/colour: bright white high up, dim warm at the horizon, off below.\n const day = clamp01(elevation / 6);\n this.sun.intensity = day * 3;\n this.sun.color.copy(SUNSET).lerp(DAYLIGHT, clamp01(elevation / 20));\n this.hemi.intensity = lerp(0.04, 0.6, clamp01((elevation + 6) / 12));\n\n // Stars fade in once the sun is below the horizon.\n const night = clamp01(-elevation / 6);\n const starMat = this.stars.material as PointsMaterial;\n starMat.opacity = night;\n this.stars.visible = night > 0;\n\n // Fog tinted to the sky, fading the horizon (off when density is 0).\n const fogDensity = this.def?.fog ?? 0;\n if (fogDensity > 0) {\n const fog = this.scene.fog instanceof FogExp2 ? this.scene.fog : new FogExp2(0x000000, fogDensity);\n fog.density = fogDensity;\n fog.color.copy(NIGHT_FOG).lerp(DAY_FOG, clamp01((elevation + 4) / 16));\n const lowSun = clamp01(1 - elevation / 12) * clamp01(elevation / 3 + 0.4);\n fog.color.lerp(SUNSET, lowSun * 0.4);\n this.scene.fog = fog;\n } else if (this.scene.fog instanceof FogExp2) {\n this.scene.fog = null;\n }\n\n // The sky changed; refresh the IBL (throttled in update()).\n this.envDirty = true;\n }\n\n /** Current hour of day. */\n getTime(): number {\n return this.time;\n }\n\n /** Set the sun's compass offset (degrees) and re-place the sun. */\n setAzimuth(degrees: number): void {\n this.azimuth = degrees;\n this.setTime(this.time);\n }\n\n /** Current sun compass offset (degrees). */\n getAzimuth(): number {\n return this.azimuth;\n }\n\n /** Advance throttled work (IBL regeneration). Call once per frame. */\n update(deltaMs: number): void {\n if (!this.envDirty || !this.dome) return;\n this.envTimer += deltaMs;\n if (this.envTimer < ENV_THROTTLE_MS) return;\n this.envTimer = 0;\n this.envDirty = false;\n this.regenerateEnvironment();\n }\n\n /** Render just the dome to a PMREM and use it as the scene's IBL. */\n private regenerateEnvironment(): void {\n if (!this.dome) return;\n this.scene.remove(this.dome);\n const envScene = new Scene();\n envScene.add(this.dome);\n const rt = this.pmrem.fromScene(envScene);\n envScene.remove(this.dome);\n this.scene.add(this.dome);\n this.envRT?.dispose();\n this.envRT = rt;\n this.scene.environment = rt.texture;\n }\n\n private applyAtmosphere(): void {\n if (!this.dome || !this.def) return;\n const u = this.dome.material.uniforms;\n u.turbidity.value = this.def.turbidity ?? 4;\n u.rayleigh.value = this.def.rayleigh ?? 2;\n u.mieCoefficient.value = 0.005;\n u.mieDirectionalG.value = 0.8;\n }\n\n private ensureDome(): void {\n if (this.dome) return;\n void import(\"three/examples/jsm/objects/Sky.js\").then(({ Sky }) => {\n if (!this.def) return; // reconciled away before it loaded\n const dome = new Sky() as unknown as Object3D & { material: SkyMaterial };\n dome.scale.setScalar(DOME);\n this.dome = dome;\n this.scene.add(dome);\n this.applyAtmosphere();\n this.setTime(this.time);\n });\n }\n\n private buildStars(): Points {\n const positions = new Float32Array(STAR_COUNT * 3);\n for (let i = 0; i < STAR_COUNT; i++) {\n // Upper hemisphere shell.\n const theta = Math.random() * Math.PI * 2;\n const phi = Math.acos(Math.random()); // 0..90° from zenith\n const r = DOME * 0.9;\n positions[i * 3] = r * Math.sin(phi) * Math.cos(theta);\n positions[i * 3 + 1] = r * Math.cos(phi);\n positions[i * 3 + 2] = r * Math.sin(phi) * Math.sin(theta);\n }\n const geometry = new BufferGeometry();\n geometry.setAttribute(\"position\", new Float32BufferAttribute(positions, 3));\n const material = new PointsMaterial({\n color: 0xffffff,\n size: 12,\n sizeAttenuation: true,\n transparent: true,\n opacity: 0,\n depthWrite: false,\n });\n const stars = new Points(geometry, material);\n stars.visible = false;\n return stars;\n }\n\n private clear(): void {\n if (!this.def) return;\n this.scene.remove(this.sun, this.sun.target, this.hemi, this.stars);\n if (this.dome) {\n this.scene.remove(this.dome);\n this.dome = null;\n }\n if (this.scene.fog instanceof FogExp2) this.scene.fog = null;\n this.scene.environment = null;\n this.envRT?.dispose();\n this.envRT = null;\n this.envDirty = false;\n this.def = null;\n }\n\n dispose(): void {\n this.clear();\n this.stars.geometry.dispose();\n (this.stars.material as PointsMaterial).dispose();\n this.pmrem.dispose();\n this.hash = null;\n }\n}\n","import type { SkyApi, SystemPlugin } from \"@scenography/engine\";\n\nimport { SkyController } from \"./SkyController\";\n\n/**\n * Procedural time-of-day sky — a *system* plugin. It renders an atmospheric sky\n * dome, moves a sun light along its arc, fades in stars at night and drives the\n * image-based lighting, all from `scene.sky`. Exposes `ctx.sky` (setTime/…). It\n * manages its own objects, so it takes the scene graph and renderer (for IBL).\n */\nexport const sky = (): SystemPlugin<SkyApi> => ({\n name: \"sky\",\n setup: (ctx) => {\n const controller = new SkyController(ctx.scene3d, ctx.renderer);\n controller.reconcile(ctx.scene.sky);\n return {\n update: (deltaMs) => controller.update(deltaMs),\n setScene: (scene) => controller.reconcile(scene.sky),\n dispose: () => controller.dispose(),\n api: {\n setTime: (hour) => controller.setTime(hour),\n getTime: () => controller.getTime(),\n setAzimuth: (degrees) => controller.setAzimuth(degrees),\n getAzimuth: () => controller.getAzimuth(),\n },\n };\n },\n});\n"],"mappings":"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"}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { n as validateSky, r as Sky, t as skyData } from "./index-GwVhqXni.cjs";
|
|
2
|
+
import { SkyApi, SystemPlugin } from "@scenography/engine";
|
|
3
|
+
import { Scene, WebGLRenderer } from "three";
|
|
4
|
+
|
|
5
|
+
//#region src/plugin.d.ts
|
|
6
|
+
/**
|
|
7
|
+
* Procedural time-of-day sky — a *system* plugin. It renders an atmospheric sky
|
|
8
|
+
* dome, moves a sun light along its arc, fades in stars at night and drives the
|
|
9
|
+
* image-based lighting, all from `scene.sky`. Exposes `ctx.sky` (setTime/…). It
|
|
10
|
+
* manages its own objects, so it takes the scene graph and renderer (for IBL).
|
|
11
|
+
*/
|
|
12
|
+
declare const sky: () => SystemPlugin<SkyApi>;
|
|
13
|
+
//#endregion
|
|
14
|
+
//#region src/SkyController.d.ts
|
|
15
|
+
/**
|
|
16
|
+
* Owns the procedural time-of-day sky: an atmospheric dome (three's `Sky`
|
|
17
|
+
* addon), a sun `DirectionalLight` riding its daily arc, a hemisphere fill, and a
|
|
18
|
+
* star field that fades in at night. `setTime(hour)` drives everything; the
|
|
19
|
+
* dome loads on demand (own chunk) and catches up to the latest time.
|
|
20
|
+
*/
|
|
21
|
+
declare class SkyController {
|
|
22
|
+
private readonly scene;
|
|
23
|
+
private readonly sun;
|
|
24
|
+
private readonly hemi;
|
|
25
|
+
private readonly stars;
|
|
26
|
+
private dome;
|
|
27
|
+
private def;
|
|
28
|
+
private time;
|
|
29
|
+
private azimuth;
|
|
30
|
+
private hash;
|
|
31
|
+
private readonly renderer;
|
|
32
|
+
private readonly pmrem;
|
|
33
|
+
private envRT;
|
|
34
|
+
private envDirty;
|
|
35
|
+
private envTimer;
|
|
36
|
+
constructor(scene: Scene, renderer: WebGLRenderer);
|
|
37
|
+
reconcile(def: Sky | undefined): void;
|
|
38
|
+
/** Set the hour of day (0..24) and update sun, sky, fill and stars live. */
|
|
39
|
+
setTime(hour: number): void;
|
|
40
|
+
/** Current hour of day. */
|
|
41
|
+
getTime(): number;
|
|
42
|
+
/** Set the sun's compass offset (degrees) and re-place the sun. */
|
|
43
|
+
setAzimuth(degrees: number): void;
|
|
44
|
+
/** Current sun compass offset (degrees). */
|
|
45
|
+
getAzimuth(): number;
|
|
46
|
+
/** Advance throttled work (IBL regeneration). Call once per frame. */
|
|
47
|
+
update(deltaMs: number): void;
|
|
48
|
+
/** Render just the dome to a PMREM and use it as the scene's IBL. */
|
|
49
|
+
private regenerateEnvironment;
|
|
50
|
+
private applyAtmosphere;
|
|
51
|
+
private ensureDome;
|
|
52
|
+
private buildStars;
|
|
53
|
+
private clear;
|
|
54
|
+
dispose(): void;
|
|
55
|
+
}
|
|
56
|
+
//#endregion
|
|
57
|
+
export { type Sky, SkyController, sky, skyData, validateSky };
|
|
58
|
+
//# sourceMappingURL=index.d.cts.map
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { n as validateSky, r as Sky, t as skyData } from "./index-GwVhqXni.mjs";
|
|
2
|
+
import { Scene, WebGLRenderer } from "three";
|
|
3
|
+
import { SkyApi, SystemPlugin } from "@scenography/engine";
|
|
4
|
+
|
|
5
|
+
//#region src/plugin.d.ts
|
|
6
|
+
/**
|
|
7
|
+
* Procedural time-of-day sky — a *system* plugin. It renders an atmospheric sky
|
|
8
|
+
* dome, moves a sun light along its arc, fades in stars at night and drives the
|
|
9
|
+
* image-based lighting, all from `scene.sky`. Exposes `ctx.sky` (setTime/…). It
|
|
10
|
+
* manages its own objects, so it takes the scene graph and renderer (for IBL).
|
|
11
|
+
*/
|
|
12
|
+
declare const sky: () => SystemPlugin<SkyApi>;
|
|
13
|
+
//#endregion
|
|
14
|
+
//#region src/SkyController.d.ts
|
|
15
|
+
/**
|
|
16
|
+
* Owns the procedural time-of-day sky: an atmospheric dome (three's `Sky`
|
|
17
|
+
* addon), a sun `DirectionalLight` riding its daily arc, a hemisphere fill, and a
|
|
18
|
+
* star field that fades in at night. `setTime(hour)` drives everything; the
|
|
19
|
+
* dome loads on demand (own chunk) and catches up to the latest time.
|
|
20
|
+
*/
|
|
21
|
+
declare class SkyController {
|
|
22
|
+
private readonly scene;
|
|
23
|
+
private readonly sun;
|
|
24
|
+
private readonly hemi;
|
|
25
|
+
private readonly stars;
|
|
26
|
+
private dome;
|
|
27
|
+
private def;
|
|
28
|
+
private time;
|
|
29
|
+
private azimuth;
|
|
30
|
+
private hash;
|
|
31
|
+
private readonly renderer;
|
|
32
|
+
private readonly pmrem;
|
|
33
|
+
private envRT;
|
|
34
|
+
private envDirty;
|
|
35
|
+
private envTimer;
|
|
36
|
+
constructor(scene: Scene, renderer: WebGLRenderer);
|
|
37
|
+
reconcile(def: Sky | undefined): void;
|
|
38
|
+
/** Set the hour of day (0..24) and update sun, sky, fill and stars live. */
|
|
39
|
+
setTime(hour: number): void;
|
|
40
|
+
/** Current hour of day. */
|
|
41
|
+
getTime(): number;
|
|
42
|
+
/** Set the sun's compass offset (degrees) and re-place the sun. */
|
|
43
|
+
setAzimuth(degrees: number): void;
|
|
44
|
+
/** Current sun compass offset (degrees). */
|
|
45
|
+
getAzimuth(): number;
|
|
46
|
+
/** Advance throttled work (IBL regeneration). Call once per frame. */
|
|
47
|
+
update(deltaMs: number): void;
|
|
48
|
+
/** Render just the dome to a PMREM and use it as the scene's IBL. */
|
|
49
|
+
private regenerateEnvironment;
|
|
50
|
+
private applyAtmosphere;
|
|
51
|
+
private ensureDome;
|
|
52
|
+
private buildStars;
|
|
53
|
+
private clear;
|
|
54
|
+
dispose(): void;
|
|
55
|
+
}
|
|
56
|
+
//#endregion
|
|
57
|
+
export { type Sky, SkyController, sky, skyData, validateSky };
|
|
58
|
+
//# sourceMappingURL=index.d.mts.map
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
import { n as validateSky, t as skyData } from "./data-BY6HfW4z.mjs";
|
|
2
|
+
import { BufferGeometry, Color, DirectionalLight, Float32BufferAttribute, FogExp2, HemisphereLight, MathUtils, PMREMGenerator, Points, PointsMaterial, Scene, Vector3 } from "three";
|
|
3
|
+
//#region src/SkyController.ts
|
|
4
|
+
const clamp01 = (x) => Math.min(Math.max(x, 0), 1);
|
|
5
|
+
const lerp = (a, b, t) => a + (b - a) * t;
|
|
6
|
+
const DOME = 4500;
|
|
7
|
+
const STAR_COUNT = 1500;
|
|
8
|
+
const SUN_DISTANCE = 2e3;
|
|
9
|
+
const MAX_ELEVATION = 60;
|
|
10
|
+
const ENV_THROTTLE_MS = 150;
|
|
11
|
+
const SUNSET = new Color("#ff7a3c");
|
|
12
|
+
const DAYLIGHT = new Color("#fff4e6");
|
|
13
|
+
const NIGHT_FOG = new Color("#0a1222");
|
|
14
|
+
const DAY_FOG = new Color("#bcd3e0");
|
|
15
|
+
/**
|
|
16
|
+
* Owns the procedural time-of-day sky: an atmospheric dome (three's `Sky`
|
|
17
|
+
* addon), a sun `DirectionalLight` riding its daily arc, a hemisphere fill, and a
|
|
18
|
+
* star field that fades in at night. `setTime(hour)` drives everything; the
|
|
19
|
+
* dome loads on demand (own chunk) and catches up to the latest time.
|
|
20
|
+
*/
|
|
21
|
+
var SkyController = class {
|
|
22
|
+
scene;
|
|
23
|
+
sun = new DirectionalLight(16777215, 0);
|
|
24
|
+
hemi = new HemisphereLight(8961023, 5917238, 0);
|
|
25
|
+
stars;
|
|
26
|
+
dome = null;
|
|
27
|
+
def = null;
|
|
28
|
+
time = 12;
|
|
29
|
+
azimuth = 0;
|
|
30
|
+
hash = null;
|
|
31
|
+
renderer;
|
|
32
|
+
pmrem;
|
|
33
|
+
envRT = null;
|
|
34
|
+
envDirty = false;
|
|
35
|
+
envTimer = 0;
|
|
36
|
+
constructor(scene, renderer) {
|
|
37
|
+
this.scene = scene;
|
|
38
|
+
this.renderer = renderer;
|
|
39
|
+
this.pmrem = new PMREMGenerator(renderer);
|
|
40
|
+
this.sun.castShadow = true;
|
|
41
|
+
this.sun.shadow.mapSize.set(2048, 2048);
|
|
42
|
+
const cam = this.sun.shadow.camera;
|
|
43
|
+
cam.left = cam.bottom = -1500;
|
|
44
|
+
cam.right = cam.top = 1500;
|
|
45
|
+
cam.near = 1;
|
|
46
|
+
cam.far = SUN_DISTANCE * 2;
|
|
47
|
+
this.stars = this.buildStars();
|
|
48
|
+
}
|
|
49
|
+
reconcile(def) {
|
|
50
|
+
const hash = def ? JSON.stringify(def) : null;
|
|
51
|
+
if (hash === this.hash) return;
|
|
52
|
+
this.hash = hash;
|
|
53
|
+
if (!def) {
|
|
54
|
+
this.clear();
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
if (!this.def) {
|
|
58
|
+
this.scene.add(this.sun, this.sun.target, this.hemi, this.stars);
|
|
59
|
+
this.ensureDome();
|
|
60
|
+
}
|
|
61
|
+
this.def = def;
|
|
62
|
+
this.azimuth = def.azimuth ?? 0;
|
|
63
|
+
this.applyAtmosphere();
|
|
64
|
+
this.setTime(def.time ?? 12);
|
|
65
|
+
}
|
|
66
|
+
/** Set the hour of day (0..24) and update sun, sky, fill and stars live. */
|
|
67
|
+
setTime(hour) {
|
|
68
|
+
this.time = hour;
|
|
69
|
+
const elevation = Math.sin((hour - 6) / 12 * Math.PI) * MAX_ELEVATION;
|
|
70
|
+
const azimuth = 90 + (hour - 6) / 12 * 180 + this.azimuth;
|
|
71
|
+
const dir = new Vector3().setFromSphericalCoords(1, MathUtils.degToRad(90 - elevation), MathUtils.degToRad(azimuth));
|
|
72
|
+
if (this.dome) this.dome.material.uniforms.sunPosition.value.copy(dir);
|
|
73
|
+
this.sun.position.copy(dir).multiplyScalar(SUN_DISTANCE);
|
|
74
|
+
const day = clamp01(elevation / 6);
|
|
75
|
+
this.sun.intensity = day * 3;
|
|
76
|
+
this.sun.color.copy(SUNSET).lerp(DAYLIGHT, clamp01(elevation / 20));
|
|
77
|
+
this.hemi.intensity = lerp(.04, .6, clamp01((elevation + 6) / 12));
|
|
78
|
+
const night = clamp01(-elevation / 6);
|
|
79
|
+
const starMat = this.stars.material;
|
|
80
|
+
starMat.opacity = night;
|
|
81
|
+
this.stars.visible = night > 0;
|
|
82
|
+
const fogDensity = this.def?.fog ?? 0;
|
|
83
|
+
if (fogDensity > 0) {
|
|
84
|
+
const fog = this.scene.fog instanceof FogExp2 ? this.scene.fog : new FogExp2(0, fogDensity);
|
|
85
|
+
fog.density = fogDensity;
|
|
86
|
+
fog.color.copy(NIGHT_FOG).lerp(DAY_FOG, clamp01((elevation + 4) / 16));
|
|
87
|
+
const lowSun = clamp01(1 - elevation / 12) * clamp01(elevation / 3 + .4);
|
|
88
|
+
fog.color.lerp(SUNSET, lowSun * .4);
|
|
89
|
+
this.scene.fog = fog;
|
|
90
|
+
} else if (this.scene.fog instanceof FogExp2) this.scene.fog = null;
|
|
91
|
+
this.envDirty = true;
|
|
92
|
+
}
|
|
93
|
+
/** Current hour of day. */
|
|
94
|
+
getTime() {
|
|
95
|
+
return this.time;
|
|
96
|
+
}
|
|
97
|
+
/** Set the sun's compass offset (degrees) and re-place the sun. */
|
|
98
|
+
setAzimuth(degrees) {
|
|
99
|
+
this.azimuth = degrees;
|
|
100
|
+
this.setTime(this.time);
|
|
101
|
+
}
|
|
102
|
+
/** Current sun compass offset (degrees). */
|
|
103
|
+
getAzimuth() {
|
|
104
|
+
return this.azimuth;
|
|
105
|
+
}
|
|
106
|
+
/** Advance throttled work (IBL regeneration). Call once per frame. */
|
|
107
|
+
update(deltaMs) {
|
|
108
|
+
if (!this.envDirty || !this.dome) return;
|
|
109
|
+
this.envTimer += deltaMs;
|
|
110
|
+
if (this.envTimer < ENV_THROTTLE_MS) return;
|
|
111
|
+
this.envTimer = 0;
|
|
112
|
+
this.envDirty = false;
|
|
113
|
+
this.regenerateEnvironment();
|
|
114
|
+
}
|
|
115
|
+
/** Render just the dome to a PMREM and use it as the scene's IBL. */
|
|
116
|
+
regenerateEnvironment() {
|
|
117
|
+
if (!this.dome) return;
|
|
118
|
+
this.scene.remove(this.dome);
|
|
119
|
+
const envScene = new Scene();
|
|
120
|
+
envScene.add(this.dome);
|
|
121
|
+
const rt = this.pmrem.fromScene(envScene);
|
|
122
|
+
envScene.remove(this.dome);
|
|
123
|
+
this.scene.add(this.dome);
|
|
124
|
+
this.envRT?.dispose();
|
|
125
|
+
this.envRT = rt;
|
|
126
|
+
this.scene.environment = rt.texture;
|
|
127
|
+
}
|
|
128
|
+
applyAtmosphere() {
|
|
129
|
+
if (!this.dome || !this.def) return;
|
|
130
|
+
const u = this.dome.material.uniforms;
|
|
131
|
+
u.turbidity.value = this.def.turbidity ?? 4;
|
|
132
|
+
u.rayleigh.value = this.def.rayleigh ?? 2;
|
|
133
|
+
u.mieCoefficient.value = .005;
|
|
134
|
+
u.mieDirectionalG.value = .8;
|
|
135
|
+
}
|
|
136
|
+
ensureDome() {
|
|
137
|
+
if (this.dome) return;
|
|
138
|
+
import("three/examples/jsm/objects/Sky.js").then(({ Sky }) => {
|
|
139
|
+
if (!this.def) return;
|
|
140
|
+
const dome = new Sky();
|
|
141
|
+
dome.scale.setScalar(DOME);
|
|
142
|
+
this.dome = dome;
|
|
143
|
+
this.scene.add(dome);
|
|
144
|
+
this.applyAtmosphere();
|
|
145
|
+
this.setTime(this.time);
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
buildStars() {
|
|
149
|
+
const positions = new Float32Array(STAR_COUNT * 3);
|
|
150
|
+
for (let i = 0; i < STAR_COUNT; i++) {
|
|
151
|
+
const theta = Math.random() * Math.PI * 2;
|
|
152
|
+
const phi = Math.acos(Math.random());
|
|
153
|
+
const r = DOME * .9;
|
|
154
|
+
positions[i * 3] = r * Math.sin(phi) * Math.cos(theta);
|
|
155
|
+
positions[i * 3 + 1] = r * Math.cos(phi);
|
|
156
|
+
positions[i * 3 + 2] = r * Math.sin(phi) * Math.sin(theta);
|
|
157
|
+
}
|
|
158
|
+
const geometry = new BufferGeometry();
|
|
159
|
+
geometry.setAttribute("position", new Float32BufferAttribute(positions, 3));
|
|
160
|
+
const stars = new Points(geometry, new PointsMaterial({
|
|
161
|
+
color: 16777215,
|
|
162
|
+
size: 12,
|
|
163
|
+
sizeAttenuation: true,
|
|
164
|
+
transparent: true,
|
|
165
|
+
opacity: 0,
|
|
166
|
+
depthWrite: false
|
|
167
|
+
}));
|
|
168
|
+
stars.visible = false;
|
|
169
|
+
return stars;
|
|
170
|
+
}
|
|
171
|
+
clear() {
|
|
172
|
+
if (!this.def) return;
|
|
173
|
+
this.scene.remove(this.sun, this.sun.target, this.hemi, this.stars);
|
|
174
|
+
if (this.dome) {
|
|
175
|
+
this.scene.remove(this.dome);
|
|
176
|
+
this.dome = null;
|
|
177
|
+
}
|
|
178
|
+
if (this.scene.fog instanceof FogExp2) this.scene.fog = null;
|
|
179
|
+
this.scene.environment = null;
|
|
180
|
+
this.envRT?.dispose();
|
|
181
|
+
this.envRT = null;
|
|
182
|
+
this.envDirty = false;
|
|
183
|
+
this.def = null;
|
|
184
|
+
}
|
|
185
|
+
dispose() {
|
|
186
|
+
this.clear();
|
|
187
|
+
this.stars.geometry.dispose();
|
|
188
|
+
this.stars.material.dispose();
|
|
189
|
+
this.pmrem.dispose();
|
|
190
|
+
this.hash = null;
|
|
191
|
+
}
|
|
192
|
+
};
|
|
193
|
+
//#endregion
|
|
194
|
+
//#region src/plugin.ts
|
|
195
|
+
/**
|
|
196
|
+
* Procedural time-of-day sky — a *system* plugin. It renders an atmospheric sky
|
|
197
|
+
* dome, moves a sun light along its arc, fades in stars at night and drives the
|
|
198
|
+
* image-based lighting, all from `scene.sky`. Exposes `ctx.sky` (setTime/…). It
|
|
199
|
+
* manages its own objects, so it takes the scene graph and renderer (for IBL).
|
|
200
|
+
*/
|
|
201
|
+
const sky = () => ({
|
|
202
|
+
name: "sky",
|
|
203
|
+
setup: (ctx) => {
|
|
204
|
+
const controller = new SkyController(ctx.scene3d, ctx.renderer);
|
|
205
|
+
controller.reconcile(ctx.scene.sky);
|
|
206
|
+
return {
|
|
207
|
+
update: (deltaMs) => controller.update(deltaMs),
|
|
208
|
+
setScene: (scene) => controller.reconcile(scene.sky),
|
|
209
|
+
dispose: () => controller.dispose(),
|
|
210
|
+
api: {
|
|
211
|
+
setTime: (hour) => controller.setTime(hour),
|
|
212
|
+
getTime: () => controller.getTime(),
|
|
213
|
+
setAzimuth: (degrees) => controller.setAzimuth(degrees),
|
|
214
|
+
getAzimuth: () => controller.getAzimuth()
|
|
215
|
+
}
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
});
|
|
219
|
+
//#endregion
|
|
220
|
+
export { SkyController, sky, skyData, validateSky };
|
|
221
|
+
|
|
222
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../src/SkyController.ts","../src/plugin.ts"],"sourcesContent":["import {\n BufferGeometry,\n Color,\n DirectionalLight,\n Float32BufferAttribute,\n FogExp2,\n HemisphereLight,\n MathUtils,\n PMREMGenerator,\n Points,\n PointsMaterial,\n Scene,\n Vector3,\n type Object3D,\n type Scene as ThreeScene,\n type WebGLRenderer,\n type WebGLRenderTarget,\n} from \"three\";\nimport type { Sky as SkyDef } from \"./data\";\n\nconst clamp01 = (x: number): number => Math.min(Math.max(x, 0), 1);\nconst lerp = (a: number, b: number, t: number): number => a + (b - a) * t;\n\n// Sky dome radius (within the camera far plane) and star shell radius.\nconst DOME = 4500;\nconst STAR_COUNT = 1500;\nconst SUN_DISTANCE = 2000;\nconst MAX_ELEVATION = 60; // noon sun angle (less than zenith for longer shadows)\nconst ENV_THROTTLE_MS = 150; // min gap between IBL regenerations\n\nconst SUNSET = new Color(\"#ff7a3c\");\nconst DAYLIGHT = new Color(\"#fff4e6\");\nconst NIGHT_FOG = new Color(\"#0a1222\");\nconst DAY_FOG = new Color(\"#bcd3e0\");\n\n/** Minimal shape of the Sky addon's material uniforms we touch. */\ninterface SkyMaterial {\n uniforms: {\n turbidity: { value: number };\n rayleigh: { value: number };\n mieCoefficient: { value: number };\n mieDirectionalG: { value: number };\n sunPosition: { value: Vector3 };\n };\n}\n\n/**\n * Owns the procedural time-of-day sky: an atmospheric dome (three's `Sky`\n * addon), a sun `DirectionalLight` riding its daily arc, a hemisphere fill, and a\n * star field that fades in at night. `setTime(hour)` drives everything; the\n * dome loads on demand (own chunk) and catches up to the latest time.\n */\nexport class SkyController {\n private readonly scene: ThreeScene;\n private readonly sun = new DirectionalLight(0xffffff, 0);\n private readonly hemi = new HemisphereLight(0x88bbff, 0x5a4a36, 0);\n private readonly stars: Points;\n private dome: (Object3D & { material: SkyMaterial }) | null = null;\n private def: SkyDef | null = null;\n private time = 12;\n private azimuth = 0;\n private hash: string | null = null;\n\n // Image-based lighting: a PMREM of the sky dome, regenerated (throttled) when\n // the sky changes, so the water reflects the sky and PBR picks up its color.\n private readonly renderer: WebGLRenderer;\n private readonly pmrem: PMREMGenerator;\n private envRT: WebGLRenderTarget | null = null;\n private envDirty = false;\n private envTimer = 0;\n\n constructor(scene: ThreeScene, renderer: WebGLRenderer) {\n this.scene = scene;\n this.renderer = renderer;\n this.pmrem = new PMREMGenerator(renderer);\n this.sun.castShadow = true;\n this.sun.shadow.mapSize.set(2048, 2048);\n const cam = this.sun.shadow.camera;\n cam.left = cam.bottom = -1500;\n cam.right = cam.top = 1500;\n cam.near = 1;\n cam.far = SUN_DISTANCE * 2;\n this.stars = this.buildStars();\n }\n\n reconcile(def: SkyDef | undefined): void {\n const hash = def ? JSON.stringify(def) : null;\n if (hash === this.hash) return;\n this.hash = hash;\n\n if (!def) {\n this.clear();\n return;\n }\n if (!this.def) {\n this.scene.add(this.sun, this.sun.target, this.hemi, this.stars);\n this.ensureDome();\n }\n this.def = def;\n this.azimuth = def.azimuth ?? 0;\n this.applyAtmosphere();\n this.setTime(def.time ?? 12);\n }\n\n /** Set the hour of day (0..24) and update sun, sky, fill and stars live. */\n setTime(hour: number): void {\n this.time = hour;\n\n // Sun along its arc: elevation peaks at noon, dips below at night.\n const elevation = Math.sin(((hour - 6) / 12) * Math.PI) * MAX_ELEVATION;\n const azimuth = 90 + ((hour - 6) / 12) * 180 + this.azimuth;\n const dir = new Vector3().setFromSphericalCoords(\n 1,\n MathUtils.degToRad(90 - elevation),\n MathUtils.degToRad(azimuth),\n );\n\n if (this.dome) this.dome.material.uniforms.sunPosition.value.copy(dir);\n this.sun.position.copy(dir).multiplyScalar(SUN_DISTANCE);\n\n // Intensity/colour: bright white high up, dim warm at the horizon, off below.\n const day = clamp01(elevation / 6);\n this.sun.intensity = day * 3;\n this.sun.color.copy(SUNSET).lerp(DAYLIGHT, clamp01(elevation / 20));\n this.hemi.intensity = lerp(0.04, 0.6, clamp01((elevation + 6) / 12));\n\n // Stars fade in once the sun is below the horizon.\n const night = clamp01(-elevation / 6);\n const starMat = this.stars.material as PointsMaterial;\n starMat.opacity = night;\n this.stars.visible = night > 0;\n\n // Fog tinted to the sky, fading the horizon (off when density is 0).\n const fogDensity = this.def?.fog ?? 0;\n if (fogDensity > 0) {\n const fog = this.scene.fog instanceof FogExp2 ? this.scene.fog : new FogExp2(0x000000, fogDensity);\n fog.density = fogDensity;\n fog.color.copy(NIGHT_FOG).lerp(DAY_FOG, clamp01((elevation + 4) / 16));\n const lowSun = clamp01(1 - elevation / 12) * clamp01(elevation / 3 + 0.4);\n fog.color.lerp(SUNSET, lowSun * 0.4);\n this.scene.fog = fog;\n } else if (this.scene.fog instanceof FogExp2) {\n this.scene.fog = null;\n }\n\n // The sky changed; refresh the IBL (throttled in update()).\n this.envDirty = true;\n }\n\n /** Current hour of day. */\n getTime(): number {\n return this.time;\n }\n\n /** Set the sun's compass offset (degrees) and re-place the sun. */\n setAzimuth(degrees: number): void {\n this.azimuth = degrees;\n this.setTime(this.time);\n }\n\n /** Current sun compass offset (degrees). */\n getAzimuth(): number {\n return this.azimuth;\n }\n\n /** Advance throttled work (IBL regeneration). Call once per frame. */\n update(deltaMs: number): void {\n if (!this.envDirty || !this.dome) return;\n this.envTimer += deltaMs;\n if (this.envTimer < ENV_THROTTLE_MS) return;\n this.envTimer = 0;\n this.envDirty = false;\n this.regenerateEnvironment();\n }\n\n /** Render just the dome to a PMREM and use it as the scene's IBL. */\n private regenerateEnvironment(): void {\n if (!this.dome) return;\n this.scene.remove(this.dome);\n const envScene = new Scene();\n envScene.add(this.dome);\n const rt = this.pmrem.fromScene(envScene);\n envScene.remove(this.dome);\n this.scene.add(this.dome);\n this.envRT?.dispose();\n this.envRT = rt;\n this.scene.environment = rt.texture;\n }\n\n private applyAtmosphere(): void {\n if (!this.dome || !this.def) return;\n const u = this.dome.material.uniforms;\n u.turbidity.value = this.def.turbidity ?? 4;\n u.rayleigh.value = this.def.rayleigh ?? 2;\n u.mieCoefficient.value = 0.005;\n u.mieDirectionalG.value = 0.8;\n }\n\n private ensureDome(): void {\n if (this.dome) return;\n void import(\"three/examples/jsm/objects/Sky.js\").then(({ Sky }) => {\n if (!this.def) return; // reconciled away before it loaded\n const dome = new Sky() as unknown as Object3D & { material: SkyMaterial };\n dome.scale.setScalar(DOME);\n this.dome = dome;\n this.scene.add(dome);\n this.applyAtmosphere();\n this.setTime(this.time);\n });\n }\n\n private buildStars(): Points {\n const positions = new Float32Array(STAR_COUNT * 3);\n for (let i = 0; i < STAR_COUNT; i++) {\n // Upper hemisphere shell.\n const theta = Math.random() * Math.PI * 2;\n const phi = Math.acos(Math.random()); // 0..90° from zenith\n const r = DOME * 0.9;\n positions[i * 3] = r * Math.sin(phi) * Math.cos(theta);\n positions[i * 3 + 1] = r * Math.cos(phi);\n positions[i * 3 + 2] = r * Math.sin(phi) * Math.sin(theta);\n }\n const geometry = new BufferGeometry();\n geometry.setAttribute(\"position\", new Float32BufferAttribute(positions, 3));\n const material = new PointsMaterial({\n color: 0xffffff,\n size: 12,\n sizeAttenuation: true,\n transparent: true,\n opacity: 0,\n depthWrite: false,\n });\n const stars = new Points(geometry, material);\n stars.visible = false;\n return stars;\n }\n\n private clear(): void {\n if (!this.def) return;\n this.scene.remove(this.sun, this.sun.target, this.hemi, this.stars);\n if (this.dome) {\n this.scene.remove(this.dome);\n this.dome = null;\n }\n if (this.scene.fog instanceof FogExp2) this.scene.fog = null;\n this.scene.environment = null;\n this.envRT?.dispose();\n this.envRT = null;\n this.envDirty = false;\n this.def = null;\n }\n\n dispose(): void {\n this.clear();\n this.stars.geometry.dispose();\n (this.stars.material as PointsMaterial).dispose();\n this.pmrem.dispose();\n this.hash = null;\n }\n}\n","import type { SkyApi, SystemPlugin } from \"@scenography/engine\";\n\nimport { SkyController } from \"./SkyController\";\n\n/**\n * Procedural time-of-day sky — a *system* plugin. It renders an atmospheric sky\n * dome, moves a sun light along its arc, fades in stars at night and drives the\n * image-based lighting, all from `scene.sky`. Exposes `ctx.sky` (setTime/…). It\n * manages its own objects, so it takes the scene graph and renderer (for IBL).\n */\nexport const sky = (): SystemPlugin<SkyApi> => ({\n name: \"sky\",\n setup: (ctx) => {\n const controller = new SkyController(ctx.scene3d, ctx.renderer);\n controller.reconcile(ctx.scene.sky);\n return {\n update: (deltaMs) => controller.update(deltaMs),\n setScene: (scene) => controller.reconcile(scene.sky),\n dispose: () => controller.dispose(),\n api: {\n setTime: (hour) => controller.setTime(hour),\n getTime: () => controller.getTime(),\n setAzimuth: (degrees) => controller.setAzimuth(degrees),\n getAzimuth: () => controller.getAzimuth(),\n },\n };\n },\n});\n"],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@scenography/plugin-sky",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Sky plugin for scenography: procedural time-of-day sky (sun, atmosphere, stars, IBL) with ctx.sky runtime control.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"files": [
|
|
7
|
+
"dist"
|
|
8
|
+
],
|
|
9
|
+
"type": "module",
|
|
10
|
+
"main": "./dist/index.cjs",
|
|
11
|
+
"module": "./dist/index.mjs",
|
|
12
|
+
"types": "./dist/index.d.cts",
|
|
13
|
+
"exports": {
|
|
14
|
+
".": {
|
|
15
|
+
"import": {
|
|
16
|
+
"types": "./dist/index.d.mts",
|
|
17
|
+
"default": "./dist/index.mjs"
|
|
18
|
+
},
|
|
19
|
+
"require": {
|
|
20
|
+
"types": "./dist/index.d.cts",
|
|
21
|
+
"default": "./dist/index.cjs"
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
"./data": {
|
|
25
|
+
"import": {
|
|
26
|
+
"types": "./dist/data.d.mts",
|
|
27
|
+
"default": "./dist/data.mjs"
|
|
28
|
+
},
|
|
29
|
+
"require": {
|
|
30
|
+
"types": "./dist/data.d.cts",
|
|
31
|
+
"default": "./dist/data.cjs"
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
"dependencies": {
|
|
36
|
+
"@scenography/scene": "0.2.0"
|
|
37
|
+
},
|
|
38
|
+
"devDependencies": {
|
|
39
|
+
"@types/three": "^0.160.0",
|
|
40
|
+
"three": "^0.160.0",
|
|
41
|
+
"@scenography/engine": "0.2.0"
|
|
42
|
+
},
|
|
43
|
+
"peerDependencies": {
|
|
44
|
+
"three": ">=0.160.0",
|
|
45
|
+
"@scenography/engine": "0.2.0"
|
|
46
|
+
},
|
|
47
|
+
"scripts": {
|
|
48
|
+
"build": "tsdown",
|
|
49
|
+
"dev": "tsdown --watch",
|
|
50
|
+
"typecheck": "tsc --noEmit"
|
|
51
|
+
}
|
|
52
|
+
}
|