react-planner
Version:
react-planner is a React Component for plans design. Draw a 2D floorplan and navigate it in 3D mode.
372 lines (285 loc) • 13.1 kB
JavaScript
import * as Three from 'three';
import React from 'react';
import convert from 'convert-units';
export default {
name: "simple-stair",
prototype: "items",
info: {
title: "simple stair",
tag: ['building', 'stair'],
description: "Simple stair",
image: require('./simple-stair.png')
},
properties: {
width: {
label: "Width",
type: "length-measure",
defaultValue: {
length: 50,
unit: 'cm'
}
},
depth: {
label: "Depth",
type: "length-measure",
defaultValue: {
length: 300,
unit: 'cm'
}
},
height: {
label: "Height",
type: "length-measure",
defaultValue: {
length: 300,
unit: 'cm'
}
},
altitude: {
label: "Altitude",
type: "length-measure",
defaultValue: {
length: 0,
unit: 'cm'
}
}
},
render2D: function (element, layer, scene) {
let newWidth = convert(element.properties.get('width').get('length'))
.from(element.properties.get('width').get('unit'))
.to(scene.unit);
let newDepth = convert(element.properties.get('depth').get('length'))
.from(element.properties.get('depth').get('unit'))
.to(scene.unit);
let angle = element.rotation + 90;
let textRotation = 0;
if (Math.sin(angle * Math.PI / 180) < 0) {
textRotation = 180;
}
let style = {stroke: element.selected ? '#0096fd' : '#000', strokeWidth: "2px", fill: "#84e1ce"};
let arrow_style = {stroke: element.selected ? '#0096fd' : null, strokeWidth: "2px", fill: "#84e1ce"};
return (
<g transform={`translate(${-newWidth / 2},${-newDepth / 2})`}>
<rect key="1" x="0" y="0" width={newWidth} height={newDepth} style={style}/>
<line key="2" x1={newWidth / 2} x2={newWidth / 2} y1={newDepth} y2={newDepth + 30} style={arrow_style}/>
<line key="3" x1={.35 * newWidth} x2={newWidth / 2} y1={newDepth + 15} y2={newDepth + 30} style={arrow_style}/>
<line key="4" x1={newWidth / 2} x2={.65 * newWidth} y1={newDepth + 30} y2={newDepth + 15} style={arrow_style}/>
<text key="5" x="0" y="0"
transform={`translate(${newWidth / 2}, ${newDepth / 2}) scale(1,-1) rotate(${textRotation})`}
style={{textAnchor: "middle", fontSize: "11px"}}>
{element.type}
</text>
</g>
);
},
render3D: function (element, layer, scene) {
let loader = new Three.TextureLoader();
let whitePaintTextureRepeatFactor = 1 / 20; // In a 100x100 area i want to repeat this texture 5x5 times
let newWidth = convert(element.properties.get('width').get('length'))
.from(element.properties.get('width').get('unit'))
.to(scene.unit);
let newDepth = convert(element.properties.get('depth').get('length'))
.from(element.properties.get('depth').get('unit'))
.to(scene.unit);
let newHeight = convert(element.properties.get('height').get('length'))
.from(element.properties.get('height').get('unit'))
.to(scene.unit);
let newAltitude = convert(element.properties.get('altitude').get('length'))
.from(element.properties.get('altitude').get('unit'))
.to(scene.unit);
let stair = new Three.Object3D();
// compute step dimensions with Blondel formula
let a = (63 * newHeight) / (newDepth + 2 * newHeight);
let p = 63 - 2 * a;
let numberOfSteps = Math.round(newHeight / a);
let stepHeight = newHeight / numberOfSteps;
let stepDepth = newDepth / numberOfSteps;
let stepWidth = newWidth;
// Build planes for every step
let stepPlaneGeometry = new Three.PlaneGeometry(stepWidth, stepHeight);
assignUVs(stepPlaneGeometry);
let stepPlaneMaterial = new Three.MeshBasicMaterial({side: Three.FrontSide});
stepPlaneMaterial.map = loader.load(require('./textures/white-paint.jpg'));
stepPlaneMaterial.needsUpdate = true;
stepPlaneMaterial.map.wrapS = Three.RepeatWrapping;
stepPlaneMaterial.map.wrapT = Three.RepeatWrapping;
stepPlaneMaterial.map.repeat.set(stepWidth * whitePaintTextureRepeatFactor, stepHeight * whitePaintTextureRepeatFactor);
// Build stair profile shape
let starProfileShapePoints = [];
for (let i = 0; i < numberOfSteps; i++) {
starProfileShapePoints.push([(numberOfSteps - i) * stepDepth, i * stepHeight],
[(numberOfSteps - i) * stepDepth, (i + 1) * stepHeight]);
let stepPlane = new Three.Mesh(stepPlaneGeometry, stepPlaneMaterial);
stepPlane.position.x += stepWidth / 2;
stepPlane.position.z = (numberOfSteps - i) * stepDepth;
stepPlane.position.y = i * stepHeight + stepHeight / 2;
stair.add(stepPlane);
let stepCover = buildStepCover(stepWidth, stepHeight, stepDepth);
stepCover.position.y += stepHeight * i + stepHeight / 2;
stepCover.position.z += (numberOfSteps - i) * stepDepth;
stair.add(stepCover);
}
starProfileShapePoints.push([0, numberOfSteps * stepHeight],
[0, (numberOfSteps - 1) * stepHeight],
[(numberOfSteps - 1) * stepDepth, 0]);
let stairShapeProfile = new Three.Shape();
stairShapeProfile.moveTo(starProfileShapePoints[0][0], starProfileShapePoints[0][1]);
for (let i = 1; i < starProfileShapePoints.length; i++) {
stairShapeProfile.lineTo(starProfileShapePoints[i][0], starProfileShapePoints[i][1]);
}
let stairShapeProfileGeometry = new Three.ShapeGeometry(stairShapeProfile);
assignUVs(stairShapeProfileGeometry);
let stairProfileMaterial = new Three.MeshPhongMaterial({side: Three.FrontSide});
stairProfileMaterial.map = loader.load(require('./textures/white-paint.jpg'));
stairProfileMaterial.needsUpdate = true;
stairProfileMaterial.map.wrapS = Three.RepeatWrapping;
stairProfileMaterial.map.wrapT = Three.RepeatWrapping;
stairProfileMaterial.map.repeat.set(numberOfSteps * stepDepth * whitePaintTextureRepeatFactor,
numberOfSteps * stepHeight * whitePaintTextureRepeatFactor);
let stairProfile = new Three.Mesh(stairShapeProfileGeometry, stairProfileMaterial);
stairProfile.rotation.y = -Math.PI / 2;
stair.add(stairProfile);
let stairProfileMaterial2 = new Three.MeshPhongMaterial({side: Three.BackSide});
stairProfileMaterial2.map = loader.load(require('./textures/white-paint.jpg'));
stairProfileMaterial2.needsUpdate = true;
stairProfileMaterial2.map.wrapS = Three.RepeatWrapping;
stairProfileMaterial2.map.wrapT = Three.RepeatWrapping;
stairProfileMaterial2.map.repeat.set(numberOfSteps * stepDepth * whitePaintTextureRepeatFactor,
numberOfSteps * stepHeight * whitePaintTextureRepeatFactor);
let stairProfile2 = new Three.Mesh(stairShapeProfileGeometry, stairProfileMaterial2);
stairProfile2.rotation.y = -Math.PI / 2;
stairProfile2.position.x += newWidth;
stair.add(stairProfile2);
// Build closures for the stair
/*** CLOSURE 1 ***/
let closure1Slope = -Math.atan(stepDepth / stepHeight);
let stairClosure1Width = newWidth;
let stairClosure1Height = (numberOfSteps - 1) * stepHeight / Math.cos(closure1Slope);
let stairClosure1Geometry = new Three.PlaneGeometry(stairClosure1Width, stairClosure1Height);
let closure1Material = new Three.MeshPhongMaterial({side: Three.BackSide});
closure1Material.map = loader.load(require('./textures/white-paint.jpg'));
closure1Material.needsUpdate = true;
closure1Material.map.wrapS = Three.RepeatWrapping;
closure1Material.map.wrapT = Three.RepeatWrapping;
closure1Material.map.repeat.set(stairClosure1Width * whitePaintTextureRepeatFactor,
stairClosure1Height * whitePaintTextureRepeatFactor);
let stairClosure1 = new Three.Mesh(stairClosure1Geometry, closure1Material);
let pivotClosure1 = new Three.Object3D();
stairClosure1.position.y += stairClosure1Height / 2;
pivotClosure1.add(stairClosure1);
pivotClosure1.position.x = newWidth / 2;
pivotClosure1.position.z = (numberOfSteps - 1) * stepDepth;
pivotClosure1.rotation.x = closure1Slope;
stair.add(pivotClosure1);
/*** CLOSURE 2 ***/
let closure2 = new Three.Mesh(stepPlaneGeometry, stepPlaneMaterial);
closure2.rotation.y = Math.PI;
closure2.position.x = stepWidth / 2;
closure2.position.y = numberOfSteps * stepHeight - stepHeight / 2;
stair.add(closure2);
/*** CLOSURE 2 ***/
let closure3Width = 0;
let closure3Depth = 0;
let stairClosure3Geometry = new Three.PlaneGeometry(stepWidth, stepDepth);
let closure3 = new Three.Mesh(stairClosure3Geometry, stepPlaneMaterial);
closure3.rotation.x = Math.PI / 2;
closure3.position.x = stepWidth / 2;
closure3.position.z = (numberOfSteps - 1) * stepDepth + stepDepth / 2;
stair.add(closure3);
if (element.selected) {
let box = new Three.BoxHelper(stair, 0x99c3fb);
box.material.linewidth = 2;
box.material.depthTest = false;
box.renderOrder = 1000;
stair.add(box);
}
// Normalize the origin of the object
let boundingBox = new Three.Box3().setFromObject(stair);
let center = [
(boundingBox.max.x - boundingBox.min.x) / 2 + boundingBox.min.x,
(boundingBox.max.y - boundingBox.min.y) / 2 + boundingBox.min.y,
(boundingBox.max.z - boundingBox.min.z) / 2 + boundingBox.min.z];
stair.position.x -= center[0];
stair.position.y -= center[1] - (boundingBox.max.y - boundingBox.min.y) / 2;
stair.position.z -= center[2];
// I re-scale the stair following the initial attributes
stair.scale.set(newWidth / (boundingBox.max.x - boundingBox.min.x),
newHeight / (boundingBox.max.y - boundingBox.min.y),
newDepth / (boundingBox.max.z - boundingBox.min.z));
stair.position.y += newAltitude;
return Promise.resolve(stair);
}
};
function assignUVs(geometry) {
geometry.computeBoundingBox();
let max = geometry.boundingBox.max;
let min = geometry.boundingBox.min;
let offset = new Three.Vector2(0 - min.x, 0 - min.y);
let range = new Three.Vector2(max.x - min.x, max.y - min.y);
geometry.faceVertexUvs[0] = [];
let faces = geometry.faces;
for (let i = 0; i < geometry.faces.length; i++) {
let v1 = geometry.vertices[faces[i].a];
let v2 = geometry.vertices[faces[i].b];
let v3 = geometry.vertices[faces[i].c];
geometry.faceVertexUvs[0].push([
new Three.Vector2(( v1.x + offset.x ) / range.x, ( v1.y + offset.y ) / range.y),
new Three.Vector2(( v2.x + offset.x ) / range.x, ( v2.y + offset.y ) / range.y),
new Three.Vector2(( v3.x + offset.x ) / range.x, ( v3.y + offset.y ) / range.y)
]);
}
geometry.uvsNeedUpdate = true;
}
let buildStepCover = (width, height, depth) => {
let loader = new Three.TextureLoader();
let stepCoverHeight = 2;
let stepCoverLength = 2;
let planeGeometry = new Three.PlaneGeometry(width + stepCoverLength * 2, depth + stepCoverHeight);
let planeMaterial = new Three.MeshBasicMaterial({side: Three.FrontSide});
assignUVs(planeGeometry);
let planeGeometry2 = new Three.PlaneGeometry(depth + stepCoverLength, stepCoverHeight);
assignUVs(planeGeometry2);
let planeGeometry3 = new Three.PlaneGeometry(width + stepCoverLength * 2, stepCoverHeight);
assignUVs(planeGeometry3);
planeMaterial.map = loader.load(require('./textures/marble.jpg'));
planeMaterial.needsUpdate = true;
planeMaterial.map.wrapS = Three.RepeatWrapping;
planeMaterial.map.wrapT = Three.RepeatWrapping;
let plane = new Three.Mesh(planeGeometry, planeMaterial);
plane.rotation.x = -Math.PI / 2;
plane.position.x = width / 2;
plane.position.z = -depth / 2;
plane.position.y += height / 2 + stepCoverHeight;
let plane2 = new Three.Mesh(planeGeometry2, planeMaterial);
plane2.rotation.y = -Math.PI / 2;
plane2.position.x -= stepCoverLength;
plane2.position.y += height / 2 + stepCoverHeight / 2;
plane2.position.z -= depth / 2;
let plane3 = new Three.Mesh(planeGeometry, planeMaterial);
plane3.rotation.x = Math.PI / 2;
plane3.position.x = width / 2;
plane3.position.z = -depth / 2;
plane3.position.y += height / 2;
let plane4 = new Three.Mesh(planeGeometry2, planeMaterial);
plane4.rotation.y = Math.PI / 2;
plane4.position.x += width + stepCoverLength;
plane4.position.y += height / 2 + stepCoverHeight / 2;
plane4.position.z -= depth / 2;
let plane5 = new Three.Mesh(planeGeometry3, planeMaterial);
plane5.position.x += width / 2;
plane5.position.y += height / 2 + stepCoverHeight / 2;
plane5.position.z += stepCoverLength / 2;
let plane6 = new Three.Mesh(planeGeometry3, planeMaterial);
plane6.rotation.y = Math.PI;
plane6.position.x += width / 2;
plane6.position.y += height / 2 + stepCoverHeight / 2;
plane6.position.z -= depth + stepCoverLength / 2;
let stepCover = new Three.Object3D();
stepCover.add(plane);
stepCover.add(plane2);
stepCover.add(plane3);
stepCover.add(plane4);
stepCover.add(plane5);
stepCover.add(plane6);
return stepCover;
};