You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

787 lines
15 KiB

import * as THREE from 'three';
import { TransformControls } from 'three/addons/controls/TransformControls.js';
import { UIPanel } from './libs/ui.js';
import { EditorControls } from './EditorControls.js';
import { ViewportCamera } from './Viewport.Camera.js';
import { ViewportInfo } from './Viewport.Info.js';
import { ViewHelper } from './Viewport.ViewHelper.js';
import { VR } from './Viewport.VR.js';
import { SetPositionCommand } from './commands/SetPositionCommand.js';
import { SetRotationCommand } from './commands/SetRotationCommand.js';
import { SetScaleCommand } from './commands/SetScaleCommand.js';
import { RoomEnvironment } from 'three/addons/environments/RoomEnvironment.js';
import { CSS2DRenderer, CSS2DObject } from 'three/addons/renderers/CSS2DRenderer.js';
import { OrbitControls } from '/examples/jsm/controls/OrbitControls.js';
function Viewport(editor) {
const signals = editor.signals;
const container = new UIPanel();
container.setId('viewport');
container.setPosition('absolute');
container.add(new ViewportCamera(editor));
container.add(new ViewportInfo(editor));
//
let renderer = null;
let css2DRenderer = null;
let pmremGenerator = null;
const camera = editor.camera;
const scene = editor.scene;
const sceneHelpers = editor.sceneHelpers;
let showSceneHelpers = true;
// helpers
const grid = new THREE.Group();
const grid1 = new THREE.GridHelper(30, 30, 0x888888);
grid1.material.color.setHex(0x888888);
grid1.material.vertexColors = false;
grid.add(grid1);
const grid2 = new THREE.GridHelper(30, 6, 0x222222);
grid2.material.color.setHex(0x222222);
grid2.material.depthFunc = THREE.AlwaysDepth;
grid2.material.vertexColors = false;
grid.add(grid2);
const viewHelper = new ViewHelper(camera, container);
const vr = new VR(editor);
//
const box = new THREE.Box3();
const selectionBox = new THREE.Box3Helper(box);
selectionBox.material.depthTest = false;
selectionBox.material.transparent = true;
selectionBox.visible = false;
sceneHelpers.add(selectionBox);
let objectPositionOnDown = null;
let objectRotationOnDown = null;
let objectScaleOnDown = null;
const transformControls = new TransformControls(camera, container.dom);
transformControls.addEventListener('change', function () {
const object = transformControls.object;
if (object !== undefined) {
box.setFromObject(object, true);
const helper = editor.helpers[object.id];
if (helper !== undefined && helper.isSkeletonHelper !== true) {
helper.update();
}
signals.refreshSidebarObject3D.dispatch(object);
}
render();
});
transformControls.addEventListener('mouseDown', function () {
const object = transformControls.object;
objectPositionOnDown = object.position.clone();
objectRotationOnDown = object.rotation.clone();
objectScaleOnDown = object.scale.clone();
controls.enabled = false;
});
transformControls.addEventListener('mouseUp', function () {
const object = transformControls.object;
if (object !== undefined) {
switch (transformControls.getMode()) {
case 'translate':
if (!objectPositionOnDown.equals(object.position)) {
editor.execute(new SetPositionCommand(editor, object, object.position, objectPositionOnDown));
}
break;
case 'rotate':
if (!objectRotationOnDown.equals(object.rotation)) {
editor.execute(new SetRotationCommand(editor, object, object.rotation, objectRotationOnDown));
}
break;
case 'scale':
if (!objectScaleOnDown.equals(object.scale)) {
editor.execute(new SetScaleCommand(editor, object, object.scale, objectScaleOnDown));
}
break;
}
}
controls.enabled = true;
});
sceneHelpers.add(transformControls);
// object picking
const raycaster = new THREE.Raycaster();
const mouse = new THREE.Vector2();
// events
function updateAspectRatio() {
camera.aspect = container.dom.offsetWidth / container.dom.offsetHeight;
camera.updateProjectionMatrix();
}
function getIntersects(point) {
mouse.set((point.x * 2) - 1, - (point.y * 2) + 1);
raycaster.setFromCamera(mouse, camera);
const objects = [];
scene.traverseVisible(function (child) {
objects.push(child);
});
sceneHelpers.traverseVisible(function (child) {
if (child.name === 'picker') objects.push(child);
});
return raycaster.intersectObjects(objects, false);
}
const onDownPosition = new THREE.Vector2();
const onUpPosition = new THREE.Vector2();
const onDoubleClickPosition = new THREE.Vector2();
function getMousePosition(dom, x, y) {
const rect = dom.getBoundingClientRect();
return [(x - rect.left) / rect.width, (y - rect.top) / rect.height];
}
function handleClick() {
if (onDownPosition.distanceTo(onUpPosition) === 0) {
const intersects = getIntersects(onUpPosition);
signals.intersectionsDetected.dispatch(intersects);
render();
}
}
function onMouseDown(event) {
// event.preventDefault();
const array = getMousePosition(container.dom, event.clientX, event.clientY);
onDownPosition.fromArray(array);
document.addEventListener('mouseup', onMouseUp);
}
function onMouseUp(event) {
const array = getMousePosition(container.dom, event.clientX, event.clientY);
onUpPosition.fromArray(array);
handleClick();
document.removeEventListener('mouseup', onMouseUp);
}
function onTouchStart(event) {
const touch = event.changedTouches[0];
const array = getMousePosition(container.dom, touch.clientX, touch.clientY);
onDownPosition.fromArray(array);
document.addEventListener('touchend', onTouchEnd);
}
function onTouchEnd(event) {
const touch = event.changedTouches[0];
const array = getMousePosition(container.dom, touch.clientX, touch.clientY);
onUpPosition.fromArray(array);
handleClick();
document.removeEventListener('touchend', onTouchEnd);
}
function onDoubleClick(event) {
const array = getMousePosition(container.dom, event.clientX, event.clientY);
onDoubleClickPosition.fromArray(array);
const intersects = getIntersects(onDoubleClickPosition);
if (intersects.length > 0) {
const intersect = intersects[0];
signals.objectFocused.dispatch(intersect.object);
}
}
container.dom.addEventListener('mousedown', onMouseDown);
container.dom.addEventListener('touchstart', onTouchStart);
container.dom.addEventListener('dblclick', onDoubleClick);
// controls need to be added *after* main logic,
// otherwise controls.enabled doesn't work.
const controls = new EditorControls(camera, container.dom);
controls.addEventListener('change', function () {
signals.cameraChanged.dispatch(camera);
signals.refreshSidebarObject3D.dispatch(camera);
});
viewHelper.controls = controls;
// signals
signals.editorCleared.add(function () {
controls.center.set(0, 0, 0);
render();
});
signals.transformModeChanged.add(function (mode) {
transformControls.setMode(mode);
});
signals.snapChanged.add(function (dist) {
transformControls.setTranslationSnap(dist);
});
signals.spaceChanged.add(function (space) {
transformControls.setSpace(space);
});
signals.rendererUpdated.add(function () {
scene.traverse(function (child) {
if (child.material !== undefined) {
child.material.needsUpdate = true;
}
});
render();
});
signals.rendererCreated.add(function (newRenderer) {
if (renderer !== null) {
renderer.setAnimationLoop(null);
renderer.dispose();
pmremGenerator.dispose();
container.dom.removeChild(renderer.domElement);
}
renderer = newRenderer;
renderer.setAnimationLoop(animate);
renderer.setClearColor(0xaaaaaa);
if (window.matchMedia) {
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
mediaQuery.addEventListener('change', function (event) {
renderer.setClearColor(event.matches ? 0x333333 : 0xaaaaaa);
updateGridColors(grid1, grid2, event.matches ? [0x222222, 0x888888] : [0x888888, 0x282828]);
render();
});
renderer.setClearColor(mediaQuery.matches ? 0x333333 : 0xaaaaaa);
updateGridColors(grid1, grid2, mediaQuery.matches ? [0x222222, 0x888888] : [0x888888, 0x282828]);
}
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(container.dom.offsetWidth, container.dom.offsetHeight);
pmremGenerator = new THREE.PMREMGenerator(renderer);
pmremGenerator.compileEquirectangularShader();
container.dom.appendChild(renderer.domElement);
render();
});
signals.rendererCSSCreated.add(function (newRenderer) {
if (css2DRenderer !== null) {
css2DRenderer.setAnimationLoop(null);
css2DRenderer.dispose();
container.dom.removeChild(css2DRenderer.domElement);
}
css2DRenderer = newRenderer;
container.dom.appendChild(css2DRenderer.domElement);
});
signals.sceneGraphChanged.add(function () {
render();
});
signals.cameraChanged.add(function () {
render();
});
signals.objectSelected.add(function (object) {
selectionBox.visible = false;
transformControls.detach();
if (object !== null && object !== scene && object !== camera) {
box.setFromObject(object, true);
if (box.isEmpty() === false) {
selectionBox.visible = true;
}
transformControls.attach(object);
}
render();
});
signals.objectFocused.add(function (object) {
controls.focus(object);
});
signals.geometryChanged.add(function (object) {
if (object !== undefined) {
box.setFromObject(object, true);
}
render();
});
signals.objectChanged.add(function (object) {
if (editor.selected === object) {
box.setFromObject(object, true);
}
if (object.isPerspectiveCamera) {
object.updateProjectionMatrix();
}
const helper = editor.helpers[object.id];
if (helper !== undefined && helper.isSkeletonHelper !== true) {
helper.update();
}
render();
});
signals.objectRemoved.add(function (object) {
controls.enabled = true; // see #14180
if (object === transformControls.object) {
transformControls.detach();
}
});
signals.materialChanged.add(function () {
render();
});
// background
signals.sceneBackgroundChanged.add(function (backgroundType, backgroundColor, backgroundTexture, backgroundEquirectangularTexture, backgroundBlurriness) {
switch (backgroundType) {
case 'None':
scene.background = null;
break;
case 'Color':
scene.background = new THREE.Color(backgroundColor);
break;
case 'Texture':
if (backgroundTexture) {
scene.background = backgroundTexture;
}
break;
case 'Equirectangular':
if (backgroundEquirectangularTexture) {
backgroundEquirectangularTexture.mapping = THREE.EquirectangularReflectionMapping;
scene.background = backgroundEquirectangularTexture;
scene.backgroundBlurriness = backgroundBlurriness;
}
break;
}
render();
});
// environment
signals.sceneEnvironmentChanged.add(function (environmentType, environmentEquirectangularTexture) {
switch (environmentType) {
case 'None':
scene.environment = null;
break;
case 'Equirectangular':
scene.environment = null;
if (environmentEquirectangularTexture) {
environmentEquirectangularTexture.mapping = THREE.EquirectangularReflectionMapping;
scene.environment = environmentEquirectangularTexture;
}
break;
case 'ModelViewer':
scene.environment = pmremGenerator.fromScene(new RoomEnvironment(), 0.04).texture;
break;
}
render();
});
// fog
signals.sceneFogChanged.add(function (fogType, fogColor, fogNear, fogFar, fogDensity) {
switch (fogType) {
case 'None':
scene.fog = null;
break;
case 'Fog':
scene.fog = new THREE.Fog(fogColor, fogNear, fogFar);
break;
case 'FogExp2':
scene.fog = new THREE.FogExp2(fogColor, fogDensity);
break;
}
render();
});
signals.sceneFogSettingsChanged.add(function (fogType, fogColor, fogNear, fogFar, fogDensity) {
switch (fogType) {
case 'Fog':
scene.fog.color.setHex(fogColor);
scene.fog.near = fogNear;
scene.fog.far = fogFar;
break;
case 'FogExp2':
scene.fog.color.setHex(fogColor);
scene.fog.density = fogDensity;
break;
}
render();
});
signals.viewportCameraChanged.add(function () {
const viewportCamera = editor.viewportCamera;
if (viewportCamera.isPerspectiveCamera) {
viewportCamera.aspect = editor.camera.aspect;
viewportCamera.projectionMatrix.copy(editor.camera.projectionMatrix);
} else if (viewportCamera.isOrthographicCamera) {
// TODO
}
// disable EditorControls when setting a user camera
controls.enabled = (viewportCamera === editor.camera);
render();
});
signals.exitedVR.add(render);
//
signals.windowResize.add(function () {
updateAspectRatio();
renderer.setSize(container.dom.offsetWidth, container.dom.offsetHeight);
if (css2DRenderer != null) {
css2DRenderer.setSize(window.innerWidth, window.innerHeight);
}
render();
});
signals.showGridChanged.add(function (showGrid) {
grid.visible = showGrid;
render();
});
signals.showHelpersChanged.add(function (showHelpers) {
showSceneHelpers = showHelpers;
transformControls.enabled = showHelpers;
render();
});
signals.render.add( function(){
render();
});
signals.cameraResetted.add(updateAspectRatio);
// animations
let prevActionsInUse = 0;
const clock = new THREE.Clock(); // only used for animations
function animate() {
const mixer = editor.mixer;
const delta = clock.getDelta();
let needsUpdate = false;
// Animations
const actions = mixer.stats.actions;
if (actions.inUse > 0 || prevActionsInUse > 0) {
prevActionsInUse = actions.inUse;
mixer.update(delta);
needsUpdate = true;
}
// View Helper
if (viewHelper.animating === true) {
viewHelper.update(delta);
needsUpdate = true;
}
if (vr.currentSession !== null) {
needsUpdate = true;
}
if (needsUpdate === true) render();
}
//
let startTime = 0;
let endTime = 0;
function render() {
startTime = performance.now();
// Adding/removing grid to scene so materials with depthWrite false
// don't render under the grid.
scene.add(grid);
renderer.setViewport(0, 0, container.dom.offsetWidth, container.dom.offsetHeight);
renderer.render(scene, editor.viewportCamera);
if (css2DRenderer != null) {
console.log("css2DRenderer render")
css2DRenderer.render(scene, editor.viewportCamera);
}
scene.remove(grid);
if (camera === editor.viewportCamera) {
renderer.autoClear = false;
if (showSceneHelpers === true) renderer.render(sceneHelpers, camera);
if (vr.currentSession === null) viewHelper.render(renderer);
renderer.autoClear = true;
}
endTime = performance.now();
editor.signals.sceneRendered.dispatch(endTime - startTime);
}
return container;
}
function updateGridColors(grid1, grid2, colors) {
grid1.material.color.setHex(colors[0]);
grid2.material.color.setHex(colors[1]);
}
export { Viewport };