arx-level-generator
Version:
A tool for creating Arx Fatalis maps
92 lines • 3.9 kB
JavaScript
import { MathUtils } from 'three';
import { sum } from '../../faux-ramda.js';
import { randomBetween, randomIntBetween } from '../../random.js';
import { getVertices } from '../mesh/getVertices.js';
/**
* @param magnitude - the maximum amount in both directions which the peeks will reach, or if an array with 2
* numbers specified then that will be used as minimum maximum range
* @param percentage - for every vertex there's a certain percentage chance that it will be a peek. control the
* percentage with this parameter
* @param smoothenPeeks - whether to apply smoothing around the peeks
* @param geometry - any threejs geometry
* @returns the vectors of the peeks
*/
export const makeBumpy = (magnitude, percentage, smoothenPeeks, geometry) => {
const vertices = getVertices(geometry);
const coords = geometry.getAttribute('position');
const peeks = [];
const notPeeks = [];
// split vertices into peeks and not-peeks while also calculating the peek heights.
// this does not yet apply the height change for the peeks
vertices.forEach((v) => {
const isToBePeeked = randomIntBetween(0, 100) < percentage;
let newY = v.vector.y;
if (isToBePeeked) {
if (typeof magnitude === 'number') {
newY += randomBetween(-magnitude, magnitude);
}
else {
newY += randomBetween(magnitude[0], magnitude[1]);
}
}
const data = {
y: newY,
position: v.vector,
idx: v.idx,
affectedVertices: [],
};
if (isToBePeeked) {
peeks.push(data);
}
else {
notPeeks.push(data);
}
});
if (smoothenPeeks) {
// smooth peeks by raising not-peeks around them - the dropoff angle is 45 degrees.
// the implementation does this by measuring the height of all near peeks and sums the distance based elevations.
// it also saves which peeks affect which not-peeks
notPeeks.forEach(({ y, position, idx }) => {
const diffs = [];
peeks.forEach((p) => {
const distance = p.position.distanceTo(position);
const elevation = p.y - y;
const volume = MathUtils.clamp(1 - distance / Math.abs(elevation), 0, 1);
if (volume > 0) {
p.affectedVertices.push({ elevation, distance });
diffs.push(elevation * volume);
}
});
let addition;
if (typeof magnitude === 'number') {
addition = MathUtils.clamp(sum(diffs), -magnitude, magnitude);
}
else {
addition = MathUtils.clamp(sum(diffs), magnitude[0], magnitude[1]);
}
coords.setY(idx, y + addition);
});
}
// applies peek height.
// if the peek affected any near vertices' height, then lower it's own height
// to match the height of the highest affected not-peek
peeks.forEach(({ y, affectedVertices, idx }, i) => {
let peekY = y;
if (affectedVertices.length > 0) {
const closestVertexY = affectedVertices.sort((a, b) => {
if (a.distance !== b.distance) {
// sort by distance, ascending order
return a.distance - b.distance;
}
// distance is the same, sort by elevation, descending magnitude
return Math.abs(b.elevation) - Math.abs(a.elevation);
});
// TODO: this doesn't seem to work as intended, the terrain is still spikey
peekY = closestVertexY[0].elevation;
peeks[i].y = peekY;
}
coords.setY(idx, peekY);
});
return peeks.map(({ position }) => position);
};
//# sourceMappingURL=makeBumpy.js.map