diff --git a/editor/css/main.css b/editor/css/main.css index a2a81b9d9efb5c..623653636ca396 100644 --- a/editor/css/main.css +++ b/editor/css/main.css @@ -103,7 +103,7 @@ hr { .TabbedPanel .Panels { position: absolute; - top: 40px; + top: 36px; display: block; width: 100%; } @@ -303,7 +303,8 @@ hr { .Select { color: #666; background-color: #ddd; - border: 0px; + border: 3px solid #ddd; + border-radius: 4px; text-transform: uppercase; cursor: pointer; outline: none; @@ -318,7 +319,7 @@ hr { #resizer { position: absolute; z-index: 2; /* Above #sidebar */ - top: 32px; + top: 36px; right: 350px; width: 5px; bottom: 0px; @@ -339,7 +340,7 @@ hr { #viewport { position: absolute; - top: 32px; + top: 36px; left: 0; right: 350px; bottom: 0; @@ -352,7 +353,7 @@ hr { #script { position: absolute; - top: 32px; + top: 36px; left: 0; right: 350px; bottom: 0; @@ -361,7 +362,7 @@ hr { #player { position: absolute; - top: 32px; + top: 36px; left: 0; right: 350px; bottom: 0; @@ -370,7 +371,7 @@ hr { #menubar { position: absolute; width: 100%; - height: 32px; + height: 36px; background: #eee; padding: 0; margin: 0; @@ -395,7 +396,7 @@ hr { display: inline-block; color: #888; margin: 0; - padding: 8px; + padding: 10px; line-height: 16px; } @@ -406,6 +407,7 @@ hr { border: 1px solid #ccc; border-radius: 4px; font-size: 9px; + line-height: normal; padding: 2px 4px; right: 10px; pointer-events: none; @@ -435,6 +437,7 @@ hr { color: #666; background-color: transparent; padding: 5px 10px; + line-height: 25px; margin: 0 !important; } @@ -472,6 +475,7 @@ hr { color: #bbb; background-color: transparent; padding: 5px 10px; + line-height: 25px; margin: 0 !important; cursor: not-allowed; } @@ -481,7 +485,7 @@ hr { #sidebar { position: absolute; right: 0; - top: 32px; + top: 36px; bottom: 0; width: 350px; background: #eee; @@ -530,15 +534,26 @@ hr { #toolbar { position: absolute; - left: 10px; - top: 42px; - width: 32px; + left: calc(50% - 175px); + transform: translateX(-50%); + bottom: 20px; + height: 32px; background: #eee; text-align: center; + display: flex; + align-items: center; + gap: 2px; + border-radius: 6px; + border: 3px solid #eee; + overflow: hidden; } #toolbar .Button, #toolbar .Input { height: 32px; + border-radius: 4px; + display: flex; + align-items: center; + justify-content: center; } #toolbar .Button img { @@ -617,22 +632,22 @@ hr { #viewport { left: 0; right: 0; - top: 32px; - height: calc(100% - 352px); + top: 36px; + height: calc(100% - 356px); } #script { left: 0; right: 0; - top: 32px; - height: calc(100% - 352px); + top: 36px; + height: calc(100% - 356px); } #player { left: 0; right: 0; - top: 32px; - height: calc(100% - 352px); + top: 36px; + height: calc(100% - 356px); } #sidebar { @@ -642,6 +657,12 @@ hr { bottom: 0; } + #toolbar { + left: 50%; + transform: translateX(-50%); + bottom: 330px; + } + } /* DARK MODE */ @@ -672,6 +693,7 @@ hr { .Select { color: #aaa; background-color: #222; + border-color: #222; } .Select:hover { @@ -734,6 +756,7 @@ hr { #toolbar { background-color: #111; + border-color: #111; } #toolbar img { diff --git a/editor/index.html b/editor/index.html index 5a1e5e56458eef..e0e8cf437ab3f4 100644 --- a/editor/index.html +++ b/editor/index.html @@ -16,6 +16,7 @@ { "imports": { "three": "../build/three.module.js", + "three/webgpu": "../build/three.webgpu.js", "three/addons/": "../examples/jsm/", "three/examples/": "../examples/", diff --git a/editor/js/Config.js b/editor/js/Config.js index 1f2fb51c8ef8a1..63d4bf4425bdb3 100644 --- a/editor/js/Config.js +++ b/editor/js/Config.js @@ -15,10 +15,11 @@ function Config() { 'project/editable': false, 'project/vr': false, + 'project/renderer/type': 'WebGLRenderer', 'project/renderer/antialias': true, 'project/renderer/shadows': true, 'project/renderer/shadowType': 1, // PCF - 'project/renderer/toneMapping': 0, // NoToneMapping + 'project/renderer/toneMapping': 7, // NeutralToneMapping 'project/renderer/toneMappingExposure': 1, 'settings/history': false, diff --git a/editor/js/Editor.js b/editor/js/Editor.js index 7dc023348dbeaf..3cf4d2a3cdfd9f 100644 --- a/editor/js/Editor.js +++ b/editor/js/Editor.js @@ -723,6 +723,7 @@ Editor.prototype = { metadata: {}, project: { + renderer: this.config.getKey( 'project/renderer/type' ), shadows: this.config.getKey( 'project/renderer/shadows' ), shadowType: this.config.getKey( 'project/renderer/shadowType' ), toneMapping: this.config.getKey( 'project/renderer/toneMapping' ), diff --git a/editor/js/Menubar.Render.js b/editor/js/Menubar.Render.js index 16b5887b4dc29e..3b257eedf436c6 100644 --- a/editor/js/Menubar.Render.js +++ b/editor/js/Menubar.Render.js @@ -411,7 +411,7 @@ class RenderVideoDialog { renderButton.onClick( async () => { const player = new APP.Player(); - player.load( editor.toJSON() ); + await player.load( editor.toJSON() ); player.setPixelRatio( 1 ); player.setSize( videoWidth.getValue(), videoHeight.getValue() ); diff --git a/editor/js/Player.js b/editor/js/Player.js index 75af096dae515e..7d19c1deb9e741 100644 --- a/editor/js/Player.js +++ b/editor/js/Player.js @@ -27,11 +27,11 @@ function Player( editor ) { } ); - signals.startPlayer.add( function () { + signals.startPlayer.add( async function () { container.setDisplay( '' ); - player.load( editor.toJSON() ); + await player.load( editor.toJSON() ); player.setSize( container.dom.clientWidth, container.dom.clientHeight ); player.play(); diff --git a/editor/js/Sidebar.Project.Renderer.js b/editor/js/Sidebar.Project.Renderer.js index 7b833d9496c5b7..8961ed04846aa7 100644 --- a/editor/js/Sidebar.Project.Renderer.js +++ b/editor/js/Sidebar.Project.Renderer.js @@ -1,4 +1,5 @@ import * as THREE from 'three'; +import { WebGPURenderer } from 'three/webgpu'; import { UINumber, UIPanel, UIRow, UISelect, UIText } from './libs/ui.js'; import { UIBoolean } from './libs/ui.three.js'; @@ -14,6 +15,20 @@ function SidebarProjectRenderer( editor ) { const container = new UIPanel(); container.setBorderTop( '0px' ); + // Renderer + + const rendererRow = new UIRow(); + container.add( rendererRow ); + + rendererRow.add( new UIText( strings.getKey( 'sidebar/project/renderer' ) ).setClass( 'Label' ) ); + + const rendererTypeSelect = new UISelect().setOptions( { + 'WebGLRenderer': 'WebGL', + 'WebGPURenderer': 'WebGPU' + } ).setWidth( '150px' ).onChange( createRenderer ); + rendererTypeSelect.setValue( config.getKey( 'project/renderer/type' ) ); + rendererRow.add( rendererTypeSelect ); + // Antialias const antialiasRow = new UIRow(); @@ -89,9 +104,22 @@ function SidebarProjectRenderer( editor ) { // - function createRenderer() { + async function createRenderer() { + + const rendererType = rendererTypeSelect.getValue(); + const antialias = antialiasBoolean.getValue(); + + if ( rendererType === 'WebGPURenderer' ) { + + currentRenderer = new WebGPURenderer( { antialias: antialias, logarithmicDepthBuffer: true } ); + await currentRenderer.init(); + + } else { + + currentRenderer = new THREE.WebGLRenderer( { antialias: antialias, logarithmicDepthBuffer: true } ); + + } - currentRenderer = new THREE.WebGLRenderer( { antialias: antialiasBoolean.getValue(), logarithmicDepthBuffer: true } ); currentRenderer.shadowMap.enabled = shadowsBoolean.getValue(); currentRenderer.shadowMap.type = parseFloat( shadowTypeSelect.getValue() ); currentRenderer.toneMapping = parseFloat( toneMappingSelect.getValue() ); @@ -111,7 +139,7 @@ function SidebarProjectRenderer( editor ) { currentRenderer.shadowMap.enabled = true; currentRenderer.shadowMap.type = THREE.PCFShadowMap; - currentRenderer.toneMapping = THREE.NoToneMapping; + currentRenderer.toneMapping = THREE.NeutralToneMapping; currentRenderer.toneMappingExposure = 1; shadowsBoolean.setValue( currentRenderer.shadowMap.enabled ); @@ -127,6 +155,7 @@ function SidebarProjectRenderer( editor ) { signals.rendererUpdated.add( function () { config.setKey( + 'project/renderer/type', rendererTypeSelect.getValue(), 'project/renderer/antialias', antialiasBoolean.getValue(), 'project/renderer/shadows', shadowsBoolean.getValue(), 'project/renderer/shadowType', parseFloat( shadowTypeSelect.getValue() ), diff --git a/editor/js/Strings.js b/editor/js/Strings.js index a6150471418936..dd959a74e9ee79 100644 --- a/editor/js/Strings.js +++ b/editor/js/Strings.js @@ -351,6 +351,7 @@ function Strings( config ) { 'sidebar/script/remove': 'حذف', 'sidebar/project': 'پروژه ها', + 'sidebar/project/renderer': 'رندرر', 'sidebar/project/antialias': 'آنتی الآیس', 'sidebar/project/shadows': 'سایه ها', 'sidebar/project/toneMapping': 'تون مپینگ', @@ -766,6 +767,7 @@ function Strings( config ) { 'sidebar/script/remove': 'Remove', 'sidebar/project': 'Project', + 'sidebar/project/renderer': 'Renderer', 'sidebar/project/antialias': 'Antialias', 'sidebar/project/shadows': 'Shadows', 'sidebar/project/toneMapping': 'Tonemapping', @@ -1182,6 +1184,7 @@ function Strings( config ) { 'sidebar/script/remove': 'Supprimer', 'sidebar/project': 'Projet', + 'sidebar/project/renderer': 'Moteur', 'sidebar/project/antialias': 'Anticrénelage', 'sidebar/project/shadows': 'Ombres', 'sidebar/project/toneMapping': 'Mappage des nuances', @@ -1598,6 +1601,7 @@ function Strings( config ) { 'sidebar/script/remove': '删除', 'sidebar/project': '项目', + 'sidebar/project/renderer': '渲染器', 'sidebar/project/antialias': '抗锯齿', 'sidebar/project/shadows': '阴影', 'sidebar/project/toneMapping': '色调映射', @@ -2014,6 +2018,7 @@ function Strings( config ) { 'sidebar/script/remove': '削除', 'sidebar/project': 'プロジェクト', + 'sidebar/project/renderer': 'レンダラー', 'sidebar/project/antialias': 'アンチエイリアス', 'sidebar/project/shadows': 'シャドウ', 'sidebar/project/toneMapping': 'トーンマッピング', @@ -2429,6 +2434,7 @@ function Strings( config ) { 'sidebar/script/remove': '삭제', 'sidebar/project': '프로젝트', + 'sidebar/project/renderer': '렌더러', 'sidebar/project/antialias': '안티앨리어싱', 'sidebar/project/shadows': '그림자', 'sidebar/project/toneMapping': '톤 매핑', diff --git a/editor/js/Toolbar.js b/editor/js/Toolbar.js index 87149fb4ac757e..1fe6d35f84d837 100644 --- a/editor/js/Toolbar.js +++ b/editor/js/Toolbar.js @@ -1,4 +1,4 @@ -import { UIPanel, UIButton, UICheckbox } from './libs/ui.js'; +import { UIPanel, UIButton } from './libs/ui.js'; function Toolbar( editor ) { @@ -50,15 +50,6 @@ function Toolbar( editor ) { } ); container.add( scale ); - const local = new UICheckbox( false ); - local.dom.title = strings.getKey( 'toolbar/local' ); - local.onChange( function () { - - signals.spaceChanged.dispatch( this.getValue() === true ? 'local' : 'world' ); - - } ); - container.add( local ); - // signals.transformModeChanged.add( function ( mode ) { diff --git a/editor/js/Viewport.Controls.js b/editor/js/Viewport.Controls.js index ad2ecdabb10b2a..aa9d92e4911032 100644 --- a/editor/js/Viewport.Controls.js +++ b/editor/js/Viewport.Controls.js @@ -8,12 +8,10 @@ function ViewportControls( editor ) { container.setPosition( 'absolute' ); container.setRight( '10px' ); container.setTop( '10px' ); - container.setColor( '#ffffff' ); // camera const cameraSelect = new UISelect(); - cameraSelect.setMarginLeft( '10px' ); cameraSelect.setMarginRight( '10px' ); cameraSelect.onChange( function () { diff --git a/editor/js/Viewport.Info.js b/editor/js/Viewport.Info.js index 4874eca1546c2e..38a621cc390a47 100644 --- a/editor/js/Viewport.Info.js +++ b/editor/js/Viewport.Info.js @@ -9,7 +9,7 @@ function ViewportInfo( editor ) { container.setId( 'info' ); container.setPosition( 'absolute' ); container.setLeft( '10px' ); - container.setBottom( '20px' ); + container.setBottom( '50px' ); container.setFontSize( '12px' ); container.setColor( '#fff' ); container.setTextTransform( 'lowercase' ); @@ -133,7 +133,7 @@ function ViewportInfo( editor ) { samplesText.setHidden( ! isRealisticShading ); samplesUnitText.setHidden( ! isRealisticShading ); - container.setBottom( isRealisticShading ? '32px' : '20px' ); + container.setBottom( isRealisticShading ? '62px' : '50px' ); } ); diff --git a/editor/js/Viewport.ViewHelper.js b/editor/js/Viewport.ViewHelper.js index f350db5f3e1f39..09a0f93db2619c 100644 --- a/editor/js/Viewport.ViewHelper.js +++ b/editor/js/Viewport.ViewHelper.js @@ -8,11 +8,13 @@ class ViewHelper extends ViewHelperBase { super( editorCamera, container.dom ); + this.location.top = 30; + const panel = new UIPanel(); panel.setId( 'viewHelper' ); panel.setPosition( 'absolute' ); panel.setRight( '0px' ); - panel.setBottom( '0px' ); + panel.setTop( '30px' ); panel.setHeight( '128px' ); panel.setWidth( '128px' ); diff --git a/editor/js/Viewport.js b/editor/js/Viewport.js index 7e50961b2d774b..e02dbfc1f432e5 100644 --- a/editor/js/Viewport.js +++ b/editor/js/Viewport.js @@ -1,4 +1,5 @@ import * as THREE from 'three'; +import { PMREMGenerator } from 'three/webgpu'; import { TransformControls } from 'three/addons/controls/TransformControls.js'; @@ -291,7 +292,7 @@ function Viewport( editor ) { signals.editorCleared.add( function () { controls.center.set( 0, 0, 0 ); - pathtracer.reset(); + if ( pathtracer ) pathtracer.reset(); initPT(); @@ -342,8 +343,18 @@ function Viewport( editor ) { if ( renderer !== null ) { renderer.setAnimationLoop( null ); + + try { + + pmremGenerator.dispose(); + + } catch ( e ) { + + console.warn( 'PMREMGenerator dispose error:', e ); + + } + renderer.dispose(); - pmremGenerator.dispose(); container.dom.removeChild( renderer.domElement ); @@ -377,13 +388,25 @@ function Viewport( editor ) { renderer.setPixelRatio( window.devicePixelRatio ); renderer.setSize( container.dom.offsetWidth, container.dom.offsetHeight ); - pmremGenerator = new THREE.PMREMGenerator( renderer ); - pmremGenerator.compileEquirectangularShader(); + if ( renderer.isWebGLRenderer ) { + + pmremGenerator = new THREE.PMREMGenerator( renderer ); + pmremGenerator.compileEquirectangularShader(); + + pathtracer = new ViewportPathtracer( renderer ); - pathtracer = new ViewportPathtracer( renderer ); + } else { + + pmremGenerator = new PMREMGenerator( renderer ); + + pathtracer = null; + + } container.dom.appendChild( renderer.domElement ); + signals.sceneEnvironmentChanged.dispatch( editor.environmentType ); + render(); } ); @@ -403,7 +426,7 @@ function Viewport( editor ) { signals.cameraChanged.add( function () { - pathtracer.reset(); + if ( pathtracer ) pathtracer.reset(); render(); @@ -682,7 +705,7 @@ function Viewport( editor ) { switch ( viewportShading ) { case 'realistic': - pathtracer.init( scene, editor.viewportCamera ); + if ( pathtracer ) pathtracer.init( scene, editor.viewportCamera ); break; case 'solid': @@ -709,8 +732,10 @@ function Viewport( editor ) { updateAspectRatio(); + if ( renderer === null ) return; + renderer.setSize( container.dom.offsetWidth, container.dom.offsetHeight ); - pathtracer.setSize( container.dom.offsetWidth, container.dom.offsetHeight ); + if ( pathtracer ) pathtracer.setSize( container.dom.offsetWidth, container.dom.offsetHeight ); render(); @@ -831,7 +856,7 @@ function Viewport( editor ) { function initPT() { - if ( editor.viewportShading === 'realistic' ) { + if ( pathtracer && editor.viewportShading === 'realistic' ) { pathtracer.init( scene, editor.viewportCamera ); @@ -841,7 +866,7 @@ function Viewport( editor ) { function updatePTBackground() { - if ( editor.viewportShading === 'realistic' ) { + if ( pathtracer && editor.viewportShading === 'realistic' ) { pathtracer.setBackground( scene.background, scene.backgroundBlurriness ); @@ -851,7 +876,7 @@ function Viewport( editor ) { function updatePTEnvironment() { - if ( editor.viewportShading === 'realistic' ) { + if ( pathtracer && editor.viewportShading === 'realistic' ) { pathtracer.setEnvironment( scene.environment ); @@ -861,7 +886,7 @@ function Viewport( editor ) { function updatePTMaterials() { - if ( editor.viewportShading === 'realistic' ) { + if ( pathtracer && editor.viewportShading === 'realistic' ) { pathtracer.updateMaterials(); @@ -871,7 +896,7 @@ function Viewport( editor ) { function updatePT() { - if ( editor.viewportShading === 'realistic' ) { + if ( pathtracer && editor.viewportShading === 'realistic' ) { pathtracer.update(); editor.signals.pathTracerUpdated.dispatch( pathtracer.getSamples() ); @@ -887,6 +912,8 @@ function Viewport( editor ) { function render() { + if ( renderer === null ) return; + startTime = performance.now(); renderer.setViewport( 0, 0, container.dom.offsetWidth, container.dom.offsetHeight ); diff --git a/editor/js/libs/app.js b/editor/js/libs/app.js index 4c861d266d79d4..781b5c87819aab 100644 --- a/editor/js/libs/app.js +++ b/editor/js/libs/app.js @@ -2,8 +2,7 @@ const APP = { Player: function () { - const renderer = new THREE.WebGLRenderer( { antialias: true, logarithmicDepthBuffer: true } ); - renderer.setPixelRatio( window.devicePixelRatio ); // TODO: Use player.setPixelRatio() + let renderer; const loader = new THREE.ObjectLoader(); let camera, scene; @@ -11,23 +10,46 @@ const APP = { let events = {}; const dom = document.createElement( 'div' ); - dom.appendChild( renderer.domElement ); this.dom = dom; - this.canvas = renderer.domElement; this.width = 500; this.height = 500; - this.load = function ( json ) { + this.load = async function ( json ) { const project = json.project; + // Create renderer based on project settings + + if ( renderer !== undefined ) { + + renderer.dispose(); + dom.removeChild( renderer.domElement ); + + } + + if ( project.renderer === 'WebGPURenderer' ) { + + const { WebGPURenderer } = await import( 'three/webgpu' ); + renderer = new WebGPURenderer( { antialias: true, logarithmicDepthBuffer: true } ); + await renderer.init(); + + } else { + + renderer = new THREE.WebGLRenderer( { antialias: true, logarithmicDepthBuffer: true } ); + + } + + renderer.setPixelRatio( window.devicePixelRatio ); + if ( project.shadows !== undefined ) renderer.shadowMap.enabled = project.shadows; if ( project.shadowType !== undefined ) renderer.shadowMap.type = project.shadowType; if ( project.toneMapping !== undefined ) renderer.toneMapping = project.toneMapping; if ( project.toneMappingExposure !== undefined ) renderer.toneMappingExposure = project.toneMappingExposure; + dom.appendChild( renderer.domElement ); + this.setScene( loader.parse( json.scene ) ); this.setCamera( loader.parse( json.camera ) ); @@ -129,7 +151,11 @@ const APP = { } - renderer.setSize( width, height ); + if ( renderer ) { + + renderer.setSize( width, height ); + + } }; @@ -205,7 +231,11 @@ const APP = { this.dispose = function () { - renderer.dispose(); + if ( renderer ) { + + renderer.dispose(); + + } camera = undefined; scene = undefined; diff --git a/editor/js/libs/app/index.html b/editor/js/libs/app/index.html index 5574deab0babcd..e8da53802e31ac 100644 --- a/editor/js/libs/app/index.html +++ b/editor/js/libs/app/index.html @@ -21,7 +21,8 @@ @@ -33,10 +34,10 @@ window.THREE = THREE; // Used by APP Scripts. var loader = new THREE.FileLoader(); - loader.load( 'app.json', function ( text ) { + loader.load( 'app.json', async function ( text ) { var player = new APP.Player(); - player.load( JSON.parse( text ) ); + await player.load( JSON.parse( text ) ); player.setSize( window.innerWidth, window.innerHeight ); player.play(); diff --git a/examples/jsm/helpers/ViewHelper.js b/examples/jsm/helpers/ViewHelper.js index 653f342006aa82..ef8b89482b3800 100644 --- a/examples/jsm/helpers/ViewHelper.js +++ b/examples/jsm/helpers/ViewHelper.js @@ -65,6 +65,20 @@ class ViewHelper extends Object3D { */ this.center = new Vector3(); + /** + * Controls the position of the helper in the viewport. + * Use `top`/`bottom` for vertical positioning and `left`/`right` for horizontal. + * If `left` is `null`, `right` is used. If `top` is `null`, `bottom` is used. + * + * @type {{top: number|null, right: number, bottom: number, left: number|null}} + */ + this.location = { + top: null, + right: 0, + bottom: 0, + left: null + }; + const color1 = new Color( '#ff4466' ); const color2 = new Color( '#88ff44' ); const color3 = new Color( '#4488ff' ); @@ -142,8 +156,8 @@ class ViewHelper extends Object3D { const turnRate = 2 * Math.PI; // turn rate in angles per second /** - * Renders the helper in a separate view in the bottom-right corner - * of the viewport. + * Renders the helper in a separate view in the viewport. + * Position is controlled by the `location` property. * * @param {WebGLRenderer|WebGPURenderer} renderer - The renderer. */ @@ -157,8 +171,31 @@ class ViewHelper extends Object3D { // - const x = domElement.offsetWidth - dim; - const y = renderer.isWebGPURenderer ? domElement.offsetHeight - dim : 0; + const location = this.location; + + let x, y; + + if ( location.left !== null ) { + + x = location.left; + + } else { + + x = domElement.offsetWidth - dim - location.right; + + } + + if ( location.top !== null ) { + + // Position from top + y = renderer.isWebGPURenderer ? location.top : domElement.offsetHeight - dim - location.top; + + } else { + + // Position from bottom + y = renderer.isWebGPURenderer ? domElement.offsetHeight - dim - location.bottom : location.bottom; + + } renderer.clearDepth(); @@ -191,10 +228,32 @@ class ViewHelper extends Object3D { if ( this.animating === true ) return false; const rect = domElement.getBoundingClientRect(); - const offsetX = rect.left + ( domElement.offsetWidth - dim ); - const offsetY = rect.top + ( domElement.offsetHeight - dim ); - mouse.x = ( ( event.clientX - offsetX ) / ( rect.right - offsetX ) ) * 2 - 1; - mouse.y = - ( ( event.clientY - offsetY ) / ( rect.bottom - offsetY ) ) * 2 + 1; + const location = this.location; + + let offsetX, offsetY; + + if ( location.left !== null ) { + + offsetX = rect.left + location.left; + + } else { + + offsetX = rect.left + domElement.offsetWidth - dim - location.right; + + } + + if ( location.top !== null ) { + + offsetY = rect.top + location.top; + + } else { + + offsetY = rect.top + domElement.offsetHeight - dim - location.bottom; + + } + + mouse.x = ( ( event.clientX - offsetX ) / dim ) * 2 - 1; + mouse.y = - ( ( event.clientY - offsetY ) / dim ) * 2 + 1; raycaster.setFromCamera( mouse, orthoCamera );