Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions demo/main.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { mat4 } from 'gl-matrix'
import type { ItemRendererResources, ItemRenderingContext, NbtTag, Resources, Voxel } from '../src/index.js'
import type { Color, ItemRendererResources, ItemRenderingContext, NbtTag, Resources, Voxel } from '../src/index.js'
import { BlockDefinition, BlockModel, Identifier, ItemRenderer, ItemStack, jsonToNbt, NormalNoise, Structure, StructureRenderer, TextureAtlas, upperPowerOfTwo, VoxelRenderer, XoroshiroRandom } from '../src/index.js'
import { } from '../src/nbt/Util.js'
import { ItemModel } from '../src/render/ItemModel.js'
Expand Down Expand Up @@ -200,14 +200,16 @@ Promise.all([

const voxels: Voxel[] = []
const random = XoroshiroRandom.create(BigInt(123))
const noise = new NormalNoise(random, { firstOctave: -5, amplitudes: [1, 1, 1] })
const normalNoise = new NormalNoise(1, -5, 3, 'enabled', [])
const noise = normalNoise.create(random)
const sampleRegion = 50
for (let x = -sampleRegion; x <= sampleRegion; x += 1) {
for (let y = -sampleRegion; y <= sampleRegion; y += 1) {
for (let z = -sampleRegion; z <= sampleRegion; z += 1) {
const d = noise.sample(x, y, z)
const d = noise.get3D(x, y, z)
if (d > 0) {
voxels.push({ x, y, z, color: [200, 200, 200] })
const color: Color = d < 0.2 ? [200, 200, 200] : [120, 30, 180]
voxels.push({ x, y, z, color })
}
}
}
Expand Down
21 changes: 11 additions & 10 deletions src/core/BlockState.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { NbtTag } from '../nbt/index.js'
import { NbtCompound, type NbtTag } from '../nbt/index.js'
import { Json } from '../util/index.js'
import { Identifier } from './Identifier.js'

Expand Down Expand Up @@ -45,8 +45,8 @@ export class BlockState {
return false
}
if (Object.keys(this.properties).length !== Object.keys(other.properties).length) {
return false
}
return false
}
return Object.keys(this.properties).every(p => {
return other.properties[p] === this.properties[p]
})
Expand Down Expand Up @@ -85,19 +85,20 @@ export class BlockState {
if (nbt.isString()) {
return new BlockState(nbt.getAsString())
}
if (!nbt.isCompound()) {
throw new Error(`Invalid block state format ${nbt.toString()}`)
}
const name = Identifier.parse(nbt.hasString('id') ? nbt.getString('id') : nbt.getString('Name'))
const properties = (nbt.hasCompound('properties') ? nbt.getCompound('properties') : nbt.getCompound('Properties'))
const root = nbt.isCompound() ? nbt : new NbtCompound()
const name = Identifier.parse(root.hasString('id') ? root.getString('id') : root.getString('Name'))
const properties = (root.hasCompound('properties') ? root.getCompound('properties') : root.getCompound('Properties'))
.map((key, value) => [key, value.getAsString()])
return new BlockState(name, properties)
}

public static fromJson(obj: unknown) {
if (typeof obj === 'string') {
return new BlockState(obj)
}
const root = Json.readObject(obj) ?? {}
const name = Identifier.parse(Json.readString(root.Name) ?? BlockState.STONE.name.toString())
const properties = Json.readMap(root.Properties, p => Json.readString(p) ?? '')
const name = Identifier.parse(Json.readString(root.id ?? root.Name) ?? '')
const properties = Json.readMap(root.properties ?? root.Properties, p => Json.readString(p) ?? '')
return new BlockState(name, properties)
}
}
104 changes: 104 additions & 0 deletions src/math/Interval.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,18 @@ export class Interval {
return Interval.of(value, value)
}

public static between(first: number, second: number) {
if (isNaN(first) && isNaN(second)) {
return Interval.NaI
} else if (isNaN(first)) {
return Interval.ofExact(second)
} else if (isNaN(second)) {
return Interval.ofExact(first)
} else {
return Interval.of(Math.min(first, second), Math.max(first, second))
}
}

public static encapsulating(...intervals: Interval[]) {
let min = Infinity
let max = -Infinity
Expand Down Expand Up @@ -131,6 +143,38 @@ export class Interval {
}
}

public static pow(base: Interval, exponent: Interval) {
if (base.isNaI() || exponent.isNaI()) {
return Interval.NaI
}
if (base.min == base.max) {
return Interval.powNum(base.min, exponent)
}
let result = Interval.encapsulating(Interval.powNum(base.min, exponent), Interval.powNum(base.max, exponent))
if (base.contains(0)) {
if (base.max > 0) {
result = Interval.encapsulating(result, Interval.powNum(0, exponent))
}
if (base.min < 0) {
result = Interval.encapsulating(result, Interval.powNum(-0, exponent))
}
}
return result
}

private static powNum(base: number, exponent: Interval) {
// TODO: Implement this
return Interval.INFINITE
}

public static log(input: Interval) {
if (input.max < 0) {
return Interval.NaI
}
const clipped = Interval.max(input, Interval.ofExact(0))
return Interval.mapMonotonic(clipped, Math.log)
}

public static mapMonotonic(input: Interval, op: (value: number) => number) {
if (input.isNaI()) {
return Interval.NaI
Expand All @@ -140,6 +184,66 @@ export class Interval {
return Interval.of(Math.min(mappedMin, mappedMax), Math.max(mappedMin, mappedMax))
}

public static lerp(alpha: Interval, first: Interval, second: Interval) {
if (alpha.isNaI() || first.isNaI() || second.isNaI()) {
return Interval.NaI
}
return Interval.encapsulating(Interval.lerpNums(alpha, first.min, second.min), Interval.lerpNums(alpha, first.max, second.min), Interval.lerpNums(alpha, first.min, second.max), Interval.lerpNums(alpha, first.max, second.max))
}

public static lerpNums(alpha: Interval, first: number, second: number) {
if (alpha.isNaI() || isNaN(first) || isNaN(second)) {
return Interval.NaI
}
if (isFinite(first) && isFinite(second)) {
return Interval.between(Interval.lerpFiniteBound(alpha.min, first, second), Interval.lerpFiniteBound(alpha.max, first, second))
}
if (first === second) {
return Interval.ofExact(first)
}
const newMin = Interval.lerpInfiniteBound(alpha.min, first, second)
const newMax = Interval.lerpInfiniteBound(alpha.max, first, second)
if (isNaN(newMin) || isNaN(newMax)) {
return Interval.NaI
}
return Interval.between(newMin, newMax)
}

private static lerpFiniteBound(alpha: number, first: number, second: number) {
return first + Interval.mulBound(alpha, second - first)
}

private static lerpInfiniteBound(alpha: number, first: number, second: number) {
const firstPart = Interval.mulBound(1 - alpha, first)
const secondPart = Interval.mulBound(alpha, second)
if (isFinite(firstPart) || isFinite(secondPart)) {
return firstPart + secondPart
} else if (alpha <= 0) {
return second > first ? -Infinity : Infinity
} else if (alpha >= 1) {
return second > first ? Infinity : -Infinity
} else {
return NaN
}
}

public static sign(input: Interval) {
if (input.isNaI()) {
return Interval.NaI
}
if (input.min === input.max) {
return Interval.ofExact(Math.sign(input.min))
} else if (!input.contains(0)) {
return Interval.ofExact(input.min > 0 ? 1 : -1)
} else if (input.min === 0) {
return Interval.of(0, 1)
} else if (input.max === 0) {
return Interval.of(-1, 0)
} else {
return Interval.of(-1, 1)
}
}

public contains(value: number) {
return this.min <= value && value <= this.max
}
Expand Down
24 changes: 17 additions & 7 deletions src/math/Util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,12 @@ export function lazyLerp3(a: number, b: number, c: number, d: () => number, e: (
}

export function clampedLerp(a: number, b: number, c: number): number {
if (c < 0) {
return a
} else if (c > 1) {
if (a < 0) {
return b
} else if (a > 1) {
return c
} else {
return lerp(c, a, b)
return lerp(a, b, c)
}
}

Expand All @@ -66,7 +66,7 @@ export function map(a: number, b: number, c: number, d: number, e: number) {
}

export function clampedMap(a: number, b: number, c: number, d: number, e: number) {
return clampedLerp(d, e, inverseLerp(a, b, c))
return clampedLerp(inverseLerp(a, b, c), d, e)
}

export function intFloor(a: number) {
Expand All @@ -77,6 +77,10 @@ export function longFloor(a: number) {
return clamp(Math.floor(a), MIN_LONG, MAX_LONG)
}

export function quantize(value: number, resolution: number) {
return intFloor(value / resolution) * resolution
}

export function binarySearch(n: number, n2: number, predicate: (value: number) => boolean) {
let n3 = n2 - n
while (n3 > 0) {
Expand All @@ -93,8 +97,8 @@ export function binarySearch(n: number, n2: number, predicate: (value: number) =
}

export function getSeed(x: number, y: number, z: number) {
let seed = BigInt(x * 3129871) ^ BigInt(z) * BigInt(116129781) ^ BigInt(y)
seed = seed * seed * BigInt(42317861) + seed * BigInt(11)
let seed = BigInt.asIntN(64, BigInt(x) * BigInt(3129871) ^ BigInt(z) * BigInt(116129781) ^ BigInt(y))
seed = BigInt.asIntN(64, BigInt.asIntN(64, seed * seed * BigInt(42317861)) + BigInt.asIntN(64, seed * BigInt(11)))
return seed >> BigInt(16)
}

Expand Down Expand Up @@ -141,3 +145,9 @@ export function shuffle(array: unknown[], random: Random) {
array[i - 1] = tmp
}
}

export function hashCode(s: string) {
for(var i = 0, h = 0; i < s.length; i++)
h = Math.imul(31, h) + s.charCodeAt(i) | 0
return h
}
97 changes: 44 additions & 53 deletions src/math/noise/BlendedNoise.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,20 @@
import { Interval } from '../Interval.js'
import type { Random } from '../random/index.js'
import { clampedLerp } from '../Util.js'
import type { ImprovedNoise } from './ImprovedNoise.js'
import { PerlinNoise } from './PerlinNoise.js'
import { clamp, floatLerp } from '../Util.js'
import type { NoiseLayer } from './NoiseStack.js'
import { NoiseStack } from './NoiseStack.js'
import { SmearedPerlinNoise } from './SmearedPerlinNoise.js'

export class BlendedNoise {
public readonly minLimitNoise: PerlinNoise
public readonly maxLimitNoise: PerlinNoise
public readonly mainNoise: PerlinNoise
private static readonly BASE_SCALE = 684.412
private static readonly LIMIT_FACTOR = 0.99998474
private static readonly MAIN_FACTOR = 12.75

public readonly minLimitNoise: NoiseStack
public readonly maxLimitNoise: NoiseStack
public readonly mainNoise: NoiseStack
private readonly xzMultiplier: number
private readonly yMultiplier: number
public readonly maxValue: number

constructor(
random: Random,
Expand All @@ -19,58 +24,44 @@ export class BlendedNoise {
public readonly yFactor: number,
public readonly smearScaleMultiplier: number
) {
this.minLimitNoise = new PerlinNoise(random, -15, [1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0], true)
this.maxLimitNoise = new PerlinNoise(random, -15, [1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0], true)
this.mainNoise = new PerlinNoise(random, -7, [1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0], true)
this.xzMultiplier = 684.412 * xzScale
this.yMultiplier = 684.412 * yScale
this.maxValue = this.minLimitNoise.edgeValue(this.yMultiplier + 2)
this.xzMultiplier = BlendedNoise.BASE_SCALE * xzScale
this.yMultiplier = BlendedNoise.BASE_SCALE * yScale
const limitSmearScaleY = this.yMultiplier * smearScaleMultiplier
const mainSmearScaleY = limitSmearScaleY / yFactor
this.minLimitNoise = BlendedNoise.createNoise(random, -15, limitSmearScaleY, BlendedNoise.LIMIT_FACTOR)
this.maxLimitNoise = BlendedNoise.createNoise(random, -15, limitSmearScaleY, BlendedNoise.LIMIT_FACTOR)
this.mainNoise = BlendedNoise.createNoise(random, -7, mainSmearScaleY, BlendedNoise.MAIN_FACTOR)
}

public sample(x: number, y: number, z: number) {
const scaledX = x * this.xzMultiplier
const scaledY = y * this.yMultiplier
const scaledZ = z * this.xzMultiplier

const factoredX = scaledX / this.xzFactor
const factoredY = scaledY / this.yFactor
const factoredZ = scaledZ / this.xzFactor

const smear = this.yMultiplier * this.smearScaleMultiplier
const factoredSmear = smear / this.yFactor

let noise: ImprovedNoise | undefined
let value = 0
private static createNoise(random: Random, firstOctave: number, smearScaleY: number, valueFactor: number) {
const octaves = -firstOctave + 1
let factor = 1
for (let i = 0; i < 8; i += 1) {
noise = this.mainNoise.getOctaveNoise(i)
if (noise) {
const xx = PerlinNoise.wrap(factoredX * factor)
const yy = PerlinNoise.wrap(factoredY * factor)
const zz = PerlinNoise.wrap(factoredZ * factor)
value += noise.sample(xx, yy, zz, factoredSmear * factor, factoredY * factor) / factor
}
valueFactor /= Math.pow(2, octaves) - 1
const layers: NoiseLayer[] = []
for (let i = octaves - 1; i >= 0; i -= 1) {
layers.push({ noise: new SmearedPerlinNoise(random, smearScaleY * factor), frequency: factor, amplitude: valueFactor })
factor /= 2
valueFactor *= 2
}
return new NoiseStack(layers)
}

value = (value / 10 + 1) / 2
factor = 1
let min = 0
let max = 0
for (let i = 0; i < 16; i += 1) {
const xx = PerlinNoise.wrap(scaledX * factor)
const yy = PerlinNoise.wrap(scaledY * factor)
const zz = PerlinNoise.wrap(scaledZ * factor)
const smearsmear = smear * factor
if (value < 1 && (noise = this.minLimitNoise.getOctaveNoise(i))) {
min += noise.sample(xx, yy, zz, smearsmear, scaledY * factor) / factor
}
if (value > 0 && (noise = this.maxLimitNoise.getOctaveNoise(i))) {
max += noise.sample(xx, yy, zz, smearsmear, scaledY * factor) / factor
}
factor /= 2
}
public range(): Interval {
return Interval.lerp(Interval.of(0, 1), this.minLimitNoise.range(), this.maxLimitNoise.range())
}

return clampedLerp(min / 512, max / 512, value) / 128
public sample(x: number, y: number, z: number) {
const xx = x * this.xzMultiplier
const yy = y * this.yMultiplier
const zz = z * this.xzMultiplier
let mainValue = this.mainNoise.get3D(xx / this.xzFactor, yy / this.yFactor, zz / this.xzFactor)
mainValue = clamp(mainValue + 0.5, 0, 1)
if (mainValue === 0) {
return this.minLimitNoise.get3D(xx, yy, zz)
} else if (mainValue === 1) {
return this.maxLimitNoise.get3D(xx, yy, zz)
} else {
return floatLerp(mainValue, this.minLimitNoise.get3D(xx, yy, zz), this.maxLimitNoise.get3D(xx, yy, zz))
}
}
}
Loading