import { Coord3D, CoordDistance3D, SubCoord3D } from '../geometry/coord3d.js'; import { Direction } from '../geometry/geometry.js'; import { ColorComponentToFloat } from '../model/color.js'; import { ShadingType } from '../threejs/threeutils.js'; import { Camera } from './camera.js'; import { GetDomElementInnerDimensions } from './domutils.js'; import { Navigation } from './navigation.js'; import { ViewerExtraGeometry, ViewerGeometry } from './viewergeometry.js'; import * as THREE from 'three'; export function GetDefaultCamera (direction) { let fieldOfView = 45.0; if (direction === Direction.X) { return new Camera ( new Coord3D (2.0, -3.0, 1.5), new Coord3D (0.0, 0.0, 0.0), new Coord3D (1.0, 0.0, 0.0), fieldOfView ); } else if (direction === Direction.Y) { return new Camera ( new Coord3D (-1.5, 2.0, 3.0), new Coord3D (0.0, 0.0, 0.0), new Coord3D (0.0, 1.0, 0.0), fieldOfView ); } else if (direction === Direction.Z) { return new Camera ( new Coord3D (-1.5, -3.0, 2.0), new Coord3D (0.0, 0.0, 0.0), new Coord3D (0.0, 0.0, 1.0), fieldOfView ); } return null; } export function TraverseThreeObject (object, processor) { if (!processor (object)) { return false; } for (let child of object.children) { if (!TraverseThreeObject (child, processor)) { return false; } } return true; } export function GetShadingTypeOfObject (mainObject) { let shadingType = null; TraverseThreeObject (mainObject, (obj) => { if (obj.isMesh) { for (const material of obj.material) { if (material.type === 'MeshPhongMaterial') { shadingType = ShadingType.Phong; } else if (material.type === 'MeshStandardMaterial') { shadingType = ShadingType.Physical; } return false; } } return true; }); return shadingType; } export class UpVector { constructor () { this.direction = Direction.Z; this.isFixed = true; this.isFlipped = false; } SetDirection (newDirection, oldCamera) { this.direction = newDirection; this.isFlipped = false; let defaultCamera = GetDefaultCamera (this.direction); let defaultDir = SubCoord3D (defaultCamera.eye, defaultCamera.center); let distance = CoordDistance3D (oldCamera.center, oldCamera.eye); let newEye = oldCamera.center.Clone ().Offset (defaultDir, distance); let newCamera = oldCamera.Clone (); if (this.direction === Direction.X) { newCamera.up = new Coord3D (1.0, 0.0, 0.0); newCamera.eye = newEye; } else if (this.direction === Direction.Y) { newCamera.up = new Coord3D (0.0, 1.0, 0.0); newCamera.eye = newEye; } else if (this.direction === Direction.Z) { newCamera.up = new Coord3D (0.0, 0.0, 1.0); newCamera.eye = newEye; } return newCamera; } SetFixed (isFixed, oldCamera) { this.isFixed = isFixed; if (this.isFixed) { return this.SetDirection (this.direction, oldCamera); } return null; } Flip (oldCamera) { this.isFlipped = !this.isFlipped; let newCamera = oldCamera.Clone (); newCamera.up.MultiplyScalar (-1.0); return newCamera; } } export class ShadingModel { constructor (scene) { this.scene = scene; this.type = ShadingType.Phong; this.ambientLight = new THREE.AmbientLight (0x888888); this.directionalLight = new THREE.DirectionalLight (0x888888); this.environment = null; this.backgroundIsEnvMap = false; this.scene.add (this.ambientLight); this.scene.add (this.directionalLight); } SetType (type) { this.type = type; this.UpdateShading (); } UpdateShading () { if (this.type === ShadingType.Phong) { this.ambientLight.color.set (0x888888); this.directionalLight.color.set (0x888888); this.scene.environment = null; } else if (this.type === ShadingType.Physical) { this.ambientLight.color.set (0x000000); this.directionalLight.color.set (0x555555); this.scene.environment = this.environment; } if (this.backgroundIsEnvMap) { this.scene.background = this.environment; } else { this.scene.background = null; } } SetEnvironment (textures, useAsBackground, onLoaded) { let loader = new THREE.CubeTextureLoader (); this.environment = loader.load (textures, () => { onLoaded (); }); this.backgroundIsEnvMap = useAsBackground; } UpdateByCamera (camera) { const lightDir = SubCoord3D (camera.eye, camera.center); this.directionalLight.position.set (lightDir.x, lightDir.y, lightDir.z); } CreateHighlightMaterial (highlightColor, withOffset) { let material = null; if (this.type === ShadingType.Phong) { material = new THREE.MeshPhongMaterial ({ color : highlightColor, side : THREE.DoubleSide }); } else if (this.type === ShadingType.Physical) { material = new THREE.MeshStandardMaterial ({ color : highlightColor, side : THREE.DoubleSide }); } if (material !== null && withOffset) { material.polygonOffset = true; material.polygonOffsetUnit = 1; material.polygonOffsetFactor = 1; } return material; } } export class Viewer { constructor () { this.canvas = null; this.renderer = null; this.scene = null; this.geometry = null; this.extraGeometry = null; this.camera = null; this.shading = null; this.navigation = null; this.upVector = null; this.settings = { animationSteps : 40 }; } Init (canvas) { this.canvas = canvas; this.canvas.id = 'viewer'; let parameters = { canvas : this.canvas, antialias : true }; this.renderer = new THREE.WebGLRenderer (parameters); if (window.devicePixelRatio) { this.renderer.setPixelRatio (window.devicePixelRatio); } this.renderer.setClearColor ('#ffffff', 1.0); this.renderer.setSize (this.canvas.width, this.canvas.height); this.scene = new THREE.Scene (); this.geometry = new ViewerGeometry (this.scene); this.extraGeometry = new ViewerExtraGeometry (this.scene); this.InitNavigation (); this.InitShading (); this.Render (); } SetMouseClickHandler (onMouseClick) { this.navigation.SetMouseClickHandler (onMouseClick); } SetMouseMoveHandler (onMouseMove) { this.navigation.SetMouseMoveHandler (onMouseMove); } SetContextMenuHandler (onContext) { this.navigation.SetContextMenuHandler (onContext); } SetEnvironmentMapSettings (textures, useAsBackground) { this.shading.SetEnvironment (textures, useAsBackground, () => { this.Render (); }); this.shading.UpdateShading (); this.Render (); } SetBackgroundColor (color) { let bgColor = new THREE.Color ( ColorComponentToFloat (color.r), ColorComponentToFloat (color.g), ColorComponentToFloat (color.b) ); let alpha = ColorComponentToFloat (color.a); this.renderer.setClearColor (bgColor, alpha); this.Render (); } SetEdgeSettings (show, color, threshold) { this.geometry.SetEdgeSettings (show, color, threshold); this.Render (); } GetCanvas () { return this.canvas; } GetCamera () { return this.navigation.GetCamera (); } SetCamera (camera) { this.navigation.SetCamera (camera); this.Render (); } Resize (width, height) { let innerSize = GetDomElementInnerDimensions (this.canvas, width, height); this.ResizeRenderer (innerSize.width, innerSize.height); } ResizeRenderer (width, height) { if (window.devicePixelRatio) { this.renderer.setPixelRatio (window.devicePixelRatio); } this.camera.aspect = width / height; this.camera.updateProjectionMatrix (); this.renderer.setSize (width, height); this.Render (); } FitSphereToWindow (boundingSphere, animation) { if (boundingSphere === null) { return; } let center = new Coord3D (boundingSphere.center.x, boundingSphere.center.y, boundingSphere.center.z); let radius = boundingSphere.radius; let newCamera = this.navigation.GetFitToSphereCamera (center, radius); this.navigation.MoveCamera (newCamera, animation ? this.settings.animationSteps : 0); } AdjustClippingPlanesToSphere (boundingSphere) { if (boundingSphere === null) { return; } if (boundingSphere.radius < 10.0) { this.camera.near = 0.01; this.camera.far = 100.0; } else if (boundingSphere.radius < 100.0) { this.camera.near = 0.1; this.camera.far = 1000.0; } else if (boundingSphere.radius < 1000.0) { this.camera.near = 10.0; this.camera.far = 10000.0; } else { this.camera.near = 100.0; this.camera.far = 1000000.0; } this.camera.updateProjectionMatrix (); this.Render (); } IsFixUpVector () { return this.navigation.IsFixUpVector (); } SetFixUpVector (fixUpVector) { let oldCamera = this.navigation.GetCamera (); let newCamera = this.upVector.SetFixed (fixUpVector, oldCamera); this.navigation.SetFixUpVector (fixUpVector); if (newCamera !== null) { this.navigation.MoveCamera (newCamera, this.settings.animationSteps); } this.Render (); } SetUpVector (upDirection, animate) { let oldCamera = this.navigation.GetCamera (); let newCamera = this.upVector.SetDirection (upDirection, oldCamera); let animationSteps = animate ? this.settings.animationSteps : 0; this.navigation.MoveCamera (newCamera, animationSteps); this.Render (); } FlipUpVector () { let oldCamera = this.navigation.GetCamera (); let newCamera = this.upVector.Flip (oldCamera); this.navigation.MoveCamera (newCamera, 0); this.Render (); } Render () { let navigationCamera = this.navigation.GetCamera (); this.camera.position.set (navigationCamera.eye.x, navigationCamera.eye.y, navigationCamera.eye.z); this.camera.up.set (navigationCamera.up.x, navigationCamera.up.y, navigationCamera.up.z); this.camera.lookAt (new THREE.Vector3 (navigationCamera.center.x, navigationCamera.center.y, navigationCamera.center.z)); this.shading.UpdateByCamera (navigationCamera); this.renderer.render (this.scene, this.camera); } SetMainObject (object) { const shadingType = GetShadingTypeOfObject (object); this.geometry.SetMainObject (object); this.shading.SetType (shadingType); this.Render (); } AddExtraObject (object) { this.extraGeometry.AddObject (object); this.Render (); } Clear () { this.geometry.Clear (); this.extraGeometry.Clear (); this.Render (); } ClearExtra () { this.extraGeometry.Clear (); this.Render (); } SetMeshesVisibility (isVisible) { this.geometry.EnumerateMeshes ((mesh) => { let visible = isVisible (mesh.userData); if (mesh.visible !== visible) { mesh.visible = visible; } }); this.geometry.EnumerateEdges ((edge) => { let visible = isVisible (edge.userData); if (edge.visible !== visible) { edge.visible = visible; } }); this.Render (); } SetMeshesHighlight (highlightColor, isHighlighted) { function CreateHighlightMaterials (originalMaterials, highlightMaterial) { let highlightMaterials = []; for (let i = 0; i < originalMaterials.length; i++) { highlightMaterials.push (highlightMaterial); } return highlightMaterials; } const highlightMaterial = this.CreateHighlightMaterial (highlightColor); this.geometry.EnumerateMeshes ((mesh) => { let highlighted = isHighlighted (mesh.userData); if (highlighted) { if (mesh.userData.threeMaterials === null) { mesh.userData.threeMaterials = mesh.material; mesh.material = CreateHighlightMaterials (mesh.material, highlightMaterial); } } else { if (mesh.userData.threeMaterials !== null) { mesh.material = mesh.userData.threeMaterials; mesh.userData.threeMaterials = null; } } }); this.Render (); } CreateHighlightMaterial (highlightColor) { const showEdges = this.geometry.edgeSettings.showEdges; return this.shading.CreateHighlightMaterial (highlightColor, showEdges); } GetMeshUserDataUnderMouse (mouseCoords) { let intersection = this.GetMeshIntersectionUnderMouse (mouseCoords); if (intersection === null) { return null; } return intersection.object.userData; } GetMeshIntersectionUnderMouse (mouseCoords) { let canvasSize = this.GetCanvasSize (); let intersection = this.geometry.GetMeshIntersectionUnderMouse (mouseCoords, this.camera, canvasSize.width, canvasSize.height); if (intersection === null) { return null; } return intersection; } GetBoundingBox (needToProcess) { return this.geometry.GetBoundingBox (needToProcess); } GetBoundingSphere (needToProcess) { return this.geometry.GetBoundingSphere (needToProcess); } EnumerateMeshesUserData (enumerator) { this.geometry.EnumerateMeshes ((mesh) => { enumerator (mesh.userData); }); } InitNavigation () { let camera = GetDefaultCamera (Direction.Z); this.camera = new THREE.PerspectiveCamera (camera.fov, this.canvas.width / this.canvas.height, 0.1, 1000.0); this.scene.add (this.camera); let canvasElem = this.renderer.domElement; this.navigation = new Navigation (canvasElem, camera, { onUpdate : () => { this.Render (); } }); this.upVector = new UpVector (); } InitShading () { this.shading = new ShadingModel (this.scene); } GetShadingType () { return this.shading.type; } GetImageSize () { let originalSize = new THREE.Vector2 (); this.renderer.getSize (originalSize); return { width : parseInt (originalSize.x, 10), height : parseInt (originalSize.y, 10) }; } GetCanvasSize () { let width = this.canvas.width; let height = this.canvas.height; if (window.devicePixelRatio) { width /= window.devicePixelRatio; height /= window.devicePixelRatio; } return { width : width, height : height }; } GetImageAsDataUrl (width, height) { let originalSize = this.GetImageSize (); let renderWidth = width; let renderHeight = height; if (window.devicePixelRatio) { renderWidth /= window.devicePixelRatio; renderHeight /= window.devicePixelRatio; } this.ResizeRenderer (renderWidth, renderHeight); this.Render (); let url = this.renderer.domElement.toDataURL (); this.ResizeRenderer (originalSize.width, originalSize.height); return url; } }