/*global Module*/ "use strict"; import * as THREE from 'three'; import TrackballControls from 'three-trackballcontrols'; import PartMeshFactory from './partmeshfactory'; import JointMeshFactory from './jointmeshfactory'; import Scene from './scene'; import Camera from './camera'; import { geometry } from './transformations'; /** Camera class configuration for Viewer */ export const cameraconfig = { fieldOfView: 75, near: 0.1, far: 1000, autoRotateSpeed: 2, defaultSettings: { target: { x: 0, y: 0, z: 0 }, position: { x: 0.56, y: 0.56, z: 0.56 }, } }; /** Light configuration for Viewer */ export const lightconfig = { ambient: { color: 0x303030 }, directional: { top: { color: 0x808080, intensity: 1.0, positionZ: 1000 }, bottom: { color: 0x808080, intensity: 1.0, positionZ: -1000 } } }; /** * Class for visualizing static Framsticks creatures. Connects all visualization * classes and render scene in given HTML canvas. For resizing it is recommended * to put canvas in resizable div and then compute new widths and heights from * this parent. */ class Viewer { /** * Default constructor for Viewer. It initializes all 3D visualization objects * in Scene without creature Meshes. It needs canvas to be created in * HTML - it does not create canvas on its own, like default WebGLRenderer. * Width and height should be taken from canvas parent * @param {number} width width of canvas * @param {number} height height of canvas * @param {Element} canvasdom DOM reference to canvas. */ constructor(width, height, canvasdom) { this.parts = []; this.joints = []; this.meshes = []; this.parts2 = []; this.joints2 = []; this.meshes2 = []; this.creature1 = new THREE.Group(); this.creature2 = new THREE.Group(); this.partfactory = new PartMeshFactory(geometry.part); this.jointfactory = new JointMeshFactory(geometry.joint); this.renderer = new THREE.WebGLRenderer({ antialias: true, devicePixelRatio: window.devicePixelRatio || 1, canvas: canvasdom }); this.renderer.setSize(width, height); this.raycaster = new THREE.Raycaster(); this.scene = new Scene(); this.camera = new Camera(cameraconfig, this.renderer.domElement, false); //console.log(this.camera); let ambientLight = new THREE.AmbientLight(lightconfig.ambient.color); this.scene.add(ambientLight); let topDirectionalLight = new THREE.DirectionalLight( lightconfig.directional.top.color, lightconfig.directional.top.intensity); topDirectionalLight.position.set(0, 0, lightconfig.directional.top.positionZ); this.scene.add(topDirectionalLight); let bottomDirectionalLight = new THREE.DirectionalLight( lightconfig.directional.bottom.color, lightconfig.directional.bottom.intensity); bottomDirectionalLight.position.set(0, 0, lightconfig.directional.bottom.positionZ); this.scene.add(bottomDirectionalLight); this.renderer.setClearColor('gray'); this.rezoom = true; this.renderScene = this.renderScene.bind(this); this.selectedelements = []; this.mouse = new THREE.Vector2(); this.offset = new THREE.Vector3(); this.INTERSECTED = null; this.SELECTED = null; this.plane = new THREE.Mesh( new THREE.PlaneGeometry(500, 500, 8, 8 ), new THREE.MeshBasicMaterial( { color: 0x000000, opacity: 0.25, transparent: true, wireframe: true } ) ); this.plane.visible = true; this.scene.add( this.plane ); this.controls = new TrackballControls( this.camera.getPerspectiveCamera() ); this.projector = new THREE.Projector(); this.controls.target = new THREE.Vector3(0, 0, 0); this.controls.maxDistance = 150; this.controls2 = null; } addMesh(meshes, parts, joints, model, creature) { let partsforjoints = []; meshes = []; parts = []; joints = []; for (let i = 0; i < model.getPartCount(); i++) { let mesh = this.partfactory.create(model.getPart(i)); partsforjoints.push(mesh.userData); meshes.push(mesh); parts.push(mesh); creature.add(mesh); } for (let i = 0; i < model.getJointCount(); i++) { let mesh = this.jointfactory.create(model.getJoint(i), partsforjoints); meshes.push(mesh); joints.push(mesh); creature.add(mesh); } } /** * Selects object from scene according to current mouse position. The position * of mouse should be transformed to screen space, which has values from -1 to 1 * for each dimension. * @param {{x: number, y: number}} position position of mouse in canvas normalized to [-1,1]x[-1,1] space */ handleMouseDown(position) { this.raycaster.setFromCamera( position, this.camera.getPerspectiveCamera() ); let intersectsC1 = this.raycaster.intersectObjects( this.creature1.children ); let intersectsC2 = this.raycaster.intersectObjects( this.creature2.children ); if ( intersectsC1.length > 0 || intersectsC2.length > 0 ) { this.controls.enabled = false; let obj = intersectsC1.length > 0 ? this.creature1 : this.creature2; this.SELECTED = obj; let intersects = this.raycaster.intersectObject( this.plane ); this.offset.copy( intersects[ 0 ].point ).sub( this.plane.position ); } } handleMouseUp() { this.controls.enabled = true; this.SELECTED = null; if (this.INTERSECTED) { this.plane.position.copy(this.INTERSECTED.position); this.SELECTED = null; } } handleMouseMove(position) { this.raycaster.setFromCamera( position, this.camera.getPerspectiveCamera() ); if ( this.SELECTED ) { let intersects = this.raycaster.intersectObject( this.plane ); if (intersects[ 0 ]) {this.SELECTED.position.copy( intersects[ 0 ].point.sub( this.offset ) );} return; } let intersectsC1 = this.raycaster.intersectObjects(this.creature1.children); let intersectsC2 = this.raycaster.intersectObjects(this.creature2.children); if (intersectsC1.length > 0 || intersectsC2.length > 0) { let obj = intersectsC1.length > 0 ? this.creature1 : this.creature2; this.INTERSECTED = obj; this.plane.position.copy( obj.position ); this.plane.lookAt( this.camera.getPerspectiveCamera().position ); } else { this.INTERSECTED = null; } } /** * Method uses ready Model to create and add meshes to the scene. If model is * undefined, an empty scene will be rendered. * @param {Module.Model} model input model 1 * @param {Module.Model} model input model 2 */ loadMeshesFromModel(model, model2) { this.clearView(); if (typeof model !== 'undefined') { this.addMesh(this.meshes, this.parts, this.joints, model, this.creature1); this.addMesh(this.meshes2, this.parts2, this.joints2, model2, this.creature2); this.creature2.position.set( 0, 0, 0 ); this.creature2.position.set( 0, -1, 0 ); this.scene.add(this.creature1); this.scene.add(this.creature2); if (this.rezoom) { this.camera.zoomAll(this.meshes); } } } /** * Method for rendering Scene. It should be passed as drawing element in * owners method. * * Example of usage is visible in GenoViewer Component. * @returns {number} frameId of animation */ renderScene() { this.camera.getCameraControl().update(); let frameId = window.requestAnimationFrame(this.renderScene); this.scene.render(this.renderer, this.camera.getPerspectiveCamera()); return frameId; } /** * Resizes scene. Use with caution - this is performance-consuming function, * so it should only be used when necessary. To perform resize, the canvas * should have absolute position within parent, otherwise it * will change size on its own. * @param {number} width new width of canvas * @param {number} height new height of canvas */ resizeView(width, height) { this.renderer.context.canvas.width = width; this.renderer.context.canvas.height = height; this.camera.getPerspectiveCamera().aspect = width / height; this.camera.getPerspectiveCamera().updateProjectionMatrix(); this.renderer.setSize(width, height); //console.log(this.renderer.getSize()); this.renderer.setPixelRatio(window.devicePixelRatio || 1); } /** * Determines whether camera should change position in order to show all body * or not. * @param {boolean} rezoom true if should rezoom, false otherwise */ setRezoom(rezoom) { this.rezoom = rezoom; this.camera.zoomAll(this.meshes); } /** * Determines whether camera should rotate automatically. * @param {boolean} autorotate true if should autorotate, false otherwise */ setAutoRotate(autorotate) { this.camera.setAutoRotate(autorotate); } /** * Clears scene from meshes. */ clearView() { this.scene.clear(); } /** * Returns reference to renderer DOM Element. * @returns {Element} DOM Element of Viewer. */ getDOM() { return this.renderer.domElement; } /** * Highlights all parts and joints presented in selectedelements argument. * @param {SelectedElements} selectedelements object of class SelectedElements defined in mappingresolver.js, determines which parts and joints should be highlighted */ highlightElements(selectedelements) { this.selectedelements = []; for (let i = 0; i < selectedelements.parts.length; i++) { this.parts[selectedelements.parts[i]].material.transparent = false; this.parts[selectedelements.parts[i]].material.emissive.set('green'); this.selectedelements.push(this.parts[selectedelements.parts[i]]); } for (let i = 0; i < selectedelements.joints.length; i++) { this.joints[selectedelements.joints[i]].material.transparent = false; this.joints[selectedelements.joints[i]].material.emissive.set('green'); this.selectedelements.push(this.joints[selectedelements.joints[i]]); } } /** * Removes all highlights from objects. */ clearHighlights() { for (let i = 0; i < this.selectedelements.length; i++) { if (this.selectedelements[i] && this.selectedelements[i].material) { this.selectedelements[i].material.transparent = this.selectedelements[i].userData.showTransparent; this.selectedelements[i].material.emissive.set('black'); } } this.selectedelements = []; } } export default Viewer;