├── .eslintrc.js
├── .github
└── workflows
│ ├── build.yml
│ ├── main.yml.disabled
│ └── size.yml.disabled
├── .gitignore
├── .husky
└── pre-commit
├── .prettierignore
├── LICENSE
├── README.md
├── ci
└── publish-gh-pages.js
├── examples
├── simple
│ ├── .gitignore
│ ├── README.md
│ └── index.html
└── webpack_simple
│ ├── .gitignore
│ ├── README.md
│ ├── package.json
│ ├── src
│ ├── index.js
│ └── index.template
│ └── webpack.config.js
├── package.json
├── src
├── DDDViewerConfig.ts
├── SceneViewer.ts
├── ViewerState.ts
├── camera
│ ├── BaseCameraController.ts
│ ├── FreeCameraController.ts
│ ├── GeolocationCameraController.ts
│ ├── OrbitCameraController.ts
│ ├── PanningCameraController.ts
│ ├── WalkCameraController.ts
│ └── WalkCollideCameraController.ts
├── core
│ ├── DDDObjectRef.ts
│ └── ScenePosition.ts
├── format
│ ├── DDDFormat.ts
│ └── GeoJSON.ts
├── index.ts
├── layers
│ ├── Base3DLayer.ts
│ ├── DDD3DLayer.ts
│ ├── GeoJson3DLayer.ts
│ ├── GeoTile3DLayer.ts
│ ├── LayerManager.ts
│ └── OverlayLayer.ts
├── loading
│ └── QueueLoader.ts
├── process
│ ├── ViewerProcess.ts
│ ├── ViewerProcessManager.ts
│ ├── anim
│ │ ├── AnimationProcess.ts
│ │ ├── CameraMoveAnimationProcess.ts
│ │ ├── DateTimeAnimationProcess.ts
│ │ └── TextAnimationProcess.ts
│ └── sequencer
│ │ └── ViewerSequencer.ts
└── render
│ ├── environment
│ └── DefaultEnvironment.ts
│ ├── materials
│ ├── SkyboxMaterial.ts
│ ├── TerrainMaterial.ts
│ └── TextMaterial.ts
│ ├── pipeline
│ └── DefaultRenderPipeline.ts
│ └── skybox
│ ├── CubemapSkybox.ts
│ ├── DynamicSkybox.ts
│ └── Skybox.ts
├── test
└── blah.test.ts.disabled
├── tsconfig.json
└── tsdx.config.js
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | env: {
3 | browser: true,
4 | es2015: true,
5 | //"commonjs": true
6 | },
7 | ignorePatterns: ['**/ddd-viewer/**/*.js', '**/ddd-viewer/**/*.ts'],
8 | extends: [
9 | 'eslint:recommended',
10 | 'plugin:@typescript-eslint/eslint-recommended',
11 | 'plugin:@typescript-eslint/recommended',
12 | ],
13 | parser: '@typescript-eslint/parser',
14 | parserOptions: {
15 | ecmaVersion: 12,
16 | sourceType: 'module',
17 | },
18 | plugins: ['@typescript-eslint'],
19 | rules: {
20 | indent: ['error', 4],
21 | quotes: ['error', 'double'],
22 | semi: ['error', 'always'],
23 | 'object-curly-spacing': ['error', 'always'],
24 | 'array-bracket-spacing': ['error', 'always'],
25 | 'no-console': 'off',
26 | '@typescript-eslint/no-non-null-assertion': 'off',
27 | '@typescript-eslint/no-inferrable-types': 'off',
28 | '@typescript-eslint/no-empty-function': 'off',
29 | '@typescript-eslint/no-unused-vars': 'warn',
30 | "@typescript-eslint/no-explicit-any": "off",
31 | "no-mixed-spaces-and-tabs": "off"
32 | },
33 | };
34 |
--------------------------------------------------------------------------------
/.github/workflows/build.yml:
--------------------------------------------------------------------------------
1 | name: ddd-viewer build
2 | run-name: Full build has been triggered by (${{ github.actor }})
3 | on:
4 | push:
5 | tags:
6 | - 'v*'
7 | jobs:
8 | build:
9 | name: Build job on Node ${{ matrix.node }} and ${{ matrix.os }}
10 | runs-on: ${{ matrix.os }}
11 | continue-on-error: true
12 | strategy:
13 | matrix:
14 | node: [ '18.0', '20.0']
15 | os: [ubuntu-latest, windows-latest, macOS-latest]
16 | steps:
17 | - name: Checkout repo
18 | uses: actions/checkout@v2
19 |
20 | - name: Use Node ${{ matrix.node }}
21 | uses: actions/setup-node@v3
22 | with:
23 | node-version: ${{ matrix.node }}
24 |
25 | # - name: Remove lock files under windows systems
26 | # run: |
27 | # if exist "./package-lock.json" del "./package-lock.json"
28 | # if exist "./yarn-lock.json" del "./yarn-lock.json"
29 |
30 | - name: Remove lock files under non-windows systems
31 | run: |
32 | rm -rf ./package-lock.json
33 | rm -rf ./yarn-lock.json
34 | if: matrix.os != 'windows-latest'
35 |
36 | - name: Install dependencies
37 | run: |
38 | yarn
39 |
40 | - name: Lint
41 | run: yarn lint
42 |
43 | # - name: Audit
44 | # run: yarn audit
45 |
46 | - name: Test
47 | run: yarn test --ci --coverage --maxWorkers=2
48 |
49 | - name: Build
50 | env:
51 | NODE_OPTIONS: '--max_old_space_size=8192'
52 | run: yarn build
53 |
54 | - name: Deploy GitHub pages
55 | run: |
56 | mkdir -p ./examples/simple/lib/
57 | cp dist/ddd-viewer.umd.production.min.js examples/simple/lib/ddd-viewer.umd.production.min.js
58 | node ci/publish-gh-pages.js
59 | if: matrix.os != 'ubuntu-latest' && matrix.node == '20.0'
--------------------------------------------------------------------------------
/.github/workflows/main.yml.disabled:
--------------------------------------------------------------------------------
1 | name: CI
2 | on: [push]
3 | jobs:
4 | build:
5 | name: Build, lint, and test on Node ${{ matrix.node }} and ${{ matrix.os }}
6 |
7 | runs-on: ${{ matrix.os }}
8 | strategy:
9 | matrix:
10 | node: ['10.x', '12.x', '14.x']
11 | os: [ubuntu-latest, windows-latest, macOS-latest]
12 |
13 | steps:
14 | - name: Checkout repo
15 | uses: actions/checkout@v2
16 |
17 | - name: Use Node ${{ matrix.node }}
18 | uses: actions/setup-node@v1
19 | with:
20 | node-version: ${{ matrix.node }}
21 |
22 | - name: Install deps and build (with cache)
23 | uses: bahmutov/npm-install@v1
24 |
25 | - name: Lint
26 | run: yarn lint
27 |
28 | - name: Test
29 | run: yarn test --ci --coverage --maxWorkers=2
30 |
31 | - name: Build
32 | run: yarn build
33 |
--------------------------------------------------------------------------------
/.github/workflows/size.yml.disabled:
--------------------------------------------------------------------------------
1 | name: size
2 | on: [pull_request]
3 | jobs:
4 | size:
5 | runs-on: ubuntu-latest
6 | env:
7 | CI_JOB_NUMBER: 1
8 | steps:
9 | - uses: actions/checkout@v1
10 | - uses: andresz1/size-limit-action@v1
11 | with:
12 | github_token: ${{ secrets.GITHUB_TOKEN }}
13 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *~
2 | .DS_Store
3 | node_modules
4 | /dist
5 | /.settings/
6 | .project
7 | .pydevproject
8 |
9 | .nyc_output
10 | /coverage/
11 | /tests/e2e/reports/
12 | selenium-debug.log
13 |
14 | # local env files
15 | .env.local
16 | .env.*.local
17 |
18 | # Log files
19 | npm-debug.log*
20 | yarn-debug.log*
21 | yarn-error.log*
22 |
23 | # Editor directories and files
24 | .idea
25 | .vscode
26 | *.suo
27 | *.ntvs*
28 | *.njsproj
29 | *.sln
30 | *.sw*
31 |
32 | *.log
33 |
34 | package-lock.json
35 | yarn.lock
36 |
--------------------------------------------------------------------------------
/.husky/pre-commit:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 | . "$(dirname -- "$0")/_/husky.sh"
3 |
4 | yarn lint-staged
5 |
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | dist
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 Jose Juan Montes and contributors
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://github.com/blackyale/ddd-viewer/actions/workflows/build.yml)
2 |
3 | # DDD-Viewer
4 |
5 |
6 |
11 |
12 |
13 | > DDD-Viewer library for viewing DDD and OpenStreetMap data in 3D.
14 |
15 |
16 | ## Overview
17 |
18 | DDD-Viewer is an HTML5 library for 3D visualization of scenes generated by the [DDD tool](https://github.com/jjmontesl/ddd).
19 |
20 | In particular, DDD-Viewer allows to render and interact with 3D maps generated from [OpenStreetMap](https://www.openstreetmap.org/) and other datasouces.
21 |
22 | DDD-Viewer is a TypeScript library based on [BabylonJS](https://www.babylonjs.com/).
23 |
24 | You can embed DDD-Viewer scenes in your website or use it to build a custom application.
25 |
26 | *NOTE that this project is currently experimental and the data formats and library are expected to change.*
27 |
28 |
29 | ## Examples
30 |
31 | - DDD-Viewer-App at [3dsmaps.com](https://3dsmaps.com).
32 | - The [examples](examples/) directory contains simple usage examples.
33 |
34 | *TODO: Add 1-2 screenshots*
35 |
36 |
37 | ## Using the library
38 |
39 | DDD-Viewer is meant to be easily used as a standalone viewer for websites or
40 | as a library from 3rd party projects.
41 |
42 | Note that if serving your own copy of DDD-Viewer, you may wish to point your users to your own
43 | assets. See the [Configuration] and the [Assets] sections below for further information.
44 |
45 |
46 | ### From vanilla Javascript, with no bundler (\
23 |
24 | ```
25 |
26 | We used Tailwind CSS library for presentation, but it`s not necessary for the viewer.
27 |
28 | We have to setup our html with a canvas element that is going to be our container for the 3D scene rendering.
29 |
30 | ```html
31 |
32 |
Simple Example DDD-Viewer
33 |
34 |
35 | ```
36 |
37 | We have to initialize the viewer passing it the configuration and the layers we want to show up:
38 |
39 | ```js
40 | function initViewer() {
41 |
42 | var dddviewer = window['ddd-viewer'];
43 |
44 | const dddConfig = {
45 | "defaultCoords": [-8.723, 42.238],
46 | //"defaultCoords": [-8.4069793, 43.3861094],
47 | "tileUrlBase": "https://3dsmaps.com/cache/ddd_http/",
48 | "assetsUrlbase": "https://3dsmaps.com/assets/",
49 | "materialsTextureSet": "default256",
50 | "geolocation": false
51 | }
52 |
53 | const canvas = document.getElementById('ddd-scene');
54 | const sceneViewer = new dddviewer.SceneViewer(canvas, dddConfig);
55 |
56 | const layerDddOsm3d = new dddviewer.GeoTile3DLayer();
57 | sceneViewer.layerManager.addLayer(layerDddOsm3d);
58 |
59 | // Events
60 | //window.addEventListener('resize', this.resize);
61 |
62 | }
63 | ```
64 |
65 | Finally you have to call the library when the page loads:
66 |
67 | ```js
68 | window.addEventListener("load", () => {
69 | initViewer();
70 | });
71 | ```
--------------------------------------------------------------------------------
/examples/simple/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | DDD-Viewer Simple Example
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
Simple Example DDD-Viewer
24 |
25 |
26 |
27 |
58 |
59 |
60 |
--------------------------------------------------------------------------------
/examples/webpack_simple/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | build/
3 | package-lock.json
--------------------------------------------------------------------------------
/examples/webpack_simple/README.md:
--------------------------------------------------------------------------------
1 | ## Overview
2 |
3 | Webpack Simple is an incorporated ddd-viewer in one page with webpack.
4 |
5 | The example of Webpack Simple is perfect if you are thinking of incorporating the library into a project with webpack.
6 |
7 | ## Building the example
8 |
9 | **Installation**
10 |
11 | 1. Go to webpack simple with the terminal.
12 |
13 | 2. First you can init the draft with:
14 |
15 | `npm init`
16 |
17 | 3. After you you have to install the dependencies with:
18 |
19 | `npm install`
20 |
21 | ***NOTE:***
22 |
23 | *The library is now experimental, so it is not uploaded to npm, as seen in the package.json:*
24 |
25 | `"ddd-viewer": "file:../../ddd-viewer",`
26 |
27 | You have to make a local npm to be able to use the library then run this line from npm:
28 |
29 | `npm link ddd-viewer`
30 |
31 | 4. You will have the project ready now to run the server:
32 |
33 | `npm run serve`
34 |
35 | ## How do we use the library?
36 |
37 | You import the scene and the tiles:
38 |
39 | ```js
40 | import { SceneViewer } from 'ddd-viewer';
41 |
42 | `import { GeoTile3DLayer } from 'ddd-viewer';
43 | ```
44 |
45 | And after you init the viewer:
46 |
47 | ```js
48 | export function initViewer() {
49 |
50 | var dddviewer = window['ddd-viewer'];
51 |
52 | const dddConfig = {
53 | "defaultCoords": [-8.723, 42.238],
54 | //"defaultCoords": [-8.4069793, 43.3861094],
55 | "tileUrlBase": "https://3dsmaps.com/cache/ddd_http/",
56 | "assetsUrlbase": "https://3dsmaps.com/assets/",
57 | "materialsTextureSet": "default256",
58 | "geolocation": false
59 | }
60 |
61 | const canvas = document.getElementById('ddd-scene');
62 | const sceneViewer = new SceneViewer(canvas, dddConfig);
63 |
64 | const layerDddOsm3d = new GeoTile3DLayer();
65 | sceneViewer.layerManager.addLayer("ddd-osm-3d", layerDddOsm3d);
66 |
67 | }
68 | ```
69 |
70 | Finally you have to call the library when the page loads:
71 |
72 | ```js
73 | window.addEventListener("load", () => {
74 | initViewer();
75 | });
76 | ```
--------------------------------------------------------------------------------
/examples/webpack_simple/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "webpack_simple",
3 | "version": "1.0.0",
4 | "description": "DDD(3Ds) Viewer Webpack Example",
5 | "main": "./src/index.js",
6 | "scripts": {
7 | "serve": "webpack serve",
8 | "build": "webpack build"
9 | },
10 | "author": "",
11 | "license": "MIT",
12 | "dependencies": {
13 | "ddd-viewer": "file:../../ddd-viewer",
14 | "webpack": "^5.58.0",
15 | "webpack-cli": "^4.8.0"
16 | },
17 | "devDependencies": {
18 | "html-webpack-plugin": "^4.5.2",
19 | "webpack-dev-server": "^4.3.1"
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/examples/webpack_simple/src/index.js:
--------------------------------------------------------------------------------
1 | import { SceneViewer } from 'ddd-viewer';
2 | import { GeoTile3DLayer } from 'ddd-viewer';
3 |
4 | export function initViewer() {
5 |
6 | var dddviewer = window['ddd-viewer'];
7 |
8 | const dddConfig = {
9 | "defaultCoords": [-8.723, 42.238],
10 | //"defaultCoords": [-8.4069793, 43.3861094],
11 | "tileUrlBase": "https://3dsmaps.com/cache/ddd_http/",
12 | "tileUrlSuffix": "",
13 | "assetsUrlbase": "https://3dsmaps.com/assets/",
14 | "materialsTextureSet": "default256",
15 | "geolocation": false
16 | }
17 |
18 | const canvas = document.getElementById('ddd-scene');
19 | const sceneViewer = new SceneViewer(canvas, dddConfig);
20 |
21 | const layerDddOsm3d = new GeoTile3DLayer();
22 | sceneViewer.layerManager.addLayer(layerDddOsm3d);
23 |
24 | // Events
25 | //window.addEventListener('resize', this.resize);
26 |
27 | }
28 |
29 | window.addEventListener("load", () => {
30 | initViewer();
31 | });
--------------------------------------------------------------------------------
/examples/webpack_simple/src/index.template:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | DDD-Viewer Simple Example
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
Webpack Example DDD-Viewer
26 |
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/examples/webpack_simple/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const HtmlWebpackPlugin = require('html-webpack-plugin');
3 |
4 | module.exports = {
5 | entry: './src/index.js',
6 | mode: 'development',
7 | output: {
8 | path: path.resolve(__dirname, 'build'),
9 | filename: 'webpack_simple.js',
10 | },
11 | module: {
12 | rules: [
13 | {
14 | test: /\.m?js/,
15 | resolve: {
16 | fullySpecified: false
17 | }
18 | },
19 | ]
20 | },
21 | plugins: [new HtmlWebpackPlugin({template: './src/index.template'})],
22 | };
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ddd-viewer",
3 | "version": "0.1.0",
4 | "license": "MIT",
5 | "author": "Jose Juan Montes",
6 | "contributors": [
7 | "Farvell"
8 | ],
9 | "description": "DDD(3Ds) Viewer library for DDD-generated GIS 3D scenes",
10 | "main": "dist/ddd-viewer.esm.js",
11 | "typings": "dist/index.d.ts",
12 | "files": [
13 | "dist"
14 | ],
15 | "engines": {
16 | "node": ">=10"
17 | },
18 | "scripts": {
19 | "dev": "barrelsby -d src --delete && tsdx watch --format esm --target web",
20 | "build-esm": "barrelsby -d src --delete && tsdx build --format esm --target web",
21 | "build-umd": "barrelsby -d src --delete && tsdx build --max_old_space_size=8192 --format umd --target web",
22 | "build": "barrelsby -d src --delete && tsdx build --max_old_space_size=8192 --format esm,umd --target web",
23 | "barrelsby": "barrelsby -d src --delete",
24 | "doc": "barrelsby -d src --delete && npx typedoc --out doc/api src/index.ts",
25 | "test": "tsdx test --passWithNoTests",
26 | "lint": "eslint . --ext .ts --fix",
27 | "_prepare": "tsdx build --format esm,umd --target web",
28 | "size": "size-limit",
29 | "analyze": "size-limit --why"
30 | },
31 | "husky": {
32 | "hooks": {
33 | "pre-commit": "npx lint-staged"
34 | }
35 | },
36 | "module": "dist/ddd-viewer.esm.js",
37 | "size-limit": [
38 | {
39 | "path": "dist/ddd-viewer.cjs.production.min.js",
40 | "limit": "10 KB"
41 | },
42 | {
43 | "path": "dist/ddd-viewer.esm.js",
44 | "limit": "10 KB"
45 | }
46 | ],
47 | "devDependencies": {
48 | "@rollup/plugin-node-resolve": "^15.2.1",
49 | "@size-limit/preset-small-lib": "^9.0.0",
50 | "@types/ol": "^6.5.1",
51 | "@types/proj4": "^2.5.0",
52 | "@types/suncalc": "^1.8.0",
53 | "@typescript-eslint/eslint-plugin": "^6.7.2",
54 | "@typescript-eslint/parser": "^6.7.2",
55 | "barrelsby": "^2.8.1",
56 | "eslint": "^8.50.0",
57 | "gh-pages": "^6.0.0",
58 | "husky": "^7.0.1",
59 | "lint-staged": "^14.0.1",
60 | "ol": "^6.5.0",
61 | "prettier": "^3.0.3",
62 | "proj4": "^2.7.2",
63 | "size-limit": "^9.0.0",
64 | "suncalc": "^1.8.0",
65 | "tsdx": "^0.14.1",
66 | "tslib": "^2.2.0",
67 | "typedoc": "^0.25.1",
68 | "typescript": "^4.3.2"
69 | },
70 | "dependencies": {
71 | "@babylonjs/core": "5.0.0-rc.4",
72 | "@babylonjs/loaders": "5.0.0-rc.4",
73 | "@babylonjs/materials": "5.0.0-rc.4",
74 | "earcut": "^2.2.2"
75 | },
76 | "lint-staged": {
77 | "*.ts": [
78 | "prettier -w",
79 | "eslint --fix"
80 | ]
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/src/DDDViewerConfig.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * DDDViewer - DDD(3Ds) Viewer library for DDD-generated GIS 3D models
3 | * Copyright 2021 Jose Juan Montes and contributors
4 | * MIT License (see LICENSE file)
5 | */
6 |
7 | /**
8 | * This object represents ddd-viewer configuration, and is used to pass
9 | * options to the main object (DDDViewer) constructor.
10 | */
11 | class DDDViewerConfig {
12 | defaultCoords: number[] | null = [ -8.726, 42.233 ]; // [0.0, 0.0];
13 |
14 | // TODO: Move this to layer configuration (?)
15 | tileUrlBase: string = "/cache/ddd_http/";
16 | tileUrlSuffix: string = "";
17 |
18 | assetsUrlbase: string = "/assets/";
19 |
20 | materialsTextureSet: string | null = "default256";
21 | materialsSplatmap: number | null = null; // 256
22 |
23 | //sceneGroundTextureOverrideUrl: string | null = null;
24 |
25 | timeScale: number = 0.0;
26 |
27 | moveSpeeds: number[] = [ 2.0, 5.0, 10.0 ];
28 |
29 | sceneTileDrawDistanceDefault: number = 1;
30 | }
31 |
32 | export { DDDViewerConfig };
33 |
--------------------------------------------------------------------------------
/src/ViewerState.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * DDDViewer - DDD(3Ds) Viewer library for DDD-generated GIS 3D scenes
3 | * Copyright 2021 Jose Juan Montes and contributors
4 | * MIT License (see LICENSE file)
5 | */
6 |
7 | import { DDDViewerConfig } from "./DDDViewerConfig";
8 |
9 | /**
10 | * Holds DDDViewer global state like viewer position, date/time, configuration...
11 | * Some internal values are also stored here for convenience (FPS, drawcalls, mobile detection...).
12 | * This object must be JSON-serializable.
13 | */
14 | class ViewerState {
15 |
16 | mapVisible = true;
17 | sceneVisible = false;
18 | dddConfig: DDDViewerConfig;
19 | isMobile = false;
20 | positionTileZoomLevel = 11;
21 | positionWGS84 = [ -8.726, 42.233 ]; // [0.0, 0.0];
22 |
23 | // Position in scene, in engine coordinates (elevation is Y)
24 | positionScene = [ 0, 0, 0 ];
25 | positionGroundHeight: number = 150.0;
26 | positionTerrainElevation = 0;
27 | positionHeading = 0.0;
28 | positionTilt = 0.0;
29 | positionName: string | null = null;
30 | positionDate: Date = new Date();
31 | positionDateSeconds: number = this.positionDate.getTime() / 1000;
32 | geolocationEnabled = false;
33 | timeScale = 24 * 2; // 24 * 2 = 48x faster (1 day = 30 min)
34 |
35 | // TODO: These nodes are instrumented: remove selectedMesh from here and use ids.
36 | // TODO: Try removing this and this.sceneViewer id still used
37 | sceneSelectedMesh = null;
38 | sceneSelectedMeshId: string | null = null;
39 |
40 | sceneFPS: number = 0;
41 | sceneDrawCalls: number = 0;
42 | sceneTriangles: number = 0;
43 |
44 | sceneShadowsEnabled = false;
45 | sceneTextsEnabled = false;
46 | scenePostprocessingEnabled = false;
47 | scenePickingEnabled = true;
48 | sceneViewModeShow = true;
49 | sceneSelectedShowNormals = true;
50 | sceneTileDrawDistance = 1;
51 | sceneMoveSpeed = 5;
52 | sceneEnvironmentProbe = 16; // 16; // null to use a static environment (should be associated to the skybox, but it's currently fixed)
53 | sceneSkybox = "/textures/TropicalSunnyDay"; // /textures/skybox/clouds1/clouds1 // "@dynamic"; // ""/textures/TropicalSunnyDay";
54 |
55 | // TODO: This shall be a per-layer setting
56 | sceneGroundTextureOverrideUrl: string | null = null;
57 |
58 | sceneTitleText:string | null = null;
59 |
60 | /**
61 | * 3dsmaps DDD tile server supports on-demand generation. When a tile that's not available is enqueued, it responds
62 | * with information about the job status. This array contains enqueued tiles status.
63 | */
64 | remoteQueueJobsStatus: any[] = [];
65 |
66 |
67 | constructor(dddConfig: DDDViewerConfig, initialCoords: number[] | null = null) {
68 |
69 | this.dddConfig = dddConfig;
70 |
71 | if (dddConfig.defaultCoords) {
72 | this.positionWGS84 = dddConfig.defaultCoords;
73 | }
74 |
75 | if (initialCoords) {
76 | this.positionWGS84 = initialCoords;
77 | }
78 |
79 | const shadowsEnabled = localStorage.getItem( "dddSceneShadowsEnabled" );
80 | this.sceneShadowsEnabled = shadowsEnabled ? JSON.parse( shadowsEnabled ) : this.sceneShadowsEnabled;
81 |
82 | const textsEnabled = localStorage.getItem( "dddSceneTextsEnabled" );
83 | this.sceneTextsEnabled = textsEnabled ? JSON.parse( textsEnabled ) : this.sceneTextsEnabled;
84 |
85 | this.sceneTileDrawDistance = dddConfig.sceneTileDrawDistanceDefault;
86 |
87 | // Start time
88 | this.positionDate.setHours( 11 );
89 | this.positionDate.setMinutes( 0 );
90 |
91 | }
92 | }
93 |
94 | export { ViewerState };
95 |
--------------------------------------------------------------------------------
/src/camera/BaseCameraController.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * DDDViewer - DDD(3Ds) Viewer library for DDD-generated GIS 3D scenes
3 | * Copyright 2021 Jose Juan Montes and Contributors
4 | * MIT License (see LICENSE file)
5 | */
6 |
7 | import { Camera } from "@babylonjs/core";
8 | import { SceneViewer } from "SceneViewer";
9 |
10 | /**
11 | * A Camera and Input controller.
12 | *
13 | * This allows controlling the main camera, which is the main interface for viewing.
14 | * Controllers process input devices if they wish to respond to user input.
15 | *
16 | * Client code may use custom controllers or disable these controllers
17 | * and position the camera manually.
18 | */
19 | abstract class BaseCameraController {
20 |
21 | protected dddViewer: SceneViewer;
22 |
23 | constructor(dddViewer: SceneViewer) {
24 |
25 | // Reference to DDDViewer
26 | this.dddViewer = dddViewer;
27 |
28 | // Babylon camera which we are controlling
29 | //this.camera = dddViewer.camera;
30 | }
31 |
32 | protected getCamera(): Camera {
33 | return this.dddViewer.camera;
34 | }
35 |
36 | abstract update(deltaTime: number): void;
37 |
38 | abstract activate(): void;
39 |
40 | }
41 |
42 | export { BaseCameraController };
--------------------------------------------------------------------------------
/src/camera/FreeCameraController.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * DDDViewer - DDD(3Ds) Viewer library for DDD-generated GIS 3D scenes
3 | * Copyright 2021 Jose Juan Montes and Contributors
4 | * MIT License (see LICENSE file)
5 | */
6 |
7 | import { Camera, UniversalCamera, Vector3 } from "@babylonjs/core";
8 | import { SceneViewer } from "SceneViewer";
9 | import { BaseCameraController } from "./BaseCameraController";
10 |
11 | /**
12 | * DDD Viewer base layer class.
13 | */
14 | class FreeCameraController extends BaseCameraController {
15 |
16 | fixMinHeight = false;
17 |
18 | update(deltaTime: number): void {
19 | // Fix viewer to floor
20 | if (this.fixMinHeight) {
21 | const terrainElevation = this.dddViewer.viewerState.positionTerrainElevation;
22 | if (terrainElevation && this.dddViewer.camera.position.y < ( terrainElevation + 1.0 )) {
23 | this.getCamera().position.y = terrainElevation + 1.0;
24 | }
25 | }
26 | }
27 |
28 | activate(): void {
29 | if (this.dddViewer.camera) {
30 | this.dddViewer.camera.customRenderTargets.length = 0; //4 = [];
31 | this.dddViewer.camera.detachControl();
32 | this.dddViewer.camera.dispose();
33 | }
34 |
35 | //console.debug("Creating free camera.");
36 | const camera = new UniversalCamera( "Camera", Vector3.Zero(), this.dddViewer.scene );
37 | camera.minZ = 1.0; // 0.1;
38 | camera.maxZ = 4500;
39 | camera.angularSensibility = 500.0;
40 | camera.touchAngularSensibility = 1000.0;
41 | //camera.touchMoveSensibility = 1.0;
42 | camera.inertia = 0.0;
43 | camera.keysUp.push( 87 );
44 | camera.keysDown.push( 83 );
45 | camera.keysLeft.push( 65 );
46 | camera.keysRight.push( 68 );
47 | camera.keysUpward.push( 69 );
48 | camera.keysDownward.push( 81 );
49 | camera.attachControl( this.dddViewer.engine.getRenderingCanvas(), true );
50 | camera.fov = 40.0 * ( Math.PI / 180.0 ); // 35.0 might be GM, 45.8... is default // 35
51 | const positionScene = this.dddViewer.wgs84ToScene( this.dddViewer.viewerState.positionWGS84 );
52 | camera.position = new Vector3( positionScene[0], this.dddViewer.viewerState.positionGroundHeight + this.dddViewer.viewerState.positionTerrainElevation + 1, positionScene[2]);
53 | camera.rotation = new Vector3(( 90.0 - this.dddViewer.viewerState.positionTilt ) * ( Math.PI / 180.0 ), this.dddViewer.viewerState.positionHeading * ( Math.PI / 180.0 ), 0.0 );
54 | //camera.cameraRotation = new Vector2(/* (90.0 - this.viewerState.positionTilt) * (Math.PI / 180.0) */ 0, this.viewerState.positionHeading * (Math.PI / 180.0));
55 | this.dddViewer.camera = camera;
56 | this.dddViewer.setMoveSpeed( this.dddViewer.viewerState.sceneMoveSpeed );
57 |
58 | this.dddViewer.updateRenderTargets();
59 | if (this.dddViewer.shadowGenerator) {
60 | this.dddViewer.shadowGenerator.splitFrustum();
61 | }
62 | }
63 |
64 | }
65 |
66 | export { FreeCameraController };
--------------------------------------------------------------------------------
/src/camera/GeolocationCameraController.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * DDDViewer - DDD(3Ds) Viewer library for DDD-generated GIS 3D scenes
3 | * Copyright 2021 Jose Juan Montes and Contributors
4 | * MIT License (see LICENSE file)
5 | */
6 |
7 | import { Camera } from "@babylonjs/core";
8 | import { SceneViewer } from "SceneViewer";
9 | import { BaseCameraController } from "./BaseCameraController";
10 |
11 | /**
12 | * DDD Viewer base layer class.
13 | */
14 | class GeolocationCameraController extends BaseCameraController {
15 | update(deltaTime: number): void {}
16 |
17 | activate(): void {}
18 |
19 | /*
20 | geolocationPosition( enabled ) {
21 |
22 | //this.selectCameraFree();
23 | //this.walkMode = true;
24 | //this.camera.detachControl();
25 |
26 | /-
27 | this.app.$getLocation({enableHighAccuracy: true}).then(coordinates => {
28 | console.log(coordinates);
29 | let altitude = coordinates.altitude !== null ? coordinates.altitude : 2.0;
30 | let scenePos = this.wgs84ToScene([coordinates.lng, coordinates.lat, altitude]);
31 | //console.log(scenePos);
32 | this.camera.position.x = scenePos[0];
33 | this.camera.position.y = altitude;
34 | this.camera.position.z = scenePos[2];
35 |
36 | let heading = coordinates.heading;
37 | if (heading) {
38 | this.sceneViewer.viewerState.positionHeading = heading;
39 | let rotation = new Vector3((90.0 - this.sceneViewer.viewerState.positionTilt) * (Math.PI / 180.0), this.sceneViewer.viewerState.positionHeading * (Math.PI / 180.0), 0.0);
40 | this.camera.rotation = rotation;
41 | }
42 | });
43 | -/
44 |
45 | this.viewerState.geolocationEnabled = enabled;
46 |
47 | if ( enabled ) {
48 |
49 | const that = this;
50 |
51 | // Enable geolocation
52 | this.selectCameraFree();
53 |
54 | //this._geolocationWatchId = this.app.$watchLocation({enableHighAccuracy: true, maximumAge: 5}).then(coordinates => {
55 | this.app.$getLocation({ enableHighAccuracy: true, maximumAge: 5 }).then(( coords ) => { that.onDeviceLocation( coords ); });
56 |
57 | // Compass
58 | this._onDeviceOrientation = function( e ) { that.onDeviceOrientation( e ); };
59 | this._onDeviceOrientation.bind( that );
60 | const isIOS = false;
61 | if ( isIOS ) {
62 | DeviceOrientationEvent.requestPermission().then(( response ) => {
63 | if ( response === "granted" ) {
64 | window.addEventListener( "deviceorientation", this._onDeviceOrientation );
65 | } else {
66 | alert( "Compass usage permission not granted." );
67 | }
68 | }).catch(() => alert( "Compass not supported." ));
69 | } else {
70 | window.addEventListener( "deviceorientationabsolute", this._onDeviceOrientation );
71 | }
72 |
73 | } else {
74 |
75 | // Disable geolocation
76 |
77 | this.viewerState.geolocationEnabled = false;
78 | if ( this._geolocationWatchId !== null ) {
79 | this.app.$clearLocationWatch( this._geolocationWatchId );
80 | this._geolocationWatchId = null;
81 | }
82 | window.removeEventListener( "deviceorientationabsolute", this._onDeviceOrientation );
83 | window.removeEventListener( "deviceorientation", this._onDeviceOrientation );
84 | this._onDeviceOrientation = null;
85 | }
86 |
87 | }
88 |
89 | onDeviceLocation( coordinates ) {
90 | //console.log(coordinates);
91 | if ( coordinates ) {
92 |
93 | const altitude = coordinates.altitude !== null ? coordinates.altitude : 2.0;
94 | if ( this.walkMode ) { altitude.y = 2.5; }
95 |
96 | this.viewerState.positionWGS84 = [ coordinates.lng, coordinates.lat, altitude ];
97 | const scenePos = this.wgs84ToScene( this.viewerState.positionWGS84 );
98 | this.viewerState.positionScene = scenePos;
99 |
100 | this.camera.position.x = scenePos[0];
101 | this.camera.position.y = altitude;
102 | this.camera.position.z = scenePos[2];
103 |
104 | /-
105 | let heading = coordinates.heading;
106 | if (heading !== null && !isNaN(heading)) {
107 | this.viewerState.positionHeading = heading;
108 | let rotation = new Vector3((90.0 - this.viewerState.positionTilt) * (Math.PI / 180.0), this.viewerState.positionHeading * (Math.PI / 180.0), 0.0);
109 | this.camera.rotation = rotation;
110 | //console.debug(heading);
111 | }
112 | -/
113 | }
114 |
115 | if ( this.viewerState.geolocationEnabled ) {
116 | const that = this;
117 | setTimeout( function() {
118 | that.app.$getLocation({ enableHighAccuracy: true, maximumAge: 5 }).then(( coords ) => { that.onDeviceLocation( coords ); });
119 | }, 1000 );
120 | }
121 |
122 | }
123 | */
124 |
125 | /**
126 | * From: https://www.w3.org/TR/orientation-event/
127 | */
128 | /*
129 | getQuaternion( alpha, beta, gamma ) {
130 |
131 | var degtorad = Math.PI / 180; // Degree-to-Radian conversion
132 |
133 | var _x = beta ? beta * degtorad : 0; // beta value
134 | var _y = gamma ? gamma * degtorad : 0; // gamma value
135 | var _z = alpha ? alpha * degtorad : 0; // alpha value
136 |
137 | var cX = Math.cos( _x/2 );
138 | var cY = Math.cos( _y/2 );
139 | var cZ = Math.cos( _z/2 );
140 | var sX = Math.sin( _x/2 );
141 | var sY = Math.sin( _y/2 );
142 | var sZ = Math.sin( _z/2 );
143 |
144 | //
145 | // ZXY quaternion construction.
146 | //
147 |
148 | var w = cX * cY * cZ - sX * sY * sZ;
149 | var x = sX * cY * cZ - cX * sY * sZ;
150 | var y = cX * sY * cZ + sX * cY * sZ;
151 | var z = cX * cY * sZ + sX * sY * cZ;
152 |
153 | //return [ w, x, y, z ];
154 | return new Quaternion(x, y, z, w);
155 | }
156 | */
157 |
158 | /*
159 | onDeviceOrientation( e ) {
160 |
161 | //let rotation = Quaternion.FromEulerAngles(e.alpha * Math.PI / 180.0, e.beta * Math.PI / 180.0, e.gamma * Math.PI / 180.0);
162 | //let forward = Vector3.Forward().rotateByQuaternionToRef(rotation, new Vector3());
163 | //let heading = Math.atan2(forward.y, forward.x) * 180.0 / Math.PI;
164 | //alert(heading);
165 |
166 | let heading = e.webkitCompassHeading || Math.abs( e.alpha - 360 );
167 |
168 | if ( heading !== null && !isNaN( heading )) {
169 |
170 | heading = ( heading ) % 360.0;
171 | this.viewerState.positionHeading = heading;
172 |
173 | let tilt = e.webkitCompassTilt || Math.abs( e.beta - 360 );
174 | if ( tilt !== null && !isNaN( tilt )) {
175 | this.viewerState.positionTilt = ( - tilt );
176 | }
177 |
178 | const tiltRotation = ( 90.0 - this.viewerState.positionTilt ) * ( Math.PI / 180.0 );
179 | if ( tiltRotation < 0 ) { tilt = Math.PI * 2 - tiltRotation; }
180 | const rotation = new Vector3( tiltRotation, this.viewerState.positionHeading * ( Math.PI / 180.0 ), 0.0 );
181 | //let rotation = new Vector3(Math.PI / 2 + -e.beta * Math.PI / 180.0, -e.alpha * Math.PI / 180.0, e.gamma * Math.PI / 180.0 );
182 | this.camera!.rotation = rotation;
183 | //console.debug(heading);
184 | }
185 | //compassCircle.style.transform = `translate(-50%, -50%) rotate(${-compass}deg)`;
186 | }
187 | */
188 | }
189 |
190 | export { GeolocationCameraController };
191 |
--------------------------------------------------------------------------------
/src/camera/OrbitCameraController.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * DDDViewer - DDD(3Ds) Viewer library for DDD-generated GIS 3D scenes
3 | * Copyright 2021 Jose Juan Montes and Contributors
4 | * MIT License (see LICENSE file)
5 | */
6 |
7 | import { ArcRotateCamera, BoundingInfo, Camera, Mesh, Vector3 } from "@babylonjs/core";
8 | import { SceneViewer } from "SceneViewer";
9 | import { BaseCameraController } from "./BaseCameraController";
10 |
11 | /**
12 | * DDD Viewer base layer class.
13 | */
14 | class OrbitCameraController extends BaseCameraController {
15 |
16 | update(deltaTime: number): void {
17 | // Fix viewer to floor
18 | const terrainElevation = this.dddViewer.viewerState.positionTerrainElevation;
19 | if (terrainElevation && this.dddViewer.camera.position.y < ( terrainElevation + 1.0 )) {
20 | this.getCamera().position.y = terrainElevation + 1.0;
21 | }
22 | }
23 |
24 | activate(): void {
25 |
26 | let targetCoords = Vector3.Zero();
27 | if ( this.dddViewer.selectedObject ) {
28 | const boundingBox: BoundingInfo = this.dddViewer.getBoundsRecursively( this.dddViewer.selectedObject!.mesh );
29 | //targetCoords = this.selectedMesh.absolutePosition;
30 | const minWorld = boundingBox.minimum;
31 | const maxWorld = boundingBox.maximum;
32 | targetCoords = new Vector3(( minWorld.x + maxWorld.x ) / 2, ( minWorld.y + maxWorld.y ) / 2, ( minWorld.z + maxWorld.z ) / 2 );
33 | }
34 |
35 | let distance = 75.0;
36 | if ( this.dddViewer.camera ) {
37 | distance = Vector3.Distance( this.dddViewer.camera.position, targetCoords );
38 |
39 | this.dddViewer.camera.customRenderTargets.length = 0; // = [];
40 |
41 | this.dddViewer.camera.detachControl();
42 | this.dddViewer.camera.dispose();
43 | }
44 |
45 | console.debug( "Creating orbit camera pointing to: " + targetCoords );
46 |
47 | const camera = new ArcRotateCamera( "Camera", -( 90 + this.dddViewer.viewerState.positionHeading ) * Math.PI / 180.0, this.dddViewer.viewerState.positionTilt * Math.PI / 180.0, distance, targetCoords, this.dddViewer.scene );
48 | camera.attachControl( this.dddViewer.engine.getRenderingCanvas(), true );
49 | camera.minZ = 0.5; // 1.0; // 0.1;
50 | //camera.maxZ = 2500; // Automatic? see focusOn()
51 | camera.lowerRadiusLimit = 15;
52 | camera.upperRadiusLimit = 1000;
53 | camera.upperBetaLimit = Math.PI; // /2; // Math.PI / 2 = limit to flat view
54 | camera.panningSensibility = 50.0; // 0.5;
55 | //camera.angularSensibility = 50.0;
56 | //camera.inertia = 0.10;
57 |
58 | //camera.multiTouchPanning = false;
59 | //camera.multiTouchPanAndZoom = false;
60 | //camera.pinchZoom = true;
61 |
62 | camera.useNaturalPinchZoom = true;
63 | camera.fov = 35.0 * ( Math.PI / 180.0 );
64 | this.dddViewer.camera = camera;
65 |
66 | this.dddViewer.updateRenderTargets();
67 | if (this.dddViewer.shadowGenerator) {
68 | this.dddViewer.shadowGenerator.splitFrustum();
69 | }
70 | }
71 |
72 | }
73 |
74 | export { OrbitCameraController };
--------------------------------------------------------------------------------
/src/camera/PanningCameraController.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * DDDViewer - DDD(3Ds) Viewer library for DDD-generated GIS 3D scenes
3 | * Copyright 2021 Jose Juan Montes and Contributors
4 | * MIT License (see LICENSE file)
5 | */
6 |
7 | import { Camera } from "@babylonjs/core";
8 | import { SceneViewer } from "SceneViewer";
9 | import { BaseCameraController } from "./BaseCameraController";
10 |
11 | /**
12 | * DDD Viewer base layer class.
13 | */
14 | class PanningCameraController extends BaseCameraController {
15 |
16 | update(deltaTime: number): void {
17 |
18 | }
19 |
20 | activate(): void {
21 |
22 | }
23 |
24 | }
25 |
26 | export { PanningCameraController };
--------------------------------------------------------------------------------
/src/camera/WalkCameraController.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * DDDViewer - DDD(3Ds) Viewer library for DDD-generated GIS 3D scenes
3 | * Copyright 2021 Jose Juan Montes and Contributors
4 | * MIT License (see LICENSE file)
5 | */
6 |
7 | import { Camera, Vector3 } from "@babylonjs/core";
8 | import { SceneViewer } from "SceneViewer";
9 | import { BaseCameraController } from "./BaseCameraController";
10 | import { FreeCameraController } from "./FreeCameraController";
11 |
12 | /**
13 | * DDD Viewer base layer class.
14 | */
15 | class WalkCameraController extends FreeCameraController {
16 |
17 | sceneCameraWalkHeight = 1.75; // 2.0;
18 |
19 | //falling = false;
20 |
21 | update(deltaTime: number): void {
22 | // Fix viewer to floor
23 | const terrainElevation = this.dddViewer.viewerState.positionTerrainElevation;
24 | if ( terrainElevation !== null && this.dddViewer.camera ) {
25 | this.getCamera().position.y = terrainElevation + this.sceneCameraWalkHeight; // 3.0;
26 | }
27 | }
28 |
29 | activate(): void {
30 | super.activate();
31 | //this.walkMode = true;
32 | this.dddViewer.camera!.inertia = 0.2; // 0.0;
33 | this.dddViewer.setMoveSpeed( this.dddViewer.viewerState.sceneMoveSpeed );
34 | }
35 |
36 | }
37 |
38 | export { WalkCameraController };
--------------------------------------------------------------------------------
/src/camera/WalkCollideCameraController.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * DDDViewer - DDD(3Ds) Viewer library for DDD-generated GIS 3D scenes
3 | * Copyright 2021 Jose Juan Montes and Contributors
4 | * MIT License (see LICENSE file)
5 | */
6 |
7 | import { Camera, Vector3 } from "@babylonjs/core";
8 | import { SceneViewer } from "SceneViewer";
9 | import { BaseCameraController } from "./BaseCameraController";
10 | import { WalkCameraController } from "./WalkCameraController";
11 |
12 | /**
13 | * DDD Viewer base layer class.
14 | */
15 | class WalkCollideCameraController extends WalkCameraController {
16 |
17 | velocity = new Vector3();
18 |
19 | gravity = -9.8;
20 |
21 | falling = false;
22 |
23 | fallStartDistance = 0.5;
24 |
25 | update(deltaTime: number): void {
26 | // Fix viewer to floor
27 | const terrainElevation = this.dddViewer.viewerState.positionTerrainElevation;
28 | if ( terrainElevation !== null && this.dddViewer.camera ) {
29 | const currentHeight = this.getCamera().position.y;
30 | const baseHeight = terrainElevation + this.sceneCameraWalkHeight;
31 | if (currentHeight > baseHeight + this.fallStartDistance) {
32 | this.falling = true;
33 | }
34 | if (this.falling) {
35 | this.velocity.y += (this.gravity * deltaTime);
36 | this.getCamera().position.addInPlace(this.velocity.scale(deltaTime));
37 | }
38 |
39 | if (!this.falling || this.getCamera().position.y < baseHeight) {
40 | this.falling = false;
41 | this.velocity.set(0, 0, 0);
42 | this.getCamera().position.y = terrainElevation + this.sceneCameraWalkHeight;
43 | }
44 |
45 | }
46 | }
47 |
48 | }
49 |
50 | export { WalkCollideCameraController };
--------------------------------------------------------------------------------
/src/core/DDDObjectRef.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * DDDViewer - DDD(3Ds) Viewer library for DDD-generated GIS 3D models
3 | * Copyright 2021 Jose Juan Montes and contributors
4 | * MIT License (see LICENSE file)
5 | */
6 |
7 | import { Mesh, Node } from "@babylonjs/core";
8 |
9 | /**
10 | * A reference to a "logical" object in the scene.
11 | *
12 | * Like real scene mesh objects, DDDObjectRefs have metadata, a path, parent and children.
13 | *
14 | * This allows to reference sets of triangles in larger objects (eg. if several logical objects were
15 | * batched into a larger mesh at design time or elsewhere).
16 | *
17 | * TODO: Reference the containing layer so each layer can resolve its objects.
18 | */
19 | class DDDObjectRef {
20 | mesh: Node;
21 |
22 | submeshIdx: number = -1;
23 |
24 | faceIndexStart: number = -1;
25 | faceIndexEnd: number = -1;
26 |
27 | constructor(mesh: Node, submeshIdx: number = -1) {
28 | this.mesh = mesh;
29 | this.submeshIdx = submeshIdx;
30 |
31 | if (this.submeshIdx > -1) {
32 | const metadata = DDDObjectRef.nodeMetadata(mesh);
33 | const indexes =
34 | "ddd:combined:indexes" in metadata
35 | ? metadata["ddd:combined:indexes"]
36 | : metadata["ddd:batch:indexes"];
37 | if (indexes && submeshIdx in indexes) {
38 | this.faceIndexStart = submeshIdx > 0 ? indexes[submeshIdx - 1][0] : 0;
39 | this.faceIndexEnd = indexes[submeshIdx][0];
40 | }
41 | }
42 |
43 | /*
44 | metadata = indexes[i][1];
45 |
46 | // WARN: TODO: this transformation is done in other places
47 | let meshName = null;
48 | if (!combined) {
49 | meshName = pickResult.pickedMesh.id.split("/").pop().replaceAll('#', '_'); // .replaceAll("_", " ");
50 | } else {
51 | meshName = metadata['ddd:path'].split("/").pop().replaceAll('#', '_'); // .replaceAll("_", " ");
52 | }
53 | */
54 | }
55 |
56 | static nodeMetadata(node: Node) {
57 | if (
58 | node &&
59 | node.metadata &&
60 | node.metadata.gltf &&
61 | node.metadata.gltf.extras
62 | ) {
63 | return node.metadata.gltf.extras;
64 | } else if (node.metadata !== null) {
65 | return node.metadata;
66 | } else {
67 | return null;
68 | }
69 | }
70 |
71 | static fromMeshFace(mesh: Mesh, faceIndex: number): DDDObjectRef {
72 | //console.debug("Selecting from mesh and face (mesh=" + mesh.id + ", faceIndex=" + faceIndex + ")");
73 |
74 | const metadata = DDDObjectRef.nodeMetadata(mesh);
75 |
76 | let subIndex = -1;
77 | if (metadata) {
78 | if (
79 | "ddd:combined:indexes" in metadata ||
80 | "ddd:batch:indexes" in metadata
81 | ) {
82 | const indexes =
83 | "ddd:combined:indexes" in metadata
84 | ? metadata["ddd:combined:indexes"]
85 | : metadata["ddd:batch:indexes"];
86 | // Find triangle in indexes
87 | let prevIndex = -1;
88 | //if (indexes.length > 0) { subIndex = 0; }
89 | for (let i = 0; i < indexes.length; i++) {
90 | if (faceIndex > prevIndex && faceIndex < indexes[i][0]) {
91 | subIndex = i;
92 | break;
93 | }
94 | prevIndex = indexes[i][0];
95 | }
96 | }
97 | }
98 |
99 | const objectRef = new DDDObjectRef(mesh, subIndex);
100 | return objectRef;
101 | }
102 |
103 | /*
104 | findChildByName(name: string): DDDObjectRef | null {
105 | return null;
106 | }
107 | */
108 |
109 | getMetadata(): any {
110 | let metadata = DDDObjectRef.nodeMetadata(this.mesh);
111 | if (metadata && this.submeshIdx >= 0) {
112 | const indexes =
113 | "ddd:combined:indexes" in metadata
114 | ? metadata["ddd:combined:indexes"]
115 | : metadata["ddd:batch:indexes"];
116 | metadata = indexes[this.submeshIdx][1];
117 | }
118 | return metadata;
119 | }
120 |
121 | static urlId(value: string): string {
122 | let result = value;
123 | if (result) {
124 | result = result.replaceAll("/", "-");
125 | result = result.replaceAll("#", "_");
126 | result = result.replaceAll(" ", "_");
127 | result = encodeURIComponent(result);
128 | result = result.replaceAll("%3A", ":");
129 | //result = result.replace("/", "_");
130 | //result = result.replace("#", "_");
131 | }
132 | return result;
133 | }
134 |
135 | getId(): string {
136 | const metadata = this.getMetadata();
137 |
138 | let result = this.mesh.id;
139 | if (metadata && "ddd:rpath" in metadata) {
140 | result = metadata["ddd:rpath"];
141 | } else if (metadata && "ddd:path" in metadata) {
142 | result = metadata["ddd:path"];
143 | }
144 | return result;
145 | }
146 |
147 | getUrlId(): string {
148 | let result = this.getId();
149 | result = DDDObjectRef.urlId(result);
150 | return result;
151 | }
152 |
153 | getLabel(): string {
154 | const result = this.getId();
155 | return result;
156 | }
157 |
158 | getChildren(): DDDObjectRef[] {
159 | const result: DDDObjectRef[] = [];
160 |
161 | //if (this.submeshIdx < 0) {
162 | for (const child of this.mesh.getChildren()) {
163 | result.push(new DDDObjectRef(child));
164 | }
165 | //}
166 |
167 | const metadata = this.getMetadata();
168 | if (
169 | metadata &&
170 | ("ddd:combined:indexes" in metadata || "ddd:batch:indexes" in metadata)
171 | ) {
172 | const indexes =
173 | "ddd:combined:indexes" in metadata
174 | ? metadata["ddd:combined:indexes"]
175 | : metadata["ddd:batch:indexes"];
176 | for (let i = 0; i < indexes.length; i++) {
177 | result.push(new DDDObjectRef(this.mesh, i));
178 | }
179 | }
180 |
181 | return result;
182 | }
183 |
184 | getParent(): DDDObjectRef | null {
185 | let result = null;
186 | const parent = this.mesh.parent;
187 | if (parent) {
188 | result = new DDDObjectRef(parent);
189 | }
190 | return result;
191 | }
192 | }
193 |
194 | export { DDDObjectRef };
195 |
--------------------------------------------------------------------------------
/src/core/ScenePosition.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * DDDViewer - DDD(3Ds) Viewer library for DDD-generated GIS 3D models
3 | * Copyright 2021 Jose Juan Montes and contributors
4 | * MIT License (see LICENSE file)
5 | */
6 |
7 |
8 | /**
9 | * ScenePosition represents
10 | */
11 | class ScenePosition {
12 |
13 | // Position is Lat, Lon, Altitude (currently MSL)
14 | positionWGS84: number[] = [ 0, 0, 0 ];
15 |
16 | positionTileZoomLevel: number = 0;
17 |
18 | // Ground altitude
19 | positionGroundHeight: number = 0;
20 |
21 | positionHeading: number = 0;
22 |
23 | positionTilt: number = 0;
24 |
25 | }
26 |
27 | export { ScenePosition };
--------------------------------------------------------------------------------
/src/format/DDDFormat.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * DDDViewer - DDD(3Ds) Viewer library for DDD-generated GIS 3D scenes
3 | * Copyright 2021 Jose Juan Montes and Contributors
4 | * MIT License (see LICENSE file)
5 | */
6 |
7 | import { Camera } from "@babylonjs/core";
8 | import { SceneViewer } from "SceneViewer";
9 |
10 | /**
11 | * Manages reading of files in DDD format. DDD files are glTF files
12 | * with custom metadata. This class processes nodes and metadata.
13 | */
14 | class DDDFormat {
15 |
16 | protected dddViewer: SceneViewer;
17 |
18 | constructor(dddViewer: SceneViewer) {
19 | // Reference to DDDViewer
20 | this.dddViewer = dddViewer;
21 | }
22 |
23 |
24 |
25 | }
26 |
27 | export { DDDFormat };
--------------------------------------------------------------------------------
/src/format/GeoJSON.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * DDDViewer - DDD(3Ds) Viewer library for DDD-generated GIS 3D scenes
3 | * Copyright 2021 Jose Juan Montes and Contributors
4 | * MIT License (see LICENSE file)
5 | */
6 |
7 | import { Camera } from "@babylonjs/core";
8 | import { SceneViewer } from "SceneViewer";
9 |
10 | /**
11 | */
12 | class GeoJSONFormat {
13 |
14 | protected dddViewer: SceneViewer;
15 |
16 | constructor(dddViewer: SceneViewer) {
17 | // Reference to DDDViewer
18 | this.dddViewer = dddViewer;
19 | }
20 |
21 | }
22 |
23 | export { GeoJSONFormat };
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @file Automatically generated by barrelsby.
3 | */
4 |
5 | export * from "./DDDViewerConfig";
6 | export * from "./SceneViewer";
7 | export * from "./ViewerState";
8 | export * from "./camera/BaseCameraController";
9 | export * from "./camera/FreeCameraController";
10 | export * from "./camera/GeolocationCameraController";
11 | export * from "./camera/OrbitCameraController";
12 | export * from "./camera/PanningCameraController";
13 | export * from "./camera/WalkCameraController";
14 | export * from "./camera/WalkCollideCameraController";
15 | export * from "./core/DDDObjectRef";
16 | export * from "./core/ScenePosition";
17 | export * from "./format/DDDFormat";
18 | export * from "./format/GeoJSON";
19 | export * from "./layers/Base3DLayer";
20 | export * from "./layers/DDD3DLayer";
21 | export * from "./layers/GeoJson3DLayer";
22 | export * from "./layers/GeoTile3DLayer";
23 | export * from "./layers/LayerManager";
24 | export * from "./layers/OverlayLayer";
25 | export * from "./loading/QueueLoader";
26 | export * from "./process/ViewerProcess";
27 | export * from "./process/ViewerProcessManager";
28 | export * from "./process/anim/AnimationProcess";
29 | export * from "./process/anim/CameraMoveAnimationProcess";
30 | export * from "./process/anim/DateTimeAnimationProcess";
31 | export * from "./process/anim/TextAnimationProcess";
32 | export * from "./process/sequencer/ViewerSequencer";
33 | export * from "./render/environment/DefaultEnvironment";
34 | export * from "./render/materials/SkyboxMaterial";
35 | export * from "./render/materials/TerrainMaterial";
36 | export * from "./render/materials/TextMaterial";
37 | export * from "./render/pipeline/DefaultRenderPipeline";
38 | export * from "./render/skybox/CubemapSkybox";
39 | export * from "./render/skybox/DynamicSkybox";
40 | export * from "./render/skybox/Skybox";
41 |
--------------------------------------------------------------------------------
/src/layers/Base3DLayer.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * DDDViewer - DDD(3Ds) Viewer library for DDD-generated GIS 3D scenes
3 | * Copyright 2021 Jose Juan Montes and contributors
4 | * MIT License (see LICENSE file)
5 | */
6 |
7 | import { SceneViewer } from "SceneViewer";
8 | import { LayerManager } from "./LayerManager";
9 |
10 | /**
11 | * DDD Viewer base layer class.
12 | */
13 | abstract class Base3DLayer {
14 |
15 | key: string;
16 | visible: boolean = true;
17 |
18 | protected dddViewer: SceneViewer | null;
19 | protected layerManager: LayerManager | null; // TODO: added for backwards compat (for convenience), should be removed
20 |
21 | constructor(key: string) {
22 | this.key = key;
23 | this.visible = true;
24 |
25 | this.dddViewer = null;
26 | this.layerManager = null;
27 | }
28 |
29 | abstract update(deltaTime: number): void;
30 |
31 | abstract clearScene(): void;
32 |
33 | setVisible(visible: boolean): void {
34 | this.visible = visible;
35 | }
36 |
37 | setViewer(dddViewer: SceneViewer | null): void {
38 | this.dddViewer = dddViewer;
39 | this.layerManager = dddViewer ? dddViewer.layerManager : null;
40 |
41 | if (!dddViewer) {
42 | this.clearScene();
43 | }
44 | }
45 |
46 | }
47 |
48 | export { Base3DLayer };
--------------------------------------------------------------------------------
/src/layers/DDD3DLayer.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * DDDViewer - DDD(3Ds) Viewer library for DDD-generated GIS 3D models
3 | * Copyright 2021 Jose Juan Montes and contributors
4 | * MIT License (see LICENSE file)
5 | */
6 |
7 | import {
8 | AbstractMesh,
9 | Color3,
10 | Mesh,
11 | MeshBuilder,
12 | Node,
13 | Ray,
14 | StandardMaterial,
15 | Texture,
16 | TransformNode,
17 | Vector3,
18 | } from "@babylonjs/core";
19 | import { Coordinate } from "ol/coordinate";
20 | import * as extent from "ol/extent";
21 | import * as olProj from "ol/proj";
22 | import { createXYZ, extentFromProjection } from "ol/tilegrid";
23 | import TileGrid from "ol/tilegrid/TileGrid";
24 | import { SceneViewer } from "../SceneViewer";
25 | import { Base3DLayer } from "./Base3DLayer";
26 |
27 | class DDD3DLayer extends Base3DLayer {
28 | node: Node | null = null;
29 |
30 | constructor(key: string) {
31 | super(key);
32 | console.log("Constructing new DDD3DLayer.");
33 | // TODO: This makes sense here, but is also duplicated on SceneViewer
34 | }
35 |
36 | update(): void {}
37 |
38 | // TODO: Tile coordinates should be made a type or reuse OpenLayers grid coordinates type
39 | loadData(data: string): void {
40 | const objectBlob = new Blob([ data ], { type: "model/gltf-binary" });
41 | const objectUrl = URL.createObjectURL(objectBlob);
42 |
43 | const layerKey = "model";
44 | const pivot = new Mesh(
45 | "dddobject_" + layerKey,
46 | this.layerManager!.sceneViewer.scene,
47 | );
48 |
49 | this.layerManager!.sceneViewer.queueLoader.enqueueLoadModel(
50 | objectUrl,
51 | // onSuccess
52 | (newMeshes: AbstractMesh[], _particleSystems: any, _skeletons: any) => {
53 | //console.log("DDD3DLayer GLB loaded", newMeshes);
54 |
55 | newMeshes.forEach((mesh: AbstractMesh, _i: number) => {
56 | if (this.layerManager!.sceneViewer.shadowGenerator) {
57 | mesh.receiveShadows = true;
58 | if (
59 | mesh.metadata &&
60 | mesh.metadata.gltf.extras &&
61 | (mesh.metadata.gltf.extras["ddd:shadows"] === false ||
62 | mesh.metadata.gltf.extras["ddd:shadows"] === "false" ||
63 | mesh.metadata.gltf.extras["ddd:path"].indexOf("/Areas_") > 0 ||
64 | mesh.metadata.gltf.extras["ddd:path"].indexOf("/Ways_") > 0)
65 | ) {
66 | //console.debug("No shadow");
67 | return;
68 | }
69 |
70 | // TODO: Do this at SceneViewer processMesh level
71 | this.layerManager!.sceneViewer.shadowGenerator.getShadowMap()!.renderList!.push(
72 | mesh,
73 | );
74 | }
75 | });
76 |
77 | // Reparent root
78 | (newMeshes[0]).parent = pivot;
79 | this.node = pivot;
80 |
81 | pivot.freezeWorldMatrix();
82 |
83 | this.layerManager!.sceneViewer.scene.blockfreeActiveMeshesAndRenderingGroups =
84 | true;
85 | this.layerManager!.sceneViewer.processMesh(pivot, pivot); // TODO: Wrong conversion, use Node for "processMesh"
86 | this.layerManager!.sceneViewer.scene.blockfreeActiveMeshesAndRenderingGroups =
87 | false;
88 |
89 | //pivot.occlusionType = AbstractMesh.OCCLUSION_TYPE_OPTIMISTIC;
90 | pivot.freezeWorldMatrix();
91 | },
92 | // onError
93 | (_scene: any, _msg: string, ex: any) => {
94 | // eslint-disable-next-line no-console
95 | console.log("Tile model (.glb) loading error: ", ex);
96 | },
97 | );
98 | //model.parent = pivot;
99 | }
100 |
101 | clearScene() {
102 | console.log("Deleting DDD3DLayer: " + this.key);
103 | if (this.node) {
104 | //tile.node.setEnabled(false);
105 | this.node.parent = null;
106 | this.node.dispose();
107 | this.node = null;
108 | }
109 | }
110 |
111 | setVisible(visible: boolean) {
112 | super.setVisible(visible);
113 | if (this.node) this.node.setEnabled(this.visible);
114 | }
115 | }
116 |
117 | export { DDD3DLayer };
118 |
--------------------------------------------------------------------------------
/src/layers/GeoJson3DLayer.ts:
--------------------------------------------------------------------------------
1 | import {
2 | AbstractMesh,
3 | Color3,
4 | Mesh,
5 | MeshBuilder,
6 | Node,
7 | Ray,
8 | StandardMaterial,
9 | Texture,
10 | TransformNode,
11 | Vector3,
12 | } from "@babylonjs/core";
13 | import { Coordinate } from "ol/coordinate";
14 | import * as extent from "ol/extent";
15 | import * as olProj from "ol/proj";
16 | import { createXYZ, extentFromProjection } from "ol/tilegrid";
17 | import TileGrid from "ol/tilegrid/TileGrid";
18 | import { SceneViewer } from "../SceneViewer";
19 | import { Base3DLayer } from "./Base3DLayer";
20 |
21 | class GeoJsonItem {
22 | properties: any;
23 | }
24 |
25 | class GeoJsonPoint extends GeoJsonItem {
26 | coordsWgs84: Vector3 = Vector3.Zero();
27 | coordsScene: Vector3 = Vector3.Zero();
28 |
29 | constructor(coordsWgs84: Vector3) {
30 | super();
31 | this.coordsWgs84 = coordsWgs84;
32 | }
33 |
34 | /**
35 | * Currently receives a viewer for coordinate transformations
36 | */
37 | transformCoords(viewer: SceneViewer) {
38 | const csa = viewer.wgs84ToScene(this.coordsWgs84.asArray());
39 | this.coordsScene = Vector3.FromArray(csa);
40 | }
41 | }
42 |
43 | class GeoJsonLine extends GeoJsonItem {
44 | // TODO: These should be buffers
45 | coordsWgs84: Vector3[] = [];
46 | coordsScene: Vector3[] = [];
47 |
48 | constructor(coordsWgs84: Vector3[]) {
49 | super();
50 | this.coordsWgs84 = coordsWgs84;
51 | }
52 |
53 | /**
54 | * Currently receives a viewer for coordinate transformations
55 | */
56 | transformCoords(viewer: SceneViewer) {
57 | this.coordsScene = [];
58 | for (const v of this.coordsWgs84) {
59 | const csa = viewer.wgs84ToScene(v.asArray());
60 | this.coordsScene.push(Vector3.FromArray(csa));
61 | }
62 | }
63 | }
64 |
65 | class GeoJson3DLayer extends Base3DLayer {
66 | featuresPoints: GeoJsonPoint[] = [];
67 | featuresLines: GeoJsonLine[] = [];
68 |
69 | altitudeOffset: number = 50;
70 | colorHex: string = "#ff00ff";
71 |
72 | //private sceneNodes: Mesh[] = [];
73 | private rootNode: TransformNode | null = null;
74 |
75 | private featureMaterial: StandardMaterial | null = null;
76 |
77 | constructor(key: string, geojsonData: any) {
78 | super(key);
79 | setTimeout(() => {
80 | this.loadFromGeoJson(geojsonData);
81 | this.projectFeatures();
82 | this.updateSceneFromFeatures();
83 | }, 0);
84 | }
85 |
86 | update(): void {}
87 |
88 | setColor(colorHex: string) {
89 | this.colorHex = colorHex;
90 |
91 | if (this.featureMaterial) {
92 | const color = Color3.FromHexString(colorHex);
93 | //this.featureMaterial.unfreeze();
94 | this.featureMaterial.diffuseColor = color;
95 | this.featureMaterial.emissiveColor = color;
96 | this.featureMaterial.disableLighting = true;
97 | //this.featureMaterial.freeze();
98 | }
99 | }
100 |
101 | setVisible(visible: boolean) {
102 | super.setVisible(visible);
103 | if (this.rootNode) this.rootNode.setEnabled(this.visible);
104 | /*
105 | for (let node of this.sceneNodes) {
106 | node.setEnabled(this.visible);
107 | }
108 | */
109 | }
110 |
111 | setAltitudeOffset(altitudeOffset: number) {
112 | this.altitudeOffset = altitudeOffset;
113 | if (this.rootNode) {
114 | this.rootNode.position.y = this.altitudeOffset; // Apply offset
115 | }
116 | }
117 |
118 | /**
119 | * Processes GeoJSON data (already loaded as Javascript objects) and loads the different features.
120 | */
121 | loadFromGeoJson(data: any): void {
122 | for (const feature of data["features"]) {
123 | this.loadFeature(feature);
124 | }
125 | }
126 |
127 | loadFeature(feature: any): void {
128 | const properties = feature["properties"];
129 | const geometry = feature["geometry"];
130 |
131 | if (geometry["type"] == "Point") {
132 | const lat = geometry["coordinates"][0];
133 | const lon = geometry["coordinates"][1];
134 | const alt =
135 | geometry["coordinates"].length > 2 ? geometry["coordinates"][2] : 0;
136 | const geojsonItem = new GeoJsonPoint(new Vector3(lat, lon, alt));
137 | geojsonItem.properties = properties;
138 |
139 | this.featuresPoints.push(geojsonItem);
140 | } else if (geometry["type"] == "LineString") {
141 | const coords: Vector3[] = [];
142 | for (const coord of geometry["coordinates"]) {
143 | const lat = coord[0];
144 | const lon = coord[1];
145 | const alt = coord.length > 2 ? coord[2] : 0;
146 | const v = new Vector3(lat, lon, alt);
147 | coords.push(v);
148 | }
149 | const geojsonItem = new GeoJsonLine(coords);
150 | geojsonItem.properties = properties;
151 |
152 | this.featuresLines.push(geojsonItem);
153 | } else {
154 | console.info("Unknown GeoJSON geometry type: " + geometry["type"]);
155 | }
156 | }
157 |
158 | projectFeatures(): void {
159 | for (const feature of this.featuresPoints) {
160 | feature.transformCoords(this.layerManager!.sceneViewer);
161 | }
162 | for (const feature of this.featuresLines) {
163 | feature.transformCoords(this.layerManager!.sceneViewer);
164 | }
165 | }
166 |
167 | /**
168 | * TODO: This should be a more generic "markers" and "lines" vector visualization facility.
169 | */
170 | updateSceneFromFeatures(): void {
171 | const sceneViewer = this.layerManager!.sceneViewer;
172 |
173 | // Create material only if it hasn't already been created
174 | if (!this.featureMaterial) {
175 | const featureMaterial = new StandardMaterial(
176 | "featureMaterial",
177 | sceneViewer.scene,
178 | );
179 | this.featureMaterial = featureMaterial;
180 | }
181 |
182 | if (!this.rootNode) {
183 | this.rootNode = new TransformNode(
184 | "geoJson3DLayer-root",
185 | sceneViewer.scene,
186 | );
187 | }
188 |
189 | this.setColor(this.colorHex);
190 |
191 | for (const feature of this.featuresPoints) {
192 | const marker = MeshBuilder.CreateSphere(
193 | "pointMarker",
194 | { diameter: 1.5, segments: 3 },
195 | sceneViewer.scene,
196 | );
197 | marker.material = this.featureMaterial;
198 | marker.position = feature.coordsScene;
199 | marker.parent = this.rootNode;
200 | //sceneNodes.push(marker);
201 | }
202 |
203 | for (const feature of this.featuresLines) {
204 | const marker = MeshBuilder.CreateLines(
205 | "lineMarker",
206 | { points: feature.coordsScene },
207 | sceneViewer.scene,
208 | );
209 | marker.material = this.featureMaterial;
210 | marker.parent = this.rootNode;
211 | //this.sceneNodes.push(marker);
212 | }
213 |
214 | this.setAltitudeOffset(this.altitudeOffset);
215 | }
216 |
217 | clearScene() {
218 | if (this.rootNode) {
219 | this.rootNode.parent = null;
220 | this.rootNode.dispose();
221 | this.rootNode = null;
222 | }
223 | }
224 | }
225 |
226 | export { GeoJson3DLayer };
227 |
--------------------------------------------------------------------------------
/src/layers/GeoTile3DLayer.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * DDDViewer - DDD(3Ds) Viewer library for DDD-generated GIS 3D models
3 | * Copyright 2021 Jose Juan Montes and contributors
4 | * MIT License (see LICENSE file)
5 | */
6 |
7 | import {
8 | AbstractMesh,
9 | Color3,
10 | Mesh,
11 | MeshBuilder,
12 | Node,
13 | Ray,
14 | StandardMaterial,
15 | Texture,
16 | TransformNode,
17 | Vector3,
18 | } from "@babylonjs/core";
19 | import { Coordinate } from "ol/coordinate";
20 | import * as extent from "ol/extent";
21 | import * as olProj from "ol/proj";
22 | import { createXYZ, extentFromProjection } from "ol/tilegrid";
23 | import TileGrid from "ol/tilegrid/TileGrid";
24 | import { SceneViewer } from "../SceneViewer";
25 | import { Base3DLayer } from "./Base3DLayer";
26 |
27 | class Tile3D {
28 | key: string;
29 | status: string | null;
30 | node: Node | null;
31 |
32 | constructor(key: string) {
33 | this.key = key;
34 | this.status = null;
35 | this.node = null;
36 | }
37 | }
38 |
39 | class GeoTile3D extends Tile3D {
40 | coordsTileGrid: number[] | null;
41 |
42 | constructor(key: string) {
43 | super(key);
44 | this.coordsTileGrid = null;
45 | }
46 | }
47 |
48 | class GeoTile3DLayer extends Base3DLayer {
49 | tiles: { [key: string]: GeoTile3D };
50 |
51 | groundTextureLayerUrl: string | null = null;
52 | //groundTextureLayerUrl = "https://a.tile.openstreetmap.org/{z}/{x}/{y}.png"; // "https://a.tile.openstreetmap.org/{z}/{x}/{y}.png";
53 | //groundTextureLayerUrl = "http://localhost:8090/wmts/ign_ortho/GLOBAL_WEBMERCATOR/{z}/{x}/{y}.jpeg"; // "https://a.tile.openstreetmap.org/{z}/{x}/{y}.png";
54 |
55 | private _lastHeight = 0; // Used to hack positioning of tiles before height is known
56 | private _lastLoadDynamic = 0;
57 | private _initialHeightSet = false;
58 |
59 | tilesLoadedCount = 0;
60 | tileGrid: TileGrid;
61 |
62 | constructor() {
63 | super("ddd-osm-3d"); // FIXME: key is hardcoded
64 | this.tiles = {};
65 | // TODO: This makes sense here, but is also duplicated on SceneViewer
66 | this.tileGrid = createXYZ({
67 | extent: extentFromProjection("EPSG:3857"),
68 | //maxResolution: options.maxResolution,
69 | //maxZoom: options.maxZoom,
70 | //minZoom: options.minZoom,
71 | //tileSize: options.tileSize,
72 | });
73 | }
74 |
75 | update(): void {
76 | this.updateTilesDynamic();
77 | }
78 |
79 | /*
80 | * From: https://bartwronski.com/2017/04/13/cull-that-cone/
81 | */
82 | testConeSphere(
83 | origin: Vector3,
84 | forward: Vector3,
85 | size: number,
86 | angle: number,
87 | sphereCenter: Vector3,
88 | sphereRadius: number,
89 | ): boolean {
90 | //console.debug(origin, forward, size, angle, sphereCenter, sphereRadius);
91 | const V = sphereCenter.subtract(origin);
92 | const VlenSq = Vector3.Dot(V, V);
93 | const V1len = Vector3.Dot(V, forward);
94 | const distanceClosestPoint =
95 | Math.cos(angle) * Math.sqrt(VlenSq - V1len * V1len) -
96 | V1len * Math.sin(angle);
97 |
98 | const angleCull = distanceClosestPoint > sphereRadius;
99 | const frontCull = V1len > sphereRadius + size;
100 | const backCull = V1len < -sphereRadius;
101 |
102 | return !(angleCull || frontCull || backCull);
103 | }
104 |
105 | updateTilesDynamic(): void {
106 | // loading chunks each 100 frames. Bad performance
107 | this._lastLoadDynamic -= 1;
108 | if (this._lastLoadDynamic > 0) {
109 | return;
110 | }
111 | this._lastLoadDynamic = 1;
112 |
113 | const sceneViewer: SceneViewer = this.layerManager!.sceneViewer;
114 |
115 | const positionWGS84: number[] = (
116 | this.layerManager?.sceneViewer.positionWGS84()
117 | );
118 | const coordsWGS84: Coordinate = [ positionWGS84[0], positionWGS84[1] ];
119 | const coordsUtm: Coordinate = olProj.transform(
120 | coordsWGS84,
121 | "EPSG:4326",
122 | "EPSG:3857",
123 | );
124 | const tileCoords = this.tileGrid.getTileCoordForCoordAndZ(coordsUtm, 17);
125 | //const tileKey = tileCoords[0] + "/" + tileCoords[1] + "/" + tileCoords[2];
126 |
127 | // Calculate frustrum (2D)
128 | const frustrumOrigin = sceneViewer.camera!.position.clone();
129 | //if (this._lastHeight) { frustrumOrigin.y -= this._lastHeight; } // Considers all tiles in a plane centered on last
130 | frustrumOrigin.y = 0;
131 | const frustrumForward = sceneViewer.camera!.getDirection(Vector3.Forward());
132 | frustrumForward.y = 0;
133 | frustrumForward.normalize();
134 | const frustrumSize = sceneViewer.viewerState.sceneTileDrawDistance * 300.0; // 1500.0;
135 | const frustrumAngle = sceneViewer.camera!.fov * 2.0; // * (Math.PI / 180.0); // 30.0;
136 |
137 | this.loadTile(tileCoords); // ensure elevation for current tile
138 |
139 | // Project frustrum corners to tiles
140 |
141 | // Calculate tiles inside frustrum
142 | const tiledistWalk = sceneViewer.viewerState.sceneTileDrawDistance + 3;
143 | const tiledistDraw = sceneViewer.viewerState.sceneTileDrawDistance + 0.7;
144 | for (let i = -tiledistWalk; i <= tiledistWalk; i++) {
145 | for (let j = -tiledistWalk; j <= tiledistWalk; j++) {
146 | // Current tile is already enqueued
147 | if (i === 0 && j === 0) {
148 | continue;
149 | }
150 |
151 | if (i * i + j * j > tiledistDraw * tiledistDraw) {
152 | this.disableTile([
153 | tileCoords[0],
154 | tileCoords[1] + i,
155 | tileCoords[2] + j,
156 | ]);
157 | } else {
158 | const tileCenter = this.tileGrid.getTileCoordCenter([
159 | tileCoords[0],
160 | tileCoords[1] + i,
161 | tileCoords[2] + j,
162 | ]);
163 | const tileCenterWGS84 = olProj.transform(
164 | tileCenter,
165 | "EPSG:3857",
166 | "EPSG:4326",
167 | );
168 | const tileCenterScene =
169 | sceneViewer.projection.forward(tileCenterWGS84);
170 | const sphereCenter = new Vector3(
171 | tileCenterScene[0],
172 | 0,
173 | tileCenterScene[1],
174 | ); // TODO: Get median height from tile
175 | const sphereRadius = 230.0 / 2.0;
176 | if (
177 | this.testConeSphere(
178 | frustrumOrigin,
179 | frustrumForward,
180 | frustrumSize,
181 | frustrumAngle,
182 | sphereCenter,
183 | sphereRadius,
184 | )
185 | ) {
186 | //console.debug("Loading: ", [tileCoords[0], tileCoords[1] + i, tileCoords[2] + j])
187 | this.loadTile([
188 | tileCoords[0],
189 | tileCoords[1] + i,
190 | tileCoords[2] + j,
191 | ]);
192 | } else {
193 | //console.debug("Ignoring: ", [tileCoords[0], tileCoords[1] + i, tileCoords[2] + j])
194 | this.disableTile([
195 | tileCoords[0],
196 | tileCoords[1] + i,
197 | tileCoords[2] + j,
198 | ]);
199 | }
200 | }
201 | }
202 | }
203 |
204 | // Sort tiles by distance
205 |
206 | // Enqueue (1 on mobile? 2 on PC?)
207 |
208 | // setEnabled(false) on culled chunks
209 |
210 | // update LOD levels (call lodLevel - remove items, etc) per distance
211 |
212 | /*
213 | for (let i = -1; i <= 1; i++) {
214 | for (let j = -1; j <= 1; j++) {
215 | this.loadTile([tileCoords[0], tileCoords[1] + i, tileCoords[2] + j]);
216 | }
217 | }
218 | */
219 | }
220 |
221 | disableTile(tileCoords: number[]): void {
222 | const z = tileCoords[0];
223 | const x = tileCoords[1];
224 | const y = tileCoords[2];
225 | const tileKey = z + "/" + x + "/" + y;
226 |
227 | if (!(tileKey in this.tiles)) {
228 | return;
229 | }
230 |
231 | const tile = this.tiles[tileKey];
232 | if (tile.status !== "loading" && tile.node!.isEnabled(false)) {
233 | tile.node!.setEnabled(false);
234 | tile.node!.parent = null; // TODO: this was not working before (tile.parent did not apply)
235 | }
236 | }
237 |
238 | /**
239 | * Gets tile metadata.
240 | * It does this recursively searching for a "Metadata" named node, as the path exporting root metadata to the root node or scene itself hasn't been found to work.
241 | */
242 | getTileMetadata(node: Node): any {
243 | /*if (node.id.startsWith("Metadata")) {
244 | return node.metadata.gltf.extras;
245 | }*/
246 | for (const child of node.getChildren()) {
247 | // Must start with Metadata
248 | if (child.id.indexOf("Metadata") == 0) {
249 | return child.metadata.gltf.extras;
250 | }
251 | }
252 | for (const child of node.getChildren()) {
253 | const md = this.getTileMetadata(child);
254 | if (md !== null) {
255 | return md;
256 | }
257 | }
258 | return null;
259 | }
260 |
261 | // TODO: Tile coordinates should be made a type or reuse OpenLayers grid coordinates type
262 | loadTile(tileCoords: number[]): void {
263 | //console.debug(tileCoords);
264 | const z = tileCoords[0];
265 | const x = tileCoords[1];
266 | const y = tileCoords[2];
267 | const tileKey = z + "/" + x + "/" + y;
268 |
269 | const tileExtent = this.tileGrid.getTileCoordExtent(tileCoords);
270 | const tileCenter = extent.getCenter(tileExtent);
271 | const tileCenterWGS84 = olProj.transform(
272 | tileCenter,
273 | "EPSG:3857",
274 | "EPSG:4326",
275 | );
276 | //const tileCenterScene = this.layerManager!.sceneViewer.projection.forward( tileCenterWGS84 );
277 |
278 | const tileExtentMinScene =
279 | this.layerManager!.sceneViewer.projection.forward(
280 | olProj.transform(
281 | extent.getBottomLeft(tileExtent),
282 | "EPSG:3857",
283 | "EPSG:4326",
284 | ),
285 | );
286 | const tileExtentMaxScene =
287 | this.layerManager!.sceneViewer.projection.forward(
288 | olProj.transform(
289 | extent.getTopRight(tileExtent),
290 | "EPSG:3857",
291 | "EPSG:4326",
292 | ),
293 | );
294 | const sizeWidth = Math.abs(tileExtentMaxScene[0] - tileExtentMinScene[0]);
295 | const sizeHeight = Math.abs(tileExtentMaxScene[1] - tileExtentMinScene[1]);
296 |
297 | if (tileKey in this.tiles) {
298 | const tile = this.tiles[tileKey];
299 | if (tile.status !== "loading" && !tile.node!.isEnabled(false)) {
300 | tile.node!.parent = null; // this.layerManager!.sceneViewer.scene;
301 | tile.node!.setEnabled(true);
302 | //tile.node.freezeWorldMatrix();
303 | }
304 | return;
305 | } else {
306 | this.tiles[tileKey] = new GeoTile3D(tileKey);
307 | this.tiles[tileKey].status = "loading";
308 | this.tiles[tileKey].coordsTileGrid = tileCoords;
309 | }
310 |
311 | //const tileUrlBase = './scenes/ddd_http_';
312 | //const tileUrlBase = 'http://localhost:8000/cache/ddd_http/';
313 | //const tileUrlBase = 'http://' + app.dddConfig.tileUrlBase + ':8000/cache/ddd_http/';
314 | //const tileUrlBase = 'http://' + location.hostname + '/cache/ddd_http/';
315 | const tileUrlBase =
316 | this.layerManager!.sceneViewer.viewerState.dddConfig.tileUrlBase;
317 | const tileUrlSuffix =
318 | this.layerManager!.sceneViewer.viewerState.dddConfig.tileUrlSuffix; // e.g. ".uncompressed"
319 |
320 | //const tileUrl = tileUrlBase + z + "/" + x + "/" + y + ".glb";
321 | const tileUrl =
322 | tileUrlBase + z + "/" + x + "/" + y + tileUrlSuffix + ".glb";
323 |
324 | //console.debug("Loading: " + tileUrl);
325 |
326 | //const pivot = new TransformNode( "chunk_" + tileKey.replaceAll( "/", "_" ), this.layerManager!.sceneViewer.scene ); // new Mesh("chunk_" + tileKey, this.layerManager.sceneViewer.scene);
327 | const pivot = new Mesh(
328 | "chunk_" + tileKey.replaceAll("/", "_"),
329 | this.layerManager!.sceneViewer.scene,
330 | ); // new Mesh("chunk_" + tileKey, this.layerManager.sceneViewer.scene);
331 |
332 | //let reversePivot = new TransformNode("chunk_reverse_" + tileKey, this.scene); // new Mesh("chunk_" + tileKey, this.scene);
333 | //let rawPivot = new TransformNode("chunk_raw_" + tileKey, this.scene); // new Mesh("chunk_" + tileKey, this.scene);
334 | //reversePivot.scaling = new Vector3(1, 1, -1); // Babylon uses a parent node with this scale to flip glTF models, redone here
335 | //rawPivot.parent = reversePivot;
336 | //reversePivot.parent = pivot;
337 | //pivot.parent = this.scene;
338 |
339 | let marker = this.loadQuadMarker(tileCoords, Color3.Gray());
340 | this.tiles[tileKey].node = marker;
341 |
342 | this.layerManager!.sceneViewer.queueLoader.enqueueLoadModel(
343 | tileUrl,
344 | // onSuccess
345 | (newMeshes: AbstractMesh[], _particleSystems: any, _skeletons: any) => {
346 | // Ensure tile still exists:
347 | if (!(tileKey in this.tiles)) {
348 | newMeshes[0].parent = null;
349 | newMeshes[0].dispose();
350 | return;
351 | }
352 |
353 | //console.log("GLB loaded", newMeshes);
354 |
355 | marker.dispose(false, true);
356 | //marker.parent = null;
357 |
358 | let minHeight = Number.POSITIVE_INFINITY;
359 | let maxHeight = Number.NEGATIVE_INFINITY;
360 | newMeshes.forEach((mesh: AbstractMesh, _i: number) => {
361 | if (this.layerManager!.sceneViewer.shadowGenerator) {
362 | mesh.receiveShadows = true;
363 | if (
364 | mesh.metadata &&
365 | mesh.metadata.gltf.extras &&
366 | (mesh.metadata.gltf.extras["ddd:shadows"] === false ||
367 | mesh.metadata.gltf.extras["ddd:shadows"] === "false" ||
368 | mesh.metadata.gltf.extras["ddd:path"].indexOf("/Areas_") > 0 ||
369 | mesh.metadata.gltf.extras["ddd:path"].indexOf("/Ways_") > 0)
370 | ) {
371 | //console.debug("No shadow");
372 | return;
373 | }
374 |
375 | // TODO: Do this at SceneViewer processMesh level
376 | this.layerManager!.sceneViewer.shadowGenerator.getShadowMap()!.renderList!.push(
377 | mesh,
378 | );
379 | }
380 |
381 | //console.debug(mesh.getBoundingInfo());
382 | const heightMin = mesh.getBoundingInfo().boundingBox.minimumWorld.y;
383 | if (heightMin < minHeight && heightMin !== 0) {
384 | minHeight = heightMin;
385 | }
386 | const heightMax = mesh.getBoundingInfo().boundingBox.maximumWorld.y;
387 | if (heightMax > maxHeight && heightMax !== 0) {
388 | maxHeight = heightMax;
389 | }
390 |
391 | /*
392 | if(mesh.material) {
393 | if (mesh.id.indexOf("Item") < 0 && mesh.id.indexOf("Building") < 0) {
394 | mesh.material = materialPlane;
395 | }
396 | //mesh.overrideMaterialSideOrientation = Mesh.DOUBLESIDE;
397 | //mesh.updateMeshPositions();
398 | }
399 | */
400 | //console.debug(mesh.absolutePosition);
401 | //mesh.position = new Vector3(mesh.position.x, mesh.position.y, -mesh.position.z);
402 | //mesh.updateMeshPositions();
403 | //mesh.parent = rawPivot;
404 | });
405 |
406 | // Reparent root
407 | (newMeshes[0]).parent = pivot;
408 | newMeshes[0].id = tileKey.replaceAll("/", "_");
409 | this.tiles[tileKey].node = pivot;
410 | this.tiles[tileKey].status = "loaded";
411 |
412 | const tileExtent = this.tileGrid.getTileCoordExtent(tileCoords);
413 | const tileCenter = extent.getCenter(tileExtent);
414 | const tileCenterWGS84 = olProj.transform(
415 | tileCenter,
416 | "EPSG:3857",
417 | "EPSG:4326",
418 | );
419 | const tileCenterScene =
420 | this.layerManager!.sceneViewer.projection.forward(tileCenterWGS84);
421 |
422 | //let distance = 225.0;
423 | //pivot.position = new Vector3((x - 62360) * distance, 0, -(y - 48539) * distance);
424 | //pivot.scaling = new Vector3(1, 1, -1);
425 | pivot.position = new Vector3(tileCenterScene[0], 0, tileCenterScene[1]);
426 | pivot.rotation = new Vector3(0, Math.PI, 0);
427 |
428 | pivot.freezeWorldMatrix();
429 |
430 | this.tiles[tileKey].node = pivot;
431 |
432 | this._lastHeight = minHeight;
433 |
434 | this.tilesLoadedCount++;
435 | if (!this._initialHeightSet) {
436 | //console.debug("Repositioning camera height based on terrain height: " + maxHeight);
437 | //that.layerManager.sceneViewer.camera.position.y += maxHeight;
438 |
439 | const ray = new Ray(
440 | new Vector3(
441 | this.layerManager!.sceneViewer.camera!.position.x,
442 | -100.0,
443 | this.layerManager!.sceneViewer.camera!.position.z,
444 | ),
445 | new Vector3(0, 1, 0),
446 | 3000.0,
447 | );
448 | const pickResult =
449 | this.layerManager!.sceneViewer.scene.pickWithRay(ray);
450 | if (
451 | pickResult &&
452 | pickResult.pickedMesh &&
453 | pickResult.pickedMesh.id &&
454 | pickResult.pickedMesh.id.indexOf("placeholder_") !== 0 &&
455 | pickResult.pickedMesh.id.indexOf("skyBox") !== 0
456 | ) {
457 | //console.debug("Setting height from: " + pickResult.pickedMesh.id);
458 | this._initialHeightSet = true;
459 | this.layerManager!.sceneViewer.camera!.position.y =
460 | pickResult.distance - 100.0;
461 | if (
462 | this.layerManager!.sceneViewer.viewerState.positionGroundHeight
463 | ) {
464 | this.layerManager!.sceneViewer.camera!.position.y +=
465 | this.layerManager!.sceneViewer.viewerState.positionGroundHeight;
466 | } else {
467 | this.layerManager!.sceneViewer.camera!.position.y += 40.0;
468 | }
469 | } else {
470 | //that._tilesLoadedCount--;
471 | //that.layerManager.sceneViewer.camera.position.y += maxHeight;
472 | }
473 | }
474 |
475 | const tileMetadata = this.getTileMetadata(pivot);
476 | //console.debug("Tile metadata: ", tileMetadata);
477 |
478 | // Replace materials, instancing...
479 | pivot.metadata = {
480 | tileCoords: tileCoords,
481 | tileSize: [ sizeWidth, sizeHeight ],
482 | tileInfo: tileMetadata,
483 | };
484 |
485 | this.layerManager!.sceneViewer.scene.blockfreeActiveMeshesAndRenderingGroups =
486 | true;
487 | this.layerManager!.sceneViewer.processMesh(pivot, pivot); // TODO: Wrong conversion, use Node for "processMesh"
488 | this.layerManager!.sceneViewer.scene.blockfreeActiveMeshesAndRenderingGroups =
489 | false;
490 |
491 | //pivot.occlusionType = AbstractMesh.OCCLUSION_TYPE_OPTIMISTIC;
492 | pivot.freezeWorldMatrix();
493 |
494 | // TODO: Removed during TS migration, but this is needed to support ground texture replacement
495 | //this.groundTextureLayerProcessNode( tileCoords, pivot );
496 |
497 | // Check if the selected node is in the recently loaded node
498 | // TODO: Should use a generic notification + object id/naming system
499 | if (this.layerManager!.sceneViewer.viewerState.sceneSelectedMeshId) {
500 | const criteria = {
501 | _node_name:
502 | this.layerManager!.sceneViewer.viewerState.sceneSelectedMeshId,
503 | };
504 | //console.debug(criteria);
505 | const foundMesh = this.layerManager!.sceneViewer.findNode(
506 | pivot,
507 | criteria,
508 | );
509 | //console.debug(foundMesh);
510 | if (foundMesh) {
511 | this.layerManager!.sceneViewer.selectMesh(foundMesh, true);
512 | this.layerManager!.sceneViewer.viewerState.sceneSelectedMeshId =
513 | null; // Triggers watchers update
514 | }
515 | }
516 |
517 | /*
518 | this.sceneViewer.selectMesh(pickResult.pickedMesh);
519 | let meshName = pickResult.pickedMesh.id.split("/").pop().replaceAll('#', '_'); // .replaceAll("_", " ");
520 | this.$router.push('/3d/item/' + meshName + '/' + this.sceneViewer.positionString()).catch(()=>{});
521 | */
522 | },
523 | // onError
524 | (_scene: any, _msg: string, ex: any) => {
525 | // eslint-disable-next-line no-console
526 |
527 | //console.debug( "Tile model (.glb) loading error: ", ex );
528 |
529 | const request = ex.innerError.request;
530 |
531 | if (request != null && request.status === 404) {
532 | // 404 - tile is being generated, show OSM tile as replacement
533 | //console.debug(ex.request);
534 |
535 | marker.dispose(false, true);
536 | marker = this.loadQuadTile(tileCoords); // , Color3.Red()
537 | this.tiles[tileKey].node = marker; // "notfound";
538 | this.tiles[tileKey].status = "notfound";
539 |
540 | // Process response, DDD server includes JSON info about tile generation status
541 | try {
542 | const dataView = new DataView(request.response);
543 | // TextDecoder interface is documented at http://encoding.spec.whatwg.org/#interface-textdecoder
544 | const decoder = new TextDecoder("utf-8");
545 | const decodedString = decoder.decode(dataView);
546 | const responseData = JSON.parse(decodedString);
547 | console.debug(responseData);
548 | this.layerManager!.sceneViewer.viewerState.remoteQueueJobsStatus.push(
549 | responseData,
550 | );
551 | } catch (e) {
552 | console.error("Could not add queued job to viewer state: " + e);
553 | }
554 | } else {
555 | // Error: colour marker red
556 | console.debug("Tile model (.glb) loading error (not 404): ", ex);
557 |
558 | marker.dispose(false, true);
559 | marker = this.loadQuadTile(tileCoords); // , Color3.Red()
560 | this.tiles[tileKey].node = marker; // "notfound";
561 | this.tiles[tileKey].status = "error";
562 |
563 | const color = Color3.Red();
564 | ((marker).material).emissiveColor = color;
565 | }
566 | },
567 | );
568 | //model.parent = pivot;
569 | }
570 |
571 | loadQuadMarker(tileCoords: number[], color: Color3 = Color3.Gray()): Node {
572 | const z = tileCoords[0];
573 | const x = tileCoords[1];
574 | const y = tileCoords[2];
575 | const tileKey = z + "/" + x + "/" + y;
576 |
577 | const tileExtent = this.tileGrid.getTileCoordExtent(tileCoords);
578 | const tileCenter = extent.getCenter(tileExtent);
579 | const tileCenterWGS84 = olProj.transform(
580 | tileCenter,
581 | "EPSG:3857",
582 | "EPSG:4326",
583 | );
584 | const tileCenterScene =
585 | this.layerManager!.sceneViewer.projection.forward(tileCenterWGS84);
586 |
587 | const tileExtentMinScene =
588 | this.layerManager!.sceneViewer.projection.forward(
589 | olProj.transform(
590 | extent.getBottomLeft(tileExtent),
591 | "EPSG:3857",
592 | "EPSG:4326",
593 | ),
594 | );
595 | const tileExtentMaxScene =
596 | this.layerManager!.sceneViewer.projection.forward(
597 | olProj.transform(
598 | extent.getTopRight(tileExtent),
599 | "EPSG:3857",
600 | "EPSG:4326",
601 | ),
602 | );
603 | const sizeWidth = Math.abs(tileExtentMaxScene[0] - tileExtentMinScene[0]);
604 | const sizeHeight = Math.abs(tileExtentMaxScene[1] - tileExtentMinScene[1]);
605 |
606 | const markerName = "Tile " + tileKey; // "placeholder_" + tileKey
607 | const marker = MeshBuilder.CreatePlane(
608 | "placeholder_" + tileKey,
609 | {
610 | width: sizeWidth,
611 | height: sizeHeight,
612 | sideOrientation: Mesh.DOUBLESIDE,
613 | },
614 | this.layerManager!.sceneViewer.scene,
615 | );
616 | marker.metadata = { "ddd:title": markerName };
617 |
618 | marker.position = new Vector3(
619 | tileCenterScene[0],
620 | this._lastHeight,
621 | tileCenterScene[1],
622 | );
623 | marker.rotation = new Vector3(Math.PI * 0.5, 0, 0);
624 |
625 | //Creation of a repeated textured material
626 | const materialPlane = new StandardMaterial(
627 | "textureTile_" + tileKey,
628 | this.layerManager!.sceneViewer.scene,
629 | );
630 | //materialPlane.diffuseTexture = new Texture("https://a.tile.openstreetmap.org/" + z + "/" + x + "/" + y + ".png", this.scene);
631 | materialPlane.diffuseColor = color;
632 | materialPlane.specularColor = Color3.Black();
633 | /*
634 | materialPlane.diffuseTexture.uScale = 1.0 / 225.0;
635 | materialPlane.diffuseTexture.vScale = -1.0 / 225.0;
636 | materialPlane.diffuseTexture.uOffset = -0.5;
637 | materialPlane.diffuseTexture.vOffset = -0.5;
638 | */
639 | materialPlane.emissiveColor = color; // new Color3(1.0, 1.0, 1.);
640 | materialPlane.disableLighting = true;
641 | materialPlane.backFaceCulling = false;
642 |
643 | marker.material = materialPlane;
644 |
645 | return marker;
646 | }
647 |
648 | loadQuadTile(tileCoords: number[], color = Color3.White()): Node {
649 | const z = tileCoords[0];
650 | const x = tileCoords[1];
651 | const y = tileCoords[2];
652 | const tileKey = z + "/" + x + "/" + y;
653 |
654 | const tileExtent = this.tileGrid.getTileCoordExtent(tileCoords);
655 | const tileCenter = extent.getCenter(tileExtent);
656 | const tileCenterWGS84 = olProj.transform(
657 | tileCenter,
658 | "EPSG:3857",
659 | "EPSG:4326",
660 | );
661 | const tileCenterScene =
662 | this.layerManager!.sceneViewer.projection.forward(tileCenterWGS84);
663 |
664 | const tileExtentMinScene =
665 | this.layerManager!.sceneViewer.projection.forward(
666 | olProj.transform(
667 | extent.getBottomLeft(tileExtent),
668 | "EPSG:3857",
669 | "EPSG:4326",
670 | ),
671 | );
672 | const tileExtentMaxScene =
673 | this.layerManager!.sceneViewer.projection.forward(
674 | olProj.transform(
675 | extent.getTopRight(tileExtent),
676 | "EPSG:3857",
677 | "EPSG:4326",
678 | ),
679 | );
680 | const sizeWidth = Math.abs(tileExtentMaxScene[0] - tileExtentMinScene[0]);
681 | const sizeHeight = Math.abs(tileExtentMaxScene[1] - tileExtentMinScene[1]);
682 |
683 | //console.debug(sizeWidth, sizeHeight);
684 | const markerName = "Tile " + tileKey; // "placeholder_" + tileKey
685 | const marker = MeshBuilder.CreatePlane(
686 | "placeholder_" + tileKey,
687 | {
688 | width: sizeWidth,
689 | height: sizeHeight,
690 | sideOrientation: Mesh.DOUBLESIDE,
691 | },
692 | this.layerManager!.sceneViewer.scene,
693 | );
694 | marker.metadata = { "ddd:title": markerName };
695 |
696 | marker.position = new Vector3(
697 | tileCenterScene[0],
698 | this._lastHeight,
699 | tileCenterScene[1],
700 | );
701 | marker.rotation = new Vector3(Math.PI * 0.5, 0, 0);
702 |
703 | //Creation of a repeated textured material
704 | const materialPlane = new StandardMaterial(
705 | "textureTile_" + tileKey,
706 | this.layerManager!.sceneViewer.scene,
707 | );
708 | materialPlane.diffuseTexture = new Texture(
709 | "https://a.tile.openstreetmap.org/" + z + "/" + x + "/" + y + ".png",
710 | this.layerManager!.sceneViewer.scene,
711 | );
712 |
713 | //if (!color) color = Color3.Black; //new Color3(0, 0, 0);
714 | materialPlane.specularColor = Color3.Black();
715 | /*
716 | materialPlane.diffuseTexture.uScale = 1.0 / 225.0;
717 | materialPlane.diffuseTexture.vScale = -1.0 / 225.0;
718 | materialPlane.diffuseTexture.uOffset = -0.5;
719 | materialPlane.diffuseTexture.vOffset = -0.5;
720 | */
721 | materialPlane.emissiveColor = color; // new Color3(1.0, 1.0, 1.);
722 | materialPlane.disableLighting = true;
723 | materialPlane.backFaceCulling = false;
724 |
725 | marker.material = materialPlane;
726 |
727 | return marker;
728 | }
729 |
730 | groundTextureLayerProcessNode(tile: GeoTile3D, node: Node): void {
731 | const tileCoords: number[] = tile.coordsTileGrid;
732 |
733 | let materialGround = null;
734 |
735 | if (this.groundTextureLayerUrl) {
736 | const z = tileCoords[0];
737 | const x = tileCoords[1];
738 | const y = tileCoords[2];
739 |
740 | const tileExtent = this.tileGrid.getTileCoordExtent(tileCoords);
741 | const tileCenter = extent.getCenter(tileExtent);
742 | const tileCenterWGS84 = olProj.transform(
743 | tileCenter,
744 | "EPSG:3857",
745 | "EPSG:4326",
746 | );
747 | const tileCenterScene =
748 | this.layerManager!.sceneViewer.projection.forward(tileCenterWGS84);
749 |
750 | const tileExtentMinScene =
751 | this.layerManager!.sceneViewer.projection.forward(
752 | olProj.transform(
753 | extent.getBottomLeft(tileExtent),
754 | "EPSG:3857",
755 | "EPSG:4326",
756 | ),
757 | );
758 | const tileExtentMaxScene =
759 | this.layerManager!.sceneViewer.projection.forward(
760 | olProj.transform(
761 | extent.getTopRight(tileExtent),
762 | "EPSG:3857",
763 | "EPSG:4326",
764 | ),
765 | );
766 | const sizeWidth = Math.abs(tileExtentMaxScene[0] - tileExtentMinScene[0]);
767 | const sizeHeight = Math.abs(
768 | tileExtentMaxScene[1] - tileExtentMinScene[1],
769 | );
770 |
771 | // Create material
772 | //console.debug("Creating material for ground texture: " + url);
773 | const tileKey = tileCoords[0] + "/" + tileCoords[1] + "/" + tileCoords[2];
774 | const url = this.replaceTileCoordsUrl(
775 | tileCoords,
776 | this.groundTextureLayerUrl,
777 | );
778 | materialGround = new StandardMaterial(
779 | "materialGround_" + tileKey,
780 | this.layerManager!.sceneViewer.scene,
781 | );
782 | materialGround.roughness = 1.0;
783 | //materialGround.reflectionFresnelParameters = new FresnelParameters();
784 | materialGround.specularPower = 0.0;
785 | materialGround.diffuseColor = new Color3(0.2, 0.2, 0.2); // Color3.Black();
786 | materialGround.ambientColor = new Color3(0.07, 0.07, 0.07); // Color3.Black();
787 | materialGround.specularColor = new Color3(0.05, 0.05, 0.05); // Color3.Black();
788 | materialGround.emissiveColor = new Color3(0.05, 0.05, 0.05); // Color3.White(); // new Color3(1.0, 1.0, 1.);
789 | //materialGround.disableLighting = true;
790 | //materialGround.backFaceCulling = false;
791 | const materialGroundTexture: Texture = new Texture(
792 | url,
793 | this.layerManager!.sceneViewer.scene,
794 | );
795 | materialGround.diffuseTexture = materialGroundTexture;
796 | materialGround.ambientTexture = materialGroundTexture;
797 | //materialGround.specularTexture = materialGroundTexture;
798 | materialGround.emissiveTexture = materialGroundTexture;
799 | materialGround.linkEmissiveWithDiffuse = true;
800 |
801 | //materialGroundTexture.uScale = 4 * 1.0 / ( sizeWidth + 0 ); // Force small texture overlap to avoid texture repeating
802 | //materialGroundTexture.vScale = 4 * -1.0 / ( sizeHeight + 1 ); // Force small texture overlap to avoid texture repeating
803 | materialGroundTexture.uScale = ((4 * 1.0) / sizeWidth) * (127 / 128); // Force small texture overlap to avoid texture repeating
804 | materialGroundTexture.vScale = ((4 * -1.0) / sizeHeight) * (127 / 128); // Force small texture overlap to avoid texture repeating
805 | materialGroundTexture.uOffset = -0.5;
806 | materialGroundTexture.vOffset = -0.5;
807 | materialGroundTexture.wrapU = Texture.WRAP_ADDRESSMODE;
808 | materialGroundTexture.wrapV = Texture.WRAP_ADDRESSMODE;
809 |
810 | //materialGround.bumpTexture = materialGround.diffuseTexture;
811 | //materialGround.bumpTexture.uScale = 1.0 / sizeWidth;
812 | //materialGround.bumpTexture.vScale = 1.0 / sizeHeight;
813 | //materialGround.bumpTexture.uOffset = -0.5;
814 | //materialGround.bumpTexture.vOffset = -0.5;
815 | }
816 |
817 | // Assign
818 | const meshes = node.getChildMeshes();
819 | for (const mesh of meshes) {
820 | if (
821 | mesh &&
822 | mesh.metadata &&
823 | mesh.metadata.gltf &&
824 | mesh.metadata.gltf.extras
825 | ) {
826 | const metadata = mesh.metadata.gltf.extras;
827 | if (
828 | metadata["ddd:path"].indexOf("/Areas") > 0 ||
829 | metadata["ddd:path"].indexOf("/Ways") > 0
830 | ) {
831 | if (materialGround !== null) {
832 | if (!("_ground_material_original" in mesh.metadata)) {
833 | mesh.metadata["_ground_material_original"] = mesh.material;
834 | }
835 | mesh.material = materialGround;
836 | } else {
837 | if (mesh.metadata["_ground_material_original"]) {
838 | mesh.material = mesh.metadata["_ground_material_original"];
839 | }
840 | }
841 | }
842 | }
843 | }
844 | }
845 |
846 | groundTextureLayerSetUrl(url: string): void {
847 | // "https://a.tile.openstreetmap.org/" + z + "/" + x + "/" + y + ".png"
848 | //console.debug("Layer setting ground texture layer: " + url);
849 | this.groundTextureLayerUrl = url;
850 |
851 | // Update existing tiles
852 | for (const key in this.tiles) {
853 | const tile = this.tiles[key];
854 | this.groundTextureLayerProcessNode(tile, tile.node);
855 | }
856 | }
857 |
858 | replaceTileCoordsUrl(tileCoords: number[], url: string): string {
859 | let result = url;
860 | result = result.replace("{z}", tileCoords[0].toString());
861 | result = result.replace("{x}", tileCoords[1].toString());
862 | result = result.replace("{y}", tileCoords[2].toString());
863 | return result;
864 | }
865 |
866 | /*
867 | createTextMaterial( text: string ): StandardMaterial {
868 |
869 | //Create dynamic texture
870 | const texture = new DynamicTexture( "dynamicTexture_text_" + text , { width:512, height:256 }, this.layerManager!.sceneViewer.scene );
871 | //var textureContext = texture.getContext();
872 | const font = "bold 44px monospace";
873 | texture.drawText( "Generating...\nPlease try again later (5+ min).", 75, 135, font, "green", "white", true, true );
874 |
875 | const material = new StandardMaterial( "Mat" + text, this.layerManager!.sceneViewer.scene );
876 | material.diffuseTexture = texture;
877 |
878 | return material;
879 | }
880 | */
881 |
882 | disposeTile(tile: Tile3D) {
883 | console.debug("Disposing tile: " + tile.key);
884 | if (tile.node) {
885 | //tile.node.setEnabled(false);
886 | tile.node.parent = null;
887 | tile.node.dispose();
888 | tile.node = null;
889 | }
890 | delete this.tiles[tile.key];
891 | }
892 |
893 | clearScene() {
894 | for (const tileKey in this.tiles) {
895 | const tile = this.tiles[tileKey];
896 | this.disposeTile(tile);
897 | }
898 | }
899 | }
900 |
901 | export { GeoTile3DLayer };
902 |
--------------------------------------------------------------------------------
/src/layers/LayerManager.ts:
--------------------------------------------------------------------------------
1 | import { SceneViewer } from "../SceneViewer";
2 | import { Base3DLayer } from "./Base3DLayer";
3 | import { GeoTile3DLayer } from "./GeoTile3DLayer";
4 |
5 |
6 | /**
7 | * Manages the list of layers known to DDD Viewer.
8 | */
9 | class LayerManager {
10 |
11 | sceneViewer: SceneViewer;
12 | layers: { [ key: string ]: Base3DLayer };
13 |
14 | constructor( sceneViewer: SceneViewer ) {
15 | this.sceneViewer = sceneViewer;
16 | this.layers = {};
17 | }
18 |
19 | update( deltaTime: number ): void {
20 | for (const key in this.layers) {
21 | // Load tiles dynamically as needed
22 | this.layers[key].update(deltaTime);
23 | }
24 | }
25 |
26 | /**
27 | * Adds a layer to DDD viewer.
28 | * @param key
29 | * @param layer
30 | */
31 | addLayer(layer: Base3DLayer): void {
32 | layer.setViewer(this.sceneViewer);
33 | this.layers[layer.key] = layer;
34 | }
35 |
36 | removeLayer(layer: Base3DLayer) {
37 | layer.setViewer(null); // have the layer cleanup itself from the scene
38 | delete this.layers[layer.key];
39 | }
40 |
41 | /**
42 | * Retrieves a layer from DDD viewer managed layers.
43 | * Returns null if the layer does not exist.
44 | */
45 | getLayer(key: string) {
46 | if (key in this.layers) {
47 | return this.layers[key];
48 | }
49 | return null;
50 | }
51 |
52 | }
53 |
54 | export { LayerManager };
55 |
--------------------------------------------------------------------------------
/src/layers/OverlayLayer.ts:
--------------------------------------------------------------------------------
1 | import {
2 | AbstractMesh,
3 | Color3,
4 | Mesh,
5 | MeshBuilder,
6 | Node,
7 | Ray,
8 | StandardMaterial,
9 | Texture,
10 | TransformNode,
11 | Vector3,
12 | } from "@babylonjs/core";
13 | import { Coordinate } from "ol/coordinate";
14 | import * as extent from "ol/extent";
15 | import * as olProj from "ol/proj";
16 | import { createXYZ, extentFromProjection } from "ol/tilegrid";
17 | import TileGrid from "ol/tilegrid/TileGrid";
18 | import { SceneViewer } from "../SceneViewer";
19 | import { Base3DLayer } from "./Base3DLayer";
20 | import { GeoJson3DLayer } from "./GeoJson3DLayer";
21 |
22 | class OverlayLayer extends Base3DLayer {
23 | sourceLayerKey: string;
24 |
25 | items: HTMLElement[] = [];
26 |
27 | div: HTMLElement | null = null;
28 |
29 | template: string | null = null;
30 |
31 | maxDistance: number = 0;
32 |
33 | maxItems: number = 10;
34 |
35 | occlude: boolean = false;
36 |
37 | constructor(key: string, sourceLayerKey: string) {
38 | super(key);
39 | this.sourceLayerKey = sourceLayerKey;
40 | setTimeout(() => {
41 | this.createOverlayDiv();
42 | this.updateSceneFromFeatures();
43 | }, 0);
44 | }
45 |
46 | createOverlayDiv(): void {
47 | // Add an overlay DIV over the 3D canvas
48 | // FIXME: This should be created by the scene viewer, and other divs
49 | const sceneViewer: SceneViewer = this.layerManager!.sceneViewer;
50 | this.div = document.createElement("div");
51 | sceneViewer.element.appendChild(this.div);
52 | this.div.className = "ddd-layer-overlay";
53 |
54 | this.div.style.zIndex = "5";
55 | this.div.style.width = "100%";
56 | this.div.style.height = "100%";
57 | this.div.style.position = "absolute";
58 | this.div.style.top = "0";
59 | this.div.style.left = "0";
60 | this.div.style.right = "0";
61 | this.div.style.bottom = "0";
62 | this.div.style.pointerEvents = "none";
63 | }
64 |
65 | resizeOverlayDiv() {
66 | const sceneViewer: SceneViewer = this.layerManager!.sceneViewer;
67 | this.div!.style.width = sceneViewer.canvas.style.width;
68 | this.div!.style.height = sceneViewer.canvas.style.height;
69 | }
70 |
71 | update(): void {}
72 |
73 | setVisible(visible: boolean) {
74 | super.setVisible(visible);
75 | if (this.div) this.div.style.display == "visible" ? "block" : "none";
76 | /*
77 | for (let node of this.sceneNodes) {
78 | node.setEnabled(this.visible);
79 | }
80 | */
81 | }
82 |
83 | /**
84 | * Update scene generating a DIV for each feature in the source layer.
85 | */
86 | updateSceneFromFeatures() {
87 | const sourceLayer = (
88 | this.layerManager!.getLayer(this.sourceLayerKey)!
89 | );
90 | for (const feature of sourceLayer.featuresPoints) {
91 | const html =
92 | "Feature: " +
93 | feature +
94 | "
";
95 | const featureDiv = document.createElement("div");
96 | this.div!.appendChild(featureDiv);
97 | featureDiv.outerHTML = html;
98 | }
99 |
100 | /*
101 | for (let feature of this.featuresLines) {
102 | let marker = MeshBuilder.CreateLines("lineMarker", { points: feature.coordsScene }, sceneViewer.scene);
103 | marker.material = this.featureMaterial;
104 | marker.parent = this.rootNode;
105 | //this.sceneNodes.push(marker);
106 | }
107 | */
108 | }
109 |
110 | clearScene() {
111 | /*
112 | if (this.rootNode) {
113 | this.rootNode.parent = null;
114 | this.rootNode.dispose();
115 | this.rootNode = null;
116 | }
117 | */
118 | }
119 | }
120 |
121 | export { OverlayLayer };
122 |
--------------------------------------------------------------------------------
/src/loading/QueueLoader.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable @typescript-eslint/no-explicit-any */
2 |
3 | import { SceneLoader } from "@babylonjs/core";
4 | import { SceneViewer } from "../SceneViewer";
5 | //import "babylonjs-loaders";
6 |
7 |
8 | class QueueLoaderTask {
9 | url?: string;
10 | onSuccess?: any;
11 | onFailure?: any;
12 | }
13 |
14 | class QueueLoader {
15 |
16 | sceneViewer: SceneViewer;
17 |
18 | queue: any[];
19 |
20 | current: any[];
21 |
22 | concurrentTasks: number = 2; // 1 on mobile? 2 on PC?
23 |
24 |
25 | constructor( sceneViewer: SceneViewer ) {
26 | this.sceneViewer = sceneViewer;
27 | this.queue = [];
28 | this.current = [];
29 | }
30 |
31 | update(): void {
32 | //loadNext();
33 | }
34 |
35 | processNext(): void {
36 | if ( this.queue.length < 1 ) {
37 | return;
38 | }
39 |
40 | const task: any = this.queue.pop();
41 | this.processTask( task );
42 | }
43 |
44 | enqueueLoadModel( url: string, onSuccess: any, onFailure: any ): void {
45 | const task = { "url": url, "onSuccess": onSuccess, "onFailure": onFailure };
46 | this.queue.push(task);
47 | if ( this.current.length < this.concurrentTasks ) {
48 | this.processNext();
49 | }
50 | }
51 |
52 | processTask( task: {[key: string]: any} ): void {
53 | const url: string = task["url"];
54 | SceneLoader.ImportMesh( null, "", url, this.sceneViewer.scene,
55 | ( newMeshes, particleSystems, skeletons ) => {
56 | this.processNext();
57 | task.onSuccess( newMeshes, particleSystems, skeletons );
58 | },
59 | () => {
60 | },
61 | ( scene, msg, ex ) => {
62 | task.onFailure( scene, msg, ex );
63 | this.processNext();
64 | },
65 | ".glb" // this is to force format in case a blob URL is being used
66 | );
67 | }
68 |
69 | //loadResource() {
70 | //}
71 |
72 | }
73 |
74 | export { QueueLoader };
--------------------------------------------------------------------------------
/src/process/ViewerProcess.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * DDDViewer - DDD(3Ds) Viewer library for DDD-generated GIS 3D scenes
3 | * Copyright 2021 Jose Juan Montes and contributors
4 | * MIT License (see LICENSE file)
5 | */
6 |
7 | import { SceneViewer } from "../SceneViewer";
8 |
9 | /**
10 | * A process that can be running in a DDDViewer instance.
11 | * Processes are updated every frame before drawing the scene.
12 | */
13 | abstract class ViewerProcess {
14 |
15 | sceneViewer: SceneViewer;
16 | finished: boolean = false;
17 | time: number = 0;
18 |
19 | constructor( sceneViewer: SceneViewer ) {
20 | this.sceneViewer = sceneViewer;
21 | this.finished = false;
22 | }
23 |
24 | update( deltaTime: number ): void {
25 | // TODO: Consider providing an (optional) initialize() lifecycle method for processes (to be run before the first frame)
26 | //if (this.time == 0) initialize();
27 | this.time += deltaTime;
28 | }
29 |
30 | }
31 |
32 | export { ViewerProcess };
33 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/src/process/ViewerProcessManager.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * DDDViewer - DDD(3Ds) Viewer library for DDD-generated GIS 3D models
3 | * Copyright 2021 Jose Juan Montes and contributors
4 | * MIT License (see LICENSE file)
5 | */
6 |
7 | import { SceneViewer } from "../SceneViewer";
8 | import { ViewerProcess } from "./ViewerProcess";
9 |
10 |
11 | class ViewerProcessManager {
12 |
13 | sceneViewer: SceneViewer;
14 | currentProcesses: ViewerProcess[];
15 |
16 | playing = true;
17 | currentTasks = [];
18 | time = 0.0;
19 |
20 | constructor( sceneViewer: SceneViewer ) {
21 |
22 | this.sceneViewer = sceneViewer;
23 | this.currentProcesses = [];
24 | }
25 |
26 | update( deltaTime: number ): void {
27 |
28 | if ( !( this.playing )) { return; }
29 |
30 | this.time += deltaTime;
31 |
32 | // Update all current tasks
33 | for ( const proc of this.currentProcesses ) {
34 | proc.update( deltaTime );
35 | }
36 |
37 | // Remove finished steps
38 | this.currentProcesses = this.currentProcesses.filter( ( item ) => { return ( ! item.finished ); } );
39 |
40 | }
41 |
42 | add( process: ViewerProcess ): void {
43 | //console.debug("Adding process: ", process);
44 |
45 | // Sanity check
46 | if (process.sceneViewer != this.sceneViewer) {
47 | throw new Error("Tried to add a ViewerProcess to a ViewerProcessManager which belongs ot a different SceneViewer.");
48 | }
49 |
50 | this.currentProcesses.push( process );
51 | }
52 |
53 | remove( process: ViewerProcess ): void {
54 | //console.debug("Removing process: ", process);
55 | this.currentProcesses = this.currentProcesses.filter(( item ) => { return ( item !== process ); });
56 | }
57 |
58 |
59 | }
60 |
61 | export { ViewerProcessManager };
62 |
63 |
--------------------------------------------------------------------------------
/src/process/anim/AnimationProcess.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * DDDViewer - DDD(3Ds) Viewer library for DDD-generated GIS 3D scenes
3 | * Copyright 2021 Jose Juan Montes and contributors
4 | * MIT License (see LICENSE file)
5 | */
6 |
7 | import { EasingFunction, QuadraticEase } from "@babylonjs/core";
8 | import { SceneViewer } from "../../SceneViewer";
9 | import { ViewerProcess } from "../ViewerProcess";
10 |
11 | abstract class AnimationProcess extends ViewerProcess {
12 |
13 | sceneViewer: SceneViewer;
14 |
15 | animTime: number;
16 | time: number;
17 | interpFactor: number;
18 |
19 | easing: EasingFunction;
20 |
21 | /**
22 | *
23 | * @param sceneViewer
24 | * @param animTime if set to null, the Process must manually mark itself as finished
25 | */
26 | constructor(sceneViewer: SceneViewer, animTime: number | null = null) {
27 |
28 | super(sceneViewer);
29 |
30 | this.sceneViewer = sceneViewer;
31 |
32 | this.time = 0.0;
33 | this.interpFactor = 0.0; // Stored as a class property for convenience of derived classes
34 |
35 | this.animTime = animTime || 0;
36 |
37 | this.easing = new QuadraticEase();
38 | this.easing.setEasingMode(EasingFunction.EASINGMODE_EASEINOUT);
39 | }
40 |
41 | update( deltaTime: number): void {
42 |
43 | // Avoid calling parent just to update deltaTime, do it here for performance
44 | this.time += deltaTime;
45 |
46 | this.interpFactor = ( this.animTime > 0 ) ? (( this.time ) / ( this.animTime)) : 1.0;
47 | if (this.interpFactor > 1.0) this.interpFactor = 1.0;
48 |
49 | // Ease interpolation
50 | this.interpFactor = this.easing.ease(this.interpFactor);
51 |
52 | if (this.animTime != null && this.time >= this.animTime) {
53 | this.finished = true;
54 | }
55 | }
56 |
57 | }
58 |
59 | export { AnimationProcess };
--------------------------------------------------------------------------------
/src/process/anim/CameraMoveAnimationProcess.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * DDDViewer - DDD(3Ds) Viewer library for DDD-generated GIS 3D models
3 | * Copyright 2021 Jose Juan Montes and contributors
4 | * MIT License (see LICENSE file)
5 | */
6 |
7 | import { Scalar, TargetCamera, Vector3 } from "@babylonjs/core";
8 | import { ScenePosition } from "../../core/ScenePosition";
9 | import { SceneViewer } from "../../SceneViewer";
10 | import { AnimationProcess } from "./AnimationProcess";
11 |
12 | class CameraMovementAnimationProcess extends AnimationProcess {
13 | moveStart: ScenePosition;
14 | moveEnd: ScenePosition;
15 |
16 | // Move camera adding a vertical arc which height is a factor of movement distance
17 | moveArcHeightFactor: number = 0.0; // 0.05;
18 |
19 | _mslHeightStart: number | null = null;
20 | _mslHeightEnd: number | null = null;
21 |
22 | constructor(
23 | sceneViewer: SceneViewer,
24 | moveStart: ScenePosition,
25 | moveEnd: ScenePosition,
26 | animTime: number,
27 | ) {
28 | super(sceneViewer, animTime);
29 |
30 | this.moveStart = moveStart;
31 | this.moveEnd = moveEnd;
32 | }
33 |
34 | calculateMslHeights() {
35 | if (!this._mslHeightStart) {
36 | const startPositionScene = this.sceneViewer.wgs84ToScene(
37 | this.moveStart.positionWGS84,
38 | );
39 | const startPositionSceneVec = new Vector3(
40 | startPositionScene[0],
41 | startPositionScene[1],
42 | startPositionScene[2],
43 | );
44 | const [ terrainElevation, terrainMesh ] =
45 | this.sceneViewer.elevationMSLFromSceneCoords(startPositionSceneVec);
46 | if (terrainElevation !== null)
47 | this._mslHeightStart =
48 | terrainElevation + this.moveStart.positionGroundHeight;
49 | }
50 | if (!this._mslHeightEnd) {
51 | const endPositionScene = this.sceneViewer.wgs84ToScene(
52 | this.moveEnd.positionWGS84,
53 | );
54 | const endPositionSceneVec = new Vector3(
55 | endPositionScene[0],
56 | endPositionScene[1],
57 | endPositionScene[2],
58 | );
59 | const [ terrainElevation, terrainMesh ] =
60 | this.sceneViewer.elevationMSLFromSceneCoords(endPositionSceneVec);
61 | if (terrainElevation !== null)
62 | this._mslHeightEnd =
63 | terrainElevation + this.moveEnd.positionGroundHeight;
64 | }
65 | }
66 |
67 | update(deltaTime: number): void {
68 | super.update(deltaTime);
69 |
70 | // Update camera interpolating between start and end
71 | const move_start = this.moveStart;
72 | const move_end = this.moveEnd;
73 |
74 | const sceneViewer = this.sceneViewer;
75 |
76 | sceneViewer.viewerState.positionWGS84 = [
77 | Scalar.Lerp(
78 | move_start.positionWGS84[0],
79 | move_end.positionWGS84[0],
80 | this.interpFactor,
81 | ),
82 | Scalar.Lerp(
83 | move_start.positionWGS84[1],
84 | move_end.positionWGS84[1],
85 | this.interpFactor,
86 | ),
87 | ];
88 |
89 | this.calculateMslHeights();
90 |
91 | const heightStart =
92 | this._mslHeightStart !== null
93 | ? this._mslHeightStart
94 | : move_start.positionGroundHeight;
95 | const heightEnd =
96 | this._mslHeightEnd !== null
97 | ? this._mslHeightEnd
98 | : move_end.positionGroundHeight +
99 | (this._mslHeightStart !== null
100 | ? this._mslHeightStart - move_start.positionGroundHeight
101 | : 0);
102 |
103 | // Add arc height offset if set
104 | const moveDistance = [
105 | (move_end.positionWGS84[0] - move_start.positionWGS84[0]) * 111000,
106 | (move_end.positionWGS84[1] - move_start.positionWGS84[1]) * 111000,
107 | ];
108 | const moveDistanceMag = Math.sqrt(
109 | moveDistance[0] ** 2 + moveDistance[1] ** 2,
110 | );
111 | const moveArcHeight = moveDistanceMag * this.moveArcHeightFactor;
112 | const moveArcOffset = Math.sin(this.interpFactor * Math.PI) * moveArcHeight;
113 |
114 | const mslHeight =
115 | Scalar.Lerp(heightStart, heightEnd, this.interpFactor) + moveArcOffset;
116 |
117 | sceneViewer.viewerState.positionGroundHeight =
118 | mslHeight - sceneViewer.viewerState.positionTerrainElevation;
119 | sceneViewer.viewerState.positionTilt = Scalar.Lerp(
120 | move_start.positionTilt,
121 | move_end.positionTilt,
122 | this.interpFactor,
123 | );
124 |
125 | let startHeading = move_start.positionHeading;
126 | const targetHeading = move_end.positionHeading;
127 | if (
128 | Math.abs(move_end.positionHeading - move_start.positionHeading) > 180.0
129 | ) {
130 | if (move_end.positionHeading - move_start.positionHeading > 0) {
131 | startHeading += 360;
132 | } else {
133 | startHeading -= 360;
134 | }
135 | }
136 | const newPositionHeading = Scalar.LerpAngle(
137 | startHeading,
138 | targetHeading,
139 | this.interpFactor,
140 | );
141 | sceneViewer.viewerState.positionHeading =
142 | ((newPositionHeading % 360) + 360) % 360;
143 | //sceneViewer.viewerState.positionHeading = 180 / Math.PI * Scalar.LerpAngle(move_start.positionHeading * Math.PI / 180.0, move_end.positionHeading * Math.PI / 180.0, interp_factor);
144 |
145 | const positionScene = sceneViewer.wgs84ToScene(
146 | sceneViewer.viewerState.positionWGS84,
147 | );
148 | const position = new Vector3(positionScene[0], mslHeight, positionScene[2]);
149 | const rotation = new Vector3(
150 | (90.0 - sceneViewer.viewerState.positionTilt) * (Math.PI / 180.0),
151 | sceneViewer.viewerState.positionHeading * (Math.PI / 180.0),
152 | 0.0,
153 | );
154 |
155 | sceneViewer.camera!.position = position;
156 | if (sceneViewer.camera instanceof TargetCamera) {
157 | (sceneViewer.camera!).rotation = rotation;
158 | }
159 | }
160 | }
161 |
162 | export { CameraMovementAnimationProcess };
163 |
--------------------------------------------------------------------------------
/src/process/anim/DateTimeAnimationProcess.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * DDDViewer - DDD(3Ds) Viewer library for DDD-generated GIS 3D models
3 | * Copyright 2021 Jose Juan Montes and contributors
4 | * MIT License (see LICENSE file)
5 | */
6 |
7 |
8 | import { SceneViewer } from "../../SceneViewer";
9 | import { AnimationProcess } from "./AnimationProcess";
10 |
11 | class DateTimeAnimationProcess extends AnimationProcess {
12 |
13 | dtStart: Date;
14 | dtEnd: Date;
15 |
16 | constructor( sceneViewer: SceneViewer, dtStart: Date, dtEnd: Date, animTime: number ) {
17 |
18 | super(sceneViewer, animTime);
19 |
20 | this.dtStart = dtStart;
21 | this.dtEnd = dtEnd;
22 |
23 | //console.debug("Datetime anim from " + dtStart + " to " + dtEnd);
24 | console.debug("TODO: Restore missing call sceneViewer.lightSetupFromDatePos();");
25 | }
26 |
27 | update( deltaTime: number ): void {
28 |
29 | super.update(deltaTime);
30 |
31 | const interpTime = ( this.dtEnd.getTime() / 1000 - this.dtStart.getTime() / 1000 ) * this.interpFactor;
32 | this.sceneViewer.viewerState.positionDate = new Date( this.dtStart.getTime() + interpTime * 1000 );
33 |
34 | //console.debug("Datetime set by animation to: " + this.sceneViewer.viewerState.positionDate);
35 |
36 | this.sceneViewer.lightSetupFromDatePos();
37 | }
38 |
39 | }
40 |
41 | export { DateTimeAnimationProcess };
42 |
--------------------------------------------------------------------------------
/src/process/anim/TextAnimationProcess.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * DDDViewer - DDD(3Ds) Viewer library for DDD-generated GIS 3D models
3 | * Copyright 2021 Jose Juan Montes and contributors
4 | * MIT License (see LICENSE file)
5 | */
6 |
7 |
8 | import { SceneViewer } from "../../SceneViewer";
9 | import { AnimationProcess } from "./AnimationProcess";
10 |
11 | /**
12 | *
13 | */
14 | class TextAnimationProcess extends AnimationProcess {
15 |
16 | text: string;
17 |
18 | /**
19 | *
20 | * @param text Text to animate.
21 | * @param animTime Animation duration in seconds.
22 | */
23 | constructor( sceneViewer: SceneViewer, text: string, animTime: number ) {
24 | super( sceneViewer, animTime );
25 | this.text = text;
26 | }
27 |
28 | update( deltaTime: number ): void {
29 |
30 | super.update( deltaTime );
31 |
32 | const interpChars = Math.ceil(( this.text.length ) * this.interpFactor );
33 | const interpText = this.text.substr( 0, interpChars );
34 |
35 | this.sceneViewer.viewerState.sceneTitleText = interpText;
36 |
37 | if ( this.finished ) {
38 | this.sceneViewer.viewerState.sceneTitleText = null;
39 | }
40 | }
41 | }
42 |
43 | export { TextAnimationProcess };
44 |
--------------------------------------------------------------------------------
/src/process/sequencer/ViewerSequencer.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * DDDViewer - DDD(3Ds) Viewer library for DDD-generated GIS 3D scenes
3 | * Copyright 2021 Jose Juan Montes and contributors
4 | * MIT License (see LICENSE file)
5 | */
6 |
7 |
8 | import { CameraMovementAnimationProcess } from "../anim/CameraMoveAnimationProcess";
9 | import { DateTimeAnimationProcess } from "../anim/DateTimeAnimationProcess";
10 | import { TextAnimationProcess } from "../anim/TextAnimationProcess";
11 |
12 | import { SceneViewer } from "../../SceneViewer";
13 | // import ScenePosition from "../ScenePosition";
14 | /* eslint-disable no-console, */
15 |
16 | type Step = (string | number)[];
17 | type Sequence = Step[];
18 |
19 | // TODO: this should be a ViewerProcess
20 | class ViewerSequencer {
21 |
22 | sceneViewer: SceneViewer;
23 | seq: Sequence | null;
24 | time: number;
25 | index: number;
26 | playing: boolean;
27 |
28 | waitTime: number;
29 |
30 | constructor( sceneViewer: SceneViewer ) {
31 |
32 | this.sceneViewer = sceneViewer;
33 |
34 | this.seq = null;
35 | this.playing = false;
36 | this.time = 0.0;
37 | this.index = 0;
38 | this.waitTime = 0.0;
39 | }
40 |
41 | update( deltaTime: number ): void {
42 |
43 | if ( !( this.playing )) { return; }
44 |
45 | this.time += deltaTime;
46 |
47 | if ( this.waitTime > 0.0 ) {
48 | this.waitTime -= deltaTime;
49 | return;
50 | }
51 |
52 | // Run all possible steps
53 | while ( this.index < this.seq!.length && this.waitTime <= 0.0 ) {
54 | const step = this.seq![this.index];
55 | this.index++;
56 | this.runStep( step );
57 | }
58 |
59 | }
60 |
61 | runStep( step: Step ): void {
62 |
63 | console.debug( "Running step: ", step );
64 |
65 | const command: string | number = step[0];
66 | //if ( ! ((command instanceof String )) throw new Error( "No command specified." );
67 |
68 | if ( command === "m" ) {
69 | const posString: string | null = this.sceneViewer.positionString(8);
70 |
71 | if ( posString ) {
72 | const move_start = this.sceneViewer.parsePositionString( posString );
73 | const move_end = this.sceneViewer.parsePositionString( step[1] );
74 | const animTime = step[2];
75 | const moveAnimationProcess = new CameraMovementAnimationProcess( this.sceneViewer, move_start, move_end, animTime );
76 | console.debug(move_start, move_end, animTime);
77 | this.sceneViewer.processes.add( moveAnimationProcess );
78 | }
79 |
80 | } else if ( command === "dt" ) {
81 | const dtStart = this.sceneViewer.viewerState.positionDate;
82 | console.debug( dtStart );
83 | const dtEnd = new Date( dtStart );
84 | console.debug( dtEnd );
85 | dtEnd.setHours( parseInt( ( step[1]).split( ":" )[0]));
86 | dtEnd.setMinutes( parseInt( ( step[1]).split( ":" )[1]));
87 | console.debug( dtEnd );
88 | const animTime = step[2];
89 | const process = new DateTimeAnimationProcess( this.sceneViewer, dtStart, dtEnd, animTime );
90 | this.sceneViewer.processes.add( process );
91 |
92 | } else if ( command === "t" ) {
93 | const text = step[1];
94 | const animTime = step[2];
95 | const process = new TextAnimationProcess( this.sceneViewer, text, animTime );
96 | this.sceneViewer.processes.add( process );
97 |
98 | } else if ( command === "s" ) {
99 | this.waitTime = step[1];
100 |
101 |
102 | /*} else if ( command === "u" ) {
103 | const url = step[1];
104 | // Do not change URL if in settings
105 | if ( this.sceneViewer.app.$route.name !== "sceneTools" ) {
106 | this.sceneViewer.app.$router.push( url );
107 | }*/
108 |
109 |
110 |
111 | } else if ( command === "goto" ) {
112 | this.index = step[1];
113 |
114 | } else if ( (command).startsWith( "#" )) {
115 | // Command is a comment. Ignore.
116 | } else {
117 | // Unknown step type
118 | console.debug( "Invalid sequence step: ", step );
119 | }
120 | }
121 |
122 | play( seq: Sequence ): void {
123 | console.debug( "Playing sequence: ", seq );
124 |
125 | this.sceneViewer.camera!.detachControl();
126 | this.sceneViewer.viewerState.sceneViewModeShow = false;
127 | this.seq = seq;
128 | this.playing = true;
129 | this.time = 0.0;
130 | this.index = 0;
131 | }
132 | }
133 |
134 | export { ViewerSequencer };
135 |
--------------------------------------------------------------------------------
/src/render/environment/DefaultEnvironment.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * DDDViewer - DDD(3Ds) Viewer library for DDD-generated GIS 3D scenes
3 | * Copyright 2021 Jose Juan Montes and Contributors
4 | * MIT License (see LICENSE file)
5 | */
6 |
7 | import { Camera } from "@babylonjs/core";
8 | import { SceneViewer } from "SceneViewer";
9 |
10 | /**
11 | * Manages environment rendering, using time, date or other information
12 | * to set up the skybox and lighting.
13 | */
14 | class DefaultEnvironment {
15 |
16 | protected dddViewer: SceneViewer;
17 |
18 | constructor(dddViewer: SceneViewer) {
19 |
20 | // Reference to DDDViewer
21 | this.dddViewer = dddViewer;
22 |
23 | // Babylon camera which we are controlling
24 | //this.camera = dddViewer.camera;
25 | }
26 |
27 | update(deltaTime: number): void {
28 |
29 | }
30 |
31 | initialize(): void {
32 |
33 | }
34 |
35 | dispose(): void {
36 |
37 | }
38 |
39 | }
40 |
41 | export { DefaultEnvironment };
--------------------------------------------------------------------------------
/src/render/materials/SkyboxMaterial.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable indent */
2 |
3 | import { Effect, Material, Scene, ShaderMaterial, Texture } from "@babylonjs/core";
4 |
5 | /* eslint-disable no-unused-vars, no-var, no-undef, no-debugger, no-console, */
6 |
7 | class SkyMaterialWrapper {
8 |
9 | material: Material;
10 |
11 | constructor(scene: Scene) { //, splatmapTexture, atlasTexture, atlasNormalTexture, options) {
12 | this.material = this.initMaterial(scene);
13 | //this.testSplatMaterial(scene);
14 | }
15 |
16 | initMaterial(scene: Scene): Material {
17 | //, options: any) {
18 |
19 | Effect.ShadersStore["customVertexShader"] = `
20 | precision highp float;
21 |
22 | // Attributes
23 | attribute vec3 position;
24 | attribute vec3 normal;
25 | attribute vec2 uv;
26 |
27 | // Uniforms
28 | uniform mat4 worldViewProjection;
29 |
30 | // Varying
31 | varying vec4 vPosition;
32 | varying vec3 vNormal;
33 | varying vec2 vUV;
34 | void main() {
35 |
36 | vec4 p = vec4( position, 1. );
37 |
38 | vPosition = p;
39 | vNormal = normal;
40 |
41 | vUV = uv;
42 | // vUV.y =1.-vUV.y; // flip uv screen ;
43 | gl_Position = worldViewProjection * p;
44 |
45 | }`;
46 |
47 | Effect.ShadersStore["customFragmentShader"] = `
48 | precision highp float;
49 |
50 | uniform mat4 worldView;
51 |
52 | varying vec4 vPosition;
53 |
54 | precision mediump float;
55 |
56 | // Day and night sky cycle. By László Matuska (@BitOfGold)
57 | // Creates a sky texture for a skydome
58 | // https://www.shadertoy.com/view/ltlSWB
59 |
60 |
61 | // based on other shaders, Greetings goes to:
62 |
63 | // Weather. By David Hoskins, May 2014.
64 | // https://www.shadertoy.com/view/4dsXWn
65 |
66 | // Edge of atmosphere
67 | // created by dmytro rubalskyi (ruba)
68 | // https://www.shadertoy.com/view/XlXGzB
69 |
70 | // Starfield01 by xaot88
71 | // https://www.shadertoy.com/view/Md2SR3
72 | // ======================================================================
73 |
74 | //#define shadertoy 1
75 |
76 | //#define cloud2 1 //second layer of clouds, altocumulus or stratocumulus. (in 4K, too slow on my GTX970. HD is OK.)
77 | //plan was to make cirrus too...
78 |
79 | #ifdef GL_ES
80 | precision highp float;
81 | #endif
82 |
83 | const float M_PI = 3.1415926535;
84 | const float DEGRAD = M_PI / 180.0;
85 |
86 | #ifdef shadertoy
87 | float height = 500.0; //viewer height
88 | float cloudy = 0.6; //0.6 //0.0 clear sky
89 | #else
90 | varying vec3 vNormal;
91 | varying vec2 vUV;
92 | uniform sampler2D iChannel0;
93 | uniform float sunx;
94 | uniform float suny;
95 | //uniform float moonx;
96 | //uniform float moony;
97 | //uniform float cloudy;
98 | // uniform float height;
99 | uniform float time;
100 | #endif
101 | //float moonx = 1.0;
102 | //float moony = 9.6;
103 | //float sunx = 1.0;
104 | //float suny = 1.6;
105 | float cloudy = 0.1;
106 | float height = 500.0;
107 |
108 | //rendering quality
109 | const int steps = 8; //16 is fast, 128 or 256 is extreme high
110 | const int stepss = 8; //16 is fast, 16 or 32 is high
111 |
112 | //float t = 12.0; //fix time. 12.0 91.0, 97.0, 188.0, 72.0, 74.0
113 |
114 | float camroty = 0. * DEGRAD; //20.
115 | float haze = 0.1; //0.2
116 | float cloudyhigh = 0.05; //if cloud2 defined
117 |
118 | float cloudnear = 1.0; //9e3 12e3 //do not render too close clouds on the zenith
119 | float cloudfar = 1e3; //15e3 17e3
120 |
121 | float startreshold = 0.99; //0.99 0.98 star density treshold.
122 |
123 | const float I = 10.; //sun light power, 10.0 is normal
124 | const float g = 0.45; //light concentration .76 //.45 //.6 .45 is normaL
125 | const float g2 = g * g;
126 |
127 | //Reyleigh scattering (sky color, atmospheric up to 8km)
128 | vec3 bR = vec3(5.8e-6, 13.5e-6, 33.1e-6); //normal earth
129 | //vec3 bR = vec3(5.8e-6, 33.1e-6, 13.5e-6); //purple
130 | //vec3 bR = vec3( 63.5e-6, 13.1e-6, 50.8e-6 ); //green
131 | //vec3 bR = vec3( 13.5e-6, 23.1e-6, 115.8e-6 ); //yellow
132 | //vec3 bR = vec3( 5.5e-6, 15.1e-6, 355.8e-6 ); //yeellow
133 | //vec3 bR = vec3(3.5e-6, 333.1e-6, 235.8e-6 ); //red-purple
134 |
135 | //Mie scattering (water particles up to 1km)
136 | vec3 bM = vec3(21e-6); //normal mie
137 | //vec3 bM = vec3(50e-6); //high mie
138 |
139 | //-----
140 | //positions
141 |
142 | const float Hr = 8000.0; //Reyleight scattering top
143 | const float Hm = 1000.0; //Mie scattering top
144 |
145 | const float R0 = 6360e3; //planet radius
146 | const float Ra = 6380e3; //atmosphere radius
147 | vec3 C = vec3(0., -R0, 0.); //planet center
148 | vec3 Ds = normalize(vec3(0., .09, -1.)); //sun direction?
149 |
150 | //--------------------------------------------------------------------------
151 | //Starfield
152 | // License Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License.
153 |
154 | // Return random noise in the range [0.0, 1.0], as a function of x.
155 | float Noise2d( in vec2 x )
156 | {
157 | float xhash = cos( x.x * 37.0 );
158 | float yhash = cos( x.y * 57.0 );
159 | return fract( 415.92653 * ( xhash + yhash ) );
160 | }
161 |
162 | // Convert Noise2d() into a "star field" by stomping everthing below fThreshhold to zero.
163 | float NoisyStarField( in vec2 vSamplePos, float fThreshhold )
164 | {
165 | float StarVal = Noise2d( vSamplePos );
166 | if ( StarVal >= fThreshhold )
167 | StarVal = pow( (StarVal - fThreshhold)/(1.0 - fThreshhold), 6.0 );
168 | else
169 | StarVal = 0.0;
170 | return StarVal;
171 | }
172 |
173 | // Stabilize NoisyStarField() by only sampling at integer values.
174 | float StableStarField( in vec2 vSamplePos, float fThreshhold )
175 | {
176 | // Linear interpolation between four samples.
177 | // Note: This approach has some visual artifacts.
178 | // There must be a better way to "anti alias" the star field.
179 | float fractX = fract( vSamplePos.x );
180 | float fractY = fract( vSamplePos.y );
181 | vec2 floorSample = floor( vSamplePos );
182 | float v1 = NoisyStarField( floorSample, fThreshhold );
183 | float v2 = NoisyStarField( floorSample + vec2( 0.0, 1.0 ), fThreshhold );
184 | float v3 = NoisyStarField( floorSample + vec2( 1.0, 0.0 ), fThreshhold );
185 | float v4 = NoisyStarField( floorSample + vec2( 1.0, 1.0 ), fThreshhold );
186 |
187 | float StarVal = v1 * ( 1.0 - fractX ) * ( 1.0 - fractY )
188 | + v2 * ( 1.0 - fractX ) * fractY
189 | + v3 * fractX * ( 1.0 - fractY )
190 | + v4 * fractX * fractY;
191 | return StarVal;
192 | }
193 |
194 |
195 | //--------------------------------------------------------------------------
196 | //Cloud noise
197 |
198 | float Noise( in vec3 x )
199 | {
200 | vec3 p = floor(x);
201 | vec3 f = fract(x);
202 | f = f*f*(3.0-2.0*f);
203 |
204 | vec2 uv = (p.xy+vec2(37.0,17.0)*p.z) + f.xy;
205 |
206 | vec2 rg = texture( iChannel0, (uv+ 0.5)/256.0, -100.0).yx;
207 | return mix( rg.x, rg.y, f.z );
208 | }
209 |
210 | float fnoise( vec3 p, in float t )
211 | {
212 | p *= .25;
213 | float f;
214 |
215 | f = 0.5000 * Noise(p); p = p * 3.02; p.y -= t*.2;
216 | f += 0.2500 * Noise(p); p = p * 3.03; p.y += t*.06;
217 | f += 0.1250 * Noise(p); p = p * 3.01;
218 | f += 0.0625 * Noise(p); p = p * 3.03;
219 | f += 0.03125 * Noise(p); p = p * 3.02;
220 | f += 0.015625 * Noise(p);
221 | return f;
222 | }
223 |
224 | //--------------------------------------------------------------------------
225 | //clouds, scattering
226 |
227 | float cloud(vec3 p, in float t) {
228 | float cld = fnoise(p*2e-4,t) + cloudy*0.1 ;
229 | cld = smoothstep(.4+.04, .6+.04, cld);
230 | cld *= 70.;
231 | return cld+haze;
232 | }
233 |
234 |
235 | void densities(in vec3 pos, out float rayleigh, out float mie, in float t) {
236 | float h = length(pos - C) - R0;
237 | rayleigh = exp(-h/Hr);
238 | vec3 d = pos;
239 | d.y = 0.0;
240 | float dist = length(d);
241 |
242 | float cld = 0.;
243 | if (5e3 < h && h < 8e3) {
244 | cld = cloud(pos+vec3(23175.7, 0.,-t*3e3), t);
245 | cld *= sin(3.1415*(h-5e3)/5e3) * cloudy;
246 | }
247 | #ifdef cloud2
248 | float cld2 = 0.;
249 | if (12e3 < h && h < 15.5e3) {
250 | cld2 = fnoise(pos*3e-4,t)*cloud(pos*32.0+vec3(27612.3, 0.,-t*15e3), t);
251 | cld2 *= sin(3.1413*(h-12e3)/12e3) * cloudyhigh;
252 | cld2 = clamp(cld2,0.0,1.0);
253 | }
254 |
255 | #endif
256 |
257 |
258 | if ( dist < cloudfar) {
259 | float factor = clamp(1.0-((cloudfar - dist)/(cloudfar-cloudnear)),0.0,1.0);
260 | cld *= factor;
261 | }
262 |
263 | mie = exp(-h/Hm) + cld + haze;
264 | #ifdef cloud2
265 | mie += cld2;
266 | #endif
267 |
268 | }
269 |
270 | float escape(in vec3 p, in vec3 d, in float R) {
271 |
272 | vec3 v = p - C;
273 | float b = dot(v, d);
274 | float c = dot(v, v) - R*R;
275 | float det2 = b * b - c;
276 | if (det2 < 0.) return -1.;
277 | float det = sqrt(det2);
278 | float t1 = -b - det, t2 = -b + det;
279 | return (t1 >= 0.) ? t1 : t2;
280 | }
281 |
282 | // this can be explained: http://www.scratchapixel.com/lessons/3d-advanced-lessons/simulating-the-colors-of-the-sky/atmospheric-scattering/
283 | void scatter(vec3 o, vec3 d, out vec3 col, out float scat, in float t) {
284 | float L = escape(o, d, Ra);
285 | float mu = dot(d, Ds);
286 | float opmu2 = 1. + mu*mu;
287 | float phaseR = .0596831 * opmu2;
288 | float phaseM = .1193662 * (1. - g2) * opmu2 / ((2. + g2) * pow(1. + g2 - 2.*g*mu, 1.5));
289 |
290 | float depthR = 0., depthM = 0.;
291 | vec3 R = vec3(0.), M = vec3(0.);
292 |
293 | float dl = L / float(steps);
294 | for (int i = 0; i < steps; ++i) {
295 | float l = float(i) * dl;
296 | vec3 p = o + d * l;
297 |
298 | float dR, dM;
299 | densities(p, dR, dM, t);
300 | dR *= dl; dM *= dl;
301 | depthR += dR;
302 | depthM += dM;
303 |
304 | float Ls = escape(p, Ds, Ra);
305 | if (Ls > 0.) {
306 | float dls = Ls / float(stepss);
307 | float depthRs = 0., depthMs = 0.;
308 | for (int j = 0; j < stepss; ++j) {
309 | float ls = float(j) * dls;
310 | vec3 ps = p + Ds * ls;
311 | float dRs, dMs;
312 | densities(ps, dRs, dMs, t);
313 | depthRs += dRs * dls;
314 | depthMs += dMs * dls;
315 | }
316 |
317 | vec3 A = exp(-(bR * (depthRs + depthR) + bM * (depthMs + depthM)));
318 | R += A * dR;
319 | M += A * dM;
320 | }
321 | }
322 |
323 |
324 | col = I * (R * bR * phaseR + M * bM * phaseM);
325 | scat = 1.0 - clamp(depthM*1e-5,0.,1.);
326 | }
327 |
328 | //--------------------------------------------------------------------------
329 | // ray casting
330 |
331 |
332 | vec3 rotate_y(vec3 v, float angle)
333 | {
334 | float ca = cos(angle); float sa = sin(angle);
335 | return v*mat3(
336 | +ca, +.0, -sa,
337 | +.0,+1.0, +.0,
338 | +sa, +.0, +ca);
339 | }
340 |
341 | vec3 rotate_x(vec3 v, float angle)
342 | {
343 | float ca = cos(angle); float sa = sin(angle);
344 | return v*mat3(
345 | +1.0, +.0, +.0,
346 | +.0, +ca, -sa,
347 | +.0, +sa, +ca);
348 | }
349 |
350 | float map(float value, float min1, float max1, float min2, float max2) {
351 | return min2 + (value - min1) * (max2 - min2) / (max1 - min1);
352 | }
353 |
354 | vec4 generate(in vec2 uv, in vec2 fragCoord, in vec2 sunpos, in float t) {
355 |
356 | //if (fragCoord.y < -0.25) discard;
357 |
358 | float att = 1.0;
359 | float staratt = 0.0;
360 | if (sunpos.y < 0.10) {
361 | staratt = map(sunpos.y, 0.20, -1.0, 0.0, 1.0);
362 | }
363 | if (sunpos.y < -0.10) {
364 | att = map(sunpos.y, -0.10, -1.0, 1.0, 0.25);
365 | //sunpos.y = -sunpos.y;
366 | }
367 |
368 | vec3 O = vec3(0., height, 0.);
369 |
370 | vec3 D = normalize(rotate_y(rotate_x(vec3(0.0, 0.0, 1.0),-uv.y*M_PI/2.0),-uv.x*M_PI+camroty));
371 |
372 | if (D.y <= -0.15) {
373 | D.y = -0.3 -D.y;
374 | }
375 |
376 | Ds= normalize(rotate_y(rotate_x(vec3(0.0, 0.0, 1.0),-sunpos.y*M_PI/2.0),-sunpos.x*M_PI));
377 | float scat = 0.;
378 | vec3 color = vec3(0.);
379 | scatter(O, D, color, scat, t);
380 | color *= att;
381 |
382 | float starcolor = StableStarField(fragCoord * 1024.0, startreshold);
383 | color += vec3(scat * starcolor * staratt);
384 |
385 | // Water mix to bottom half, black at night
386 | if (fragCoord.y < 0.0) {
387 | float waterFactor = smoothstep(-0.05, 0.0, sunpos.y);
388 | vec3 waterColor = vec3(70.0 / 255.0, 135.0 / 255.0, 240.0 / 255.0) * waterFactor;
389 | float waterMix = smoothstep(0.0, -0.1, fragCoord.y);
390 | waterColor = (color + waterColor) / 2.0;
391 | color = color + (waterColor - color) * waterMix;
392 | }
393 |
394 | float env = 1.0;
395 | return(vec4(env * pow(color, vec3(.7)),1.0));
396 | }
397 |
398 | void main() {
399 | vec2 uv = vec2(2.0 * vUV.x - 1.0, -2.0 * vUV.y + 1.0);
400 | vec2 sunpos = vec2(sunx,suny);
401 | float t = time;
402 | gl_FragColor = generate(uv, uv, sunpos,t);
403 | }
404 |
405 | `;
406 |
407 |
408 | // Compile
409 | var shaderMaterial = new ShaderMaterial( "skyShader", scene, {
410 | vertex: "custom",
411 | fragment: "custom",
412 | },
413 | {
414 | attributes: [ "position", "normal", "uv" ],
415 | uniforms: [ "world", "worldView", "worldViewProjection", "view", "projection" ]
416 | });
417 |
418 | const mainTexture = new Texture("/textures/skynoise.png", scene, true, false, 12); // NEAREST
419 |
420 | //https://www.shadertoy.com/view/ltlSWB
421 | shaderMaterial.setTexture("iChannel0", mainTexture);
422 | shaderMaterial.setFloat("time", 0);
423 | shaderMaterial.setFloat("offset", 10);
424 | shaderMaterial.setFloat("sunx", 2.0);
425 | shaderMaterial.setFloat("suny", 0.9);
426 | shaderMaterial.backFaceCulling = false;
427 |
428 |
429 | /*
430 | var time = 0;
431 | scene.registerBeforeRender(function () {
432 | var shaderMaterial = scene.getMaterialByName("skyShader");
433 | shaderMaterial.setFloat("time", time);
434 |
435 | //Animate Move Sun
436 | shaderMaterial.setFloat("suny", Math.sin(time/3));
437 | shaderMaterial.setFloat("sunx", Math.sin(time/3));
438 | time += 0.008;
439 | });
440 | */
441 |
442 | this.material = shaderMaterial;
443 | return shaderMaterial;
444 |
445 | }
446 |
447 | }
448 |
449 | export { SkyMaterialWrapper };
--------------------------------------------------------------------------------
/src/render/materials/TerrainMaterial.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable object-curly-spacing */
2 | /* eslint-disable array-bracket-spacing */
3 | /* eslint-disable indent */
4 |
5 | //import * as BABYLON from "babylonjs";
6 | //import * as BABYLONMAT from "babylonjs-materials";
7 |
8 | //import "babylonjs-loaders";
9 | import { PBRCustomMaterial } from "@babylonjs/materials";
10 | import {
11 | AbstractMesh,
12 | Color3,
13 | Effect,
14 | Mesh,
15 | Scene,
16 | Texture,
17 | Vector2,
18 | } from "@babylonjs/core";
19 | import { SceneViewer } from "SceneViewer";
20 |
21 | /* eslint-disable no-unused-vars, no-var, no-undef, no-debugger, no-console, */
22 |
23 | /**
24 | * From: https://forum.babylonjs.com/t/pbr-texture-splatting-up-to-64-textures/1994/28
25 | * and: https://www.babylonjs-playground.com/#LIVRIY#58
26 | */
27 | class TerrainMaterialWrapper {
28 | sceneViewer: SceneViewer;
29 | material: PBRCustomMaterial;
30 | dedupDouble: boolean;
31 |
32 | options: any;
33 |
34 | tileIndexes = [];
35 | shaderinjectpoint1 = "";
36 | shaderinjectpoint2 = "";
37 | shaderinjectpoint3 = "";
38 | shaderinjectpoint4 = "";
39 |
40 | numTilesHorizontal: number = 0;
41 | numTilesVertical: number = 0;
42 | totalTiles: number = 0;
43 | tileScale: Vector2 = new Vector2(1, 1);
44 |
45 | numSplatTilesHorizontal: number = 0;
46 | numSplatTilesVertical: number = 0;
47 | totalSplatTiles: number = 0;
48 | splatScale: Vector2 = new Vector2(1, 1);
49 |
50 | splatMap: Texture | null = null;
51 | atlasBumpTexture: Texture | null = null;
52 |
53 | static terrainMaterialShared: PBRCustomMaterial | null = null;
54 | static terrainEffectShared: Effect | null = null;
55 | static matIdx = 1;
56 | initialized = false;
57 |
58 | constructor(
59 | sceneViewer: SceneViewer,
60 | splatmapTexture: Texture,
61 | atlasTexture: Texture,
62 | atlasNormalTexture: Texture,
63 | options: any = null,
64 | ) {
65 | // TODO: Options should be merged with defaults!
66 |
67 | this.sceneViewer = sceneViewer;
68 | this.dedupDouble = false;
69 |
70 | const compileShader = true;
71 | // Compile shader only first time
72 | if (compileShader || TerrainMaterialWrapper.terrainMaterialShared == null) {
73 | this.material = this.initSplatMaterial(
74 | this.sceneViewer.scene,
75 | splatmapTexture,
76 | atlasTexture,
77 | atlasNormalTexture,
78 | options,
79 | );
80 | TerrainMaterialWrapper.terrainMaterialShared = this.material;
81 | } else {
82 | //this.material = TerrainMaterialWrapper.terrainMaterialShared;
83 |
84 | this.splatMap = splatmapTexture;
85 | if (atlasTexture !== null) {
86 | this.atlasBumpTexture = atlasNormalTexture;
87 | }
88 | this.material = new PBRCustomMaterial(
89 | "splatMaterial" + (TerrainMaterialWrapper.matIdx++).toString(),
90 | this.sceneViewer.scene,
91 | ); // + (TerrainMaterialWrapper.matIdx++)
92 | this.material.metallic = 0.0; // 0.0;
93 | this.material.roughness = 0.0; // 0.43 (asphalt); // 0.95;
94 | //this.material.environmentIntensity = 1.0; // This one is needed to avoid saturation due to env
95 | this.material.albedoTexture = atlasTexture;
96 | this.material.AddUniform("splatmap", "sampler2D", {});
97 | this.material.AddUniform("atlasNormalsSampler", "sampler2D", {});
98 | // Reuse shader
99 | this.material["_prepareEffect"] = () => {
100 | if (TerrainMaterialWrapper.terrainMaterialShared!["_activeEffect"]) {
101 | TerrainMaterialWrapper.terrainEffectShared =
102 | TerrainMaterialWrapper.terrainMaterialShared!["_activeEffect"];
103 | }
104 |
105 | if (TerrainMaterialWrapper.terrainEffectShared) {
106 | return TerrainMaterialWrapper.terrainEffectShared;
107 | }
108 | return null;
109 | };
110 | }
111 |
112 | // TODO: Check how much this impacts performance, it has a subtle impact on terrain and roads but it's nice to have it.
113 | // TODO: At least make it optional / linked to quality settings.
114 | this.material.reflectionTexture = this.sceneViewer.scene.environmentTexture;
115 |
116 | // This one is needed to control saturation due to env, should be aligned with PBRMaterials, StandarMaterials and light intensity
117 | this.material.environmentIntensity =
118 | this.sceneViewer.baseEnvironmentIntensity;
119 |
120 | this.material.onBindObservable.add((mesh) => {
121 | this.bind(mesh);
122 | //this.material.getEffect().setTexture("splatmap", this.splatMap);
123 |
124 | const ubo = this.material["_uniformBuffer"];
125 | ubo.update();
126 | });
127 | }
128 |
129 | initSplatMaterial(
130 | scene: Scene,
131 | splatMap: Texture,
132 | atlas: Texture,
133 | atlasnormals: Texture,
134 | options: any = null,
135 | ): PBRCustomMaterial {
136 | // TODO: Options should be merged with defaults!
137 |
138 | var defScale = 100.0;
139 | if (!options) {
140 | options = {
141 | numTilesHorizontal: 4,
142 | numTilesVertical: 4,
143 | numSplatTilesHorizontal: 2,
144 | numSplatTilesVertical: 2,
145 | //tileScale:[[20.0,20.0],[20.0,20.0],[20.0,20.0]],
146 | splatInfos: {
147 | // Positions is X / Y, Y grows upwards
148 | layers: [
149 | {
150 | name: "Ground",
151 | position: [0, 3],
152 | scale: [defScale, defScale],
153 | displScales: 1.0,
154 | },
155 | // ...
156 | ],
157 | positions: [
158 | [0.0, 3.0],
159 | [1.0, 3.0],
160 | [2.0, 3.0],
161 | [3.0, 3.0], // encoded by first splat (top left), first row (index 3 from bottom)
162 | [0.0, 2.0],
163 | [1.0, 2.0],
164 | [2.0, 2.0],
165 | [3.0, 2.0], // encoded by splat (top right), second row (index 2 from bottom)
166 | [0.0, 1.0],
167 | [1.0, 1.0],
168 | [2.0, 1.0],
169 | [3.0, 1.0], // encoded by third splat (down left), third row (index 1 starting from bottom)
170 | [0.0, 0.0],
171 | [1.0, 0.0],
172 | [2.0, 0.0],
173 | [3.0, 0.0], // encoded by splat (down right), last row (index 0 from bottom)
174 | ],
175 | scales: [
176 | [defScale * 0.75, defScale * 0.75],
177 | [defScale, defScale],
178 | [defScale, defScale],
179 | [defScale * 0.5, defScale * 0.5],
180 | [defScale * 0.5, defScale * 0.5],
181 | [defScale * 0.5, defScale * 0.5],
182 | [defScale, defScale],
183 | [defScale, defScale],
184 | [defScale * 1.0, defScale * 1.0],
185 | [defScale * 1.6, defScale * 1.6],
186 | [defScale, defScale],
187 | [defScale, defScale], // Grass
188 | [defScale, defScale],
189 | [defScale * 0.5, defScale * 0.5],
190 | [defScale * 0.25, defScale * 0.25],
191 | [defScale, defScale],
192 | ], // Sand, rock, rock orange
193 | displScales: [
194 | 0.0, 0, 0.0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.0, 0.0, 0.0, 0.0,
195 | ],
196 | dedupScales: [
197 | 1.0, 1.0, 1.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.1, 1.1, 1.1, 1.2, 1.2,
198 | 1.2, 0.5, 1.1,
199 | ],
200 | roughness: [
201 | 1.2, 1.3, 2.5, 0.8, 0.8, 0.9, 1.0, 1.0, 1.85, 1.85, 1.15, 1.25,
202 | 1.45, 0.8, 1.0, 1.0,
203 | ],
204 | metallic: [
205 | 0.2, 0.1, 0.1, 0.3, 0.3, 0.1, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
206 | 0.4, 0.0, 0.0,
207 | ],
208 | },
209 | };
210 | }
211 |
212 | this.options = options;
213 | this.tileIndexes = [];
214 | this.shaderinjectpoint1 = "";
215 | this.shaderinjectpoint2 = "";
216 | this.shaderinjectpoint3 = "";
217 | this.shaderinjectpoint4 = "";
218 |
219 | // 4x4 = 16
220 | this.numTilesHorizontal = options.numTilesHorizontal;
221 | this.numTilesVertical = options.numTilesVertical;
222 | this.totalTiles = this.numTilesVertical * this.numTilesHorizontal;
223 | this.tileScale = new Vector2(
224 | 1.0 / this.numTilesHorizontal,
225 | 1.0 / this.numTilesVertical,
226 | );
227 |
228 | // 2x2 = 4
229 | this.numSplatTilesHorizontal = options.numSplatTilesHorizontal;
230 | this.numSplatTilesVertical = options.numSplatTilesVertical;
231 | this.totalSplatTiles =
232 | this.numSplatTilesVertical * this.numSplatTilesHorizontal;
233 | this.splatScale = new Vector2(
234 | 1.0 / this.numSplatTilesHorizontal,
235 | 1.0 / this.numSplatTilesVertical,
236 | );
237 |
238 | this.shaderinjectpoint1 +=
239 | "vec2 splatScale = vec2(" +
240 | this.splatScale.x +
241 | "," +
242 | this.splatScale.y +
243 | ");\r\n";
244 | this.shaderinjectpoint1 +=
245 | "vec2 scale = vec2(" +
246 | this.tileScale.x +
247 | "," +
248 | this.tileScale.y +
249 | ");\r\n";
250 |
251 | //this.shaderinjectpoint3 += 'normalW = vec3(0.0, 1.0, 0.0);\r\n';
252 |
253 | this.shaderinjectpoint3 += "vec4 finalColor1 = baseColor1;\r\n";
254 | this.shaderinjectpoint3 += "vec3 finalNormal1 = baseNormal1;\r\n";
255 | //this.shaderinjectpoint3 += 'finalColor1.a = 0.05;\r\n';
256 |
257 | this.shaderinjectpoint4 += "vec4 finalColor1 = baseColor1;\r\n";
258 | this.shaderinjectpoint4 += "float finalRough1 = baseRough1;\r\n";
259 | this.shaderinjectpoint4 += "float finalMetallic1 = baseMetallic1;\r\n";
260 | //this.shaderinjectpoint4 += 'finalColor1.a = 0.05;\r\n';
261 |
262 | var v = 0.0,
263 | h = 0.0;
264 | for (let i = 0; i < this.totalTiles; i++) {
265 | var tpos = Math.floor(i / 4);
266 | h = tpos % this.numSplatTilesHorizontal;
267 | v =
268 | this.numSplatTilesHorizontal -
269 | 1 -
270 | Math.floor(tpos / this.numSplatTilesHorizontal);
271 |
272 | if (i < this.totalTiles - 1) {
273 | this.shaderinjectpoint3 +=
274 | `
275 | ` +
276 | "//vec4 finalColor" +
277 | (i + 2) +
278 | " = finalColor" +
279 | (i + 1) +
280 | ".a >= baseColor" +
281 | (i + 2) +
282 | ".a ? finalColor" +
283 | (i + 1) +
284 | " : baseColor" +
285 | (i + 2) +
286 | `;
287 | ` +
288 | "//vec4 finalColor" +
289 | (i + 2) +
290 | " = finalColor" +
291 | (i + 1) +
292 | " * (1.0 - baseColor" +
293 | (i + 2) +
294 | ".a) + baseColor" +
295 | (i + 2) +
296 | " * baseColor" +
297 | (i + 2) +
298 | `.a;
299 | ` +
300 | "vec4 finalColor" +
301 | (i + 2) +
302 | " = blend(finalColor" +
303 | (i + 1) +
304 | ", " +
305 | this.options.splatInfos.displScales[i].toFixed(5) +
306 | ", baseColor" +
307 | (i + 2) +
308 | ", " +
309 | this.options.splatInfos.displScales[i + 1].toFixed(5) +
310 | "); " +
311 | `
312 | //finalColor` +
313 | (i + 2) +
314 | `.a *= 0.95;
315 |
316 | //vec3 finalNormal` +
317 | (i + 2) +
318 | " = finalColor" +
319 | (i + 1) +
320 | ".a >= baseColor" +
321 | (i + 2) +
322 | ".a ? finalNormal" +
323 | (i + 1) +
324 | " : baseNormal" +
325 | (i + 2) +
326 | `;
327 | vec3 finalNormal` +
328 | (i + 2) +
329 | " = blendNormal(finalColor" +
330 | (i + 1) +
331 | ", " +
332 | this.options.splatInfos.displScales[i].toFixed(5) +
333 | ", baseColor" +
334 | (i + 2) +
335 | ", " +
336 | this.options.splatInfos.displScales[i + 1].toFixed(5) +
337 | ", finalNormal" +
338 | (i + 1) +
339 | ", baseNormal" +
340 | (i + 2) +
341 | "); " +
342 | `
343 | `;
344 |
345 | this.shaderinjectpoint4 +=
346 | `
347 | ` +
348 | "vec4 finalColor" +
349 | (i + 2) +
350 | " = blend(finalColor" +
351 | (i + 1) +
352 | ", " +
353 | this.options.splatInfos.displScales[i].toFixed(5) +
354 | ", baseColor" +
355 | (i + 2) +
356 | ", " +
357 | this.options.splatInfos.displScales[i + 1].toFixed(5) +
358 | "); " +
359 | `
360 | float finalRough` +
361 | (i + 2) +
362 | " = finalColor" +
363 | (i + 1) +
364 | ".a >= baseColor" +
365 | (i + 2) +
366 | ".a ? finalRough" +
367 | (i + 1) +
368 | " : baseRough" +
369 | (i + 2) +
370 | `;
371 | float finalMetallic` +
372 | (i + 2) +
373 | " = finalColor" +
374 | (i + 1) +
375 | ".a >= baseColor" +
376 | (i + 2) +
377 | ".a ? finalMetallic" +
378 | (i + 1) +
379 | " : baseMetallic" +
380 | (i + 2) +
381 | `;
382 | `;
383 | }
384 |
385 | // Get basecolors from tiles
386 | this.shaderinjectpoint2 +=
387 | "vec2 uv" +
388 | (i + 1) +
389 | " = vec2((vAlbedoUV.x + " +
390 | h +
391 | ".0) * splatScale.x, (vAlbedoUV.y + " +
392 | v +
393 | ".0) * splatScale.y);\r\n";
394 | this.shaderinjectpoint2 +=
395 | "mat4 chanInfo" +
396 | (i + 1) +
397 | " = colInfo(vAlbedoUV, uv" +
398 | (i + 1) +
399 | ", " +
400 | this.options.splatInfos.dedupScales[i].toFixed(5) +
401 | ", vec2(" +
402 | this.options.splatInfos.scales[i][0] +
403 | "," +
404 | this.options.splatInfos.scales[i][1] +
405 | "), vec2(" +
406 | this.options.splatInfos.positions[i][0] +
407 | "," +
408 | this.options.splatInfos.positions[i][1] +
409 | "), " +
410 | (i % 4) +
411 | ", scale, splatmap, albedoSampler, atlasNormalsSampler);\r\n";
412 | //this.shaderinjectpoint2 += 'vec4 baseColor' + (i + 1) +' = col(vAlbedoUV, uv' + (i + 1) + ', vec2('+this.options.splatInfos.scales[i][0]+','+this.options.splatInfos.scales[i][1]+'), vec2('+this.options.splatInfos.positions[i][0] + ','+this.options.splatInfos.positions[i][1]+'), ' + (i % 4) + ', scale, splatmap, albedoSampler, bumpSampler);\r\n';
413 | this.shaderinjectpoint2 +=
414 | "vec4 baseColor" + (i + 1) + " = chanInfo" + (i + 1) + "[0];\r\n";
415 | this.shaderinjectpoint2 +=
416 | "vec3 baseNormal" +
417 | (i + 1) +
418 | " = vec3(chanInfo" +
419 | (i + 1) +
420 | "[1].x, chanInfo" +
421 | (i + 1) +
422 | "[1].y, chanInfo" +
423 | (i + 1) +
424 | "[1].z);\r\n";
425 |
426 | this.shaderinjectpoint2 +=
427 | "float baseRough" +
428 | (i + 1) +
429 | " = chanInfo" +
430 | (i + 1) +
431 | "[1].a * " +
432 | this.options.splatInfos.roughness[i].toFixed(5) +
433 | ";\r\n";
434 | //this.shaderinjectpoint2 += "float baseRough" + (i + 1) +" = /*chanInfo" + (i + 1) + "[1].a * */ " + this.options.splatInfos.roughness[i].toFixed(5) + ";\r\n";
435 | //this.shaderinjectpoint2 += "float baseRough" + (i + 1) +" = chanInfo" + (i + 1) + "[1].a; /* " + this.options.splatInfos.roughness[i].toFixed(5) + "; */\r\n";
436 |
437 | this.shaderinjectpoint2 +=
438 | "float baseMetallic" +
439 | (i + 1) +
440 | " = " +
441 | this.options.splatInfos.metallic[i].toFixed(5) +
442 | ";\r\n";
443 | }
444 |
445 | //this.shaderinjectpoint3 += 'finalColor16 = col(vAlbedoUV, uv16, vec2(20.0, 20.0), vec2(1.0, 2.0), 0, scale, splatmap, albedoSampler);';
446 |
447 | this.shaderinjectpoint3 += `
448 | mat3 TBN = cotangent_frame(normalW, vPositionW, vAlbedoUV, vec2(-1.0, 1.0));
449 | `;
450 |
451 | this.shaderinjectpoint3 +=
452 | "normalW = TBN * finalNormal" + this.totalTiles + ";"; // TODO: adding these vectors is incorrect
453 | //this.shaderinjectpoint3 += "normalW = normalize(normalW * 0.75 + 0.25 * finalNormal" + (this.totalTiles) + ");"; // TODO: adding these vectors is incorrect
454 | //this.shaderinjectpoint3 += 'normalW = perturbNormal(cotangentFrame, finalNormal' + (this.totalTiles) + ', 1.0);';
455 | //this.shaderinjectpoint3 += 'normalW = normalW;';
456 | //this.shaderinjectpoint3 += 'normalW.y *= -1.0;';
457 | //this.shaderinjectpoint3 += 'result = finalNormal' + (this.totalTiles) + ';';
458 | this.shaderinjectpoint3 +=
459 | "result = finalColor" + this.totalTiles + ".rgb;";
460 |
461 | //this.shaderinjectpoint4 += 'normalW = normalW + finalNormal' + (this.totalTiles) + ';'; // TODO: adding these vectors is incorrect
462 |
463 | // MetallicRoughness.r is the computed metallicness
464 | // MetallicRoughness.g is the computed roughness
465 | this.shaderinjectpoint4 +=
466 | "metallicRoughness.g = finalRough" + this.totalTiles + ";";
467 | this.shaderinjectpoint4 +=
468 | "metallicRoughness.r = finalMetallic" + this.totalTiles + ";";
469 | //this.shaderinjectpoint4 += "metallicRoughness.r = 0.;";
470 | //this.shaderinjectpoint4 += "metallicRoughness.r = 0.;";
471 | //this.shaderinjectpoint4 += "metallicRoughness.g = 0.43;";
472 | //this.shaderinjectpoint4 += "reflectivityOut.microSurface = 0.0;";
473 |
474 | this.splatMap = splatMap;
475 | //this.needsUpdating = true;
476 |
477 | this.material = new PBRCustomMaterial("splatMaterial", scene);
478 | this.material.metallic = 0.0; // 0.0;
479 | this.material.roughness = 0.0; // 0.43 (asphalt); // 0.95;
480 | //this.material.indexOfRefraction = 1.4;
481 |
482 | //this.material.twoSidedLighting = true;
483 | //this.material.disableLighting = false;
484 | //this.material.ambientColor = new Color3(1.0, 1.0, 1.0); // Color3.Black();
485 | //this.material.disableBumpMap = true;
486 | //this.material.metallicspecularColor = new Color3(0.15, 0.15, 0.15); // Color3.Black();
487 | //this.material.specularIntensity = 1.0;
488 | //this.material.emissiveColor = new Color3(0.0, 0.0, 0.0); // Color3.Black();
489 | //this.material.emissiveIntensity = 0.0;
490 | //this.material.usePhysicalLightFalloff= false;
491 | //this.material.environmentIntensity = 1.0; // This one is needed to avoid saturation due to env
492 |
493 | this.material.albedoTexture = atlas;
494 | if (atlasnormals !== null) {
495 | //this.material.bumpTexture = atlasnormals;
496 | this.atlasBumpTexture = atlasnormals;
497 | }
498 | this.material.AddUniform("splatmap", "sampler2D", {});
499 | this.material.AddUniform("atlasNormalsSampler", "sampler2D", {});
500 |
501 | this.material.Vertex_Before_PositionUpdated(`
502 | uvUpdated = vec2(positionUpdated.x, -positionUpdated.z);
503 | `);
504 |
505 | this.material.Vertex_MainEnd(`
506 | //uvUpdated = uvUpdated + 0.5;
507 | `);
508 |
509 | this.material.Fragment_Definitions(`
510 | //#define REFLECTION
511 | #define TANGENT
512 | //#define METALLICWORKFLOW
513 | `);
514 |
515 | this.material.Fragment_Begin(
516 | "precision highp float;\r\n" +
517 | "precision highp int;\r\n" +
518 | this.shaderinjectpoint1 +
519 | `
520 |
521 | // From: https://github.com/BabylonJS/Babylon.js/blob/master/src/Shaders/ShadersInclude/bumpFragmentMainFunctions.fx
522 |
523 | vec3 perturbNormalBase(mat3 cotangentFrame, vec3 normal, float scale)
524 | {
525 | #ifdef NORMALXYSCALE
526 | normal = normalize(normal * vec3(scale, scale, 1.0));
527 | #endif
528 |
529 | return normalize(cotangentFrame * normal);
530 | }
531 |
532 | vec3 perturbNormal(mat3 cotangentFrame, vec3 textureSample, float scale)
533 | {
534 | return perturbNormalBase(cotangentFrame, textureSample * 2.0 - 1.0, scale);
535 | }
536 |
537 | mat3 cotangent_frame(vec3 normal, vec3 p, vec2 uv, vec2 tangentSpaceParams)
538 | {
539 | // get edge vectors of the pixel triangle
540 | vec3 dp1 = dFdx(p);
541 | vec3 dp2 = dFdy(p);
542 | vec2 duv1 = dFdx(uv);
543 | vec2 duv2 = dFdy(uv);
544 |
545 | // solve the linear system
546 | vec3 dp2perp = cross(dp2, normal);
547 | vec3 dp1perp = cross(normal, dp1);
548 | vec3 tangent = dp2perp * duv1.x + dp1perp * duv2.x;
549 | vec3 bitangent = dp2perp * duv1.y + dp1perp * duv2.y;
550 |
551 | // invert the tangent/bitangent if requested
552 | tangent *= tangentSpaceParams.x;
553 | bitangent *= tangentSpaceParams.y;
554 |
555 | // construct a scale-invariant frame
556 | float invmax = inversesqrt(max(dot(tangent, tangent), dot(bitangent, bitangent)));
557 | return mat3(tangent * invmax, bitangent * invmax, normal);
558 | }
559 |
560 | // Functions
561 |
562 | float heightval(vec4 col) {
563 | //return ((col.r + col.g + col.b) / 3.0);
564 | return col.a;
565 | }
566 |
567 | /*
568 | vec4 blend(vec4 texture1, float displScale1, vec4 texture2, float displScale2) {
569 | return ((texture1.a * displScale1) > (texture2.a * displScale2) ? texture1 : texture2);
570 | }
571 | */
572 |
573 | vec4 blend(vec4 texture1, float displScale1, vec4 texture2, float displScale2) {
574 | if (texture2.a == 0.) return texture1;
575 | if (texture1.a == 0.) return texture2;
576 | float a1 = texture1.a + displScale1;
577 | float a2 = texture2.a + displScale2;
578 | float depth = 0.2;
579 | float ma = max(texture1.a + a1, texture2.a + a2) - depth;
580 |
581 | float b1 = max(texture1.a + a1 - ma, 0.0);
582 | float b2 = max(texture2.a + a2 - ma, 0.0);
583 |
584 | vec4 result = (texture1 * b1 + texture2 * b2) / (b1 + b2);
585 | result.a = (texture1.a > 0. && texture2.a > 0.) ? (a1 + a2) / (2.0 * (b1 + b2)) : result.a;
586 | return result;
587 | }
588 |
589 | vec3 blendNormal(vec4 texture1, float displScale1, vec4 texture2, float displScale2, vec3 normal1, vec3 normal2) {
590 | float a1 = texture1.a + displScale1;
591 | float a2 = texture2.a + displScale2;
592 | float depth = 0.2;
593 | float ma = max(texture1.a + a1, texture2.a + a2) - depth;
594 |
595 | float b1 = max(texture1.a + a1 - ma, 0.0);
596 | float b2 = max(texture2.a + a2 - ma, 0.0);
597 |
598 | vec3 result = (normal1 * b1 + normal2 * b2) / (b1 + b2);
599 | result = normalize(result);
600 |
601 | return result;
602 | }
603 |
604 |
605 | vec2 hash22(vec2 p)
606 | {
607 | p = p * mat2(127.1, 311.7, 269.5, 183.3);
608 | p = -1.0 + 2.0 * fract(sin(p) * 43758.5453123);
609 | return sin(p * 6.283); // + timeScale
610 | }
611 |
612 | float interpolationNoise(vec2 p)
613 | {
614 | vec2 pi = floor(p);
615 | vec2 pf = p-pi;
616 |
617 | vec2 w = pf * pf * (3.-2. * pf);
618 |
619 | float f00 = dot(hash22(pi + vec2(.0,.0)), pf-vec2(.0,.0));
620 | float f01 = dot(hash22(pi + vec2(.0,1.)), pf-vec2(.0,1.));
621 | float f10 = dot(hash22(pi + vec2(1.0,0.)), pf-vec2(1.0,0.));
622 | float f11 = dot(hash22(pi + vec2(1.0,1.)), pf-vec2(1.0,1.));
623 |
624 | float xm1 = mix(f00,f10,w.x);
625 | float xm2 = mix(f01,f11,w.x);
626 |
627 | float ym = mix(xm1,xm2,w.y);
628 | return ym;
629 |
630 | }
631 |
632 | float perlinNoise2D(float x,float y)
633 | {
634 | int OCTAVES = 3;
635 | float persistence = 0.5;
636 | float sum = 0.0;
637 | float frequency = 0.0;
638 | float amplitude = 0.0;
639 | for(int i = 0; i < OCTAVES; i++)
640 | {
641 | frequency = pow(2.0, float(i));
642 | amplitude = pow(persistence, float(i));
643 | sum = sum + interpolationNoise(vec2(x * frequency, y * frequency)) * amplitude;
644 | }
645 |
646 | return sum;
647 | }
648 | ` +
649 | //+"vec4 col(vec2 vAlbedoUV, vec2 uvT, vec2 tile1Scale, vec2 tile1Position, int chanIdx, vec2 scale, sampler2D splatmap, sampler2D atlas, sampler2D atlasNormals) {"
650 | "mat4 colInfo(vec2 vAlbedoUV, vec2 uvT, float dedupScale, vec2 tile1Scale, vec2 tile1Position, int chanIdx, vec2 scale, sampler2D splatmap, sampler2D atlas, sampler2D atlasNormals) {" +
651 | `
652 | float offsetInputScale = 2.0;
653 | float offsetBaseScale = 0.25;
654 | vec2 offset = vec2(perlinNoise2D(offsetInputScale * (uvT.x + 0.01 * tile1Position.x), offsetInputScale * (uvT.y + 0.1 * tile1Position.y)),
655 | perlinNoise2D(offsetInputScale * (uvT.x + 0.07 * tile1Position.y), offsetInputScale * (-uvT.y + 0.13 * tile1Position.x)));
656 | offset = offset * offsetBaseScale * dedupScale;
657 | ` +
658 | // if this.dedupDouble...
659 | /*
660 | + `
661 | vec2 offset2 = vec2(perlinNoise2D(uvT.y - 0.08 * tile1Position.x, uvT.x + 0.04 * tile1Position.y),
662 | perlinNoise2D(uvT.y + 0.05 * tile1Position.y, -uvT.x + 0.19 * tile1Position.x));
663 | offset2 = offset * offsetBaseScale * dedupScale;
664 | `
665 | */
666 |
667 | "vec2 scaledUv1 = fract((vAlbedoUV + offset) * tile1Scale);" + // Curvy antitiling factor
668 | "scaledUv1 = scaledUv1 * (254.0/256.0) + vec2(1.0 / 256.0, 1.0 / 256.0);" +
669 | "vec2 uv1 = vec2((scaledUv1.x + tile1Position.x) * scale.x, (scaledUv1.y + tile1Position.y) * scale.y);" +
670 | /*
671 | +'vec2 scaledUv2 = fract((vAlbedoUV + offset2) * tile1Scale).yx;' // Curvy antitiling factor
672 | +'scaledUv2 = scaledUv2 * (254.0/256.0) + vec2(1.0 / 256.0, 1.0 / 256.0);'
673 | +'vec2 uv2 = vec2((scaledUv2.x + tile1Position.x) * scale.x, (scaledUv2.y + tile1Position.y) * scale.y);'
674 | */
675 |
676 | `
677 | vec4 splatColor1 = texture2D(splatmap, uvT);
678 | ` +
679 | `
680 | float dedupMix = perlinNoise2D(100.0 * uvT.x, 100.0 * uvT.y);
681 | dedupMix = dedupScale > 0.0 ? smoothstep(-0.02, 0.02, dedupMix) : 0.0;
682 |
683 | //vec4 diffuse1Color = texture2DLodEXT(atlas, uv1, -1.0);
684 | vec4 diffuseColorA = texture2D(atlas, uv1);
685 |
686 | //vec4 diffuseColorB = texture2D(atlas, uv2);
687 |
688 | //vec4 diffuse1Color = vec4(dedupMix, 0.0, 0.0, 1.0); // Debug dedup mix factor
689 | //vec4 diffuse1Color = diffuseColorA * dedupMix + diffuseColorB * (1.0 - dedupMix);
690 | vec4 diffuse1Color = diffuseColorA;
691 |
692 | vec4 diffuseNormalA = texture2D(atlasNormals, uv1);
693 | diffuseNormalA.rgb = (diffuseNormalA.rgb * 2.0 - 1.0);
694 | //vec4 diffuseNormalB = texture2D(atlasNormals, uv2);
695 | //diffuseNormalB.rgb = (diffuseNormalB.rgb * 2.0 - 1.0);
696 |
697 | /*
698 | vec3 diffuse1NormalVec = normalize(diffuseNormalA.xyz * dedupMix + diffuseNormalB.xyz * (1.0 - dedupMix));
699 | float diffuse1NormalAlpha = diffuseNormalA.a * dedupMix + diffuseNormalB.a * (1.0 - dedupMix);
700 | vec4 diffuse1Normal = vec4(diffuse1NormalVec.x, diffuse1NormalVec.y, diffuse1NormalVec.z, diffuse1NormalAlpha);
701 | */
702 |
703 | vec4 diffuse1Normal = diffuseNormalA;
704 | ` +
705 | `
706 | float blend = (chanIdx == 0 ? splatColor1.r : (chanIdx == 1 ? splatColor1.g : (chanIdx == 2 ? splatColor1.b : splatColor1.a)));
707 | //blend = 1.0;
708 |
709 | //diffuse1Color.rgb = splatColor1.rgb;
710 | //diffuse1Color.a = blend;
711 |
712 | //diffuse1Color.a = ((blend > 0.0) ? (heightval(diffuse1Color) * blend) : 0.0);
713 | diffuse1Color.a = ((blend > 0.0) ? (diffuse1Color.a * blend) : 0.0);
714 |
715 | mat4 chanInfo = mat4(diffuse1Color, vec4(diffuse1Normal.x, diffuse1Normal.y, diffuse1Normal.z, diffuse1Normal.a), vec4(0.0), vec4(0.0));
716 |
717 | ` +
718 | //+"return diffuse1Color;"
719 | "return chanInfo;" +
720 | "} ",
721 | );
722 |
723 | this.material.Fragment_MainBegin(this.shaderinjectpoint2);
724 |
725 | this.material.Fragment_Custom_Albedo(this.shaderinjectpoint3);
726 |
727 | this.material.Fragment_Custom_MetallicRoughness(this.shaderinjectpoint4);
728 |
729 | return this.material;
730 | }
731 |
732 | bind(mesh: AbstractMesh): void {
733 | //console.debug("Binding mesh for TerrainMaterial.");
734 | if (!this.material.getEffect()) return;
735 | this.material.getEffect().setTexture("splatmap", this.splatMap);
736 | this.material
737 | .getEffect()
738 | .setTexture("atlasNormalsSampler", this.atlasBumpTexture);
739 |
740 | /*
741 |
742 | //this.material.freeze();
743 |
744 | //this.material.reflectionTexture = this.envReflectionProbe.cubeTexture;
745 | //this.sceneViewer.scene.environmentTexture = this.sceneViewer.envReflectionProbe.cubeTexture;
746 | //this.sceneViewer.scene.environmentTexture = this.sceneViewer.envReflectionProbe!.cubeTexture;
747 |
748 | //this.material.detailMap.texture = this.sceneViewer.textureDetailSurfaceImp;
749 | this.material.useHorizonOcclusion = true;
750 | if (this.sceneViewer.envReflectionProbe) {
751 | const reflectionTexture = this.sceneViewer.envReflectionProbe.cubeTexture;
752 | //this.material.reflectionTexture = reflectionTexture;
753 | //this.material.getEffect().setTexture( "reflectionSampler", reflectionTexture);
754 | this.material.getEffect().setTexture("reflectionSampler", reflectionTexture._lodTextureMid || reflectionTexture);
755 | this.material.getEffect().setTexture("reflectionSamplerLow", reflectionTexture._lodTextureLow || reflectionTexture);
756 | this.material.getEffect().setTexture("reflectionSamplerHigh", reflectionTexture._lodTextureHigh || reflectionTexture);
757 | //this.material.getEffect().setTexture("environmentBrdfSampler", TerrainMaterialWrapper.terrainMaterialShared!._environmentBRDFTexture);
758 | }
759 | */
760 |
761 | /*
762 | const ubo = this.material['_uniformBuffer'];
763 | ubo.update();
764 | console.debug("Done");
765 | */
766 | }
767 | }
768 |
769 | export { TerrainMaterialWrapper };
770 |
--------------------------------------------------------------------------------
/src/render/materials/TextMaterial.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * DDDViewer - DDD(3Ds) Viewer library for DDD-generated GIS 3D scenes
3 | * Copyright 2021 Jose Juan Montes and Contributors
4 | * MIT License (see LICENSE file)
5 | */
6 |
7 | import { PBRCustomMaterial } from "@babylonjs/materials";
8 | import { BaseTexture, Color3, Scene, Texture, Vector2 } from "@babylonjs/core";
9 | import { SceneViewer } from "SceneViewer";
10 |
11 | /**
12 | */
13 | class TextMaterialWrapper {
14 |
15 | sceneViewer: SceneViewer;
16 | material: PBRCustomMaterial;
17 |
18 | options: any;
19 |
20 | constructor(sceneViewer: SceneViewer, atlasTexture: BaseTexture, atlasNormalTexture: BaseTexture | null, options: any = null) {
21 | this.sceneViewer = sceneViewer;
22 | this.options = options;
23 | this.material = this.initMaterial( this.sceneViewer.scene, atlasTexture, atlasNormalTexture);
24 | }
25 |
26 | initMaterial(scene: Scene, atlas: BaseTexture, atlasnormals: BaseTexture | null): PBRCustomMaterial {
27 |
28 | this.material = new PBRCustomMaterial("TextMaterial", scene);
29 | this.material.metallic = 0.05; // 0.0;
30 | this.material.roughness = 0.975; // 0.43 (asphalt); // 0.95;
31 | //this.material.indexOfRefraction = 1.4;
32 |
33 | //this.material.twoSidedLighting = true;
34 | //this.material.disableLighting = false;
35 | //this.material.ambientColor = new Color3(1.0, 1.0, 1.0); // Color3.Black();
36 | //this.material.disableBumpMap = true;
37 | //this.material.metallicspecularColor = new Color3(0.15, 0.15, 0.15); // Color3.Black();
38 | //this.material.specularIntensity = 1.0;
39 | //this.material.emissiveColor = new Color3(0.0, 0.0, 0.0); // Color3.Black();
40 | //this.material.emissiveIntensity = 0.0;
41 | //this.material.usePhysicalLightFalloff= false;
42 | //this.material.environmentIntensity = sceneViewer.baseEnvironmentIntensity; // This one is needed to avoid saturation due to env
43 |
44 | this.material.albedoTexture = atlas;
45 | if (atlasnormals !== null) {
46 | //this.material.bumpTexture = atlasnormals;
47 | this.material.bumpTexture = atlasnormals;
48 | }
49 |
50 | this.material.Fragment_Custom_Albedo(`
51 | vec3 txtColor = result;
52 | if (txtColor.r < 0.02) {
53 | discard;
54 | }
55 | //vec3 col = vec3(1.0, 0.8, 0.8);
56 | //vec3 col = vec3(1.0, 1.0, 1.0) * tmp;
57 | //col = vec3(col.r * result.r, col.g * result.g, col.b * result.b);
58 | vec3 col = vec3(0.0, 0.0, 0.0);
59 | surfaceAlbedo = col;
60 | `);
61 |
62 | /*
63 | this.material.onBindObservable.add(() => {
64 | this.update();
65 | });
66 | */
67 |
68 | return this.material;
69 | }
70 |
71 | update(): void {
72 | //this.material.getEffect().setTexture( "splatmap", this.splatMap );
73 | //this.material.getEffect().setTexture( "atlasNormalsSampler", this.atlasBumpTexture );
74 |
75 | //this.material.reflectionTexture = this.envReflectionProbe.cubeTexture;
76 | //this.material.reflectionTexture = this.scene.environmentTexture;
77 | //this.sceneViewer.scene.environmentTexture = this.sceneViewer.envReflectionProbe.cubeTexture;
78 | //this.scene.environmentTexture = this.envReflectionProbe.cubeTexture;
79 | }
80 |
81 | }
82 |
83 | export { TextMaterialWrapper };
84 |
--------------------------------------------------------------------------------
/src/render/pipeline/DefaultRenderPipeline.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * DDDViewer - DDD(3Ds) Viewer library for DDD-generated GIS 3D scenes
3 | * Copyright 2021 Jose Juan Montes and Contributors
4 | * MIT License (see LICENSE file)
5 | */
6 |
7 | import {
8 | Camera,
9 | Color3,
10 | ColorCurves,
11 | DefaultRenderingPipeline,
12 | ImageProcessingPostProcess,
13 | LensRenderingPipeline,
14 | Scene,
15 | } from "@babylonjs/core";
16 | import { SceneViewer } from "SceneViewer";
17 | import { ViewerState } from "ViewerState";
18 |
19 | /**
20 | * Rendering and pipeline configuration
21 | * (effects, shadows...)
22 | */
23 | class DefaultRenderPipeline {
24 | protected dddViewer: SceneViewer;
25 |
26 | protected scene: Scene;
27 |
28 | protected viewerState: ViewerState;
29 |
30 | constructor(dddViewer: SceneViewer) {
31 | // Reference to DDDViewer
32 | this.dddViewer = dddViewer;
33 | this.viewerState = dddViewer.viewerState;
34 | this.scene = dddViewer.scene;
35 |
36 | // Babylon camera which we are controlling
37 | //this.camera = dddViewer.camera;
38 | }
39 |
40 | update(deltaTime: number): void {}
41 |
42 | initialize(): void {}
43 |
44 | dispose(): void {}
45 |
46 | scenePostprocessingSetEnabled(value: boolean): void {
47 | this.viewerState.scenePostprocessingEnabled = value;
48 | //localStorage.setItem('dddScenePostprocessingSetEnabled', value);
49 | //alert('Reload the viewer for changes to take effect.');
50 | this.updateRenderPipeline();
51 | }
52 |
53 | updateRenderPipeline(): void {
54 | this.scene.postProcessesEnabled =
55 | this.viewerState.scenePostprocessingEnabled;
56 |
57 | if (!this.viewerState.scenePostprocessingEnabled) {
58 | return;
59 | }
60 | }
61 |
62 | initRenderPipeline(): void {
63 | // Postprocess
64 | // The default pipeline applies other settings, we'd better off using Bloom independently if possible
65 | // Also note this is tied to the camera, and thus if used, this should be updated when the camera changes
66 | const defaultPipeline = new DefaultRenderingPipeline(
67 | "default",
68 | true,
69 | this.scene,
70 | [ this.dddViewer.camera ],
71 | );
72 | defaultPipeline.fxaaEnabled = true;
73 | defaultPipeline.bloomEnabled = true;
74 | defaultPipeline.bloomWeight = 1.0; // 1.5 is exagerated but maybe usable for pics
75 | //defaultPipeline.cameraFov = this.camera.fov;
76 | defaultPipeline.imageProcessing.toneMappingEnabled = true;
77 |
78 | //var postProcessHighlights = new HighlightsPostProcess("highlights", 0.1, camera);
79 | //var postProcessTonemap = new TonemapPostProcess("tonemap", TonemappingOperator.Hable, 1.2, camera);
80 |
81 | // See: https://doc.babylonjs.com/divingDeeper/postProcesses/postProcessRenderPipeline
82 | /*
83 | var standardPipeline = new PostProcessRenderPipeline(this.engine, "standardPipeline");
84 | var effectBloom = new BloomEffect(this.scene, 4, 5.0, 2.0);
85 | //var effectDepthOfField = new DepthOfFieldEffect(this.scene);
86 | var postProcessChain = new PostProcessRenderEffect(this.engine, "postProcessChain", function() { return [effectBloom, effectDepthOfField] });
87 | standardPipeline.addEffect(effectBloom);
88 | this.scene.postProcessRenderPipelineManager.addPipeline(standardPipeline);
89 | */
90 |
91 | // Screen space reflections
92 | /*
93 | const ssr = new ScreenSpaceReflectionPostProcess(
94 | "ssr", // The name of the post-process
95 | this.scene, // The scene where to add the post-process
96 | 1.0, // The ratio of the post-process
97 | this.camera // To camera to attach the post-process
98 | );
99 | ssr.reflectionSamples = 32; // Low quality.
100 | ssr.strength = 2; // Set default strength of reflections.
101 | ssr.reflectionSpecularFalloffExponent = 3; // Attenuate the reflections a little bit. (typically in interval [1, 3])
102 | */
103 |
104 | const lensRenderingPipeline = new LensRenderingPipeline(
105 | "lens",
106 | {
107 | edge_blur: 0.25, // 1.0 is too distorted in the borders for walk/view mode (maybe for pictures)
108 | chromatic_aberration: 1.0,
109 | distortion: 0.7, // (dilate effect) 0.5 -> subtle
110 | dof_focus_distance: 60,
111 | dof_aperture: 1.0, // 1.2 is already too blurry for OSM, 6.0 is very high
112 | grain_amount: 0.0, // 0.5,
113 | dof_pentagon: false, // true,
114 | dof_gain: 1.0,
115 | dof_threshold: 1.0,
116 | dof_darken: 0.25,
117 | },
118 | this.scene,
119 | 1.0,
120 | [ this.dddViewer.camera ],
121 | );
122 | //this.scene.postProcessRenderPipelineManager.attachCamerasToRenderPipeline('lensEffects', camera);
123 |
124 | /*
125 | const ssao = new SSAO2RenderingPipeline('ssao', this.scene, {
126 | ssaoRatio: .5,
127 | blurRatio: 1
128 | }, [ this.camera ], true)
129 | */
130 |
131 | const curve = new ColorCurves();
132 | curve.globalHue = 0;
133 | curve.globalDensity = 80;
134 | curve.globalSaturation = 5;
135 | curve.highlightsHue = 0;
136 | curve.highlightsDensity = 80;
137 | curve.highlightsSaturation = 40;
138 | curve.shadowsHue = 0;
139 | curve.shadowsDensity = 80;
140 | curve.shadowsSaturation = 40;
141 | this.scene.imageProcessingConfiguration.colorCurvesEnabled = true;
142 | this.scene.imageProcessingConfiguration.colorCurves = curve;
143 | const postProcess = new ImageProcessingPostProcess(
144 | "processing",
145 | 1.0,
146 | this.dddViewer.camera,
147 | );
148 |
149 | // Fog
150 | //this.scene.fogMode = Scene.FOGMODE_EXP;
151 | //this.scene.fogDensity = 0.005; // default is 0.1
152 | this.scene.fogMode = Scene.FOGMODE_LINEAR;
153 | this.scene.fogStart = 350.0;
154 | this.scene.fogEnd = 700.0;
155 | this.scene.fogColor = new Color3(0.75, 0.75, 0.85);
156 |
157 | /*
158 | pixels = rp.cubeTexture.readPixels(0,0)
159 | // i take the first pixel of the reflection probe texture for fog color.
160 | // since pixels are stored as buffer array, first pixel are first 4 values of array [r,g,b,a....]
161 | scene.fogColor = new Color3(pixels[0]/255, pixels[1]/255, pixels[2]/255)
162 | */
163 | }
164 | }
165 |
166 | export { DefaultRenderPipeline };
167 |
--------------------------------------------------------------------------------
/src/render/skybox/CubemapSkybox.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * DDDViewer - DDD(3Ds) Viewer library for DDD-generated GIS 3D scenes
3 | * Copyright 2021 Jose Juan Montes and Contributors
4 | * MIT License (see LICENSE file)
5 | */
6 |
7 | import { Camera } from "@babylonjs/core";
8 | import { SceneViewer } from "SceneViewer";
9 | import { Skybox } from "./Skybox";
10 |
11 | /**
12 | * A skybox based on a cubemap.
13 | */
14 | class CubemapSkybox extends Skybox {
15 |
16 | update(deltaTime: number): void {
17 | throw new Error("Method not implemented.");
18 | }
19 |
20 | }
21 |
22 | export { CubemapSkybox };
--------------------------------------------------------------------------------
/src/render/skybox/DynamicSkybox.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * DDDViewer - DDD(3Ds) Viewer library for DDD-generated GIS 3D scenes
3 | * Copyright 2021 Jose Juan Montes and Contributors
4 | * MIT License (see LICENSE file)
5 | */
6 |
7 | import { Camera } from "@babylonjs/core";
8 | import { SceneViewer } from "SceneViewer";
9 | import { Skybox } from "./Skybox";
10 |
11 | /**
12 | * A skybox based on a shader.
13 | */
14 | class DynamicSkybox extends Skybox {
15 |
16 | update(deltaTime: number): void {
17 | throw new Error("Method not implemented.");
18 | }
19 |
20 | }
21 |
22 | export { DynamicSkybox };
--------------------------------------------------------------------------------
/src/render/skybox/Skybox.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * DDDViewer - DDD(3Ds) Viewer library for DDD-generated GIS 3D scenes
3 | * Copyright 2021 Jose Juan Montes and Contributors
4 | * MIT License (see LICENSE file)
5 | */
6 |
7 | import { Camera } from "@babylonjs/core";
8 | import { SceneViewer } from "SceneViewer";
9 |
10 | /**
11 | * DDD Viewer base skybox class.
12 | */
13 | abstract class Skybox {
14 |
15 | protected dddViewer: SceneViewer;
16 |
17 | constructor(dddViewer: SceneViewer) {
18 |
19 | // Reference to DDDViewer
20 | this.dddViewer = dddViewer;
21 |
22 | // Babylon camera which we are controlling
23 | //this.camera = dddViewer.camera;
24 | }
25 |
26 | abstract update(deltaTime: number): void;
27 |
28 | }
29 |
30 | export { Skybox };
--------------------------------------------------------------------------------
/test/blah.test.ts.disabled:
--------------------------------------------------------------------------------
1 | // import { sum } from "../src";
2 |
3 | // describe( "blah", () => {
4 | // it( "works", () => {
5 | // expect( sum( 1, 1 )).toEqual( 2 );
6 | // });
7 | // });
8 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | // see https://www.typescriptlang.org/tsconfig to better understand tsconfigs
3 | "include": [
4 | "src"
5 | //"types"
6 | ],
7 | "compilerOptions": {
8 | "baseUrl": "src",
9 | /*"paths": {
10 | "./main/*": ["main/*"],
11 | "./layers/*": ["layers/*"],
12 | "./loading/*": ["loading/*"],
13 | "./process/*": ["process/*"],
14 | "./render/*": ["render/*"],
15 | },*/
16 | "target": "es5",
17 | "module": "esnext",
18 | "types": [
19 | "ol",
20 | "proj4",
21 | "suncalc"
22 | ],
23 | "lib": [
24 | "DOM",
25 | "ESNext"
26 | ],
27 | "importHelpers": true,
28 | // output .d.ts declaration files for consumers
29 | "declaration": true,
30 | // output .js.map sourcemap files for consumers
31 | "sourceMap": true,
32 | // match output dir to input dir. e.g. dist/index instead of dist/src/index
33 | "rootDir": "./src",
34 | // stricter type-checking for stronger correctness. Recommended by TS
35 | "strict": true,
36 | // linter checks for common issues
37 | "noImplicitReturns": true,
38 | "noFallthroughCasesInSwitch": true,
39 | // noUnused* overlap with @typescript-eslint/no-unused-vars, can disable if duplicative
40 | "noUnusedLocals": false,
41 | "noUnusedParameters": false,
42 | // use Node's module resolution algorithm, instead of the legacy TS one
43 | "moduleResolution": "node",
44 | // transpile JSX to React.createElement
45 | //"jsx": "react",
46 | // interop between ESM and CJS modules. Recommended by TS
47 | "esModuleInterop": true,
48 | // significant perf increase by skipping checking .d.ts files, particularly those in node_modules. Recommended by TS
49 | "skipLibCheck": true,
50 | // error out if import and file system have a casing mismatch. Recommended by TS
51 | "forceConsistentCasingInFileNames": true,
52 | // `tsdx build` ignores this option, but it is commonly used when type-checking separately with `tsc`
53 | "noEmit": true
54 | }
55 | }
56 |
57 |
58 |
59 |
--------------------------------------------------------------------------------
/tsdx.config.js:
--------------------------------------------------------------------------------
1 | // Not transpiled with TypeScript or Babel, so use plain Es6/Node.js!
2 | const replace = require("@rollup/plugin-replace");
3 |
4 | module.exports = {
5 | // This function will run for each entry/format/env combination
6 | rollup(config, opts) {
7 |
8 | // TODO: See https://github.com/formium/tsdx to see if options currently package.json
9 |
10 | // Added to workaround deprecation warning in @rollup/plugin-replace about 'preventAssignment' default value change
11 | config.plugins = config.plugins.map(p =>
12 | p.name === "replace"
13 | ? replace({ "process.env.NODE_ENV": JSON.stringify(opts.env), preventAssignment: true, })
14 | : p
15 | );
16 |
17 | // In order to bundle external deps in UMD format:
18 | // From: https://github.com/formium/tsdx/issues/179
19 | // and: https://github.com/formium/tsdx/issues/898
20 | if (config.output.format === "umd") {
21 | const origExternal = config.external;
22 | config.external = (id) => {
23 | //console.log(id);
24 | //if (id.startsWith("@babylonjs")) return true;
25 | //return origExternal(id);
26 | return false; // Embed all libraries by default
27 | };
28 | //config.output.globals["babylon"] = "BABYLON";
29 | }
30 |
31 | // Return final customized config for the plugin
32 | return config;
33 | },
34 | };
35 |
36 |
--------------------------------------------------------------------------------