spoint 0.1.602 → 0.1.603

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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "spoint",
3
- "version": "0.1.602",
3
+ "version": "0.1.603",
4
4
  "description": "Physics and netcode SDK for multiplayer game servers",
5
5
  "type": "module",
6
6
  "workspaces": [
@@ -6,6 +6,15 @@ function cellKey(cx, cz) {
6
6
  return (cx + OFF) * BIG + (cz + OFF)
7
7
  }
8
8
 
9
+ function carveHash(seed, i) {
10
+ let h = seed | 0
11
+ h = Math.imul(h ^ (i | 0), 0x27d4eb2d) >>> 0
12
+ h ^= h >>> 15; h = Math.imul(h, 0x2c1b3c6d) >>> 0
13
+ h ^= h >>> 12; h = Math.imul(h, 0x297a2d39) >>> 0
14
+ h ^= h >>> 15
15
+ return (h >>> 0) / 4294967296
16
+ }
17
+
9
18
  function catmullRom(p0, p1, p2, p3, t) {
10
19
  const getX = (p) => Array.isArray(p) ? p[0] : p.x
11
20
  const getZ = (p) => Array.isArray(p) ? p[1] : p.z
@@ -134,30 +143,47 @@ export function createSplineCarveLayer() {
134
143
  const cells = new Map()
135
144
  const appliedSplines = []
136
145
 
137
- function carve(controlPoints, width, depth, kind, baseHeightFn) {
146
+ function carve(controlPoints, width, depth, kind, baseHeightFn, opts = {}) {
138
147
  if (!Array.isArray(controlPoints) || controlPoints.length < 2) return { touched: 0 }
139
148
  if (!Number.isFinite(width) || width <= 0 || !Number.isFinite(depth)) return { touched: 0 }
140
149
  if (kind !== 'river' && kind !== 'road') return { touched: 0 }
150
+ const { seed = 0, widthVariance = 0, bankErosion = 0 } = opts
151
+ const isRiver = kind === 'river'
141
152
  const stepSize = Math.max(1, width * 0.5)
142
153
  const spinePoints = sampleSpline(controlPoints, stepSize)
143
154
  if (spinePoints.length === 0) return { touched: 0 }
144
- const halfWidth = width * 0.5
155
+ const maxHalfWidth = (width * 0.5) * (1 + (isRiver ? widthVariance : 0)) + (isRiver ? bankErosion : 0)
145
156
  let touched = 0
146
- for (const sp of spinePoints) {
157
+ let arcLen = 0
158
+ for (let si = 0; si < spinePoints.length; si++) {
159
+ const sp = spinePoints[si]
147
160
  const sx = sp.x, sz = sp.z
148
- const cx0 = Math.floor((sx - halfWidth) / CELL_M), cx1 = Math.ceil((sx + halfWidth) / CELL_M)
149
- const cz0 = Math.floor((sz - halfWidth) / CELL_M), cz1 = Math.ceil((sz + halfWidth) / CELL_M)
161
+ if (si > 0) { const px = spinePoints[si - 1].x, pz = spinePoints[si - 1].z; arcLen += Math.hypot(sx - px, sz - pz) }
162
+ let halfWidth = width * 0.5
163
+ if (isRiver && widthVariance > 0) {
164
+ const t = carveHash(seed, Math.round(arcLen * 0.1))
165
+ halfWidth *= 1 + (t * 2 - 1) * widthVariance
166
+ }
167
+ const cx0 = Math.floor((sx - maxHalfWidth) / CELL_M), cx1 = Math.ceil((sx + maxHalfWidth) / CELL_M)
168
+ const cz0 = Math.floor((sz - maxHalfWidth) / CELL_M), cz1 = Math.ceil((sz + maxHalfWidth) / CELL_M)
150
169
  for (let cz = cz0; cz <= cz1; cz++) {
151
170
  for (let cx = cx0; cx <= cx1; cx++) {
152
171
  const cMinX = cx * CELL_M, cMinZ = cz * CELL_M
172
+ const ccx = cMinX + CELL_M * 0.5, ccz = cMinZ + CELL_M * 0.5
173
+ let bankOffset = 0
174
+ if (isRiver && bankErosion > 0) {
175
+ const angle = carveHash(seed ^ 0x5bd1e995, cx * 92821 + cz) * Math.PI * 2
176
+ bankOffset = (carveHash(seed ^ 0x1b873593, cx * 15485863 + cz) * 2 - 1) * bankErosion * (0.5 + 0.5 * Math.cos(angle))
177
+ }
178
+ const cellHalfWidth = halfWidth + bankOffset
179
+ if (cellHalfWidth <= 0) continue
153
180
  const nearestX = Math.max(cMinX, Math.min(sx, cMinX + CELL_M))
154
181
  const nearestZ = Math.max(cMinZ, Math.min(sz, cMinZ + CELL_M))
155
182
  const d = Math.hypot(nearestX - sx, nearestZ - sz)
156
- if (d > halfWidth) continue
157
- const ccx = cMinX + CELL_M * 0.5, ccz = cMinZ + CELL_M * 0.5
183
+ if (d > cellHalfWidth) continue
158
184
  const dCenter = Math.hypot(ccx - sx, ccz - sz)
159
- const falloffDist = Math.min(dCenter, halfWidth)
160
- const falloff = 0.5 * (1 + Math.cos((falloffDist / halfWidth) * Math.PI))
185
+ const falloffDist = Math.min(dCenter, cellHalfWidth)
186
+ const falloff = 0.5 * (1 + Math.cos((falloffDist / cellHalfWidth) * Math.PI))
161
187
  const key = cellKey(cx, cz)
162
188
  const base = typeof baseHeightFn === 'function' ? baseHeightFn(ccx, ccz) : null
163
189
  const targetDelta = Number.isFinite(base) ? -depth * falloff : 0
@@ -169,7 +195,7 @@ export function createSplineCarveLayer() {
169
195
  }
170
196
  }
171
197
  }
172
- if (touched > 0) appliedSplines.push({ controlPoints: controlPoints.map(p => Array.isArray(p) ? [p[0], p[1]] : [p.x, p.z]), width, depth, kind })
198
+ if (touched > 0) appliedSplines.push({ controlPoints: controlPoints.map(p => Array.isArray(p) ? [p[0], p[1]] : [p.x, p.z]), width, depth, kind, seed, widthVariance, bankErosion })
173
199
  return { touched }
174
200
  }
175
201
 
@@ -224,7 +250,7 @@ export function loadSplineCarveLayer(json, baseHeightFn) {
224
250
  if (json && Array.isArray(json.splines)) {
225
251
  for (const s of json.splines) {
226
252
  if (!s || !Array.isArray(s.controlPoints) || !Number.isFinite(s.width) || !Number.isFinite(s.depth)) continue
227
- layer.carve(s.controlPoints, s.width, s.depth, s.kind, baseHeightFn)
253
+ layer.carve(s.controlPoints, s.width, s.depth, s.kind, baseHeightFn, { seed: s.seed ?? 0, widthVariance: s.widthVariance ?? 0, bankErosion: s.bankErosion ?? 0 })
228
254
  }
229
255
  }
230
256
  return layer