@deck.gl-community/three 9.2.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +19 -0
- package/README.md +81 -0
- package/dist/index.cjs +317 -0
- package/dist/index.cjs.map +7 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -0
- package/dist/tree-layer/tree-geometry.d.ts +56 -0
- package/dist/tree-layer/tree-geometry.d.ts.map +1 -0
- package/dist/tree-layer/tree-geometry.js +163 -0
- package/dist/tree-layer/tree-geometry.js.map +1 -0
- package/dist/tree-layer/tree-layer.d.ts +112 -0
- package/dist/tree-layer/tree-layer.d.ts.map +1 -0
- package/dist/tree-layer/tree-layer.js +206 -0
- package/dist/tree-layer/tree-layer.js.map +1 -0
- package/package.json +46 -0
- package/src/index.ts +6 -0
- package/src/tree-layer/tree-geometry.ts +204 -0
- package/src/tree-layer/tree-layer.ts +365 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
Copyright (c) 2020 vis.gl a Series of LF Projects, LLC
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
4
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
5
|
+
in the Software without restriction, including without limitation the rights
|
|
6
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
7
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
8
|
+
furnished to do so, subject to the following conditions:
|
|
9
|
+
|
|
10
|
+
The above copyright notice and this permission notice shall be included in
|
|
11
|
+
all copies or substantial portions of the Software.
|
|
12
|
+
|
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
19
|
+
THE SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# @deck.gl-community/three
|
|
2
|
+
|
|
3
|
+
A collection of deck.gl layers powered by [Three.js](https://threejs.org/), giving access to Three.js geometry primitives, materials, and scene graph tooling directly inside deck.gl visualisations.
|
|
4
|
+
|
|
5
|
+
`TreeLayer` is the first layer in this module — a fully parametric 3D tree renderer backed by Three.js `BufferGeometry` and rendered via deck.gl's `SimpleMeshLayer`.
|
|
6
|
+
|
|
7
|
+
## Layers
|
|
8
|
+
|
|
9
|
+
| Layer | Description |
|
|
10
|
+
|-------|-------------|
|
|
11
|
+
| [`TreeLayer`](#treelayer) | Procedural 3D trees with 5 species silhouettes and season-driven colours |
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## TreeLayer
|
|
16
|
+
|
|
17
|
+
Renders richly configurable 3D trees at geographic positions using procedural geometry generated with Three.js `BufferGeometry` primitives.
|
|
18
|
+
|
|
19
|
+
### Features
|
|
20
|
+
|
|
21
|
+
- **5 tree species / silhouettes**: pine (tiered cones), oak (sphere), palm (flat crown), birch (narrow oval), cherry (round sphere)
|
|
22
|
+
- **Parametric geometry**: per-instance height, trunk-to-canopy ratio, trunk radius, canopy radius
|
|
23
|
+
- **Season-driven colours**: spring / summer / autumn / winter palettes with species-specific defaults
|
|
24
|
+
- **Explicit colour overrides**: `getTrunkColor` and `getCanopyColor` accessors for full control
|
|
25
|
+
- **Pine tier density**: `getBranchLevels` (1–5) controls the number of overlapping cone tiers
|
|
26
|
+
- **Global scale factor**: `sizeScale` multiplier for easy zoom-level adjustment
|
|
27
|
+
|
|
28
|
+
## Install
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
npm install @deck.gl-community/three
|
|
32
|
+
# or
|
|
33
|
+
yarn add @deck.gl-community/three
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
> Three.js is a peer dependency pulled in automatically.
|
|
37
|
+
|
|
38
|
+
### Usage
|
|
39
|
+
|
|
40
|
+
```tsx
|
|
41
|
+
import {TreeLayer} from '@deck.gl-community/three';
|
|
42
|
+
|
|
43
|
+
const layer = new TreeLayer({
|
|
44
|
+
id: 'trees',
|
|
45
|
+
data: myForestData,
|
|
46
|
+
getPosition: d => d.coordinates,
|
|
47
|
+
getTreeType: d => d.species, // 'pine' | 'oak' | 'palm' | 'birch' | 'cherry'
|
|
48
|
+
getHeight: d => d.heightMetres,
|
|
49
|
+
getTrunkRadius: d => d.trunkRadius,
|
|
50
|
+
getCanopyRadius: d => d.canopyRadius,
|
|
51
|
+
getTrunkHeightFraction: d => 0.35,
|
|
52
|
+
getSeason: d => 'autumn',
|
|
53
|
+
sizeScale: 1,
|
|
54
|
+
pickable: true,
|
|
55
|
+
});
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## TreeLayer Props
|
|
59
|
+
|
|
60
|
+
| Prop | Type | Default | Description |
|
|
61
|
+
|------|------|---------|-------------|
|
|
62
|
+
| `getPosition` | accessor → `[lon, lat]` | `d.position` | Tree base position |
|
|
63
|
+
| `getElevation` | accessor → `number` | `0` | Base elevation in metres |
|
|
64
|
+
| `getTreeType` | accessor → `TreeType` | `'pine'` | Silhouette variant |
|
|
65
|
+
| `getHeight` | accessor → `number` | `10` | Total height (metres) |
|
|
66
|
+
| `getTrunkHeightFraction` | accessor → `number` | `0.35` | Trunk fraction of total height |
|
|
67
|
+
| `getTrunkRadius` | accessor → `number` | `0.5` | Trunk base radius (metres) |
|
|
68
|
+
| `getCanopyRadius` | accessor → `number` | `3` | Canopy horizontal radius (metres) |
|
|
69
|
+
| `getTrunkColor` | accessor → `Color\|null` | `null` | Explicit trunk RGBA; `null` uses species default |
|
|
70
|
+
| `getCanopyColor` | accessor → `Color\|null` | `null` | Explicit canopy RGBA; `null` uses season default |
|
|
71
|
+
| `getSeason` | accessor → `Season` | `'summer'` | Drives default canopy colour |
|
|
72
|
+
| `getBranchLevels` | accessor → `number` | `3` | Pine tier count (1–5) |
|
|
73
|
+
| `sizeScale` | `number` | `1` | Global size multiplier |
|
|
74
|
+
|
|
75
|
+
## TreeType values
|
|
76
|
+
|
|
77
|
+
`'pine'` · `'oak'` · `'palm'` · `'birch'` · `'cherry'`
|
|
78
|
+
|
|
79
|
+
## Season values
|
|
80
|
+
|
|
81
|
+
`'spring'` · `'summer'` · `'autumn'` · `'winter'`
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
3
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
4
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
5
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
var __publicField = (obj, key, value) => {
|
|
20
|
+
__defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
21
|
+
return value;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
// dist/index.js
|
|
25
|
+
var dist_exports = {};
|
|
26
|
+
__export(dist_exports, {
|
|
27
|
+
TreeLayer: () => TreeLayer
|
|
28
|
+
});
|
|
29
|
+
module.exports = __toCommonJS(dist_exports);
|
|
30
|
+
|
|
31
|
+
// dist/tree-layer/tree-layer.js
|
|
32
|
+
var import_core = require("@deck.gl/core");
|
|
33
|
+
var import_mesh_layers = require("@deck.gl/mesh-layers");
|
|
34
|
+
|
|
35
|
+
// dist/tree-layer/tree-geometry.js
|
|
36
|
+
var import_three = require("three");
|
|
37
|
+
var Y_TO_Z_UP = new import_three.Matrix4().makeRotationX(-Math.PI / 2);
|
|
38
|
+
function extractMesh(geo) {
|
|
39
|
+
geo.computeVertexNormals();
|
|
40
|
+
const posAttr = geo.attributes.position;
|
|
41
|
+
const norAttr = geo.attributes.normal;
|
|
42
|
+
const idx = geo.index;
|
|
43
|
+
return {
|
|
44
|
+
attributes: {
|
|
45
|
+
POSITION: { value: new Float32Array(posAttr.array), size: 3 },
|
|
46
|
+
NORMAL: { value: new Float32Array(norAttr.array), size: 3 }
|
|
47
|
+
},
|
|
48
|
+
indices: { value: new Uint32Array(idx ? idx.array : new Uint32Array(0)), size: 1 },
|
|
49
|
+
topology: "triangle-list",
|
|
50
|
+
mode: 4
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
function copyIndices(out, outOffset, geo, vertexBase) {
|
|
54
|
+
if (geo.index) {
|
|
55
|
+
const src = geo.index.array;
|
|
56
|
+
for (let i = 0; i < src.length; i++)
|
|
57
|
+
out[outOffset + i] = src[i] + vertexBase;
|
|
58
|
+
return src.length;
|
|
59
|
+
}
|
|
60
|
+
const count = geo.attributes.position.count;
|
|
61
|
+
for (let i = 0; i < count; i++)
|
|
62
|
+
out[outOffset + i] = vertexBase + i;
|
|
63
|
+
return count;
|
|
64
|
+
}
|
|
65
|
+
function mergeGeometries(geos) {
|
|
66
|
+
let totalVertices = 0;
|
|
67
|
+
let totalIndices = 0;
|
|
68
|
+
for (const geo of geos) {
|
|
69
|
+
totalVertices += geo.attributes.position.count;
|
|
70
|
+
totalIndices += geo.index ? geo.index.count : geo.attributes.position.count;
|
|
71
|
+
}
|
|
72
|
+
const positions = new Float32Array(totalVertices * 3);
|
|
73
|
+
const normals = new Float32Array(totalVertices * 3);
|
|
74
|
+
const indices = new Uint32Array(totalIndices);
|
|
75
|
+
let vOffset = 0;
|
|
76
|
+
let iOffset = 0;
|
|
77
|
+
for (const geo of geos) {
|
|
78
|
+
const count = geo.attributes.position.count;
|
|
79
|
+
const srcNor = geo.attributes.normal ? geo.attributes.normal.array : null;
|
|
80
|
+
positions.set(geo.attributes.position.array, vOffset * 3);
|
|
81
|
+
if (srcNor)
|
|
82
|
+
normals.set(srcNor, vOffset * 3);
|
|
83
|
+
iOffset += copyIndices(indices, iOffset, geo, vOffset);
|
|
84
|
+
vOffset += count;
|
|
85
|
+
}
|
|
86
|
+
const merged = new import_three.BufferGeometry();
|
|
87
|
+
merged.setAttribute("position", new import_three.BufferAttribute(positions, 3));
|
|
88
|
+
merged.setAttribute("normal", new import_three.BufferAttribute(normals, 3));
|
|
89
|
+
merged.setIndex(new import_three.BufferAttribute(indices, 1));
|
|
90
|
+
return merged;
|
|
91
|
+
}
|
|
92
|
+
function createTrunkMesh(segments = 8) {
|
|
93
|
+
const geo = new import_three.CylinderGeometry(0.7, 1, 1, segments);
|
|
94
|
+
geo.applyMatrix4(Y_TO_Z_UP);
|
|
95
|
+
geo.translate(0, 0, 0.5);
|
|
96
|
+
return extractMesh(geo);
|
|
97
|
+
}
|
|
98
|
+
function createPineCanopyMesh(levels = 3, segments = 8) {
|
|
99
|
+
const geos = [];
|
|
100
|
+
const tierHeight = 0.55 / levels;
|
|
101
|
+
for (let i = 0; i < levels; i++) {
|
|
102
|
+
const t = i / (levels - 1 || 1);
|
|
103
|
+
const radius = (1 - t * 0.5) * 0.85;
|
|
104
|
+
const zBase = t * (1 - tierHeight * 1.2);
|
|
105
|
+
const cone = new import_three.ConeGeometry(radius, tierHeight, segments);
|
|
106
|
+
cone.applyMatrix4(Y_TO_Z_UP);
|
|
107
|
+
cone.translate(0, 0, zBase + tierHeight);
|
|
108
|
+
geos.push(cone);
|
|
109
|
+
}
|
|
110
|
+
const tip = new import_three.ConeGeometry(0.12, 0.18, 6);
|
|
111
|
+
tip.applyMatrix4(Y_TO_Z_UP);
|
|
112
|
+
tip.translate(0, 0, 1);
|
|
113
|
+
geos.push(tip);
|
|
114
|
+
const merged = mergeGeometries(geos);
|
|
115
|
+
merged.computeVertexNormals();
|
|
116
|
+
return extractMesh(merged);
|
|
117
|
+
}
|
|
118
|
+
function createOakCanopyMesh() {
|
|
119
|
+
const geo = new import_three.SphereGeometry(0.5, 12, 8);
|
|
120
|
+
geo.applyMatrix4(Y_TO_Z_UP);
|
|
121
|
+
geo.translate(0, 0, 0.5);
|
|
122
|
+
return extractMesh(geo);
|
|
123
|
+
}
|
|
124
|
+
function createPalmCanopyMesh() {
|
|
125
|
+
const geo = new import_three.SphereGeometry(0.7, 12, 5);
|
|
126
|
+
const flatten = new import_three.Matrix4().makeScale(1.4, 0.35, 1.4);
|
|
127
|
+
geo.applyMatrix4(flatten);
|
|
128
|
+
geo.applyMatrix4(Y_TO_Z_UP);
|
|
129
|
+
geo.translate(0, 0, 0.18);
|
|
130
|
+
return extractMesh(geo);
|
|
131
|
+
}
|
|
132
|
+
function createBirchCanopyMesh() {
|
|
133
|
+
const geo = new import_three.SphereGeometry(0.42, 10, 8);
|
|
134
|
+
const elongate = new import_three.Matrix4().makeScale(1, 1.45, 1);
|
|
135
|
+
geo.applyMatrix4(elongate);
|
|
136
|
+
geo.applyMatrix4(Y_TO_Z_UP);
|
|
137
|
+
geo.translate(0, 0, 0.5);
|
|
138
|
+
return extractMesh(geo);
|
|
139
|
+
}
|
|
140
|
+
function createCherryCanopyMesh() {
|
|
141
|
+
const geo = new import_three.SphereGeometry(0.52, 12, 8);
|
|
142
|
+
geo.applyMatrix4(Y_TO_Z_UP);
|
|
143
|
+
geo.translate(0, 0, 0.5);
|
|
144
|
+
return extractMesh(geo);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// dist/tree-layer/tree-layer.js
|
|
148
|
+
var DEFAULT_TRUNK_COLORS = {
|
|
149
|
+
pine: [80, 50, 20, 255],
|
|
150
|
+
oak: [91, 57, 23, 255],
|
|
151
|
+
palm: [140, 100, 55, 255],
|
|
152
|
+
birch: [220, 215, 205, 255],
|
|
153
|
+
// white-grey birch bark
|
|
154
|
+
cherry: [100, 60, 40, 255]
|
|
155
|
+
};
|
|
156
|
+
var DEFAULT_CANOPY_COLORS = {
|
|
157
|
+
pine: {
|
|
158
|
+
spring: [34, 100, 34, 255],
|
|
159
|
+
summer: [0, 64, 0, 255],
|
|
160
|
+
autumn: [0, 64, 0, 255],
|
|
161
|
+
// evergreen — no colour change
|
|
162
|
+
winter: [0, 55, 0, 255]
|
|
163
|
+
},
|
|
164
|
+
oak: {
|
|
165
|
+
spring: [100, 180, 80, 255],
|
|
166
|
+
summer: [34, 120, 15, 255],
|
|
167
|
+
autumn: [180, 85, 20, 255],
|
|
168
|
+
winter: [100, 80, 60, 160]
|
|
169
|
+
// sparse, semi-transparent
|
|
170
|
+
},
|
|
171
|
+
palm: {
|
|
172
|
+
spring: [50, 160, 50, 255],
|
|
173
|
+
summer: [20, 145, 20, 255],
|
|
174
|
+
autumn: [55, 150, 30, 255],
|
|
175
|
+
winter: [40, 130, 30, 255]
|
|
176
|
+
},
|
|
177
|
+
birch: {
|
|
178
|
+
spring: [150, 210, 110, 255],
|
|
179
|
+
summer: [80, 160, 60, 255],
|
|
180
|
+
autumn: [230, 185, 40, 255],
|
|
181
|
+
winter: [180, 180, 170, 90]
|
|
182
|
+
// near-bare
|
|
183
|
+
},
|
|
184
|
+
cherry: {
|
|
185
|
+
spring: [255, 180, 205, 255],
|
|
186
|
+
// pink blossom
|
|
187
|
+
summer: [50, 140, 50, 255],
|
|
188
|
+
autumn: [200, 60, 40, 255],
|
|
189
|
+
winter: [120, 90, 80, 110]
|
|
190
|
+
// bare
|
|
191
|
+
}
|
|
192
|
+
};
|
|
193
|
+
var TRUNK_MESH = createTrunkMesh();
|
|
194
|
+
var CANOPY_MESHES = {
|
|
195
|
+
pine: createPineCanopyMesh(3),
|
|
196
|
+
oak: createOakCanopyMesh(),
|
|
197
|
+
palm: createPalmCanopyMesh(),
|
|
198
|
+
birch: createBirchCanopyMesh(),
|
|
199
|
+
cherry: createCherryCanopyMesh()
|
|
200
|
+
};
|
|
201
|
+
var ALL_TREE_TYPES = ["pine", "oak", "palm", "birch", "cherry"];
|
|
202
|
+
var defaultProps = {
|
|
203
|
+
getPosition: { type: "accessor", value: (d) => d.position },
|
|
204
|
+
getElevation: { type: "accessor", value: (_d) => 0 },
|
|
205
|
+
getTreeType: { type: "accessor", value: (_d) => "pine" },
|
|
206
|
+
getHeight: { type: "accessor", value: (_d) => 10 },
|
|
207
|
+
getTrunkHeightFraction: { type: "accessor", value: (_d) => 0.35 },
|
|
208
|
+
getTrunkRadius: { type: "accessor", value: (_d) => 0.5 },
|
|
209
|
+
getCanopyRadius: { type: "accessor", value: (_d) => 3 },
|
|
210
|
+
getTrunkColor: { type: "accessor", value: (_d) => null },
|
|
211
|
+
getCanopyColor: { type: "accessor", value: (_d) => null },
|
|
212
|
+
getSeason: { type: "accessor", value: (_d) => "summer" },
|
|
213
|
+
getBranchLevels: { type: "accessor", value: (_d) => 3 },
|
|
214
|
+
sizeScale: { type: "number", value: 1, min: 0 }
|
|
215
|
+
};
|
|
216
|
+
var TreeLayer = class extends import_core.CompositeLayer {
|
|
217
|
+
initializeState() {
|
|
218
|
+
this.state = {
|
|
219
|
+
grouped: { pine: [], oak: [], palm: [], birch: [], cherry: [] },
|
|
220
|
+
pineMeshes: {}
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
updateState({ props, changeFlags }) {
|
|
224
|
+
if (changeFlags.dataChanged || changeFlags.updateTriggersChanged) {
|
|
225
|
+
const { data, getTreeType, getBranchLevels } = props;
|
|
226
|
+
const grouped = {
|
|
227
|
+
pine: [],
|
|
228
|
+
oak: [],
|
|
229
|
+
palm: [],
|
|
230
|
+
birch: [],
|
|
231
|
+
cherry: []
|
|
232
|
+
};
|
|
233
|
+
const pineMeshes = {};
|
|
234
|
+
for (const d of data) {
|
|
235
|
+
const type = getTreeType(d);
|
|
236
|
+
if (grouped[type])
|
|
237
|
+
grouped[type].push(d);
|
|
238
|
+
if (type === "pine") {
|
|
239
|
+
const levels = Math.max(1, Math.min(5, Math.round(getBranchLevels(d))));
|
|
240
|
+
pineMeshes[levels] ??= createPineCanopyMesh(levels);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
this.setState({ grouped, pineMeshes });
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
/** Build a single canopy sub-layer for one tree type. */
|
|
247
|
+
_buildCanopyLayer(type) {
|
|
248
|
+
const { getPosition, getElevation, getHeight, getTrunkHeightFraction, getCanopyRadius, getCanopyColor, getSeason, sizeScale } = this.props;
|
|
249
|
+
const { grouped, pineMeshes } = this.state;
|
|
250
|
+
let mesh = CANOPY_MESHES[type];
|
|
251
|
+
if (type === "pine") {
|
|
252
|
+
const firstLevel = Object.keys(pineMeshes)[0];
|
|
253
|
+
if (firstLevel)
|
|
254
|
+
mesh = pineMeshes[Number(firstLevel)];
|
|
255
|
+
}
|
|
256
|
+
return new import_mesh_layers.SimpleMeshLayer(this.getSubLayerProps({
|
|
257
|
+
id: `canopy-${type}`,
|
|
258
|
+
data: grouped[type],
|
|
259
|
+
mesh,
|
|
260
|
+
getPosition: (d) => {
|
|
261
|
+
const pos = getPosition(d);
|
|
262
|
+
const elevation = getElevation(d) || 0;
|
|
263
|
+
const h = getHeight(d) * sizeScale;
|
|
264
|
+
const f = getTrunkHeightFraction(d);
|
|
265
|
+
return [pos[0], pos[1], elevation + h * f];
|
|
266
|
+
},
|
|
267
|
+
getScale: (d) => {
|
|
268
|
+
const h = getHeight(d) * sizeScale;
|
|
269
|
+
const f = getTrunkHeightFraction(d);
|
|
270
|
+
const r = getCanopyRadius(d) * sizeScale;
|
|
271
|
+
return [r, r, h * (1 - f)];
|
|
272
|
+
},
|
|
273
|
+
getColor: (d) => {
|
|
274
|
+
const explicit = getCanopyColor(d);
|
|
275
|
+
if (explicit)
|
|
276
|
+
return explicit;
|
|
277
|
+
const season = getSeason(d) || "summer";
|
|
278
|
+
return DEFAULT_CANOPY_COLORS[type][season];
|
|
279
|
+
},
|
|
280
|
+
pickable: this.props.pickable,
|
|
281
|
+
material: { ambient: 0.4, diffuse: 0.7, shininess: 12 }
|
|
282
|
+
}));
|
|
283
|
+
}
|
|
284
|
+
renderLayers() {
|
|
285
|
+
const { getPosition, getElevation, getTreeType, getHeight, getTrunkHeightFraction, getTrunkRadius, getTrunkColor, sizeScale } = this.props;
|
|
286
|
+
const { grouped } = this.state;
|
|
287
|
+
const trunkLayer = new import_mesh_layers.SimpleMeshLayer(this.getSubLayerProps({
|
|
288
|
+
id: "trunks",
|
|
289
|
+
data: this.props.data,
|
|
290
|
+
mesh: TRUNK_MESH,
|
|
291
|
+
getPosition: (d) => {
|
|
292
|
+
const pos = getPosition(d);
|
|
293
|
+
return [pos[0], pos[1], getElevation(d) || 0];
|
|
294
|
+
},
|
|
295
|
+
getScale: (d) => {
|
|
296
|
+
const h = getHeight(d) * sizeScale;
|
|
297
|
+
const f = getTrunkHeightFraction(d);
|
|
298
|
+
const r = getTrunkRadius(d) * sizeScale;
|
|
299
|
+
return [r, r, h * f];
|
|
300
|
+
},
|
|
301
|
+
getColor: (d) => {
|
|
302
|
+
const explicit = getTrunkColor(d);
|
|
303
|
+
if (explicit)
|
|
304
|
+
return explicit;
|
|
305
|
+
const type = getTreeType(d) || "pine";
|
|
306
|
+
return DEFAULT_TRUNK_COLORS[type] ?? DEFAULT_TRUNK_COLORS.pine;
|
|
307
|
+
},
|
|
308
|
+
pickable: this.props.pickable,
|
|
309
|
+
material: { ambient: 0.35, diffuse: 0.6, shininess: 8 }
|
|
310
|
+
}));
|
|
311
|
+
const canopyLayers = ALL_TREE_TYPES.filter((type) => grouped[type].length > 0).map((type) => this._buildCanopyLayer(type));
|
|
312
|
+
return [trunkLayer, ...canopyLayers];
|
|
313
|
+
}
|
|
314
|
+
};
|
|
315
|
+
__publicField(TreeLayer, "layerName", "TreeLayer");
|
|
316
|
+
__publicField(TreeLayer, "defaultProps", defaultProps);
|
|
317
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/index.ts", "../src/tree-layer/tree-layer.ts", "../src/tree-layer/tree-geometry.ts"],
|
|
4
|
+
"sourcesContent": ["// deck.gl-community\n// SPDX-License-Identifier: MIT\n// Copyright (c) vis.gl contributors\n\nexport type {TreeLayerProps, TreeType, Season} from './tree-layer/tree-layer';\nexport {TreeLayer} from './tree-layer/tree-layer';\n", "// deck.gl-community\n// SPDX-License-Identifier: MIT\n// Copyright (c) vis.gl contributors\n\nimport {CompositeLayer} from '@deck.gl/core';\nimport type {Color, DefaultProps, LayerProps, Position} from '@deck.gl/core';\nimport {SimpleMeshLayer} from '@deck.gl/mesh-layers';\nimport {\n createTrunkMesh,\n createPineCanopyMesh,\n createOakCanopyMesh,\n createPalmCanopyMesh,\n createBirchCanopyMesh,\n createCherryCanopyMesh\n} from './tree-geometry';\n\n// ---------------------------------------------------------------------------\n// Public types\n// ---------------------------------------------------------------------------\n\n/** Tree species / silhouette variant. */\nexport type TreeType = 'pine' | 'oak' | 'palm' | 'birch' | 'cherry';\n\n/** Season that drives default canopy colour when no explicit colour is supplied. */\nexport type Season = 'spring' | 'summer' | 'autumn' | 'winter';\n\n// ---------------------------------------------------------------------------\n// Default colours\n// ---------------------------------------------------------------------------\n\n/** Default trunk colours per tree type [r, g, b, a]. */\nconst DEFAULT_TRUNK_COLORS: Record<TreeType, Color> = {\n pine: [80, 50, 20, 255],\n oak: [91, 57, 23, 255],\n palm: [140, 100, 55, 255],\n birch: [220, 215, 205, 255], // white-grey birch bark\n cherry: [100, 60, 40, 255]\n};\n\n/** Default canopy colours per (tree type, season) [r, g, b, a]. */\nconst DEFAULT_CANOPY_COLORS: Record<TreeType, Record<Season, Color>> = {\n pine: {\n spring: [34, 100, 34, 255],\n summer: [0, 64, 0, 255],\n autumn: [0, 64, 0, 255], // evergreen \u2014 no colour change\n winter: [0, 55, 0, 255]\n },\n oak: {\n spring: [100, 180, 80, 255],\n summer: [34, 120, 15, 255],\n autumn: [180, 85, 20, 255],\n winter: [100, 80, 60, 160] // sparse, semi-transparent\n },\n palm: {\n spring: [50, 160, 50, 255],\n summer: [20, 145, 20, 255],\n autumn: [55, 150, 30, 255],\n winter: [40, 130, 30, 255]\n },\n birch: {\n spring: [150, 210, 110, 255],\n summer: [80, 160, 60, 255],\n autumn: [230, 185, 40, 255],\n winter: [180, 180, 170, 90] // near-bare\n },\n cherry: {\n spring: [255, 180, 205, 255], // pink blossom\n summer: [50, 140, 50, 255],\n autumn: [200, 60, 40, 255],\n winter: [120, 90, 80, 110] // bare\n }\n};\n\n// ---------------------------------------------------------------------------\n// Pre-built unit-scale meshes (shared across all layer instances)\n// ---------------------------------------------------------------------------\n\nconst TRUNK_MESH = createTrunkMesh();\n\nconst CANOPY_MESHES: Record<TreeType, ReturnType<typeof createTrunkMesh>> = {\n pine: createPineCanopyMesh(3),\n oak: createOakCanopyMesh(),\n palm: createPalmCanopyMesh(),\n birch: createBirchCanopyMesh(),\n cherry: createCherryCanopyMesh()\n};\n\nconst ALL_TREE_TYPES: TreeType[] = ['pine', 'oak', 'palm', 'birch', 'cherry'];\n\n// ---------------------------------------------------------------------------\n// Props\n// ---------------------------------------------------------------------------\n\ntype _TreeLayerProps<DataT> = {\n /** Source data. */\n data: DataT[];\n\n /** Longitude/latitude position of the tree base. */\n getPosition?: (d: DataT) => Position;\n\n /** Base elevation (metres above sea level). @default 0 */\n getElevation?: (d: DataT) => number;\n\n /**\n * Silhouette / species variant.\n * 'pine' \u2013 layered conical tiers (evergreen)\n * 'oak' \u2013 wide spherical canopy\n * 'palm' \u2013 tall thin trunk with flat crown\n * 'birch' \u2013 narrow oval canopy, pale bark\n * 'cherry' \u2013 round lush canopy, seasonal blossom\n * @default 'pine'\n */\n getTreeType?: (d: DataT) => TreeType;\n\n /**\n * Total tree height in metres.\n * @default 10\n */\n getHeight?: (d: DataT) => number;\n\n /**\n * Fraction of total height occupied by the trunk (0\u20131).\n * @default 0.35\n */\n getTrunkHeightFraction?: (d: DataT) => number;\n\n /**\n * Trunk base radius in metres.\n * @default 0.5\n */\n getTrunkRadius?: (d: DataT) => number;\n\n /**\n * Horizontal radius of the canopy in metres.\n * @default 3\n */\n getCanopyRadius?: (d: DataT) => number;\n\n /**\n * Explicit trunk colour [r, g, b, a].\n * When null the species default is used.\n * @default null\n */\n getTrunkColor?: (d: DataT) => Color | null;\n\n /**\n * Explicit canopy colour [r, g, b, a].\n * When null the species \u00D7 season default is used.\n * @default null\n */\n getCanopyColor?: (d: DataT) => Color | null;\n\n /**\n * Season used to pick the default canopy colour when no explicit colour is provided.\n * @default 'summer'\n */\n getSeason?: (d: DataT) => Season;\n\n /**\n * Number of cone tiers for pine trees (1\u20135).\n * Higher values produce a denser layered silhouette.\n * @default 3\n */\n getBranchLevels?: (d: DataT) => number;\n\n /**\n * Global size multiplier applied to all dimensions.\n * @default 1\n */\n sizeScale?: number;\n};\n\nexport type TreeLayerProps<DataT = unknown> = _TreeLayerProps<DataT> & LayerProps;\n\nconst defaultProps: DefaultProps<TreeLayerProps<unknown>> = {\n getPosition: {type: 'accessor', value: (d: any) => d.position},\n getElevation: {type: 'accessor', value: (_d: any) => 0},\n getTreeType: {type: 'accessor', value: (_d: any) => 'pine' as TreeType},\n getHeight: {type: 'accessor', value: (_d: any) => 10},\n getTrunkHeightFraction: {type: 'accessor', value: (_d: any) => 0.35},\n getTrunkRadius: {type: 'accessor', value: (_d: any) => 0.5},\n getCanopyRadius: {type: 'accessor', value: (_d: any) => 3},\n getTrunkColor: {type: 'accessor', value: (_d: any) => null},\n getCanopyColor: {type: 'accessor', value: (_d: any) => null},\n getSeason: {type: 'accessor', value: (_d: any) => 'summer' as Season},\n getBranchLevels: {type: 'accessor', value: (_d: any) => 3},\n sizeScale: {type: 'number', value: 1, min: 0}\n};\n\n// ---------------------------------------------------------------------------\n// State\n// ---------------------------------------------------------------------------\n\ntype TreeLayerState = {\n grouped: Record<TreeType, unknown[]>;\n pineMeshes: Record<number, ReturnType<typeof createPineCanopyMesh>>;\n};\n\n// ---------------------------------------------------------------------------\n// Layer\n// ---------------------------------------------------------------------------\n\n/**\n * **TreeLayer** \u2014 A parametric, Three.js-powered deck.gl layer that renders\n * richly configurable 3D trees at geographic positions.\n *\n * Each tree is composed of two `SimpleMeshLayer` instances: one for the trunk\n * (a tapered cylinder) and one for the canopy (silhouette depends on `getTreeType`).\n * All geometry is generated procedurally using Three.js `BufferGeometry` primitives\n * and converted to the `@loaders.gl/schema` `MeshGeometry` format accepted by\n * `SimpleMeshLayer`.\n *\n * Parametric controls include:\n * - Species / silhouette (`getTreeType`)\n * - Total height (`getHeight`) and trunk-to-canopy proportion (`getTrunkHeightFraction`)\n * - Trunk and canopy radii (`getTrunkRadius`, `getCanopyRadius`)\n * - Explicit or season-driven colours (`getTrunkColor`, `getCanopyColor`, `getSeason`)\n * - Pine tier density (`getBranchLevels`)\n * - Global scale factor (`sizeScale`)\n */\nexport class TreeLayer<DataT = unknown, ExtraPropsT extends {} = {}> extends CompositeLayer<\n ExtraPropsT & Required<_TreeLayerProps<DataT>>\n> {\n static layerName = 'TreeLayer';\n static defaultProps = defaultProps;\n\n declare state: TreeLayerState;\n\n initializeState() {\n this.state = {\n grouped: {pine: [], oak: [], palm: [], birch: [], cherry: []},\n pineMeshes: {}\n };\n }\n\n updateState({props, changeFlags}) {\n if (changeFlags.dataChanged || changeFlags.updateTriggersChanged) {\n const {data, getTreeType, getBranchLevels} = props;\n const grouped: Record<TreeType, DataT[]> = {\n pine: [],\n oak: [],\n palm: [],\n birch: [],\n cherry: []\n };\n\n // Build per-level pine mesh cache\n const pineMeshes: Record<number, ReturnType<typeof createPineCanopyMesh>> = {};\n\n for (const d of data as DataT[]) {\n const type = getTreeType(d) as TreeType;\n if (grouped[type]) grouped[type].push(d);\n if (type === 'pine') {\n const levels = Math.max(1, Math.min(5, Math.round(getBranchLevels(d) as number)));\n pineMeshes[levels] ??= createPineCanopyMesh(levels);\n }\n }\n\n this.setState({grouped, pineMeshes});\n }\n }\n\n /** Build a single canopy sub-layer for one tree type. */\n private _buildCanopyLayer(type: TreeType) {\n const {\n getPosition,\n getElevation,\n getHeight,\n getTrunkHeightFraction,\n getCanopyRadius,\n getCanopyColor,\n getSeason,\n sizeScale\n } = this.props; // eslint-disable-line max-len\n const {grouped, pineMeshes} = this.state;\n\n let mesh = CANOPY_MESHES[type];\n if (type === 'pine') {\n const firstLevel = Object.keys(pineMeshes)[0];\n if (firstLevel) mesh = pineMeshes[Number(firstLevel)];\n }\n\n return new SimpleMeshLayer(\n this.getSubLayerProps({\n id: `canopy-${type}`,\n data: grouped[type],\n mesh,\n getPosition: (d) => {\n const pos = getPosition(d);\n const elevation = getElevation(d) || 0;\n const h = getHeight(d) * sizeScale;\n const f = getTrunkHeightFraction(d);\n return [pos[0], pos[1], elevation + h * f];\n },\n getScale: (d) => {\n const h = getHeight(d) * sizeScale;\n const f = getTrunkHeightFraction(d);\n const r = getCanopyRadius(d) * sizeScale;\n return [r, r, h * (1 - f)];\n },\n getColor: (d) => {\n const explicit = getCanopyColor(d);\n if (explicit) return explicit;\n const season = getSeason(d) || 'summer';\n return DEFAULT_CANOPY_COLORS[type][season];\n },\n pickable: this.props.pickable,\n material: {ambient: 0.4, diffuse: 0.7, shininess: 12}\n })\n );\n }\n\n renderLayers() {\n const {\n getPosition,\n getElevation,\n getTreeType,\n getHeight,\n getTrunkHeightFraction,\n getTrunkRadius,\n getTrunkColor,\n sizeScale\n } = this.props;\n\n const {grouped} = this.state;\n\n // -----------------------------------------------------------------------\n // 1. Trunk layer \u2014 one layer for ALL tree types, shared cylinder geometry\n // -----------------------------------------------------------------------\n const trunkLayer = new SimpleMeshLayer(\n this.getSubLayerProps({\n id: 'trunks',\n data: this.props.data,\n mesh: TRUNK_MESH,\n getPosition: (d) => {\n const pos = getPosition(d);\n return [pos[0], pos[1], getElevation(d) || 0];\n },\n getScale: (d) => {\n const h = getHeight(d) * sizeScale;\n const f = getTrunkHeightFraction(d);\n const r = getTrunkRadius(d) * sizeScale;\n return [r, r, h * f];\n },\n getColor: (d) => {\n const explicit = getTrunkColor(d);\n if (explicit) return explicit;\n const type = getTreeType(d) || 'pine';\n return DEFAULT_TRUNK_COLORS[type] ?? DEFAULT_TRUNK_COLORS.pine;\n },\n pickable: this.props.pickable,\n material: {ambient: 0.35, diffuse: 0.6, shininess: 8}\n })\n );\n\n // -----------------------------------------------------------------------\n // 2. Canopy layers \u2014 one per tree type, only for trees of that type\n // -----------------------------------------------------------------------\n const canopyLayers = ALL_TREE_TYPES.filter((type) => grouped[type].length > 0).map((type) =>\n this._buildCanopyLayer(type)\n );\n\n return [trunkLayer, ...canopyLayers];\n }\n}\n", "// deck.gl-community\n// SPDX-License-Identifier: MIT\n// Copyright (c) vis.gl contributors\n\nimport {\n BufferGeometry,\n BufferAttribute,\n CylinderGeometry,\n ConeGeometry,\n SphereGeometry,\n Matrix4\n} from 'three';\n\n/**\n * Mesh format compatible with deck.gl SimpleMeshLayer.\n * All geometries are Z-up (deck.gl convention), unit scale (0..1 in Z = bottom to top).\n */\nexport type TreeMesh = {\n attributes: {\n POSITION: {value: Float32Array; size: 3};\n NORMAL: {value: Float32Array; size: 3};\n };\n indices: {value: Uint32Array; size: 1};\n topology: 'triangle-list';\n mode: 4;\n};\n\n/**\n * Rotation matrix that converts from Three.js Y-up to deck.gl Z-up.\n * Rotates -90 degrees around the X axis: Y -> Z, Z -> -Y.\n */\nconst Y_TO_Z_UP = new Matrix4().makeRotationX(-Math.PI / 2);\n\n/**\n * Extract a TreeMesh from a Three.js BufferGeometry.\n * Assumes the geometry has already been rotated to Z-up.\n */\nfunction extractMesh(geo: BufferGeometry): TreeMesh {\n geo.computeVertexNormals();\n const posAttr = geo.attributes.position as BufferAttribute;\n const norAttr = geo.attributes.normal as BufferAttribute;\n const idx = geo.index;\n\n return {\n attributes: {\n POSITION: {value: new Float32Array(posAttr.array), size: 3},\n NORMAL: {value: new Float32Array(norAttr.array), size: 3}\n },\n indices: {value: new Uint32Array(idx ? idx.array : new Uint32Array(0)), size: 1},\n topology: 'triangle-list',\n mode: 4\n };\n}\n\n/** Copy indices for one geometry slice, offsetting by the current vertex base. */\nfunction copyIndices(\n out: Uint32Array,\n outOffset: number,\n geo: BufferGeometry,\n vertexBase: number\n): number {\n if (geo.index) {\n const src = geo.index.array;\n for (let i = 0; i < src.length; i++) out[outOffset + i] = src[i] + vertexBase;\n return src.length;\n }\n const count = geo.attributes.position.count;\n for (let i = 0; i < count; i++) out[outOffset + i] = vertexBase + i;\n return count;\n}\n\n/**\n * Merge multiple Three.js BufferGeometries into a single geometry.\n * All input geometries must be indexed.\n */\nfunction mergeGeometries(geos: BufferGeometry[]): BufferGeometry {\n let totalVertices = 0;\n let totalIndices = 0;\n for (const geo of geos) {\n totalVertices += geo.attributes.position.count;\n totalIndices += geo.index ? geo.index.count : geo.attributes.position.count;\n }\n\n const positions = new Float32Array(totalVertices * 3);\n const normals = new Float32Array(totalVertices * 3);\n const indices = new Uint32Array(totalIndices);\n let vOffset = 0;\n let iOffset = 0;\n\n for (const geo of geos) {\n const count = geo.attributes.position.count;\n const srcNor = geo.attributes.normal ? (geo.attributes.normal.array as Float32Array) : null;\n positions.set(geo.attributes.position.array as Float32Array, vOffset * 3);\n if (srcNor) normals.set(srcNor, vOffset * 3);\n iOffset += copyIndices(indices, iOffset, geo, vOffset);\n vOffset += count;\n }\n\n const merged = new BufferGeometry();\n merged.setAttribute('position', new BufferAttribute(positions, 3));\n merged.setAttribute('normal', new BufferAttribute(normals, 3));\n merged.setIndex(new BufferAttribute(indices, 1));\n return merged;\n}\n\n/**\n * Unit trunk cylinder mesh: from z=0 (base) to z=1 (top), radius tapers from 1 to 0.7.\n * Scale via `getScale = [trunkRadius, trunkRadius, trunkHeight]`.\n */\nexport function createTrunkMesh(segments = 8): TreeMesh {\n const geo = new CylinderGeometry(0.7, 1.0, 1.0, segments);\n // Three.js CylinderGeometry is centered at origin, extends from y=-0.5 to y=0.5\n geo.applyMatrix4(Y_TO_Z_UP); // Rotate to Z-up: now z=-0.5 to z=0.5\n geo.translate(0, 0, 0.5); // Shift so base is at z=0, top at z=1\n return extractMesh(geo);\n}\n\n/**\n * Unit pine canopy mesh: multiple tiered cones creating a Christmas tree silhouette.\n * Extends from z=0 (base of canopy) to z=1 (tip).\n *\n * @param levels - number of cone tiers (1-5)\n * @param segments - polygon segments per cone\n */\nexport function createPineCanopyMesh(levels = 3, segments = 8): TreeMesh {\n const geos: BufferGeometry[] = [];\n const tierHeight = 0.55 / levels;\n\n for (let i = 0; i < levels; i++) {\n const t = i / (levels - 1 || 1);\n // Bottom tiers are wider, top tiers are narrower\n const radius = (1 - t * 0.5) * 0.85;\n // Tiers are staggered: each one starts 60% up the previous tier\n const zBase = t * (1 - tierHeight * 1.2);\n\n const cone = new ConeGeometry(radius, tierHeight, segments);\n cone.applyMatrix4(Y_TO_Z_UP);\n // ConeGeometry apex is at y=+height/2 -> z=+height/2 after rotation\n // Translate so apex points upward and base is at zBase\n cone.translate(0, 0, zBase + tierHeight);\n geos.push(cone);\n }\n\n // Sharp tip at top\n const tip = new ConeGeometry(0.12, 0.18, 6);\n tip.applyMatrix4(Y_TO_Z_UP);\n tip.translate(0, 0, 1.0);\n geos.push(tip);\n\n const merged = mergeGeometries(geos);\n merged.computeVertexNormals();\n return extractMesh(merged);\n}\n\n/**\n * Unit oak canopy mesh: a large sphere.\n * Extends from z=0 to z=1, center at z=0.5.\n */\nexport function createOakCanopyMesh(): TreeMesh {\n const geo = new SphereGeometry(0.5, 12, 8);\n // SphereGeometry is centered at origin, radius=0.5\n geo.applyMatrix4(Y_TO_Z_UP);\n geo.translate(0, 0, 0.5); // center at z=0.5, extends z=0 to z=1\n return extractMesh(geo);\n}\n\n/**\n * Unit palm canopy mesh: a flat, wide disk crown typical of palm trees.\n * Extends from z=0 to z=0.35, radius=1.\n */\nexport function createPalmCanopyMesh(): TreeMesh {\n // Flattened sphere acting as a spread crown\n const geo = new SphereGeometry(0.7, 12, 5);\n const flatten = new Matrix4().makeScale(1.4, 0.35, 1.4);\n geo.applyMatrix4(flatten);\n geo.applyMatrix4(Y_TO_Z_UP);\n geo.translate(0, 0, 0.18);\n return extractMesh(geo);\n}\n\n/**\n * Unit birch canopy mesh: a narrow oval / diamond shape.\n * Extends from z=0 to z=1, narrower than an oak.\n */\nexport function createBirchCanopyMesh(): TreeMesh {\n const geo = new SphereGeometry(0.42, 10, 8);\n // Elongate vertically (Z after rotation)\n const elongate = new Matrix4().makeScale(1, 1.45, 1);\n geo.applyMatrix4(elongate);\n geo.applyMatrix4(Y_TO_Z_UP);\n geo.translate(0, 0, 0.5);\n return extractMesh(geo);\n}\n\n/**\n * Unit cherry canopy mesh: a full, round sphere slightly larger than oak.\n * Extends from z=0 to z=1.1 (slightly wider than tall for a lush look).\n */\nexport function createCherryCanopyMesh(): TreeMesh {\n const geo = new SphereGeometry(0.52, 12, 8);\n geo.applyMatrix4(Y_TO_Z_UP);\n geo.translate(0, 0, 0.5);\n return extractMesh(geo);\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAIA,YAAY,EAAC,cAAc,EAAE,QAAQ,EAAE,MAAM,EAAC,mCAAgC;AAC9E,OAAO,EAAC,SAAS,EAAC,mCAAgC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,oBAAoB;AACpB,+BAA+B;AAC/B,oCAAoC;AAGpC,OAAO,EAAC,SAAS,EAAC,mCAAgC"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mesh format compatible with deck.gl SimpleMeshLayer.
|
|
3
|
+
* All geometries are Z-up (deck.gl convention), unit scale (0..1 in Z = bottom to top).
|
|
4
|
+
*/
|
|
5
|
+
export type TreeMesh = {
|
|
6
|
+
attributes: {
|
|
7
|
+
POSITION: {
|
|
8
|
+
value: Float32Array;
|
|
9
|
+
size: 3;
|
|
10
|
+
};
|
|
11
|
+
NORMAL: {
|
|
12
|
+
value: Float32Array;
|
|
13
|
+
size: 3;
|
|
14
|
+
};
|
|
15
|
+
};
|
|
16
|
+
indices: {
|
|
17
|
+
value: Uint32Array;
|
|
18
|
+
size: 1;
|
|
19
|
+
};
|
|
20
|
+
topology: 'triangle-list';
|
|
21
|
+
mode: 4;
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* Unit trunk cylinder mesh: from z=0 (base) to z=1 (top), radius tapers from 1 to 0.7.
|
|
25
|
+
* Scale via `getScale = [trunkRadius, trunkRadius, trunkHeight]`.
|
|
26
|
+
*/
|
|
27
|
+
export declare function createTrunkMesh(segments?: number): TreeMesh;
|
|
28
|
+
/**
|
|
29
|
+
* Unit pine canopy mesh: multiple tiered cones creating a Christmas tree silhouette.
|
|
30
|
+
* Extends from z=0 (base of canopy) to z=1 (tip).
|
|
31
|
+
*
|
|
32
|
+
* @param levels - number of cone tiers (1-5)
|
|
33
|
+
* @param segments - polygon segments per cone
|
|
34
|
+
*/
|
|
35
|
+
export declare function createPineCanopyMesh(levels?: number, segments?: number): TreeMesh;
|
|
36
|
+
/**
|
|
37
|
+
* Unit oak canopy mesh: a large sphere.
|
|
38
|
+
* Extends from z=0 to z=1, center at z=0.5.
|
|
39
|
+
*/
|
|
40
|
+
export declare function createOakCanopyMesh(): TreeMesh;
|
|
41
|
+
/**
|
|
42
|
+
* Unit palm canopy mesh: a flat, wide disk crown typical of palm trees.
|
|
43
|
+
* Extends from z=0 to z=0.35, radius=1.
|
|
44
|
+
*/
|
|
45
|
+
export declare function createPalmCanopyMesh(): TreeMesh;
|
|
46
|
+
/**
|
|
47
|
+
* Unit birch canopy mesh: a narrow oval / diamond shape.
|
|
48
|
+
* Extends from z=0 to z=1, narrower than an oak.
|
|
49
|
+
*/
|
|
50
|
+
export declare function createBirchCanopyMesh(): TreeMesh;
|
|
51
|
+
/**
|
|
52
|
+
* Unit cherry canopy mesh: a full, round sphere slightly larger than oak.
|
|
53
|
+
* Extends from z=0 to z=1.1 (slightly wider than tall for a lush look).
|
|
54
|
+
*/
|
|
55
|
+
export declare function createCherryCanopyMesh(): TreeMesh;
|
|
56
|
+
//# sourceMappingURL=tree-geometry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tree-geometry.d.ts","sourceRoot":"","sources":["../../src/tree-layer/tree-geometry.ts"],"names":[],"mappings":"AAaA;;;GAGG;AACH,MAAM,MAAM,QAAQ,GAAG;IACrB,UAAU,EAAE;QACV,QAAQ,EAAE;YAAC,KAAK,EAAE,YAAY,CAAC;YAAC,IAAI,EAAE,CAAC,CAAA;SAAC,CAAC;QACzC,MAAM,EAAE;YAAC,KAAK,EAAE,YAAY,CAAC;YAAC,IAAI,EAAE,CAAC,CAAA;SAAC,CAAC;KACxC,CAAC;IACF,OAAO,EAAE;QAAC,KAAK,EAAE,WAAW,CAAC;QAAC,IAAI,EAAE,CAAC,CAAA;KAAC,CAAC;IACvC,QAAQ,EAAE,eAAe,CAAC;IAC1B,IAAI,EAAE,CAAC,CAAC;CACT,CAAC;AAgFF;;;GAGG;AACH,wBAAgB,eAAe,CAAC,QAAQ,SAAI,GAAG,QAAQ,CAMtD;AAED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,SAAI,EAAE,QAAQ,SAAI,GAAG,QAAQ,CA4BvE;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,IAAI,QAAQ,CAM9C;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,IAAI,QAAQ,CAQ/C;AAED;;;GAGG;AACH,wBAAgB,qBAAqB,IAAI,QAAQ,CAQhD;AAED;;;GAGG;AACH,wBAAgB,sBAAsB,IAAI,QAAQ,CAKjD"}
|