[
  {
    "path": ".gitignore",
    "content": "dist\npackage-lock.json\n\n# Created by .ignore support plugin (hsz.mobi)\n### Node template\n# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\n\n# Runtime data\npids\n*.pid\n*.seed\n*.pid.lock\n\n# Directory for instrumented libs generated by jscoverage/JSCover\nlib-cov\n\n# Coverage directory used by tools like istanbul\ncoverage\n\n# nyc test coverage\n.nyc_output\n\n# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)\n.grunt\n\n# Bower dependency directory (https://bower.io/)\nbower_components\n\n# node-waf configuration\n.lock-wscript\n\n# Compiled binary addons (https://nodejs.org/api/addons.html)\nbuild/Release\n\n# Dependency directories\nnode_modules/\njspm_packages/\n\n# TypeScript v1 declaration files\ntypings/\n\n# Optional npm cache directory\n.npm\n\n# Optional eslint cache\n.eslintcache\n\n# Optional REPL history\n.node_repl_history\n\n# Output of 'npm pack'\n*.tgz\n\n# Yarn Integrity file\n.yarn-integrity\n\n# dotenv environment variables file\n.env\n\n# next.js build output\n.next\n\n"
  },
  {
    "path": ".idea/.gitignore",
    "content": "# Default ignored files\n/workspace.xml"
  },
  {
    "path": ".idea/misc.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"JavaScriptSettings\">\n    <option name=\"languageLevel\" value=\"ES6\" />\n  </component>\n</project>"
  },
  {
    "path": ".idea/modules.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"ProjectModuleManager\">\n    <modules>\n      <module fileurl=\"file://$PROJECT_DIR$/.idea/voxeljs-next.iml\" filepath=\"$PROJECT_DIR$/.idea/voxeljs-next.iml\" />\n    </modules>\n  </component>\n</project>"
  },
  {
    "path": ".idea/vcs.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"VcsDirectoryMappings\">\n    <mapping directory=\"$PROJECT_DIR$\" vcs=\"Git\" />\n  </component>\n</project>"
  },
  {
    "path": ".idea/voxeljs-next.iml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<module type=\"WEB_MODULE\" version=\"4\">\n  <component name=\"NewModuleRootManager\">\n    <content url=\"file://$MODULE_DIR$\">\n      <excludeFolder url=\"file://$MODULE_DIR$/.tmp\" />\n      <excludeFolder url=\"file://$MODULE_DIR$/temp\" />\n      <excludeFolder url=\"file://$MODULE_DIR$/tmp\" />\n    </content>\n    <orderEntry type=\"inheritedJdk\" />\n    <orderEntry type=\"sourceFolder\" forTests=\"false\" />\n  </component>\n</module>"
  },
  {
    "path": "LICENSE",
    "content": "BSD 3-Clause License\n\nCopyright (c) 2019, Josh Marinacci\nAll rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are met:\n\n* Redistributions of source code must retain the above copyright notice, this\n  list of conditions and the following disclaimer.\n\n* Redistributions in binary form must reproduce the above copyright notice,\n  this list of conditions and the following disclaimer in the documentation\n  and/or other materials provided with the distribution.\n\n* Neither the name of the copyright holder nor the names of its\n  contributors may be used to endorse or promote products derived from\n  this software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"\nAND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\nIMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\nDISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE\nFOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\nDAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR\nSERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER\nCAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,\nOR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n"
  },
  {
    "path": "README.md",
    "content": "# voxeljs-next\nThe next generation of Voxel JS.\n\n# Play with it\n\nTry out a [live demo here](https://vr.josh.earth/voxeljs-next/examples/ecsy.html).  This demo shows:\n* create a flat world\n* move with keyboard \n* add and remove blocks w/ mouse\n\n# What is it\n\nVoxelJS is a voxel engine for games, similar to Minecraft.  It provides the ability to draw voxels on the screen,\ndefine the landscape with a function, load up textures, and navigate around the world in desktop mode and VR,\nand place/remove blocks.  VoxelJS uses the voxel code from the [original Voxel.js project](http://www.voxeljs.com/), updated to the latest\nThreeJS, adds WebXR support, and uses the new entity component system [ECSY](https://ecsy.io/);\n\n\nNotably VoxelJS does *not* provide any sort of server component, multi-player support, or scripting. To create\ninteractive effects like a TNT block you would need to write that code yourself (examples coming soon)..\n\n# How to use it\n\nFirst install the dependencies using npm\n\n```shell script\nnpm install\nnpm start\nopen http://localhost:8080\n```\n\n# old stuff\n\n\nThis project updates the original [voxeljs](https://voxeljs.com/) with modern Javascript (classes, ES6 modules, arrow\nfunctions, etc), and brings it in line with the latest [ThreeJS](https://threejs.org/), with support for the latest\nWebGL features, and also adds VR/AR support.  \n\nBecause of these improvements, *voxeljs-next is not compatible with the original VoxelJS modules*. All old modules and\nfeatures will need to be ported to the new code.\n\n# Dependencies\n\nVoxelJS is built on [ThreeJS](https://threejs.org/) and the WebGL & WebXR standards.\nWith the right options enabled it will run on desktop in embedded mode, full screen\nwith pointer lock, on a touch screen (phone, tablet), and in any VR headsets with a\nbrowser.\n\n\n# How to use VoxelJS\n\nStart by copying and modifying the main [example](examples/simple.html) application.\n\n## Add new textures\n\nTextures are loaded by the TextureManager. Add new images to the `examples/textures`\ndirectory then add their file names (minus the .png extension) to the `load` call\nlike this:\n\n```javascript\napp.textureManager.load(['grass','brick','dirt','anim','tnt','arrows'])\n```\n\n## create a custom world with code\n\nVoxelJS does not specify any on disk format for maps. It is up to you to provide that, though you can\nlook at the PersistenceManager example to see how it could work.  The only thing you need to provide\nis to provide a function to the ChunkManager which accepts a low and high dimension and coordinates.\nThis function should generate a new info bject with the data for that chunk.\n\nIf you already have a function which returns the block number at a particular spot in the chunk then you can use\nthe utils.generateChunkInfoFromFunction to build the chunk info.  Here's a simple example that creates\na completely flat world 10 blocks thick.\n\n\n```javascript\nconst flat = (i,j,k) => {\n    //the floor is brick, from depth 0 to -10\n    if(j < 1 && j > -10) return 2\n    //nothing else in the world\n    return 0\n}\n\napp.chunkManager = new ChunkManager({\n    generateVoxelChunk: (low, high, cx,cy,cz) => {\n        const id = [cx,cy,cz].join('|')\n        if(app.CHUNK_CACHE[id]) return app.CHUNK_CACHE[id]\n        return generateChunkInfoFromFunction(low, high, flat)\n    },\n});\n```\n\n## entities\n\nVoxel only tracks movementt of the player. To add other autonomus entityes like enemies and\nfriendlies create a new ThreeJS group for tthem in your scene then add the ThreeJS\nobjects. To make the entity interact with blocks and other objects you'll need to use\na `PhysHandler`. See the pig example for details\n\n\n## Networking\n\nVoxelJS does not provide voice chat or networked play out of the box, but you can look at\nthe example code for an example of using WebRTC for voice chat and PubNub for tracking\nplayer movement and terrain changes.\n\n\n# Help and Contributing\n\nThe general algorithm for rendering voxel data and how our implementation works is documented in [this](https://blog.mozvr.com/voxeljs-chunking-magic/) blog.\n\nFor help try asking in the `#voxel` channel in the ThreeJS slack.\n\nIf you'd like to contribute take a look at the issues. There are a ton of features\nthat need implementing including\n\n* make cheap ambient occulsion work when the greedy mesh is turned on\n* an api to set multiple chunks at once\n* better example of networked play\n* more demos\n* alternative rending. smaller cubes, cool textures, weird effects\n* Fix full screen in iOS and Mac safari\n* Touch screen dragging\n* Can’t choose block type in iPad\n* implement water: [How Water Works In DwarfCorp](https://www.gamasutra.com/blogs/MattKlingensmith/20130811/198050/How_Water_Works_In_DwarfCorp.php)\n* API to set multiple blocks at once. Batches help network as well.\n* other modules should Never know about chunks. Just get and set blocks.\n* level of detail: [A level of detail method for blocky voxels | 0 FPS](https://0fps.net/2018/03/03/a-level-of-detail-method-for-blocky-voxels/)\n* fix AO for greedy mesher. Explain the problem.\n\nThese particular issues are newbie friendly.\n\n# TBD\n\n* explain how meshing and chunking works. the core algorithm\n* explain how rendering works. esp texture mapping.\n"
  },
  {
    "path": "examples/css/dashboard.css",
    "content": "\n/* dashboard.css */\ndiv.dom-dashboard {\n    border: 5px solid black;\n    z-index: 10;\n    position: fixed;\n    top: 10vh;\n    left: 10vw;\n    right: 10vw;\n    bottom: 10vh;\n    background: white;\n    opacity: 0.8;\n    display: none;\n}\ndiv.dom-dashboard img {\n    width: 10vw;\n    image-rendering: crisp-edges;\n    border: 5px solid black;\n    padding: 5px;\n    margin: 5px;\n    background-color: white;\n}\ndiv.dom-dashboard.visible {\n    display: block;\n}\ndiv.dom-dashboard button {\n    position: absolute;\n    bottom:0;\n    right:0;\n    font-size: 200%;\n}\n"
  },
  {
    "path": "examples/css/fullscreen.css",
    "content": "button.fullscreen {\n    border: 5px solid red;\n    position: fixed;\n    left: 10vw;\n    top: 10vh;\n    font-size: 200%;\n}\n"
  },
  {
    "path": "examples/css/index.css",
    "content": "\nhtml, body {\n  padding: 0;\n  margin:0;\n  overflow: hidden;\n}"
  },
  {
    "path": "examples/css/main.css",
    "content": "body {\n    max-width: 40em;\n    margin: auto;\n}\n#container {\n    border: 3px solid black;\n    width: 700px;\n    height: 400px;\n}\n#fullscreen, #entervr {\n    display: none;\n}\n\n#container {\n    position: relative;\n}\n#touch-overlay {\n    border: 0px solid black;\n    position: absolute;\n    bottom: 0;\n    top:0;\n    left:0;\n    right:0;\n    display: none;\n    visibility: hidden;\n}\n#touch-overlay button {\n    position: absolute;\n    width: 15vmin;\n    height: 15vmin;\n    padding:0;\n    border: 1px solid black;\n}\n#touch-overlay #left {\n    bottom: 7.5vmin;\n    left: 0vmin;\n}\n#touch-overlay #right {\n    bottom: 7.5vmin;\n    left: 30vmin;\n}\n#touch-overlay #up {\n    bottom: 15vmin;\n    left: 15vmin;\n}\n#touch-overlay #down {\n    bottom: 0em;\n    left: 15vmin;\n}\n#touch-overlay #exit-fullscreen {\n    position: absolute;\n    top: 0vmin;\n    right: 0vmin;\n}\n#touch-overlay #menu-button {\n    position: absolute;\n    bottom: 0vmin;\n    right: 0em;\n}\n#touch-overlay #jump-button {\n    position: absolute;\n    bottom: 15vmin;\n    right: 0em;\n}\n\nbody.fullscreen #container {\n    border-width:0;\n    position: absolute;\n    top:0;\n    left:0;\n    width:100%;\n    height:100%;\n}\n\n\nbutton {\n    border: 1px solid black;\n    border-radius: 0.25em;\n    background-color: aqua;\n}\nbutton.selected {\n    background-color: darkblue;\n    color:white;\n}\n\n"
  },
  {
    "path": "examples/css/webxr.css",
    "content": "button.webxr {\n    border: 5px solid red;\n    position: fixed;\n    left: 30vw;\n    top: 10vh;\n    font-size: 200%;\n}\n"
  },
  {
    "path": "examples/package.json",
    "content": "{\n  \"name\": \"examples\",\n  \"version\": \"1.0.0\",\n  \"main\": \"index.js\",\n  \"private\": true,\n  \"dependencies\": {\n    \"voxeljs-next\": \"..\"\n  }\n}\n"
  },
  {
    "path": "examples/public/ecsy.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"UTF-8\">\n    <title>Title</title>\n    <link rel=\"stylesheet\" href=\"../src/ecsy/fullscreen.css\">\n    <link rel=\"stylesheet\" href=\"../src/ecsy/webxr.css\">\n    <link rel=\"stylesheet\" href=\"../src/ecsy/dashboard.css\">\n    <style type=\"text/css\">\n        html, body {\n            padding: 0;\n            margin:0;\n            overflow: hidden;\n        }\n    </style>\n</head>\n<body>\n\n<script type=\"module\">\n\n</script>\n\n</body>\n</html>\n"
  },
  {
    "path": "examples/public/index.html",
    "content": "\n<!DOCTYPE html>\n<html>\n  <head>\n    <meta charset=\"utf-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n  </head>\n  <body>\n\n  </body>\n</html>\n"
  },
  {
    "path": "examples/public/networked.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"UTF-8\">\n    <title>Mine Kitten</title>\n    <script>\n        /*\n        (function(f, a, t, h, o, m){\n            a[h]=a[h]||function(){\n                (a[h].q=a[h].q||[]).push(arguments)\n            };\n            o=f.createElement('script'),\n                m=f.getElementsByTagName('script')[0];\n            o.async=1; o.src=t; o.id='fathom-script';\n            m.parentNode.insertBefore(o,m)\n        })(document, window, '//stats.josh.earth/tracker.js', 'fathom');\n        fathom('set', 'siteId', 'GISNV');\n        fathom('trackPageview');\n        */\n    </script>\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, user-scalable=no\">\n    <meta name=\"apple-mobile-web-app-capable\" content=\"yes\">\n    <link rel=\"stylesheet\" href=\"css/main.css\">\n</head>\n<body>\n\n<h1>VoxelJS for VR Test</h1>\n<p>\n    Press <b>play full screen</b> in desktop mode. Press <b>play in vr</b> to play in VR mode (if available).\n    Or just play in regular windowed mode. Whatever you like.\n</p>\n\n<div>\n    <div id=\"progress\">\n        <label>loading</label>\n        <progress id=\"progress-bar\" value=\"0.5\"></progress>\n    </div>\n    <button id=\"fullscreen\">play full screen</button>\n    <button id=\"entervr\">play in vr</button>\n    <div class=\"button-bar\">\n        <button id=\"save\">save</button>\n        <button id=\"reload\">reload</button>\n    </div>\n    <div class=\"button-bar\">\n        <button id=\"use-greedy-mesher\" class=\"selected mesher\">greedy mesher</button>\n        <button id=\"use-culled-mesher\" class=\"mesher\">culled mesher</button>\n        <button id=\"toggle-texture\" class=\"selected\">textures</button>\n        <button id=\"toggle-ao\" class=\"selected\">shadows</button>\n        <button id=\"connect-voice\">voice</button>\n        <button id=\"share\">shared playing</button>\n    </div>\n</div>\n<div id=\"container\">\n    <div id=\"touch-overlay\">\n        <button id=\"left\">left</button>\n        <button id=\"right\">right</button>\n        <button id=\"up\">up</button>\n        <button id=\"down\">down</button>\n        <button id=\"exit-fullscreen\">escape</button>\n        <button id=\"menu-button\">menu</button>\n        <button id=\"jump-button\">jump</button>\n    </div>\n</div>\n\n\n<ul>\n    <li>full screen: mouse to move camera, arrows and WASD to move</li>\n    <li>window mode: mouse to click on things, arrows to turn, WASD to move</li>\n    <li>VR mode: <ul>\n        <li>trigger to click</li>\n        <li>hold 'up' butotn to move in direction of pointer</li>\n        <li>swipe left and right to rotate camera</li>\n        <li>click left to toggle create vs destroy</li>\n        <li>click right to open block settings</li>\n    </ul>\n    <li>voice: turn on voice to talk to others playing the game</li>\n    <li>toggle active vs creative mode with the 'c' key</li>\n</ul>\n\n<script src=\"../node_modules/webrtc-sdk/js/webrtc-v2.js\"></script>\n<script src=\"../node_modules/pubnub/dist/web/pubnub.js\"></script>\n<script type=\"module\">\n    import {TextureManager} from '../src/TextureManager.js'\n    import {WebRTCAudioChat} from './js/WebRTCAudioChat.js'\n    import {RemotePlayersProxy} from './js/RemotePlayersProxy.js'\n\n    const $ = (sel) => document.querySelector(sel)\n    const $$ = (sel) => document.querySelectorAll(sel)\n    const on = (elem, type, cb) => elem.addEventListener(type,cb)\n    const rand = (min,max) => Math.random()*(max-min) + min\n\n    const app = new WebXRBoilerPlate({\n        container: $(\"#container\")\n    })\n    app.init().then((app) => {\n        app.scene.background = new Color( 0xcccccc );\n        app.comps = []\n        app.textureManager = new TextureManager()\n        app.comps.push(app.textureManager)\n\n\n\n        app.player = new Mesh()\n        app.player.position.y = 30\n        app.player_phys = new PhysHandler(app, app.player,[new SimpleMeshCollider(app)])\n        app.comps.push(player_phys)\n\n\n        // ========= event handlers\n\n        //Enable VR button\n        //when VR support is detected\n        on(app,'detected',()=>{\n            // show the enter VR button\n            $(\"#entervr\").style.display = 'block'\n            on($(\"#entervr\"),'click',()=> {\n                app.desktopControls.disable()\n                app.vrControls.enable()\n                app.enterVR()\n            })\n        })\n\n\n        // WebRTC Voice connection\n        app.webrtcAudio = new WebRTCAudioChat(app)\n        on($(\"#connect-voice\"),'click',() => {\n            if(app.webrtcAudio.connected) {\n                $(\"#connect-voice\").classList.remove('selected')\n                app.webrtcAudio.disconnect()\n            } else {\n                $(\"#connect-voice\").classList.add('selected')\n                app.webrtcAudio.connect()\n            }\n        })\n\n\n        // ============= Network Play ================\n        app.networkPlay = new PubnubNetworkplay()\n        const shared_button = $(\"#share\")\n        on(shared_button,'click',()=>{\n            app.networkPlay.connect()\n            if(app.networkPlay.isConnected() || app.networkPlay.isConnecting()) {\n                shared_button.classList.add('selected')\n            } else {\n                shared_button.classList.remove('selected')\n            }\n            app.rebuildAllMeshes()\n        })\n\n        app.remotePlayers = new RemotePlayersProxy(app)\n        on(app.player_phys,\"move\",()=>{\n            if(app.networkPlay) app.networkPlay.playerMoved(app.player_phys)\n        })\n        on(app.networkPlay,'remote-player-moved',(msg)=>{\n            console.log(msg.publisher,'moved to',msg.message.position)\n            app.remotePlayers.remotePlayerMoved(msg.publisher,msg.message.position)\n        })\n        on(app.networkPlay,'remote-player-voxels',(msg) => {\n            console.log(msg.publisher,'changed voxels',msg.message.voxels)\n            msg.message.voxels.forEach(voxel => {\n                const pos = new Vector3(voxel.position.x,voxel.position.y,voxel.position.z)\n                app.chunkManager.setVoxelAtCoordinates(pos, voxel.type)\n                const chunkIndex = app.chunkManager.chunkIndexAtCoordinates(pos.x,pos.y,pos.z)\n                const chunk = app.chunkManager.chunks[chunkIndex.join(\"|\")]\n                if(chunk) rebuildMesh(chunk)\n            })\n        })\n\n        return app.textureManager.load(['grass','brick','dirt','anim','tnt','arrows'])\n    }).then(()=>{\n        app.rebuildAllMeshes()\n    }).catch(e => {\n        console.log('ERROR',e)\n    })\n\n\n</script>\n\n</body>\n</html>"
  },
  {
    "path": "examples/simple.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"UTF-8\">\n    <title>Mine Kitten</title>\n    <script>\n        /*\n        (function(f, a, t, h, o, m){\n            a[h]=a[h]||function(){\n                (a[h].q=a[h].q||[]).push(arguments)\n            };\n            o=f.createElement('script'),\n                m=f.getElementsByTagName('script')[0];\n            o.async=1; o.src=t; o.id='fathom-script';\n\n\n\n\n\n\n            m.parentNode.insertBefore(o,m)\n        })(document, window, '//stats.josh.earth/tracker.js', 'fathom');\n        fathom('set', 'siteId', 'GISNV');\n        fathom('trackPageview');\n        */\n    </script>\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, user-scalable=no\">\n    <meta name=\"apple-mobile-web-app-capable\" content=\"yes\">\n    <link rel=\"stylesheet\" href=\"css/main.css\">\n    <style>\n        #texture {\n            border: 0 solid green;\n            image-rendering: -moz-crisp-edges;\n        }\n    </style>\n</head>\n<body>\n\n<h1>VoxelJS for VR Test</h1>\n<p>\n    Press <b>play full screen</b> in desktop mode. Press <b>play in vr</b> to play in VR mode (if available).\n    Or just play in regular windowed mode. Whatever you like.\n</p>\n\n<p>Textures CC0 licensed from <a href=\"https://www.kenney.nl/assets/voxel-pack\">Kenney.nl</a>\n</p>\n\n<div>\n    <div id=\"progress\">\n        <label>loading</label>\n        <progress id=\"progress-bar\" value=\"0.5\"></progress>\n    </div>\n    <button id=\"fullscreen\">play full screen</button>\n    <button id=\"entervr\">play in vr</button>\n</div>\n<div id=\"container\">\n    <div id=\"touch-overlay\">\n        <button id=\"left\">left</button>\n        <button id=\"right\">right</button>\n        <button id=\"up\">up</button>\n        <button id=\"down\">down</button>\n        <button id=\"jump-button\">jump</button>\n    </div>\n</div>\n\n<script src=\"../node_modules/atlaspack/index.js\"></script>\n<script type=\"module\">\n\n    import WebXRBoilerPlate, {FULLSCREEN_ENTERED, FULLSCREEN_EXITED} from \"../node_modules/webxr-boilerplate/WebXRBoilerPlate.js\"\n    import {Mesh, MeshLambertMaterial, PlaneGeometry,\n        CubeGeometry,\n        Color, DirectionalLight, AmbientLight, Vector3, TextureLoader, Group} from \"../node_modules/three/build/three.module.js\"\n    import {TextureManager} from '../src/TextureManager.js'\n    import {PhysHandler} from '../src/PhysHandler.js'\n    import {SimpleMeshCollider} from \"../src/SimpleMeshCollider.js\"\n    import {ChunkManager} from \"../src/ChunkManager.js\"\n    import {GreedyMesher} from \"../src/GreedyMesher.js\"\n    import {CulledMesher} from \"../src/CulledMesher.js\"\n    import {BlockPicker} from './js/BlockPicker.js'\n    import {VRControls} from \"../src/VRControls.js\"\n    import {DesktopControls} from '../src/DesktopControls.js'\n    import {FullScreenControls} from '../src/FullscreenControls.js'\n    import {KeyboardControls} from '../src/KeyboardControls.js'\n    import {TouchControls} from '../src/TouchControls.js'\n    import {generateChunkInfoFromFunction, toRad} from '../src/utils.js'\n    import {VoxelMesh} from \"../src/VoxelMesh.js\"\n\n    //JQuery-like selectors\n    const $ = (sel) => document.querySelector(sel)\n    const on = (elem, type, cb) => elem.addEventListener(type,cb)\n\n\n    const app = new WebXRBoilerPlate({\n        container: $(\"#container\")\n    })\n\n    app.init().then((app) => {\n        // app.CHUNK_SIZE = 16\n        // app.RAYCAST_DISTANCE = 30\n        app.comps = []\n        app.scene.background = new Color( 0xcccccc );\n\n        app.aoEnabled = true\n\n        app.active = true\n\n        //force the ThreeJS loading framework to have at least one thing in the que\n        new TextureLoader().load('./dummy.jpg')\n\n\n        app.player = new Mesh()\n        app.player.position.y = 30\n        //physics for the player\n        app.player_phys = new PhysHandler(app, app.player,[new SimpleMeshCollider(app)])\n        app.comps.push(app.player_phys)\n        // app.player_phys.enable()\n\n\n\n        // function to generate a flat world\n        const flat = (i,j,k) => {\n            //an gap in the floor made of air\n            // if(j <1 && k < -5 && k > -10 ) return 0\n            //the floor is brick, from depth 0 to -10\n            if(j < 1 && j > -10) return 1\n\n            //move back 10\n            k+=20\n            // a dome\n            if((i*i + j*j + k*k) < 80) {\n                return 2\n            }\n            //nothing else in the world\n            return 0\n        }\n        const wide_block = (i,j,k) => {\n            if(i>=-4 && i<0 && j===1 && k===6) return 1\n            return 0\n        }\n\n        // ========== setup the chunk manager and cache\n        app.chunkManager = new ChunkManager({\n            chunkDistance:1,\n            blockSize:1,\n            mesher: new CulledMesher(),\n            chunkSize:16,\n            generateVoxelChunk: (low, high, pos) => {\n                const id = [pos.x,pos.y,pos.z].join('|')\n                return generateChunkInfoFromFunction(low, high, flat)\n            },\n            container: new Group(),\n            textureManager: new TextureManager({aoEnabled:true}),\n        },app);\n\n        app.comps.push(app.chunkManager.textureManager)\n        //start loading these textures\n        app.chunkManager.textureManager.loadTextures([\n            {\n                src:'./textures/kenneynl/tiles/grass_top.png'\n            },\n            {\n                src:'./textures/kenneynl/tiles/dirt.png'\n            },\n            // {\n            //     src:'./textures/kenneynl/tiles/ice.png',\n            // },\n            {\n                src:'./textures/kenneynl/tiles/lava.png',\n            },\n            {\n                src:'./textures/kenneynl/tiles/stone.png',\n            },\n            {\n                src:'./textures/kenneynl/tiles/sand.png',\n            },\n            {\n                src:'./textures/tnt.png',\n            },\n            {\n                src:'./textures/heart.png',\n            },\n            {\n                src:'./textures/tnt.png',\n            },\n        ]).then(()=>{\n            app.chunkManager.rebuildAllMeshes()\n            app.chunkManager.requestMissingChunks(new Vector3(0,0,0))\n            app.dialog.setSelectedToDefault()\n        })\n\n        // ======== setup chunk manipulation functions =======\n\n        //set the mesher to use\n        // app.mesher = new GreedyMesher()\n        // app.mesher = new CulledMesher()\n\n        // create a block with the currently selected block type\n        app.createBlock = (pos) => {\n            if(app.dialog.panel.visible) return\n            const name = app.dialog.selectedColor\n            if(name === 'tnt') {\n                // console.log(\"doing explosion carve out at\",pos)\n                pos.y -= 1\n                pos.x -= 1\n                pos.z -= 1\n                const dim = new Vector3(3,3,3)\n                const data = [] //dim.x*dim.y*dim.z\n                for(let y=0; y<dim.y; y++) {\n                    for(let z=0; z<dim.z; z++) {\n                        for(let x=0; x<dim.x; x++) {\n                            const n = x + z*dim.x + y*dim.x*dim.z\n                            data[n] = 0\n                        }\n                    }\n                }\n                app.chunkManager.setBlockRange(pos,dim,data)\n            } else {\n                const type = app.chunkManager.textureManager.getBlockTypeForName(name)\n                app.setBlock(pos, type)\n            }\n        }\n\n        // remove a block at the position\n        app.removeBlock = (pos) => {\n            if(app.dialog.panel.visible) return\n            pos.floor()\n            app.setBlock(pos, 0)\n        }\n\n        // actually set the block\n        app.setBlock = function(pos, value) {\n            pos.floor()\n            //set the actual value\n            this.chunkManager.setVoxelAtCoordinates(pos, value)\n        }\n\n\n        // ===== setup the camera groups =============\n        app.stageRot = new Group()\n        app.scene.add(app.stageRot)\n        app.stagePos = new Group()\n        app.stageRot.add(app.stagePos)\n        app.stagePos.position.z = -10\n        app.stagePos.position.y = -1.5\n        app.stagePos.position.x = 0\n\n        app.stagePos.add(app.chunkManager.container)\n\n\n        // ======== EVENT HANDLERS ==========\n\n        // ========= crosshairs for full screen mode =========\n        // TODO: move this into full screen mode\n        const crosshairs_geometry = new PlaneGeometry(0.1,0.01)\n        crosshairs_geometry.merge(new PlaneGeometry(0.01,0.1))\n        const crosshairs = new Mesh( crosshairs_geometry, new MeshLambertMaterial({color:'red'}))\n        crosshairs.position.set(0,1.6,-1.0)\n        app.scene.add(crosshairs)\n        crosshairs.visible = false\n\n\n\n\n        /// ============= Highlight Object ==========\n        app.highlight_cube = new Mesh(\n            new CubeGeometry(1.1,1.1,1.1, 4,4).translate(0.5,0.5,0.5),\n            new MeshLambertMaterial({color:'green',\n                depthTest:true,\n                wireframe:true,\n                wireframeLinewidth: 3,\n                transparent: true,\n                opacity: 0.5,\n            }))\n        app.highlight_cube.position.set(-2,0,-5)\n        app.stagePos.add(app.highlight_cube)\n        app.moveHighlight = (pos) => {\n            if(app.dialog.panel.visible) return\n            app.highlight_cube.position.copy(pos)\n        }\n\n        //a standard directional light from above\n        const light = new DirectionalLight( 0xffffff, 1.0 );\n        light.position.set( 0, 10, 5 ).normalize();\n        app.scene.add( light );\n\n        //a standard ambient light\n        app.scene.add(new AmbientLight(0xffffff,0.3))\n\n\n        // a dialog to choose a different block type\n        app.dialog = new BlockPicker(app)\n        app.dialog.panel.position.set(0,1.5,-1)\n        app.dialog.panel.visible = false\n        app.toggleDialog = () => {\n            app.dialog.rebuild()\n            app.dialog.panel.visible = !app.dialog.panel.visible\n        }\n\n        app.destroyBlockActive = true\n        app.togglePointer = () => {\n            app.destroyBlockActive = !app.destroyBlockActive\n            if(app.destroyBlockActive) {\n                app.highlight_cube.material.color.set(0xff0000)\n                if(!app.vrControls.pointer.controller1.line) {\n                    console.log('warning. no line on controller1')\n                } else {\n                    app.vrControls.pointer.controller1.line.material.color.set(0x880000)\n                }\n            } else {\n                app.highlight_cube.material.color.set(0x00ff00)\n                if(!app.vrControls.pointer.controller1.line) {\n                    console.log('warning. no line on controller1')\n                } else {\n                    app.vrControls.pointer.controller1.line.material.color.set(0x008800)\n                }\n            }\n        }\n\n\n        // ========= Setup VR Controls\n        app.vrControls = new VRControls(app)\n        app.comps.push(app.vrControls)\n        on(app.vrControls,'highlight',(e) => {\n            if(app.destroyBlockActive) {\n                app.moveHighlight(e.hitPosition)\n            } else {\n                e.hitPosition.add(e.hitNormal)\n                app.moveHighlight(e.hitPosition)\n            }\n        })\n        on(app.vrControls,'trigger',(a) => {\n            if(app.destroyBlockActive) {\n                app.removeBlock(a.hitPosition)\n            } else {\n                a.hitPosition.add(a.hitNormal)\n                app.createBlock(a.hitPosition)\n            }\n        })\n        on(app.vrControls,'toggle-pointer',app.togglePointer)\n        on(app.vrControls,'show-dialog',app.toggleDialog)\n\n\n        // =========== setup the desktop controls ===========\n        app.desktopControls = new DesktopControls(app,30)\n        app.comps.push(app.desktopControls)\n        on(app.desktopControls,'highlight',app.moveHighlight)\n        on(app.desktopControls,'setblock',app.createBlock)\n        on(app.desktopControls,'removeblock',app.removeBlock)\n        app.desktopControls.enable()\n\n        // ================ setup the full screen controls\n        app.fullscreenControls = new FullScreenControls(app)\n        app.comps.push(app.fullscreenControls)\n        on(app.fullscreenControls,'highlight', app.moveHighlight)\n        on(app.fullscreenControls,'setblock', app.createBlock)\n        on(app.fullscreenControls,'removeblock',app.removeBlock)\n\n        // on click, if fullscreen support, then really enter fullscreen, else fake it with CSS\n        on($(\"#fullscreen\"),'click',()=> {\n            if(app.isFullscreenSupported()) {\n                crosshairs.visible = true\n                app.desktopControls.disable()\n                app.playFullscreen()\n            } else {\n                $(\"body\").classList.add('fullscreen')\n                app.resizeOnNextRepaint = true\n            }\n        })\n\n        on(app,FULLSCREEN_ENTERED,() => {\n            $(\"#touch-overlay\").style.visibility = 'visible'\n            console.log(\"really entered fullscreen\")\n            $(\"body\").classList.add('fullscreen')\n            app.fullscreenControls.enable()\n            app.resizeOnNextRepaint = true\n        })\n        on(app,FULLSCREEN_EXITED,() => {\n            $(\"#touch-overlay\").style.visibility = 'hidden'\n            console.log(\"really exited fullscreen\")\n            $(\"body\").classList.remove('fullscreen')\n            app.fullscreenControls.disable()\n            app.desktopControls.enable()\n            app.resizeOnNextRepaint = true\n        })\n\n        // ============= setup the keyboard controls =========\n        app.keyboardControls = new KeyboardControls(app)\n        app.comps.push(app.keyboardControls)\n        on(app.keyboardControls, 'show-dialog',app.toggleDialog)\n        app.keyboardControls.enable()\n\n        // ============ setup the touch controls =============\n        app.touchControls = new TouchControls(app)\n        app.comps.push(app.touchControls)\n        on(app.touchControls,'highlight',app.moveHighlight)\n        on(app.touchControls,'setblock', app.createBlock)\n        on(app.touchControls,'removeblock',app.removeBlock)\n        on(app.touchControls,'show-dialog',app.toggleDialog)\n        if(app.touchControls.isTouchEnabled()) {\n            app.touchControls.enable()\n        }\n\n        on($(\"#jump-button\"),'touchstart',(e)=> {\n            e.preventDefault()\n            e.stopPropagation()\n            app.player_phys.startJump()\n        })\n        on($(\"#jump-button\"),'touchmove',(e)=> {\n            e.preventDefault()\n            e.stopPropagation()\n        })\n        on($(\"#jump-button\"),'touchend',(e)=> {\n            e.preventDefault()\n            e.stopPropagation()\n            app.player_phys.endJump()\n        })\n\n\n        // things to do on every render tick\n        // this is the render loop\n        let lastTime = 0\n        let lastPos = new Vector3(0,0,0)\n        app.onRender((time, app)=> {\n            const dt = time-lastTime\n            lastTime = time\n            //check every frame if the player has moved.\n            // if so tell the chunk manager to update any needed chunks\n            if(!lastPos.equals(app.stagePos.position)) {\n                app.chunkManager.updateCenterPosition(app.stagePos.position.clone().multiplyScalar(-1))\n            }\n            lastPos.copy(app.stagePos.position)\n            app.comps.forEach(comp => {\n                if(comp.isEnabled()) comp.update(time,dt)\n            })\n        })\n\n        //update progress indicator while loading\n        on(app,'progress',(prog)=> $(\"#progress\").setAttribute('value',100*prog))\n\n        //when all assets are loaded, hide progbar\n        on(app,'loaded',()=>{\n            // hide the loading progress bar\n            $(\"#progress\").style.display = 'none'\n            //show the fullscreen button\n            $(\"#fullscreen\").style.display = 'block'\n        })\n        //when VR support is detected\n        on(app,'detected',()=>{\n            // show the enter VR button\n            $(\"#entervr\").style.display = 'block'\n            on($(\"#entervr\"),'click',()=> {\n                app.desktopControls.disable()\n                app.vrControls.enable()\n                app.enterVR()\n            })\n        })\n\n    })\n        .catch(e => console.log(e))\n\n\n</script>\n</body>\n</html>\n"
  },
  {
    "path": "examples/src/BlockPicker.js",
    "content": "import Panel2D from \"threejs-2d-components/src/panel2d\"\nimport Label2D from \"threejs-2d-components/src/label2d\"\nimport Button2D from \"threejs-2d-components/src/button2d\"\n\n\nconst on = (elem, type, cb) => elem.addEventListener(type,cb)\n\nclass BlockTypeButton extends Button2D {\n    draw(ctx) {\n        ctx.font = `${this.fsize}px sans-serif`\n        const metrics = ctx.measureText(this.text)\n        // this.w = 5 + metrics.width + 5\n        // this.h = 0 + this.fsize + 4\n        ctx.fillStyle = this.bg\n        ctx.fillRect(this.x,this.y,this.w,this.h)\n        ctx.fillStyle = 'black'\n        ctx.fillText(this.text,this.x+3,this.y+this.fsize-2)\n        ctx.strokeStyle = 'black'\n        ctx.strokeRect(this.x,this.y,this.w,this.h)\n\n\n        // const x = (i%4)*64\n        // const y = Math.floor((i/4))*64\n        ctx.fillStyle = 'red'\n        ctx.fillRect(this.x,this.y,64,64)\n        let info = this.info\n        ctx.drawImage(this.owner.app.chunkManager.textureManager.canvas,\n            info.x,info.y,info.w,info.h,\n            this.x,this.y,64,64\n        )\n\n        if(this.owner.selectedColor === this.text) {\n            ctx.lineWidth = 2;\n            ctx.strokeStyle = 'black'\n            ctx.strokeRect(this.x+2,this.y+2,64-4,64-4)\n            ctx.strokeStyle = 'white'\n            ctx.strokeRect(this.x+4,this.y+4,64-8,64-8)\n        }\n    }\n}\n\nexport class BlockPicker {\n    constructor(app) {\n        this.app = app\n        this.panel = new Panel2D(app.scene,app.camera, {\n            draggable: false,\n            width: 256,\n            height: 256,\n        })\n        this.app.scene.add(this.panel)\n        this.selectedColor = 'none'\n    }\n\n    rebuild() {\n        this.panel.removeAll()\n        this.panel.add(new Label2D().set('text','block type').set('x',20).set('y',0))\n        const index = this.app.chunkManager.textureManager.getAtlasIndex()\n        index.forEach((info,i) => {\n            const button = new BlockTypeButton().setAll({\n                text:info.name,\n                x:(i%4)*64,\n                y:Math.floor(i/4)*64+40,\n                w:64,\n                h:64,\n                owner:this,\n                info:info,\n            },info.name)\n            on(button,'click',()=>{\n                console.log(\"selected block\", info.name)\n                const infos = this.app.chunkManager.textureManager.getAtlasIndex()\n                if(infos[i]) {\n                    this.selectedColor = infos[i].name\n                    this.panel.redraw()\n                } else {\n                    console.log(\"nothing selected\")\n                }\n            })\n            this.panel.add(button)\n        })\n\n        this.panel.add(new Button2D().setAll({\n            text:this.app.active?'creative':'active',\n            x:10,\n            y:225,\n            w:40,\n            h:40,\n        }).on('click',()=>{\n            console.log('toggling creative mode')\n            this.app.active = !this.app.active\n            this.app.player_phys.endFlying()\n            this.panel.visible = false\n        }))\n        this.panel.add(new Button2D().setAll({\n            text:'close',\n            x: 190,\n            y: 225,\n        }).on('click',()=>{\n            this.panel.visible = false\n        }))\n    }\n\n    setSelectedToDefault() {\n        // const index = this.app.chunkManager.textureManager.getAtlasIndex()\n        // this.selectedColor = index[0].name\n    }\n    /*\n    redraw() {\n        const ctx = this.canvas.getContext('2d')\n        ctx.fillStyle = 'white'\n        ctx.fillRect(0,0,this.canvas.width,this.canvas.height)\n        const index = this.app.textureManager.getAtlasIndex()\n        index.forEach((info,i) => {\n            const x = (i%4)*64\n            const y = Math.floor((i/4))*64\n            ctx.fillStyle = 'red'\n            ctx.fillRect(x,y,64,64)\n            ctx.drawImage(this.app.textureManager.canvas,\n                info.x,info.y,info.w,info.h,\n                x,y,64,64\n            )\n\n            if(this.selectedColor === info.name) {\n                ctx.lineWidth = 2;\n                ctx.strokeStyle = 'black'\n                ctx.strokeRect(x+2,y+2,64-4,64-4)\n                ctx.strokeStyle = 'white'\n                ctx.strokeRect(x+4,y+4,64-8,64-8)\n            }\n        })\n    }\n    */\n}\n"
  },
  {
    "path": "examples/src/ExplosionParticles.js",
    "content": "import {Vector3, Color, AdditiveBlending} from \"three\"\nimport {ECSComp} from '../../src/ECSComp.js'\nimport {GPUParticleSystem} from './GPUParticleSystem.js'\nimport {rand} from \"../../src/utils.js\"\n\nexport class ExplosionParticles extends ECSComp {\n    constructor(app) {\n        super()\n        this.app = app\n        this.startTime = -1\n        this.options = {\n            position: new Vector3(0, 0, 0),\n            positionRandomness: 0.0,\n            velocity: new Vector3(0.0, 1.0, 0.0),\n            velocityRandomness: 1.0,\n            acceleration: new Vector3(0.0, 0.0, 0.0),\n            color: new Color(1.0, 1.0, 1.0),\n            endColor: new Color(0.5, 0.5, 0.5),\n            colorRandomness: 0.0,\n            lifetime: 1,\n            fadeIn: 0.001,\n            fadeOut: 0.001,\n            size: 60,\n            sizeRandomness: 1.0,\n        }\n        let scorch_texture = app.textureLoader.load('./textures/smoke_08.png')\n        this.particles = new GPUParticleSystem({\n            maxParticles: 10000,\n            particleSpriteTex: scorch_texture,\n            blending: AdditiveBlending,\n            onTick: (system, time) => {\n                if (this.startTime === -1) this.startTime = time\n                if (time < this.startTime + 0.2) {\n                    for (let i = 0; i < 100; i++) {\n                        this.options.velocity.set(rand(-5, 5), rand(-5, 5), rand(-5, 5))\n                        system.spawnParticle(this.options);\n                    }\n                }\n            }\n        })\n        app.playersGroup.add(this.particles)\n    }\n\n    fire(pos) {\n        this.enable()\n        this.particles.position.copy(pos)\n        setTimeout(() => {\n            this.disable()\n            this.startTime = -1\n        }, 1500)\n    }\n\n    update(time, dt) {\n        this.particles.update(time)\n    }\n}\n"
  },
  {
    "path": "examples/src/GPUParticleSystem.js",
    "content": "import {Object3D, Vector3, Color, AdditiveBlending, RepeatWrapping, ShaderMaterial, BufferGeometry, BufferAttribute, Points } from \"three\"\nimport {ECSComp} from '../../src/ECSComp.js'\n\n/*\n* modified from the version from the ThreeJS examples repo\n */\n\nconst vertexShader =\n    `\n                uniform float uTime;\n                uniform float uScale;\n                uniform bool reverseTime;\n                uniform float fadeIn;\n                uniform float fadeOut;\n    \n                attribute vec3 positionStart;\n                attribute float startTime;\n                attribute vec3 velocity;\n                attribute vec3 acceleration;\n                attribute vec3 color;\n                attribute vec3 endColor;\n                attribute float size;\n                attribute float lifeTime;\n    \n                varying vec4 vColor;\n                varying vec4 vEndColor;\n                varying float lifeLeft;\n                varying float alpha;\n    \n                void main() {\n                    vColor = vec4( color, 1.0 );\n                    vEndColor = vec4( endColor, 1.0);\n                    vec3 newPosition;\n                    float timeElapsed = uTime - startTime;\n                    if(reverseTime) timeElapsed = lifeTime - timeElapsed;\n                    if(timeElapsed < fadeIn) {\n                        alpha = timeElapsed/fadeIn;\n                    }\n                    if(timeElapsed >= fadeIn && timeElapsed <= (lifeTime - fadeOut)) {\n                        alpha = 1.0;\n                    }\n                    if(timeElapsed > (lifeTime - fadeOut)) {\n                        alpha = 1.0 - (timeElapsed - (lifeTime-fadeOut))/fadeOut;\n                    }\n                    \n                    lifeLeft = 1.0 - ( timeElapsed / lifeTime );\n                    gl_PointSize = ( uScale * size );// * lifeLeft;\n                    newPosition = positionStart \n                        + (velocity * timeElapsed)\n                        + (acceleration * 0.5 * timeElapsed * timeElapsed)\n                        ;\n                    if (lifeLeft < 0.0) { \n                        lifeLeft = 0.0; \n                        gl_PointSize = 0.;\n                    }\n                    //while active use the new position\n                    if( timeElapsed > 0.0 ) {\n                        gl_Position = projectionMatrix * modelViewMatrix * vec4( newPosition, 1.0 );\n                    } else {\n                        //if dead use the initial position and set point size to 0\n                        gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );\n                        lifeLeft = 0.0;\n                        gl_PointSize = 0.;\n                    }\n                }\n                `\n\nconst fragmentShader = `\n                varying vec4 vColor;\n                varying vec4 vEndColor;\n                varying float lifeLeft;\n                varying float alpha;\n                uniform sampler2D tSprite;\n                void main() {\n                    // color based on particle texture and the lifeLeft. \n                    // if lifeLeft is 0 then make invisible\n                    vec4 tex = texture2D( tSprite, gl_PointCoord );\n                    vec4 color = mix(vColor, vEndColor, 1.0-lifeLeft);\n                    gl_FragColor = vec4( color.rgb*tex.rgb, alpha * tex.a);\n                }\n    \n            `\n\nconst UPDATEABLE_ATTRIBUTES = [\n    'positionStart', 'startTime',\n    'velocity', 'acceleration',\n    'color', 'endColor',\n    'size', 'lifeTime']\n\nexport class GPUParticleSystem extends  Object3D {\n    constructor(options) {\n        super()\n        options = options || {};\n\n\n        this.blending = options.blending? options.blending : NormalBlending\n        this.PARTICLE_COUNT = options.maxParticles || 1000000;\n        this.PARTICLE_CURSOR = 0;\n        this.time = 0;\n        this.offset = 0;\n        this.count = 0;\n        this.DPR = window.devicePixelRatio;\n        this.particleUpdate = false;\n        this.onTick = options.onTick\n\n        this.reverseTime = options.reverseTime\n        this.fadeIn = options.fadeIn || 1\n        if(this.fadeIn === 0) this.fadeIn = 0.001\n        this.fadeOut = options.fadeOut || 1\n        if(this.fadeOut === 0) this.fadeOut = 0.001\n\n        // preload a 10_000 random numbers from -0.5 to 0.5\n        this.rand = [];\n        let i;\n        for (i = 1e5; i>0; i--) {\n            this.rand.push( Math.random() - 0.5 );\n        }\n        this.i = i\n\n        //setup the texture\n        this.sprite = options.particleSpriteTex || null;\n        if(!this.sprite) throw new Error(\"No particle sprite texture specified\")\n        this.sprite.wrapS = this.sprite.wrapT = RepeatWrapping;\n\n        //setup the shader material\n        this.material = new ShaderMaterial( {\n            transparent: true,\n            depthWrite: false,\n            uniforms: {\n                'uTime': {\n                    value: 0.0\n                },\n                'uScale': {\n                    value: 1.0\n                },\n                'tSprite': {\n                    value: this.sprite\n                },\n                reverseTime: {\n                    value: this.reverseTime\n                },\n                fadeIn: {\n                    value: this.fadeIn\n                },\n                fadeOut: {\n                    value: this.fadeOut,\n                }\n            },\n            blending: this.blending,\n            vertexShader: vertexShader,\n            fragmentShader: fragmentShader\n        } );\n\n        // define defaults for all values\n        this.material.defaultAttributeValues.particlePositionsStartTime = [ 0, 0, 0, 0 ];\n        this.material.defaultAttributeValues.particleVelColSizeLife = [ 0, 0, 0, 0 ];\n\n\n        // geometry\n        this.geometry = new BufferGeometry();\n\n        //vec3 attributes\n        this.geometry.addAttribute('position',      new BufferAttribute(new Float32Array(this.PARTICLE_COUNT * 3), 3).setDynamic(true));\n        this.geometry.addAttribute('positionStart', new BufferAttribute(new Float32Array(this.PARTICLE_COUNT * 3), 3).setDynamic(true));\n        this.geometry.addAttribute('velocity',      new BufferAttribute(new Float32Array(this.PARTICLE_COUNT * 3), 3).setDynamic(true));\n        this.geometry.addAttribute('acceleration',  new BufferAttribute(new Float32Array(this.PARTICLE_COUNT * 3), 3).setDynamic(true));\n        this.geometry.addAttribute('color',         new BufferAttribute(new Float32Array(this.PARTICLE_COUNT * 3), 3).setDynamic(true));\n        this.geometry.addAttribute('endColor',      new BufferAttribute(new Float32Array(this.PARTICLE_COUNT * 3), 3).setDynamic(true));\n\n        //scalar attributes\n        this.geometry.addAttribute('startTime',     new BufferAttribute(new Float32Array(this.PARTICLE_COUNT), 1).setDynamic(true));\n        this.geometry.addAttribute('size',          new BufferAttribute(new Float32Array(this.PARTICLE_COUNT), 1).setDynamic(true));\n        this.geometry.addAttribute('lifeTime',      new BufferAttribute(new Float32Array(this.PARTICLE_COUNT), 1).setDynamic(true));\n\n\n        this.particleSystem = new Points(this.geometry, this.material);\n        this.particleSystem.frustumCulled = false;\n        this.add(this.particleSystem);\n    }\n\n    /*\n      This updates the geometry on the shader if at least one particle has been spawned.\n      It uses the offset and the count to determine which part of the data needs to actually\n      be sent to the GPU. This ensures no more data than necessary is sent.\n     */\n    geometryUpdate () {\n        if (this.particleUpdate === true) {\n            this.particleUpdate = false;\n            UPDATEABLE_ATTRIBUTES.forEach(name => {\n                const attr = this.geometry.getAttribute(name)\n                if (this.offset + this.count < this.PARTICLE_COUNT) {\n                    attr.updateRange.offset = this.offset * attr.itemSize\n                    attr.updateRange.count = this.count * attr.itemSize\n                } else {\n                    attr.updateRange.offset = 0\n                    attr.updateRange.count = -1\n                }\n                attr.needsUpdate = true\n            })\n            this.offset = 0;\n            this.count = 0;\n        }\n    }\n\n\n    //use one of the random numbers\n    random () {\n        return ++ this.i >= this.rand.length ? this.rand[ this.i = 1 ] : this.rand[ this.i ];\n    }\n\n    update ( ttime ) {\n        this.time = ttime/1000\n        this.material.uniforms.uTime.value = this.time;\n        if(this.onTick) this.onTick(this,this.time)\n        this.geometryUpdate();\n    }\n\n    dispose () {\n        this.material.dispose();\n        this.sprite.dispose();\n        this.geometry.dispose();\n    }\n\n    /* spawn a particle\n\n    This works by updating values inside of\n    the attribute arrays, then updates the count and the PARTICLE_CURSOR and\n    sets particleUpdate to true.\n\n    This if spawnParticle is called three times in a row before rendering,\n    then count will be 3 and the cursor will have moved by three.\n     */\n    spawnParticle ( options ) {\n        let position = new Vector3()\n        let velocity = new Vector3()\n        let acceleration = new Vector3()\n        let color = new Color()\n        let endColor = new Color()\n\n        const positionStartAttribute = this.geometry.getAttribute('positionStart')\n        const startTimeAttribute = this.geometry.getAttribute('startTime')\n        const velocityAttribute = this.geometry.getAttribute('velocity')\n        const accelerationAttribute = this.geometry.getAttribute('acceleration')\n        const colorAttribute = this.geometry.getAttribute('color')\n        const endcolorAttribute = this.geometry.getAttribute('endColor')\n        const sizeAttribute = this.geometry.getAttribute('size')\n        const lifeTimeAttribute = this.geometry.getAttribute('lifeTime')\n\n        options = options || {};\n\n        // setup reasonable default values for all arguments\n\n        position = options.position !== undefined ? position.copy(options.position) : position.set(0, 0, 0);\n        velocity = options.velocity !== undefined ? velocity.copy(options.velocity) : velocity.set(0, 0, 0);\n        acceleration = options.acceleration !== undefined ? acceleration.copy(options.acceleration) : acceleration.set(0, 0, 0);\n        color = options.color !== undefined ? color.copy(options.color) : color.set(0xffffff);\n        endColor = options.endColor !== undefined ? endColor.copy(options.endColor) : endColor.copy(color)\n\n        const lifetime = options.lifetime !== undefined ? options.lifetime : 5\n        let size = options.size !== undefined ? options.size : 10\n        const sizeRandomness = options.sizeRandomness !== undefined ? options.sizeRandomness : 0\n\n        if (this.DPR !== undefined) size *= this.DPR;\n\n        const i = this.PARTICLE_CURSOR\n\n        // position\n        positionStartAttribute.array[i * 3 + 0] = position.x\n        positionStartAttribute.array[i * 3 + 1] = position.y\n        positionStartAttribute.array[i * 3 + 2] = position.z\n\n        velocityAttribute.array[i * 3 + 0] = velocity.x;\n        velocityAttribute.array[i * 3 + 1] = velocity.y;\n        velocityAttribute.array[i * 3 + 2] = velocity.z;\n\n        accelerationAttribute.array[i * 3 + 0] = acceleration.x;\n        accelerationAttribute.array[i * 3 + 1] = acceleration.y;\n        accelerationAttribute.array[i * 3 + 2] = acceleration.z;\n\n        colorAttribute.array[i * 3 + 0] = color.r;\n        colorAttribute.array[i * 3 + 1] = color.g;\n        colorAttribute.array[i * 3 + 2] = color.b;\n\n        endcolorAttribute.array[i * 3 + 0] = endColor.r;\n        endcolorAttribute.array[i * 3 + 1] = endColor.g;\n        endcolorAttribute.array[i * 3 + 2] = endColor.b;\n\n        //size, lifetime and starttime\n        sizeAttribute.array[i] = size + this.random() * sizeRandomness;\n        lifeTimeAttribute.array[i] = lifetime;\n        startTimeAttribute.array[i] = this.time + this.random() * 2e-2;\n\n        // offset\n        if (this.offset === 0) this.offset = this.PARTICLE_CURSOR;\n        // counter and cursor\n        this.count++;\n        this.PARTICLE_CURSOR++;\n        //wrap the cursor around\n        if (this.PARTICLE_CURSOR >= this.PARTICLE_COUNT) this.PARTICLE_CURSOR = 0;\n        this.particleUpdate = true;\n    };\n}\n"
  },
  {
    "path": "examples/src/ItemManager.js",
    "content": "import {Vector3,} from \"three\"\nimport {ECSComp} from '../../src/ECSComp.js'\n\nexport class ItemManager extends ECSComp {\n    constructor(app) {\n        super()\n        this.app = app\n    }\n\n    isBlockTypeItem(type) {\n        if(type === 5) return true\n        return false\n    }\n\n    removeItem(pos) {\n        const type = this.app.chunkManager.voxelAtCoordinates(pos)\n        const radius = 3;\n        if(type === 5) { //the type code for TNT\n            console.log(\"triggering TNT explosion\")\n            const cursor = new Vector3()\n            const actual = new Vector3()\n            for(let x=-radius; x<+radius; x++) {\n                cursor.x = x\n                for(let y=-radius; y<=+radius; y++) {\n                    cursor.y = y\n                    for(let z = -radius; z<=+radius; z++) {\n                        cursor.z = z\n                        if(cursor.length()<radius) {\n                            actual.copy(pos)\n                            actual.add(cursor)\n                            this.app.setBlock(actual,0)\n                        }\n                    }\n                }\n            }\n            const chunkIndex = this.app.chunkManager.chunkIndexAtCoordinates(pos.x,pos.y,pos.z)\n            const chunk = this.app.chunkManager.chunks[chunkIndex.join(\"|\")]\n            if(chunk) this.app.rebuildMesh(chunk)\n        }\n        this.app.explosionParticles.fire(pos)\n    }\n}\n"
  },
  {
    "path": "examples/src/PersistenceManager.js",
    "content": "\nfunction GET_JSON(url) {\n    return fetch(url+`?cachebust=${Math.random()}`)\n        .then(res => res.json())\n        // .then(res => {\n        //     return res\n            // const blocks = game.blockService.loadFromJSON(res)\n            // blocks.forEach(b => {\n            //     on(b.getObject3D(), 'click', blockClicked)\n            // })\n            // dataChanger.fire('changed',{})\n        // })\n}\nfunction POST_JSON(url,data) {\n    console.log(\"posting to\",url)\n    return fetch(url, {\n        method: 'POST',\n        mode: 'cors',\n        headers: {\n            'Content-Type': 'application/json'\n        },\n        body: JSON.stringify(data)\n    })\n    .then(resp => resp.json())\n    .then(resp => {\n        console.log(\"real response is\",resp)\n        return resp\n    })\n}\n\nfunction loadImageFromURL(url) {\n    return new Promise((res,rej)=>{\n        const img = new Image()\n        img.addEventListener('load',()=>{\n            res(img)\n        })\n        img.src = url\n\n    })\n}\n\n\n\nconst BASE_URL =  \"https://vr.josh.earth/360/doc/\"\n\nexport class PersistenceManager {\n    constructor() {\n\n    }\n\n    save(chunkManager, cache) {\n        const chunkCount = Object.keys(chunkManager.chunks).length\n        const width = 512\n        const height = 1024\n        const canvas = document.createElement('canvas')\n        canvas.width = width\n        canvas.height = height\n        const ctx = canvas.getContext('2d')\n        ctx.fillStyle = 'rgba(255,255,255,1.0)'\n        ctx.fillRect(0,0,canvas.width,canvas.height)\n        const data = ctx.getImageData(0,0,canvas.width,canvas.height)\n        console.log(\"saving\",Object.keys(chunkManager.chunks).length,'chunks')\n        const output = {\n            chunks:[],\n            image:null,\n        }\n        Object.keys(chunkManager.chunks).forEach((id,i)=> {\n            const chunk = chunkManager.chunks[id]\n            const info = {\n                id: id,\n                low: chunk.data.low,\n                high: chunk.data.high,\n                dims: chunk.data.dims,\n                position: chunk.chunkPosition,\n            }\n            //turn a 4096 array into an 8x512 section of the image\n            for(let k=0; k<chunk.data.voxels.length; k++) {\n                const val = chunk.data.voxels[k]\n                const vx = k%512\n                const vy = Math.floor(k/512) + i*8\n                const n = (vy*512 + vx)*4\n                data.data[n+0] = 0\n                data.data[n+1] = 0\n                data.data[n+2] = val\n                data.data[n+3] = 255\n            }\n            info.imageCoords = {\n                x:0,\n                y:i*8,\n                width:512,\n                height:8,\n            }\n            output.chunks.push(info)\n        })\n        ctx.putImageData(data,0,0)\n        // document.body.appendChild(canvas)\n        output.image = canvas.toDataURL('png')\n\n        const url = BASE_URL+'foozoo88'\n        return POST_JSON(url,output)\n    }\n\n    load(chunkManager) {\n        const url = BASE_URL+'foozoo88'\n        return GET_JSON(url).then(data => {\n            // console.log(\"parsing\",data)\n            chunkManager.clear()\n            return loadImageFromURL(data.image).then(img => {\n                const canvas = document.createElement('canvas')\n                canvas.width = img.width\n                canvas.height = img.height\n                const ctx = canvas.getContext('2d')\n                ctx.drawImage(img,0,0)\n                // document.body.appendChild(canvas)\n                data.chunks.forEach(chunk => {\n                    const imageData = ctx.getImageData(chunk.imageCoords.x,chunk.imageCoords.y, chunk.imageCoords.width, chunk.imageCoords.height)\n                    const voxels = []\n                    for(let i=0; i<4096; i++) voxels[i] = imageData.data[i*4+2]\n                    chunkManager.makeChunkFromData(chunk,voxels)\n                })\n            })\n        })\n\n    }\n}\n\n\n"
  },
  {
    "path": "examples/src/PigComp.js",
    "content": "import {Vector3, Mesh, MeshLambertMaterial, BoxBufferGeometry, Box3} from \"three\"\nimport {ECSComp} from \"../../src/ECSComp.js\"\nimport {traceRay} from '../../src/raycast.js'\nimport {PhysHandler} from '../../src/PhysHandler.js'\nimport {checkHitTileY, checkHitTileX, checkHitTileZ} from \"../../src/SimpleMeshCollider.js\"\n\nexport class PigComp extends ECSComp {\n    constructor(app) {\n        super()\n        this.app = app\n\n        this.mesh = new Mesh(\n            new BoxBufferGeometry(1,1,1),\n            new MeshLambertMaterial({color:'pink', map:app.textureLoader.load('./textures/pig.png')})\n        )\n\n        this.heading = new Vector3(2,0,-1).normalize()\n        this.mesh.position.set(-8,1.5,2)\n        this.velocity = 0.005\n        this.app.mobs.add(this.mesh)\n\n\n        this.physics = new PhysHandler(this.app,this.mesh,[this])\n    }\n\n    update(time,dt) {\n        this.physics.update(dt)\n    }\n\n    collide(phys, target, pos, diff) {\n        // phys.vel.y = 0//0.01\n\n\n\n        if(!this.app.active) {\n            //don't do any physics when the world is paused. just let the user move around\n            // phys.vel.y = 0\n            return\n        }\n        const bounds = new Box3(new Vector3(0,-1.5,0),new Vector3(1,0,1))\n        // console.log(\"pos\",pos.y)\n        const size = new Vector3()\n        bounds.getSize(size)\n        // console.log(\"size\",size)\n        const rpos = pos.clone()\n        pos.y -= 1.5\n        //while on the floor, no y velocity\n        if(checkHitTileY(this.app.chunkManager,bounds,pos)) {\n            phys.vel.y = 0\n        }\n        //check forwards\n        if(checkHitTileX(this.app.chunkManager,bounds,pos)) {\n            this.heading.x = Math.random()-0.5\n            this.heading.z = Math.random()-0.5\n            this.heading.normalize()\n        }\n        if(checkHitTileZ(this.app.chunkManager,bounds,pos)) {\n            this.heading.x = Math.random()-0.5\n            this.heading.z = Math.random()-0.5\n            this.heading.normalize()\n        }\n\n\n        if(pos.y < -1000) {\n            console.log(\"pig is dead\")\n            this.disable()\n        }\n\n        //set vel to the heading\n        phys.vel.z = this.heading.z\n        phys.vel.x = this.heading.x\n    }\n\n}\n"
  },
  {
    "path": "examples/src/PubnubNetworkplay.js",
    "content": "import {Vector3,} from \"three\"\nimport {ECSComp} from '../../src/ECSComp.js'\n\nconst pubkey = 'pub-c-0a0c49cb-8e11-4b10-8347-3af6cf048b46';\nconst subkey = 'sub-c-1cf05cbc-4d88-11e9-82b8-86fda2e42ae9';\n\nconst CHANNEL = 'beta-movement'\nconst SEND_INTERVAL = 333;\nexport class PubnubNetworkplay extends ECSComp {\n    constructor() {\n        super()\n        this.connecting = false\n        this.connected = false\n        this.lastPos = new Vector3(-100,-100,-100)\n        this.currPos = new Vector3()\n        this.voxels = []\n        this.pubnub = new PubNub({\n            publishKey: pubkey,\n            subscribeKey: subkey,\n        })\n        this.pubnub.setUUID('voxeluser_'+Math.floor(Math.random()*1000000))\n        this.pubnub.addListener({\n            status:(e) => {\n                console.log(\"PUBNUB status\",e)\n                if(e.category === 'PNConnectedCategory') {\n                    this.connected = true\n                }\n            },\n            message: (msg) =>{\n                if(msg.publisher !== this.pubnub.getUUID()) {\n                    // console.log(\"someone else moved\",msg.publisher)\n                    // console.log(\"PUBNUB message\",msg)\n                    if(msg.message.type === 'movement') this._fire('remote-player-moved',msg)\n                    if(msg.message.type === 'voxels') this._fire('remote-player-voxels',msg)\n                } else {\n                    // console.log(\"it's me\")\n                }\n            },\n        })\n        this.sendUpdates = () => {\n            if(!this.lastPos.equals(this.currPos)) {\n                // console.log(\"sending updates\", this.currPos)\n                this.lastPos.copy(this.currPos)\n                this.pubnub.publish({\n                    channel:CHANNEL,\n                    message: {\n                        type:'movement',\n                        position: {\n                            x:this.currPos.x,\n                            y:this.currPos.y,\n                            z:this.currPos.z\n                        }\n                    }\n                },(status,response)=>{\n                    if(status.error)   console.log(\"PUBNUB error?\",status)\n                })\n            }\n            if(this.voxels.length > 0) {\n                const voxels = this.voxels.slice()\n                // console.log(\"sending changed voxels:\",voxels.length)\n                this.voxels = []\n                this.pubnub.publish({\n                    channel: CHANNEL,\n                    message: {\n                        type:'voxels',\n                        voxels:voxels\n                    }\n                }, (status,response) => {\n                    if(status.error)   console.log(\"PUBNUB error?\",status)\n                })\n            }\n        }\n    }\n\n    connect() {\n        console.log(\"PUBNUB subscribing\")\n        this.connecting = true\n        this.pubnub.subscribe({channels:[CHANNEL]})\n        setInterval(this.sendUpdates,SEND_INTERVAL)\n    }\n\n    isConnected() {\n        return this.connected\n    }\n\n    isConnecting() {\n        return this.connecting\n    }\n\n    playerMoved(phys) {\n        this.currPos.copy(phys.target.position)\n    }\n    playerSetVoxel(pos,type) {\n        this.voxels.push({\n            type:type,\n            position: {\n                x:pos.x,\n                y:pos.y,\n                z:pos.z\n            }\n        })\n    }\n}\n"
  },
  {
    "path": "examples/src/RemotePlayersProxy.js",
    "content": "import {Vector3, Mesh, MeshLambertMaterial, SphereBufferGeometry} from \"three\"\nimport {ECSComp} from '../../src/ECSComp.js'\n\nconst BUMP_HEIGHT = new Vector3(0,1,0)\nexport class RemotePlayersProxy extends ECSComp {\n    constructor(app) {\n        super()\n        this.app = app;\n        this.players = {}\n    }\n    remotePlayerMoved(id,pos) {\n        if(!this.players[id]) {\n            console.log(\"a new player joined!\")\n            this.players[id] = new Mesh(new SphereBufferGeometry(1), new MeshLambertMaterial({color:'aqua'}))\n            this.app.playersGroup.add(this.players[id])\n        }\n        this.players[id].position.copy(pos).add(BUMP_HEIGHT)\n        console.log(\"remote player moved to\",pos)\n    }\n}\n"
  },
  {
    "path": "examples/src/SmashParticles.js",
    "content": "import {Vector3, Color, AdditiveBlending} from \"three\"\nimport {ECSComp} from '../../src/ECSComp.js'\nimport {GPUParticleSystem} from './GPUParticleSystem.js'\nimport {rand} from \"../../src/utils.js\"\n\nexport class SmashParticles extends ECSComp {\n    constructor(app) {\n        super()\n        this.app = app\n        this.startTime = -1\n        this.options = {\n            position: new Vector3(0, 0, 0),\n            positionRandomness: 0.0,\n            velocity: new Vector3(0.0, 1.0, 0.0),\n            velocityRandomness: 1.0,\n            acceleration: new Vector3(0.0, 0.0, 0.0),\n            color: new Color(1.0, 0.0, 0.0),\n            endColor: new Color(0.5, 0.5, 0.5),\n            colorRandomness: 0.0,\n            lifetime: 0.20,\n            fadeIn: 0.000,\n            fadeOut: 0.000,\n            size: 20,\n            sizeRandomness: 1.0,\n        }\n        let scorch_texture = app.textureLoader.load('./textures/smoke_08.png')\n        this.particles = new GPUParticleSystem({\n            maxParticles: 10000,\n            particleSpriteTex: scorch_texture,\n            blending: AdditiveBlending,\n            onTick: (system, time) => {\n                if (this.startTime === -1) this.startTime = time\n                if (time < this.startTime + 0.05) {\n                    for (let i = 0; i < 100; i++) {\n                        this.options.velocity.set(rand(-5, 5), rand(-5, 5), rand(-5, 5))\n                        system.spawnParticle(this.options);\n                    }\n                }\n            }\n        })\n        app.playersGroup.add(this.particles)\n    }\n\n    fire(pos) {\n        this.enable()\n        this.particles.position.copy(pos)\n        this.particles.position.add(new Vector3(0.5, 0.5, 0.5))\n        setTimeout(() => {\n            this.disable()\n            this.startTime = -1\n        }, 250)\n    }\n\n    update(time, dt) {\n        this.particles.update(time)\n    }\n}\n"
  },
  {
    "path": "examples/src/WebRTCAudioChat.js",
    "content": "// https://github.com/stephenlb/webrtc-sdk\n\nconst pubkey = 'pub-c-0a0c49cb-8e11-4b10-8347-3af6cf048b46';\nconst subkey = 'sub-c-1cf05cbc-4d88-11e9-82b8-86fda2e42ae9';\nconst number = 'testnum1'\n\nexport class WebRTCAudioChat {\n    constructor(app) {\n        this.connected = false\n    }\n\n    connect() {\n        this.phone = PHONE({\n            number        : number\n            ,   publish_key   : pubkey\n            ,   subscribe_key : subkey\n            ,   media:  {audio:true}\n        });\n        this.phone.debug( info => console.info('PHONE',info) );\n        // Debugging Output\n        // As soon as the phone is ready we can make calls\n\n        this.phone.ready(()=>{\n            console.log(\"PHONE_READY: system is connected!\")\n            this.connected = true\n//         let session = phone.dial(number);\n        });\n\n        // When Call Comes In\n        this.phone.receive((session) => {\n            console.log(\"a phone call came in\")\n            // Display Your Friend's Live Video\n            session.connected( session => {\n                console.log('Session: CONNECTED');\n//             phone.$('video-out').appendChild(session.video);\n            });\n            session.ended( session => {\n                this.connected = false\n                console.log('Session: ENDED')\n            } );\n        });\n    }\n\n\n    disconnect() {\n        console.log(\"DISCONNECTING\")\n        this.phone.hangup()\n    }\n\n\n}\n"
  },
  {
    "path": "examples/src/index.js",
    "content": "import '../css/fullscreen.css'\nimport '../css/webxr.css';\nimport '../css/dashboard.css';\nimport '../css/index.css';\n\nimport {Group,\n  Vector3,\n  TextureLoader,\n  CubeGeometry,\n  MeshLambertMaterial,\n  Mesh,\n  AmbientLight,\n} from 'three';\nimport { Component, System, World } from 'ecsy';\nimport {\n  initialize,\n  Parent,\n  Transform,\n  Object3D,\n} from 'ecsy-three';\n\nimport {MouseCursor, MouseSystem,\n  KeyboardBindingSet, KeyboardSystem,\n  VoxelLandscape, VoxelSystem, VoxelTextures,\n  ActiveBlock, Highlight, HighlightSystem,\n  StagePosition, StageRotation,\n  WebXRSystem, WebXRButton, WebXRController,\n  FullscreenSystem, FullscreenButton,\n  DashboardDOMOvleraySystem, DomDashboard, DashboardVisible,\n  InputFrame, VoxelPlayerSystem,\n} from 'voxeljs-next'\n\n\nclass VoxelWebXRControllerSystem extends System {\n  execute(delta, time) {\n      this.queries.controllers.added.forEach(ent => {\n          let con = ent.getComponent(WebXRController);\n          let mesh2 = new Mesh(\n              new CubeGeometry(1.1,0.1,0.1),\n              new MeshLambertMaterial({\n                  color:'yellow',\n              }));\n          ent.addComponent(Transform)\n          ent.addComponent(Object3D, {value: mesh2})\n          ent.addComponent(Parent, con.controller)\n      })\n      this.queries.controllers.results.forEach(ent => {\n          let con = ent.getComponent(WebXRController)\n          if(con.selected) {\n              console.log(\"xr is pressed \", con.index)\n          }\n      })\n  }\n}\nVoxelWebXRControllerSystem.queries = {\n  controllers: {\n      components:[WebXRController],\n      listen: {\n          added:true,\n          removed:true,\n      }\n  },\n}\n\n\n// Create a new world to hold all our highlights and systems\nlet world = new World();\n\n// Register all of the systems we will need\nworld.registerSystem(VoxelSystem)\nworld.registerSystem(KeyboardSystem)\nworld.registerSystem(MouseSystem)\nworld.registerSystem(HighlightSystem)\nworld.registerSystem(DashboardDOMOvleraySystem)\nworld.registerSystem(WebXRSystem);\nworld.registerSystem(FullscreenSystem);\nworld.registerSystem(VoxelPlayerSystem)\n\n// Initialize the default sets of highlights and systems\nlet data = initialize(world);\nlet {scene, renderer, camera} = data.entities;\nconsole.log(\"got it\",data)\n\n// Modify the position for the default camera\n// let transform = camera.getMutableComponent(Transform);\n// transform.position.z = 5;\n\nscene.addComponent(FullscreenButton)\nscene.addComponent(WebXRButton)\n// one InputFrame is required for all inputs to work\nscene.addComponent(InputFrame)\n\n// the binding keys match dom keyboard events\nworld.createEntity()\n  .addComponent(KeyboardBindingSet, {bindings: {\n          'a': InputFrame.LEFT_STRAFE,\n          'ArrowLeft': InputFrame.LEFT_STRAFE,\n          'd': InputFrame.RIGHT_STRAFE,\n          'ArrowRight': InputFrame.RIGHT_STRAFE,\n          'w': InputFrame.MOVE_FORWARD,\n          's': InputFrame.MOVE_BACKWARD,\n          'ArrowUp': InputFrame.MOVE_FORWARD,\n          'ArrowDown': InputFrame.MOVE_BACKWARD,\n          'e': InputFrame.OPEN_DASHBOARD,\n          't': InputFrame.LEVITATE_UP,\n          'g': InputFrame.LEVITATE_DOWN,\n      }})\n\n\nnew TextureLoader().load('./dummy.jpg')\n\n\n// add a dashboard to the scene\nscene.addComponent(DomDashboard)\n\n//set the active block to type 3 (TNT)\nscene.addComponent(ActiveBlock, {type:3})\n\n// a pivot for rotating the world around\nlet stageRot = world.createEntity()\n  .addComponent(Object3D, {value: new Group()})\n  .addComponent(Transform)\n  .addComponent(Parent, {value: scene})\n  .addComponent(StageRotation) // StageRotation is how the rest of the system can use this\n\n// a position for moving the world around\nlet stagePos = world.createEntity()\n  .addComponent(Object3D, {value: new Group})\n  .addComponent(Transform)\n  .addComponent(Parent, {value:stageRot})\n  .addComponent(StagePosition) // StagePosition\n\n//make the actual landscape\nworld.createEntity()\n  .addComponent(Transform)\n  .addComponent(Parent, {value: stagePos})\n  .addComponent(VoxelLandscape, {\n      make_voxel: (x,y,z) => {\n          // make a floor between -2 and -5\n          if(y < -2 && y > -5) return 1 // grass\n          // make a 4x4x4 cube floating in space\n          if(    x > 0 && x < 5\n              && z > 5 && z < 10\n              && y > 5 && y < 10\n          ) return 2 // brick\n          return 0\n      }\n      ,\n  })\n  .addComponent(VoxelTextures,{\n      textures:[\n          {\n              src:'./textures/dirt.png'\n          },\n          {\n              src:'./textures/grass.png'\n          },\n          {\n              src:'./textures/brick.png'\n          },\n          {\n              src:'./textures/tnt.png'\n          },\n          {\n              src:'./textures/heart.png',\n          },\n  ]})\n\nworld.execute();\n\n// create a mouse cursor so that we can look for mouse events\nworld.createEntity()\n  .addComponent(MouseCursor)\n\n//create a ThreeJS mesh as the highlighter\nlet mesh = new Mesh(\n  new CubeGeometry(1.1,1.1,1.1, 4,4,4).translate(0.5,0.5,0.5),\n  new MeshLambertMaterial({\n      color:'green',\n      depthTest:true,\n      wireframe:true,\n      wireframeLinewidth: 3,\n      transparent: true,\n      opacity: 0.5,\n  }));\n\n// make the highlighter\nlet highlight = world.createEntity()\n  .addComponent(Transform)\n  .addComponent(Object3D, { value: mesh})\n  .addComponent(Parent, {value: stagePos})\n  .addComponent(Highlight)\n\n//add some ambient light or the highlight mesh won't have any color\nworld.createEntity()\n  .addComponent(Object3D, { value: new AmbientLight()})\n  .addComponent(Parent, {value: scene})\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"voxeljs-next\",\n  \"version\": \"0.0.13\",\n  \"description\": \"The next generation of Voxel JS.\",\n  \"main\": \"dist/voxeljs-next.js\",\n  \"module\": \"src/index.js\",\n  \"scripts\": {\n    \"prepublish\": \"npm run build\",\n    \"start\": \"webpack-dev-server --mode development\",\n    \"nuke\": \"rm package-lock.json && rm -rf dist && rm -rf node_modules && rm -rf examples/node_modules\",\n    \"build\": \"cd examples && npm i && cd .. && LIBRARY=true webpack\"\n  },\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/joshmarinacci/voxeljs-next.git\"\n  },\n  \"author\": \"Josh Marinacci\",\n  \"license\": \"BSD3\",\n  \"bugs\": {\n    \"url\": \"https://github.com/joshmarinacci/voxeljs-next/issues\"\n  },\n  \"files\": [\n    \"src\",\n    \"dist/voxeljs-next.js\",\n    \"dist/voxeljs-next.js.map\"\n  ],\n  \"homepage\": \"https://github.com/joshmarinacci/voxeljs-next#readme\",\n  \"peerDependencies\": {\n    \"three\": \"^0.116.1\",\n    \"ecsy\": \"^0.2.3\",\n    \"ecsy-three\": \"^0.1.0\"\n  },\n  \"devDependencies\": {\n    \"clean-webpack-plugin\": \"^3.0.0\",\n    \"copy-webpack-plugin\": \"^5.1.1\",\n    \"css-loader\": \"^3.5.3\",\n    \"ecsy\": \"^0.2.3\",\n    \"ecsy-three\": \"^0.1.0\",\n    \"html-webpack-plugin\": \"^4.3.0\",\n    \"style-loader\": \"^1.2.1\",\n    \"three\": \"^0.116.1\",\n    \"webpack\": \"^4.43.0\",\n    \"webpack-cli\": \"^3.3.11\",\n    \"webpack-dev-server\": \"^3.10.3\"\n  },\n  \"dependencies\": {}\n}\n"
  },
  {
    "path": "src/ChunkManager.js",
    "content": "import {Vector3,} from \"three\"\nimport {CulledMesher} from \"./CulledMesher.js\"\nimport {VoxelMesh} from \"./VoxelMesh.js\"\n\nclass Chunk {\n    constructor(data, pos, chunkBits) {\n        this.data = data\n        this.dims = data.dims\n        this.voxels = data.voxels\n        this.vmesh = null\n        this.surfaceMesh = null\n        this.realPosition = pos\n        this.chunkPosition = [pos.x, pos.y, pos.z]\n        this.id = this.chunkPosition.join('|')\n        this.chunkBits = chunkBits\n    }\n\n    voxelIndexFromCoordinates(x, y, z) {\n        const bits = this.chunkBits\n        const mask = (1 << bits) - 1\n        return (x & mask) + ((y & mask) << bits) + ((z & mask) << bits * 2)\n    }\n\n    voxelAtCoordinates(pt) {\n        const vidx = this.voxelIndexFromCoordinates(pt.x, pt.y, pt.z)\n        return this.voxels[vidx]\n    }\n    setVoxelAtCoordinates(pt, val) {\n        const vidx = this.voxelIndexFromCoordinates(pt.x, pt.y, pt.z)\n        const v = this.voxels[vidx]\n        this.voxels[vidx] = val\n        return v\n    }\n\n    dispose() {\n        if (this.vmesh) {\n            delete this.vmesh.data\n            delete this.vmesh.geometry\n            delete this.vmesh.meshed\n            delete this.vmesh.surfaceMesh\n        }\n    }\n}\n\nconst SCALE = new Vector3(1.0,1.0,1.0)\n\nexport class ChunkManager {\n    constructor(opts) {\n        this.listeners = {}\n        this.container = opts.container\n        this.distance = opts.chunkDistance || 2\n        this.chunkSize = opts.chunkSize || 32\n        this.blockSize = opts.blockSize || 1\n        this.generateVoxelChunk = opts.generateVoxelChunk\n        this.chunks = {}\n        this.mesher = opts.mesher || new CulledMesher()\n        this.textureManager = opts.textureManager\n\n        if (this.chunkSize & this.chunkSize - 1 !== 0)\n            throw new Error('chunkSize must be a power of 2')\n        if (!this.textureManager)\n            throw new Error(\"missing texture manager\")\n\n        //TODO: count the number of bits wide the chunksize is. seems like we could just use Math.log()\n        //ex: if chunksize is 16 the bits is 4\n        //I think bits is just used for efficient multiplication and division.\n        let bits = 0\n        for (let size = this.chunkSize; size > 0; size >>= 1) bits++;\n        this.chunkBits = bits - 1;\n        this.CHUNK_CACHE = {}\n    }\n\n    on(type, cb) {\n        if(!this.listeners[type]) this.listeners[type] = []\n        this.listeners[type].push(cb)\n    }\n    emit(type,evt) {\n        if(!this.listeners[type]) this.listeners[type] = []\n        this.listeners[type].forEach(cb => cb(evt))\n    }\n\n    clear() {\n        Object.keys(this.chunks).forEach(key => {\n            const chunk = this.chunks[key]\n            this.container.remove(chunk.surfaceMesh)\n            chunk.surfaceMesh.geometry.dispose()\n            this.CHUNK_CACHE[chunk.id] = chunk.data\n            chunk.dispose()\n        })\n        this.chunks = {}\n    }\n\n\n    // position in chunk indexes?\n    nearbyChunks(position, distance) {\n        const current = this.chunkAtPosition(position)\n        const x = current[0]\n        const y = current[1]\n        const z = current[2]\n        const dist = distance || this.distance\n        const nearby = []\n        for (let cx = (x - dist); cx !== (x + dist); ++cx) {\n            for (let cy = (y - dist); cy !== (y + dist); ++cy) {\n                for (let cz = (z - dist); cz !== (z + dist); ++cz) {\n                    nearby.push([cx, cy, cz])\n                }\n            }\n        }\n        return nearby\n    }\n\n    //get missing chunks. position is in world coords\n    requestMissingChunks(pos) {\n        this.nearbyChunks(pos).map((chunkIndex) => {\n            if (!this.chunks[chunkIndex.join('|')]) {\n                this.rebuildMesh(this.generateChunk(new Vector3(chunkIndex[0],chunkIndex[1],chunkIndex[2])))\n            }\n        })\n    }\n\n    getBounds(x, y, z) {\n        const bits = this.chunkBits\n        const low = [x << bits, y << bits, z << bits]\n        const high = [(x + 1) << bits, (y + 1) << bits, (z + 1) << bits]\n        return [low, high]\n    }\n\n    //make a chunk at the position in chunk coords\n    generateChunk(pos) {\n        const bounds = this.getBounds(pos.x, pos.y, pos.z)\n        const id = [pos.x,pos.y,pos.z].join('|')\n        let chunkData\n        if(this.CHUNK_CACHE[id]) {\n            chunkData = this.CHUNK_CACHE[id]\n        } else {\n            chunkData = this.generateVoxelChunk(bounds[0], bounds[1], pos)\n        }\n        const chunkObj = new Chunk(chunkData, pos, this.chunkBits)\n        this.chunks[chunkObj.id] = chunkObj\n        return chunkObj\n    }\n\n    makeChunkFromData(info,voxels) {\n        const pos = new Vector3(info.position[0],info.position[1],info.position[2])\n        const chunkData = {\n            low:info.low,\n            high:info.high,\n            voxels:voxels,\n            dims:info.dims,\n        }\n        const chunk = new Chunk(chunkData, pos, this.chunkBits)\n        this.chunks[chunk.id] = chunk\n        return chunk\n    }\n\n    chunkIndexAtCoordinates(x, y, z) {\n        const bits = this.chunkBits\n        const cx = x >> bits\n        const cy = y >> bits\n        const cz = z >> bits\n        return [cx, cy, cz];\n    }\n\n    //position in world coords\n    chunkAtPosition(position) {\n        const pt = position.divideScalar(this.blockSize).floor()\n        return this.chunkIndexAtCoordinates(pt.x, pt.y, pt.z)\n    }\n\n    voxelIndexFromCoordinates(x, y, z) {\n        const bits = this.chunkBits\n        const mask = (1 << bits) - 1\n        return (x & mask) + ((y & mask) << bits) + ((z & mask) << bits * 2)\n    }\n\n    //get voxel at point in world space\n    voxelAtCoordinates(pt) {\n        const ckey = this.chunkIndexAtCoordinates(pt.x, pt.y, pt.z).join('|')\n        const chunk = this.chunks[ckey]\n        if (!chunk) return false\n        return chunk.voxelAtCoordinates(pt)\n    }\n\n    setVoxelAtCoordinates(pt, val) {\n        const ckey = this.chunkIndexAtCoordinates(pt.x, pt.y, pt.z).join('|')\n        const chunk = this.chunks[ckey]\n        if (!chunk) return false\n        const ret = chunk.setVoxelAtCoordinates(pt,val)\n        this.rebuildMesh(chunk)\n        return ret\n    }\n\n    setBlockRange(pos, dim, data) {\n        pos.floor()\n        const ckey = this.chunkIndexAtCoordinates(pos.x, pos.y, pos.z).join('|')\n        const chunk = this.chunks[ckey]\n        const pt = pos.clone()\n        if(chunk) {\n            for(let y=0; y<dim.y; y++) {\n                for(let z=0; z<dim.z; z++) {\n                    for(let x=0; x<dim.x; x++) {\n                        const n = x + z*dim.x + y*dim.x*dim.z\n                        const val = data[n]\n                        pt.x = pos.x + x\n                        pt.y = pos.y + y\n                        pt.z = pos.z + z\n                        if(val !== -1) this.setVoxelAtCoordinates(pt, val)\n                    }\n                }\n            }\n            this.rebuildMesh(chunk)\n        }\n    }\n\n    //get voxel at position in world coordinates\n    voxelAtPosition(pos, val) {\n        return this.voxelAtCoordinates(pos.divideScalar(this.blockSize).floor(),val)\n    }\n\n    //report the number of chunks currently loaded into memory\n    debug_getChunksLoadedCount() {\n        return Object.keys(this.chunks).length\n    }\n\n    /**\n     * remove chunks that are too far away\n     * _pos_ is the center of the chunks to look at\n     * _group_ is the ThreeJS group that the chunks are stored in\n     */\n    removeFarChunks(pos) {\n        const nearbyChunks = this.nearbyChunks(pos,this.distance+1).map(chunkPos => chunkPos.join('|'))\n        Object.keys(this.chunks).map((chunkIndex) => {\n            //skip the nearby chunks\n            if (nearbyChunks.indexOf(chunkIndex) > -1) return\n\n            const chunk = this.chunks[chunkIndex]\n            if (!chunk) return\n            this.container.remove(chunk.surfaceMesh)\n            chunk.surfaceMesh.geometry.dispose()\n            this.CHUNK_CACHE[chunk.id] = chunk.data\n            chunk.dispose()\n            delete this.chunks[chunkIndex]\n        })\n    }\n\n    getBlock(x,y,z) {\n        return this.voxelAtPosition(new Vector3(x,y,z))\n    }\n\n    rebuildMesh(chunk) {\n        if(chunk.surfaceMesh) this.container.remove(chunk.surfaceMesh)\n        chunk.surfaceMesh = new VoxelMesh(chunk, this.mesher, SCALE, this)\n            .createSurfaceMesh(this.textureManager.material)\n        this.container.add(chunk.surfaceMesh)\n        const pos = chunk.realPosition.clone().multiplyScalar(this.chunkSize)\n        chunk.surfaceMesh.position.copy(pos)\n    }\n    rebuildAllMeshes() {\n        Object.keys(this.chunks).forEach(key => this.rebuildMesh(this.chunks[key]))\n    }\n\n    updateCenterPosition(pos) {\n        this.requestMissingChunks(pos)\n        // and remove the chunks that might be out of range now\n        this.removeFarChunks(pos, this.container)\n    }\n}\n\n"
  },
  {
    "path": "src/CulledMesher.js",
    "content": "export class CulledMesher {\n    constructor()\n    {\n\n    }\n\n//Naive meshing (with face culling)\n    mesh(volume, dims)\n    {\n        //Precalculate direction vectors for convenience\n        var dir = new Array(3)\n        for (var i = 0; i < 3; ++i) {\n            dir[i] = [[0, 0, 0], [0, 0, 0]]\n            dir[i][0][(i + 1) % 3] = 1\n            dir[i][1][(i + 2) % 3] = 1\n        }\n        //March over the volume\n        var vertices = []\n            , faces = []\n            , x = [0, 0, 0]\n            , B = [[false, true]    //Incrementally update bounds (this is a bit ugly)\n            , [false, true]\n            , [false, true]]\n            , n = -dims[0] * dims[1]\n        for (B[2] = [false, true], x[2] = -1; x[2] < dims[2]; B[2] = [true, (++x[2] < dims[2] - 1)])\n            for (n -= dims[0], B[1] = [false, true], x[1] = -1; x[1] < dims[1]; B[1] = [true, (++x[1] < dims[1] - 1)])\n                for (n -= 1, B[0] = [false, true], x[0] = -1; x[0] < dims[0]; B[0] = [true, (++x[0] < dims[0] - 1)], ++n) {\n                    //Read current voxel and 3 neighboring voxels using bounds check results\n                    var p = (B[0][0] && B[1][0] && B[2][0]) ? volume[n] : 0\n                        , b = [(B[0][1] && B[1][0] && B[2][0]) ? volume[n + 1] : 0\n                        , (B[0][0] && B[1][1] && B[2][0]) ? volume[n + dims[0]] : 0\n                        , (B[0][0] && B[1][0] && B[2][1]) ? volume[n + dims[0] * dims[1]] : 0\n                    ]\n                    //Generate faces\n                    for (var d = 0; d < 3; ++d)\n                        if ((!!p) !== (!!b[d])) {\n                            var s = !p ? 1 : 0\n                            var t = [x[0], x[1], x[2]]\n                                , u = dir[d][s]\n                                , v = dir[d][s ^ 1]\n                            ++t[d]\n\n                            var vertex_count = vertices.length\n                            vertices.push([t[0], t[1], t[2]])\n                            vertices.push([t[0] + u[0], t[1] + u[1], t[2] + u[2]])\n                            vertices.push([t[0] + u[0] + v[0], t[1] + u[1] + v[1], t[2] + u[2] + v[2]])\n                            vertices.push([t[0] + v[0], t[1] + v[1], t[2] + v[2]])\n                            faces.push([vertex_count, vertex_count + 1, vertex_count + 2, vertex_count + 3, s ? b[d] : p])\n                        }\n                }\n        return {vertices: vertices, faces: faces}\n    }\n\n}\n\n\n// if(exports) {\n//     exports.mesher = CulledMesh;\n// }"
  },
  {
    "path": "src/DesktopControls.js",
    "content": "import {Vector2} from \"three\"\nimport {Pointer} from \"./webxr-boilerplate/Pointer\"\nimport {ECSComp} from './ECSComp.js'\nimport {traceRayAtScreenCoords} from './utils.js'\n\nconst LEFT_MOUSE_BUTTON = 1\nconst RIGHT_MOUSE_BUTTON = 2\n\nexport class DesktopControls extends ECSComp {\n\n    constructor(app, distance) {\n        super()\n        this.app = app\n        this.distance = distance\n        this.canvas = this.app.renderer.domElement\n        this.canvas.addEventListener('contextmenu',e => {\n            e.preventDefault()\n            e.stopPropagation()\n        })\n        this.canvas.addEventListener('mousemove',e => {\n            if(!this.isEnabled()) return\n            const pt = new Vector2(e.clientX,e.clientY)\n            const res = traceRayAtScreenCoords(this.app,pt, this.distance)\n            res.hitPosition.floor()\n            this._fire('highlight',res.hitPosition)\n        })\n        this.canvas.addEventListener('mousedown',e => {\n            if(!this.isEnabled()) return\n            const pt = new Vector2(e.clientX,e.clientY)\n            if(e.buttons === LEFT_MOUSE_BUTTON) {\n                const res = traceRayAtScreenCoords(this.app, pt, this.distance)\n                res.hitPosition.add(res.hitNormal)\n                this._fire('setblock',res.hitPosition)\n            }\n            if(e.buttons === RIGHT_MOUSE_BUTTON) {\n                const res = traceRayAtScreenCoords(this.app, pt, this.distance)\n                this._fire('removeblock',res.hitPosition)\n            }\n        })\n        this.canvas.addEventListener('mouseup',e => {\n        })\n\n        this.pointer = new Pointer(app,{\n            //don't intersect with anything. only use for orientation and trigger state\n            intersectionFilter: o => o.userData.clickable,\n            enableLaser: false,\n            mouseSimulatesController:false,\n        })\n        // this.pointer.disable()\n    }\n    enable() {\n        super.enable()\n        // this.pointer.enable()\n    }\n    disable() {\n        super.disable()\n        // this.pointer.disable()\n    }\n    update(time) {\n        this.pointer.tick(time)\n    }\n}\n"
  },
  {
    "path": "src/ECSComp.js",
    "content": "export class ECSComp {\n    constructor() {\n        this._listeners = {}\n        this._enabled = false\n    }\n    addEventListener(type, cb) {\n        if(!this._listeners[type]) this._listeners[type] = []\n        this._listeners[type].push(cb)\n    }\n\n    _fire(type,payload) {\n        if(!this._listeners[type]) this._listeners[type] = []\n        this._listeners[type].forEach(cb => cb(payload))\n    }\n\n    enable() {\n        this._enabled = true\n    }\n\n    disable() {\n        this._enabled = false\n    }\n\n    isEnabled() {\n        return this._enabled\n    }\n\n    update(time) {\n\n    }\n\n\n}\n"
  },
  {
    "path": "src/FullscreenControls.js",
    "content": "import {Ray, Vector3,} from \"three\"\nimport {traceRay} from './raycast.js'\nimport {ECSComp} from './ECSComp.js'\nimport {toRad, EPSILON} from \"./utils.js\"\n\nconst HAS_POINTER_LOCK = 'pointerLockElement' in document ||\n    'mozPointerLockElement' in document ||\n    'webkitPointerLockElement' in document;\n\n\nfunction requestPointerLock(el) {\n    if(el.requestPointerLock) return el.requestPointerLock()\n    console.log(\"request pointer lock not found\")\n}\n\n\nexport class FullScreenControls extends ECSComp {\n    constructor(app) {\n        super()\n        this.app = app\n\n        this.changeCallback = () => {\n            if(document.pointerLockElement) {\n                // console.log(\"entered pointer lock\")\n            } else {\n                // console.log(\"exited pointer lock\")\n                this.disable()\n            }\n        }\n        this.moveCallback = (e) => {\n            if(!this.isEnabled()) return\n            this.app.stageRot.rotation.y += e.movementX/300\n            this.app.stageRot.rotation.y += e.movementX/300\n\n            if(e.movementY) {\n                this.app.stageRot.rotation.x += e.movementY/500\n                this.app.stageRot.rotation.x = Math.max(this.app.stageRot.rotation.x,toRad(-60))\n                this.app.stageRot.rotation.x = Math.min(this.app.stageRot.rotation.x,toRad(60))\n            }\n\n            const res = this.traceRay()\n            res.hitPosition.floor()\n            this._fire('highlight',res.hitPosition)\n        }\n        this.mousedownCallback = (e) => {\n            if(!this.isEnabled()) return\n            e.preventDefault()\n            const LEFT_MOUSE_BUTTON = 1\n            const RIGHT_MOUSE_BUTTON = 2\n            if(e.buttons === LEFT_MOUSE_BUTTON) {\n                const res = this.traceRay()\n                res.hitPosition.add(res.hitNormal)\n                this._fire('setblock',res.hitPosition)\n            }\n            if(e.buttons === RIGHT_MOUSE_BUTTON) {\n                const res = this.traceRay()\n                this._fire('removeblock',res.hitPosition)\n            }\n        }\n        this.errorCallback = (e) => {\n            console.log(\"error getting pointer lock\",e)\n        }\n        this.contextmenuCallback = (e) => {\n            e.preventDefault()\n            e.stopPropagation()\n        }\n    }\n\n    traceRay() {\n        const target = new Vector3(0,1.6,-1)\n        this.app.stagePos.worldToLocal(target)\n        const pos = new Vector3(0,1.6,0)\n        this.app.stagePos.worldToLocal(pos)\n        const ray = new Ray(pos)\n        ray.lookAt(target)\n\n        const hitNormal = new Vector3(0,0,0)\n        const hitPosition = new Vector3(0,0,0)\n        const hitBlock = traceRay(this.app.chunkManager,ray.origin,ray.direction,this.distance,hitPosition,hitNormal,EPSILON)\n        return {\n            hitBlock:hitBlock,\n            hitPosition:hitPosition,\n            hitNormal: hitNormal\n        }\n    }\n\n    enable() {\n        super.enable()\n        if(HAS_POINTER_LOCK) {\n            // console.log(\"we have pointer lock\")\n            document.addEventListener('pointerlockchange',this.changeCallback,false)\n            document.addEventListener('mousemove',this.moveCallback,false)\n            document.addEventListener('pointerlockerror', this.errorCallback, false);\n            document.addEventListener('mousedown',this.mousedownCallback,false)\n            document.addEventListener('contextmenu',this.contextmenuCallback,false)\n            requestPointerLock(this.app.renderer.domElement)\n        }\n    }\n    disable() {\n        if(!this.isEnabled()) return //don't recurse if already disabled\n        super.disable()\n        document.removeEventListener('pointerlockchange', this.changeCallback, false)\n        document.removeEventListener('mousemove', this.moveCallback, false)\n        document.removeEventListener('pointerlockerror', this.errorCallback, false);\n        document.removeEventListener('contextmenu',this.contextmenuCallback,false)\n        this._fire('exit', this)\n    }\n}\n\n"
  },
  {
    "path": "src/GreedyMesher.js",
    "content": "let mask = new Int32Array(4096);\n\nexport class GreedyMesher {\n    constructor() {\n    }\n\n    mesh(volume, dims) {\n        var vertices = [], faces = []\n            , dimsX = dims[0]\n            , dimsY = dims[1]\n            , dimsXY = dimsX * dimsY;\n\n        //Sweep over 3-axes\n        for (var d = 0; d < 3; ++d) {\n            var i, j, k, l, w, W, h, n, c\n                , u = (d + 1) % 3\n                , v = (d + 2) % 3\n                , x = [0, 0, 0]\n                , q = [0, 0, 0]\n                , du = [0, 0, 0]\n                , dv = [0, 0, 0]\n                , dimsD = dims[d]\n                , dimsU = dims[u]\n                , dimsV = dims[v]\n                , qdimsX, qdimsXY\n                , xd\n\n            if (mask.length < dimsU * dimsV) {\n                mask = new Int32Array(dimsU * dimsV);\n            }\n\n            q[d] = 1;\n            x[d] = -1;\n\n            qdimsX = dimsX * q[1]\n            qdimsXY = dimsXY * q[2]\n\n            // Compute mask\n            while (x[d] < dimsD) {\n                xd = x[d]\n                n = 0;\n\n                for (x[v] = 0; x[v] < dimsV; ++x[v]) {\n                    for (x[u] = 0; x[u] < dimsU; ++x[u], ++n) {\n                        var a = xd >= 0 && volume[x[0] + dimsX * x[1] + dimsXY * x[2]]\n                            ,\n                            b = xd < dimsD - 1 && volume[x[0] + q[0] + dimsX * x[1] + qdimsX + dimsXY * x[2] + qdimsXY]\n                        if (a ? b : !b) {\n                            mask[n] = 0;\n                            continue;\n                        }\n                        mask[n] = a ? a : -b;\n                    }\n                }\n\n                ++x[d];\n\n                // Generate mesh for mask using lexicographic ordering\n                n = 0;\n                for (j = 0; j < dimsV; ++j) {\n                    for (i = 0; i < dimsU;) {\n                        c = mask[n];\n                        if (!c) {\n                            i++;\n                            n++;\n                            continue;\n                        }\n\n                        //Compute width\n                        w = 1;\n                        while (c === mask[n + w] && i + w < dimsU) w++;\n\n                        //Compute height (this is slightly awkward)\n                        for (h = 1; j + h < dimsV; ++h) {\n                            k = 0;\n                            while (k < w && c === mask[n + k + h * dimsU]) k++\n                            if (k < w) break;\n                        }\n\n                        // Add quad\n                        // The du/dv arrays are reused/reset\n                        // for each iteration.\n                        du[d] = 0;\n                        dv[d] = 0;\n                        x[u] = i;\n                        x[v] = j;\n\n                        if (c > 0) {\n                            dv[v] = h;\n                            dv[u] = 0;\n                            du[u] = w;\n                            du[v] = 0;\n                        } else {\n                            c = -c;\n                            du[v] = h;\n                            du[u] = 0;\n                            dv[u] = w;\n                            dv[v] = 0;\n                        }\n                        var vertex_count = vertices.length;\n                        vertices.push([x[0], x[1], x[2]]);\n                        vertices.push([x[0] + du[0], x[1] + du[1], x[2] + du[2]]);\n                        vertices.push([x[0] + du[0] + dv[0], x[1] + du[1] + dv[1], x[2] + du[2] + dv[2]]);\n                        vertices.push([x[0] + dv[0], x[1] + dv[1], x[2] + dv[2]]);\n                        faces.push([vertex_count, vertex_count + 1, vertex_count + 2, vertex_count + 3, c]);\n\n                        //Zero-out mask\n                        W = n + w;\n                        for (l = 0; l < h; ++l) {\n                            for (k = n; k < W; ++k) {\n                                mask[k + l * dimsU] = 0;\n                            }\n                        }\n\n                        //Increment counters and continue\n                        i += w;\n                        n += w;\n                    }\n                }\n            }\n        }\n        return {vertices: vertices, faces: faces};\n    }\n}\n\n"
  },
  {
    "path": "src/KeyboardControls.js",
    "content": "import {Vector3,} from \"three\"\nimport {ECSComp} from './ECSComp.js'\nconst toRad = (deg) => Math.PI / 180 * deg\nconst Y_AXIS = new Vector3(0,1,0)\nconst SPEED = 0.1\n\nexport class KeyboardControls extends ECSComp {\n    constructor(app) {\n        super()\n        this.app = app\n\n        this.keystates = {\n            ArrowLeft:{current:false, previous:false},\n            ArrowRight:{current:false, previous:false},\n            ArrowUp:{current:false, previous:false},\n            ArrowDown:{current:false, previous:false},\n            a: { current: false, previous: false},\n            d: { current: false, previous: false},\n            s: { current: false, previous: false},\n            w: { current: false, previous: false},\n            q: { current: false, previous: false},\n            e: { current: false, previous: false},\n            Enter: { current: false, previous: false},\n            c: { current: false, previous: false},\n        }\n        this.keystates[' '] = { current: false, previous: false}\n\n\n        this._keydown_handler = (e)=>{\n            if(!this.isEnabled()) return\n            if(this.keystates[e.key]) {\n                this.keystates[e.key].current = true\n            }\n        }\n        this._keyup_handler = (e)=>{\n            if(!this.isEnabled()) return\n            if(this.keystates[e.key]) {\n                this.keystates[e.key].current = false\n            }\n        }\n        document.addEventListener('keydown',this._keydown_handler)\n        document.addEventListener('keyup',this._keyup_handler)\n    }\n\n    update(time) {\n        if(this.keystates.ArrowUp.current === true)  this.glideForward()\n        if(this.keystates.ArrowDown.current === true)  this.glideBackward()\n        if(this.keystates.ArrowLeft.current === true)  this.rotateLeft()\n        if(this.keystates.ArrowRight.current === true)  this.rotateRight()\n        if(this.keystates.a.current === true)  this.glideLeft()\n        if(this.keystates.d.current === true)  this.glideRight()\n        if(this.keystates.w.current === true)  this.glideForward()\n        if(this.keystates.s.current === true)  this.glideBackward()\n        if(this.keystates.q.current === true)  this.glideDown()\n        if(this.keystates.e.current === true)  this.glideUp()\n        if(this.keystates[' '].current === true) this.app.player_phys.startJump()\n        if(this.keystates[' '].current === false && this.keystates[' '].previous === true) this.app.player_phys.endJump()\n\n        if(this.keystates.Enter.current === false && this.keystates.Enter.previous === true) {\n            this._fire('show-dialog',this)\n        }\n        if(this.keystates.c.current === true && this.keystates.c.previous === false) {\n            this.app.active = !this.app.active\n            this.app.player_phys.endFlying()\n        }\n\n        Object.keys(this.keystates).forEach(key => {\n            this.keystates[key].previous = this.keystates[key].current\n        })\n    }\n\n    rotateLeft() {\n        this.app.stageRot.rotation.y -= toRad(3)\n    }\n\n    rotateRight() {\n        this.app.stageRot.rotation.y += toRad(3)\n    }\n\n    getSpeedDirection() {\n        const dir = new Vector3(0,0,1)\n        dir.applyAxisAngle(Y_AXIS, -this.app.stageRot.rotation.y)\n        return dir.normalize().multiplyScalar(SPEED)\n    }\n    glideForward() {\n        const vel = this.getSpeedDirection().multiplyScalar(-40)\n        this.app.player_phys.vel.x = vel.x\n        this.app.player_phys.vel.z = vel.z\n        this.app.player_phys.markChanged()\n    }\n    glideBackward() {\n        const vel = this.getSpeedDirection().multiplyScalar(40)\n        this.app.player_phys.vel.x = vel.x\n        this.app.player_phys.vel.z = vel.z\n        this.app.player_phys.markChanged()\n    }\n    glideLeft() {\n        const vel = this.getSpeedDirection().multiplyScalar(40).applyAxisAngle(Y_AXIS,toRad(-90))\n        this.app.player_phys.vel.x = vel.x\n        this.app.player_phys.vel.z = vel.z\n        this.app.player_phys.markChanged()\n    }\n    glideRight() {\n        const vel = this.getSpeedDirection().multiplyScalar(40).applyAxisAngle(Y_AXIS,toRad(90))\n        this.app.player_phys.vel.x = vel.x\n        this.app.player_phys.vel.z = vel.z\n        this.app.player_phys.markChanged()\n    }\n\n    glideUp() {\n        if(!this.app.player_phys.isFlying()) {\n            this.app.player_phys.startFlying()\n        }\n        this.app.player_phys.vel.y = 0.1\n        this.app.player_phys.markChanged()\n    }\n    glideDown() {\n        if(!this.app.player_phys.isFlying()) {\n            this.app.player_phys.startFlying()\n        }\n        this.app.player_phys.vel.y = -0.1\n        this.app.player_phys.markChanged()\n    }\n}\n"
  },
  {
    "path": "src/PhysHandler.js",
    "content": "import {Vector3,} from \"three\"\nimport {ECSComp} from './ECSComp.js'\nconst GRAVITY = new Vector3(0,-9.8,0)\n\nexport class PhysHandler extends ECSComp {\n    constructor(app, target, colliders) {\n        super()\n        this.app = app\n        this.target = target\n        this.colliders = colliders\n        this.vel = new Vector3(0,0,0)\n        this.flying = false\n        this.jumping = false\n    }\n    isFlying() {\n        return this.flying\n    }\n    startFlying() {\n        this.flying = true\n    }\n    endFlying() {\n        this.flying = false\n    }\n    startJump() {\n        if(!this.jumping) {\n            this.jumping = true\n            this.flying = false\n            this.jumpTime = Date.now()\n        }\n    }\n    endJump() {\n        this.jumping = false\n    }\n    markChanged() {\n        this._fire('move',{position:this.target.position})\n    }\n    update(time,dt) {\n        dt = dt/1000\n        // const dt = (time/1000)\n        // console.log(\"tick\",dt)\n        if(!this.flying && this.app.active) {\n            let acc = GRAVITY.y * 0.5\n            this.vel.y += acc * dt\n        }\n        // console.log(\"now\",this.vel.y)\n        const pos = this.target.position.clone()\n        pos.y += this.vel.y*dt\n        pos.z += this.vel.z*dt\n        pos.x += this.vel.x*dt\n        // console.log(this.vel)\n        const diff = new Vector3()\n        diff.y = this.vel.y*dt\n        diff.x = this.vel.x*dt\n        diff.z = this.vel.z*dt\n        this.colliders.forEach(col => {\n            col.collide(this,this.target,pos,diff)\n        })\n\n        //apply final velocity\n        this.target.position.y += this.vel.y\n        this.target.position.z += this.vel.z*dt\n        this.target.position.x += this.vel.x*dt\n\n        //apply some friction\n        this.vel.z *= 0.8\n        this.vel.x *= 0.8\n        if(this.flying) {\n            this.vel.y *= 0.8\n        }\n        // console.log(this.vel.y)\n        this.markChanged()\n    }\n}\n"
  },
  {
    "path": "src/SimpleMeshCollider.js",
    "content": "import {Vector3, Box3} from \"three\"\n\nconst SIZE = new Vector3()\nconst CENTER = new Vector3()\n\nexport function checkHitTileY(voxels, bounds, pos) {\n    bounds.getSize(SIZE)\n    // console.log(\"checking for hits near height\",SIZE.y)\n    //scan in the y direction only for now.\n    let sy = Math.floor(pos.y)\n    let ey = Math.ceil(pos.y+SIZE.y)\n    let sz = Math.floor(pos.z)\n    let sx = Math.floor(pos.x)\n    let ex = Math.ceil(pos.x+SIZE.x)\n    for(let i=sy; i<=ey; i++) {\n        // for(let j=sx; j<ex; j++) {\n        const vox = voxels.voxelAtCoordinates(new Vector3(sx, i, sz))\n        // console.log(\"hit\", vox)\n        if (vox > 0) return true\n        // }\n    }\n    return false\n}\nexport function checkHitTileX(voxels, bounds, pos) {\n    bounds.getCenter(CENTER)\n    let sx = Math.floor(pos.x-CENTER.x)\n    let ex = Math.ceil(pos.x+CENTER.x)\n    let sy = Math.round(pos.y+1)\n    let sz = Math.floor(pos.z)\n    for(let j=sx; j<ex; j++) {\n        const vox = voxels.voxelAtCoordinates(new Vector3(j, sy, sz))\n        if (vox > 0) return true\n    }\n    return false\n}\nexport function checkHitTileZ(voxels, bounds, pos) {\n    bounds.getCenter(CENTER)\n    let sx = Math.floor(pos.x)\n    let sy = Math.round(pos.y+1)\n    let sz = Math.floor(pos.z-CENTER.z)\n    let ez = Math.ceil(pos.z+CENTER.z)\n    for(let k=sz; k<ez; k++) {\n        const vox = voxels.voxelAtCoordinates(new Vector3(sx, sy, k))\n        if (vox > 0) return true\n    }\n    return false\n}\n\nexport class SimpleMeshCollider {\n    constructor(app) {\n        this.app = app\n    }\n    collide(phys, target, pos, diff) {\n        //pos is the potential position. we can choose to veto it\n        this.app.stagePos.position.y = -target.position.y\n        this.app.stagePos.position.z = -target.position.z\n        this.app.stagePos.position.x = -target.position.x\n        if(!this.app.active) {\n            //don't do any physics when the world is paused. just let the user move around\n            // phys.vel.y = 0\n            return\n        }\n        //check if too far beyond terminal velocity\n        if(phys.vel.y < -1) phys.vel.y = -1\n\n\n        const bounds = new Box3(new Vector3(0,0,0),new Vector3(1,1,1))\n        //check downwards\n        if(checkHitTileY(this.app.chunkManager,bounds,pos)) {\n            if(!phys.isFlying()) {\n                phys.vel.y = 0\n            }\n        }\n        //check forwards\n        if(checkHitTileX(this.app.chunkManager,bounds,pos)) {\n            // console.log(\"hit something to the left or right\")\n            phys.vel.x = 0\n        }\n        if(checkHitTileZ(this.app.chunkManager,bounds,pos)) {\n            // console.log(\"hit something to the front or back\")\n            phys.vel.z = 0\n        }\n        //if fell off the world\n        if(pos.y < -30) {\n            console.log(\"fell off the world\")\n            phys.vel.y = 0\n            target.position.y = 10\n            target.position.x = 0\n            target.position.z = 0\n        }\n\n        if(phys.jumping) {\n            const diff = Date.now() - phys.jumpTime;\n            if(diff > 300) {\n                // console.log(\"over one second\")\n            } else {\n                phys.vel.y = 1.0;\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "src/TextureManager.js",
    "content": "import {\n    LinearMipMapLinearFilter,\n    NearestFilter,\n    ShaderMaterial,\n    Texture,\n    VertexColors\n} from \"three\"\n\n// const createAtlas = window.atlaspack\n\n/*\n\n* get what I have working w/o the atlas function\n* switch to 17 x 17 to address lines\n* manually create mip-maps as additional smaller textures\n\n* check out sample3D texture polyfill\n\n*/\n\n\n\nexport class TextureManager {\n    constructor(opts) {\n        this.canvas = document.createElement('canvas')\n        this.canvas.setAttribute('id','texture')\n        // document.getElementsByTagName('body')[0].appendChild(this.canvas)\n        this.aoEnabled = opts.aoEnabled || false\n        this.canvas.width = 128;\n        this.canvas.height = 128;\n        this.canvas.style.width = '512px';\n        this.canvas.style.height = '512px';\n        this.tiles = []\n        // this.atlas = createAtlas(this.canvas);\n        // this.atlas.tilepad = true // this will cost 8x texture memory.\n        this.animated = {}\n        const ctx = this.canvas.getContext('2d')\n\n        this.texturesEnabled = true\n        ctx.fillStyle = 'red'\n        ctx.fillRect(0,0,this.canvas.width,this.canvas.height)\n\n        this.texture = new Texture(this.canvas);\n        this.texture.needsUpdate = true\n        this.texture.magFilter = NearestFilter;\n        this.texture.minFilter = NearestFilter;\n        this.texturePath =  './textures/';\n        this.material = new ShaderMaterial( {\n            uniforms: {\n                'uTime': { value: 0.0 },\n                textureSamp: { value: this.texture},\n                texturesEnabled: { value: this.texturesEnabled },\n            },\n            vertexColors:VertexColors,\n            vertexShader: `\n            attribute vec2 repeat;\n            attribute vec4 subrect;\n            attribute float frameCount;\n            attribute float occlusion;\n            varying vec2 vUv;\n            varying vec2 vRepeat;\n            varying vec4 vSubrect;\n            varying float vFrameCount;\n            varying float vOcclusion;\n            void main() {\n                vUv = uv;\n                vSubrect = subrect;\n                vRepeat = repeat;\n                vFrameCount = frameCount;\n                vOcclusion = occlusion;\n                vec4 mvPosition = modelViewMatrix * vec4(position,1.0);\n                gl_Position = projectionMatrix * mvPosition;\n            } \n            `,\n            fragmentShader: `\n                uniform sampler2D textureSamp;\n                uniform float uTime;\n                uniform bool texturesEnabled;\n                varying vec2 vUv;\n                varying vec2 vRepeat;\n                varying vec4 vSubrect;\n                varying float vFrameCount;\n                varying float vOcclusion;\n                void main() {\n                    vec2 fuv = vUv;\n                    vec4 sr = vSubrect;\n                    //sr.z = sub rect width\n                    //sr.w = sub rect height\n                    float frameCount = 3.0;\n                    // float cframe = mod(uTime,frameCount);\n                    float cframe = mod(uTime,vFrameCount);\n                    float cframe2 = floor(cframe); \n                    sr.x = sr.x + cframe2*sr.z;\n                    fuv.x = sr.x + fract(vUv.x*vRepeat.x)*sr.z;\n                    fuv.y = sr.y + fract(vUv.y*vRepeat.y)*sr.w;\n                    vec4 color = vec4(1.0,1.0,1.0,1.0);\n                    \n                    if(texturesEnabled) {\n                        color = texture2D(textureSamp, fuv);\n                    }\n                    color = color*(vOcclusion);\n                    gl_FragColor = vec4(color.xyz,1.0);\n                }\n            `,\n        } );\n    }\n\n    packImage(img,index) {\n        const info = {\n            index:index,\n            image:img,\n            x:0,\n            y:0,\n            w:16,\n            h:16,\n        }\n        info.x = (info.index*16)%128 + (info.index)*2 + 1\n        info.y = Math.floor(info.index/8)*16 + 1\n        const ctx = this.canvas.getContext('2d')\n        ctx.imageSmoothingEnabled = false\n        //draw image center\n        ctx.drawImage(img,info.x,info.y, info.w, info.h)\n        //left edge\n        ctx.drawImage(img,\n            0,0,1,info.h,\n            info.x-1,info.y,1,info.h)\n        //right edge\n        ctx.drawImage(img,\n            info.w-1,0,1,info.h,\n            info.x+info.w,info.y,1,info.h)\n        //top edge\n        ctx.drawImage(img,\n            0,0,info.w,1,\n            info.x,info.y-1,info.w,1)\n        ctx.drawImage(img,\n            0,info.h-1,info.w,1,\n            info.x,info.y+info.h,info.w,1)\n\n        ctx.fillStyle = 'yellow'\n        // ctx.fillRect(info.x,info.y,info.w,info.h)\n        this.texture.needsUpdate = true\n        return info\n    }\n\n\n\n    isEnabled() {\n        return true\n    }\n\n    update(ttime) {\n        const time = ttime/1000\n        this.material.uniforms.uTime.value = time;\n        this.material.uniforms.texturesEnabled.value = this.texturesEnabled\n    }\n\n    lookupUVsForBlockType(typeNum) {\n        const info = this.tiles[typeNum]\n        if(!info) {\n            const x = 0 / 8.0\n            const x2 = 1 / 8.0\n            const y = 0\n            const y2 = 1 / 8.0\n            return [[x, y], [x2, y], [x2, y2], [x, y2]]\n        }\n        // console.log(x)\n        // console.log(\"looking up type number\",typeNum,info)\n        const x = info.x/128\n        const y = info.y/128\n        const x2 = (info.x+info.w)/128\n        const y2 = (info.y+info.h)/128\n        return [[x,y],[x2,y],[x2,y2],[x,y2]]\n        /*\n        return [\n            [info.x/128,info.y/128],\n            [info.x/128,(info.y+info.h)/128],\n            [(info.x+info.w)/128,(info.y)/128],\n            [(info.x+info.w)/128,(info.y+info.h)/128],\n        ]\n         */\n        // const uvs = this.atlas.uv()[this.names[typeNum-1]]\n        // if(!uvs) return [[0,0],[0,1],[1,1],[1,0]]\n        // return [[0.0,0],[0.0,1],[0,1],[1,0]]\n        // return uvs\n    }\n\n    lookupInfoForBlockType(typeNum) {\n        return {\n            animated:false\n        }\n    }\n\n\n    getBlockTypeForName(name) {\n        return this.names.findIndex(n => n===name)+1\n    }\n\n\n\n    loadTextures(infos) {\n        const proms = infos.map((info,index) => {\n            console.log(\"loading\",info.src)\n            return new Promise((res,rej)=>{\n                const img = new Image()\n                img.id = info.src\n                img.src = info.src\n                img.onload = () => {\n                    res(this.packImage(img,index))\n                }\n                img.onerror = (e) => {\n                    console.error(`Couldn't load texture from url ${infos.src}`)\n                    rej(e)\n                }\n            })\n        })\n        return Promise.all(proms).then((infos)=>{\n            this.tiles = infos\n            this.texture.needsUpdate = true\n        })\n    }\n\n}\n\nfunction ext(name) {\n    return (String(name).indexOf('.') !== -1) ? name : name + '.png';\n}\n"
  },
  {
    "path": "src/TouchControls.js",
    "content": "import {Vector2, Vector3,} from \"three\"\nimport {ECSComp} from './ECSComp.js'\nimport {$, DIRS, on, toRad, traceRayAtScreenCoords} from './utils.js'\n\nconst Y_AXIS = new Vector3(0,1,0)\nconst SPEED = 0.1\n\n\nexport class TouchControls extends ECSComp {\n    isTouchEnabled() {\n        return ('ontouchstart' in document.documentElement)\n    }\n    constructor(app, distance, chunkManager) {\n        super()\n        this.app = app\n        this.canvas = this.app.container\n        this.distance = distance\n        this.chunkManager = chunkManager\n\n        this.dir_button = 'none'\n\n        let point = new Vector2()\n        let startAngleY = 0\n        let startAngleX = 0\n        let startTime = 0\n        let timeoutID\n        let intervalID\n        let mode = 'node'\n        let currentPoint=  new Vector2()\n        this.touchStart = (e) => {\n            e.preventDefault()\n            startAngleY = this.app.stageRot.rotation.y\n            startAngleX = this.app.stageRot.rotation.x\n            if(e.changedTouches.length <= 0) return\n            const tch = e.changedTouches[0]\n            point.set(tch.clientX, tch.clientY)\n            currentPoint.copy(point)\n            startTime = Date.now()\n            const res = traceRayAtScreenCoords(this.app,point, this.distance)\n            res.hitPosition.add(res.hitNormal)\n            res.hitPosition.floor()\n            this._fire('highlight',res)\n            timeoutID = setTimeout(this.startRemoval,1000)\n        }\n        this.startRemoval = () => {\n            mode = 'remove'\n            const res = traceRayAtScreenCoords(this.app,currentPoint, this.distance)\n            res.hitPosition.floor()\n            this._fire('highlight',res)\n            this._fire('removeblock',res.hitPosition)\n            intervalID = setInterval(this.removeAgain,500)\n        }\n        this.removeAgain = () => {\n            const res = traceRayAtScreenCoords(this.app, currentPoint, this.distance)\n            res.hitPosition.floor()\n            this._fire('highlight',res)\n            this._fire('removeblock',res.hitPosition)\n        }\n        this.touchMove = (e) => {\n            e.preventDefault()\n            if(e.changedTouches.length <= 0) return\n            const tch = e.changedTouches[0]\n            const pt2 = new Vector2(tch.clientX, tch.clientY)\n            const diffx = pt2.x - point.x\n            const diffy = pt2.y - point.y\n            this.app.stageRot.rotation.y = +diffx/150 + startAngleY\n            this.app.stageRot.rotation.x = +diffy/200 + startAngleX\n\n            currentPoint.copy(pt2)\n            const res = traceRayAtScreenCoords(this.app, pt2, this.distance)\n            if(mode === 'add') {\n                res.hitPosition.add(res.hitNormal)\n            }\n            res.hitPosition.floor()\n            this._fire('highlight',res)\n\n            if(this.mode === 'remove') {\n                this._fire('removeblock',res.hitPosition)\n            }\n        }\n        this.touchEnd = (e) => {\n            e.preventDefault()\n            clearTimeout(timeoutID)\n            clearInterval(intervalID)\n            mode = 'node'\n            if(e.changedTouches.length <= 0) return\n            const tch = e.changedTouches[0]\n            const pt2 = new Vector2(tch.clientX, tch.clientY)\n\n            const endTime = Date.now()\n            if(point.distanceTo(pt2) < 10) {\n\n                const res = traceRayAtScreenCoords(this.app, pt2, this.distance)\n                if(endTime - startTime > 500) {\n                    this._fire('removeblock',res.hitPosition)\n                } else {\n                    res.hitPosition.add(res.hitNormal)\n                    this._fire('setblock', res.hitPosition)\n                }\n            }\n        }\n\n        this.attachButton = (b,dir) => {\n            on(b,'touchstart',e => {\n                e.preventDefault()\n                e.stopPropagation()\n                this.dir_button = dir\n            })\n            on(b,'touchmove',e => {\n                e.preventDefault()\n                e.stopPropagation()\n            })\n            on(b,'touchend',e => {\n                e.preventDefault()\n                e.stopPropagation()\n                this.dir_button = DIRS.NONE\n            })\n            on(b,'mousedown',e => {\n                e.preventDefault()\n                this.dir_button = dir\n            })\n            on(b,'mouseup',e => {\n                e.preventDefault()\n                this.dir_button = DIRS.NONE\n            })\n        }\n        this.attachButton ($(\"#left\"),DIRS.LEFT)\n        this.attachButton ($(\"#right\"),DIRS.RIGHT)\n        this.attachButton ($(\"#up\"),DIRS.UP)\n        this.attachButton ($(\"#down\"),DIRS.DOWN)\n\n        const overlay = $(\"#touch-overlay\")\n        const menuButton = document.createElement('button')\n        menuButton.id = 'menu-button'\n        overlay.appendChild(menuButton)\n        menuButton.innerText = 'Menu'\n\n        function setupTouchButton(sel,cb) {\n            on(sel,'touchstart',e => {\n                e.preventDefault()\n                e.stopPropagation()\n            })\n            on(sel,'touchmove',e => {\n                e.preventDefault()\n                e.stopPropagation()\n            })\n            on(sel,'touchend',e => {\n                e.preventDefault()\n                e.stopPropagation()\n                cb()\n            })\n            on(sel,'mousedown',e => {\n                e.preventDefault()\n                e.stopPropagation()\n            })\n            on(sel, 'mouseup', e => {\n                e.preventDefault()\n                e.stopPropagation()\n                cb()\n            })\n        }\n        setupTouchButton(menuButton,()=>this._fire('show-dialog',this))\n\n        const exitButton = document.createElement('button')\n        overlay.appendChild(exitButton)\n        exitButton.innerText = 'Exit'\n        exitButton.id = \"exit-fullscreen\"\n        setupTouchButton(exitButton, ()=>this.app.exitFullscreen())\n\n    }\n\n\n\n    update() {\n        if(this.dir_button === DIRS.LEFT) this.glideLeft()\n        if(this.dir_button === DIRS.RIGHT) this.glideRight()\n        if(this.dir_button === DIRS.UP) this.glideForward()\n        if(this.dir_button === DIRS.DOWN) this.glideBackward()\n    }\n    enable() {\n        super.enable()\n        $(\"#touch-overlay\").style.display = 'block'\n        this.canvas.addEventListener('touchstart',this.touchStart,false)\n        this.canvas.addEventListener('touchmove',this.touchMove,false)\n        this.canvas.addEventListener('touchend',this.touchEnd,false)\n    }\n    disable() {\n        if(!this.isEnabled()) return //don't recurse if already disabled\n        super.disable()\n        $(\"#touch-overlay\").style.display = 'none'\n        this.canvas.removeEventListener('touchstart',this.touchStart)\n        this.canvas.removeEventListener('touchmove',this.touchMove)\n        this.canvas.removeEventListener('touchend',this.touchEnd)\n    }\n\n    glideForward() {\n        const vel = this.getSpeedDirection().multiplyScalar(-40)\n        this.app.player_phys.vel.x = vel.x\n        this.app.player_phys.vel.z = vel.z\n        this.app.player_phys.markChanged()\n    }\n    glideBackward() {\n        const vel = this.getSpeedDirection().multiplyScalar(40)\n        this.app.player_phys.vel.x = vel.x\n        this.app.player_phys.vel.z = vel.z\n        this.app.player_phys.markChanged()\n    }\n    getSpeedDirection() {\n        const dir = new Vector3(0,0,1)\n        dir.applyAxisAngle(Y_AXIS, -this.app.stageRot.rotation.y)\n        return dir.normalize().multiplyScalar(SPEED)\n    }\n    glideLeft() {\n        const vel = this.getSpeedDirection().multiplyScalar(40).applyAxisAngle(Y_AXIS,toRad(-90))\n        this.app.player_phys.vel.x = vel.x\n        this.app.player_phys.vel.z = vel.z\n        this.app.player_phys.markChanged()\n    }\n    glideRight() {\n        // this.app.stagePos.position.add(this.getSpeedDirection().applyAxisAngle(Y_AXIS,toRad(-90)))\n        const vel = this.getSpeedDirection().multiplyScalar(40).applyAxisAngle(Y_AXIS,toRad(90))\n        this.app.player_phys.vel.x = vel.x\n        this.app.player_phys.vel.z = vel.z\n        this.app.player_phys.markChanged()\n    }\n}\n"
  },
  {
    "path": "src/VRControls.js",
    "content": "import {Vector3,} from \"three\"\nimport {Pointer, POINTER_CLICK} from \"./webxr-boilerplate/Pointer\"\nimport {traceRay} from \"./raycast.js\"\nimport {ECSComp} from './ECSComp.js'\nimport {DIRS, toRad} from \"./utils.js\"\n\n\nconst Y_AXIS = new Vector3(0,1,0)\nconst SPEED = 0.1\n\nconst TRIGGER = 'trigger'\n\nexport class VRControls extends ECSComp {\n\n    constructor(app) {\n        super()\n        this.app = app\n        this.distance = 30\n        this.states = { touchpad: false}\n        \n        this.pointer = new Pointer(app,{\n            //don't intersect with anything. only use for orientation and trigger state\n            intersectionFilter: o => false,\n            enableLaser: true,\n            mouseSimulatesController:false,\n        })\n        this.pointer.on(POINTER_CLICK, () => {\n            if(!this.isEnabled()) return\n            const res = this.traceRay()\n            this._fire(TRIGGER,res)\n        })\n\n        // this.activeDir = DIRS.NONE\n    }\n\n    traceRay() {\n        const direction = new Vector3(0, 0, -1)\n        direction.applyQuaternion(this.pointer.controller1.quaternion)\n        direction.applyAxisAngle(Y_AXIS,-this.app.stageRot.rotation.y)\n\n        const pos = this.app.stagePos.worldToLocal(this.pointer.controller1.position.clone())\n\n        const epilson = 1e-8\n        const hitNormal = new Vector3(0,0,0)\n        const hitPosition = new Vector3(0,0,0)\n        const hitBlock = traceRay(this.app.chunkManager,pos,direction,this.distance,hitPosition,hitNormal,epilson)\n        return {\n            hitBlock:hitBlock,\n            hitPosition:hitPosition,\n            hitNormal: hitNormal\n        }\n    }\n\n    rotateLeft() {\n        this.app.stageRot.rotation.y -= toRad(30)\n    }\n    rotateRight() {\n        this.app.stageRot.rotation.y += toRad(30)\n    }\n    getSpeedDirection() {\n        const direction = new Vector3(0, 0, 1)\n        //apply the controller rotation to it\n        direction.applyQuaternion(this.pointer.controller1.quaternion)\n        //apply the stage rotation to it\n        direction.applyAxisAngle(Y_AXIS,-this.app.stageRot.rotation.y)\n        return direction.normalize().multiplyScalar(SPEED)\n    }\n    glideBackward() {\n        this.app.stagePos.position.add(this.getSpeedDirection().multiplyScalar(-1))\n    }\n    glideForward() {\n        this.app.stagePos.position.add(this.getSpeedDirection())\n    }\n\n    update(time) {\n        this.scanGamepads(time)\n        this.updateCursor(time)\n    }\n\n    updateCursor(time) {\n        this.pointer.tick(time)\n        const res = this.traceRay()\n        res.hitPosition.floor()\n        this._fire('highlight',res)\n    }\n\n    scanGamepads(time) {\n        const gamepads = navigator.getGamepads()\n        for(let i=0; i<gamepads.length; i++) {\n            const gamepad = gamepads[i]\n            if ( gamepad && ( gamepad.id === 'Daydream Controller' ||\n                gamepad.id === 'Gear VR Controller' || gamepad.id === 'Oculus Go Controller' ||\n                gamepad.id === 'OpenVR Gamepad' || gamepad.id.startsWith( 'Oculus Touch' ) ||\n                gamepad.id.startsWith( 'Spatial Controller' ) ) ) {\n                //we should have at least two buttons\n                if(gamepad.buttons.length < 2) return\n                this.updateGamepad(gamepad, time)\n            }\n\n        }\n    }\n\n    updateGamepad(gamepad, time) {\n        // const touchpad = gamepad.buttons[0]\n        // console.log(\"axes\", gamepad.axes[0], gamepad.axes[1])\n        if(!this.states[gamepad.id]) {\n            this.states[gamepad.id] = {}\n        }\n\n\n\n        let newDir = DIRS.NONE\n        console.log(gamepad.axes[1])\n        if(gamepad.axes[1] < -0.5) newDir = DIRS.UP\n        if(gamepad.axes[1] > +0.5) newDir = DIRS.DOWN\n        if(gamepad.axes[0] < -0.5) newDir = DIRS.LEFT\n        if(gamepad.axes[0] > +0.5) newDir = DIRS.RIGHT\n\n        let activeDir = this.states[gamepad.id]\n        if(!activeDir) activeDir = DIRS.NONE\n        if(activeDir === DIRS.NONE && newDir !== DIRS.NONE) {\n            if(newDir === DIRS.LEFT) {\n                this._fire('toggle-pointer',this)\n            }\n            if(newDir === DIRS.RIGHT) {\n                this._fire('show-dialog',this)\n            }\n        }\n        if(newDir === DIRS.UP) {\n            this.glideForward()\n        }\n        if(newDir === DIRS.DOWN) {\n            this.glideBackward()\n        }\n\n\n        activeDir = newDir\n        this.states[gamepad.id] = activeDir\n\n        // console.log(\"prev dir\",this.activeDir)\n        //on click start\n        // if(/*touchpad.pressed === true &&*/ this.states.touchpad === false) {\n/*            if(gamepad.axes && gamepad.axes.length === 2) {\n                // this.activeDir = DIRS.NONE\n                if(gamepad.axes[1] < -0.2) this.activeDir = DIRS.UP\n                if(gamepad.axes[1] > +0.4) this.activeDir = DIRS.DOWN\n\n                if(this.activeDir === DIRS.NONE) {\n                    if(gamepad.axes[0] < -0.5) this.activeDir = DIRS.LEFT\n                    if(gamepad.axes[0] > +0.5) this.activeDir = DIRS.RIGHT\n                }\n            }*/\n        // }\n        // console.log('new dir', this.activeDir)\n\n        /*\n        //on click end\n        //left and right clicks\n        if(touchpad.pressed === false && this.states.touchpad === true) {\n            if(this.activeDir === DIRS.LEFT) {\n                // console.log(\"left click\")\n                this._fire('toggle-pointer',this)\n            }\n            if(this.activeDir === DIRS.RIGHT) {\n                // console.log(\"right click\")\n                this._fire('show-dialog',this)\n            }\n        }\n\n        //movement\n        if(touchpad.pressed) {\n            if(this.activeDir === DIRS.UP) {\n                // console.log(\"moving\", this.activeDir)\n                this.glideForward()\n            }\n            if(this.activeDir === DIRS.DOWN) {\n                // console.log(\"moving\", this.activeDir)\n                this.glideBackward()\n            }\n        }*/\n\n        /*\n        //swipe detection\n        if(!touchpad.pressed && gamepad.axes[0] < -0.5) {\n            if(!this.startRight) {\n                this.startLeft = true\n                this.timeStart = time\n            }\n            const diff = time - this.timeStart\n            if(this.startRight && diff < 250) {\n                // console.log('swiped left')\n                this.rotateRight()\n            }\n            this.startRight = false\n        }\n        //swipe detection\n        if(!touchpad.pressed && gamepad.axes[0] > +0.5) {\n            if(!this.startLeft) {\n                this.startRight = true\n                this.timeStart = time\n            }\n            const diff = time - this.timeStart\n            if(this.startLeft && diff < 250) {\n                // console.log('swiped right')\n                this.rotateLeft()\n            }\n            this.startLeft = false\n        }\n        if(!touchpad.pressed && gamepad.axes[0] === 0 && gamepad.axes[1] === 0) {\n            this.startLeft = false\n            this.startRight = false\n        }\n         */\n\n        // this.states.touchpad = touchpad.pressed\n\n    }\n}\n"
  },
  {
    "path": "src/VRStats.js",
    "content": "import {Vector3, Mesh, MeshLambertMaterial, BoxBufferGeometry,\n    CanvasTexture, PlaneGeometry, MeshBasicMaterial,\n} from \"three\"\nimport {ECSComp} from \"./ECSComp.js\"\n\nexport default class VRStats extends ECSComp {\n    constructor(app) {\n        super();\n        this.app = app\n        // this.renderer = renderer\n        const can = document.createElement('canvas')\n        can.width = 256\n        can.height = 128\n        this.canvas = can\n        const c = can.getContext('2d')\n        c.fillStyle = '#00ffff'\n        c.fillRect(0,0,can.width,can.height)\n        const ctex = new CanvasTexture(can)\n        const mesh = new Mesh(\n            new PlaneGeometry(1,0.5),\n            new MeshBasicMaterial({map:ctex})\n        )\n        mesh.position.z = -3\n        mesh.position.y = 2.5\n        mesh.material.depthTest = false\n        mesh.material.depthWrite = false\n        mesh.renderOrder = 1000\n        // this.add(mesh)\n        this.cmesh = mesh\n\n        this.last = 0\n        this.lastFrame = 0\n        this.customProps = {}\n        this.app.scene.add(mesh)\n    }\n\n    update(time) {\n        if(time - this.last > 300) {\n            // console.log(\"updating\",this.rendereer.info)\n            // console.log(`stats calls:`,this.renderer.info)\n\n            const fps = ((this.app.renderer.info.render.frame - this.lastFrame)*1000)/(time-this.last)\n            // console.log(fps)\n\n            const c = this.canvas.getContext('2d')\n            c.fillStyle = 'white'\n            c.fillRect(0, 0, this.canvas.width, this.canvas.height)\n            c.fillStyle = 'black'\n            c.font = '16pt sans-serif'\n            c.fillText(`calls: ${this.app.renderer.info.render.calls}`, 3, 20)\n            c.fillText(`tris : ${this.app.renderer.info.render.triangles}`, 3, 40)\n            c.fillText(`fps : ${fps.toFixed(2)}`,3,60)\n            Object.keys(this.customProps).forEach((key,i) => {\n                const val = this.customProps[key]\n                c.fillText(`${key} : ${val}`,3,80+i*20)\n            })\n            this.cmesh.material.map.needsUpdate = true\n            this.last = time\n            this.lastFrame = this.app.renderer.info.render.frame\n        }\n    }\n\n    setProperty(name, value) {\n        this.customProps[name] = value\n    }\n\n}\n"
  },
  {
    "path": "src/VoxelMesh.js",
    "content": "import {\n    Color,\n    Face3,\n    FaceColors,\n    Geometry,\n    BufferGeometry,\n    Mesh,\n    MeshLambertMaterial,\n    Vector2,\n    Vector3,\n    MeshNormalMaterial,\n    Float32BufferAttribute,\n    BufferAttribute,\n} from \"three\"\n\n\nfunction generateAmbientOcclusion(grid) {\n    return [\n        vertexAO(grid[3], grid[1], grid[0])/3.0,\n        vertexAO(grid[1], grid[5], grid[2])/3.0,\n        vertexAO(grid[5], grid[7], grid[8])/3.0,\n        vertexAO(grid[3], grid[7], grid[6])/3.0\n    ]\n}\n\nfunction generateGrid(chunkManager,pos,indexes,vertices) {\n    const quad = []\n    for(let r=0; r<4; r++) {\n        quad.push(new Vector3(\n            vertices[indexes[r]][0],\n            vertices[indexes[r]][1],\n            vertices[indexes[r]][2],\n            ))\n    }\n\n    const ab = new Vector3()\n    ab.copy(quad[1])\n    ab.sub(quad[0])\n\n    const ad = new Vector3()\n    ad.copy(quad[3])\n    ad.sub(quad[0])\n\n    const anorm = new Vector3()\n    anorm.copy(ab)\n    anorm.cross(ad)\n\n    const grid = []\n    const pt2 = new Vector3()\n    for(let q=-1; q<2; q++) {\n        for(let p=-1;p<2; p++) {\n            pt2.copy(pos)\n            pt2.x += ab.x * p\n            pt2.y += ab.y * p\n            pt2.z += ab.z * p\n            pt2.x += ad.x * q\n            pt2.y += ad.y * q\n            pt2.z += ad.z * q\n            pt2.x += anorm.x * 1\n            pt2.y += anorm.y * 1\n            pt2.z += anorm.z * 1\n            const type =chunkManager.voxelAtCoordinates(pt2)\n            grid.push(type>0?1:0)\n        }\n    }\n    return grid\n}\n\nexport class VoxelMesh {\n    constructor(chunk, mesher, scaleFactor, chunkManager) {\n        this.data = chunk\n        const geometry = this.geometry = new BufferGeometry()\n        this.scale = scaleFactor || new Vector3(10, 10, 10)\n\n        const result = mesher.mesh(chunk.voxels, chunk.dims)\n        this.meshed = result\n\n        //create empty geometry\n        const vertices = []\n        const repeatUV = []\n        const subrects = []\n\n        //copy all verticies in from meshed data\n        for (let i = 0; i < result.vertices.length; ++i) {\n            let q = result.vertices[i]\n            vertices.push(q[0],q[1],q[2])\n        }\n\n        const indices = []\n        const normaluvs = []\n        const frameCount = []\n        const occlusion = []\n        // if(result.faces.length > 0) console.log(result)\n        /*\n            generate faces from meshed data\n\n            Note: that quad faces do not use shared vertices. There will always be faces*4 vertices, even\n            if some of the faces could share vertices because all attributes are per vertex, and\n            those values, such as normals, cannot be shared even if the vertex positions could be.\n\n            each face is represented by two triangles using indexes and one set of uvs (4) for the whole\n            face.\n        */\n\n        const chunkOffset = chunk.realPosition.clone().multiplyScalar(16)\n        for (let i = 0; i < result.faces.length; ++i) {\n            let q = result.faces[i]\n            const info = chunkManager.textureManager.lookupInfoForBlockType(q[4])\n            const realUVs = chunkManager.textureManager.lookupUVsForBlockType(q[4])\n            // if(i==0) console.log(realUVs)\n            const a = q[0]\n            const b = q[1]\n            const c = q[2]\n            const d = q[3]\n\n            //make two triangles\n            /*\n                d --- c\n                |     |\n                a --- b\n             */\n            indices.push(a,b,d)\n            indices.push(b,c,d)\n            let repU = 1\n            let repV = 1\n            const {size, spans} = this.faceVertexUv(i)\n\n            let ao = [1,1,1,1]\n\n            let uv_a = new Vector2(0,0)\n            let uv_b = new Vector2(1,0)\n            let uv_c = new Vector2(1,1)\n            let uv_d = new Vector2(0,1)\n\n            const pos = new Vector3()\n\n            if(size.x > 0 && size.y > 0) {\n                // console.log(\"front or back\", size, uvs, spans)\n\n                if(spans.x0 > spans.x1) {\n                    //calculate AO for back face\n                    repU = size.x\n                    repV = size.y\n                    pos.set(result.vertices[a][0], result.vertices[a][1], result.vertices[a][2])\n                    pos.add(chunkOffset)\n                    //rotate UVs by 90 degrees\n                    normaluvs.push(\n                        uv_b.x,uv_b.y,\n                        uv_c.x, uv_c.y,\n                        uv_d.x,uv_d.y,\n                        uv_a.x,uv_a.y,\n                    )\n                } else {\n                    //calculate AO for front face\n                    repU = size.x\n                    repV = size.y\n                    pos.set(result.vertices[a][0], result.vertices[a][1], result.vertices[a][2]-1)\n                    pos.add(chunkOffset)\n                    //set standard uvs for the whole quad\n                    normaluvs.push(\n                        uv_a.x,uv_a.y,\n                        uv_b.x,uv_b.y,\n                        uv_c.x, uv_c.y,\n                        uv_d.x,uv_d.y,\n                        )\n                }\n            }\n\n\n            //top and bottom\n            if(size.z > 0 && size.x > 0) {\n                if(spans.x0 > spans.x1) {\n                    //calculate AO for top face\n                    repU = size.z\n                    repV = size.x\n                    pos.set(result.vertices[a][0], result.vertices[a][1]-1, result.vertices[a][2])\n                    pos.add(chunkOffset)\n                    //set standard uvs for the whole quad\n                    normaluvs.push(\n                        uv_a.x, uv_a.y,\n                        uv_b.x, uv_b.y,\n                        uv_c.x, uv_c.y,\n                        uv_d.x, uv_d.y,\n                    )\n                } else {\n                    // bottom\n                    repU = size.x\n                    repV = size.z\n                    pos.set(result.vertices[a][0], result.vertices[a][1], result.vertices[a][2])\n                    pos.add(chunkOffset)\n                    //set standard uvs for the whole quad\n                    normaluvs.push(\n                        uv_a.x, uv_a.y,\n                        uv_b.x, uv_b.y,\n                        uv_c.x, uv_c.y,\n                        uv_d.x, uv_d.y,\n                    )\n                }\n            }\n\n            //left and right\n            if(size.z > 0 && size.y > 0) {\n                if(spans.y0 > spans.y1) {\n                    //left side\n                    repU = size.z\n                    repV = size.y\n                    pos.set(result.vertices[a][0], result.vertices[a][1], result.vertices[a][2])\n                    pos.add(chunkOffset)\n                    //set standard uvs for the whole quad\n                    normaluvs.push(uv_a.x,uv_a.y, uv_b.x,uv_b.y, uv_c.x, uv_c.y, uv_d.x,uv_d.y)\n                } else {\n                    //right side\n                    repU = size.z\n                    repV = size.y\n                    pos.set(result.vertices[a][0]-1, result.vertices[a][1], result.vertices[a][2])\n                    pos.add(chunkOffset)\n                    //rotate UVs by 90 degrees\n                    normaluvs.push(\n                        uv_b.x,uv_b.y,\n                        uv_c.x,uv_c.y,\n                        uv_d.x,uv_d.y,\n                        uv_a.x,uv_a.y,\n                    )\n                }\n            }\n\n            if(chunkManager.textureManager.aoEnabled) {\n                const grid = generateGrid(chunkManager,pos,q,result.vertices)\n                ao = generateAmbientOcclusion(grid)\n                occlusion.push(ao[0], ao[1], ao[2], ao[3])\n            } else {\n                occlusion.push(1,1,1,1)\n            }\n\n            for(let j=0; j<4; j++) {\n                repeatUV.push(repU, repV);\n            }\n\n\n            const rect = {\n                x:realUVs[0][0],\n                y:1.0 - realUVs[0][1],\n                w:realUVs[1][0] - realUVs[0][0],\n                h:realUVs[2][1] - realUVs[1][1],\n            }\n            // if(i===0) console.log(rect)\n            let fc = 1\n            if(info.animated) {\n                fc = rect.w/rect.h\n                rect.w = rect.h\n            }\n            //flip the y axis properly\n            rect.y = 1.0 - realUVs[0][1] - rect.h\n            for(let j=0; j<4; j++) {\n                subrects.push(rect.x,rect.y,rect.w,rect.h)\n            }\n\n            for(let j=0; j<4; j++) {\n                frameCount.push(fc)\n            }\n        }\n        geometry.setIndex(indices)\n        geometry.addAttribute('position',new Float32BufferAttribute(vertices,3))\n        geometry.addAttribute('uv', new Float32BufferAttribute(normaluvs,2))\n        geometry.addAttribute('subrect',new Float32BufferAttribute(subrects,4))\n        geometry.addAttribute('repeat', new Float32BufferAttribute(repeatUV,2))\n        geometry.addAttribute('frameCount',new Float32BufferAttribute(frameCount,1))\n        geometry.addAttribute('occlusion',new Float32BufferAttribute(occlusion,1))\n\n        geometry.computeFaceNormals()\n        geometry.uvsNeedUpdate = true\n        geometry.verticesNeedUpdate = true\n        geometry.elementsNeedUpdate = true\n        geometry.normalsNeedUpdate = true\n\n        geometry.computeBoundingBox()\n        geometry.computeBoundingSphere()\n\n    }\n\n    createSurfaceMesh(material) {\n        const surfaceMesh = new Mesh(this.geometry, material)\n        surfaceMesh.scale.copy(this.scale)\n        this.surfaceMesh = surfaceMesh\n        return surfaceMesh\n    }\n\n    faceVertexUv(i) {\n        let height\n        let width\n        const vs = [\n            this.meshed.vertices[i * 4 + 0],\n            this.meshed.vertices[i * 4 + 1],\n            this.meshed.vertices[i * 4 + 2],\n            this.meshed.vertices[i * 4 + 3]\n        ]\n        const spans = {\n            x0: vs[0][0] - vs[1][0],\n            x1: vs[1][0] - vs[2][0],\n            y0: vs[0][1] - vs[1][1],\n            y1: vs[1][1] - vs[2][1],\n            z0: vs[0][2] - vs[1][2],\n            z1: vs[1][2] - vs[2][2]\n        }\n        const size = {\n            x: Math.max(Math.abs(spans.x0), Math.abs(spans.x1)),\n            y: Math.max(Math.abs(spans.y0), Math.abs(spans.y1)),\n            z: Math.max(Math.abs(spans.z0), Math.abs(spans.z1))\n        }\n\n        if (size.x === 0) {\n            if (spans.y0 > spans.y1) {\n                width = size.y\n                height = size.z\n            } else {\n                width = size.z\n                height = size.y\n            }\n        }\n        if (size.y === 0) {\n            if (spans.x0 > spans.x1) {\n                width = size.x\n                height = size.z\n            } else {\n                width = size.z\n                height = size.x\n            }\n        }\n        if (size.z === 0) {\n            if (spans.x0 > spans.x1) {\n                width = size.x\n                height = size.y\n            } else {\n                width = size.y\n                height = size.x\n            }\n        }\n\n    /*\n        let uvs = []\n        if ((size.z === 0 && spans.x0 < spans.x1) || (size.x === 0 && spans.y0 > spans.y1)) {\n            uvs = [\n                new Vector2(height, 0),\n                new Vector2(0, 0),\n                new Vector2(0, width),\n                new Vector2(height, width)\n            ]\n        } else {\n            uvs = [\n                new Vector2(0, 0),\n                new Vector2(0, height),\n                new Vector2(width, height),\n                new Vector2(width, 0)\n            ]\n        }\n        */\n        return {size, spans}\n\n    }\n}\n\n\nfunction vertexAO(side1, side2, corner) {\n    if(side1 && side2) {\n        return 0\n    }\n    return 3 - (side1 + side2 + corner)\n}\n"
  },
  {
    "path": "src/VoxelTexture.js",
    "content": "// var isTransparent = require('opaque').transparent;\n\nimport {\n    Color,\n    DoubleSide,\n    FaceColors,\n    LinearMipMapLinearFilter,\n    MeshBasicMaterial,\n    MeshLambertMaterial,\n    NearestFilter,\n    MeshFaceMaterial,\n    Texture,\n} from \"three\"\n\nconst createAtlas = window.atlaspack\n\nexport class VoxelTexture {\n    constructor(opts) {\n        this.game = opts.game;\n        delete opts.game;\n        this.materials = [];\n        this.transparents = [];\n        this.texturePath = opts.texturePath || '/textures/';\n        this.loading = 0;\n        // this.ao = require('voxel-fakeao')(this.game);\n\n        var useFlatColors = opts.materialFlatColor === true;\n        delete opts.materialFlatColor;\n\n        this.options = defaults(opts || {}, {\n            crossOrigin: 'Anonymous',\n            materialParams: defaults(opts.materialParams || {}, {\n                ambient: 0xbbbbbb,\n                transparent: false,\n                side: DoubleSide,\n            }),\n            materialTransparentParams: defaults(opts.materialTransparentParams || {}, {\n                ambient: 0xbbbbbb,\n                transparent: true,\n                side: DoubleSide,\n                //depthWrite: false,\n                //depthTest: false\n            }),\n            materialType: MeshLambertMaterial,\n            applyTextureParams: function (map) {\n                map.magFilter = NearestFilter;\n                map.minFilter = LinearMipMapLinearFilter;\n            }\n        });\n\n        // create a canvas for the texture atlas\n        this.canvas = (typeof document !== 'undefined') ? document.createElement('canvas') : {};\n        this.canvas.width = opts.atlasWidth || 512;\n        this.canvas.height = opts.atlasHeight || 512;\n        var ctx = this.canvas.getContext('2d');\n        ctx.fillStyle = 'black';\n        ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);\n\n        // create core atlas and texture\n        this.atlas = createAtlas(this.canvas);\n        this.atlas.tilepad = true;\n        this._atlasuv = false;\n        this._atlaskey = false;\n        this.texture = new Texture(this.canvas);\n        this.options.applyTextureParams(this.texture);\n\n        if (useFlatColors) {\n            // If were using simple colors\n            this.material = new MeshBasicMaterial({\n                vertexColors: FaceColors\n            });\n        } else {\n            var opaque = new this.options.materialType(this.options.materialParams);\n            opaque.map = this.texture;\n            // var transparent = new this.options.materialType(this.options.materialTransparentParams);\n            // transparent.map = this.texture;\n            this.material = opaque\n            // this.material = new MeshFaceMaterial([\n            //     opaque,\n            //     transparent\n            // ]);\n        }\n\n        // a place for meshes to wait while textures are loading\n        this._meshQueue = [];\n    }\n\n    load(names, done) {\n        if (!Array.isArray(names)) names = [names];\n        done = done || function () {\n        };\n        this.loading++;\n\n        var materialSlice = names.map(this._expandName);\n        this.materials = this.materials.concat(materialSlice);\n\n        // load onto the texture atlas\n        var load = Object.create(null);\n        materialSlice.forEach(function (mats) {\n            mats.forEach(function (mat) {\n                if (mat.slice(0, 1) === '#') return;\n                // todo: check if texture already exists\n                load[mat] = true;\n            });\n        });\n        if (Object.keys(load).length > 0) {\n            each(Object.keys(load), this.pack.bind(this), () => {\n                this._afterLoading();\n                done(materialSlice);\n            });\n        } else {\n            this._afterLoading();\n        }\n    };\n\n    pack(name, done) {\n        const self = this\n\n        function pack(img) {\n            var node = self.atlas.pack(img);\n            if (node === false) {\n                self.atlas = self.atlas.expand(img);\n                self.atlas.tilepad = true;\n            }\n            done();\n        }\n\n        if (typeof name === 'string') {\n            var img = new Image();\n            img.id = name;\n            img.crossOrigin = this.options.crossOrigin;\n            img.src = this.texturePath + ext(name);\n            img.onload = function () {\n                // if (isTransparent(img)) {\n                //   self.transparents.push(name);\n                // }\n                pack(img);\n            };\n            img.onerror = function () {\n                console.error('Couldn\\'t load URL [' + img.src + ']');\n                done();\n            };\n        } else {\n            pack(name);\n        }\n        return this;\n    };\n\n    find(name) {\n        var type = 0;\n        this.materials.forEach(function (mats, i) {\n            mats.forEach(function (mat) {\n                if (mat === name) {\n                    type = i + 1;\n                    return false;\n                }\n            });\n            if (type !== 0) return false;\n        });\n        return type;\n    };\n\n\n    _expandName(name) {\n        if (name === null) return Array(6);\n        if (name.top) return [name.back, name.front, name.top, name.bottom, name.left, name.right];\n        if (!Array.isArray(name)) name = [name];\n        // load the 0 texture to all\n        if (name.length === 1) name = [name[0], name[0], name[0], name[0], name[0], name[0]];\n        // 0 is top/bottom, 1 is sides\n        if (name.length === 2) name = [name[1], name[1], name[0], name[0], name[1], name[1]];\n        // 0 is top, 1 is bottom, 2 is sides\n        if (name.length === 3) name = [name[2], name[2], name[0], name[1], name[2], name[2]];\n        // 0 is top, 1 is bottom, 2 is front/back, 3 is left/right\n        if (name.length === 4) name = [name[2], name[2], name[0], name[1], name[3], name[3]];\n        return name;\n    };\n\n    _afterLoading() {\n        const alldone = () => {\n            this.loading--;\n            this._atlasuv = this.atlas.uv(this.canvas.width, this.canvas.height);\n            this._atlaskey = Object.create(null);\n            this.atlas.index().forEach((key) => {\n                this._atlaskey[key.name] = key;\n            });\n            this.texture.needsUpdate = true;\n            this.material.needsUpdate = true;\n            //window.open(this.canvas.toDataURL());\n            if (this._meshQueue.length > 0) {\n                this._meshQueue.forEach((queue, i) => {\n                    this.paint.apply(queue.self, queue.args);\n                    delete this._meshQueue[i];\n                });\n            }\n        }\n\n        this._powerof2(function () {\n            setTimeout(alldone, 100);\n        });\n    };\n\n// Ensure the texture stays at a power of 2 for mipmaps\n// this is cheating :D\n    _powerof2(done) {\n        var w = this.canvas.width;\n        var h = this.canvas.height;\n\n        function pow2(x) {\n            x--;\n            x |= x >> 1;\n            x |= x >> 2;\n            x |= x >> 4;\n            x |= x >> 8;\n            x |= x >> 16;\n            x++;\n            return x;\n        }\n\n        if (h > w) w = h;\n        var old = this.canvas.getContext('2d').getImageData(0, 0, this.canvas.width, this.canvas.height);\n        this.canvas.width = this.canvas.height = pow2(w);\n        this.canvas.getContext('2d').putImageData(old, 0, 0);\n        done();\n    };\n\n    paint(mesh, materials) {\n        var self = this;\n        // if were loading put into queue\n        if (this.loading > 0) {\n            this._meshQueue.push({self: this, args: arguments});\n            return false;\n        }\n\n        var isVoxelMesh = (materials) ? false : true;\n        if (!isVoxelMesh) materials = this._expandName(materials);\n\n        // mesh.material.vertexColors = FaceColors\n        // mesh.material.flatShading = true\n        mesh.geometry.faces.forEach((face, i) => {\n            if (mesh.geometry.faceVertexUvs[0].length < 1) return;\n\n            if (isVoxelMesh) {\n                var index = Math.floor(face.color.b*255 + face.color.g*255*255 + face.color.r*255*255*255);\n                materials = this.materials[index - 1];\n                if (!materials) materials = this.materials[0];\n            }\n\n            // BACK, FRONT, TOP, BOTTOM, LEFT, RIGHT\n            var name = materials[0] || '';\n            if      (face.normal.z === 1)  name = materials[1] || '';\n            else if (face.normal.y === 1)  name = materials[2] || '';\n            else if (face.normal.y === -1) name = materials[3] || '';\n            else if (face.normal.x === -1) name = materials[4] || '';\n            else if (face.normal.x === 1)  name = materials[5] || '';\n\n            // if just a simple color\n            if (name.slice(0, 1) === '#') {\n                face.color = new Color(name)\n                return;\n            }\n\n            var atlasuv = this._atlasuv[name];\n            if (!atlasuv) return;\n\n            // If a transparent texture use transparent material\n            face.materialIndex = (self.transparents.indexOf(name) !== -1) ? 1 : 0;\n\n            // 0 -- 1\n            // |    |\n            // 3 -- 2\n            // faces on these meshes are flipped vertically, so we map in reverse\n            // TODO: tops need rotate\n            if (isVoxelMesh) {\n                if (face.normal.z === -1 || face.normal.x === 1) {\n                    atlasuv = uvrot(atlasuv, 90);\n                }\n                atlasuv = uvinvert(atlasuv);\n            } else {\n                atlasuv = uvrot(atlasuv, -90);\n            }\n            //use different indexes for even and odd.\n            if(i%2 === 0) {\n                for (var j = 0; j < mesh.geometry.faceVertexUvs[0][i].length; j++) {\n                    let n = j\n                    if(j === 0) n = 0;\n                    if(j === 1) n = 1;\n                    if(j === 2) n = 3\n                    mesh.geometry.faceVertexUvs[0][i][j].x = atlasuv[n][0]\n                    mesh.geometry.faceVertexUvs[0][i][j].y = 1 - atlasuv[n][1]\n                }\n            } else {\n                for (let j = 0; j < mesh.geometry.faceVertexUvs[0][i].length; j++) {\n                    mesh.geometry.faceVertexUvs[0][i][j].x = atlasuv[j+1][0]\n                    mesh.geometry.faceVertexUvs[0][i][j].y = 1 - atlasuv[j+1][1]\n                }\n            }\n        });\n\n        mesh.geometry.elementsNeedUpdate = true\n        mesh.geometry.uvsNeedUpdate = true;\n    };\n\n    sprite(name, w, h, cb) {\n        if (typeof w === 'function') {\n            cb = w;\n            w = null;\n        }\n        if (typeof h === 'function') {\n            cb = h;\n            h = null;\n        }\n        w = w || 16;\n        h = h || w;\n        this.loading++;\n        var img = new Image();\n        img.src = this.texturePath + ext(name);\n        img.onerror = cb;\n        img.onload = function () {\n            var canvases = [];\n            for (var x = 0; x < img.width; x += w) {\n                for (var y = 0; y < img.height; y += h) {\n                    var canvas = document.createElement('canvas');\n                    canvas.width = w;\n                    canvas.height = h;\n                    canvas.name = name + '_' + x + '_' + y;\n                    canvas.getContext('2d').drawImage(img, x, y, w, h, 0, 0, w, h);\n                    canvases.push(canvas);\n                }\n            }\n            var textures = [];\n            each(canvases, function (canvas, next) {\n                var tex = new Image();\n                tex.name = canvas.name;\n                tex.src = canvas.toDataURL();\n                tex.onload = function () {\n                    this.pack(tex, next);\n                };\n                tex.onerror = next;\n                textures.push([\n                    tex.name, tex.name, tex.name,\n                    tex.name, tex.name, tex.name\n                ]);\n            }, function () {\n                this._afterLoading();\n                // delete canvases;\n                this.materials = this.materials.concat(textures);\n                cb(textures);\n            });\n        };\n        return this;\n    };\n\n    animate(mesh, names, delay) {\n        delay = delay || 1000;\n        if (!Array.isArray(names) || names.length < 2) return false;\n        var i = 0;\n        var mat = new this.options.materialType(this.options.materialParams);\n        mat.map = this.texture;\n        mat.transparent = true;\n        mat.needsUpdate = true;\n        // tic.interval(function() {\n        //   this.paint(mesh, names[i % names.length]);\n        //   i++;\n        // }, delay);\n        return mat;\n    };\n\n    tick(dt) {\n        // tic.tick(dt);\n    };\n}\n\nfunction uvrot(coords, deg) {\n    if (deg === 0) return coords;\n    var c = [];\n    var i = (4 - Math.ceil(deg / 90)) % 4;\n    for (var j = 0; j < 4; j++) {\n        c.push(coords[i]);\n        if (i === 3) i = 0; else i++;\n    }\n    return c;\n}\n\nfunction uvinvert(coords) {\n    var c = coords.slice(0);\n    return [c[3], c[2], c[1], c[0]];\n}\n\nfunction ext(name) {\n    return (String(name).indexOf('.') !== -1) ? name : name + '.png';\n}\n\nfunction defaults(obj) {\n    [].slice.call(arguments, 1).forEach(function (from) {\n        if (from) for (var k in from) if (obj[k] == null) obj[k] = from[k];\n    });\n    return obj;\n}\n\nfunction each(arr, it, done) {\n    var count = 0;\n    arr.forEach(function (a) {\n        it(a, function () {\n            count++;\n            if (count >= arr.length) done();\n        });\n    });\n}\n"
  },
  {
    "path": "src/ecsy/camera_gimbal.js",
    "content": "export class StagePosition {}\nexport class StageRotation {}\n"
  },
  {
    "path": "src/ecsy/dashboard.js",
    "content": "import { Component, System, World } from 'ecsy';\nimport {VoxelLandscape, VoxelSystem, VoxelTextures} from './voxels.js'\nimport {ActiveBlock, Highlight, HighlightSystem} from './highlight.js'\nimport {InputFrame} from './input.js'\n\nexport class DomDashboard extends Component {\n    constructor() {\n        super();\n        this.domElement = null\n    }\n}\n\nexport class DashboardVisible extends Component {\n\n}\n\nexport class DashboardDOMOvleraySystem extends System {\n    execute(delta, time) {\n        this.queries.dash.added.forEach(me=>{\n            const dash = me.getComponent(DomDashboard);\n            let div = document.createElement('div');\n            div.classList.add(\"dom-dashboard\");\n            div.addEventListener('mousedown',e => e.stopPropagation());\n            div.addEventListener('mouseup',e => e.stopPropagation());\n            div.addEventListener('mousemove',e => e.stopPropagation());\n            document.documentElement.append(div)\n\n            this.queries.textures.results.forEach(ent => {\n                console.log(\"textures are\", ent.getComponent(VoxelTextures))\n                let texs = ent.getComponent(VoxelTextures).textures\n                texs.forEach((tex,i) => {\n                    console.log(\"adding texture\",tex);\n                    let img = document.createElement('img')\n                    img.src = tex.src;\n                    div.append(img);\n                    img.addEventListener('click',(e)=>{\n                        e.preventDefault();\n                        e.stopPropagation()\n                        console.log(`chose this image ${i}`,i)\n                        this.queries.active.results.forEach(ent=>{\n                            ent.getMutableComponent(ActiveBlock).type = i\n                        })\n                    })\n                })\n            })\n            let dismiss = document.createElement('button');\n            dismiss.innerHTML = \"dismiss\"\n            dismiss.addEventListener('click',()=>{\n                this.queries.visible.results.forEach(ent => {\n                    ent.removeComponent(DashboardVisible)\n                })\n            })\n            div.append(dismiss)\n            dash.domElement = div\n        })\n        this.queries.input.results.forEach(ent => {\n            let input = ent.getComponent(InputFrame)\n            if (input.state[InputFrame.OPEN_DASHBOARD] === true) {\n                this.queries.dash.results.forEach(dash_ent => {\n                    if(!dash_ent.hasComponent(DashboardVisible)) {\n                        dash_ent.addComponent(DashboardVisible)\n                    }\n                })\n            }\n        })\n        this.queries.visible.added.forEach(ent => {\n            console.log(\"made visible\")\n            ent.getMutableComponent(DomDashboard).domElement.classList.add('visible')\n        })\n        this.queries.visible.removed.forEach(ent => {\n            console.log(\"made in-visible\")\n            ent.getMutableComponent(DomDashboard).domElement.classList.remove('visible')\n        })\n    }\n}\nDashboardDOMOvleraySystem.queries = {\n    dash: {\n        components:[DomDashboard],\n        listen: {\n            added:true,\n        }\n    },\n    visible: {\n        components:[DashboardVisible, DomDashboard],\n        listen: {\n            added:true,\n            removed:true,\n        }\n    },\n    input: {\n        components:[InputFrame]\n    },\n    textures: {\n        components:[VoxelTextures],\n    },\n    active: {\n        components:[ActiveBlock]\n    },\n}\n\n"
  },
  {
    "path": "src/ecsy/fullscreen.js",
    "content": "import { Component, System, World } from 'ecsy';\n\nexport class FullscreenMode extends Component {\n\n}\nexport class FullscreenButton extends Component {\n\n}\n/*\n        this.fullscreenchangeHandler = () => {\n            if(document.fullscreenElement) return this._fire(FULLSCREEN_ENTERED, this)\n            if(document.webkitFullscreenElement) return this._fire(FULLSCREEN_ENTERED, this)\n            this._fire(FULLSCREEN_EXITED,this)\n        }\n        document.addEventListener('fullscreenchange',this.fullscreenchangeHandler)\n        document.addEventListener('webkitfullscreenchange',this.fullscreenchangeHandler)\n\n    playFullscreen() {\n        this.resizeOnNextRepaint = true\n        this.container.requestFullscreen()\n    }\n\n    exitFullscreen() {\n        this.resizeOnNextRepaint = true\n        if(document.exitFullscreen) document.exitFullscreen()\n        if(document.webkitExitFullscreen) document.webkitExitFullscreen()\n    }\n\n\n */\nexport class FullscreenSystem extends  System {\n    execute(delta, time) {\n        this.queries.buttons.added.forEach(ent => {\n            let elem = document.createElement('button')\n            elem.innerText = \"fullscreen\"\n            elem.classList.add(\"fullscreen\")\n            elem.addEventListener('click',(e)=>{\n                e.stopPropagation()\n                e.preventDefault()\n                ent.addComponent(FullscreenMode);\n            })\n            document.documentElement.append(elem);\n        })\n        this.queries.active.added.forEach(ent => {\n            console.log(\"turned on full screen\")\n            this.fullscreenchangeHandler = () => {\n                console.log(\"entered full screen\")\n                if(document.fullscreenElement || document.webkitFullscreenElement) {\n                    console.log(\"entered\")\n                } else {\n                    console.log(\"exited\")\n                }\n            }\n            document.addEventListener('fullscreenchange',this.fullscreenchangeHandler)\n            document.addEventListener('webkitfullscreenchange',this.fullscreenchangeHandler)\n            const domElement = document.querySelector(\"canvas\")\n            domElement.requestFullscreen()\n        })\n    }\n}\nFullscreenSystem.queries = {\n    buttons: {\n        components: [FullscreenButton],\n        listen: {\n            added:true\n        }\n    },\n    active: {\n        components: [FullscreenMode],\n        listen: {\n            added:true,\n        }\n    }\n}\n"
  },
  {
    "path": "src/ecsy/highlight.js",
    "content": "import * as util from \"../utils.js\"\nimport {System} from 'ecsy'\nimport {Quaternion, Ray, Vector2, Vector3} from 'three'\nimport {Camera, Object3D, Transform} from 'ecsy-three'\nimport {traceRay} from \"../raycast.js\"\nimport {StagePosition, StageRotation} from './camera_gimbal.js'\nimport {MouseCursor} from './mouse.js'\nimport {VoxelLandscape} from './voxels.js'\nimport {InputFrame} from './input.js'\n\nexport class Highlight {\n}\nexport class ActiveBlock {\n    constructor() {\n        this.type = 1;\n    }\n}\n\n\nexport class HighlightSystem extends System {\n    init() {\n    }\n    traceRayAtScreenCoords(\n        stageRot, stagePos, domElement, camera,\n        chunkManager, pt, distance) {\n        const ray = new Ray()\n\n        // e = e.changedTouches[0]\n        const mouse = new Vector2()\n        const bounds = domElement.getBoundingClientRect()\n        mouse.x = ((pt.x - bounds.left) / bounds.width) * 2 - 1\n        mouse.y = -((pt.y - bounds.top) / bounds.height) * 2 + 1\n\n        ray.origin.copy(camera.position)\n        ray.direction.set(mouse.x, mouse.y, 0.5).unproject(camera).sub(ray.origin).normalize()\n        // console.log(\"new bounds is\",mouse,camera)\n        // console.log(\"stage pos is\",stagePos)\n\n        stagePos.worldToLocal(ray.origin)\n        ray.origin.add(new Vector3(0,0,-0.5))\n        const quat = new Quaternion()\n        quat.copy(stageRot.quaternion)\n        quat.inverse()\n        ray.direction.applyQuaternion(quat)\n\n        const hitNormal = new Vector3(0,0,0)\n        const hitPosition = new Vector3(0,0,0)\n        // console.log(\"chunk manager is\", chunkManager)\n        const hitBlock = traceRay(chunkManager,ray.origin,ray.direction,distance,hitPosition,hitNormal,util.EPSILON)\n        return {\n            hitBlock:hitBlock,\n            hitPosition:hitPosition,\n            hitNormal: hitNormal\n        }\n    }\n    execute(delta,time) {\n        this.queries.mouse.results.forEach(mousEnt => {\n            let mouse = mousEnt.getComponent(MouseCursor)\n            let stageRot = this.queries.stageRot.results[0].getComponent(Object3D).value\n            let stagePos = this.queries.stagePos.results[0].getComponent(Object3D).value;\n            this.queries.landscape.results.forEach(ent=>{\n                const landscape = ent.getComponent(VoxelLandscape);\n                this.queries.highlights.results.forEach(ent => {\n                    // console.log(\"checking\",mouse.position, stageRot, stagePos);\n                    const domElement = document.querySelector(\"canvas\")\n                    const camera = this.queries.camera.results[0].getComponent(Object3D).value\n                    const distance = 10;\n\n                    const res = this.traceRayAtScreenCoords(\n                        stageRot, stagePos, domElement, camera,\n                        landscape.chunkManager, mouse.position, distance)\n                    //console.log(\"res is\",res);\n                    res.hitPosition.floor()\n\n                    //move the highlight\n                    let tran = ent.getMutableComponent(Transform);\n                    tran.position.copy(res.hitPosition)\n\n                    //if left button\n                    this.queries.inputs.results.forEach(ent => {\n                        let input = ent.getComponent(InputFrame)\n                        if(input.state[InputFrame.CREATE_AT_CURSOR] === true) {\n                            let pos = res.hitPosition.clone()\n                            pos.add(res.hitNormal)\n                            pos.floor()\n                            let active = this.queries.active.results[0]\n                            landscape.chunkManager.setVoxelAtCoordinates(pos,active.getComponent(ActiveBlock).type)\n                        }\n                        input.state[InputFrame.CREATE_AT_CURSOR] = false\n                        if(input.state[InputFrame.DESTROY_AT_CURSOR] === true) {\n                            let pos = res.hitPosition.clone()\n                            pos.floor()\n                            landscape.chunkManager.setVoxelAtCoordinates(pos,0)\n                        }\n                        input.state[InputFrame.DESTROY_AT_CURSOR] = false\n                    })\n                })\n            })\n        })\n    }\n}\nHighlightSystem.queries = {\n    highlights: { components: [Highlight]},\n    stagePos: { components: [StagePosition]},\n    stageRot: { components: [StageRotation]},\n    mouse: { components:[MouseCursor] },\n    inputs: { components:[InputFrame]},\n    camera: { components:[Camera] },\n    landscape: { components:[VoxelLandscape]},\n    active: { components:[ActiveBlock]}\n}\n"
  },
  {
    "path": "src/ecsy/index.js",
    "content": "export * from './mouse.js'\nexport * from './keyboard.js'\nexport * from './voxels.js'\nexport * from './highlight.js'\nexport * from './camera_gimbal.js'\nexport * from './webxr.js'\nexport * from './fullscreen.js'\nexport * from './dashboard.js'\nexport * from './input.js'\n"
  },
  {
    "path": "src/ecsy/input.js",
    "content": "import {Component, System} from 'ecsy'\nimport {StagePosition, StageRotation} from './camera_gimbal.js'\nimport {\n    initialize,\n    Parent,\n    Transform,\n    Object3D,\n} from 'ecsy-three';\nimport {Group,\n    Vector3,\n    TextureLoader,\n    CubeGeometry,\n    MeshLambertMaterial,\n    Mesh,\n    AmbientLight,\n} from 'three';\n\nexport class InputFrame extends Component {\n    constructor() {\n        super();\n        this.state = {\n            ROTATE_LEFT:false,\n            ROTATE_RIGHT:false,\n            LEFT_STRAFE:false,\n            RIGHT_STRAFE:false,\n            MOVE_FORWARD:false,\n            MOVE_BACKWARD:false,\n            LEVITATE_UP:false,\n            LEVITATE_DOWN:false,\n        }\n    }\n}\nInputFrame.LEFT_STRAFE = 'LEFT_STRAFE'\nInputFrame.RIGHT_STRAFE = 'RIGHT_STRAFE'\nInputFrame.MOVE_FORWARD = 'MOVE_FORWARD'\nInputFrame.MOVE_BACKWARD = 'MOVE_BACKWARD'\nInputFrame.ROTATE_LEFT = 'ROTATE_LEFT'\nInputFrame.ROTATE_RIGHT = 'ROTATE_RIGHT'\nInputFrame.OPEN_DASHBOARD = 'OPEN_DASHBOARD'\nInputFrame.ROTATION_DRAGGING = 'ROTATION_DRAGGING'\nInputFrame.ROTATION_ANGLE = 'ROTATION_ANGLE'\nInputFrame.CREATE_AT_CURSOR = 'CREATE_AT_CURSOR'\nInputFrame.DESTROY_AT_CURSOR = 'DESTROY_AT_CURSOR'\nInputFrame.LEVITATE_UP = 'LEVITATE_UP'\nInputFrame.LEVITATE_DOWN = 'LEVITATE_DOWN'\n\nconst Y_AXIS = new Vector3(0,1,0)\nconst Z_AXIS = new Vector3(0,0,1)\nconst SPEED = 0.1\n\nexport class VoxelPlayerSystem extends System {\n    init() {\n        console.log(\"voxel system initting\")\n    }\n    execute(delta, time) {\n        this.queries.inputs.results.forEach(ent => {\n            let input = ent.getComponent(InputFrame)\n            this.queries.stageRot.results.forEach(ent => {\n                let rot_trans = ent.getMutableComponent(Transform)\n                if (input.state[InputFrame.ROTATE_LEFT] === true) {\n                    rot_trans.rotation.y -= 0.05\n                }\n                if (input.state[InputFrame.ROTATE_RIGHT] === true) {\n                    rot_trans.rotation.y += 0.05\n                }\n                if (input.state[InputFrame.ROTATION_DRAGGING] === true) {\n                    rot_trans.rotation.y = input.state[InputFrame.ROTATION_ANGLE]\n                } else {\n                    input.state[InputFrame.ROTATION_ANGLE] = rot_trans.rotation.y\n                }\n            })\n\n            this.queries.stagePos.results.forEach(ent => {\n                if(input.state[InputFrame.MOVE_FORWARD] === true) {\n                    let stageRot = ent.getComponent(Parent).value\n                    let pos_trans = ent.getMutableComponent(Transform)\n                    const dir = new Vector3(0,0,1)\n                    dir.applyAxisAngle(Y_AXIS, -stageRot.getComponent(Transform).rotation.y)\n                    let d2 = dir.normalize().multiplyScalar(SPEED)\n                    const vel = d2.multiplyScalar(4)\n                    pos_trans.position.x += vel.x;\n                    pos_trans.position.z += vel.z;\n                }\n                if(input.state[InputFrame.MOVE_BACKWARD] === true) {\n                    let stageRot = ent.getComponent(Parent).value\n                    let pos_trans = ent.getMutableComponent(Transform)\n                    const dir = new Vector3(0,0,1)\n                    dir.applyAxisAngle(Y_AXIS, -stageRot.getComponent(Transform).rotation.y)\n                    let d2 = dir.normalize().multiplyScalar(SPEED)\n                    const vel = d2.multiplyScalar(-4)\n                    pos_trans.position.x += vel.x;\n                    pos_trans.position.z += vel.z;\n                }\n                if(input.state[InputFrame.LEFT_STRAFE] === true) {\n                    let stageRot = ent.getComponent(Parent).value\n                    let pos_trans = ent.getMutableComponent(Transform)\n                    const dir = new Vector3(0,0,1)\n                    dir.applyAxisAngle(Y_AXIS, -stageRot.getComponent(Transform).rotation.y + Math.PI/2)\n                    let d2 = dir.normalize().multiplyScalar(SPEED)\n                    const vel = d2.multiplyScalar(4)\n                    pos_trans.position.x += vel.x;\n                    pos_trans.position.z += vel.z;\n                }\n                if(input.state[InputFrame.RIGHT_STRAFE] === true) {\n                    let stageRot = ent.getComponent(Parent).value\n                    let pos_trans = ent.getMutableComponent(Transform)\n                    const dir = new Vector3(0,0,1)\n                    dir.applyAxisAngle(Y_AXIS, -stageRot.getComponent(Transform).rotation.y - Math.PI/2)\n                    let d2 = dir.normalize().multiplyScalar(SPEED)\n                    const vel = d2.multiplyScalar(4)\n                    pos_trans.position.x += vel.x;\n                    pos_trans.position.z += vel.z;\n                }\n                if(input.state[InputFrame.LEVITATE_DOWN] === true) {\n                    let stageRot = ent.getComponent(Parent).value\n                    let pos_trans = ent.getMutableComponent(Transform)\n                    const dir = new Vector3(0,1,0)\n                    dir.applyAxisAngle(Z_AXIS, -stageRot.getComponent(Transform).rotation.z)\n                    let d2 = dir.normalize().multiplyScalar(SPEED)\n                    const vel = d2.multiplyScalar(4)\n                    pos_trans.position.x += vel.x;\n                    pos_trans.position.y += vel.y;\n                }\n                if(input.state[InputFrame.LEVITATE_UP] === true) {\n                    let stageRot = ent.getComponent(Parent).value\n                    let pos_trans = ent.getMutableComponent(Transform)\n                    const dir = new Vector3(0,1,0)\n                    dir.applyAxisAngle(Z_AXIS, -stageRot.getComponent(Transform).rotation.z)\n                    let d2 = dir.normalize().multiplyScalar(SPEED)\n                    const vel = d2.multiplyScalar(-4)\n                    pos_trans.position.x += vel.x;\n                    pos_trans.position.y += vel.y;\n                }\n            })\n        })\n    }\n}\n\nVoxelPlayerSystem.queries = {\n    inputs: {\n        components: [InputFrame],\n    },\n    stageRot: {\n        components: [StageRotation, Transform],\n    },\n    stagePos: {\n        components: [StagePosition, Transform],\n    }\n}\n"
  },
  {
    "path": "src/ecsy/keyboard.js",
    "content": "import {Component, System} from 'ecsy'\nimport {InputFrame} from './input.js'\n\nexport class KeyboardBindingSet extends Component {\n    constructor() {\n        super();\n        this.bindings = {}\n    }\n}\n\nexport class KeyboardSystem extends System {\n    _setBindingValue(keyboard_key, new_value) {\n        this.queries.bindings.results.forEach(ent => {\n            let binding = ent.getComponent(KeyboardBindingSet)\n            if(binding.bindings[keyboard_key]) {\n                let state_key = binding.bindings[keyboard_key]\n                this.queries.inputs.results.forEach(ent => {\n                    let input = ent.getMutableComponent(InputFrame)\n                    input.state[state_key] = new_value\n                })\n            }\n        })\n    }\n\n    _keydown(e) {\n        this._setBindingValue(e.key,true);\n    }\n    _keyup(e) {\n        this._setBindingValue(e.key,false);\n    }\n    init() {\n        document.addEventListener('keydown',this._keydown.bind(this));\n        document.addEventListener('keyup',this._keyup.bind(this));\n    }\n}\nKeyboardSystem.queries = {\n    bindings: {\n        components:[KeyboardBindingSet]\n    },\n    inputs: {\n        components: [InputFrame],\n    }\n}\n"
  },
  {
    "path": "src/ecsy/mouse.js",
    "content": "import {Component, System} from 'ecsy'\nimport {Vector2} from 'three'\nimport {InputFrame} from './input.js'\n\nexport class MouseCursor extends Component {\n    constructor() {\n        super();\n        this.position = new Vector2()\n        this.buttons = 0\n        this.down = false\n    }\n}\nexport class MouseSystem extends System {\n    _mouse_down(e) {\n        this.pressed = true\n        this.buttons = e.buttons\n        this.start_point = this.last_point.clone()\n        this.queries.inputs.results.forEach(ent => {\n            let input = ent.getMutableComponent(InputFrame)\n            input.state[InputFrame.ROTATION_DRAGGING] = true\n            this.start_angle = input.state[InputFrame.ROTATION_ANGLE]\n        })\n\n    }\n    _mouse_move(e) {\n        this.last_point = new Vector2(e.clientX,e.clientY)\n        if(this.pressed) {\n            let diff = this.last_point.clone().sub(this.start_point)\n            let new_angle = this.start_angle - 0.003*diff.x\n            this.queries.inputs.results.forEach(ent => {\n                let input = ent.getMutableComponent(InputFrame)\n                input.state[InputFrame.ROTATION_ANGLE] = new_angle\n            })\n        }\n    }\n    _mouse_up(e) {\n        this.pressed = false\n        let diff = this.last_point.clone().sub(this.start_point)\n        this.queries.inputs.results.forEach(ent => {\n            let input = ent.getMutableComponent(InputFrame)\n            input.state[InputFrame.ROTATION_DRAGGING] = false\n            if(Math.abs(diff.x) < 10) {\n                if(this.buttons === 1) {\n                    input.state[InputFrame.CREATE_AT_CURSOR] = true\n                }\n                if(this.buttons === 2) {\n                    input.state[InputFrame.DESTROY_AT_CURSOR] = true\n                }\n\n            }\n        })\n    }\n    init() {\n        this.last_point = new Vector2(200,200)\n        document.addEventListener('contextmenu',e => {\n            e.preventDefault()\n            e.stopPropagation()\n        })\n        document.addEventListener('mousemove',(e)=>this._mouse_move(e))\n        document.addEventListener('mousedown', (e)=>this._mouse_down(e))\n        document.addEventListener('mouseup', (e)=>this._mouse_up(e))\n    }\n    execute(delta,time) {\n        this.queries.targets.results.forEach(ent => {\n            ent.getMutableComponent(MouseCursor).position.copy(this.last_point)\n        })\n    }\n}\nMouseSystem.queries = {\n    targets: {\n        components: [MouseCursor]\n    },\n    inputs: {\n        components: [InputFrame],\n    }\n}\n"
  },
  {
    "path": "src/ecsy/voxels.js",
    "content": "import {TextureManager} from '../TextureManager.js'\nimport {Component, System} from 'ecsy'\nimport {ChunkManager} from \"../ChunkManager.js\"\nimport {CulledMesher} from \"../CulledMesher.js\"\nimport {generateChunkInfoFromFunction} from '../utils.js'\nimport {Group, Vector3} from 'three'\nimport {Object3D} from 'ecsy-three'\n\nexport class VoxelLandscape extends Component {\n    constructor() {\n        super()\n        this.chunkManager = null\n        this.make_voxel = null\n    }\n}\nexport class VoxelTextures extends Component {\n    constructor() {\n        super();\n        this.tm = null\n        this.textures = []\n    }\n}\nexport class VoxelSystem extends System {\n    execute(delta, time) {\n        this.queries.entities.added.forEach(entity => {\n            let land = entity.getMutableComponent(VoxelLandscape)\n            //setup the chunk manager\n            land.chunkManager = new ChunkManager({\n                chunkDistance:1,\n                blockSize:1,\n                mesher: new CulledMesher(),\n                chunkSize:16,\n                generateVoxelChunk: (low, high, pos) => {\n                    const id = [pos.x,pos.y,pos.z].join('|')\n                    return generateChunkInfoFromFunction(low, high, land.make_voxel)\n                },\n                container: new Group(),\n                textureManager: new TextureManager({aoEnabled:true}),\n            });\n            let texs = entity.getMutableComponent(VoxelTextures)\n            texs.tm = land.chunkManager.textureManager\n            texs.tm.loadTextures(texs.textures).then(()=>{\n                console.log(\"textures are loaded\")\n                land.chunkManager.rebuildAllMeshes()\n                land.chunkManager.requestMissingChunks(new Vector3(0,0,0))\n            })\n            entity.addComponent(Object3D, {value: land.chunkManager.container})\n        })\n    }\n}\nVoxelSystem.queries = {\n    entities: {\n        components: [VoxelLandscape, VoxelTextures],\n        listen: {\n            added: true,\n            removed: true\n        }\n    }\n}\n"
  },
  {
    "path": "src/ecsy/webxr.js",
    "content": "import { Component, System, World } from 'ecsy';\nimport {FullscreenMode} from './fullscreen.js'\n\nexport class WebXRActive extends Component {\n}\n\nexport class WebXRButton extends Component {\n    constructor() {\n        super();\n        this.currentSession = null\n    }\n}\n\nexport class WebXRController extends Component {\n    constructor() {\n        super()\n        this.controller = null\n        this.index = -1\n        this.selected = false\n    }\n}\nexport class WebXRSystem extends System {\n    enterWebXR(ent) {\n        console.log('trying to enter webxr')\n        let state = ent.getMutableComponent(WebXRButton);\n        if(state.currentSession === null) {\n            navigator.xr.requestSession('immersive-vr').then(session=>{\n                console.log(\"session has started\")\n                let onSessionEnded = () => {\n                    console.log(\"session ended\")\n                    state.currentSession.removeEventListener('end',onSessionEnded)\n                    renderer.vr.setSession(null)\n                    ent.removeComponent(WebXRActive)\n                }\n                session.addEventListener('end',onSessionEnded)\n                renderer.vr.setSession(session)\n                state.currentSession = session\n                ent.addComponent(WebXRActive)\n            })\n        } else {\n            state.currentSession.end()\n        }\n    }\n    execute(delta, time) {\n        this.queries.buttons.added.forEach(ent => {\n            console.log(\"button added\");\n            let elem = document.createElement('button')\n            elem.innerText = \"webxr\"\n            elem.classList.add(\"webxr\")\n            elem.addEventListener('click', (e) => {\n                e.stopPropagation()\n                e.preventDefault()\n                this.enterWebXR(ent);\n            })\n            elem.disabled = true\n            document.documentElement.append(elem)\n            console.log(\"added the element\", elem)\n\n            if ( 'xr' in navigator && 'supportsSession' in navigator.xr ) {\n                navigator.xr.supportsSession( 'immersive-vr' ).then(()=>{\n                    console.log(\"immersive is supported\");\n                    elem.disabled = false\n                }).catch((e)=>{\n                    console.error(e)\n                });\n            } else {\n                console.log(\"does not have webxr\");\n            }\n        })\n        this.queries.controllers.added.forEach(ent => {\n            const con = ent.getMutableComponent(WebXRController)\n            // const domElement = document.querySelector(\"canvas\")\n\n            con.controller = renderer.xr.getController(con.index)\n            con.controller.addEventListener('selectstart', (evt)=>{\n                console.log(\"controller select start\")\n                con.selected = true\n            });\n            con.controller.addEventListener('selectend', (evt)=>{\n                console.log(\"controller select end\")\n                con.selected = false\n            });\n        })\n    }\n}\nWebXRSystem.queries = {\n    buttons: {\n        components: [ WebXRButton],\n        listen: {\n            added:true,\n        }\n    },\n    active: {\n        components: [WebXRActive],\n        listen: {\n            added:true,\n            removed:false,\n        }\n    },\n    controllers: {\n        components: [WebXRController],\n        listen: {\n            added: true,\n            removed: true\n        }\n    },\n}\n\n\n\n\n"
  },
  {
    "path": "src/index.js",
    "content": "//export * from './ecsy';\nexport * from './ChunkManager';\nexport * from './CulledMesher';\nexport * from './DesktopControls';\nexport * from './ECSComp';\nexport * from './FullscreenControls';\nexport * from './GreedyMesher';\nexport * from './KeyboardControls';\nexport * from './PhysHandler';\nexport * from './physical';\nexport * from './raycast';\nexport * from './SimpleMeshCollider';\nexport * from './TextureManager';\nexport * from './TouchControls';\nexport * from './utils';\nexport * from './VoxelMesh';\nexport * from './VoxelTexture';\nexport * from './VRControls';\nexport * from './VRStats';\nexport * from './ecsy';\n"
  },
  {
    "path": "src/physical.js",
    "content": "// module.exports = physical\n//import {AABB as aabb} from \"./aabb.js\"\nimport {Vector3} from \"three\"\n\n// make these *once*, so we're not generating\n// garbage for every object in the game.\nconst WORLD_DESIRED = new Vector3(0, 0, 0)\n    , DESIRED = new Vector3(0, 0, 0)\n    , START = new Vector3(0, 0, 0)\n    , END = new Vector3(0, 0, 0)\n    , DIRECTION = new Vector3()\n    , LOCAL_ATTRACTOR = new Vector3()\n    , TOTAL_FORCES = new Vector3()\n\nfunction applyTo(which) {\n    return function(world) {\n        var local = this.avatar.worldToLocal(world)\n        this[which].x += local.x\n        this[which].y += local.y\n        this[which].z += local.z\n    }\n}\n\nconst abs = Math.abs\n\n//JOSH: I don't know what this is for\n// , axes = ['x', 'y', 'z']\n\nexport class Physical {\n\n    constructor(avatar, collidables, dimensions, terminal) {\n        //a connection to the underlying threejs object\n        this.avatar = avatar\n        //terminal velocity. default is pretty slow?\n        this.terminal = terminal || new Vector3(0.9, 0.1, 0.9)\n        //the size of the object as width, height, depth in a vector3\n        //default dimensions are a 1x1x1 cube\n        this.dimensions = dimensions = dimensions || new Vector3(1, 1, 1)\n        //turn dimensions into an AABB (axis aligned bounding box)\n        this._aabb = aabb(new Vector3(0, 0, 0), dimensions)\n        //indicates if not moving in each direction\n        this.resting = {x: false, y: false, z: false}\n        this.old_resting_y = 0\n        this.last_rest_y = NaN\n\n        //a list of objects that this Physical can collide with\n        this.collidables = collidables\n        //default fiction. should this be modifyable?\n        this.friction = new Vector3(1, 1, 1)\n\n        //the current rotation of the avatar. a threejs euler angle\n        this.rotation = this.avatar.rotation\n        this.default_friction = 1\n\n        // default yaw/pitch/roll controls to the avatar\n        this.yaw =\n            this.pitch =\n                this.roll = avatar\n\n        // the current total of forces affecting this object\n        this.forces = new Vector3(0, 0, 0)\n        // a list of attractors affecting this object. meaning something you are pulled towards\n        this.attractors = []\n        //current acceleration.\n        this.acceleration = new Vector3(0, 0, 0)\n        //current velocity\n        this.velocity = new Vector3(0, 0, 0)\n\n\n        this.applyWorldAcceleration = applyTo('acceleration')\n        this.applyWorldVelocity = applyTo('velocity')\n    }\n\n\n    tick(dt) {\n        let forces = this.forces\n        let acceleration = this.acceleration\n        let velocity = this.velocity\n        let terminal = this.terminal\n        let friction = this.friction\n        let desired = DESIRED\n        let world_desired = WORLD_DESIRED\n        let bbox\n        let pcs\n\n        TOTAL_FORCES.multiplyScalar(0)\n\n        desired.x =\n            desired.y =\n                desired.z =\n                    world_desired.x =\n                        world_desired.y =\n                            world_desired.z = 0\n\n        //add in the attractors force\n        /*\n        for (let i = 0; i < this.attractors.length; i++) {\n            var distance_factor = this.avatar.position.distanceToSquared(this.attractors[i])\n            LOCAL_ATTRACTOR.copy(this.attractors[i])\n            LOCAL_ATTRACTOR = this.avatar.worldToLocal(LOCAL_ATTRACTOR)\n\n            DIRECTION.sub(LOCAL_ATTRACTOR, this.avatar.position)\n\n            DIRECTION.divideScalar(DIRECTION.length() * distance_factor)\n            DIRECTION.multiplyScalar(this.attractors[i].mass)\n\n            TOTAL_FORCES.addSelf(DIRECTION)\n        }\n        */\n\n        dt = dt/1000\n        // console.log('the forces',this.forces)\n        // console.log(\"dt\",dt)\n        // apply the forces\n        if (!this.resting.x) {\n            acceleration.x /= 8 * dt\n            // acceleration.x += TOTAL_FORCES.x * dt\n            acceleration.x += forces.x * dt\n\n            velocity.x += acceleration.x * dt\n            velocity.x *= friction.x\n\n            if (abs(velocity.x) < terminal.x) {\n                desired.x = (velocity.x * dt)\n            } else if (velocity.x !== 0) {\n                desired.x = (velocity.x / abs(velocity.x)) * terminal.x\n            }\n        } else {\n            acceleration.x = velocity.x = 0\n        }\n        if (!this.resting.y) {\n            // console.log('starting acc',acceleration.y)\n            // acceleration.y /= 8 * dt\n            acceleration.y = 0;\n            // console.log(\"now\",acceleration.y)\n            // acceleration.y += TOTAL_FORCES.y * dt\n            acceleration.y += forces.y * dt\n            // console.log(\"ending accel\",acceleration.y)\n            // console.log('starting vel',velocity.y)\n            velocity.y += acceleration.y// * dt\n            // velocity.y *= friction.y\n            console.log(\"ending vel\",velocity.y)\n\n            // console.log('desired y', desired.y)\n            console.log('ternimal',terminal.y)\n            if (abs(velocity.y) < terminal.y) {\n                // console.log('less than terminal',)\n                desired.y = (velocity.y * dt)\n                // console.log('dt is',dt)\n            } else if (velocity.y !== 0) {\n                desired.y = (velocity.y / abs(velocity.y)) * terminal.y\n            }\n        } else {\n            // console.log(\"resting again\")\n            acceleration.y = velocity.y = 0\n        }\n        if (!this.resting.z) {\n            acceleration.z /= 8 * dt\n            acceleration.z += TOTAL_FORCES.z * dt\n            acceleration.z += forces.z * dt\n\n            velocity.z += acceleration.z * dt\n            velocity.z *= friction.z\n\n            if (abs(velocity.z) < terminal.z) {\n                desired.z = (velocity.z * dt)\n            } else if (velocity.z !== 0) {\n                desired.z = (velocity.z / abs(velocity.z)) * terminal.z\n            }\n        } else {\n            acceleration.z = velocity.z = 0\n        }\n\n        // console.log('starting postion',this.avatar.position)\n        // console.log('desired is',desired)\n        START.copy(this.avatar.position)\n        this.avatar.translateX(desired.x)\n        this.avatar.translateY(desired.y)\n        this.avatar.translateZ(desired.z)\n        END.copy(this.avatar.position)\n        this.avatar.position.copy(START)\n\n        //START is where the object is now\n        //END is where the object will be after the movement is applied\n        //desired is the direction vector that was calculated\n        //JOSH: I think world desired is a direction vector for the current motion\n        world_desired.x = END.x - START.x\n        world_desired.y = END.y - START.y\n        world_desired.z = END.z - START.z\n        // console.log('world destired stare is',world_desired)\n\n        // console.log('start',START,'end',END, 'diff', world_desired)\n        // console.log(\"world desired\",world_desired)\n        //set the friction in all directions\n        this.friction.x =\n            this.friction.y =\n                this.friction.z = this.default_friction\n\n        // save old copies, since when normally on the\n        // ground, this.resting.y alternates (false,-1)\n        // JOSH: this part confuses me\n        this.old_resting_y = (this.old_resting_y << 1) >>> 0\n        this.old_resting_y |= !!this.resting.y | 0\n\n        // run collisions\n        this.resting.x =\n            this.resting.y =\n                this.resting.z = false\n\n        bbox = this.aabb()\n        pcs = this.collidables\n\n        //collide against everything except myself\n        for (let i = 0, len = pcs.length; i < len; ++i) {\n            if (pcs[i] !== this) {\n                pcs[i].collide(this, bbox, world_desired, this.resting)\n            }\n        }\n\n        //what does this part do?\n        /*\n        // fall distance\n        if (!!(this.old_resting_y & 0x4) !== !!this.resting.y) {\n            if (!this.resting.y) {\n                this.last_rest_y = this.avatar.position.y\n            } else if (!isNaN(this.last_rest_y)) {\n                this.fell(this.last_rest_y - this.avatar.position.y)\n                this.last_rest_y = NaN\n            }\n        }\n        */\n\n        // apply translation\n        this.avatar.position.x += world_desired.x\n        this.avatar.position.y += world_desired.y\n        this.avatar.position.z += world_desired.z\n    }\n\n\n    subjectTo (force) {\n        this.forces.x += force[0]\n        this.forces.y += force[1]\n        this.forces.z += force[2]\n        return this\n    }\n\n\n\n    removeForce (force) {\n        this.forces.x -= force[0]\n        this.forces.y -= force[1]\n        this.forces.z -= force[2]\n        return this\n    }\n\n\n\n    attractTo (vector, mass) {\n        vector.mass = mass\n        this.attractors.push(vector)\n    }\n\n\n\n    aabb () {\n        const pos = this.avatar.position\n        const d = this.dimensions\n        return aabb(\n            new Vector3(pos.x - (d.x / 2), pos.y, pos.z - (d.z / 2)),\n            this.dimensions\n        )\n    }\n\n// no object -> object collisions for now, thanks\n\n\n    collide (other, bbox, world_vec, resting) {\n        return\n    }\n\n\n\n    atRestX () {\n        return this.resting.x\n    }\n\n\n\n    atRestY () {\n        return this.resting.y\n    }\n\n    atRestZ () {\n        return this.resting.z\n    }\n\n    fell (distance) {\n        return\n    }\n}\n"
  },
  {
    "path": "src/raycast.js",
    "content": "function traceRay_impl(\n    voxelProvider,\n    px, py, pz,\n    dx, dy, dz,\n    max_d,\n    hit_pos,\n    hit_norm,\n    EPSILON) {\n    var t = 0.0\n        , nx=0, ny=0, nz=0\n        , ix, iy, iz\n        , fx, fy, fz\n        , ox, oy, oz\n        , ex, ey, ez\n        , b, step, min_step\n        , floor = Math.floor\n    //Step block-by-block along ray\n    while(t <= max_d) {\n        ox = px + t * dx\n        oy = py + t * dy\n        oz = pz + t * dz\n        ix = floor(ox)|0\n        iy = floor(oy)|0\n        iz = floor(oz)|0\n        fx = ox - ix\n        fy = oy - iy\n        fz = oz - iz\n        b = voxelProvider.getBlock(ix, iy, iz)\n        if(b) {\n            if(hit_pos) {\n                //Clamp to face on hit\n                hit_pos.x = fx < EPSILON ? +ix : (fx > 1.0-EPSILON ? ix+1.0-EPSILON : ox)\n                hit_pos.y = fy < EPSILON ? +iy : (fy > 1.0-EPSILON ? iy+1.0-EPSILON : oy)\n                hit_pos.z = fz < EPSILON ? +iz : (fz > 1.0-EPSILON ? iz+1.0-EPSILON : oz)\n            }\n            if(hit_norm) {\n                hit_norm.x = nx\n                hit_norm.y = ny\n                hit_norm.z = nz\n            }\n            return b\n        }\n        //Check edge cases\n        min_step = +(EPSILON * (1.0 + t))\n        if(t > min_step) {\n            ex = nx < 0 ? fx <= min_step : fx >= 1.0 - min_step\n            ey = ny < 0 ? fy <= min_step : fy >= 1.0 - min_step\n            ez = nz < 0 ? fz <= min_step : fz >= 1.0 - min_step\n            if(ex && ey && ez) {\n                b = voxelProvider.getBlock(ix+nx, iy+ny, iz) ||\n                    voxelProvider.getBlock(ix, iy+ny, iz+nz) ||\n                    voxelProvider.getBlock(ix+nx, iy, iz+nz)\n                if(b) {\n                    if(hit_pos) {\n                        hit_pos.x = nx < 0 ? ix-EPSILON : ix + 1.0-EPSILON\n                        hit_pos.y = ny < 0 ? iy-EPSILON : iy + 1.0-EPSILON\n                        hit_pos.z = nz < 0 ? iz-EPSILON : iz + 1.0-EPSILON\n                    }\n                    if(hit_norm) {\n                        hit_norm.x = nx\n                        hit_norm.y = ny\n                        hit_norm.z = nz\n                    }\n                    return b\n                }\n            }\n            if(ex && (ey || ez)) {\n                b = voxelProvider.getBlock(ix+nx, iy, iz)\n                if(b) {\n                    if(hit_pos) {\n                        hit_pos.x = nx < 0 ? ix-EPSILON : ix + 1.0-EPSILON\n                        hit_pos.y = fy < EPSILON ? +iy : oy\n                        hit_pos.z = fz < EPSILON ? +iz : oz\n                    }\n                    if(hit_norm) {\n                        hit_norm.x = nx\n                        hit_norm.y = ny\n                        hit_norm.z = nz\n                    }\n                    return b\n                }\n            }\n            if(ey && (ex || ez)) {\n                b = voxelProvider.getBlock(ix, iy+ny, iz)\n                if(b) {\n                    if(hit_pos) {\n                        hit_pos.x = fx < EPSILON ? +ix : ox\n                        hit_pos.y = ny < 0 ? iy-EPSILON : iy + 1.0-EPSILON\n                        hit_pos.z = fz < EPSILON ? +iz : oz\n                    }\n                    if(hit_norm) {\n                        hit_norm.x = nx\n                        hit_norm.y = ny\n                        hit_norm.z = nz\n                    }\n                    return b\n                }\n            }\n            if(ez && (ex || ey)) {\n                b = voxelProvider.getBlock(ix, iy, iz+nz)\n                if(b) {\n                    if(hit_pos) {\n                        hit_pos.x = fx < EPSILON ? +ix : ox\n                        hit_pos.y = fy < EPSILON ? +iy : oy\n                        hit_pos.z = nz < 0 ? iz-EPSILON : iz + 1.0-EPSILON\n                    }\n                    if(hit_norm) {\n                        hit_norm.x = nx\n                        hit_norm.y = ny\n                        hit_norm.z = nz\n                    }\n                    return b\n                }\n            }\n        }\n        //Walk to next face of cube along ray\n        nx = ny = nz = 0\n        step = 2.0\n        if(dx < -EPSILON) {\n            var s = -fx/dx\n            nx = 1\n            step = s\n        }\n        if(dx > EPSILON) {\n            var s = (1.0-fx)/dx\n            nx = -1\n            step = s\n        }\n        if(dy < -EPSILON) {\n            var s = -fy/dy\n            if(s < step-min_step) {\n                nx = 0\n                ny = 1\n                step = s\n            } else if(s < step+min_step) {\n                ny = 1\n            }\n        }\n        if(dy > EPSILON) {\n            var s = (1.0-fy)/dy\n            if(s < step-min_step) {\n                nx = 0\n                ny = -1\n                step = s\n            } else if(s < step+min_step) {\n                ny = -1\n            }\n        }\n        if(dz < -EPSILON) {\n            var s = -fz/dz\n            if(s < step-min_step) {\n                nx = ny = 0\n                nz = 1\n                step = s\n            } else if(s < step+min_step) {\n                nz = 1\n            }\n        }\n        if(dz > EPSILON) {\n            var s = (1.0-fz)/dz\n            if(s < step-min_step) {\n                nx = ny = 0\n                nz = -1\n                step = s\n            } else if(s < step+min_step) {\n                nz = -1\n            }\n        }\n        if(step > max_d - t) {\n            step = max_d - t - min_step\n        }\n        if(step < min_step) {\n            step = min_step\n        }\n        t += step\n    }\n    if(hit_pos) {\n        hit_pos.x = ox;\n        hit_pos.y = oy;\n        hit_pos.z = oz;\n    }\n    if(hit_norm) {\n        hit_norm.x = hit_norm.y = hit_norm.z = 0;\n    }\n    return 0\n}\n\nfunction startTraceRay(voxelProvider, origin, direction, max_d, hit_pos, hit_norm, EPSILON) {\n    // console.log(\"tracing\",origin,direction)\n    var px = +origin.x\n        , py = +origin.y\n        , pz = +origin.z\n        , dx = +direction.x\n        , dy = +direction.y\n        , dz = +direction.z\n        , ds = Math.sqrt(dx*dx + dy*dy + dz*dz)\n    if(typeof(EPSILON) === \"undefined\") {\n        EPSILON = 1e-8\n    }\n    if(ds < EPSILON) {\n        if(hit_pos) {\n            hit_pos.x = hit_pos.y = hit_pos.z\n        }\n        if(hit_norm) {\n            hit_norm.x = hit_norm.y = hit_norm.z\n        }\n        return 0;\n    }\n    dx /= ds\n    dy /= ds\n    dz /= ds\n    if(typeof(max_d) === \"undefined\") {\n        max_d = 64.0\n    } else {\n        max_d = +max_d\n    }\n    return traceRay_impl(voxelProvider, px, py, pz, dx, dy, dz, max_d, hit_pos, hit_norm, EPSILON)\n}\n\nexport const traceRay = startTraceRay\n// module.exports = traceRay\n"
  },
  {
    "path": "src/utils.js",
    "content": "import {Quaternion, Ray, Vector2, Vector3,} from \"three\"\nimport {traceRay} from './raycast.js'\n\nexport function toHexColor(num) {\n    let str = num.toString(16)\n    while(str.length < 6) str = '0' + str\n    return '#' + str\n}\n\n\nexport function generateChunkInfoFromFunction(l, h, f) {\n    let d = [ h[0]-l[0], h[1]-l[1], h[2]-l[2] ]\n    let v = new Int32Array(d[0]*d[1]*d[2])\n    let n = 0;\n    for(let k=l[2]; k<h[2]; ++k)\n        for(let j=l[1]; j<h[1]; ++j)\n            for(let i=l[0]; i<h[0]; ++i, ++n) {\n                v[n] = f(i,j,k);\n            }\n    return {\n        low:l,\n        high:h,\n        voxels:v,\n        dims:d,\n    };\n}\n\n\nexport const toRad = (deg) => Math.PI / 180 * deg\n\n\nexport const EPSILON = 1e-8\n\n\nexport const $ = (sel) => document.querySelector(sel)\n\nexport const DIRS = {\n    NONE:'NONE',\n    UP:'UP',\n    DOWN:'DOWN',\n    LEFT:'LEFT',\n    RIGHT:'RIGHT'\n}\n\nexport const on = (elem, type, cb) => elem.addEventListener(type,cb)\n\n\nexport function traceRayAtScreenCoords(app, pt, distance) {\n    const ray = new Ray()\n\n    // e = e.changedTouches[0]\n    const mouse = new Vector2()\n    const bounds = app.renderer.domElement.getBoundingClientRect()\n    mouse.x = ((pt.x - bounds.left) / bounds.width) * 2 - 1\n    mouse.y = -((pt.y - bounds.top) / bounds.height) * 2 + 1\n\n    ray.origin.copy(app.camera.position)\n    ray.direction.set(mouse.x, mouse.y, 0.5).unproject(app.camera).sub(ray.origin).normalize()\n\n    app.stagePos.worldToLocal(ray.origin)\n    ray.origin.add(new Vector3(0,0,-0.5))\n    const quat = new Quaternion()\n    quat.copy(app.stageRot.quaternion)\n    quat.inverse()\n    ray.direction.applyQuaternion(quat)\n\n    const hitNormal = new Vector3(0,0,0)\n    const hitPosition = new Vector3(0,0,0)\n    const hitBlock = traceRay(app.chunkManager,ray.origin,ray.direction,distance,hitPosition,hitNormal,EPSILON)\n    return {\n        hitBlock:hitBlock,\n        hitPosition:hitPosition,\n        hitNormal: hitNormal\n    }\n}\n\n\nexport const rand = (min,max) => Math.random()*(max-min) + min\n"
  },
  {
    "path": "src/webxr-boilerplate/BackgroundAudioLoader.js",
    "content": "import {\n  DefaultLoadingManager,\n} from \"three\"\n\nexport default class BackgroundAudioLoader {\n  constructor(manager) {\n      this.manager = ( manager !== undefined ) ? manager : DefaultLoadingManager;\n  }\n\n  load( url, onLoad, onProgress, onError ) {\n      console.log(\"BGAL loading\",url)\n\n      const music = new Audio(url)\n      music.autoplay = false\n      music.loop = true\n      music.controls = false\n      music.preload = 'auto'\n      music.volume = 0.75\n      music.addEventListener('canplay',()=>{\n          onLoad(music)\n          this.manager.itemEnd(url)\n      })\n\n      this.manager.itemStart(url)\n  }\n}\n"
  },
  {
    "path": "src/webxr-boilerplate/Pointer.js",
    "content": "import {Raycaster} from \"./raycaster.js\"\nimport {\n    Object3D,\n    Vector2,\n    Vector3,\n    Quaternion,\n    BufferGeometry,\n    Float32BufferAttribute,\n    LineBasicMaterial,\n    NormalBlending,\n    SphereBufferGeometry,\n    Line,\n    Mesh,\n    MeshLambertMaterial,\n} from \"three\"\n\nexport const POINTER_ENTER = \"enter\"\nexport const POINTER_EXIT = \"exit\"\nexport const POINTER_CLICK = \"click\"\nexport const POINTER_MOVE = \"move\"\nexport const POINTER_PRESS = \"press\"\nexport const POINTER_RELEASE = \"release\"\n\n\nconst toRad = (degrees) => degrees*Math.PI/180\n\nexport class Pointer {\n    constructor(app, opts) {\n        this.scene = app.scene\n        this.renderer = app.renderer\n        this.camera = app.camera\n        this.listeners = {}\n        this.opts = opts || {}\n        this.opts.enableLaser = (this.opts.enableLaser !== undefined) ? this.opts.enableLaser : true\n        this.opts.laserLength = (this.opts.laserLength !== undefined) ? this.opts.laserLength : 3\n        this.canvas = this.renderer.domElement\n\n        this.raycaster = new Raycaster()\n        this.waitcb = null\n        this.hoverTarget = null\n\n        this.intersectionFilter = this.opts.intersectionFilter || ((o) => true)\n        this.raycaster.recurseFilter = this.opts.recurseFilter || (()=> true)\n        this.multiTarget = this.opts.multiTarget || false\n\n\n        // setup the mouse\n        this.canvas.addEventListener('mousemove', this.mouseMove.bind(this))\n        this.canvas.addEventListener('click', this.mouseClick.bind(this))\n        this.canvas.addEventListener('mousedown',this.mouseDown.bind(this))\n        this.canvas.addEventListener('mouseup',this.mouseUp.bind(this))\n\n        //touch events\n        this.canvas.addEventListener('touchstart',this.touchStart.bind(this))\n        this.canvas.addEventListener('touchmove',this.touchMove.bind(this))\n        this.canvas.addEventListener('touchend',this.touchEnd.bind(this))\n\n        // setup the VR controllers\n        this.controller1 = this.renderer.vr.getController(0);\n        this.controller1.addEventListener('selectstart', this.controllerSelectStart.bind(this));\n        this.controller1.addEventListener('selectend', this.controllerSelectEnd.bind(this));\n\n\n        this.controller2 = this.renderer.vr.getController(1);\n        this.controller2.addEventListener('selectstart', this.controllerSelectStart.bind(this));\n        this.controller2.addEventListener('selectend', this.controllerSelectEnd.bind(this));\n\n        this.setMouseSimulatesController(opts.mouseSimulatesController)\n\n        this.scene.add(this.controller1);\n        this.scene.add(this.controller2);\n\n\n\n        if(this.opts.enableLaser) {\n            //create visible lines for the two controllers\n            const geometry = new BufferGeometry()\n            geometry.addAttribute('position', new Float32BufferAttribute([0, 0, 0, 0, 0, -this.opts.laserLength], 3));\n            geometry.addAttribute('color', new Float32BufferAttribute([1.0, 0.5, 0.5, 0, 0, 0], 3));\n\n            const material = new LineBasicMaterial({\n                vertexColors: false,\n                color: 0x880000,\n                linewidth: 5,\n                blending: NormalBlending\n            })\n\n            this.controller1.add(new Line(geometry, material));\n            this.controller2.add(new Line(geometry, material));\n        }\n\n    }\n\n    //override this to do something w/ the controllers on every tick\n    tick(time) {\n        this.controllerMove(this.controller1)\n        this.controllerMove(this.controller2)\n    }\n\n\n    fire(obj, type, payload) {\n        obj.dispatchEvent(payload)\n    }\n    fireSelf(type,payload) {\n        if(!this.listeners[type]) return\n        this.listeners[type].forEach(cb => cb(payload))\n    }\n\n    //make the camera follow the mouse in desktop mode. Helps w/ debugging.\n    cameraFollowMouse(e) {\n        const bounds = this.canvas.getBoundingClientRect()\n        const ry = ((e.clientX - bounds.left) / bounds.width) * 2 - 1\n        const rx = 1 - ((e.clientY - bounds.top) / bounds.height) * 2\n        this.camera.rotation.y = -ry*2\n        this.camera.rotation.x = +rx\n    }\n\n    mouseMove(e) {\n        const mouse = new Vector2()\n        const bounds = this.canvas.getBoundingClientRect()\n        mouse.x = ((e.clientX - bounds.left) / bounds.width) * 2 - 1\n        mouse.y = -((e.clientY - bounds.top) / bounds.height) * 2 + 1\n        this.raycaster.setFromCamera(mouse, this.camera)\n        if(this.opts.mouseSimulatesController) {\n            //create target from the mouse controls\n            const target = new Vector3()\n            target.x = mouse.x\n            target.y = mouse.y\n            target.z = -3\n            //convert to camera space\n            target.add(this.camera.position)\n            this.spot.position.copy(target)\n            this.controller1.lookAt(target)\n            //have to flip over because the UP is down on controllers\n            const flip = new Quaternion().setFromAxisAngle(new Vector3(0,1,0),toRad(180))\n            this.controller1.quaternion.multiply(flip)\n        }\n        this._processMove()\n\n        if(this.opts.cameraFollowMouse) this.cameraFollowMouse(e)\n    }\n\n    touchStart(e) {\n        e.preventDefault()\n        if(e.changedTouches.length <= 0) return\n        const tch = e.changedTouches[0]\n        const mouse = new Vector2()\n        const bounds = this.canvas.getBoundingClientRect()\n        mouse.x = ((tch.clientX - bounds.left) / bounds.width) * 2 - 1\n        mouse.y = -((tch.clientY - bounds.top) / bounds.height) * 2 + 1\n        this.raycaster.setFromCamera(mouse, this.camera)\n        const intersects = this.raycaster.intersectObjects(this.scene.children, true)\n            .filter(it => this.intersectionFilter(it.object))\n        intersects.forEach((it,i) => {\n            this.fire(it.object, POINTER_PRESS, {type: POINTER_PRESS})\n        })\n    }\n    touchMove(e) {\n        e.preventDefault()\n        if(e.changedTouches.length <= 0) return\n        const tch = e.changedTouches[0]\n        const mouse = new Vector2()\n        const bounds = this.canvas.getBoundingClientRect()\n        mouse.x = ((tch.clientX - bounds.left) / bounds.width) * 2 - 1\n        mouse.y = -((tch.clientY - bounds.top) / bounds.height) * 2 + 1\n        this.raycaster.setFromCamera(mouse, this.camera)\n        this._processMove()\n    }\n    touchEnd(e) {\n        e.preventDefault()\n        if(e.changedTouches.length <= 0) return\n        const tch = e.changedTouches[0]\n        const mouse = new Vector2()\n        const bounds = this.canvas.getBoundingClientRect()\n        mouse.x = ((tch.clientX - bounds.left) / bounds.width) * 2 - 1\n        mouse.y = -((tch.clientY - bounds.top) / bounds.height) * 2 + 1\n        this.raycaster.setFromCamera(mouse, this.camera)\n        const intersects = this.raycaster.intersectObjects(this.scene.children, true)\n            .filter(it => this.intersectionFilter(it.object))\n        intersects.forEach((it) => {\n            this.fire(it.object, POINTER_RELEASE, {type: POINTER_RELEASE, point: it.point})\n        })\n        this._processClick()\n    }\n\n    controllerMove(controller) {\n        if(!controller.visible) return\n        const c = controller\n        const dir = new Vector3(0, 0, -1)\n        dir.applyQuaternion(c.quaternion)\n        this.raycaster.set(c.position, dir)\n        this._processMove()\n    }\n\n    _processMove() {\n        const intersects = this.raycaster.intersectObjects(this.scene.children, true)\n            .filter(it => this.intersectionFilter(it.object))\n\n        if(intersects.length === 0 && this.hoverTarget) {\n            this.fire(this.hoverTarget, POINTER_EXIT, {type: POINTER_EXIT})\n            this.hoverTarget = null\n        }\n        if(intersects.length >= 1) {\n            const it = intersects[0]\n            const obj = it.object\n            if (!obj) return\n            this.fire(obj, POINTER_MOVE, {type: POINTER_MOVE, point: it.point, intersection:it})\n            if (obj === this.hoverTarget) {\n                //still inside\n            } else {\n                if (this.hoverTarget)\n                    this.fire(this.hoverTarget, POINTER_EXIT, {type: POINTER_EXIT})\n                this.hoverTarget = obj\n                this.fire(this.hoverTarget, POINTER_ENTER, {type: POINTER_ENTER})\n            }\n        }\n    }\n\n    _processClick() {\n        if (this.waitcb) {\n            this.waitcb()\n            this.waitcb = null\n            return\n        }\n\n        const intersects = this.raycaster.intersectObjects(this.scene.children, true)\n            .filter(it => this.intersectionFilter(it.object))\n        if(intersects.length > 0) {\n            const it = intersects[0]\n            this.fire(it.object, POINTER_CLICK, {type: POINTER_CLICK, point: it.point, intersection:it})\n        } else {\n            this.fireSelf(POINTER_CLICK, {type: POINTER_CLICK})\n        }\n    }\n    mouseClick(e) {\n        const mouse = new Vector2()\n        const bounds = this.canvas.getBoundingClientRect()\n        mouse.x = ((e.clientX - bounds.left) / bounds.width) * 2 - 1\n        mouse.y = -((e.clientY - bounds.top) / bounds.height) * 2 + 1\n        this.raycaster.setFromCamera(mouse, this.camera)\n        this._processClick()\n    }\n    mouseDown(e) {\n        const mouse = new Vector2()\n        const bounds = this.canvas.getBoundingClientRect()\n        mouse.x = ((e.clientX - bounds.left) / bounds.width) * 2 - 1\n        mouse.y = -((e.clientY - bounds.top) / bounds.height) * 2 + 1\n        this.raycaster.setFromCamera(mouse, this.camera)\n        const intersects = this.raycaster.intersectObjects(this.scene.children, true)\n            .filter(it => this.intersectionFilter(it.object))\n\n        intersects.forEach((it,i) => {\n            if(!this.multiTarget && i > 0) return\n            this.fire(it.object, POINTER_PRESS, {type: POINTER_PRESS, point: it.point, intersection:it})\n        })\n    }\n    mouseUp(e) {\n        const mouse = new Vector2()\n        const bounds = this.canvas.getBoundingClientRect()\n        mouse.x = ((e.clientX - bounds.left) / bounds.width) * 2 - 1\n        mouse.y = -((e.clientY - bounds.top) / bounds.height) * 2 + 1\n        this.raycaster.setFromCamera(mouse, this.camera)\n        const intersects = this.raycaster.intersectObjects(this.scene.children, true)\n            .filter(it => this.intersectionFilter(it.object))\n        intersects.forEach((it,i) => {\n            if(!this.multiTarget && i > 0) return //skip all but the first\n            this.fire(it.object, POINTER_RELEASE, {type: POINTER_RELEASE, point: it.point, intersection:it})\n        })\n    }\n\n    controllerSelectStart(e) {\n        e.target.userData.isSelecting = true;\n        const intersects = this.raycaster.intersectObjects(this.scene.children, true)\n            .filter(it => this.intersectionFilter(it.object))\n        intersects.forEach((it,i) => {\n            if(!this.multiTarget && i > 0) return //skip all but the first\n            this.fire(it.object, POINTER_PRESS, {type: POINTER_PRESS, point: it.point, intersection:it})\n        })\n    }\n\n    controllerSelectEnd(e) {\n        e.target.userData.isSelecting = false;\n        const c = e.target\n        const dir = new Vector3(0, 0, -1)\n        dir.applyQuaternion(c.quaternion)\n        this.raycaster.set(c.position, dir)\n        const intersects = this.raycaster.intersectObjects(this.scene.children, true)\n            .filter(it => this.intersectionFilter(it.object))\n        intersects.forEach((it,i) => {\n            if(!this.multiTarget && i > 0) return //skip all but the first\n            this.fire(it.object, POINTER_RELEASE, {type: POINTER_RELEASE, point: it.point})\n        })\n        this._processClick()\n    }\n\n    waitSceneClick(cb) {\n        this.waitcb = cb\n    }\n\n\n    addEventListener(type,cb) {\n        this.on(type,cb)\n    }\n    on(type,cb) {\n        if(!this.listeners[type]) this.listeners[type] = []\n        this.listeners[type].push(cb)\n    }\n    off(type,cb) {\n        this.listeners[type] = this.listeners[type].filter(c => c !== cb)\n    }\n    setMouseSimulatesController(val) {\n        this.opts.mouseSimulatesController = val\n        if(this.opts.mouseSimulatesController) {\n            this.controller1 = new Group()\n            this.controller1.position.set(0,1,-2)\n            this.controller1.quaternion.setFromUnitVectors(Object3D.DefaultUp, new Vector3(0,0,1))\n            this.spot = new Mesh(\n                new SphereBufferGeometry(0.1),\n                new MeshLambertMaterial({color: 'red'})\n            )\n            this.scene.add(this.spot)\n        } else {\n        }\n\n    }\n}\n"
  },
  {
    "path": "src/webxr-boilerplate/WebXRBoilerPlate.js",
    "content": "import {DefaultLoadingManager, PerspectiveCamera, Scene, WebGLRenderer,} from \"three\"\nimport VRManager, {VR_DETECTED} from \"./vrmanager.js\";\n\nexport const FULLSCREEN_ENTERED = \"fullscreenenter\"\nexport const FULLSCREEN_EXITED = \"fullscreenexit\"\nexport default class WebXRBoilerPlate {\n    constructor(options) {\n        this.listeners = {}\n        this.container = options.container\n        this.resizeOnNextRepaint = false\n\n        //polyfill the container for fullscreen support\n        if(!container.requestFullscreen) {\n            if(container.webkitRequestFullscreen) {\n                container.requestFullscreen = container.webkitRequestFullScreen\n            }\n        }\n    }\n    addEventListener(type,cb) {\n        if(!this.listeners[type]) this.listeners[type] = []\n        this.listeners[type].push(cb)\n    }\n    _fire(type,payload) {\n        if(!this.listeners[type]) this.listeners[type] = []\n        this.listeners[type].forEach(cb => cb(payload))\n    }\n\n    isFullscreenSupported() {\n        return this.container.requestFullscreen?true:false\n    }\n\n    init() {\n        this.scene = new Scene();\n        this.camera = new PerspectiveCamera(70, this.container.clientWidth / this.container.clientHeight, 0.1, 50);\n        this.renderer = new WebGLRenderer({antialias: true});\n        this.renderer.setPixelRatio(window.devicePixelRatio);\n        this.renderer.setSize(this.container.clientWidth, this.container.clientHeight);\n        this.renderer.gammaOutput = true\n        this.renderer.vr.enabled = true;\n        this.container.appendChild(this.renderer.domElement);\n        this.vrmanager = new VRManager(this.renderer)\n        this.vrmanager.addEventListener(VR_DETECTED, ()=> this._fire(VR_DETECTED,this))\n\n        this.loadingManager = DefaultLoadingManager\n\n        DefaultLoadingManager.joshtest = true\n        DefaultLoadingManager.onStart = (url, loaded, total) => {\n            console.log(`XR: loading ${url}.  loaded ${loaded} of ${total}`)\n        }\n        DefaultLoadingManager.onLoad = () => {\n            console.log(`XR: loading complete`)\n            if (this.listeners.loaded) this.listeners.loaded.forEach(cb => cb(this))\n        }\n        DefaultLoadingManager.onProgress = (url, loaded, total) => {\n            console.log(`XR: prog ${url}.  loaded ${loaded} of ${total}`)\n            if(this.listeners.progress) this.listeners.progress.forEach(cb => cb(loaded/total))\n        }\n        DefaultLoadingManager.onError = (url) => {\n            console.log(`XR: error loading ${url}`)\n        }\n\n        this.lastSize = { width: 0, height: 0}\n        this.render = (time) => {\n            if (this.onRenderCb) this.onRenderCb(time,this)\n            this.checkContainerSize()\n            this.renderer.render(this.scene, this.camera);\n        }\n\n        this.renderer.setAnimationLoop(this.render)\n\n        this.fullscreenchangeHandler = () => {\n            if(document.fullscreenElement) return this._fire(FULLSCREEN_ENTERED, this)\n            if(document.webkitFullscreenElement) return this._fire(FULLSCREEN_ENTERED, this)\n            this._fire(FULLSCREEN_EXITED,this)\n        }\n        document.addEventListener('fullscreenchange',this.fullscreenchangeHandler)\n        document.addEventListener('webkitfullscreenchange',this.fullscreenchangeHandler)\n\n        return new Promise((res, rej) => {\n            res(this)\n        })\n    }\n\n\n    onRender(cb) {\n        this.onRenderCb = cb\n    }\n\n    enterVR() {\n        this.vrmanager.enterVR()\n    }\n\n    playFullscreen() {\n        this.resizeOnNextRepaint = true\n        this.container.requestFullscreen()\n    }\n\n    exitFullscreen() {\n        this.resizeOnNextRepaint = true\n        if(document.exitFullscreen) document.exitFullscreen()\n        if(document.webkitExitFullscreen) document.webkitExitFullscreen()\n    }\n\n    checkContainerSize() {\n        if(this.lastSize.width !== this.container.clientWidth || this.lastSize.height !== this.container.clientHeight) {\n            this.lastSize.width = this.container.clientWidth\n            this.lastSize.height = this.container.clientHeight\n            this.camera.aspect = this.lastSize.width / this.lastSize.height;\n            this.camera.updateProjectionMatrix();\n            this.renderer.setSize(this.lastSize.width, this.lastSize.height);\n        }\n    }\n}\n"
  },
  {
    "path": "src/webxr-boilerplate/raycaster.js",
    "content": "import {Ray} from \"three\"\n\n/**\n * @author mrdoob / http://mrdoob.com/\n * @author bhouston / http://clara.io/\n * @author stephomi / http://stephaneginier.com/\n */\n\nfunction Raycaster( origin, direction, near, far ) {\n\n    this.ray = new Ray( origin, direction );\n    // direction is assumed to be normalized (for accurate distance calculations)\n\n    this.near = near || 0;\n    this.far = far || Infinity;\n\n    this.params = {\n        Mesh: {},\n        Line: {},\n        LOD: {},\n        Points: { threshold: 1 },\n        Sprite: {}\n    };\n\n    Object.defineProperties( this.params, {\n        PointCloud: {\n            get: function () {\n\n                console.warn( 'THREE.Raycaster: params.PointCloud has been renamed to params.Points.' );\n                return this.Points;\n\n            }\n        }\n    } );\n\n}\n\nfunction ascSort( a, b ) {\n\n    return a.distance - b.distance;\n\n}\n\nlet count = 0\n\nfunction intersectObject( object, raycaster, intersects, recursive ) {\n\n    if ( object.visible === false ) return;\n    if(raycaster.recurseFilter && !raycaster.recurseFilter(object)) return;\n\n    count++\n    object.raycast( raycaster, intersects );\n\n    if ( recursive === true ) {\n\n        var children = object.children;\n\n        for ( var i = 0, l = children.length; i < l; i ++ ) {\n\n            intersectObject( children[ i ], raycaster, intersects, true );\n\n        }\n\n    }\n\n}\n\nObject.assign( Raycaster.prototype, {\n\n    linePrecision: 1,\n\n    set: function ( origin, direction ) {\n\n        // direction is assumed to be normalized (for accurate distance calculations)\n\n        this.ray.set( origin, direction );\n\n    },\n\n    setFromCamera: function ( coords, camera ) {\n\n        if ( ( camera && camera.isPerspectiveCamera ) ) {\n\n            this.ray.origin.setFromMatrixPosition( camera.matrixWorld );\n            this.ray.direction.set( coords.x, coords.y, 0.5 ).unproject( camera ).sub( this.ray.origin ).normalize();\n\n        } else if ( ( camera && camera.isOrthographicCamera ) ) {\n\n            this.ray.origin.set( coords.x, coords.y, ( camera.near + camera.far ) / ( camera.near - camera.far ) ).unproject( camera ); // set origin in plane of camera\n            this.ray.direction.set( 0, 0, - 1 ).transformDirection( camera.matrixWorld );\n\n        } else {\n\n            console.error( 'THREE.Raycaster: Unsupported camera type.' );\n\n        }\n\n    },\n\n    intersectObject: function ( object, recursive, optionalTarget ) {\n\n        var intersects = optionalTarget || [];\n\n        intersectObject( object, this, intersects, recursive);\n\n        intersects.sort( ascSort );\n\n        return intersects;\n\n    },\n\n    intersectObjects: function ( objects, recursive, optionalTarget ) {\n        count = 0\n        var intersects = optionalTarget || [];\n\n        if ( Array.isArray( objects ) === false ) {\n\n            console.warn( 'THREE.Raycaster.intersectObjects: objects is not an Array.' );\n            return intersects;\n\n        }\n\n        for ( var i = 0, l = objects.length; i < l; i ++ ) {\n\n            intersectObject( objects[ i ], this, intersects, recursive);\n\n        }\n\n        // console.log(\"intersected objects\",count)\n\n        intersects.sort( ascSort );\n\n        return intersects;\n\n    }\n\n} );\n\n\nexport { Raycaster };\n"
  },
  {
    "path": "src/webxr-boilerplate/vrmanager.js",
    "content": "function printError(err) {\n  console.log(err)\n}\n\nexport const VR_DETECTED = \"detected\"\nexport const VR_CONNECTED = \"connected\"\nexport const VR_DISCONNECTED = \"disconnected\"\nexport const VR_PRESENTCHANGE = \"presentchange\"\nexport const VR_ACTIVATED = \"activated\"\n\n\n\nexport default class VRManager {\n  constructor(renderer) {\n      this.device = null\n      this.renderer = renderer\n      if(!this.renderer) throw new Error(\"VR Manager requires a valid ThreeJS renderer instance\")\n      this.listeners = {}\n\n      if ('xr' in navigator && navigator.xr.requestDevice) {\n          console.log(\"has webxr\")\n          navigator.xr.requestDevice().then((device) => {\n              device.supportsSession({immersive: true, exclusive: true /* DEPRECATED */})\n                  .then(() => {\n                      this.device = device\n                      this.fire(VR_DETECTED,{})\n                  })\n                  .catch(printError);\n\n          }).catch(printError);\n      } else if ('getVRDisplays' in navigator) {\n          console.log(\"has webvr\")\n\n          window.addEventListener( 'vrdisplayconnect', ( event ) => {\n              this.device = event.display\n              this.fire(VR_CONNECTED)\n          }, false );\n\n          window.addEventListener( 'vrdisplaydisconnect', ( event )  => {\n              this.fire(VR_DISCONNECTED)\n          }, false );\n\n          window.addEventListener( 'vrdisplaypresentchange', ( event ) => {\n              this.fire(VR_PRESENTCHANGE)\n          }, false );\n\n          window.addEventListener( 'vrdisplayactivate',  ( event ) => {\n              this.device = event.display\n              this.device.requestPresent([{source:this.renderer.domElement}])\n              this.fire(VR_ACTIVATED)\n          }, false );\n\n          navigator.getVRDisplays()\n              .then( ( displays ) => {\n                  console.log(\"vr scanned\")\n                  if ( displays.length > 0 ) {\n\n                      // showEnterVR( displays[ 0 ] );\n                      console.log(\"found vr\")\n                      this.device = displays[0]\n                      this.fire(VR_DETECTED,{})\n\n                  } else {\n                      console.log(\"no vr at all\")\n                      // showVRNotFound();\n                  }\n\n              } ).catch(printError);\n\n      } else {\n          // no vr\n          console.log(\"no vr at all\")\n      }\n  }\n\n  addEventListener(type, cb) {\n      if(!this.listeners[type]) this.listeners[type] = []\n      this.listeners[type].push(cb)\n  }\n  fire(type,evt) {\n      if(!evt) evt = {}\n      evt.type = type\n      if(!this.listeners[type]) this.listeners[type] = []\n      this.listeners[type].forEach(cb => cb(evt))\n  }\n\n  enterVR() {\n      if(!this.device) {\n          console.warn(\"tried to connect VR on an invalid device\")\n          return\n      }\n      console.log(\"entering VR\")\n      this.renderer.vr.setDevice( this.device );\n\n      if(this.device.isPresenting) {\n          this.device.exitPresent()\n      } else {\n          this.device.requestPresent([{source: this.renderer.domElement}]);\n      }\n  }\n\n}\n"
  },
  {
    "path": "src/webxr-boilerplate/vrstats.js",
    "content": "import { Group, CanvasTexture, Mesh, PlaneBufferGeometry, PlaneGeometry, MeshBasicMaterial, } from 'three'\nexport default class VRStats extends Group {\n    constructor(app) {\n        super();\n        this.renderer = app.renderer\n        const can = document.createElement('canvas')\n        can.width = 256\n        can.height = 128\n        this.canvas = can\n        const c = can.getContext('2d')\n        c.fillStyle = '#00ffff'\n        c.fillRect(0,0,can.width,can.height)\n        const ctex = new CanvasTexture(can)\n        const mesh = new Mesh(\n            new PlaneGeometry(1,0.5),\n            new MeshBasicMaterial({map:ctex})\n        )\n        this.position.z = -3\n        this.position.y = 1.5\n        this.add(mesh)\n        this.cmesh = mesh\n\n        this.last = 0\n        this.lastFrame = 0\n        this.customProps = {}\n    }\n\n    update(time) {\n        if(time - this.last > 300) {\n            // console.log(\"updating\",this.renderer.info)\n            // console.log(`stats calls:`,this.renderer.info)\n\n            const fps = ((this.renderer.info.render.frame - this.lastFrame)*1000)/(time-this.last)\n            // console.log(fps)\n\n            const c = this.canvas.getContext('2d')\n            c.fillStyle = 'white'\n            c.fillRect(0, 0, this.canvas.width, this.canvas.height)\n            c.fillStyle = 'black'\n            c.font = '16pt sans-serif'\n            c.fillText(`calls: ${this.renderer.info.render.calls}`, 3, 20)\n            c.fillText(`tris : ${this.renderer.info.render.triangles}`, 3, 40)\n            c.fillText(`fps : ${fps.toFixed(2)}`,3,60)\n            Object.keys(this.customProps).forEach((key,i) => {\n                const val = this.customProps[key]\n                c.fillText(`${key} : ${val}`,3,80+i*20)\n            })\n            this.cmesh.material.map.needsUpdate = true\n            this.last = time\n            this.lastFrame = this.renderer.info.render.frame\n        }\n    }\n\n    setProperty(name, value) {\n        this.customProps[name] = value\n    }\n\n}\n"
  },
  {
    "path": "webpack.config.js",
    "content": "const path = require('path');\nconst HtmlWebpackPlugin = require('html-webpack-plugin');\nconst CopyPlugin = require('copy-webpack-plugin');\nconst { CleanWebpackPlugin } = require('clean-webpack-plugin');\n\nconst isLibrary = !!process.env.LIBRARY\n\n\nlet config;\nif (isLibrary) {\n  console.log(\"Building for library\")\n  config = {\n    entry: {\n      'voxeljs-next': path.join(__dirname, \"src/index.js\"),\n    },\n    plugins:[\n      new CleanWebpackPlugin(),\n    ],\n    externals: {\n      three: {\n        commonjs: 'three',\n        commonjs2: 'three',\n        amd: 'three',\n      },\n      ecsy: {\n        commonjs: 'ecsy',\n        commonjs2: 'ecsy',\n        amd: 'ecsy',\n      },\n      'ecsy-three': {\n        commonjs: 'ecsy-three',\n        commonjs2: 'ecsy-three',\n        amd: 'ecsy-three',\n      },\n    }\n  }\n} else {\n  config = {\n    entry: {\n      'voxeljs-next': path.join(__dirname, \"src/index.js\"),\n      'app': path.join(__dirname, \"examples/src/index.js\"),\n    },\n    plugins:[\n      new CleanWebpackPlugin(),\n      new HtmlWebpackPlugin({ title: 'Output', template: 'examples/public/index.html', inject: true}),\n      new CopyPlugin([\n        { from: path.join(__dirname, 'examples/public'), to: path.resolve(__dirname, 'dist') },\n      ]),\n    ],\n  }\n}\n\nmodule.exports = ['source-map'].map(devtool => ({\n  mode: 'development',\n\n  output: {\n    path: path.resolve(__dirname, 'dist'),\n    filename: '[name].js'\n  },\n\n  module: {\n    rules: [\n      {\n        test: /\\.css$/,\n        use: [\n          'style-loader',\n          'css-loader',\n        ],\n      },\n    ],\n  },\n\n  optimization: {\n    splitChunks: {\n      chunks: 'all',\n    },\n  },\n\n  devtool,\n  ...config\n}));\n"
  }
]