tosijs-3d 0.10.7 → 0.10.9

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 (50) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/dist/b3d-planet.d.ts +34 -0
  3. package/dist/b3d-planet.d.ts.map +1 -1
  4. package/dist/b3d-planet.js +202 -57
  5. package/dist/b3d-planet.js.map +1 -1
  6. package/dist/b3d-terrain.d.ts +26 -2
  7. package/dist/b3d-terrain.d.ts.map +1 -1
  8. package/dist/b3d-terrain.js +131 -13
  9. package/dist/b3d-terrain.js.map +1 -1
  10. package/dist/b3d-utils.d.ts +5 -0
  11. package/dist/b3d-utils.d.ts.map +1 -1
  12. package/dist/b3d-utils.js +17 -0
  13. package/dist/b3d-utils.js.map +1 -1
  14. package/dist/dial-art.d.ts +3 -0
  15. package/dist/dial-art.d.ts.map +1 -0
  16. package/dist/dial-art.js +4 -0
  17. package/dist/dial-art.js.map +1 -0
  18. package/dist/dials.d.ts +76 -0
  19. package/dist/dials.d.ts.map +1 -0
  20. package/dist/dials.js +253 -0
  21. package/dist/dials.js.map +1 -0
  22. package/dist/gas-giant-material.d.ts +26 -0
  23. package/dist/gas-giant-material.d.ts.map +1 -1
  24. package/dist/gas-giant-material.js +45 -1
  25. package/dist/gas-giant-material.js.map +1 -1
  26. package/dist/gas-giant.d.ts +1 -0
  27. package/dist/gas-giant.d.ts.map +1 -1
  28. package/dist/gas-giant.js +57 -0
  29. package/dist/gas-giant.js.map +1 -1
  30. package/dist/icon-data.d.ts +1 -0
  31. package/dist/icon-data.d.ts.map +1 -1
  32. package/dist/icon-data.js +1 -0
  33. package/dist/icon-data.js.map +1 -1
  34. package/dist/scene-schemas.d.ts +16 -0
  35. package/dist/scene-schemas.d.ts.map +1 -1
  36. package/dist/scene-schemas.js +189 -0
  37. package/dist/scene-schemas.js.map +1 -1
  38. package/dist/sphere-map.d.ts +13 -1
  39. package/dist/sphere-map.d.ts.map +1 -1
  40. package/dist/sphere-map.js +38 -14
  41. package/dist/sphere-map.js.map +1 -1
  42. package/dist/tosi-b3d.d.ts.map +1 -1
  43. package/dist/tosi-b3d.js +8 -0
  44. package/dist/tosi-b3d.js.map +1 -1
  45. package/dist/widgets3d.d.ts.map +1 -1
  46. package/dist/widgets3d.js +44 -5
  47. package/dist/widgets3d.js.map +1 -1
  48. package/package.json +2 -1
  49. package/static/attributes.json +20 -0
  50. package/static/attributes.txt +4 -0
package/CHANGELOG.md CHANGED
@@ -6,6 +6,38 @@ All notable changes to **tosijs-3d**. This project is pre-1.0 (`0.x`), so minor
6
6
  versions may carry breaking peer-dependency changes — each is called out in a
7
7
  **⚠️ Breaking** block in its version section below, with what a consumer must do.
8
8
 
