├── .babelrc
├── .editorconfig
├── .github
└── main.workflow
├── .gitignore
├── LICENSE.md
├── README.md
├── assets
├── 3d
│ └── car.glb
├── boulder.png
├── boulder2.png
├── car-army.png
├── car-blue.png
├── car-green.png
├── car-red.png
├── car-yellow.png
├── clouds.png
├── clouds2.png
├── fonts
│ ├── cosmicavenger.png
│ ├── cosmicavenger.xml
│ ├── impact-24-outline.png
│ ├── impact-24-outline.xml
│ ├── number-font.png
│ └── number-font.xml
├── hills.png
├── mountain.png
├── smoke-particle.png
├── sound
│ ├── car-collision.wav
│ ├── confirm.wav
│ ├── drozerix_-_dream_candy.xm
│ ├── engine-loop.wav
│ ├── explosion.wav
│ ├── select.wav
│ ├── time-extended.wav
│ └── tire-squeal.wav
├── tree.png
├── tree2.png
├── tree3.png
└── turn-sign.png
├── ava.config.js
├── buildConfig.js
├── number-font.png
├── number-font.xml
├── package-lock.json
├── package.json
├── src
├── Components
│ ├── Car.ts
│ ├── CarManager.ts
│ ├── Colors.ts
│ ├── Player.ts
│ ├── Prop.ts
│ ├── RadarCar.ts
│ ├── Renderer.ts
│ ├── Road.ts
│ ├── SegmentPoint.ts
│ ├── SegmentType.ts
│ ├── SpeedGauge.ts
│ ├── TrackRadar.ts
│ ├── TrackSegment.ts
│ └── Util.ts
├── config
│ ├── GameConfig.ts
│ └── GameSettings.ts
├── css
│ └── styles.css
├── custom.d.ts
├── favicon.ico
├── game.ts
├── index.html
├── libs
│ ├── GLTFLoader.js
│ └── Phaser3D.js
├── phaser.d.ts
└── scenes
│ ├── BaseScene.ts
│ ├── BootScene.ts
│ ├── GameScene.ts
│ ├── LoadScene.ts
│ └── RaceUiScene.ts
├── tasks
└── createZip.js
├── tsconfig.json
├── tslint.json
└── webpack.config.js
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | "@babel/typescript",
4 | ["@babel/preset-env", { "modules": false }]
5 | ],
6 | "plugins": [
7 | "@babel/proposal-class-properties",
8 | "@babel/proposal-object-rest-spread",
9 | ],
10 | "env": {
11 | "production": {
12 | "plugins": [
13 | ["transform-remove-console", { "exclude": [ "error", "warn"] }],
14 | ]
15 | },
16 | "development": {}
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | charset = utf-8
5 | insert_final_newline = true
6 | indent_style = tab
7 | indent_size = 4
8 |
9 | [{package.json,*.yml}]
10 | indent_style = space
11 | indent_size = 2
--------------------------------------------------------------------------------
/.github/main.workflow:
--------------------------------------------------------------------------------
1 | workflow "Publish master to gh-pages" {
2 | on = "push"
3 | resolves = ["Publish to gh-pages branch"]
4 | }
5 |
6 | action "Filter branch" {
7 | uses = "actions/bin/filter@master"
8 | args = "branch master"
9 | }
10 |
11 | action "Publish to gh-pages branch" {
12 | uses = "pinkkis/page-publisher-gh-action@master"
13 | env = {
14 | TARGET_REPO = "pinkkis/phaser-driving"
15 | CNAME = "driver.poisonvial.com"
16 | }
17 | secrets = [
18 | "GITHUB_TOKEN",
19 | ]
20 | args = "gh-pages"
21 | needs = ["Filter branch"]
22 | }
23 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Apple garbage
2 | .DS_Store
3 |
4 | # Logs
5 | logs
6 | *.log
7 | npm-debug.log*
8 |
9 | # Runtime data
10 | pids
11 | *.pid
12 | *.seed
13 |
14 | # Directory for instrumented libs generated by jscoverage/JSCover
15 | lib-cov
16 |
17 | # Coverage directory used by tools like istanbul
18 | coverage
19 |
20 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
21 | .grunt
22 |
23 | # node-waf configuration
24 | .lock-wscript
25 |
26 | # Compiled binary addons (http://nodejs.org/api/addons.html)
27 | build/Release
28 |
29 | # Dependency directory
30 | # https://docs.npmjs.com/misc/faq#should-i-check-my-node-modules-folder-into-git
31 | node_modules
32 |
33 | # build/dist folders
34 | dist/
35 | build/
36 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 |
2 | The MIT License (MIT)
3 |
4 | Copyright (c) 2019 Kristian Koivisto-Kokko
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy
7 | of this software and associated documentation files (the "Software"), to deal
8 | in the Software without restriction, including without limitation the rights
9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | copies of the Software, and to permit persons to whom the Software is
11 | furnished to do so, subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in all
14 | copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | SOFTWARE.
23 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Phaser Driver
2 |
3 | Outrun style driving game in phaser. Try it on Itch: https://poisonvial.itch.io/phaser-driving
4 |
5 | Code licensed under MIT, see LICENSE.md.
6 | Original Assets licensed under [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/), excluding assets from other sources, listed below.
7 |
8 | Shameless plug of using my phaser plugin for playing tracker music: https://github.com/pinkkis/phaser-plugin-pasuuna
9 |
10 | ---
11 |
12 | Thanks to Jake Gordon for their blog post on javascript pseudo 3d racers
13 | * https://codeincomplete.com/posts/javascript-racer/
14 |
15 | Assets used:
16 | * Low Poly Jeep by https://sketchfab.com/BosstoneBaga - used under CC-BY license
17 | * Dream Candy by Drozerix https://modarchive.org/index.php?request=view_by_moduleid&query=178565 - Public Domain
18 |
--------------------------------------------------------------------------------
/assets/3d/car.glb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pinkkis/phaser-driving/b974b3a14ce06c23337fdddac78ceb8db9f39c47/assets/3d/car.glb
--------------------------------------------------------------------------------
/assets/boulder.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pinkkis/phaser-driving/b974b3a14ce06c23337fdddac78ceb8db9f39c47/assets/boulder.png
--------------------------------------------------------------------------------
/assets/boulder2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pinkkis/phaser-driving/b974b3a14ce06c23337fdddac78ceb8db9f39c47/assets/boulder2.png
--------------------------------------------------------------------------------
/assets/car-army.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pinkkis/phaser-driving/b974b3a14ce06c23337fdddac78ceb8db9f39c47/assets/car-army.png
--------------------------------------------------------------------------------
/assets/car-blue.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pinkkis/phaser-driving/b974b3a14ce06c23337fdddac78ceb8db9f39c47/assets/car-blue.png
--------------------------------------------------------------------------------
/assets/car-green.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pinkkis/phaser-driving/b974b3a14ce06c23337fdddac78ceb8db9f39c47/assets/car-green.png
--------------------------------------------------------------------------------
/assets/car-red.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pinkkis/phaser-driving/b974b3a14ce06c23337fdddac78ceb8db9f39c47/assets/car-red.png
--------------------------------------------------------------------------------
/assets/car-yellow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pinkkis/phaser-driving/b974b3a14ce06c23337fdddac78ceb8db9f39c47/assets/car-yellow.png
--------------------------------------------------------------------------------
/assets/clouds.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pinkkis/phaser-driving/b974b3a14ce06c23337fdddac78ceb8db9f39c47/assets/clouds.png
--------------------------------------------------------------------------------
/assets/clouds2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pinkkis/phaser-driving/b974b3a14ce06c23337fdddac78ceb8db9f39c47/assets/clouds2.png
--------------------------------------------------------------------------------
/assets/fonts/cosmicavenger.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pinkkis/phaser-driving/b974b3a14ce06c23337fdddac78ceb8db9f39c47/assets/fonts/cosmicavenger.png
--------------------------------------------------------------------------------
/assets/fonts/cosmicavenger.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
--------------------------------------------------------------------------------
/assets/fonts/impact-24-outline.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pinkkis/phaser-driving/b974b3a14ce06c23337fdddac78ceb8db9f39c47/assets/fonts/impact-24-outline.png
--------------------------------------------------------------------------------
/assets/fonts/impact-24-outline.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
--------------------------------------------------------------------------------
/assets/fonts/number-font.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pinkkis/phaser-driving/b974b3a14ce06c23337fdddac78ceb8db9f39c47/assets/fonts/number-font.png
--------------------------------------------------------------------------------
/assets/fonts/number-font.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/assets/hills.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pinkkis/phaser-driving/b974b3a14ce06c23337fdddac78ceb8db9f39c47/assets/hills.png
--------------------------------------------------------------------------------
/assets/mountain.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pinkkis/phaser-driving/b974b3a14ce06c23337fdddac78ceb8db9f39c47/assets/mountain.png
--------------------------------------------------------------------------------
/assets/smoke-particle.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pinkkis/phaser-driving/b974b3a14ce06c23337fdddac78ceb8db9f39c47/assets/smoke-particle.png
--------------------------------------------------------------------------------
/assets/sound/car-collision.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pinkkis/phaser-driving/b974b3a14ce06c23337fdddac78ceb8db9f39c47/assets/sound/car-collision.wav
--------------------------------------------------------------------------------
/assets/sound/confirm.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pinkkis/phaser-driving/b974b3a14ce06c23337fdddac78ceb8db9f39c47/assets/sound/confirm.wav
--------------------------------------------------------------------------------
/assets/sound/drozerix_-_dream_candy.xm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pinkkis/phaser-driving/b974b3a14ce06c23337fdddac78ceb8db9f39c47/assets/sound/drozerix_-_dream_candy.xm
--------------------------------------------------------------------------------
/assets/sound/engine-loop.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pinkkis/phaser-driving/b974b3a14ce06c23337fdddac78ceb8db9f39c47/assets/sound/engine-loop.wav
--------------------------------------------------------------------------------
/assets/sound/explosion.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pinkkis/phaser-driving/b974b3a14ce06c23337fdddac78ceb8db9f39c47/assets/sound/explosion.wav
--------------------------------------------------------------------------------
/assets/sound/select.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pinkkis/phaser-driving/b974b3a14ce06c23337fdddac78ceb8db9f39c47/assets/sound/select.wav
--------------------------------------------------------------------------------
/assets/sound/time-extended.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pinkkis/phaser-driving/b974b3a14ce06c23337fdddac78ceb8db9f39c47/assets/sound/time-extended.wav
--------------------------------------------------------------------------------
/assets/sound/tire-squeal.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pinkkis/phaser-driving/b974b3a14ce06c23337fdddac78ceb8db9f39c47/assets/sound/tire-squeal.wav
--------------------------------------------------------------------------------
/assets/tree.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pinkkis/phaser-driving/b974b3a14ce06c23337fdddac78ceb8db9f39c47/assets/tree.png
--------------------------------------------------------------------------------
/assets/tree2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pinkkis/phaser-driving/b974b3a14ce06c23337fdddac78ceb8db9f39c47/assets/tree2.png
--------------------------------------------------------------------------------
/assets/tree3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pinkkis/phaser-driving/b974b3a14ce06c23337fdddac78ceb8db9f39c47/assets/tree3.png
--------------------------------------------------------------------------------
/assets/turn-sign.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pinkkis/phaser-driving/b974b3a14ce06c23337fdddac78ceb8db9f39c47/assets/turn-sign.png
--------------------------------------------------------------------------------
/ava.config.js:
--------------------------------------------------------------------------------
1 | export default {
2 | files: [
3 | 'src/**/*spec.ts',
4 | ],
5 | sources: [
6 | 'src/**/*.ts',
7 | ],
8 | compileEnhancements: false,
9 | verbose: true,
10 | extensions: ["ts"],
11 | require: [
12 | "ts-node/register/transpile-only"
13 | ]
14 | };
15 |
--------------------------------------------------------------------------------
/buildConfig.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 |
3 | const {name, description, version} = require('./package.json');
4 | const buildDir = path.join(__dirname + '/build');
5 | const distDir = path.join(__dirname + '/dist');
6 |
7 | module.exports = {
8 | name,
9 | description,
10 | version,
11 | buildDir,
12 | distDir,
13 | };
14 |
--------------------------------------------------------------------------------
/number-font.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pinkkis/phaser-driving/b974b3a14ce06c23337fdddac78ceb8db9f39c47/number-font.png
--------------------------------------------------------------------------------
/number-font.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "phaser-driver",
3 | "version": "0.8.0",
4 | "description": "Outrun style driving game in Phaser 3",
5 | "main": "index.js",
6 | "scripts": {
7 | "itchio": "npm run build && node ./tasks/createZip.js",
8 | "build": "cross-env NODE_ENV=production webpack --mode production",
9 | "start": "cross-env NODE_ENV=development webpack-dev-server --mode development --progress --port=8000 --open"
10 | },
11 | "browserslist": "> 1%, not dead",
12 | "repository": {
13 | "type": "git",
14 | "url": "git+https://github.com/pinkkis/phaser-driver.git"
15 | },
16 | "keywords": ["phaser", "game", "driving", "outrun"],
17 | "author": "Kristian Koivisto-Kokko (http://pinkkis.com/)",
18 | "license": "MIT",
19 | "licenses": [
20 | {
21 | "type": "MIT",
22 | "url": "https://github.com/pinkkis/phaser-minesweeper/blob/master/LICENSE.md"
23 | }
24 | ],
25 | "bugs": {
26 | "url": "https://github.com/pinkkis/phaser-driver/issues"
27 | },
28 | "homepage": "https://github.com/pinkkis/phaser-driver#readme",
29 | "devDependencies": {
30 | "@babel/core": "^7.4.0",
31 | "@babel/plugin-proposal-class-properties": "^7.4.0",
32 | "@babel/plugin-proposal-object-rest-spread": "^7.4.0",
33 | "@babel/preset-env": "^7.4.2",
34 | "@babel/preset-typescript": "^7.3.3",
35 | "@csstools/normalize.css": "^9.0.1",
36 | "@pinkkis/phaser-plugin-pasuuna": "^0.5.2",
37 | "archiver": "^3.0.0",
38 | "babel-loader": "^8.0.5",
39 | "babel-plugin-transform-remove-console": "^6.9.4",
40 | "clean-webpack-plugin": "^2.0.1",
41 | "copy-webpack-plugin": "^5.0.2",
42 | "cross-env": "^5.2.0",
43 | "css-loader": "^2.1.1",
44 | "expose-loader": "^0.7.5",
45 | "file-loader": "^3.0.1",
46 | "html-loader": "^0.5.5",
47 | "html-webpack-plugin": "^3.2.0",
48 | "mini-css-extract-plugin": "^0.5.0",
49 | "optimize-css-assets-webpack-plugin": "^5.0.1",
50 | "phaser": "^3.16.2",
51 | "raw-loader": "^2.0.0",
52 | "source-map-loader": "^0.2.4",
53 | "stats-js": "^1.0.0",
54 | "terser-webpack-plugin": "^1.2.3",
55 | "three": "^0.103.0",
56 | "ts-loader": "^5.3.3",
57 | "ts-node": "^8.0.3",
58 | "tslint": "^5.14.0",
59 | "typescript": "^3.4.1",
60 | "webpack": "^4.29.6",
61 | "webpack-cli": "^3.3.0",
62 | "webpack-dev-server": "^3.2.1"
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/src/Components/Car.ts:
--------------------------------------------------------------------------------
1 | import { GameScene } from '../scenes/GameScene';
2 | import { TrackSegment } from './TrackSegment';
3 | import { gameSettings } from '../config/GameSettings';
4 | import { Util } from './Util';
5 | import { Road } from './Road';
6 |
7 | export class Car {
8 | public scene: GameScene;
9 | public road: Road;
10 | public sprite: Phaser.GameObjects.Sprite;
11 | public offset: number = 0;
12 | public speed: number = 0;
13 | public trackPosition: number = 0;
14 | public percent: number = 0;
15 | public scale: number = 1500;
16 |
17 | constructor(scene: GameScene, road: Road, offset: number, trackPosition: number, sprite: string, speed: number) {
18 | this.scene = scene;
19 | this.road = road;
20 | this.offset = offset;
21 | this.speed = speed;
22 | this.trackPosition = trackPosition;
23 | this.sprite = this.scene.add.sprite(-999, 999, sprite, 0).setOrigin(0.5, 1);
24 | }
25 |
26 | public get isOnGravel(): boolean {
27 | return Math.abs(this.offset) > 1;
28 | }
29 |
30 | public update(delta: number, carSegment: TrackSegment, playerSegment: TrackSegment, playerOffset: number): void {
31 | this.updateOffset(delta, carSegment, playerSegment);
32 | this.updateAngleFrame(carSegment, playerSegment, playerOffset);
33 | }
34 |
35 | public draw(x: number = 0, y: number = 0, scale: number = 1, segmentClip: number = 0) {
36 | this.sprite.setPosition(x, y);
37 | this.sprite.setScale(this.scale * scale);
38 | this.sprite.setDepth(10 + scale); // draw order
39 |
40 | if (!this.sprite.visible) {
41 | this.sprite.setVisible(true);
42 | }
43 |
44 | // calculate clipping behind hills
45 | if (y > segmentClip) {
46 | const clipped = (y - segmentClip) / this.sprite.scaleY;
47 | const cropY = this.sprite.height - clipped;
48 | this.sprite.setCrop(0, 0, this.sprite.width, cropY);
49 | } else {
50 | this.sprite.setCrop();
51 | }
52 | }
53 |
54 | public updateAngleFrame(carSegment: TrackSegment, playerSegment: TrackSegment, playerOffset: number): void {
55 | const roadDistance = Math.abs(carSegment.index - playerSegment.index);
56 | const offsetDistance = Math.abs(playerOffset - this.offset);
57 | const isLeft = playerOffset > this.offset;
58 |
59 | if (roadDistance < 20 && offsetDistance > 0.3) {
60 | this.sprite.setFrame(1);
61 | this.sprite.flipX = !isLeft;
62 | } else {
63 | this.sprite.setFrame(0);
64 | }
65 | }
66 |
67 | public updateOffset(delta: number, carSegment: TrackSegment, playerSegment: TrackSegment): void {
68 | // segments ahead to see if there's somethign to avoid
69 | const lookahead = 50;
70 |
71 | const player = this.scene.player;
72 |
73 | // car not visible, don't do ai behaviour
74 | if (carSegment.index - playerSegment.index > gameSettings.drawDistance) {
75 | return;
76 | }
77 |
78 | for (let i = 0; i < lookahead; i++) {
79 | const segment = this.road.segments[ (carSegment.index + i) % this.road.segments.length];
80 |
81 | if (segment === playerSegment && this.speed > player.speed && Util.overlapPlayer(player, this)) {
82 | if (player.x < this.offset) {
83 | this.offset = Util.interpolate(this.offset, 1, delta * 0.1);
84 | } else {
85 | this.offset = Util.interpolate(this.offset, -1, delta * 0.1);
86 | }
87 | }
88 |
89 | if (segment.cars.size) {
90 | segment.cars.forEach((car: Car) => {
91 | if (car === this) { return; }
92 |
93 | if (this.speed > car.speed && Util.overlapSprite(car.sprite, this.sprite)) {
94 | if (car.offset < this.offset) {
95 | this.offset = Util.interpolate(this.offset, 1, delta * 0.1);
96 | } else {
97 | this.offset = Util.interpolate(this.offset, -1, delta * 0.1);
98 | }
99 | }
100 | });
101 | }
102 |
103 | }
104 |
105 | // steer towards center of track if outside it
106 | if (Math.abs(this.offset) > 0.9) {
107 | this.offset = Util.interpolate(this.offset, 0, delta);
108 | }
109 | }
110 |
111 | public destroy(): void {
112 | this.sprite.destroy();
113 | }
114 | }
115 |
--------------------------------------------------------------------------------
/src/Components/CarManager.ts:
--------------------------------------------------------------------------------
1 | import { Car } from './Car';
2 | import { Road } from './Road';
3 | import { gameSettings } from '../config/GameSettings';
4 | import { GameScene } from '../scenes/GameScene';
5 | import { TrackSegment } from './TrackSegment';
6 | import { Util } from './Util';
7 |
8 | export class CarManager {
9 | public scene: GameScene;
10 | public cars: Set = new Set();
11 |
12 | private road: Road;
13 |
14 | constructor(scene: GameScene, road: Road) {
15 | this.scene = scene;
16 | this.road = road;
17 | }
18 |
19 | public resetCars(): void {
20 | this.destroy();
21 |
22 | for (let i = 0; i < gameSettings.totalCars; i++) {
23 | const roadOffset = Phaser.Math.FloatBetween(-0.8, 0.8);
24 | const trackPosition = Phaser.Math.Between(0, this.road.trackLength);
25 | const spriteString = this.getRandomCarType();
26 | const speed = Phaser.Math.Between(gameSettings.maxSpeed * 0.2, gameSettings.maxSpeed * 0.8);
27 |
28 | const car = new Car(this.scene, this.road, roadOffset, trackPosition, spriteString, speed);
29 | const carSegment = this.road.findSegmentByZ(trackPosition);
30 |
31 | this.cars.add(car);
32 | carSegment.cars.add(car);
33 | }
34 | }
35 |
36 | public update(delta: number, playerSegment: TrackSegment, playerOffset: number): void {
37 | for (const car of this.cars) {
38 | const oldSegment = this.road.findSegmentByZ(car.trackPosition);
39 |
40 | car.update(delta, oldSegment, playerSegment, playerOffset);
41 | car.trackPosition = Util.increase(car.trackPosition, delta * car.speed, this.road.trackLength);
42 | car.percent = Util.percentRemaining(car.trackPosition, gameSettings.segmentLength);
43 |
44 | const newSegment = this.road.findSegmentByZ(car.trackPosition);
45 | if (newSegment.index !== oldSegment.index) {
46 | oldSegment.cars.delete(car);
47 | newSegment.cars.add(car);
48 | }
49 | }
50 | }
51 |
52 | public hideAll(): void {
53 | this.cars.forEach( (car: Car) => {
54 | car.sprite.setVisible(false);
55 | });
56 | }
57 |
58 | public destroy(): void {
59 | this.cars.forEach( (car: Car) => car.destroy() );
60 | this.cars.clear();
61 | }
62 |
63 | private getRandomCarType(): string {
64 | const availableSprites = ['car-army', 'car-yellow', 'car-red', 'car-green', 'car-blue'];
65 | const rndIndex = Phaser.Math.Between(0, availableSprites.length - 1);
66 |
67 | return availableSprites[rndIndex];
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/src/Components/Colors.ts:
--------------------------------------------------------------------------------
1 | export const Colors = {
2 | ROAD_LIGHT: new Phaser.Display.Color(127, 127, 127, 1),
3 | ROAD_DARK: new Phaser.Display.Color(123, 123, 123, 1),
4 |
5 | GRASS_LIGHT: new Phaser.Display.Color(118, 197, 120, 1),
6 | GRASS_DARK: new Phaser.Display.Color(130, 211, 34, 1),
7 |
8 | LANE_MARKER: new Phaser.Display.Color(220, 220, 220, 1),
9 |
10 | RUMBLE_LIGHT: new Phaser.Display.Color(200, 200, 200, 1),
11 | RUMBLE_DARK: new Phaser.Display.Color(177, 64, 64, 1),
12 |
13 | SKY: new Phaser.Display.Color(127, 127, 255, 1),
14 | };
15 |
16 | export const DarkColors = {
17 | ROAD: Colors.ROAD_DARK.color,
18 | GRASS: Colors.GRASS_DARK.color,
19 | RUMBLE: Colors.RUMBLE_LIGHT.color,
20 | };
21 |
22 | export const LightColors = {
23 | ROAD: Colors.ROAD_LIGHT.color,
24 | GRASS: Colors.GRASS_LIGHT.color,
25 | RUMBLE: Colors.RUMBLE_DARK.color,
26 | LANE: Colors.LANE_MARKER.color,
27 | };
28 |
--------------------------------------------------------------------------------
/src/Components/Player.ts:
--------------------------------------------------------------------------------
1 | import { GameScene } from '../scenes/GameScene';
2 | import { Phaser3D } from '../libs/Phaser3D';
3 | import { gameSettings } from '../config/GameSettings';
4 | import { Util } from './Util';
5 |
6 | const HALFPI = Math.PI / 2;
7 |
8 | export class Player {
9 | public position: Phaser.Math.Vector3;
10 | public sprite: Phaser.GameObjects.Rectangle;
11 | public scene: GameScene;
12 | public p3d: Phaser3D;
13 | public model: any;
14 | public smokeParticles: Phaser.GameObjects.Particles.ParticleEmitterManager;
15 | public smokeEmitterLeft: Phaser.GameObjects.Particles.ParticleEmitter;
16 | public smokeEmitterRight: Phaser.GameObjects.Particles.ParticleEmitter;
17 |
18 | public engineSound: Phaser.Sound.WebAudioSound;
19 | public tireScreechSound: Phaser.Sound.WebAudioSound;
20 | public explosionSound: Phaser.Sound.WebAudioSound;
21 | public collideSound: Phaser.Sound.WebAudioSound;
22 |
23 | public turn: number;
24 | public pitch: number;
25 | public speed: number;
26 | public trackPosition: number;
27 | public accelerating: boolean = false;
28 | public screeching: boolean = false;
29 | public collisionRadius: number = 20;
30 | private turnVector: Phaser.Math.Vector3;
31 |
32 | constructor(scene: GameScene, x: number, y: number, z: number, modelKey: string) {
33 | this.position = new Phaser.Math.Vector3(x, y, z);
34 | this.scene = scene;
35 | this.turn = 0;
36 | this.pitch = 0;
37 | this.speed = 0;
38 | this.trackPosition = 0;
39 | this.turnVector = new Phaser.Math.Vector3(0, 0, 0);
40 |
41 | this.smokeParticles = this.scene.add.particles('particles').setDepth(21);
42 | const particleSettings = {
43 | x: -100,
44 | y: -100,
45 | lifespan: 500,
46 | frequency: 66,
47 | frame: 0,
48 | blendMode: 'NORMAL',
49 | gravityY: -100,
50 | speed: 0,
51 | rotate: { onEmit: () => Math.random() * 359 },
52 | scale: { start: 0.3, end: 2 },
53 | };
54 |
55 | this.engineSound = this.scene.sound.add('engine', { volume: 0.7, loop: true }) as Phaser.Sound.WebAudioSound;
56 | this.engineSound.play();
57 |
58 | this.tireScreechSound = this.scene.sound.add('tire-squeal', { volume: 0.5, loop: true}) as Phaser.Sound.WebAudioSound;
59 | this.explosionSound = this.scene.sound.add('explosion', { volume: 0.75 }) as Phaser.Sound.WebAudioSound;
60 | this.collideSound = this.scene.sound.add('collision', { volume: 0.75 }) as Phaser.Sound.WebAudioSound;
61 |
62 | this.smokeEmitterLeft = this.smokeParticles.createEmitter(particleSettings);
63 | this.smokeEmitterRight = this.smokeParticles.createEmitter(particleSettings);
64 |
65 | this.p3d = new Phaser3D(this.scene, { fov: 35, x: 0, y: 7, z: -20, antialias: false });
66 | this.p3d.view.setDepth(20);
67 | this.p3d.addGLTFModel(modelKey);
68 |
69 | this.p3d.camera.lookAt(0, 5.1, 0);
70 |
71 | this.p3d.add.hemisphereLight({ skyColor: 0xefefff, groundColor: 0x111111, intensity: 2 });
72 | this.p3d.on('loadgltf', (gltf: any, model: any) => {
73 | model.rotateY(HALFPI);
74 | model.position.set(0, 0, 0);
75 | model.scale.set(1, 1, 1);
76 | this.model = model;
77 | });
78 | }
79 |
80 | public get x(): number { return this.position.x; }
81 | public set x(x: number) {
82 | this.position.x = x;
83 | this.scene.registry.set('playerx', x);
84 | }
85 |
86 | public get y(): number { return this.position.y; }
87 | public set y(y: number) {
88 | this.position.y = y;
89 | }
90 |
91 | public get z(): number { return this.position.z; }
92 | public set z(z: number) {
93 | this.position.z = z;
94 | }
95 |
96 | public get isOnGravel(): boolean {
97 | return Math.abs(this.x) > 1;
98 | }
99 |
100 | public update(delta: number, dx: number) {
101 | this.position.x += (this.turn * 0.08) * (this.speed / gameSettings.maxSpeed);
102 |
103 | if (this.model) {
104 | this.turnVector.y = HALFPI + -this.turn;
105 | this.turnVector.x = Phaser.Math.Clamp(this.pitch, -0.3, 0.3);
106 | this.model.rotation.setFromVector3(this.turnVector);
107 | this.p3d.camera.rotation.z = Math.PI + Phaser.Math.DegToRad(this.scene.cameraAngle);
108 |
109 | if (this.pitch > 0) {
110 | this.model.position.y = Util.interpolate(this.model.position.y, -this.pitch * 3, 0.33);
111 | }
112 |
113 | if (this.speed > 20) {
114 | this.model.position.y = Util.interpolate(this.model.position.y + Phaser.Math.Between(-1, 1) * (this.isOnGravel ? 0.05 : 0.01), 0, 0.2);
115 | }
116 |
117 | this.updateParticles();
118 |
119 | this.playEngineSound();
120 | this.tireScreech(this.screeching);
121 | }
122 | }
123 |
124 | public playEngineSound(): void {
125 | if (this.speed > 0 && this.engineSound.isPlaying) {
126 | this.engineSound.setDetune( this.speed * 1.25 );
127 | this.engineSound.setVolume( 0.7 + Phaser.Math.Clamp(this.speed * 0.0001, 0, 0.2) );
128 | }
129 | }
130 |
131 | public tireScreech(play = false): void {
132 | if (play && !this.tireScreechSound.isPlaying) {
133 | this.tireScreechSound.play();
134 | } else {
135 | this.tireScreechSound.stop();
136 | }
137 | }
138 |
139 | public collide(type: string): void {
140 | switch (type) {
141 | case 'car':
142 | if (!this.collideSound.isPlaying) {
143 | this.collideSound.play();
144 | }
145 | break;
146 |
147 | case 'prop':
148 | if (!this.explosionSound.isPlaying && this.speed > 700) {
149 | this.explosionSound.play();
150 | } else if (!this.collideSound.isPlaying && this.speed > 200) {
151 | this.collideSound.play();
152 | }
153 | break;
154 |
155 | default:
156 | break;
157 | }
158 | }
159 |
160 | public updateParticles() {
161 | const particleSpeed = -this.turn * 100;
162 | const particleAngle = this.turn < 0 ? { min: -30, max: 0 } : { min: 180, max: 210 };
163 | const halfWidth = this.scene.scale.gameSize.width / 2;
164 | const particleX = halfWidth + (-this.turn * 20);
165 |
166 | this.smokeEmitterLeft.setPosition(particleX - 13, this.scene.scale.gameSize.height - 5 - this.pitch * 15 - (this.turn > 0 ? this.turn * 7 : 0));
167 | this.smokeEmitterRight.setPosition(particleX + 13, this.scene.scale.gameSize.height - 5 - this.pitch * 15 + (this.turn < 0 ? this.turn * 7 : 0));
168 |
169 | this.smokeEmitterLeft.setSpeed(particleSpeed);
170 | this.smokeEmitterRight.setSpeed(particleSpeed);
171 |
172 | this.smokeEmitterLeft.setAngle(particleAngle);
173 | this.smokeEmitterRight.setAngle(particleAngle);
174 |
175 | if (this.isOnGravel) {
176 | this.smokeEmitterLeft.setFrame(1);
177 | this.smokeEmitterRight.setFrame(1);
178 | } else {
179 | this.smokeEmitterLeft.setFrame(0);
180 | this.smokeEmitterRight.setFrame(0);
181 | }
182 |
183 | if (this.speed > 300 && Math.abs(this.turn) > 0.66 && !this.smokeEmitterLeft.on) {
184 | this.smokeEmitterLeft.on = true;
185 | this.smokeEmitterRight.on = true;
186 | this.screeching = true;
187 | } else if (this.speed > 100 && this.isOnGravel) {
188 | this.smokeEmitterLeft.on = true;
189 | this.smokeEmitterRight.on = true;
190 | this.screeching = false;
191 | } else if (this.speed < 400 && this.accelerating) {
192 | this.smokeEmitterLeft.on = true;
193 | this.smokeEmitterRight.on = true;
194 | this.screeching = true;
195 | } else {
196 | this.smokeEmitterLeft.stop();
197 | this.smokeEmitterRight.stop();
198 | this.screeching = false;
199 | }
200 | }
201 | }
202 |
--------------------------------------------------------------------------------
/src/Components/Prop.ts:
--------------------------------------------------------------------------------
1 | import { GameScene } from '../scenes/GameScene';
2 |
3 | export class Prop {
4 | public scene: GameScene;
5 | public sprite: Phaser.GameObjects.Sprite;
6 | public offset: number;
7 | public height: number;
8 | public collides: boolean;
9 | public scale: number = 1; // scale calculation in game is _really_ small
10 |
11 | constructor(scene: GameScene, name: string, offset: number, height: number, scale: number = 3000, flipX: boolean = false, collides: boolean = false) {
12 | this.scene = scene;
13 | this.sprite = scene.add.sprite(-999, -999, name).setOrigin(0.5, 1).setVisible(false);
14 | this.offset = offset;
15 | this.height = height;
16 | this.collides = collides;
17 | this.scale = scale;
18 |
19 | this.sprite.flipX = flipX;
20 | }
21 |
22 | public update(x: number = 0, y: number = 0, scale: number = 1, segmentClip: number = 0) {
23 | this.sprite.setPosition(x, y + this.height);
24 | this.sprite.setScale(this.scale * scale);
25 | this.sprite.setDepth(10 + scale); // draw order
26 |
27 | if (!this.sprite.visible) {
28 | this.sprite.setVisible(true);
29 | }
30 |
31 | // calculate clipping behind hills
32 | if (y > segmentClip) {
33 | const clipped = (y - segmentClip) / this.sprite.scaleY;
34 | const cropY = this.sprite.height - clipped;
35 | this.sprite.setCrop(0, 0, this.sprite.width, cropY);
36 | } else {
37 | this.sprite.setCrop();
38 | }
39 | }
40 |
41 | public destroy(): void {
42 | this.sprite.destroy();
43 | this.scene = undefined;
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/Components/RadarCar.ts:
--------------------------------------------------------------------------------
1 | export class RadarCar {
2 | public x: number;
3 | public y: number;
4 | public color: number;
5 |
6 | constructor(x: number, y: number, color: number) {
7 | this.x = x;
8 | this.y = y;
9 | this.color = color;
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/src/Components/Renderer.ts:
--------------------------------------------------------------------------------
1 | import { SegmentPoint } from './SegmentPoint';
2 | import { Util } from './Util';
3 | import { gameSettings } from '../config/GameSettings';
4 | import { GameScene } from '../scenes/GameScene';
5 |
6 | export class Renderer {
7 | public static project(sp: SegmentPoint, cameraX: number, cameraY: number, cameraZ: number, cameraDepth: number, width: number, height: number, roadWidth: number) {
8 | sp.camera.x = (sp.world.x || 0) - cameraX;
9 | sp.camera.y = (sp.world.y || 0) - cameraY;
10 | sp.camera.z = (sp.world.z || 0) - cameraZ;
11 | sp.screen.scale = cameraDepth / sp.camera.z;
12 | sp.screen.x = Math.round((width / 2) + (sp.screen.scale * sp.camera.x * width / 2));
13 | sp.screen.y = Math.round((height / 2) - (sp.screen.scale * sp.camera.y * height / 2));
14 | sp.screen.w = Math.round((sp.screen.scale * roadWidth * width / 2));
15 | }
16 |
17 | public static drawPolygon(g: Phaser.GameObjects.Graphics, x1: number, y1: number, x2: number, y2: number, x3: number, y3: number, x4: number, y4: number, color: number) {
18 | g.save().fillStyle(color).beginPath()
19 | .moveTo(x1, y1).lineTo(x2, y2).lineTo(x3, y3).lineTo(x4, y4)
20 | .closePath().fillPath().restore();
21 | }
22 |
23 | public static drawSegment(ctx: Phaser.GameObjects.Graphics, width: number, lanes: number, x1: number, y1: number, w1: number, x2: number, y2: number, w2: number, colors: any) {
24 | const r1 = Util.rumbleWidth(w1, lanes);
25 | const r2 = Util.rumbleWidth(w2, lanes);
26 | const l1 = Util.laneMarkerWidth(w1, lanes);
27 | const l2 = Util.laneMarkerWidth(w1, lanes);
28 | const h = y1 - y2;
29 |
30 | ctx.fillStyle(colors.GRASS);
31 | ctx.fillRect(-10, y2, width, h);
32 |
33 | Renderer.drawPolygon(ctx, x1 - w1 - r1, y1, x1 - w1, y1, x2 - w2, y2, x2 - w2 - r2, y2, colors.RUMBLE);
34 | Renderer.drawPolygon(ctx, x1 + w1 + r1, y1, x1 + w1, y1, x2 + w2, y2, x2 + w2 + r2, y2, colors.RUMBLE);
35 | Renderer.drawPolygon(ctx, x1 - w1, y1, x1 + w1, y1, x2 + w2, y2, x2 - w2, y2, colors.ROAD);
36 |
37 | const lanew1 = w1 * 2 / lanes;
38 | const lanew2 = w2 * 2 / lanes;
39 | let lanex1 = x1 - w1 + lanew1;
40 | let lanex2 = x2 - w2 + lanew2;
41 |
42 | if (colors.LANE) {
43 | for (let lane = 1; lane < lanes; lanex1 += lanew1, lanex2 += lanew2, lane++) {
44 | Renderer.drawPolygon(ctx, lanex1 - l1 / 2, y1, lanex1 + l1 / 2, y1, lanex2 + l2 / 2, y2, lanex2 - l2 / 2, y2, colors.LANE);
45 | }
46 | }
47 | }
48 |
49 | // -------------
50 |
51 | public scene: GameScene;
52 | public roadGraphics: Phaser.GameObjects.Graphics;
53 |
54 | constructor(scene: GameScene, depth: number = 0) {
55 | this.scene = scene;
56 | this.roadGraphics = this.scene.add.graphics().setDepth(depth);
57 | }
58 |
59 | public update(time: number, delta: number): void {
60 | this.roadGraphics.clear();
61 | this.drawRoad();
62 | }
63 |
64 | public drawRoad(): void {
65 | const gameWidth = this.scene.scale.gameSize.width + 20;
66 | const gameHeight = this.scene.scale.gameSize.height + 20;
67 |
68 | const baseSegment = this.scene.road.findSegmentByZ(this.scene.player.trackPosition);
69 | const basePercent = Util.percentRemaining(this.scene.player.trackPosition, gameSettings.segmentLength);
70 |
71 | let maxY = gameHeight; // used for clipping things behind a hill
72 | let roadCenterX = 0;
73 | let deltaX = -(baseSegment.curve * basePercent);
74 |
75 | // draw road front to back
76 | for (let n = 0; n < gameSettings.drawDistance; n++) {
77 | const segmentIndex = (baseSegment.index + n) % this.scene.road.segments.length;
78 | const segment = this.scene.road.segments[segmentIndex];
79 |
80 | segment.clip = maxY;
81 | segment.looped = segment.index < baseSegment.index;
82 |
83 | Renderer.project(segment.p1, this.scene.player.x * gameSettings.roadWidth - roadCenterX, this.scene.player.y + gameSettings.cameraHeight,
84 | this.scene.player.trackPosition - (segment.looped ? this.scene.road.trackLength : 0), gameSettings.cameraDepth,
85 | gameWidth, gameHeight - gameSettings.projectYCompensation, gameSettings.roadWidth);
86 |
87 | Renderer.project(segment.p2, this.scene.player.x * gameSettings.roadWidth - roadCenterX - deltaX, this.scene.player.y + gameSettings.cameraHeight,
88 | this.scene.player.trackPosition - (segment.looped ? this.scene.road.trackLength : 0), gameSettings.cameraDepth,
89 | gameWidth, gameHeight - gameSettings.projectYCompensation, gameSettings.roadWidth);
90 |
91 | roadCenterX = roadCenterX + deltaX;
92 | deltaX = deltaX + segment.curve;
93 |
94 | if (segment.p1.camera.z <= gameSettings.cameraDepth || segment.p2.screen.y >= maxY || segment.p2.screen.y >= segment.p1.screen.y) {
95 | continue;
96 | }
97 |
98 | Renderer.drawSegment(this.roadGraphics, gameWidth, gameSettings.lanes,
99 | segment.p1.screen.x - 10, segment.p1.screen.y, segment.p1.screen.w,
100 | segment.p2.screen.x - 10, segment.p2.screen.y, segment.p2.screen.w,
101 | segment.colors);
102 |
103 | maxY = segment.p2.screen.y;
104 | }
105 |
106 | // draw props and cars back to front
107 | for (let n = gameSettings.drawDistance; n > 0; n--) {
108 | const segmentIndex = (baseSegment.index + n) % this.scene.road.segments.length;
109 | const segment = this.scene.road.segments[segmentIndex];
110 |
111 | const scale = segment.p1.screen.scale;
112 |
113 | for (const prop of segment.props) {
114 | const x = segment.p1.screen.x - 10 + (scale * prop.offset * gameSettings.roadWidth * gameWidth / 2);
115 | prop.update(x, segment.p1.screen.y, scale, segment.clip);
116 | }
117 |
118 | for (const car of segment.cars) {
119 | const spriteScale = Util.interpolate(segment.p1.screen.scale, segment.p2.screen.scale, car.percent);
120 | const spriteX = Util.interpolate(segment.p1.screen.x - 10, segment.p2.screen.x - 10, car.percent) + (spriteScale * car.offset * gameSettings.roadWidth * gameWidth / 2);
121 | const spriteY = Util.interpolate(segment.p1.screen.y, segment.p2.screen.y, car.percent);
122 | car.draw(spriteX, spriteY, spriteScale, segment.clip);
123 | }
124 | }
125 | }
126 | }
127 |
--------------------------------------------------------------------------------
/src/Components/Road.ts:
--------------------------------------------------------------------------------
1 | import { TrackSegment } from './TrackSegment';
2 | import { gameSettings } from '../config/GameSettings';
3 | import { Util } from './Util';
4 | import { SEGMENT } from './SegmentType';
5 | import { Prop } from './Prop';
6 | import { GameScene } from '../scenes/GameScene';
7 |
8 | export class Road {
9 | public scene: GameScene;
10 | public segments: TrackSegment[];
11 | public trackLength: number;
12 |
13 | constructor(scene: GameScene) {
14 | this.scene = scene;
15 | this.segments = [];
16 | this.trackLength = 0;
17 | }
18 |
19 | public addRoadSegment(curve: number, y: number): void {
20 | this.segments.push(new TrackSegment(this.segments.length, curve, y, this.getLastSegmentYPos()));
21 | }
22 |
23 | public addStraight(num: number = SEGMENT.LENGTH.MEDIUM): void {
24 | this.addRoad(num, num, num, 0, 0);
25 | }
26 |
27 | public addCurve(num: number = SEGMENT.LENGTH.MEDIUM, curve: number = SEGMENT.CURVE.MEDIUM, height: number = SEGMENT.HILL.NONE): void {
28 | this.addRoad(num, num, num, curve, height);
29 | }
30 |
31 | public addHill(num: number = SEGMENT.LENGTH.MEDIUM, height: number = SEGMENT.HILL.NONE): void {
32 | this.addRoad(num, num, num, 0, height);
33 | }
34 |
35 | public addRoad(enter: number, hold: number, leave: number, curve: number, y: number): void {
36 | const startY = this.getLastSegmentYPos();
37 | const endY = startY + Util.toInt(y, 0) * gameSettings.segmentLength;
38 | const totalLength = enter + hold + leave;
39 |
40 | for (let n = 0; n < enter; n++) {
41 | this.addRoadSegment(Util.easeIn(0, curve, n / enter), Util.easeInOut(startY, endY, n / totalLength));
42 | }
43 |
44 | for (let n = 0; n < hold; n++) {
45 | this.addRoadSegment(curve, Util.easeInOut(startY, endY, (enter + n) / totalLength));
46 | }
47 |
48 | for (let n = 0; n < leave; n++) {
49 | this.addRoadSegment(Util.easeInOut(curve, 0, n / leave), Util.easeInOut(startY, endY, (enter + hold + n) / totalLength));
50 | }
51 | }
52 |
53 | public getLastSegmentYPos(): number {
54 | const lastSegment = this.getLastSegment();
55 | return lastSegment ? lastSegment.p2.world.y : 0;
56 | }
57 |
58 | public getLastSegment(): TrackSegment {
59 | return this.segments.length > 0 ? this.segments[this.segments.length - 1] : null;
60 | }
61 |
62 | public findSegmentByZ(z: number): TrackSegment {
63 | const index = Math.floor(z / gameSettings.segmentLength) % this.segments.length;
64 | return this.segments[index];
65 | }
66 |
67 | public resetRoad(): void {
68 | this.segments = [];
69 |
70 | this.addStraight(SEGMENT.LENGTH.MEDIUM);
71 | this.addCurve(SEGMENT.LENGTH.MEDIUM, SEGMENT.CURVE.MEDIUM, SEGMENT.HILL.LOW);
72 | this.addHill(SEGMENT.LENGTH.SHORT, -SEGMENT.HILL.LOW);
73 | this.addHill(SEGMENT.LENGTH.SHORT, SEGMENT.HILL.LOW);
74 | this.addHill(SEGMENT.LENGTH.SHORT, -SEGMENT.HILL.LOW);
75 | this.addCurve(SEGMENT.LENGTH.LONG, SEGMENT.CURVE.MEDIUM, SEGMENT.HILL.MEDIUM);
76 | this.addHill(SEGMENT.LENGTH.SHORT, -SEGMENT.HILL.LOW);
77 | this.addCurve(SEGMENT.LENGTH.LONG, SEGMENT.CURVE.MEDIUM, -SEGMENT.HILL.MEDIUM);
78 | this.addCurve(SEGMENT.LENGTH.LONG, -SEGMENT.CURVE.MINIMAL, SEGMENT.HILL.HIGH);
79 | this.addStraight(SEGMENT.LENGTH.SHORT);
80 | this.addCurve(SEGMENT.LENGTH.VERYLONG, SEGMENT.CURVE.MINIMAL);
81 | this.addHill(SEGMENT.LENGTH.SHORT, -SEGMENT.HILL.MEDIUM);
82 | this.addStraight(SEGMENT.LENGTH.SHORT);
83 | this.addHill(SEGMENT.LENGTH.MEDIUM, SEGMENT.HILL.HIGH);
84 | this.addCurve(SEGMENT.LENGTH.SHORT, SEGMENT.CURVE.MEDIUM, SEGMENT.HILL.LOW);
85 | this.addHill(SEGMENT.LENGTH.LONG, -SEGMENT.HILL.HIGH);
86 | this.addCurve(SEGMENT.LENGTH.LONG, -SEGMENT.CURVE.MEDIUM);
87 | this.addStraight();
88 | this.addCurve(SEGMENT.LENGTH.LONG, SEGMENT.CURVE.MEDIUM);
89 | this.addStraight();
90 | this.addCurve(SEGMENT.LENGTH.LONG, -SEGMENT.CURVE.EASY);
91 | this.addHill(SEGMENT.LENGTH.LONG, -SEGMENT.HILL.MEDIUM);
92 | this.addCurve(SEGMENT.LENGTH.LONG, SEGMENT.CURVE.MEDIUM, -SEGMENT.HILL.LOW);
93 |
94 | this.addRoad(200, 200, 200, SEGMENT.CURVE.NONE, Math.round(-this.getLastSegmentYPos() / gameSettings.segmentLength));
95 |
96 | this.trackLength = this.segments.length * gameSettings.segmentLength;
97 |
98 | this.createRandomProps();
99 | this.createTurnSigns();
100 | }
101 |
102 | public addProp(scene: GameScene, segmentIndex: number, name: string, offset: number, height: number = 0, scale: number = 3000, flipX: boolean = false, collides: boolean = false): boolean {
103 | try {
104 | const seg = this.segments[segmentIndex];
105 | const prop = new Prop(scene, name, offset, height, scale, flipX, collides);
106 | seg.props.add(prop);
107 |
108 | return true;
109 | } catch (e) {
110 | return false;
111 | }
112 | }
113 |
114 | public hideAllProps(): void {
115 | this.segments.forEach( (segment: TrackSegment) => {
116 | for (const prop of segment.props) {
117 | prop.sprite.setVisible(false);
118 | }
119 | });
120 | }
121 |
122 | // add some road side props
123 | // offsets <-1 & >1 are outside of the road
124 | public createRandomProps(): void {
125 | for (let n = 0; n < this.segments.length; n += Phaser.Math.Between(1, 5)) {
126 | const offset = Phaser.Math.FloatBetween(1.75, 10);
127 | const negated = Math.random() - 0.5 > 0;
128 |
129 | let type;
130 | let scale = 3000;
131 | switch (Phaser.Math.Between(1, 5)) {
132 | case 1:
133 | type = 'boulder1';
134 | scale = 1500;
135 | break;
136 | case 2:
137 | type = 'boulder2';
138 | scale = 2000;
139 | break;
140 | case 3:
141 | type = 'tree1';
142 | scale = 5000;
143 | break;
144 | case 4:
145 | type = 'tree2';
146 | scale = 5500;
147 | break;
148 | case 5:
149 | type = 'tree3';
150 | scale = 5000;
151 | break;
152 | }
153 |
154 | this.addProp(this.scene, n, type, negated ? -offset : offset, 0, scale, false);
155 | }
156 | }
157 |
158 | public createTurnSigns(): void {
159 | const signOffset = 1.75;
160 | const signScale = 5000;
161 |
162 | for (let n = 0; n < this.segments.length; n++) {
163 | const segment = this.segments[n];
164 | const isCurve = Math.abs(segment.curve) > 1;
165 |
166 | if (isCurve) {
167 | if (segment.curve > 0.33) {
168 | this.removeProps(segment, 1, -signOffset);
169 | this.addProp(this.scene, n, 'turnsign', -signOffset, 0, signScale, true, false);
170 | } else {
171 | this.removeProps(segment, 1, signOffset);
172 | this.addProp(this.scene, n, 'turnsign', signOffset, 0, signScale, false, false);
173 | }
174 |
175 | // increment n so we don't put signs too close
176 | n += 10;
177 | }
178 | }
179 | }
180 |
181 | public removeProps(sourceSegment: TrackSegment, breadth: number, side: number) {
182 | const isLeft = side < 0;
183 | const breadthSegments = this.segments.slice(sourceSegment.index - breadth, sourceSegment.index + breadth);
184 |
185 | for (const segment of breadthSegments) {
186 | if (segment.props.size) {
187 | for (const prop of segment.props) {
188 | if ((prop.offset < 0 && isLeft) || (prop.offset > 0 && !isLeft)) {
189 | segment.props.delete(prop);
190 | }
191 | }
192 | }
193 | }
194 | }
195 | }
196 |
--------------------------------------------------------------------------------
/src/Components/SegmentPoint.ts:
--------------------------------------------------------------------------------
1 | export interface IScreenPoint {
2 | x: number;
3 | y: number;
4 | w: number;
5 | scale: number;
6 | }
7 |
8 | export class SegmentPoint {
9 | public world: Phaser.Math.Vector3;
10 | public camera: Phaser.Math.Vector3;
11 | public screen: IScreenPoint;
12 |
13 | constructor(x: number = 0, y: number = 0, z: number = 0) {
14 | this.world = new Phaser.Math.Vector3(x, y, z);
15 | this.camera = new Phaser.Math.Vector3(0, 0, 0);
16 | this.screen = { x: 0, y: 0, w: 0, scale: 0 };
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/Components/SegmentType.ts:
--------------------------------------------------------------------------------
1 | export const SEGMENT = {
2 | LENGTH: {
3 | NONE: 0,
4 | SHORT: 25,
5 | MEDIUM: 50,
6 | LONG: 100,
7 | VERYLONG: 200,
8 | },
9 | CURVE: {
10 | NONE: 0,
11 | MINIMAL: 1,
12 | EASY: 2,
13 | MEDIUM: 4,
14 | HARD: 6,
15 | },
16 | HILL: {
17 | NONE: 0,
18 | LOW: 20,
19 | MEDIUM: 40,
20 | HIGH: 60,
21 | },
22 | WIDTH: {
23 | NORMAL: 1,
24 | WIDE: 2,
25 | NARROW: 0.5,
26 | },
27 | };
28 |
--------------------------------------------------------------------------------
/src/Components/SpeedGauge.ts:
--------------------------------------------------------------------------------
1 | import { RaceUiScene } from '../scenes/RaceUiScene';
2 | import { Util } from './Util';
3 | import { gameSettings } from '../config/GameSettings';
4 |
5 | export class SpeedGauge {
6 | public scene: RaceUiScene;
7 | public speedText: Phaser.GameObjects.BitmapText;
8 | public graphics: Phaser.GameObjects.Graphics;
9 |
10 | private x: number;
11 | private y: number;
12 | private radius: number;
13 | private speedValue: number = 0;
14 |
15 | constructor(scene: RaceUiScene, x: number, y: number, radius: number) {
16 | this.scene = scene;
17 | this.x = x;
18 | this.y = y;
19 | this.radius = radius;
20 |
21 | this.graphics = this.scene.add.graphics();
22 | this.speedText = this.scene.add.bitmapText(x + 35, y - 15, 'numbers', this.speedValue.toString(), 48).setOrigin(1, 0.5);
23 | this.scene.add.bitmapText(x - 58, y - 8, 'impact', `kmh`, 12).setTint(0xffff00);
24 |
25 | this.update();
26 | }
27 |
28 | public get speed(): number {
29 | return this.speedValue;
30 | }
31 |
32 | public set speed(value: number) {
33 | this.speedValue = value;
34 | this.update();
35 | }
36 |
37 | public update(): void {
38 | this.graphics.clear();
39 | this.drawGauge();
40 | this.speedText.setText(this.speedValue.toString());
41 | }
42 |
43 | public destroy(): void {
44 | //
45 | }
46 |
47 | // -------------
48 |
49 | private drawGauge(): void {
50 | const speedColor = Phaser.Display.Color.HSVToRGB(0.3 - this.speedValue / 600, 1, 1) as Phaser.Display.Color;
51 |
52 | this.graphics.lineStyle(17, 0x555555, 1)
53 | .beginPath()
54 | .arc(this.x, this.y, this.radius, Phaser.Math.DegToRad(180), Phaser.Math.DegToRad(315), false)
55 | .strokePath();
56 |
57 | this.graphics.lineStyle(12, speedColor.color)
58 | .beginPath()
59 | .arc(this.x, this.y, this.radius, Phaser.Math.DegToRad(183), Phaser.Math.DegToRad(183) + this.speedToAngle(), false)
60 | .strokePath();
61 | }
62 |
63 | private speedToAngle(): number {
64 | const speedPercentage = this.speedValue > 0 ? (this.speedValue * 1000) / gameSettings.maxSpeed : 0;
65 | const angle = Util.interpolate(0, 130, speedPercentage * .01);
66 | return Phaser.Math.DegToRad(angle);
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/src/Components/TrackRadar.ts:
--------------------------------------------------------------------------------
1 | import { RaceUiScene } from '../scenes/RaceUiScene';
2 | import { gameSettings } from '../config/GameSettings';
3 | import { Util } from './Util';
4 | import { RadarCar } from './RadarCar';
5 |
6 | const centerX = 17;
7 | const playerY = 130;
8 |
9 | export class TrackRadar {
10 | public scene: RaceUiScene;
11 | // public radarText: Phaser.GameObjects.BitmapText;
12 | public graphics: Phaser.GameObjects.Graphics;
13 | public container: Phaser.GameObjects.Container;
14 | public bg: Phaser.GameObjects.Rectangle;
15 | public grid: Phaser.GameObjects.Grid;
16 |
17 | public player: Phaser.GameObjects.Rectangle;
18 | public cars: Set;
19 |
20 | private x: number;
21 | private y: number;
22 |
23 |
24 | constructor(scene: RaceUiScene, x: number, y: number) {
25 | this.scene = scene;
26 | this.x = x;
27 | this.y = y;
28 |
29 | this.cars = new Set();
30 |
31 | this.container = this.scene.add.container(this.x, this.y);
32 |
33 | this.bg = this.scene.add.rectangle(0, 0, 33, 163, 0xffffff, .75).setOrigin(0, 0);
34 | this.grid = this.scene.add.grid(2, 2, 30, 160, 30 / gameSettings.lanes, 160 / 5, 0x333333, 0.8).setOrigin(0, 0);
35 |
36 | this.graphics = this.scene.add.graphics();
37 |
38 | this.player = this.scene.add.rectangle(centerX, playerY, 3, 5, 0xffff00);
39 |
40 | this.container.add([this.bg, this.grid, this.player, this.graphics]);
41 | }
42 |
43 | public update(): void {
44 | this.graphics.clear();
45 | }
46 |
47 | public destroy(): void {
48 | //
49 | }
50 |
51 | public updatePlayerX(value: number): void {
52 | const interpolatedValue = Util.interpolate(2, 17, value + 1);
53 | this.player.x = Phaser.Math.Clamp(interpolatedValue, 4, 30);
54 | }
55 |
56 | public drawCar(offset: number, distance: number) {
57 | const interpolatedOffset = Util.interpolate(1, 16, offset + 1);
58 | const x = Phaser.Math.Clamp(interpolatedOffset, 4, 30);
59 |
60 | const interpolatedDistance = Util.interpolate(0, 156, 1 / (distance * 0.0007));
61 | const y = Phaser.Math.Clamp(interpolatedDistance, 2, 156);
62 |
63 | this.graphics.fillStyle(0xff0000).fillRect(x, y, 3, 5);
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/src/Components/TrackSegment.ts:
--------------------------------------------------------------------------------
1 | import { SegmentPoint } from './SegmentPoint';
2 | import { gameSettings } from '../config/GameSettings';
3 | import { DarkColors, LightColors } from './Colors';
4 | import { Prop } from './Prop';
5 | import { Car } from './Car';
6 |
7 | export class TrackSegment {
8 | public index: number;
9 | public p1: SegmentPoint;
10 | public p2: SegmentPoint;
11 | public looped: boolean = false;
12 | public fog: number = 0;
13 | public curve: number;
14 | public colors: any;
15 | public props: Set;
16 | public cars: Set;
17 | public clip: number;
18 |
19 | constructor(z: number, curve: number, y: number, lastY: number) {
20 | this.index = z;
21 | this.p1 = new SegmentPoint(0, lastY, z * gameSettings.segmentLength);
22 | this.p2 = new SegmentPoint(0, y, (z + 1) * gameSettings.segmentLength);
23 | this.colors = Math.floor(z / gameSettings.rumbleLength) % 2 ? DarkColors : LightColors;
24 | this.curve = curve;
25 | this.clip = 0;
26 |
27 | this.props = new Set();
28 | this.cars = new Set();
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/Components/Util.ts:
--------------------------------------------------------------------------------
1 | import { Player } from './Player';
2 | import { Prop } from './Prop';
3 | import { Car } from './Car';
4 |
5 | export class Util {
6 | public static rumbleWidth(projectedRoadWidth: number, lanes: number): number {
7 | return projectedRoadWidth / Math.max(6, 2 * lanes);
8 | }
9 |
10 | public static laneMarkerWidth(projectedRoadWidth: number, lanes: number): number {
11 | return projectedRoadWidth / Math.max(32, 8 * lanes);
12 | }
13 |
14 | public static increase(start: number, increment: number, max: number): number { // with looping
15 | let result = start + increment;
16 | while (result >= max) {
17 | result -= max;
18 | }
19 |
20 | while (result < 0) {
21 | result += max;
22 | }
23 | return result;
24 | }
25 |
26 | public static accelerate(current: number, accel: number, delta: number): number {
27 | return current + (accel * delta);
28 | }
29 |
30 | public static interpolate(a: number, b: number, percent: number): number {
31 | return a + (b - a) * percent;
32 | }
33 |
34 | public static easeIn(a: number, b: number, percent: number): number {
35 | return a + (b - a) * Math.pow(percent, 2);
36 | }
37 |
38 | public static easeOut(a: number, b: number, percent: number): number {
39 | return a + (b - a) * (1 - Math.pow(1 - percent, 2));
40 | }
41 |
42 | public static easeInOut(a: number, b: number, percent: number): number {
43 | return a + (b - a) * ((-Math.cos(percent * Math.PI) / 2) + 0.5);
44 | }
45 |
46 | public static percentRemaining(n: number, total: number): number {
47 | return (n % total) / total;
48 | }
49 |
50 | public static toInt(obj: any, def: any): number {
51 | if (obj !== null) {
52 | const x = parseInt(obj, 10);
53 | if (!isNaN(x)) {
54 | return x;
55 | }
56 | }
57 |
58 | return Util.toInt(def, 0);
59 | }
60 |
61 | public static overlapPlayer(player: Player, target: Prop|Car): boolean {
62 | const playerCenter = player.scene.scale.gameSize.width / 2;
63 | const rect = new Phaser.Geom.Rectangle();
64 | const overlaps = target.sprite.getBounds(rect).contains(playerCenter - player.collisionRadius, 150)
65 | || target.sprite.getBounds(rect).contains(playerCenter + player.collisionRadius, 150);
66 |
67 | return overlaps;
68 | }
69 |
70 | public static overlapSprite(a: Phaser.GameObjects.Sprite, b: Phaser.GameObjects.Sprite): boolean {
71 | const aBounds = a.getBounds();
72 | const bBounds = b.getBounds();
73 |
74 | return Phaser.Geom.Rectangle.Overlaps(aBounds, bBounds);
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/src/config/GameConfig.ts:
--------------------------------------------------------------------------------
1 | import { PasuunaPlugin } from '@pinkkis/phaser-plugin-pasuuna';
2 |
3 | // phaser game config
4 | export const gameConfig: GameConfig = {
5 | type: Phaser.AUTO,
6 | scale: {
7 | parent: 'game-container',
8 | mode: Phaser.Scale.FIT,
9 | autoCenter: Phaser.Scale.CENTER_BOTH,
10 | width: 320,
11 | height: 180,
12 | },
13 | render: {
14 | pixelArt: true,
15 | },
16 | plugins: {
17 | global: [
18 | {
19 | key: 'PasuunaPlayerPlugin',
20 | plugin: PasuunaPlugin,
21 | start: true,
22 | mapping: 'pasuuna',
23 | },
24 | ] as any[],
25 | },
26 | };
27 |
--------------------------------------------------------------------------------
/src/config/GameSettings.ts:
--------------------------------------------------------------------------------
1 | class GameSettings {
2 | public roadWidth = 2200;
3 | public roadWidthClamp = 3;
4 | public segmentLength = 200;
5 | public rumbleLength = 6;
6 | public lanes = 3;
7 | public fieldOfView = 110;
8 | public cameraHeight = 2000;
9 | public cameraDepth = 1 / Math.tan( (this.fieldOfView / 2) * Math.PI / 180 );
10 | public projectYCompensation = 30;
11 | public drawDistance = 500;
12 | public fogDensity = 5;
13 | public maxSpeed = this.segmentLength * 9;
14 | public accel = this.maxSpeed / 50;
15 | public decel = -this.maxSpeed / 70;
16 | public screechDecel = -this.maxSpeed / 100;
17 | public breaking = -this.maxSpeed / 20;
18 | public offRoadDecel = -this.maxSpeed / 10;
19 | public offRoadLimit = this.maxSpeed / 4;
20 | public centrifugal = 0.2;
21 | public steerCompensation = 0.5;
22 | public maxTurn = 1;
23 | public turnResetMultiplier = 0.1;
24 | public cameraAngleResetMultiplier = 0.07;
25 | public totalCars = 30;
26 | }
27 |
28 | export const gameSettings = new GameSettings();
29 |
--------------------------------------------------------------------------------
/src/css/styles.css:
--------------------------------------------------------------------------------
1 | * {
2 | box-sizing: border-box;
3 | }
4 |
5 | html, body {
6 | margin: 0;
7 | padding: 0;
8 | }
9 |
10 | #game-container {
11 | margin: 0;
12 | padding: 0;
13 | max-height: 100vh;
14 | }
15 |
--------------------------------------------------------------------------------
/src/custom.d.ts:
--------------------------------------------------------------------------------
1 | declare module "worker-loader!*" {
2 | export default class WebpackWorker extends Worker {
3 | constructor();
4 | }
5 | }
6 |
7 | declare var require: any;
8 |
9 | interface Window {
10 | env?: any;
11 | }
12 |
13 | declare module '@pinkkis/phaser-plugin-pasuuna' {
14 | export class PasuunaPlugin extends Phaser.Plugins.BasePlugin {
15 | loadSongFromCache(key: string, autoplay: boolean): void;
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/src/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pinkkis/phaser-driving/b974b3a14ce06c23337fdddac78ceb8db9f39c47/src/favicon.ico
--------------------------------------------------------------------------------
/src/game.ts:
--------------------------------------------------------------------------------
1 | import './libs/GLTFLoader';
2 | import 'phaser';
3 | import '@csstools/normalize.css';
4 | import './css/styles.css';
5 | import { BootScene } from './scenes/BootScene';
6 | import { gameConfig } from './config/GameConfig';
7 | import { LoadScene } from './scenes/LoadScene';
8 | import { GameScene } from './scenes/GameScene';
9 | import { RaceUiScene } from './scenes/RaceUiScene';
10 |
11 | // set up game class, and global stuff
12 | export class PoisonVialGame extends Phaser.Game {
13 | private debug: boolean = false;
14 |
15 | constructor(config: GameConfig) {
16 | super(config);
17 | }
18 | }
19 |
20 | // start the game
21 | window.onload = () => {
22 | const game = new PoisonVialGame(gameConfig);
23 |
24 | // set up stats
25 | if (window.env.buildType !== 'production') {
26 | const Stats = require('stats-js');
27 | const stats = new Stats();
28 | stats.setMode(0); // 0: fps, 1: ms
29 | stats.domElement.style.position = 'absolute';
30 | stats.domElement.style.left = '0px';
31 | stats.domElement.style.top = '0px';
32 | document.body.appendChild(stats.domElement);
33 |
34 | game.events.on('prestep', () => stats.begin());
35 | game.events.on('postrender', () => stats.end());
36 | }
37 |
38 | game.scene.add('BootScene', BootScene, true);
39 | game.scene.add('LoadScene', LoadScene, false);
40 | game.scene.add('GameScene', GameScene, false);
41 | game.scene.add('RaceUiScene', RaceUiScene, false);
42 | };
43 |
--------------------------------------------------------------------------------
/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
9 |
10 | Phaser Driving
11 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/src/libs/GLTFLoader.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @author Rich Tibbett / https://github.com/richtr
3 | * @author mrdoob / http://mrdoob.com/
4 | * @author Tony Parisi / http://www.tonyparisi.com/
5 | * @author Takahiro / https://github.com/takahirox
6 | * @author Don McCurdy / https://www.donmccurdy.com
7 | */
8 |
9 | THREE.GLTFLoader = ( function () {
10 |
11 | function GLTFLoader( manager ) {
12 |
13 | this.manager = ( manager !== undefined ) ? manager : THREE.DefaultLoadingManager;
14 | this.dracoLoader = null;
15 |
16 | }
17 |
18 | GLTFLoader.prototype = {
19 |
20 | constructor: GLTFLoader,
21 |
22 | crossOrigin: 'anonymous',
23 |
24 | load: function ( url, onLoad, onProgress, onError ) {
25 |
26 | var scope = this;
27 |
28 | var resourcePath;
29 |
30 | if ( this.resourcePath !== undefined ) {
31 |
32 | resourcePath = this.resourcePath;
33 |
34 | } else if ( this.path !== undefined ) {
35 |
36 | resourcePath = this.path;
37 |
38 | } else {
39 |
40 | resourcePath = THREE.LoaderUtils.extractUrlBase( url );
41 |
42 | }
43 |
44 | // Tells the LoadingManager to track an extra item, which resolves after
45 | // the model is fully loaded. This means the count of items loaded will
46 | // be incorrect, but ensures manager.onLoad() does not fire early.
47 | scope.manager.itemStart( url );
48 |
49 | var _onError = function ( e ) {
50 |
51 | if ( onError ) {
52 |
53 | onError( e );
54 |
55 | } else {
56 |
57 | console.error( e );
58 |
59 | }
60 |
61 | scope.manager.itemError( url );
62 | scope.manager.itemEnd( url );
63 |
64 | };
65 |
66 | var loader = new THREE.FileLoader( scope.manager );
67 |
68 | loader.setPath( this.path );
69 | loader.setResponseType( 'arraybuffer' );
70 |
71 | loader.load( url, function ( data ) {
72 |
73 | try {
74 |
75 | scope.parse( data, resourcePath, function ( gltf ) {
76 |
77 | onLoad( gltf );
78 |
79 | scope.manager.itemEnd( url );
80 |
81 | }, _onError );
82 |
83 | } catch ( e ) {
84 |
85 | _onError( e );
86 |
87 | }
88 |
89 | }, onProgress, _onError );
90 |
91 | },
92 |
93 | setCrossOrigin: function ( value ) {
94 |
95 | this.crossOrigin = value;
96 | return this;
97 |
98 | },
99 |
100 | setPath: function ( value ) {
101 |
102 | this.path = value;
103 | return this;
104 |
105 | },
106 |
107 | setResourcePath: function ( value ) {
108 |
109 | this.resourcePath = value;
110 | return this;
111 |
112 | },
113 |
114 | setDRACOLoader: function ( dracoLoader ) {
115 |
116 | this.dracoLoader = dracoLoader;
117 | return this;
118 |
119 | },
120 |
121 | parse: function ( data, path, onLoad, onError ) {
122 |
123 | var content;
124 | var extensions = {};
125 |
126 | if ( typeof data === 'string' ) {
127 |
128 | content = data;
129 |
130 | } else {
131 |
132 | var magic = THREE.LoaderUtils.decodeText( new Uint8Array( data, 0, 4 ) );
133 |
134 | if ( magic === BINARY_EXTENSION_HEADER_MAGIC ) {
135 |
136 | try {
137 |
138 | extensions[ EXTENSIONS.KHR_BINARY_GLTF ] = new GLTFBinaryExtension( data );
139 |
140 | } catch ( error ) {
141 |
142 | if ( onError ) onError( error );
143 | return;
144 |
145 | }
146 |
147 | content = extensions[ EXTENSIONS.KHR_BINARY_GLTF ].content;
148 |
149 | } else {
150 |
151 | content = THREE.LoaderUtils.decodeText( new Uint8Array( data ) );
152 |
153 | }
154 |
155 | }
156 |
157 | var json = JSON.parse( content );
158 |
159 | if ( json.asset === undefined || json.asset.version[ 0 ] < 2 ) {
160 |
161 | if ( onError ) onError( new Error( 'THREE.GLTFLoader: Unsupported asset. glTF versions >=2.0 are supported. Use LegacyGLTFLoader instead.' ) );
162 | return;
163 |
164 | }
165 |
166 | if ( json.extensionsUsed ) {
167 |
168 | for ( var i = 0; i < json.extensionsUsed.length; ++ i ) {
169 |
170 | var extensionName = json.extensionsUsed[ i ];
171 | var extensionsRequired = json.extensionsRequired || [];
172 |
173 | switch ( extensionName ) {
174 |
175 | case EXTENSIONS.KHR_LIGHTS_PUNCTUAL:
176 | extensions[ extensionName ] = new GLTFLightsExtension( json );
177 | break;
178 |
179 | case EXTENSIONS.KHR_MATERIALS_UNLIT:
180 | extensions[ extensionName ] = new GLTFMaterialsUnlitExtension( json );
181 | break;
182 |
183 | case EXTENSIONS.KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS:
184 | extensions[ extensionName ] = new GLTFMaterialsPbrSpecularGlossinessExtension( json );
185 | break;
186 |
187 | case EXTENSIONS.KHR_DRACO_MESH_COMPRESSION:
188 | extensions[ extensionName ] = new GLTFDracoMeshCompressionExtension( json, this.dracoLoader );
189 | break;
190 |
191 | case EXTENSIONS.MSFT_TEXTURE_DDS:
192 | extensions[ EXTENSIONS.MSFT_TEXTURE_DDS ] = new GLTFTextureDDSExtension();
193 | break;
194 |
195 | case EXTENSIONS.KHR_TEXTURE_TRANSFORM:
196 | extensions[ EXTENSIONS.KHR_TEXTURE_TRANSFORM ] = new GLTFTextureTransformExtension( json );
197 | break;
198 |
199 | default:
200 |
201 | if ( extensionsRequired.indexOf( extensionName ) >= 0 ) {
202 |
203 | console.warn( 'THREE.GLTFLoader: Unknown extension "' + extensionName + '".' );
204 |
205 | }
206 |
207 | }
208 |
209 | }
210 |
211 | }
212 |
213 | var parser = new GLTFParser( json, extensions, {
214 |
215 | path: path || this.resourcePath || '',
216 | crossOrigin: this.crossOrigin,
217 | manager: this.manager
218 |
219 | } );
220 |
221 | parser.parse( onLoad, onError );
222 |
223 | }
224 |
225 | };
226 |
227 | /* GLTFREGISTRY */
228 |
229 | function GLTFRegistry() {
230 |
231 | var objects = {};
232 |
233 | return {
234 |
235 | get: function ( key ) {
236 |
237 | return objects[ key ];
238 |
239 | },
240 |
241 | add: function ( key, object ) {
242 |
243 | objects[ key ] = object;
244 |
245 | },
246 |
247 | remove: function ( key ) {
248 |
249 | delete objects[ key ];
250 |
251 | },
252 |
253 | removeAll: function () {
254 |
255 | objects = {};
256 |
257 | }
258 |
259 | };
260 |
261 | }
262 |
263 | /*********************************/
264 | /********** EXTENSIONS ***********/
265 | /*********************************/
266 |
267 | var EXTENSIONS = {
268 | KHR_BINARY_GLTF: 'KHR_binary_glTF',
269 | KHR_DRACO_MESH_COMPRESSION: 'KHR_draco_mesh_compression',
270 | KHR_LIGHTS_PUNCTUAL: 'KHR_lights_punctual',
271 | KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS: 'KHR_materials_pbrSpecularGlossiness',
272 | KHR_MATERIALS_UNLIT: 'KHR_materials_unlit',
273 | KHR_TEXTURE_TRANSFORM: 'KHR_texture_transform',
274 | MSFT_TEXTURE_DDS: 'MSFT_texture_dds'
275 | };
276 |
277 | /**
278 | * DDS Texture Extension
279 | *
280 | * Specification:
281 | * https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Vendor/MSFT_texture_dds
282 | *
283 | */
284 | function GLTFTextureDDSExtension() {
285 |
286 | if ( ! THREE.DDSLoader ) {
287 |
288 | throw new Error( 'THREE.GLTFLoader: Attempting to load .dds texture without importing THREE.DDSLoader' );
289 |
290 | }
291 |
292 | this.name = EXTENSIONS.MSFT_TEXTURE_DDS;
293 | this.ddsLoader = new THREE.DDSLoader();
294 |
295 | }
296 |
297 | /**
298 | * Lights Extension
299 | *
300 | * Specification: PENDING
301 | */
302 | function GLTFLightsExtension( json ) {
303 |
304 | this.name = EXTENSIONS.KHR_LIGHTS_PUNCTUAL;
305 |
306 | var extension = ( json.extensions && json.extensions[ EXTENSIONS.KHR_LIGHTS_PUNCTUAL ] ) || {};
307 | this.lightDefs = extension.lights || [];
308 |
309 | }
310 |
311 | GLTFLightsExtension.prototype.loadLight = function ( lightIndex ) {
312 |
313 | var lightDef = this.lightDefs[ lightIndex ];
314 | var lightNode;
315 |
316 | var color = new THREE.Color( 0xffffff );
317 | if ( lightDef.color !== undefined ) color.fromArray( lightDef.color );
318 |
319 | var range = lightDef.range !== undefined ? lightDef.range : 0;
320 |
321 | switch ( lightDef.type ) {
322 |
323 | case 'directional':
324 | lightNode = new THREE.DirectionalLight( color );
325 | lightNode.target.position.set( 0, 0, - 1 );
326 | lightNode.add( lightNode.target );
327 | break;
328 |
329 | case 'point':
330 | lightNode = new THREE.PointLight( color );
331 | lightNode.distance = range;
332 | break;
333 |
334 | case 'spot':
335 | lightNode = new THREE.SpotLight( color );
336 | lightNode.distance = range;
337 | // Handle spotlight properties.
338 | lightDef.spot = lightDef.spot || {};
339 | lightDef.spot.innerConeAngle = lightDef.spot.innerConeAngle !== undefined ? lightDef.spot.innerConeAngle : 0;
340 | lightDef.spot.outerConeAngle = lightDef.spot.outerConeAngle !== undefined ? lightDef.spot.outerConeAngle : Math.PI / 4.0;
341 | lightNode.angle = lightDef.spot.outerConeAngle;
342 | lightNode.penumbra = 1.0 - lightDef.spot.innerConeAngle / lightDef.spot.outerConeAngle;
343 | lightNode.target.position.set( 0, 0, - 1 );
344 | lightNode.add( lightNode.target );
345 | break;
346 |
347 | default:
348 | throw new Error( 'THREE.GLTFLoader: Unexpected light type, "' + lightDef.type + '".' );
349 |
350 | }
351 |
352 | // Some lights (e.g. spot) default to a position other than the origin. Reset the position
353 | // here, because node-level parsing will only override position if explicitly specified.
354 | lightNode.position.set( 0, 0, 0 );
355 |
356 | lightNode.decay = 2;
357 |
358 | if ( lightDef.intensity !== undefined ) lightNode.intensity = lightDef.intensity;
359 |
360 | lightNode.name = lightDef.name || ( 'light_' + lightIndex );
361 |
362 | return Promise.resolve( lightNode );
363 |
364 | };
365 |
366 | /**
367 | * Unlit Materials Extension (pending)
368 | *
369 | * PR: https://github.com/KhronosGroup/glTF/pull/1163
370 | */
371 | function GLTFMaterialsUnlitExtension( json ) {
372 |
373 | this.name = EXTENSIONS.KHR_MATERIALS_UNLIT;
374 |
375 | }
376 |
377 | GLTFMaterialsUnlitExtension.prototype.getMaterialType = function ( material ) {
378 |
379 | return THREE.MeshBasicMaterial;
380 |
381 | };
382 |
383 | GLTFMaterialsUnlitExtension.prototype.extendParams = function ( materialParams, material, parser ) {
384 |
385 | var pending = [];
386 |
387 | materialParams.color = new THREE.Color( 1.0, 1.0, 1.0 );
388 | materialParams.opacity = 1.0;
389 |
390 | var metallicRoughness = material.pbrMetallicRoughness;
391 |
392 | if ( metallicRoughness ) {
393 |
394 | if ( Array.isArray( metallicRoughness.baseColorFactor ) ) {
395 |
396 | var array = metallicRoughness.baseColorFactor;
397 |
398 | materialParams.color.fromArray( array );
399 | materialParams.opacity = array[ 3 ];
400 |
401 | }
402 |
403 | if ( metallicRoughness.baseColorTexture !== undefined ) {
404 |
405 | pending.push( parser.assignTexture( materialParams, 'map', metallicRoughness.baseColorTexture ) );
406 |
407 | }
408 |
409 | }
410 |
411 | return Promise.all( pending );
412 |
413 | };
414 |
415 | /* BINARY EXTENSION */
416 |
417 | var BINARY_EXTENSION_BUFFER_NAME = 'binary_glTF';
418 | var BINARY_EXTENSION_HEADER_MAGIC = 'glTF';
419 | var BINARY_EXTENSION_HEADER_LENGTH = 12;
420 | var BINARY_EXTENSION_CHUNK_TYPES = { JSON: 0x4E4F534A, BIN: 0x004E4942 };
421 |
422 | function GLTFBinaryExtension( data ) {
423 |
424 | this.name = EXTENSIONS.KHR_BINARY_GLTF;
425 | this.content = null;
426 | this.body = null;
427 |
428 | var headerView = new DataView( data, 0, BINARY_EXTENSION_HEADER_LENGTH );
429 |
430 | this.header = {
431 | magic: THREE.LoaderUtils.decodeText( new Uint8Array( data.slice( 0, 4 ) ) ),
432 | version: headerView.getUint32( 4, true ),
433 | length: headerView.getUint32( 8, true )
434 | };
435 |
436 | if ( this.header.magic !== BINARY_EXTENSION_HEADER_MAGIC ) {
437 |
438 | throw new Error( 'THREE.GLTFLoader: Unsupported glTF-Binary header.' );
439 |
440 | } else if ( this.header.version < 2.0 ) {
441 |
442 | throw new Error( 'THREE.GLTFLoader: Legacy binary file detected. Use LegacyGLTFLoader instead.' );
443 |
444 | }
445 |
446 | var chunkView = new DataView( data, BINARY_EXTENSION_HEADER_LENGTH );
447 | var chunkIndex = 0;
448 |
449 | while ( chunkIndex < chunkView.byteLength ) {
450 |
451 | var chunkLength = chunkView.getUint32( chunkIndex, true );
452 | chunkIndex += 4;
453 |
454 | var chunkType = chunkView.getUint32( chunkIndex, true );
455 | chunkIndex += 4;
456 |
457 | if ( chunkType === BINARY_EXTENSION_CHUNK_TYPES.JSON ) {
458 |
459 | var contentArray = new Uint8Array( data, BINARY_EXTENSION_HEADER_LENGTH + chunkIndex, chunkLength );
460 | this.content = THREE.LoaderUtils.decodeText( contentArray );
461 |
462 | } else if ( chunkType === BINARY_EXTENSION_CHUNK_TYPES.BIN ) {
463 |
464 | var byteOffset = BINARY_EXTENSION_HEADER_LENGTH + chunkIndex;
465 | this.body = data.slice( byteOffset, byteOffset + chunkLength );
466 |
467 | }
468 |
469 | // Clients must ignore chunks with unknown types.
470 |
471 | chunkIndex += chunkLength;
472 |
473 | }
474 |
475 | if ( this.content === null ) {
476 |
477 | throw new Error( 'THREE.GLTFLoader: JSON content not found.' );
478 |
479 | }
480 |
481 | }
482 |
483 | /**
484 | * DRACO Mesh Compression Extension
485 | *
486 | * Specification: https://github.com/KhronosGroup/glTF/pull/874
487 | */
488 | function GLTFDracoMeshCompressionExtension( json, dracoLoader ) {
489 |
490 | if ( ! dracoLoader ) {
491 |
492 | throw new Error( 'THREE.GLTFLoader: No DRACOLoader instance provided.' );
493 |
494 | }
495 |
496 | this.name = EXTENSIONS.KHR_DRACO_MESH_COMPRESSION;
497 | this.json = json;
498 | this.dracoLoader = dracoLoader;
499 |
500 | }
501 |
502 | GLTFDracoMeshCompressionExtension.prototype.decodePrimitive = function ( primitive, parser ) {
503 |
504 | var json = this.json;
505 | var dracoLoader = this.dracoLoader;
506 | var bufferViewIndex = primitive.extensions[ this.name ].bufferView;
507 | var gltfAttributeMap = primitive.extensions[ this.name ].attributes;
508 | var threeAttributeMap = {};
509 | var attributeNormalizedMap = {};
510 | var attributeTypeMap = {};
511 |
512 | for ( var attributeName in gltfAttributeMap ) {
513 |
514 | var threeAttributeName = ATTRIBUTES[ attributeName ] || attributeName.toLowerCase();
515 |
516 | threeAttributeMap[ threeAttributeName ] = gltfAttributeMap[ attributeName ];
517 |
518 | }
519 |
520 | for ( attributeName in primitive.attributes ) {
521 |
522 | var threeAttributeName = ATTRIBUTES[ attributeName ] || attributeName.toLowerCase();
523 |
524 | if ( gltfAttributeMap[ attributeName ] !== undefined ) {
525 |
526 | var accessorDef = json.accessors[ primitive.attributes[ attributeName ] ];
527 | var componentType = WEBGL_COMPONENT_TYPES[ accessorDef.componentType ];
528 |
529 | attributeTypeMap[ threeAttributeName ] = componentType;
530 | attributeNormalizedMap[ threeAttributeName ] = accessorDef.normalized === true;
531 |
532 | }
533 |
534 | }
535 |
536 | return parser.getDependency( 'bufferView', bufferViewIndex ).then( function ( bufferView ) {
537 |
538 | return new Promise( function ( resolve ) {
539 |
540 | dracoLoader.decodeDracoFile( bufferView, function ( geometry ) {
541 |
542 | for ( var attributeName in geometry.attributes ) {
543 |
544 | var attribute = geometry.attributes[ attributeName ];
545 | var normalized = attributeNormalizedMap[ attributeName ];
546 |
547 | if ( normalized !== undefined ) attribute.normalized = normalized;
548 |
549 | }
550 |
551 | resolve( geometry );
552 |
553 | }, threeAttributeMap, attributeTypeMap );
554 |
555 | } );
556 |
557 | } );
558 |
559 | };
560 |
561 | /**
562 | * Texture Transform Extension
563 | *
564 | * Specification:
565 | */
566 | function GLTFTextureTransformExtension( json ) {
567 |
568 | this.name = EXTENSIONS.KHR_TEXTURE_TRANSFORM;
569 |
570 | }
571 |
572 | GLTFTextureTransformExtension.prototype.extendTexture = function ( texture, transform ) {
573 |
574 | texture = texture.clone();
575 |
576 | if ( transform.offset !== undefined ) {
577 |
578 | texture.offset.fromArray( transform.offset );
579 |
580 | }
581 |
582 | if ( transform.rotation !== undefined ) {
583 |
584 | texture.rotation = transform.rotation;
585 |
586 | }
587 |
588 | if ( transform.scale !== undefined ) {
589 |
590 | texture.repeat.fromArray( transform.scale );
591 |
592 | }
593 |
594 | if ( transform.texCoord !== undefined ) {
595 |
596 | console.warn( 'THREE.GLTFLoader: Custom UV sets in "' + this.name + '" extension not yet supported.' );
597 |
598 | }
599 |
600 | texture.needsUpdate = true;
601 |
602 | return texture;
603 |
604 | };
605 |
606 | /**
607 | * Specular-Glossiness Extension
608 | *
609 | * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_pbrSpecularGlossiness
610 | */
611 | function GLTFMaterialsPbrSpecularGlossinessExtension() {
612 |
613 | return {
614 |
615 | name: EXTENSIONS.KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS,
616 |
617 | specularGlossinessParams: [
618 | 'color',
619 | 'map',
620 | 'lightMap',
621 | 'lightMapIntensity',
622 | 'aoMap',
623 | 'aoMapIntensity',
624 | 'emissive',
625 | 'emissiveIntensity',
626 | 'emissiveMap',
627 | 'bumpMap',
628 | 'bumpScale',
629 | 'normalMap',
630 | 'displacementMap',
631 | 'displacementScale',
632 | 'displacementBias',
633 | 'specularMap',
634 | 'specular',
635 | 'glossinessMap',
636 | 'glossiness',
637 | 'alphaMap',
638 | 'envMap',
639 | 'envMapIntensity',
640 | 'refractionRatio',
641 | ],
642 |
643 | getMaterialType: function () {
644 |
645 | return THREE.ShaderMaterial;
646 |
647 | },
648 |
649 | extendParams: function ( params, material, parser ) {
650 |
651 | var pbrSpecularGlossiness = material.extensions[ this.name ];
652 |
653 | var shader = THREE.ShaderLib[ 'standard' ];
654 |
655 | var uniforms = THREE.UniformsUtils.clone( shader.uniforms );
656 |
657 | var specularMapParsFragmentChunk = [
658 | '#ifdef USE_SPECULARMAP',
659 | ' uniform sampler2D specularMap;',
660 | '#endif'
661 | ].join( '\n' );
662 |
663 | var glossinessMapParsFragmentChunk = [
664 | '#ifdef USE_GLOSSINESSMAP',
665 | ' uniform sampler2D glossinessMap;',
666 | '#endif'
667 | ].join( '\n' );
668 |
669 | var specularMapFragmentChunk = [
670 | 'vec3 specularFactor = specular;',
671 | '#ifdef USE_SPECULARMAP',
672 | ' vec4 texelSpecular = texture2D( specularMap, vUv );',
673 | ' texelSpecular = sRGBToLinear( texelSpecular );',
674 | ' // reads channel RGB, compatible with a glTF Specular-Glossiness (RGBA) texture',
675 | ' specularFactor *= texelSpecular.rgb;',
676 | '#endif'
677 | ].join( '\n' );
678 |
679 | var glossinessMapFragmentChunk = [
680 | 'float glossinessFactor = glossiness;',
681 | '#ifdef USE_GLOSSINESSMAP',
682 | ' vec4 texelGlossiness = texture2D( glossinessMap, vUv );',
683 | ' // reads channel A, compatible with a glTF Specular-Glossiness (RGBA) texture',
684 | ' glossinessFactor *= texelGlossiness.a;',
685 | '#endif'
686 | ].join( '\n' );
687 |
688 | var lightPhysicalFragmentChunk = [
689 | 'PhysicalMaterial material;',
690 | 'material.diffuseColor = diffuseColor.rgb;',
691 | 'material.specularRoughness = clamp( 1.0 - glossinessFactor, 0.04, 1.0 );',
692 | 'material.specularColor = specularFactor.rgb;',
693 | ].join( '\n' );
694 |
695 | var fragmentShader = shader.fragmentShader
696 | .replace( 'uniform float roughness;', 'uniform vec3 specular;' )
697 | .replace( 'uniform float metalness;', 'uniform float glossiness;' )
698 | .replace( '#include ', specularMapParsFragmentChunk )
699 | .replace( '#include ', glossinessMapParsFragmentChunk )
700 | .replace( '#include ', specularMapFragmentChunk )
701 | .replace( '#include ', glossinessMapFragmentChunk )
702 | .replace( '#include ', lightPhysicalFragmentChunk );
703 |
704 | delete uniforms.roughness;
705 | delete uniforms.metalness;
706 | delete uniforms.roughnessMap;
707 | delete uniforms.metalnessMap;
708 |
709 | uniforms.specular = { value: new THREE.Color().setHex( 0x111111 ) };
710 | uniforms.glossiness = { value: 0.5 };
711 | uniforms.specularMap = { value: null };
712 | uniforms.glossinessMap = { value: null };
713 |
714 | params.vertexShader = shader.vertexShader;
715 | params.fragmentShader = fragmentShader;
716 | params.uniforms = uniforms;
717 | params.defines = { 'STANDARD': '' };
718 |
719 | params.color = new THREE.Color( 1.0, 1.0, 1.0 );
720 | params.opacity = 1.0;
721 |
722 | var pending = [];
723 |
724 | if ( Array.isArray( pbrSpecularGlossiness.diffuseFactor ) ) {
725 |
726 | var array = pbrSpecularGlossiness.diffuseFactor;
727 |
728 | params.color.fromArray( array );
729 | params.opacity = array[ 3 ];
730 |
731 | }
732 |
733 | if ( pbrSpecularGlossiness.diffuseTexture !== undefined ) {
734 |
735 | pending.push( parser.assignTexture( params, 'map', pbrSpecularGlossiness.diffuseTexture ) );
736 |
737 | }
738 |
739 | params.emissive = new THREE.Color( 0.0, 0.0, 0.0 );
740 | params.glossiness = pbrSpecularGlossiness.glossinessFactor !== undefined ? pbrSpecularGlossiness.glossinessFactor : 1.0;
741 | params.specular = new THREE.Color( 1.0, 1.0, 1.0 );
742 |
743 | if ( Array.isArray( pbrSpecularGlossiness.specularFactor ) ) {
744 |
745 | params.specular.fromArray( pbrSpecularGlossiness.specularFactor );
746 |
747 | }
748 |
749 | if ( pbrSpecularGlossiness.specularGlossinessTexture !== undefined ) {
750 |
751 | var specGlossMapDef = pbrSpecularGlossiness.specularGlossinessTexture;
752 | pending.push( parser.assignTexture( params, 'glossinessMap', specGlossMapDef ) );
753 | pending.push( parser.assignTexture( params, 'specularMap', specGlossMapDef ) );
754 |
755 | }
756 |
757 | return Promise.all( pending );
758 |
759 | },
760 |
761 | createMaterial: function ( params ) {
762 |
763 | // setup material properties based on MeshStandardMaterial for Specular-Glossiness
764 |
765 | var material = new THREE.ShaderMaterial( {
766 | defines: params.defines,
767 | vertexShader: params.vertexShader,
768 | fragmentShader: params.fragmentShader,
769 | uniforms: params.uniforms,
770 | fog: true,
771 | lights: true,
772 | opacity: params.opacity,
773 | transparent: params.transparent
774 | } );
775 |
776 | material.isGLTFSpecularGlossinessMaterial = true;
777 |
778 | material.color = params.color;
779 |
780 | material.map = params.map === undefined ? null : params.map;
781 |
782 | material.lightMap = null;
783 | material.lightMapIntensity = 1.0;
784 |
785 | material.aoMap = params.aoMap === undefined ? null : params.aoMap;
786 | material.aoMapIntensity = 1.0;
787 |
788 | material.emissive = params.emissive;
789 | material.emissiveIntensity = 1.0;
790 | material.emissiveMap = params.emissiveMap === undefined ? null : params.emissiveMap;
791 |
792 | material.bumpMap = params.bumpMap === undefined ? null : params.bumpMap;
793 | material.bumpScale = 1;
794 |
795 | material.normalMap = params.normalMap === undefined ? null : params.normalMap;
796 | if ( params.normalScale ) material.normalScale = params.normalScale;
797 |
798 | material.displacementMap = null;
799 | material.displacementScale = 1;
800 | material.displacementBias = 0;
801 |
802 | material.specularMap = params.specularMap === undefined ? null : params.specularMap;
803 | material.specular = params.specular;
804 |
805 | material.glossinessMap = params.glossinessMap === undefined ? null : params.glossinessMap;
806 | material.glossiness = params.glossiness;
807 |
808 | material.alphaMap = null;
809 |
810 | material.envMap = params.envMap === undefined ? null : params.envMap;
811 | material.envMapIntensity = 1.0;
812 |
813 | material.refractionRatio = 0.98;
814 |
815 | material.extensions.derivatives = true;
816 |
817 | return material;
818 |
819 | },
820 |
821 | /**
822 | * Clones a GLTFSpecularGlossinessMaterial instance. The ShaderMaterial.copy() method can
823 | * copy only properties it knows about or inherits, and misses many properties that would
824 | * normally be defined by MeshStandardMaterial.
825 | *
826 | * This method allows GLTFSpecularGlossinessMaterials to be cloned in the process of
827 | * loading a glTF model, but cloning later (e.g. by the user) would require these changes
828 | * AND also updating `.onBeforeRender` on the parent mesh.
829 | *
830 | * @param {THREE.ShaderMaterial} source
831 | * @return {THREE.ShaderMaterial}
832 | */
833 | cloneMaterial: function ( source ) {
834 |
835 | var target = source.clone();
836 |
837 | target.isGLTFSpecularGlossinessMaterial = true;
838 |
839 | var params = this.specularGlossinessParams;
840 |
841 | for ( var i = 0, il = params.length; i < il; i ++ ) {
842 |
843 | target[ params[ i ] ] = source[ params[ i ] ];
844 |
845 | }
846 |
847 | return target;
848 |
849 | },
850 |
851 | // Here's based on refreshUniformsCommon() and refreshUniformsStandard() in WebGLRenderer.
852 | refreshUniforms: function ( renderer, scene, camera, geometry, material, group ) {
853 |
854 | if ( material.isGLTFSpecularGlossinessMaterial !== true ) {
855 |
856 | return;
857 |
858 | }
859 |
860 | var uniforms = material.uniforms;
861 | var defines = material.defines;
862 |
863 | uniforms.opacity.value = material.opacity;
864 |
865 | uniforms.diffuse.value.copy( material.color );
866 | uniforms.emissive.value.copy( material.emissive ).multiplyScalar( material.emissiveIntensity );
867 |
868 | uniforms.map.value = material.map;
869 | uniforms.specularMap.value = material.specularMap;
870 | uniforms.alphaMap.value = material.alphaMap;
871 |
872 | uniforms.lightMap.value = material.lightMap;
873 | uniforms.lightMapIntensity.value = material.lightMapIntensity;
874 |
875 | uniforms.aoMap.value = material.aoMap;
876 | uniforms.aoMapIntensity.value = material.aoMapIntensity;
877 |
878 | // uv repeat and offset setting priorities
879 | // 1. color map
880 | // 2. specular map
881 | // 3. normal map
882 | // 4. bump map
883 | // 5. alpha map
884 | // 6. emissive map
885 |
886 | var uvScaleMap;
887 |
888 | if ( material.map ) {
889 |
890 | uvScaleMap = material.map;
891 |
892 | } else if ( material.specularMap ) {
893 |
894 | uvScaleMap = material.specularMap;
895 |
896 | } else if ( material.displacementMap ) {
897 |
898 | uvScaleMap = material.displacementMap;
899 |
900 | } else if ( material.normalMap ) {
901 |
902 | uvScaleMap = material.normalMap;
903 |
904 | } else if ( material.bumpMap ) {
905 |
906 | uvScaleMap = material.bumpMap;
907 |
908 | } else if ( material.glossinessMap ) {
909 |
910 | uvScaleMap = material.glossinessMap;
911 |
912 | } else if ( material.alphaMap ) {
913 |
914 | uvScaleMap = material.alphaMap;
915 |
916 | } else if ( material.emissiveMap ) {
917 |
918 | uvScaleMap = material.emissiveMap;
919 |
920 | }
921 |
922 | if ( uvScaleMap !== undefined ) {
923 |
924 | // backwards compatibility
925 | if ( uvScaleMap.isWebGLRenderTarget ) {
926 |
927 | uvScaleMap = uvScaleMap.texture;
928 |
929 | }
930 |
931 | if ( uvScaleMap.matrixAutoUpdate === true ) {
932 |
933 | uvScaleMap.updateMatrix();
934 |
935 | }
936 |
937 | uniforms.uvTransform.value.copy( uvScaleMap.matrix );
938 |
939 | }
940 |
941 | if ( material.envMap ) {
942 |
943 | uniforms.envMap.value = material.envMap;
944 | uniforms.envMapIntensity.value = material.envMapIntensity;
945 |
946 | // don't flip CubeTexture envMaps, flip everything else:
947 | // WebGLRenderTargetCube will be flipped for backwards compatibility
948 | // WebGLRenderTargetCube.texture will be flipped because it's a Texture and NOT a CubeTexture
949 | // this check must be handled differently, or removed entirely, if WebGLRenderTargetCube uses a CubeTexture in the future
950 | uniforms.flipEnvMap.value = material.envMap.isCubeTexture ? - 1 : 1;
951 |
952 | uniforms.reflectivity.value = material.reflectivity;
953 | uniforms.refractionRatio.value = material.refractionRatio;
954 |
955 | uniforms.maxMipLevel.value = renderer.properties.get( material.envMap ).__maxMipLevel;
956 |
957 | }
958 |
959 | uniforms.specular.value.copy( material.specular );
960 | uniforms.glossiness.value = material.glossiness;
961 |
962 | uniforms.glossinessMap.value = material.glossinessMap;
963 |
964 | uniforms.emissiveMap.value = material.emissiveMap;
965 | uniforms.bumpMap.value = material.bumpMap;
966 | uniforms.normalMap.value = material.normalMap;
967 |
968 | uniforms.displacementMap.value = material.displacementMap;
969 | uniforms.displacementScale.value = material.displacementScale;
970 | uniforms.displacementBias.value = material.displacementBias;
971 |
972 | if ( uniforms.glossinessMap.value !== null && defines.USE_GLOSSINESSMAP === undefined ) {
973 |
974 | defines.USE_GLOSSINESSMAP = '';
975 | // set USE_ROUGHNESSMAP to enable vUv
976 | defines.USE_ROUGHNESSMAP = '';
977 |
978 | }
979 |
980 | if ( uniforms.glossinessMap.value === null && defines.USE_GLOSSINESSMAP !== undefined ) {
981 |
982 | delete defines.USE_GLOSSINESSMAP;
983 | delete defines.USE_ROUGHNESSMAP;
984 |
985 | }
986 |
987 | }
988 |
989 | };
990 |
991 | }
992 |
993 | /*********************************/
994 | /********** INTERPOLATION ********/
995 | /*********************************/
996 |
997 | // Spline Interpolation
998 | // Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#appendix-c-spline-interpolation
999 | function GLTFCubicSplineInterpolant( parameterPositions, sampleValues, sampleSize, resultBuffer ) {
1000 |
1001 | THREE.Interpolant.call( this, parameterPositions, sampleValues, sampleSize, resultBuffer );
1002 |
1003 | }
1004 |
1005 | GLTFCubicSplineInterpolant.prototype = Object.create( THREE.Interpolant.prototype );
1006 | GLTFCubicSplineInterpolant.prototype.constructor = GLTFCubicSplineInterpolant;
1007 |
1008 | GLTFCubicSplineInterpolant.prototype.copySampleValue_ = function ( index ) {
1009 |
1010 | // Copies a sample value to the result buffer. See description of glTF
1011 | // CUBICSPLINE values layout in interpolate_() function below.
1012 |
1013 | var result = this.resultBuffer,
1014 | values = this.sampleValues,
1015 | valueSize = this.valueSize,
1016 | offset = index * valueSize * 3 + valueSize;
1017 |
1018 | for ( var i = 0; i !== valueSize; i ++ ) {
1019 |
1020 | result[ i ] = values[ offset + i ];
1021 |
1022 | }
1023 |
1024 | return result;
1025 |
1026 | };
1027 |
1028 | GLTFCubicSplineInterpolant.prototype.beforeStart_ = GLTFCubicSplineInterpolant.prototype.copySampleValue_;
1029 |
1030 | GLTFCubicSplineInterpolant.prototype.afterEnd_ = GLTFCubicSplineInterpolant.prototype.copySampleValue_;
1031 |
1032 | GLTFCubicSplineInterpolant.prototype.interpolate_ = function ( i1, t0, t, t1 ) {
1033 |
1034 | var result = this.resultBuffer;
1035 | var values = this.sampleValues;
1036 | var stride = this.valueSize;
1037 |
1038 | var stride2 = stride * 2;
1039 | var stride3 = stride * 3;
1040 |
1041 | var td = t1 - t0;
1042 |
1043 | var p = ( t - t0 ) / td;
1044 | var pp = p * p;
1045 | var ppp = pp * p;
1046 |
1047 | var offset1 = i1 * stride3;
1048 | var offset0 = offset1 - stride3;
1049 |
1050 | var s2 = - 2 * ppp + 3 * pp;
1051 | var s3 = ppp - pp;
1052 | var s0 = 1 - s2;
1053 | var s1 = s3 - pp + p;
1054 |
1055 | // Layout of keyframe output values for CUBICSPLINE animations:
1056 | // [ inTangent_1, splineVertex_1, outTangent_1, inTangent_2, splineVertex_2, ... ]
1057 | for ( var i = 0; i !== stride; i ++ ) {
1058 |
1059 | var p0 = values[ offset0 + i + stride ]; // splineVertex_k
1060 | var m0 = values[ offset0 + i + stride2 ] * td; // outTangent_k * (t_k+1 - t_k)
1061 | var p1 = values[ offset1 + i + stride ]; // splineVertex_k+1
1062 | var m1 = values[ offset1 + i ] * td; // inTangent_k+1 * (t_k+1 - t_k)
1063 |
1064 | result[ i ] = s0 * p0 + s1 * m0 + s2 * p1 + s3 * m1;
1065 |
1066 | }
1067 |
1068 | return result;
1069 |
1070 | };
1071 |
1072 | /*********************************/
1073 | /********** INTERNALS ************/
1074 | /*********************************/
1075 |
1076 | /* CONSTANTS */
1077 |
1078 | var WEBGL_CONSTANTS = {
1079 | FLOAT: 5126,
1080 | //FLOAT_MAT2: 35674,
1081 | FLOAT_MAT3: 35675,
1082 | FLOAT_MAT4: 35676,
1083 | FLOAT_VEC2: 35664,
1084 | FLOAT_VEC3: 35665,
1085 | FLOAT_VEC4: 35666,
1086 | LINEAR: 9729,
1087 | REPEAT: 10497,
1088 | SAMPLER_2D: 35678,
1089 | POINTS: 0,
1090 | LINES: 1,
1091 | LINE_LOOP: 2,
1092 | LINE_STRIP: 3,
1093 | TRIANGLES: 4,
1094 | TRIANGLE_STRIP: 5,
1095 | TRIANGLE_FAN: 6,
1096 | UNSIGNED_BYTE: 5121,
1097 | UNSIGNED_SHORT: 5123
1098 | };
1099 |
1100 | var WEBGL_TYPE = {
1101 | 5126: Number,
1102 | //35674: THREE.Matrix2,
1103 | 35675: THREE.Matrix3,
1104 | 35676: THREE.Matrix4,
1105 | 35664: THREE.Vector2,
1106 | 35665: THREE.Vector3,
1107 | 35666: THREE.Vector4,
1108 | 35678: THREE.Texture
1109 | };
1110 |
1111 | var WEBGL_COMPONENT_TYPES = {
1112 | 5120: Int8Array,
1113 | 5121: Uint8Array,
1114 | 5122: Int16Array,
1115 | 5123: Uint16Array,
1116 | 5125: Uint32Array,
1117 | 5126: Float32Array
1118 | };
1119 |
1120 | var WEBGL_FILTERS = {
1121 | 9728: THREE.NearestFilter,
1122 | 9729: THREE.LinearFilter,
1123 | 9984: THREE.NearestMipMapNearestFilter,
1124 | 9985: THREE.LinearMipMapNearestFilter,
1125 | 9986: THREE.NearestMipMapLinearFilter,
1126 | 9987: THREE.LinearMipMapLinearFilter
1127 | };
1128 |
1129 | var WEBGL_WRAPPINGS = {
1130 | 33071: THREE.ClampToEdgeWrapping,
1131 | 33648: THREE.MirroredRepeatWrapping,
1132 | 10497: THREE.RepeatWrapping
1133 | };
1134 |
1135 | var WEBGL_SIDES = {
1136 | 1028: THREE.BackSide, // Culling front
1137 | 1029: THREE.FrontSide // Culling back
1138 | //1032: THREE.NoSide // Culling front and back, what to do?
1139 | };
1140 |
1141 | var WEBGL_DEPTH_FUNCS = {
1142 | 512: THREE.NeverDepth,
1143 | 513: THREE.LessDepth,
1144 | 514: THREE.EqualDepth,
1145 | 515: THREE.LessEqualDepth,
1146 | 516: THREE.GreaterEqualDepth,
1147 | 517: THREE.NotEqualDepth,
1148 | 518: THREE.GreaterEqualDepth,
1149 | 519: THREE.AlwaysDepth
1150 | };
1151 |
1152 | var WEBGL_BLEND_EQUATIONS = {
1153 | 32774: THREE.AddEquation,
1154 | 32778: THREE.SubtractEquation,
1155 | 32779: THREE.ReverseSubtractEquation
1156 | };
1157 |
1158 | var WEBGL_BLEND_FUNCS = {
1159 | 0: THREE.ZeroFactor,
1160 | 1: THREE.OneFactor,
1161 | 768: THREE.SrcColorFactor,
1162 | 769: THREE.OneMinusSrcColorFactor,
1163 | 770: THREE.SrcAlphaFactor,
1164 | 771: THREE.OneMinusSrcAlphaFactor,
1165 | 772: THREE.DstAlphaFactor,
1166 | 773: THREE.OneMinusDstAlphaFactor,
1167 | 774: THREE.DstColorFactor,
1168 | 775: THREE.OneMinusDstColorFactor,
1169 | 776: THREE.SrcAlphaSaturateFactor
1170 | // The followings are not supported by Three.js yet
1171 | //32769: CONSTANT_COLOR,
1172 | //32770: ONE_MINUS_CONSTANT_COLOR,
1173 | //32771: CONSTANT_ALPHA,
1174 | //32772: ONE_MINUS_CONSTANT_COLOR
1175 | };
1176 |
1177 | var WEBGL_TYPE_SIZES = {
1178 | 'SCALAR': 1,
1179 | 'VEC2': 2,
1180 | 'VEC3': 3,
1181 | 'VEC4': 4,
1182 | 'MAT2': 4,
1183 | 'MAT3': 9,
1184 | 'MAT4': 16
1185 | };
1186 |
1187 | var ATTRIBUTES = {
1188 | POSITION: 'position',
1189 | NORMAL: 'normal',
1190 | TANGENT: 'tangent',
1191 | TEXCOORD_0: 'uv',
1192 | TEXCOORD_1: 'uv2',
1193 | COLOR_0: 'color',
1194 | WEIGHTS_0: 'skinWeight',
1195 | JOINTS_0: 'skinIndex',
1196 | };
1197 |
1198 | var PATH_PROPERTIES = {
1199 | scale: 'scale',
1200 | translation: 'position',
1201 | rotation: 'quaternion',
1202 | weights: 'morphTargetInfluences'
1203 | };
1204 |
1205 | var INTERPOLATION = {
1206 | CUBICSPLINE: undefined, // We use a custom interpolant (GLTFCubicSplineInterpolation) for CUBICSPLINE tracks. Each
1207 | // keyframe track will be initialized with a default interpolation type, then modified.
1208 | LINEAR: THREE.InterpolateLinear,
1209 | STEP: THREE.InterpolateDiscrete
1210 | };
1211 |
1212 | var STATES_ENABLES = {
1213 | 2884: 'CULL_FACE',
1214 | 2929: 'DEPTH_TEST',
1215 | 3042: 'BLEND',
1216 | 3089: 'SCISSOR_TEST',
1217 | 32823: 'POLYGON_OFFSET_FILL',
1218 | 32926: 'SAMPLE_ALPHA_TO_COVERAGE'
1219 | };
1220 |
1221 | var ALPHA_MODES = {
1222 | OPAQUE: 'OPAQUE',
1223 | MASK: 'MASK',
1224 | BLEND: 'BLEND'
1225 | };
1226 |
1227 | var MIME_TYPE_FORMATS = {
1228 | 'image/png': THREE.RGBAFormat,
1229 | 'image/jpeg': THREE.RGBFormat
1230 | };
1231 |
1232 | /* UTILITY FUNCTIONS */
1233 |
1234 | function resolveURL( url, path ) {
1235 |
1236 | // Invalid URL
1237 | if ( typeof url !== 'string' || url === '' ) return '';
1238 |
1239 | // Absolute URL http://,https://,//
1240 | if ( /^(https?:)?\/\//i.test( url ) ) return url;
1241 |
1242 | // Data URI
1243 | if ( /^data:.*,.*$/i.test( url ) ) return url;
1244 |
1245 | // Blob URL
1246 | if ( /^blob:.*$/i.test( url ) ) return url;
1247 |
1248 | // Relative URL
1249 | return path + url;
1250 |
1251 | }
1252 |
1253 | var defaultMaterial;
1254 |
1255 | /**
1256 | * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#default-material
1257 | */
1258 | function createDefaultMaterial() {
1259 |
1260 | defaultMaterial = defaultMaterial || new THREE.MeshStandardMaterial( {
1261 | color: 0xFFFFFF,
1262 | emissive: 0x000000,
1263 | metalness: 1,
1264 | roughness: 1,
1265 | transparent: false,
1266 | depthTest: true,
1267 | side: THREE.FrontSide
1268 | } );
1269 |
1270 | return defaultMaterial;
1271 |
1272 | }
1273 |
1274 | function addUnknownExtensionsToUserData( knownExtensions, object, objectDef ) {
1275 |
1276 | // Add unknown glTF extensions to an object's userData.
1277 |
1278 | for ( var name in objectDef.extensions ) {
1279 |
1280 | if ( knownExtensions[ name ] === undefined ) {
1281 |
1282 | object.userData.gltfExtensions = object.userData.gltfExtensions || {};
1283 | object.userData.gltfExtensions[ name ] = objectDef.extensions[ name ];
1284 |
1285 | }
1286 |
1287 | }
1288 |
1289 | }
1290 |
1291 | /**
1292 | * @param {THREE.Object3D|THREE.Material|THREE.BufferGeometry} object
1293 | * @param {GLTF.definition} gltfDef
1294 | */
1295 | function assignExtrasToUserData( object, gltfDef ) {
1296 |
1297 | if ( gltfDef.extras !== undefined ) {
1298 |
1299 | if ( typeof gltfDef.extras === 'object' ) {
1300 |
1301 | object.userData = gltfDef.extras;
1302 |
1303 | } else {
1304 |
1305 | console.warn( 'THREE.GLTFLoader: Ignoring primitive type .extras, ' + gltfDef.extras );
1306 |
1307 | }
1308 |
1309 | }
1310 |
1311 | }
1312 |
1313 | /**
1314 | * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#morph-targets
1315 | *
1316 | * @param {THREE.BufferGeometry} geometry
1317 | * @param {Array} targets
1318 | * @param {GLTFParser} parser
1319 | * @return {Promise}
1320 | */
1321 | function addMorphTargets( geometry, targets, parser ) {
1322 |
1323 | var hasMorphPosition = false;
1324 | var hasMorphNormal = false;
1325 |
1326 | for ( var i = 0, il = targets.length; i < il; i ++ ) {
1327 |
1328 | var target = targets[ i ];
1329 |
1330 | if ( target.POSITION !== undefined ) hasMorphPosition = true;
1331 | if ( target.NORMAL !== undefined ) hasMorphNormal = true;
1332 |
1333 | if ( hasMorphPosition && hasMorphNormal ) break;
1334 |
1335 | }
1336 |
1337 | if ( ! hasMorphPosition && ! hasMorphNormal ) return Promise.resolve( geometry );
1338 |
1339 | var pendingPositionAccessors = [];
1340 | var pendingNormalAccessors = [];
1341 |
1342 | for ( var i = 0, il = targets.length; i < il; i ++ ) {
1343 |
1344 | var target = targets[ i ];
1345 |
1346 | if ( hasMorphPosition ) {
1347 |
1348 | var pendingAccessor = target.POSITION !== undefined
1349 | ? parser.getDependency( 'accessor', target.POSITION )
1350 | : geometry.attributes.position;
1351 |
1352 | pendingPositionAccessors.push( pendingAccessor );
1353 |
1354 | }
1355 |
1356 | if ( hasMorphNormal ) {
1357 |
1358 | var pendingAccessor = target.NORMAL !== undefined
1359 | ? parser.getDependency( 'accessor', target.NORMAL )
1360 | : geometry.attributes.normal;
1361 |
1362 | pendingNormalAccessors.push( pendingAccessor );
1363 |
1364 | }
1365 |
1366 | }
1367 |
1368 | return Promise.all( [
1369 | Promise.all( pendingPositionAccessors ),
1370 | Promise.all( pendingNormalAccessors )
1371 | ] ).then( function ( accessors ) {
1372 |
1373 | var morphPositions = accessors[ 0 ];
1374 | var morphNormals = accessors[ 1 ];
1375 |
1376 | // Clone morph target accessors before modifying them.
1377 |
1378 | for ( var i = 0, il = morphPositions.length; i < il; i ++ ) {
1379 |
1380 | if ( geometry.attributes.position === morphPositions[ i ] ) continue;
1381 |
1382 | morphPositions[ i ] = cloneBufferAttribute( morphPositions[ i ] );
1383 |
1384 | }
1385 |
1386 | for ( var i = 0, il = morphNormals.length; i < il; i ++ ) {
1387 |
1388 | if ( geometry.attributes.normal === morphNormals[ i ] ) continue;
1389 |
1390 | morphNormals[ i ] = cloneBufferAttribute( morphNormals[ i ] );
1391 |
1392 | }
1393 |
1394 | for ( var i = 0, il = targets.length; i < il; i ++ ) {
1395 |
1396 | var target = targets[ i ];
1397 | var attributeName = 'morphTarget' + i;
1398 |
1399 | if ( hasMorphPosition ) {
1400 |
1401 | // Three.js morph position is absolute value. The formula is
1402 | // basePosition
1403 | // + weight0 * ( morphPosition0 - basePosition )
1404 | // + weight1 * ( morphPosition1 - basePosition )
1405 | // ...
1406 | // while the glTF one is relative
1407 | // basePosition
1408 | // + weight0 * glTFmorphPosition0
1409 | // + weight1 * glTFmorphPosition1
1410 | // ...
1411 | // then we need to convert from relative to absolute here.
1412 |
1413 | if ( target.POSITION !== undefined ) {
1414 |
1415 | var positionAttribute = morphPositions[ i ];
1416 | positionAttribute.name = attributeName;
1417 |
1418 | var position = geometry.attributes.position;
1419 |
1420 | for ( var j = 0, jl = positionAttribute.count; j < jl; j ++ ) {
1421 |
1422 | positionAttribute.setXYZ(
1423 | j,
1424 | positionAttribute.getX( j ) + position.getX( j ),
1425 | positionAttribute.getY( j ) + position.getY( j ),
1426 | positionAttribute.getZ( j ) + position.getZ( j )
1427 | );
1428 |
1429 | }
1430 |
1431 | }
1432 |
1433 | }
1434 |
1435 | if ( hasMorphNormal ) {
1436 |
1437 | // see target.POSITION's comment
1438 |
1439 | if ( target.NORMAL !== undefined ) {
1440 |
1441 | var normalAttribute = morphNormals[ i ];
1442 | normalAttribute.name = attributeName;
1443 |
1444 | var normal = geometry.attributes.normal;
1445 |
1446 | for ( var j = 0, jl = normalAttribute.count; j < jl; j ++ ) {
1447 |
1448 | normalAttribute.setXYZ(
1449 | j,
1450 | normalAttribute.getX( j ) + normal.getX( j ),
1451 | normalAttribute.getY( j ) + normal.getY( j ),
1452 | normalAttribute.getZ( j ) + normal.getZ( j )
1453 | );
1454 |
1455 | }
1456 |
1457 | }
1458 |
1459 | }
1460 |
1461 | }
1462 |
1463 | if ( hasMorphPosition ) geometry.morphAttributes.position = morphPositions;
1464 | if ( hasMorphNormal ) geometry.morphAttributes.normal = morphNormals;
1465 |
1466 | return geometry;
1467 |
1468 | } );
1469 |
1470 | }
1471 |
1472 | /**
1473 | * @param {THREE.Mesh} mesh
1474 | * @param {GLTF.Mesh} meshDef
1475 | */
1476 | function updateMorphTargets( mesh, meshDef ) {
1477 |
1478 | mesh.updateMorphTargets();
1479 |
1480 | if ( meshDef.weights !== undefined ) {
1481 |
1482 | for ( var i = 0, il = meshDef.weights.length; i < il; i ++ ) {
1483 |
1484 | mesh.morphTargetInfluences[ i ] = meshDef.weights[ i ];
1485 |
1486 | }
1487 |
1488 | }
1489 |
1490 | // .extras has user-defined data, so check that .extras.targetNames is an array.
1491 | if ( meshDef.extras && Array.isArray( meshDef.extras.targetNames ) ) {
1492 |
1493 | var targetNames = meshDef.extras.targetNames;
1494 |
1495 | if ( mesh.morphTargetInfluences.length === targetNames.length ) {
1496 |
1497 | mesh.morphTargetDictionary = {};
1498 |
1499 | for ( var i = 0, il = targetNames.length; i < il; i ++ ) {
1500 |
1501 | mesh.morphTargetDictionary[ targetNames[ i ] ] = i;
1502 |
1503 | }
1504 |
1505 | } else {
1506 |
1507 | console.warn( 'THREE.GLTFLoader: Invalid extras.targetNames length. Ignoring names.' );
1508 |
1509 | }
1510 |
1511 | }
1512 |
1513 | }
1514 | function isObjectEqual( a, b ) {
1515 |
1516 | if ( Object.keys( a ).length !== Object.keys( b ).length ) return false;
1517 |
1518 | for ( var key in a ) {
1519 |
1520 | if ( a[ key ] !== b[ key ] ) return false;
1521 |
1522 | }
1523 |
1524 | return true;
1525 |
1526 | }
1527 |
1528 | function createPrimitiveKey( primitiveDef ) {
1529 |
1530 | var dracoExtension = primitiveDef.extensions && primitiveDef.extensions[ EXTENSIONS.KHR_DRACO_MESH_COMPRESSION ];
1531 | var geometryKey;
1532 |
1533 | if ( dracoExtension ) {
1534 |
1535 | geometryKey = 'draco:' + dracoExtension.bufferView
1536 | + ':' + dracoExtension.indices
1537 | + ':' + createAttributesKey( dracoExtension.attributes );
1538 |
1539 | } else {
1540 |
1541 | geometryKey = primitiveDef.indices + ':' + createAttributesKey( primitiveDef.attributes ) + ':' + primitiveDef.mode;
1542 |
1543 | }
1544 |
1545 | return geometryKey;
1546 |
1547 | }
1548 |
1549 | function createAttributesKey( attributes ) {
1550 |
1551 | var attributesKey = '';
1552 |
1553 | var keys = Object.keys( attributes ).sort();
1554 |
1555 | for ( var i = 0, il = keys.length; i < il; i ++ ) {
1556 |
1557 | attributesKey += keys[ i ] + ':' + attributes[ keys[ i ] ] + ';';
1558 |
1559 | }
1560 |
1561 | return attributesKey;
1562 |
1563 | }
1564 |
1565 | function cloneBufferAttribute( attribute ) {
1566 |
1567 | if ( attribute.isInterleavedBufferAttribute ) {
1568 |
1569 | var count = attribute.count;
1570 | var itemSize = attribute.itemSize;
1571 | var array = attribute.array.slice( 0, count * itemSize );
1572 |
1573 | for ( var i = 0, j = 0; i < count; ++ i ) {
1574 |
1575 | array[ j ++ ] = attribute.getX( i );
1576 | if ( itemSize >= 2 ) array[ j ++ ] = attribute.getY( i );
1577 | if ( itemSize >= 3 ) array[ j ++ ] = attribute.getZ( i );
1578 | if ( itemSize >= 4 ) array[ j ++ ] = attribute.getW( i );
1579 |
1580 | }
1581 |
1582 | return new THREE.BufferAttribute( array, itemSize, attribute.normalized );
1583 |
1584 | }
1585 |
1586 | return attribute.clone();
1587 |
1588 | }
1589 |
1590 | /* GLTF PARSER */
1591 |
1592 | function GLTFParser( json, extensions, options ) {
1593 |
1594 | this.json = json || {};
1595 | this.extensions = extensions || {};
1596 | this.options = options || {};
1597 |
1598 | // loader object cache
1599 | this.cache = new GLTFRegistry();
1600 |
1601 | // BufferGeometry caching
1602 | this.primitiveCache = {};
1603 |
1604 | this.textureLoader = new THREE.TextureLoader( this.options.manager );
1605 | this.textureLoader.setCrossOrigin( this.options.crossOrigin );
1606 |
1607 | this.fileLoader = new THREE.FileLoader( this.options.manager );
1608 | this.fileLoader.setResponseType( 'arraybuffer' );
1609 |
1610 | }
1611 |
1612 | GLTFParser.prototype.parse = function ( onLoad, onError ) {
1613 |
1614 | var parser = this;
1615 | var json = this.json;
1616 | var extensions = this.extensions;
1617 |
1618 | // Clear the loader cache
1619 | this.cache.removeAll();
1620 |
1621 | // Mark the special nodes/meshes in json for efficient parse
1622 | this.markDefs();
1623 |
1624 | Promise.all( [
1625 |
1626 | this.getDependencies( 'scene' ),
1627 | this.getDependencies( 'animation' ),
1628 | this.getDependencies( 'camera' ),
1629 |
1630 | ] ).then( function ( dependencies ) {
1631 |
1632 | var result = {
1633 | scene: dependencies[ 0 ][ json.scene || 0 ],
1634 | scenes: dependencies[ 0 ],
1635 | animations: dependencies[ 1 ],
1636 | cameras: dependencies[ 2 ],
1637 | asset: json.asset,
1638 | parser: parser,
1639 | userData: {}
1640 | };
1641 |
1642 | addUnknownExtensionsToUserData( extensions, result, json );
1643 |
1644 | onLoad( result );
1645 |
1646 | } ).catch( onError );
1647 |
1648 | };
1649 |
1650 | /**
1651 | * Marks the special nodes/meshes in json for efficient parse.
1652 | */
1653 | GLTFParser.prototype.markDefs = function () {
1654 |
1655 | var nodeDefs = this.json.nodes || [];
1656 | var skinDefs = this.json.skins || [];
1657 | var meshDefs = this.json.meshes || [];
1658 |
1659 | var meshReferences = {};
1660 | var meshUses = {};
1661 |
1662 | // Nothing in the node definition indicates whether it is a Bone or an
1663 | // Object3D. Use the skins' joint references to mark bones.
1664 | for ( var skinIndex = 0, skinLength = skinDefs.length; skinIndex < skinLength; skinIndex ++ ) {
1665 |
1666 | var joints = skinDefs[ skinIndex ].joints;
1667 |
1668 | for ( var i = 0, il = joints.length; i < il; i ++ ) {
1669 |
1670 | nodeDefs[ joints[ i ] ].isBone = true;
1671 |
1672 | }
1673 |
1674 | }
1675 |
1676 | // Meshes can (and should) be reused by multiple nodes in a glTF asset. To
1677 | // avoid having more than one THREE.Mesh with the same name, count
1678 | // references and rename instances below.
1679 | //
1680 | // Example: CesiumMilkTruck sample model reuses "Wheel" meshes.
1681 | for ( var nodeIndex = 0, nodeLength = nodeDefs.length; nodeIndex < nodeLength; nodeIndex ++ ) {
1682 |
1683 | var nodeDef = nodeDefs[ nodeIndex ];
1684 |
1685 | if ( nodeDef.mesh !== undefined ) {
1686 |
1687 | if ( meshReferences[ nodeDef.mesh ] === undefined ) {
1688 |
1689 | meshReferences[ nodeDef.mesh ] = meshUses[ nodeDef.mesh ] = 0;
1690 |
1691 | }
1692 |
1693 | meshReferences[ nodeDef.mesh ] ++;
1694 |
1695 | // Nothing in the mesh definition indicates whether it is
1696 | // a SkinnedMesh or Mesh. Use the node's mesh reference
1697 | // to mark SkinnedMesh if node has skin.
1698 | if ( nodeDef.skin !== undefined ) {
1699 |
1700 | meshDefs[ nodeDef.mesh ].isSkinnedMesh = true;
1701 |
1702 | }
1703 |
1704 | }
1705 |
1706 | }
1707 |
1708 | this.json.meshReferences = meshReferences;
1709 | this.json.meshUses = meshUses;
1710 |
1711 | };
1712 |
1713 | /**
1714 | * Requests the specified dependency asynchronously, with caching.
1715 | * @param {string} type
1716 | * @param {number} index
1717 | * @return {Promise}
1718 | */
1719 | GLTFParser.prototype.getDependency = function ( type, index ) {
1720 |
1721 | var cacheKey = type + ':' + index;
1722 | var dependency = this.cache.get( cacheKey );
1723 |
1724 | if ( ! dependency ) {
1725 |
1726 | switch ( type ) {
1727 |
1728 | case 'scene':
1729 | dependency = this.loadScene( index );
1730 | break;
1731 |
1732 | case 'node':
1733 | dependency = this.loadNode( index );
1734 | break;
1735 |
1736 | case 'mesh':
1737 | dependency = this.loadMesh( index );
1738 | break;
1739 |
1740 | case 'accessor':
1741 | dependency = this.loadAccessor( index );
1742 | break;
1743 |
1744 | case 'bufferView':
1745 | dependency = this.loadBufferView( index );
1746 | break;
1747 |
1748 | case 'buffer':
1749 | dependency = this.loadBuffer( index );
1750 | break;
1751 |
1752 | case 'material':
1753 | dependency = this.loadMaterial( index );
1754 | break;
1755 |
1756 | case 'texture':
1757 | dependency = this.loadTexture( index );
1758 | break;
1759 |
1760 | case 'skin':
1761 | dependency = this.loadSkin( index );
1762 | break;
1763 |
1764 | case 'animation':
1765 | dependency = this.loadAnimation( index );
1766 | break;
1767 |
1768 | case 'camera':
1769 | dependency = this.loadCamera( index );
1770 | break;
1771 |
1772 | case 'light':
1773 | dependency = this.extensions[ EXTENSIONS.KHR_LIGHTS_PUNCTUAL ].loadLight( index );
1774 | break;
1775 |
1776 | default:
1777 | throw new Error( 'Unknown type: ' + type );
1778 |
1779 | }
1780 |
1781 | this.cache.add( cacheKey, dependency );
1782 |
1783 | }
1784 |
1785 | return dependency;
1786 |
1787 | };
1788 |
1789 | /**
1790 | * Requests all dependencies of the specified type asynchronously, with caching.
1791 | * @param {string} type
1792 | * @return {Promise>}
1793 | */
1794 | GLTFParser.prototype.getDependencies = function ( type ) {
1795 |
1796 | var dependencies = this.cache.get( type );
1797 |
1798 | if ( ! dependencies ) {
1799 |
1800 | var parser = this;
1801 | var defs = this.json[ type + ( type === 'mesh' ? 'es' : 's' ) ] || [];
1802 |
1803 | dependencies = Promise.all( defs.map( function ( def, index ) {
1804 |
1805 | return parser.getDependency( type, index );
1806 |
1807 | } ) );
1808 |
1809 | this.cache.add( type, dependencies );
1810 |
1811 | }
1812 |
1813 | return dependencies;
1814 |
1815 | };
1816 |
1817 | /**
1818 | * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#buffers-and-buffer-views
1819 | * @param {number} bufferIndex
1820 | * @return {Promise}
1821 | */
1822 | GLTFParser.prototype.loadBuffer = function ( bufferIndex ) {
1823 |
1824 | var bufferDef = this.json.buffers[ bufferIndex ];
1825 | var loader = this.fileLoader;
1826 |
1827 | if ( bufferDef.type && bufferDef.type !== 'arraybuffer' ) {
1828 |
1829 | throw new Error( 'THREE.GLTFLoader: ' + bufferDef.type + ' buffer type is not supported.' );
1830 |
1831 | }
1832 |
1833 | // If present, GLB container is required to be the first buffer.
1834 | if ( bufferDef.uri === undefined && bufferIndex === 0 ) {
1835 |
1836 | return Promise.resolve( this.extensions[ EXTENSIONS.KHR_BINARY_GLTF ].body );
1837 |
1838 | }
1839 |
1840 | var options = this.options;
1841 |
1842 | return new Promise( function ( resolve, reject ) {
1843 |
1844 | loader.load( resolveURL( bufferDef.uri, options.path ), resolve, undefined, function () {
1845 |
1846 | reject( new Error( 'THREE.GLTFLoader: Failed to load buffer "' + bufferDef.uri + '".' ) );
1847 |
1848 | } );
1849 |
1850 | } );
1851 |
1852 | };
1853 |
1854 | /**
1855 | * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#buffers-and-buffer-views
1856 | * @param {number} bufferViewIndex
1857 | * @return {Promise}
1858 | */
1859 | GLTFParser.prototype.loadBufferView = function ( bufferViewIndex ) {
1860 |
1861 | var bufferViewDef = this.json.bufferViews[ bufferViewIndex ];
1862 |
1863 | return this.getDependency( 'buffer', bufferViewDef.buffer ).then( function ( buffer ) {
1864 |
1865 | var byteLength = bufferViewDef.byteLength || 0;
1866 | var byteOffset = bufferViewDef.byteOffset || 0;
1867 | return buffer.slice( byteOffset, byteOffset + byteLength );
1868 |
1869 | } );
1870 |
1871 | };
1872 |
1873 | /**
1874 | * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#accessors
1875 | * @param {number} accessorIndex
1876 | * @return {Promise}
1877 | */
1878 | GLTFParser.prototype.loadAccessor = function ( accessorIndex ) {
1879 |
1880 | var parser = this;
1881 | var json = this.json;
1882 |
1883 | var accessorDef = this.json.accessors[ accessorIndex ];
1884 |
1885 | if ( accessorDef.bufferView === undefined && accessorDef.sparse === undefined ) {
1886 |
1887 | // Ignore empty accessors, which may be used to declare runtime
1888 | // information about attributes coming from another source (e.g. Draco
1889 | // compression extension).
1890 | return Promise.resolve( null );
1891 |
1892 | }
1893 |
1894 | var pendingBufferViews = [];
1895 |
1896 | if ( accessorDef.bufferView !== undefined ) {
1897 |
1898 | pendingBufferViews.push( this.getDependency( 'bufferView', accessorDef.bufferView ) );
1899 |
1900 | } else {
1901 |
1902 | pendingBufferViews.push( null );
1903 |
1904 | }
1905 |
1906 | if ( accessorDef.sparse !== undefined ) {
1907 |
1908 | pendingBufferViews.push( this.getDependency( 'bufferView', accessorDef.sparse.indices.bufferView ) );
1909 | pendingBufferViews.push( this.getDependency( 'bufferView', accessorDef.sparse.values.bufferView ) );
1910 |
1911 | }
1912 |
1913 | return Promise.all( pendingBufferViews ).then( function ( bufferViews ) {
1914 |
1915 | var bufferView = bufferViews[ 0 ];
1916 |
1917 | var itemSize = WEBGL_TYPE_SIZES[ accessorDef.type ];
1918 | var TypedArray = WEBGL_COMPONENT_TYPES[ accessorDef.componentType ];
1919 |
1920 | // For VEC3: itemSize is 3, elementBytes is 4, itemBytes is 12.
1921 | var elementBytes = TypedArray.BYTES_PER_ELEMENT;
1922 | var itemBytes = elementBytes * itemSize;
1923 | var byteOffset = accessorDef.byteOffset || 0;
1924 | var byteStride = accessorDef.bufferView !== undefined ? json.bufferViews[ accessorDef.bufferView ].byteStride : undefined;
1925 | var normalized = accessorDef.normalized === true;
1926 | var array, bufferAttribute;
1927 |
1928 | // The buffer is not interleaved if the stride is the item size in bytes.
1929 | if ( byteStride && byteStride !== itemBytes ) {
1930 |
1931 | var ibCacheKey = 'InterleavedBuffer:' + accessorDef.bufferView + ':' + accessorDef.componentType;
1932 | var ib = parser.cache.get( ibCacheKey );
1933 |
1934 | if ( ! ib ) {
1935 |
1936 | // Use the full buffer if it's interleaved.
1937 | array = new TypedArray( bufferView );
1938 |
1939 | // Integer parameters to IB/IBA are in array elements, not bytes.
1940 | ib = new THREE.InterleavedBuffer( array, byteStride / elementBytes );
1941 |
1942 | parser.cache.add( ibCacheKey, ib );
1943 |
1944 | }
1945 |
1946 | bufferAttribute = new THREE.InterleavedBufferAttribute( ib, itemSize, byteOffset / elementBytes, normalized );
1947 |
1948 | } else {
1949 |
1950 | if ( bufferView === null ) {
1951 |
1952 | array = new TypedArray( accessorDef.count * itemSize );
1953 |
1954 | } else {
1955 |
1956 | array = new TypedArray( bufferView, byteOffset, accessorDef.count * itemSize );
1957 |
1958 | }
1959 |
1960 | bufferAttribute = new THREE.BufferAttribute( array, itemSize, normalized );
1961 |
1962 | }
1963 |
1964 | // https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#sparse-accessors
1965 | if ( accessorDef.sparse !== undefined ) {
1966 |
1967 | var itemSizeIndices = WEBGL_TYPE_SIZES.SCALAR;
1968 | var TypedArrayIndices = WEBGL_COMPONENT_TYPES[ accessorDef.sparse.indices.componentType ];
1969 |
1970 | var byteOffsetIndices = accessorDef.sparse.indices.byteOffset || 0;
1971 | var byteOffsetValues = accessorDef.sparse.values.byteOffset || 0;
1972 |
1973 | var sparseIndices = new TypedArrayIndices( bufferViews[ 1 ], byteOffsetIndices, accessorDef.sparse.count * itemSizeIndices );
1974 | var sparseValues = new TypedArray( bufferViews[ 2 ], byteOffsetValues, accessorDef.sparse.count * itemSize );
1975 |
1976 | if ( bufferView !== null ) {
1977 |
1978 | // Avoid modifying the original ArrayBuffer, if the bufferView wasn't initialized with zeroes.
1979 | bufferAttribute.setArray( bufferAttribute.array.slice() );
1980 |
1981 | }
1982 |
1983 | for ( var i = 0, il = sparseIndices.length; i < il; i ++ ) {
1984 |
1985 | var index = sparseIndices[ i ];
1986 |
1987 | bufferAttribute.setX( index, sparseValues[ i * itemSize ] );
1988 | if ( itemSize >= 2 ) bufferAttribute.setY( index, sparseValues[ i * itemSize + 1 ] );
1989 | if ( itemSize >= 3 ) bufferAttribute.setZ( index, sparseValues[ i * itemSize + 2 ] );
1990 | if ( itemSize >= 4 ) bufferAttribute.setW( index, sparseValues[ i * itemSize + 3 ] );
1991 | if ( itemSize >= 5 ) throw new Error( 'THREE.GLTFLoader: Unsupported itemSize in sparse BufferAttribute.' );
1992 |
1993 | }
1994 |
1995 | }
1996 |
1997 | return bufferAttribute;
1998 |
1999 | } );
2000 |
2001 | };
2002 |
2003 | /**
2004 | * Specification: https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#textures
2005 | * @param {number} textureIndex
2006 | * @return {Promise}
2007 | */
2008 | GLTFParser.prototype.loadTexture = function ( textureIndex ) {
2009 |
2010 | var parser = this;
2011 | var json = this.json;
2012 | var options = this.options;
2013 | var textureLoader = this.textureLoader;
2014 |
2015 | var URL = window.URL || window.webkitURL;
2016 |
2017 | var textureDef = json.textures[ textureIndex ];
2018 |
2019 | var textureExtensions = textureDef.extensions || {};
2020 |
2021 | var source;
2022 |
2023 | if ( textureExtensions[ EXTENSIONS.MSFT_TEXTURE_DDS ] ) {
2024 |
2025 | source = json.images[ textureExtensions[ EXTENSIONS.MSFT_TEXTURE_DDS ].source ];
2026 |
2027 | } else {
2028 |
2029 | source = json.images[ textureDef.source ];
2030 |
2031 | }
2032 |
2033 | var sourceURI = source.uri;
2034 | var isObjectURL = false;
2035 |
2036 | if ( source.bufferView !== undefined ) {
2037 |
2038 | // Load binary image data from bufferView, if provided.
2039 |
2040 | sourceURI = parser.getDependency( 'bufferView', source.bufferView ).then( function ( bufferView ) {
2041 |
2042 | isObjectURL = true;
2043 | var blob = new Blob( [ bufferView ], { type: source.mimeType } );
2044 | sourceURI = URL.createObjectURL( blob );
2045 | return sourceURI;
2046 |
2047 | } );
2048 |
2049 | }
2050 |
2051 | return Promise.resolve( sourceURI ).then( function ( sourceURI ) {
2052 |
2053 | // Load Texture resource.
2054 |
2055 | var loader = THREE.Loader.Handlers.get( sourceURI );
2056 |
2057 | if ( ! loader ) {
2058 |
2059 | loader = textureExtensions[ EXTENSIONS.MSFT_TEXTURE_DDS ]
2060 | ? parser.extensions[ EXTENSIONS.MSFT_TEXTURE_DDS ].ddsLoader
2061 | : textureLoader;
2062 |
2063 | }
2064 |
2065 | return new Promise( function ( resolve, reject ) {
2066 |
2067 | loader.load( resolveURL( sourceURI, options.path ), resolve, undefined, reject );
2068 |
2069 | } );
2070 |
2071 | } ).then( function ( texture ) {
2072 |
2073 | // Clean up resources and configure Texture.
2074 |
2075 | if ( isObjectURL === true ) {
2076 |
2077 | URL.revokeObjectURL( sourceURI );
2078 |
2079 | }
2080 |
2081 | texture.flipY = false;
2082 |
2083 | if ( textureDef.name !== undefined ) texture.name = textureDef.name;
2084 |
2085 | // Ignore unknown mime types, like DDS files.
2086 | if ( source.mimeType in MIME_TYPE_FORMATS ) {
2087 |
2088 | texture.format = MIME_TYPE_FORMATS[ source.mimeType ];
2089 |
2090 | }
2091 |
2092 | var samplers = json.samplers || {};
2093 | var sampler = samplers[ textureDef.sampler ] || {};
2094 |
2095 | texture.magFilter = WEBGL_FILTERS[ sampler.magFilter ] || THREE.LinearFilter;
2096 | texture.minFilter = WEBGL_FILTERS[ sampler.minFilter ] || THREE.LinearMipMapLinearFilter;
2097 | texture.wrapS = WEBGL_WRAPPINGS[ sampler.wrapS ] || THREE.RepeatWrapping;
2098 | texture.wrapT = WEBGL_WRAPPINGS[ sampler.wrapT ] || THREE.RepeatWrapping;
2099 |
2100 | return texture;
2101 |
2102 | } );
2103 |
2104 | };
2105 |
2106 | /**
2107 | * Asynchronously assigns a texture to the given material parameters.
2108 | * @param {Object} materialParams
2109 | * @param {string} mapName
2110 | * @param {Object} mapDef
2111 | * @return {Promise}
2112 | */
2113 | GLTFParser.prototype.assignTexture = function ( materialParams, mapName, mapDef ) {
2114 |
2115 | var parser = this;
2116 |
2117 | return this.getDependency( 'texture', mapDef.index ).then( function ( texture ) {
2118 |
2119 | switch ( mapName ) {
2120 |
2121 | case 'aoMap':
2122 | case 'emissiveMap':
2123 | case 'metalnessMap':
2124 | case 'normalMap':
2125 | case 'roughnessMap':
2126 | texture.format = THREE.RGBFormat;
2127 | break;
2128 |
2129 | }
2130 |
2131 | if ( parser.extensions[ EXTENSIONS.KHR_TEXTURE_TRANSFORM ] ) {
2132 |
2133 | var transform = mapDef.extensions !== undefined ? mapDef.extensions[ EXTENSIONS.KHR_TEXTURE_TRANSFORM ] : undefined;
2134 |
2135 | if ( transform ) {
2136 |
2137 | texture = parser.extensions[ EXTENSIONS.KHR_TEXTURE_TRANSFORM ].extendTexture( texture, transform );
2138 |
2139 | }
2140 |
2141 | }
2142 |
2143 | materialParams[ mapName ] = texture;
2144 |
2145 | } );
2146 |
2147 | };
2148 |
2149 | /**
2150 | * Assigns final material to a Mesh, Line, or Points instance. The instance
2151 | * already has a material (generated from the glTF material options alone)
2152 | * but reuse of the same glTF material may require multiple threejs materials
2153 | * to accomodate different primitive types, defines, etc. New materials will
2154 | * be created if necessary, and reused from a cache.
2155 | * @param {THREE.Object3D} mesh Mesh, Line, or Points instance.
2156 | */
2157 | GLTFParser.prototype.assignFinalMaterial = function ( mesh ) {
2158 |
2159 | var geometry = mesh.geometry;
2160 | var material = mesh.material;
2161 | var extensions = this.extensions;
2162 |
2163 | var useVertexTangents = geometry.attributes.tangent !== undefined;
2164 | var useVertexColors = geometry.attributes.color !== undefined;
2165 | var useFlatShading = geometry.attributes.normal === undefined;
2166 | var useSkinning = mesh.isSkinnedMesh === true;
2167 | var useMorphTargets = Object.keys( geometry.morphAttributes ).length > 0;
2168 | var useMorphNormals = useMorphTargets && geometry.morphAttributes.normal !== undefined;
2169 |
2170 | if ( mesh.isPoints ) {
2171 |
2172 | var cacheKey = 'PointsMaterial:' + material.uuid;
2173 |
2174 | var pointsMaterial = this.cache.get( cacheKey );
2175 |
2176 | if ( ! pointsMaterial ) {
2177 |
2178 | pointsMaterial = new THREE.PointsMaterial();
2179 | THREE.Material.prototype.copy.call( pointsMaterial, material );
2180 | pointsMaterial.color.copy( material.color );
2181 | pointsMaterial.map = material.map;
2182 | pointsMaterial.lights = false; // PointsMaterial doesn't support lights yet
2183 |
2184 | this.cache.add( cacheKey, pointsMaterial );
2185 |
2186 | }
2187 |
2188 | material = pointsMaterial;
2189 |
2190 | } else if ( mesh.isLine ) {
2191 |
2192 | var cacheKey = 'LineBasicMaterial:' + material.uuid;
2193 |
2194 | var lineMaterial = this.cache.get( cacheKey );
2195 |
2196 | if ( ! lineMaterial ) {
2197 |
2198 | lineMaterial = new THREE.LineBasicMaterial();
2199 | THREE.Material.prototype.copy.call( lineMaterial, material );
2200 | lineMaterial.color.copy( material.color );
2201 | lineMaterial.lights = false; // LineBasicMaterial doesn't support lights yet
2202 |
2203 | this.cache.add( cacheKey, lineMaterial );
2204 |
2205 | }
2206 |
2207 | material = lineMaterial;
2208 |
2209 | }
2210 |
2211 | // Clone the material if it will be modified
2212 | if ( useVertexTangents || useVertexColors || useFlatShading || useSkinning || useMorphTargets ) {
2213 |
2214 | var cacheKey = 'ClonedMaterial:' + material.uuid + ':';
2215 |
2216 | if ( material.isGLTFSpecularGlossinessMaterial ) cacheKey += 'specular-glossiness:';
2217 | if ( useSkinning ) cacheKey += 'skinning:';
2218 | if ( useVertexTangents ) cacheKey += 'vertex-tangents:';
2219 | if ( useVertexColors ) cacheKey += 'vertex-colors:';
2220 | if ( useFlatShading ) cacheKey += 'flat-shading:';
2221 | if ( useMorphTargets ) cacheKey += 'morph-targets:';
2222 | if ( useMorphNormals ) cacheKey += 'morph-normals:';
2223 |
2224 | var cachedMaterial = this.cache.get( cacheKey );
2225 |
2226 | if ( ! cachedMaterial ) {
2227 |
2228 | cachedMaterial = material.isGLTFSpecularGlossinessMaterial
2229 | ? extensions[ EXTENSIONS.KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS ].cloneMaterial( material )
2230 | : material.clone();
2231 |
2232 | if ( useSkinning ) cachedMaterial.skinning = true;
2233 | if ( useVertexTangents ) cachedMaterial.vertexTangents = true;
2234 | if ( useVertexColors ) cachedMaterial.vertexColors = THREE.VertexColors;
2235 | if ( useFlatShading ) cachedMaterial.flatShading = true;
2236 | if ( useMorphTargets ) cachedMaterial.morphTargets = true;
2237 | if ( useMorphNormals ) cachedMaterial.morphNormals = true;
2238 |
2239 | this.cache.add( cacheKey, cachedMaterial );
2240 |
2241 | }
2242 |
2243 | material = cachedMaterial;
2244 |
2245 | }
2246 |
2247 | // workarounds for mesh and geometry
2248 |
2249 | if ( material.aoMap && geometry.attributes.uv2 === undefined && geometry.attributes.uv !== undefined ) {
2250 |
2251 | console.log( 'THREE.GLTFLoader: Duplicating UVs to support aoMap.' );
2252 | geometry.addAttribute( 'uv2', new THREE.BufferAttribute( geometry.attributes.uv.array, 2 ) );
2253 |
2254 | }
2255 |
2256 | if ( material.isGLTFSpecularGlossinessMaterial ) {
2257 |
2258 | // for GLTFSpecularGlossinessMaterial(ShaderMaterial) uniforms runtime update
2259 | mesh.onBeforeRender = extensions[ EXTENSIONS.KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS ].refreshUniforms;
2260 |
2261 | }
2262 |
2263 | mesh.material = material;
2264 |
2265 | };
2266 |
2267 | /**
2268 | * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#materials
2269 | * @param {number} materialIndex
2270 | * @return {Promise}
2271 | */
2272 | GLTFParser.prototype.loadMaterial = function ( materialIndex ) {
2273 |
2274 | var parser = this;
2275 | var json = this.json;
2276 | var extensions = this.extensions;
2277 | var materialDef = json.materials[ materialIndex ];
2278 |
2279 | var materialType;
2280 | var materialParams = {};
2281 | var materialExtensions = materialDef.extensions || {};
2282 |
2283 | var pending = [];
2284 |
2285 | if ( materialExtensions[ EXTENSIONS.KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS ] ) {
2286 |
2287 | var sgExtension = extensions[ EXTENSIONS.KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS ];
2288 | materialType = sgExtension.getMaterialType( materialDef );
2289 | pending.push( sgExtension.extendParams( materialParams, materialDef, parser ) );
2290 |
2291 | } else if ( materialExtensions[ EXTENSIONS.KHR_MATERIALS_UNLIT ] ) {
2292 |
2293 | var kmuExtension = extensions[ EXTENSIONS.KHR_MATERIALS_UNLIT ];
2294 | materialType = kmuExtension.getMaterialType( materialDef );
2295 | pending.push( kmuExtension.extendParams( materialParams, materialDef, parser ) );
2296 |
2297 | } else {
2298 |
2299 | // Specification:
2300 | // https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#metallic-roughness-material
2301 |
2302 | materialType = THREE.MeshStandardMaterial;
2303 |
2304 | var metallicRoughness = materialDef.pbrMetallicRoughness || {};
2305 |
2306 | materialParams.color = new THREE.Color( 1.0, 1.0, 1.0 );
2307 | materialParams.opacity = 1.0;
2308 |
2309 | if ( Array.isArray( metallicRoughness.baseColorFactor ) ) {
2310 |
2311 | var array = metallicRoughness.baseColorFactor;
2312 |
2313 | materialParams.color.fromArray( array );
2314 | materialParams.opacity = array[ 3 ];
2315 |
2316 | }
2317 |
2318 | if ( metallicRoughness.baseColorTexture !== undefined ) {
2319 |
2320 | pending.push( parser.assignTexture( materialParams, 'map', metallicRoughness.baseColorTexture ) );
2321 |
2322 | }
2323 |
2324 | materialParams.metalness = metallicRoughness.metallicFactor !== undefined ? metallicRoughness.metallicFactor : 1.0;
2325 | materialParams.roughness = metallicRoughness.roughnessFactor !== undefined ? metallicRoughness.roughnessFactor : 1.0;
2326 |
2327 | if ( metallicRoughness.metallicRoughnessTexture !== undefined ) {
2328 |
2329 | pending.push( parser.assignTexture( materialParams, 'metalnessMap', metallicRoughness.metallicRoughnessTexture ) );
2330 | pending.push( parser.assignTexture( materialParams, 'roughnessMap', metallicRoughness.metallicRoughnessTexture ) );
2331 |
2332 | }
2333 |
2334 | }
2335 |
2336 | if ( materialDef.doubleSided === true ) {
2337 |
2338 | materialParams.side = THREE.DoubleSide;
2339 |
2340 | }
2341 |
2342 | var alphaMode = materialDef.alphaMode || ALPHA_MODES.OPAQUE;
2343 |
2344 | if ( alphaMode === ALPHA_MODES.BLEND ) {
2345 |
2346 | materialParams.transparent = true;
2347 |
2348 | } else {
2349 |
2350 | materialParams.transparent = false;
2351 |
2352 | if ( alphaMode === ALPHA_MODES.MASK ) {
2353 |
2354 | materialParams.alphaTest = materialDef.alphaCutoff !== undefined ? materialDef.alphaCutoff : 0.5;
2355 |
2356 | }
2357 |
2358 | }
2359 |
2360 | if ( materialDef.normalTexture !== undefined && materialType !== THREE.MeshBasicMaterial ) {
2361 |
2362 | pending.push( parser.assignTexture( materialParams, 'normalMap', materialDef.normalTexture ) );
2363 |
2364 | materialParams.normalScale = new THREE.Vector2( 1, 1 );
2365 |
2366 | if ( materialDef.normalTexture.scale !== undefined ) {
2367 |
2368 | materialParams.normalScale.set( materialDef.normalTexture.scale, materialDef.normalTexture.scale );
2369 |
2370 | }
2371 |
2372 | }
2373 |
2374 | if ( materialDef.occlusionTexture !== undefined && materialType !== THREE.MeshBasicMaterial ) {
2375 |
2376 | pending.push( parser.assignTexture( materialParams, 'aoMap', materialDef.occlusionTexture ) );
2377 |
2378 | if ( materialDef.occlusionTexture.strength !== undefined ) {
2379 |
2380 | materialParams.aoMapIntensity = materialDef.occlusionTexture.strength;
2381 |
2382 | }
2383 |
2384 | }
2385 |
2386 | if ( materialDef.emissiveFactor !== undefined && materialType !== THREE.MeshBasicMaterial ) {
2387 |
2388 | materialParams.emissive = new THREE.Color().fromArray( materialDef.emissiveFactor );
2389 |
2390 | }
2391 |
2392 | if ( materialDef.emissiveTexture !== undefined && materialType !== THREE.MeshBasicMaterial ) {
2393 |
2394 | pending.push( parser.assignTexture( materialParams, 'emissiveMap', materialDef.emissiveTexture ) );
2395 |
2396 | }
2397 |
2398 | return Promise.all( pending ).then( function () {
2399 |
2400 | var material;
2401 |
2402 | if ( materialType === THREE.ShaderMaterial ) {
2403 |
2404 | material = extensions[ EXTENSIONS.KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS ].createMaterial( materialParams );
2405 |
2406 | } else {
2407 |
2408 | material = new materialType( materialParams );
2409 |
2410 | }
2411 |
2412 | if ( materialDef.name !== undefined ) material.name = materialDef.name;
2413 |
2414 | // baseColorTexture, emissiveTexture, and specularGlossinessTexture use sRGB encoding.
2415 | if ( material.map ) material.map.encoding = THREE.sRGBEncoding;
2416 | if ( material.emissiveMap ) material.emissiveMap.encoding = THREE.sRGBEncoding;
2417 | if ( material.specularMap ) material.specularMap.encoding = THREE.sRGBEncoding;
2418 |
2419 | assignExtrasToUserData( material, materialDef );
2420 |
2421 | if ( materialDef.extensions ) addUnknownExtensionsToUserData( extensions, material, materialDef );
2422 |
2423 | return material;
2424 |
2425 | } );
2426 |
2427 | };
2428 |
2429 | /**
2430 | * @param {THREE.BufferGeometry} geometry
2431 | * @param {GLTF.Primitive} primitiveDef
2432 | * @param {GLTFParser} parser
2433 | * @return {Promise}
2434 | */
2435 | function addPrimitiveAttributes( geometry, primitiveDef, parser ) {
2436 |
2437 | var attributes = primitiveDef.attributes;
2438 |
2439 | var pending = [];
2440 |
2441 | function assignAttributeAccessor( accessorIndex, attributeName ) {
2442 |
2443 | return parser.getDependency( 'accessor', accessorIndex )
2444 | .then( function ( accessor ) {
2445 |
2446 | geometry.addAttribute( attributeName, accessor );
2447 |
2448 | } );
2449 |
2450 | }
2451 |
2452 | for ( var gltfAttributeName in attributes ) {
2453 |
2454 | var threeAttributeName = ATTRIBUTES[ gltfAttributeName ] || gltfAttributeName.toLowerCase();
2455 |
2456 | // Skip attributes already provided by e.g. Draco extension.
2457 | if ( threeAttributeName in geometry.attributes ) continue;
2458 |
2459 | pending.push( assignAttributeAccessor( attributes[ gltfAttributeName ], threeAttributeName ) );
2460 |
2461 | }
2462 |
2463 | if ( primitiveDef.indices !== undefined && ! geometry.index ) {
2464 |
2465 | var accessor = parser.getDependency( 'accessor', primitiveDef.indices ).then( function ( accessor ) {
2466 |
2467 | geometry.setIndex( accessor );
2468 |
2469 | } );
2470 |
2471 | pending.push( accessor );
2472 |
2473 | }
2474 |
2475 | assignExtrasToUserData( geometry, primitiveDef );
2476 |
2477 | return Promise.all( pending ).then( function () {
2478 |
2479 | return primitiveDef.targets !== undefined
2480 | ? addMorphTargets( geometry, primitiveDef.targets, parser )
2481 | : geometry;
2482 |
2483 | } );
2484 |
2485 | }
2486 |
2487 | /**
2488 | * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#geometry
2489 | *
2490 | * Creates BufferGeometries from primitives.
2491 | *
2492 | * @param {Array} primitives
2493 | * @return {Promise>}
2494 | */
2495 | GLTFParser.prototype.loadGeometries = function ( primitives ) {
2496 |
2497 | var parser = this;
2498 | var extensions = this.extensions;
2499 | var cache = this.primitiveCache;
2500 |
2501 | function createDracoPrimitive( primitive ) {
2502 |
2503 | return extensions[ EXTENSIONS.KHR_DRACO_MESH_COMPRESSION ]
2504 | .decodePrimitive( primitive, parser )
2505 | .then( function ( geometry ) {
2506 |
2507 | return addPrimitiveAttributes( geometry, primitive, parser );
2508 |
2509 | } );
2510 |
2511 | }
2512 |
2513 | var pending = [];
2514 |
2515 | for ( var i = 0, il = primitives.length; i < il; i ++ ) {
2516 |
2517 | var primitive = primitives[ i ];
2518 | var cacheKey = createPrimitiveKey( primitive );
2519 |
2520 | // See if we've already created this geometry
2521 | var cached = cache[ cacheKey ];
2522 |
2523 | if ( cached ) {
2524 |
2525 | // Use the cached geometry if it exists
2526 | pending.push( cached.promise );
2527 |
2528 | } else {
2529 |
2530 | var geometryPromise;
2531 |
2532 | if ( primitive.extensions && primitive.extensions[ EXTENSIONS.KHR_DRACO_MESH_COMPRESSION ] ) {
2533 |
2534 | // Use DRACO geometry if available
2535 | geometryPromise = createDracoPrimitive( primitive );
2536 |
2537 | } else {
2538 |
2539 | // Otherwise create a new geometry
2540 | geometryPromise = addPrimitiveAttributes( new THREE.BufferGeometry(), primitive, parser );
2541 |
2542 | }
2543 |
2544 | // Cache this geometry
2545 | cache[ cacheKey ] = { primitive: primitive, promise: geometryPromise };
2546 |
2547 | pending.push( geometryPromise );
2548 |
2549 | }
2550 |
2551 | }
2552 |
2553 | return Promise.all( pending );
2554 |
2555 | };
2556 |
2557 | /**
2558 | * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#meshes
2559 | * @param {number} meshIndex
2560 | * @return {Promise}
2561 | */
2562 | GLTFParser.prototype.loadMesh = function ( meshIndex ) {
2563 |
2564 | var parser = this;
2565 | var json = this.json;
2566 | var extensions = this.extensions;
2567 |
2568 | var meshDef = json.meshes[ meshIndex ];
2569 | var primitives = meshDef.primitives;
2570 |
2571 | var pending = [];
2572 |
2573 | for ( var i = 0, il = primitives.length; i < il; i ++ ) {
2574 |
2575 | var material = primitives[ i ].material === undefined
2576 | ? createDefaultMaterial()
2577 | : this.getDependency( 'material', primitives[ i ].material );
2578 |
2579 | pending.push( material );
2580 |
2581 | }
2582 |
2583 | return Promise.all( pending ).then( function ( originalMaterials ) {
2584 |
2585 | return parser.loadGeometries( primitives ).then( function ( geometries ) {
2586 |
2587 | var meshes = [];
2588 |
2589 | for ( var i = 0, il = geometries.length; i < il; i ++ ) {
2590 |
2591 | var geometry = geometries[ i ];
2592 | var primitive = primitives[ i ];
2593 |
2594 | // 1. create Mesh
2595 |
2596 | var mesh;
2597 |
2598 | var material = originalMaterials[ i ];
2599 |
2600 | if ( primitive.mode === WEBGL_CONSTANTS.TRIANGLES ||
2601 | primitive.mode === WEBGL_CONSTANTS.TRIANGLE_STRIP ||
2602 | primitive.mode === WEBGL_CONSTANTS.TRIANGLE_FAN ||
2603 | primitive.mode === undefined ) {
2604 |
2605 | // .isSkinnedMesh isn't in glTF spec. See .markDefs()
2606 | mesh = meshDef.isSkinnedMesh === true
2607 | ? new THREE.SkinnedMesh( geometry, material )
2608 | : new THREE.Mesh( geometry, material );
2609 |
2610 | if ( mesh.isSkinnedMesh === true ) mesh.normalizeSkinWeights(); // #15319
2611 |
2612 | if ( primitive.mode === WEBGL_CONSTANTS.TRIANGLE_STRIP ) {
2613 |
2614 | mesh.drawMode = THREE.TriangleStripDrawMode;
2615 |
2616 | } else if ( primitive.mode === WEBGL_CONSTANTS.TRIANGLE_FAN ) {
2617 |
2618 | mesh.drawMode = THREE.TriangleFanDrawMode;
2619 |
2620 | }
2621 |
2622 | } else if ( primitive.mode === WEBGL_CONSTANTS.LINES ) {
2623 |
2624 | mesh = new THREE.LineSegments( geometry, material );
2625 |
2626 | } else if ( primitive.mode === WEBGL_CONSTANTS.LINE_STRIP ) {
2627 |
2628 | mesh = new THREE.Line( geometry, material );
2629 |
2630 | } else if ( primitive.mode === WEBGL_CONSTANTS.LINE_LOOP ) {
2631 |
2632 | mesh = new THREE.LineLoop( geometry, material );
2633 |
2634 | } else if ( primitive.mode === WEBGL_CONSTANTS.POINTS ) {
2635 |
2636 | mesh = new THREE.Points( geometry, material );
2637 |
2638 | } else {
2639 |
2640 | throw new Error( 'THREE.GLTFLoader: Primitive mode unsupported: ' + primitive.mode );
2641 |
2642 | }
2643 |
2644 | if ( Object.keys( mesh.geometry.morphAttributes ).length > 0 ) {
2645 |
2646 | updateMorphTargets( mesh, meshDef );
2647 |
2648 | }
2649 |
2650 | mesh.name = meshDef.name || ( 'mesh_' + meshIndex );
2651 |
2652 | if ( geometries.length > 1 ) mesh.name += '_' + i;
2653 |
2654 | assignExtrasToUserData( mesh, meshDef );
2655 |
2656 | parser.assignFinalMaterial( mesh );
2657 |
2658 | meshes.push( mesh );
2659 |
2660 | }
2661 |
2662 | if ( meshes.length === 1 ) {
2663 |
2664 | return meshes[ 0 ];
2665 |
2666 | }
2667 |
2668 | var group = new THREE.Group();
2669 |
2670 | for ( var i = 0, il = meshes.length; i < il; i ++ ) {
2671 |
2672 | group.add( meshes[ i ] );
2673 |
2674 | }
2675 |
2676 | return group;
2677 |
2678 | } );
2679 |
2680 | } );
2681 |
2682 | };
2683 |
2684 | /**
2685 | * Specification: https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#cameras
2686 | * @param {number} cameraIndex
2687 | * @return {Promise}
2688 | */
2689 | GLTFParser.prototype.loadCamera = function ( cameraIndex ) {
2690 |
2691 | var camera;
2692 | var cameraDef = this.json.cameras[ cameraIndex ];
2693 | var params = cameraDef[ cameraDef.type ];
2694 |
2695 | if ( ! params ) {
2696 |
2697 | console.warn( 'THREE.GLTFLoader: Missing camera parameters.' );
2698 | return;
2699 |
2700 | }
2701 |
2702 | if ( cameraDef.type === 'perspective' ) {
2703 |
2704 | camera = new THREE.PerspectiveCamera( THREE.Math.radToDeg( params.yfov ), params.aspectRatio || 1, params.znear || 1, params.zfar || 2e6 );
2705 |
2706 | } else if ( cameraDef.type === 'orthographic' ) {
2707 |
2708 | camera = new THREE.OrthographicCamera( params.xmag / - 2, params.xmag / 2, params.ymag / 2, params.ymag / - 2, params.znear, params.zfar );
2709 |
2710 | }
2711 |
2712 | if ( cameraDef.name !== undefined ) camera.name = cameraDef.name;
2713 |
2714 | assignExtrasToUserData( camera, cameraDef );
2715 |
2716 | return Promise.resolve( camera );
2717 |
2718 | };
2719 |
2720 | /**
2721 | * Specification: https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#skins
2722 | * @param {number} skinIndex
2723 | * @return {Promise