Repository: bgstaal/multipleWindow3dScene Branch: main Commit: a99ecb8a865a Files: 6 Total size: 11.3 KB Directory structure: gitextract_56hlv5ma/ ├── .gitignore ├── README.md ├── WindowManager.js ├── index.html ├── main.js └── three-LICENSE ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ node_modules build deploy screens* npm-debug.log *.png .env .DS_Store ================================================ FILE: README.md ================================================ # Multiple Window 3D Scene using Three.js ## Introduction This project demonstrates a unique approach to creating and managing a 3D scene across multiple browser windows using Three.js and localStorage. It's designed for developers interested in advanced web graphics and window management techniques. ## Features - 3D scene creation and rendering with Three.js. - Synchronization of 3D scenes across multiple browser windows. - Dynamic window management and state synchronization using localStorage. ## Installation Clone the repository and open `index.html` in your browser to start exploring the 3D scene. ``` git clone https://github.com/bgstaal/multipleWindow3dScene ``` ## Usage The main application logic is contained within `main.js` and `WindowManager.js`. The 3D scene is rendered in `index.html`, which serves as the entry point of the application. ## Structure and Components - `index.html`: Entry point that sets up the HTML structure and includes the Three.js library and the main script. - `WindowManager.js`: Core class managing window creation, synchronization, and state management across multiple windows. - `main.js`: Contains the logic for initializing the 3D scene, handling window events, and rendering the scene. - `three.r124.min.js`: Minified version of the Three.js library used for 3D graphics rendering. ## Detailed Functionality - `WindowManager.js` handles the lifecycle of multiple browser windows, including creation, synchronization, and removal. It uses localStorage to maintain state across windows. - `main.js` initializes the 3D scene using Three.js, manages the window's resize events, and updates the scene based on window interactions. ## Contributing Contributions to enhance or expand the project are welcome. Feel free to fork the repository, make changes, and submit pull requests. ## License This project is open-sourced under the MIT License. ## Acknowledgments - The Three.js team for their comprehensive 3D library. - x.com/didntdrinkwater for this readme. ## Contact For more information and updates, follow [@_nonfigurativ_](https://twitter.com/_nonfigurativ_) on Twitter. ================================================ FILE: WindowManager.js ================================================ class WindowManager { #windows; #count; #id; #winData; #winShapeChangeCallback; #winChangeCallback; constructor () { let that = this; // event listener for when localStorage is changed from another window addEventListener("storage", (event) => { if (event.key == "windows") { let newWindows = JSON.parse(event.newValue); let winChange = that.#didWindowsChange(that.#windows, newWindows); that.#windows = newWindows; if (winChange) { if (that.#winChangeCallback) that.#winChangeCallback(); } } }); // event listener for when current window is about to ble closed window.addEventListener('beforeunload', function (e) { let index = that.getWindowIndexFromId(that.#id); //remove this window from the list and update local storage that.#windows.splice(index, 1); that.updateWindowsLocalStorage(); }); } // check if theres any changes to the window list #didWindowsChange (pWins, nWins) { if (pWins.length != nWins.length) { return true; } else { let c = false; for (let i = 0; i < pWins.length; i++) { if (pWins[i].id != nWins[i].id) c = true; } return c; } } // initiate current window (add metadata for custom data to store with each window instance) init (metaData) { this.#windows = JSON.parse(localStorage.getItem("windows")) || []; this.#count= localStorage.getItem("count") || 0; this.#count++; this.#id = this.#count; let shape = this.getWinShape(); this.#winData = {id: this.#id, shape: shape, metaData: metaData}; this.#windows.push(this.#winData); localStorage.setItem("count", this.#count); this.updateWindowsLocalStorage(); } getWinShape () { let shape = {x: window.screenLeft, y: window.screenTop, w: window.innerWidth, h: window.innerHeight}; return shape; } getWindowIndexFromId (id) { let index = -1; for (let i = 0; i < this.#windows.length; i++) { if (this.#windows[i].id == id) index = i; } return index; } updateWindowsLocalStorage () { localStorage.setItem("windows", JSON.stringify(this.#windows)); } update () { //console.log(step); let winShape = this.getWinShape(); //console.log(winShape.x, winShape.y); if (winShape.x != this.#winData.shape.x || winShape.y != this.#winData.shape.y || winShape.w != this.#winData.shape.w || winShape.h != this.#winData.shape.h) { this.#winData.shape = winShape; let index = this.getWindowIndexFromId(this.#id); this.#windows[index].shape = winShape; //console.log(windows); if (this.#winShapeChangeCallback) this.#winShapeChangeCallback(); this.updateWindowsLocalStorage(); } } setWinShapeChangeCallback (callback) { this.#winShapeChangeCallback = callback; } setWinChangeCallback (callback) { this.#winChangeCallback = callback; } getWindows () { return this.#windows; } getThisWindowData () { return this.#winData; } getThisWindowID () { return this.#id; } } export default WindowManager; ================================================ FILE: index.html ================================================ 3d example using three.js and multiple windows ================================================ FILE: main.js ================================================ import WindowManager from './WindowManager.js' const t = THREE; let camera, scene, renderer, world; let near, far; let pixR = window.devicePixelRatio ? window.devicePixelRatio : 1; let cubes = []; let sceneOffsetTarget = {x: 0, y: 0}; let sceneOffset = {x: 0, y: 0}; let today = new Date(); today.setHours(0); today.setMinutes(0); today.setSeconds(0); today.setMilliseconds(0); today = today.getTime(); let internalTime = getTime(); let windowManager; let initialized = false; // get time in seconds since beginning of the day (so that all windows use the same time) function getTime () { return (new Date().getTime() - today) / 1000.0; } if (new URLSearchParams(window.location.search).get("clear")) { localStorage.clear(); } else { // this code is essential to circumvent that some browsers preload the content of some pages before you actually hit the url document.addEventListener("visibilitychange", () => { if (document.visibilityState != 'hidden' && !initialized) { init(); } }); window.onload = () => { if (document.visibilityState != 'hidden') { init(); } }; function init () { initialized = true; // add a short timeout because window.offsetX reports wrong values before a short period setTimeout(() => { setupScene(); setupWindowManager(); resize(); updateWindowShape(false); render(); window.addEventListener('resize', resize); }, 500) } function setupScene () { camera = new t.OrthographicCamera(0, 0, window.innerWidth, window.innerHeight, -10000, 10000); camera.position.z = 2.5; near = camera.position.z - .5; far = camera.position.z + 0.5; scene = new t.Scene(); scene.background = new t.Color(0.0); scene.add( camera ); renderer = new t.WebGLRenderer({antialias: true, depthBuffer: true}); renderer.setPixelRatio(pixR); world = new t.Object3D(); scene.add(world); renderer.domElement.setAttribute("id", "scene"); document.body.appendChild( renderer.domElement ); } function setupWindowManager () { windowManager = new WindowManager(); windowManager.setWinShapeChangeCallback(updateWindowShape); windowManager.setWinChangeCallback(windowsUpdated); // here you can add your custom metadata to each windows instance let metaData = {foo: "bar"}; // this will init the windowmanager and add this window to the centralised pool of windows windowManager.init(metaData); // call update windows initially (it will later be called by the win change callback) windowsUpdated(); } function windowsUpdated () { updateNumberOfCubes(); } function updateNumberOfCubes () { let wins = windowManager.getWindows(); // remove all cubes cubes.forEach((c) => { world.remove(c); }) cubes = []; // add new cubes based on the current window setup for (let i = 0; i < wins.length; i++) { let win = wins[i]; let c = new t.Color(); c.setHSL(i * .1, 1.0, .5); let s = 100 + i * 50; let cube = new t.Mesh(new t.BoxGeometry(s, s, s), new t.MeshBasicMaterial({color: c , wireframe: true})); cube.position.x = win.shape.x + (win.shape.w * .5); cube.position.y = win.shape.y + (win.shape.h * .5); world.add(cube); cubes.push(cube); } } function updateWindowShape (easing = true) { // storing the actual offset in a proxy that we update against in the render function sceneOffsetTarget = {x: -window.screenX, y: -window.screenY}; if (!easing) sceneOffset = sceneOffsetTarget; } function render () { let t = getTime(); windowManager.update(); // calculate the new position based on the delta between current offset and new offset times a falloff value (to create the nice smoothing effect) let falloff = .05; sceneOffset.x = sceneOffset.x + ((sceneOffsetTarget.x - sceneOffset.x) * falloff); sceneOffset.y = sceneOffset.y + ((sceneOffsetTarget.y - sceneOffset.y) * falloff); // set the world position to the offset world.position.x = sceneOffset.x; world.position.y = sceneOffset.y; let wins = windowManager.getWindows(); // loop through all our cubes and update their positions based on current window positions for (let i = 0; i < cubes.length; i++) { let cube = cubes[i]; let win = wins[i]; let _t = t;// + i * .2; let posTarget = {x: win.shape.x + (win.shape.w * .5), y: win.shape.y + (win.shape.h * .5)} cube.position.x = cube.position.x + (posTarget.x - cube.position.x) * falloff; cube.position.y = cube.position.y + (posTarget.y - cube.position.y) * falloff; cube.rotation.x = _t * .5; cube.rotation.y = _t * .3; }; renderer.render(scene, camera); requestAnimationFrame(render); } // resize the renderer to fit the window size function resize () { let width = window.innerWidth; let height = window.innerHeight camera = new t.OrthographicCamera(0, width, 0, height, -10000, 10000); camera.updateProjectionMatrix(); renderer.setSize( width, height ); } } ================================================ FILE: three-LICENSE ================================================ The MIT License Copyright © 2010-2023 three.js authors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.