spoint 0.1.586 → 0.1.588
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/package.json
CHANGED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { marchRockSurface } from './RockShapes.js'
|
|
2
|
+
|
|
3
|
+
export function makeSphereCaveSDF(cx, cy, cz, radius) {
|
|
4
|
+
return (x, y, z) => {
|
|
5
|
+
const dx = x - cx, dy = y - cy, dz = z - cz
|
|
6
|
+
return Math.sqrt(dx * dx + dy * dy + dz * dz) - radius
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function polygonizeCaveSDF(sdf, res = 24) {
|
|
11
|
+
return marchRockSurface(res, sdf)
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function createCaveVolume(worldX, worldY, worldZ, worldRadius) {
|
|
15
|
+
const sdf = makeSphereCaveSDF(0, 0, 0, 1)
|
|
16
|
+
const localToWorld = (lx, ly, lz) => [worldX + lx * worldRadius, worldY + ly * worldRadius, worldZ + lz * worldRadius]
|
|
17
|
+
const worldToLocal = (wx, wy, wz) => [(wx - worldX) / worldRadius, (wy - worldY) / worldRadius, (wz - worldZ) / worldRadius]
|
|
18
|
+
const worldSDF = (wx, wy, wz) => {
|
|
19
|
+
const [lx, ly, lz] = worldToLocal(wx, wy, wz)
|
|
20
|
+
return sdf(lx, ly, lz) * worldRadius
|
|
21
|
+
}
|
|
22
|
+
return { sdf: worldSDF, localToWorld, worldToLocal, worldX, worldY, worldZ, worldRadius }
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function polygonizeCaveVolume(caveVolume, res = 24) {
|
|
26
|
+
const local = polygonizeCaveSDF((lx, ly, lz) => makeSphereCaveSDF(0, 0, 0, 1)(lx, ly, lz), res)
|
|
27
|
+
const positions = new Float32Array(local.positions.length)
|
|
28
|
+
for (let v = 0; v < local.vc; v++) {
|
|
29
|
+
const [wx, wy, wz] = caveVolume.localToWorld(local.positions[v * 3], local.positions[v * 3 + 1], local.positions[v * 3 + 2])
|
|
30
|
+
positions[v * 3] = wx
|
|
31
|
+
positions[v * 3 + 1] = wy
|
|
32
|
+
positions[v * 3 + 2] = wz
|
|
33
|
+
}
|
|
34
|
+
return { positions: positions.subarray(0, local.vc * 3), vc: local.vc, indices: local.indices, ic: local.ic }
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function createCaveCarveLayer() {
|
|
38
|
+
const volumes = []
|
|
39
|
+
|
|
40
|
+
function addSphereCave(worldX, worldY, worldZ, worldRadius) {
|
|
41
|
+
if (!Number.isFinite(worldX) || !Number.isFinite(worldY) || !Number.isFinite(worldZ)) return { added: false }
|
|
42
|
+
if (!Number.isFinite(worldRadius) || worldRadius <= 0) return { added: false }
|
|
43
|
+
volumes.push(createCaveVolume(worldX, worldY, worldZ, worldRadius))
|
|
44
|
+
return { added: true, count: volumes.length }
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function heightDeltaAt(x, z, surfaceY) {
|
|
48
|
+
if (volumes.length === 0) return 0
|
|
49
|
+
let delta = 0
|
|
50
|
+
for (const vol of volumes) {
|
|
51
|
+
const dx = x - vol.worldX, dz = z - vol.worldZ
|
|
52
|
+
const horizontalDist = Math.hypot(dx, dz)
|
|
53
|
+
if (horizontalDist > vol.worldRadius) continue
|
|
54
|
+
const verticalSpan = Math.sqrt(Math.max(0, vol.worldRadius * vol.worldRadius - horizontalDist * horizontalDist))
|
|
55
|
+
const caveTop = vol.worldY + verticalSpan
|
|
56
|
+
if (caveTop < surfaceY) continue
|
|
57
|
+
delta -= (caveTop - surfaceY) + verticalSpan
|
|
58
|
+
}
|
|
59
|
+
return delta
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function wrapHeightFn(baseHeightFn) {
|
|
63
|
+
if (typeof baseHeightFn !== 'function') return baseHeightFn
|
|
64
|
+
return function caveWrappedHeightFn(x, z) {
|
|
65
|
+
const base = baseHeightFn(x, z)
|
|
66
|
+
if (!Number.isFinite(base)) return base
|
|
67
|
+
return base + heightDeltaAt(x, z, base)
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function toJSON() {
|
|
72
|
+
return { version: 1, volumes: volumes.map(v => ({ worldX: v.worldX, worldY: v.worldY, worldZ: v.worldZ, worldRadius: v.worldRadius })) }
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function clear() { volumes.length = 0 }
|
|
76
|
+
|
|
77
|
+
return {
|
|
78
|
+
addSphereCave, heightDeltaAt, wrapHeightFn, toJSON, clear,
|
|
79
|
+
get volumeCount() { return volumes.length },
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function loadCaveCarveLayer(json) {
|
|
84
|
+
const layer = createCaveCarveLayer()
|
|
85
|
+
if (json && Array.isArray(json.volumes)) {
|
|
86
|
+
for (const v of json.volumes) {
|
|
87
|
+
if (!v || !Number.isFinite(v.worldX) || !Number.isFinite(v.worldY) || !Number.isFinite(v.worldZ) || !Number.isFinite(v.worldRadius)) continue
|
|
88
|
+
layer.addSphereCave(v.worldX, v.worldY, v.worldZ, v.worldRadius)
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return layer
|
|
92
|
+
}
|
|
@@ -6,6 +6,7 @@ import { createCachedAnchorField } from './ClimateCache.js'
|
|
|
6
6
|
import { createHeightDelta, loadHeightDelta } from './HeightDelta.js'
|
|
7
7
|
import { createBiomeOverride, loadBiomeOverride } from './BiomeOverride.js'
|
|
8
8
|
import { loadSplineCarveLayer } from './SplineCarve.js'
|
|
9
|
+
import { loadCaveCarveLayer } from './CaveSDF.js'
|
|
9
10
|
|
|
10
11
|
const _now = () => (typeof performance !== 'undefined' ? performance.now() : Date.now())
|
|
11
12
|
|
|
@@ -235,7 +236,7 @@ async function createGpuPatchHeightFn({ frame, tcfg, offsetY }) {
|
|
|
235
236
|
// (re)build -- kept strictly separate from tcfg/seed (never merged into it) so seeds stay shareable.
|
|
236
237
|
// biomeOverrideJSON (optional): a previously-serialized src/terrain/BiomeOverride.js toJSON() payload
|
|
237
238
|
// (paint-biome strokes), same replay discipline as heightDeltaJSON.
|
|
238
|
-
export async function setupTerrainStreaming({ physics, playerManager, worldDef, terrain, heightDeltaJSON, biomeOverrideJSON, splineCarveJSON }) {
|
|
239
|
+
export async function setupTerrainStreaming({ physics, playerManager, worldDef, terrain, heightDeltaJSON, biomeOverrideJSON, splineCarveJSON, caveCarveJSON }) {
|
|
239
240
|
// accepts either the terrain app's merged tcfg directly, or legacy worldDef.terrain.
|
|
240
241
|
const tcfg = terrain || (worldDef && worldDef.terrain) || null
|
|
241
242
|
if (!tcfg || tcfg.enabled === false || !physics || typeof physics.addHeightField !== 'function') return null
|
|
@@ -285,7 +286,8 @@ export async function setupTerrainStreaming({ physics, playerManager, worldDef,
|
|
|
285
286
|
// stays byte-identical, sculpting is purely an on-top layer. heightDeltaJSON (if any) replays prior
|
|
286
287
|
// strokes so a rebuild (reseed/streamer restart) keeps existing sculpting intact.
|
|
287
288
|
const heightDelta = loadHeightDelta(heightDeltaJSON, baseHeightFn)
|
|
288
|
-
const
|
|
289
|
+
const caveCarve = loadCaveCarveLayer(caveCarveJSON)
|
|
290
|
+
const heightFn = caveCarve.wrapHeightFn(splineCarve.wrapHeightFn(heightDelta.wrapHeightFn(baseHeightFn)))
|
|
289
291
|
const getCenter = () => {
|
|
290
292
|
let sx = 0, sz = 0, n = 0
|
|
291
293
|
const players = playerManager && playerManager.players
|
|
@@ -362,6 +364,7 @@ export async function setupTerrainStreaming({ physics, playerManager, worldDef,
|
|
|
362
364
|
// keep serving pre-paint species/density for already-visited territory.
|
|
363
365
|
streamer.biomeOverride = biomeOverride
|
|
364
366
|
streamer.splineCarve = splineCarve
|
|
367
|
+
streamer.caveCarve = caveCarve
|
|
365
368
|
streamer.repaintBiome = async function repaintBiome() {
|
|
366
369
|
// Re-cook the FULL current multi-player ring (not just one point) so every connected player's
|
|
367
370
|
// territory picks up the repaint, matching this row's "around each player" streaming policy.
|