├── .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 |
22 |
23 | PAUSED
24 |
25 |
26 |
27 |
28 |
29 | $1000
30 |
31 |
32 | My City
33 | -
34 | 1/1/2023
35 |
36 |
37 |

38 |
0
39 |
40 |
41 |
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 += '';
90 | for (const worker of this.workers) {
91 | html += worker.toHTML();
92 | }
93 | 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 += '';
90 | for (const resident of this.#residents) {
91 | html += resident.toHTML();
92 | }
93 | 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 | }
--------------------------------------------------------------------------------