612 lines
17 KiB
JavaScript
612 lines
17 KiB
JavaScript
import { Coord3D, CoordDistance3D, SubCoord3D } from '../geometry/coord3d.js';
|
|
import { DegRad, Direction, IsEqual } from '../geometry/geometry.js';
|
|
import { ColorComponentToFloat } from '../model/color.js';
|
|
import { ShadingType } from '../threejs/threeutils.js';
|
|
import { Camera, CameraMode } from './camera.js';
|
|
import { GetDomElementInnerDimensions } from './domutils.js';
|
|
import { Navigation } from './navigation.js';
|
|
import { ShadingModel } from './shadingmodel.js';
|
|
import { ViewerModel, ViewerMainModel } from './viewermodel.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 CameraValidator
|
|
{
|
|
constructor ()
|
|
{
|
|
this.eyeCenterDistance = 0.0;
|
|
this.forceUpdate = true;
|
|
}
|
|
|
|
ForceUpdate ()
|
|
{
|
|
this.forceUpdate = true;
|
|
}
|
|
|
|
ValidatePerspective ()
|
|
{
|
|
if (this.forceUpdate) {
|
|
this.forceUpdate = false;
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
ValidateOrthographic (eyeCenterDistance)
|
|
{
|
|
if (this.forceUpdate || !IsEqual (this.eyeCenterDistance, eyeCenterDistance)) {
|
|
this.eyeCenterDistance = eyeCenterDistance;
|
|
this.forceUpdate = false;
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
|
|
export class UpVector
|
|
{
|
|
constructor ()
|
|
{
|
|
this.direction = Direction.Y;
|
|
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 Viewer
|
|
{
|
|
constructor ()
|
|
{
|
|
THREE.ColorManagement.enabled = false;
|
|
|
|
this.canvas = null;
|
|
this.renderer = null;
|
|
this.scene = null;
|
|
this.mainModel = null;
|
|
this.extraModel = null;
|
|
this.camera = null;
|
|
this.cameraMode = null;
|
|
this.cameraValidator = null;
|
|
this.shadingModel = 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);
|
|
this.renderer.outputColorSpace = THREE.LinearSRGBColorSpace;
|
|
|
|
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.mainModel = new ViewerMainModel (this.scene);
|
|
this.extraModel = new ViewerModel (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);
|
|
}
|
|
|
|
SetEdgeSettings (edgeSettings)
|
|
{
|
|
let newEdgeSettings = edgeSettings.Clone ();
|
|
this.mainModel.SetEdgeSettings (newEdgeSettings);
|
|
this.Render ();
|
|
}
|
|
|
|
SetEnvironmentMapSettings (environmentSettings)
|
|
{
|
|
let newEnvironmentSettings = environmentSettings.Clone ();
|
|
this.shadingModel.SetEnvironmentMapSettings (newEnvironmentSettings, () => {
|
|
this.Render ();
|
|
});
|
|
this.shadingModel.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 ();
|
|
}
|
|
|
|
GetCanvas ()
|
|
{
|
|
return this.canvas;
|
|
}
|
|
|
|
GetCamera ()
|
|
{
|
|
return this.navigation.GetCamera ();
|
|
}
|
|
|
|
GetCameraMode ()
|
|
{
|
|
return this.cameraMode;
|
|
}
|
|
|
|
SetCamera (camera)
|
|
{
|
|
this.navigation.SetCamera (camera);
|
|
this.cameraValidator.ForceUpdate ();
|
|
this.Render ();
|
|
}
|
|
|
|
SetCameraMode (cameraMode)
|
|
{
|
|
if (this.cameraMode === cameraMode) {
|
|
return;
|
|
}
|
|
|
|
this.scene.remove (this.camera);
|
|
if (cameraMode === CameraMode.Perspective) {
|
|
this.camera = new THREE.PerspectiveCamera (45.0, 1.0, 0.1, 1000.0);
|
|
} else if (cameraMode === CameraMode.Orthographic) {
|
|
this.camera = new THREE.OrthographicCamera (-1.0, 1.0, 1.0, -1.0, 0.1, 1000.0);
|
|
}
|
|
this.scene.add (this.camera);
|
|
|
|
this.cameraMode = cameraMode;
|
|
this.shadingModel.SetCameraMode (cameraMode);
|
|
this.cameraValidator.ForceUpdate ();
|
|
|
|
this.AdjustClippingPlanes ();
|
|
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.renderer.setSize (width, height);
|
|
this.cameraValidator.ForceUpdate ();
|
|
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);
|
|
}
|
|
|
|
AdjustClippingPlanes ()
|
|
{
|
|
let boundingSphere = this.GetBoundingSphere ((meshUserData) => {
|
|
return true;
|
|
});
|
|
this.AdjustClippingPlanesToSphere (boundingSphere);
|
|
}
|
|
|
|
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.cameraValidator.ForceUpdate ();
|
|
this.Render ();
|
|
}
|
|
|
|
IsFixUpVector ()
|
|
{
|
|
return this.navigation.IsFixUpVector ();
|
|
}
|
|
|
|
SetFixUpVector (isFixUpVector)
|
|
{
|
|
let oldCamera = this.navigation.GetCamera ();
|
|
let newCamera = this.upVector.SetFixed (isFixUpVector, oldCamera);
|
|
this.navigation.SetFixUpVector (isFixUpVector);
|
|
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));
|
|
|
|
if (this.cameraMode === CameraMode.Perspective) {
|
|
if (!this.cameraValidator.ValidatePerspective ()) {
|
|
this.camera.aspect = this.canvas.width / this.canvas.height;
|
|
this.camera.fov = navigationCamera.fov;
|
|
this.camera.updateProjectionMatrix ();
|
|
}
|
|
} else if (this.cameraMode === CameraMode.Orthographic) {
|
|
let eyeCenterDistance = CoordDistance3D (navigationCamera.eye, navigationCamera.center);
|
|
if (!this.cameraValidator.ValidateOrthographic (eyeCenterDistance)) {
|
|
let aspect = this.canvas.width / this.canvas.height;
|
|
let eyeCenterDistance = CoordDistance3D (navigationCamera.eye, navigationCamera.center);
|
|
let frustumHalfHeight = eyeCenterDistance * Math.tan (0.5 * navigationCamera.fov * DegRad);
|
|
this.camera.left = -frustumHalfHeight * aspect;
|
|
this.camera.right = frustumHalfHeight * aspect;
|
|
this.camera.top = frustumHalfHeight;
|
|
this.camera.bottom = -frustumHalfHeight;
|
|
this.camera.updateProjectionMatrix ();
|
|
}
|
|
}
|
|
|
|
this.shadingModel.UpdateByCamera (navigationCamera);
|
|
this.renderer.render (this.scene, this.camera);
|
|
}
|
|
|
|
SetMainObject (object)
|
|
{
|
|
const shadingType = GetShadingTypeOfObject (object);
|
|
this.mainModel.SetMainObject (object);
|
|
this.shadingModel.SetShadingType (shadingType);
|
|
|
|
this.Render ();
|
|
}
|
|
|
|
AddExtraObject (object)
|
|
{
|
|
this.extraModel.AddObject (object);
|
|
this.Render ();
|
|
}
|
|
|
|
Clear ()
|
|
{
|
|
this.mainModel.Clear ();
|
|
this.extraModel.Clear ();
|
|
this.Render ();
|
|
}
|
|
|
|
ClearExtra ()
|
|
{
|
|
this.extraModel.Clear ();
|
|
this.Render ();
|
|
}
|
|
|
|
SetMeshesVisibility (isVisible)
|
|
{
|
|
this.mainModel.EnumerateMeshes ((mesh) => {
|
|
let visible = isVisible (mesh.userData);
|
|
if (mesh.visible !== visible) {
|
|
mesh.visible = visible;
|
|
}
|
|
});
|
|
this.mainModel.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.mainModel.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.mainModel.edgeSettings.showEdges;
|
|
return this.shadingModel.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.mainModel.GetMeshIntersectionUnderMouse (mouseCoords, this.camera, canvasSize.width, canvasSize.height);
|
|
if (intersection === null) {
|
|
return null;
|
|
}
|
|
return intersection;
|
|
}
|
|
|
|
GetBoundingBox (needToProcess)
|
|
{
|
|
return this.mainModel.GetBoundingBox (needToProcess);
|
|
}
|
|
|
|
GetBoundingSphere (needToProcess)
|
|
{
|
|
return this.mainModel.GetBoundingSphere (needToProcess);
|
|
}
|
|
|
|
EnumerateMeshesUserData (enumerator)
|
|
{
|
|
this.mainModel.EnumerateMeshes ((mesh) => {
|
|
enumerator (mesh.userData);
|
|
});
|
|
}
|
|
|
|
InitNavigation ()
|
|
{
|
|
let camera = GetDefaultCamera (Direction.Y);
|
|
this.camera = new THREE.PerspectiveCamera (45.0, 1.0, 0.1, 1000.0);
|
|
this.cameraMode = CameraMode.Perspective;
|
|
this.cameraValidator = new CameraValidator ();
|
|
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.shadingModel = new ShadingModel (this.scene);
|
|
}
|
|
|
|
GetShadingType ()
|
|
{
|
|
return this.shadingModel.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;
|
|
}
|
|
|
|
Destroy ()
|
|
{
|
|
this.Clear ();
|
|
this.renderer.dispose ();
|
|
}
|
|
}
|