react-floorplanner
Version:
react-floorplanner is a React Component for plans design. Draw a 2D floorplan and navigate it in 3D mode.
347 lines (277 loc) • 10.9 kB
JavaScript
import {List, Map} from 'immutable';
import {
SELECT_TOOL_DRAWING_HOLE,
UPDATE_DRAWING_HOLE,
END_DRAWING_HOLE,
BEGIN_DRAGGING_HOLE,
UPDATE_DRAGGING_HOLE,
END_DRAGGING_HOLE,
SELECT_HOLE,
MODE_IDLE,
MODE_DRAWING_HOLE,
MODE_DRAGGING_HOLE,
} from '../constants';
import * as Geometry from '../utils/geometry';
import {
select,
unselect,
unselectAll,
addHole,
removeHole,
} from '../utils/layer-operations';
import {
nearestSnap,
addPointSnap,
addLineSnap,
addLineSegmentSnap,
SNAP_POINT,
SNAP_LINE,
SNAP_SEGMENT
} from '../utils/snap';
export default function (state, action) {
switch (action.type) {
case SELECT_TOOL_DRAWING_HOLE:
return selectToolDrawingHole(state, action.sceneComponentType);
case UPDATE_DRAWING_HOLE:
return updateDrawingHole(state, action.layerID, action.x, action.y);
case END_DRAWING_HOLE:
return endDrawingHole(state, action.layerID, action.x, action.y);
case BEGIN_DRAGGING_HOLE:
return beginDraggingHole(state, action.layerID, action.holeID, action.x, action.y);
case UPDATE_DRAGGING_HOLE:
return updateDraggingHole(state, action.x, action.y);
case END_DRAGGING_HOLE:
return endDraggingHole(state, action.x, action.y);
case SELECT_HOLE:
return selectHole(state, action.layerID, action.holeID);
default:
return state;
}
}
function selectToolDrawingHole(state, sceneComponentType) {
let snapElements = (new List()).withMutations(snapElements => {
let {lines, vertices} = state.getIn(['scene', 'layers', state.scene.selectedLayer]);
lines.forEach(line => {
let {x: x1, y: y1} = vertices.get(line.vertices.get(0));
let {x: x2, y: y2} = vertices.get(line.vertices.get(1));
addLineSegmentSnap(snapElements, x1, y1, x2, y2, 20, 1, line.id);
})
});
return state.merge({
mode: MODE_DRAWING_HOLE,
snapElements,
drawingSupport: Map({
type: sceneComponentType
})
});
}
/** holes operations **/
function updateDrawingHole(state, layerID, x, y) {
let catalog = state.catalog;
//calculate snap and overwrite coords if needed
//force snap to segment
let snap = nearestSnap(state.snapElements, x, y, state.snapMask.merge({SNAP_SEGMENT: true}));
if (snap) ({x, y} = snap.point);
let scene = state.scene.updateIn(['layers', layerID], layer => layer.withMutations(layer => {
let selectedHole = layer.getIn(['selected', 'holes']).first();
if (selectedHole) {
unselect(layer, 'holes', selectedHole);
removeHole(layer, selectedHole);
}
if (snap) {
let lineID = snap.snap.related.get(0);
let line = layer.getIn(['lines', lineID]);
let {x: x1, y: y1} = layer.vertices.get(line.vertices.get(0));
let {x: x2, y: y2} = layer.vertices.get(line.vertices.get(1));
// I need min and max vertices on this line segment
let minVertex = Geometry.minVertex({x: x1, y: y1}, {x: x2, y: y2});
let maxVertex = Geometry.maxVertex({x: x1, y: y1}, {x: x2, y: y2});
let width = catalog.factoryElement(state.drawingSupport.get('type'), {}, {}).properties.get('width').get('length');
// Now I need min and max possible coordinates for the hole on the line. They depend on the width of the hole
// let width = hole.properties.get('width').get('length');
let lineLength = Geometry.pointsDistance(x1, y1, x2, y2);
let alpha = Math.atan2(Math.abs(y2 - y1), Math.abs(x2 - x1));
let cosWithThreshold = (alpha) => {
let cos = Math.cos(alpha);
return cos < 0.0000001 ? 0 : cos;
};
let sinWithThreshold = (alpha) => {
let sin = Math.sin(alpha);
return sin < 0.0000001 ? 0 : sin;
};
let cosAlpha = cosWithThreshold(alpha);
let sinAlpha = sinWithThreshold(alpha);
let minLeftVertexHole = {
x: minVertex.x + width / 2 * cosAlpha,
y: minVertex.y + width / 2 * sinAlpha
};
let maxRightVertexHole = {
x: minVertex.x + lineLength * cosAlpha - width / 2 * cosAlpha,
y: minVertex.y + lineLength * sinAlpha - width / 2 * sinAlpha
};
let offset;
if (x < minLeftVertexHole.x) {
offset = Geometry.pointPositionOnLineSegment(minVertex.x, minVertex.y,
maxVertex.x, maxVertex.y,
minLeftVertexHole.x, minLeftVertexHole.y);
} else if (x > maxRightVertexHole.x) {
offset = Geometry.pointPositionOnLineSegment(minVertex.x, minVertex.y,
maxVertex.x, maxVertex.y,
maxRightVertexHole.x, maxRightVertexHole.y);
} else {
if (x === minLeftVertexHole.x && x === maxRightVertexHole.x) {
if (y < minLeftVertexHole.y) {
offset = Geometry.pointPositionOnLineSegment(minVertex.x, minVertex.y,
maxVertex.x, maxVertex.y,
minLeftVertexHole.x, minLeftVertexHole.y);
offset = minVertex.x === x1 && minVertex.y === y1 ? offset : 1 - offset;
} else if (y > maxRightVertexHole.y) {
offset = Geometry.pointPositionOnLineSegment(minVertex.x, minVertex.y,
maxVertex.x, maxVertex.y,
maxRightVertexHole.x, maxRightVertexHole.y);
offset = minVertex.x === x1 && minVertex.y === y1 ? offset : 1 - offset;
} else {
offset = Geometry.pointPositionOnLineSegment(x1, y1, x2, y2, x, y);
}
} else {
offset = Geometry.pointPositionOnLineSegment(x1, y1, x2, y2, x, y);
}
}
// let offset = Geometry.pointPositionOnLineSegment(x1, y1, x2, y2, x, y);
let {hole} = addHole(layer, state.drawingSupport.get('type'), lineID, offset, catalog);
select(layer, 'holes', hole.id);
}
}));
return state.set('scene', scene);
}
function endDrawingHole(state, layerID, x, y) {
let catalog = state.catalog;
state = updateDrawingHole(state, layerID, x, y, catalog);
let scene = state.scene.updateIn(['layers', layerID], layer => unselectAll(layer));
return state.merge({
scene,
sceneHistory: state.sceneHistory.push(scene)
});
}
function beginDraggingHole(state, layerID, holeID, x, y) {
let layer = state.getIn(['scene', 'layers', layerID]);
let hole = layer.getIn(['holes', holeID]);
let line = layer.getIn(['lines', hole.line]);
let v0 = layer.getIn(['vertices', line.vertices.get(0)]);
let v1 = layer.getIn(['vertices', line.vertices.get(1)]);
let snapElements = addLineSegmentSnap(List(), v0.x, v0.y, v1.x, v1.y, 9999999, 1, null);
return state.merge({
mode: MODE_DRAGGING_HOLE,
snapElements,
draggingSupport: Map({
layerID,
holeID,
startPointX: x,
startPointY: y,
})
});
}
function updateDraggingHole(state, x, y) {
//calculate snap and overwrite coords if needed
//force snap to segment
let snap = nearestSnap(state.snapElements, x, y, state.snapMask.merge({SNAP_SEGMENT: true}));
if (!snap) return state;
let {draggingSupport, scene} = state;
let layerID = draggingSupport.get('layerID');
let holeID = draggingSupport.get('holeID');
let startPointX = draggingSupport.get('startPointX');
let startPointY = draggingSupport.get('startPointY');
let layer = state.getIn(['scene', 'layers', layerID]);
let hole = layer.getIn(['holes', holeID]);
let line = layer.getIn(['lines', hole.line]);
let v0 = layer.getIn(['vertices', line.vertices.get(0)]);
let v1 = layer.getIn(['vertices', line.vertices.get(1)]);
({x, y} = snap.point);
// I need min and max vertices on this line segment
let minVertex = Geometry.minVertex(v0, v1);
let maxVertex = Geometry.maxVertex(v0, v1);
// Now I need min and max possible coordinates for the hole on the line. They depend on the width of the hole
let width = hole.properties.get('width').get('length');
let lineLength = Geometry.pointsDistance(v0.x, v0.y, v1.x, v1.y);
let alpha = Math.atan2(Math.abs(v1.y - v0.y), Math.abs(v1.x - v0.x));
let cosWithThreshold = (alpha) => {
let cos = Math.cos(alpha);
return cos < 0.0000001 ? 0 : cos;
};
let sinWithThreshold = (alpha) => {
let sin = Math.sin(alpha);
return sin < 0.0000001 ? 0 : sin;
};
let cosAlpha = cosWithThreshold(alpha);
let sinAlpha = sinWithThreshold(alpha);
let minLeftVertexHole = {
x: minVertex.x + width / 2 * cosAlpha,
y: minVertex.y + width / 2 * sinAlpha
};
let maxRightVertexHole = {
x: minVertex.x + lineLength * cosAlpha - width / 2 * cosAlpha,
y: minVertex.y + lineLength * sinAlpha - width / 2 * sinAlpha
};
// Now I need to verify if the snap vertex (with coordinates x and y) is on the line segment
let offset;
if (x < minLeftVertexHole.x) {
// Snap point is previous the the line
offset = Geometry.pointPositionOnLineSegment(minVertex.x, minVertex.y,
maxVertex.x, maxVertex.y,
minLeftVertexHole.x, minLeftVertexHole.y);
} else {
// Snap point is after the line or on the line
if (x > maxRightVertexHole.x) {
offset = Geometry.pointPositionOnLineSegment(minVertex.x, minVertex.y,
maxVertex.x, maxVertex.y,
maxRightVertexHole.x, maxRightVertexHole.y);
} else if (x === minLeftVertexHole.x && x === maxRightVertexHole.x) {
// I am on a vertical line, I need to check y coordinates
if (y < minLeftVertexHole.y) {
offset = Geometry.pointPositionOnLineSegment(minVertex.x, minVertex.y,
maxVertex.x, maxVertex.y,
minLeftVertexHole.x, minLeftVertexHole.y);
offset = minVertex === v0 ? offset : 1 - offset;
} else if (y > maxRightVertexHole.y) {
offset = Geometry.pointPositionOnLineSegment(minVertex.x, minVertex.y,
maxVertex.x, maxVertex.y,
maxRightVertexHole.x, maxRightVertexHole.y);
offset = minVertex === v0 ? offset : 1 - offset;
} else {
offset = Geometry.pointPositionOnLineSegment(minVertex.x, minVertex.y,
maxVertex.x, maxVertex.y,
x, y);
offset = minVertex === v0 ? offset : 1 - offset;
}
} else {
offset = Geometry.pointPositionOnLineSegment(minVertex.x, minVertex.y,
maxVertex.x, maxVertex.y,
x, y);
}
}
hole = hole.set('offset', offset);
return state.merge({
scene: scene.mergeIn(['layers', layerID, 'holes', holeID], hole)
});
}
function endDraggingHole(state, x, y) {
state = updateDraggingHole(state, x, y);
return state.merge({
mode: MODE_IDLE,
sceneHistory: state.sceneHistory.push(state.scene)
});
}
function selectHole(state, layerID, holeID) {
let scene = state.scene;
scene = scene.merge({
layers: scene.layers.map(unselectAll),
selectedLayer: layerID
});
scene = scene.updateIn(['layers', layerID], layer => layer.withMutations(layer => {
select(layer, 'holes', holeID);
}));
return state.merge({
scene,
sceneHistory: state.sceneHistory.push(scene)
})
}