@hello-terrain/three 0.0.0-alpha.1
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/README.md +42 -0
- package/dist/index.cjs +240 -0
- package/dist/index.d.cts +116 -0
- package/dist/index.d.mts +116 -0
- package/dist/index.d.ts +116 -0
- package/dist/index.mjs +236 -0
- package/package.json +37 -0
package/README.md
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# @hello-terrain/three
|
|
2
|
+
Realtime web terrain engine, for vast virtual worlds. Built for [three.js](https://threejs.org/).
|
|
3
|
+
## Features
|
|
4
|
+
|
|
5
|
+
- Performant variable LOD system for huge (earth-scale!) open worlds
|
|
6
|
+
- Elevation manipulation, terrain holes, texture painting, overlays, colors, and wetness
|
|
7
|
+
- TSL-based elevation and texture assignment nodes
|
|
8
|
+
- Composable compute stage plugins
|
|
9
|
+
|
|
10
|
+
## Getting Started
|
|
11
|
+
|
|
12
|
+
1. Read the [Introduction](http://hello-terrain.kenny.wtf/docs) to understand the architecture and see how it's used.
|
|
13
|
+
|
|
14
|
+
2. Read the [Installation](http://hello-terrain.kenny.wtf/docs/installation) instructions.
|
|
15
|
+
|
|
16
|
+
3. Review the [Examples](http://hello-terrain.kenny.wtf/examples) to get an idea of how to do things.
|
|
17
|
+
|
|
18
|
+
4. For support, join the [Discord server](https://discord.gg/HgTd2B828n).
|
|
19
|
+
|
|
20
|
+
## Project Architecture
|
|
21
|
+
This library uses [unbuild](https://github.com/unjs/unbuild) for building.
|
|
22
|
+
- `src/index.ts` is the main entry point for your library exports
|
|
23
|
+
- Add your library code in the `src` folder
|
|
24
|
+
- `tests/` contains your test files
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
## Libraries
|
|
28
|
+
The following libraries are used - checkout the linked docs to learn more
|
|
29
|
+
- [unbuild](https://github.com/unjs/unbuild) - Unified JavaScript build system
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
## Tools
|
|
33
|
+
- [Vitest](https://vitest.dev/) - Fast unit test framework powered by Vite
|
|
34
|
+
- [Oxlint](https://oxc.rs/docs/guide/usage/linter) - A fast linter for JavaScript and TypeScript
|
|
35
|
+
- [Oxfmt](https://oxc.rs/docs/guide/usage/formatter) - Fast Prettier-compatible code formatter
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
## Development Commands
|
|
39
|
+
- `pnpm install` to install the dependencies
|
|
40
|
+
- `pnpm run build` to build the library into the `dist` folder
|
|
41
|
+
- `pnpm run test` to run the tests
|
|
42
|
+
- `pnpm run release` to build and publish to npm
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const three = require('three');
|
|
4
|
+
const tsl = require('three/tsl');
|
|
5
|
+
|
|
6
|
+
class TerrainGeometry extends three.BufferGeometry {
|
|
7
|
+
constructor(innerSegments = 14, extendUV = false) {
|
|
8
|
+
super();
|
|
9
|
+
if (innerSegments < 1 || !Number.isFinite(innerSegments) || !Number.isInteger(innerSegments)) {
|
|
10
|
+
throw new Error(
|
|
11
|
+
`Invalid innerSegments: ${innerSegments}. Must be a positive integer.`
|
|
12
|
+
);
|
|
13
|
+
}
|
|
14
|
+
try {
|
|
15
|
+
this.setIndex(this.generateIndices(innerSegments));
|
|
16
|
+
this.setAttribute(
|
|
17
|
+
"position",
|
|
18
|
+
new three.BufferAttribute(
|
|
19
|
+
new Float32Array(this.generatePositions(innerSegments)),
|
|
20
|
+
3
|
|
21
|
+
)
|
|
22
|
+
);
|
|
23
|
+
this.setAttribute(
|
|
24
|
+
"normal",
|
|
25
|
+
new three.BufferAttribute(
|
|
26
|
+
new Float32Array(this.generateNormals(innerSegments)),
|
|
27
|
+
3
|
|
28
|
+
)
|
|
29
|
+
);
|
|
30
|
+
this.setAttribute(
|
|
31
|
+
"uv",
|
|
32
|
+
new three.BufferAttribute(
|
|
33
|
+
new Float32Array(
|
|
34
|
+
extendUV ? this.generateUvsExtended(innerSegments) : this.generateUvsOnlyInner(innerSegments)
|
|
35
|
+
),
|
|
36
|
+
2
|
|
37
|
+
)
|
|
38
|
+
);
|
|
39
|
+
} catch (error) {
|
|
40
|
+
console.error("Error creating TerrainGeometry:", error);
|
|
41
|
+
throw error;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Generate indices for terrain geometry with proper skirt corner handling.
|
|
46
|
+
* The key improvement is in how corner triangles are subdivided.
|
|
47
|
+
*/
|
|
48
|
+
/**
|
|
49
|
+
* Generate indices for terrain geometry with proper skirt corner handling.
|
|
50
|
+
*
|
|
51
|
+
* The mesh layout is a regular grid (with duplicated outermost ring for skirt):
|
|
52
|
+
*
|
|
53
|
+
* SKIRT RING (rotational symmetry for proper corners):
|
|
54
|
+
* o---o---o---o---o
|
|
55
|
+
* | \ | / | \ | / |
|
|
56
|
+
* o---o---o---o---o
|
|
57
|
+
* | / | | \ |
|
|
58
|
+
* o---o o---o
|
|
59
|
+
* | \ | | / |
|
|
60
|
+
* o---o---o---o---o
|
|
61
|
+
* | / | \ | / | \ |
|
|
62
|
+
* o---o---o---o---o
|
|
63
|
+
*
|
|
64
|
+
* INNER GRID (consistent diagonal, no rotational symmetry):
|
|
65
|
+
* o---o---o
|
|
66
|
+
* | \ | \ |
|
|
67
|
+
* o---o---o
|
|
68
|
+
* | \ | \ |
|
|
69
|
+
* o---o---o
|
|
70
|
+
*
|
|
71
|
+
* Where o = vertex
|
|
72
|
+
* Each square cell is split into 2 triangles.
|
|
73
|
+
* - Skirt cells (outer ring): diagonal flip based on quadrant for corner correctness
|
|
74
|
+
* - Inner cells: consistent diagonal direction (all triangles "point" the same way)
|
|
75
|
+
*
|
|
76
|
+
* Vertex layout (for innerSegments = 2):
|
|
77
|
+
*
|
|
78
|
+
* 0----1----2----3----4
|
|
79
|
+
* | | | | |
|
|
80
|
+
* 5----6----7----8----9
|
|
81
|
+
* | | | | |
|
|
82
|
+
* 10---11---12---13---14
|
|
83
|
+
* | | | | |
|
|
84
|
+
* 15---16---17---18---19
|
|
85
|
+
* | | | | |
|
|
86
|
+
* 20---21---22---23---24
|
|
87
|
+
*
|
|
88
|
+
* For each cell:
|
|
89
|
+
* a = top-left,
|
|
90
|
+
* b = top-right,
|
|
91
|
+
* c = bottom-left,
|
|
92
|
+
* d = bottom-right (all as flat array indices)
|
|
93
|
+
*
|
|
94
|
+
* Diagonal a-d:
|
|
95
|
+
* triangle 1: a, d, b
|
|
96
|
+
* triangle 2: a, c, d
|
|
97
|
+
* Diagonal b-c:
|
|
98
|
+
* triangle 1: a, c, b
|
|
99
|
+
* triangle 2: b, c, d
|
|
100
|
+
*/
|
|
101
|
+
generateIndices(innerSegments) {
|
|
102
|
+
const innerEdgeVertexCount = innerSegments + 1;
|
|
103
|
+
const edgeVertexCountWithSkirt = innerEdgeVertexCount + 2;
|
|
104
|
+
const indices = [];
|
|
105
|
+
const cellsPerEdge = edgeVertexCountWithSkirt - 1;
|
|
106
|
+
const mid = Math.floor(cellsPerEdge / 2);
|
|
107
|
+
for (let y = 0; y < cellsPerEdge; y++) {
|
|
108
|
+
for (let x = 0; x < cellsPerEdge; x++) {
|
|
109
|
+
const a = y * edgeVertexCountWithSkirt + x;
|
|
110
|
+
const b = a + 1;
|
|
111
|
+
const c = a + edgeVertexCountWithSkirt;
|
|
112
|
+
const d = c + 1;
|
|
113
|
+
const isSkirtCell = x === 0 || x === cellsPerEdge - 1 || y === 0 || y === cellsPerEdge - 1;
|
|
114
|
+
let useDefaultDiagonal;
|
|
115
|
+
if (isSkirtCell) {
|
|
116
|
+
const leftHalf = x < mid;
|
|
117
|
+
const topHalf = y < mid;
|
|
118
|
+
useDefaultDiagonal = leftHalf && topHalf || !leftHalf && !topHalf;
|
|
119
|
+
} else {
|
|
120
|
+
useDefaultDiagonal = true;
|
|
121
|
+
}
|
|
122
|
+
if (useDefaultDiagonal) {
|
|
123
|
+
indices.push(a, d, b);
|
|
124
|
+
indices.push(a, c, d);
|
|
125
|
+
} else {
|
|
126
|
+
indices.push(a, c, b);
|
|
127
|
+
indices.push(b, c, d);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
return indices;
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Generate vertex positions for the terrain with skirts.
|
|
135
|
+
* Positions are normalized to [-0.5, 0.5] range.
|
|
136
|
+
*/
|
|
137
|
+
generatePositions(innerSegments) {
|
|
138
|
+
const edgeVertexCountWithSkirt = innerSegments + 1 + 2;
|
|
139
|
+
const positions = [];
|
|
140
|
+
for (let iy = 0; iy < edgeVertexCountWithSkirt; iy++) {
|
|
141
|
+
const v = Math.min(Math.max((iy - 1) / innerSegments, 0), 1);
|
|
142
|
+
const z = v - 0.5;
|
|
143
|
+
for (let ix = 0; ix < edgeVertexCountWithSkirt; ix++) {
|
|
144
|
+
const u = Math.min(Math.max((ix - 1) / innerSegments, 0), 1);
|
|
145
|
+
const x = u - 0.5;
|
|
146
|
+
positions.push(x, 0, z);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
return positions;
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Generate UV coordinates for the inner grid only (skirt duplicates clamped to border).
|
|
153
|
+
* UVs are normalized to [0, 1] range with flipped V.
|
|
154
|
+
*/
|
|
155
|
+
generateUvsOnlyInner(innerSegments) {
|
|
156
|
+
const edgeVertexCountWithSkirt = innerSegments + 1 + 2;
|
|
157
|
+
const uvs = [];
|
|
158
|
+
for (let iy = 0; iy < edgeVertexCountWithSkirt; iy++) {
|
|
159
|
+
const v = Math.min(Math.max((iy - 1) / innerSegments, 0), 1);
|
|
160
|
+
for (let ix = 0; ix < edgeVertexCountWithSkirt; ix++) {
|
|
161
|
+
const u = Math.min(Math.max((ix - 1) / innerSegments, 0), 1);
|
|
162
|
+
uvs.push(u, 1 - v);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
return uvs;
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Generate UVs that extend 1 extra unit outward to the skirt ring.
|
|
169
|
+
* Map the entire geometry (including skirts) into [0,1] so side faces
|
|
170
|
+
* receive proper UVs without relying on texture wrapping. V is flipped.
|
|
171
|
+
*/
|
|
172
|
+
generateUvsExtended(innerSegments) {
|
|
173
|
+
const edgeVertexCountWithSkirt = innerSegments + 1 + 2;
|
|
174
|
+
const uvs = [];
|
|
175
|
+
const denom = edgeVertexCountWithSkirt - 1;
|
|
176
|
+
for (let iy = 0; iy < edgeVertexCountWithSkirt; iy++) {
|
|
177
|
+
const v = iy / denom;
|
|
178
|
+
for (let ix = 0; ix < edgeVertexCountWithSkirt; ix++) {
|
|
179
|
+
const u = ix / denom;
|
|
180
|
+
uvs.push(u, 1 - v);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
return uvs;
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Generate vertex normals.
|
|
187
|
+
*/
|
|
188
|
+
generateNormals(innerSegments) {
|
|
189
|
+
const edgeVertexCountWithSkirt = innerSegments + 1 + 2;
|
|
190
|
+
const last = edgeVertexCountWithSkirt - 1;
|
|
191
|
+
const normals = [];
|
|
192
|
+
for (let iy = 0; iy < edgeVertexCountWithSkirt; iy++) {
|
|
193
|
+
for (let ix = 0; ix < edgeVertexCountWithSkirt; ix++) {
|
|
194
|
+
const onEdgeX = ix === 0 || ix === last;
|
|
195
|
+
const onEdgeY = iy === 0 || iy === last;
|
|
196
|
+
if (onEdgeX || onEdgeY) {
|
|
197
|
+
let nx = 0;
|
|
198
|
+
let nz = 0;
|
|
199
|
+
if (ix === 0) nx -= 1;
|
|
200
|
+
if (ix === last) nx += 1;
|
|
201
|
+
if (iy === 0) nz -= 1;
|
|
202
|
+
if (iy === last) nz += 1;
|
|
203
|
+
const len = Math.hypot(nx, nz);
|
|
204
|
+
if (len > 0) {
|
|
205
|
+
normals.push(nx / len, 0, nz / len);
|
|
206
|
+
} else {
|
|
207
|
+
normals.push(0, 1, 0);
|
|
208
|
+
}
|
|
209
|
+
} else {
|
|
210
|
+
normals.push(0, 1, 0);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
return normals;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const isSkirtVertex = tsl.Fn(([segments]) => {
|
|
219
|
+
const segmentsNode = typeof segments === "number" ? tsl.int(segments) : segments;
|
|
220
|
+
const vIndex = tsl.int(tsl.vertexIndex);
|
|
221
|
+
const segmentEdges = tsl.int(segmentsNode.add(3));
|
|
222
|
+
const vx = vIndex.mod(segmentEdges);
|
|
223
|
+
const vy = vIndex.div(segmentEdges);
|
|
224
|
+
const last = segmentEdges.sub(tsl.int(1));
|
|
225
|
+
return vx.equal(tsl.int(0)).or(vx.equal(last)).or(vy.equal(tsl.int(0))).or(vy.equal(last));
|
|
226
|
+
});
|
|
227
|
+
const isSkirtUV = tsl.Fn(([segments]) => {
|
|
228
|
+
const segmentsNode = typeof segments === "number" ? tsl.int(segments) : segments;
|
|
229
|
+
const ux = tsl.uv().x;
|
|
230
|
+
const uy = tsl.uv().y;
|
|
231
|
+
const segmentCount = segmentsNode.add(2);
|
|
232
|
+
const segmentStep = tsl.float(1).div(segmentCount);
|
|
233
|
+
const innerX = ux.greaterThan(segmentStep).and(ux.lessThan(segmentStep.oneMinus()));
|
|
234
|
+
const innerY = uy.greaterThan(segmentStep).and(uy.lessThan(segmentStep.oneMinus()));
|
|
235
|
+
return innerX.and(innerY).not();
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
exports.TerrainGeometry = TerrainGeometry;
|
|
239
|
+
exports.isSkirtUV = isSkirtUV;
|
|
240
|
+
exports.isSkirtVertex = isSkirtVertex;
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { BufferGeometry } from 'three';
|
|
2
|
+
import * as three_src_nodes_TSL_js from 'three/src/nodes/TSL.js';
|
|
3
|
+
import { Node } from 'three/webgpu';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Custom geometry for terrain tiles with properly handled skirts.
|
|
7
|
+
* This geometry ensures that corner triangles are subdivided correctly.
|
|
8
|
+
*/
|
|
9
|
+
declare class TerrainGeometry extends BufferGeometry {
|
|
10
|
+
constructor(innerSegments?: number, extendUV?: boolean);
|
|
11
|
+
/**
|
|
12
|
+
* Generate indices for terrain geometry with proper skirt corner handling.
|
|
13
|
+
* The key improvement is in how corner triangles are subdivided.
|
|
14
|
+
*/
|
|
15
|
+
/**
|
|
16
|
+
* Generate indices for terrain geometry with proper skirt corner handling.
|
|
17
|
+
*
|
|
18
|
+
* The mesh layout is a regular grid (with duplicated outermost ring for skirt):
|
|
19
|
+
*
|
|
20
|
+
* SKIRT RING (rotational symmetry for proper corners):
|
|
21
|
+
* o---o---o---o---o
|
|
22
|
+
* | \ | / | \ | / |
|
|
23
|
+
* o---o---o---o---o
|
|
24
|
+
* | / | | \ |
|
|
25
|
+
* o---o o---o
|
|
26
|
+
* | \ | | / |
|
|
27
|
+
* o---o---o---o---o
|
|
28
|
+
* | / | \ | / | \ |
|
|
29
|
+
* o---o---o---o---o
|
|
30
|
+
*
|
|
31
|
+
* INNER GRID (consistent diagonal, no rotational symmetry):
|
|
32
|
+
* o---o---o
|
|
33
|
+
* | \ | \ |
|
|
34
|
+
* o---o---o
|
|
35
|
+
* | \ | \ |
|
|
36
|
+
* o---o---o
|
|
37
|
+
*
|
|
38
|
+
* Where o = vertex
|
|
39
|
+
* Each square cell is split into 2 triangles.
|
|
40
|
+
* - Skirt cells (outer ring): diagonal flip based on quadrant for corner correctness
|
|
41
|
+
* - Inner cells: consistent diagonal direction (all triangles "point" the same way)
|
|
42
|
+
*
|
|
43
|
+
* Vertex layout (for innerSegments = 2):
|
|
44
|
+
*
|
|
45
|
+
* 0----1----2----3----4
|
|
46
|
+
* | | | | |
|
|
47
|
+
* 5----6----7----8----9
|
|
48
|
+
* | | | | |
|
|
49
|
+
* 10---11---12---13---14
|
|
50
|
+
* | | | | |
|
|
51
|
+
* 15---16---17---18---19
|
|
52
|
+
* | | | | |
|
|
53
|
+
* 20---21---22---23---24
|
|
54
|
+
*
|
|
55
|
+
* For each cell:
|
|
56
|
+
* a = top-left,
|
|
57
|
+
* b = top-right,
|
|
58
|
+
* c = bottom-left,
|
|
59
|
+
* d = bottom-right (all as flat array indices)
|
|
60
|
+
*
|
|
61
|
+
* Diagonal a-d:
|
|
62
|
+
* triangle 1: a, d, b
|
|
63
|
+
* triangle 2: a, c, d
|
|
64
|
+
* Diagonal b-c:
|
|
65
|
+
* triangle 1: a, c, b
|
|
66
|
+
* triangle 2: b, c, d
|
|
67
|
+
*/
|
|
68
|
+
private generateIndices;
|
|
69
|
+
/**
|
|
70
|
+
* Generate vertex positions for the terrain with skirts.
|
|
71
|
+
* Positions are normalized to [-0.5, 0.5] range.
|
|
72
|
+
*/
|
|
73
|
+
private generatePositions;
|
|
74
|
+
/**
|
|
75
|
+
* Generate UV coordinates for the inner grid only (skirt duplicates clamped to border).
|
|
76
|
+
* UVs are normalized to [0, 1] range with flipped V.
|
|
77
|
+
*/
|
|
78
|
+
private generateUvsOnlyInner;
|
|
79
|
+
/**
|
|
80
|
+
* Generate UVs that extend 1 extra unit outward to the skirt ring.
|
|
81
|
+
* Map the entire geometry (including skirts) into [0,1] so side faces
|
|
82
|
+
* receive proper UVs without relying on texture wrapping. V is flipped.
|
|
83
|
+
*/
|
|
84
|
+
private generateUvsExtended;
|
|
85
|
+
/**
|
|
86
|
+
* Generate vertex normals.
|
|
87
|
+
*/
|
|
88
|
+
private generateNormals;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Returns a node that is true for skirt vertices in the vertex stage.
|
|
93
|
+
*
|
|
94
|
+
* @remarks
|
|
95
|
+
* Only valid in the vertex shader. A vertex belongs to the skirt if it is on
|
|
96
|
+
* the outermost ring of the tile grid (first/last column or row). The grid
|
|
97
|
+
* resolution is derived from `segments`.
|
|
98
|
+
*
|
|
99
|
+
* @param segments - The number of inner segments in the terrain grid.
|
|
100
|
+
* @returns A node resolving to a boolean indicating a skirt vertex.
|
|
101
|
+
*/
|
|
102
|
+
declare const isSkirtVertex: three_src_nodes_TSL_js.ShaderNodeFn<[segments: number | Node]>;
|
|
103
|
+
/**
|
|
104
|
+
* Returns a node that is true for skirt UVs.
|
|
105
|
+
*
|
|
106
|
+
* @remarks
|
|
107
|
+
* Uses interpolated UVs and the grid size
|
|
108
|
+
* from `segments` to mark fragments outside the inner range
|
|
109
|
+
* `(step, 1 - step)` on either axis as skirt, where `step = 1 / (segments + 2)`.
|
|
110
|
+
*
|
|
111
|
+
* @param segments - The number of inner segments in the terrain grid.
|
|
112
|
+
* @returns A node resolving to a boolean indicating a skirt fragment.
|
|
113
|
+
*/
|
|
114
|
+
declare const isSkirtUV: three_src_nodes_TSL_js.ShaderNodeFn<[segments: number | Node]>;
|
|
115
|
+
|
|
116
|
+
export { TerrainGeometry, isSkirtUV, isSkirtVertex };
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { BufferGeometry } from 'three';
|
|
2
|
+
import * as three_src_nodes_TSL_js from 'three/src/nodes/TSL.js';
|
|
3
|
+
import { Node } from 'three/webgpu';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Custom geometry for terrain tiles with properly handled skirts.
|
|
7
|
+
* This geometry ensures that corner triangles are subdivided correctly.
|
|
8
|
+
*/
|
|
9
|
+
declare class TerrainGeometry extends BufferGeometry {
|
|
10
|
+
constructor(innerSegments?: number, extendUV?: boolean);
|
|
11
|
+
/**
|
|
12
|
+
* Generate indices for terrain geometry with proper skirt corner handling.
|
|
13
|
+
* The key improvement is in how corner triangles are subdivided.
|
|
14
|
+
*/
|
|
15
|
+
/**
|
|
16
|
+
* Generate indices for terrain geometry with proper skirt corner handling.
|
|
17
|
+
*
|
|
18
|
+
* The mesh layout is a regular grid (with duplicated outermost ring for skirt):
|
|
19
|
+
*
|
|
20
|
+
* SKIRT RING (rotational symmetry for proper corners):
|
|
21
|
+
* o---o---o---o---o
|
|
22
|
+
* | \ | / | \ | / |
|
|
23
|
+
* o---o---o---o---o
|
|
24
|
+
* | / | | \ |
|
|
25
|
+
* o---o o---o
|
|
26
|
+
* | \ | | / |
|
|
27
|
+
* o---o---o---o---o
|
|
28
|
+
* | / | \ | / | \ |
|
|
29
|
+
* o---o---o---o---o
|
|
30
|
+
*
|
|
31
|
+
* INNER GRID (consistent diagonal, no rotational symmetry):
|
|
32
|
+
* o---o---o
|
|
33
|
+
* | \ | \ |
|
|
34
|
+
* o---o---o
|
|
35
|
+
* | \ | \ |
|
|
36
|
+
* o---o---o
|
|
37
|
+
*
|
|
38
|
+
* Where o = vertex
|
|
39
|
+
* Each square cell is split into 2 triangles.
|
|
40
|
+
* - Skirt cells (outer ring): diagonal flip based on quadrant for corner correctness
|
|
41
|
+
* - Inner cells: consistent diagonal direction (all triangles "point" the same way)
|
|
42
|
+
*
|
|
43
|
+
* Vertex layout (for innerSegments = 2):
|
|
44
|
+
*
|
|
45
|
+
* 0----1----2----3----4
|
|
46
|
+
* | | | | |
|
|
47
|
+
* 5----6----7----8----9
|
|
48
|
+
* | | | | |
|
|
49
|
+
* 10---11---12---13---14
|
|
50
|
+
* | | | | |
|
|
51
|
+
* 15---16---17---18---19
|
|
52
|
+
* | | | | |
|
|
53
|
+
* 20---21---22---23---24
|
|
54
|
+
*
|
|
55
|
+
* For each cell:
|
|
56
|
+
* a = top-left,
|
|
57
|
+
* b = top-right,
|
|
58
|
+
* c = bottom-left,
|
|
59
|
+
* d = bottom-right (all as flat array indices)
|
|
60
|
+
*
|
|
61
|
+
* Diagonal a-d:
|
|
62
|
+
* triangle 1: a, d, b
|
|
63
|
+
* triangle 2: a, c, d
|
|
64
|
+
* Diagonal b-c:
|
|
65
|
+
* triangle 1: a, c, b
|
|
66
|
+
* triangle 2: b, c, d
|
|
67
|
+
*/
|
|
68
|
+
private generateIndices;
|
|
69
|
+
/**
|
|
70
|
+
* Generate vertex positions for the terrain with skirts.
|
|
71
|
+
* Positions are normalized to [-0.5, 0.5] range.
|
|
72
|
+
*/
|
|
73
|
+
private generatePositions;
|
|
74
|
+
/**
|
|
75
|
+
* Generate UV coordinates for the inner grid only (skirt duplicates clamped to border).
|
|
76
|
+
* UVs are normalized to [0, 1] range with flipped V.
|
|
77
|
+
*/
|
|
78
|
+
private generateUvsOnlyInner;
|
|
79
|
+
/**
|
|
80
|
+
* Generate UVs that extend 1 extra unit outward to the skirt ring.
|
|
81
|
+
* Map the entire geometry (including skirts) into [0,1] so side faces
|
|
82
|
+
* receive proper UVs without relying on texture wrapping. V is flipped.
|
|
83
|
+
*/
|
|
84
|
+
private generateUvsExtended;
|
|
85
|
+
/**
|
|
86
|
+
* Generate vertex normals.
|
|
87
|
+
*/
|
|
88
|
+
private generateNormals;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Returns a node that is true for skirt vertices in the vertex stage.
|
|
93
|
+
*
|
|
94
|
+
* @remarks
|
|
95
|
+
* Only valid in the vertex shader. A vertex belongs to the skirt if it is on
|
|
96
|
+
* the outermost ring of the tile grid (first/last column or row). The grid
|
|
97
|
+
* resolution is derived from `segments`.
|
|
98
|
+
*
|
|
99
|
+
* @param segments - The number of inner segments in the terrain grid.
|
|
100
|
+
* @returns A node resolving to a boolean indicating a skirt vertex.
|
|
101
|
+
*/
|
|
102
|
+
declare const isSkirtVertex: three_src_nodes_TSL_js.ShaderNodeFn<[segments: number | Node]>;
|
|
103
|
+
/**
|
|
104
|
+
* Returns a node that is true for skirt UVs.
|
|
105
|
+
*
|
|
106
|
+
* @remarks
|
|
107
|
+
* Uses interpolated UVs and the grid size
|
|
108
|
+
* from `segments` to mark fragments outside the inner range
|
|
109
|
+
* `(step, 1 - step)` on either axis as skirt, where `step = 1 / (segments + 2)`.
|
|
110
|
+
*
|
|
111
|
+
* @param segments - The number of inner segments in the terrain grid.
|
|
112
|
+
* @returns A node resolving to a boolean indicating a skirt fragment.
|
|
113
|
+
*/
|
|
114
|
+
declare const isSkirtUV: three_src_nodes_TSL_js.ShaderNodeFn<[segments: number | Node]>;
|
|
115
|
+
|
|
116
|
+
export { TerrainGeometry, isSkirtUV, isSkirtVertex };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { BufferGeometry } from 'three';
|
|
2
|
+
import * as three_src_nodes_TSL_js from 'three/src/nodes/TSL.js';
|
|
3
|
+
import { Node } from 'three/webgpu';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Custom geometry for terrain tiles with properly handled skirts.
|
|
7
|
+
* This geometry ensures that corner triangles are subdivided correctly.
|
|
8
|
+
*/
|
|
9
|
+
declare class TerrainGeometry extends BufferGeometry {
|
|
10
|
+
constructor(innerSegments?: number, extendUV?: boolean);
|
|
11
|
+
/**
|
|
12
|
+
* Generate indices for terrain geometry with proper skirt corner handling.
|
|
13
|
+
* The key improvement is in how corner triangles are subdivided.
|
|
14
|
+
*/
|
|
15
|
+
/**
|
|
16
|
+
* Generate indices for terrain geometry with proper skirt corner handling.
|
|
17
|
+
*
|
|
18
|
+
* The mesh layout is a regular grid (with duplicated outermost ring for skirt):
|
|
19
|
+
*
|
|
20
|
+
* SKIRT RING (rotational symmetry for proper corners):
|
|
21
|
+
* o---o---o---o---o
|
|
22
|
+
* | \ | / | \ | / |
|
|
23
|
+
* o---o---o---o---o
|
|
24
|
+
* | / | | \ |
|
|
25
|
+
* o---o o---o
|
|
26
|
+
* | \ | | / |
|
|
27
|
+
* o---o---o---o---o
|
|
28
|
+
* | / | \ | / | \ |
|
|
29
|
+
* o---o---o---o---o
|
|
30
|
+
*
|
|
31
|
+
* INNER GRID (consistent diagonal, no rotational symmetry):
|
|
32
|
+
* o---o---o
|
|
33
|
+
* | \ | \ |
|
|
34
|
+
* o---o---o
|
|
35
|
+
* | \ | \ |
|
|
36
|
+
* o---o---o
|
|
37
|
+
*
|
|
38
|
+
* Where o = vertex
|
|
39
|
+
* Each square cell is split into 2 triangles.
|
|
40
|
+
* - Skirt cells (outer ring): diagonal flip based on quadrant for corner correctness
|
|
41
|
+
* - Inner cells: consistent diagonal direction (all triangles "point" the same way)
|
|
42
|
+
*
|
|
43
|
+
* Vertex layout (for innerSegments = 2):
|
|
44
|
+
*
|
|
45
|
+
* 0----1----2----3----4
|
|
46
|
+
* | | | | |
|
|
47
|
+
* 5----6----7----8----9
|
|
48
|
+
* | | | | |
|
|
49
|
+
* 10---11---12---13---14
|
|
50
|
+
* | | | | |
|
|
51
|
+
* 15---16---17---18---19
|
|
52
|
+
* | | | | |
|
|
53
|
+
* 20---21---22---23---24
|
|
54
|
+
*
|
|
55
|
+
* For each cell:
|
|
56
|
+
* a = top-left,
|
|
57
|
+
* b = top-right,
|
|
58
|
+
* c = bottom-left,
|
|
59
|
+
* d = bottom-right (all as flat array indices)
|
|
60
|
+
*
|
|
61
|
+
* Diagonal a-d:
|
|
62
|
+
* triangle 1: a, d, b
|
|
63
|
+
* triangle 2: a, c, d
|
|
64
|
+
* Diagonal b-c:
|
|
65
|
+
* triangle 1: a, c, b
|
|
66
|
+
* triangle 2: b, c, d
|
|
67
|
+
*/
|
|
68
|
+
private generateIndices;
|
|
69
|
+
/**
|
|
70
|
+
* Generate vertex positions for the terrain with skirts.
|
|
71
|
+
* Positions are normalized to [-0.5, 0.5] range.
|
|
72
|
+
*/
|
|
73
|
+
private generatePositions;
|
|
74
|
+
/**
|
|
75
|
+
* Generate UV coordinates for the inner grid only (skirt duplicates clamped to border).
|
|
76
|
+
* UVs are normalized to [0, 1] range with flipped V.
|
|
77
|
+
*/
|
|
78
|
+
private generateUvsOnlyInner;
|
|
79
|
+
/**
|
|
80
|
+
* Generate UVs that extend 1 extra unit outward to the skirt ring.
|
|
81
|
+
* Map the entire geometry (including skirts) into [0,1] so side faces
|
|
82
|
+
* receive proper UVs without relying on texture wrapping. V is flipped.
|
|
83
|
+
*/
|
|
84
|
+
private generateUvsExtended;
|
|
85
|
+
/**
|
|
86
|
+
* Generate vertex normals.
|
|
87
|
+
*/
|
|
88
|
+
private generateNormals;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Returns a node that is true for skirt vertices in the vertex stage.
|
|
93
|
+
*
|
|
94
|
+
* @remarks
|
|
95
|
+
* Only valid in the vertex shader. A vertex belongs to the skirt if it is on
|
|
96
|
+
* the outermost ring of the tile grid (first/last column or row). The grid
|
|
97
|
+
* resolution is derived from `segments`.
|
|
98
|
+
*
|
|
99
|
+
* @param segments - The number of inner segments in the terrain grid.
|
|
100
|
+
* @returns A node resolving to a boolean indicating a skirt vertex.
|
|
101
|
+
*/
|
|
102
|
+
declare const isSkirtVertex: three_src_nodes_TSL_js.ShaderNodeFn<[segments: number | Node]>;
|
|
103
|
+
/**
|
|
104
|
+
* Returns a node that is true for skirt UVs.
|
|
105
|
+
*
|
|
106
|
+
* @remarks
|
|
107
|
+
* Uses interpolated UVs and the grid size
|
|
108
|
+
* from `segments` to mark fragments outside the inner range
|
|
109
|
+
* `(step, 1 - step)` on either axis as skirt, where `step = 1 / (segments + 2)`.
|
|
110
|
+
*
|
|
111
|
+
* @param segments - The number of inner segments in the terrain grid.
|
|
112
|
+
* @returns A node resolving to a boolean indicating a skirt fragment.
|
|
113
|
+
*/
|
|
114
|
+
declare const isSkirtUV: three_src_nodes_TSL_js.ShaderNodeFn<[segments: number | Node]>;
|
|
115
|
+
|
|
116
|
+
export { TerrainGeometry, isSkirtUV, isSkirtVertex };
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
import { BufferGeometry, BufferAttribute } from 'three';
|
|
2
|
+
import { Fn, int, vertexIndex, uv, float } from 'three/tsl';
|
|
3
|
+
|
|
4
|
+
class TerrainGeometry extends BufferGeometry {
|
|
5
|
+
constructor(innerSegments = 14, extendUV = false) {
|
|
6
|
+
super();
|
|
7
|
+
if (innerSegments < 1 || !Number.isFinite(innerSegments) || !Number.isInteger(innerSegments)) {
|
|
8
|
+
throw new Error(
|
|
9
|
+
`Invalid innerSegments: ${innerSegments}. Must be a positive integer.`
|
|
10
|
+
);
|
|
11
|
+
}
|
|
12
|
+
try {
|
|
13
|
+
this.setIndex(this.generateIndices(innerSegments));
|
|
14
|
+
this.setAttribute(
|
|
15
|
+
"position",
|
|
16
|
+
new BufferAttribute(
|
|
17
|
+
new Float32Array(this.generatePositions(innerSegments)),
|
|
18
|
+
3
|
|
19
|
+
)
|
|
20
|
+
);
|
|
21
|
+
this.setAttribute(
|
|
22
|
+
"normal",
|
|
23
|
+
new BufferAttribute(
|
|
24
|
+
new Float32Array(this.generateNormals(innerSegments)),
|
|
25
|
+
3
|
|
26
|
+
)
|
|
27
|
+
);
|
|
28
|
+
this.setAttribute(
|
|
29
|
+
"uv",
|
|
30
|
+
new BufferAttribute(
|
|
31
|
+
new Float32Array(
|
|
32
|
+
extendUV ? this.generateUvsExtended(innerSegments) : this.generateUvsOnlyInner(innerSegments)
|
|
33
|
+
),
|
|
34
|
+
2
|
|
35
|
+
)
|
|
36
|
+
);
|
|
37
|
+
} catch (error) {
|
|
38
|
+
console.error("Error creating TerrainGeometry:", error);
|
|
39
|
+
throw error;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Generate indices for terrain geometry with proper skirt corner handling.
|
|
44
|
+
* The key improvement is in how corner triangles are subdivided.
|
|
45
|
+
*/
|
|
46
|
+
/**
|
|
47
|
+
* Generate indices for terrain geometry with proper skirt corner handling.
|
|
48
|
+
*
|
|
49
|
+
* The mesh layout is a regular grid (with duplicated outermost ring for skirt):
|
|
50
|
+
*
|
|
51
|
+
* SKIRT RING (rotational symmetry for proper corners):
|
|
52
|
+
* o---o---o---o---o
|
|
53
|
+
* | \ | / | \ | / |
|
|
54
|
+
* o---o---o---o---o
|
|
55
|
+
* | / | | \ |
|
|
56
|
+
* o---o o---o
|
|
57
|
+
* | \ | | / |
|
|
58
|
+
* o---o---o---o---o
|
|
59
|
+
* | / | \ | / | \ |
|
|
60
|
+
* o---o---o---o---o
|
|
61
|
+
*
|
|
62
|
+
* INNER GRID (consistent diagonal, no rotational symmetry):
|
|
63
|
+
* o---o---o
|
|
64
|
+
* | \ | \ |
|
|
65
|
+
* o---o---o
|
|
66
|
+
* | \ | \ |
|
|
67
|
+
* o---o---o
|
|
68
|
+
*
|
|
69
|
+
* Where o = vertex
|
|
70
|
+
* Each square cell is split into 2 triangles.
|
|
71
|
+
* - Skirt cells (outer ring): diagonal flip based on quadrant for corner correctness
|
|
72
|
+
* - Inner cells: consistent diagonal direction (all triangles "point" the same way)
|
|
73
|
+
*
|
|
74
|
+
* Vertex layout (for innerSegments = 2):
|
|
75
|
+
*
|
|
76
|
+
* 0----1----2----3----4
|
|
77
|
+
* | | | | |
|
|
78
|
+
* 5----6----7----8----9
|
|
79
|
+
* | | | | |
|
|
80
|
+
* 10---11---12---13---14
|
|
81
|
+
* | | | | |
|
|
82
|
+
* 15---16---17---18---19
|
|
83
|
+
* | | | | |
|
|
84
|
+
* 20---21---22---23---24
|
|
85
|
+
*
|
|
86
|
+
* For each cell:
|
|
87
|
+
* a = top-left,
|
|
88
|
+
* b = top-right,
|
|
89
|
+
* c = bottom-left,
|
|
90
|
+
* d = bottom-right (all as flat array indices)
|
|
91
|
+
*
|
|
92
|
+
* Diagonal a-d:
|
|
93
|
+
* triangle 1: a, d, b
|
|
94
|
+
* triangle 2: a, c, d
|
|
95
|
+
* Diagonal b-c:
|
|
96
|
+
* triangle 1: a, c, b
|
|
97
|
+
* triangle 2: b, c, d
|
|
98
|
+
*/
|
|
99
|
+
generateIndices(innerSegments) {
|
|
100
|
+
const innerEdgeVertexCount = innerSegments + 1;
|
|
101
|
+
const edgeVertexCountWithSkirt = innerEdgeVertexCount + 2;
|
|
102
|
+
const indices = [];
|
|
103
|
+
const cellsPerEdge = edgeVertexCountWithSkirt - 1;
|
|
104
|
+
const mid = Math.floor(cellsPerEdge / 2);
|
|
105
|
+
for (let y = 0; y < cellsPerEdge; y++) {
|
|
106
|
+
for (let x = 0; x < cellsPerEdge; x++) {
|
|
107
|
+
const a = y * edgeVertexCountWithSkirt + x;
|
|
108
|
+
const b = a + 1;
|
|
109
|
+
const c = a + edgeVertexCountWithSkirt;
|
|
110
|
+
const d = c + 1;
|
|
111
|
+
const isSkirtCell = x === 0 || x === cellsPerEdge - 1 || y === 0 || y === cellsPerEdge - 1;
|
|
112
|
+
let useDefaultDiagonal;
|
|
113
|
+
if (isSkirtCell) {
|
|
114
|
+
const leftHalf = x < mid;
|
|
115
|
+
const topHalf = y < mid;
|
|
116
|
+
useDefaultDiagonal = leftHalf && topHalf || !leftHalf && !topHalf;
|
|
117
|
+
} else {
|
|
118
|
+
useDefaultDiagonal = true;
|
|
119
|
+
}
|
|
120
|
+
if (useDefaultDiagonal) {
|
|
121
|
+
indices.push(a, d, b);
|
|
122
|
+
indices.push(a, c, d);
|
|
123
|
+
} else {
|
|
124
|
+
indices.push(a, c, b);
|
|
125
|
+
indices.push(b, c, d);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return indices;
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Generate vertex positions for the terrain with skirts.
|
|
133
|
+
* Positions are normalized to [-0.5, 0.5] range.
|
|
134
|
+
*/
|
|
135
|
+
generatePositions(innerSegments) {
|
|
136
|
+
const edgeVertexCountWithSkirt = innerSegments + 1 + 2;
|
|
137
|
+
const positions = [];
|
|
138
|
+
for (let iy = 0; iy < edgeVertexCountWithSkirt; iy++) {
|
|
139
|
+
const v = Math.min(Math.max((iy - 1) / innerSegments, 0), 1);
|
|
140
|
+
const z = v - 0.5;
|
|
141
|
+
for (let ix = 0; ix < edgeVertexCountWithSkirt; ix++) {
|
|
142
|
+
const u = Math.min(Math.max((ix - 1) / innerSegments, 0), 1);
|
|
143
|
+
const x = u - 0.5;
|
|
144
|
+
positions.push(x, 0, z);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
return positions;
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Generate UV coordinates for the inner grid only (skirt duplicates clamped to border).
|
|
151
|
+
* UVs are normalized to [0, 1] range with flipped V.
|
|
152
|
+
*/
|
|
153
|
+
generateUvsOnlyInner(innerSegments) {
|
|
154
|
+
const edgeVertexCountWithSkirt = innerSegments + 1 + 2;
|
|
155
|
+
const uvs = [];
|
|
156
|
+
for (let iy = 0; iy < edgeVertexCountWithSkirt; iy++) {
|
|
157
|
+
const v = Math.min(Math.max((iy - 1) / innerSegments, 0), 1);
|
|
158
|
+
for (let ix = 0; ix < edgeVertexCountWithSkirt; ix++) {
|
|
159
|
+
const u = Math.min(Math.max((ix - 1) / innerSegments, 0), 1);
|
|
160
|
+
uvs.push(u, 1 - v);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
return uvs;
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Generate UVs that extend 1 extra unit outward to the skirt ring.
|
|
167
|
+
* Map the entire geometry (including skirts) into [0,1] so side faces
|
|
168
|
+
* receive proper UVs without relying on texture wrapping. V is flipped.
|
|
169
|
+
*/
|
|
170
|
+
generateUvsExtended(innerSegments) {
|
|
171
|
+
const edgeVertexCountWithSkirt = innerSegments + 1 + 2;
|
|
172
|
+
const uvs = [];
|
|
173
|
+
const denom = edgeVertexCountWithSkirt - 1;
|
|
174
|
+
for (let iy = 0; iy < edgeVertexCountWithSkirt; iy++) {
|
|
175
|
+
const v = iy / denom;
|
|
176
|
+
for (let ix = 0; ix < edgeVertexCountWithSkirt; ix++) {
|
|
177
|
+
const u = ix / denom;
|
|
178
|
+
uvs.push(u, 1 - v);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
return uvs;
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Generate vertex normals.
|
|
185
|
+
*/
|
|
186
|
+
generateNormals(innerSegments) {
|
|
187
|
+
const edgeVertexCountWithSkirt = innerSegments + 1 + 2;
|
|
188
|
+
const last = edgeVertexCountWithSkirt - 1;
|
|
189
|
+
const normals = [];
|
|
190
|
+
for (let iy = 0; iy < edgeVertexCountWithSkirt; iy++) {
|
|
191
|
+
for (let ix = 0; ix < edgeVertexCountWithSkirt; ix++) {
|
|
192
|
+
const onEdgeX = ix === 0 || ix === last;
|
|
193
|
+
const onEdgeY = iy === 0 || iy === last;
|
|
194
|
+
if (onEdgeX || onEdgeY) {
|
|
195
|
+
let nx = 0;
|
|
196
|
+
let nz = 0;
|
|
197
|
+
if (ix === 0) nx -= 1;
|
|
198
|
+
if (ix === last) nx += 1;
|
|
199
|
+
if (iy === 0) nz -= 1;
|
|
200
|
+
if (iy === last) nz += 1;
|
|
201
|
+
const len = Math.hypot(nx, nz);
|
|
202
|
+
if (len > 0) {
|
|
203
|
+
normals.push(nx / len, 0, nz / len);
|
|
204
|
+
} else {
|
|
205
|
+
normals.push(0, 1, 0);
|
|
206
|
+
}
|
|
207
|
+
} else {
|
|
208
|
+
normals.push(0, 1, 0);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
return normals;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const isSkirtVertex = Fn(([segments]) => {
|
|
217
|
+
const segmentsNode = typeof segments === "number" ? int(segments) : segments;
|
|
218
|
+
const vIndex = int(vertexIndex);
|
|
219
|
+
const segmentEdges = int(segmentsNode.add(3));
|
|
220
|
+
const vx = vIndex.mod(segmentEdges);
|
|
221
|
+
const vy = vIndex.div(segmentEdges);
|
|
222
|
+
const last = segmentEdges.sub(int(1));
|
|
223
|
+
return vx.equal(int(0)).or(vx.equal(last)).or(vy.equal(int(0))).or(vy.equal(last));
|
|
224
|
+
});
|
|
225
|
+
const isSkirtUV = Fn(([segments]) => {
|
|
226
|
+
const segmentsNode = typeof segments === "number" ? int(segments) : segments;
|
|
227
|
+
const ux = uv().x;
|
|
228
|
+
const uy = uv().y;
|
|
229
|
+
const segmentCount = segmentsNode.add(2);
|
|
230
|
+
const segmentStep = float(1).div(segmentCount);
|
|
231
|
+
const innerX = ux.greaterThan(segmentStep).and(ux.lessThan(segmentStep.oneMinus()));
|
|
232
|
+
const innerY = uy.greaterThan(segmentStep).and(uy.lessThan(segmentStep.oneMinus()));
|
|
233
|
+
return innerX.and(innerY).not();
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
export { TerrainGeometry, isSkirtUV, isSkirtVertex };
|
package/package.json
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@hello-terrain/three",
|
|
3
|
+
"description": "High performance terrain system for three.js",
|
|
4
|
+
"version": "0.0.0-alpha.1",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.mjs",
|
|
7
|
+
"module": "./dist/index.mjs",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"import": "./dist/index.mjs",
|
|
13
|
+
"require": "./dist/index.cjs"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"dist"
|
|
18
|
+
],
|
|
19
|
+
"peerDependencies": {
|
|
20
|
+
"three": ">=0.182.0"
|
|
21
|
+
},
|
|
22
|
+
"devDependencies": {
|
|
23
|
+
"unbuild": "^3.5.0",
|
|
24
|
+
"vitest": "^4.0.16",
|
|
25
|
+
"@types/three": ">=0.182.0",
|
|
26
|
+
"@config/oxfmt": "0.1.0",
|
|
27
|
+
"@config/oxlint": "0.1.0",
|
|
28
|
+
"@config/typescript": "0.1.0"
|
|
29
|
+
},
|
|
30
|
+
"scripts": {
|
|
31
|
+
"build": "unbuild",
|
|
32
|
+
"release": "pnpm run build && pnpm publish --access=public",
|
|
33
|
+
"test": "vitest",
|
|
34
|
+
"lint": "oxlint -c node_modules/@config/oxlint/base.json",
|
|
35
|
+
"format": "oxfmt -c node_modules/@config/oxfmt/base.json --write ."
|
|
36
|
+
}
|
|
37
|
+
}
|