├── .github └── workflows │ └── static.yml ├── .gitignore ├── .vscode └── launch.json ├── LICENSE ├── README.md ├── package-lock.json ├── package.json ├── src ├── index.html ├── public │ ├── favicon.ico │ ├── fonts │ │ ├── Bebas-Regular.otf │ │ ├── Digitalt.otf │ │ └── Stanberry.ttf │ ├── icons │ │ ├── bulldozer-color.png │ │ ├── bulldozer.png │ │ ├── calendar.png │ │ ├── factory-color.png │ │ ├── factory.png │ │ ├── house-color.png │ │ ├── house.png │ │ ├── job.png │ │ ├── office.png │ │ ├── pause-color.png │ │ ├── pause.png │ │ ├── person.png │ │ ├── play-color.png │ │ ├── play.png │ │ ├── power-color.png │ │ ├── power-line-color.png │ │ ├── power.png │ │ ├── road-color.png │ │ ├── road.png │ │ ├── select-color.png │ │ ├── select.png │ │ └── store-color.png │ ├── main.css │ ├── models │ │ ├── FBX2glTF │ │ ├── armored-truck.glb │ │ ├── atm-mechine.glb │ │ ├── baby-carriage.glb │ │ ├── balloon-stripes.glb │ │ ├── basketball-stand.glb │ │ ├── bench-forest.glb │ │ ├── bench-old.glb │ │ ├── bike-old.glb │ │ ├── bike-stand.glb │ │ ├── bin-wheelie.glb │ │ ├── boat-fishing.glb │ │ ├── boat-sail.glb │ │ ├── boat-speed.glb │ │ ├── building-antique-china.glb │ │ ├── building-apartment-china.glb │ │ ├── building-bank.glb │ │ ├── building-block-4floor-back.glb │ │ ├── building-block-4floor-corner.glb │ │ ├── building-block-4floor-front.glb │ │ ├── building-block-4floor-short.glb │ │ ├── building-block-5floor-corner.glb │ │ ├── building-block-5floor-front.glb │ │ ├── building-block-5floor-short.glb │ │ ├── building-block-5floor.glb │ │ ├── building-burger-joint.glb │ │ ├── building-cabin-big.glb │ │ ├── building-cabin-small.glb │ │ ├── building-cafe.glb │ │ ├── building-carwash.glb │ │ ├── building-casino.glb │ │ ├── building-cinema.glb │ │ ├── building-firestation.glb │ │ ├── building-hospital.glb │ │ ├── building-hotel.glb │ │ ├── building-house-block-big.glb │ │ ├── building-house-block-old.glb │ │ ├── building-house-block.glb │ │ ├── building-house-family-large.glb │ │ ├── building-house-family-small.glb │ │ ├── building-house-modern-big.glb │ │ ├── building-house-modern.glb │ │ ├── building-mall.glb │ │ ├── building-market-china.glb │ │ ├── building-museum.glb │ │ ├── building-office-balcony.glb │ │ ├── building-office-big.glb │ │ ├── building-office-pyramid.glb │ │ ├── building-office-rounded.glb │ │ ├── building-office-tall.glb │ │ ├── building-office.glb │ │ ├── building-pagoda-china.glb │ │ ├── building-policestation-garage.glb │ │ ├── building-policestation.glb │ │ ├── building-port-sea.glb │ │ ├── building-post.glb │ │ ├── building-restaurant.glb │ │ ├── building-restuarant-china.glb │ │ ├── building-school.glb │ │ ├── building-shop-china.glb │ │ ├── building-skyscraper.glb │ │ ├── building-stadium.glb │ │ ├── building-temple-china.glb │ │ ├── building-train-station.glb │ │ ├── burger-joint-sign.glb │ │ ├── burger-statue.glb │ │ ├── bus-passenger.glb │ │ ├── bus-school.glb │ │ ├── bus-stop-sign.glb │ │ ├── bus-stop.glb │ │ ├── cactus-big.glb │ │ ├── cactus-medium.glb │ │ ├── canal-cover.glb │ │ ├── car-ambulance-pickup.glb │ │ ├── car-baywatch.glb │ │ ├── car-firefighter-pickup.glb │ │ ├── car-formula.glb │ │ ├── car-hippie-van.glb │ │ ├── car-passenger-race.glb │ │ ├── car-passenger.glb │ │ ├── car-police.glb │ │ ├── car-taxi.glb │ │ ├── car-tow-truck.glb │ │ ├── car-truck-cement.glb │ │ ├── car-truck-dump.glb │ │ ├── car-truck-tanker.glb │ │ ├── car-veteran.glb │ │ ├── cargo-shipping_blue.glb │ │ ├── cargo-shipping_green.glb │ │ ├── cargo-shipping_orange.glb │ │ ├── cargo-shipping_red.glb │ │ ├── cargo-shipping_white.glb │ │ ├── cargo-smple.glb │ │ ├── chair-folding.glb │ │ ├── chimney-big.glb │ │ ├── construction-small.glb │ │ ├── control-tower.glb │ │ ├── cooling-tower.glb │ │ ├── data-center.glb │ │ ├── dryer-outside.glb │ │ ├── dumpster.glb │ │ ├── excavator.glb │ │ ├── fence-big.glb │ │ ├── fence-classic.glb │ │ ├── fence-picket.glb │ │ ├── fence-shrub.glb │ │ ├── fence-start.glb │ │ ├── fence-stone-gate-small.glb │ │ ├── fence-stone-gate.glb │ │ ├── fence-stone-metal.glb │ │ ├── fence-stone-tower.glb │ │ ├── fence-stone.glb │ │ ├── fence-vineyard.glb │ │ ├── fence.glb │ │ ├── ferris-wheel.glb │ │ ├── fire-hydrant.glb │ │ ├── firetruck.glb │ │ ├── flag-golf.glb │ │ ├── floatplane.glb │ │ ├── flowers-window.glb │ │ ├── forklift.glb │ │ ├── fountain.glb │ │ ├── free-fall-ride.glb │ │ ├── freight-train.glb │ │ ├── gate-china.glb │ │ ├── golf-cart.glb │ │ ├── grass-basic.glb │ │ ├── grass-clumb.glb │ │ ├── grass-long.glb │ │ ├── grass-tall.glb │ │ ├── grass.glb │ │ ├── grill-round.glb │ │ ├── ground-cracked.glb │ │ ├── ground-lines.glb │ │ ├── guidepost.glb │ │ ├── helicopter.glb │ │ ├── industry-building.glb │ │ ├── industry-factory-hall.glb │ │ ├── industry-factory-old.glb │ │ ├── industry-factory.glb │ │ ├── industry-refinery.glb │ │ ├── industry-storage.glb │ │ ├── industry-warehouse.glb │ │ ├── jeep-open.glb │ │ ├── lamp-city.glb │ │ ├── lamp-road-double.glb │ │ ├── lamp-road.glb │ │ ├── lantern-long.glb │ │ ├── lantern-sphere.glb │ │ ├── mail-box.glb │ │ ├── mainroad-sign-green.glb │ │ ├── marketplace-stand-simple.glb │ │ ├── mosque-tower.glb │ │ ├── mosque.glb │ │ ├── mountain-desert.glb │ │ ├── mountains.glb │ │ ├── nuclear-power-plant.glb │ │ ├── palette.glb │ │ ├── palm-round.glb │ │ ├── palm-small.glb │ │ ├── palm.glb │ │ ├── pier-tile-straight.glb │ │ ├── plane-passenger.glb │ │ ├── power_line_pole.glb │ │ ├── power_line_pole_modified.glb │ │ ├── rock-pillar.glb │ │ ├── rock-sharp.glb │ │ ├── rock-terrasse.glb │ │ ├── rocks-small.glb │ │ ├── sand-box.glb │ │ ├── ship-cargo.glb │ │ ├── ship-yacht.glb │ │ ├── shrub-round.glb │ │ ├── shrub.glb │ │ ├── skyscraper-huge.glb │ │ ├── skyscraper-large.glb │ │ ├── skyscraper-medium.glb │ │ ├── skyscraper-part-bottom.glb │ │ ├── skyscraper-part-middle.glb │ │ ├── skyscraper-part-top.glb │ │ ├── skyscraper-small.glb │ │ ├── skyscraper-tiny.glb │ │ ├── skyscraper.glb │ │ ├── solar-panel-house.glb │ │ ├── stone-diamond.glb │ │ ├── stone-flat.glb │ │ ├── stone-oval.glb │ │ ├── stone-pointy.glb │ │ ├── stone-round.glb │ │ ├── stone-small.glb │ │ ├── stump-small.glb │ │ ├── stump.glb │ │ ├── sunflower.glb │ │ ├── tent-circus-big.glb │ │ ├── terrain-mountain-range.glb │ │ ├── terrain-mountains.glb │ │ ├── terrain-valley.glb │ │ ├── tile-golf-green-circle.glb │ │ ├── tile-golf-green.glb │ │ ├── tile-golf-sand.glb │ │ ├── tile-hill-askew-valley-corner.glb │ │ ├── tile-hill-askew.glb │ │ ├── tile-hill-corner.glb │ │ ├── tile-hill-curve.glb │ │ ├── tile-hill-valley.glb │ │ ├── tile-hill.glb │ │ ├── tile-mainroad-curve.glb │ │ ├── tile-mainroad-hill.glb │ │ ├── tile-mainroad-intersection-t.glb │ │ ├── tile-mainroad-intersection.glb │ │ ├── tile-mainroad-road-intersection-t.glb │ │ ├── tile-mainroad-road-intersection.glb │ │ ├── tile-mainroad-straight-crosswalk.glb │ │ ├── tile-mainroad-straight.glb │ │ ├── tile-park.glb │ │ ├── tile-plain-hump.glb │ │ ├── tile-plain_asphalt.glb │ │ ├── tile-plain_concrete.glb │ │ ├── tile-plain_dirt.glb │ │ ├── tile-plain_grass.glb │ │ ├── tile-plain_sand.glb │ │ ├── tile-road-curve.glb │ │ ├── tile-road-end.glb │ │ ├── tile-road-hill.glb │ │ ├── tile-road-intersection-t.glb │ │ ├── tile-road-intersection.glb │ │ ├── tile-road-mainroad-intersection-t.glb │ │ ├── tile-road-mainroad-intersection.glb │ │ ├── tile-road-straight-crosswalk.glb │ │ ├── tile-road-straight.glb │ │ ├── tile-road-to-mainroad-intersection-t.glb │ │ ├── tile-road-to-mainroad.glb │ │ ├── tile-roads-mainroad-intersection.glb │ │ ├── tile-sidewalk-hill.glb │ │ ├── tile-sidewalk-straight.glb │ │ ├── tile-water.glb │ │ ├── traffic-lights.glb │ │ ├── tree-beech.glb │ │ ├── tree-birch-tall.glb │ │ ├── tree-birch.glb │ │ ├── tree-bonsai.glb │ │ ├── tree-conifer.glb │ │ ├── tree-dead.glb │ │ ├── tree-dry.glb │ │ ├── tree-elipse.glb │ │ ├── tree-fir.glb │ │ ├── tree-forest.glb │ │ ├── tree-lime.glb │ │ ├── tree-little.glb │ │ ├── tree-oak.glb │ │ ├── tree-old.glb │ │ ├── tree-poplar.glb │ │ ├── tree-pot.glb │ │ ├── tree-round.glb │ │ ├── tree-spruce.glb │ │ ├── tree-square.glb │ │ ├── tree-tall.glb │ │ ├── tree.glb │ │ ├── tribune-streight.glb │ │ └── truck.glb │ ├── statusIcons │ │ ├── no-power.png │ │ └── no-road-access.png │ └── textures │ │ ├── base.png │ │ ├── grid.png │ │ └── specular.png └── scripts │ ├── assets │ ├── assetManager.js │ └── models.js │ ├── camera.js │ ├── config.js │ ├── game.js │ ├── input.js │ ├── sim │ ├── buildings │ │ ├── building.js │ │ ├── buildingFactory.js │ │ ├── buildingStatus.js │ │ ├── buildingType.js │ │ ├── modules │ │ │ ├── development.js │ │ │ ├── jobs.js │ │ │ ├── power.js │ │ │ ├── residents.js │ │ │ ├── roadAccess.js │ │ │ └── simModule.js │ │ ├── power │ │ │ ├── powerLine.js │ │ │ └── powerPlant.js │ │ ├── transportation │ │ │ └── road.js │ │ └── zones │ │ │ ├── commercial.js │ │ │ ├── industrial.js │ │ │ ├── residential.js │ │ │ └── zone.js │ ├── citizen.js │ ├── city.js │ ├── services │ │ ├── power.js │ │ └── simService.js │ ├── simObject.js │ ├── tile.js │ └── vehicles │ │ ├── vehicle.js │ │ ├── vehicleGraph.js │ │ ├── vehicleGraphHelper.js │ │ ├── vehicleGraphNode.js │ │ └── vehicleGraphTile.js │ └── ui.js └── vite.config.js /.github/workflows/static.yml: -------------------------------------------------------------------------------- 1 | # Simple workflow for deploying static content to GitHub Pages 2 | name: Deploy static content to Pages 3 | 4 | on: 5 | # Runs on pushes targeting the default branch 6 | push: 7 | branches: ['main'] 8 | 9 | # Allows you to run this workflow manually from the Actions tab 10 | workflow_dispatch: 11 | 12 | # Sets the GITHUB_TOKEN permissions to allow deployment to GitHub Pages 13 | permissions: 14 | contents: read 15 | pages: write 16 | id-token: write 17 | 18 | # Allow one concurrent deployment 19 | concurrency: 20 | group: 'pages' 21 | cancel-in-progress: true 22 | 23 | jobs: 24 | # Single deploy job since we're just deploying 25 | deploy: 26 | environment: 27 | name: github-pages 28 | url: ${{ steps.deployment.outputs.page_url }} 29 | runs-on: ubuntu-latest 30 | steps: 31 | - name: Checkout 32 | uses: actions/checkout@v3 33 | - name: Set up Node 34 | uses: actions/setup-node@v3 35 | with: 36 | node-version: 18 37 | cache: 'npm' 38 | - name: Install dependencies 39 | run: npm install 40 | - name: Build 41 | run: npm run build 42 | - name: Setup Pages 43 | uses: actions/configure-pages@v3 44 | - name: Upload artifact 45 | uses: actions/upload-pages-artifact@v1 46 | with: 47 | # Upload dist repository 48 | path: './src/dist' 49 | - name: Deploy to GitHub Pages 50 | id: deployment 51 | uses: actions/deploy-pages@v1 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "chrome", 9 | "request": "launch", 10 | "name": "Launch Chrome against localhost", 11 | "url": "http://localhost:5501/index.html", 12 | "webRoot": "${workspaceFolder}" 13 | } 14 | ] 15 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Daniel Greenheck 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CitySim.js 2 | 3 | ## What is this? 4 | 5 | This is an ongoing project where I attempt to create a clone of SimCity using [Three.js](https://threejs.org/). 6 | 7 | ## How do I run this locally? 8 | 9 | You will first need to install [Node.js](https://nodejs.org). 10 | 11 | After that, clone this repository, navigate to the root directory and run the following command 12 | 13 | ```bash 14 | npm run dev 15 | ``` 16 | 17 | ## How did you make this? 18 | 19 | Want to know how I made this? Follow the YouTube tutorial series [here](https://www.youtube.com/playlist?list=PLtzt35QOXmkJ9unmoeA5gXHcscQHJVQpW) 20 | 21 | ## I want to play with it! 22 | 23 | Here you go! (FYI - At the moment this game does not have mobile support) 24 | 25 | https://dgreenheck.github.io/simcity-threejs-clone 26 | 27 | ## Support 28 | 29 | 🌟 Love what I’m building? Consider showing your support by becoming a patron! Your support helps me dedicate more time to developing this project. Plus, you’ll get the chance to influence future development by voting on what features I work on next! Every bit of support makes a huge difference! 30 | 31 | 💖 Join the community of supporters now: [Become a Patron](https://www.patreon.com/coffeecodecreate) 32 | 33 | ## License 34 | 35 | This code is covered by the MIT License. TLDR; you can do whatever you want with it! 36 | 37 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "simcity-threejs-clone", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite --host 127.0.0.1 --port 3000", 8 | "build": "vite build", 9 | "preview": "vite preview" 10 | }, 11 | "devDependencies": { 12 | "vite": "^4.4.5" 13 | }, 14 | "dependencies": { 15 | "three": "^0.155.0" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 | 15 |
16 |
17 |
18 | LOADING... 19 |
20 |
21 | 26 |
27 |
28 |
29 | $1000 30 |
31 |
32 | My City 33 |  -  34 | 1/1/2023 35 |
36 |
37 | 38 | 0 39 |
40 |
41 |
42 | 46 | 50 | 54 | 58 | 62 | 66 | 70 | 74 | 77 |
78 |
79 |
80 |
81 | INTERACT - Left Mouse
82 | ROTATE - Right Mouse
83 | PAN - Control + Right Mouse
84 | ZOOM - Scroll 85 |
86 |
87 | v0.3.0 88 |
89 |
90 |
91 | 92 | 93 | -------------------------------------------------------------------------------- /src/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/favicon.ico -------------------------------------------------------------------------------- /src/public/fonts/Bebas-Regular.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/fonts/Bebas-Regular.otf -------------------------------------------------------------------------------- /src/public/fonts/Digitalt.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/fonts/Digitalt.otf -------------------------------------------------------------------------------- /src/public/fonts/Stanberry.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/fonts/Stanberry.ttf -------------------------------------------------------------------------------- /src/public/icons/bulldozer-color.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/icons/bulldozer-color.png -------------------------------------------------------------------------------- /src/public/icons/bulldozer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/icons/bulldozer.png -------------------------------------------------------------------------------- /src/public/icons/calendar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/icons/calendar.png -------------------------------------------------------------------------------- /src/public/icons/factory-color.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/icons/factory-color.png -------------------------------------------------------------------------------- /src/public/icons/factory.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/icons/factory.png -------------------------------------------------------------------------------- /src/public/icons/house-color.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/icons/house-color.png -------------------------------------------------------------------------------- /src/public/icons/house.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/icons/house.png -------------------------------------------------------------------------------- /src/public/icons/job.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/icons/job.png -------------------------------------------------------------------------------- /src/public/icons/office.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/icons/office.png -------------------------------------------------------------------------------- /src/public/icons/pause-color.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/icons/pause-color.png -------------------------------------------------------------------------------- /src/public/icons/pause.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/icons/pause.png -------------------------------------------------------------------------------- /src/public/icons/person.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/icons/person.png -------------------------------------------------------------------------------- /src/public/icons/play-color.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/icons/play-color.png -------------------------------------------------------------------------------- /src/public/icons/play.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/icons/play.png -------------------------------------------------------------------------------- /src/public/icons/power-color.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/icons/power-color.png -------------------------------------------------------------------------------- /src/public/icons/power-line-color.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/icons/power-line-color.png -------------------------------------------------------------------------------- /src/public/icons/power.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/icons/power.png -------------------------------------------------------------------------------- /src/public/icons/road-color.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/icons/road-color.png -------------------------------------------------------------------------------- /src/public/icons/road.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/icons/road.png -------------------------------------------------------------------------------- /src/public/icons/select-color.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/icons/select-color.png -------------------------------------------------------------------------------- /src/public/icons/select.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/icons/select.png -------------------------------------------------------------------------------- /src/public/icons/store-color.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/icons/store-color.png -------------------------------------------------------------------------------- /src/public/main.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: Bebas-Regular; 3 | src: url('fonts/Bebas-Regular.otf'); 4 | } 5 | 6 | html, body { 7 | height: 100%; 8 | margin: 0; 9 | font-family: Bebas-Regular, sans-serif; 10 | 11 | background: linear-gradient(0deg, #3a5177, #81b1e0); 12 | } 13 | 14 | h1 { 15 | text-align: center; 16 | font-size: x-large; 17 | font-weight: normal; 18 | margin: 0; 19 | color: white; 20 | } 21 | 22 | #root-window { 23 | height: 100%; 24 | } 25 | 26 | #render-target { 27 | position: relative; 28 | height: 100%; 29 | } 30 | 31 | #instructions { 32 | position: absolute; 33 | color: white; 34 | left: 0; 35 | bottom: 0; 36 | margin: 8px; 37 | } 38 | 39 | #version { 40 | position: absolute; 41 | color: white; 42 | right: 0; 43 | bottom: 0; 44 | margin: 8px; 45 | } 46 | 47 | .text-overlay { 48 | position: absolute; 49 | top: 0; 50 | left: 0; 51 | right: 0; 52 | bottom: 0; 53 | color: white; 54 | font-size: 5em; 55 | display: flex; 56 | align-items: center; 57 | justify-content: center; 58 | } 59 | 60 | .container { 61 | background-color: #00000048; 62 | padding: 12px; 63 | } 64 | 65 | #ui-toolbar { 66 | position: absolute; 67 | top: 64px; 68 | bottom: 0px; 69 | 70 | box-shadow: 0px 0px 1px black; 71 | 72 | display: flex; 73 | flex-direction: column; 74 | justify-content: flex-start; 75 | } 76 | 77 | .ui-button { 78 | height: 48px; 79 | width: 48px; 80 | margin: 4px 0; 81 | 82 | transition: background-color 0.1s ease-in-out; 83 | 84 | background-color: #1e2331cd; 85 | border: none; 86 | border-radius: 8px; 87 | 88 | display: flex; 89 | justify-content: center; 90 | align-items: center; 91 | } 92 | 93 | .ui-button.selected { 94 | background-color: #3a3f50d3; 95 | outline: 2px solid rgb(101, 101, 135); 96 | } 97 | 98 | .ui-button:not(.selected):hover { 99 | background-color: #121622d3; 100 | } 101 | 102 | .ui-container { 103 | background-color: #33333355; 104 | padding: 16px; 105 | } 106 | 107 | .toolbar-icon { 108 | width: 32px; 109 | height: 32px; 110 | pointer-events: none; 111 | } 112 | 113 | /* TITLE BAR */ 114 | 115 | #title-bar { 116 | position: fixed; 117 | 118 | top: 0; 119 | left: 0px; 120 | right: 0; 121 | height: 48px; 122 | 123 | background-color: #00000048; 124 | color: white; 125 | padding: 8px; 126 | 127 | box-shadow: 0px 0px 1px black; 128 | 129 | display: flex; 130 | justify-content: space-evenly; 131 | align-items: center; 132 | } 133 | 134 | .title-bar-items { 135 | flex: 1 1 33%; 136 | display: flex; 137 | justify-content: center; 138 | align-items: center; 139 | font-size: 1.5em; 140 | } 141 | 142 | .title-bar-items.title-bar-center-items { 143 | font-size: 2em; 144 | } 145 | 146 | #population-icon { 147 | position: relative; 148 | width: 32px; 149 | height: 32px; 150 | top: -3px; 151 | } 152 | 153 | #population-counter { 154 | margin-left: 8px; 155 | } 156 | 157 | /* INFO PANEL */ 158 | 159 | #info-panel { 160 | visibility: hidden; 161 | position: fixed; 162 | top: 64px; 163 | right: 0px; 164 | padding: 0; 165 | padding-bottom: 16px; 166 | width: 300px; 167 | 168 | background-color: #00000048; 169 | } 170 | 171 | .info-heading { 172 | text-align: center; 173 | font-size: 1em; 174 | margin-top: 12px; 175 | margin-bottom: 12px; 176 | 177 | background-color: #00000060; 178 | 179 | box-shadow: 0px 0px 1px black; 180 | 181 | color: white; 182 | padding: 4px; 183 | } 184 | 185 | .info-heading:first-of-type { 186 | margin-top: 0px; 187 | } 188 | 189 | .info-label { 190 | margin-left: 12px; 191 | font-size: 1em; 192 | color: rgb(193, 193, 193) 193 | } 194 | 195 | .info-value { 196 | margin-left: 4px; 197 | font-size: 1.2em; 198 | color: #ffffff; 199 | text-align: right; 200 | } 201 | 202 | .info-citizen-list { 203 | list-style-type: none; 204 | padding: 0; 205 | } 206 | 207 | .info-citizen { 208 | background-color: #222941a0; 209 | color: white; 210 | padding: 4px 16px; 211 | margin: 4px 0; 212 | border-radius: 8px; 213 | box-shadow: 0 2px 0 0px #151820; 214 | } 215 | 216 | .info-citizen-name { 217 | font-size: 1.1em; 218 | } 219 | 220 | .info-citizen-icon { 221 | position: relative; 222 | width: 18px; 223 | height: 18px; 224 | top: 2px; 225 | margin-right: 2px; 226 | } 227 | 228 | .info-citizen-details { 229 | font-size: 1em; 230 | display: flex; 231 | justify-content: flex-start; 232 | gap: 16px; 233 | } -------------------------------------------------------------------------------- /src/public/models/FBX2glTF: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/FBX2glTF -------------------------------------------------------------------------------- /src/public/models/armored-truck.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/armored-truck.glb -------------------------------------------------------------------------------- /src/public/models/atm-mechine.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/atm-mechine.glb -------------------------------------------------------------------------------- /src/public/models/baby-carriage.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/baby-carriage.glb -------------------------------------------------------------------------------- /src/public/models/balloon-stripes.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/balloon-stripes.glb -------------------------------------------------------------------------------- /src/public/models/basketball-stand.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/basketball-stand.glb -------------------------------------------------------------------------------- /src/public/models/bench-forest.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/bench-forest.glb -------------------------------------------------------------------------------- /src/public/models/bench-old.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/bench-old.glb -------------------------------------------------------------------------------- /src/public/models/bike-old.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/bike-old.glb -------------------------------------------------------------------------------- /src/public/models/bike-stand.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/bike-stand.glb -------------------------------------------------------------------------------- /src/public/models/bin-wheelie.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/bin-wheelie.glb -------------------------------------------------------------------------------- /src/public/models/boat-fishing.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/boat-fishing.glb -------------------------------------------------------------------------------- /src/public/models/boat-sail.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/boat-sail.glb -------------------------------------------------------------------------------- /src/public/models/boat-speed.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/boat-speed.glb -------------------------------------------------------------------------------- /src/public/models/building-antique-china.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/building-antique-china.glb -------------------------------------------------------------------------------- /src/public/models/building-apartment-china.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/building-apartment-china.glb -------------------------------------------------------------------------------- /src/public/models/building-bank.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/building-bank.glb -------------------------------------------------------------------------------- /src/public/models/building-block-4floor-back.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/building-block-4floor-back.glb -------------------------------------------------------------------------------- /src/public/models/building-block-4floor-corner.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/building-block-4floor-corner.glb -------------------------------------------------------------------------------- /src/public/models/building-block-4floor-front.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/building-block-4floor-front.glb -------------------------------------------------------------------------------- /src/public/models/building-block-4floor-short.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/building-block-4floor-short.glb -------------------------------------------------------------------------------- /src/public/models/building-block-5floor-corner.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/building-block-5floor-corner.glb -------------------------------------------------------------------------------- /src/public/models/building-block-5floor-front.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/building-block-5floor-front.glb -------------------------------------------------------------------------------- /src/public/models/building-block-5floor-short.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/building-block-5floor-short.glb -------------------------------------------------------------------------------- /src/public/models/building-block-5floor.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/building-block-5floor.glb -------------------------------------------------------------------------------- /src/public/models/building-burger-joint.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/building-burger-joint.glb -------------------------------------------------------------------------------- /src/public/models/building-cabin-big.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/building-cabin-big.glb -------------------------------------------------------------------------------- /src/public/models/building-cabin-small.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/building-cabin-small.glb -------------------------------------------------------------------------------- /src/public/models/building-cafe.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/building-cafe.glb -------------------------------------------------------------------------------- /src/public/models/building-carwash.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/building-carwash.glb -------------------------------------------------------------------------------- /src/public/models/building-casino.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/building-casino.glb -------------------------------------------------------------------------------- /src/public/models/building-cinema.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/building-cinema.glb -------------------------------------------------------------------------------- /src/public/models/building-firestation.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/building-firestation.glb -------------------------------------------------------------------------------- /src/public/models/building-hospital.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/building-hospital.glb -------------------------------------------------------------------------------- /src/public/models/building-hotel.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/building-hotel.glb -------------------------------------------------------------------------------- /src/public/models/building-house-block-big.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/building-house-block-big.glb -------------------------------------------------------------------------------- /src/public/models/building-house-block-old.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/building-house-block-old.glb -------------------------------------------------------------------------------- /src/public/models/building-house-block.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/building-house-block.glb -------------------------------------------------------------------------------- /src/public/models/building-house-family-large.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/building-house-family-large.glb -------------------------------------------------------------------------------- /src/public/models/building-house-family-small.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/building-house-family-small.glb -------------------------------------------------------------------------------- /src/public/models/building-house-modern-big.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/building-house-modern-big.glb -------------------------------------------------------------------------------- /src/public/models/building-house-modern.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/building-house-modern.glb -------------------------------------------------------------------------------- /src/public/models/building-mall.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/building-mall.glb -------------------------------------------------------------------------------- /src/public/models/building-market-china.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/building-market-china.glb -------------------------------------------------------------------------------- /src/public/models/building-museum.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/building-museum.glb -------------------------------------------------------------------------------- /src/public/models/building-office-balcony.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/building-office-balcony.glb -------------------------------------------------------------------------------- /src/public/models/building-office-big.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/building-office-big.glb -------------------------------------------------------------------------------- /src/public/models/building-office-pyramid.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/building-office-pyramid.glb -------------------------------------------------------------------------------- /src/public/models/building-office-rounded.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/building-office-rounded.glb -------------------------------------------------------------------------------- /src/public/models/building-office-tall.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/building-office-tall.glb -------------------------------------------------------------------------------- /src/public/models/building-office.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/building-office.glb -------------------------------------------------------------------------------- /src/public/models/building-pagoda-china.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/building-pagoda-china.glb -------------------------------------------------------------------------------- /src/public/models/building-policestation-garage.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/building-policestation-garage.glb -------------------------------------------------------------------------------- /src/public/models/building-policestation.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/building-policestation.glb -------------------------------------------------------------------------------- /src/public/models/building-port-sea.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/building-port-sea.glb -------------------------------------------------------------------------------- /src/public/models/building-post.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/building-post.glb -------------------------------------------------------------------------------- /src/public/models/building-restaurant.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/building-restaurant.glb -------------------------------------------------------------------------------- /src/public/models/building-restuarant-china.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/building-restuarant-china.glb -------------------------------------------------------------------------------- /src/public/models/building-school.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/building-school.glb -------------------------------------------------------------------------------- /src/public/models/building-shop-china.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/building-shop-china.glb -------------------------------------------------------------------------------- /src/public/models/building-skyscraper.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/building-skyscraper.glb -------------------------------------------------------------------------------- /src/public/models/building-stadium.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/building-stadium.glb -------------------------------------------------------------------------------- /src/public/models/building-temple-china.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/building-temple-china.glb -------------------------------------------------------------------------------- /src/public/models/building-train-station.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/building-train-station.glb -------------------------------------------------------------------------------- /src/public/models/burger-joint-sign.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/burger-joint-sign.glb -------------------------------------------------------------------------------- /src/public/models/burger-statue.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/burger-statue.glb -------------------------------------------------------------------------------- /src/public/models/bus-passenger.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/bus-passenger.glb -------------------------------------------------------------------------------- /src/public/models/bus-school.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/bus-school.glb -------------------------------------------------------------------------------- /src/public/models/bus-stop-sign.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/bus-stop-sign.glb -------------------------------------------------------------------------------- /src/public/models/bus-stop.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/bus-stop.glb -------------------------------------------------------------------------------- /src/public/models/cactus-big.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/cactus-big.glb -------------------------------------------------------------------------------- /src/public/models/cactus-medium.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/cactus-medium.glb -------------------------------------------------------------------------------- /src/public/models/canal-cover.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/canal-cover.glb -------------------------------------------------------------------------------- /src/public/models/car-ambulance-pickup.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/car-ambulance-pickup.glb -------------------------------------------------------------------------------- /src/public/models/car-baywatch.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/car-baywatch.glb -------------------------------------------------------------------------------- /src/public/models/car-firefighter-pickup.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/car-firefighter-pickup.glb -------------------------------------------------------------------------------- /src/public/models/car-formula.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/car-formula.glb -------------------------------------------------------------------------------- /src/public/models/car-hippie-van.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/car-hippie-van.glb -------------------------------------------------------------------------------- /src/public/models/car-passenger-race.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/car-passenger-race.glb -------------------------------------------------------------------------------- /src/public/models/car-passenger.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/car-passenger.glb -------------------------------------------------------------------------------- /src/public/models/car-police.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/car-police.glb -------------------------------------------------------------------------------- /src/public/models/car-taxi.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/car-taxi.glb -------------------------------------------------------------------------------- /src/public/models/car-tow-truck.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/car-tow-truck.glb -------------------------------------------------------------------------------- /src/public/models/car-truck-cement.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/car-truck-cement.glb -------------------------------------------------------------------------------- /src/public/models/car-truck-dump.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/car-truck-dump.glb -------------------------------------------------------------------------------- /src/public/models/car-truck-tanker.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/car-truck-tanker.glb -------------------------------------------------------------------------------- /src/public/models/car-veteran.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/car-veteran.glb -------------------------------------------------------------------------------- /src/public/models/cargo-shipping_blue.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/cargo-shipping_blue.glb -------------------------------------------------------------------------------- /src/public/models/cargo-shipping_green.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/cargo-shipping_green.glb -------------------------------------------------------------------------------- /src/public/models/cargo-shipping_orange.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/cargo-shipping_orange.glb -------------------------------------------------------------------------------- /src/public/models/cargo-shipping_red.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/cargo-shipping_red.glb -------------------------------------------------------------------------------- /src/public/models/cargo-shipping_white.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/cargo-shipping_white.glb -------------------------------------------------------------------------------- /src/public/models/cargo-smple.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/cargo-smple.glb -------------------------------------------------------------------------------- /src/public/models/chair-folding.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/chair-folding.glb -------------------------------------------------------------------------------- /src/public/models/chimney-big.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/chimney-big.glb -------------------------------------------------------------------------------- /src/public/models/construction-small.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/construction-small.glb -------------------------------------------------------------------------------- /src/public/models/control-tower.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/control-tower.glb -------------------------------------------------------------------------------- /src/public/models/cooling-tower.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/cooling-tower.glb -------------------------------------------------------------------------------- /src/public/models/data-center.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/data-center.glb -------------------------------------------------------------------------------- /src/public/models/dryer-outside.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/dryer-outside.glb -------------------------------------------------------------------------------- /src/public/models/dumpster.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/dumpster.glb -------------------------------------------------------------------------------- /src/public/models/excavator.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/excavator.glb -------------------------------------------------------------------------------- /src/public/models/fence-big.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/fence-big.glb -------------------------------------------------------------------------------- /src/public/models/fence-classic.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/fence-classic.glb -------------------------------------------------------------------------------- /src/public/models/fence-picket.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/fence-picket.glb -------------------------------------------------------------------------------- /src/public/models/fence-shrub.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/fence-shrub.glb -------------------------------------------------------------------------------- /src/public/models/fence-start.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/fence-start.glb -------------------------------------------------------------------------------- /src/public/models/fence-stone-gate-small.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/fence-stone-gate-small.glb -------------------------------------------------------------------------------- /src/public/models/fence-stone-gate.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/fence-stone-gate.glb -------------------------------------------------------------------------------- /src/public/models/fence-stone-metal.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/fence-stone-metal.glb -------------------------------------------------------------------------------- /src/public/models/fence-stone-tower.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/fence-stone-tower.glb -------------------------------------------------------------------------------- /src/public/models/fence-stone.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/fence-stone.glb -------------------------------------------------------------------------------- /src/public/models/fence-vineyard.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/fence-vineyard.glb -------------------------------------------------------------------------------- /src/public/models/fence.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/fence.glb -------------------------------------------------------------------------------- /src/public/models/ferris-wheel.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/ferris-wheel.glb -------------------------------------------------------------------------------- /src/public/models/fire-hydrant.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/fire-hydrant.glb -------------------------------------------------------------------------------- /src/public/models/firetruck.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/firetruck.glb -------------------------------------------------------------------------------- /src/public/models/flag-golf.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/flag-golf.glb -------------------------------------------------------------------------------- /src/public/models/floatplane.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/floatplane.glb -------------------------------------------------------------------------------- /src/public/models/flowers-window.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/flowers-window.glb -------------------------------------------------------------------------------- /src/public/models/forklift.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/forklift.glb -------------------------------------------------------------------------------- /src/public/models/fountain.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/fountain.glb -------------------------------------------------------------------------------- /src/public/models/free-fall-ride.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/free-fall-ride.glb -------------------------------------------------------------------------------- /src/public/models/freight-train.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/freight-train.glb -------------------------------------------------------------------------------- /src/public/models/gate-china.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/gate-china.glb -------------------------------------------------------------------------------- /src/public/models/golf-cart.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/golf-cart.glb -------------------------------------------------------------------------------- /src/public/models/grass-basic.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/grass-basic.glb -------------------------------------------------------------------------------- /src/public/models/grass-clumb.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/grass-clumb.glb -------------------------------------------------------------------------------- /src/public/models/grass-long.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/grass-long.glb -------------------------------------------------------------------------------- /src/public/models/grass-tall.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/grass-tall.glb -------------------------------------------------------------------------------- /src/public/models/grass.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/grass.glb -------------------------------------------------------------------------------- /src/public/models/grill-round.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/grill-round.glb -------------------------------------------------------------------------------- /src/public/models/ground-cracked.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/ground-cracked.glb -------------------------------------------------------------------------------- /src/public/models/ground-lines.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/ground-lines.glb -------------------------------------------------------------------------------- /src/public/models/guidepost.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/guidepost.glb -------------------------------------------------------------------------------- /src/public/models/helicopter.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/helicopter.glb -------------------------------------------------------------------------------- /src/public/models/industry-building.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/industry-building.glb -------------------------------------------------------------------------------- /src/public/models/industry-factory-hall.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/industry-factory-hall.glb -------------------------------------------------------------------------------- /src/public/models/industry-factory-old.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/industry-factory-old.glb -------------------------------------------------------------------------------- /src/public/models/industry-factory.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/industry-factory.glb -------------------------------------------------------------------------------- /src/public/models/industry-refinery.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/industry-refinery.glb -------------------------------------------------------------------------------- /src/public/models/industry-storage.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/industry-storage.glb -------------------------------------------------------------------------------- /src/public/models/industry-warehouse.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/industry-warehouse.glb -------------------------------------------------------------------------------- /src/public/models/jeep-open.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/jeep-open.glb -------------------------------------------------------------------------------- /src/public/models/lamp-city.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/lamp-city.glb -------------------------------------------------------------------------------- /src/public/models/lamp-road-double.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/lamp-road-double.glb -------------------------------------------------------------------------------- /src/public/models/lamp-road.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/lamp-road.glb -------------------------------------------------------------------------------- /src/public/models/lantern-long.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/lantern-long.glb -------------------------------------------------------------------------------- /src/public/models/lantern-sphere.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/lantern-sphere.glb -------------------------------------------------------------------------------- /src/public/models/mail-box.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/mail-box.glb -------------------------------------------------------------------------------- /src/public/models/mainroad-sign-green.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/mainroad-sign-green.glb -------------------------------------------------------------------------------- /src/public/models/marketplace-stand-simple.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/marketplace-stand-simple.glb -------------------------------------------------------------------------------- /src/public/models/mosque-tower.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/mosque-tower.glb -------------------------------------------------------------------------------- /src/public/models/mosque.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/mosque.glb -------------------------------------------------------------------------------- /src/public/models/mountain-desert.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/mountain-desert.glb -------------------------------------------------------------------------------- /src/public/models/mountains.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/mountains.glb -------------------------------------------------------------------------------- /src/public/models/nuclear-power-plant.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/nuclear-power-plant.glb -------------------------------------------------------------------------------- /src/public/models/palette.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/palette.glb -------------------------------------------------------------------------------- /src/public/models/palm-round.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/palm-round.glb -------------------------------------------------------------------------------- /src/public/models/palm-small.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/palm-small.glb -------------------------------------------------------------------------------- /src/public/models/palm.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/palm.glb -------------------------------------------------------------------------------- /src/public/models/pier-tile-straight.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/pier-tile-straight.glb -------------------------------------------------------------------------------- /src/public/models/plane-passenger.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/plane-passenger.glb -------------------------------------------------------------------------------- /src/public/models/power_line_pole.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/power_line_pole.glb -------------------------------------------------------------------------------- /src/public/models/power_line_pole_modified.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/power_line_pole_modified.glb -------------------------------------------------------------------------------- /src/public/models/rock-pillar.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/rock-pillar.glb -------------------------------------------------------------------------------- /src/public/models/rock-sharp.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/rock-sharp.glb -------------------------------------------------------------------------------- /src/public/models/rock-terrasse.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/rock-terrasse.glb -------------------------------------------------------------------------------- /src/public/models/rocks-small.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/rocks-small.glb -------------------------------------------------------------------------------- /src/public/models/sand-box.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/sand-box.glb -------------------------------------------------------------------------------- /src/public/models/ship-cargo.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/ship-cargo.glb -------------------------------------------------------------------------------- /src/public/models/ship-yacht.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/ship-yacht.glb -------------------------------------------------------------------------------- /src/public/models/shrub-round.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/shrub-round.glb -------------------------------------------------------------------------------- /src/public/models/shrub.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/shrub.glb -------------------------------------------------------------------------------- /src/public/models/skyscraper-huge.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/skyscraper-huge.glb -------------------------------------------------------------------------------- /src/public/models/skyscraper-large.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/skyscraper-large.glb -------------------------------------------------------------------------------- /src/public/models/skyscraper-medium.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/skyscraper-medium.glb -------------------------------------------------------------------------------- /src/public/models/skyscraper-part-bottom.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/skyscraper-part-bottom.glb -------------------------------------------------------------------------------- /src/public/models/skyscraper-part-middle.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/skyscraper-part-middle.glb -------------------------------------------------------------------------------- /src/public/models/skyscraper-part-top.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/skyscraper-part-top.glb -------------------------------------------------------------------------------- /src/public/models/skyscraper-small.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/skyscraper-small.glb -------------------------------------------------------------------------------- /src/public/models/skyscraper-tiny.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/skyscraper-tiny.glb -------------------------------------------------------------------------------- /src/public/models/skyscraper.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/skyscraper.glb -------------------------------------------------------------------------------- /src/public/models/solar-panel-house.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/solar-panel-house.glb -------------------------------------------------------------------------------- /src/public/models/stone-diamond.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/stone-diamond.glb -------------------------------------------------------------------------------- /src/public/models/stone-flat.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/stone-flat.glb -------------------------------------------------------------------------------- /src/public/models/stone-oval.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/stone-oval.glb -------------------------------------------------------------------------------- /src/public/models/stone-pointy.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/stone-pointy.glb -------------------------------------------------------------------------------- /src/public/models/stone-round.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/stone-round.glb -------------------------------------------------------------------------------- /src/public/models/stone-small.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/stone-small.glb -------------------------------------------------------------------------------- /src/public/models/stump-small.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/stump-small.glb -------------------------------------------------------------------------------- /src/public/models/stump.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/stump.glb -------------------------------------------------------------------------------- /src/public/models/sunflower.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/sunflower.glb -------------------------------------------------------------------------------- /src/public/models/tent-circus-big.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/tent-circus-big.glb -------------------------------------------------------------------------------- /src/public/models/terrain-mountain-range.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/terrain-mountain-range.glb -------------------------------------------------------------------------------- /src/public/models/terrain-mountains.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/terrain-mountains.glb -------------------------------------------------------------------------------- /src/public/models/terrain-valley.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/terrain-valley.glb -------------------------------------------------------------------------------- /src/public/models/tile-golf-green-circle.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/tile-golf-green-circle.glb -------------------------------------------------------------------------------- /src/public/models/tile-golf-green.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/tile-golf-green.glb -------------------------------------------------------------------------------- /src/public/models/tile-golf-sand.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/tile-golf-sand.glb -------------------------------------------------------------------------------- /src/public/models/tile-hill-askew-valley-corner.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/tile-hill-askew-valley-corner.glb -------------------------------------------------------------------------------- /src/public/models/tile-hill-askew.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/tile-hill-askew.glb -------------------------------------------------------------------------------- /src/public/models/tile-hill-corner.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/tile-hill-corner.glb -------------------------------------------------------------------------------- /src/public/models/tile-hill-curve.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/tile-hill-curve.glb -------------------------------------------------------------------------------- /src/public/models/tile-hill-valley.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/tile-hill-valley.glb -------------------------------------------------------------------------------- /src/public/models/tile-hill.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/tile-hill.glb -------------------------------------------------------------------------------- /src/public/models/tile-mainroad-curve.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/tile-mainroad-curve.glb -------------------------------------------------------------------------------- /src/public/models/tile-mainroad-hill.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/tile-mainroad-hill.glb -------------------------------------------------------------------------------- /src/public/models/tile-mainroad-intersection-t.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/tile-mainroad-intersection-t.glb -------------------------------------------------------------------------------- /src/public/models/tile-mainroad-intersection.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/tile-mainroad-intersection.glb -------------------------------------------------------------------------------- /src/public/models/tile-mainroad-road-intersection-t.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/tile-mainroad-road-intersection-t.glb -------------------------------------------------------------------------------- /src/public/models/tile-mainroad-road-intersection.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/tile-mainroad-road-intersection.glb -------------------------------------------------------------------------------- /src/public/models/tile-mainroad-straight-crosswalk.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/tile-mainroad-straight-crosswalk.glb -------------------------------------------------------------------------------- /src/public/models/tile-mainroad-straight.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/tile-mainroad-straight.glb -------------------------------------------------------------------------------- /src/public/models/tile-park.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/tile-park.glb -------------------------------------------------------------------------------- /src/public/models/tile-plain-hump.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/tile-plain-hump.glb -------------------------------------------------------------------------------- /src/public/models/tile-plain_asphalt.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/tile-plain_asphalt.glb -------------------------------------------------------------------------------- /src/public/models/tile-plain_concrete.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/tile-plain_concrete.glb -------------------------------------------------------------------------------- /src/public/models/tile-plain_dirt.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/tile-plain_dirt.glb -------------------------------------------------------------------------------- /src/public/models/tile-plain_grass.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/tile-plain_grass.glb -------------------------------------------------------------------------------- /src/public/models/tile-plain_sand.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/tile-plain_sand.glb -------------------------------------------------------------------------------- /src/public/models/tile-road-curve.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/tile-road-curve.glb -------------------------------------------------------------------------------- /src/public/models/tile-road-end.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/tile-road-end.glb -------------------------------------------------------------------------------- /src/public/models/tile-road-hill.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/tile-road-hill.glb -------------------------------------------------------------------------------- /src/public/models/tile-road-intersection-t.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/tile-road-intersection-t.glb -------------------------------------------------------------------------------- /src/public/models/tile-road-intersection.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/tile-road-intersection.glb -------------------------------------------------------------------------------- /src/public/models/tile-road-mainroad-intersection-t.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/tile-road-mainroad-intersection-t.glb -------------------------------------------------------------------------------- /src/public/models/tile-road-mainroad-intersection.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/tile-road-mainroad-intersection.glb -------------------------------------------------------------------------------- /src/public/models/tile-road-straight-crosswalk.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/tile-road-straight-crosswalk.glb -------------------------------------------------------------------------------- /src/public/models/tile-road-straight.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/tile-road-straight.glb -------------------------------------------------------------------------------- /src/public/models/tile-road-to-mainroad-intersection-t.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/tile-road-to-mainroad-intersection-t.glb -------------------------------------------------------------------------------- /src/public/models/tile-road-to-mainroad.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/tile-road-to-mainroad.glb -------------------------------------------------------------------------------- /src/public/models/tile-roads-mainroad-intersection.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/tile-roads-mainroad-intersection.glb -------------------------------------------------------------------------------- /src/public/models/tile-sidewalk-hill.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/tile-sidewalk-hill.glb -------------------------------------------------------------------------------- /src/public/models/tile-sidewalk-straight.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/tile-sidewalk-straight.glb -------------------------------------------------------------------------------- /src/public/models/tile-water.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/tile-water.glb -------------------------------------------------------------------------------- /src/public/models/traffic-lights.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/traffic-lights.glb -------------------------------------------------------------------------------- /src/public/models/tree-beech.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/tree-beech.glb -------------------------------------------------------------------------------- /src/public/models/tree-birch-tall.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/tree-birch-tall.glb -------------------------------------------------------------------------------- /src/public/models/tree-birch.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/tree-birch.glb -------------------------------------------------------------------------------- /src/public/models/tree-bonsai.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/tree-bonsai.glb -------------------------------------------------------------------------------- /src/public/models/tree-conifer.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/tree-conifer.glb -------------------------------------------------------------------------------- /src/public/models/tree-dead.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/tree-dead.glb -------------------------------------------------------------------------------- /src/public/models/tree-dry.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/tree-dry.glb -------------------------------------------------------------------------------- /src/public/models/tree-elipse.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/tree-elipse.glb -------------------------------------------------------------------------------- /src/public/models/tree-fir.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/tree-fir.glb -------------------------------------------------------------------------------- /src/public/models/tree-forest.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/tree-forest.glb -------------------------------------------------------------------------------- /src/public/models/tree-lime.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/tree-lime.glb -------------------------------------------------------------------------------- /src/public/models/tree-little.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/tree-little.glb -------------------------------------------------------------------------------- /src/public/models/tree-oak.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/tree-oak.glb -------------------------------------------------------------------------------- /src/public/models/tree-old.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/tree-old.glb -------------------------------------------------------------------------------- /src/public/models/tree-poplar.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/tree-poplar.glb -------------------------------------------------------------------------------- /src/public/models/tree-pot.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/tree-pot.glb -------------------------------------------------------------------------------- /src/public/models/tree-round.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/tree-round.glb -------------------------------------------------------------------------------- /src/public/models/tree-spruce.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/tree-spruce.glb -------------------------------------------------------------------------------- /src/public/models/tree-square.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/tree-square.glb -------------------------------------------------------------------------------- /src/public/models/tree-tall.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/tree-tall.glb -------------------------------------------------------------------------------- /src/public/models/tree.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/tree.glb -------------------------------------------------------------------------------- /src/public/models/tribune-streight.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/tribune-streight.glb -------------------------------------------------------------------------------- /src/public/models/truck.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/models/truck.glb -------------------------------------------------------------------------------- /src/public/statusIcons/no-power.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/statusIcons/no-power.png -------------------------------------------------------------------------------- /src/public/statusIcons/no-road-access.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/statusIcons/no-road-access.png -------------------------------------------------------------------------------- /src/public/textures/base.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/textures/base.png -------------------------------------------------------------------------------- /src/public/textures/grid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/textures/grid.png -------------------------------------------------------------------------------- /src/public/textures/specular.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgreenheck/simcity-threejs-clone/9116cf680f677c3476f8159d5bbc7a4cfda00654/src/public/textures/specular.png -------------------------------------------------------------------------------- /src/scripts/assets/assetManager.js: -------------------------------------------------------------------------------- 1 | import * as THREE from 'three'; 2 | import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js'; 3 | import viteConfig from '../../../vite.config.js'; 4 | import models from './models.js'; 5 | 6 | const baseUrl = viteConfig.base; 7 | 8 | export class AssetManager { 9 | textureLoader = new THREE.TextureLoader(); 10 | modelLoader = new GLTFLoader(); 11 | 12 | textures = { 13 | 'base': this.#loadTexture(`${baseUrl}textures/base.png`), 14 | 'specular': this.#loadTexture(`${baseUrl}textures/specular.png`), 15 | 'grid': this.#loadTexture(`${baseUrl}textures/grid.png`), 16 | 17 | }; 18 | 19 | statusIcons = { 20 | 'no-power': this.#loadTexture(`${baseUrl}statusIcons/no-power.png`, true), 21 | 'no-road-access': this.#loadTexture(`${baseUrl}statusIcons/no-road-access.png`, true) 22 | } 23 | 24 | models = {}; 25 | 26 | sprites = {}; 27 | 28 | constructor(onLoad) { 29 | this.modelCount = Object.keys(models).length; 30 | this.loadedModelCount = 0; 31 | 32 | for (const [name, meta] of Object.entries(models)) { 33 | this.#loadModel(name, meta); 34 | } 35 | 36 | this.onLoad = onLoad; 37 | } 38 | 39 | /** 40 | * Returns a cloned copy of a mesh 41 | * @param {string} name The name of the mesh to retrieve 42 | * @param {Object} simObject The SimObject object that corresponds to this mesh 43 | * @param {boolean} transparent True if materials should be transparent. Default is false. 44 | * @returns {THREE.Mesh} 45 | */ 46 | getModel(name, simObject, transparent = false) { 47 | const mesh = this.models[name].clone(); 48 | 49 | // Clone materials so each object has a unique material 50 | // This is so we can set the modify the texture of each 51 | // mesh independently (e.g. highlight on mouse over, 52 | // abandoned buildings, etc.)) 53 | mesh.traverse((obj) => { 54 | obj.userData = simObject; 55 | if(obj.material) { 56 | obj.material = obj.material.clone(); 57 | obj.material.transparent = transparent; 58 | } 59 | }); 60 | 61 | return mesh; 62 | } 63 | 64 | /** 65 | * Loads the texture at the specified URL 66 | * @param {string} url 67 | * @returns {THREE.Texture} A texture object 68 | */ 69 | #loadTexture(url, flipY = false) { 70 | const texture = this.textureLoader.load(url) 71 | texture.colorSpace = THREE.SRGBColorSpace; 72 | texture.flipY = flipY; 73 | return texture; 74 | } 75 | 76 | /** 77 | * Load the 3D models 78 | * @param {string} url The URL of the model to load 79 | */ 80 | #loadModel(name, {filename, scale = 1, rotation = 0, receiveShadow = true, castShadow = true}) { 81 | this.modelLoader.load(`${baseUrl}models/${filename}`, 82 | (glb) => { 83 | let mesh = glb.scene; 84 | 85 | mesh.name = filename; 86 | 87 | mesh.traverse((obj) => { 88 | if (obj.material) { 89 | obj.material = new THREE.MeshLambertMaterial({ 90 | map: this.textures.base, 91 | specularMap: this.textures.specular 92 | }) 93 | obj.receiveShadow = receiveShadow; 94 | obj.castShadow = castShadow; 95 | } 96 | }); 97 | 98 | mesh.rotation.set(0, THREE.MathUtils.degToRad(rotation), 0); 99 | mesh.scale.set(scale / 30, scale / 30, scale / 30); 100 | 101 | this.models[name] = mesh; 102 | 103 | // Once all models are loaded 104 | this.loadedModelCount++; 105 | if (this.loadedModelCount == this.modelCount) { 106 | this.onLoad() 107 | } 108 | }, 109 | (xhr) => { 110 | //console.log(`${name} ${(xhr.loaded / xhr.total) * 100}% loaded`); 111 | }, 112 | (error) => { 113 | console.error(error); 114 | }); 115 | } 116 | } -------------------------------------------------------------------------------- /src/scripts/assets/models.js: -------------------------------------------------------------------------------- 1 | export default { 2 | "under-construction": { 3 | "type": "zone", 4 | "filename": "construction-small.glb", 5 | "scale": 3 6 | }, 7 | "residential-A1": { 8 | "type": "zone", 9 | "filename": "building-house-block-big.glb" 10 | }, 11 | "residential-B1": { 12 | "type": "zone", 13 | "filename": "building-house-family-small.glb" 14 | }, 15 | "residential-C1": { 16 | "type": "zone", 17 | "filename": "building-house-family-large.glb" 18 | }, 19 | "residential-A2": { 20 | "type": "zone", 21 | "filename": "building-block-4floor-short.glb", 22 | }, 23 | "residential-B2": { 24 | "type": "zone", 25 | "filename": "building-block-4floor-corner.glb", 26 | }, 27 | "residential-C2": { 28 | "type": "zone", 29 | "filename": "building-block-5floor.glb", 30 | }, 31 | "residential-A3": { 32 | "type": "zone", 33 | "filename": "building-office-balcony.glb" 34 | }, 35 | "residential-B3": { 36 | "type": "zone", 37 | "filename": "building-office-pyramid.glb" 38 | }, 39 | "residential-C3": { 40 | "type": "zone", 41 | "filename": "building-office-tall.glb" 42 | }, 43 | "commercial-A1": { 44 | "type": "zone", 45 | "filename": "building-cafe.glb" 46 | }, 47 | "commercial-B1": { 48 | "type": "zone", 49 | "filename": "building-burger-joint.glb" 50 | }, 51 | "commercial-C1": { 52 | "type": "zone", 53 | "filename": "building-restaurant.glb" 54 | }, 55 | "commercial-A2": { 56 | "type": "zone", 57 | "filename": "building-cinema.glb" 58 | }, 59 | "commercial-B2": { 60 | "type": "zone", 61 | "filename": "building-casino.glb" 62 | }, 63 | "commercial-C2": { 64 | "type": "zone", 65 | "filename": "data-center.glb" 66 | }, 67 | "commercial-A3": { 68 | "type": "zone", 69 | "filename": "building-office.glb" 70 | }, 71 | "commercial-B3": { 72 | "type": "zone", 73 | "filename": "building-office-big.glb" 74 | }, 75 | "commercial-C3": { 76 | "type": "zone", 77 | "filename": "building-skyscraper.glb" 78 | }, 79 | "industrial-A1": { 80 | "type": "zone", 81 | "filename": "industry-factory.glb" 82 | }, 83 | "industrial-B1": { 84 | "type": "zone", 85 | "filename": "industry-refinery.glb" 86 | }, 87 | "industrial-C1": { 88 | "type": "zone", 89 | "filename": "industry-warehouse.glb" 90 | }, 91 | "industrial-A2": { 92 | "type": "zone", 93 | "filename": "industry-factory.glb" 94 | }, 95 | "industrial-B2": { 96 | "type": "zone", 97 | "filename": "industry-refinery.glb" 98 | }, 99 | "industrial-C2": { 100 | "type": "zone", 101 | "filename": "industry-warehouse.glb" 102 | }, 103 | "industrial-A3": { 104 | "type": "zone", 105 | "filename": "industry-factory.glb" 106 | }, 107 | "industrial-B3": { 108 | "type": "zone", 109 | "filename": "industry-refinery.glb" 110 | }, 111 | "industrial-C3": { 112 | "type": "zone", 113 | "filename": "industry-warehouse.glb" 114 | }, 115 | "power-plant": { 116 | "type": "power", 117 | "filename": "industry-factory-old.glb" 118 | }, 119 | "power-line": { 120 | "type": "power", 121 | "filename": "power_line_pole_modified.glb" 122 | }, 123 | "road-straight": { 124 | "type": "road", 125 | "filename": "tile-road-straight.glb", 126 | "castShadow": false 127 | }, 128 | "road-end": { 129 | "type": "road", 130 | "filename": "tile-road-end.glb", 131 | "castShadow": false 132 | }, 133 | "road-corner": { 134 | "type": "road", 135 | "filename": "tile-road-curve.glb", 136 | "castShadow": false 137 | }, 138 | "road-three-way": { 139 | "type": "road", 140 | "filename": "tile-road-intersection-t.glb", 141 | "castShadow": false 142 | }, 143 | "road-four-way": { 144 | "type": "road", 145 | "filename": "tile-road-intersection.glb", 146 | "castShadow": false 147 | }, 148 | "grass": { 149 | "type": "terrain", 150 | "filename": "tile-plain_grass.glb", 151 | "castShadow": false 152 | }, 153 | "car-taxi": { 154 | "type": "vehicle", 155 | "filename": "car-taxi.glb", 156 | "rotation": 90 157 | }, 158 | "car-police": { 159 | "type": "vehicle", 160 | "filename": "car-police.glb", 161 | "rotation": 90 162 | }, 163 | "car-passenger": { 164 | "type": "vehicle", 165 | "filename": "car-passenger.glb", 166 | "rotation": 90 167 | }, 168 | "car-veteran": { 169 | "type": "vehicle", 170 | "filename": "car-veteran.glb", 171 | "rotation": 90 172 | }, 173 | "truck": { 174 | "type": "vehicle", 175 | "filename": "truck.glb", 176 | "rotation": 90 177 | }, 178 | "car-hippie-van": { 179 | "type": "vehicle", 180 | "filename": "car-hippie-van.glb", 181 | "rotation": 90 182 | }, 183 | "car-tow-truck": { 184 | "type": "vehicle", 185 | "filename": "car-tow-truck.glb", 186 | "rotation": 90 187 | }, 188 | "car-ambulance-pickup": { 189 | "type": "vehicle", 190 | "filename": "car-ambulance-pickup.glb", 191 | "rotation": 90 192 | }, 193 | "car-passenger-race": { 194 | "type": "vehicle", 195 | "filename": "car-passenger-race.glb", 196 | "rotation": 90 197 | }, 198 | "car-baywatch": { 199 | "type": "vehicle", 200 | "filename": "car-baywatch.glb", 201 | "rotation": 90 202 | }, 203 | "car-truck-dump": { 204 | "type": "vehicle", 205 | "filename": "car-truck-dump.glb", 206 | "rotation": 90 207 | }, 208 | "car-truck-armored-truck": { 209 | "type": "vehicle", 210 | "filename": "armored-truck.glb", 211 | "rotation": 90 212 | } 213 | } -------------------------------------------------------------------------------- /src/scripts/camera.js: -------------------------------------------------------------------------------- 1 | import * as THREE from 'three'; 2 | 3 | // -- Constants -- 4 | const DEG2RAD = Math.PI / 180.0; 5 | const RIGHT_MOUSE_BUTTON = 2; 6 | 7 | // Camera constraints 8 | const CAMERA_SIZE = 5; 9 | const MIN_CAMERA_RADIUS = 0.1; 10 | const MAX_CAMERA_RADIUS = 5; 11 | const MIN_CAMERA_ELEVATION = 45; 12 | const MAX_CAMERA_ELEVATION = 45; 13 | 14 | // Camera sensitivity 15 | const AZIMUTH_SENSITIVITY = 0.2; 16 | const ELEVATION_SENSITIVITY = 0.2; 17 | const ZOOM_SENSITIVITY = 0.002; 18 | const PAN_SENSITIVITY = -0.01; 19 | 20 | const Y_AXIS = new THREE.Vector3(0, 1, 0); 21 | 22 | export class CameraManager { 23 | constructor() { 24 | const aspect = window.ui.gameWindow.clientWidth / window.ui.gameWindow.clientHeight; 25 | 26 | this.camera = new THREE.OrthographicCamera( 27 | (CAMERA_SIZE * aspect) / -2, 28 | (CAMERA_SIZE * aspect) / 2, 29 | CAMERA_SIZE / 2, 30 | CAMERA_SIZE / -2, 1, 1000); 31 | this.camera.layers.enable(1); 32 | 33 | this.cameraOrigin = new THREE.Vector3(8, 0, 8); 34 | this.cameraRadius = 0.5; 35 | this.cameraAzimuth = 225; 36 | this.cameraElevation = 45; 37 | 38 | this.updateCameraPosition(); 39 | 40 | window.ui.gameWindow.addEventListener('wheel', this.onMouseScroll.bind(this), false); 41 | window.ui.gameWindow.addEventListener('mousedown', this.onMouseMove.bind(this), false); 42 | window.ui.gameWindow.addEventListener('mousemove', this.onMouseMove.bind(this), false); 43 | } 44 | 45 | /** 46 | * Applies any changes to camera position/orientation 47 | */ 48 | updateCameraPosition() { 49 | this.camera.zoom = this.cameraRadius; 50 | this.camera.position.x = 100 * Math.sin(this.cameraAzimuth * DEG2RAD) * Math.cos(this.cameraElevation * DEG2RAD); 51 | this.camera.position.y = 100 * Math.sin(this.cameraElevation * DEG2RAD); 52 | this.camera.position.z = 100 * Math.cos(this.cameraAzimuth * DEG2RAD) * Math.cos(this.cameraElevation * DEG2RAD); 53 | this.camera.position.add(this.cameraOrigin); 54 | this.camera.lookAt(this.cameraOrigin); 55 | this.camera.updateProjectionMatrix(); 56 | this.camera.updateMatrixWorld(); 57 | } 58 | 59 | /** 60 | * Event handler for `mousemove` event 61 | * @param {MouseEvent} event Mouse event arguments 62 | */ 63 | onMouseMove(event) { 64 | // Handles the rotation of the camera 65 | if (event.buttons & RIGHT_MOUSE_BUTTON && !event.ctrlKey) { 66 | this.cameraAzimuth += -(event.movementX * AZIMUTH_SENSITIVITY); 67 | this.cameraElevation += (event.movementY * ELEVATION_SENSITIVITY); 68 | this.cameraElevation = Math.min(MAX_CAMERA_ELEVATION, Math.max(MIN_CAMERA_ELEVATION, this.cameraElevation)); 69 | } 70 | 71 | // Handles the panning of the camera 72 | if (event.buttons & RIGHT_MOUSE_BUTTON && event.ctrlKey) { 73 | const forward = new THREE.Vector3(0, 0, 1).applyAxisAngle(Y_AXIS, this.cameraAzimuth * DEG2RAD); 74 | const left = new THREE.Vector3(1, 0, 0).applyAxisAngle(Y_AXIS, this.cameraAzimuth * DEG2RAD); 75 | this.cameraOrigin.add(forward.multiplyScalar(PAN_SENSITIVITY * event.movementY)); 76 | this.cameraOrigin.add(left.multiplyScalar(PAN_SENSITIVITY * event.movementX)); 77 | } 78 | 79 | this.updateCameraPosition(); 80 | } 81 | 82 | /** 83 | * Event handler for `wheel` event 84 | * @param {MouseEvent} event Mouse event arguments 85 | */ 86 | onMouseScroll(event) { 87 | this.cameraRadius *= 1 - (event.deltaY * ZOOM_SENSITIVITY); 88 | this.cameraRadius = Math.min(MAX_CAMERA_RADIUS, Math.max(MIN_CAMERA_RADIUS, this.cameraRadius)); 89 | 90 | this.updateCameraPosition(); 91 | } 92 | 93 | resize() { 94 | const aspect = window.ui.gameWindow.clientWidth / window.ui.gameWindow.clientHeight; 95 | this.camera.left = (CAMERA_SIZE * aspect) / -2; 96 | this.camera.right = (CAMERA_SIZE * aspect) / 2; 97 | this.camera.updateProjectionMatrix(); 98 | } 99 | } -------------------------------------------------------------------------------- /src/scripts/config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | modules: { 3 | development: { 4 | // Number of simulation cycles the road must fail the abandonment 5 | // criteria before it has a chance of becoming abandoned 6 | abandonThreshold: 10, 7 | // Probability of building being abandoned after it has met the 8 | // abandonment criteria for 'delay' cycles 9 | abandonChance: 0.25, 10 | // Number of days it takes to build a building 11 | constructionTime: 3, 12 | // Probability of a building leveling up 13 | levelUpChance: 0.05, 14 | // Probability of building being re-developed after it is no longer 15 | // meeting the abandonment criteria 16 | redevelopChance: 0.25, 17 | }, 18 | jobs: { 19 | // Max # of workers at a building 20 | maxWorkers: 2, 21 | }, 22 | residents: { 23 | // Max # of residents in a house 24 | maxResidents: 2, 25 | // Chance for a resident to move in 26 | residentMoveInChance: 0.5, 27 | }, 28 | roadAccess: { 29 | // Max distance to search for a road when determining road access 30 | searchDistance: 3 31 | }, 32 | }, 33 | citizen: { 34 | // Minimum working age for a citizen 35 | minWorkingAge: 16, 36 | // Age when citizens retire 37 | retirementAge: 65, 38 | // Max Manhattan distance a citizen will search for a job 39 | maxJobSearchDistance: 4 40 | }, 41 | vehicle: { 42 | // The distance travelled per millisecond 43 | speed: 0.0005, 44 | // The start/end time where the vehicle should fade 45 | fadeTime: 500, 46 | // Maximum lifetime of a vehicle (controls max # of vehicles on screen) 47 | maxLifetime: 10000, 48 | // How often vehicles are spawned in milliseconds 49 | spawnInterval: 1000 50 | }, 51 | } 52 | -------------------------------------------------------------------------------- /src/scripts/game.js: -------------------------------------------------------------------------------- 1 | import * as THREE from 'three'; 2 | import { AssetManager } from './assets/assetManager.js'; 3 | import { CameraManager } from './camera.js'; 4 | import { InputManager } from './input.js'; 5 | import { City } from './sim/city.js'; 6 | import { SimObject } from './sim/simObject.js'; 7 | 8 | /** 9 | * Manager for the Three.js scene. Handles rendering of a `City` object 10 | */ 11 | export class Game { 12 | /** 13 | * @type {City} 14 | */ 15 | city; 16 | /** 17 | * Object that currently hs focus 18 | * @type {SimObject | null} 19 | */ 20 | focusedObject = null; 21 | /** 22 | * Class for managing user input 23 | * @type {InputManager} 24 | */ 25 | inputManager; 26 | /** 27 | * Object that is currently selected 28 | * @type {SimObject | null} 29 | */ 30 | selectedObject = null; 31 | 32 | constructor(city) { 33 | this.city = city; 34 | 35 | this.renderer = new THREE.WebGLRenderer({ 36 | antialias: true 37 | }); 38 | this.scene = new THREE.Scene(); 39 | 40 | this.inputManager = new InputManager(window.ui.gameWindow); 41 | this.cameraManager = new CameraManager(window.ui.gameWindow); 42 | 43 | // Configure the renderer 44 | this.renderer.setSize(window.ui.gameWindow.clientWidth, window.ui.gameWindow.clientHeight); 45 | this.renderer.setClearColor(0x000000, 0); 46 | this.renderer.shadowMap.enabled = true; 47 | this.renderer.shadowMap.type = THREE.PCFShadowMap; 48 | 49 | // Add the renderer to the DOM 50 | window.ui.gameWindow.appendChild(this.renderer.domElement); 51 | 52 | // Variables for object selection 53 | this.raycaster = new THREE.Raycaster(); 54 | 55 | /** 56 | * Global instance of the asset manager 57 | */ 58 | window.assetManager = new AssetManager(() => { 59 | window.ui.hideLoadingText(); 60 | 61 | this.city = new City(16); 62 | this.initialize(this.city); 63 | this.start(); 64 | 65 | setInterval(this.simulate.bind(this), 1000); 66 | }); 67 | 68 | window.addEventListener('resize', this.onResize.bind(this), false); 69 | } 70 | 71 | /** 72 | * Initalizes the scene, clearing all existing assets 73 | */ 74 | initialize(city) { 75 | this.scene.clear(); 76 | this.scene.add(city); 77 | this.#setupLights(); 78 | this.#setupGrid(city); 79 | } 80 | 81 | #setupGrid(city) { 82 | // Add the grid 83 | const gridMaterial = new THREE.MeshBasicMaterial({ 84 | color: 0x000000, 85 | map: window.assetManager.textures['grid'], 86 | transparent: true, 87 | opacity: 0.2 88 | }); 89 | gridMaterial.map.repeat = new THREE.Vector2(city.size, city.size); 90 | gridMaterial.map.wrapS = city.size; 91 | gridMaterial.map.wrapT = city.size; 92 | 93 | const grid = new THREE.Mesh( 94 | new THREE.BoxGeometry(city.size, 0.1, city.size), 95 | gridMaterial 96 | ); 97 | grid.position.set(city.size / 2 - 0.5, -0.04, city.size / 2 - 0.5); 98 | this.scene.add(grid); 99 | } 100 | 101 | /** 102 | * Setup the lights for the scene 103 | */ 104 | #setupLights() { 105 | const sun = new THREE.DirectionalLight(0xffffff, 2) 106 | sun.position.set(-10, 20, 0); 107 | sun.castShadow = true; 108 | sun.shadow.camera.left = -20; 109 | sun.shadow.camera.right = 20; 110 | sun.shadow.camera.top = 20; 111 | sun.shadow.camera.bottom = -20; 112 | sun.shadow.mapSize.width = 2048; 113 | sun.shadow.mapSize.height = 2048; 114 | sun.shadow.camera.near = 10; 115 | sun.shadow.camera.far = 50; 116 | sun.shadow.normalBias = 0.01; 117 | this.scene.add(sun); 118 | this.scene.add(new THREE.AmbientLight(0xffffff, 0.5)); 119 | } 120 | 121 | /** 122 | * Starts the renderer 123 | */ 124 | start() { 125 | this.renderer.setAnimationLoop(this.draw.bind(this)); 126 | } 127 | 128 | /** 129 | * Stops the renderer 130 | */ 131 | stop() { 132 | this.renderer.setAnimationLoop(null); 133 | } 134 | 135 | /** 136 | * Render the contents of the scene 137 | */ 138 | draw() { 139 | this.city.draw(); 140 | this.updateFocusedObject(); 141 | 142 | if (this.inputManager.isLeftMouseDown) { 143 | this.useTool(); 144 | } 145 | 146 | this.renderer.render(this.scene, this.cameraManager.camera); 147 | } 148 | 149 | /** 150 | * Moves the simulation forward by one step 151 | */ 152 | simulate() { 153 | if (window.ui.isPaused) return; 154 | 155 | // Update the city data model first, then update the scene 156 | this.city.simulate(1); 157 | 158 | window.ui.updateTitleBar(this); 159 | window.ui.updateInfoPanel(this.selectedObject); 160 | } 161 | 162 | /** 163 | * Uses the currently active tool 164 | */ 165 | useTool() { 166 | switch (window.ui.activeToolId) { 167 | case 'select': 168 | this.updateSelectedObject(); 169 | window.ui.updateInfoPanel(this.selectedObject); 170 | break; 171 | case 'bulldoze': 172 | if (this.focusedObject) { 173 | const { x, y } = this.focusedObject; 174 | this.city.bulldoze(x, y); 175 | } 176 | break; 177 | default: 178 | if (this.focusedObject) { 179 | const { x, y } = this.focusedObject; 180 | this.city.placeBuilding(x, y, window.ui.activeToolId); 181 | } 182 | break; 183 | } 184 | } 185 | 186 | /** 187 | * Sets the currently selected object and highlights it 188 | */ 189 | updateSelectedObject() { 190 | this.selectedObject?.setSelected(false); 191 | this.selectedObject = this.focusedObject; 192 | this.selectedObject?.setSelected(true); 193 | } 194 | 195 | /** 196 | * Sets the object that is currently highlighted 197 | */ 198 | updateFocusedObject() { 199 | this.focusedObject?.setFocused(false); 200 | const newObject = this.#raycast(); 201 | if (newObject !== this.focusedObject) { 202 | this.focusedObject = newObject; 203 | } 204 | this.focusedObject?.setFocused(true); 205 | } 206 | 207 | /** 208 | * Gets the mesh currently under the the mouse cursor. If there is nothing under 209 | * the the mouse cursor, returns null 210 | * @param {MouseEvent} event Mouse event 211 | * @returns {THREE.Mesh | null} 212 | */ 213 | #raycast() { 214 | var coords = { 215 | x: (this.inputManager.mouse.x / this.renderer.domElement.clientWidth) * 2 - 1, 216 | y: -(this.inputManager.mouse.y / this.renderer.domElement.clientHeight) * 2 + 1 217 | }; 218 | 219 | this.raycaster.setFromCamera(coords, this.cameraManager.camera); 220 | 221 | let intersections = this.raycaster.intersectObjects(this.city.root.children, true); 222 | if (intersections.length > 0) { 223 | // The SimObject attached to the mesh is stored in the user data 224 | const selectedObject = intersections[0].object.userData; 225 | return selectedObject; 226 | } else { 227 | return null; 228 | } 229 | } 230 | 231 | /** 232 | * Resizes the renderer to fit the current game window 233 | */ 234 | onResize() { 235 | this.cameraManager.resize(window.ui.gameWindow); 236 | this.renderer.setSize(window.ui.gameWindow.clientWidth, window.ui.gameWindow.clientHeight); 237 | } 238 | } 239 | 240 | // Create a new game when the window is loaded 241 | window.onload = () => { 242 | window.game = new Game(); 243 | } -------------------------------------------------------------------------------- /src/scripts/input.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Manages mouse and keyboard input 3 | */ 4 | export class InputManager { 5 | /** 6 | * Last mouse position 7 | * @type {x: number, y: number} 8 | */ 9 | mouse = { x: 0, y: 0 }; 10 | /** 11 | * True if left mouse button is currently down 12 | * @type {boolean} 13 | */ 14 | isLeftMouseDown = false; 15 | /** 16 | * True if the middle mouse button is currently down 17 | * @type {boolean} 18 | */ 19 | isMiddleMouseDown = false; 20 | /** 21 | * True if the right mouse button is currently down 22 | * @type {boolean} 23 | */ 24 | isRightMouseDown = false; 25 | 26 | constructor() { 27 | window.ui.gameWindow.addEventListener('mousedown', this.#onMouseDown.bind(this), false); 28 | window.ui.gameWindow.addEventListener('mouseup', this.#onMouseUp.bind(this), false); 29 | window.ui.gameWindow.addEventListener('mousemove', this.#onMouseMove.bind(this), false); 30 | window.ui.gameWindow.addEventListener('contextmenu', (event) => event.preventDefault(), false); 31 | } 32 | 33 | /** 34 | * Event handler for `mousedown` event 35 | * @param {MouseEvent} event 36 | */ 37 | #onMouseDown(event) { 38 | if (event.button === 0) { 39 | this.isLeftMouseDown = true; 40 | } 41 | if (event.button === 1) { 42 | this.isMiddleMouseDown = true; 43 | } 44 | if (event.button === 2) { 45 | this.isRightMouseDown = true; 46 | } 47 | } 48 | 49 | /** 50 | * Event handler for `mouseup` event 51 | * @param {MouseEvent} event 52 | */ 53 | #onMouseUp(event) { 54 | if (event.button === 0) { 55 | this.isLeftMouseDown = false; 56 | } 57 | if (event.button === 1) { 58 | this.isMiddleMouseDown = false; 59 | } 60 | if (event.button === 2) { 61 | this.isRightMouseDown = false; 62 | } 63 | } 64 | 65 | /** 66 | * Event handler for 'mousemove' event 67 | * @param {MouseEvent} event 68 | */ 69 | #onMouseMove(event) { 70 | this.isLeftMouseDown = event.buttons & 1; 71 | this.isRightMouseDown = event.buttons & 2; 72 | this.isMiddleMouseDown = event.buttons & 4; 73 | this.mouse.x = event.clientX; 74 | this.mouse.y = event.clientY; 75 | } 76 | } -------------------------------------------------------------------------------- /src/scripts/sim/buildings/building.js: -------------------------------------------------------------------------------- 1 | import * as THREE from 'three'; 2 | import { SimObject } from '../simObject'; 3 | import { BuildingStatus } from './buildingStatus'; 4 | import { PowerModule } from './modules/power'; 5 | import { RoadAccessModule } from './modules/roadAccess'; 6 | 7 | export class Building extends SimObject { 8 | /** 9 | * The building type 10 | * @type {string} 11 | */ 12 | type = 'building'; 13 | /** 14 | * True if the terrain should not be rendered with this building type 15 | * @type {boolean} 16 | */ 17 | hideTerrain = false; 18 | /** 19 | * @type {PowerModule} 20 | */ 21 | power = new PowerModule(this); 22 | /** 23 | * @type {RoadAccessModule} 24 | */ 25 | roadAccess = new RoadAccessModule(this); 26 | /** 27 | * The current status of the building 28 | * @type {string} 29 | */ 30 | status = BuildingStatus.Ok; 31 | /** 32 | * Icon displayed when building status 33 | * @type {Sprite} 34 | */ 35 | #statusIcon = new THREE.Sprite(); 36 | 37 | constructor() { 38 | super(); 39 | this.#statusIcon.visible = false; 40 | this.#statusIcon.material = new THREE.SpriteMaterial({ depthTest: false }) 41 | this.#statusIcon.layers.set(1); 42 | this.#statusIcon.scale.set(0.5, 0.5, 0.5); 43 | this.add(this.#statusIcon); 44 | } 45 | 46 | /** 47 | * 48 | * @param {*} status 49 | */ 50 | setStatus(status) { 51 | if (status !== this.status) { 52 | switch(status) { 53 | case BuildingStatus.NoPower: 54 | this.#statusIcon.visible = true; 55 | this.#statusIcon.material.map = window.assetManager.statusIcons[BuildingStatus.NoPower]; 56 | break; 57 | case BuildingStatus.NoRoadAccess: 58 | this.#statusIcon.visible = true; 59 | this.#statusIcon.material.map = window.assetManager.statusIcons[BuildingStatus.NoRoadAccess]; 60 | break; 61 | default: 62 | this.#statusIcon.visible = false; 63 | } 64 | } 65 | } 66 | 67 | simulate(city) { 68 | super.simulate(city); 69 | 70 | this.power.simulate(city); 71 | this.roadAccess.simulate(city); 72 | 73 | if (!this.power.isFullyPowered) { 74 | this.setStatus(BuildingStatus.NoPower); 75 | } else if (!this.roadAccess.value) { 76 | this.setStatus(BuildingStatus.NoRoadAccess); 77 | } else { 78 | this.setStatus(null); 79 | } 80 | } 81 | 82 | dispose() { 83 | this.power.dispose(); 84 | this.roadAccess.dispose(); 85 | super.dispose(); 86 | } 87 | 88 | /** 89 | * Returns an HTML representation of this object 90 | * @returns {string} 91 | */ 92 | toHTML() { 93 | let html = ` 94 |
Building
95 | Name 96 | ${this.name} 97 |
98 | Type 99 | ${this.type} 100 |
101 | Road Access 102 | ${this.roadAccess.value} 103 |
`; 104 | 105 | if (this.power.required > 0) { 106 | html += ` 107 | Power (kW) 108 | ${this.power.supplied}/${this.power.required} 109 |
`; 110 | } 111 | return html; 112 | } 113 | } -------------------------------------------------------------------------------- /src/scripts/sim/buildings/buildingFactory.js: -------------------------------------------------------------------------------- 1 | import { BuildingType } from './buildingType.js'; 2 | import { CommercialZone } from './zones/commercial.js'; 3 | import { ResidentialZone } from './zones/residential.js'; 4 | import { IndustrialZone } from './zones/industrial.js'; 5 | import { Road } from './transportation/road.js'; 6 | import { Building } from './building.js'; 7 | import { PowerPlant } from './power/powerPlant.js'; 8 | import { PowerLine } from './power/powerLine.js'; 9 | 10 | /** 11 | * Creates a new building object 12 | * @param {number} x The x-coordinate of the building 13 | * @param {number} y The y-coordinate of the building 14 | * @param {string} type The building type 15 | * @returns {Building} A new building object 16 | */ 17 | export function createBuilding(x, y, type) { 18 | switch (type) { 19 | case BuildingType.residential: 20 | return new ResidentialZone(); 21 | case BuildingType.commercial: 22 | return new CommercialZone(); 23 | case BuildingType.industrial: 24 | return new IndustrialZone(); 25 | case BuildingType.road: 26 | return new Road(); 27 | case BuildingType.powerPlant: 28 | return new PowerPlant(); 29 | case BuildingType.powerLine: 30 | return new PowerLine(); 31 | default: 32 | console.error(`${type} is not a recognized building type.`); 33 | } 34 | } -------------------------------------------------------------------------------- /src/scripts/sim/buildings/buildingStatus.js: -------------------------------------------------------------------------------- 1 | export const BuildingStatus = { 2 | NoPower: 'no-power', 3 | NoRoadAccess: 'no-road-access', 4 | Ok: 'ok' 5 | } -------------------------------------------------------------------------------- /src/scripts/sim/buildings/buildingType.js: -------------------------------------------------------------------------------- 1 | export const BuildingType = { 2 | residential: 'residential', 3 | commercial: 'commercial', 4 | industrial: 'industrial', 5 | road: 'road', 6 | powerPlant: 'power-plant', 7 | powerLine: 'power-line' 8 | } -------------------------------------------------------------------------------- /src/scripts/sim/buildings/modules/development.js: -------------------------------------------------------------------------------- 1 | import config from '../../../config.js'; 2 | import { City } from '../../city.js'; 3 | import { Zone } from '../../buildings/zones/zone.js'; 4 | import { SimModule } from './simModule.js'; 5 | 6 | export const DevelopmentState = { 7 | abandoned: 'abandoned', 8 | developed: 'developed', 9 | underConstruction: 'under-construction', 10 | undeveloped: 'undeveloped', 11 | }; 12 | 13 | export class DevelopmentModule extends SimModule { 14 | /** 15 | * Number of simulation steps that building has met abandonment criteria 16 | * If abandonment criteria are not met, value is zero 17 | * @type {number} 18 | */ 19 | #abandonmentCounter = 0; 20 | 21 | /** 22 | * Counter for days under construction 23 | * @type {number} 24 | */ 25 | #constructionCounter = 0; 26 | 27 | /** 28 | * Level of development 29 | * @type {number} 30 | */ 31 | #level = 1; 32 | 33 | /** 34 | * Maximum level of development 35 | * @type {number} 36 | */ 37 | maxLevel = 3; 38 | 39 | /** 40 | * The zone's current state of development 41 | * @type {string} 42 | */ 43 | #state = DevelopmentState.undeveloped; 44 | 45 | /** 46 | * The parent zone object 47 | * @type {Zone} 48 | */ 49 | #zone; 50 | 51 | /** 52 | * 53 | * @param {Zone} zone 54 | */ 55 | constructor(zone) { 56 | super(); 57 | this.#zone = zone; 58 | } 59 | 60 | get level() { 61 | return this.#level; 62 | } 63 | 64 | set level(value) { 65 | this.#level = value; 66 | this.#zone.refreshView(); 67 | } 68 | 69 | get state() { 70 | return this.#state; 71 | } 72 | 73 | set state(value) { 74 | this.#state = value; 75 | this.#zone.refreshView(); 76 | } 77 | 78 | /** 79 | * @param {City} city 80 | */ 81 | simulate(city) { 82 | this.#checkAbandonmentCriteria(); 83 | 84 | switch (this.state) { 85 | case DevelopmentState.undeveloped: 86 | if (this.#checkDevelopmentCriteria() && 87 | Math.random() < config.modules.development.redevelopChance) { 88 | this.state = DevelopmentState.underConstruction; 89 | this.#constructionCounter = 0; 90 | } 91 | break; 92 | case DevelopmentState.underConstruction: 93 | if (++this.#constructionCounter === config.modules.development.constructionTime) { 94 | this.state = DevelopmentState.developed; 95 | this.level = 1; 96 | this.#constructionCounter = 0; 97 | } 98 | break; 99 | case DevelopmentState.developed: 100 | if (this.#abandonmentCounter > config.modules.development.abandonThreshold) { 101 | if (Math.random() < config.modules.development.abandonChance) { 102 | this.state = DevelopmentState.abandoned; 103 | } 104 | } else { 105 | if (this.level < this.maxLevel && Math.random() < config.modules.development.levelUpChance) { 106 | this.level++; 107 | } 108 | } 109 | break; 110 | case DevelopmentState.abandoned: 111 | if (this.#abandonmentCounter == 0) { 112 | if (Math.random() < config.modules.development.redevelopChance) { 113 | this.state = DevelopmentState.developed; 114 | } 115 | } 116 | break; 117 | } 118 | } 119 | 120 | /** 121 | * @param {City} city 122 | * @returns 123 | */ 124 | #checkDevelopmentCriteria() { 125 | return ( 126 | this.#zone.roadAccess.value && 127 | this.#zone.power.isFullyPowered 128 | ); 129 | } 130 | 131 | /** 132 | * @param {City} city 133 | * @returns 134 | */ 135 | #checkAbandonmentCriteria() { 136 | if (!this.#checkDevelopmentCriteria()) { 137 | this.#abandonmentCounter++; 138 | } else { 139 | this.#abandonmentCounter = 0; 140 | } 141 | } 142 | 143 | /** 144 | * Returns an HTML representation of this object 145 | * @returns {string} 146 | */ 147 | toHTML() { 148 | return ` 149 | State 150 | ${this.state} 151 |
152 | Level 153 | ${this.level} 154 |
`; 155 | } 156 | } -------------------------------------------------------------------------------- /src/scripts/sim/buildings/modules/jobs.js: -------------------------------------------------------------------------------- 1 | import config from '../../../config.js'; 2 | import { Citizen } from '../../citizen.js'; 3 | import { City } from '../../city.js'; 4 | import { Zone } from '../../buildings/zones/zone.js'; 5 | import { DevelopmentState } from './development.js'; 6 | import { SimModule } from './simModule.js'; 7 | 8 | export class JobsModule extends SimModule { 9 | /** 10 | * @type {Zone} 11 | */ 12 | #zone; 13 | 14 | /** 15 | * @type {Citizen[]} 16 | */ 17 | workers = []; 18 | 19 | constructor(zone) { 20 | super(); 21 | this.#zone = zone; 22 | } 23 | 24 | /** 25 | * Maximuim number of workers that can work at this building 26 | * @returns {number} 27 | */ 28 | get maxWorkers() { 29 | // If building is not developed, there are no available jobs 30 | if (this.#zone.development.state !== DevelopmentState.developed) { 31 | return 0; 32 | } else { 33 | return Math.pow(config.modules.jobs.maxWorkers, this.#zone.development.level); 34 | } 35 | } 36 | 37 | /** 38 | * Returns the number of job openings 39 | * @returns {number} 40 | */ 41 | get availableJobs() { 42 | return this.maxWorkers - this.workers.length; 43 | } 44 | 45 | /** 46 | * Returns the number of positions that are filled 47 | * @returns {number} 48 | */ 49 | get filledJobs() { 50 | return this.workers.length; 51 | } 52 | 53 | /** 54 | * Steps the state of the zone forward in time by one simulation step 55 | * @param {City} city 56 | */ 57 | simulate(city) { 58 | // If building is abandoned, all workers are laid off and no 59 | // more workers are allowed to work here 60 | if (this.#zone.development.state === DevelopmentState.abandoned) { 61 | this.#layOffWorkers(); 62 | } 63 | } 64 | 65 | /** 66 | * Lay off all existing workers 67 | */ 68 | #layOffWorkers() { 69 | for (const worker of this.workers) { 70 | worker.setWorkplace(null); 71 | } 72 | this.workers = []; 73 | } 74 | 75 | /** 76 | * Handles any clean up needed before a building is removed 77 | */ 78 | dispose() { 79 | this.#layOffWorkers(); 80 | } 81 | 82 | /** 83 | * Returns an HTML representation of this object 84 | * @returns {string} 85 | */ 86 | toHTML() { 87 | let html = `
Workers (${this.filledJobs}/${this.maxWorkers})
`; 88 | 89 | html += ''; 94 | 95 | return html; 96 | } 97 | } -------------------------------------------------------------------------------- /src/scripts/sim/buildings/modules/power.js: -------------------------------------------------------------------------------- 1 | import { SimModule } from './simModule.js'; 2 | 3 | /** 4 | * Logic for determining whether or not a tile has road access 5 | */ 6 | export class PowerModule extends SimModule { 7 | /** 8 | * Amount of power supplied to this building (if powerRequired > 0) 9 | * @type {number} 10 | */ 11 | supplied = 0; 12 | 13 | /** 14 | * Amount of power this building needs 15 | * @type {number} 16 | */ 17 | required = 0; 18 | 19 | /** 20 | * @param {number} powerRequired Amount of power (kWh) this building needs 21 | */ 22 | constructor(powerRequired) { 23 | super(); 24 | this.required = this.supplied; 25 | } 26 | 27 | /** 28 | * Returns true if building is fully powered 29 | * @type {boolean} 30 | */ 31 | get isFullyPowered () { 32 | return this.supplied >= this.required; 33 | } 34 | } -------------------------------------------------------------------------------- /src/scripts/sim/buildings/modules/residents.js: -------------------------------------------------------------------------------- 1 | import config from '../../../config.js'; 2 | import { Citizen } from '../../citizen.js'; 3 | import { City } from '../../city.js'; 4 | import { Zone as ResidentialZone } from '../../buildings/zones/zone.js'; 5 | import { DevelopmentState } from './development.js'; 6 | import { SimModule } from './simModule.js'; 7 | 8 | /** 9 | * Logic for residents moving into and out of a building 10 | */ 11 | export class ResidentsModule extends SimModule { 12 | /** 13 | * @type {ResidentialZone} 14 | */ 15 | #zone; 16 | 17 | /** 18 | * @type {Citizen[]} 19 | */ 20 | #residents = []; 21 | 22 | /** 23 | * @param {ResidentialZone} zone 24 | */ 25 | constructor(zone) { 26 | super(); 27 | this.#zone = zone; 28 | } 29 | 30 | /** 31 | * Returns the number of residents 32 | * @type {number} 33 | */ 34 | get count() { 35 | return this.#residents.length; 36 | } 37 | 38 | /** 39 | * Maximuim number of residents that can live in this building 40 | * @returns {number} 41 | */ 42 | get maximum() { 43 | return Math.pow(config.modules.residents.maxResidents, this.#zone.development.level); 44 | } 45 | 46 | /** 47 | * @param {City} city 48 | */ 49 | simulate(city) { 50 | // If building is abandoned, all residents are evicted and no more residents are allowed to move in. 51 | if (this.#zone.development.state === DevelopmentState.abandoned && this.#residents.length > 0) { 52 | this.evictAll(); 53 | } else if (this.#zone.development.state === DevelopmentState.developed) { 54 | // Move in new residents if there is room 55 | if (this.#residents.length < this.maximum && Math.random() < config.modules.residents.residentMoveInChance) { 56 | this.#residents.push(new Citizen(this.#zone)); 57 | } 58 | } 59 | 60 | for (const resident of this.#residents) { 61 | resident.simulate(city); 62 | } 63 | } 64 | 65 | /** 66 | * Evicts all residents from the building 67 | */ 68 | #evictAll() { 69 | for (const resident of this.#residents) { 70 | resident.dispose(); 71 | } 72 | this.#residents = []; 73 | } 74 | 75 | /** 76 | * Handles any clean up needed before a building is removed 77 | */ 78 | dispose() { 79 | this.#evictAll(); 80 | } 81 | 82 | /** 83 | * Returns an HTML representation of this object 84 | * @returns {string} 85 | */ 86 | toHTML() { 87 | let html = `
Residents (${this.#residents.length}/${this.maximum})
`; 88 | 89 | html += ''; 94 | 95 | return html; 96 | } 97 | } -------------------------------------------------------------------------------- /src/scripts/sim/buildings/modules/roadAccess.js: -------------------------------------------------------------------------------- 1 | import config from '../../../config.js'; 2 | import { City } from '../../city.js'; 3 | import { Building } from '../building.js'; 4 | import { SimModule } from './simModule.js'; 5 | 6 | /** 7 | * Logic for determining whether or not a tile has road access 8 | */ 9 | export class RoadAccessModule extends SimModule { 10 | /** 11 | * @type {Building} 12 | */ 13 | building; 14 | /** 15 | * @type {boolean} 16 | */ 17 | enabled = true; 18 | /** 19 | * Whether or not the tile has access to a road 20 | * @type {boolean} 21 | */ 22 | value; 23 | 24 | /** 25 | * @param {Building} building 26 | */ 27 | constructor(building) { 28 | super(); 29 | this.building = building; 30 | } 31 | 32 | /** 33 | * Updates the state of this attribute 34 | * @param {City} city 35 | */ 36 | simulate(city) { 37 | if (!this.enabled) { 38 | this.value = true; 39 | } else { 40 | const road = city.findTile( 41 | this.building, 42 | (tile) => tile.building?.type === 'road', 43 | config.modules.roadAccess.searchDistance); 44 | 45 | this.value = (road !== null); 46 | } 47 | } 48 | } -------------------------------------------------------------------------------- /src/scripts/sim/buildings/modules/simModule.js: -------------------------------------------------------------------------------- 1 | import { City } from '../../city.js'; 2 | 3 | export class SimModule { 4 | /** 5 | * Simulates one day passing 6 | * @param {City} city 7 | */ 8 | simulate(city) { 9 | // Implement in subclass 10 | } 11 | 12 | /** 13 | * Cleans up this module, disposing of any assets and unlinking any references 14 | */ 15 | dispose() { 16 | // Implement in subclass 17 | } 18 | 19 | /** 20 | * Returns an HTML representation of this object 21 | * @returns {string} 22 | */ 23 | toHTML() { 24 | // Implement in subclass 25 | } 26 | } -------------------------------------------------------------------------------- /src/scripts/sim/buildings/power/powerLine.js: -------------------------------------------------------------------------------- 1 | import * as THREE from 'three'; 2 | import { Building } from '../building.js'; 3 | import { BuildingType } from '../buildingType.js'; 4 | 5 | const Side = { 6 | Left: 'left', 7 | Right: 'right', 8 | Top: 'top', 9 | Bottom: 'bottom' 10 | } 11 | 12 | const powerLineMaterial = new THREE.LineBasicMaterial({ color: 0 }); 13 | 14 | export class PowerLine extends Building { 15 | 16 | constructor(x, y) { 17 | super(x, y); 18 | this.type = BuildingType.powerLine; 19 | this.roadAccess.enabled = false; 20 | } 21 | 22 | refreshView(city) { 23 | let group = new THREE.Group(); 24 | 25 | // Merge two powerline models, offset by 90 degrees 26 | let tower = window.assetManager.getModel(this.type, this); 27 | tower.rotation.y = Math.PI / 4; 28 | 29 | // Check which adjacent tiles are powerlines 30 | let top = (city.getTile(this.x, this.y - 1)?.building?.type === this.type) ?? false; 31 | let bottom = (city.getTile(this.x, this.y + 1)?.building?.type === this.type) ?? false; 32 | let left = (city.getTile(this.x - 1, this.y)?.building?.type === this.type) ?? false; 33 | let right = (city.getTile(this.x + 1, this.y)?.building?.type === this.type) ?? false; 34 | 35 | group.add(tower); 36 | 37 | if (top) { 38 | this.#addLines(group, Side.Top); 39 | } 40 | if (bottom) { 41 | this.#addLines(group, Side.Bottom); 42 | } 43 | if (left) { 44 | this.#addLines(group, Side.Left); 45 | } 46 | if (right) { 47 | this.#addLines(group, Side.Right); 48 | } 49 | 50 | this.setMesh(group); 51 | } 52 | 53 | #addLines(group, side) { 54 | switch (side) { 55 | case Side.Left: 56 | group.add(this.#createPowerLine(-0.09, 0.36, 0.09, -0.5, 0.36, 0.09)); 57 | group.add(this.#createPowerLine(-0.09, 0.36, -0.09, -0.5, 0.36, -0.09)); 58 | break; 59 | case Side.Right: 60 | group.add(this.#createPowerLine(0.09, 0.36, 0.09, 0.5, 0.36, 0.09)); 61 | group.add(this.#createPowerLine(0.09, 0.36, -0.09, 0.5, 0.36, -0.09)); 62 | break; 63 | case Side.Top: 64 | group.add(this.#createPowerLine(0.09, 0.36, -0.09, 0.09, 0.36, -0.5)); 65 | group.add(this.#createPowerLine(-0.09, 0.36, -0.09, -0.09, 0.36, -0.5)); 66 | break; 67 | case Side.Bottom: 68 | group.add(this.#createPowerLine(0.09, 0.36, 0.09, 0.09, 0.36, 0.5)); 69 | group.add(this.#createPowerLine(-0.09, 0.36, 0.09, -0.09, 0.36, 0.5)); 70 | break; 71 | } 72 | } 73 | 74 | /** 75 | * Creates a new power line between the start/stop points 76 | * @returns 77 | */ 78 | #createPowerLine(x1, y1, z1, x2, y2, z2) { 79 | const points = [ 80 | new THREE.Vector3(x1, y1, z1), 81 | new THREE.Vector3(x2, y2, z2) 82 | ]; 83 | const geometry = new THREE.BufferGeometry().setFromPoints(points); 84 | const powerLine = new THREE.Line(geometry, powerLineMaterial); 85 | // Put in layer 1 so it doesn't interact with raycaster 86 | powerLine.layers.set(1); 87 | return powerLine; 88 | } 89 | 90 | 91 | } -------------------------------------------------------------------------------- /src/scripts/sim/buildings/power/powerPlant.js: -------------------------------------------------------------------------------- 1 | import { Building } from '../building.js'; 2 | import { BuildingType } from '../buildingType.js'; 3 | 4 | export class PowerPlant extends Building { 5 | 6 | /** 7 | * Available units of power (kW) 8 | */ 9 | powerCapacity = 100; 10 | 11 | /** 12 | * Consumed units of power 13 | */ 14 | powerConsumed = 0; 15 | 16 | constructor(x, y) { 17 | super(x, y); 18 | this.type = BuildingType.powerPlant; 19 | } 20 | 21 | /** 22 | * Gets the amount of power available 23 | */ 24 | get powerAvailable() { 25 | // Power plant must have road access in order to provide power 26 | if (this.roadAccess.value) { 27 | return this.powerCapacity - this.powerConsumed; 28 | } else { 29 | return 0; 30 | } 31 | } 32 | 33 | refreshView() { 34 | let mesh = window.assetManager.getModel(this.type, this); 35 | this.setMesh(mesh); 36 | } 37 | 38 | /** 39 | * Returns an HTML representation of this object 40 | * @returns {string} 41 | */ 42 | toHTML() { 43 | let html = super.toHTML(); 44 | html += ` 45 |
Power
46 | Power Capacity (kW) 47 | ${this.powerCapacity} 48 |
49 | Power Consumed (kW) 50 | ${this.powerConsumed} 51 |
52 | Power Available (kW) 53 | ${this.powerAvailable} 54 |
55 | `; 56 | return html; 57 | } 58 | } -------------------------------------------------------------------------------- /src/scripts/sim/buildings/transportation/road.js: -------------------------------------------------------------------------------- 1 | import * as THREE from 'three'; 2 | import { Building } from '../building.js'; 3 | import { City } from '../../city.js'; 4 | import { DEG2RAD } from 'three/src/math/MathUtils.js'; 5 | 6 | export class Road extends Building { 7 | constructor(x, y) { 8 | super(x, y); 9 | this.type = 'road'; 10 | this.name = 'Road'; 11 | this.style = 'straight'; 12 | this.hideTerrain = true; 13 | this.roadAccess.enabled = false; 14 | } 15 | 16 | /** 17 | * Updates the road mesh based on which adjacent tiles are roads as well 18 | * @param {City} city 19 | */ 20 | refreshView(city) { 21 | // Check which adjacent tiles are roads 22 | let top = (city.getTile(this.x, this.y - 1)?.building?.type === this.type) ?? false; 23 | let bottom = (city.getTile(this.x, this.y + 1)?.building?.type === this.type) ?? false; 24 | let left = (city.getTile(this.x - 1, this.y)?.building?.type === this.type) ?? false; 25 | let right = (city.getTile(this.x + 1, this.y)?.building?.type === this.type) ?? false; 26 | 27 | // Check all combinations 28 | // Four-way intersection 29 | if (top && bottom && left && right) { 30 | this.style = 'four-way'; 31 | this.rotation.y = 0; 32 | // T intersection 33 | } else if (!top && bottom && left && right) { // bottom-left-right 34 | this.style = 'three-way'; 35 | this.rotation.y = 0; 36 | } else if (top && !bottom && left && right) { // top-left-right 37 | this.style = 'three-way'; 38 | this.rotation.y = 180 * DEG2RAD; 39 | } else if (top && bottom && !left && right) { // top-bottom-right 40 | this.style = 'three-way'; 41 | this.rotation.y = 90 * DEG2RAD; 42 | } else if (top && bottom && left && !right) { // top-bottom-left 43 | this.style = 'three-way'; 44 | this.rotation.y = 270 * DEG2RAD; 45 | // Corner 46 | } else if (top && !bottom && left && !right) { // top-left 47 | this.style = 'corner'; 48 | this.rotation.y = 180 * DEG2RAD; 49 | } else if (top && !bottom && !left && right) { // top-right 50 | this.style = 'corner'; 51 | this.rotation.y = 90 * DEG2RAD; 52 | } else if (!top && bottom && left && !right) { // bottom-left 53 | this.style = 'corner'; 54 | this.rotation.y = 270 * DEG2RAD; 55 | } else if (!top && bottom && !left && right) { // bottom-right 56 | this.style = 'corner'; 57 | this.rotation.y = 0; 58 | // Straight 59 | } else if (top && bottom && !left && !right) { // top-bottom 60 | this.style = 'straight'; 61 | this.rotation.y = 0; 62 | } else if (!top && !bottom && left && right) { // left-right 63 | this.style = 'straight'; 64 | this.rotation.y = 90 * DEG2RAD; 65 | // Dead end 66 | } else if (top && !bottom && !left && !right) { // top 67 | this.style = 'end'; 68 | this.rotation.y = 180 * DEG2RAD; 69 | } else if (!top && bottom && !left && !right) { // bottom 70 | this.style = 'end'; 71 | this.rotation.y = 0; 72 | } else if (!top && !bottom && left && !right) { // left 73 | this.style = 'end'; 74 | this.rotation.y = 270 * DEG2RAD; 75 | } else if (!top && !bottom && !left && right) { // right 76 | this.style = 'end'; 77 | this.rotation.y = 90 * DEG2RAD; 78 | } 79 | 80 | const mesh = window.assetManager.getModel(`road-${this.style}`, this); 81 | this.setMesh(mesh); 82 | city.vehicleGraph.updateTile(this.x, this.y, this); 83 | } 84 | 85 | /** 86 | * Returns an HTML representation of this object 87 | * @returns {string} 88 | */ 89 | toHTML() { 90 | let html = super.toHTML(); 91 | html += ` 92 | Style 93 | ${this.style} 94 |
95 | `; 96 | return html; 97 | } 98 | } -------------------------------------------------------------------------------- /src/scripts/sim/buildings/zones/commercial.js: -------------------------------------------------------------------------------- 1 | import { City } from '../../city.js'; 2 | import { Zone } from './zone.js'; 3 | import { JobsModule } from '../modules/jobs.js'; 4 | import { BuildingType } from '../buildingType.js'; 5 | 6 | export class CommercialZone extends Zone { 7 | /** 8 | * @type {JobsModule} 9 | */ 10 | jobs = new JobsModule(this); 11 | 12 | constructor(x, y) { 13 | super(x, y); 14 | this.name = generateBusinessName(); 15 | this.type = BuildingType.commercial; 16 | } 17 | 18 | /** 19 | * Steps the state of the zone forward in time by one simulation step 20 | * @param {City} city 21 | */ 22 | simulate(city) { 23 | super.simulate(city); 24 | this.jobs.simulate(); 25 | } 26 | 27 | /** 28 | * Handles any clean up needed before a building is removed 29 | */ 30 | dispose() { 31 | this.jobs.dispose(); 32 | super.dispose(); 33 | } 34 | 35 | /** 36 | * Returns an HTML representation of this object 37 | * @returns {string} 38 | */ 39 | toHTML() { 40 | let html = super.toHTML(); 41 | html += this.jobs.toHTML(); 42 | return html; 43 | } 44 | } 45 | 46 | // Arrays of words for generating business names 47 | const prefixes = ['Prime', 'Elite', 'Global', 'Exquisite', 'Vibrant', 'Luxury', 'Innovative', 'Sleek', 'Premium', 'Dynamic']; 48 | const suffixes = ['Commerce', 'Trade', 'Marketplace', 'Ventures', 'Enterprises', 'Retail', 'Group', 'Emporium', 'Boutique', 'Mall']; 49 | const businessSuffixes = ['LLC', 'Inc.', 'Co.', 'Corp.', 'Ltd.']; 50 | 51 | // Function to generate a random commercial business name 52 | function generateBusinessName() { 53 | const prefix = prefixes[Math.floor(Math.random() * prefixes.length)]; 54 | const suffix = suffixes[Math.floor(Math.random() * suffixes.length)]; 55 | const businessSuffix = businessSuffixes[Math.floor(Math.random() * businessSuffixes.length)]; 56 | 57 | return prefix + ' ' + suffix + ' ' + businessSuffix; 58 | } -------------------------------------------------------------------------------- /src/scripts/sim/buildings/zones/industrial.js: -------------------------------------------------------------------------------- 1 | import { City } from '../../city.js'; 2 | import { JobsModule } from '../modules/jobs.js'; 3 | import { BuildingType } from '../buildingType.js'; 4 | import { Zone } from './zone.js'; 5 | 6 | export class IndustrialZone extends Zone { 7 | /** 8 | * @type {JobsModule} 9 | */ 10 | jobs = new JobsModule(this); 11 | 12 | constructor(x, y) { 13 | super(x, y); 14 | this.name = generateBusinessName(); 15 | this.type = BuildingType.industrial; 16 | } 17 | 18 | /** 19 | * Steps the state of the zone forward in time by one simulation step 20 | * @param {City} city 21 | */ 22 | simulate(city) { 23 | super.simulate(city); 24 | this.jobs.simulate(); 25 | } 26 | 27 | /** 28 | * Handles any clean up needed before a building is removed 29 | */ 30 | dispose() { 31 | this.jobs.dispose(); 32 | super.dispose(); 33 | } 34 | 35 | /** 36 | * Returns an HTML representation of this object 37 | * @returns {string} 38 | */ 39 | toHTML() { 40 | let html = super.toHTML(); 41 | html += this.jobs.toHTML(); 42 | return html; 43 | } 44 | } 45 | 46 | // Arrays of words for generating business names 47 | const prefixes = ['Apex', 'Vortex', 'Elevate', 'Zenith', 'Nova', 'Synapse', 'Pulse', 'Enigma', 'Catalyst', 'Axiom']; 48 | const suffixes = ['Dynamics', 'Ventures', 'Solutions', 'Technologies', 'Innovations', 'Industries', 'Enterprises', 'Systems', 'Mechanics', 'Manufacturing']; 49 | const businessSuffixes = ['LLC', 'Inc.', 'Co.', 'Corp.', 'Ltd.']; 50 | 51 | // Function to generate a random industrial business name 52 | function generateBusinessName() { 53 | const prefix = prefixes[Math.floor(Math.random() * prefixes.length)]; 54 | const suffix = suffixes[Math.floor(Math.random() * suffixes.length)]; 55 | const businessSuffix = businessSuffixes[Math.floor(Math.random() * businessSuffixes.length)]; 56 | 57 | return prefix + ' ' + suffix + ' ' + businessSuffix; 58 | } -------------------------------------------------------------------------------- /src/scripts/sim/buildings/zones/residential.js: -------------------------------------------------------------------------------- 1 | import { City } from '../../city.js'; 2 | import { Zone } from './zone.js'; 3 | import { ResidentsModule } from '../modules/residents.js'; 4 | import { BuildingType } from '../buildingType.js'; 5 | 6 | export class ResidentialZone extends Zone { 7 | /** 8 | * @type {ResidentsModule} 9 | */ 10 | residents = new ResidentsModule(this); 11 | 12 | constructor(x, y) { 13 | super(x, y); 14 | this.name = generateBuildingName(); 15 | this.type = BuildingType.residential; 16 | } 17 | 18 | /** 19 | * Steps the state of the zone forward in time by one simulation step 20 | * @param {City} city 21 | */ 22 | simulate(city) { 23 | super.simulate(city); 24 | this.residents.simulate(city); 25 | } 26 | 27 | /** 28 | * Handles any clean up needed before a building is removed 29 | */ 30 | dispose() { 31 | this.residents.dispose(); 32 | super.dispose(); 33 | } 34 | 35 | /** 36 | * Returns an HTML representation of this object 37 | * @returns {string} 38 | */ 39 | toHTML() { 40 | let html = super.toHTML(); 41 | html += this.residents.toHTML(); 42 | return html; 43 | } 44 | } 45 | 46 | // Arrays of different name components 47 | const prefixes = ['Emerald', 'Ivory', 'Crimson', 'Opulent', 'Celestial', 'Enchanted', 'Serene', 'Whispering', 'Stellar', 'Tranquil']; 48 | const suffixes = ['Tower', 'Residence', 'Manor', 'Court', 'Plaza', 'House', 'Mansion', 'Place', 'Villa', 'Gardens']; 49 | 50 | // Function to generate a random building name 51 | function generateBuildingName() { 52 | const prefix = prefixes[Math.floor(Math.random() * prefixes.length)]; 53 | const suffix = suffixes[Math.floor(Math.random() * suffixes.length)]; 54 | 55 | return prefix + ' ' + suffix; 56 | } -------------------------------------------------------------------------------- /src/scripts/sim/buildings/zones/zone.js: -------------------------------------------------------------------------------- 1 | import * as THREE from 'three'; 2 | import { DEG2RAD } from 'three/src/math/MathUtils.js'; 3 | import { DevelopmentModule, DevelopmentState } from '../modules/development.js'; 4 | import { Building } from '../building.js'; 5 | 6 | /** 7 | * Represents a zoned building such as residential, commercial or industrial 8 | */ 9 | export class Zone extends Building { 10 | /** 11 | * The mesh style to use when rendering 12 | */ 13 | style = ['A', 'B', 'C'][Math.floor(3 * Math.random())]; 14 | 15 | /** 16 | * @type {DevelopmentModule} 17 | */ 18 | development = new DevelopmentModule(this); 19 | 20 | constructor(x = 0, y = 0) { 21 | super(x, y); 22 | 23 | this.name = 'Zone'; 24 | this.power.required = 10; 25 | 26 | // Randomize the building rotation 27 | this.rotation.y = 90 * Math.floor(4 * Math.random()) * DEG2RAD; 28 | } 29 | 30 | refreshView() { 31 | let modelName; 32 | switch (this.development.state) { 33 | case DevelopmentState.underConstruction: 34 | case DevelopmentState.undeveloped: 35 | modelName = 'under-construction'; 36 | break; 37 | default: 38 | modelName = `${this.type}-${this.style}${this.development.level}`; 39 | break; 40 | } 41 | 42 | let mesh = window.assetManager.getModel(modelName, this); 43 | 44 | // Tint building a dark color if it is abandoned 45 | if (this.development.state === DevelopmentState.abandoned) { 46 | mesh.traverse((obj) => { 47 | if (obj.material) { 48 | obj.material.color = new THREE.Color(0x707070); 49 | } 50 | }); 51 | } 52 | 53 | this.setMesh(mesh); 54 | } 55 | 56 | simulate(city) { 57 | super.simulate(city); 58 | this.development.simulate(city); 59 | } 60 | 61 | /** 62 | * Returns an HTML representation of this object 63 | * @returns {string} 64 | */ 65 | toHTML() { 66 | let html = super.toHTML(); 67 | html += this.development.toHTML(); 68 | return html; 69 | } 70 | } -------------------------------------------------------------------------------- /src/scripts/sim/citizen.js: -------------------------------------------------------------------------------- 1 | import { CommercialZone } from './buildings/zones/commercial.js'; 2 | import { IndustrialZone } from './buildings/zones/industrial.js'; 3 | import { ResidentialZone } from './buildings/zones/residential.js'; 4 | import config from '../config.js'; 5 | 6 | export class Citizen { 7 | /** 8 | * @param {ResidentialZone} residence 9 | */ 10 | constructor(residence) { 11 | /** 12 | * Unique identifier for the citizen 13 | * @type {string} 14 | */ 15 | this.id = crypto.randomUUID(); 16 | 17 | /** 18 | * Name of this citizen 19 | * @type {string} 20 | */ 21 | this.name = generateRandomName(); 22 | 23 | /** 24 | * Age of the citizen in years 25 | * @type {number} 26 | */ 27 | this.age = 1 + Math.floor(100*Math.random()); 28 | 29 | /** 30 | * The current state of the citizen 31 | * @type {'idle' | 'school' | 'employed' | 'unemployed' | 'retired'} 32 | */ 33 | this.state = 'idle'; 34 | 35 | /** 36 | * Number of simulation steps in the current state 37 | */ 38 | this.stateCounter = 0; 39 | 40 | /** 41 | * Reference to the building the citizen lives at 42 | * @type {ResidentialZone} 43 | */ 44 | this.residence = residence; 45 | 46 | /** 47 | * Reference to the building the citizen works at 48 | * @type {CommercialZone | IndustrialZone} 49 | */ 50 | this.workplace = null; 51 | 52 | this.#initializeState(); 53 | } 54 | 55 | /** 56 | * Sets the initial state of the citizen 57 | */ 58 | #initializeState() { 59 | if (this.age < config.citizen.minWorkingAge) { 60 | this.state = 'school'; 61 | } else if (this.age >= config.citizen.retirementAge) { 62 | this.state = 'retired'; 63 | } else { 64 | this.state = 'unemployed'; 65 | } 66 | } 67 | 68 | /** 69 | * Steps the state of the citizen forward in time by one simulation step 70 | * @param {object} city 71 | */ 72 | simulate(city) { 73 | switch (this.state) { 74 | case 'idle': 75 | case 'school': 76 | case 'retired': 77 | // Action - None 78 | 79 | // Transitions - None 80 | 81 | break; 82 | case 'unemployed': 83 | // Action - Look for a job 84 | this.workplace = this.#findJob(city); 85 | 86 | // Transitions 87 | if (this.workplace) { 88 | this.state = 'employed'; 89 | } 90 | 91 | break; 92 | case 'employed': 93 | // Actions - None 94 | 95 | // Transitions 96 | if (!this.workplace) { 97 | this.state = 'unemployed'; 98 | } 99 | 100 | break; 101 | default: 102 | console.error(`Citizen ${this.id} is in an unknown state (${this.state})`); 103 | } 104 | } 105 | 106 | /** 107 | * Handles any clean up needed before a building is removed 108 | */ 109 | dispose() { 110 | // Remove resident from its workplace 111 | const workerIndex = this.workplace?.jobs.workers.indexOf(this); 112 | 113 | if (workerIndex !== undefined && workerIndex > -1) { 114 | this.workplace.jobs.workers.splice(workerIndex); 115 | } 116 | } 117 | 118 | /** 119 | * Search for a job nearby 120 | * @param {object} city 121 | * @returns 122 | */ 123 | #findJob(city) { 124 | const tile = city.findTile(this.residence, (tile) => { 125 | // Search for an industrial or commercial building with at least one available job 126 | if (tile.building?.type === 'industrial' || 127 | tile.building?.type === 'commercial') { 128 | if (tile.building.jobs.availableJobs > 0) { 129 | return true; 130 | } 131 | } 132 | 133 | return false; 134 | }, config.citizen.maxJobSearchDistance); 135 | 136 | if (tile) { 137 | // Employ the citizen at the building 138 | tile.building.jobs.workers.push(this); 139 | return tile.building; 140 | } else { 141 | return null; 142 | } 143 | } 144 | 145 | /** 146 | * Sets the workplace for the citizen 147 | * @param {CommercialZone | IndustrialZone} workplace 148 | */ 149 | setWorkplace(workplace) { 150 | this.workplace = workplace; 151 | } 152 | 153 | /** 154 | * Returns an HTML representation of this object 155 | * @returns {string} 156 | */ 157 | toHTML() { 158 | return ` 159 |
  • 160 | ${this.name} 161 |
    162 | 163 | 164 | 165 | ${this.age} 166 | 167 | 168 | 169 | ${this.state} 170 | 171 | 172 |
  • 173 | `; 174 | } 175 | } 176 | 177 | function generateRandomName() { 178 | const firstNames = [ 179 | 'Emma', 'Olivia', 'Ava', 'Sophia', 'Isabella', 180 | 'Liam', 'Noah', 'William', 'James', 'Benjamin', 181 | 'Elizabeth', 'Margaret', 'Alice', 'Dorothy', 'Eleanor', 182 | 'John', 'Robert', 'William', 'Charles', 'Henry', 183 | 'Alex', 'Taylor', 'Jordan', 'Casey', 'Robin' 184 | ]; 185 | 186 | const lastNames = [ 187 | 'Smith', 'Johnson', 'Williams', 'Jones', 'Brown', 188 | 'Davis', 'Miller', 'Wilson', 'Moore', 'Taylor', 189 | 'Anderson', 'Thomas', 'Jackson', 'White', 'Harris', 190 | 'Clark', 'Lewis', 'Walker', 'Hall', 'Young', 191 | 'Lee', 'King', 'Wright', 'Adams', 'Green' 192 | ]; 193 | 194 | const randomFirstName = firstNames[Math.floor(Math.random() * firstNames.length)]; 195 | const randomLastName = lastNames[Math.floor(Math.random() * lastNames.length)]; 196 | 197 | return randomFirstName + ' ' + randomLastName; 198 | } -------------------------------------------------------------------------------- /src/scripts/sim/city.js: -------------------------------------------------------------------------------- 1 | import * as THREE from 'three'; 2 | import { BuildingType } from './buildings/buildingType.js'; 3 | import { createBuilding } from './buildings/buildingFactory.js'; 4 | import { Tile } from './tile.js'; 5 | import { VehicleGraph } from './vehicles/vehicleGraph.js'; 6 | import { PowerService } from './services/power.js'; 7 | import { SimService } from './services/simService.js'; 8 | 9 | export class City extends THREE.Group { 10 | /** 11 | * Separate group for organizing debug meshes so they aren't included 12 | * in raycasting checks 13 | * @type {THREE.Group} 14 | */ 15 | debugMeshes = new THREE.Group(); 16 | /** 17 | * Root node for all scene objects 18 | * @type {THREE.Group} 19 | */ 20 | root = new THREE.Group(); 21 | /** 22 | * List of services for the city 23 | * @type {SimService} 24 | */ 25 | services = []; 26 | /** 27 | * The size of the city in tiles 28 | * @type {number} 29 | */ 30 | size = 16; 31 | /** 32 | * The current simulation time 33 | */ 34 | simTime = 0; 35 | /** 36 | * 2D array of tiles that make up the city 37 | * @type {Tile[][]} 38 | */ 39 | tiles = []; 40 | /** 41 | * 42 | * @param {VehicleGraph} size 43 | */ 44 | vehicleGraph; 45 | 46 | constructor(size, name = 'My City') { 47 | super(); 48 | 49 | this.name = name; 50 | this.size = size; 51 | 52 | this.add(this.debugMeshes); 53 | this.add(this.root); 54 | 55 | this.tiles = []; 56 | for (let x = 0; x < this.size; x++) { 57 | const column = []; 58 | for (let y = 0; y < this.size; y++) { 59 | const tile = new Tile(x, y); 60 | tile.refreshView(this); 61 | this.root.add(tile); 62 | column.push(tile); 63 | } 64 | this.tiles.push(column); 65 | } 66 | 67 | this.services = []; 68 | this.services.push(new PowerService()); 69 | 70 | this.vehicleGraph = new VehicleGraph(this.size); 71 | this.debugMeshes.add(this.vehicleGraph); 72 | } 73 | 74 | /** 75 | * The total population of the city 76 | * @type {number} 77 | */ 78 | get population() { 79 | let population = 0; 80 | for (let x = 0; x < this.size; x++) { 81 | for (let y = 0; y < this.size; y++) { 82 | const tile = this.getTile(x, y); 83 | population += tile.building?.residents?.count ?? 0; 84 | } 85 | } 86 | return population; 87 | } 88 | 89 | /** Returns the title at the coordinates. If the coordinates 90 | * are out of bounds, then `null` is returned. 91 | * @param {number} x The x-coordinate of the tile 92 | * @param {number} y The y-coordinate of the tile 93 | * @returns {Tile | null} 94 | */ 95 | getTile(x, y) { 96 | if (x === undefined || y === undefined || 97 | x < 0 || y < 0 || 98 | x >= this.size || y >= this.size) { 99 | return null; 100 | } else { 101 | return this.tiles[x][y]; 102 | } 103 | } 104 | 105 | /** 106 | * Step the simulation forward by one step 107 | * @type {number} steps Number of steps to simulate forward in time 108 | */ 109 | simulate(steps = 1) { 110 | let count = 0; 111 | while (count++ < steps) { 112 | // Update services 113 | this.services.forEach((service) => service.simulate(this)); 114 | 115 | // Update each building 116 | for (let x = 0; x < this.size; x++) { 117 | for (let y = 0; y < this.size; y++) { 118 | this.getTile(x, y).simulate(this); 119 | } 120 | } 121 | } 122 | this.simTime++; 123 | } 124 | 125 | /** 126 | * Places a building at the specified coordinates if the 127 | * tile does not already have a building on it 128 | * @param {number} x 129 | * @param {number} y 130 | * @param {string} buildingType 131 | */ 132 | placeBuilding(x, y, buildingType) { 133 | const tile = this.getTile(x, y); 134 | 135 | // If the tile doesnt' already have a building, place one there 136 | if (tile && !tile.building) { 137 | tile.setBuilding(createBuilding(x, y, buildingType)); 138 | tile.refreshView(this); 139 | 140 | // Update buildings on adjacent tile in case they need to 141 | // change their mesh (e.g. roads) 142 | this.getTile(x - 1, y)?.refreshView(this); 143 | this.getTile(x + 1, y)?.refreshView(this); 144 | this.getTile(x, y - 1)?.refreshView(this); 145 | this.getTile(x, y + 1)?.refreshView(this); 146 | 147 | if (tile.building.type === BuildingType.road) { 148 | this.vehicleGraph.updateTile(x, y, tile.building); 149 | } 150 | } 151 | } 152 | 153 | /** 154 | * Bulldozes the building at the specified coordinates 155 | * @param {number} x 156 | * @param {number} y 157 | */ 158 | bulldoze(x, y) { 159 | const tile = this.getTile(x, y); 160 | 161 | if (tile.building) { 162 | if (tile.building.type === BuildingType.road) { 163 | this.vehicleGraph.updateTile(x, y, null); 164 | } 165 | 166 | tile.building.dispose(); 167 | tile.setBuilding(null); 168 | tile.refreshView(this); 169 | 170 | // Update neighboring tiles in case they need to change their mesh (e.g. roads) 171 | this.getTile(x - 1, y)?.refreshView(this); 172 | this.getTile(x + 1, y)?.refreshView(this); 173 | this.getTile(x, y - 1)?.refreshView(this); 174 | this.getTile(x, y + 1)?.refreshView(this); 175 | } 176 | } 177 | 178 | draw() { 179 | this.vehicleGraph.updateVehicles(); 180 | } 181 | 182 | /** 183 | * Finds the first tile where the criteria are true 184 | * @param {{x: number, y: number}} start The starting coordinates of the search 185 | * @param {(Tile) => (boolean)} filter This function is called on each 186 | * tile in the search field until `filter` returns true, or there are 187 | * no more tiles left to search. 188 | * @param {number} maxDistance The maximum distance to search from the starting tile 189 | * @returns {Tile | null} The first tile matching `criteria`, otherwiser `null` 190 | */ 191 | findTile(start, filter, maxDistance) { 192 | const startTile = this.getTile(start.x, start.y); 193 | const visited = new Set(); 194 | const tilesToSearch = []; 195 | 196 | // Initialze our search with the starting tile 197 | tilesToSearch.push(startTile); 198 | 199 | while (tilesToSearch.length > 0) { 200 | const tile = tilesToSearch.shift(); 201 | 202 | // Has this tile been visited? If so, ignore it and move on 203 | if (visited.has(tile.id)) { 204 | continue; 205 | } else { 206 | visited.add(tile.id); 207 | } 208 | 209 | // Check if tile is outside the search bounds 210 | const distance = startTile.distanceTo(tile); 211 | if (distance > maxDistance) continue; 212 | 213 | // Add this tiles neighbor's to the search list 214 | tilesToSearch.push(...this.getTileNeighbors(tile.x, tile.y)); 215 | 216 | // If this tile passes the criteria 217 | if (filter(tile)) { 218 | return tile; 219 | } 220 | } 221 | 222 | return null; 223 | } 224 | 225 | /** 226 | * Finds and returns the neighbors of this tile 227 | * @param {number} x The x-coordinate of the tile 228 | * @param {number} y The y-coordinate of the tile 229 | */ 230 | getTileNeighbors(x, y) { 231 | const neighbors = []; 232 | 233 | if (x > 0) { 234 | neighbors.push(this.getTile(x - 1, y)); 235 | } 236 | if (x < this.size - 1) { 237 | neighbors.push(this.getTile(x + 1, y)); 238 | } 239 | if (y > 0) { 240 | neighbors.push(this.getTile(x, y - 1)); 241 | } 242 | if (y < this.size - 1) { 243 | neighbors.push(this.getTile(x, y + 1)); 244 | } 245 | 246 | return neighbors; 247 | } 248 | } -------------------------------------------------------------------------------- /src/scripts/sim/services/power.js: -------------------------------------------------------------------------------- 1 | import { BuildingType } from '../buildings/buildingType.js'; 2 | import { City } from '../city.js'; 3 | 4 | export class PowerService { 5 | /** 6 | * @param {City} city 7 | */ 8 | simulate(city) { 9 | // Find all power plants in the city 10 | const powerPlantList = []; 11 | for (let x = 0; x < city.size; x++) { 12 | for (let y = 0; y < city.size; y++) { 13 | const tile = city.getTile(x, y); 14 | const building = city.getTile(x, y).building; 15 | if (building) { 16 | if (building.type === BuildingType.powerPlant) { 17 | const powerPlant = building; 18 | // Reset power consumption for each power plant 19 | powerPlant.powerConsumed = 0; 20 | // Create an object with the power plant, the search frontier, and a visited array 21 | powerPlantList.push({ 22 | powerPlant, 23 | frontier: [tile], 24 | visited: [] 25 | }); 26 | } else { 27 | // Reset supplier power for each building 28 | building.power.supplied = 0; 29 | } 30 | } 31 | } 32 | } 33 | 34 | // If there are no power plants, return early 35 | if (powerPlantList.length === 0) { 36 | return; 37 | } 38 | 39 | // Power is allocated by performing a BFS starting at each power plants and 40 | // distributing power to buildings that require power. The search frontier 41 | // for each power plant is expanded simultaneously so the load is shared 42 | // as evenly as possible between poewr plants. Search is terminated when 43 | // all buildings have been visited or the power plant has no power remaining. 44 | 45 | let searching = true; 46 | while (searching) { 47 | searching = false; 48 | 49 | // Iterate over each power plant 50 | for (const item of powerPlantList) { 51 | const { powerPlant, frontier, visited } = item; 52 | 53 | // If power plant has no power left to give, ignore it 54 | if (powerPlant.powerAvailable === 0) continue; 55 | 56 | // If power plant has power available, find the next building on the search frontier 57 | if (frontier.length > 0) { 58 | searching = true; 59 | 60 | // Get the next tile 61 | const tile = frontier.shift(); 62 | const building = tile.building; 63 | visited.push(tile); 64 | 65 | // Does this building need power? 66 | if (building.power.supplied < building.power.required) { 67 | const powerSupplied = Math.min(powerPlant.powerAvailable, building.power.required); 68 | powerPlant.powerConsumed += powerSupplied; 69 | building.power.supplied = powerSupplied; 70 | } 71 | 72 | // Add adjacent buildings to the search frontier 73 | const { x, y } = tile; 74 | 75 | // Add neighboring tiles to search if 76 | // 1) They haven't already been visited 77 | // 2) The tile has a building (power can pass through non-powered buildings) 78 | const shouldVisit = (tile) => tile && !visited.includes(tile) && tile.building; 79 | 80 | let left = city.getTile(x - 1, y); 81 | let right = city.getTile(x + 1, y); 82 | let top = city.getTile(x, y - 1); 83 | let bottom = city.getTile(x, y + 1); 84 | 85 | if (shouldVisit(left)) { 86 | frontier.push(left); 87 | } 88 | if (shouldVisit(right)) { 89 | frontier.push(right); 90 | } 91 | if (shouldVisit(top)) { 92 | frontier.push(top); 93 | } 94 | if (shouldVisit(bottom)) { 95 | frontier.push(bottom); 96 | } 97 | } 98 | } 99 | } 100 | } 101 | 102 | /** 103 | * Cleans up this module, disposing of any assets and unlinking any references 104 | */ 105 | dispose() { 106 | // Implement in subclass 107 | } 108 | 109 | /** 110 | * Returns an HTML representation of this object 111 | * @returns {string} 112 | */ 113 | toHTML() { 114 | // Implement in subclass 115 | } 116 | } -------------------------------------------------------------------------------- /src/scripts/sim/services/simService.js: -------------------------------------------------------------------------------- 1 | import { City } from '../city.js'; 2 | 3 | /** 4 | * A service represents simulation logic that is applied at the city level. 5 | */ 6 | export class SimService { 7 | /** 8 | * @param {City} city 9 | */ 10 | simulate(city) { 11 | // Implement in subclass 12 | } 13 | } -------------------------------------------------------------------------------- /src/scripts/sim/simObject.js: -------------------------------------------------------------------------------- 1 | import * as THREE from 'three'; 2 | import { SimModule } from './buildings/modules/simModule'; 3 | 4 | const SELECTED_COLOR = 0xaaaa55; 5 | const HIGHLIGHTED_COLOR = 0x555555; 6 | 7 | export class SimObject extends THREE.Object3D { 8 | /** 9 | * @type {THREE.Mesh?} 10 | */ 11 | #mesh = null; 12 | /** 13 | * World position of the object 14 | * @type {THREE.Vector3} 15 | */ 16 | #worldPos = new THREE.Vector3(); 17 | 18 | /** 19 | * @param {number} x The x-coordinate of the object 20 | * @param {number} y The y-coordinate of the object 21 | */ 22 | constructor(x = 0, y = 0) { 23 | super(); 24 | this.name = 'SimObject'; 25 | this.position.x = x; 26 | this.position.z = y; 27 | } 28 | 29 | get x() { 30 | this.getWorldPosition(this.#worldPos); 31 | return Math.floor(this.#worldPos.x); 32 | } 33 | 34 | get y() { 35 | this.getWorldPosition(this.#worldPos); 36 | return Math.floor(this.#worldPos.z); 37 | } 38 | 39 | /** 40 | * @type {THREE.Mesh?} 41 | */ 42 | get mesh() { 43 | return this.#mesh; 44 | } 45 | 46 | /** 47 | * @type {THREE.Mesh} value 48 | */ 49 | setMesh(value) { 50 | // Remove resources for existing mesh 51 | if (this.#mesh) { 52 | this.dispose(); 53 | this.remove(this.#mesh); 54 | } 55 | 56 | this.#mesh = value; 57 | 58 | // Add to scene graph 59 | if (this.#mesh) { 60 | this.add(this.#mesh); 61 | } 62 | } 63 | 64 | /** 65 | * Updates the state of this object by one simulation step 66 | * @param {City} city 67 | */ 68 | simulate(city) { 69 | // Override in subclass 70 | } 71 | 72 | setSelected(value) { 73 | if (value) { 74 | this.#setMeshEmission(SELECTED_COLOR); 75 | } else { 76 | this.#setMeshEmission(0); 77 | } 78 | } 79 | 80 | setFocused(value) { 81 | if (value) { 82 | this.#setMeshEmission(HIGHLIGHTED_COLOR); 83 | } else { 84 | this.#setMeshEmission(0); 85 | } 86 | } 87 | 88 | /** 89 | * Sets the emission color of the mesh 90 | * @param {number} color 91 | */ 92 | #setMeshEmission(color) { 93 | if (!this.mesh) return; 94 | this.mesh.traverse((obj) => obj.material?.emissive?.setHex(color)); 95 | } 96 | 97 | /** 98 | * Handles any clean up needed before an object is removed 99 | */ 100 | dispose() { 101 | this.#mesh.traverse((obj) => { 102 | if (obj.material) { 103 | obj.material?.dispose(); 104 | } 105 | }) 106 | } 107 | } -------------------------------------------------------------------------------- /src/scripts/sim/tile.js: -------------------------------------------------------------------------------- 1 | import * as THREE from 'three'; 2 | import { Building } from './buildings/building.js'; 3 | import { SimObject } from './simObject.js'; 4 | 5 | export class Tile extends SimObject { 6 | /** 7 | * The type of terrain 8 | * @type {string} 9 | */ 10 | terrain = 'grass'; 11 | /** 12 | * The building on this tile 13 | * @type {Building?} 14 | */ 15 | #building = null; 16 | 17 | constructor(x, y) { 18 | super(x, y); 19 | this.name = `Tile-${this.x}-${this.y}`; 20 | } 21 | 22 | /** 23 | * @type {Building} 24 | */ 25 | get building() { 26 | return this.#building; 27 | } 28 | 29 | /** 30 | * @type {Building} value 31 | */ 32 | setBuilding(value) { 33 | // Remove and dispose resources for existing building 34 | if (this.#building) { 35 | this.#building.dispose(); 36 | this.remove(this.#building); 37 | } 38 | 39 | this.#building = value; 40 | 41 | // Add to scene graph 42 | if (value) { 43 | this.add(this.#building); 44 | } 45 | } 46 | 47 | refreshView(city) { 48 | this.building?.refreshView(city); 49 | if (this.building?.hideTerrain) { 50 | this.setMesh(null); 51 | } else { 52 | /** 53 | * @type {THREE.Mesh} 54 | */ 55 | const mesh = window.assetManager.getModel(this.terrain, this); 56 | mesh.name = this.terrain; 57 | this.setMesh(mesh); 58 | } 59 | } 60 | 61 | simulate(city) { 62 | this.building?.simulate(city); 63 | } 64 | 65 | /** 66 | * Gets the Manhattan distance between two tiles 67 | * @param {Tile} tile 68 | * @returns 69 | */ 70 | distanceTo(tile) { 71 | return Math.abs(this.x - tile.x) + Math.abs(this.y - tile.y); 72 | } 73 | 74 | /** 75 | * 76 | * @returns {string} HTML representation of this object 77 | */ 78 | toHTML() { 79 | let html = ` 80 |
    Tile
    81 | Coordinates 82 | X: ${this.x}, Y: ${this.y} 83 |
    84 | Terrain 85 | ${this.terrain} 86 |
    87 | `; 88 | 89 | if (this.building) { 90 | html += this.building.toHTML(); 91 | } 92 | 93 | return html; 94 | } 95 | }; -------------------------------------------------------------------------------- /src/scripts/sim/vehicles/vehicle.js: -------------------------------------------------------------------------------- 1 | import * as THREE from 'three'; 2 | import { VehicleGraphNode } from './vehicleGraphNode.js'; 3 | import config from '../../config.js'; 4 | import { SimObject } from '../simObject.js'; 5 | import models from '../../assets/models.js'; 6 | 7 | const FORWARD = new THREE.Vector3(1, 0, 0); 8 | 9 | export class Vehicle extends SimObject { 10 | constructor(origin, destination) { 11 | super(); 12 | 13 | this.createdTime = Date.now(); 14 | this.cycleStartTime = this.createdTime; 15 | 16 | /** 17 | * @type {VehicleGraphNode} 18 | */ 19 | this.origin = origin; 20 | 21 | /** 22 | * @type {VehicleGraphNode} 23 | */ 24 | this.destination = destination; 25 | 26 | this.originWorldPosition = new THREE.Vector3(); 27 | this.destinationWorldPosition = new THREE.Vector3(); 28 | this.originToDestination = new THREE.Vector3(); 29 | this.orientation = new THREE.Vector3(); 30 | 31 | this.updateWorldPositions(); 32 | 33 | const types = Object.entries(models) 34 | .filter(x => x[1].type === 'vehicle') 35 | .map(x => x[0]); 36 | 37 | const i = Math.floor(types.length * Math.random()); 38 | 39 | this.setMesh(window.assetManager.getModel(types[i], this, true)); 40 | } 41 | 42 | /** 43 | * @returns {number} Returns cycle time between 0 and 1 44 | */ 45 | get cycleTime() { 46 | const distance = this.originToDestination.length(); 47 | const cycleDuration = distance / config.vehicle.speed; 48 | const value = (Date.now() - this.cycleStartTime) / cycleDuration; 49 | 50 | return Math.max(0, Math.min(value, 1)); 51 | } 52 | 53 | /** 54 | * @returns {number} Age of the vehicle in milliseconds 55 | */ 56 | get age() { 57 | return Date.now() - this.createdTime; 58 | } 59 | 60 | /** 61 | * Updates the vehicle position each render frame 62 | */ 63 | simulate() { 64 | if (!this.origin || !this.destination) { 65 | this.dispose(); 66 | return; 67 | } 68 | 69 | // If a road tile was removed, the vehicles will still maintain reference 70 | // to the nodes. Automatically remove vehicles when the destination node 71 | // is no longer part of the scene 72 | if (!this.destination.parent) { 73 | this.dispose(); 74 | return; 75 | } 76 | 77 | if (this.age > config.vehicle.maxLifetime) { 78 | this.dispose(); 79 | return; 80 | } 81 | 82 | if (this.cycleTime === 1) { 83 | this.pickNewDestination(); 84 | } else { 85 | this.position.copy(this.originWorldPosition); 86 | this.position.lerp(this.destinationWorldPosition, this.cycleTime); 87 | } 88 | 89 | this.updateOpacity(); 90 | } 91 | 92 | updateOpacity() { 93 | const setOpacity = (opacity) => { 94 | this.traverse(obj => { 95 | if (obj.material) { 96 | obj.material.opacity = Math.max(0, Math.min(opacity, 1)) 97 | } 98 | }); 99 | } 100 | 101 | if (this.age < config.vehicle.fadeTime) { 102 | setOpacity(this.age / config.vehicle.fadeTime); 103 | } else if ((config.vehicle.maxLifetime - this.age) < config.vehicle.fadeTime) { 104 | setOpacity((config.vehicle.maxLifetime - this.age) / config.vehicle.fadeTime); 105 | } else { 106 | setOpacity(1); 107 | } 108 | } 109 | 110 | pickNewDestination() { 111 | this.origin = this.destination; 112 | this.destination = this.origin?.getRandomNextNode(); 113 | this.updateWorldPositions(); 114 | this.cycleStartTime = Date.now(); 115 | } 116 | 117 | /** 118 | * Updates the world positions each cycle start 119 | */ 120 | updateWorldPositions() { 121 | if (!this.origin || !this.destination) { 122 | return; 123 | } 124 | 125 | this.origin.getWorldPosition(this.originWorldPosition); 126 | this.destination.getWorldPosition(this.destinationWorldPosition); 127 | 128 | this.originToDestination.copy(this.destinationWorldPosition); 129 | this.originToDestination.sub(this.originWorldPosition); 130 | 131 | this.orientation.copy(this.originToDestination); 132 | this.orientation.normalize(); 133 | 134 | this.quaternion.setFromUnitVectors(FORWARD, this.orientation); 135 | } 136 | 137 | dispose() { 138 | this.traverse((obj) => obj.material?.dispose()); 139 | this.removeFromParent(); 140 | } 141 | 142 | toHTML() { 143 | return 'Car'; 144 | } 145 | } -------------------------------------------------------------------------------- /src/scripts/sim/vehicles/vehicleGraph.js: -------------------------------------------------------------------------------- 1 | import * as THREE from 'three'; 2 | import { VehicleGraphTile } from './vehicleGraphTile.js'; 3 | import { VehicleGraphHelper } from './vehicleGraphHelper.js'; 4 | import config from '../../config.js'; 5 | import { Vehicle } from './vehicle.js'; 6 | import { Road } from '../buildings/transportation/road.js'; 7 | 8 | export class VehicleGraph extends THREE.Group { 9 | constructor(size) { 10 | super(); 11 | 12 | this.size = size; 13 | 14 | /** 15 | * @type {VehicleGraphTile[][]} 16 | */ 17 | this.tiles = []; 18 | 19 | this.vehicles = new THREE.Group(); 20 | this.add(this.vehicles); 21 | 22 | /** 23 | * @type {VehicleGraphHelper} 24 | */ 25 | this.helper = new VehicleGraphHelper(); 26 | this.add(this.helper); 27 | 28 | // Initialize the vehicle graph tiles array 29 | for (let x = 0; x < this.size; x++) { 30 | const column = []; 31 | for (let y = 0; y < this.size; y++) { 32 | column.push(null); 33 | } 34 | this.tiles.push(column); 35 | } 36 | 37 | this.helper.refreshView(this); 38 | 39 | setInterval(this.spawnVehicle.bind(this), config.vehicle.spawnInterval); 40 | } 41 | 42 | updateVehicles() { 43 | for (const vehicle of this.vehicles.children) { 44 | vehicle.simulate(); 45 | } 46 | } 47 | 48 | /** 49 | * 50 | * @param {number} x 51 | * @param {number} y 52 | * @param {Road | null} road 53 | */ 54 | updateTile(x, y, road) { 55 | const existingTile = this.getTile(x, y); 56 | const leftTile = this.getTile(x - 1, y); 57 | const rightTile = this.getTile(x + 1, y); 58 | const topTile = this.getTile(x, y - 1); 59 | const bottomTile = this.getTile(x, y + 1); 60 | 61 | // Disconnect the existing tile and all adjacent tiles from each other 62 | existingTile?.disconnectAll(); 63 | leftTile?.getWorldRightSide()?.out?.disconnectAll(); 64 | rightTile?.getWorldLeftSide()?.out?.disconnectAll(); 65 | topTile?.getWorldBottomSide()?.out?.disconnectAll(); 66 | bottomTile?.getWorldTopSide()?.out?.disconnectAll(); 67 | 68 | if (road) { 69 | const tile = VehicleGraphTile.create(x, y, road.rotation.y, road.style); 70 | 71 | // Connect tile to adjacent tiles 72 | if (leftTile) { 73 | tile.getWorldLeftSide().out?.connect(leftTile.getWorldRightSide().in); 74 | leftTile.getWorldRightSide().out?.connect(tile.getWorldLeftSide().in); 75 | } 76 | if (rightTile) { 77 | tile.getWorldRightSide().out?.connect(rightTile.getWorldLeftSide().in); 78 | rightTile.getWorldLeftSide().out?.connect(tile.getWorldRightSide().in); 79 | } 80 | if (topTile) { 81 | tile.getWorldTopSide().out?.connect(topTile.getWorldBottomSide().in); 82 | topTile.getWorldBottomSide().out?.connect(tile.getWorldTopSide().in); 83 | } 84 | if (bottomTile) { 85 | tile.getWorldBottomSide().out?.connect(bottomTile.getWorldTopSide().in); 86 | bottomTile.getWorldTopSide().out?.connect(tile.getWorldBottomSide().in); 87 | } 88 | 89 | this.tiles[x][y] = tile; 90 | this.add(tile); 91 | } else { 92 | this.tiles[x][y] = null; 93 | } 94 | 95 | // Update the vehicle graph visualization 96 | this.helper.refreshView(this); 97 | } 98 | 99 | /** 100 | * @param {number} x 101 | * @param {number} y 102 | * @returns {VehicleGraphTile} 103 | */ 104 | getTile(x, y) { 105 | if (x >= 0 && x < this.size && y >= 0 && y < this.size) { 106 | return this.tiles[x][y]; 107 | } else { 108 | return null; 109 | } 110 | } 111 | 112 | spawnVehicle() { 113 | const startingTile = this.getStartingTile(); 114 | 115 | if (startingTile != null) { 116 | const origin = startingTile.getRandomNode(); 117 | const destination = origin?.getRandomNextNode(); 118 | 119 | if (origin && destination) { 120 | const vehicle = new Vehicle(origin, destination); 121 | this.vehicles.add(vehicle); 122 | } 123 | } 124 | } 125 | 126 | /** 127 | * Gets a random tile for a vehicle to spawn at 128 | * @returns {VehicleGraphTile | null} 129 | */ 130 | getStartingTile() { 131 | const tiles = []; 132 | for (let x = 0; x < this.size; x++) { 133 | for (let y = 0; y < this.size; y++) { 134 | let tile = this.getTile(x, y); 135 | if (tile) tiles.push(tile); 136 | } 137 | } 138 | 139 | if (tiles.length === 0) { 140 | return null; 141 | } else { 142 | const i = Math.floor(tiles.length * Math.random()); 143 | return tiles[i]; 144 | } 145 | } 146 | } -------------------------------------------------------------------------------- /src/scripts/sim/vehicles/vehicleGraphHelper.js: -------------------------------------------------------------------------------- 1 | import * as THREE from 'three'; 2 | import { VehicleGraph } from './vehicleGraph.js'; 3 | import { VehicleGraphNode } from './vehicleGraphNode.js'; 4 | 5 | const UP = new THREE.Vector3(0, 1, 0); 6 | 7 | const NODE_GEOMETRY = new THREE.SphereGeometry(0.03, 6, 6); 8 | const EDGE_GEOMETRY = new THREE.ConeGeometry(0.02, 1, 6); 9 | 10 | const EDGE_MATERIAL = new THREE.MeshBasicMaterial({ color: 0x5050ff }); 11 | const CONNECTED_MATERIAL = new THREE.MeshBasicMaterial({ color: 0x00ff00 }); 12 | const DISCONNECTED_MATERIAL = new THREE.MeshBasicMaterial({ color: 0xff0000 }); 13 | 14 | export class VehicleGraphHelper extends THREE.Group { 15 | constructor() { 16 | super(); 17 | this.visible = false; 18 | } 19 | 20 | /** 21 | * 22 | * @param {VehicleGraph} graph 23 | */ 24 | refreshView(graph) { 25 | this.clear(); 26 | 27 | for (let x = 0; x < graph.size; x++) { 28 | for (let y = 0; y < graph.size; y++) { 29 | const tile = graph.getTile(x, y); 30 | 31 | if (!tile) continue; 32 | 33 | for (const node of tile.children) { 34 | this.createNodeVisualization(node); 35 | } 36 | } 37 | } 38 | } 39 | 40 | /** 41 | * 42 | * @param {VehicleGraphNode} node 43 | */ 44 | createNodeVisualization(node) { 45 | const nodeMesh = new THREE.Mesh( 46 | NODE_GEOMETRY, 47 | node.next.length > 0 ? CONNECTED_MATERIAL : DISCONNECTED_MATERIAL 48 | ); 49 | 50 | const nodeWorldPosition = new THREE.Vector3(); 51 | node.getWorldPosition(nodeWorldPosition); 52 | 53 | nodeMesh.position.set( 54 | nodeWorldPosition.x, 55 | nodeWorldPosition.y, 56 | nodeWorldPosition.z 57 | ); 58 | 59 | // Add edge visualizations for the connected nodes 60 | if(node.next.length > 0) { 61 | for (const next of node.next) { 62 | // Get world position of the next node 63 | const nextWorldPosition = new THREE.Vector3(); 64 | next.getWorldPosition(nextWorldPosition); 65 | 66 | const edgeVector = new THREE.Vector3(); 67 | edgeVector.copy(nextWorldPosition); 68 | edgeVector.sub(nodeWorldPosition); 69 | 70 | const distance = edgeVector.length(); 71 | 72 | const edgeMesh = new THREE.Mesh( 73 | EDGE_GEOMETRY, 74 | EDGE_MATERIAL 75 | ); 76 | 77 | edgeMesh.scale.set(1, distance, 1); 78 | 79 | edgeMesh.quaternion.setFromUnitVectors( 80 | UP, 81 | edgeVector.clone().normalize() 82 | ); 83 | 84 | const offset = new THREE.Vector3(0, distance / 2, 0) 85 | .applyQuaternion(edgeMesh.quaternion.clone()); 86 | 87 | edgeMesh.position.set( 88 | nodeWorldPosition.x + offset.x, 89 | nodeWorldPosition.y + offset.y, 90 | nodeWorldPosition.z + offset.z 91 | ); 92 | 93 | this.add(edgeMesh); 94 | } 95 | } 96 | 97 | this.add(nodeMesh); 98 | } 99 | } -------------------------------------------------------------------------------- /src/scripts/sim/vehicles/vehicleGraphNode.js: -------------------------------------------------------------------------------- 1 | import * as THREE from 'three'; 2 | 3 | export class VehicleGraphNode extends THREE.Object3D { 4 | constructor(x, y) { 5 | super(); 6 | 7 | this.position.set(x, 0, y); 8 | 9 | /** 10 | * @type {VehicleGraphNode[]} 11 | */ 12 | this.next = []; 13 | } 14 | 15 | connect(node) { 16 | if (!node) return; 17 | 18 | if (!this.next.includes(node)) { 19 | this.next.push(node); 20 | } 21 | } 22 | 23 | disconnectAll() { 24 | this.next = []; 25 | } 26 | 27 | /** 28 | * @returns {VehicleGraphNode | null} 29 | */ 30 | getRandomNextNode() { 31 | if (this.next.length === 0) { 32 | return null; 33 | } else { 34 | const i = Math.floor(this.next.length * Math.random()); 35 | return this.next[i]; 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /src/scripts/sim/vehicles/vehicleGraphTile.js: -------------------------------------------------------------------------------- 1 | import * as THREE from 'three'; 2 | import { VehicleGraphNode } from './vehicleGraphNode.js'; 3 | import { RAD2DEG } from 'three/src/math/MathUtils.js'; 4 | 5 | const roadOffset = 0.05; 6 | const tileOffset = 0.25; 7 | 8 | export class VehicleGraphTile extends THREE.Group { 9 | constructor(x, y, rotation) { 10 | super(); 11 | 12 | this.position.set(x, 0, y); 13 | this.rotation.set(0, rotation, 0); 14 | 15 | this.roadRotation = Math.round(rotation * RAD2DEG); 16 | 17 | /** 18 | * @type {{ in: VehicleGraphNode, out: VehicleGraphNode }} 19 | */ 20 | this.left = { in: null, out: null }; 21 | 22 | /** 23 | * @type {{ in: VehicleGraphNode, out: VehicleGraphNode }} 24 | */ 25 | this.right = { in: null, out: null }; 26 | 27 | /** 28 | * @type {{ in: VehicleGraphNode, out: VehicleGraphNode }} 29 | */ 30 | this.top = { in: null, out: null }; 31 | 32 | /** 33 | * @type {{ in: VehicleGraphNode, out: VehicleGraphNode }} 34 | */ 35 | this.bottom = { in: null, out: null }; 36 | } 37 | 38 | /** 39 | * Factory function for creating a road tile of a specified type 40 | * @param {number} x 41 | * @param {number} y 42 | * @param {number} rotation 43 | * @param {string} style 44 | * @returns {VehicleGraphTile | null} 45 | */ 46 | static create(x, y, rotation, style) { 47 | switch (style) { 48 | case 'end': 49 | return new EndRoadTile(x, y, rotation); 50 | case 'straight': 51 | return new StraightRoadTile(x, y, rotation); 52 | case 'corner': 53 | return new CornerRoadTile(x, y, rotation); 54 | case 'three-way': 55 | return new ThreeWayRoadTile(x, y, rotation); 56 | case 'four-way': 57 | return new FourWayRoadTile(x, y, rotation); 58 | default: 59 | console.error(`Road type ${style} is not a known value`); 60 | } 61 | } 62 | 63 | /** 64 | * Disconnect the tile from the vehicle graph 65 | */ 66 | disconnectAll() { 67 | for (let node of this.children) { 68 | node.disconnectAll(); 69 | node.removeFromParent(); 70 | } 71 | } 72 | 73 | /** 74 | * Get a random starting node for a vehicle 75 | * @returns {VehicleGraphNode} 76 | */ 77 | getRandomNode() { 78 | const nodes = []; 79 | 80 | if (this.left.in) nodes.push(this.left.in); 81 | if (this.right.in) nodes.push(this.right.in); 82 | if (this.top.in) nodes.push(this.top.in); 83 | if (this.bottom.in) nodes.push(this.bottom.in); 84 | 85 | if (nodes.length > 0) { 86 | const i = Math.floor(nodes.length * Math.random()); 87 | return nodes[i]; 88 | } else { 89 | return null; 90 | } 91 | } 92 | 93 | getWorldLeftSide() { 94 | switch (this.roadRotation) { 95 | case 0: return this.left; 96 | case 90: return this.top; 97 | case 180: return this.right; 98 | case 270: return this.bottom; 99 | default: return this.left; 100 | } 101 | } 102 | 103 | getWorldRightSide() { 104 | switch (this.roadRotation) { 105 | case 0: return this.right; 106 | case 90: return this.bottom; 107 | case 180: return this.left; 108 | case 270: return this.top; 109 | default: return this.right; 110 | } 111 | } 112 | 113 | getWorldTopSide() { 114 | switch (this.roadRotation) { 115 | case 0: return this.top; 116 | case 90: return this.right; 117 | case 180: return this.bottom; 118 | case 270: return this.left; 119 | default: return this.top; 120 | } 121 | } 122 | 123 | getWorldBottomSide() { 124 | switch (this.roadRotation) { 125 | case 0: return this.bottom; 126 | case 90: return this.left; 127 | case 180: return this.top; 128 | case 270: return this.right; 129 | default: return this.bottom; 130 | } 131 | } 132 | } 133 | 134 | export class EndRoadTile extends VehicleGraphTile { 135 | constructor(x, y, rotation) { 136 | super(x, y, rotation); 137 | 138 | this.name = `EndRoadTile (${this.position})` 139 | 140 | this.bottom = { 141 | in: new VehicleGraphNode(roadOffset, tileOffset), 142 | out: new VehicleGraphNode(-roadOffset, tileOffset) 143 | }; 144 | 145 | const midpoint = { 146 | in: new VehicleGraphNode(roadOffset, 0), 147 | out: new VehicleGraphNode(-roadOffset, 0) 148 | }; 149 | 150 | this.add(this.bottom.in); 151 | this.add(this.bottom.out); 152 | this.add(midpoint.in); 153 | this.add(midpoint.out); 154 | 155 | // Connect together 156 | // Path #1: U-turn 157 | this.bottom.in.connect(midpoint.in); 158 | midpoint.in.connect(midpoint.out); 159 | midpoint.out.connect(this.bottom.out); 160 | } 161 | } 162 | 163 | export class StraightRoadTile extends VehicleGraphTile { 164 | constructor(x, y, rotation) { 165 | super(x, y, rotation); 166 | 167 | this.name = `StraightRoadTile (${this.position})` 168 | 169 | // Create nodes 170 | this.top = { 171 | in: new VehicleGraphNode(-roadOffset, -tileOffset), 172 | out: new VehicleGraphNode(roadOffset, -tileOffset) 173 | }; 174 | 175 | this.bottom = { 176 | in: new VehicleGraphNode(roadOffset, tileOffset), 177 | out: new VehicleGraphNode(-roadOffset, tileOffset) 178 | }; 179 | 180 | // Add to tile 181 | this.add(this.top.in); 182 | this.add(this.top.out); 183 | this.add(this.bottom.in); 184 | this.add(this.bottom.out); 185 | 186 | // Connect together 187 | // Path #1: Bottom -> Top 188 | this.bottom.in.connect(this.top.out); 189 | // Path #2: Top -> Bototm 190 | this.top.in.connect(this.bottom.out); 191 | } 192 | } 193 | 194 | 195 | export class CornerRoadTile extends VehicleGraphTile { 196 | constructor(x, y, rotation) { 197 | super(x, y, rotation); 198 | 199 | this.name = `CornerRoadTile (${this.position})` 200 | 201 | this.bottom = { 202 | in: new VehicleGraphNode(roadOffset, tileOffset + 0.1), 203 | out: new VehicleGraphNode(-roadOffset, tileOffset + 0.1) 204 | }; 205 | 206 | this.right = { 207 | in: new VehicleGraphNode(tileOffset + 0.1, -roadOffset), 208 | out: new VehicleGraphNode(tileOffset + 0.1, roadOffset) 209 | }; 210 | 211 | const midpointBottomRight = new VehicleGraphNode( 212 | tileOffset - 1.5 * roadOffset, 213 | tileOffset - 1.5 * roadOffset); 214 | 215 | const midpointTopLeft = new VehicleGraphNode( 216 | tileOffset - 3 * roadOffset, 217 | tileOffset - 3 * roadOffset); 218 | 219 | this.add(midpointBottomRight); 220 | this.add(midpointTopLeft); 221 | this.add(this.right.in); 222 | this.add(this.right.out); 223 | this.add(this.bottom.in); 224 | this.add(this.bottom.out); 225 | 226 | // Connect together 227 | // Path #1: Bottom -> Right 228 | this.bottom.in.connect(midpointBottomRight); 229 | midpointBottomRight.connect(this.right.out); 230 | // Path #2: Right -> Bottom 231 | this.right.in.connect(midpointTopLeft); 232 | midpointTopLeft.connect(this.bottom.out); 233 | } 234 | } 235 | 236 | 237 | export class ThreeWayRoadTile extends VehicleGraphTile { 238 | constructor(x, y, rotation) { 239 | super(x, y, rotation); 240 | 241 | this.name = `TeeRoadTile (${this.position})` 242 | 243 | // Create nodes 244 | this.left = { 245 | in: new VehicleGraphNode(-tileOffset, roadOffset), 246 | out: new VehicleGraphNode(-tileOffset, -roadOffset) 247 | }; 248 | 249 | this.right = { 250 | in: new VehicleGraphNode(tileOffset, -roadOffset), 251 | out: new VehicleGraphNode(tileOffset, roadOffset) 252 | }; 253 | 254 | this.bottom = { 255 | in: new VehicleGraphNode(roadOffset, tileOffset), 256 | out: new VehicleGraphNode(-roadOffset, tileOffset) 257 | }; 258 | 259 | const midpointBottomLeft = new VehicleGraphNode(-roadOffset, roadOffset); 260 | const midpointBottomRight = new VehicleGraphNode(roadOffset, roadOffset); 261 | const midpointTopLeft = new VehicleGraphNode(-roadOffset, -roadOffset); 262 | const midpointTopRight = new VehicleGraphNode(roadOffset, -roadOffset); 263 | 264 | // Add to tile 265 | this.add(this.left.in); 266 | this.add(this.left.out); 267 | this.add(this.right.in); 268 | this.add(this.right.out); 269 | this.add(this.bottom.in); 270 | this.add(this.bottom.out); 271 | this.add(midpointBottomLeft); 272 | this.add(midpointBottomRight); 273 | this.add(midpointTopLeft); 274 | this.add(midpointTopRight); 275 | 276 | // Connect midpoints 277 | midpointBottomLeft.connect(midpointBottomRight); 278 | midpointBottomRight.connect(midpointTopRight); 279 | midpointTopRight.connect(midpointTopLeft); 280 | midpointTopLeft.connect(midpointBottomLeft); 281 | 282 | // Connect inputs to midpoints 283 | this.left.in.connect(midpointBottomLeft); 284 | this.right.in.connect(midpointTopRight); 285 | this.bottom.in.connect(midpointBottomRight); 286 | 287 | // Connect midpoints to outputs 288 | midpointBottomLeft.connect(this.bottom.out); 289 | midpointBottomRight.connect(this.right.out); 290 | midpointTopLeft.connect(this.left.out); 291 | } 292 | } 293 | 294 | export class FourWayRoadTile extends VehicleGraphTile { 295 | constructor(x, y, rotation) { 296 | super(x, y, rotation); 297 | 298 | this.name = `IntersectionRoadTile (${this.position})` 299 | 300 | // Create nodes 301 | this.left = { 302 | in: new VehicleGraphNode(-tileOffset, roadOffset), 303 | out: new VehicleGraphNode(-tileOffset, -roadOffset) 304 | }; 305 | 306 | this.right = { 307 | in: new VehicleGraphNode(tileOffset, -roadOffset), 308 | out: new VehicleGraphNode(tileOffset, roadOffset) 309 | }; 310 | 311 | this.bottom = { 312 | in: new VehicleGraphNode(roadOffset, tileOffset), 313 | out: new VehicleGraphNode(-roadOffset, tileOffset) 314 | }; 315 | 316 | this.top = { 317 | in: new VehicleGraphNode(-roadOffset, -tileOffset), 318 | out: new VehicleGraphNode(roadOffset, -tileOffset) 319 | }; 320 | 321 | const midpointBottomLeft = new VehicleGraphNode(-roadOffset, roadOffset); 322 | const midpointBottomRight = new VehicleGraphNode(roadOffset, roadOffset); 323 | const midpointTopLeft = new VehicleGraphNode(-roadOffset, -roadOffset); 324 | const midpointTopRight = new VehicleGraphNode(roadOffset, -roadOffset); 325 | 326 | // Add to tile 327 | this.add(this.left.in); 328 | this.add(this.left.out); 329 | this.add(this.right.in); 330 | this.add(this.right.out); 331 | this.add(this.bottom.in); 332 | this.add(this.bottom.out); 333 | this.add(this.top.in); 334 | this.add(this.top.out); 335 | this.add(midpointBottomLeft); 336 | this.add(midpointBottomRight); 337 | this.add(midpointTopLeft); 338 | this.add(midpointTopRight); 339 | 340 | // Connect midpoints 341 | midpointBottomLeft.connect(midpointBottomRight); 342 | midpointBottomRight.connect(midpointTopRight); 343 | midpointTopRight.connect(midpointTopLeft); 344 | midpointTopLeft.connect(midpointBottomLeft); 345 | 346 | // Connect inputs to midpoints 347 | this.left.in.connect(midpointBottomLeft); 348 | this.right.in.connect(midpointTopRight); 349 | this.bottom.in.connect(midpointBottomRight); 350 | this.top.in.connect(midpointTopLeft); 351 | 352 | // Connect midpoints to outputs 353 | midpointBottomLeft.connect(this.bottom.out); 354 | midpointBottomRight.connect(this.right.out); 355 | midpointTopRight.connect(this.top.out); 356 | midpointTopLeft.connect(this.left.out); 357 | } 358 | } -------------------------------------------------------------------------------- /src/scripts/ui.js: -------------------------------------------------------------------------------- 1 | import { Game } from './game'; 2 | import { SimObject } from './sim/simObject'; 3 | import playIconUrl from '/icons/play-color.png'; 4 | import pauseIconUrl from '/icons/pause-color.png'; 5 | 6 | export class GameUI { 7 | /** 8 | * Currently selected tool 9 | * @type {string} 10 | */ 11 | activeToolId = 'select'; 12 | /** 13 | * @type {HTMLElement | null } 14 | */ 15 | selectedControl = document.getElementById('button-select'); 16 | /** 17 | * True if the game is currently paused 18 | * @type {boolean} 19 | */ 20 | isPaused = false; 21 | 22 | get gameWindow() { 23 | return document.getElementById('render-target'); 24 | } 25 | 26 | showLoadingText() { 27 | document.getElementById('loading').style.visibility = 'visible'; 28 | } 29 | 30 | hideLoadingText() { 31 | document.getElementById('loading').style.visibility = 'hidden'; 32 | } 33 | 34 | /** 35 | * 36 | * @param {*} event 37 | */ 38 | onToolSelected(event) { 39 | // Deselect previously selected button and selected this one 40 | if (this.selectedControl) { 41 | this.selectedControl.classList.remove('selected'); 42 | } 43 | this.selectedControl = event.target; 44 | this.selectedControl.classList.add('selected'); 45 | 46 | this.activeToolId = this.selectedControl.getAttribute('data-type'); 47 | } 48 | 49 | /** 50 | * Toggles the pause state of the game 51 | */ 52 | togglePause() { 53 | this.isPaused = !this.isPaused; 54 | if (this.isPaused) { 55 | document.getElementById('pause-button-icon').src = playIconUrl; 56 | document.getElementById('paused-text').style.visibility = 'visible'; 57 | } else { 58 | document.getElementById('pause-button-icon').src = pauseIconUrl; 59 | document.getElementById('paused-text').style.visibility = 'hidden'; 60 | } 61 | } 62 | 63 | /** 64 | * Updates the values in the title bar 65 | * @param {Game} game 66 | */ 67 | updateTitleBar(game) { 68 | document.getElementById('city-name').innerHTML = game.city.name; 69 | document.getElementById('population-counter').innerHTML = game.city.population; 70 | 71 | const date = new Date('1/1/2023'); 72 | date.setDate(date.getDate() + game.city.simTime); 73 | document.getElementById('sim-time').innerHTML = date.toLocaleDateString(); 74 | } 75 | 76 | /** 77 | * Updates the info panel with the information in the object 78 | * @param {SimObject} object 79 | */ 80 | updateInfoPanel(object) { 81 | const infoElement = document.getElementById('info-panel') 82 | if (object) { 83 | infoElement.style.visibility = 'visible'; 84 | infoElement.innerHTML = object.toHTML(); 85 | } else { 86 | infoElement.style.visibility = 'hidden'; 87 | infoElement.innerHTML = ''; 88 | } 89 | } 90 | } 91 | 92 | window.ui = new GameUI(); -------------------------------------------------------------------------------- /vite.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | // Set the base directory for GitHub pages 3 | base: '/simcity-threejs-clone/', 4 | 5 | // Set the project root directory (relative to the config file) 6 | root: './src', 7 | 8 | // Set the directory to serve static files from (relative to the root) 9 | publicDir: './public', 10 | 11 | // Set the build output directory 12 | build: { 13 | outDir: './dist' 14 | } 15 | } --------------------------------------------------------------------------------