react-floorplanner
Version:
react-floorplanner is a React Component for plans design. Draw a 2D floorplan and navigate it in 3D mode.
137 lines (119 loc) • 3.94 kB
JavaScript
import {Map, List, Record} from 'immutable';
import * as Geometry from './geometry';
export const SNAP_POINT = 'SNAP_POINT';
export const SNAP_LINE = 'SNAP_LINE';
export const SNAP_SEGMENT = 'SNAP_SEGMENT';
export const SNAP_GRID = 'SNAP_GRID';
export const SNAP_MASK = new Map({
SNAP_POINT : true,
SNAP_LINE : true,
SNAP_SEGMENT : true,
SNAP_GRID : false
});
class PointSnap extends Record({
type: 'point',
x: -1, y: -1,
radius: 1, priority: 1,
related: new List()
}) {
nearestPoint(x, y) {
return {
x: this.x,
y: this.y,
distance: Geometry.pointsDistance(this.x, this.y, x, y)
};
}
isNear(x,y,distance){ return ~(this.x - x) + 1 < distance && ~(this.y - y) + 1 < distance; }
}
class LineSnap extends Record({
type: 'line',
a: -1, b: -1, c: -1,
radius: 1, priority: 1,
related: new List()
}) {
nearestPoint(x, y) {
return {
...Geometry.closestPointFromLine(this.a, this.b, this.c, x, y),
distance: Geometry.distancePointFromLine(this.a, this.b, this.c, x, y)
};
}
isNear(x,y,distance){ return true; }
}
class LineSegmentSnap extends Record({
type: 'line-segment',
x1: -1, y1: -1, x2: -1, y2: -1,
radius: 1, priority: 1,
related: new List()
}) {
nearestPoint(x, y) {
return {
...Geometry.closestPointFromLineSegment(this.x1, this.y1, this.x2, this.y2, x, y),
distance: Geometry.distancePointFromLineSegment(this.x1, this.y1, this.x2, this.y2, x, y)
};
}
isNear(x,y,distance){ return true; }
}
class GridSnap extends Record({
type: 'grid',
x: -1, y: -1,
radius: 1, priority: 1,
related: new List()
}) {
nearestPoint(x, y) {
return {
x: this.x,
y: this.y,
distance: Geometry.pointsDistance(this.x, this.y, x, y)
};
}
isNear(x,y,distance){ return ~(this.x - x) + 1 < distance && ~(this.y - y) + 1 < distance; }
}
export function nearestSnap(snapElements, x, y, snapMask) {
let filter = {
'point': snapMask.get(SNAP_POINT),
'line': snapMask.get(SNAP_LINE),
'line-segment': snapMask.get(SNAP_SEGMENT),
'grid': snapMask.get(SNAP_GRID)
};
return snapElements
.valueSeq()
.filter( ( el ) => filter[el.type] && el.isNear(x,y, el.radius) )
.map(snap => { return {snap, point: snap.nearestPoint(x, y)} })
.filter(({snap: {radius}, point: {distance}}) => distance < radius)
.min(
(
{snap: { priority : p1 }, point: { distance : d1 }},
{snap: { priority : p2 }, point: { distance : d2 }}
) => p1 === p2 ? ( d1 < d2 ? -1 : 1 ) : ( p1 > p2 ? -1 : 1 )
);
}
export function addPointSnap(snapElements, x, y, radius, priority, related) {
related = new List([related]);
return snapElements.push(new PointSnap({x, y, radius, priority, related}));
}
export function addLineSnap(snapElements, a, b, c, radius, priority, related) {
related = new List([related]);
return snapElements.withMutations(snapElements => {
let alreadyPresent = snapElements.some(lineSnap =>
lineSnap.type === 'line' &&
a === lineSnap.a &&
b === lineSnap.b &&
c === lineSnap.c);
if (alreadyPresent) return snapElements;
let intersections = snapElements
.valueSeq()
.filter(snap => snap.type === 'line')
.map(snap => Geometry.intersectionFromTwoLines(snap.a, snap.b, snap.c, a, b, c))
.filter(intersection => intersection !== undefined)
.forEach(({x, y}) => addPointSnap(snapElements, x, y, 20, 40));
snapElements.push(new LineSnap({a, b, c, radius, priority, related}));
})
}
export function addLineSegmentSnap(snapElements, x1, y1, x2, y2, radius, priority, related) {
related = new List([related]);
return snapElements.push(new LineSegmentSnap({x1, y1, x2, y2, radius, priority, related}));
}
export function addGridSnap(snapElements, x, y, radius, priority, related) {
related = new List([related]);
return snapElements.push(new GridSnap({x, y, radius, priority, related}));
}