Skip to content
Closed
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
106 changes: 104 additions & 2 deletions src/worldgen/DensityFunction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,10 +128,15 @@ export namespace DensityFunction {
case 'quarter_negative':
case 'squeeze':
return new Mapped(type, inputParser(root.argument))
case 'interval_select': return new IntervalSelect(
inputParser(root.input),
Json.readArray(root.thresholds, (e) => Json.readNumber(e) ?? 0) ?? [],
Json.readArray(root.functions, inputParser) ?? []
)
case 'add':
case 'mul':
case 'min':
case 'max': return new Ap2(
case 'max': return createAp2(
Json.readEnum(type, Ap2Type),
inputParser(root.argument1),
inputParser(root.argument2),
Expand Down Expand Up @@ -737,7 +742,7 @@ export namespace DensityFunction {
}
}
public mapAll(visitor: Visitor) {
return visitor.map(new Ap2(this.type, this.argument1.mapAll(visitor), this.argument2.mapAll(visitor)))
return visitor.map(createAp2(this.type, this.argument1.mapAll(visitor), this.argument2.mapAll(visitor)))
}
public minValue() {
return this.min ?? -Infinity
Expand Down Expand Up @@ -826,4 +831,101 @@ export namespace DensityFunction {
return Math.max(this.fromValue, this.toValue)
}
}

export type MulOrAddType = 'add' | 'mul'

export class MulOrAdd extends Transformer {
constructor(
public readonly type: MulOrAddType,
input: DensityFunction,
public readonly argument: number,
private readonly min?: number,
private readonly max?: number,
) {
super(input)
}
public transform(context: Context, density: number) {
switch (this.type) {
case 'add': return density + this.argument
case 'mul': return density * this.argument
}
}
public mapAll(visitor: Visitor) {
return visitor.map(new MulOrAdd(this.type, this.input.mapAll(visitor), this.argument))
}
public minValue() {
return this.min ?? -Infinity
}
public maxValue() {
return this.max ?? Infinity
}
public withMinMax() {
const min1 = this.input.minValue()
const max1 = this.input.maxValue()
const min2 = this.argument
const max2 = this.argument
let min, max
switch (this.type) {
case 'add':
min = min1 + min2
max = max1 + max2
break
case 'mul':
min = min1 > 0 && min2 > 0 ? (min1 * min2) || 0
: max1 < 0 && max2 < 0 ? (max1 * max2) || 0
: Math.min((min1 * max2) || 0, (min2 * max1) || 0)
max = min1 > 0 && min2 > 0 ? (max1 * max2) || 0
: max1 < 0 && max2 < 0 ? (min1 * min2) || 0
: Math.max((min1 * min2) || 0, (max1 * max2) || 0)
break
}
return new MulOrAdd(this.type, this.input, this.argument, min, max)
}
}

export function createAp2(type: typeof Ap2Type[number], argument1: DensityFunction, argument2: DensityFunction) {
if ((type === 'add' || type === 'mul') && argument1 instanceof Constant) {
return new MulOrAdd(type, argument2, argument1.minValue())
}
if ((type === 'add' || type === 'mul') && argument2 instanceof Constant) {
return new MulOrAdd(type, argument1, argument2.minValue())
}
return new Ap2(type, argument1, argument2)
}

export class IntervalSelect extends DensityFunction {
constructor(
public readonly input: DensityFunction,
public readonly thresholds: number[],
public readonly functions: DensityFunction[],
) {
super()
}
public compute(context: Context) {
const inputValue = this.input.compute(context)
for (let i = 0; i < this.thresholds.length; i++) {
if (inputValue < this.thresholds[i]) {
return this.functions[i].compute(context)
}
}
return this.functions[this.functions.length - 1].compute(context)
}
public mapAll(visitor: Visitor) {
return visitor.map(new IntervalSelect(this.input.mapAll(visitor), this.thresholds, this.functions.map(f => f.mapAll(visitor))))
}
public minValue() {
let min = Infinity
for (const f of this.functions) {
min = Math.min(min, f.minValue())
}
return min
}
public maxValue() {
let max = -Infinity
for (const f of this.functions) {
max = Math.max(max, f.maxValue())
}
return max
}
}
}
18 changes: 18 additions & 0 deletions test/worldgen/DensityFunction.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -193,4 +193,22 @@ describe('DensityFunction', () => {
const fn3 = wrap(new DF.FindTopSurface(new DF.YClampedGradient(128, -128, -1, 1), new DF.Constant(50), 128, 1))
expect(fn3.compute(ContextA)).toEqual(128)
})

it('Mul with 0 and Infinity', () => {
const fn = wrap(DF.createAp2('add', DF.createAp2('mul', DF.Constant.ZERO, new DF.Mapped('invert', DF.Constant.ZERO)), new DF.Constant(5)))
expect(fn.compute(ContextA)).toEqual(Number.NaN)
})

it('IntervalSelect', () => {
const fn = wrap(new DF.IntervalSelect(new DF.YClampedGradient(0, 10, 0, 10), [2, 5, 8], [
new DF.Constant(100),
new DF.Constant(200),
new DF.Constant(300),
new DF.Constant(400),
]))
expect(fn.compute(DF.context(0, 1, 0))).toEqual(100)
expect(fn.compute(DF.context(0, 3, 0))).toEqual(200)
expect(fn.compute(DF.context(0, 6, 0))).toEqual(300)
expect(fn.compute(DF.context(0, 9, 0))).toEqual(400)
})
})