9
+ ## 0.10.9
10
+
11
+ ### Added
12
+
13
+ - **A compass and a watch** (`import … from 'tosijs-3d/dials'`, not in the barrel: the artwork is 36 KB). Both are drawn by Tonio, and the code only moves their named parts. The compass needle points north as you turn (`sceneCompass(b3d)` follows the active camera, flat or in a headset; north is +Z). On the watch, a dot on the ring is the time of day (noon at the top, midnight at the bottom) and a dot on the bar is how fast it passes, from still to an hour a second. Drag either: `skyWatch(skybox)` reads and writes the sky's `timeOfDay` and `realtimeScale`. Each goes on the system bar as a `<tosi-b3d-panel icon title>`. Feather's `clock` icon is added. `bun run dial-art` re-embeds the SVGs after they are redrawn.
14
+ - **A planet and its sphere terrain are one world** (#3344). Tonio: "if you pick a point in the middle of what looks like the ocean, coast, mountain range on a planet you end up with corresponding terrain in the sphere terrain and vice versa." Either can be the source, stated by one attribute:
15
+ - **`<tosi-b3d-terrain planet="#p">`**: the ground takes its large shape (sea, coasts, mountain ranges) from the planet, scaled to its `radius` with the sea at 0. Its own detail layer and landforms ride on top, and it rebuilds when the planet changes (`planet.shapeKey`). Measured at 200 random places: the ground sits within the detail layer of the globe's height (mean 2.5 m, worst 3.9 m with 5 m of detail), and sea or land agrees except at the waterline.
16
+ - **`<tosi-b3d-planet terrain="#t">`**: the globe is drawn from the ground's own height function, and again whenever the ground's shape changes. This replaces the hand-written `planet.surface = terrain.planetSurface()` loop, and the sphere-map demo now uses it.
17
+
18
+ Linked, not automatic: a gas giant in a moon's sky, or every body of a star system, must not become the ground you stand on. `planet.relativeHeightAt(direction)` is the globe's height above its sea as a fraction of its radius. Both attributes are in the schemas.
19
+ - **Planetary rings catch shadows** (#3320). Both are worked out in the shaders for a sphere and a flat ring lit by one star, with no shadow maps. **The planet's shadow falls across its rings** (the dark wedge behind Saturn): each ring fragment asks whether the ray toward the star passes through the planet, softened slightly at the edge. This applies to any `<tosi-b3d-planet>` with `rings`. **The rings' shadow bands a gas giant**: each fragment finds where the ray toward the star crosses the ring plane and dims by how much ring is there, so the shadow has the ring's own gaps (a lighter stripe where the Cassini gap lets light through). The ring is now one GLSL function (`RING_GLSL` in `gas-giant`) that the ring draws itself from and the planet reads, and `createGasGiantMaterial` takes `ring: () => RingShadow | null` for a planet built by hand. Not yet: the rings' shadow on a ROCKY planet's surface.
20
+
21
+ Size: barrel (min, packages external) 1114.2 KB raw / 379.4 KB gz, +0.46% on 0.10.8.
22
+
23
+ ## 0.10.8
24
+
25
+ ### Added
26
+
27
+ - **`planetSchema()`** (`sceneSchemas.planet`): `<tosi-b3d-planet>` described like the other scene primitives, with sections (World, Surface, Sea, Air, Clouds, Craters, Rings, Gas giant, Maps), `heightMap`/`colorMap` marked as fetched, and the `world`/`gas` lists taken from the preset tables so they cannot drift. **`x-preset-written`** names the attributes a `world` preset writes into the planet. A consumer that applies schema defaults on bind should show them but not write them back over the preset (#103, ensemble).
28
+ - **`lat` / `lon` on `<tosi-b3d-terrain>`**: where this ground is on its planet, the latitude and longitude of the terrain's origin in degrees, so "this ground is at 12° N, 40° E" is one attribute pair, in markup and in `terrainSchema()`. `worldU`/`worldV` are now JS properties over them (`worldU = lon / 360`, `worldV = (lat + 90) / 180`). Before, they were plain properties that markup could not set and that needed a `regenerate()`. Changing `lat`/`lon` (or either view of them) rebuilds the ground. The defaults (-45, 0) are the old 0.25 / 0, so nothing moves (#103).
29
+ - **`seamBlend` / `seamWeight`** (`sphere-map`): make a layer laid out in flat metres periodic round a world, mixed with its copy one circumference away near the far meridian and untouched elsewhere.
30
+
31
+ ### Fixed
32
+
33
+ - **A widget bound to a tosijs box lets go of it** (#100). `toggle3d`, `slider3d` and `select3d` subscribed to a bound box and threw the unsubscribe away. Every bound row left a listener in tosijs's module-level list that held the row, its panel and its host, so ensemble's editor grew by 125 listeners and 5,360 nodes per round of panel rebuilds. Each now unsubscribes in its `dispose()`. `B3d` already disposes the widget lists it rebuilds, and **`panel3d(…)` has a `dispose()`** that disposes its rows, for a consumer that drops its own panels. Not reaped by `isConnected`: an in-scene panel's SVG is never in the document.
34
+ - **Removing the last instance of a library model no longer turns every later one white** (#102). `disposeMeshTree` disposed a material when no mesh in the scene used it, but a library's source meshes live in its AssetContainer, out of that list, so the guard could not see that the library still needed the material. Ensemble's editor lost the colour of a whole kit on its first rebuild, and a game would see it when the last enemy of a type despawned and the next one spawned white. It now disposes only materials the scene owns (`scene.materials`); a container's are the container's.
35
+ - **A frozen or paused scene builds its system bar** (#3342). The bar and flat panels were built only on running frames, so a scene that started frozen (Manta freezes on every menu) showed the HTML lozenge, and a `<tosi-b3d-panel icon title>` got no button until it unfroze. Stopped frames keep the scene's chrome current now, as they already did its fog.
36
+ - **A sphere terrain joins at the far meridian** (#3318). Its noise is read on the sphere and always repeated; its profile fields (`blendProfiles`, `profileField`) and landforms (`craterField`, …) read flat metres and did not. The ground opposite the terrain's origin (longitude 180 at the default `worldU`) was two different places: a 116 m step on Manta's cratered moon, 19.5 m without the craters. Within 5% of a turn of that meridian they are now mixed with their copy one turn away, so the two sides meet. Elsewhere the ground is exactly as before, and a place one whole turn round is the same place. Spheres only: a cylinder is used as a big flat world with things placed in metres.
37
+ - **A globe of a world with no sea is drawn at its size** (#3319). A planet drawn from a `PlanetSurface` measured every height from `seaLevel`, so a dry world that says "no sea" with a sea level far below its ground (Manta's -10000 m) gained that depth as ground. A globe meant to be 40 units across came out at 2476. The globe now measures from the higher of the sea and the lowest ground. A world with a real sea is unchanged, and on a dry world the lowest ground sits on the base radius. The convention for "no sea" is a `seaLevel` below all of the ground.
38
+
39
+ Size: barrel (min, packages external) 1109.1 KB raw / 377.6 KB gz, +0.38% on 0.10.7.
40
+
9
41
  ## 0.10.7
10
42
 
11
43
  **What you will see on upgrading** (nothing exported in 0.10.6 is removed or renamed, but a `^0.10` range picks this up unasked):
@@ -32,6 +32,7 @@ export declare class B3dPlanet extends B3dChild {
32
32
  ringOuter: number;
33
33
  ringOpacity: number;
34
34
  ringColor: string;
35
+ terrain: string;
35
36
  world: string;
36
37
  craters: number;
37
38
  seaShallow: string;
@@ -115,6 +116,35 @@ export declare class B3dPlanet extends B3dChild {
115
116
  private _builtSea;
116
117
  private _seaKey;
117
118
  private _surfaceFromMap;
119
+ /** The sea level of the last build, in this planet's units. */
120
+ private _seaAt;
121
+ /** Bumped by every build: what `shapeKey` reports. */
122
+ private _shapeGen;
123
+ /**
124
+ * Changes whenever this planet's SHAPE does (every build). A terrain that
125
+ * takes its shape from this planet (`planet` on the terrain) compares it to
126
+ * know when to rebuild.
127
+ */
128
+ get shapeKey(): string;
129
+ /**
130
+ * HOW HIGH THE GROUND IS ABOVE THE SEA in a direction from the centre, as
131
+ * a fraction of the planet's radius: what this globe draws there, with
132
+ * its sea at 0 (on a world with no sea, its lowest ground). A terrain
133
+ * with `planet` set multiplies it by its own radius, which is how the
134
+ * ground under a pin is the place the globe shows. Null before the
135
+ * planet is built, and for a gas giant.
136
+ */
137
+ relativeHeightAt(d: {
138
+ x: number;
139
+ y: number;
140
+ z: number;
141
+ }): number | null;
142
+ /** The terrain `terrain` names, when it is a sphere. */
143
+ private _linkedTerrain;
144
+ private _terrainWarned;
145
+ private _terrainBuilt;
146
+ private _surfaceFromTerrain;
147
+ private _followTerrain;
118
148
  /**
119
149
  * Fetch `heightMap` (once per URL and sea setting) and rebuild from it. The
120
150
  * planet shows as a smooth ball, in its colours, until the heights arrive.
@@ -138,6 +168,10 @@ export declare class B3dPlanet extends B3dChild {
138
168
  * buildPlanet). Kept as a named step so the build order still reads.
139
169
  */
140
170
  private buildOcean;
171
+ /** The ring's shape, for its shadow on the planet (null: no ring). */
172
+ private _ring;
173
+ /** The ring as the gas giant material's shadow needs it, now. */
174
+ private _ringShadow;
141
175
  private buildRings;
142
176
  /** The rocky world's look, when `world` names one (and it is not a gas giant). */
143
177
  private _look;
@@ -1 +1 @@
1
- {"version":3,"file":"b3d-planet.d.ts","sourceRoot":"","sources":["../src/b3d-planet.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"b3d-planet.d.ts","sourceRoot":"","sources":["../src/b3d-planet.ts"],"names":[],"mappings":"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"}
@@ -167,6 +167,7 @@ tosi-b3d {
167
167
  | `seaGlint` | `1` | The sun's glint on the sea, `0…1`: a patch of sparkle, broken up by fine wave texture, that slides over the water as you move |
168
168
  | `seaIce` | `0` | Sea ice toward the poles, `0…1` |
169
169
  | `inlandWater` | `'off'` | `'on'` guesses LAKES from a `colorMap`: where the picture is blue, or nearly black (how NASA's Blue Marble paints deep lakes), it is drawn as water: the map's colour, with the glint. The height map only knows the sea, and a lake is above it. A guess: blue ice or black lava can pass for a lake |
170
+ | `terrain` | `''` | A selector for the sphere `<tosi-b3d-terrain>` this is a GLOBE OF (`'#moon-ground'`): drawn from that ground's own height function, and again whenever its shape changes, so a place on the globe is the place on the ground. (The other way round, a terrain's `planet` gives the ground this planet's shape) |
170
171
  | `relief` | `1` | With a `heightMap` or a `surface` set: how much its true relief is exaggerated. `1` is to scale, which on a globe is flat to the eye; 20 to 60 reads as a relief map |
171
172
  | `world` | `''` | A ROCKY WORLD preset: `'moon'`, `'mercury'`, `'mars'`, `'venus'`, `'io'`, `'titan'` or `'earth'` (see [rocky-world](/rocky-world/)). Choosing one writes its sea, air, clouds, relief, craters and climate into this planet's own settings, which stay yours to tune; `worldOverrides` (a property) changes the preset for this planet |
172
173
  | `craters` | `0` | How cratered, `0…1` (see landform's `sphereCraters`) |
@@ -201,7 +202,7 @@ tosi-b3d {
201
202
  /*{ "parent": "space", "order": 40 }*/
202
203
  import { Color } from 'tosijs';
203
204
  import { isOff, B3dChild, sceneDelta, sceneSeconds } from './b3d-utils.js';
204
- import { GAS_GIANT_GLSL, gasGiantRgb, resolveGasGiant } from './gas-giant.js';
205
+ import { GAS_GIANT_GLSL, RING_GLSL, gasGiantRgb, resolveGasGiant, } from './gas-giant.js';
205
206
  import { climateForGlobe } from './sphere-map.js';
206
207
  import { loadPlanetMap } from './planet-map.js';
207
208
  import { createPlanetClouds } from './planet-clouds.js';
@@ -274,6 +275,14 @@ export class B3dPlanet extends B3dChild {
274
275
  ringOuter: 0,
275
276
  ringOpacity: -1,
276
277
  ringColor: '',
278
+ /*
279
+ THE GROUND THIS IS A GLOBE OF (#3344): a selector for a sphere
280
+ `<tosi-b3d-terrain>` ('#moon-ground'). The planet draws from it, and
281
+ draws again whenever the ground's shape changes, so the globe and the
282
+ ground are one world. '' for none. (A terrain can take its shape from
283
+ a planet instead: its `planet` attribute.)
284
+ */
285
+ terrain: '',
277
286
  // A ROCKY WORLD's look (rocky-world): 'moon', 'mercury', 'mars',
278
287
  // 'venus', 'earth'. A preset: choosing one writes its sea, air, clouds,
279
288
  // relief, craters and climate into this planet's own settings, which
@@ -407,6 +416,7 @@ export class B3dPlanet extends B3dChild {
407
416
  }
408
417
  }
409
418
  update() {
419
+ this._followTerrain();
410
420
  this._followGroundClimate();
411
421
  if (this.rootNode == null)
412
422
  return;
@@ -444,7 +454,9 @@ export class B3dPlanet extends B3dChild {
444
454
  turbulence: attrs.turbulence,
445
455
  storm: attrs.storm,
446
456
  colors: attrs.bandColors,
447
- }));
457
+ }),
458
+ // The rings' shadow on the bands (#3320).
459
+ { ring: () => this._ringShadow() });
448
460
  mesh.parent = this.rootNode;
449
461
  this.planetMesh = mesh;
450
462
  this.vertexHeights = null;
@@ -510,6 +522,31 @@ export class B3dPlanet extends B3dChild {
510
522
  as data (uv.x), so the shader can tell land from water per pixel and
511
523
  knows how deep the water is. There is no sea sphere.
512
524
  */
525
+ /*
526
+ A SEA BELOW ALL THE GROUND IS NO SEA (board #3319). Heights from a
527
+ surface are measured from its `seaLevel`, so a dry world that says "no
528
+ sea" with a sea far down (Manta's moon: -10000 m under ground near 320 m)
529
+ gained ten kilometres of ground and was drawn hundreds of times too big.
530
+ The datum is the higher of the sea and the lowest ground: with a real sea
531
+ nothing changes, and on a dry world the lowest ground sits on the base
532
+ radius.
533
+ */
534
+ if (this.surface != null) {
535
+ let lowest = Infinity;
536
+ for (let i = 0; i < totalVerts; i++)
537
+ if (heights[i] < lowest)
538
+ lowest = heights[i];
539
+ if (lowest > 0 && Number.isFinite(lowest)) {
540
+ for (let i = 0; i < totalVerts; i++) {
541
+ heights[i] -= lowest;
542
+ const vi = i * 3;
543
+ const r = radius + heights[i];
544
+ positions[vi] = normals[vi] * r;
545
+ positions[vi + 1] = normals[vi + 1] * r;
546
+ positions[vi + 2] = normals[vi + 2] * r;
547
+ }
548
+ }
549
+ }
513
550
  let highest = 1e-3;
514
551
  let deepest = 1e-3;
515
552
  this.vertexHeights = heights;
@@ -517,6 +554,8 @@ export class B3dPlanet extends B3dChild {
517
554
  const seaAt = this.surface != null || !hasSea
518
555
  ? 0
519
556
  : this.heightPercentile(this._val('ocean'));
557
+ this._seaAt = seaAt;
558
+ this._shapeGen++;
520
559
  const heightData = new Float32Array(totalVerts * 2);
521
560
  for (let i = 0; i < totalVerts; i++) {
522
561
  const above = heights[i] - seaAt;
@@ -696,6 +735,97 @@ export class B3dPlanet extends B3dChild {
696
735
  return `${this._val('ocean')}|${attrs.flatSea}|${attrs.world}`;
697
736
  }
698
737
  _surfaceFromMap = false;
738
+ /** The sea level of the last build, in this planet's units. */
739
+ _seaAt = 0;
740
+ /** Bumped by every build: what `shapeKey` reports. */
741
+ _shapeGen = 0;
742
+ /**
743
+ * Changes whenever this planet's SHAPE does (every build). A terrain that
744
+ * takes its shape from this planet (`planet` on the terrain) compares it to
745
+ * know when to rebuild.
746
+ */
747
+ get shapeKey() {
748
+ return `${this.seed}|${this._shapeGen}`;
749
+ }
750
+ /**
751
+ * HOW HIGH THE GROUND IS ABOVE THE SEA in a direction from the centre, as
752
+ * a fraction of the planet's radius: what this globe draws there, with
753
+ * its sea at 0 (on a world with no sea, its lowest ground). A terrain
754
+ * with `planet` set multiplies it by its own radius, which is how the
755
+ * ground under a pin is the place the globe shows. Null before the
756
+ * planet is built, and for a gas giant.
757
+ */
758
+ relativeHeightAt(d) {
759
+ const attrs = this;
760
+ if (this.noise == null || attrs.gas)
761
+ return null;
762
+ const len = Math.hypot(d.x, d.y, d.z) || 1;
763
+ const s = this.surface;
764
+ if (s != null) {
765
+ const h = s.heightAt({ x: d.x / len, y: d.y / len, z: d.z / len });
766
+ return (h - s.seaLevel) / s.radius;
767
+ }
768
+ return ((this.heightAt(d.x / len, d.y / len, d.z / len) - this._seaAt) /
769
+ attrs.radius);
770
+ }
771
+ /** The terrain `terrain` names, when it is a sphere. */
772
+ _linkedTerrain() {
773
+ const sel = String(this.terrain ?? '').trim();
774
+ if (sel === '' || this.owner == null)
775
+ return null;
776
+ let el;
777
+ try {
778
+ el =
779
+ this.owner.querySelector(sel) ??
780
+ document.querySelector(sel);
781
+ }
782
+ catch {
783
+ if (!this._terrainWarned) {
784
+ this._terrainWarned = true;
785
+ console.warn(`b3d-planet: terrain "${sel}" is not a valid selector`);
786
+ }
787
+ return null;
788
+ }
789
+ const t = el;
790
+ if (t == null || typeof t.planetSurface !== 'function')
791
+ return null;
792
+ return t;
793
+ }
794
+ _terrainWarned = false;
795
+ _terrainBuilt = '';
796
+ _surfaceFromTerrain = false;
797
+ /*
798
+ FOLLOW THE GROUND (#3344). Tonio: "if you pick a point in the middle of
799
+ what looks like the ocean, coast, mountain range on a planet you end up
800
+ with corresponding terrain in the sphere terrain and vice versa." The
801
+ globe is drawn from the terrain's own height function, again whenever
802
+ its `generationKey` changes. Not when that terrain takes ITS shape from
803
+ this planet: each would follow the other.
804
+ */
805
+ _followTerrain() {
806
+ const t = this._linkedTerrain();
807
+ if (t == null) {
808
+ if (this._surfaceFromTerrain) {
809
+ this._surfaceFromTerrain = false;
810
+ this._terrainBuilt = '';
811
+ this.surface = null;
812
+ this.regenerate();
813
+ }
814
+ return;
815
+ }
816
+ if (typeof t.planetSource === 'function' && t.planetSource() === this)
817
+ return;
818
+ const key = t.generationKey;
819
+ if (!key || key === this._terrainBuilt)
820
+ return;
821
+ const surface = t.planetSurface();
822
+ if (surface == null)
823
+ return;
824
+ this._terrainBuilt = key;
825
+ this._surfaceFromTerrain = true;
826
+ this.surface = surface;
827
+ this.regenerate();
828
+ }
699
829
  /**
700
830
  * Fetch `heightMap` (once per URL and sea setting) and rebuild from it. The
701
831
  * planet shows as a smooth ball, in its colours, until the heights arrive.
@@ -946,7 +1076,25 @@ export class B3dPlanet extends B3dChild {
946
1076
  buildOcean() {
947
1077
  this.oceanMesh = null;
948
1078
  }
1079
+ /** The ring's shape, for its shadow on the planet (null: no ring). */
1080
+ _ring = null;
1081
+ /** The ring as the gas giant material's shadow needs it, now. */
1082
+ _ringShadow() {
1083
+ const ring = this._ring;
1084
+ const root = this.rootNode;
1085
+ if (ring == null || root == null)
1086
+ return null;
1087
+ const c = root.getAbsolutePosition();
1088
+ const up = BABYLON.Vector3.TransformNormal(BABYLON.Axis.Y, root.getWorldMatrix()).normalize();
1089
+ return {
1090
+ ...ring,
1091
+ center: { x: c.x, y: c.y, z: c.z },
1092
+ radius: this.radius * Math.abs(root.absoluteScaling.x),
1093
+ normal: { x: up.x, y: up.y, z: up.z },
1094
+ };
1095
+ }
949
1096
  buildRings() {
1097
+ this._ring = null;
950
1098
  if (this.owner == null || this.rootNode == null)
951
1099
  return;
952
1100
  const attrs = this;
@@ -956,27 +1104,34 @@ export class B3dPlanet extends B3dChild {
956
1104
  const scene = this.owner.scene;
957
1105
  const radius = attrs.radius;
958
1106
  // Register ring shader once
959
- if (!BABYLON.Effect.ShadersStore['planetRing6VertexShader']) {
960
- BABYLON.Effect.ShadersStore['planetRing6VertexShader'] = `
1107
+ if (!BABYLON.Effect.ShadersStore['planetRing7VertexShader']) {
1108
+ BABYLON.Effect.ShadersStore['planetRing7VertexShader'] = `
961
1109
  precision highp float;
962
1110
  attribute vec3 position;
963
1111
  attribute vec2 uv;
1112
+ uniform mat4 world;
964
1113
  uniform mat4 worldViewProjection;
965
1114
  varying vec2 vUV;
1115
+ varying vec3 vWorld;
966
1116
  void main() {
967
1117
  gl_Position = worldViewProjection * vec4(position, 1.0);
1118
+ vWorld = (world * vec4(position, 1.0)).xyz;
968
1119
  vUV = uv;
969
1120
  }
970
1121
  `;
971
- BABYLON.Effect.ShadersStore['planetRing6FragmentShader'] = `
1122
+ BABYLON.Effect.ShadersStore['planetRing7FragmentShader'] = `
972
1123
  #extension GL_OES_standard_derivatives : enable
973
1124
  precision highp float;
974
1125
  varying vec2 vUV;
1126
+ varying vec3 vWorld;
1127
+ uniform vec4 ringLight;
1128
+ uniform vec4 ringPlanet;
975
1129
  uniform vec3 ringColor;
976
1130
  uniform float ringOpacity;
977
1131
  uniform float ringInner;
978
1132
  uniform float seed;
979
1133
 
1134
+ ${RING_GLSL}
980
1135
  float hash(float p) {
981
1136
  return fract(sin(p * 127.1) * 43758.5453);
982
1137
  }
@@ -986,49 +1141,13 @@ export class B3dPlanet extends B3dChild {
986
1141
  vec2 uv = vUV * 2.0 - 1.0;
987
1142
  float r = length(uv);
988
1143
  if (r < ringInner || r > 1.0) discard;
989
-
990
- // Normalize to ring band
991
1144
  float t = (r - ringInner) / (1.0 - ringInner);
992
-
993
- // SHARP, as rings are, and irregular: the density along the
994
- // radius is 1D noise at several scales, TERRACED, so it holds
995
- // plateaus of different widths and steps between them with edges
996
- // about a pixel wide. Only detail finer than a pixel fades to its
997
- // average (drawn, it shimmered into moire and flickered).
998
1145
  float px = max(fwidth(t), 1e-5);
999
1146
  float theta = atan(uv.y, uv.x);
1000
-
1001
- // A shepherd moon in the narrow gap: the edges either side of it
1002
- // ripple round the ring, fading away from the gap. (The integer
1003
- // count keeps the ripple whole across atan's jump.)
1004
- float gapB = hash(seed + 1.0) * 0.15 + 0.8;
1005
- float near = abs(t - gapB);
1006
- t += 0.004 * sin(theta * 36.0) * exp(-near / 0.02);
1007
-
1008
- float n = 0.0;
1009
- float a = 0.5;
1010
- float f = 6.0;
1011
- float norm = 0.0;
1012
- for (float i = 0.0; i < 5.0; i++) {
1013
- float x = t * f + seed * 13.1 + i * 7.3;
1014
- float c = floor(x);
1015
- float k = fract(x);
1016
- float vn = mix(hash(c), hash(c + 1.0), k * k * (3.0 - 2.0 * k));
1017
- float fade = 1.0 - smoothstep(0.3, 0.6, f * px);
1018
- n += a * fade * (vn - 0.5);
1019
- norm += a;
1020
- a *= 0.55;
1021
- f *= 2.7;
1022
- }
1023
- n = clamp(n / norm * 1.6 + 0.5, 0.0, 1.0);
1024
- float steps = n * 6.0;
1025
- float sw = max(fwidth(steps), 1e-4);
1026
- float terrace = (floor(steps) + smoothstep(1.0 - sw, 1.0, fract(steps))) / 6.0;
1027
- float density = mix(n, terrace, 0.75);
1028
-
1029
- // The broad profile: faint inside, brightest a little out.
1030
- float profile = 0.35 + 0.65 * sin(3.1416 * pow(t, 0.75));
1031
- float bands = profile * (0.3 + 1.1 * density) * 1.3;
1147
+ // The ring itself is one function, shared with the planet that
1148
+ // reads its shadow (gas-giant RING_GLSL).
1149
+ vec2 ring = b3dRing(t, theta, seed, px);
1150
+ float bands = ring.x;
1032
1151
 
1033
1152
  // Imperfect round the ring: a faint clumping by angle and radius.
1034
1153
  vec2 cq = uv * 9.0 + seed;
@@ -1040,18 +1159,22 @@ export class B3dPlanet extends B3dChild {
1040
1159
  float h01 = hash(ci.x + (ci.y + 1.0) * 57.0);
1041
1160
  float h11 = hash(ci.x + 1.0 + (ci.y + 1.0) * 57.0);
1042
1161
  float clump = mix(mix(h00, h10, cf.x), mix(h01, h11, cf.x), cf.y);
1043
- bands *= 0.95 + 0.1 * clump;
1044
-
1045
- // Gaps: the wide one (Cassini's) and the moon's narrow one,
1046
- // cut clean, a pixel soft.
1047
- float g1 = abs(t - hash(seed) * 0.4 - 0.45);
1048
- float gap1 = smoothstep(0.022 - px, 0.022 + px, g1);
1049
- float gap2 = smoothstep(0.005 - px, 0.005 + px, abs(t - gapB));
1050
- bands *= mix(0.04, 1.0, gap1) * mix(0.06, 1.0, gap2);
1162
+ float k = 0.95 + 0.1 * clump;
1163
+ bands *= k;
1164
+ float alpha = clamp(ring.y * k, 0.0, 1.0) * ringOpacity;
1051
1165
 
1052
- // Edges: crisp, a few pixels at most.
1053
- float edgeFade = smoothstep(0.0, max(0.015, px * 2.0), t) * smoothstep(1.0, 1.0 - max(0.01, px * 2.0), t);
1054
- float alpha = clamp(bands, 0.0, 1.0) * edgeFade * ringOpacity;
1166
+ /*
1167
+ THE PLANET'S SHADOW ON THE RING (#3320): the dark wedge behind
1168
+ the planet. From this point toward the star, does the ray pass
1169
+ through the planet? A sphere, so it is exact, softened over a
1170
+ couple of percent of the radius for the penumbra.
1171
+ */
1172
+ vec3 toL = ringLight.w > 0.5 ? normalize(ringLight.xyz - vWorld) : normalize(ringLight.xyz);
1173
+ vec3 oc = ringPlanet.xyz - vWorld;
1174
+ float along = dot(oc, toL);
1175
+ float miss = length(oc - toL * along);
1176
+ float lit = along > 0.0 ? smoothstep(ringPlanet.w * 0.97, ringPlanet.w * 1.03, miss) : 1.0;
1177
+ bands *= mix(0.03, 1.0, lit);
1055
1178
 
1056
1179
  gl_FragColor = vec4(ringColor * bands, alpha);
1057
1180
  }
@@ -1080,10 +1203,13 @@ export class B3dPlanet extends B3dChild {
1080
1203
  mesh.rotation.x = Math.PI / 2; // Flat in XZ plane
1081
1204
  // In the equator, as rings are. It used to be tipped off it, and as
1082
1205
  // the planet turned (the ring is its child) the ring precessed.
1083
- const mat = new BABYLON.ShaderMaterial('planet-ring-mat', scene, { vertex: 'planetRing6', fragment: 'planetRing6' }, {
1206
+ const mat = new BABYLON.ShaderMaterial('planet-ring-mat', scene, { vertex: 'planetRing7', fragment: 'planetRing7' }, {
1084
1207
  attributes: ['position', 'uv'],
1085
1208
  uniforms: [
1209
+ 'world',
1086
1210
  'worldViewProjection',
1211
+ 'ringLight',
1212
+ 'ringPlanet',
1087
1213
  'ringColor',
1088
1214
  'ringOpacity',
1089
1215
  'ringInner',
@@ -1095,8 +1221,27 @@ export class B3dPlanet extends B3dChild {
1095
1221
  mat.setFloat('ringOpacity', Math.max(0, Math.min(1, opacity)));
1096
1222
  mat.setFloat('ringInner', innerRadii / outerRadii);
1097
1223
  mat.setFloat('seed', attrs.seed % 100);
1224
+ this._ring = {
1225
+ inner: innerRadii,
1226
+ outer: outerRadii,
1227
+ opacity: Math.max(0, Math.min(1, opacity)),
1228
+ seed: attrs.seed % 100,
1229
+ };
1098
1230
  mat.backFaceCulling = false;
1099
1231
  mat.alphaMode = BABYLON.Constants.ALPHA_COMBINE;
1232
+ // Where the star and the planet are, each frame, for the shadow.
1233
+ const light = new BABYLON.Vector4(0, 1, 0, 0);
1234
+ const planet = new BABYLON.Vector4();
1235
+ mat.onBind = () => {
1236
+ sceneLight(scene, light);
1237
+ mat.setVector4('ringLight', light);
1238
+ const root = this.rootNode;
1239
+ if (root != null) {
1240
+ const c = root.getAbsolutePosition();
1241
+ planet.set(c.x, c.y, c.z, radius * Math.abs(root.absoluteScaling.x));
1242
+ }
1243
+ mat.setVector4('ringPlanet', planet);
1244
+ };
1100
1245
  mesh.material = mat;
1101
1246
  mesh.parent = this.rootNode;
1102
1247
  this.ringMesh = mesh;