mirror of
https://github.com/xibyte/jsketcher
synced 2025-12-06 08:25:19 +01:00
310 lines
8.9 KiB
TypeScript
310 lines
8.9 KiB
TypeScript
import DPR from 'dpr';
|
|
import './utils/threeLoader';
|
|
import './utils/vectorThreeEnhancement';
|
|
import {CADTrackballControls} from './controls/CADTrackballControls';
|
|
import {
|
|
AmbientLight,
|
|
Box3,
|
|
DirectionalLight,
|
|
Euler,
|
|
Matrix4,
|
|
Object3D,
|
|
OrthographicCamera,
|
|
PerspectiveCamera,
|
|
Raycaster,
|
|
Scene,
|
|
Vector3,
|
|
WebGLRenderer
|
|
} from "three";
|
|
import {Emitter, stream} from "lstream";
|
|
import {Camera} from "three/src/cameras/Camera";
|
|
|
|
export default class SceneSetUp {
|
|
workingSphere: number;
|
|
container: HTMLElement;
|
|
scene: Scene;
|
|
rootGroup: Object3D;
|
|
oCamera: OrthographicCamera;
|
|
pCamera: PerspectiveCamera;
|
|
camera: Camera;
|
|
light: DirectionalLight;
|
|
renderer: WebGLRenderer;
|
|
private _prevContainerWidth: number;
|
|
private _prevContainerHeight: number;
|
|
trackballControls: CADTrackballControls;
|
|
viewportSizeUpdate$ = stream();
|
|
sceneRendered$: Emitter<any> = stream();
|
|
viewCube;
|
|
|
|
renderRequested: boolean;
|
|
|
|
constructor(container) {
|
|
|
|
this.workingSphere = 10000;
|
|
this.container = container;
|
|
this.scene = new Scene();
|
|
this.rootGroup = this.scene;
|
|
this.scene.userData.sceneSetUp = this;
|
|
this.renderRequested = false;
|
|
this.viewCube = document.querySelector('.cube');
|
|
|
|
this.setUpCamerasAndLights();
|
|
this.setUpControls();
|
|
|
|
this.animate();
|
|
}
|
|
|
|
aspect() {
|
|
return this.container.clientWidth / this.container.clientHeight;
|
|
}
|
|
|
|
requestRender() {
|
|
this.renderRequested = true;
|
|
}
|
|
|
|
createOrthographicCamera() {
|
|
const width = this.container.clientWidth;
|
|
const height = this.container.clientHeight;
|
|
const factor = ORTHOGRAPHIC_CAMERA_FACTOR;
|
|
this.oCamera = new OrthographicCamera(-width / factor,
|
|
width / factor,
|
|
height / factor,
|
|
-height / factor, 0.1, 1000000);
|
|
this.oCamera.position.z = 1000;
|
|
this.oCamera.position.x = -1000;
|
|
this.oCamera.position.y = 300;
|
|
}
|
|
|
|
createPerspectiveCamera() {
|
|
this.pCamera = new PerspectiveCamera( 60, this.aspect(), 0.1, 1000000 );
|
|
this.pCamera.position.z = 1000;
|
|
this.pCamera.position.x = -1000;
|
|
this.pCamera.position.y = 300;
|
|
}
|
|
|
|
setUpCamerasAndLights() {
|
|
this.createOrthographicCamera();
|
|
this.createPerspectiveCamera();
|
|
|
|
this.camera = this.oCamera;
|
|
|
|
this.light = new DirectionalLight( 0xffffff );
|
|
this.light.position.set( 10, 10, 10 );
|
|
this.scene.add(this.light);
|
|
|
|
this.scene.add( new AmbientLight( 0xffffff, 0.25 ) );
|
|
|
|
this.renderer = new WebGLRenderer();
|
|
this.renderer.setPixelRatio(DPR);
|
|
this.renderer.setClearColor(0x808080, 1);
|
|
this.renderer.setSize( this.container.clientWidth, this.container.clientHeight );
|
|
this.container.appendChild( this.renderer.domElement );
|
|
}
|
|
|
|
updateViewportSize() {
|
|
if (this.container.clientWidth > 0 && this.container.clientHeight > 0) {
|
|
this.updatePerspectiveCameraViewport();
|
|
this.updateOrthographicCameraViewport();
|
|
this.renderer.setSize( this.container.clientWidth, this.container.clientHeight );
|
|
this.viewportSizeUpdate$.next();
|
|
this.__render_NeverCallMeFromOutside();
|
|
}
|
|
}
|
|
|
|
updateViewportSizeIfNeeded() {
|
|
if (this._prevContainerWidth !== this.container.clientWidth ||
|
|
this._prevContainerHeight !== this.container.clientHeight) {
|
|
this.updateViewportSize();
|
|
this._prevContainerWidth = this.container.clientWidth;
|
|
this._prevContainerHeight = this.container.clientHeight;
|
|
}
|
|
}
|
|
|
|
updatePerspectiveCameraViewport() {
|
|
this.pCamera.aspect = this.aspect();
|
|
this.pCamera.updateProjectionMatrix();
|
|
}
|
|
|
|
updateOrthographicCameraViewport() {
|
|
const width = this.container.clientWidth;
|
|
const height = this.container.clientHeight;
|
|
const factor = ORTHOGRAPHIC_CAMERA_FACTOR;
|
|
this.oCamera.left = - width / factor;
|
|
this.oCamera.right = width / factor;
|
|
this.oCamera.top = height / factor;
|
|
this.oCamera.bottom = - height / factor;
|
|
this.oCamera.updateProjectionMatrix();
|
|
}
|
|
|
|
syncCameras(sourceCamera, targetCamera) {
|
|
const camPosition = new Vector3();
|
|
const camRotation = new Euler();
|
|
const tempMatrix = new Matrix4();
|
|
|
|
camPosition.setFromMatrixPosition( targetCamera.matrixWorld );
|
|
camRotation.setFromRotationMatrix( tempMatrix.extractRotation( targetCamera.matrixWorld ) );
|
|
const camDistance = sourceCamera.position.length();
|
|
|
|
sourceCamera.up.copy(this.camera.up);
|
|
sourceCamera.position.copy(camPosition);
|
|
sourceCamera.quaternion.copy(camPosition);
|
|
sourceCamera.position.normalize();
|
|
sourceCamera.position.multiplyScalar(camDistance);
|
|
}
|
|
|
|
setCamera(camera) {
|
|
this.syncCameras(camera, this.camera);
|
|
this.camera = camera;
|
|
this.trackballControls.setCameraMode(camera.isOrthographicCamera);
|
|
this.trackballControls.object = camera;
|
|
this.requestRender();
|
|
}
|
|
|
|
setUpControls() {
|
|
// controls = new THREE.OrbitControls( camera , renderer.domElement);
|
|
const trackballControls: any = new CADTrackballControls(this.camera , this.renderer.domElement);
|
|
|
|
// document.addEventListener( 'mousemove', function(){
|
|
|
|
// controls.update();
|
|
|
|
// }, false );
|
|
trackballControls.rotateSpeed = 3.8 * DPR;
|
|
trackballControls.projectionZoomSpeed = 0.5 * DPR;
|
|
trackballControls.zoomSpeed = 1.2 * DPR;
|
|
trackballControls.panSpeed = 0.3 * DPR;
|
|
|
|
trackballControls.noZoom = false;
|
|
trackballControls.noPan = false;
|
|
|
|
trackballControls.staticMoving = true;
|
|
trackballControls.dynamicDampingFactor = 0.3;
|
|
|
|
trackballControls.keys = [ 65, 83, 68 ];
|
|
this.trackballControls = trackballControls;
|
|
}
|
|
|
|
createRaycaster(viewX, viewY) {
|
|
const raycaster = new Raycaster();
|
|
raycaster.params.Line.threshold = 12 * (this._zoomMeasure() * 0.8);
|
|
|
|
(raycaster.params as any).Line2 = {
|
|
threshold: 20
|
|
};
|
|
|
|
const x = ( viewX / this.container.clientWidth ) * 2 - 1;
|
|
const y = - ( viewY / this.container.clientHeight ) * 2 + 1;
|
|
|
|
const mouse = new Vector3( x, y, 1 );
|
|
raycaster.setFromCamera( mouse, this.camera );
|
|
return raycaster;
|
|
}
|
|
|
|
raycast(event, objects, logInfoOut = null) {
|
|
const raycaster = this.createRaycaster(event.offsetX, event.offsetY);
|
|
if (logInfoOut !== null) {
|
|
logInfoOut.ray = raycaster.ray
|
|
}
|
|
|
|
const intersects = [];
|
|
|
|
function intersectObject(object) {
|
|
|
|
object.raycast( raycaster, intersects );
|
|
|
|
const children = object.children;
|
|
|
|
if (object.visible) {
|
|
for ( let i = 0, l = children.length; i < l; i ++ ) {
|
|
intersectObject(children[ i ]);
|
|
}
|
|
}
|
|
}
|
|
|
|
objects.forEach(intersectObject);
|
|
|
|
intersects.sort((a, b) => {
|
|
if (Math.abs(a.distance - b.distance) < 0.01 && (a.object.raycastPriority || b.object.raycastPriority)) {
|
|
return b.object.raycastPriority||0 - a.object.raycastPriority||0;
|
|
}
|
|
return a.distance - b.distance;
|
|
})
|
|
return intersects;
|
|
}
|
|
|
|
customRaycast(from3, to3, objects) {
|
|
const raycaster = new Raycaster();
|
|
const from = new Vector3().fromArray(from3);
|
|
const to = new Vector3().fromArray(to3);
|
|
const dir = to.sub(from);
|
|
const dist = dir.length();
|
|
raycaster.set(from, dir.normalize());
|
|
return raycaster.intersectObjects(objects, true ).filter(h => h.distance <= dist);
|
|
}
|
|
|
|
modelToScreen(pos) {
|
|
const width = this.container.clientWidth, height = this.container.clientHeight;
|
|
const widthHalf = width / 2, heightHalf = height / 2;
|
|
|
|
const vector = new Vector3();
|
|
vector.copy(pos);
|
|
vector.project(this.camera);
|
|
|
|
vector.x = ( vector.x * widthHalf ) + widthHalf;
|
|
vector.y = - ( vector.y * heightHalf ) + heightHalf;
|
|
return vector;
|
|
}
|
|
|
|
lookAtObject(obj) {
|
|
const camera = this.camera;
|
|
|
|
const box = new Box3();
|
|
box.setFromObject(obj);
|
|
const size = box.getSize(new Vector3());
|
|
//this.camera.position.set(0,0,0);
|
|
|
|
box.getCenter(camera.position);
|
|
const maxSize = Math.max(size.x, size.z);
|
|
camera.position.addScaledVector(camera.position.normalize(), 5000);
|
|
//this.camera.position.sub(new THREE.Vector3(0, 0, dist));
|
|
camera.up = new Vector3(0, 1, 0);
|
|
}
|
|
|
|
_zoomMeasure() {
|
|
return this.trackballControls.object.position.length() / 1e3;
|
|
}
|
|
|
|
|
|
|
|
animate() {
|
|
requestAnimationFrame( () => this.animate() );
|
|
const controlsChangedViewpoint = this.trackballControls.evaluate();
|
|
if (controlsChangedViewpoint || this.renderRequested) {
|
|
this.__render_NeverCallMeFromOutside();
|
|
}
|
|
this.updateViewportSizeIfNeeded();
|
|
}
|
|
|
|
private __render_NeverCallMeFromOutside() {
|
|
this.renderRequested = false;
|
|
this.light.position.set(this.camera.position.x, this.camera.position.y, this.camera.position.z);
|
|
this.renderer.render(this.scene, this.camera);
|
|
this.sceneRendered$.next();
|
|
}
|
|
|
|
domElement() {
|
|
return this.renderer.domElement;
|
|
}
|
|
}
|
|
|
|
export function getSceneSetup(object3D) {
|
|
do {
|
|
if (object3D.userData.sceneSetUp) {
|
|
return object3D.userData.sceneSetUp;
|
|
}
|
|
object3D = object3D.parent;
|
|
} while(object3D);
|
|
return null;
|
|
}
|
|
|
|
const ORTHOGRAPHIC_CAMERA_FACTOR = 1;
|