UNPKG

@woosh/meep-engine

Version:

Pure JavaScript game engine. Fully featured and production ready.

230 lines (190 loc) 5.09 kB
import { assert } from "../../assert.js"; import Signal from "../../events/signal/Signal.js"; import { computeHashFloat } from "../../primitives/numbers/computeHashFloat.js"; import { inverseLerp } from "../inverseLerp.js"; export class NumericInterval { /** * * @param {number} [min=-Infinity] * @param {number} [max=Infinity] * @constructor */ constructor( min = Number.NEGATIVE_INFINITY, max = Number.POSITIVE_INFINITY ) { assert.isNumber(min, 'min'); assert.isNumber(max, 'max'); assert.greaterThanOrEqual(max, min, `max [${max}] must be >= than min[${min}]`); /** * * @type {number} */ this.min = min; /** * * @type {number} */ this.max = max; /** * @readonly * @type {Signal<number, number, number, number>} */ this.onChanged = new Signal(); } /** * * @param {number} min * @param {number} max */ set(min, max) { assert.isNumber(min, 'min'); assert.isNumber(max, 'max'); assert.notNaN(min, 'min'); assert.notNaN(max, 'max'); assert.greaterThanOrEqual(max, min, `max [${max}] must be >= than min[${min}]`); const oldMin = this.min; const oldMax = this.max; if (min === oldMin && max === oldMax) { return; } this.min = min; this.max = max; if (!this.onChanged.hasHandlers()) { return; } this.onChanged.send4(min, max, oldMin, oldMax); } /** * * @param {number} value */ multiplyScalar(value) { const v0 = this.min * value; const v1 = this.max * value; if (v0 > v1) { //probably negative scale this.set(v1, v0); } else { this.set(v0, v1); } } /** * Compute normalized position of input within this interval, where result is 0 if input is equal to `min`, and 1 when input is equal to `max` * @param {number} v value to be normalized * @returns {number} value between 0..1 if input is within [min,max] range, otherwise result will be extrapolated proportionately outside the 0,1 region */ normalizeValue(v) { return inverseLerp(this.min, this.max, v); } /** * Both min and max are exactly 0 * @returns {boolean} */ isZero() { return this.min === 0 && this.max === 0; } /** * Whether min and max are the same * In other words if span is 0 * @returns {boolean} */ isExact() { return this.min === this.max; } /** * * @returns {number} */ computeAverage() { return (this.min + this.max) * 0.5; } /** * * @param {function} random Random number generator function, must return values between 0 and 1 * @returns {number} */ sampleRandom(random) { assert.isFunction(random, "random"); return this.min + random() * (this.max - this.min); } fromJSON(json) { this.set(json.min, json.max); } toJSON() { return { min: this.min, max: this.max }; } toString() { return `NumericInterval{ min=${this.min}, max=${this.max} }`; } /** * * @param {BinaryBuffer} buffer */ toBinaryBuffer(buffer) { buffer.writeFloat64(this.min); buffer.writeFloat64(this.max); } /** * * @param {BinaryBuffer} buffer */ fromBinaryBuffer(buffer) { this.min = buffer.readFloat64(); this.max = buffer.readFloat64(); } /** * * @param {NumericInterval} other */ copy(other) { this.set(other.min, other.max); } /** * * @param {NumericInterval} other * @returns {boolean} */ equals(other) { return this.min === other.min && this.max === other.max; } /** * * @returns {number} */ hash() { let hash = computeHashFloat(this.min); hash = ((hash << 5) - hash) + computeHashFloat(this.max); return hash; } /** * Distance between min and max (= max - min) * @returns {number} */ get span() { return this.max - this.min; } } /** * @readonly * @type {boolean} */ NumericInterval.prototype.isNumericInterval = true; /** * @readonly * @type {NumericInterval} */ NumericInterval.zero_zero = Object.freeze(new NumericInterval(0, 0)); /** * @readonly * @type {NumericInterval} */ NumericInterval.zero_one = Object.freeze(new NumericInterval(0, 1)); /** * @readonly * @type {NumericInterval} */ NumericInterval.one_one = Object.freeze(new NumericInterval(1, 1));