forum-logic/src/classes/display/pairwise-forces.js

66 lines
2.2 KiB
JavaScript

import {
DEFAULT_OVERLAP_FORCE,
DEFAULT_TARGET_RADIUS,
DEFAULT_DISTANCE_FACTOR,
DEFAULT_OVERLAP_BUFFER,
OVERLAP_THRESHOLD_RANDOMIZE,
} from '../../util/constants.js';
import { Rectangle, Vector } from '../supporting/geometry/index.js';
const getRectangles = (boxes) => boxes.map((box) => (box instanceof Rectangle ? box : box.rect));
const getCenters = (boxes) => getRectangles(boxes).map((rect) => rect.center);
export const overlapRepulsionForce = (
boxA,
boxB,
force = DEFAULT_OVERLAP_FORCE,
margin = DEFAULT_OVERLAP_BUFFER,
) => {
const [rectA, rectB] = getRectangles([boxA, boxB]);
const [centerA, centerB] = getCenters([rectA, rectB]);
const r = centerB.subtract(centerA);
// Apply a stronger force when overlap occurs
const overlap = rectA.doesOverlap(rectB);
if (overlap) {
// If there is sufficient overlap, randomize the direction of force.
// Note that we don't want to keep randomizing it once we've picked a direction
if (overlap <= OVERLAP_THRESHOLD_RANDOMIZE) {
if (!boxB.overlapForceDirection) {
boxB.overlapForceDirection = Vector.randomUnitVector(rectB.dim);
}
return boxB.overlapForceDirection.scale(force);
}
return r.normalize().scale(force);
}
boxB.overlapForceDirection = null;
// Apply a weaker force until distance > margin
const separation = rectA.separationFromRect(rectB);
if (separation < margin) {
return r.normalize().scale(force * ((margin - separation) / margin));
}
// Otherwise, zero force
return Vector.zeros(rectA.dim);
};
export const targetRadiusForce = (
boxA,
boxB,
targetRadius = DEFAULT_TARGET_RADIUS,
distanceFactor = DEFAULT_DISTANCE_FACTOR,
) => {
const [rectA, rectB] = getRectangles([boxA, boxB]);
const [centerA, centerB] = getCenters([rectA, rectB]);
const r = centerB.subtract(centerA);
// Use the distance between the outer edges of the boxes.
const outerA = rectA.lineIntersect(centerB, r.scale(-1));
const outerB = rectB.lineIntersect(centerA, r);
const distance = outerB.subtract(outerA).magnitude;
// Repel if closer than targetRadius
// Attract if farther than targetRadius
const force = -distanceFactor * (distance - targetRadius);
return r.normalize().scale(force);
};