tosijs-3d 0.10.2 → 0.10.3
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/CHANGELOG.md +54 -0
- package/dist/b3d-planet.d.ts +47 -1
- package/dist/b3d-planet.d.ts.map +1 -1
- package/dist/b3d-planet.js +202 -49
- package/dist/b3d-planet.js.map +1 -1
- package/dist/b3d-terrain.d.ts +30 -0
- package/dist/b3d-terrain.d.ts.map +1 -1
- package/dist/b3d-terrain.js +48 -0
- package/dist/b3d-terrain.js.map +1 -1
- package/dist/biome-plugin.d.ts +2 -1
- package/dist/biome-plugin.d.ts.map +1 -1
- package/dist/biome-plugin.js +46 -12
- package/dist/biome-plugin.js.map +1 -1
- package/dist/gas-giant-material.d.ts +2 -0
- package/dist/gas-giant-material.d.ts.map +1 -1
- package/dist/gas-giant-material.js +1 -1
- package/dist/gas-giant-material.js.map +1 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/planet-clouds.d.ts +34 -0
- package/dist/planet-clouds.d.ts.map +1 -0
- package/dist/planet-clouds.js +280 -0
- package/dist/planet-clouds.js.map +1 -0
- package/dist/planet-map-material.d.ts +4 -0
- package/dist/planet-map-material.d.ts.map +1 -0
- package/dist/planet-map-material.js +74 -0
- package/dist/planet-map-material.js.map +1 -0
- package/dist/planet-map.d.ts +35 -0
- package/dist/planet-map.d.ts.map +1 -0
- package/dist/planet-map.js +271 -0
- package/dist/planet-map.js.map +1 -0
- package/dist/sphere-map.d.ts +74 -0
- package/dist/sphere-map.d.ts.map +1 -0
- package/dist/sphere-map.js +208 -0
- package/dist/sphere-map.js.map +1 -0
- package/package.json +1 -1
- package/static/attributes.json +51 -1
- package/static/attributes.txt +14 -1
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,60 @@ 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.3
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- **One world, two views.** A terrain in `surfaceType="sphere"` can hand out
|
|
14
|
+
its own surface as a globe (`terrain.planetSurface()`), and
|
|
15
|
+
`<tosi-b3d-planet>` draws from it (`planet.surface`, with `relief` to
|
|
16
|
+
exaggerate the heights). The planet you see from orbit is then the ground
|
|
17
|
+
you land on: the same height function, checked to agree to the centimetre.
|
|
18
|
+
- **Places.** `terrain.latLonAt(x, z)` and `terrain.groundAt(lat, lon)`, and
|
|
19
|
+
the pure `sphere-map` module behind them (ground, latitude and longitude,
|
|
20
|
+
and direction on the globe, each from the others), so a pin on the globe is
|
|
21
|
+
a spot on the ground. On the ground +x is east and +z is north.
|
|
22
|
+
- **Real worlds.** `<tosi-b3d-planet heightMap colorMap>` draws a planet
|
|
23
|
+
from its maps: the Moon, Mars and the Earth from NASA's and NOAA's data on
|
|
24
|
+
the CDN (`nasa/moon`, `nasa/mars`, `nasa/earth`, `noaa/earth`). `relief`
|
|
25
|
+
exaggerates the heights, `flatSea` fills the oceans level, `bodyRadiusKm`
|
|
26
|
+
is the real body's radius. The new `planet-map` module reads the 16-bit
|
|
27
|
+
height PNGs itself (a browser's own decoding keeps eight bits):
|
|
28
|
+
`loadPlanetMap`, `decodeGray16Png`, `mapSurface`.
|
|
29
|
+
- **Weather from orbit.** `<tosi-b3d-planet clouds="0.5">` puts a shell of
|
|
30
|
+
cloud inside the air: one coverage dial, winds that reverse with latitude,
|
|
31
|
+
more cloud along the equator and the storm tracks, and a shadow on the
|
|
32
|
+
ground beneath. `cloudColor`, `cloudSpeed`. `planet.storms` places up to
|
|
33
|
+
four cyclones by latitude and longitude, each with an eye, turning the
|
|
34
|
+
right way for its hemisphere, with lightning inside. Provisional: this is
|
|
35
|
+
the weather from orbit only, its own field and its own storms; it does not
|
|
36
|
+
yet agree with the cloud deck and weather cells on that world's ground.
|
|
37
|
+
- `planet.rootNode` is public, for placing a planet.
|
|
38
|
+
|
|
39
|
+
### Fixed
|
|
40
|
+
|
|
41
|
+
- **(behaviour) Planets turn west to east.** `rotationSpeed` spun them the
|
|
42
|
+
other way, so the Earth ran backwards. A negative `rotationSpeed` turns a
|
|
43
|
+
planet the old way (it used to be ignored).
|
|
44
|
+
- **A planet that has been moved is shaded where it is.** The biome shader
|
|
45
|
+
measured altitude and latitude from the world origin.
|
|
46
|
+
- **A rotating planet's land no longer shimmers.** The biome shader read its
|
|
47
|
+
noise by world position, so a planet turned under its own climate. On a
|
|
48
|
+
planet it now works in the planet's own frame, and reads its climate noise
|
|
49
|
+
in 3D (it was smeared down the sides of the sphere).
|
|
50
|
+
- **Volcanism on a planet is planet-sized.** The volcanic patterns were sized
|
|
51
|
+
in metres of terrain, so on a globe the regions were one value everywhere
|
|
52
|
+
(the dial flipped the whole planet) and the plates were a ninth of its
|
|
53
|
+
width. They scale with the planet now: regions about a twentieth of the
|
|
54
|
+
way round, plates you can see.
|
|
55
|
+
- **A planet's air no longer pinches at the poles.** Its clouds were 2D noise
|
|
56
|
+
over latitude and longitude, which converges to a point at each pole and has
|
|
57
|
+
a seam down the back. They are 3D noise read on the sphere itself now, and
|
|
58
|
+
in the planet's own frame, so they turn with a rotating planet where they
|
|
59
|
+
used to stand still while it turned beneath them.
|
|
60
|
+
|
|
61
|
+
Size: barrel (min, packages external) 1005.5 KB raw / 340.6 KB gz, +1.8% on 0.10.2.
|
|
62
|
+
|
|
9
63
|
## 0.10.2
|
|
10
64
|
|
|
11
65
|
### Added
|
package/dist/b3d-planet.d.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { B3dChild } from './b3d-utils.js';
|
|
2
2
|
import { BiomePlugin } from './biome-plugin.js';
|
|
3
|
+
import type { PlanetSurface } from './sphere-map.js';
|
|
4
|
+
import { type PlanetStorm } from './planet-clouds.js';
|
|
3
5
|
import * as BABYLON from '@babylonjs/core';
|
|
4
6
|
import type { B3d } from './tosi-b3d.js';
|
|
5
7
|
import type { GradientFilter } from './gradient-filter.js';
|
|
@@ -13,6 +15,11 @@ export declare class B3dPlanet extends B3dChild {
|
|
|
13
15
|
static initAttributes: {
|
|
14
16
|
biome: "on" | "off";
|
|
15
17
|
gas: string;
|
|
18
|
+
heightMap: string;
|
|
19
|
+
colorMap: string;
|
|
20
|
+
bodyRadiusKm: number;
|
|
21
|
+
flatSea: "on" | "off";
|
|
22
|
+
relief: number;
|
|
16
23
|
bands: number;
|
|
17
24
|
turbulence: number;
|
|
18
25
|
storm: number;
|
|
@@ -31,22 +38,44 @@ export declare class B3dPlanet extends B3dChild {
|
|
|
31
38
|
atmosphere: number;
|
|
32
39
|
atmosphereColor: string;
|
|
33
40
|
atmosphereTurbulence: number;
|
|
41
|
+
clouds: number;
|
|
42
|
+
cloudColor: string;
|
|
43
|
+
cloudSpeed: number;
|
|
34
44
|
ocean: number;
|
|
35
45
|
rings: number;
|
|
36
46
|
wireframe: boolean;
|
|
37
47
|
rotationSpeed: number;
|
|
38
48
|
};
|
|
39
49
|
owner: B3d | null;
|
|
50
|
+
/**
|
|
51
|
+
* ANOTHER WORLD'S SURFACE, to draw this planet from: set it to a terrain's
|
|
52
|
+
* `planetSurface()` and the globe is that terrain seen from orbit (then
|
|
53
|
+
* `regenerate()`). Its heights are true metres on a planet of its own
|
|
54
|
+
* radius; `relief` says how much to exaggerate them, because real relief
|
|
55
|
+
* is invisible at this size. With a surface set, the planet's own noise
|
|
56
|
+
* settings are unused and the sea sits at the surface's sea level.
|
|
57
|
+
*/
|
|
58
|
+
surface: PlanetSurface | null;
|
|
59
|
+
/**
|
|
60
|
+
* STORMS placed on the weather (needs `clouds` above 0): up to four, each
|
|
61
|
+
* `{ lat, lon, size, intensity, lightning }`. A storm is a cyclone that
|
|
62
|
+
* turns the right way for its hemisphere, with an eye, and lightning
|
|
63
|
+
* flickering inside it, which on the night side is all you see of it.
|
|
64
|
+
* Read every frame, so move one by changing its `lat` and `lon`.
|
|
65
|
+
*/
|
|
66
|
+
storms: PlanetStorm[];
|
|
40
67
|
grossFilter: GradientFilter;
|
|
41
68
|
detailFilter: GradientFilter;
|
|
42
69
|
private noise;
|
|
43
70
|
private planetMesh;
|
|
44
71
|
private atmosphereMesh;
|
|
45
72
|
private oceanMesh;
|
|
73
|
+
private cloudMesh;
|
|
46
74
|
/** Live-tunable biome shader parameters (biome="on") — see biome-plugin. */
|
|
47
75
|
biomePlugin: BiomePlugin | null;
|
|
48
76
|
private ringMesh;
|
|
49
|
-
|
|
77
|
+
/** The node everything of this planet hangs from: move it to place the planet. */
|
|
78
|
+
rootNode: BABYLON.TransformNode | null;
|
|
50
79
|
private registered;
|
|
51
80
|
private _beforeRender;
|
|
52
81
|
private vertexHeights;
|
|
@@ -55,9 +84,26 @@ export declare class B3dPlanet extends B3dChild {
|
|
|
55
84
|
sceneDispose(): void;
|
|
56
85
|
private update;
|
|
57
86
|
private buildPlanet;
|
|
87
|
+
private _heightMapLoaded;
|
|
88
|
+
private _surfaceFromMap;
|
|
89
|
+
/**
|
|
90
|
+
* Fetch `heightMap` (once per URL and sea setting) and rebuild from it. The
|
|
91
|
+
* planet shows as a smooth ball, in its colours, until the heights arrive.
|
|
92
|
+
*/
|
|
93
|
+
private _loadHeightMap;
|
|
94
|
+
/** A vertex's height above the base sphere, in this planet's own units. */
|
|
95
|
+
private _heightFor;
|
|
58
96
|
/** Get the height at a given percentile (0..1) of all vertex heights */
|
|
59
97
|
private heightPercentile;
|
|
60
98
|
private buildAtmosphere;
|
|
99
|
+
/** The highest ground above the base sphere, in this planet's units. */
|
|
100
|
+
private _maxHeight;
|
|
101
|
+
/**
|
|
102
|
+
* The weather, seen from orbit (planet-clouds): a shell just above the
|
|
103
|
+
* highest ground and INSIDE the air shell, which stays outside it as the
|
|
104
|
+
* glow at the limb.
|
|
105
|
+
*/
|
|
106
|
+
private buildClouds;
|
|
61
107
|
private buildOcean;
|
|
62
108
|
private buildRings;
|
|
63
109
|
private heightAt;
|
package/dist/b3d-planet.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"b3d-planet.d.ts","sourceRoot":"","sources":["../src/b3d-planet.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"b3d-planet.d.ts","sourceRoot":"","sources":["../src/b3d-planet.ts"],"names":[],"mappings":"AAmLA,OAAO,EAAS,QAAQ,EAAc,MAAM,gBAAgB,CAAA;AAC5D,OAAO,EAAqB,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAElE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAA;AAEpD,OAAO,EAAsB,KAAK,WAAW,EAAE,MAAM,oBAAoB,CAAA;AAIzE,OAAO,KAAK,OAAO,MAAM,iBAAiB,CAAA;AAC1C,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,eAAe,CAAA;AAGxC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAA;AAoB1D,qBAAa,SAAU,SAAQ,QAAQ;IACrC,MAAM,CAAC,gBAAgB,SAAoB;IAE3C,MAAM,CAAC,eAAe;;;;MAIrB;IAED,MAAM,CAAC,cAAc;eAIH,IAAI,GAAG,KAAK;;;;;iBAaV,IAAI,GAAG,KAAK;;;;;;;;;;;;;;;;;;;;;;;;;;;MAgC/B;IAED,KAAK,EAAE,GAAG,GAAG,IAAI,CAAO;IACxB;;;;;;;OAOG;IACH,OAAO,EAAE,aAAa,GAAG,IAAI,CAAO;IACpC;;;;;;OAMG;IACH,MAAM,EAAE,WAAW,EAAE,CAAK;IAC1B,WAAW,EAAE,cAAc,CAA8B;IACzD,YAAY,EAAE,cAAc,CAA8B;IAE1D,OAAO,CAAC,KAAK,CAAc;IAC3B,OAAO,CAAC,UAAU,CAA4B;IAC9C,OAAO,CAAC,cAAc,CAA4B;IAClD,OAAO,CAAC,SAAS,CAA4B;IAC7C,OAAO,CAAC,SAAS,CAA4B;IAC7C,4EAA4E;IAC5E,WAAW,EAAE,WAAW,GAAG,IAAI,CAAO;IACtC,OAAO,CAAC,QAAQ,CAA4B;IAC5C,kFAAkF;IAClF,QAAQ,EAAE,OAAO,CAAC,aAAa,GAAG,IAAI,CAAO;IAC7C,OAAO,CAAC,UAAU,CAAQ;IAC1B,OAAO,CAAC,aAAa,CAA4B;IACjD,OAAO,CAAC,aAAa,CAA4B;IAEjD,OAAO,eAAW;IAElB,UAAU,CAAC,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK;IAkB3C,YAAY;IAgBZ,OAAO,CAAC,MAAM;IAed,OAAO,CAAC,WAAW;IA8KnB,OAAO,CAAC,gBAAgB,CAAK;IAC7B,OAAO,CAAC,eAAe,CAAQ;IAC/B;;;OAGG;IACH,OAAO,CAAC,cAAc;IA4BtB,2EAA2E;IAC3E,OAAO,CAAC,UAAU;IAYlB,wEAAwE;IACxE,OAAO,CAAC,gBAAgB;IAOxB,OAAO,CAAC,eAAe;IA+HvB,wEAAwE;IACxE,OAAO,CAAC,UAAU;IASlB;;;;OAIG;IACH,OAAO,CAAC,WAAW;IA+BnB,OAAO,CAAC,UAAU;IA6BlB,OAAO,CAAC,UAAU;IAgIlB,OAAO,CAAC,QAAQ;IAqBhB,sDAAsD;IACtD,UAAU;IA2BV,iDAAiD;IACjD,aAAa;CAqBd;AAED,eAAO,MAAM,SAAS,4CAA6B,CAAA"}
|
package/dist/b3d-planet.js
CHANGED
|
@@ -22,6 +22,7 @@ const { demo } = tosi({
|
|
|
22
22
|
grossAmplitude: 5,
|
|
23
23
|
detailAmplitude: 1,
|
|
24
24
|
atmosphere: 0.08,
|
|
25
|
+
clouds: 0.45,
|
|
25
26
|
ocean: 0.6,
|
|
26
27
|
wireframe: false,
|
|
27
28
|
rotationSpeed: 0.05,
|
|
@@ -39,6 +40,7 @@ const planet = b3dPlanet({
|
|
|
39
40
|
grossAmplitude: demo.grossAmplitude,
|
|
40
41
|
detailAmplitude: demo.detailAmplitude,
|
|
41
42
|
atmosphere: demo.atmosphere,
|
|
43
|
+
clouds: demo.clouds,
|
|
42
44
|
ocean: demo.ocean,
|
|
43
45
|
wireframe: demo.wireframe,
|
|
44
46
|
rotationSpeed: demo.rotationSpeed,
|
|
@@ -55,6 +57,7 @@ const scene = b3d(
|
|
|
55
57
|
slider3d({ label: 'detail amp', value: demo.detailAmplitude, min: 0, max: 5, step: 0.1 }),
|
|
56
58
|
slider3d({ label: 'rotation', value: demo.rotationSpeed, min: 0, max: 0.5, step: 0.01 }),
|
|
57
59
|
slider3d({ label: 'atmosphere', value: demo.atmosphere, min: 0, max: 0.2, step: 0.01 }),
|
|
60
|
+
slider3d({ label: 'clouds', value: demo.clouds, min: 0, max: 1, step: 0.05 }),
|
|
58
61
|
slider3d({ label: 'ocean', value: demo.ocean, min: 0, max: 1, step: 0.05 }),
|
|
59
62
|
toggle3d({ label: 'wireframe', value: demo.wireframe }),
|
|
60
63
|
// The biome climate, in the terms you think in: temperature at the
|
|
@@ -110,7 +113,7 @@ for (const key of ['grossScale', 'detailScale', 'grossAmplitude', 'detailAmplitu
|
|
|
110
113
|
planet.regenerate()
|
|
111
114
|
})
|
|
112
115
|
}
|
|
113
|
-
for (const key of ['atmosphere', 'ocean', 'wireframe', 'rotationSpeed']) {
|
|
116
|
+
for (const key of ['atmosphere', 'clouds', 'ocean', 'wireframe', 'rotationSpeed']) {
|
|
114
117
|
demo[key].observe(() => {
|
|
115
118
|
planet.updateOptions()
|
|
116
119
|
})
|
|
@@ -142,6 +145,11 @@ tosi-b3d {
|
|
|
142
145
|
| --- | --- | --- |
|
|
143
146
|
| `seed` | `12345` | Noise seed |
|
|
144
147
|
| `radius` | `50` | Base sphere radius |
|
|
148
|
+
| `heightMap` | `''` | A real world's HEIGHTS: a 16-bit greyscale PNG, 2:1, `metres = value − 32768` (see [planet-map](/planet-map/)). A full URL, or a path resolved by `assetUrl`: call `setAssetBase('https://cdn.tosijs.net')` once and `nasa/moon/height-2k.png`, `nasa/mars/…` and `noaa/earth/…` are there. Sets `surface` when it has loaded. Those maps are NASA's and NOAA's and free to use with credit, which is yours to show |
|
|
149
|
+
| `bodyRadiusKm` | `0` | The real body's radius in km, which is what the heights are a fraction of (Moon 1737.4, Mars 3389.5, Earth 6371). `0` reads the height map's `body-radius-km` header, which a browser only shows to a page on the same origin unless the server exposes it: set this for a map from another host |
|
|
150
|
+
| `colorMap` | `''` | A real world's COLOUR: a 2:1 image, longitude −180° to 180° left to right. `nasa/moon/color-2k.jpg` and so on. Replaces the biome shading and the sea sphere |
|
|
151
|
+
| `flatSea` | `'off'` | `'on'` fills everything below 0 m level, for a world whose colour map shows water (the Earth) |
|
|
152
|
+
| `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 |
|
|
145
153
|
| `gas` | `''` | Make it a GAS GIANT: `'jupiter'`, `'saturn'`, `'uranus'` or `'neptune'`. A smooth banded sphere with winds and a storm in place of terrain, sea and air shell; `seed` gives a different planet of the same kind and `rings` still applies. See [gas-giant](/gas-giant/) |
|
|
146
154
|
| `bands` | `0` | Gas giant: stripes from pole to pole. `0` = the preset's |
|
|
147
155
|
| `turbulence` | `-1` | Gas giant: how torn the band edges are, `0…1`. `-1` = the preset's |
|
|
@@ -157,16 +165,24 @@ tosi-b3d {
|
|
|
157
165
|
| `grossAmplitude` | `5` | Gross height multiplier |
|
|
158
166
|
| `detailAmplitude` | `1` | Detail height multiplier |
|
|
159
167
|
| `atmosphere` | `0.08` | Atmosphere thickness (fraction of radius, 0=none) |
|
|
168
|
+
| `clouds` | `0` | (Provisional: this is the weather from ORBIT only. It is its own field, and does not yet agree with the cloud deck or the weather cells on that world's ground.) Weather seen from orbit: how much of the sky is cloud, `0…1`. A shell inside the air, with winds that reverse with latitude, more cloud along the equator and the storm tracks, and a shadow on the ground beneath. `0` = none |
|
|
169
|
+
| `cloudColor` | `'#ffffff'` | The cloud's colour where lit |
|
|
170
|
+
| `storms` (property) | `[]` | Storms placed on the weather, up to four: `planet.storms = [{ lat: 22, lon: -60, size: 8, intensity: 1, lightning: 1 }]`. Each is a cyclone with an eye, turning the right way for its hemisphere, with lightning inside; `cyclone: 0` makes it a thunderstorm complex that sits and flashes. Needs `clouds` above 0. Provisional: a storm here is not yet a weather cell on the ground, and the shape of this may change when it is |
|
|
171
|
+
| `cloudSpeed` | `1` | How fast the weather moves. `0` holds it still |
|
|
160
172
|
| `ocean` | `0.6` | Ocean coverage (0=none, 0.6=60% of surface underwater) |
|
|
161
173
|
| `wireframe` | `false` | Debug: render as wireframe |
|
|
162
|
-
| `rotationSpeed` | `0` | Auto-rotation speed (radians/sec) |
|
|
174
|
+
| `rotationSpeed` | `0` | Auto-rotation speed (radians/sec), west to east as planets turn. Negative turns it the other way |
|
|
163
175
|
|
|
164
176
|
*/
|
|
165
177
|
/*{ "parent": "space", "order": 40 }*/
|
|
166
178
|
import { Color } from 'tosijs';
|
|
167
179
|
import { isOff, B3dChild, sceneDelta } from './b3d-utils.js';
|
|
168
180
|
import { attachBiomePlugin } from './biome-plugin.js';
|
|
169
|
-
import { gasGiantRgb, resolveGasGiant } from './gas-giant.js';
|
|
181
|
+
import { GAS_GIANT_GLSL, gasGiantRgb, resolveGasGiant } from './gas-giant.js';
|
|
182
|
+
import { loadPlanetMap } from './planet-map.js';
|
|
183
|
+
import { createPlanetClouds } from './planet-clouds.js';
|
|
184
|
+
import { createMappedPlanetMaterial } from './planet-map-material.js';
|
|
185
|
+
import { assetUrl } from './asset-url.js';
|
|
170
186
|
import { createGasGiantMaterial } from './gas-giant-material.js';
|
|
171
187
|
import * as BABYLON from '@babylonjs/core';
|
|
172
188
|
import { PerlinNoise } from './perlin-noise.js';
|
|
@@ -203,6 +219,19 @@ export class B3dPlanet extends B3dChild {
|
|
|
203
219
|
// A gas giant: 'jupiter' | 'saturn' | 'uranus' | 'neptune'. A smooth,
|
|
204
220
|
// banded sphere in place of terrain and sea (see gas-giant).
|
|
205
221
|
gas: '',
|
|
222
|
+
// A real world from its maps (planet-map): a 16-bit height PNG and an
|
|
223
|
+
// equirectangular colour image. Paths resolve through `assetUrl`.
|
|
224
|
+
heightMap: '',
|
|
225
|
+
colorMap: '',
|
|
226
|
+
// The real body's radius, km: what the heights are measured against.
|
|
227
|
+
// 0 = the height map's own `body-radius-km` header, where it can be read.
|
|
228
|
+
bodyRadiusKm: 0,
|
|
229
|
+
// Fill the seas level (heights below 0 read as 0): for a world whose
|
|
230
|
+
// colour map shows water.
|
|
231
|
+
flatSea: 'off',
|
|
232
|
+
// With `surface` set: how much its true relief is exaggerated (1 = to
|
|
233
|
+
// scale, which on a globe is flat to the eye).
|
|
234
|
+
relief: 1,
|
|
206
235
|
// Gas giant overrides; 0 / -1 / '' mean the preset's own.
|
|
207
236
|
bands: 0,
|
|
208
237
|
turbulence: -1,
|
|
@@ -223,21 +252,44 @@ export class B3dPlanet extends B3dChild {
|
|
|
223
252
|
atmosphere: 0.08,
|
|
224
253
|
atmosphereColor: 'rgba(77,128,230,0.15)',
|
|
225
254
|
atmosphereTurbulence: 0.5,
|
|
255
|
+
// Weather seen from orbit: how much of the sky is cloud, 0 = none.
|
|
256
|
+
clouds: 0,
|
|
257
|
+
cloudColor: '#ffffff',
|
|
258
|
+
cloudSpeed: 1,
|
|
226
259
|
ocean: 0.6,
|
|
227
260
|
rings: 0,
|
|
228
261
|
wireframe: false,
|
|
229
262
|
rotationSpeed: 0,
|
|
230
263
|
};
|
|
231
264
|
owner = null;
|
|
265
|
+
/**
|
|
266
|
+
* ANOTHER WORLD'S SURFACE, to draw this planet from: set it to a terrain's
|
|
267
|
+
* `planetSurface()` and the globe is that terrain seen from orbit (then
|
|
268
|
+
* `regenerate()`). Its heights are true metres on a planet of its own
|
|
269
|
+
* radius; `relief` says how much to exaggerate them, because real relief
|
|
270
|
+
* is invisible at this size. With a surface set, the planet's own noise
|
|
271
|
+
* settings are unused and the sea sits at the surface's sea level.
|
|
272
|
+
*/
|
|
273
|
+
surface = null;
|
|
274
|
+
/**
|
|
275
|
+
* STORMS placed on the weather (needs `clouds` above 0): up to four, each
|
|
276
|
+
* `{ lat, lon, size, intensity, lightning }`. A storm is a cyclone that
|
|
277
|
+
* turns the right way for its hemisphere, with an eye, and lightning
|
|
278
|
+
* flickering inside it, which on the night side is all you see of it.
|
|
279
|
+
* Read every frame, so move one by changing its `lat` and `lon`.
|
|
280
|
+
*/
|
|
281
|
+
storms = [];
|
|
232
282
|
grossFilter = new PiecewiseLinearFilter();
|
|
233
283
|
detailFilter = new PiecewiseLinearFilter();
|
|
234
284
|
noise;
|
|
235
285
|
planetMesh = null;
|
|
236
286
|
atmosphereMesh = null;
|
|
237
287
|
oceanMesh = null;
|
|
288
|
+
cloudMesh = null;
|
|
238
289
|
/** Live-tunable biome shader parameters (biome="on") — see biome-plugin. */
|
|
239
290
|
biomePlugin = null;
|
|
240
291
|
ringMesh = null;
|
|
292
|
+
/** The node everything of this planet hangs from: move it to place the planet. */
|
|
241
293
|
rootNode = null;
|
|
242
294
|
registered = false;
|
|
243
295
|
_beforeRender = null;
|
|
@@ -248,9 +300,11 @@ export class B3dPlanet extends B3dChild {
|
|
|
248
300
|
const attrs = this;
|
|
249
301
|
this.noise = new PerlinNoise(attrs.seed);
|
|
250
302
|
this.rootNode = new BABYLON.TransformNode('planet-root', scene);
|
|
303
|
+
this._loadHeightMap();
|
|
251
304
|
this.buildPlanet();
|
|
252
305
|
this.buildAtmosphere();
|
|
253
306
|
this.buildOcean();
|
|
307
|
+
this.buildClouds();
|
|
254
308
|
this.buildRings();
|
|
255
309
|
this._beforeRender = () => this.update();
|
|
256
310
|
scene.registerBeforeRender(this._beforeRender);
|
|
@@ -264,7 +318,10 @@ export class B3dPlanet extends B3dChild {
|
|
|
264
318
|
this.planetMesh = null;
|
|
265
319
|
this.atmosphereMesh = null;
|
|
266
320
|
this.oceanMesh = null;
|
|
321
|
+
this.cloudMesh = null;
|
|
267
322
|
this.ringMesh = null;
|
|
323
|
+
// So a planet put back in a scene fetches its heights again.
|
|
324
|
+
this._heightMapLoaded = '';
|
|
268
325
|
this.owner = null;
|
|
269
326
|
}
|
|
270
327
|
update() {
|
|
@@ -272,9 +329,14 @@ export class B3dPlanet extends B3dChild {
|
|
|
272
329
|
return;
|
|
273
330
|
const attrs = this;
|
|
274
331
|
const speed = attrs.rotationSpeed;
|
|
275
|
-
|
|
332
|
+
// Negative runs it the other way (the direction it had before 0.10.3).
|
|
333
|
+
if (speed !== 0) {
|
|
276
334
|
const dt = sceneDelta(this.owner.scene);
|
|
277
|
-
|
|
335
|
+
// WEST TO EAST, as planets turn: seen from above the north pole,
|
|
336
|
+
// anticlockwise. East is +longitude, toward +Z from +X, and in
|
|
337
|
+
// Babylon's left-handed frame that is a NEGATIVE turn about Y. (It was
|
|
338
|
+
// positive, and the Earth ran backwards.)
|
|
339
|
+
this.rootNode.rotation.y -= speed * dt;
|
|
278
340
|
}
|
|
279
341
|
}
|
|
280
342
|
buildPlanet() {
|
|
@@ -331,7 +393,7 @@ export class B3dPlanet extends B3dChild {
|
|
|
331
393
|
const ny = cy / len;
|
|
332
394
|
const nz = cz / len;
|
|
333
395
|
// Height from noise
|
|
334
|
-
const h = this.
|
|
396
|
+
const h = this._heightFor(nx, ny, nz);
|
|
335
397
|
heights[vertOffset] = h;
|
|
336
398
|
const r = radius + h;
|
|
337
399
|
const vi = vertOffset * 3;
|
|
@@ -364,6 +426,21 @@ export class B3dPlanet extends B3dChild {
|
|
|
364
426
|
vertexData.indices = indices;
|
|
365
427
|
vertexData.normals = normals;
|
|
366
428
|
vertexData.applyToMesh(mesh, true);
|
|
429
|
+
if (attrs.colorMap) {
|
|
430
|
+
// A REAL WORLD'S COLOUR: the map, looked up by direction (planet-map).
|
|
431
|
+
const mapped = createMappedPlanetMaterial(this.owner.scene, assetUrl(String(attrs.colorMap)));
|
|
432
|
+
mapped.wireframe = attrs.wireframe;
|
|
433
|
+
mesh.material = mapped;
|
|
434
|
+
mesh.parent = this.rootNode;
|
|
435
|
+
this.planetMesh = mesh;
|
|
436
|
+
this.vertexHeights = heights;
|
|
437
|
+
this.biomePlugin = null;
|
|
438
|
+
if (!this.registered) {
|
|
439
|
+
this.registered = true;
|
|
440
|
+
this.owner.register({ meshes: [mesh] });
|
|
441
|
+
}
|
|
442
|
+
return;
|
|
443
|
+
}
|
|
367
444
|
const mat = new BABYLON.StandardMaterial('planet-mat', this.owner.scene);
|
|
368
445
|
mat.diffuseColor = new BABYLON.Color3(0.6, 0.75, 0.45);
|
|
369
446
|
mat.specularColor = new BABYLON.Color3(0.05, 0.05, 0.05);
|
|
@@ -376,7 +453,8 @@ export class B3dPlanet extends B3dChild {
|
|
|
376
453
|
// crawls if the planet SPINS — fine for a static planet; a rotating one
|
|
377
454
|
// wants object-space sampling (a step-5 concern with triplanar).
|
|
378
455
|
const seaRadius = attrs.ocean > 0
|
|
379
|
-
? attrs.radius +
|
|
456
|
+
? attrs.radius +
|
|
457
|
+
(this.surface != null ? 0 : this.heightPercentile(attrs.ocean))
|
|
380
458
|
: 0.001;
|
|
381
459
|
this.biomePlugin = attachBiomePlugin(mat, {
|
|
382
460
|
seaRadius,
|
|
@@ -404,6 +482,53 @@ export class B3dPlanet extends B3dChild {
|
|
|
404
482
|
this.owner.register({ meshes: [mesh] });
|
|
405
483
|
}
|
|
406
484
|
}
|
|
485
|
+
_heightMapLoaded = '';
|
|
486
|
+
_surfaceFromMap = false;
|
|
487
|
+
/**
|
|
488
|
+
* Fetch `heightMap` (once per URL and sea setting) and rebuild from it. The
|
|
489
|
+
* planet shows as a smooth ball, in its colours, until the heights arrive.
|
|
490
|
+
*/
|
|
491
|
+
_loadHeightMap() {
|
|
492
|
+
const attrs = this;
|
|
493
|
+
const url = attrs.heightMap ? assetUrl(String(attrs.heightMap)) : '';
|
|
494
|
+
const key = `${url}|${attrs.flatSea}|${attrs.bodyRadiusKm}`;
|
|
495
|
+
if (key === this._heightMapLoaded)
|
|
496
|
+
return;
|
|
497
|
+
// A change of map (or none) drops the heights of the last one: the planet
|
|
498
|
+
// is its own noise, in the new colours, until the new heights arrive.
|
|
499
|
+
if (this._surfaceFromMap) {
|
|
500
|
+
this.surface = null;
|
|
501
|
+
this._surfaceFromMap = false;
|
|
502
|
+
}
|
|
503
|
+
this._heightMapLoaded = key;
|
|
504
|
+
if (url === '')
|
|
505
|
+
return;
|
|
506
|
+
loadPlanetMap(url, {
|
|
507
|
+
...(isOff(attrs.flatSea) ? {} : { floor: 0 }),
|
|
508
|
+
...(attrs.bodyRadiusKm > 0 ? { radius: attrs.bodyRadiusKm * 1000 } : {}),
|
|
509
|
+
})
|
|
510
|
+
.then((surface) => {
|
|
511
|
+
if (this.owner == null || key !== this._heightMapLoaded)
|
|
512
|
+
return;
|
|
513
|
+
this.surface = surface;
|
|
514
|
+
this._surfaceFromMap = true;
|
|
515
|
+
this.regenerate();
|
|
516
|
+
})
|
|
517
|
+
.catch((err) => {
|
|
518
|
+
console.warn(`b3d-planet: could not load heightMap "${url}"`, err);
|
|
519
|
+
});
|
|
520
|
+
}
|
|
521
|
+
/** A vertex's height above the base sphere, in this planet's own units. */
|
|
522
|
+
_heightFor(nx, ny, nz) {
|
|
523
|
+
const s = this.surface;
|
|
524
|
+
if (s == null)
|
|
525
|
+
return this.heightAt(nx, ny, nz);
|
|
526
|
+
const attrs = this;
|
|
527
|
+
const relief = attrs.relief > 0 ? attrs.relief : 1;
|
|
528
|
+
return (((s.heightAt({ x: nx, y: ny, z: nz }) - s.seaLevel) / s.radius) *
|
|
529
|
+
attrs.radius *
|
|
530
|
+
relief);
|
|
531
|
+
}
|
|
407
532
|
/** Get the height at a given percentile (0..1) of all vertex heights */
|
|
408
533
|
heightPercentile(p) {
|
|
409
534
|
if (this.vertexHeights == null)
|
|
@@ -420,9 +545,16 @@ export class B3dPlanet extends B3dChild {
|
|
|
420
545
|
if (atmoFrac <= 0 || attrs.gas)
|
|
421
546
|
return;
|
|
422
547
|
const scene = this.owner.scene;
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
548
|
+
/*
|
|
549
|
+
Register the atmosphere shader once. Its clouds are 3D noise read ON THE
|
|
550
|
+
SPHERE, at the point itself, in the planet's own frame. They used to be
|
|
551
|
+
2D noise over latitude and longitude, which pinches to a point at each
|
|
552
|
+
pole and has a seam down the back; and they were read from the WORLD
|
|
553
|
+
normal, so they stood still while a rotating planet turned under them.
|
|
554
|
+
The winds are the point turned about the axis before the noise is read.
|
|
555
|
+
*/
|
|
556
|
+
if (!BABYLON.Effect.ShadersStore['planetAtmo2VertexShader']) {
|
|
557
|
+
BABYLON.Effect.ShadersStore['planetAtmo2VertexShader'] = `
|
|
426
558
|
precision highp float;
|
|
427
559
|
attribute vec3 position;
|
|
428
560
|
attribute vec3 normal;
|
|
@@ -430,62 +562,36 @@ export class B3dPlanet extends B3dChild {
|
|
|
430
562
|
uniform mat4 world;
|
|
431
563
|
varying vec3 vWorldPos;
|
|
432
564
|
varying vec3 vWorldNormal;
|
|
565
|
+
varying vec3 vBody;
|
|
433
566
|
void main() {
|
|
434
567
|
gl_Position = worldViewProjection * vec4(position, 1.0);
|
|
435
568
|
vWorldPos = (world * vec4(position, 1.0)).xyz;
|
|
436
569
|
vWorldNormal = normalize((world * vec4(normal, 0.0)).xyz);
|
|
570
|
+
vBody = position;
|
|
437
571
|
}
|
|
438
572
|
`;
|
|
439
|
-
BABYLON.Effect.ShadersStore['
|
|
573
|
+
BABYLON.Effect.ShadersStore['planetAtmo2FragmentShader'] = `
|
|
440
574
|
precision highp float;
|
|
441
575
|
varying vec3 vWorldPos;
|
|
442
576
|
varying vec3 vWorldNormal;
|
|
577
|
+
varying vec3 vBody;
|
|
443
578
|
uniform vec3 cameraPosition;
|
|
444
579
|
uniform vec3 atmoColor;
|
|
445
580
|
uniform float atmoOpacity;
|
|
446
581
|
uniform float time;
|
|
447
582
|
uniform float turbulence;
|
|
448
|
-
|
|
449
|
-
float hash(vec2 p) {
|
|
450
|
-
vec3 p3 = fract(vec3(p.xyx) * 0.1031);
|
|
451
|
-
p3 += dot(p3, p3.yzx + 33.33);
|
|
452
|
-
return fract((p3.x + p3.y) * p3.z);
|
|
453
|
-
}
|
|
454
|
-
|
|
455
|
-
float noise(vec2 p) {
|
|
456
|
-
vec2 i = floor(p);
|
|
457
|
-
vec2 f = fract(p);
|
|
458
|
-
f = f * f * (3.0 - 2.0 * f);
|
|
459
|
-
float a = hash(i);
|
|
460
|
-
float b = hash(i + vec2(1.0, 0.0));
|
|
461
|
-
float c = hash(i + vec2(0.0, 1.0));
|
|
462
|
-
float d = hash(i + vec2(1.0, 1.0));
|
|
463
|
-
return mix(mix(a, b, f.x), mix(c, d, f.x), f.y);
|
|
464
|
-
}
|
|
465
|
-
|
|
466
|
-
float fbm(vec2 p) {
|
|
467
|
-
float v = 0.0;
|
|
468
|
-
float amp = 0.5;
|
|
469
|
-
for (int i = 0; i < 4; i++) {
|
|
470
|
-
v += amp * noise(p);
|
|
471
|
-
p *= 2.0;
|
|
472
|
-
amp *= 0.5;
|
|
473
|
-
}
|
|
474
|
-
return v;
|
|
475
|
-
}
|
|
476
|
-
|
|
583
|
+
${GAS_GIANT_GLSL}
|
|
477
584
|
void main() {
|
|
478
585
|
vec3 viewDir = normalize(cameraPosition - vWorldPos);
|
|
479
586
|
float facing = abs(dot(viewDir, vWorldNormal));
|
|
480
587
|
// Transparent at edges (dot→0), visible when facing camera
|
|
481
588
|
float glow = pow(facing, 0.5);
|
|
482
589
|
|
|
483
|
-
// Turbulent cloud patterns
|
|
484
|
-
vec3
|
|
485
|
-
float
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
turb += fbm(vec2(lon - time * 0.01, lat * 1.5 + time * 0.005) * 5.0) * turbulence * 0.5;
|
|
590
|
+
// Turbulent cloud patterns: two layers drifting opposite ways.
|
|
591
|
+
vec3 d = normalize(vBody);
|
|
592
|
+
float turb = b3dGgFbm(b3dGgTurn(d, time * 0.02) * 9.0) * turbulence;
|
|
593
|
+
turb += b3dGgFbm(b3dGgTurn(d, -time * 0.01) * vec3(15.0, 22.0, 15.0)
|
|
594
|
+
+ vec3(0.0, time * 0.025, 3.1)) * turbulence * 0.5;
|
|
489
595
|
|
|
490
596
|
float alpha = glow * atmoOpacity + turb * 0.3;
|
|
491
597
|
alpha = clamp(alpha, 0.0, 1.0);
|
|
@@ -508,7 +614,7 @@ export class B3dPlanet extends B3dChild {
|
|
|
508
614
|
}
|
|
509
615
|
const atmoRadius = attrs.radius + maxH + atmoFrac * attrs.radius;
|
|
510
616
|
const mesh = BABYLON.MeshBuilder.CreateSphere('atmosphere', { diameter: atmoRadius * 2, segments: 32 }, scene);
|
|
511
|
-
const mat = new BABYLON.ShaderMaterial('atmo-shader-mat', scene, { vertex: '
|
|
617
|
+
const mat = new BABYLON.ShaderMaterial('atmo-shader-mat', scene, { vertex: 'planetAtmo2', fragment: 'planetAtmo2' }, {
|
|
512
618
|
attributes: ['position', 'normal'],
|
|
513
619
|
uniforms: [
|
|
514
620
|
'worldViewProjection',
|
|
@@ -539,16 +645,60 @@ export class B3dPlanet extends B3dChild {
|
|
|
539
645
|
mesh.parent = this.rootNode;
|
|
540
646
|
this.atmosphereMesh = mesh;
|
|
541
647
|
}
|
|
648
|
+
/** The highest ground above the base sphere, in this planet's units. */
|
|
649
|
+
_maxHeight() {
|
|
650
|
+
const attrs = this;
|
|
651
|
+
if (this.vertexHeights == null || this.vertexHeights.length === 0)
|
|
652
|
+
return attrs.gas ? 0 : attrs.grossAmplitude + attrs.detailAmplitude;
|
|
653
|
+
let max = -Infinity;
|
|
654
|
+
for (const h of this.vertexHeights)
|
|
655
|
+
if (h > max)
|
|
656
|
+
max = h;
|
|
657
|
+
return max;
|
|
658
|
+
}
|
|
659
|
+
/**
|
|
660
|
+
* The weather, seen from orbit (planet-clouds): a shell just above the
|
|
661
|
+
* highest ground and INSIDE the air shell, which stays outside it as the
|
|
662
|
+
* glow at the limb.
|
|
663
|
+
*/
|
|
664
|
+
buildClouds() {
|
|
665
|
+
if (this.cloudMesh) {
|
|
666
|
+
this.cloudMesh.dispose(false, true);
|
|
667
|
+
this.cloudMesh = null;
|
|
668
|
+
}
|
|
669
|
+
if (this.owner == null || this.rootNode == null)
|
|
670
|
+
return;
|
|
671
|
+
const attrs = this;
|
|
672
|
+
if (!(attrs.clouds > 0) || attrs.gas)
|
|
673
|
+
return;
|
|
674
|
+
let color;
|
|
675
|
+
try {
|
|
676
|
+
color = BABYLON.Color3.FromHexString(String(attrs.cloudColor || '#ffffff').slice(0, 7));
|
|
677
|
+
}
|
|
678
|
+
catch {
|
|
679
|
+
color = new BABYLON.Color3(1, 1, 1);
|
|
680
|
+
}
|
|
681
|
+
const lift = Math.max(0.004, Math.min(0.02, attrs.atmosphere * 0.3));
|
|
682
|
+
this.cloudMesh = createPlanetClouds(this.owner.scene, this.rootNode, attrs.radius + Math.max(0, this._maxHeight()) + lift * attrs.radius, {
|
|
683
|
+
coverage: Math.min(1, attrs.clouds),
|
|
684
|
+
color,
|
|
685
|
+
speed: attrs.cloudSpeed,
|
|
686
|
+
seed: 1 + (Math.abs(attrs.seed) % 89),
|
|
687
|
+
storms: () => this.storms,
|
|
688
|
+
});
|
|
689
|
+
}
|
|
542
690
|
buildOcean() {
|
|
543
691
|
if (this.owner == null || this.rootNode == null)
|
|
544
692
|
return;
|
|
545
693
|
const attrs = this;
|
|
546
694
|
const oceanPct = attrs.ocean;
|
|
547
|
-
|
|
695
|
+
// A colour map already shows its seas.
|
|
696
|
+
if (oceanPct <= 0 || attrs.gas || attrs.colorMap)
|
|
548
697
|
return;
|
|
549
698
|
// Ocean radius: height at the given percentile of vertices
|
|
550
699
|
// e.g. ocean=0.6 means 60% of surface is underwater
|
|
551
|
-
|
|
700
|
+
// A borrowed surface says where its sea is: the datum, height 0 here.
|
|
701
|
+
const oceanHeight = this.surface != null ? 0 : this.heightPercentile(oceanPct);
|
|
552
702
|
const oceanRadius = attrs.radius + oceanHeight;
|
|
553
703
|
const mesh = BABYLON.MeshBuilder.CreateSphere('ocean', { diameter: oceanRadius * 2, segments: 32 }, this.owner.scene);
|
|
554
704
|
const mat = new BABYLON.StandardMaterial('ocean-mat', this.owner.scene);
|
|
@@ -702,9 +852,11 @@ export class B3dPlanet extends B3dChild {
|
|
|
702
852
|
this.ringMesh = null;
|
|
703
853
|
}
|
|
704
854
|
this.registered = false;
|
|
855
|
+
this._loadHeightMap();
|
|
705
856
|
this.buildPlanet();
|
|
706
857
|
this.buildAtmosphere();
|
|
707
858
|
this.buildOcean();
|
|
859
|
+
this.buildClouds();
|
|
708
860
|
this.buildRings();
|
|
709
861
|
}
|
|
710
862
|
/** Update atmosphere/ocean/wireframe/rotation */
|
|
@@ -721,6 +873,7 @@ export class B3dPlanet extends B3dChild {
|
|
|
721
873
|
}
|
|
722
874
|
this.buildAtmosphere();
|
|
723
875
|
this.buildOcean();
|
|
876
|
+
this.buildClouds();
|
|
724
877
|
if (this.planetMesh?.material) {
|
|
725
878
|
;
|
|
726
879
|
this.planetMesh.material.wireframe =
|