UNPKG

@jake-niemiec/react-planner

Version:

react-planner is a React Component for plans design. Draw a 2D floorplan and navigate it in 3D mode.

305 lines (225 loc) 9.55 kB
"use strict"; import React from 'react'; import PropTypes from 'prop-types'; import ReactDOM from 'react-dom'; import * as Three from 'three'; import {parseData, updateScene} from './scene-creator'; import {disposeScene} from './three-memory-cleaner'; import OrbitControls from './libs/orbit-controls'; import diff from 'immutablediff'; import * as SharedStyle from '../../shared-style'; export default class Scene3DViewer extends React.Component { constructor(props) { super(props); this.lastMousePosition = {}; this.width = props.width; this.height = props.height; this.stopRendering = false; this.renderer = window.__threeRenderer || new Three.WebGLRenderer({preserveDrawingBuffer: true}); window.__threeRenderer = this.renderer; } componentDidMount() { let actions = { areaActions: this.context.areaActions, holesActions: this.context.holesActions, itemsActions: this.context.itemsActions, linesActions: this.context.linesActions, projectActions: this.context.projectActions }; let {state} = this.props; let data = state.scene; let canvasWrapper = ReactDOM.findDOMNode(this.refs.canvasWrapper); let scene3D = new Three.Scene(); //RENDERER this.renderer.setClearColor(new Three.Color(SharedStyle.COLORS.white)); this.renderer.setSize(this.width, this.height); // LOAD DATA let planData = parseData(data, actions, this.context.catalog); scene3D.add(planData.plan); scene3D.add(planData.grid); let aspectRatio = this.width / this.height; let camera = new Three.PerspectiveCamera(45, aspectRatio, 1, 300000); scene3D.add(camera); // Set position for the camera let cameraPositionX = -(planData.boundingBox.max.x - planData.boundingBox.min.x) / 2; let cameraPositionY = (planData.boundingBox.max.y - planData.boundingBox.min.y) / 2 * 10; let cameraPositionZ = (planData.boundingBox.max.z - planData.boundingBox.min.z) / 2; camera.position.set(cameraPositionX, cameraPositionY, cameraPositionZ); camera.up = new Three.Vector3(0, 1, 0); // HELPER AXIS // let axisHelper = new Three.AxisHelper(100); // scene3D.add(axisHelper); // LIGHT let light = new Three.AmbientLight(0xafafaf); // soft white light scene3D.add(light); // Add another light let spotLight1 = new Three.SpotLight(SharedStyle.COLORS.white, 0.30); spotLight1.position.set(cameraPositionX, cameraPositionY, cameraPositionZ); scene3D.add(spotLight1); // OBJECT PICKING let toIntersect = [planData.plan]; let mouse = new Three.Vector2(); let raycaster = new Three.Raycaster(); this.mouseDownEvent = (event) => { this.lastMousePosition.x = event.offsetX / this.width * 2 - 1; this.lastMousePosition.y = -event.offsetY / this.height * 2 + 1; }; this.mouseUpEvent = (event) => { event.preventDefault(); mouse.x = (event.offsetX / this.width) * 2 - 1; mouse.y = -(event.offsetY / this.height) * 2 + 1; if (Math.abs(mouse.x - this.lastMousePosition.x) <= 0.02 && Math.abs(mouse.y - this.lastMousePosition.y) <= 0.02) { raycaster.setFromCamera(mouse, camera); let intersects = raycaster.intersectObjects(toIntersect, true); if (intersects.length > 0 && !(isNaN(intersects[0].distance))) { intersects[0].object.interact && intersects[0].object.interact(); } else { this.context.projectActions.unselectAll(); } } }; this.renderer.domElement.addEventListener('mousedown', this.mouseDownEvent); this.renderer.domElement.addEventListener('mouseup', this.mouseUpEvent); this.renderer.domElement.style.display = 'block'; // add the output of the renderer to the html element canvasWrapper.appendChild(this.renderer.domElement); // create orbit controls let orbitController = new OrbitControls(camera, this.renderer.domElement); let spotLightTarget = new Three.Object3D(); spotLightTarget.position.set(orbitController.target.x, orbitController.target.y, orbitController.target.z); scene3D.add(spotLightTarget); spotLight1.target = spotLightTarget; /************************************/ /********* SCENE EXPORTER ***********/ /************************************/ let exportScene = () => { let convertToBufferGeometry = (geometry) => { console.log("geometry = ", geometry); let bufferGeometry = new Three.BufferGeometry().fromGeometry(geometry); return bufferGeometry; }; scene3D.remove(planData.grid); scene3D.traverse((child) => { console.log(child); if (child instanceof Three.Mesh && !(child.geometry instanceof Three.BufferGeometry)) child.geometry = convertToBufferGeometry(child.geometry); }); let output = scene3D.toJSON(); output = JSON.stringify(output, null, '\t'); output = output.replace(/[\n\t]+([\d\.e\-\[\]]+)/g, '$1'); let name = prompt('insert file name'); name = name.trim() || 'scene'; let blob = new Blob([output], {type: 'text/plain'}); let fileOutputLink = document.createElement('a'); let url = window.URL.createObjectURL(blob); fileOutputLink.setAttribute('download', name); fileOutputLink.href = url; document.body.appendChild(fileOutputLink); fileOutputLink.click(); document.body.removeChild(fileOutputLink); scene3D.add(planData.grid); }; // window.exportScene = exportScene; /************************************/ /************************************/ /********** PLAN EXPORTER ***********/ /************************************/ let exportPlan = () => { let convertToBufferGeometry = (geometry) => { console.log("geometry = ", geometry); return new Three.BufferGeometry().fromGeometry(geometry); }; planData.plan.traverse((child) => { console.log(child); if (child instanceof Three.Mesh && !(child.geometry instanceof Three.BufferGeometry)) child.geometry = convertToBufferGeometry(child.geometry); }); let output = planData.plan.toJSON(); output = JSON.stringify(output, null, '\t'); output = output.replace(/[\n\t]+([\d\.e\-\[\]]+)/g, '$1'); let name = prompt('insert file name'); name = name.trim() || 'plan'; let blob = new Blob([output], {type: 'text/plain'}); let fileOutputLink = document.createElement('a'); let url = window.URL.createObjectURL(blob); fileOutputLink.setAttribute('download', name); fileOutputLink.href = url; document.body.appendChild(fileOutputLink); fileOutputLink.click(); document.body.removeChild(fileOutputLink); scene3D.add(planData.grid); }; // window.exportPlan = exportPlan; /************************************/ let render = () => { if (!this.stopRendering) { orbitController.update(); spotLight1.position.set(camera.position.x, camera.position.y, camera.position.z); spotLightTarget.position.set(orbitController.target.x, orbitController.target.y, orbitController.target.z); camera.updateMatrix(); camera.updateMatrixWorld(); for (let elemID in planData.sceneGraph.LODs) { planData.sceneGraph.LODs[elemID].update(camera) } this.renderer.render(scene3D, camera); requestAnimationFrame(render); } }; render(); this.orbitControls = orbitController; this.camera = camera; this.scene3D = scene3D; this.planData = planData; } componentWillUnmount() { this.orbitControls.dispose(); this.stopRendering = true; this.renderer.domElement.removeEventListener('mousedown', this.mouseDownEvent); this.renderer.domElement.removeEventListener('mouseup', this.mouseUpEvent); disposeScene(this.scene3D); this.scene3D.remove(this.planData.plan); this.scene3D.remove(this.planData.grid); this.scene3D = null; // this.planData.sceneGraph = null; this.planData = null; } componentWillReceiveProps(nextProps) { let {width, height} = nextProps; let {camera, renderer, scene3D} = this; let actions = { areaActions: this.context.areaActions, holesActions: this.context.holesActions, itemsActions: this.context.itemsActions, linesActions: this.context.linesActions, projectActions: this.context.projectActions }; this.width = width; this.height = height; camera.aspect = width / height; camera.updateProjectionMatrix(); if (nextProps.state.scene !== this.props.state.scene) { let changedValues = diff(this.props.state.scene, nextProps.state.scene); updateScene(this.planData, nextProps.state.scene, this.props.state.scene, changedValues.toJS(), actions, this.context.catalog); } renderer.setSize(width, height); //renderer.render(scene3D, camera); } render() { return React.createElement("div", { ref: "canvasWrapper" }); } } Scene3DViewer.propTypes = { state: PropTypes.object.isRequired, width: PropTypes.number.isRequired, height: PropTypes.number.isRequired }; Scene3DViewer.contextTypes = { areaActions: PropTypes.object.isRequired, holesActions: PropTypes.object.isRequired, itemsActions: PropTypes.object.isRequired, linesActions: PropTypes.object.isRequired, projectActions: PropTypes.object.isRequired, catalog: PropTypes.object };