diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..aeae059c --- /dev/null +++ b/.gitignore @@ -0,0 +1,54 @@ +# See http://help.github.com/ignore-files/ for more about ignoring files. + +# compiled output +/builds +/release +/dist +/tmp +/out-tsc +# Only exists if Bazel was run +/bazel-out + +# dependencies +/node_modules + +# profiling files +chrome-profiler-events.json +speed-measure-plugin.json + +# IDEs and editors +*.idea +.project +.classpath +.c9/ +*.launch +.settings/ +*.sublime-workspace + +# IDE - VSCode +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +.history/* + +# misc +/.sass-cache +/connect.lock +/coverage +/libpeerconnection.log +npm-debug.log +yarn-error.log +testem.log +/typings +*venv + +# System Files +.DS_Store +Thumbs.db + +protos/csharp +scenario +bin +*.pyc \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 00000000..63a5852b --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,76 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "Electron-File", + "type": "chrome", + "request": "launch", + "runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron", + "windows": { + "runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron.cmd" + }, + "runtimeArgs": [ + "${workspaceRoot}/main.js", + "--remote-debugging-port=9222", + "--open-dev-tools", + "--disable-splash", + ], + "webRoot": "${workspaceRoot}" + }, + { + "name": "Electron-Localhost", + "type": "chrome", + "request": "launch", + "runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron", + "windows": { + "runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron.cmd" + }, + "runtimeArgs": [ + "${workspaceRoot}/main.js", + "--remote-debugging-port=9222", + "--open-dev-tools", + "--disable-splash", + "--locahost" + ], + "webRoot": "${workspaceRoot}/src" + }, + { + "name": "Attach", + "type": "node", + "request": "attach", + "port": 9222, + }, + { + "name": "Karma-Test-Debug", + "type": "chrome", + "request": "attach", + "address": "127.0.0.1", + "port": 9333, + "webRoot": "${workspaceRoot}", + "pathMapping": { + "/": "${workspaceRoot}/", + "/base/": "${workspaceRoot}/" + } + }, + { + "name": "Debug Main Process", + "type": "node", + "request": "launch", + "cwd": "${workspaceFolder}", + "runtimeExecutable": "${workspaceFolder}/node_modules/.bin/electron", + "windows": { + "runtimeExecutable": "${workspaceFolder}/node_modules/.bin/electron.cmd" + }, + "args" : [ + ".", + "--open-dev-tools", + "--disable-splash", + "--localhost" + ], + "outputCapture": "std" + } + ] +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..8b105763 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,44 @@ +{ + "git.ignoreLimitWarning": true, + "javascript.format.insertSpaceAfterConstructor": true, + "javascript.format.insertSpaceAfterOpeningAndBeforeClosingJsxExpressionBraces": true, + "javascript.format.insertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets": true, + "javascript.format.insertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces": true, + "javascript.format.insertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis": true, + "javascript.format.insertSpaceBeforeFunctionParenthesis": true, + "typescript.updateImportsOnFileMove.enabled": "always", + "typescript.format.insertSpaceAfterConstructor": true, + "typescript.format.insertSpaceAfterOpeningAndBeforeClosingJsxExpressionBraces": true, + "typescript.format.insertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets": true, + "typescript.format.insertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis": true, + "typescript.format.insertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces": true, + "typescript.format.insertSpaceBeforeFunctionParenthesis": true, + "editor.detectIndentation": false, + "files.exclude": { + "**/.git": true, + "**/.svn": true, + "**/.hg": true, + "**/CVS": true, + "**/.DS_Store": true, + "**/*.pyc": true + }, + "python.pythonPath": "/usr/bin/python", + "python.testing.unittestArgs": [ + "-v", + "-s", + "./python-client/", + "-p", + "test_*.py" + ], + "python.testing.pytestEnabled": false, + "python.testing.nosetestsEnabled": false, + "python.testing.unittestEnabled": true, + "editor.rulers": [ + 80,120 + ], + "typescript.tsdk": "node_modules\\typescript\\lib", + "javascript.validate.enable": true, + "typescript.validate.enable": true, + "html.format.wrapAttributes": "aligned-multiple", + "html.format.wrapLineLength": 240 +} \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..7e6e2d8f --- /dev/null +++ b/Dockerfile @@ -0,0 +1,22 @@ +# Nodejs Base image +FROM node:12 as build + +WORKDIR /app + +ENV PATH /app/node_modules/.bin:$PATH + +# install and app dependencies + +COPY package.json /app/package.json + +RUN npm install + +RUN npm install -g @angular/cli + +# add app +COPY . /app + +EXPOSE 4200 49153 + +# start app +CMD ng serve --host 0.0.0.0 --poll diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 00000000..cbce2ef3 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1 @@ +Use of the Truevision Designer is governed by the terms of the Truevision Designer End User License Agreement, which can be found at https://www.truevision.ai/eula \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 00000000..80a9be3d --- /dev/null +++ b/README.md @@ -0,0 +1,39 @@ +# Truevision Designer + +Truevision Designer is a 3D tool to design roads, intersections and environments for testing and validating autonomous vehicles and robots. + +From this repository you can build the Truevision Designer for Windows and Linux. + +[![Video](docs/img/welcome.jpg)](https://www.youtube.com/watch?v=7jej46ALVRE) + + +## Features + +- Create and modify roads, lanes +- Import/Export OpenDrive 1.4 +- Export to CARLA simulator +- Export 3D environment as GLTF or GLB file +- Import and place 3d assets in the environment using various prop tools + +## Download + +Click here to download [] + +## Documentation + +Latest documentation can be found at [https://www.truevision.ai/designer/docs](https://www.truevision.ai/designer/docs) + +## Tutorials + +Latest tutorials and videos can be found at [https://www.youtube.com/channel/UCv4Gkf0Z0JyWdKQHakXpbAw/videos](https://www.youtube.com/channel/UCv4Gkf0Z0JyWdKQHakXpbAw/videos) + +## Recommended System +- Intel i7 or above +- 16 GB RAM +- Ubuntu/Windows + +## License + +In brief, you are free to use, modify Truevision Designer for all non-commercial purposes only. Please read the license for more details. + +Your access to and use of Truevision Designer on GitHub is governed by the Truevision Designer End User License Agreement. If you don't agree to those terms, as amended from time to time, you are not permitted to access or use Truevision Designer. \ No newline at end of file diff --git a/angular.json b/angular.json new file mode 100644 index 00000000..503f8462 --- /dev/null +++ b/angular.json @@ -0,0 +1,138 @@ +{ + "$schema": "./node_modules/@angular/cli/lib/config/schema.json", + "version": 1, + "newProjectRoot": "projects", + "projects": { + "truevision": { + "root": "", + "sourceRoot": "src", + "projectType": "application", + "prefix": "app", + "schematics": {}, + "architect": { + "build": { + "builder": "@angular-devkit/build-angular:browser", + "options": { + "outputPath": "dist", + "index": "src/index.html", + "main": "src/main.ts", + "polyfills": "src/polyfills.ts", + "tsConfig": "src/tsconfig.app.json", + "assets": [ + "src/favicon.ico", + "src/assets" + ], + "styles": [ + "src/assets/styles/app.scss", + "src/styles.css" + ], + "scripts": [], + "es5BrowserSupport": true + }, + "configurations": { + "production": { + "fileReplacements": [ + { + "replace": "src/environments/environment.ts", + "with": "src/environments/environment.prod.ts" + } + ], + "optimization": true, + "outputHashing": "all", + "sourceMap": false, + "extractCss": true, + "namedChunks": false, + "aot": true, + "extractLicenses": true, + "vendorChunk": false, + "buildOptimizer": true, + "budgets": [ + { + "type": "initial", + "maximumWarning": "5mb", + "maximumError": "10mb" + } + ] + } + } + }, + "serve": { + "builder": "@angular-devkit/build-angular:dev-server", + "options": { + "browserTarget": "truevision:build" + }, + "configurations": { + "production": { + "browserTarget": "truevision:build:production" + } + } + }, + "extract-i18n": { + "builder": "@angular-devkit/build-angular:extract-i18n", + "options": { + "browserTarget": "truevision:build" + } + }, + "test": { + "builder": "@angular-devkit/build-angular:karma", + "options": { + "main": "src/test.ts", + "polyfills": "src/polyfills.ts", + "tsConfig": "src/tsconfig.spec.json", + "karmaConfig": "src/karma.conf.js", + "sourceMap": true, + "styles": [ + "src/assets/styles/app.scss" + ], + "scripts": [], + "assets": [ + "src/favicon.ico", + "src/assets" + ] + } + }, + "lint": { + "builder": "@angular-devkit/build-angular:tslint", + "options": { + "tsConfig": [ + "src/tsconfig.app.json", + "src/tsconfig.spec.json" + ], + "exclude": [ + "**/node_modules/**" + ] + } + } + } + }, + "truevision-e2e": { + "root": "e2e/", + "projectType": "application", + "prefix": "", + "architect": { + "e2e": { + "builder": "@angular-devkit/build-angular:protractor", + "options": { + "protractorConfig": "e2e/protractor.conf.js", + "devServerTarget": "truevision:serve" + }, + "configurations": { + "production": { + "devServerTarget": "truevision:serve:production" + } + } + }, + "lint": { + "builder": "@angular-devkit/build-angular:tslint", + "options": { + "tsConfig": "e2e/tsconfig.e2e.json", + "exclude": [ + "**/node_modules/**" + ] + } + } + } + } + }, + "defaultProject": "truevision" +} diff --git a/build/icon.png b/build/icon.png new file mode 100644 index 00000000..1da2ab11 Binary files /dev/null and b/build/icon.png differ diff --git a/build/icons/256x256.png b/build/icons/256x256.png new file mode 100644 index 00000000..bddbe38b Binary files /dev/null and b/build/icons/256x256.png differ diff --git a/build/icons/512x512.png b/build/icons/512x512.png new file mode 100644 index 00000000..3a39c146 Binary files /dev/null and b/build/icons/512x512.png differ diff --git a/build/icons/icon.png b/build/icons/icon.png new file mode 100644 index 00000000..1da2ab11 Binary files /dev/null and b/build/icons/icon.png differ diff --git a/buildspec.yml b/buildspec.yml new file mode 100644 index 00000000..795995ce --- /dev/null +++ b/buildspec.yml @@ -0,0 +1,30 @@ +version: 0.2 + +phases: + install: + commands: + # install dependencies + - echo installng source NPM dependencies... + - npm install npm@latest -g + - npm install -g @angular/cli + + pre_build: + commands: + - echo prebuild steps + - npm install + - ng test --progress=false --browsers=ChromeHeadless --watch=false + + build: + commands: + # build angular app + - ng build --prod + + + post_build: + commands: + # clear S3 bucket + # - aws s3 rm s3://${S3_BUCKET} --recursive + # - echo S3 bucket cleared + # copy files from dist folder into S3 bucket + # - aws s3 cp dist s3://${S3_BUCKET} --recursive + - echo Build completed on `date` \ No newline at end of file diff --git a/default-project/Materials/Asphalt-01.jpg b/default-project/Materials/Asphalt-01.jpg new file mode 100644 index 00000000..0711bd7a Binary files /dev/null and b/default-project/Materials/Asphalt-01.jpg differ diff --git a/default-project/Materials/Asphalt-01.jpg.meta b/default-project/Materials/Asphalt-01.jpg.meta new file mode 100644 index 00000000..c3a593b6 --- /dev/null +++ b/default-project/Materials/Asphalt-01.jpg.meta @@ -0,0 +1,40 @@ +{ + "guid": "AAC88E06-1805-4B33-9D03-D6BA62031E38", + "version": 4.5, + "type": "Texture", + "importer": "TextureImporter", + "data": { + "metadata": null, + "uuid": "7DD16D3C-C6F1-46E7-B33D-266217C5000E", + "name": "", + "mapping": 300, + "repeat": [ + 1, + 1 + ], + "offset": [ + 0, + 0 + ], + "center": [ + 0, + 0 + ], + "rotation": 0, + "wrap": [ + 1000, + 1000 + ], + "format": 1023, + "type": 1009, + "encoding": 3000, + "minFilter": 1008, + "magFilter": 1006, + "anisotropy": 1, + "flipY": true, + "premultiplyAlpha": false, + "unpackAlignment": 4 + }, + "path": "/home/himanshu/Documents/Truevision/Materials/Asphalt-01.jpg", + "preview": null +} \ No newline at end of file diff --git a/default-project/Materials/Asphalt-01.material b/default-project/Materials/Asphalt-01.material new file mode 100644 index 00000000..beb96f63 --- /dev/null +++ b/default-project/Materials/Asphalt-01.material @@ -0,0 +1,14 @@ +{ + "version": 0.1, + "uuid": "3C52858A-B32B-4927-87CF-814E8E5FFFAE", + "type": "MeshStandardMaterial", + "name": "Asphalt-01", + "color": 16777215, + "roughness": 1, + "metalness": 0.3, + "emissive": 0, + "mapGuid": "AAC88E06-1805-4B33-9D03-D6BA62031E38", + "depthFunc": 3, + "depthTest": true, + "depthWrite": true +} \ No newline at end of file diff --git a/default-project/Materials/Asphalt-01.material.meta b/default-project/Materials/Asphalt-01.material.meta new file mode 100644 index 00000000..12ea94a4 --- /dev/null +++ b/default-project/Materials/Asphalt-01.material.meta @@ -0,0 +1,7 @@ +{ + "guid": "09B39764-2409-4A58-B9AB-D9C18AD5485C", + "importer": "MaterialImporter", + "data": {}, + "path": "/home/himanshu/Documents/Truevision/Materials/Asphalt-01.material", + "preview": null +} \ No newline at end of file diff --git a/default-project/Materials/Grass-01.jpg b/default-project/Materials/Grass-01.jpg new file mode 100644 index 00000000..ed6c7f85 Binary files /dev/null and b/default-project/Materials/Grass-01.jpg differ diff --git a/default-project/Materials/Grass-01.jpg.meta b/default-project/Materials/Grass-01.jpg.meta new file mode 100644 index 00000000..f9cddb7b --- /dev/null +++ b/default-project/Materials/Grass-01.jpg.meta @@ -0,0 +1,40 @@ +{ + "guid": "DB932B7B-8B5E-4BB0-B092-25D31FD7DFF3", + "version": 4.5, + "type": "Texture", + "importer": "TextureImporter", + "data": { + "metadata": null, + "uuid": "5AABBD8D-7F38-4141-A857-3E38F65BB682", + "name": "", + "mapping": 300, + "repeat": [ + 1, + 1 + ], + "offset": [ + 0, + 0 + ], + "center": [ + 0, + 0 + ], + "rotation": 0, + "wrap": [ + 1000, + 1000 + ], + "format": 1023, + "type": 1009, + "encoding": 3000, + "minFilter": 1008, + "magFilter": 1006, + "anisotropy": 1, + "flipY": true, + "premultiplyAlpha": false, + "unpackAlignment": 4 + }, + "path": "/home/himanshu/Documents/Truevision/Materials/Grass-01.jpg", + "preview": null +} \ No newline at end of file diff --git a/default-project/Materials/Grass-01.material b/default-project/Materials/Grass-01.material new file mode 100644 index 00000000..92d79e69 --- /dev/null +++ b/default-project/Materials/Grass-01.material @@ -0,0 +1,14 @@ +{ + "version": 0.1, + "uuid": "EC4297BB-7C10-4E49-BCEF-F90C54D68F6F", + "type": "MeshStandardMaterial", + "name": "Grass-01", + "color": 16777215, + "roughness": 1, + "metalness": 0.2, + "emissive": 0, + "mapGuid": "DB932B7B-8B5E-4BB0-B092-25D31FD7DFF3", + "depthFunc": 3, + "depthTest": true, + "depthWrite": true +} \ No newline at end of file diff --git a/default-project/Materials/Grass-01.material.meta b/default-project/Materials/Grass-01.material.meta new file mode 100644 index 00000000..fa5d5339 --- /dev/null +++ b/default-project/Materials/Grass-01.material.meta @@ -0,0 +1,7 @@ +{ + "guid": "E9FF8597-DB71-4615-B55F-A01800C9B68D", + "importer": "MaterialImporter", + "data": {}, + "path": "/home/himanshu/Documents/Truevision/Materials/Grass-01.material", + "preview": null +} \ No newline at end of file diff --git a/default-project/Materials/Sidewalk-01.jpg b/default-project/Materials/Sidewalk-01.jpg new file mode 100644 index 00000000..d8938e66 Binary files /dev/null and b/default-project/Materials/Sidewalk-01.jpg differ diff --git a/default-project/Materials/Sidewalk-01.jpg.meta b/default-project/Materials/Sidewalk-01.jpg.meta new file mode 100644 index 00000000..423af1d0 --- /dev/null +++ b/default-project/Materials/Sidewalk-01.jpg.meta @@ -0,0 +1,40 @@ +{ + "guid": "D6871CAD-6304-4584-BDF2-CD1780B4F6D3", + "version": 4.5, + "type": "Texture", + "importer": "TextureImporter", + "data": { + "metadata": null, + "uuid": "71A9E97C-C4E1-444D-B334-5D087E52AE7A", + "name": "", + "mapping": 300, + "repeat": [ + 1, + 1 + ], + "offset": [ + 0, + 0 + ], + "center": [ + 0, + 0 + ], + "rotation": 0, + "wrap": [ + 1000, + 1000 + ], + "format": 1023, + "type": 1009, + "encoding": 3000, + "minFilter": 1008, + "magFilter": 1006, + "anisotropy": 1, + "flipY": true, + "premultiplyAlpha": false, + "unpackAlignment": 4 + }, + "path": "/home/himanshu/Documents/Truevision/Materials/Sidewalk-01.jpg", + "preview": null +} \ No newline at end of file diff --git a/default-project/Materials/Sidewalk-01.material b/default-project/Materials/Sidewalk-01.material new file mode 100644 index 00000000..189b936f --- /dev/null +++ b/default-project/Materials/Sidewalk-01.material @@ -0,0 +1,14 @@ +{ + "version": 0.1, + "uuid": "3F5A41C8-AF34-4A26-A0E6-55DE6E6077CF", + "type": "MeshStandardMaterial", + "name": "Sidewalk-01", + "color": 16777215, + "roughness": 1, + "metalness": 0.3, + "emissive": 0, + "mapGuid": "D6871CAD-6304-4584-BDF2-CD1780B4F6D3", + "depthFunc": 3, + "depthTest": true, + "depthWrite": true +} \ No newline at end of file diff --git a/default-project/Materials/Sidewalk-01.material.meta b/default-project/Materials/Sidewalk-01.material.meta new file mode 100644 index 00000000..6926be0d --- /dev/null +++ b/default-project/Materials/Sidewalk-01.material.meta @@ -0,0 +1,7 @@ +{ + "guid": "87B8CB52-7E11-4F22-9CF6-285EC8FE9218", + "importer": "MaterialImporter", + "data": {}, + "path": "/home/himanshu/Documents/Truevision/Materials/Sidewalk-01.material", + "preview": null +} \ No newline at end of file diff --git a/default-project/Props/Bush-01.glb b/default-project/Props/Bush-01.glb new file mode 100644 index 00000000..1aeacbe4 Binary files /dev/null and b/default-project/Props/Bush-01.glb differ diff --git a/default-project/Props/Bush-01.glb.meta b/default-project/Props/Bush-01.glb.meta new file mode 100644 index 00000000..bf3edd05 --- /dev/null +++ b/default-project/Props/Bush-01.glb.meta @@ -0,0 +1,18 @@ +{ + "guid": "42076FCA-53E8-4616-AA8B-EF694F834BD7", + "importer": "ModelImporter", + "data": { + "name": "Bush-01.glb", + "rotationVariance": { + "x": 0, + "y": 0, + "z": 0 + }, + "scaleVariance": { + "x": 0, + "y": 0, + "z": 0 + } + }, + "path": "/home/himanshu/Documents/Truevision/Props/Bush-01.glb" +} \ No newline at end of file diff --git a/default-project/Props/Tree-01.glb b/default-project/Props/Tree-01.glb new file mode 100644 index 00000000..37946c24 Binary files /dev/null and b/default-project/Props/Tree-01.glb differ diff --git a/default-project/Props/Tree-01.glb.meta b/default-project/Props/Tree-01.glb.meta new file mode 100644 index 00000000..0ab27a60 --- /dev/null +++ b/default-project/Props/Tree-01.glb.meta @@ -0,0 +1,18 @@ +{ + "guid": "97654775-41C1-4C02-A0C1-26A7292B6831", + "importer": "ModelImporter", + "data": { + "name": "Tree-01.glb", + "rotationVariance": { + "x": 0, + "y": 0, + "z": 0 + }, + "scaleVariance": { + "x": 0, + "y": 0, + "z": 0 + } + }, + "path": "/home/himanshu/Documents/Truevision/Props/Tree-01.glb" +} \ No newline at end of file diff --git a/default-project/RoadMarkings/NewRoadMarking.roadmarking b/default-project/RoadMarkings/NewRoadMarking.roadmarking new file mode 100644 index 00000000..41782ed9 --- /dev/null +++ b/default-project/RoadMarkings/NewRoadMarking.roadmarking @@ -0,0 +1,5 @@ +{ + "name": "NewRoadMarking", + "type": "point", + "textureGuid": "ED2FD2A5-4784-444D-A355-6979065397B7" +} \ No newline at end of file diff --git a/default-project/RoadMarkings/NewRoadMarking.roadmarking.meta b/default-project/RoadMarkings/NewRoadMarking.roadmarking.meta new file mode 100644 index 00000000..a100b247 --- /dev/null +++ b/default-project/RoadMarkings/NewRoadMarking.roadmarking.meta @@ -0,0 +1,6 @@ +{ + "guid": "71F79C7A-7326-45A0-B1BA-EE836D278C94", + "importer": "RoadMarkingImporter", + "data": {}, + "path": "/home/himanshu/Documents/Truevision/RoadMarkings/NewRoadMarking.roadmarking" +} \ No newline at end of file diff --git a/default-project/RoadMarkings/STOP.svg b/default-project/RoadMarkings/STOP.svg new file mode 100644 index 00000000..2c956987 --- /dev/null +++ b/default-project/RoadMarkings/STOP.svg @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + diff --git a/default-project/RoadMarkings/STOP.svg.meta b/default-project/RoadMarkings/STOP.svg.meta new file mode 100644 index 00000000..9de91d59 --- /dev/null +++ b/default-project/RoadMarkings/STOP.svg.meta @@ -0,0 +1,39 @@ +{ + "guid": "ED2FD2A5-4784-444D-A355-6979065397B7", + "version": 4.5, + "type": "Texture", + "importer": "TextureImporter", + "data": { + "metadata": null, + "uuid": "37C49ED2-D1E3-4A8C-99D7-DFD38BFD289A", + "name": "", + "mapping": 300, + "repeat": [ + 1, + 1 + ], + "offset": [ + 0, + 0 + ], + "center": [ + 0, + 0 + ], + "rotation": 0, + "wrap": [ + 1000, + 1000 + ], + "format": 1023, + "type": 1009, + "encoding": 3000, + "minFilter": 1008, + "magFilter": 1006, + "anisotropy": 1, + "flipY": true, + "premultiplyAlpha": false, + "unpackAlignment": 4 + }, + "path": "STOP.svg" +} diff --git a/default-project/RoadStyles/Country.roadstyle b/default-project/RoadStyles/Country.roadstyle new file mode 100644 index 00000000..d0bc58d9 --- /dev/null +++ b/default-project/RoadStyles/Country.roadstyle @@ -0,0 +1 @@ +{"version":1,"laneOffset":{"attr_s":0,"attr_a":0,"attr_b":0,"attr_c":0,"attr_d":0},"laneSection":{"attr_s":0,"left":{"lane":[{"attr_id":2,"attr_type":"shoulder","attr_level":true,"width":[{"attr_sOffset":0,"attr_a":0.5,"attr_b":0,"attr_c":0,"attr_d":0}],"roadMark":[]},{"attr_id":1,"attr_type":"driving","attr_level":true,"width":[{"attr_sOffset":0,"attr_a":3.6,"attr_b":0,"attr_c":0,"attr_d":0}],"roadMark":[{"attr_sOffset":0,"attr_type":"solid","attr_weight":"standard","attr_color":"standard","attr_width":0.15,"attr_laneChange":"none","attr_height":0}]}]},"center":{"lane":[{"attr_id":0,"attr_type":"driving","attr_level":true,"width":[],"roadMark":[{"attr_sOffset":0,"attr_type":"none","attr_weight":"standard","attr_color":"standard","attr_width":0.15,"attr_laneChange":"none","attr_height":0}]}]},"right":{"lane":[{"attr_id":-1,"attr_type":"driving","attr_level":true,"width":[{"attr_sOffset":0,"attr_a":3.6,"attr_b":0,"attr_c":0,"attr_d":0}],"roadMark":[{"attr_sOffset":0,"attr_type":"solid","attr_weight":"standard","attr_color":"standard","attr_width":0.15,"attr_laneChange":"none","attr_height":0}]},{"attr_id":-2,"attr_type":"shoulder","attr_level":true,"width":[{"attr_sOffset":0,"attr_a":0.5,"attr_b":0,"attr_c":0,"attr_d":0}],"roadMark":[]}]}}} \ No newline at end of file diff --git a/default-project/RoadStyles/Country.roadstyle.meta b/default-project/RoadStyles/Country.roadstyle.meta new file mode 100644 index 00000000..9b1a1448 --- /dev/null +++ b/default-project/RoadStyles/Country.roadstyle.meta @@ -0,0 +1,6 @@ +{ + "guid": "34902B1A-270C-4DEB-BD0A-CCCE340D00BF", + "importer": "RoadStyleImporter", + "data": {}, + "path": "/home/himanshu/Documents/Truevision/RoadStyles/Country.roadstyle" +} \ No newline at end of file diff --git a/default-project/RoadStyles/Default.roadstyle b/default-project/RoadStyles/Default.roadstyle new file mode 100644 index 00000000..8c439b0d --- /dev/null +++ b/default-project/RoadStyles/Default.roadstyle @@ -0,0 +1 @@ +{"version":1,"laneOffset":{"attr_s":0,"attr_a":0,"attr_b":0,"attr_c":0,"attr_d":0},"laneSection":{"attr_s":0,"left":{"lane":[{"attr_id":3,"attr_type":"sidewalk","attr_level":true,"width":[{"attr_sOffset":0,"attr_a":2,"attr_b":0,"attr_c":0,"attr_d":0}],"roadMark":[]},{"attr_id":2,"attr_type":"shoulder","attr_level":true,"width":[{"attr_sOffset":0,"attr_a":0.5,"attr_b":0,"attr_c":0,"attr_d":0}],"roadMark":[]},{"attr_id":1,"attr_type":"driving","attr_level":true,"width":[{"attr_sOffset":0,"attr_a":3.6,"attr_b":0,"attr_c":0,"attr_d":0}],"roadMark":[{"attr_sOffset":0,"attr_type":"none","attr_weight":"standard","attr_color":"standard","attr_width":0.15,"attr_laneChange":"none","attr_height":0}]}]},"center":{"lane":[{"attr_id":0,"attr_type":"driving","attr_level":true,"width":[],"roadMark":[{"attr_sOffset":0,"attr_type":"broken","attr_weight":"standard","attr_color":"standard","attr_width":0.15,"attr_laneChange":"none","attr_height":0}]}]},"right":{"lane":[{"attr_id":-1,"attr_type":"driving","attr_level":true,"width":[{"attr_sOffset":0,"attr_a":3.6,"attr_b":0,"attr_c":0,"attr_d":0}],"roadMark":[{"attr_sOffset":0,"attr_type":"none","attr_weight":"standard","attr_color":"standard","attr_width":0.15,"attr_laneChange":"none","attr_height":0}]},{"attr_id":-2,"attr_type":"shoulder","attr_level":true,"width":[{"attr_sOffset":0,"attr_a":0.5,"attr_b":0,"attr_c":0,"attr_d":0}],"roadMark":[]},{"attr_id":-3,"attr_type":"sidewalk","attr_level":true,"width":[{"attr_sOffset":0,"attr_a":2,"attr_b":0,"attr_c":0,"attr_d":0}],"roadMark":[]}]}}} \ No newline at end of file diff --git a/default-project/RoadStyles/Default.roadstyle.meta b/default-project/RoadStyles/Default.roadstyle.meta new file mode 100644 index 00000000..a08cc717 --- /dev/null +++ b/default-project/RoadStyles/Default.roadstyle.meta @@ -0,0 +1,6 @@ +{ + "guid": "BC6945AF-45EE-4B32-B42A-23E755B3E27E", + "importer": "RoadStyleImporter", + "data": {}, + "path": "/home/himanshu/Documents/Truevision/RoadStyles/Default.roadstyle" +} \ No newline at end of file diff --git a/default-project/RoadStyles/Freeway.roadstyle b/default-project/RoadStyles/Freeway.roadstyle new file mode 100644 index 00000000..ddcf0a44 --- /dev/null +++ b/default-project/RoadStyles/Freeway.roadstyle @@ -0,0 +1 @@ +{"version":1,"laneOffset":{"attr_s":0,"attr_a":0,"attr_b":0,"attr_c":0,"attr_d":0},"laneSection":{"attr_s":0,"left":{"lane":[{"attr_id":6,"attr_type":"shoulder","attr_level":true,"width":[{"attr_sOffset":0,"attr_a":3.6,"attr_b":0,"attr_c":0,"attr_d":0}],"roadMark":[]},{"attr_id":5,"attr_type":"driving","attr_level":true,"width":[{"attr_sOffset":0,"attr_a":3.6,"attr_b":0,"attr_c":0,"attr_d":0}],"roadMark":[{"attr_sOffset":0,"attr_type":"solid","attr_weight":"standard","attr_color":"standard","attr_width":0.15,"attr_laneChange":"none","attr_height":0}]},{"attr_id":4,"attr_type":"driving","attr_level":true,"width":[{"attr_sOffset":0,"attr_a":3.6,"attr_b":0,"attr_c":0,"attr_d":0}],"roadMark":[{"attr_sOffset":0,"attr_type":"broken","attr_weight":"standard","attr_color":"standard","attr_width":0.15,"attr_laneChange":"none","attr_height":0}]},{"attr_id":3,"attr_type":"driving","attr_level":true,"width":[{"attr_sOffset":0,"attr_a":3.6,"attr_b":0,"attr_c":0,"attr_d":0}],"roadMark":[{"attr_sOffset":0,"attr_type":"broken","attr_weight":"standard","attr_color":"standard","attr_width":0.15,"attr_laneChange":"none","attr_height":0}]},{"attr_id":2,"attr_type":"driving","attr_level":true,"width":[{"attr_sOffset":0,"attr_a":3.6,"attr_b":0,"attr_c":0,"attr_d":0}],"roadMark":[{"attr_sOffset":0,"attr_type":"broken","attr_weight":"standard","attr_color":"standard","attr_width":0.15,"attr_laneChange":"none","attr_height":0}]},{"attr_id":1,"attr_type":"shoulder","attr_level":true,"width":[{"attr_sOffset":0,"attr_a":3.6,"attr_b":0,"attr_c":0,"attr_d":0}],"roadMark":[{"attr_sOffset":0,"attr_type":"solid","attr_weight":"standard","attr_color":"yellow","attr_width":0.15,"attr_laneChange":"none","attr_height":0}]}]},"center":{"lane":[{"attr_id":0,"attr_type":"none","attr_level":true,"width":[],"roadMark":[{"attr_sOffset":0,"attr_type":"none","attr_weight":"standard","attr_color":"standard","attr_width":0.15,"attr_laneChange":"none","attr_height":0}]}]},"right":{"lane":[{"attr_id":-1,"attr_type":"shoulder","attr_level":true,"width":[{"attr_sOffset":0,"attr_a":3.6,"attr_b":0,"attr_c":0,"attr_d":0}],"roadMark":[{"attr_sOffset":0,"attr_type":"solid","attr_weight":"standard","attr_color":"yellow","attr_width":0.15,"attr_laneChange":"none","attr_height":0}]},{"attr_id":-2,"attr_type":"driving","attr_level":true,"width":[{"attr_sOffset":0,"attr_a":3.6,"attr_b":0,"attr_c":0,"attr_d":0}],"roadMark":[{"attr_sOffset":0,"attr_type":"broken","attr_weight":"standard","attr_color":"standard","attr_width":0.15,"attr_laneChange":"none","attr_height":0}]},{"attr_id":-3,"attr_type":"driving","attr_level":true,"width":[{"attr_sOffset":0,"attr_a":3.6,"attr_b":0,"attr_c":0,"attr_d":0}],"roadMark":[{"attr_sOffset":0,"attr_type":"broken","attr_weight":"standard","attr_color":"standard","attr_width":0.15,"attr_laneChange":"none","attr_height":0}]},{"attr_id":-4,"attr_type":"driving","attr_level":true,"width":[{"attr_sOffset":0,"attr_a":3.6,"attr_b":0,"attr_c":0,"attr_d":0}],"roadMark":[{"attr_sOffset":0,"attr_type":"broken","attr_weight":"standard","attr_color":"standard","attr_width":0.15,"attr_laneChange":"none","attr_height":0}]},{"attr_id":-5,"attr_type":"driving","attr_level":true,"width":[{"attr_sOffset":0,"attr_a":3.6,"attr_b":0,"attr_c":0,"attr_d":0}],"roadMark":[{"attr_sOffset":0,"attr_type":"solid","attr_weight":"standard","attr_color":"standard","attr_width":0.15,"attr_laneChange":"none","attr_height":0}]},{"attr_id":-6,"attr_type":"shoulder","attr_level":true,"width":[{"attr_sOffset":0,"attr_a":3.6,"attr_b":0,"attr_c":0,"attr_d":0}],"roadMark":[]}]}}} \ No newline at end of file diff --git a/default-project/RoadStyles/Freeway.roadstyle.meta b/default-project/RoadStyles/Freeway.roadstyle.meta new file mode 100644 index 00000000..11dadb20 --- /dev/null +++ b/default-project/RoadStyles/Freeway.roadstyle.meta @@ -0,0 +1,6 @@ +{ + "guid": "DB2A86F7-B07B-4051-B718-F8FA4F391F0C", + "importer": "RoadStyleImporter", + "data": {}, + "path": "/home/himanshu/Documents/Truevision/RoadStyles/Freeway.roadstyle" +} \ No newline at end of file diff --git a/default-project/RoadStyles/FreewayOneWay.roadstyle b/default-project/RoadStyles/FreewayOneWay.roadstyle new file mode 100644 index 00000000..18e48852 --- /dev/null +++ b/default-project/RoadStyles/FreewayOneWay.roadstyle @@ -0,0 +1 @@ +{"version":1,"laneOffset":{"attr_s":0,"attr_a":0,"attr_b":0,"attr_c":0,"attr_d":0},"laneSection":{"attr_s":0,"center":{"lane":[{"attr_id":0,"attr_type":"none","attr_level":true,"width":[],"roadMark":[{"attr_sOffset":0,"attr_type":"none","attr_weight":"standard","attr_color":"standard","attr_width":0.15,"attr_laneChange":"none","attr_height":0}]}]},"right":{"lane":[{"attr_id":-1,"attr_type":"shoulder","attr_level":true,"width":[{"attr_sOffset":0,"attr_a":3.6,"attr_b":0,"attr_c":0,"attr_d":0}],"roadMark":[{"attr_sOffset":0,"attr_type":"solid","attr_weight":"standard","attr_color":"yellow","attr_width":0.15,"attr_laneChange":"none","attr_height":0}]},{"attr_id":-2,"attr_type":"driving","attr_level":true,"width":[{"attr_sOffset":0,"attr_a":3.6,"attr_b":0,"attr_c":0,"attr_d":0}],"roadMark":[{"attr_sOffset":0,"attr_type":"broken","attr_weight":"standard","attr_color":"standard","attr_width":0.15,"attr_laneChange":"none","attr_height":0}]},{"attr_id":-3,"attr_type":"driving","attr_level":true,"width":[{"attr_sOffset":0,"attr_a":3.6,"attr_b":0,"attr_c":0,"attr_d":0}],"roadMark":[{"attr_sOffset":0,"attr_type":"broken","attr_weight":"standard","attr_color":"standard","attr_width":0.15,"attr_laneChange":"none","attr_height":0}]},{"attr_id":-4,"attr_type":"driving","attr_level":true,"width":[{"attr_sOffset":0,"attr_a":3.6,"attr_b":0,"attr_c":0,"attr_d":0}],"roadMark":[{"attr_sOffset":0,"attr_type":"broken","attr_weight":"standard","attr_color":"standard","attr_width":0.15,"attr_laneChange":"none","attr_height":0}]},{"attr_id":-5,"attr_type":"driving","attr_level":true,"width":[{"attr_sOffset":0,"attr_a":3.6,"attr_b":0,"attr_c":0,"attr_d":0}],"roadMark":[{"attr_sOffset":0,"attr_type":"solid","attr_weight":"standard","attr_color":"standard","attr_width":0.15,"attr_laneChange":"none","attr_height":0}]},{"attr_id":-6,"attr_type":"shoulder","attr_level":true,"width":[{"attr_sOffset":0,"attr_a":3.6,"attr_b":0,"attr_c":0,"attr_d":0}],"roadMark":[]}]}}} \ No newline at end of file diff --git a/default-project/RoadStyles/FreewayOneWay.roadstyle.meta b/default-project/RoadStyles/FreewayOneWay.roadstyle.meta new file mode 100644 index 00000000..2ef7e6a6 --- /dev/null +++ b/default-project/RoadStyles/FreewayOneWay.roadstyle.meta @@ -0,0 +1,6 @@ +{ + "guid": "4EC98151-FD5E-4541-9C67-D11DED22B22A", + "importer": "RoadStyleImporter", + "data": {}, + "path": "/home/himanshu/Documents/Truevision/RoadStyles/FreewayOneWay.roadstyle" +} \ No newline at end of file diff --git a/default-project/RoadStyles/Highway.roadstyle b/default-project/RoadStyles/Highway.roadstyle new file mode 100644 index 00000000..a2a12a9a --- /dev/null +++ b/default-project/RoadStyles/Highway.roadstyle @@ -0,0 +1 @@ +{"version":1,"laneOffset":{"attr_s":0,"attr_a":0,"attr_b":0,"attr_c":0,"attr_d":0},"laneSection":{"attr_s":0,"left":{"lane":[{"attr_id":2,"attr_type":"shoulder","attr_level":true,"width":[{"attr_sOffset":0,"attr_a":0.5,"attr_b":0,"attr_c":0,"attr_d":0}],"roadMark":[]},{"attr_id":1,"attr_type":"driving","attr_level":true,"width":[{"attr_sOffset":0,"attr_a":3.6,"attr_b":0,"attr_c":0,"attr_d":0}],"roadMark":[{"attr_sOffset":0,"attr_type":"solid","attr_weight":"standard","attr_color":"standard","attr_width":0.15,"attr_laneChange":"none","attr_height":0}]}]},"center":{"lane":[{"attr_id":0,"attr_type":"driving","attr_level":true,"width":[],"roadMark":[{"attr_sOffset":0,"attr_type":"solid solid","attr_weight":"standard","attr_color":"yellow","attr_width":0.15,"attr_laneChange":"none","attr_height":0}]}]},"right":{"lane":[{"attr_id":-1,"attr_type":"driving","attr_level":true,"width":[{"attr_sOffset":0,"attr_a":3.6,"attr_b":0,"attr_c":0,"attr_d":0}],"roadMark":[{"attr_sOffset":0,"attr_type":"solid","attr_weight":"standard","attr_color":"standard","attr_width":0.15,"attr_laneChange":"none","attr_height":0}]},{"attr_id":-2,"attr_type":"shoulder","attr_level":true,"width":[{"attr_sOffset":0,"attr_a":0.5,"attr_b":0,"attr_c":0,"attr_d":0}],"roadMark":[]}]}}} \ No newline at end of file diff --git a/default-project/RoadStyles/Highway.roadstyle.meta b/default-project/RoadStyles/Highway.roadstyle.meta new file mode 100644 index 00000000..4d7aa753 --- /dev/null +++ b/default-project/RoadStyles/Highway.roadstyle.meta @@ -0,0 +1,6 @@ +{ + "guid": "6B579982-9DDB-4566-8346-3FE687C0C5C4", + "importer": "RoadStyleImporter", + "data": {}, + "path": "/home/himanshu/Documents/Truevision/RoadStyles/Highway.roadstyle" +} \ No newline at end of file diff --git a/default-project/RoadStyles/HighwayPassing.roadstyle b/default-project/RoadStyles/HighwayPassing.roadstyle new file mode 100644 index 00000000..508c4f2f --- /dev/null +++ b/default-project/RoadStyles/HighwayPassing.roadstyle @@ -0,0 +1 @@ +{"version":1,"laneOffset":{"attr_s":0,"attr_a":0,"attr_b":0,"attr_c":0,"attr_d":0},"laneSection":{"attr_s":0,"left":{"lane":[{"attr_id":2,"attr_type":"shoulder","attr_level":true,"width":[{"attr_sOffset":0,"attr_a":0.5,"attr_b":0,"attr_c":0,"attr_d":0}],"roadMark":[]},{"attr_id":1,"attr_type":"driving","attr_level":true,"width":[{"attr_sOffset":0,"attr_a":3.6,"attr_b":0,"attr_c":0,"attr_d":0}],"roadMark":[{"attr_sOffset":0,"attr_type":"solid","attr_weight":"standard","attr_color":"standard","attr_width":0.15,"attr_laneChange":"none","attr_height":0}]}]},"center":{"lane":[{"attr_id":0,"attr_type":"driving","attr_level":true,"width":[],"roadMark":[{"attr_sOffset":0,"attr_type":"broken","attr_weight":"standard","attr_color":"yellow","attr_width":0.15,"attr_laneChange":"none","attr_height":0}]}]},"right":{"lane":[{"attr_id":-1,"attr_type":"driving","attr_level":true,"width":[{"attr_sOffset":0,"attr_a":3.6,"attr_b":0,"attr_c":0,"attr_d":0}],"roadMark":[{"attr_sOffset":0,"attr_type":"solid","attr_weight":"standard","attr_color":"standard","attr_width":0.15,"attr_laneChange":"none","attr_height":0}]},{"attr_id":-2,"attr_type":"shoulder","attr_level":true,"width":[{"attr_sOffset":0,"attr_a":0.5,"attr_b":0,"attr_c":0,"attr_d":0}],"roadMark":[]}]}}} \ No newline at end of file diff --git a/default-project/RoadStyles/HighwayPassing.roadstyle.meta b/default-project/RoadStyles/HighwayPassing.roadstyle.meta new file mode 100644 index 00000000..2369d7b3 --- /dev/null +++ b/default-project/RoadStyles/HighwayPassing.roadstyle.meta @@ -0,0 +1,6 @@ +{ + "guid": "717A351A-EC6B-4928-B8CC-932E599B8DAA", + "importer": "RoadStyleImporter", + "data": {}, + "path": "/home/himanshu/Documents/Truevision/RoadStyles/HighwayPassing.roadstyle" +} \ No newline at end of file diff --git a/default-project/RoadStyles/OneWay.roadstyle b/default-project/RoadStyles/OneWay.roadstyle new file mode 100644 index 00000000..7314c9df --- /dev/null +++ b/default-project/RoadStyles/OneWay.roadstyle @@ -0,0 +1 @@ +{"version":1,"laneOffset":{"attr_s":0,"attr_a":0,"attr_b":0,"attr_c":0,"attr_d":0},"laneSection":{"attr_s":0,"left":{"lane":[{"attr_id":1,"attr_type":"sidewalk","attr_level":true,"width":[{"attr_sOffset":0,"attr_a":2,"attr_b":0,"attr_c":0,"attr_d":0}],"roadMark":[]}]},"center":{"lane":[{"attr_id":0,"attr_type":"none","attr_level":true,"width":[],"roadMark":[]}]},"right":{"lane":[{"attr_id":-1,"attr_type":"driving","attr_level":true,"width":[{"attr_sOffset":0,"attr_a":3.6,"attr_b":0,"attr_c":0,"attr_d":0}],"roadMark":[]},{"attr_id":-2,"attr_type":"sidewalk","attr_level":true,"width":[{"attr_sOffset":0,"attr_a":2,"attr_b":0,"attr_c":0,"attr_d":0}],"roadMark":[]}]}}} \ No newline at end of file diff --git a/default-project/RoadStyles/OneWay.roadstyle.meta b/default-project/RoadStyles/OneWay.roadstyle.meta new file mode 100644 index 00000000..0fc27349 --- /dev/null +++ b/default-project/RoadStyles/OneWay.roadstyle.meta @@ -0,0 +1,6 @@ +{ + "guid": "620C96B5-1F19-4F90-B7C1-BD9EF5FD69F5", + "importer": "RoadStyleImporter", + "data": {}, + "path": "/home/himanshu/Documents/Truevision/RoadStyles/OneWay.roadstyle" +} \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 00000000..586aad10 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,21 @@ +version: '2.0' + +services: + app: + container_name: truevision_designer + build: + context: . + dockerfile: Dockerfile + ports: + - '4200:4200' + - '49153:49153' + environment: + - NODE_ENV=production + working_dir: /app + volumes: + - ./:/app + - /app/node_modules +#Volumes +volumes: + dbdata: + driver: local \ No newline at end of file diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 00000000..606966e4 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,7 @@ +# Truevision Designer Documentation + +## Contribution Guidelines + +If you are submitting a pull-request for documentation, please submit it to the corresponding branch. For example, documentation for version 1.0 should be submitted to the 1.0 branch. Documentation intended for the next release of Simulator should be submitted to the master branch. + +You can also create a github issue in case you find any bugs or errors in the documentation. diff --git a/docs/documentation.md b/docs/documentation.md new file mode 100644 index 00000000..fc23df46 --- /dev/null +++ b/docs/documentation.md @@ -0,0 +1,10 @@ +- ## Getting Started + - [User Guide](/designer/docs/{{version}}/user-guide) + - [Installation](/designer/docs/{{version}}/installation) + - [Overview](/designer/docs/{{version}}/overview) +- ## Tools + - [Introduction](/designer/docs/{{version}}/tools) + - [Road Tool](/designer/docs/{{version}}/road-tool) + - [Lane Width Tool](/designer/docs/{{version}}/lane-width-tool) +- ## OpenDrive + - [Introduction](/designer/docs/{{version}}/open-drive) diff --git a/docs/img/welcome.jpg b/docs/img/welcome.jpg new file mode 100644 index 00000000..56fefc12 Binary files /dev/null and b/docs/img/welcome.jpg differ diff --git a/docs/installation.md b/docs/installation.md new file mode 100644 index 00000000..d5f477da --- /dev/null +++ b/docs/installation.md @@ -0,0 +1,51 @@ +# Getting Started: Installation + +- [Installation](#installation) + - [System Requirements](#system-requirements) +- [Installing On Ubuntu](#installing-on-ubuntu) +- [Launching](#launching) + - [Sign In](#sign-in) + + +## Installation + + +### System Requirements + +Truevision Simulator has a few system requirements. + +- i7 Processor or above +- Alteast 16GB RAM +- GTX 1040 GPU or above +- ~4GB of Disk space +- Active Internet Connection while running simulator + + + +## Installing On Ubuntu + +Follow the simple steps mentioned below + +- Download the latest .deb file from https://www.truevision.ai/download +- Double click the .deb file which should open the installation screen +- Click `Install` to proceed. You should now have Truevision installed on your system + +You can also download via command line + +- Open Terminal +- cd into folder that contains the .deb file +- Run the below command `sudo dpkg -i truevision-simulator-latest.deb` + + +## Launching Truevision + +After installing, simply search for `Truevision` on search and find the application + + + +### Sign-In + +Simple sign-in with your credentials and you will be able to proceed. + + + diff --git a/docs/lane-add-tool.md b/docs/lane-add-tool.md new file mode 100644 index 00000000..684411dd --- /dev/null +++ b/docs/lane-add-tool.md @@ -0,0 +1,14 @@ +# Lane Add Tool + +- [Overview](#overview) +- [Add Lane](#add-lane) + + + +## Overview + +This tool helps you add new lanes on a road. + + +## Add Lane + diff --git a/docs/lane-width-tool.md b/docs/lane-width-tool.md new file mode 100644 index 00000000..0e5925d7 --- /dev/null +++ b/docs/lane-width-tool.md @@ -0,0 +1,21 @@ +# Lane Width Tool + +- [Overview](#overview) +- [Create Width Node](#create-width-node) + + + +## Overview + +This tool helps you modify the width of a lane on the road. A single road lane can contain multiple nodes to control the width of the lane. + + +## Create Width Node + +- Select Lane Width Tool from toolbar using `left click` +- Select Road you want to modify using `left click` +- Selecting/Unselecting a road will show/hide the existing lane width nodes +- Take pointer over a lane and use `shift + left click` to create new node +- When the node is selected you will the node info in the inspector, modify the width value as desired + + \ No newline at end of file diff --git a/docs/open-drive.md b/docs/open-drive.md new file mode 100644 index 00000000..2d336b36 --- /dev/null +++ b/docs/open-drive.md @@ -0,0 +1,76 @@ +# OpenDRIVE: Introduction + +- [Introduction](#getting-started) + - [What is OpenDRIVE?](#open-drive) + - [Hello Road](#hello-road) +- [Using Designer](#using-designer) + + + +## What is OpenDRIVE +Truevision provides support for importing & describing road networks via OpenDRIVE. OpenDRIVE® files are designed to describe entire road networks with respect to all data belonging to the road environment. They do not describe the entities acting on or interacting with the road. + + +The OpenDRIVE file format provides the following features: +- XML format +- hierarchical structure +- analytical definition of road geometry (plane elements, lateral / vertical profile, lane width etc.) +- various types of lanes +- junctions and junction groups +- logical inter-connection of lanes +- signs and signals incl. dependencies +- signal controllers (e.g. for junctions) +- road and road-side objects +- user-defineable data beads +- and more + +### Road Layout + +The following figure depicts the principles of road layout: + + + +> Note: Support for OpenDRIVE version 1.4 & above is provided. Some of the OpenDRIVE tags are not supported. + + +## Hello Road + +In the section, we'll look to create our very first simple road using OpenDRIVE. You can create OpenDRIVE in two ways +- Using Truevision ScenarioDesigner +- Manually writing or modifying the XML + + +The ScenarioDesigner is a much easier way to design roads as you'll not need to know anythign about OpenDRIVE and you can just visually create road by clicking and changing and few properties etc. + +Manually writing the XML file means you'll need to understand all the tags and element in OpenDRIVE, which can be quite a long excercise depending on how deeply you want to understand the format. Instead of covering everything, we'll be covering some of the most important elements which will improve your speed to iterate without spending too much time. + + +## Using Designer + +Follow the screenshots below to create a simple road with muliple lanes. + +Open Designer + + +Create Road + + +Add Lanes + + +Modify Lane Type + + +Modify Lane Width + + +Add Roadmarks + + +Modify Roadmarks + + +Export File + + +Now, you can just import the file in the simulator with [import command](import-commands.md#import-open-drive) \ No newline at end of file diff --git a/docs/overview.md b/docs/overview.md new file mode 100644 index 00000000..d14d29c0 --- /dev/null +++ b/docs/overview.md @@ -0,0 +1,46 @@ +# Overview + +- [Overview](#overview) + - [Coordinate System](#coordinate-system) + - [Mouse Interaction](#mouse) + - [2D Navigation](#2d-navigation) + - [3D Navigation](#3d-navigation) + + +## Overview + +This section explains how to use the Road Editor to create different kinds of road geometries. + + +## Coordinate System + +- x+ forward +- y+ left +- z+ up + + + + + + +## Mouse Interation + +- `left-click` is used to select objects for inspecting +- `shift + left-click` is used to add objects or controls points or some other kind of action +- `right-click` is used for either context-menu + + + +## 2D Nagivation + +- `right-click` and drag to move the scene around +- `middle-click` and move to zoom-in and out +- `mouse-scroll` for zooming-in and out + + +## 3D Nagivation + +- `left-click` and drag to panning the camera +- `right-click` and drag to move the scene around +- `middle-click` and move to zoom-in and out +- `mouse-scroll` for zooming-in and out diff --git a/docs/road-tool.md b/docs/road-tool.md new file mode 100644 index 00000000..776de787 --- /dev/null +++ b/docs/road-tool.md @@ -0,0 +1,34 @@ +# Road Tool + +- [Overview](#overview) +- [Create Roads](#create-roads) +- [Select Road](#select-road) +- [Connect Roads](#connect-roads) + + + +## Overview + +Road tool helps you create different types of road geometries and shapes to fit you needs. + + +## Create Roads + +Use `shift + left click` to create control points. You can then drag the control points to change the shape of the road. + +If you want to create a new road then first unselect the previous road by `left click` and then `shift + left click` again to start creating a new road. + + + +## Select Road + +You can select a road for editing and modification by taking the pointer above the road and using `left click` + + +## Connect Roads + +Two roads can be connected using road nodes. +- Select a node from road A using `left click` +- Select another node from road B using `left click` +- Two road will now be connect through a new road C which also be modified if required + diff --git a/docs/tools.md b/docs/tools.md new file mode 100644 index 00000000..cb849413 --- /dev/null +++ b/docs/tools.md @@ -0,0 +1,98 @@ +# Road Editor + +- [Overview](#overview) + - [Coordinate System](#coordinate-system) + - [Mouse Interaction](#mouse) + - [2D Navigation](#2d-navigation) + - [3D Navigation](#3d-navigation) +- [Toolbar](#toolbars) + - [RoadGeometryTool](#road-geometry-tool) + - [LineGeometryTool](#line-geometry-tool) + - [ArcGeometryTool](#arc-geometry-tool) + - [LaneWidthTool](#lane-width-tool) + - [AddLaneTool](#add-lane-tool) + - [LaneMarkingTool](#lane-marking-tool) + - [LaneTool](#lane-tool) + - [RoadSignTool](#road-sign-tool) + - [VehicleTool](#vehicle-tool) + + + +## Overview + +{{product}} provides a number of different tools for displaying and modifying different aspects of road and environment. + + +## Coordinate System + +- x+ forward +- y+ left +- z+ up + + + + + + +## Mouse Interation + +- `left-click` is used to select objects for inspecting +- `shift + left-click` is used to add objects or controls points or some other kind of action +- `right-click` is used for either context-menu + + + +## 2D Nagivation + +- `right-click` and drag to move the scene around +- `middle-click` and move to zoom-in and out +- `mouse-scroll` for zooming-in and out + + +## 3D Nagivation + +- `left-click` and drag to panning the camera +- `right-click` and drag to move the scene around +- `middle-click` and move to zoom-in and out +- `mouse-scroll` for zooming-in and out + + +## Toolbar + + + +## Road Geometry Tool + +[![Watch the video](https://i9.ytimg.com/vi/KiL_G9_chhA/mq2.jpg?sqp=CNfKqewF&rs=AOn4CLBclmEXkWT-sgw89-HmIl7lnBsERw)](https://youtu.be/KiL_G9_chhA) + + + +## Lane Width Tool + +Use `shift` + `left-click` to select a lane you want to duplicate. This tool will basically add a new lane next to the selected lane. + + +[![Watch the video](https://i9.ytimg.com/vi/acloOOuakuk/mq2.jpg?sqp=CLLVqewF&rs=AOn4CLCZwQoJsxmSTWxorV_h_Q-OgL3pdA)](https://youtu.be/acloOOuakuk) + + + +## Lane Add Tool + +[![Watch the video](https://i9.ytimg.com/vi/Rnp0rGHCy2I/mq2.jpg?sqp=CJTWqewF&rs=AOn4CLCz5_MS7gHCl3fFI3vqinK83CVltw)](https://youtu.be/Rnp0rGHCy2I) + + + +## Lane Marking Tool + +[![Watch the video](https://i9.ytimg.com/vi/hCV1P3ZviVk/mq2.jpg?sqp=CKHZqewF&rs=AOn4CLDqc_afrOaQpUq9aaPR0jyG2m5bCg)](https://youtu.be/hCV1P3ZviVk) + + + +## Lane Tool + +[![Watch the video](https://i9.ytimg.com/vi/bw_6MQwBY38/mq2.jpg?sqp=CJDaqewF&rs=AOn4CLDRPdWx2wXgGAloaWhMurJGWzFv6w)](https://youtu.be/bw_6MQwBY38) + + +## Road Sign Tool + +[![Watch the video](https://i9.ytimg.com/vi/bzGq73cKlNM/mq2.jpg?sqp=CNjaqewF&rs=AOn4CLDG7cJWKcop_eZ2mO6jvlo4lwaqlw)](https://youtu.be/bzGq73cKlNM) diff --git a/docs/user-guide.md b/docs/user-guide.md new file mode 100644 index 00000000..b48eb8ba --- /dev/null +++ b/docs/user-guide.md @@ -0,0 +1,8 @@ +# Welcome User Guide {{version}} + + +## Truevision Designer User Guide {{version}} + +Use this guide to learn how Truevision Designer can help you preview, organize, edit and publish multiple road and environement quickly and easily. + +This Guide helps you learn how to use the Truevision Designer and its associated services. You can read it from start to finish, or use it as a reference. \ No newline at end of file diff --git a/electron-builder.json b/electron-builder.json new file mode 100644 index 00000000..ae420849 --- /dev/null +++ b/electron-builder.json @@ -0,0 +1,45 @@ +{ + "productName": "TruevisionDesigner", + "directories": { + "output": "release/", + "buildResources": "build/" + }, + "files": [ + "**/*", + "!**/*.ts", + "!*.code-workspace", + "!LICENSE.md", + "!buildspec.yml", + "!package.json", + "!package-lock.json", + "!python-client/", + "!scenario/", + "!scripts/", + "!samples/", + "!src/", + "!e2e/", + "!hooks/", + "!angular.json", + "!_config.yml", + "!karma.conf.js", + "!tsconfig.json", + "!tslint.json", + "!third-party", + "!docs", + "!.vscode", + "!.idea", + "!.protos" + ], + "extraResources": [ + "bin", + "default-project" + ], + "extraFiles": [], + "linux": { + "category": "Utility", + "target": [ + "deb", + "dir" + ] + } +} \ No newline at end of file diff --git a/main.js b/main.js new file mode 100644 index 00000000..2937f6e4 --- /dev/null +++ b/main.js @@ -0,0 +1,165 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +// Modules to control application life and create native browser window +const { app, BrowserWindow, ipcMain, Menu } = require( 'electron' ); +const execFile = require( 'child_process' ).execFile; +const path = require( 'path' ); + +// Keep a global reference of the window object, if you don't, the window will +// be closed automatically when the JavaScript object is garbage collected. +let editorWindow; + +const TITLE = "Truevision" +const MIN_WIDTH = 1280; +const MIN_HEIGHT = 980; + +let openDevTools = false; +let showSplashScreen = true; +let loadLocahost = false; + +// load opengl to fix line rendering issues +app.commandLine.appendSwitch( "use-angle", "gl" ); + +process.argv.forEach( function ( arg, index, array ) { + + if ( arg.includes( "open-dev-tools" ) ) { + openDevTools = true; + } else if ( arg.includes( "disable-splash" ) ) { + showSplashScreen = false; + } else if ( arg.includes( "localhost" ) ) { + loadLocahost = true; + } + +} ); + +function openEditorWindow () { + + // Create the browser window. + editorWindow = new BrowserWindow( { + title: TITLE, + width: MIN_WIDTH, + height: MIN_HEIGHT, + minWidth: MIN_WIDTH, + minHeight: MIN_HEIGHT, + backgroundColor: '#ffffff', + icon: `file://${ __dirname }/dist/assets/icon.png`, + webPreferences: { + nodeIntegration: true + } + } ); + + if ( loadLocahost ) { + + editorWindow.loadURL( `http://localhost:4200` ); + + } else { + + editorWindow.loadURL( `file://${ __dirname }/dist/index.html` ); + + } + + // Open the DevTools. + if ( openDevTools ) editorWindow.webContents.openDevTools(); + + // Emitted when the window is closed. + editorWindow.on( 'closed', function () { + // Dereference the window object, usually you would store windows + // in an array if your app supports multi windows, this is the time + // when you should delete the corresponding element. + editorWindow = null + } ) +} + +function openSplashWindow () { + + // Create the browser window. + let splashWindow = new BrowserWindow( { + title: TITLE, + width: 720, + height: 405, + backgroundColor: '#ffffff', + resizable: false, + movable: false, + minimizable: false, + maximizable: false, + closable: true, + alwaysOnTop: true, + fullscreenable: false, + frame: false, + icon: `file://${ __dirname }/dist/assets/icon.png`, + webPreferences: { + nodeIntegration: true + } + } ); + + splashWindow.loadURL( `file://${ __dirname }/src/splash.html` ); + + // Open the DevTools. + if ( openDevTools ) splashWindow.webContents.openDevTools(); + + // Emitted when the window is closed. + splashWindow.on( 'closed', function () { + // Dereference the window object, usually you would store windows + // in an array if your app supports multi windows, this is the time + // when you should delete the corresponding element. + splashWindow = null; + } ); + + + setTimeout( () => { + + openEditorWindow(); + + splashWindow.close(); + + }, 5000 ); +} + +function createWindow () { + + if ( showSplashScreen == true ) { + + openSplashWindow(); + + } else { + + openEditorWindow(); + + } + +} + +// This method will be called when Electron has finished +// initialization and is ready to create browser windows. +// Some APIs can only be used after this event occurs. +app.on( 'ready', createWindow ) + +// app.commandLine.appendSwitch( 'remote-debugging-port', '9222' ) + +// Quit when all windows are closed. +app.on( 'window-all-closed', function () { + // On macOS it is common for applications and their menu bar + // to stay active until the user quits explicitly with Cmd + Q + if ( process.platform !== 'darwin' ) { + app.quit() + } +} ); + +app.on( 'activate', function () { + // On macOS it's common to re-create a window in the app when the + // dock icon is clicked and there are no other windows open. + if ( editorWindow === null ) { + createWindow() + } +} ); + +// helpers method to read current directory in angular +ipcMain.on( 'current-directory', ( event, arg ) => { + + event.returnValue = __dirname; + +} ); + +Menu.setApplicationMenu( null ); diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 00000000..05905e5b --- /dev/null +++ b/package-lock.json @@ -0,0 +1,14472 @@ +{ + "name": "truevision-designer", + "version": "2022.3.01", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "7zip-bin": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/7zip-bin/-/7zip-bin-5.0.3.tgz", + "integrity": "sha512-GLyWIFBbGvpKPGo55JyRZAo4lVbnBiD52cKlw/0Vt+wnmKvWJkpZvsjVoaIolyBXDeAQKSicRtqFNPem9w0WYA==", + "dev": true + }, + "7zip-bin-linux": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/7zip-bin-linux/-/7zip-bin-linux-1.3.1.tgz", + "integrity": "sha512-Wv1uEEeHbTiS1+ycpwUxYNuIcyohU6Y6vEqY3NquBkeqy0YhVdsNUGsj0XKSRciHR6LoJSEUuqYUexmws3zH7Q==", + "dev": true, + "optional": true + }, + "7zip-bin-mac": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/7zip-bin-mac/-/7zip-bin-mac-1.0.1.tgz", + "integrity": "sha1-Pmh3i78JJq3GgVlCcHRQXUdVXAI=", + "dev": true, + "optional": true + }, + "7zip-bin-win": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/7zip-bin-win/-/7zip-bin-win-2.1.1.tgz", + "integrity": "sha512-6VGEW7PXGroTsoI2QW3b0ea95HJmbVBHvfANKLLMzSzFA1zKqVX5ybNuhmeGpf6vA0x8FJTt6twpprDANsY5WQ==", + "dev": true, + "optional": true + }, + "@agm/core": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/@agm/core/-/core-1.0.0-beta.2.tgz", + "integrity": "sha512-3bdfvkWDmJszpj/F6Fzgv7sks0cs/cUEQPfs37tcJFz3jc62SsXy4TGb/WJT8FpH2nSGE6DonP8lXuFxB0lblQ==" + }, + "@angular-devkit/architect": { + "version": "0.13.3", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.13.3.tgz", + "integrity": "sha512-89VL75bq3+h3m0jhzWNqXqW+HQcrihnM3i6eiUE6P81LcllP159JMlusAvB1LHLNc6Cc62wTq4BJr7KDILkPOA==", + "dev": true, + "requires": { + "@angular-devkit/core": "7.3.3", + "rxjs": "6.3.3" + }, + "dependencies": { + "@angular-devkit/core": { + "version": "7.3.3", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-7.3.3.tgz", + "integrity": "sha512-fosULDtMoDWrOyUzTmBkJccOy7zodo02kENyKai7vOv9EWfv9jytkVdNc+jl0ys9OE2QadvSYBo49jhnZxFXfQ==", + "dev": true, + "requires": { + "ajv": "6.9.1", + "chokidar": "2.0.4", + "fast-json-stable-stringify": "2.0.0", + "rxjs": "6.3.3", + "source-map": "0.7.3" + } + } + } + }, + "@angular-devkit/build-angular": { + "version": "0.13.3", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-0.13.3.tgz", + "integrity": "sha512-UxD6UR/tXypMA4lqCiXLtcStI4wuIHLOJLwADmazndFjg1oLqH1onO6UQPHJ1drAUl+AzA5zTQZHzWYokxaLtg==", + "dev": true, + "requires": { + "@angular-devkit/architect": "0.13.3", + "@angular-devkit/build-optimizer": "0.13.3", + "@angular-devkit/build-webpack": "0.13.3", + "@angular-devkit/core": "7.3.3", + "@ngtools/webpack": "7.3.3", + "ajv": "6.9.1", + "autoprefixer": "9.4.6", + "circular-dependency-plugin": "5.0.2", + "clean-css": "4.2.1", + "copy-webpack-plugin": "4.6.0", + "file-loader": "3.0.1", + "glob": "7.1.3", + "istanbul-instrumenter-loader": "3.0.1", + "karma-source-map-support": "1.3.0", + "less": "3.9.0", + "less-loader": "4.1.0", + "license-webpack-plugin": "2.1.0", + "loader-utils": "1.2.3", + "mini-css-extract-plugin": "0.5.0", + "minimatch": "3.0.4", + "node-sass": "4.11.0", + "opn": "5.4.0", + "parse5": "4.0.0", + "postcss": "7.0.14", + "postcss-import": "12.0.1", + "postcss-loader": "3.0.0", + "raw-loader": "1.0.0", + "rxjs": "6.3.3", + "sass-loader": "7.1.0", + "semver": "5.6.0", + "source-map-loader": "0.2.4", + "source-map-support": "0.5.10", + "speed-measure-webpack-plugin": "1.3.0", + "stats-webpack-plugin": "0.7.0", + "style-loader": "0.23.1", + "stylus": "0.54.5", + "stylus-loader": "3.0.2", + "terser-webpack-plugin": "1.2.2", + "tree-kill": "1.2.1", + "webpack": "4.29.0", + "webpack-dev-middleware": "3.5.1", + "webpack-dev-server": "3.1.14", + "webpack-merge": "4.2.1", + "webpack-sources": "1.3.0", + "webpack-subresource-integrity": "1.1.0-rc.6" + }, + "dependencies": { + "@angular-devkit/core": { + "version": "7.3.3", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-7.3.3.tgz", + "integrity": "sha512-fosULDtMoDWrOyUzTmBkJccOy7zodo02kENyKai7vOv9EWfv9jytkVdNc+jl0ys9OE2QadvSYBo49jhnZxFXfQ==", + "dev": true, + "requires": { + "ajv": "6.9.1", + "chokidar": "2.0.4", + "fast-json-stable-stringify": "2.0.0", + "rxjs": "6.3.3", + "source-map": "0.7.3" + } + }, + "bluebird": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", + "dev": true + }, + "copy-webpack-plugin": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-4.6.0.tgz", + "integrity": "sha512-Y+SQCF+0NoWQryez2zXn5J5knmr9z/9qSQt7fbL78u83rxmigOy8X5+BFn8CFSuX+nKT8gpYwJX68ekqtQt6ZA==", + "dev": true, + "requires": { + "cacache": "^10.0.4", + "find-cache-dir": "^1.0.0", + "globby": "^7.1.1", + "is-glob": "^4.0.0", + "loader-utils": "^1.1.0", + "minimatch": "^3.0.4", + "p-limit": "^1.0.0", + "serialize-javascript": "^1.4.0" + } + }, + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "requires": { + "locate-path": "^3.0.0" + } + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "requires": { + "yallist": "^3.0.2" + } + }, + "make-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", + "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", + "dev": true, + "requires": { + "pify": "^4.0.1", + "semver": "^5.6.0" + } + }, + "mississippi": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mississippi/-/mississippi-3.0.0.tgz", + "integrity": "sha512-x471SsVjUtBRtcvd4BzKE9kFC+/2TeWgKCgw0bZcw1b9l2X3QX5vCWgF+KaZaYm87Ss//rHnWryupDrgLvmSkA==", + "dev": true, + "requires": { + "concat-stream": "^1.5.0", + "duplexify": "^3.4.2", + "end-of-stream": "^1.1.0", + "flush-write-stream": "^1.0.0", + "from2": "^2.1.0", + "parallel-transform": "^1.1.0", + "pump": "^3.0.0", + "pumpify": "^1.3.3", + "stream-each": "^1.1.0", + "through2": "^2.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "requires": { + "p-limit": "^2.0.0" + }, + "dependencies": { + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + } + } + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true + }, + "pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "dev": true + }, + "pkg-dir": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", + "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", + "dev": true, + "requires": { + "find-up": "^3.0.0" + } + }, + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "ssri": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.2.tgz", + "integrity": "sha512-cepbSq/neFK7xB6A50KHN0xHDotYzq58wWCa5LeWqnPrHG8GzfEjO/4O8kpmcGW+oaxkvhEJCWgbgNk4/ZV93Q==", + "dev": true, + "requires": { + "figgy-pudding": "^3.5.1" + } + }, + "terser-webpack-plugin": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-1.2.2.tgz", + "integrity": "sha512-1DMkTk286BzmfylAvLXwpJrI7dWa5BnFmscV/2dCr8+c56egFcbaeFAl7+sujAjdmpLam21XRdhA4oifLyiWWg==", + "dev": true, + "requires": { + "cacache": "^11.0.2", + "find-cache-dir": "^2.0.0", + "schema-utils": "^1.0.0", + "serialize-javascript": "^1.4.0", + "source-map": "^0.6.1", + "terser": "^3.16.1", + "webpack-sources": "^1.1.0", + "worker-farm": "^1.5.2" + }, + "dependencies": { + "cacache": { + "version": "11.3.3", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-11.3.3.tgz", + "integrity": "sha512-p8WcneCytvzPxhDvYp31PD039vi77I12W+/KfR9S8AZbaiARFBCpsPJS+9uhWfeBfeAtW7o/4vt3MUqLkbY6nA==", + "dev": true, + "requires": { + "bluebird": "^3.5.5", + "chownr": "^1.1.1", + "figgy-pudding": "^3.5.1", + "glob": "^7.1.4", + "graceful-fs": "^4.1.15", + "lru-cache": "^5.1.1", + "mississippi": "^3.0.0", + "mkdirp": "^0.5.1", + "move-concurrently": "^1.0.1", + "promise-inflight": "^1.0.1", + "rimraf": "^2.6.3", + "ssri": "^6.0.1", + "unique-filename": "^1.1.1", + "y18n": "^4.0.0" + } + }, + "find-cache-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.1.0.tgz", + "integrity": "sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==", + "dev": true, + "requires": { + "commondir": "^1.0.1", + "make-dir": "^2.0.0", + "pkg-dir": "^3.0.0" + } + }, + "glob": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", + "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true + } + } + }, + "@angular-devkit/build-optimizer": { + "version": "0.13.3", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-optimizer/-/build-optimizer-0.13.3.tgz", + "integrity": "sha512-lxM1icVFy3jyoQfWEGW8TG1M7LTl/Djc98MFBYp/lXoVo2JZoLxy7eo51sRuJFaB7/0mgMP2gs0FcU/Lr4gK+Q==", + "dev": true, + "requires": { + "loader-utils": "1.2.3", + "source-map": "0.5.6", + "typescript": "3.2.4", + "webpack-sources": "1.3.0" + }, + "dependencies": { + "source-map": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.6.tgz", + "integrity": "sha1-dc449SvwczxafwwRjYEzSiu19BI=", + "dev": true + } + } + }, + "@angular-devkit/build-webpack": { + "version": "0.13.3", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.13.3.tgz", + "integrity": "sha512-o2ymctVCuz5GhKJH3LO1sl3AUbA4j7zlrqSGB5ToVRBn3GckJJnmfCZzr2SX5Ya4VofxVsIidsiZcawy4FpB2w==", + "dev": true, + "requires": { + "@angular-devkit/architect": "0.13.3", + "@angular-devkit/core": "7.3.3", + "rxjs": "6.3.3" + }, + "dependencies": { + "@angular-devkit/core": { + "version": "7.3.3", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-7.3.3.tgz", + "integrity": "sha512-fosULDtMoDWrOyUzTmBkJccOy7zodo02kENyKai7vOv9EWfv9jytkVdNc+jl0ys9OE2QadvSYBo49jhnZxFXfQ==", + "dev": true, + "requires": { + "ajv": "6.9.1", + "chokidar": "2.0.4", + "fast-json-stable-stringify": "2.0.0", + "rxjs": "6.3.3", + "source-map": "0.7.3" + } + } + } + }, + "@angular-devkit/core": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-7.0.6.tgz", + "integrity": "sha512-RPSXUtLrpYDTqAEL0rCyDKxES76EomsPBvUUZTD6UkE2pihoh9ZIxkzhzlE+HU/xdqm28+smQYFhvvEAXFWwSQ==", + "dev": true, + "requires": { + "ajv": "6.5.3", + "chokidar": "2.0.4", + "fast-json-stable-stringify": "2.0.0", + "rxjs": "6.3.3", + "source-map": "0.7.3" + }, + "dependencies": { + "ajv": { + "version": "6.5.3", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.5.3.tgz", + "integrity": "sha512-LqZ9wY+fx3UMiiPd741yB2pj3hhil+hQc8taf4o2QGRFpWgZ2V5C8HA165DY9sS3fJwsk7uT7ZlFEyC3Ig3lLg==", + "dev": true, + "requires": { + "fast-deep-equal": "^2.0.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + } + } + }, + "@angular-devkit/schematics": { + "version": "7.3.3", + "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-7.3.3.tgz", + "integrity": "sha512-SdDq9eKwceb6WLwci1fywtZ/kARR5CYyzi5dZIR1lOxrz00682uUBqH/X39mKdqc6eVqR7rtPceqNm6nQpOIMg==", + "dev": true, + "requires": { + "@angular-devkit/core": "7.3.3", + "rxjs": "6.3.3" + }, + "dependencies": { + "@angular-devkit/core": { + "version": "7.3.3", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-7.3.3.tgz", + "integrity": "sha512-fosULDtMoDWrOyUzTmBkJccOy7zodo02kENyKai7vOv9EWfv9jytkVdNc+jl0ys9OE2QadvSYBo49jhnZxFXfQ==", + "dev": true, + "requires": { + "ajv": "6.9.1", + "chokidar": "2.0.4", + "fast-json-stable-stringify": "2.0.0", + "rxjs": "6.3.3", + "source-map": "0.7.3" + } + } + } + }, + "@angular/animations": { + "version": "7.2.6", + "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-7.2.6.tgz", + "integrity": "sha512-ICKPS+bKabhQNqnPoVZegUAhgNPbVFlrxHoJ+ZZeVGxw5iBE8TnP3a2sRvakdMTKhykDlwVVGMKLxu2Y34uhmg==", + "requires": { + "tslib": "^1.9.0" + } + }, + "@angular/cdk": { + "version": "7.3.3", + "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-7.3.3.tgz", + "integrity": "sha512-0M3nwq+c9+d+p0XeU12I9djutlsajRrxcu7AvHQXUs/5grYFsXsX0f468qXDiKmcgqGUBNtN2fBOUhGNlispuQ==", + "requires": { + "parse5": "^5.0.0", + "tslib": "^1.7.1" + }, + "dependencies": { + "parse5": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.0.tgz", + "integrity": "sha512-fxNG2sQjHvlVAYmzBZS9YlDp6PTSSDwa98vkD4QgVDDCAo84z5X1t5XyJQ62ImdLXx5NdIIfihey6xpum9/gRQ==", + "optional": true + } + } + }, + "@angular/cli": { + "version": "7.3.3", + "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-7.3.3.tgz", + "integrity": "sha512-dw1iBOYbQRN2l/BH21zDItDFC9KXgqeK0A/koDLDukjrUAnW/XVATjxGi+7EPlTpABTFhqu/rHZDy8aBglLDXQ==", + "dev": true, + "requires": { + "@angular-devkit/architect": "0.13.3", + "@angular-devkit/core": "7.3.3", + "@angular-devkit/schematics": "7.3.3", + "@schematics/angular": "7.3.3", + "@schematics/update": "0.13.3", + "@yarnpkg/lockfile": "1.1.0", + "ini": "1.3.5", + "inquirer": "6.2.1", + "npm-package-arg": "6.1.0", + "opn": "5.4.0", + "pacote": "9.4.0", + "semver": "5.6.0", + "symbol-observable": "1.2.0" + }, + "dependencies": { + "@angular-devkit/core": { + "version": "7.3.3", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-7.3.3.tgz", + "integrity": "sha512-fosULDtMoDWrOyUzTmBkJccOy7zodo02kENyKai7vOv9EWfv9jytkVdNc+jl0ys9OE2QadvSYBo49jhnZxFXfQ==", + "dev": true, + "requires": { + "ajv": "6.9.1", + "chokidar": "2.0.4", + "fast-json-stable-stringify": "2.0.0", + "rxjs": "6.3.3", + "source-map": "0.7.3" + } + }, + "ini": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", + "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==", + "dev": true + } + } + }, + "@angular/common": { + "version": "7.2.6", + "resolved": "https://registry.npmjs.org/@angular/common/-/common-7.2.6.tgz", + "integrity": "sha512-jzWUgsgS0dmPy7yDHX4qCqVpt7ZZmHhApgkg5RkzTAlp+0cvZ/KsDpBgHXnZUIfmk/5g1/EtTbkbClgp1kCkIg==", + "requires": { + "tslib": "^1.9.0" + } + }, + "@angular/compiler": { + "version": "7.2.6", + "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-7.2.6.tgz", + "integrity": "sha512-GXdvgH8oxK8HRh/FelN3U5p0tsTUwGh8b/iuuJKaunBSSDDjIy7pPnn3zT+lN4YeEi6qN1XWudt+HpWHYHyymg==", + "requires": { + "tslib": "^1.9.0" + } + }, + "@angular/compiler-cli": { + "version": "7.2.6", + "resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-7.2.6.tgz", + "integrity": "sha512-sB0Bc5hE1zoXKwK4E9pC/UblCEHi3CwLBT/1nmVMYBdGzSSUxO4FaioJi+SCeGJJ+kk85Vny2up08gnupmLKqA==", + "dev": true, + "requires": { + "canonical-path": "1.0.0", + "chokidar": "^2.1.1", + "convert-source-map": "^1.5.1", + "dependency-graph": "^0.7.2", + "magic-string": "^0.25.0", + "minimist": "^1.2.0", + "reflect-metadata": "^0.1.2", + "shelljs": "^0.8.1", + "source-map": "^0.6.1", + "tslib": "^1.9.0", + "yargs": "9.0.1" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "camelcase": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", + "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=", + "dev": true + }, + "chokidar": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.2.tgz", + "integrity": "sha512-IwXUx0FXc5ibYmPC2XeEj5mpXoV66sR+t3jqu2NS2GYwCktt3KF1/Qqjws/NkegajBA4RbZ5+DDwlOiJsxDHEg==", + "dev": true, + "requires": { + "anymatch": "^2.0.0", + "async-each": "^1.0.1", + "braces": "^2.3.2", + "fsevents": "^1.2.7", + "glob-parent": "^3.1.0", + "inherits": "^2.0.3", + "is-binary-path": "^1.0.0", + "is-glob": "^4.0.0", + "normalize-path": "^3.0.0", + "path-is-absolute": "^1.0.0", + "readdirp": "^2.2.1", + "upath": "^1.1.0" + } + }, + "cross-spawn": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", + "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", + "dev": true, + "requires": { + "lru-cache": "^4.0.1", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, + "execa": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-0.7.0.tgz", + "integrity": "sha1-lEvs00zEHuMqY6n68nrVpl/Fl3c=", + "dev": true, + "requires": { + "cross-spawn": "^5.0.1", + "get-stream": "^3.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + } + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "load-json-file": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz", + "integrity": "sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "parse-json": "^2.2.0", + "pify": "^2.0.0", + "strip-bom": "^3.0.0" + } + }, + "mem": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/mem/-/mem-1.1.0.tgz", + "integrity": "sha1-Xt1StIXKHZAP5kiVUFOZoN+kX3Y=", + "dev": true, + "requires": { + "mimic-fn": "^1.0.0" + } + }, + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true + }, + "os-locale": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-2.1.0.tgz", + "integrity": "sha512-3sslG3zJbEYcaC4YVAvDorjGxc7tv6KVATnLPZONiljsUncvihe9BQoVCEs0RZ1kmf4Hk9OBqlZfJZWI4GanKA==", + "dev": true, + "requires": { + "execa": "^0.7.0", + "lcid": "^1.0.0", + "mem": "^1.1.0" + } + }, + "path-type": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-2.0.0.tgz", + "integrity": "sha1-8BLMuEFbcJb8LaoQVMPXI4lZTHM=", + "dev": true, + "requires": { + "pify": "^2.0.0" + } + }, + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true + }, + "read-pkg": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz", + "integrity": "sha1-jvHAYjxqbbDcZxPEv6xGMysjaPg=", + "dev": true, + "requires": { + "load-json-file": "^2.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^2.0.0" + } + }, + "read-pkg-up": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-2.0.0.tgz", + "integrity": "sha1-a3KoBImE4MQeeVEP1en6mbO1Sb4=", + "dev": true, + "requires": { + "find-up": "^2.0.0", + "read-pkg": "^2.0.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + } + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + }, + "strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", + "dev": true + }, + "which-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", + "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", + "dev": true + }, + "y18n": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz", + "integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE=", + "dev": true + }, + "yargs": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-9.0.1.tgz", + "integrity": "sha1-UqzCP+7Kw0BCB47njAwAf1CF20w=", + "dev": true, + "requires": { + "camelcase": "^4.1.0", + "cliui": "^3.2.0", + "decamelize": "^1.1.1", + "get-caller-file": "^1.0.1", + "os-locale": "^2.0.0", + "read-pkg-up": "^2.0.0", + "require-directory": "^2.1.1", + "require-main-filename": "^1.0.1", + "set-blocking": "^2.0.0", + "string-width": "^2.0.0", + "which-module": "^2.0.0", + "y18n": "^3.2.1", + "yargs-parser": "^7.0.0" + } + }, + "yargs-parser": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-7.0.0.tgz", + "integrity": "sha1-jQrELxbqVd69MyyvTEA4s+P139k=", + "dev": true, + "requires": { + "camelcase": "^4.1.0" + } + } + } + }, + "@angular/core": { + "version": "7.2.6", + "resolved": "https://registry.npmjs.org/@angular/core/-/core-7.2.6.tgz", + "integrity": "sha512-MZg17DWH1KUoDa9wFYK9Z+3F7DnUW2DjSwGyIi9U4cB54IWFhgt1JsA0mcuSYuRSRpvwaArCDC2AN90f+0/EFA==", + "requires": { + "tslib": "^1.9.0" + } + }, + "@angular/flex-layout": { + "version": "7.0.0-beta.23", + "resolved": "https://registry.npmjs.org/@angular/flex-layout/-/flex-layout-7.0.0-beta.23.tgz", + "integrity": "sha512-jH2i3i/M7SbK6scVlj2urVL5OhzwavbQ7KwvUjyc/UwccKnnzuOuWEGCINLja/aoaUO3I35LluCLv6a6VN0olA==", + "requires": { + "tslib": "^1.7.1" + } + }, + "@angular/forms": { + "version": "7.2.10", + "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-7.2.10.tgz", + "integrity": "sha512-fQccon0Yuni13QJt16npSRlkitPZBLXfWXDFwCEybo/QqtSar3BOJAQFW2yqokrfW5lbO5VDFJ7Pj2dDyBXEAA==", + "requires": { + "tslib": "^1.9.0" + } + }, + "@angular/http": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@angular/http/-/http-7.2.0.tgz", + "integrity": "sha512-CoEwfh9LSpyXUh/077nvy1Hheo6mmIFKOmnyo9V7JaesGJYkYpEIVFYFP1hYg7f8snKHclT3gqfQX+BynNhExQ==", + "requires": { + "tslib": "^1.9.0" + } + }, + "@angular/language-service": { + "version": "7.2.6", + "resolved": "https://registry.npmjs.org/@angular/language-service/-/language-service-7.2.6.tgz", + "integrity": "sha512-iFKgaat5MZlixsO5dqy1Km3tb4q3iAU+ZPMJCk7DN419aizF38UFnQY1gCYkw3dxZLZGIkXnwy8szGXUEhra/A==", + "dev": true + }, + "@angular/material": { + "version": "7.3.3", + "resolved": "https://registry.npmjs.org/@angular/material/-/material-7.3.3.tgz", + "integrity": "sha512-DZdJaVpXsd5QlfpN5P871llw8AKh5QvRiyro3QRmEajYN85Xiawqpbt7O60TrwcFM6DzYLI3UeyWq8LFdmi/+Q==", + "requires": { + "tslib": "^1.7.1" + } + }, + "@angular/platform-browser": { + "version": "7.2.6", + "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-7.2.6.tgz", + "integrity": "sha512-VE4yS4l8Cdx6DlvrbOFOZDKmQuyz1RhVcshgSt9hKlkehvAXMtX8Sqnp6po7z0aPykTh0TZZtMtLEerkFEe+DA==", + "requires": { + "tslib": "^1.9.0" + } + }, + "@angular/platform-browser-dynamic": { + "version": "7.2.6", + "resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-7.2.6.tgz", + "integrity": "sha512-/co/q4v11nKin2MFscCMZyixbW103I2FxbPgCAYBN5NSvfIwTrt5J6xWmDoKJ8HkZBqL3R9B+uhYdzsRN/pQxg==", + "requires": { + "tslib": "^1.9.0" + } + }, + "@angular/router": { + "version": "7.2.6", + "resolved": "https://registry.npmjs.org/@angular/router/-/router-7.2.6.tgz", + "integrity": "sha512-ayMVor4Mu4wk7JKpt51UxHovnLB4munZ8ELR1CA4w+s0rJsSSwyB4WXElC+DbgCyl7BYLAaGui2c5DbTAJ9jlw==", + "requires": { + "tslib": "^1.9.0" + } + }, + "@auth0/angular-jwt": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@auth0/angular-jwt/-/angular-jwt-3.0.0.tgz", + "integrity": "sha512-Ky8hghnEx+CtCd097YXji08/LvLTG98IAEX/j1UgnutRDhQ31eczOohDn98v3i3MHNfLjfI3HdyxPK1Qc0IkZw==", + "requires": { + "url": "^0.11.0" + } + }, + "@babel/helper-environment-visitor": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.16.7.tgz", + "integrity": "sha512-SLLb0AAn6PkUeAfKJCCOl9e1R53pQlGAfc4y4XuMRZfqeMYLE0dM1LMhqbGAlGQY0lfw5/ohoYWAe9V1yibRag==", + "dev": true, + "requires": { + "@babel/types": "^7.16.7" + }, + "dependencies": { + "@babel/types": { + "version": "7.16.8", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.16.8.tgz", + "integrity": "sha512-smN2DQc5s4M7fntyjGtyIPbRJv6wW4rU/94fmYJ7PKQuZkC0qGMHXJbg6sNGt12JmVr4k5YaptI/XtiLJBnmIg==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.16.7", + "to-fast-properties": "^2.0.0" + } + }, + "to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", + "dev": true + } + } + }, + "@babel/helper-hoist-variables": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.16.7.tgz", + "integrity": "sha512-m04d/0Op34H5v7pbZw6pSKP7weA6lsMvfiIAMeIvkY/R4xQtBSMFEigu9QTZ2qB/9l22vsxtM8a+Q8CzD255fg==", + "dev": true, + "requires": { + "@babel/types": "^7.16.7" + }, + "dependencies": { + "@babel/types": { + "version": "7.16.8", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.16.8.tgz", + "integrity": "sha512-smN2DQc5s4M7fntyjGtyIPbRJv6wW4rU/94fmYJ7PKQuZkC0qGMHXJbg6sNGt12JmVr4k5YaptI/XtiLJBnmIg==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.16.7", + "to-fast-properties": "^2.0.0" + } + }, + "to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", + "dev": true + } + } + }, + "@babel/helper-validator-identifier": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz", + "integrity": "sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw==", + "dev": true + }, + "@ctrl/tinycolor": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/@ctrl/tinycolor/-/tinycolor-3.4.0.tgz", + "integrity": "sha512-JZButFdZ1+/xAfpguQHoabIXkcqRRKpMrWKBkpEZZyxfY9C1DpADFB8PEqGSTeFr135SaTRfKqGKx5xSCLI7ZQ==", + "dev": true + }, + "@develar/schema-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@develar/schema-utils/-/schema-utils-2.1.0.tgz", + "integrity": "sha512-qjCqB4ctMig9Gz5bd6lkdFr3bO6arOdQqptdBSpF1ZpCnjofieCciEzkoS9ujY9cMGyllYSCSmBJ3x9OKHXzoA==", + "dev": true, + "requires": { + "ajv": "^6.1.0", + "ajv-keywords": "^3.1.0" + } + }, + "@electron/get": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@electron/get/-/get-1.3.1.tgz", + "integrity": "sha512-FozkPRcRbHmuXh/qnry+s/GJ84hRAaUuEYYfZHvEl8n9X7a4ndhh1tVF+w4gky69XP+KHlLEO+OR95WqDy2mfg==", + "dev": true, + "requires": { + "debug": "^4.1.1", + "env-paths": "^2.2.0", + "fs-extra": "^7.0.1", + "got": "^9.6.0", + "sumchecker": "^3.0.0" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "env-paths": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.0.tgz", + "integrity": "sha512-6u0VYSCo/OW6IoD5WCLLy9JUGARbamfSavcNXry/eu8aHVFei6CD3Sw+VGX5alea1i9pgPHW0mbu6Xj0uBh7gA==", + "dev": true + }, + "fs-extra": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", + "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "sumchecker": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/sumchecker/-/sumchecker-3.0.0.tgz", + "integrity": "sha512-yreseuC/z4iaodVoq07XULEOO9p4jnQazO7mbrnDSvWAU/y2cbyIKs+gWJptfcGu9R+1l27K8Rkj0bfvqnBpgQ==", + "dev": true, + "requires": { + "debug": "^4.1.0" + } + } + } + }, + "@grpc/proto-loader": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.1.0.tgz", + "integrity": "sha512-GHoZBR5N+65AWazsCiJDxeMSbUpp2qNYbjeD3mP27L2PT25+GvIupIIJiCgULH87NAiAd9SmWMRBVz+uSiHpgA==", + "requires": { + "@types/lodash": "^4.14.104", + "@types/node": "^9.4.6", + "lodash": "^4.17.5", + "protobufjs": "^6.8.6" + }, + "dependencies": { + "@types/node": { + "version": "9.6.49", + "resolved": "https://registry.npmjs.org/@types/node/-/node-9.6.49.tgz", + "integrity": "sha512-YY0Okyn4QXC4ugJI+Kng5iWjK8A6eIHiQVaGIhJkyn0YL6Iqo0E0tBC8BuhvYcBK87vykBijM5FtMnCqaa5anA==" + }, + "long": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", + "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==" + }, + "protobufjs": { + "version": "6.8.8", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.8.8.tgz", + "integrity": "sha512-AAmHtD5pXgZfi7GMpllpO3q1Xw1OYldr+dMUlAnffGTAhqkg72WdmSY71uKBF/JuyiKs8psYbtKrhi0ASCD8qw==", + "requires": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/long": "^4.0.0", + "@types/node": "^10.1.0", + "long": "^4.0.0" + }, + "dependencies": { + "@types/node": { + "version": "10.14.9", + "resolved": "https://registry.npmjs.org/@types/node/-/node-10.14.9.tgz", + "integrity": "sha512-NelG/dSahlXYtSoVPErrp06tYFrvzj8XLWmKA+X8x0W//4MqbUyZu++giUG/v0bjAT6/Qxa8IjodrfdACyb0Fg==" + } + } + } + } + }, + "@ncstate/sat-popover": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@ncstate/sat-popover/-/sat-popover-4.0.0.tgz", + "integrity": "sha512-JWEvphJ5BSOQLn5E8f9F/dKVrgVozHCKc930iLL7xbTa+nCP9jBFNi5MWjlbU81AXLtBweSlPHnIyqF7bmjy4A==", + "requires": { + "tslib": "^1.9.0" + } + }, + "@ngtools/webpack": { + "version": "7.3.3", + "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-7.3.3.tgz", + "integrity": "sha512-G/1P00XHWVrKT3qoSyy7yAPT5/fuja84YifcGg/2SwmNNo4hTXxWhqec0/uHwgQr6nYhGDyzwwXYeKKyQkcfgw==", + "dev": true, + "requires": { + "@angular-devkit/core": "7.3.3", + "enhanced-resolve": "4.1.0", + "rxjs": "6.3.3", + "tree-kill": "1.2.1", + "webpack-sources": "1.3.0" + }, + "dependencies": { + "@angular-devkit/core": { + "version": "7.3.3", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-7.3.3.tgz", + "integrity": "sha512-fosULDtMoDWrOyUzTmBkJccOy7zodo02kENyKai7vOv9EWfv9jytkVdNc+jl0ys9OE2QadvSYBo49jhnZxFXfQ==", + "dev": true, + "requires": { + "ajv": "6.9.1", + "chokidar": "2.0.4", + "fast-json-stable-stringify": "2.0.0", + "rxjs": "6.3.3", + "source-map": "0.7.3" + } + } + } + }, + "@ngu/carousel": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/@ngu/carousel/-/carousel-1.5.4.tgz", + "integrity": "sha512-Wf4vowWhitpptLNeCdO/LrBA+EDHT7E9OFftV78JeCrPQ7MEJ6w9RjdtHqC5QotqrMVSXenaoeifH6LZGYsGsw==", + "requires": { + "tslib": "^1.9.0" + } + }, + "@ngx-translate/core": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@ngx-translate/core/-/core-10.0.1.tgz", + "integrity": "sha1-nzo+0HfoR90NKVGmZNpu40igbSI=", + "requires": { + "tslib": "^1.9.0" + } + }, + "@ngx-translate/http-loader": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@ngx-translate/http-loader/-/http-loader-3.0.1.tgz", + "integrity": "sha1-ILD5i8bCUyESnT4zAqs8xInApCo=", + "requires": { + "tslib": "^1.9.0" + } + }, + "@protobufjs/aspromise": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", + "integrity": "sha1-m4sMxmPWaafY9vXQiToU00jzD78=" + }, + "@protobufjs/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==" + }, + "@protobufjs/codegen": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", + "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==" + }, + "@protobufjs/eventemitter": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", + "integrity": "sha1-NVy8mLr61ZePntCV85diHx0Ga3A=" + }, + "@protobufjs/fetch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", + "integrity": "sha1-upn7WYYUr2VwDBYZ/wbUVLDYTEU=", + "requires": { + "@protobufjs/aspromise": "^1.1.1", + "@protobufjs/inquire": "^1.1.0" + } + }, + "@protobufjs/float": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", + "integrity": "sha1-Xp4avctz/Ap8uLKR33jIy9l7h9E=" + }, + "@protobufjs/inquire": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", + "integrity": "sha1-/yAOPnzyQp4tyvwRQIKOjMY48Ik=" + }, + "@protobufjs/path": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", + "integrity": "sha1-bMKyDFya1q0NzP0hynZz2Nf79o0=" + }, + "@protobufjs/pool": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", + "integrity": "sha1-Cf0V8tbTq/qbZbw2ZQbWrXhG/1Q=" + }, + "@protobufjs/utf8": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", + "integrity": "sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA=" + }, + "@schematics/angular": { + "version": "7.3.3", + "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-7.3.3.tgz", + "integrity": "sha512-HbH8vajYPka0xGcFAN5IUBx8n8SFMQLFb9di2dJCOBaEakbKVkk8qtOpil54oFQbx7DFCvutq/p0u42JfEbuMQ==", + "dev": true, + "requires": { + "@angular-devkit/core": "7.3.3", + "@angular-devkit/schematics": "7.3.3", + "typescript": "3.2.4" + }, + "dependencies": { + "@angular-devkit/core": { + "version": "7.3.3", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-7.3.3.tgz", + "integrity": "sha512-fosULDtMoDWrOyUzTmBkJccOy7zodo02kENyKai7vOv9EWfv9jytkVdNc+jl0ys9OE2QadvSYBo49jhnZxFXfQ==", + "dev": true, + "requires": { + "ajv": "6.9.1", + "chokidar": "2.0.4", + "fast-json-stable-stringify": "2.0.0", + "rxjs": "6.3.3", + "source-map": "0.7.3" + } + } + } + }, + "@schematics/update": { + "version": "0.13.3", + "resolved": "https://registry.npmjs.org/@schematics/update/-/update-0.13.3.tgz", + "integrity": "sha512-sCOFQ62dd7VdEGiSUJNZshNI31ODwpJjn2WIvFgZLt6sdHHun67s/JOvOUq4mxx6I74oD6RPJPF4AP5sigVxxg==", + "dev": true, + "requires": { + "@angular-devkit/core": "7.3.3", + "@angular-devkit/schematics": "7.3.3", + "@yarnpkg/lockfile": "1.1.0", + "ini": "1.3.5", + "pacote": "9.4.0", + "rxjs": "6.3.3", + "semver": "5.6.0", + "semver-intersect": "1.4.0" + }, + "dependencies": { + "@angular-devkit/core": { + "version": "7.3.3", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-7.3.3.tgz", + "integrity": "sha512-fosULDtMoDWrOyUzTmBkJccOy7zodo02kENyKai7vOv9EWfv9jytkVdNc+jl0ys9OE2QadvSYBo49jhnZxFXfQ==", + "dev": true, + "requires": { + "ajv": "6.9.1", + "chokidar": "2.0.4", + "fast-json-stable-stringify": "2.0.0", + "rxjs": "6.3.3", + "source-map": "0.7.3" + } + }, + "ini": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", + "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==", + "dev": true + } + } + }, + "@sindresorhus/is": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.14.0.tgz", + "integrity": "sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ==", + "dev": true + }, + "@szmarczak/http-timer": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-1.1.2.tgz", + "integrity": "sha512-XIB2XbzHTN6ieIjfIMV9hlVcfPU26s2vafYWQcZHWXHOxiaRZYEDKEwdl129Zyg50+foYV2jCgtrqSA6qNuNSA==", + "dev": true, + "requires": { + "defer-to-connect": "^1.0.1" + } + }, + "@types/debug": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.5.tgz", + "integrity": "sha512-Q1y515GcOdTHgagaVFhHnIFQ38ygs/kmxdNpvpou+raI9UO3YZcHDngBSYKQklcKlvA7iuQlmIKbzvmxcOE9CQ==", + "dev": true + }, + "@types/jasmine": { + "version": "2.8.16", + "resolved": "https://registry.npmjs.org/@types/jasmine/-/jasmine-2.8.16.tgz", + "integrity": "sha512-056oRlBBp7MDzr+HoU5su099s/s7wjZ3KcHxLfv+Byqb9MwdLUvsfLgw1VS97hsh3ddxSPyQu+olHMnoVTUY6g==", + "dev": true + }, + "@types/jasminewd2": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/jasminewd2/-/jasminewd2-2.0.6.tgz", + "integrity": "sha512-2ZOKrxb8bKRmP/po5ObYnRDgFE4i+lQiEB27bAMmtMWLgJSqlIDqlLx6S0IRorpOmOPRQ6O80NujTmQAtBkeNw==", + "dev": true, + "requires": { + "@types/jasmine": "*" + } + }, + "@types/lodash": { + "version": "4.14.134", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.134.tgz", + "integrity": "sha512-2/O0khFUCFeDlbi7sZ7ZFRCcT812fAeOLm7Ev4KbwASkZ575TDrDcY7YyaoHdTOzKcNbfiwLYZqPmoC4wadrsw==" + }, + "@types/long": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.0.tgz", + "integrity": "sha512-1w52Nyx4Gq47uuu0EVcsHBxZFJgurQ+rTKS3qMHxR1GY2T8c2AJYd6vZoZ9q1rupaDjU0yT+Jc2XTyXkjeMA+Q==" + }, + "@types/mixpanel-browser": { + "version": "2.23.1", + "resolved": "https://registry.npmjs.org/@types/mixpanel-browser/-/mixpanel-browser-2.23.1.tgz", + "integrity": "sha512-wt6ZkMWxWSpGcIK1p3Mvu5XwqmQw7U1gE+rL/2ZilWIG4M5Lyy11CNNE2iA6cirbxN3Qe7LoACiyybYRuAbLRw==" + }, + "@types/node": { + "version": "8.9.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-8.9.5.tgz", + "integrity": "sha512-jRHfWsvyMtXdbhnz5CVHxaBgnV6duZnPlQuRSo/dm/GnmikNcmZhxIES4E9OZjUmQ8C+HCl4KJux+cXN/ErGDQ==", + "dev": true + }, + "@types/q": { + "version": "0.0.32", + "resolved": "https://registry.npmjs.org/@types/q/-/q-0.0.32.tgz", + "integrity": "sha1-vShOV8hPEyXacCur/IKlMoGQwMU=", + "dev": true + }, + "@types/quill": { + "version": "1.3.10", + "resolved": "https://registry.npmjs.org/@types/quill/-/quill-1.3.10.tgz", + "integrity": "sha512-IhW3fPW+bkt9MLNlycw8u8fWb7oO7W5URC9MfZYHBlA24rex9rs23D5DETChu1zvgVdc5ka64ICjJOgQMr6Shw==", + "requires": { + "parchment": "^1.1.2" + } + }, + "@types/selenium-webdriver": { + "version": "3.0.15", + "resolved": "https://registry.npmjs.org/@types/selenium-webdriver/-/selenium-webdriver-3.0.15.tgz", + "integrity": "sha512-5nh8/K2u9p4bk95GGCJB7KBvewaB0TUziZ9DTr+mR2I6RoO4OJVqx7rxK83hs2J1tomwtCGkhiW+Dy8EUnfB+Q==", + "dev": true + }, + "@types/source-list-map": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/@types/source-list-map/-/source-list-map-0.1.2.tgz", + "integrity": "sha512-K5K+yml8LTo9bWJI/rECfIPrGgxdpeNbj+d53lwN4QjW1MCwlkhUms+gtdzigTeUyBr09+u8BwOIY3MXvHdcsA==", + "dev": true + }, + "@types/three": { + "version": "0.93.31", + "resolved": "https://registry.npmjs.org/@types/three/-/three-0.93.31.tgz", + "integrity": "sha512-lR9eLY3gnrUDAdW4ujITgVTPEv1Oy2yoL3LlfKAnjQuzyjGSR+PvQuXuWmaDCD1IyWhkqbnol1nJwaW0MGe35A==", + "dev": true + }, + "@types/webpack-sources": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/@types/webpack-sources/-/webpack-sources-0.1.5.tgz", + "integrity": "sha512-zfvjpp7jiafSmrzJ2/i3LqOyTYTuJ7u1KOXlKgDlvsj9Rr0x7ZiYu5lZbXwobL7lmsRNtPXlBfmaUD8eU2Hu8w==", + "dev": true, + "requires": { + "@types/node": "*", + "@types/source-list-map": "*", + "source-map": "^0.6.1" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "@webassemblyjs/ast": { + "version": "1.7.11", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.7.11.tgz", + "integrity": "sha512-ZEzy4vjvTzScC+SH8RBssQUawpaInUdMTYwYYLh54/s8TuT0gBLuyUnppKsVyZEi876VmmStKsUs28UxPgdvrA==", + "dev": true, + "requires": { + "@webassemblyjs/helper-module-context": "1.7.11", + "@webassemblyjs/helper-wasm-bytecode": "1.7.11", + "@webassemblyjs/wast-parser": "1.7.11" + } + }, + "@webassemblyjs/floating-point-hex-parser": { + "version": "1.7.11", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.7.11.tgz", + "integrity": "sha512-zY8dSNyYcgzNRNT666/zOoAyImshm3ycKdoLsyDw/Bwo6+/uktb7p4xyApuef1dwEBo/U/SYQzbGBvV+nru2Xg==", + "dev": true + }, + "@webassemblyjs/helper-api-error": { + "version": "1.7.11", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.7.11.tgz", + "integrity": "sha512-7r1qXLmiglC+wPNkGuXCvkmalyEstKVwcueZRP2GNC2PAvxbLYwLLPr14rcdJaE4UtHxQKfFkuDFuv91ipqvXg==", + "dev": true + }, + "@webassemblyjs/helper-buffer": { + "version": "1.7.11", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.7.11.tgz", + "integrity": "sha512-MynuervdylPPh3ix+mKZloTcL06P8tenNH3sx6s0qE8SLR6DdwnfgA7Hc9NSYeob2jrW5Vql6GVlsQzKQCa13w==", + "dev": true + }, + "@webassemblyjs/helper-code-frame": { + "version": "1.7.11", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-code-frame/-/helper-code-frame-1.7.11.tgz", + "integrity": "sha512-T8ESC9KMXFTXA5urJcyor5cn6qWeZ4/zLPyWeEXZ03hj/x9weSokGNkVCdnhSabKGYWxElSdgJ+sFa9G/RdHNw==", + "dev": true, + "requires": { + "@webassemblyjs/wast-printer": "1.7.11" + } + }, + "@webassemblyjs/helper-fsm": { + "version": "1.7.11", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-fsm/-/helper-fsm-1.7.11.tgz", + "integrity": "sha512-nsAQWNP1+8Z6tkzdYlXT0kxfa2Z1tRTARd8wYnc/e3Zv3VydVVnaeePgqUzFrpkGUyhUUxOl5ML7f1NuT+gC0A==", + "dev": true + }, + "@webassemblyjs/helper-module-context": { + "version": "1.7.11", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-module-context/-/helper-module-context-1.7.11.tgz", + "integrity": "sha512-JxfD5DX8Ygq4PvXDucq0M+sbUFA7BJAv/GGl9ITovqE+idGX+J3QSzJYz+LwQmL7fC3Rs+utvWoJxDb6pmC0qg==", + "dev": true + }, + "@webassemblyjs/helper-wasm-bytecode": { + "version": "1.7.11", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.7.11.tgz", + "integrity": "sha512-cMXeVS9rhoXsI9LLL4tJxBgVD/KMOKXuFqYb5oCJ/opScWpkCMEz9EJtkonaNcnLv2R3K5jIeS4TRj/drde1JQ==", + "dev": true + }, + "@webassemblyjs/helper-wasm-section": { + "version": "1.7.11", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.7.11.tgz", + "integrity": "sha512-8ZRY5iZbZdtNFE5UFunB8mmBEAbSI3guwbrsCl4fWdfRiAcvqQpeqd5KHhSWLL5wuxo53zcaGZDBU64qgn4I4Q==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.7.11", + "@webassemblyjs/helper-buffer": "1.7.11", + "@webassemblyjs/helper-wasm-bytecode": "1.7.11", + "@webassemblyjs/wasm-gen": "1.7.11" + } + }, + "@webassemblyjs/ieee754": { + "version": "1.7.11", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.7.11.tgz", + "integrity": "sha512-Mmqx/cS68K1tSrvRLtaV/Lp3NZWzXtOHUW2IvDvl2sihAwJh4ACE0eL6A8FvMyDG9abes3saB6dMimLOs+HMoQ==", + "dev": true, + "requires": { + "@xtuc/ieee754": "^1.2.0" + } + }, + "@webassemblyjs/leb128": { + "version": "1.7.11", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.7.11.tgz", + "integrity": "sha512-vuGmgZjjp3zjcerQg+JA+tGOncOnJLWVkt8Aze5eWQLwTQGNgVLcyOTqgSCxWTR4J42ijHbBxnuRaL1Rv7XMdw==", + "dev": true, + "requires": { + "@xtuc/long": "4.2.1" + } + }, + "@webassemblyjs/utf8": { + "version": "1.7.11", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.7.11.tgz", + "integrity": "sha512-C6GFkc7aErQIAH+BMrIdVSmW+6HSe20wg57HEC1uqJP8E/xpMjXqQUxkQw07MhNDSDcGpxI9G5JSNOQCqJk4sA==", + "dev": true + }, + "@webassemblyjs/wasm-edit": { + "version": "1.7.11", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.7.11.tgz", + "integrity": "sha512-FUd97guNGsCZQgeTPKdgxJhBXkUbMTY6hFPf2Y4OedXd48H97J+sOY2Ltaq6WGVpIH8o/TGOVNiVz/SbpEMJGg==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.7.11", + "@webassemblyjs/helper-buffer": "1.7.11", + "@webassemblyjs/helper-wasm-bytecode": "1.7.11", + "@webassemblyjs/helper-wasm-section": "1.7.11", + "@webassemblyjs/wasm-gen": "1.7.11", + "@webassemblyjs/wasm-opt": "1.7.11", + "@webassemblyjs/wasm-parser": "1.7.11", + "@webassemblyjs/wast-printer": "1.7.11" + } + }, + "@webassemblyjs/wasm-gen": { + "version": "1.7.11", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.7.11.tgz", + "integrity": "sha512-U/KDYp7fgAZX5KPfq4NOupK/BmhDc5Kjy2GIqstMhvvdJRcER/kUsMThpWeRP8BMn4LXaKhSTggIJPOeYHwISA==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.7.11", + "@webassemblyjs/helper-wasm-bytecode": "1.7.11", + "@webassemblyjs/ieee754": "1.7.11", + "@webassemblyjs/leb128": "1.7.11", + "@webassemblyjs/utf8": "1.7.11" + } + }, + "@webassemblyjs/wasm-opt": { + "version": "1.7.11", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.7.11.tgz", + "integrity": "sha512-XynkOwQyiRidh0GLua7SkeHvAPXQV/RxsUeERILmAInZegApOUAIJfRuPYe2F7RcjOC9tW3Cb9juPvAC/sCqvg==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.7.11", + "@webassemblyjs/helper-buffer": "1.7.11", + "@webassemblyjs/wasm-gen": "1.7.11", + "@webassemblyjs/wasm-parser": "1.7.11" + } + }, + "@webassemblyjs/wasm-parser": { + "version": "1.7.11", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.7.11.tgz", + "integrity": "sha512-6lmXRTrrZjYD8Ng8xRyvyXQJYUQKYSXhJqXOBLw24rdiXsHAOlvw5PhesjdcaMadU/pyPQOJ5dHreMjBxwnQKg==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.7.11", + "@webassemblyjs/helper-api-error": "1.7.11", + "@webassemblyjs/helper-wasm-bytecode": "1.7.11", + "@webassemblyjs/ieee754": "1.7.11", + "@webassemblyjs/leb128": "1.7.11", + "@webassemblyjs/utf8": "1.7.11" + } + }, + "@webassemblyjs/wast-parser": { + "version": "1.7.11", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-parser/-/wast-parser-1.7.11.tgz", + "integrity": "sha512-lEyVCg2np15tS+dm7+JJTNhNWq9yTZvi3qEhAIIOaofcYlUp0UR5/tVqOwa/gXYr3gjwSZqw+/lS9dscyLelbQ==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.7.11", + "@webassemblyjs/floating-point-hex-parser": "1.7.11", + "@webassemblyjs/helper-api-error": "1.7.11", + "@webassemblyjs/helper-code-frame": "1.7.11", + "@webassemblyjs/helper-fsm": "1.7.11", + "@xtuc/long": "4.2.1" + } + }, + "@webassemblyjs/wast-printer": { + "version": "1.7.11", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.7.11.tgz", + "integrity": "sha512-m5vkAsuJ32QpkdkDOUPGSltrg8Cuk3KBx4YrmAGQwCZPRdUHXxG4phIOuuycLemHFr74sWL9Wthqss4fzdzSwg==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.7.11", + "@webassemblyjs/wast-parser": "1.7.11", + "@xtuc/long": "4.2.1" + } + }, + "@xtuc/ieee754": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", + "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", + "dev": true + }, + "@xtuc/long": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.1.tgz", + "integrity": "sha512-FZdkNBDqBRHKQ2MEbSC17xnPFOhZxeJ2YGSfr2BKf3sujG49Qe3bB+rGCwQfIaA7WHnGeGkSijX4FuBCdrzW/g==", + "dev": true + }, + "@yarnpkg/lockfile": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz", + "integrity": "sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==", + "dev": true + }, + "JSONStream": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz", + "integrity": "sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==", + "dev": true, + "requires": { + "jsonparse": "^1.2.0", + "through": ">=2.2.7 <3" + } + }, + "abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", + "dev": true, + "optional": true + }, + "accepts": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.5.tgz", + "integrity": "sha1-63d99gEXI6OxTopywIBcjoZ0a9I=", + "dev": true, + "requires": { + "mime-types": "~2.1.18", + "negotiator": "0.6.1" + } + }, + "acorn": { + "version": "6.4.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.2.tgz", + "integrity": "sha512-XtGIhXwF8YM8bJhGxG5kXgjkEuNGLTkoYqVE+KMR+aspr4KGYmKYg7yUe3KghyQ9yheNwLnjmzh/7+gfDBmHCQ==", + "dev": true + }, + "acorn-dynamic-import": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/acorn-dynamic-import/-/acorn-dynamic-import-4.0.0.tgz", + "integrity": "sha512-d3OEjQV4ROpoflsnUA8HozoIR504TFxNivYEUi6uwz0IYhBkTDXGuWlNdMtybRt3nqVx/L6XqMt0FxkXuWKZhw==", + "dev": true + }, + "adm-zip": { + "version": "0.4.13", + "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.4.13.tgz", + "integrity": "sha512-fERNJX8sOXfel6qCBCMPvZLzENBEhZTzKqg6vrOW5pvoEaQuJhRU4ndTAh6lHOxn1I6jnz2NHra56ZODM751uw==", + "dev": true + }, + "after": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/after/-/after-0.8.2.tgz", + "integrity": "sha1-/ts5T58OAqqXaOcCvaI7UF+ufh8=", + "dev": true + }, + "agent-base": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.2.1.tgz", + "integrity": "sha512-JVwXMr9nHYTUXsBFKUqhJwvlcYU/blreOEUkhNR2eXZIvwd+c+o5V4MgDPKWnMS/56awN3TRzIP+KoPn+roQtg==", + "dev": true, + "requires": { + "es6-promisify": "^5.0.0" + } + }, + "agentkeepalive": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-3.5.2.tgz", + "integrity": "sha512-e0L/HNe6qkQ7H19kTlRRqUibEAwDK5AFk6y3PtMsuut2VAH6+Q4xZml1tNDJD7kSAyqmbG/K08K5WEJYtUrSlQ==", + "dev": true, + "requires": { + "humanize-ms": "^1.2.1" + } + }, + "ajv": { + "version": "6.9.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.9.1.tgz", + "integrity": "sha512-XDN92U311aINL77ieWHmqCcNlwjoP5cHXDxIxbf2MaPYuCXOHS7gHH8jktxeK5omgd52XbSTX6a4Piwd1pQmzA==", + "dev": true, + "requires": { + "fast-deep-equal": "^2.0.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "ajv-errors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ajv-errors/-/ajv-errors-1.0.1.tgz", + "integrity": "sha512-DCRfO/4nQ+89p/RK43i8Ezd41EqdGIU4ld7nGF8OQ14oc/we5rEntLCUa7+jrn3nn83BosfwZA0wb4pon2o8iQ==", + "dev": true + }, + "ajv-keywords": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.4.0.tgz", + "integrity": "sha512-aUjdRFISbuFOl0EIZc+9e4FfZp0bDZgAdOOf30bJmw8VM9v84SHyVyxDfbWxpGYbdZD/9XoKxfHVNmxPkhwyGw==", + "dev": true + }, + "amdefine": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", + "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=", + "dev": true + }, + "angular-calendar": { + "version": "0.26.4", + "resolved": "https://registry.npmjs.org/angular-calendar/-/angular-calendar-0.26.4.tgz", + "integrity": "sha512-Bb1ZVE3N6moXCOOYJar1nF+ZqSdVX05HkgmnG/gRImjPsUxXnWnE2BjIjzzgQalE5LinbG2GPYNk8AhE1kah3Q==", + "requires": { + "angular-draggable-droppable": "^4.0.2", + "angular-resizable-element": "^3.2.2", + "calendar-utils": "^0.2.2", + "positioning": "^1.4.0", + "tslib": "^1.9.0" + } + }, + "angular-draggable-droppable": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/angular-draggable-droppable/-/angular-draggable-droppable-4.1.0.tgz", + "integrity": "sha512-cWLYEHt20nxpNA8wqzrfCDWh0o09Q7jY2sXuEFHx3Pjl5tBBg8BOZ3KYe+RV22yuoQMrfLPizuG7U8iB406ZMg==", + "requires": { + "tslib": "^1.9.0" + } + }, + "angular-file-saver": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/angular-file-saver/-/angular-file-saver-1.1.3.tgz", + "integrity": "sha1-3K7AaVIU8iakyq/IwW0hqaYffRs=", + "requires": { + "blob-tmp": "^1.0.0", + "file-saver": "^1.3.3" + }, + "dependencies": { + "file-saver": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/file-saver/-/file-saver-1.3.8.tgz", + "integrity": "sha512-spKHSBQIxxS81N/O21WmuXA2F6wppUCsutpzenOeZzOCCJ5gEfcbqJP983IrpLXzYmXnMUa6J03SubcNPdKrlg==" + } + } + }, + "angular-resizable-element": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/angular-resizable-element/-/angular-resizable-element-3.2.4.tgz", + "integrity": "sha512-Qd/WRTaJgmDJoeWzmK0F52I3X0nLtFPirmsVC2ceic4EQ/3X2yYFCOWgCtH+PvMZct6Whoky3OQLqCpFXQsKHQ==", + "requires": { + "tslib": "^1.9.0" + } + }, + "ansi-align": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz", + "integrity": "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==", + "dev": true, + "requires": { + "string-width": "^4.1.0" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + }, + "string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1" + } + } + } + }, + "ansi-colors": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.3.tgz", + "integrity": "sha512-LEHHyuhlPY3TmuUYMh2oz89lTShfvgbmzaBcxve9t/9Wuy7Dwf4yoAKcND7KFT1HAQfqZ12qtc+DUrBMeKF9nw==", + "dev": true + }, + "ansi-escapes": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz", + "integrity": "sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==", + "dev": true + }, + "ansi-html": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/ansi-html/-/ansi-html-0.0.7.tgz", + "integrity": "sha1-gTWEAhliqenm/QOflA0S9WynhZ4=", + "dev": true + }, + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "anymatch": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", + "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", + "dev": true, + "requires": { + "micromatch": "^3.1.4", + "normalize-path": "^2.1.1" + } + }, + "app-builder-bin": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/app-builder-bin/-/app-builder-bin-3.4.3.tgz", + "integrity": "sha512-qMhayIwi3juerQEVJMQ76trObEbfQT0nhUdxZz9a26/3NLT3pE6awmQ8S1cEnrGugaaM5gYqR8OElcDezfmEsg==", + "dev": true + }, + "app-builder-lib": { + "version": "21.2.0", + "resolved": "https://registry.npmjs.org/app-builder-lib/-/app-builder-lib-21.2.0.tgz", + "integrity": "sha512-aOX/nv77/Bti6NymJDg7p9T067xD8m1ipIEJR7B4Mm1GsJWpMm9PZdXtCRiMNRjHtQS5KIljT0g17781y6qn5A==", + "dev": true, + "requires": { + "7zip-bin": "~5.0.3", + "@develar/schema-utils": "~2.1.0", + "async-exit-hook": "^2.0.1", + "bluebird-lst": "^1.0.9", + "builder-util": "21.2.0", + "builder-util-runtime": "8.3.0", + "chromium-pickle-js": "^0.2.0", + "debug": "^4.1.1", + "ejs": "^2.6.2", + "electron-publish": "21.2.0", + "fs-extra": "^8.1.0", + "hosted-git-info": "^2.7.1", + "is-ci": "^2.0.0", + "isbinaryfile": "^4.0.2", + "js-yaml": "^3.13.1", + "lazy-val": "^1.0.4", + "minimatch": "^3.0.4", + "normalize-package-data": "^2.5.0", + "read-config-file": "5.0.0", + "sanitize-filename": "^1.6.2", + "semver": "^6.3.0", + "temp-file": "^3.3.4" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "dev": true, + "requires": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + } + }, + "graceful-fs": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.1.tgz", + "integrity": "sha512-b9usnbDGnD928gJB3LrCmxoibr3VE4U2SMo5PBuBnokWyDADTqDPXg4YpwKF1trpH+UbGp7QLicO3+aWEy0+mw==", + "dev": true + }, + "isbinaryfile": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-4.0.2.tgz", + "integrity": "sha512-C3FSxJdNrEr2F4z6uFtNzECDM5hXk+46fxaa+cwBe5/XrWSmzdG8DDgyjfX6/NRdBB21q2JXuRAzPCUs+fclnQ==", + "dev": true + }, + "js-yaml": { + "version": "3.13.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", + "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "sanitize-filename": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/sanitize-filename/-/sanitize-filename-1.6.2.tgz", + "integrity": "sha512-cmTzND7RMxUB+f7gI+4+KAVHWEg0lfXvQJdko+FXDP5bNbGIdx4KMP5pX6lv5jfT9jSf6OBbjyxjFtZQwYA/ig==", + "dev": true, + "requires": { + "truncate-utf8-bytes": "^1.0.0" + } + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, + "app-root-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/app-root-path/-/app-root-path-2.1.0.tgz", + "integrity": "sha1-mL9lmTJ+zqGZMJhm6BQDaP0uZGo=", + "dev": true + }, + "append-transform": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/append-transform/-/append-transform-1.0.0.tgz", + "integrity": "sha512-P009oYkeHyU742iSZJzZZywj4QRJdnTWffaKuJQLablCZ1uz6/cW4yaRgcDaoQ+uwOxxnt0gRUcwfsNP2ri0gw==", + "dev": true, + "requires": { + "default-require-extensions": "^2.0.0" + } + }, + "aproba": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", + "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", + "dev": true + }, + "are-we-there-yet": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz", + "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==", + "dev": true, + "optional": true, + "requires": { + "delegates": "^1.0.0", + "readable-stream": "^2.0.6" + } + }, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "arr-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", + "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", + "dev": true + }, + "arr-flatten": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", + "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", + "dev": true + }, + "arr-union": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", + "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=", + "dev": true + }, + "array-find-index": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz", + "integrity": "sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E=", + "dev": true + }, + "array-flatten": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-2.1.2.tgz", + "integrity": "sha512-hNfzcOV8W4NdualtqBFPyVO+54DSJuZGY9qT4pRroB6S9e3iiido2ISIC5h9R2sPJ8H3FHCIiEnsv1lPXO3KtQ==", + "dev": true + }, + "array-slice": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/array-slice/-/array-slice-0.2.3.tgz", + "integrity": "sha1-3Tz7gO15c6dRF82sabC5nshhhvU=", + "dev": true + }, + "array-union": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", + "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=", + "dev": true, + "requires": { + "array-uniq": "^1.0.1" + } + }, + "array-uniq": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", + "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=", + "dev": true + }, + "array-unique": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", + "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", + "dev": true + }, + "arraybuffer.slice": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/arraybuffer.slice/-/arraybuffer.slice-0.0.7.tgz", + "integrity": "sha512-wGUIVQXuehL5TCqQun8OW81jGzAWycqzFF8lFp+GOM5BXLYj3bKNsYC4daB7n6XjCqxQA/qgTJ+8ANR3acjrog==", + "dev": true + }, + "arrify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", + "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", + "dev": true + }, + "asap": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=", + "dev": true, + "optional": true + }, + "asar": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/asar/-/asar-2.0.1.tgz", + "integrity": "sha512-Vo9yTuUtyFahkVMFaI6uMuX6N7k5DWa6a/8+7ov0/f8Lq9TVR0tUjzSzxQSxT1Y+RJIZgnP7BVb6Uhi+9cjxqA==", + "dev": true, + "requires": { + "chromium-pickle-js": "^0.2.0", + "commander": "^2.20.0", + "cuint": "^0.2.2", + "glob": "^7.1.3", + "minimatch": "^3.0.4", + "mkdirp": "^0.5.1", + "tmp-promise": "^1.0.5" + }, + "dependencies": { + "commander": { + "version": "2.20.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.0.tgz", + "integrity": "sha512-7j2y+40w61zy6YC2iRNpUe/NwhNyoXrYpHMrSunaMG64nRnaf96zO/KMQR4OyN/UnE5KLyEBnKHd4aG3rskjpQ==", + "dev": true + } + } + }, + "ascli": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ascli/-/ascli-1.0.1.tgz", + "integrity": "sha1-vPpZdKYvGOgcq660lzKrSoj5Brw=", + "requires": { + "colour": "~0.7.1", + "optjs": "~3.2.2" + } + }, + "asn1": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", + "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", + "dev": true, + "requires": { + "safer-buffer": "~2.1.0" + } + }, + "asn1.js": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-4.10.1.tgz", + "integrity": "sha512-p32cOF5q0Zqs9uBiONKYLm6BClCoBCM5O9JfeUSlnQLBTxYdTK+pW+nXflm8UkKd2UYlEbYz5qEi0JuZR9ckSw==", + "dev": true, + "requires": { + "bn.js": "^4.0.0", + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0" + } + }, + "assert": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/assert/-/assert-1.4.1.tgz", + "integrity": "sha1-mZEtWRg2tab1s0XA8H7vwI/GXZE=", + "dev": true, + "requires": { + "util": "0.10.3" + }, + "dependencies": { + "inherits": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz", + "integrity": "sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE=", + "dev": true + }, + "util": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz", + "integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=", + "dev": true, + "requires": { + "inherits": "2.0.1" + } + } + } + }, + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", + "dev": true + }, + "assign-symbols": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", + "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", + "dev": true + }, + "async": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", + "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=" + }, + "async-each": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.1.tgz", + "integrity": "sha1-GdOGodntxufByF04iu28xW0zYC0=", + "dev": true + }, + "async-exit-hook": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/async-exit-hook/-/async-exit-hook-2.0.1.tgz", + "integrity": "sha512-NW2cX8m1Q7KPA7a5M2ULQeZ2wR5qI5PAbw5L0UOMxdioVk9PMZ0h1TmyZEkPYrCvYjDlFICusOu1dlEKAAeXBw==", + "dev": true + }, + "async-foreach": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/async-foreach/-/async-foreach-0.1.3.tgz", + "integrity": "sha1-NhIfhFwFeBct5Bmpfb6x0W7DRUI=", + "dev": true, + "optional": true + }, + "async-limiter": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.0.tgz", + "integrity": "sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg==", + "dev": true + }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", + "dev": true + }, + "atoa": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/atoa/-/atoa-1.0.0.tgz", + "integrity": "sha1-DMDpGkgOc4+SPrwQNnZHF3mzSkk=" + }, + "atob": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", + "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", + "dev": true + }, + "author-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/author-regex/-/author-regex-1.0.0.tgz", + "integrity": "sha1-0IiFvmubv5Q5/gh8dihyRfCoFFA=", + "dev": true + }, + "autoprefixer": { + "version": "9.4.6", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-9.4.6.tgz", + "integrity": "sha512-Yp51mevbOEdxDUy5WjiKtpQaecqYq9OqZSL04rSoCiry7Tc5I9FEyo3bfxiTJc1DfHeKwSFCUYbBAiOQ2VGfiw==", + "dev": true, + "requires": { + "browserslist": "^4.4.1", + "caniuse-lite": "^1.0.30000929", + "normalize-range": "^0.1.2", + "num2fraction": "^1.2.2", + "postcss": "^7.0.13", + "postcss-value-parser": "^3.3.1" + } + }, + "aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=", + "dev": true + }, + "aws4": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz", + "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==", + "dev": true + }, + "babel-code-frame": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz", + "integrity": "sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=", + "dev": true, + "requires": { + "chalk": "^1.1.3", + "esutils": "^2.0.2", + "js-tokens": "^3.0.2" + }, + "dependencies": { + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "dev": true + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + } + }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true + } + } + }, + "babel-generator": { + "version": "6.26.1", + "resolved": "https://registry.npmjs.org/babel-generator/-/babel-generator-6.26.1.tgz", + "integrity": "sha512-HyfwY6ApZj7BYTcJURpM5tznulaBvyio7/0d4zFOeMPUmfxkCjHocCuoLa2SAGzBI8AREcH3eP3758F672DppA==", + "dev": true, + "requires": { + "babel-messages": "^6.23.0", + "babel-runtime": "^6.26.0", + "babel-types": "^6.26.0", + "detect-indent": "^4.0.0", + "jsesc": "^1.3.0", + "lodash": "^4.17.4", + "source-map": "^0.5.7", + "trim-right": "^1.0.1" + }, + "dependencies": { + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + } + } + }, + "babel-messages": { + "version": "6.23.0", + "resolved": "https://registry.npmjs.org/babel-messages/-/babel-messages-6.23.0.tgz", + "integrity": "sha1-8830cDhYA1sqKVHG7F7fbGLyYw4=", + "dev": true, + "requires": { + "babel-runtime": "^6.22.0" + } + }, + "babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "requires": { + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" + } + }, + "babel-template": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-template/-/babel-template-6.26.0.tgz", + "integrity": "sha1-3gPi0WOWsGn0bdn/+FIfsaDjXgI=", + "dev": true, + "requires": { + "babel-runtime": "^6.26.0", + "babel-traverse": "^6.26.0", + "babel-types": "^6.26.0", + "babylon": "^6.18.0", + "lodash": "^4.17.4" + } + }, + "babel-traverse": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.26.0.tgz", + "integrity": "sha1-RqnL1+3MYsjlwGTi0tjQ9ANXZu4=", + "dev": true, + "requires": { + "babel-code-frame": "^6.26.0", + "babel-messages": "^6.23.0", + "babel-runtime": "^6.26.0", + "babel-types": "^6.26.0", + "babylon": "^6.18.0", + "debug": "^2.6.8", + "globals": "^9.18.0", + "invariant": "^2.2.2", + "lodash": "^4.17.4" + } + }, + "babel-types": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-types/-/babel-types-6.26.0.tgz", + "integrity": "sha1-o7Bz+Uq0nrb6Vc1lInozQ4BjJJc=", + "dev": true, + "requires": { + "babel-runtime": "^6.26.0", + "esutils": "^2.0.2", + "lodash": "^4.17.4", + "to-fast-properties": "^1.0.3" + } + }, + "babylon": { + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/babylon/-/babylon-6.18.0.tgz", + "integrity": "sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ==", + "dev": true + }, + "backo2": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/backo2/-/backo2-1.0.2.tgz", + "integrity": "sha1-MasayLEpNjRj41s+u2n038+6eUc=", + "dev": true + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" + }, + "base": { + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", + "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", + "dev": true, + "requires": { + "cache-base": "^1.0.1", + "class-utils": "^0.3.5", + "component-emitter": "^1.2.1", + "define-property": "^1.0.0", + "isobject": "^3.0.1", + "mixin-deep": "^1.2.0", + "pascalcase": "^0.1.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + } + } + }, + "base64-arraybuffer": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.1.5.tgz", + "integrity": "sha1-c5JncZI7Whl0etZmqlzUv5xunOg=", + "dev": true + }, + "base64-js": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.0.tgz", + "integrity": "sha512-ccav/yGvoa80BQDljCxsmmQ3Xvx60/UpBIij5QN21W3wBi/hhIC9OoO+KLpu9IJTS9j4DRVJ3aDDF9cMSoa2lw==", + "dev": true + }, + "base64id": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/base64id/-/base64id-1.0.0.tgz", + "integrity": "sha1-R2iMuZu2gE8OBtPnY7HDLlfY5rY=", + "dev": true + }, + "batch": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", + "integrity": "sha1-3DQxT05nkxgJP8dgJyUl+UvyXBY=", + "dev": true + }, + "bcrypt-pbkdf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", + "dev": true, + "requires": { + "tweetnacl": "^0.14.3" + } + }, + "better-assert": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/better-assert/-/better-assert-1.0.2.tgz", + "integrity": "sha1-QIZrnhueC1W0gYlDEeaPr/rrxSI=", + "dev": true, + "requires": { + "callsite": "1.0.0" + } + }, + "big.js": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", + "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", + "dev": true + }, + "binary-extensions": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.0.tgz", + "integrity": "sha512-EgmjVLMn22z7eGGv3kcnHwSnJXmFHjISTY9E/S5lIcTD3Oxw05QTcBLNkJFzcb3cNueUdF/IN4U+d78V0zO8Hw==", + "dev": true + }, + "blob": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/blob/-/blob-0.0.5.tgz", + "integrity": "sha512-gaqbzQPqOoamawKg0LGVd7SzLgXS+JH61oWprSLH+P+abTczqJbhTR8CmJ2u9/bUYNmHTGJx/UEmn6doAvvuig==", + "dev": true + }, + "blob-tmp": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/blob-tmp/-/blob-tmp-1.0.0.tgz", + "integrity": "sha1-3oJJHiIv8TVMd6k+6OTqLIlUQnM=" + }, + "block-stream": { + "version": "0.0.9", + "resolved": "https://registry.npmjs.org/block-stream/-/block-stream-0.0.9.tgz", + "integrity": "sha1-E+v+d4oDIFz+A3UUgeu0szAMEmo=", + "dev": true, + "optional": true, + "requires": { + "inherits": "~2.0.0" + } + }, + "blocking-proxy": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/blocking-proxy/-/blocking-proxy-1.0.1.tgz", + "integrity": "sha512-KE8NFMZr3mN2E0HcvCgRtX7DjhiIQrwle+nSVJVC/yqFb9+xznHl2ZcoBp2L9qzkI4t4cBFJ1efXF8Dwi132RA==", + "dev": true, + "requires": { + "minimist": "^1.2.0" + } + }, + "bluebird": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.3.tgz", + "integrity": "sha512-/qKPUQlaW1OyR51WeCPBvRnAlnZFUJkCSG5HzGnuIqhgyJtF+T94lFnn33eiazjRm2LAHVy2guNnaq48X9SJuw==" + }, + "bluebird-lst": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/bluebird-lst/-/bluebird-lst-1.0.9.tgz", + "integrity": "sha512-7B1Rtx82hjnSD4PGLAjVWeYH3tHAcVUmChh85a3lltKQm6FresXh9ErQo6oAv6CqxttczC3/kEg8SY5NluPuUw==", + "dev": true, + "requires": { + "bluebird": "^3.5.5" + }, + "dependencies": { + "bluebird": { + "version": "3.5.5", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.5.tgz", + "integrity": "sha512-5am6HnnfN+urzt4yfg7IgTbotDjIT/u8AJpEt0sIU9FtXfVeezXAPKswrG+xKUCOYAINpSdgZVDU6QFh+cuH3w==", + "dev": true + } + } + }, + "bn.js": { + "version": "4.11.8", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz", + "integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==", + "dev": true + }, + "body-parser": { + "version": "1.18.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.3.tgz", + "integrity": "sha1-WykhmP/dVTs6DyDe0FkrlWlVyLQ=", + "dev": true, + "requires": { + "bytes": "3.0.0", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "~1.1.2", + "http-errors": "~1.6.3", + "iconv-lite": "0.4.23", + "on-finished": "~2.3.0", + "qs": "6.5.2", + "raw-body": "2.3.3", + "type-is": "~1.6.16" + } + }, + "bonjour": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/bonjour/-/bonjour-3.5.0.tgz", + "integrity": "sha1-jokKGD2O6aI5OzhExpGkK897yfU=", + "dev": true, + "requires": { + "array-flatten": "^2.1.0", + "deep-equal": "^1.0.1", + "dns-equal": "^1.0.0", + "dns-txt": "^2.0.2", + "multicast-dns": "^6.0.1", + "multicast-dns-service-types": "^1.1.0" + } + }, + "boxen": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/boxen/-/boxen-3.2.0.tgz", + "integrity": "sha512-cU4J/+NodM3IHdSL2yN8bqYqnmlBTidDR4RC7nJs61ZmtGz8VZzM3HLQX0zY5mrSmPtR3xWwsq2jOUQqFZN8+A==", + "dev": true, + "requires": { + "ansi-align": "^3.0.0", + "camelcase": "^5.3.1", + "chalk": "^2.4.2", + "cli-boxes": "^2.2.0", + "string-width": "^3.0.0", + "term-size": "^1.2.0", + "type-fest": "^0.3.0", + "widest-line": "^2.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + } + } + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "dev": true, + "requires": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "brorand": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", + "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=", + "dev": true + }, + "browserify-aes": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", + "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==", + "dev": true, + "requires": { + "buffer-xor": "^1.0.3", + "cipher-base": "^1.0.0", + "create-hash": "^1.1.0", + "evp_bytestokey": "^1.0.3", + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "browserify-cipher": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/browserify-cipher/-/browserify-cipher-1.0.1.tgz", + "integrity": "sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w==", + "dev": true, + "requires": { + "browserify-aes": "^1.0.4", + "browserify-des": "^1.0.0", + "evp_bytestokey": "^1.0.0" + } + }, + "browserify-des": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/browserify-des/-/browserify-des-1.0.2.tgz", + "integrity": "sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A==", + "dev": true, + "requires": { + "cipher-base": "^1.0.1", + "des.js": "^1.0.0", + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "browserify-rsa": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz", + "integrity": "sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ=", + "dev": true, + "requires": { + "bn.js": "^4.1.0", + "randombytes": "^2.0.1" + } + }, + "browserify-sign": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.0.4.tgz", + "integrity": "sha1-qk62jl17ZYuqa/alfmMMvXqT0pg=", + "dev": true, + "requires": { + "bn.js": "^4.1.1", + "browserify-rsa": "^4.0.0", + "create-hash": "^1.1.0", + "create-hmac": "^1.1.2", + "elliptic": "^6.0.0", + "inherits": "^2.0.1", + "parse-asn1": "^5.0.0" + } + }, + "browserify-zlib": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.2.0.tgz", + "integrity": "sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==", + "dev": true, + "requires": { + "pako": "~1.0.5" + } + }, + "browserslist": { + "version": "4.19.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.19.1.tgz", + "integrity": "sha512-u2tbbG5PdKRTUoctO3NBD8FQ5HdPh1ZXPHzp1rwaa5jTc+RV9/+RlWiAIKmjRPQF+xbGM9Kklj5bZQFa2s/38A==", + "dev": true, + "requires": { + "caniuse-lite": "^1.0.30001286", + "electron-to-chromium": "^1.4.17", + "escalade": "^3.1.1", + "node-releases": "^2.0.1", + "picocolors": "^1.0.0" + }, + "dependencies": { + "caniuse-lite": { + "version": "1.0.30001303", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001303.tgz", + "integrity": "sha512-/Mqc1oESndUNszJP0kx0UaQU9kEv9nNtJ7Kn8AdA0mNnH8eR1cj0kG+NbNuC1Wq/b21eA8prhKRA3bbkjONegQ==", + "dev": true + }, + "electron-to-chromium": { + "version": "1.4.57", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.57.tgz", + "integrity": "sha512-FNC+P5K1n6pF+M0zIK+gFCoXcJhhzDViL3DRIGy2Fv5PohuSES1JHR7T+GlwxSxlzx4yYbsuzCZvHxcBSRCIOw==", + "dev": true + }, + "node-releases": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.1.tgz", + "integrity": "sha512-CqyzN6z7Q6aMeF/ktcMVTzhAHCEpf8SOarwpzpf8pNBY2k5/oM34UHldUwp8VKI7uxct2HxSRdJjBaZeESzcxA==", + "dev": true + } + } + }, + "browserstack": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/browserstack/-/browserstack-1.5.2.tgz", + "integrity": "sha512-+6AFt9HzhKykcPF79W6yjEUJcdvZOV0lIXdkORXMJftGrDl0OKWqRF4GHqpDNkxiceDT/uB7Fb/aDwktvXX7dg==", + "dev": true, + "requires": { + "https-proxy-agent": "^2.2.1" + } + }, + "buffer": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz", + "integrity": "sha1-bRu2AbB6TvztlwlBMgkwJ8lbwpg=", + "dev": true, + "requires": { + "base64-js": "^1.0.2", + "ieee754": "^1.1.4", + "isarray": "^1.0.0" + } + }, + "buffer-alloc": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/buffer-alloc/-/buffer-alloc-1.2.0.tgz", + "integrity": "sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow==", + "dev": true, + "requires": { + "buffer-alloc-unsafe": "^1.1.0", + "buffer-fill": "^1.0.0" + } + }, + "buffer-alloc-unsafe": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz", + "integrity": "sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg==", + "dev": true + }, + "buffer-equal": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/buffer-equal/-/buffer-equal-1.0.0.tgz", + "integrity": "sha1-WWFrSYME1Var1GaWayLu2j7KX74=", + "dev": true + }, + "buffer-fill": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/buffer-fill/-/buffer-fill-1.0.0.tgz", + "integrity": "sha1-+PeLdniYiO858gXNY39o5wISKyw=", + "dev": true + }, + "buffer-from": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", + "dev": true + }, + "buffer-indexof": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-indexof/-/buffer-indexof-1.1.1.tgz", + "integrity": "sha512-4/rOEg86jivtPTeOUUT61jJO1Ya1TrR/OkqCSZDyq84WJh3LuuiphBYJN+fm5xufIk4XAFcEwte/8WzC8If/1g==", + "dev": true + }, + "buffer-xor": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz", + "integrity": "sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk=", + "dev": true + }, + "builder-util": { + "version": "21.2.0", + "resolved": "https://registry.npmjs.org/builder-util/-/builder-util-21.2.0.tgz", + "integrity": "sha512-Nd6CUb6YgDY8EXAXEIegx+1kzKqyFQ5ZM5BoYkeunAlwz/zDJoH1UCyULjoS5wQe5czNClFQy07zz2bzYD0Z4A==", + "dev": true, + "requires": { + "7zip-bin": "~5.0.3", + "@types/debug": "^4.1.4", + "app-builder-bin": "3.4.3", + "bluebird-lst": "^1.0.9", + "builder-util-runtime": "8.3.0", + "chalk": "^2.4.2", + "debug": "^4.1.1", + "fs-extra": "^8.1.0", + "is-ci": "^2.0.0", + "js-yaml": "^3.13.1", + "source-map-support": "^0.5.13", + "stat-mode": "^0.3.0", + "temp-file": "^3.3.4" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "dev": true, + "requires": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + } + }, + "graceful-fs": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.1.tgz", + "integrity": "sha512-b9usnbDGnD928gJB3LrCmxoibr3VE4U2SMo5PBuBnokWyDADTqDPXg4YpwKF1trpH+UbGp7QLicO3+aWEy0+mw==", + "dev": true + }, + "js-yaml": { + "version": "3.13.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", + "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "source-map-support": { + "version": "0.5.13", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", + "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + } + } + }, + "builder-util-runtime": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/builder-util-runtime/-/builder-util-runtime-8.3.0.tgz", + "integrity": "sha512-CSOdsYqf4RXIHh1HANPbrZHlZ9JQJXSuDDloblZPcWQVN62inyYoTQuSmY3KrgefME2Sv3Kn2MxHvbGQHRf8Iw==", + "dev": true, + "requires": { + "debug": "^4.1.1", + "sax": "^1.2.4" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "sax": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", + "dev": true + } + } + }, + "builtin-modules": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", + "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=", + "dev": true + }, + "builtin-status-codes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz", + "integrity": "sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug=", + "dev": true + }, + "builtins": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/builtins/-/builtins-1.0.3.tgz", + "integrity": "sha1-y5T662HIaWRR2zZTThQi+U8K7og=", + "dev": true + }, + "bytebuffer": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/bytebuffer/-/bytebuffer-5.0.1.tgz", + "integrity": "sha1-WC7qSxqHO20CCkjVjfhfC7ps/d0=", + "requires": { + "long": "~3" + } + }, + "bytes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", + "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=", + "dev": true + }, + "cacache": { + "version": "10.0.4", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-10.0.4.tgz", + "integrity": "sha512-Dph0MzuH+rTQzGPNT9fAnrPmMmjKfST6trxJeK7NQuHRaVw24VzPRWTmg9MpcwOVQZO0E1FBICUlFeNaKPIfHA==", + "dev": true, + "requires": { + "bluebird": "^3.5.1", + "chownr": "^1.0.1", + "glob": "^7.1.2", + "graceful-fs": "^4.1.11", + "lru-cache": "^4.1.1", + "mississippi": "^2.0.0", + "mkdirp": "^0.5.1", + "move-concurrently": "^1.0.1", + "promise-inflight": "^1.0.1", + "rimraf": "^2.6.2", + "ssri": "^5.2.4", + "unique-filename": "^1.1.0", + "y18n": "^4.0.0" + } + }, + "cache-base": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", + "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", + "dev": true, + "requires": { + "collection-visit": "^1.0.0", + "component-emitter": "^1.2.1", + "get-value": "^2.0.6", + "has-value": "^1.0.0", + "isobject": "^3.0.1", + "set-value": "^2.0.0", + "to-object-path": "^0.3.0", + "union-value": "^1.0.0", + "unset-value": "^1.0.0" + } + }, + "cacheable-request": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-6.1.0.tgz", + "integrity": "sha512-Oj3cAGPCqOZX7Rz64Uny2GYAZNliQSqfbePrgAQ1wKAihYmCUnraBtJtKcGR4xz7wF+LoJC+ssFZvv5BgF9Igg==", + "dev": true, + "requires": { + "clone-response": "^1.0.2", + "get-stream": "^5.1.0", + "http-cache-semantics": "^4.0.0", + "keyv": "^3.0.0", + "lowercase-keys": "^2.0.0", + "normalize-url": "^4.1.0", + "responselike": "^1.0.2" + }, + "dependencies": { + "get-stream": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.1.0.tgz", + "integrity": "sha512-EXr1FOzrzTfGeL0gQdeFEvOMm2mzMOglyiOXSTpPC+iAjAKftbr3jpCMWynogwYnM+eSj9sHGc6wjIcDvYiygw==", + "dev": true, + "requires": { + "pump": "^3.0.0" + } + }, + "http-cache-semantics": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.0.3.tgz", + "integrity": "sha512-TcIMG3qeVLgDr1TEd2XvHaTnMPwYQUQMIBLy+5pLSDKYFc7UIqj39w8EGzZkaxoLv/l2K8HaI0t5AVA+YYgUew==", + "dev": true + }, + "lowercase-keys": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", + "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", + "dev": true + }, + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + } + } + }, + "calendar-utils": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/calendar-utils/-/calendar-utils-0.2.3.tgz", + "integrity": "sha512-wXxvJX/fdVZqrHeyxEWq5kbwALXrjifbY2L3fXvuS815cCwoWv+Uuiu0jkfqZNTtOSKaTXHdMkIhmLRqcC0E5g==" + }, + "callsite": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/callsite/-/callsite-1.0.0.tgz", + "integrity": "sha1-KAOY5dZkvXQDi28JBRU+borxvCA=", + "dev": true + }, + "camelcase": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-2.1.1.tgz", + "integrity": "sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8=" + }, + "camelcase-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz", + "integrity": "sha1-MIvur/3ygRkFHvodkyITyRuPkuc=", + "dev": true, + "requires": { + "camelcase": "^2.0.0", + "map-obj": "^1.0.0" + } + }, + "caniuse-lite": { + "version": "1.0.30000939", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000939.tgz", + "integrity": "sha512-oXB23ImDJOgQpGjRv1tCtzAvJr4/OvrHi5SO2vUgB0g0xpdZZoA/BxfImiWfdwoYdUTtQrPsXsvYU/dmCSM8gg==", + "dev": true + }, + "canonical-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/canonical-path/-/canonical-path-1.0.0.tgz", + "integrity": "sha512-feylzsbDxi1gPZ1IjystzIQZagYYLvfKrSuygUCgf7z6x790VEzze5QEkdSV1U58RA7Hi0+v6fv4K54atOzATg==", + "dev": true + }, + "caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=", + "dev": true + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "dependencies": { + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "chardet": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", + "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", + "dev": true + }, + "chart.js": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-2.5.0.tgz", + "integrity": "sha1-/m51Gok3afVucr7lrZEgfhxZKVc=", + "requires": { + "chartjs-color": "^2.0.0", + "moment": "^2.10.6" + } + }, + "chartjs-color": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/chartjs-color/-/chartjs-color-2.3.0.tgz", + "integrity": "sha512-hEvVheqczsoHD+fZ+tfPUE+1+RbV6b+eksp2LwAhwRTVXEjCSEavvk+Hg3H6SZfGlPh/UfmWKGIvZbtobOEm3g==", + "requires": { + "chartjs-color-string": "^0.6.0", + "color-convert": "^0.5.3" + }, + "dependencies": { + "color-convert": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-0.5.3.tgz", + "integrity": "sha1-vbbGnOZg+t/+CwAHzER+G59ygr0=" + } + } + }, + "chartjs-color-string": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/chartjs-color-string/-/chartjs-color-string-0.6.0.tgz", + "integrity": "sha512-TIB5OKn1hPJvO7JcteW4WY/63v6KwEdt6udfnDE9iCAZgy+V4SrbSxoIbTw/xkUIapjEI4ExGtD0+6D3KyFd7A==", + "requires": { + "color-name": "^1.0.0" + } + }, + "chokidar": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.0.4.tgz", + "integrity": "sha512-z9n7yt9rOvIJrMhvDtDictKrkFHeihkNl6uWMmZlmL6tJtX9Cs+87oK+teBx+JIgzvbX3yZHT3eF8vpbDxHJXQ==", + "dev": true, + "requires": { + "anymatch": "^2.0.0", + "async-each": "^1.0.0", + "braces": "^2.3.0", + "fsevents": "^1.2.2", + "glob-parent": "^3.1.0", + "inherits": "^2.0.1", + "is-binary-path": "^1.0.0", + "is-glob": "^4.0.0", + "lodash.debounce": "^4.0.8", + "normalize-path": "^2.1.1", + "path-is-absolute": "^1.0.0", + "readdirp": "^2.0.0", + "upath": "^1.0.5" + } + }, + "chownr": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.1.tgz", + "integrity": "sha512-j38EvO5+LHX84jlo6h4UzmOwi0UgW61WRyPtJz4qaadK5eY3BTS5TY/S1Stc3Uk2lIM6TPevAlULiEJwie860g==", + "dev": true + }, + "chrome-trace-event": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.0.tgz", + "integrity": "sha512-xDbVgyfDTT2piup/h8dK/y4QZfJRSa73bw1WZ8b4XM1o7fsFubUVGYcE+1ANtOzJJELGpYoG2961z0Z6OAld9A==", + "dev": true, + "requires": { + "tslib": "^1.9.0" + } + }, + "chromium-pickle-js": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/chromium-pickle-js/-/chromium-pickle-js-0.2.0.tgz", + "integrity": "sha1-BKEGZywYsIWrd02YPfo+oTjyIgU=", + "dev": true + }, + "ci-info": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", + "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", + "dev": true + }, + "cipher-base": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz", + "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "circular-dependency-plugin": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/circular-dependency-plugin/-/circular-dependency-plugin-5.0.2.tgz", + "integrity": "sha512-oC7/DVAyfcY3UWKm0sN/oVoDedQDQiw/vIiAnuTWTpE5s0zWf7l3WY417Xw/Fbi/QbAjctAkxgMiS9P0s3zkmA==", + "dev": true + }, + "circular-json": { + "version": "0.5.9", + "resolved": "https://registry.npmjs.org/circular-json/-/circular-json-0.5.9.tgz", + "integrity": "sha512-4ivwqHpIFJZBuhN3g/pEcdbnGUywkBblloGbkglyloVjjR3uT6tieI89MVOfbP2tHX5sgb01FuLgAOzebNlJNQ==", + "dev": true + }, + "class-utils": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", + "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", + "dev": true, + "requires": { + "arr-union": "^3.1.0", + "define-property": "^0.2.5", + "isobject": "^3.0.0", + "static-extend": "^0.1.1" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + } + } + }, + "classlist.js": { + "version": "1.1.20150312", + "resolved": "https://registry.npmjs.org/classlist.js/-/classlist.js-1.1.20150312.tgz", + "integrity": "sha1-HXCEL3Ai8I2awIbOaeWyUPLFd4k=" + }, + "clean-css": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-4.2.1.tgz", + "integrity": "sha512-4ZxI6dy4lrY6FHzfiy1aEOXgu4LIsW2MhwG0VBKdcoGoH/XLFgaHSdLTGr4O8Be6A8r3MOphEiI8Gc1n0ecf3g==", + "dev": true, + "requires": { + "source-map": "~0.6.0" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "cli-boxes": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-2.2.0.tgz", + "integrity": "sha512-gpaBrMAizVEANOpfZp/EEUixTXDyGt7DFzdK5hU+UbWt/J0lB0w20ncZj59Z9a93xHb9u12zF5BS6i9RKbtg4w==", + "dev": true + }, + "cli-cursor": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz", + "integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=", + "dev": true, + "requires": { + "restore-cursor": "^2.0.0" + } + }, + "cli-width": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.0.tgz", + "integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=", + "dev": true + }, + "cliui": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz", + "integrity": "sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=", + "requires": { + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wrap-ansi": "^2.0.0" + } + }, + "clone": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", + "integrity": "sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18=" + }, + "clone-deep": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-2.0.2.tgz", + "integrity": "sha512-SZegPTKjCgpQH63E+eN6mVEEPdQBOUzjyJm5Pora4lrwWRFS8I0QAxV/KD6vV/i0WuijHZWQC1fMsPEdxfdVCQ==", + "dev": true, + "requires": { + "for-own": "^1.0.0", + "is-plain-object": "^2.0.4", + "kind-of": "^6.0.0", + "shallow-clone": "^1.0.0" + } + }, + "clone-response": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.2.tgz", + "integrity": "sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws=", + "dev": true, + "requires": { + "mimic-response": "^1.0.0" + } + }, + "co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", + "dev": true + }, + "code-point-at": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" + }, + "codelyzer": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/codelyzer/-/codelyzer-4.5.0.tgz", + "integrity": "sha512-oO6vCkjqsVrEsmh58oNlnJkRXuA30hF8cdNAQV9DytEalDwyOFRvHMnlKFzmOStNerOmPGZU9GAHnBo4tGvtiQ==", + "dev": true, + "requires": { + "app-root-path": "^2.1.0", + "css-selector-tokenizer": "^0.7.0", + "cssauron": "^1.4.0", + "semver-dsl": "^1.0.1", + "source-map": "^0.5.7", + "sprintf-js": "^1.1.1" + }, + "dependencies": { + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + }, + "sprintf-js": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.2.tgz", + "integrity": "sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug==", + "dev": true + } + } + }, + "collection-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", + "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=", + "dev": true, + "requires": { + "map-visit": "^1.0.0", + "object-visit": "^1.0.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + }, + "colors": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.1.2.tgz", + "integrity": "sha1-FopHAXVran9RoSzgyXv6KMCE7WM=", + "dev": true + }, + "colour": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/colour/-/colour-0.7.1.tgz", + "integrity": "sha1-nLFpkX7F0SwHNtPoaFdG3xyt93g=" + }, + "combine-lists": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/combine-lists/-/combine-lists-1.0.1.tgz", + "integrity": "sha1-RYwH4J4NkA/Ci3Cj/sLazR0st/Y=", + "dev": true, + "requires": { + "lodash": "^4.5.0" + } + }, + "combined-stream": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.7.tgz", + "integrity": "sha512-brWl9y6vOB1xYPZcpZde3N9zDByXTosAeMDo4p1wzo6UMOX4vumB+TP1RZ76sfE6Md68Q0NJSrE/gbezd4Ul+w==", + "dev": true, + "requires": { + "delayed-stream": "~1.0.0" + } + }, + "commander": { + "version": "2.17.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.17.1.tgz", + "integrity": "sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg==", + "dev": true + }, + "commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=", + "dev": true + }, + "compare-version": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/compare-version/-/compare-version-0.1.2.tgz", + "integrity": "sha1-AWLsLZNR9d3VmpICy6k1NmpyUIA=", + "dev": true + }, + "compare-versions": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/compare-versions/-/compare-versions-3.4.0.tgz", + "integrity": "sha512-tK69D7oNXXqUW3ZNo/z7NXTEz22TCF0pTE+YF9cxvaAM9XnkLo1fV621xCLrRR6aevJlKxExkss0vWqUCUpqdg==", + "dev": true + }, + "component-bind": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/component-bind/-/component-bind-1.0.0.tgz", + "integrity": "sha1-AMYIq33Nk4l8AAllGx06jh5zu9E=", + "dev": true + }, + "component-emitter": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", + "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=", + "dev": true + }, + "component-inherit": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/component-inherit/-/component-inherit-0.0.3.tgz", + "integrity": "sha1-ZF/ErfWLcrZJ1crmUTVhnbJv8UM=", + "dev": true + }, + "compressible": { + "version": "2.0.16", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.16.tgz", + "integrity": "sha512-JQfEOdnI7dASwCuSPWIeVYwc/zMsu/+tRhoUvEfXz2gxOA2DNjmG5vhtFdBlhWPPGo+RdT9S3tgc/uH5qgDiiA==", + "dev": true, + "requires": { + "mime-db": ">= 1.38.0 < 2" + } + }, + "compression": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.3.tgz", + "integrity": "sha512-HSjyBG5N1Nnz7tF2+O7A9XUhyjru71/fwgNb7oIsEVHR0WShfs2tIS/EySLgiTe98aOK18YDlMXpzjCXY/n9mg==", + "dev": true, + "requires": { + "accepts": "~1.3.5", + "bytes": "3.0.0", + "compressible": "~2.0.14", + "debug": "2.6.9", + "on-headers": "~1.0.1", + "safe-buffer": "5.1.2", + "vary": "~1.1.2" + } + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" + }, + "concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + } + }, + "configstore": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/configstore/-/configstore-4.0.0.tgz", + "integrity": "sha512-CmquAXFBocrzaSM8mtGPMM/HiWmyIpr4CcJl/rgY2uCObZ/S7cKU0silxslqJejl+t/T9HS8E0PUNQD81JGUEQ==", + "requires": { + "dot-prop": "^4.1.0", + "graceful-fs": "^4.1.2", + "make-dir": "^1.0.0", + "unique-string": "^1.0.0", + "write-file-atomic": "^2.0.0", + "xdg-basedir": "^3.0.0" + } + }, + "connect": { + "version": "3.6.6", + "resolved": "https://registry.npmjs.org/connect/-/connect-3.6.6.tgz", + "integrity": "sha1-Ce/2xVr3I24TcTWnJXSFi2eG9SQ=", + "dev": true, + "requires": { + "debug": "2.6.9", + "finalhandler": "1.1.0", + "parseurl": "~1.3.2", + "utils-merge": "1.0.1" + }, + "dependencies": { + "finalhandler": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.0.tgz", + "integrity": "sha1-zgtoVbRYU+eRsvzGgARtiCU91/U=", + "dev": true, + "requires": { + "debug": "2.6.9", + "encodeurl": "~1.0.1", + "escape-html": "~1.0.3", + "on-finished": "~2.3.0", + "parseurl": "~1.3.2", + "statuses": "~1.3.1", + "unpipe": "~1.0.0" + } + }, + "statuses": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.3.1.tgz", + "integrity": "sha1-+vUbnrdKrvOzrPStX2Gr8ky3uT4=", + "dev": true + } + } + }, + "connect-history-api-fallback": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-1.6.0.tgz", + "integrity": "sha512-e54B99q/OUoH64zYYRf3HBP5z24G38h5D3qXu23JGRoigpX5Ss4r9ZnDk3g0Z8uQC2x2lPaJ+UlWBc1ZWBWdLg==", + "dev": true + }, + "console-browserify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.1.0.tgz", + "integrity": "sha1-8CQcRXMKn8YyOyBtvzjtx0HQuxA=", + "dev": true, + "requires": { + "date-now": "^0.1.4" + } + }, + "console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=", + "dev": true, + "optional": true + }, + "constants-browserify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/constants-browserify/-/constants-browserify-1.0.0.tgz", + "integrity": "sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U=", + "dev": true + }, + "content-disposition": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz", + "integrity": "sha1-DPaLud318r55YcOoUXjLhdunjLQ=", + "dev": true + }, + "content-type": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", + "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", + "dev": true + }, + "contra": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/contra/-/contra-1.9.4.tgz", + "integrity": "sha1-9TveQtfltZhcrk2ZqNYQUm3o8o0=", + "requires": { + "atoa": "1.0.0", + "ticky": "1.0.1" + } + }, + "convert-source-map": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.6.0.tgz", + "integrity": "sha512-eFu7XigvxdZ1ETfbgPBohgyQ/Z++C0eEhTor0qRwBw9unw+L0/6V8wkSuGgzdThkiS5lSpdptOQPD8Ak40a+7A==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.1" + } + }, + "cookie": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", + "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=", + "dev": true + }, + "cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=", + "dev": true + }, + "copy-concurrently": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/copy-concurrently/-/copy-concurrently-1.0.5.tgz", + "integrity": "sha512-f2domd9fsVDFtaFcbaRZuYXwtdmnzqbADSwhSWYxYB/Q8zsdUUFMXVRwXGDMWmbEzAn1kdRrtI1T/KTFOL4X2A==", + "dev": true, + "requires": { + "aproba": "^1.1.1", + "fs-write-stream-atomic": "^1.0.8", + "iferr": "^0.1.5", + "mkdirp": "^0.5.1", + "rimraf": "^2.5.4", + "run-queue": "^1.0.0" + } + }, + "copy-descriptor": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", + "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=", + "dev": true + }, + "copy-webpack-plugin": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-4.3.0.tgz", + "integrity": "sha512-5o1/xyWm8OYDmLFKAWMuPU3A/jZ4Z6kZSZGh36KD2XmtxnRa8lQyLx7bCNQm08BPaR/oqUdtJOr9jWfnYINp9g==", + "dev": true, + "requires": { + "cacache": "^10.0.1", + "find-cache-dir": "^1.0.0", + "globby": "^7.1.1", + "is-glob": "^4.0.0", + "loader-utils": "^0.2.15", + "lodash": "^4.3.0", + "minimatch": "^3.0.4", + "p-limit": "^1.0.0", + "pify": "^3.0.0", + "serialize-javascript": "^1.4.0" + }, + "dependencies": { + "big.js": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/big.js/-/big.js-3.2.0.tgz", + "integrity": "sha512-+hN/Zh2D08Mx65pZ/4g5bsmNiZUuChDiQfTUQ7qJr4/kuopCr88xZsAXv6mBoZEsUI4OuGHlX59qE94K2mMW8Q==", + "dev": true + }, + "json5": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-0.5.1.tgz", + "integrity": "sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE=", + "dev": true + }, + "loader-utils": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-0.2.17.tgz", + "integrity": "sha1-+G5jdNQyBabmxg6RlvF8Apm/s0g=", + "dev": true, + "requires": { + "big.js": "^3.1.3", + "emojis-list": "^2.0.0", + "json5": "^0.5.0", + "object-assign": "^4.0.1" + } + } + } + }, + "core-js": { + "version": "2.6.5", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.5.tgz", + "integrity": "sha512-klh/kDpwX8hryYL14M9w/xei6vrv6sE8gTHDG7/T/+SEovB/G4ejwcfE/CBzO6Edsu+OETZMZ3wcX/EjUkrl5A==" + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", + "dev": true + }, + "cosmiconfig": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-4.0.0.tgz", + "integrity": "sha512-6e5vDdrXZD+t5v0L8CrurPeybg4Fmf+FCSYxXKYVAqLUtyCSbuyqE059d0kDthTNRzKVjL7QMgNpEUlsoYH3iQ==", + "dev": true, + "requires": { + "is-directory": "^0.3.1", + "js-yaml": "^3.9.0", + "parse-json": "^4.0.0", + "require-from-string": "^2.0.1" + }, + "dependencies": { + "parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", + "dev": true, + "requires": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + } + } + } + }, + "create-ecdh": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.3.tgz", + "integrity": "sha512-GbEHQPMOswGpKXM9kCWVrremUcBmjteUaQ01T9rkKCPDXfUHX0IoP9LpHYo2NPFampa4e+/pFDc3jQdxrxQLaw==", + "dev": true, + "requires": { + "bn.js": "^4.1.0", + "elliptic": "^6.0.0" + } + }, + "create-hash": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", + "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", + "dev": true, + "requires": { + "cipher-base": "^1.0.1", + "inherits": "^2.0.1", + "md5.js": "^1.3.4", + "ripemd160": "^2.0.1", + "sha.js": "^2.4.0" + } + }, + "create-hmac": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", + "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", + "dev": true, + "requires": { + "cipher-base": "^1.0.3", + "create-hash": "^1.1.0", + "inherits": "^2.0.1", + "ripemd160": "^2.0.0", + "safe-buffer": "^5.0.1", + "sha.js": "^2.4.8" + } + }, + "cross-spawn": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-3.0.1.tgz", + "integrity": "sha1-ElYDfsufDF9549bvE14wdwGEuYI=", + "dev": true, + "optional": true, + "requires": { + "lru-cache": "^4.0.1", + "which": "^1.2.9" + } + }, + "cross-zip": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/cross-zip/-/cross-zip-2.1.5.tgz", + "integrity": "sha1-xdGgaksaEqAzBk+UbrWubUvc8+E=", + "dev": true, + "requires": { + "rimraf": "^2.5.2" + } + }, + "crossvent": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/crossvent/-/crossvent-1.5.4.tgz", + "integrity": "sha1-2ixPj0DJR4JRe/K+7BBEFIGUq5I=", + "requires": { + "custom-event": "1.0.0" + }, + "dependencies": { + "custom-event": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/custom-event/-/custom-event-1.0.0.tgz", + "integrity": "sha1-LkYovhncSyFLXAJjDFlx6BFhgGI=" + } + } + }, + "crypto-browserify": { + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.0.tgz", + "integrity": "sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg==", + "dev": true, + "requires": { + "browserify-cipher": "^1.0.0", + "browserify-sign": "^4.0.0", + "create-ecdh": "^4.0.0", + "create-hash": "^1.1.0", + "create-hmac": "^1.1.0", + "diffie-hellman": "^5.0.0", + "inherits": "^2.0.1", + "pbkdf2": "^3.0.3", + "public-encrypt": "^4.0.0", + "randombytes": "^2.0.0", + "randomfill": "^1.0.3" + } + }, + "crypto-random-string": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-1.0.0.tgz", + "integrity": "sha1-ojD2T1aDEOFJgAmUB5DsmVRbyn4=" + }, + "css-parse": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/css-parse/-/css-parse-1.7.0.tgz", + "integrity": "sha1-Mh9s9zeCpv91ERE5D8BeLGV9jJs=", + "dev": true + }, + "css-selector-tokenizer": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/css-selector-tokenizer/-/css-selector-tokenizer-0.7.1.tgz", + "integrity": "sha512-xYL0AMZJ4gFzJQsHUKa5jiWWi2vH77WVNg7JYRyewwj6oPh4yb/y6Y9ZCw9dsj/9UauMhtuxR+ogQd//EdEVNA==", + "dev": true, + "requires": { + "cssesc": "^0.1.0", + "fastparse": "^1.1.1", + "regexpu-core": "^1.0.0" + } + }, + "cssauron": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/cssauron/-/cssauron-1.4.0.tgz", + "integrity": "sha1-pmAt/34EqDBtwNuaVR6S6LVmKtg=", + "dev": true, + "requires": { + "through": "X.X.X" + } + }, + "cssesc": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-0.1.0.tgz", + "integrity": "sha1-yBSQPkViM3GgR3tAEJqq++6t27Q=", + "dev": true + }, + "cuint": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/cuint/-/cuint-0.2.2.tgz", + "integrity": "sha1-QICG1AlVDCYxFVYZ6fp7ytw7mRs=", + "dev": true + }, + "currently-unhandled": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz", + "integrity": "sha1-mI3zP+qxke95mmE2nddsF635V+o=", + "dev": true, + "requires": { + "array-find-index": "^1.0.1" + } + }, + "custom-event": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/custom-event/-/custom-event-1.0.1.tgz", + "integrity": "sha1-XQKkaFCt8bSjF5RqOSj8y1v9BCU=", + "dev": true + }, + "cyclist": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/cyclist/-/cyclist-0.2.2.tgz", + "integrity": "sha1-GzN5LhHpFKL9bW7WRHRkRE5fpkA=", + "dev": true + }, + "dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "dev": true, + "requires": { + "assert-plus": "^1.0.0" + } + }, + "date-fns": { + "version": "1.28.5", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-1.28.5.tgz", + "integrity": "sha1-JXz8RdMi30XvVlhmWWfuhBzXP68=" + }, + "date-format": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/date-format/-/date-format-1.2.0.tgz", + "integrity": "sha1-YV6CjiM90aubua4JUODOzPpuytg=", + "dev": true + }, + "date-now": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/date-now/-/date-now-0.1.4.tgz", + "integrity": "sha1-6vQ5/U1ISK105cx9vvIAZyueNFs=", + "dev": true + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=" + }, + "decode-uri-component": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", + "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=", + "dev": true + }, + "decompress-response": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz", + "integrity": "sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M=", + "dev": true, + "requires": { + "mimic-response": "^1.0.0" + } + }, + "deep-equal": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.0.1.tgz", + "integrity": "sha1-9dJgKStmDghO/0zbyfCK0yR0SLU=" + }, + "deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "dev": true + }, + "default-gateway": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/default-gateway/-/default-gateway-2.7.2.tgz", + "integrity": "sha512-lAc4i9QJR0YHSDFdzeBQKfZ1SRDG3hsJNEkrpcZa8QhBfidLAilT60BDEIVUUGqosFp425KOgB3uYqcnQrWafQ==", + "dev": true, + "requires": { + "execa": "^0.10.0", + "ip-regex": "^2.1.0" + } + }, + "default-require-extensions": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-2.0.0.tgz", + "integrity": "sha1-9fj7sYp9bVCyH2QfZJ67Uiz+JPc=", + "dev": true, + "requires": { + "strip-bom": "^3.0.0" + }, + "dependencies": { + "strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", + "dev": true + } + } + }, + "defer-to-connect": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-1.0.2.tgz", + "integrity": "sha512-k09hcQcTDY+cwgiwa6PYKLm3jlagNzQ+RSvhjzESOGOx+MNOuXkxTfEvPrO1IOQ81tArCFYQgi631clB70RpQw==", + "dev": true + }, + "define-property": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", + "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", + "dev": true, + "requires": { + "is-descriptor": "^1.0.2", + "isobject": "^3.0.1" + }, + "dependencies": { + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + } + } + }, + "del": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/del/-/del-3.0.0.tgz", + "integrity": "sha1-U+z2mf/LyzljdpGrE7rxYIGXZuU=", + "dev": true, + "requires": { + "globby": "^6.1.0", + "is-path-cwd": "^1.0.0", + "is-path-in-cwd": "^1.0.0", + "p-map": "^1.1.1", + "pify": "^3.0.0", + "rimraf": "^2.2.8" + }, + "dependencies": { + "globby": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-6.1.0.tgz", + "integrity": "sha1-9abXDoOV4hyFj7BInWTfAkJNUGw=", + "dev": true, + "requires": { + "array-union": "^1.0.1", + "glob": "^7.0.3", + "object-assign": "^4.0.1", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0" + }, + "dependencies": { + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true + } + } + } + } + }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", + "dev": true + }, + "delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=", + "dev": true, + "optional": true + }, + "depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=", + "dev": true + }, + "dependency-graph": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/dependency-graph/-/dependency-graph-0.7.2.tgz", + "integrity": "sha512-KqtH4/EZdtdfWX0p6MGP9jljvxSY6msy/pRUD4jgNwVpv3v1QmNLlsB3LDSSUg79BRVSn7jI1QPRtArGABovAQ==", + "dev": true + }, + "des.js": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.0.0.tgz", + "integrity": "sha1-wHTS4qpqipoH29YfmhXCzYPsjsw=", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0" + } + }, + "destroy": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", + "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=", + "dev": true + }, + "detect-indent": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-4.0.0.tgz", + "integrity": "sha1-920GQ1LN9Docts5hnE7jqUdd4gg=", + "dev": true, + "requires": { + "repeating": "^2.0.0" + } + }, + "detect-node": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.0.4.tgz", + "integrity": "sha512-ZIzRpLJrOj7jjP2miAtgqIfmzbxa4ZOr5jJc601zklsfEx9oTzmmj2nVpIPRpNlRTIh8lc1kyViIY7BWSGNmKw==", + "dev": true + }, + "di": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/di/-/di-0.0.1.tgz", + "integrity": "sha1-gGZJMmzqp8qjMG112YXqJ0i6kTw=", + "dev": true + }, + "diff": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", + "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", + "dev": true + }, + "diffie-hellman": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", + "integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==", + "dev": true, + "requires": { + "bn.js": "^4.1.0", + "miller-rabin": "^4.0.0", + "randombytes": "^2.0.0" + } + }, + "dir-compare": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/dir-compare/-/dir-compare-1.8.0.tgz", + "integrity": "sha512-Ork/J37pKE6M+Fvl98OB+iAuZ5CG7d2d8DIMmiCDEZVAbEWn2lp+ghSbc1lgkgVX91p8jMQs2DeTMJvpMeU9+A==", + "dev": true, + "requires": { + "bluebird": "3.4.1", + "buffer-equal": "1.0.0", + "colors": "1.0.3", + "commander": "2.9.0", + "minimatch": "3.0.2" + }, + "dependencies": { + "bluebird": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.4.1.tgz", + "integrity": "sha1-tzHd9I4t077awudeEhWhG8uR+gc=", + "dev": true + }, + "colors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.0.3.tgz", + "integrity": "sha1-BDP0TYCWgP3rYO0mDxsMJi6CpAs=", + "dev": true + }, + "commander": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.9.0.tgz", + "integrity": "sha1-nJkJQXbhIkDLItbFFGCYQA/g99Q=", + "dev": true, + "requires": { + "graceful-readlink": ">= 1.0.0" + } + }, + "minimatch": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.2.tgz", + "integrity": "sha1-DzmKcwDqRB6cNIyD2Yq4ydv5xAo=", + "dev": true, + "requires": { + "brace-expansion": "^1.0.0" + } + } + } + }, + "dir-glob": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-2.2.2.tgz", + "integrity": "sha512-f9LBi5QWzIW3I6e//uxZoLBlUt9kcp66qo0sSCxL6YZKc75R1c4MFCoe/LaZiBGmgujvQdxc5Bn3QhfyvK5Hsw==", + "dev": true, + "requires": { + "path-type": "^3.0.0" + } + }, + "dmg-builder": { + "version": "21.2.0", + "resolved": "https://registry.npmjs.org/dmg-builder/-/dmg-builder-21.2.0.tgz", + "integrity": "sha512-9cJEclnGy7EyKFCoHDYDf54pub/t92CQapyiUxU0w9Bj2vUvfoDagP1PMiX4XD5rPp96141h9A+QN0OB4VgvQg==", + "dev": true, + "requires": { + "app-builder-lib": "~21.2.0", + "bluebird-lst": "^1.0.9", + "builder-util": "~21.2.0", + "fs-extra": "^8.1.0", + "iconv-lite": "^0.5.0", + "js-yaml": "^3.13.1", + "sanitize-filename": "^1.6.2" + }, + "dependencies": { + "fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "dev": true, + "requires": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + } + }, + "graceful-fs": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.1.tgz", + "integrity": "sha512-b9usnbDGnD928gJB3LrCmxoibr3VE4U2SMo5PBuBnokWyDADTqDPXg4YpwKF1trpH+UbGp7QLicO3+aWEy0+mw==", + "dev": true + }, + "iconv-lite": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.5.0.tgz", + "integrity": "sha512-NnEhI9hIEKHOzJ4f697DMz9IQEXr/MMJ5w64vN2/4Ai+wRnvV7SBrL0KLoRlwaKVghOc7LQ5YkPLuX146b6Ydw==", + "dev": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "js-yaml": { + "version": "3.13.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", + "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "sanitize-filename": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/sanitize-filename/-/sanitize-filename-1.6.2.tgz", + "integrity": "sha512-cmTzND7RMxUB+f7gI+4+KAVHWEg0lfXvQJdko+FXDP5bNbGIdx4KMP5pX6lv5jfT9jSf6OBbjyxjFtZQwYA/ig==", + "dev": true, + "requires": { + "truncate-utf8-bytes": "^1.0.0" + } + } + } + }, + "dns-equal": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/dns-equal/-/dns-equal-1.0.0.tgz", + "integrity": "sha1-s55/HabrCnW6nBcySzR1PEfgZU0=", + "dev": true + }, + "dns-packet": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-1.3.4.tgz", + "integrity": "sha512-BQ6F4vycLXBvdrJZ6S3gZewt6rcrks9KBgM9vrhW+knGRqc8uEdT7fuCwloc7nny5xNoMJ17HGH0R/6fpo8ECA==", + "dev": true, + "requires": { + "ip": "^1.1.0", + "safe-buffer": "^5.0.1" + } + }, + "dns-txt": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/dns-txt/-/dns-txt-2.0.2.tgz", + "integrity": "sha1-uR2Ab10nGI5Ks+fRB9iBocxGQrY=", + "dev": true, + "requires": { + "buffer-indexof": "^1.0.0" + } + }, + "dom-serialize": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/dom-serialize/-/dom-serialize-2.2.1.tgz", + "integrity": "sha1-ViromZ9Evl6jB29UGdzVnrQ6yVs=", + "dev": true, + "requires": { + "custom-event": "~1.0.0", + "ent": "~2.2.0", + "extend": "^3.0.0", + "void-elements": "^2.0.0" + } + }, + "domain-browser": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.2.0.tgz", + "integrity": "sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA==", + "dev": true + }, + "dot-prop": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-4.2.1.tgz", + "integrity": "sha512-l0p4+mIuJIua0mhxGoh4a+iNL9bmeK5DvnSVQa6T0OhrVmaEa1XScX5Etc673FePCJOArq/4Pa2cLGODUWTPOQ==", + "requires": { + "is-obj": "^1.0.0" + } + }, + "dotenv": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.0.0.tgz", + "integrity": "sha512-30xVGqjLjiUOArT4+M5q9sYdvuR4riM6yK9wMcas9Vbp6zZa+ocC9dp6QoftuhTPhFAiLK/0C5Ni2nou/Bk8lg==", + "dev": true + }, + "dotenv-expand": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-5.1.0.tgz", + "integrity": "sha512-YXQl1DSa4/PQyRfgrv6aoNjhasp/p4qs9FjJ4q4cQk+8m4r6k4ZSiEyytKG8f8W9gi8WsQtIObNmKd+tMzNTmA==", + "dev": true + }, + "dragula": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/dragula/-/dragula-3.7.2.tgz", + "integrity": "sha1-SjXJ05gf+sGpScKcpyhQWOhzk84=", + "requires": { + "contra": "1.9.4", + "crossvent": "1.5.4" + } + }, + "duplexer3": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz", + "integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=", + "dev": true + }, + "duplexify": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz", + "integrity": "sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==", + "dev": true, + "requires": { + "end-of-stream": "^1.0.0", + "inherits": "^2.0.1", + "readable-stream": "^2.0.0", + "stream-shift": "^1.0.0" + } + }, + "earcut": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/earcut/-/earcut-2.2.3.tgz", + "integrity": "sha512-iRDI1QeCQIhMCZk48DRDMVgQSSBDmbzzNhnxIo+pwx3swkfjMh6vh0nWLq1NdvGHLKH6wIrAM3vQWeTj6qeoug==" + }, + "ecc-jsbn": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", + "dev": true, + "requires": { + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" + } + }, + "ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=", + "dev": true + }, + "ejs": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-2.6.2.tgz", + "integrity": "sha512-PcW2a0tyTuPHz3tWyYqtK6r1fZ3gp+3Sop8Ph+ZYN81Ob5rwmbHEzaqs10N3BEsaGTkh/ooniXK+WwszGlc2+Q==", + "dev": true + }, + "electron": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/electron/-/electron-5.0.3.tgz", + "integrity": "sha512-kgnxPQoTNzu0lKC8ToAktt93mTimgqHZGFCVmTaFjs1G2Bk9VqKHljbQemH+REKAaw1aEgIRn2JCrTe0Dy7fVQ==", + "dev": true, + "requires": { + "@types/node": "^10.12.18", + "electron-download": "^4.1.0", + "extract-zip": "^1.0.3" + }, + "dependencies": { + "@types/node": { + "version": "10.14.9", + "resolved": "https://registry.npmjs.org/@types/node/-/node-10.14.9.tgz", + "integrity": "sha512-NelG/dSahlXYtSoVPErrp06tYFrvzj8XLWmKA+X8x0W//4MqbUyZu++giUG/v0bjAT6/Qxa8IjodrfdACyb0Fg==", + "dev": true + } + } + }, + "electron-builder": { + "version": "21.2.0", + "resolved": "https://registry.npmjs.org/electron-builder/-/electron-builder-21.2.0.tgz", + "integrity": "sha512-x8EXrqFbAb2L3N22YlGar3dGh8vwptbB3ovo3OF6K7NTpcsmM2zEoJv7GhFyX73rNzSG2HaWpXwGAtOp2JWiEw==", + "dev": true, + "requires": { + "app-builder-lib": "21.2.0", + "bluebird-lst": "^1.0.9", + "builder-util": "21.2.0", + "builder-util-runtime": "8.3.0", + "chalk": "^2.4.2", + "dmg-builder": "21.2.0", + "fs-extra": "^8.1.0", + "is-ci": "^2.0.0", + "lazy-val": "^1.0.4", + "read-config-file": "5.0.0", + "sanitize-filename": "^1.6.2", + "update-notifier": "^3.0.1", + "yargs": "^13.3.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true + }, + "cliui": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", + "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", + "dev": true, + "requires": { + "string-width": "^3.1.0", + "strip-ansi": "^5.2.0", + "wrap-ansi": "^5.1.0" + } + }, + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "requires": { + "locate-path": "^3.0.0" + } + }, + "fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "dev": true, + "requires": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + } + }, + "get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true + }, + "graceful-fs": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.1.tgz", + "integrity": "sha512-b9usnbDGnD928gJB3LrCmxoibr3VE4U2SMo5PBuBnokWyDADTqDPXg4YpwKF1trpH+UbGp7QLicO3+aWEy0+mw==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "p-limit": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.0.tgz", + "integrity": "sha512-pZbTJpoUsCzV48Mc9Nh51VbwO0X9cuPFE8gYwx9BTCt9SF8/b7Zljd2fVgOxhIF/HDTKgpVzs+GPhyKfjLLFRQ==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "requires": { + "p-limit": "^2.0.0" + } + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true + }, + "require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", + "dev": true + }, + "sanitize-filename": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/sanitize-filename/-/sanitize-filename-1.6.2.tgz", + "integrity": "sha512-cmTzND7RMxUB+f7gI+4+KAVHWEg0lfXvQJdko+FXDP5bNbGIdx4KMP5pX6lv5jfT9jSf6OBbjyxjFtZQwYA/ig==", + "dev": true, + "requires": { + "truncate-utf8-bytes": "^1.0.0" + } + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + }, + "which-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", + "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", + "dev": true + }, + "wrap-ansi": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", + "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.0", + "string-width": "^3.0.0", + "strip-ansi": "^5.0.0" + } + }, + "yargs": { + "version": "13.3.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.0.tgz", + "integrity": "sha512-2eehun/8ALW8TLoIl7MVaRUrg+yCnenu8B4kBlRxj3GJGDKU1Og7sMXPNm1BYyM1DOJmTZ4YeN/Nwxv+8XJsUA==", + "dev": true, + "requires": { + "cliui": "^5.0.0", + "find-up": "^3.0.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^3.0.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^13.1.1" + } + }, + "yargs-parser": { + "version": "13.1.2", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz", + "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==", + "dev": true, + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + } + } + }, + "electron-download": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/electron-download/-/electron-download-4.1.1.tgz", + "integrity": "sha512-FjEWG9Jb/ppK/2zToP+U5dds114fM1ZOJqMAR4aXXL5CvyPE9fiqBK/9YcwC9poIFQTEJk/EM/zyRwziziRZrg==", + "dev": true, + "requires": { + "debug": "^3.0.0", + "env-paths": "^1.0.0", + "fs-extra": "^4.0.1", + "minimist": "^1.2.0", + "nugget": "^2.0.1", + "path-exists": "^3.0.0", + "rc": "^1.2.1", + "semver": "^5.4.1", + "sumchecker": "^2.0.2" + }, + "dependencies": { + "debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } + } + }, + "electron-notarize": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/electron-notarize/-/electron-notarize-0.1.1.tgz", + "integrity": "sha512-TpKfJcz4LXl5jiGvZTs5fbEx+wUFXV5u8voeG5WCHWfY/cdgdD8lDZIZRqLVOtR3VO+drgJ9aiSHIO9TYn/fKg==", + "dev": true, + "requires": { + "debug": "^4.1.1", + "fs-extra": "^8.0.1" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "fs-extra": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.0.1.tgz", + "integrity": "sha512-W+XLrggcDzlle47X/XnS7FXrXu9sDo+Ze9zpndeBxdgv88FHLm1HtmkhEwavruS6koanBjp098rUpHs65EmG7A==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } + } + }, + "electron-osx-sign": { + "version": "0.4.11", + "resolved": "https://registry.npmjs.org/electron-osx-sign/-/electron-osx-sign-0.4.11.tgz", + "integrity": "sha512-VVd40nrnVqymvFrY9ZkOYgHJOvexHHYTR3di/SN+mjJ0OWhR1I8BRVj3U+Yamw6hnkZZNKZp52rqL5EFAAPFkQ==", + "dev": true, + "requires": { + "bluebird": "^3.5.0", + "compare-version": "^0.1.2", + "debug": "^2.6.8", + "isbinaryfile": "^3.0.2", + "minimist": "^1.2.0", + "plist": "^3.0.1" + } + }, + "electron-packager": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/electron-packager/-/electron-packager-14.0.0.tgz", + "integrity": "sha512-7GmpTAvMx9OR1/AZfcEmreVszMM0d64nzDyzzQytrjejZqygRyXOlRrQlR/TkLfymx9vZ5BHyo3KXokImdhhvA==", + "dev": true, + "requires": { + "@electron/get": "^1.3.0", + "asar": "^2.0.1", + "cross-zip": "^2.1.5", + "debug": "^4.0.1", + "electron-notarize": "^0.1.1", + "electron-osx-sign": "^0.4.11", + "fs-extra": "^7.0.0", + "galactus": "^0.2.1", + "get-package-info": "^1.0.0", + "junk": "^3.1.0", + "parse-author": "^2.0.0", + "plist": "^3.0.0", + "rcedit": "^2.0.0", + "resolve": "^1.1.6", + "sanitize-filename": "^1.6.0", + "semver": "^6.0.0", + "yargs-parser": "^13.0.0" + }, + "dependencies": { + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true + }, + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "fs-extra": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", + "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "semver": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.1.2.tgz", + "integrity": "sha512-z4PqiCpomGtWj8633oeAdXm1Kn1W++3T8epkZYnwiVgIYIJ0QHszhInYSJTYxebByQH7KVCEAn8R9duzZW2PhQ==", + "dev": true + }, + "yargs-parser": { + "version": "13.1.2", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz", + "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==", + "dev": true, + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + } + } + }, + "electron-publish": { + "version": "21.2.0", + "resolved": "https://registry.npmjs.org/electron-publish/-/electron-publish-21.2.0.tgz", + "integrity": "sha512-mWavuoWJe87iaeKd0I24dNWIaR+0yRzshjNVqGyK019H766fsPWl3caQJnVKFaEyrZRP397v4JZVG0e7s16AxA==", + "dev": true, + "requires": { + "bluebird-lst": "^1.0.9", + "builder-util": "~21.2.0", + "builder-util-runtime": "8.3.0", + "chalk": "^2.4.2", + "fs-extra": "^8.1.0", + "lazy-val": "^1.0.4", + "mime": "^2.4.4" + }, + "dependencies": { + "fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "dev": true, + "requires": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + } + }, + "graceful-fs": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.1.tgz", + "integrity": "sha512-b9usnbDGnD928gJB3LrCmxoibr3VE4U2SMo5PBuBnokWyDADTqDPXg4YpwKF1trpH+UbGp7QLicO3+aWEy0+mw==", + "dev": true + }, + "mime": { + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.4.tgz", + "integrity": "sha512-LRxmNwziLPT828z+4YkNzloCFC2YM4wrB99k+AV5ZbEyfGNWfG8SO1FUXLmLDBSo89NrJZ4DIWeLjy1CHGhMGA==", + "dev": true + } + } + }, + "electron-splashscreen": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/electron-splashscreen/-/electron-splashscreen-1.0.0.tgz", + "integrity": "sha512-1+Iif7GL1IakTnRT86ee+zcDyWlAu4ggSOK1qWw9h/mcK7pnAy9biXMaeFwrUcYC+eMZM5Zx/2909WzmvxdOjQ==" + }, + "elliptic": { + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz", + "integrity": "sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==", + "dev": true, + "requires": { + "bn.js": "^4.11.9", + "brorand": "^1.1.0", + "hash.js": "^1.0.0", + "hmac-drbg": "^1.0.1", + "inherits": "^2.0.4", + "minimalistic-assert": "^1.0.1", + "minimalistic-crypto-utils": "^1.0.1" + }, + "dependencies": { + "bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", + "dev": true + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + } + } + }, + "emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "dev": true + }, + "emojis-list": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-2.1.0.tgz", + "integrity": "sha1-TapNnbAPmBmIDHn6RXrlsJof04k=", + "dev": true + }, + "encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=", + "dev": true + }, + "encoding": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.12.tgz", + "integrity": "sha1-U4tm8+5izRq1HsMjgp0flIDHS+s=", + "dev": true, + "requires": { + "iconv-lite": "~0.4.13" + } + }, + "end-of-stream": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz", + "integrity": "sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q==", + "dev": true, + "requires": { + "once": "^1.4.0" + } + }, + "engine.io": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-3.2.1.tgz", + "integrity": "sha512-+VlKzHzMhaU+GsCIg4AoXF1UdDFjHHwMmMKqMJNDNLlUlejz58FCy4LBqB2YVJskHGYl06BatYWKP2TVdVXE5w==", + "dev": true, + "requires": { + "accepts": "~1.3.4", + "base64id": "1.0.0", + "cookie": "0.3.1", + "debug": "~3.1.0", + "engine.io-parser": "~2.1.0", + "ws": "~3.3.1" + }, + "dependencies": { + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + } + } + }, + "engine.io-client": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-3.2.1.tgz", + "integrity": "sha512-y5AbkytWeM4jQr7m/koQLc5AxpRKC1hEVUb/s1FUAWEJq5AzJJ4NLvzuKPuxtDi5Mq755WuDvZ6Iv2rXj4PTzw==", + "dev": true, + "requires": { + "component-emitter": "1.2.1", + "component-inherit": "0.0.3", + "debug": "~3.1.0", + "engine.io-parser": "~2.1.1", + "has-cors": "1.1.0", + "indexof": "0.0.1", + "parseqs": "0.0.5", + "parseuri": "0.0.5", + "ws": "~3.3.1", + "xmlhttprequest-ssl": "~1.5.4", + "yeast": "0.1.2" + }, + "dependencies": { + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + } + } + }, + "engine.io-parser": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-2.1.3.tgz", + "integrity": "sha512-6HXPre2O4Houl7c4g7Ic/XzPnHBvaEmN90vtRO9uLmwtRqQmTOw0QMevL1TOfL2Cpu1VzsaTmMotQgMdkzGkVA==", + "dev": true, + "requires": { + "after": "0.8.2", + "arraybuffer.slice": "~0.0.7", + "base64-arraybuffer": "0.1.5", + "blob": "0.0.5", + "has-binary2": "~1.0.2" + } + }, + "enhanced-resolve": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-4.1.0.tgz", + "integrity": "sha512-F/7vkyTtyc/llOIn8oWclcB25KdRaiPBpZYDgJHgh/UHtpgT2p2eldQgtQnLtUvfMKPKxbRaQM/hHkvLHt1Vng==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "memory-fs": "^0.4.0", + "tapable": "^1.0.0" + } + }, + "ent": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/ent/-/ent-2.2.0.tgz", + "integrity": "sha1-6WQhkyWiHQX0RGai9obtbOX13R0=", + "dev": true + }, + "env-paths": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-1.0.0.tgz", + "integrity": "sha1-QWgTO0K7BcOKNbGuQ5fIKYqzaeA=", + "dev": true + }, + "err-code": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/err-code/-/err-code-1.1.2.tgz", + "integrity": "sha1-BuARbTAo9q70gGhJ6w6mp0iuaWA=", + "dev": true + }, + "errno": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.7.tgz", + "integrity": "sha512-MfrRBDWzIWifgq6tJj60gkAwtLNb6sQPlcFrSOflcP1aFmmruKQ2wRnze/8V6kgyz7H3FF8Npzv78mZ7XLLflg==", + "dev": true, + "requires": { + "prr": "~1.0.1" + } + }, + "error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "requires": { + "is-arrayish": "^0.2.1" + } + }, + "es6-promise": { + "version": "4.2.6", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.6.tgz", + "integrity": "sha512-aRVgGdnmW2OiySVPUC9e6m+plolMAJKjZnQlCwNSuK5yQ0JN61DZSO1X1Ufd1foqWRAlig0rhduTCHe7sVtK5Q==", + "dev": true + }, + "es6-promisify": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz", + "integrity": "sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=", + "dev": true, + "requires": { + "es6-promise": "^4.0.3" + } + }, + "escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true + }, + "escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=", + "dev": true + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true + }, + "eslint-scope": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.0.tgz", + "integrity": "sha512-1G6UTDi7Jc1ELFwnR58HV4fK9OQK4S6N985f166xqXxpjU6plxFISJa2Ba9KCQuFa8RCnj/lSFJbHo7UFDBnUA==", + "dev": true, + "requires": { + "esrecurse": "^4.1.0", + "estraverse": "^4.1.1" + } + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true + }, + "esrecurse": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz", + "integrity": "sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==", + "dev": true, + "requires": { + "estraverse": "^4.1.0" + } + }, + "estraverse": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.2.0.tgz", + "integrity": "sha1-De4/7TH81GlhjOc0IJn8GvoL2xM=", + "dev": true + }, + "esutils": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", + "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=", + "dev": true + }, + "etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=", + "dev": true + }, + "events": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.0.0.tgz", + "integrity": "sha512-Dc381HFWJzEOhQ+d8pkNon++bk9h6cdAoAj4iE6Q4y6xgTzySWXlKn05/TVNpjnfRqi/X0EpJEJohPjNI3zpVA==", + "dev": true + }, + "eventsource": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-1.0.7.tgz", + "integrity": "sha512-4Ln17+vVT0k8aWq+t/bF5arcS3EpT9gYtW66EPacdj/mAFevznsnyoHLPy2BA8gbIQeIHoPsvwmfBftfcG//BQ==", + "dev": true, + "requires": { + "original": "^1.0.0" + } + }, + "evp_bytestokey": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz", + "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==", + "dev": true, + "requires": { + "md5.js": "^1.3.4", + "safe-buffer": "^5.1.1" + } + }, + "execa": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-0.10.0.tgz", + "integrity": "sha512-7XOMnz8Ynx1gGo/3hyV9loYNPWM94jG3+3T3Y8tsfSstFmETmENCMU/A/zj8Lyaj1lkgEepKepvd6240tBRvlw==", + "dev": true, + "requires": { + "cross-spawn": "^6.0.0", + "get-stream": "^3.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + }, + "dependencies": { + "cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "dev": true, + "requires": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + } + } + }, + "exit": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", + "integrity": "sha1-BjJjj42HfMghB9MKD/8aF8uhzQw=", + "dev": true + }, + "expand-braces": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/expand-braces/-/expand-braces-0.1.2.tgz", + "integrity": "sha1-SIsdHSRRyz06axks/AMPRMWFX+o=", + "dev": true, + "requires": { + "array-slice": "^0.2.3", + "array-unique": "^0.2.1", + "braces": "^0.1.2" + }, + "dependencies": { + "array-unique": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.2.1.tgz", + "integrity": "sha1-odl8yvy8JiXMcPrc6zalDFiwGlM=", + "dev": true + }, + "braces": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/braces/-/braces-0.1.5.tgz", + "integrity": "sha1-wIVxEIUpHYt1/ddOqw+FlygHEeY=", + "dev": true, + "requires": { + "expand-range": "^0.1.0" + } + } + } + }, + "expand-brackets": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", + "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", + "dev": true, + "requires": { + "debug": "^2.3.3", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "posix-character-classes": "^0.1.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "expand-range": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/expand-range/-/expand-range-0.1.1.tgz", + "integrity": "sha1-TLjtoJk8pW+k9B/ELzy7TMrf8EQ=", + "dev": true, + "requires": { + "is-number": "^0.1.1", + "repeat-string": "^0.2.2" + }, + "dependencies": { + "is-number": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-0.1.1.tgz", + "integrity": "sha1-aaevEWlj1HIG7JvZtIoUIW8eOAY=", + "dev": true + }, + "repeat-string": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-0.2.2.tgz", + "integrity": "sha1-x6jTI2BoNiBZp+RlH8aITosftK4=", + "dev": true + } + } + }, + "express": { + "version": "4.16.4", + "resolved": "https://registry.npmjs.org/express/-/express-4.16.4.tgz", + "integrity": "sha512-j12Uuyb4FMrd/qQAm6uCHAkPtO8FDTRJZBDd5D2KOL2eLaz1yUNdUB/NOIyq0iU4q4cFarsUCrnFDPBcnksuOg==", + "dev": true, + "requires": { + "accepts": "~1.3.5", + "array-flatten": "1.1.1", + "body-parser": "1.18.3", + "content-disposition": "0.5.2", + "content-type": "~1.0.4", + "cookie": "0.3.1", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "~1.1.2", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.1.1", + "fresh": "0.5.2", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "~2.3.0", + "parseurl": "~1.3.2", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.4", + "qs": "6.5.2", + "range-parser": "~1.2.0", + "safe-buffer": "5.1.2", + "send": "0.16.2", + "serve-static": "1.13.2", + "setprototypeof": "1.1.0", + "statuses": "~1.4.0", + "type-is": "~1.6.16", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "dependencies": { + "array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=", + "dev": true + } + } + }, + "extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" + }, + "extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", + "dev": true, + "requires": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + }, + "dependencies": { + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, + "requires": { + "is-plain-object": "^2.0.4" + } + } + } + }, + "external-editor": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.0.3.tgz", + "integrity": "sha512-bn71H9+qWoOQKyZDo25mOMVpSmXROAsTJVVVYzrrtol3d4y+AsKjf4Iwl2Q+IuT0kFSQ1qo166UuIwqYq7mGnA==", + "dev": true, + "requires": { + "chardet": "^0.7.0", + "iconv-lite": "^0.4.24", + "tmp": "^0.0.33" + }, + "dependencies": { + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + } + } + }, + "extglob": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", + "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", + "dev": true, + "requires": { + "array-unique": "^0.3.2", + "define-property": "^1.0.0", + "expand-brackets": "^2.1.4", + "extend-shallow": "^2.0.1", + "fragment-cache": "^0.2.1", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + } + } + }, + "extract-zip": { + "version": "1.6.7", + "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-1.6.7.tgz", + "integrity": "sha1-qEC0uK9kAyZMjbV/Txp0Mz74H+k=", + "dev": true, + "requires": { + "concat-stream": "1.6.2", + "debug": "2.6.9", + "mkdirp": "0.5.1", + "yauzl": "2.4.1" + }, + "dependencies": { + "minimist": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", + "dev": true + }, + "mkdirp": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "dev": true, + "requires": { + "minimist": "0.0.8" + } + } + } + }, + "extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", + "dev": true + }, + "fast-deep-equal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", + "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=", + "dev": true + }, + "fast-diff": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.1.2.tgz", + "integrity": "sha512-KaJUt+M9t1qaIteSvjc6P3RbMdXsNhK61GRftR6SNxqmhthcd9MGIi4T+o0jD8LUSpSnSKXE20nLtJ3fOHxQig==" + }, + "fast-json-stable-stringify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", + "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=", + "dev": true + }, + "fast-xml-parser": { + "version": "3.12.16", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-3.12.16.tgz", + "integrity": "sha512-7ePrHTK4K9BLzY3+6ZOv2YEPOpdYJg3ohyMHxacG6kp1A0Y8KNyjrFfEHJuo8G4T7vT7cIlIXGWoHdIWu9U41A==", + "requires": { + "configstore": "^4.0.0", + "lodash": "^4.17.11", + "nimnjs": "^1.3.2" + } + }, + "fastparse": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/fastparse/-/fastparse-1.1.2.tgz", + "integrity": "sha512-483XLLxTVIwWK3QTrMGRqUfUpoOs/0hbQrl2oz4J0pAcm3A3bu84wxTFqGqkJzewCLdME38xJLJAxBABfQT8sQ==", + "dev": true + }, + "faye-websocket": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.10.0.tgz", + "integrity": "sha1-TkkvjQTftviQA1B/btvy1QHnxvQ=", + "dev": true, + "requires": { + "websocket-driver": ">=0.5.1" + } + }, + "fd-slicer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.0.1.tgz", + "integrity": "sha1-i1vL2ewyfFBBv5qwI/1nUPEXfmU=", + "dev": true, + "requires": { + "pend": "~1.2.0" + } + }, + "figgy-pudding": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/figgy-pudding/-/figgy-pudding-3.5.1.tgz", + "integrity": "sha512-vNKxJHTEKNThjfrdJwHc7brvM6eVevuO5nTj6ez8ZQ1qbXTvGthucRF7S4vf2cr71QVnT70V34v0S1DyQsti0w==", + "dev": true + }, + "figures": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", + "integrity": "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=", + "dev": true, + "requires": { + "escape-string-regexp": "^1.0.5" + } + }, + "file-loader": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/file-loader/-/file-loader-3.0.1.tgz", + "integrity": "sha512-4sNIOXgtH/9WZq4NvlfU3Opn5ynUsqBwSLyM+I7UOwdGigTBYfVVQEwe/msZNX/j4pCJTIM14Fsw66Svo1oVrw==", + "dev": true, + "requires": { + "loader-utils": "^1.0.2", + "schema-utils": "^1.0.0" + } + }, + "file-saver": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/file-saver/-/file-saver-2.0.2.tgz", + "integrity": "sha512-Wz3c3XQ5xroCxd1G8b7yL0Ehkf0TC9oYC6buPFkNnU9EnaPlifeAFCyCh+iewXTyFRcg0a6j3J7FmJsIhlhBdw==" + }, + "fileset": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/fileset/-/fileset-2.0.3.tgz", + "integrity": "sha1-jnVIqW08wjJ+5eZ0FocjozO7oqA=", + "dev": true, + "requires": { + "glob": "^7.0.3", + "minimatch": "^3.0.3" + } + }, + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "dev": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "finalhandler": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.1.tgz", + "integrity": "sha512-Y1GUDo39ez4aHAw7MysnUD5JzYX+WaIj8I57kO3aEPT1fFRL4sr7mjei97FgnwhAyyzRYmQZaTHb2+9uZ1dPtg==", + "dev": true, + "requires": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "~2.3.0", + "parseurl": "~1.3.2", + "statuses": "~1.4.0", + "unpipe": "~1.0.0" + } + }, + "find-cache-dir": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-1.0.0.tgz", + "integrity": "sha1-kojj6ePMN0hxfTnq3hfPcfww7m8=", + "dev": true, + "requires": { + "commondir": "^1.0.1", + "make-dir": "^1.0.0", + "pkg-dir": "^2.0.0" + } + }, + "find-up": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", + "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", + "dev": true, + "requires": { + "locate-path": "^2.0.0" + } + }, + "flatted": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.0.tgz", + "integrity": "sha512-R+H8IZclI8AAkSBRQJLVOsxwAoHd6WC40b4QTNWIjzAa6BXOBfQcM587MXDTVPeYaopFNWHUFLx7eNmHDSxMWg==", + "dev": true + }, + "flora-colossus": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/flora-colossus/-/flora-colossus-1.0.1.tgz", + "integrity": "sha512-d+9na7t9FyH8gBJoNDSi28mE4NgQVGGvxQ4aHtFRetjyh5SXjuus+V5EZaxFmFdXVemSOrx0lsgEl/ZMjnOWJA==", + "dev": true, + "requires": { + "debug": "^4.1.1", + "fs-extra": "^7.0.0" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "fs-extra": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", + "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } + } + }, + "flush-write-stream": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/flush-write-stream/-/flush-write-stream-1.1.1.tgz", + "integrity": "sha512-3Z4XhFZ3992uIq0XOqb9AreonueSYphE6oYbpt5+3u06JWklbsPkNv3ZKkP9Bz/r+1MWCaMoSQ28P85+1Yc77w==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "readable-stream": "^2.3.6" + } + }, + "follow-redirects": { + "version": "1.14.7", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.7.tgz", + "integrity": "sha512-+hbxoLbFMbRKDwohX8GkTataGqO6Jb7jGwpAlwgy2bIz25XtRm7KEzJM76R1WiNT5SwZkX4Y75SwBolkpmE7iQ==", + "dev": true + }, + "for-in": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", + "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", + "dev": true + }, + "for-own": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/for-own/-/for-own-1.0.0.tgz", + "integrity": "sha1-xjMy9BXO3EsE2/5wz4NklMU8tEs=", + "dev": true, + "requires": { + "for-in": "^1.0.1" + } + }, + "forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=", + "dev": true + }, + "form-data": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "dev": true, + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + } + }, + "forwarded": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", + "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=", + "dev": true + }, + "fragment-cache": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", + "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=", + "dev": true, + "requires": { + "map-cache": "^0.2.2" + } + }, + "fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=", + "dev": true + }, + "from2": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz", + "integrity": "sha1-i/tVAr3kpNNs/e6gB/zKIdfjgq8=", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "readable-stream": "^2.0.0" + } + }, + "fs-access": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/fs-access/-/fs-access-1.0.1.tgz", + "integrity": "sha1-1qh/JiJxzv6+wwxVNAf7mV2od3o=", + "dev": true, + "requires": { + "null-check": "^1.0.0" + } + }, + "fs-extra": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-4.0.3.tgz", + "integrity": "sha512-q6rbdDd1o2mAnQreO7YADIxf/Whx4AHBiRf6d+/cVT8h44ss+lHgxf1FemcqDnQt9X3ct4McHr+JMGlYSsK7Cg==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + } + }, + "fs-write-stream-atomic": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/fs-write-stream-atomic/-/fs-write-stream-atomic-1.0.10.tgz", + "integrity": "sha1-tH31NJPvkR33VzHnCp3tAYnbQMk=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "iferr": "^0.1.5", + "imurmurhash": "^0.1.4", + "readable-stream": "1 || 2" + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" + }, + "fsevents": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.7.tgz", + "integrity": "sha512-Pxm6sI2MeBD7RdD12RYsqaP0nMiwx8eZBXCa6z2L+mRHm2DYrOYwihmhjpkdjUHwQhslWQjRpEgNq4XvBmaAuw==", + "dev": true, + "optional": true, + "requires": { + "nan": "^2.9.2", + "node-pre-gyp": "^0.10.0" + }, + "dependencies": { + "abbrev": { + "version": "1.1.1", + "resolved": false, + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", + "dev": true, + "optional": true + }, + "ansi-regex": { + "version": "2.1.1", + "resolved": false, + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true, + "optional": true + }, + "aproba": { + "version": "1.2.0", + "resolved": false, + "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", + "dev": true, + "optional": true + }, + "are-we-there-yet": { + "version": "1.1.5", + "resolved": false, + "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==", + "dev": true, + "optional": true, + "requires": { + "delegates": "^1.0.0", + "readable-stream": "^2.0.6" + } + }, + "balanced-match": { + "version": "1.0.0", + "resolved": false, + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true, + "optional": true + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": false, + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "optional": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "chownr": { + "version": "1.1.1", + "resolved": false, + "integrity": "sha512-j38EvO5+LHX84jlo6h4UzmOwi0UgW61WRyPtJz4qaadK5eY3BTS5TY/S1Stc3Uk2lIM6TPevAlULiEJwie860g==", + "dev": true, + "optional": true + }, + "code-point-at": { + "version": "1.1.0", + "resolved": false, + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", + "dev": true, + "optional": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": false, + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true, + "optional": true + }, + "console-control-strings": { + "version": "1.1.0", + "resolved": false, + "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=", + "dev": true, + "optional": true + }, + "core-util-is": { + "version": "1.0.2", + "resolved": false, + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", + "dev": true, + "optional": true + }, + "debug": { + "version": "2.6.9", + "resolved": false, + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "optional": true, + "requires": { + "ms": "2.0.0" + } + }, + "deep-extend": { + "version": "0.6.0", + "resolved": false, + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "dev": true, + "optional": true + }, + "delegates": { + "version": "1.0.0", + "resolved": false, + "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=", + "dev": true, + "optional": true + }, + "detect-libc": { + "version": "1.0.3", + "resolved": false, + "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=", + "dev": true, + "optional": true + }, + "fs-minipass": { + "version": "1.2.5", + "resolved": false, + "integrity": "sha512-JhBl0skXjUPCFH7x6x61gQxrKyXsxB5gcgePLZCwfyCGGsTISMoIeObbrvVeP6Xmyaudw4TT43qV2Gz+iyd2oQ==", + "dev": true, + "optional": true, + "requires": { + "minipass": "^2.2.1" + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": false, + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true, + "optional": true + }, + "gauge": { + "version": "2.7.4", + "resolved": false, + "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", + "dev": true, + "optional": true, + "requires": { + "aproba": "^1.0.3", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.0", + "object-assign": "^4.1.0", + "signal-exit": "^3.0.0", + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wide-align": "^1.1.0" + } + }, + "glob": { + "version": "7.1.3", + "resolved": false, + "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", + "dev": true, + "optional": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "has-unicode": { + "version": "2.0.1", + "resolved": false, + "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=", + "dev": true, + "optional": true + }, + "iconv-lite": { + "version": "0.4.24", + "resolved": false, + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "optional": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "ignore-walk": { + "version": "3.0.1", + "resolved": false, + "integrity": "sha512-DTVlMx3IYPe0/JJcYP7Gxg7ttZZu3IInhuEhbchuqneY9wWe5Ojy2mXLBaQFUQmo0AW2r3qG7m1mg86js+gnlQ==", + "dev": true, + "optional": true, + "requires": { + "minimatch": "^3.0.4" + } + }, + "inflight": { + "version": "1.0.6", + "resolved": false, + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "optional": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.3", + "resolved": false, + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", + "dev": true, + "optional": true + }, + "ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true, + "optional": true + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": false, + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "dev": true, + "optional": true, + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "isarray": { + "version": "1.0.0", + "resolved": false, + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true, + "optional": true + }, + "minimatch": { + "version": "3.0.4", + "resolved": false, + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "optional": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minipass": { + "version": "2.3.5", + "resolved": false, + "integrity": "sha512-Gi1W4k059gyRbyVUZQ4mEqLm0YIUiGYfvxhF6SIlk3ui1WVxMTGfGdQ2SInh3PDrRTVvPKgULkpJtT4RH10+VA==", + "dev": true, + "optional": true, + "requires": { + "safe-buffer": "^5.1.2", + "yallist": "^3.0.0" + } + }, + "minizlib": { + "version": "1.2.1", + "resolved": false, + "integrity": "sha512-7+4oTUOWKg7AuL3vloEWekXY2/D20cevzsrNT2kGWm+39J9hGTCBv8VI5Pm5lXZ/o3/mdR4f8rflAPhnQb8mPA==", + "dev": true, + "optional": true, + "requires": { + "minipass": "^2.2.1" + } + }, + "mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "dev": true, + "optional": true, + "requires": { + "minimist": "^1.2.5" + } + }, + "ms": { + "version": "2.0.0", + "resolved": false, + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true, + "optional": true + }, + "needle": { + "version": "2.2.4", + "resolved": false, + "integrity": "sha512-HyoqEb4wr/rsoaIDfTH2aVL9nWtQqba2/HvMv+++m8u0dz808MaagKILxtfeSN7QU7nvbQ79zk3vYOJp9zsNEA==", + "dev": true, + "optional": true, + "requires": { + "debug": "^2.1.2", + "iconv-lite": "^0.4.4", + "sax": "^1.2.4" + } + }, + "node-pre-gyp": { + "version": "0.10.3", + "resolved": false, + "integrity": "sha512-d1xFs+C/IPS8Id0qPTZ4bUT8wWryfR/OzzAFxweG+uLN85oPzyo2Iw6bVlLQ/JOdgNonXLCoRyqDzDWq4iw72A==", + "dev": true, + "optional": true, + "requires": { + "detect-libc": "^1.0.2", + "mkdirp": "^0.5.1", + "needle": "^2.2.1", + "nopt": "^4.0.1", + "npm-packlist": "^1.1.6", + "npmlog": "^4.0.2", + "rc": "^1.2.7", + "rimraf": "^2.6.1", + "semver": "^5.3.0", + "tar": "^4" + } + }, + "nopt": { + "version": "4.0.1", + "resolved": false, + "integrity": "sha1-0NRoWv1UFRk8jHUFYC0NF81kR00=", + "dev": true, + "optional": true, + "requires": { + "abbrev": "1", + "osenv": "^0.1.4" + } + }, + "npm-bundled": { + "version": "1.0.5", + "resolved": false, + "integrity": "sha512-m/e6jgWu8/v5niCUKQi9qQl8QdeEduFA96xHDDzFGqly0OOjI7c+60KM/2sppfnUU9JJagf+zs+yGhqSOFj71g==", + "dev": true, + "optional": true + }, + "npm-packlist": { + "version": "1.2.0", + "resolved": false, + "integrity": "sha512-7Mni4Z8Xkx0/oegoqlcao/JpPCPEMtUvsmB0q7mgvlMinykJLSRTYuFqoQLYgGY8biuxIeiHO+QNJKbCfljewQ==", + "dev": true, + "optional": true, + "requires": { + "ignore-walk": "^3.0.1", + "npm-bundled": "^1.0.1" + } + }, + "npmlog": { + "version": "4.1.2", + "resolved": false, + "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", + "dev": true, + "optional": true, + "requires": { + "are-we-there-yet": "~1.1.2", + "console-control-strings": "~1.1.0", + "gauge": "~2.7.3", + "set-blocking": "~2.0.0" + } + }, + "number-is-nan": { + "version": "1.0.1", + "resolved": false, + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", + "dev": true, + "optional": true + }, + "object-assign": { + "version": "4.1.1", + "resolved": false, + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "dev": true, + "optional": true + }, + "once": { + "version": "1.4.0", + "resolved": false, + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "optional": true, + "requires": { + "wrappy": "1" + } + }, + "os-homedir": { + "version": "1.0.2", + "resolved": false, + "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", + "dev": true, + "optional": true + }, + "os-tmpdir": { + "version": "1.0.2", + "resolved": false, + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", + "dev": true, + "optional": true + }, + "osenv": { + "version": "0.1.5", + "resolved": false, + "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", + "dev": true, + "optional": true, + "requires": { + "os-homedir": "^1.0.0", + "os-tmpdir": "^1.0.0" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": false, + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true, + "optional": true + }, + "process-nextick-args": { + "version": "2.0.0", + "resolved": false, + "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==", + "dev": true, + "optional": true + }, + "rc": { + "version": "1.2.8", + "resolved": false, + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "dev": true, + "optional": true, + "requires": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "dependencies": { + "minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", + "dev": true, + "optional": true + } + } + }, + "readable-stream": { + "version": "2.3.6", + "resolved": false, + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "dev": true, + "optional": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "rimraf": { + "version": "2.6.3", + "resolved": false, + "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", + "dev": true, + "optional": true, + "requires": { + "glob": "^7.1.3" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": false, + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true, + "optional": true + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": false, + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true, + "optional": true + }, + "sax": { + "version": "1.2.4", + "resolved": false, + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", + "dev": true, + "optional": true + }, + "semver": { + "version": "5.6.0", + "resolved": false, + "integrity": "sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg==", + "dev": true, + "optional": true + }, + "set-blocking": { + "version": "2.0.0", + "resolved": false, + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", + "dev": true, + "optional": true + }, + "signal-exit": { + "version": "3.0.2", + "resolved": false, + "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", + "dev": true, + "optional": true + }, + "string-width": { + "version": "1.0.2", + "resolved": false, + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "dev": true, + "optional": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": false, + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "optional": true, + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": false, + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "optional": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "strip-json-comments": { + "version": "2.0.1", + "resolved": false, + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", + "dev": true, + "optional": true + }, + "tar": { + "version": "4.4.8", + "resolved": false, + "integrity": "sha512-LzHF64s5chPQQS0IYBn9IN5h3i98c12bo4NCO7e0sGM2llXQ3p2FGC5sdENN4cTW48O915Sh+x+EXx7XW96xYQ==", + "dev": true, + "optional": true, + "requires": { + "chownr": "^1.1.1", + "fs-minipass": "^1.2.5", + "minipass": "^2.3.4", + "minizlib": "^1.1.1", + "mkdirp": "^0.5.0", + "safe-buffer": "^5.1.2", + "yallist": "^3.0.2" + } + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": false, + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", + "dev": true, + "optional": true + }, + "wide-align": { + "version": "1.1.3", + "resolved": false, + "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", + "dev": true, + "optional": true, + "requires": { + "string-width": "^1.0.2 || 2" + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": false, + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true, + "optional": true + }, + "yallist": { + "version": "3.0.3", + "resolved": false, + "integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==", + "dev": true, + "optional": true + } + } + }, + "fstream": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.12.tgz", + "integrity": "sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg==", + "dev": true, + "optional": true, + "requires": { + "graceful-fs": "^4.1.2", + "inherits": "~2.0.0", + "mkdirp": ">=0.5 0", + "rimraf": "2" + } + }, + "galactus": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/galactus/-/galactus-0.2.1.tgz", + "integrity": "sha1-y+0tIKQMH1Z5o1kI4rlBVzPnjbk=", + "dev": true, + "requires": { + "debug": "^3.1.0", + "flora-colossus": "^1.0.0", + "fs-extra": "^4.0.0" + }, + "dependencies": { + "debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } + } + }, + "gauge": { + "version": "2.7.4", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", + "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", + "dev": true, + "optional": true, + "requires": { + "aproba": "^1.0.3", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.0", + "object-assign": "^4.1.0", + "signal-exit": "^3.0.0", + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wide-align": "^1.1.0" + } + }, + "gaze": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/gaze/-/gaze-1.1.3.tgz", + "integrity": "sha512-BRdNm8hbWzFzWHERTrejLqwHDfS4GibPoq5wjTPIoJHoBtKGPg3xAFfxmM+9ztbXelxcf2hwQcaz1PtmFeue8g==", + "dev": true, + "optional": true, + "requires": { + "globule": "^1.0.0" + } + }, + "genfun": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/genfun/-/genfun-5.0.0.tgz", + "integrity": "sha512-KGDOARWVga7+rnB3z9Sd2Letx515owfk0hSxHGuqjANb1M+x2bGZGqHLiozPsYMdM2OubeMni/Hpwmjq6qIUhA==", + "dev": true + }, + "get-caller-file": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz", + "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==", + "dev": true + }, + "get-package-info": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/get-package-info/-/get-package-info-1.0.0.tgz", + "integrity": "sha1-ZDJ5ZWPigRPNlHTbvQAFKYWkmZw=", + "dev": true, + "requires": { + "bluebird": "^3.1.1", + "debug": "^2.2.0", + "lodash.get": "^4.0.0", + "read-pkg-up": "^2.0.0" + }, + "dependencies": { + "load-json-file": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz", + "integrity": "sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "parse-json": "^2.2.0", + "pify": "^2.0.0", + "strip-bom": "^3.0.0" + } + }, + "path-type": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-2.0.0.tgz", + "integrity": "sha1-8BLMuEFbcJb8LaoQVMPXI4lZTHM=", + "dev": true, + "requires": { + "pify": "^2.0.0" + } + }, + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true + }, + "read-pkg": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz", + "integrity": "sha1-jvHAYjxqbbDcZxPEv6xGMysjaPg=", + "dev": true, + "requires": { + "load-json-file": "^2.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^2.0.0" + } + }, + "read-pkg-up": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-2.0.0.tgz", + "integrity": "sha1-a3KoBImE4MQeeVEP1en6mbO1Sb4=", + "dev": true, + "requires": { + "find-up": "^2.0.0", + "read-pkg": "^2.0.0" + } + }, + "strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", + "dev": true + } + } + }, + "get-stdin": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz", + "integrity": "sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4=", + "dev": true + }, + "get-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", + "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=", + "dev": true + }, + "get-value": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", + "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=", + "dev": true + }, + "getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "dev": true, + "requires": { + "assert-plus": "^1.0.0" + } + }, + "glob": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", + "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "glob-parent": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", + "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", + "dev": true, + "requires": { + "is-glob": "^3.1.0", + "path-dirname": "^1.0.0" + }, + "dependencies": { + "is-glob": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", + "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", + "dev": true, + "requires": { + "is-extglob": "^2.1.0" + } + } + } + }, + "global-dirs": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-0.1.1.tgz", + "integrity": "sha1-sxnA3UYH81PzvpzKTHL8FIxJ9EU=", + "dev": true, + "requires": { + "ini": "^1.3.4" + } + }, + "globals": { + "version": "9.18.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-9.18.0.tgz", + "integrity": "sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ==", + "dev": true + }, + "globby": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/globby/-/globby-7.1.1.tgz", + "integrity": "sha1-+yzP+UAfhgCUXfral0QMypcrhoA=", + "dev": true, + "requires": { + "array-union": "^1.0.1", + "dir-glob": "^2.0.0", + "glob": "^7.1.2", + "ignore": "^3.3.5", + "pify": "^3.0.0", + "slash": "^1.0.0" + } + }, + "globule": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/globule/-/globule-1.2.1.tgz", + "integrity": "sha512-g7QtgWF4uYSL5/dn71WxubOrS7JVGCnFPEnoeChJmBnyR9Mw8nGoEwOgJL/RC2Te0WhbsEUCejfH8SZNJ+adYQ==", + "dev": true, + "optional": true, + "requires": { + "glob": "~7.1.1", + "lodash": "~4.17.10", + "minimatch": "~3.0.2" + } + }, + "google-protobuf": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/google-protobuf/-/google-protobuf-3.8.0.tgz", + "integrity": "sha512-tx39PTc//HaIT7K/baUF/8JYLGDozEi1e4xwPP1qSx3InP78cNpbSJpxiDsDMwj77qNOndVBDXnn7oi9zKxZew==" + }, + "got": { + "version": "9.6.0", + "resolved": "https://registry.npmjs.org/got/-/got-9.6.0.tgz", + "integrity": "sha512-R7eWptXuGYxwijs0eV+v3o6+XH1IqVK8dJOEecQfTmkncw9AV4dcw/Dhxi8MdlqPthxxpZyizMzyg8RTmEsG+Q==", + "dev": true, + "requires": { + "@sindresorhus/is": "^0.14.0", + "@szmarczak/http-timer": "^1.1.2", + "cacheable-request": "^6.0.0", + "decompress-response": "^3.3.0", + "duplexer3": "^0.1.4", + "get-stream": "^4.1.0", + "lowercase-keys": "^1.0.1", + "mimic-response": "^1.0.1", + "p-cancelable": "^1.0.0", + "to-readable-stream": "^1.0.0", + "url-parse-lax": "^3.0.0" + }, + "dependencies": { + "get-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "dev": true, + "requires": { + "pump": "^3.0.0" + } + }, + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + } + } + }, + "graceful-fs": { + "version": "4.1.15", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.15.tgz", + "integrity": "sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==" + }, + "graceful-readlink": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz", + "integrity": "sha1-TK+tdrxi8C+gObL5Tpo906ORpyU=", + "dev": true + }, + "grpc": { + "version": "1.21.1", + "resolved": "https://registry.npmjs.org/grpc/-/grpc-1.21.1.tgz", + "integrity": "sha512-PFsZQazf62nP05a0xm23mlImMuw5oVlqF/0zakmsdqJgvbABe+d6VThY2PfhqJmWEL/FhQ6QNYsxS5EAM6++7g==", + "requires": { + "lodash.camelcase": "^4.3.0", + "lodash.clone": "^4.5.0", + "nan": "^2.13.2", + "node-pre-gyp": "^0.13.0", + "protobufjs": "^5.0.3" + }, + "dependencies": { + "abbrev": { + "version": "1.1.1", + "bundled": true + }, + "ansi-regex": { + "version": "2.1.1", + "bundled": true + }, + "aproba": { + "version": "1.2.0", + "bundled": true + }, + "are-we-there-yet": { + "version": "1.1.5", + "bundled": true, + "requires": { + "delegates": "^1.0.0", + "readable-stream": "^2.0.6" + } + }, + "balanced-match": { + "version": "1.0.0", + "bundled": true + }, + "brace-expansion": { + "version": "1.1.11", + "bundled": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "chownr": { + "version": "1.1.1", + "bundled": true + }, + "code-point-at": { + "version": "1.1.0", + "bundled": true + }, + "concat-map": { + "version": "0.0.1", + "bundled": true + }, + "console-control-strings": { + "version": "1.1.0", + "bundled": true + }, + "core-util-is": { + "version": "1.0.2", + "bundled": true + }, + "deep-extend": { + "version": "0.6.0", + "bundled": true + }, + "delegates": { + "version": "1.0.0", + "bundled": true + }, + "detect-libc": { + "version": "1.0.3", + "bundled": true + }, + "fs-minipass": { + "version": "1.2.5", + "bundled": true, + "requires": { + "minipass": "^2.2.1" + } + }, + "fs.realpath": { + "version": "1.0.0", + "bundled": true + }, + "gauge": { + "version": "2.7.4", + "bundled": true, + "requires": { + "aproba": "^1.0.3", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.0", + "object-assign": "^4.1.0", + "signal-exit": "^3.0.0", + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wide-align": "^1.1.0" + } + }, + "has-unicode": { + "version": "2.0.1", + "bundled": true + }, + "iconv-lite": { + "version": "0.4.23", + "bundled": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "ignore-walk": { + "version": "3.0.1", + "bundled": true, + "requires": { + "minimatch": "^3.0.4" + } + }, + "inflight": { + "version": "1.0.6", + "bundled": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.3", + "bundled": true + }, + "ini": { + "version": "1.3.5", + "bundled": true + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "bundled": true, + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "isarray": { + "version": "1.0.0", + "bundled": true + }, + "minimatch": { + "version": "3.0.4", + "bundled": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "1.2.0", + "bundled": true + }, + "minipass": { + "version": "2.3.5", + "bundled": true, + "requires": { + "safe-buffer": "^5.1.2", + "yallist": "^3.0.0" + } + }, + "minizlib": { + "version": "1.2.1", + "bundled": true, + "requires": { + "minipass": "^2.2.1" + } + }, + "mkdirp": { + "version": "0.5.1", + "bundled": true, + "requires": { + "minimist": "0.0.8" + }, + "dependencies": { + "minimist": { + "version": "0.0.8", + "bundled": true + } + } + }, + "nan": { + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.0.tgz", + "integrity": "sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==" + }, + "needle": { + "version": "2.3.1", + "bundled": true, + "requires": { + "debug": "^4.1.0", + "iconv-lite": "^0.4.4", + "sax": "^1.2.4" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "bundled": true, + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.1", + "bundled": true + } + } + }, + "node-pre-gyp": { + "version": "0.13.0", + "bundled": true, + "requires": { + "detect-libc": "^1.0.2", + "mkdirp": "^0.5.1", + "needle": "^2.2.1", + "nopt": "^4.0.1", + "npm-packlist": "^1.1.6", + "npmlog": "^4.0.2", + "rc": "^1.2.7", + "rimraf": "^2.6.1", + "semver": "^5.3.0", + "tar": "^4" + } + }, + "nopt": { + "version": "4.0.1", + "bundled": true, + "requires": { + "abbrev": "1", + "osenv": "^0.1.4" + } + }, + "npm-bundled": { + "version": "1.0.6", + "bundled": true + }, + "npm-packlist": { + "version": "1.4.1", + "bundled": true, + "requires": { + "ignore-walk": "^3.0.1", + "npm-bundled": "^1.0.1" + } + }, + "npmlog": { + "version": "4.1.2", + "bundled": true, + "requires": { + "are-we-there-yet": "~1.1.2", + "console-control-strings": "~1.1.0", + "gauge": "~2.7.3", + "set-blocking": "~2.0.0" + } + }, + "number-is-nan": { + "version": "1.0.1", + "bundled": true + }, + "object-assign": { + "version": "4.1.1", + "bundled": true + }, + "once": { + "version": "1.4.0", + "bundled": true, + "requires": { + "wrappy": "1" + } + }, + "os-homedir": { + "version": "1.0.2", + "bundled": true + }, + "os-tmpdir": { + "version": "1.0.2", + "bundled": true + }, + "osenv": { + "version": "0.1.5", + "bundled": true, + "requires": { + "os-homedir": "^1.0.0", + "os-tmpdir": "^1.0.0" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "bundled": true + }, + "process-nextick-args": { + "version": "2.0.0", + "bundled": true + }, + "rc": { + "version": "1.2.8", + "bundled": true, + "requires": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + } + }, + "readable-stream": { + "version": "2.3.6", + "bundled": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "rimraf": { + "version": "2.6.3", + "bundled": true, + "requires": { + "glob": "^7.1.3" + }, + "dependencies": { + "glob": { + "version": "7.1.4", + "bundled": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + } + } + }, + "safe-buffer": { + "version": "5.1.2", + "bundled": true + }, + "safer-buffer": { + "version": "2.1.2", + "bundled": true + }, + "sax": { + "version": "1.2.4", + "bundled": true + }, + "semver": { + "version": "5.7.0", + "bundled": true + }, + "set-blocking": { + "version": "2.0.0", + "bundled": true + }, + "signal-exit": { + "version": "3.0.2", + "bundled": true + }, + "string-width": { + "version": "1.0.2", + "bundled": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + }, + "string_decoder": { + "version": "1.1.1", + "bundled": true, + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "bundled": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "strip-json-comments": { + "version": "2.0.1", + "bundled": true + }, + "tar": { + "version": "4.4.8", + "bundled": true, + "requires": { + "chownr": "^1.1.1", + "fs-minipass": "^1.2.5", + "minipass": "^2.3.4", + "minizlib": "^1.1.1", + "mkdirp": "^0.5.0", + "safe-buffer": "^5.1.2", + "yallist": "^3.0.2" + } + }, + "util-deprecate": { + "version": "1.0.2", + "bundled": true + }, + "wide-align": { + "version": "1.1.3", + "bundled": true, + "requires": { + "string-width": "^1.0.2 || 2" + } + }, + "wrappy": { + "version": "1.0.2", + "bundled": true + }, + "yallist": { + "version": "3.0.3", + "bundled": true + } + } + }, + "hammerjs": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/hammerjs/-/hammerjs-2.0.8.tgz", + "integrity": "sha1-BO93hiz/K7edMPdpIJWTAiK/YPE=" + }, + "handle-thing": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.0.tgz", + "integrity": "sha512-d4sze1JNC454Wdo2fkuyzCr6aHcbL6PGGuFAz0Li/NcOm1tCHGnWDRmJP85dh9IhQErTc2svWFEX5xHIOo//kQ==", + "dev": true + }, + "har-schema": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=", + "dev": true + }, + "har-validator": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz", + "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==", + "dev": true, + "requires": { + "ajv": "^6.5.5", + "har-schema": "^2.0.0" + } + }, + "has-ansi": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", + "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "has-binary2": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-binary2/-/has-binary2-1.0.3.tgz", + "integrity": "sha512-G1LWKhDSvhGeAQ8mPVQlqNcOB2sJdwATtZKl2pDKKHfpf/rYj24lkinxf69blJbnsvtqqNU+L3SL50vzZhXOnw==", + "dev": true, + "requires": { + "isarray": "2.0.1" + }, + "dependencies": { + "isarray": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz", + "integrity": "sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4=", + "dev": true + } + } + }, + "has-cors": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-cors/-/has-cors-1.1.0.tgz", + "integrity": "sha1-XkdHk/fqmEPRu5nCPu9J/xJv/zk=", + "dev": true + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=", + "dev": true, + "optional": true + }, + "has-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", + "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=", + "dev": true, + "requires": { + "get-value": "^2.0.6", + "has-values": "^1.0.0", + "isobject": "^3.0.0" + } + }, + "has-values": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", + "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=", + "dev": true, + "requires": { + "is-number": "^3.0.0", + "kind-of": "^4.0.0" + }, + "dependencies": { + "kind-of": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", + "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "has-yarn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/has-yarn/-/has-yarn-2.1.0.tgz", + "integrity": "sha512-UqBRqi4ju7T+TqGNdqAO0PaSVGsDGJUBQvk9eUWNGRY1CFGDzYhLWoM7JQEemnlvVcv/YEmc2wNW8BC24EnUsw==", + "dev": true + }, + "hash-base": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.0.4.tgz", + "integrity": "sha1-X8hoaEfs1zSZQDMZprCj8/auSRg=", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "hash.js": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", + "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "minimalistic-assert": "^1.0.1" + } + }, + "hmac-drbg": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", + "integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=", + "dev": true, + "requires": { + "hash.js": "^1.0.3", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.0.1" + } + }, + "hopscotch": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/hopscotch/-/hopscotch-0.3.1.tgz", + "integrity": "sha1-BmOlqbQtQwdmapQEwCxvHqwKZYQ=" + }, + "hosted-git-info": { + "version": "2.8.9", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", + "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", + "dev": true + }, + "hpack.js": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/hpack.js/-/hpack.js-2.1.6.tgz", + "integrity": "sha1-h3dMCUnlE/QuhFdbPEVoH63ioLI=", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "obuf": "^1.0.0", + "readable-stream": "^2.0.1", + "wbuf": "^1.1.0" + } + }, + "html-entities": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-1.2.1.tgz", + "integrity": "sha1-DfKTUfByEWNRXfueVUPl9u7VFi8=", + "dev": true + }, + "html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true + }, + "http-cache-semantics": { + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-3.8.1.tgz", + "integrity": "sha512-5ai2iksyV8ZXmnZhHH4rWPoxxistEexSi5936zIQ1bnNTW5VnA85B6P/VpXiRM017IgRvb2kKo1a//y+0wSp3w==", + "dev": true + }, + "http-deceiver": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/http-deceiver/-/http-deceiver-1.2.7.tgz", + "integrity": "sha1-+nFolEq5pRnTN8sL7HKE3D5yPYc=", + "dev": true + }, + "http-errors": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", + "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", + "dev": true, + "requires": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.0", + "statuses": ">= 1.4.0 < 2" + } + }, + "http-parser-js": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.0.tgz", + "integrity": "sha512-cZdEF7r4gfRIq7ezX9J0T+kQmJNOub71dWbgAXVHDct80TKP4MCETtZQ31xyv38UwgzkWPYF/Xc0ge55dW9Z9w==", + "dev": true + }, + "http-proxy": { + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", + "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", + "dev": true, + "requires": { + "eventemitter3": "^4.0.0", + "follow-redirects": "^1.0.0", + "requires-port": "^1.0.0" + }, + "dependencies": { + "eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", + "dev": true + } + } + }, + "http-proxy-agent": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-2.1.0.tgz", + "integrity": "sha512-qwHbBLV7WviBl0rQsOzH6o5lwyOIvwp/BdFnvVxXORldu5TmjFfjzBcWUWS5kWAZhmv+JtiDhSuQCp4sBfbIgg==", + "dev": true, + "requires": { + "agent-base": "4", + "debug": "3.1.0" + }, + "dependencies": { + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + } + } + }, + "http-proxy-middleware": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-0.18.0.tgz", + "integrity": "sha512-Fs25KVMPAIIcgjMZkVHJoKg9VcXcC1C8yb9JUgeDvVXY0S/zgVIhMb+qVswDIgtJe2DfckMSY2d6TuTEutlk6Q==", + "dev": true, + "requires": { + "http-proxy": "^1.16.2", + "is-glob": "^4.0.0", + "lodash": "^4.17.5", + "micromatch": "^3.1.9" + } + }, + "http-signature": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", + "dev": true, + "requires": { + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" + } + }, + "https-browserify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz", + "integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=", + "dev": true + }, + "https-proxy-agent": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.4.tgz", + "integrity": "sha512-OmvfoQ53WLjtA9HeYP9RNrWMJzzAz1JGaSFr1nijg0PVR1JaD/xbJq1mdEIIlxGpXp9eSe/O2LgU9DJmTPd0Eg==", + "dev": true, + "requires": { + "agent-base": "^4.3.0", + "debug": "^3.1.0" + }, + "dependencies": { + "agent-base": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.3.0.tgz", + "integrity": "sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg==", + "dev": true, + "requires": { + "es6-promisify": "^5.0.0" + } + }, + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + } + } + }, + "humanize-ms": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", + "integrity": "sha1-xG4xWaKT9riW2ikxbYtv6Lt5u+0=", + "dev": true, + "requires": { + "ms": "^2.0.0" + } + }, + "iconv-lite": { + "version": "0.4.23", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz", + "integrity": "sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA==", + "dev": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "ieee754": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.12.tgz", + "integrity": "sha512-GguP+DRY+pJ3soyIiGPTvdiVXjZ+DbXOxGpXn3eMvNW4x4irjqXm4wHKscC+TfxSJ0yw/S1F24tqdMNsMZTiLA==", + "dev": true + }, + "iferr": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/iferr/-/iferr-0.1.5.tgz", + "integrity": "sha1-xg7taebY/bazEEofy8ocGS3FtQE=", + "dev": true + }, + "ignore": { + "version": "3.3.10", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.10.tgz", + "integrity": "sha512-Pgs951kaMm5GXP7MOvxERINe3gsaVjUWFm+UZPSq9xYriQAksyhg0csnS0KXSNRD5NmNdapXEpjxG49+AKh/ug==", + "dev": true + }, + "ignore-walk": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.1.tgz", + "integrity": "sha512-DTVlMx3IYPe0/JJcYP7Gxg7ttZZu3IInhuEhbchuqneY9wWe5Ojy2mXLBaQFUQmo0AW2r3qG7m1mg86js+gnlQ==", + "dev": true, + "requires": { + "minimatch": "^3.0.4" + } + }, + "image-size": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/image-size/-/image-size-0.5.5.tgz", + "integrity": "sha1-Cd/Uq50g4p6xw+gLiZA3jfnjy5w=", + "dev": true, + "optional": true + }, + "immediate": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", + "integrity": "sha1-nbHb0Pr43m++D13V5Wu2BigN5ps=", + "dev": true + }, + "immutable": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.0.0.tgz", + "integrity": "sha512-zIE9hX70qew5qTUjSS7wi1iwj/l7+m54KWU247nhM3v806UdGj1yDndXj+IOYxxtW9zyLI+xqFNZjTuDaLUqFw==", + "dev": true + }, + "import-cwd": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/import-cwd/-/import-cwd-2.1.0.tgz", + "integrity": "sha1-qmzzbnInYShcs3HsZRn1PiQ1sKk=", + "dev": true, + "requires": { + "import-from": "^2.1.0" + } + }, + "import-from": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/import-from/-/import-from-2.1.0.tgz", + "integrity": "sha1-M1238qev/VOqpHHUuAId7ja387E=", + "dev": true, + "requires": { + "resolve-from": "^3.0.0" + } + }, + "import-lazy": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-2.1.0.tgz", + "integrity": "sha1-BWmOPUXIjo1+nZLLBYTnfwlvPkM=", + "dev": true + }, + "import-local": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-2.0.0.tgz", + "integrity": "sha512-b6s04m3O+s3CGSbqDIyP4R6aAwAeYlVq9+WUWep6iHa8ETRf9yei1U48C5MmfJmV9AiLYYBKPMq/W+/WRpQmCQ==", + "dev": true, + "requires": { + "pkg-dir": "^3.0.0", + "resolve-cwd": "^2.0.0" + }, + "dependencies": { + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "requires": { + "locate-path": "^3.0.0" + } + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "p-limit": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.1.0.tgz", + "integrity": "sha512-NhURkNcrVB+8hNfLuysU8enY5xn2KXphsHBaC2YmRNTZRc7RWusw6apSpdEj3jo4CMb6W9nrF6tTnsJsJeyu6g==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "requires": { + "p-limit": "^2.0.0" + } + }, + "p-try": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.0.0.tgz", + "integrity": "sha512-hMp0onDKIajHfIkdRk3P4CdCmErkYAxxDtP3Wx/4nZ3aGlau2VKh3mZpcuFkH27WQkL/3WBCPOktzA9ZOAnMQQ==", + "dev": true + }, + "pkg-dir": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", + "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", + "dev": true, + "requires": { + "find-up": "^3.0.0" + } + } + } + }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=" + }, + "in-publish": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/in-publish/-/in-publish-2.0.0.tgz", + "integrity": "sha1-4g/146KvwmkDILbcVSaCqcf631E=", + "dev": true, + "optional": true + }, + "indent-string": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-2.1.0.tgz", + "integrity": "sha1-ji1INIdCEhtKghi3oTfppSBJ3IA=", + "dev": true, + "requires": { + "repeating": "^2.0.0" + } + }, + "indexof": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/indexof/-/indexof-0.0.1.tgz", + "integrity": "sha1-gtwzbSMrkGIXnQWrMpOmYFn9Q10=", + "dev": true + }, + "infer-owner": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz", + "integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + }, + "ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true + }, + "inquirer": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-6.2.1.tgz", + "integrity": "sha512-088kl3DRT2dLU5riVMKKr1DlImd6X7smDhpXUCkJDCKvTEJeRiXh0G132HG9u5a+6Ylw9plFRY7RuTnwohYSpg==", + "dev": true, + "requires": { + "ansi-escapes": "^3.0.0", + "chalk": "^2.0.0", + "cli-cursor": "^2.1.0", + "cli-width": "^2.0.0", + "external-editor": "^3.0.0", + "figures": "^2.0.0", + "lodash": "^4.17.10", + "mute-stream": "0.0.7", + "run-async": "^2.2.0", + "rxjs": "^6.1.0", + "string-width": "^2.1.0", + "strip-ansi": "^5.0.0", + "through": "^2.3.6" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + }, + "dependencies": { + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + } + } + }, + "strip-ansi": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.0.0.tgz", + "integrity": "sha512-Uu7gQyZI7J7gn5qLn1Np3G9vcYGTVqB+lFTytnDJv83dd8T22aGH451P3jueT2/QemInJDfxHB5Tde5OzgG1Ow==", + "dev": true, + "requires": { + "ansi-regex": "^4.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.0.0.tgz", + "integrity": "sha512-iB5Dda8t/UqpPI/IjsejXu5jOGDrzn41wJyljwPH65VCIbk6+1BzFIMJGFwTNrYXT1CrD+B4l19U7awiQ8rk7w==", + "dev": true + } + } + } + } + }, + "internal-ip": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/internal-ip/-/internal-ip-3.0.1.tgz", + "integrity": "sha512-NXXgESC2nNVtU+pqmC9e6R8B1GpKxzsAQhffvh5AL79qKnodd+L7tnEQmTiUAVngqLalPbSqRA7XGIEL5nCd0Q==", + "dev": true, + "requires": { + "default-gateway": "^2.6.0", + "ipaddr.js": "^1.5.2" + } + }, + "interpret": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.2.0.tgz", + "integrity": "sha512-mT34yGKMNceBQUoVn7iCDKDntA7SC6gycMAWzGx1z/CMCTV7b2AAtXlo3nRyHZ1FelRkQbQjprHSYGwzLtkVbw==", + "dev": true + }, + "invariant": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", + "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", + "dev": true, + "requires": { + "loose-envify": "^1.0.0" + } + }, + "invert-kv": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz", + "integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY=" + }, + "ip": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz", + "integrity": "sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo=", + "dev": true + }, + "ip-regex": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-2.1.0.tgz", + "integrity": "sha1-+ni/XS5pE8kRzp+BnuUUa7bYROk=", + "dev": true + }, + "ipaddr.js": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.8.0.tgz", + "integrity": "sha1-6qM9bd16zo9/b+DJygRA5wZzix4=", + "dev": true + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", + "dev": true + }, + "is-binary-path": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz", + "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=", + "dev": true, + "requires": { + "binary-extensions": "^1.0.0" + } + }, + "is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "dev": true + }, + "is-ci": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz", + "integrity": "sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==", + "dev": true, + "requires": { + "ci-info": "^2.0.0" + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + }, + "dependencies": { + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true + } + } + }, + "is-directory": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/is-directory/-/is-directory-0.3.1.tgz", + "integrity": "sha1-YTObbyR1/Hcv2cnYP1yFddwVSuE=", + "dev": true + }, + "is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", + "dev": true + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "dev": true + }, + "is-finite": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.0.2.tgz", + "integrity": "sha1-zGZ3aVYCvlUO8R6LSqYwU0K20Ko=", + "dev": true, + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "is-glob": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.0.tgz", + "integrity": "sha1-lSHHaEXMJhCoUgPd8ICpWML/q8A=", + "dev": true, + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-installed-globally": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.1.0.tgz", + "integrity": "sha1-Df2Y9akRFxbdU13aZJL2e/PSWoA=", + "dev": true, + "requires": { + "global-dirs": "^0.1.0", + "is-path-inside": "^1.0.0" + } + }, + "is-npm": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-npm/-/is-npm-3.0.0.tgz", + "integrity": "sha512-wsigDr1Kkschp2opC4G3yA6r9EgVA6NjRpWzIi9axXqeIaAATPRJc4uLujXe3Nd9uO8KoDyA4MD6aZSeXTADhA==", + "dev": true + }, + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-obj": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", + "integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8=" + }, + "is-path-cwd": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-1.0.0.tgz", + "integrity": "sha1-0iXsIxMuie3Tj9p2dHLmLmXxEG0=", + "dev": true + }, + "is-path-in-cwd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-1.0.1.tgz", + "integrity": "sha512-FjV1RTW48E7CWM7eE/J2NJvAEEVektecDBVBE5Hh3nM1Jd0kvhHtX68Pr3xsDf857xt3Y4AkwVULK1Vku62aaQ==", + "dev": true, + "requires": { + "is-path-inside": "^1.0.0" + } + }, + "is-path-inside": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-1.0.1.tgz", + "integrity": "sha1-jvW33lBDej/cprToZe96pVy0gDY=", + "dev": true, + "requires": { + "path-is-inside": "^1.0.1" + } + }, + "is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dev": true, + "requires": { + "isobject": "^3.0.1" + } + }, + "is-promise": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz", + "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=", + "dev": true + }, + "is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", + "dev": true + }, + "is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", + "dev": true + }, + "is-utf8": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", + "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=", + "dev": true + }, + "is-windows": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", + "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", + "dev": true + }, + "is-wsl": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz", + "integrity": "sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0=", + "dev": true + }, + "is-yarn-global": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/is-yarn-global/-/is-yarn-global-0.3.0.tgz", + "integrity": "sha512-VjSeb/lHmkoyd8ryPVIKvOCn4D1koMqY+vqyjjUfc3xyKtP4dYOxM44sZrnqQSzSds3xyOrUTLTC9LVCVgLngw==", + "dev": true + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "isbinaryfile": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-3.0.3.tgz", + "integrity": "sha512-8cJBL5tTd2OS0dM4jz07wQd5g0dCCqIhUxPIGtZfa5L6hWlvV5MHTITy/DBAsF+Oe2LS1X3krBUhNwaGUWpWxw==", + "dev": true, + "requires": { + "buffer-alloc": "^1.2.0" + } + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + }, + "isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", + "dev": true + }, + "istanbul-api": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/istanbul-api/-/istanbul-api-2.1.7.tgz", + "integrity": "sha512-LYTOa2UrYFyJ/aSczZi/6lBykVMjCCvUmT64gOe+jPZFy4w6FYfPGqFT2IiQ2BxVHHDOvCD7qrIXb0EOh4uGWw==", + "dev": true, + "requires": { + "async": "^2.6.2", + "compare-versions": "^3.4.0", + "fileset": "^2.0.3", + "istanbul-lib-coverage": "^2.0.5", + "istanbul-lib-hook": "^2.0.7", + "istanbul-lib-instrument": "^3.3.0", + "istanbul-lib-report": "^2.0.8", + "istanbul-lib-source-maps": "^3.0.6", + "istanbul-reports": "^2.2.5", + "js-yaml": "^3.13.1", + "make-dir": "^2.1.0", + "minimatch": "^3.0.4", + "once": "^1.4.0" + }, + "dependencies": { + "@babel/code-frame": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.7.tgz", + "integrity": "sha512-iAXqUn8IIeBTNd72xsFlgaXHkMBMt6y4HJp1tIaK465CWLT/fG1aqB7ykr95gHHmlBdGbFeWWfyB4NJJ0nmeIg==", + "dev": true, + "requires": { + "@babel/highlight": "^7.16.7" + } + }, + "@babel/generator": { + "version": "7.16.8", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.16.8.tgz", + "integrity": "sha512-1ojZwE9+lOXzcWdWmO6TbUzDfqLD39CmEhN8+2cX9XkDo5yW1OpgfejfliysR2AWLpMamTiOiAp/mtroaymhpw==", + "dev": true, + "requires": { + "@babel/types": "^7.16.8", + "jsesc": "^2.5.1", + "source-map": "^0.5.0" + } + }, + "@babel/helper-function-name": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.16.7.tgz", + "integrity": "sha512-QfDfEnIUyyBSR3HtrtGECuZ6DAyCkYFp7GHl75vFtTnn6pjKeK0T1DB5lLkFvBea8MdaiUABx3osbgLyInoejA==", + "dev": true, + "requires": { + "@babel/helper-get-function-arity": "^7.16.7", + "@babel/template": "^7.16.7", + "@babel/types": "^7.16.7" + } + }, + "@babel/helper-get-function-arity": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.16.7.tgz", + "integrity": "sha512-flc+RLSOBXzNzVhcLu6ujeHUrD6tANAOU5ojrRx/as+tbzf8+stUCj7+IfRRoAbEZqj/ahXEMsjhOhgeZsrnTw==", + "dev": true, + "requires": { + "@babel/types": "^7.16.7" + } + }, + "@babel/helper-split-export-declaration": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.16.7.tgz", + "integrity": "sha512-xbWoy/PFoxSWazIToT9Sif+jJTlrMcndIsaOKvTA6u7QEo7ilkRZpjew18/W3c7nm8fXdUDXh02VXTbZ0pGDNw==", + "dev": true, + "requires": { + "@babel/types": "^7.16.7" + } + }, + "@babel/highlight": { + "version": "7.16.10", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.16.10.tgz", + "integrity": "sha512-5FnTQLSLswEj6IkgVw5KusNUUFY9ZGqe/TRFnP/BKYHYgfh7tc+C7mwiy95/yNP7Dh9x580Vv8r7u7ZfTBFxdw==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.16.7", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + } + }, + "@babel/parser": { + "version": "7.16.12", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.16.12.tgz", + "integrity": "sha512-VfaV15po8RiZssrkPweyvbGVSe4x2y+aciFCgn0n0/SJMR22cwofRV1mtnJQYcSB1wUTaA/X1LnA3es66MCO5A==", + "dev": true + }, + "@babel/template": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.16.7.tgz", + "integrity": "sha512-I8j/x8kHUrbYRTUxXrrMbfCa7jxkE7tZre39x3kjr9hvI82cK1FfqLygotcWN5kdPGWcLdWMHpSBavse5tWw3w==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.16.7", + "@babel/parser": "^7.16.7", + "@babel/types": "^7.16.7" + } + }, + "@babel/traverse": { + "version": "7.16.10", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.16.10.tgz", + "integrity": "sha512-yzuaYXoRJBGMlBhsMJoUW7G1UmSb/eXr/JHYM/MsOJgavJibLwASijW7oXBdw3NQ6T0bW7Ty5P/VarOs9cHmqw==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.16.7", + "@babel/generator": "^7.16.8", + "@babel/helper-environment-visitor": "^7.16.7", + "@babel/helper-function-name": "^7.16.7", + "@babel/helper-hoist-variables": "^7.16.7", + "@babel/helper-split-export-declaration": "^7.16.7", + "@babel/parser": "^7.16.10", + "@babel/types": "^7.16.8", + "debug": "^4.1.0", + "globals": "^11.1.0" + } + }, + "@babel/types": { + "version": "7.16.8", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.16.8.tgz", + "integrity": "sha512-smN2DQc5s4M7fntyjGtyIPbRJv6wW4rU/94fmYJ7PKQuZkC0qGMHXJbg6sNGt12JmVr4k5YaptI/XtiLJBnmIg==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.16.7", + "to-fast-properties": "^2.0.0" + } + }, + "async": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz", + "integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==", + "dev": true, + "requires": { + "lodash": "^4.17.14" + } + }, + "debug": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", + "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true + }, + "istanbul-lib-coverage": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.5.tgz", + "integrity": "sha512-8aXznuEPCJvGnMSRft4udDRDtb1V3pkQkMMI5LI+6HuQz5oQ4J2UFn1H82raA3qJtyOLkkwVqICBQkjnGtn5mA==", + "dev": true + }, + "istanbul-lib-hook": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/istanbul-lib-hook/-/istanbul-lib-hook-2.0.7.tgz", + "integrity": "sha512-vrRztU9VRRFDyC+aklfLoeXyNdTfga2EI3udDGn4cZ6fpSXpHLV9X6CHvfoMCPtggg8zvDDmC4b9xfu0z6/llA==", + "dev": true, + "requires": { + "append-transform": "^1.0.0" + } + }, + "istanbul-lib-instrument": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-3.3.0.tgz", + "integrity": "sha512-5nnIN4vo5xQZHdXno/YDXJ0G+I3dAm4XgzfSVTPLQpj/zAV2dV6Juy0yaf10/zrJOJeHoN3fraFe+XRq2bFVZA==", + "dev": true, + "requires": { + "@babel/generator": "^7.4.0", + "@babel/parser": "^7.4.3", + "@babel/template": "^7.4.0", + "@babel/traverse": "^7.4.3", + "@babel/types": "^7.4.0", + "istanbul-lib-coverage": "^2.0.5", + "semver": "^6.0.0" + } + }, + "istanbul-lib-report": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-2.0.8.tgz", + "integrity": "sha512-fHBeG573EIihhAblwgxrSenp0Dby6tJMFR/HvlerBsrCTD5bkUuoNtn3gVh29ZCS824cGGBPn7Sg7cNk+2xUsQ==", + "dev": true, + "requires": { + "istanbul-lib-coverage": "^2.0.5", + "make-dir": "^2.1.0", + "supports-color": "^6.1.0" + } + }, + "istanbul-lib-source-maps": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-3.0.6.tgz", + "integrity": "sha512-R47KzMtDJH6X4/YW9XTx+jrLnZnscW4VpNN+1PViSYTejLVPWv7oov+Duf8YQSPyVRUvueQqz1TcsC6mooZTXw==", + "dev": true, + "requires": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^2.0.5", + "make-dir": "^2.1.0", + "rimraf": "^2.6.3", + "source-map": "^0.6.1" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "istanbul-reports": { + "version": "2.2.7", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-2.2.7.tgz", + "integrity": "sha512-uu1F/L1o5Y6LzPVSVZXNOoD/KXpJue9aeLRd0sM9uMXfZvzomB0WxVamWb5ue8kA2vVWEmW7EG+A5n3f1kqHKg==", + "dev": true, + "requires": { + "html-escaper": "^2.0.0" + } + }, + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true + }, + "make-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", + "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", + "dev": true, + "requires": { + "pify": "^4.0.1", + "semver": "^5.6.0" + }, + "dependencies": { + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + } + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "dev": true + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + }, + "to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", + "dev": true + } + } + }, + "istanbul-instrumenter-loader": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-instrumenter-loader/-/istanbul-instrumenter-loader-3.0.1.tgz", + "integrity": "sha512-a5SPObZgS0jB/ixaKSMdn6n/gXSrK2S6q/UfRJBT3e6gQmVjwZROTODQsYW5ZNwOu78hG62Y3fWlebaVOL0C+w==", + "dev": true, + "requires": { + "convert-source-map": "^1.5.0", + "istanbul-lib-instrument": "^1.7.3", + "loader-utils": "^1.1.0", + "schema-utils": "^0.3.0" + }, + "dependencies": { + "ajv": { + "version": "5.5.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", + "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=", + "dev": true, + "requires": { + "co": "^4.6.0", + "fast-deep-equal": "^1.0.0", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.3.0" + } + }, + "fast-deep-equal": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz", + "integrity": "sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ=", + "dev": true + }, + "json-schema-traverse": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz", + "integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=", + "dev": true + }, + "schema-utils": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-0.3.0.tgz", + "integrity": "sha1-9YdyIs4+kx7a4DnxfrNxbnE3+M8=", + "dev": true, + "requires": { + "ajv": "^5.0.0" + } + } + } + }, + "istanbul-lib-coverage": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-1.2.1.tgz", + "integrity": "sha512-PzITeunAgyGbtY1ibVIUiV679EFChHjoMNRibEIobvmrCRaIgwLxNucOSimtNWUhEib/oO7QY2imD75JVgCJWQ==", + "dev": true + }, + "istanbul-lib-instrument": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-1.10.2.tgz", + "integrity": "sha512-aWHxfxDqvh/ZlxR8BBaEPVSWDPUkGD63VjGQn3jcw8jCp7sHEMKcrj4xfJn/ABzdMEHiQNyvDQhqm5o8+SQg7A==", + "dev": true, + "requires": { + "babel-generator": "^6.18.0", + "babel-template": "^6.16.0", + "babel-traverse": "^6.18.0", + "babel-types": "^6.18.0", + "babylon": "^6.18.0", + "istanbul-lib-coverage": "^1.2.1", + "semver": "^5.3.0" + } + }, + "jasmine": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/jasmine/-/jasmine-2.8.0.tgz", + "integrity": "sha1-awicChFXax8W3xG4AUbZHU6Lij4=", + "dev": true, + "requires": { + "exit": "^0.1.2", + "glob": "^7.0.6", + "jasmine-core": "~2.8.0" + }, + "dependencies": { + "jasmine-core": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-2.8.0.tgz", + "integrity": "sha1-vMl5rh+f0FcB5F5S5l06XWPxok4=", + "dev": true + } + } + }, + "jasmine-core": { + "version": "2.99.1", + "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-2.99.1.tgz", + "integrity": "sha1-5kAN8ea1bhMLYcS80JPap/boyhU=", + "dev": true + }, + "jasmine-spec-reporter": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/jasmine-spec-reporter/-/jasmine-spec-reporter-4.2.1.tgz", + "integrity": "sha512-FZBoZu7VE5nR7Nilzy+Np8KuVIOxF4oXDPDknehCYBDE080EnlPu0afdZNmpGDBRCUBv3mj5qgqCRmk6W/K8vg==", + "dev": true, + "requires": { + "colors": "1.1.2" + } + }, + "jasminewd2": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/jasminewd2/-/jasminewd2-2.2.0.tgz", + "integrity": "sha1-43zwsX8ZnM4jvqcbIDk5Uka07E4=", + "dev": true + }, + "js-base64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.5.1.tgz", + "integrity": "sha512-M7kLczedRMYX4L8Mdh4MzyAMM9O5osx+4FcOQuTvr3A9F2D9S5JXheN0ewNbrvK2UatkTRhL5ejGmGSjNMiZuw==", + "dev": true, + "optional": true + }, + "js-tokens": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", + "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=", + "dev": true + }, + "js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", + "dev": true + }, + "jsesc": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-1.3.0.tgz", + "integrity": "sha1-RsP+yMGJKxKwgz25vHYiF226s0s=", + "dev": true + }, + "json-buffer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.0.tgz", + "integrity": "sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg=", + "dev": true + }, + "json-parse-better-errors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", + "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", + "dev": true + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", + "dev": true + }, + "json3": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/json3/-/json3-3.3.2.tgz", + "integrity": "sha1-PAQ0dD35Pi9cQq7nsZvLSDV19OE=", + "dev": true + }, + "json5": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "dev": true, + "requires": { + "minimist": "^1.2.0" + } + }, + "jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.6" + } + }, + "jsonparse": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", + "integrity": "sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA=", + "dev": true + }, + "jsprim": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz", + "integrity": "sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==", + "dev": true, + "requires": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.4.0", + "verror": "1.10.0" + }, + "dependencies": { + "json-schema": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", + "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==", + "dev": true + } + } + }, + "jszip": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.7.1.tgz", + "integrity": "sha512-ghL0tz1XG9ZEmRMcEN2vt7xabrDdqHHeykgARpmZ0BiIctWxM47Vt63ZO2dnp4QYt/xJVLLy5Zv1l/xRdh2byg==", + "dev": true, + "requires": { + "lie": "~3.3.0", + "pako": "~1.0.2", + "readable-stream": "~2.3.6", + "set-immediate-shim": "~1.0.1" + } + }, + "junk": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/junk/-/junk-3.1.0.tgz", + "integrity": "sha512-pBxcB3LFc8QVgdggvZWyeys+hnrNWg4OcZIU/1X59k5jQdLBlCsYGRQaz234SqoRLTCgMH00fY0xRJH+F9METQ==", + "dev": true + }, + "karma": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/karma/-/karma-4.0.0.tgz", + "integrity": "sha512-EFoFs3F6G0BcUGPNOn/YloGOb3h09hzTguyXlg6loHlKY76qbJikkcyPk43m2kfRF65TUGda/mig29QQtyhm1g==", + "dev": true, + "requires": { + "bluebird": "^3.3.0", + "body-parser": "^1.16.1", + "chokidar": "^2.0.3", + "colors": "^1.1.0", + "combine-lists": "^1.0.0", + "connect": "^3.6.0", + "core-js": "^2.2.0", + "di": "^0.0.1", + "dom-serialize": "^2.2.0", + "expand-braces": "^0.1.1", + "flatted": "^2.0.0", + "glob": "^7.1.1", + "graceful-fs": "^4.1.2", + "http-proxy": "^1.13.0", + "isbinaryfile": "^3.0.0", + "lodash": "^4.17.5", + "log4js": "^3.0.0", + "mime": "^2.3.1", + "minimatch": "^3.0.2", + "optimist": "^0.6.1", + "qjobs": "^1.1.4", + "range-parser": "^1.2.0", + "rimraf": "^2.6.0", + "safe-buffer": "^5.0.1", + "socket.io": "2.1.1", + "source-map": "^0.6.1", + "tmp": "0.0.33", + "useragent": "2.3.0" + }, + "dependencies": { + "mime": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.0.tgz", + "integrity": "sha512-ikBcWwyqXQSHKtciCcctu9YfPbFYZ4+gbHEmE0Q8jzcTYQg5dHCr3g2wwAZjPoJfQVXZq6KXAjpXOTf5/cjT7w==", + "dev": true + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "karma-chrome-launcher": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/karma-chrome-launcher/-/karma-chrome-launcher-2.2.0.tgz", + "integrity": "sha512-uf/ZVpAabDBPvdPdveyk1EPgbnloPvFFGgmRhYLTDH7gEB4nZdSBk8yTU47w1g/drLSx5uMOkjKk7IWKfWg/+w==", + "dev": true, + "requires": { + "fs-access": "^1.0.0", + "which": "^1.2.1" + } + }, + "karma-coverage-istanbul-reporter": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/karma-coverage-istanbul-reporter/-/karma-coverage-istanbul-reporter-2.0.5.tgz", + "integrity": "sha512-yPvAlKtY3y+rKKWbOo0CzBMVTvJEeMOgbMXuVv3yWvS8YtYKC98AU9vFF0mVBZ2RP1E9SgS90+PT6Kf14P3S4w==", + "dev": true, + "requires": { + "istanbul-api": "^2.1.1", + "minimatch": "^3.0.4" + } + }, + "karma-jasmine": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/karma-jasmine/-/karma-jasmine-1.1.2.tgz", + "integrity": "sha1-OU8rJf+0pkS5rabyLUQ+L9CIhsM=", + "dev": true + }, + "karma-jasmine-html-reporter": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/karma-jasmine-html-reporter/-/karma-jasmine-html-reporter-0.2.2.tgz", + "integrity": "sha1-SKjl7xiAdhfuK14zwRlMNbQ5Ukw=", + "dev": true, + "requires": { + "karma-jasmine": "^1.0.2" + } + }, + "karma-source-map-support": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/karma-source-map-support/-/karma-source-map-support-1.3.0.tgz", + "integrity": "sha512-HcPqdAusNez/ywa+biN4EphGz62MmQyPggUsDfsHqa7tSe4jdsxgvTKuDfIazjL+IOxpVWyT7Pr4dhAV+sxX5Q==", + "dev": true, + "requires": { + "source-map-support": "^0.5.5" + } + }, + "keyv": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-3.1.0.tgz", + "integrity": "sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA==", + "dev": true, + "requires": { + "json-buffer": "3.0.0" + } + }, + "killable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/killable/-/killable-1.0.1.tgz", + "integrity": "sha512-LzqtLKlUwirEUyl/nicirVmNiPvYs7l5n8wOPP7fyJVpUPkvCnW/vuiXGpylGUlnPDnB7311rARzAt3Mhswpjg==", + "dev": true + }, + "kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true + }, + "latest-version": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-5.1.0.tgz", + "integrity": "sha512-weT+r0kTkRQdCdYCNtkMwWXQTMEswKrFBkm4ckQOMVhhqhIMI1UT2hMj+1iigIhgSZm5gTmrRXBNoGUgaTY1xA==", + "dev": true, + "requires": { + "package-json": "^6.3.0" + } + }, + "lazy-val": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/lazy-val/-/lazy-val-1.0.4.tgz", + "integrity": "sha512-u93kb2fPbIrfzBuLjZE+w+fJbUUMhNDXxNmMfaqNgpfQf1CO5ZSe2LfsnBqVAk7i/2NF48OSoRj+Xe2VT+lE8Q==", + "dev": true + }, + "lcid": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz", + "integrity": "sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU=", + "requires": { + "invert-kv": "^1.0.0" + } + }, + "less": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/less/-/less-3.9.0.tgz", + "integrity": "sha512-31CmtPEZraNUtuUREYjSqRkeETFdyEHSEPAGq4erDlUXtda7pzNmctdljdIagSb589d/qXGWiiP31R5JVf+v0w==", + "dev": true, + "requires": { + "clone": "^2.1.2", + "errno": "^0.1.1", + "graceful-fs": "^4.1.2", + "image-size": "~0.5.0", + "mime": "^1.4.1", + "mkdirp": "^0.5.0", + "promise": "^7.1.1", + "request": "^2.83.0", + "source-map": "~0.6.0" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "optional": true + } + } + }, + "less-loader": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/less-loader/-/less-loader-4.1.0.tgz", + "integrity": "sha512-KNTsgCE9tMOM70+ddxp9yyt9iHqgmSs0yTZc5XH5Wo+g80RWRIYNqE58QJKm/yMud5wZEvz50ugRDuzVIkyahg==", + "dev": true, + "requires": { + "clone": "^2.1.1", + "loader-utils": "^1.1.0", + "pify": "^3.0.0" + } + }, + "libphonenumber-js": { + "version": "0.4.52", + "resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-0.4.52.tgz", + "integrity": "sha512-Ns5FcEp8W/lZd+lKu7OB46RYBKcncN/S3C51OFh7qXbu6kOFAd75A4M0jtasK3MUWMxKsKXII4WL+GbqaLa2DQ==", + "requires": { + "babel-runtime": "^6.6.1", + "bluebird": "^3.4.6", + "minimist": "^1.2.0", + "xml2js": "^0.4.17" + } + }, + "license-webpack-plugin": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/license-webpack-plugin/-/license-webpack-plugin-2.1.0.tgz", + "integrity": "sha512-vDiBeMWxjE9n6TabQ9J4FH8urFdsRK0Nvxn1cit9biCiR9aq1zBR0X2BlAkEiIG6qPamLeU0GzvIgLkrFc398A==", + "dev": true, + "requires": { + "@types/webpack-sources": "^0.1.5", + "webpack-sources": "^1.2.0" + } + }, + "lie": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", + "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", + "dev": true, + "requires": { + "immediate": "~3.0.5" + } + }, + "load-json-file": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", + "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "parse-json": "^2.2.0", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0", + "strip-bom": "^2.0.0" + }, + "dependencies": { + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true + } + } + }, + "loader-runner": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-2.4.0.tgz", + "integrity": "sha512-Jsmr89RcXGIwivFY21FcRrisYZfvLMTWx5kOLc+JTxtpBOG6xML0vzbc6SEQG2FO9/4Fc3wW4LVcB5DmGflaRw==", + "dev": true + }, + "loader-utils": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.2.3.tgz", + "integrity": "sha512-fkpz8ejdnEMG3s37wGL07iSBDg99O9D5yflE9RGNH3hRdx9SOwYfnGYdZOUIZitN8E+E2vkq3MUMYMvPYl5ZZA==", + "dev": true, + "requires": { + "big.js": "^5.2.2", + "emojis-list": "^2.0.0", + "json5": "^1.0.1" + } + }, + "locate-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", + "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", + "dev": true, + "requires": { + "p-locate": "^2.0.0", + "path-exists": "^3.0.0" + } + }, + "lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, + "lodash.assign": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.assign/-/lodash.assign-4.2.0.tgz", + "integrity": "sha1-DZnzzNem0mHRm9rrkkUAXShYCOc=", + "dev": true, + "optional": true + }, + "lodash.camelcase": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", + "integrity": "sha1-soqmKIorn8ZRA1x3EfZathkDMaY=" + }, + "lodash.clone": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.clone/-/lodash.clone-4.5.0.tgz", + "integrity": "sha1-GVhwRQ9aExkkeN9Lw9I9LeoZB7Y=" + }, + "lodash.clonedeep": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", + "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=", + "dev": true + }, + "lodash.debounce": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168=", + "dev": true + }, + "lodash.get": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", + "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=", + "dev": true + }, + "lodash.mergewith": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.2.tgz", + "integrity": "sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ==", + "dev": true, + "optional": true + }, + "lodash.tail": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.tail/-/lodash.tail-4.1.1.tgz", + "integrity": "sha1-0jM6NtnncXyK0vfKyv7HwytERmQ=", + "dev": true + }, + "log4js": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/log4js/-/log4js-3.0.6.tgz", + "integrity": "sha512-ezXZk6oPJCWL483zj64pNkMuY/NcRX5MPiB0zE6tjZM137aeusrOnW1ecxgF9cmwMWkBMhjteQxBPoZBh9FDxQ==", + "dev": true, + "requires": { + "circular-json": "^0.5.5", + "date-format": "^1.2.0", + "debug": "^3.1.0", + "rfdc": "^1.1.2", + "streamroller": "0.7.0" + }, + "dependencies": { + "debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "dev": true + } + } + }, + "loglevel": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.6.1.tgz", + "integrity": "sha1-4PyVEztu8nbNyIh82vJKpvFW+Po=", + "dev": true + }, + "long": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/long/-/long-3.2.0.tgz", + "integrity": "sha1-2CG3E4yhy1gcFymQ7xTbIAtcR0s=" + }, + "loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dev": true, + "requires": { + "js-tokens": "^3.0.0 || ^4.0.0" + } + }, + "loud-rejection": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/loud-rejection/-/loud-rejection-1.6.0.tgz", + "integrity": "sha1-W0b4AUft7leIcPCG0Eghz5mOVR8=", + "dev": true, + "requires": { + "currently-unhandled": "^0.4.1", + "signal-exit": "^3.0.0" + } + }, + "lowercase-keys": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", + "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==", + "dev": true + }, + "lru-cache": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", + "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", + "dev": true, + "requires": { + "pseudomap": "^1.0.2", + "yallist": "^2.1.2" + } + }, + "magic-string": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.2.tgz", + "integrity": "sha512-iLs9mPjh9IuTtRsqqhNGYcZXGei0Nh/A4xirrsqW7c+QhKVFL2vm7U09ru6cHRD22azaP/wMDgI+HCqbETMTtg==", + "dev": true, + "requires": { + "sourcemap-codec": "^1.4.4" + } + }, + "make-dir": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz", + "integrity": "sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==", + "requires": { + "pify": "^3.0.0" + } + }, + "make-error": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.5.tgz", + "integrity": "sha512-c3sIjNUow0+8swNwVpqoH4YCShKNFkMaw6oH1mNS2haDZQqkeZFlHS3dhoeEbKKmJB4vXpJucU6oH75aDYeE9g==", + "dev": true + }, + "make-fetch-happen": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-4.0.1.tgz", + "integrity": "sha512-7R5ivfy9ilRJ1EMKIOziwrns9fGeAD4bAha8EB7BIiBBLHm2KeTUGCrICFt2rbHfzheTLynv50GnNTK1zDTrcQ==", + "dev": true, + "requires": { + "agentkeepalive": "^3.4.1", + "cacache": "^11.0.1", + "http-cache-semantics": "^3.8.1", + "http-proxy-agent": "^2.1.0", + "https-proxy-agent": "^2.2.1", + "lru-cache": "^4.1.2", + "mississippi": "^3.0.0", + "node-fetch-npm": "^2.0.2", + "promise-retry": "^1.1.1", + "socks-proxy-agent": "^4.0.0", + "ssri": "^6.0.0" + }, + "dependencies": { + "cacache": { + "version": "11.3.2", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-11.3.2.tgz", + "integrity": "sha512-E0zP4EPGDOaT2chM08Als91eYnf8Z+eH1awwwVsngUmgppfM5jjJ8l3z5vO5p5w/I3LsiXawb1sW0VY65pQABg==", + "dev": true, + "requires": { + "bluebird": "^3.5.3", + "chownr": "^1.1.1", + "figgy-pudding": "^3.5.1", + "glob": "^7.1.3", + "graceful-fs": "^4.1.15", + "lru-cache": "^5.1.1", + "mississippi": "^3.0.0", + "mkdirp": "^0.5.1", + "move-concurrently": "^1.0.1", + "promise-inflight": "^1.0.1", + "rimraf": "^2.6.2", + "ssri": "^6.0.1", + "unique-filename": "^1.1.1", + "y18n": "^4.0.0" + }, + "dependencies": { + "lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "requires": { + "yallist": "^3.0.2" + } + } + } + }, + "mississippi": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mississippi/-/mississippi-3.0.0.tgz", + "integrity": "sha512-x471SsVjUtBRtcvd4BzKE9kFC+/2TeWgKCgw0bZcw1b9l2X3QX5vCWgF+KaZaYm87Ss//rHnWryupDrgLvmSkA==", + "dev": true, + "requires": { + "concat-stream": "^1.5.0", + "duplexify": "^3.4.2", + "end-of-stream": "^1.1.0", + "flush-write-stream": "^1.0.0", + "from2": "^2.1.0", + "parallel-transform": "^1.1.0", + "pump": "^3.0.0", + "pumpify": "^1.3.3", + "stream-each": "^1.1.0", + "through2": "^2.0.0" + } + }, + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "ssri": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.2.tgz", + "integrity": "sha512-cepbSq/neFK7xB6A50KHN0xHDotYzq58wWCa5LeWqnPrHG8GzfEjO/4O8kpmcGW+oaxkvhEJCWgbgNk4/ZV93Q==", + "dev": true, + "requires": { + "figgy-pudding": "^3.5.1" + } + }, + "yallist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.3.tgz", + "integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==", + "dev": true + } + } + }, + "map-age-cleaner": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz", + "integrity": "sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w==", + "dev": true, + "requires": { + "p-defer": "^1.0.0" + } + }, + "map-cache": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", + "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=", + "dev": true + }, + "map-obj": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", + "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=", + "dev": true + }, + "map-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", + "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=", + "dev": true, + "requires": { + "object-visit": "^1.0.0" + } + }, + "material-colors": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/material-colors/-/material-colors-1.2.6.tgz", + "integrity": "sha512-6qE4B9deFBIa9YSpOc9O0Sgc43zTeVYbgDT5veRKSlB2+ZuHNoVVxA1L/ckMUayV9Ay9y7Z/SZCLcGteW9i7bg==", + "dev": true + }, + "md5.js": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", + "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==", + "dev": true, + "requires": { + "hash-base": "^3.0.0", + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", + "dev": true + }, + "mem": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/mem/-/mem-4.1.0.tgz", + "integrity": "sha512-I5u6Q1x7wxO0kdOpYBB28xueHADYps5uty/zg936CiG8NTe5sJL8EjrCuLneuDW3PlMdZBGDIn8BirEVdovZvg==", + "dev": true, + "requires": { + "map-age-cleaner": "^0.1.1", + "mimic-fn": "^1.0.0", + "p-is-promise": "^2.0.0" + } + }, + "memory-fs": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.4.1.tgz", + "integrity": "sha1-OpoguEYlI+RHz7x+i7gO1me/xVI=", + "dev": true, + "requires": { + "errno": "^0.1.3", + "readable-stream": "^2.0.1" + } + }, + "meow": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/meow/-/meow-3.7.0.tgz", + "integrity": "sha1-cstmi0JSKCkKu/qFaJJYcwioAfs=", + "dev": true, + "requires": { + "camelcase-keys": "^2.0.0", + "decamelize": "^1.1.2", + "loud-rejection": "^1.0.0", + "map-obj": "^1.0.1", + "minimist": "^1.1.3", + "normalize-package-data": "^2.3.4", + "object-assign": "^4.0.1", + "read-pkg-up": "^1.0.1", + "redent": "^1.0.0", + "trim-newlines": "^1.0.0" + } + }, + "merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=", + "dev": true + }, + "methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=", + "dev": true + }, + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "dev": true, + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + } + }, + "miller-rabin": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.1.tgz", + "integrity": "sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==", + "dev": true, + "requires": { + "bn.js": "^4.0.0", + "brorand": "^1.0.1" + } + }, + "mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "dev": true, + "optional": true + }, + "mime-db": { + "version": "1.38.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.38.0.tgz", + "integrity": "sha512-bqVioMFFzc2awcdJZIzR3HjZFX20QhilVS7hytkKrv7xFAn8bM1gzc/FOX2awLISvWe0PV8ptFKcon+wZ5qYkg==", + "dev": true + }, + "mime-types": { + "version": "2.1.22", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.22.tgz", + "integrity": "sha512-aGl6TZGnhm/li6F7yx82bJiBZwgiEa4Hf6CNr8YO+r5UHr53tSTYZb102zyU50DOWWKeOv0uQLRL0/9EiKWCog==", + "dev": true, + "requires": { + "mime-db": "~1.38.0" + } + }, + "mimic-fn": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", + "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==", + "dev": true + }, + "mimic-response": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", + "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", + "dev": true + }, + "mini-css-extract-plugin": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-0.5.0.tgz", + "integrity": "sha512-IuaLjruM0vMKhUUT51fQdQzBYTX49dLj8w68ALEAe2A4iYNpIC4eMac67mt3NzycvjOlf07/kYxJDc0RTl1Wqw==", + "dev": true, + "requires": { + "loader-utils": "^1.1.0", + "schema-utils": "^1.0.0", + "webpack-sources": "^1.1.0" + } + }, + "minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", + "dev": true + }, + "minimalistic-crypto-utils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", + "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=", + "dev": true + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" + }, + "minipass": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.3.5.tgz", + "integrity": "sha512-Gi1W4k059gyRbyVUZQ4mEqLm0YIUiGYfvxhF6SIlk3ui1WVxMTGfGdQ2SInh3PDrRTVvPKgULkpJtT4RH10+VA==", + "dev": true, + "requires": { + "safe-buffer": "^5.1.2", + "yallist": "^3.0.0" + }, + "dependencies": { + "yallist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.3.tgz", + "integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==", + "dev": true + } + } + }, + "mississippi": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mississippi/-/mississippi-2.0.0.tgz", + "integrity": "sha512-zHo8v+otD1J10j/tC+VNoGK9keCuByhKovAvdn74dmxJl9+mWHnx6EMsDN4lgRoMI/eYo2nchAxniIbUPb5onw==", + "dev": true, + "requires": { + "concat-stream": "^1.5.0", + "duplexify": "^3.4.2", + "end-of-stream": "^1.1.0", + "flush-write-stream": "^1.0.0", + "from2": "^2.1.0", + "parallel-transform": "^1.1.0", + "pump": "^2.0.1", + "pumpify": "^1.3.3", + "stream-each": "^1.1.0", + "through2": "^2.0.0" + } + }, + "mixin-deep": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz", + "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==", + "dev": true, + "requires": { + "for-in": "^1.0.2", + "is-extendable": "^1.0.1" + }, + "dependencies": { + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, + "requires": { + "is-plain-object": "^2.0.4" + } + } + } + }, + "mixin-object": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mixin-object/-/mixin-object-2.0.1.tgz", + "integrity": "sha1-T7lJRB2rGCVA8f4DW6YOGUel5X4=", + "dev": true, + "requires": { + "for-in": "^0.1.3", + "is-extendable": "^0.1.1" + }, + "dependencies": { + "for-in": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/for-in/-/for-in-0.1.8.tgz", + "integrity": "sha1-2Hc5COMSVhCZUrH9ubP6hn0ndeE=", + "dev": true + } + } + }, + "mixpanel-browser": { + "version": "2.29.1", + "resolved": "https://registry.npmjs.org/mixpanel-browser/-/mixpanel-browser-2.29.1.tgz", + "integrity": "sha512-RSBqVBznOkKBz3MkCXRrkTEEXqoNNYAbASpjaCxvhpT5pykWhjh7JY54fAmOvtG9XNL3GHYA6XiB7Yos4ngNYQ==" + }, + "mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "dev": true, + "requires": { + "minimist": "^1.2.5" + } + }, + "moment": { + "version": "2.24.0", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.24.0.tgz", + "integrity": "sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg==" + }, + "move-concurrently": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz", + "integrity": "sha1-viwAX9oy4LKa8fBdfEszIUxwH5I=", + "dev": true, + "requires": { + "aproba": "^1.1.1", + "copy-concurrently": "^1.0.0", + "fs-write-stream-atomic": "^1.0.8", + "mkdirp": "^0.5.1", + "rimraf": "^2.5.4", + "run-queue": "^1.0.3" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, + "multicast-dns": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/multicast-dns/-/multicast-dns-6.2.3.tgz", + "integrity": "sha512-ji6J5enbMyGRHIAkAOu3WdV8nggqviKCEKtXcOqfphZZtQrmHKycfynJ2V7eVPUA4NhJ6V7Wf4TmGbTwKE9B6g==", + "dev": true, + "requires": { + "dns-packet": "^1.3.1", + "thunky": "^1.0.2" + } + }, + "multicast-dns-service-types": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/multicast-dns-service-types/-/multicast-dns-service-types-1.1.0.tgz", + "integrity": "sha1-iZ8R2WhuXgXLkbNdXw5jt3PPyQE=", + "dev": true + }, + "mute-stream": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz", + "integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=", + "dev": true + }, + "nan": { + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.12.1.tgz", + "integrity": "sha512-JY7V6lRkStKcKTvHO5NVSQRv+RV+FIL5pvDoLiAtSL9pKlC5x9PKQcZDsq7m4FO4d57mkhC6Z+QhAh3Jdk5JFw==", + "dev": true, + "optional": true + }, + "nanomatch": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", + "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", + "dev": true, + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "fragment-cache": "^0.2.1", + "is-windows": "^1.0.2", + "kind-of": "^6.0.2", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + } + }, + "negotiator": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz", + "integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk=", + "dev": true + }, + "neo-async": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.0.tgz", + "integrity": "sha512-MFh0d/Wa7vkKO3Y3LlacqAEeHK0mckVqzDieUKTT+KGxi+zIpeVsFxymkIiRpbpDziHc290Xr9A1O4Om7otoRA==", + "dev": true + }, + "ng2-charts": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/ng2-charts/-/ng2-charts-1.6.0.tgz", + "integrity": "sha512-9w0WH69x5/nuqC1og2WaY39NbaBqTGIP1+5gZaH7/KPN6UEPonNg/pYnsIVklLj1DWPWXKa8+XXIJZ1jy5nLxg==", + "requires": { + "chart.js": "^2.6.0" + }, + "dependencies": { + "chart.js": { + "version": "2.9.4", + "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-2.9.4.tgz", + "integrity": "sha512-B07aAzxcrikjAPyV+01j7BmOpxtQETxTSlQ26BEYJ+3iUkbNKaOJ/nDbT6JjyqYxseM0ON12COHYdU2cTIjC7A==", + "requires": { + "chartjs-color": "^2.1.0", + "moment": "^2.10.2" + } + } + } + }, + "ng2-dragula": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/ng2-dragula/-/ng2-dragula-1.3.1.tgz", + "integrity": "sha1-EDhMJAApBDOm+0XpHHRm5ojjrTc=", + "requires": { + "dragula": "^3.7.2" + } + }, + "ng2-file-upload": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ng2-file-upload/-/ng2-file-upload-1.2.1.tgz", + "integrity": "sha1-VWPF39b0P7++gVwgbjQ0ZKCmoZc=" + }, + "ng2-validation": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/ng2-validation/-/ng2-validation-4.2.0.tgz", + "integrity": "sha1-841EHT+36GIVUWZIAEWq/5rRHbs=", + "requires": { + "libphonenumber-js": "^0.4.5" + } + }, + "ngx-color": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/ngx-color/-/ngx-color-7.0.0.tgz", + "integrity": "sha512-BiTapBTT/f3sFSEFqet3xe06bpqmBm7hmA24s09ogRYYVGL1J69U13XfLwTQG8PhX4qNBceh7p5nhKA1zczHMg==", + "dev": true, + "requires": { + "@ctrl/tinycolor": "^3.4.0", + "material-colors": "^1.2.6", + "tslib": "^2.1.0" + }, + "dependencies": { + "tslib": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.2.0.tgz", + "integrity": "sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w==", + "dev": true + } + } + }, + "ngx-color-picker": { + "version": "5.3.8", + "resolved": "https://registry.npmjs.org/ngx-color-picker/-/ngx-color-picker-5.3.8.tgz", + "integrity": "sha512-LZ+GR6hZ/Tpv58mjIdnnH1uP1HyQmeZgBFQUL34914OBspeoD7f+vI4lZ7J3WpCTX8w2DzvnfXqzToWAx0SImA==" + }, + "ngx-electron": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ngx-electron/-/ngx-electron-2.1.1.tgz", + "integrity": "sha512-XCr/tiVR9J1SLbxy8TB0LjVzwahwYZCsHhFmng7pjsKUByWVkzDcvxo1EE+rR2MlHjE5jKmP3JH5qeLONdSAtw==", + "requires": { + "tslib": "^1.9.0" + } + }, + "ngx-pagination": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/ngx-pagination/-/ngx-pagination-3.1.0.tgz", + "integrity": "sha512-D/8Is3z0nipHCCQN0XZyh5/nhSrON/ybft3Wk8dfPHMGtjqzoOSorNaanypO35JD/jECcam/cS/evjlaqDAgQg==" + }, + "ngx-perfect-scrollbar": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ngx-perfect-scrollbar/-/ngx-perfect-scrollbar-6.1.0.tgz", + "integrity": "sha512-96/NE6gmP8+qF+ur84IQqDQtoLHhheKpQ+uDG11Z+WuOIuH7hHmZIKi1E7Wz+889s0AARGMTpgR05T0CMCxUew==", + "requires": { + "perfect-scrollbar": "^1.3.0", + "resize-observer-polyfill": "^1.4.0" + } + }, + "ngx-quill": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/ngx-quill/-/ngx-quill-2.0.4.tgz", + "integrity": "sha1-D1rBD2BjU04n1femGIlswlVzGJ4=", + "requires": { + "@types/quill": "^1.3.3", + "quill": "^1.3.2" + } + }, + "nice-try": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", + "dev": true + }, + "nimn-date-parser": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/nimn-date-parser/-/nimn-date-parser-1.0.0.tgz", + "integrity": "sha512-1Nf+x3EeMvHUiHsVuEhiZnwA8RMeOBVTQWfB1S2n9+i6PYCofHd2HRMD+WOHIHYshy4T4Gk8wQoCol7Hq3av8Q==" + }, + "nimn_schema_builder": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/nimn_schema_builder/-/nimn_schema_builder-1.1.0.tgz", + "integrity": "sha512-DK5/B8CM4qwzG2URy130avcwPev4uO0ev836FbQyKo1ms6I9z/i6EJyiZ+d9xtgloxUri0W+5gfR8YbPq7SheA==" + }, + "nimnjs": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/nimnjs/-/nimnjs-1.3.2.tgz", + "integrity": "sha512-TIOtI4iqkQrUM1tiM76AtTQem0c7e56SkDZ7sj1d1MfUsqRcq2ZWQvej/O+HBTZV7u/VKnwlKTDugK/75IRPPw==", + "requires": { + "nimn-date-parser": "^1.0.0", + "nimn_schema_builder": "^1.0.0" + } + }, + "node-fetch-npm": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/node-fetch-npm/-/node-fetch-npm-2.0.2.tgz", + "integrity": "sha512-nJIxm1QmAj4v3nfCvEeCrYSoVwXyxLnaPBK5W1W5DGEJwjlKuC2VEUycGw5oxk+4zZahRrB84PUJJgEmhFTDFw==", + "dev": true, + "requires": { + "encoding": "^0.1.11", + "json-parse-better-errors": "^1.0.0", + "safe-buffer": "^5.1.1" + } + }, + "node-forge": { + "version": "0.7.5", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.7.5.tgz", + "integrity": "sha512-MmbQJ2MTESTjt3Gi/3yG1wGpIMhUfcIypUCGtTizFR9IiccFwxSpfp0vtIZlkFclEqERemxfnSdZEMR9VqqEFQ==", + "dev": true + }, + "node-gyp": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-3.8.0.tgz", + "integrity": "sha512-3g8lYefrRRzvGeSowdJKAKyks8oUpLEd/DyPV4eMhVlhJ0aNaZqIrNUIPuEWWTAoPqyFkfGrM67MC69baqn6vA==", + "dev": true, + "optional": true, + "requires": { + "fstream": "^1.0.0", + "glob": "^7.0.3", + "graceful-fs": "^4.1.2", + "mkdirp": "^0.5.0", + "nopt": "2 || 3", + "npmlog": "0 || 1 || 2 || 3 || 4", + "osenv": "0", + "request": "^2.87.0", + "rimraf": "2", + "semver": "~5.3.0", + "tar": "^2.0.0", + "which": "1" + }, + "dependencies": { + "semver": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.3.0.tgz", + "integrity": "sha1-myzl094C0XxgEq0yaqa00M9U+U8=", + "dev": true, + "optional": true + } + } + }, + "node-libs-browser": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/node-libs-browser/-/node-libs-browser-2.2.0.tgz", + "integrity": "sha512-5MQunG/oyOaBdttrL40dA7bUfPORLRWMUJLQtMg7nluxUvk5XwnLdL9twQHFAjRx/y7mIMkLKT9++qPbbk6BZA==", + "dev": true, + "requires": { + "assert": "^1.1.1", + "browserify-zlib": "^0.2.0", + "buffer": "^4.3.0", + "console-browserify": "^1.1.0", + "constants-browserify": "^1.0.0", + "crypto-browserify": "^3.11.0", + "domain-browser": "^1.1.1", + "events": "^3.0.0", + "https-browserify": "^1.0.0", + "os-browserify": "^0.3.0", + "path-browserify": "0.0.0", + "process": "^0.11.10", + "punycode": "^1.2.4", + "querystring-es3": "^0.2.0", + "readable-stream": "^2.3.3", + "stream-browserify": "^2.0.1", + "stream-http": "^2.7.2", + "string_decoder": "^1.0.0", + "timers-browserify": "^2.0.4", + "tty-browserify": "0.0.0", + "url": "^0.11.0", + "util": "^0.11.0", + "vm-browserify": "0.0.4" + }, + "dependencies": { + "punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", + "dev": true + } + } + }, + "node-sass": { + "version": "4.11.0", + "resolved": "https://registry.npmjs.org/node-sass/-/node-sass-4.11.0.tgz", + "integrity": "sha512-bHUdHTphgQJZaF1LASx0kAviPH7sGlcyNhWade4eVIpFp6tsn7SV8xNMTbsQFpEV9VXpnwTTnNYlfsZXgGgmkA==", + "dev": true, + "optional": true, + "requires": { + "async-foreach": "^0.1.3", + "chalk": "^1.1.1", + "cross-spawn": "^3.0.0", + "gaze": "^1.0.0", + "get-stdin": "^4.0.1", + "glob": "^7.0.3", + "in-publish": "^2.0.0", + "lodash.assign": "^4.2.0", + "lodash.clonedeep": "^4.3.2", + "lodash.mergewith": "^4.6.0", + "meow": "^3.7.0", + "mkdirp": "^0.5.1", + "nan": "^2.10.0", + "node-gyp": "^3.8.0", + "npmlog": "^4.0.0", + "request": "^2.88.0", + "sass-graph": "^2.2.4", + "stdout-stream": "^1.4.0", + "true-case-path": "^1.0.2" + }, + "dependencies": { + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "dev": true, + "optional": true + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "optional": true, + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + } + }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true, + "optional": true + } + } + }, + "nopt": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz", + "integrity": "sha1-xkZdvwirzU2zWTF/eaxopkayj/k=", + "dev": true, + "optional": true, + "requires": { + "abbrev": "1" + } + }, + "normalize-package-data": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "dev": true, + "requires": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, + "normalize-path": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", + "dev": true, + "requires": { + "remove-trailing-separator": "^1.0.1" + } + }, + "normalize-range": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", + "integrity": "sha1-LRDAa9/TEuqXd2laTShDlFa3WUI=", + "dev": true + }, + "normalize-url": { + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-4.5.1.tgz", + "integrity": "sha512-9UZCFRHQdNrfTpGg8+1INIg93B6zE0aXMVFkw1WFwvO4SlZywU6aLg5Of0Ap/PgcbSw4LNxvMWXMeugwMCX0AA==", + "dev": true + }, + "npm-bundled": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.0.6.tgz", + "integrity": "sha512-8/JCaftHwbd//k6y2rEWp6k1wxVfpFzB6t1p825+cUb7Ym2XQfhwIC5KwhrvzZRJu+LtDE585zVaS32+CGtf0g==", + "dev": true + }, + "npm-package-arg": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-6.1.0.tgz", + "integrity": "sha512-zYbhP2k9DbJhA0Z3HKUePUgdB1x7MfIfKssC+WLPFMKTBZKpZh5m13PgexJjCq6KW7j17r0jHWcCpxEqnnncSA==", + "dev": true, + "requires": { + "hosted-git-info": "^2.6.0", + "osenv": "^0.1.5", + "semver": "^5.5.0", + "validate-npm-package-name": "^3.0.0" + } + }, + "npm-packlist": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-1.4.1.tgz", + "integrity": "sha512-+TcdO7HJJ8peiiYhvPxsEDhF3PJFGUGRcFsGve3vxvxdcpO2Z4Z7rkosRM0kWj6LfbK/P0gu3dzk5RU1ffvFcw==", + "dev": true, + "requires": { + "ignore-walk": "^3.0.1", + "npm-bundled": "^1.0.1" + } + }, + "npm-pick-manifest": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/npm-pick-manifest/-/npm-pick-manifest-2.2.3.tgz", + "integrity": "sha512-+IluBC5K201+gRU85vFlUwX3PFShZAbAgDNp2ewJdWMVSppdo/Zih0ul2Ecky/X7b51J7LrrUAP+XOmOCvYZqA==", + "dev": true, + "requires": { + "figgy-pudding": "^3.5.1", + "npm-package-arg": "^6.0.0", + "semver": "^5.4.1" + } + }, + "npm-registry-fetch": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/npm-registry-fetch/-/npm-registry-fetch-3.9.0.tgz", + "integrity": "sha512-srwmt8YhNajAoSAaDWndmZgx89lJwIZ1GWxOuckH4Coek4uHv5S+o/l9FLQe/awA+JwTnj4FJHldxhlXdZEBmw==", + "dev": true, + "requires": { + "JSONStream": "^1.3.4", + "bluebird": "^3.5.1", + "figgy-pudding": "^3.4.1", + "lru-cache": "^4.1.3", + "make-fetch-happen": "^4.0.1", + "npm-package-arg": "^6.1.0" + } + }, + "npm-run-path": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", + "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", + "dev": true, + "requires": { + "path-key": "^2.0.0" + } + }, + "npmlog": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", + "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", + "dev": true, + "optional": true, + "requires": { + "are-we-there-yet": "~1.1.2", + "console-control-strings": "~1.1.0", + "gauge": "~2.7.3", + "set-blocking": "~2.0.0" + } + }, + "nugget": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/nugget/-/nugget-2.0.1.tgz", + "integrity": "sha1-IBCVpIfhrTYIGzQy+jytpPjQcbA=", + "dev": true, + "requires": { + "debug": "^2.1.3", + "minimist": "^1.1.0", + "pretty-bytes": "^1.0.2", + "progress-stream": "^1.1.0", + "request": "^2.45.0", + "single-line-log": "^1.1.2", + "throttleit": "0.0.2" + } + }, + "null-check": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/null-check/-/null-check-1.0.0.tgz", + "integrity": "sha1-l33/1xdgErnsMNKjnbXPcqBDnt0=", + "dev": true + }, + "num2fraction": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/num2fraction/-/num2fraction-1.2.2.tgz", + "integrity": "sha1-b2gragJ6Tp3fpFZM0lidHU5mnt4=", + "dev": true + }, + "number-is-nan": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" + }, + "nwjs-builder-phoenix": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/nwjs-builder-phoenix/-/nwjs-builder-phoenix-1.15.0.tgz", + "integrity": "sha512-bYA1lsNwjm+WvfSfa2YxGanUGUN8CsSImBB6gkUZ+OWLxLAniga2sUrKcLaRi5l+qmLsDhbXmyNNppHsX4Ci5g==", + "dev": true, + "requires": { + "7zip-bin": "^2.0.4", + "bluebird": "^3.5.0", + "debug": "^2.6.1", + "dir-compare": "^1.3.0", + "fs-extra": "^3.0.1", + "globby": "^6.1.0", + "plist": "^2.0.1", + "progress": "^1.1.8", + "rcedit": "^0.8.0", + "request": "^2.80.0", + "request-progress": "^3.0.0", + "semver": "^5.3.0", + "source-map-support": "^0.4.11", + "tmp": "0.0.31", + "yargs": "^7.0.1" + }, + "dependencies": { + "7zip-bin": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/7zip-bin/-/7zip-bin-2.4.1.tgz", + "integrity": "sha512-QU3oR1dLLVrYGRkb7LU17jMCpIkWtXXW7q71ECXWXkR9vOv37VjykqpvFgs29HgSCNLZHnNKJzdG6RwAW0LwIA==", + "dev": true, + "requires": { + "7zip-bin-linux": "~1.3.1", + "7zip-bin-mac": "~1.0.1", + "7zip-bin-win": "~2.1.1" + } + }, + "base64-js": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.2.0.tgz", + "integrity": "sha1-o5mS1yNYSBGYK+XikLtqU9hnAPE=", + "dev": true + }, + "fs-extra": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-3.0.1.tgz", + "integrity": "sha1-N5TzeMWLNC6n27sjCVEJxLO2IpE=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "jsonfile": "^3.0.0", + "universalify": "^0.1.0" + } + }, + "globby": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-6.1.0.tgz", + "integrity": "sha1-9abXDoOV4hyFj7BInWTfAkJNUGw=", + "dev": true, + "requires": { + "array-union": "^1.0.1", + "glob": "^7.0.3", + "object-assign": "^4.0.1", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0" + } + }, + "jsonfile": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-3.0.1.tgz", + "integrity": "sha1-pezG9l9T9mLEQVx2daAzHQmS7GY=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.6" + } + }, + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true + }, + "plist": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/plist/-/plist-2.1.0.tgz", + "integrity": "sha1-V8zbeggh3yGDEhejytVOPhRqECU=", + "dev": true, + "requires": { + "base64-js": "1.2.0", + "xmlbuilder": "8.2.2", + "xmldom": "0.1.x" + } + }, + "rcedit": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/rcedit/-/rcedit-0.8.0.tgz", + "integrity": "sha1-3DEwrrAo9KZYrC48jm0EciLayUg=", + "dev": true + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + }, + "source-map-support": { + "version": "0.4.18", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.4.18.tgz", + "integrity": "sha512-try0/JqxPLF9nOjvSta7tVondkP5dwgyLDjVoyMDlmjugT2lRZ1OfsrYTkCd2hkDnJTKRbO/Rl3orm8vlsUzbA==", + "dev": true, + "requires": { + "source-map": "^0.5.6" + } + }, + "tmp": { + "version": "0.0.31", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.31.tgz", + "integrity": "sha1-jzirlDjhcxXl29izZX6L+yd65Kc=", + "dev": true, + "requires": { + "os-tmpdir": "~1.0.1" + } + }, + "xmlbuilder": { + "version": "8.2.2", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-8.2.2.tgz", + "integrity": "sha1-aSSGc0ELS6QuGmE2VR0pIjNap3M=", + "dev": true + } + } + }, + "oauth-sign": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", + "dev": true + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "dev": true + }, + "object-component": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/object-component/-/object-component-0.0.3.tgz", + "integrity": "sha1-8MaapQ78lbhmwYb0AKM3acsvEpE=", + "dev": true + }, + "object-copy": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", + "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=", + "dev": true, + "requires": { + "copy-descriptor": "^0.1.0", + "define-property": "^0.2.5", + "kind-of": "^3.0.3" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "object-keys": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-0.4.0.tgz", + "integrity": "sha1-KKaq50KN0sOpLz2V8hM13SBOAzY=", + "dev": true + }, + "object-visit": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", + "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=", + "dev": true, + "requires": { + "isobject": "^3.0.0" + } + }, + "object.pick": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", + "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", + "dev": true, + "requires": { + "isobject": "^3.0.1" + } + }, + "obuf": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz", + "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==", + "dev": true + }, + "on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "dev": true, + "requires": { + "ee-first": "1.1.1" + } + }, + "on-headers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", + "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", + "dev": true + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "requires": { + "wrappy": "1" + } + }, + "onetime": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz", + "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=", + "dev": true, + "requires": { + "mimic-fn": "^1.0.0" + } + }, + "opn": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/opn/-/opn-5.4.0.tgz", + "integrity": "sha512-YF9MNdVy/0qvJvDtunAOzFw9iasOQHpVthTCvGzxt61Il64AYSGdK+rYwld7NAfk9qJ7dt+hymBNSc9LNYS+Sw==", + "dev": true, + "requires": { + "is-wsl": "^1.1.0" + } + }, + "optimist": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz", + "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=", + "dev": true, + "requires": { + "minimist": "~0.0.1", + "wordwrap": "~0.0.2" + }, + "dependencies": { + "minimist": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz", + "integrity": "sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8=", + "dev": true + } + } + }, + "optjs": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/optjs/-/optjs-3.2.2.tgz", + "integrity": "sha1-aabOicRCpEQDFBrS+bNwvVu29O4=" + }, + "original": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/original/-/original-1.0.2.tgz", + "integrity": "sha512-hyBVl6iqqUOJ8FqRe+l/gS8H+kKYjrEndd5Pm1MfBtsEKA038HkkdbAl/72EAXGyonD/PFsvmVG+EvcIpliMBg==", + "dev": true, + "requires": { + "url-parse": "^1.4.3" + } + }, + "os-browserify": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.3.0.tgz", + "integrity": "sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc=", + "dev": true + }, + "os-homedir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", + "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", + "dev": true + }, + "os-locale": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz", + "integrity": "sha1-IPnxeuKe00XoveWDsT0gCYA8FNk=", + "requires": { + "lcid": "^1.0.0" + } + }, + "os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", + "dev": true + }, + "osenv": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz", + "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", + "dev": true, + "requires": { + "os-homedir": "^1.0.0", + "os-tmpdir": "^1.0.0" + } + }, + "p-cancelable": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-1.1.0.tgz", + "integrity": "sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw==", + "dev": true + }, + "p-defer": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz", + "integrity": "sha1-n26xgvbJqozXQwBKfU+WsZaw+ww=", + "dev": true + }, + "p-finally": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", + "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=", + "dev": true + }, + "p-is-promise": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-2.0.0.tgz", + "integrity": "sha512-pzQPhYMCAgLAKPWD2jC3Se9fEfrD9npNos0y150EeqZll7akhEgGhTW/slB6lHku8AvYGiJ+YJ5hfHKePPgFWg==", + "dev": true + }, + "p-limit": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", + "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "dev": true, + "requires": { + "p-try": "^1.0.0" + } + }, + "p-locate": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", + "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", + "dev": true, + "requires": { + "p-limit": "^1.1.0" + } + }, + "p-map": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-1.2.0.tgz", + "integrity": "sha512-r6zKACMNhjPJMTl8KcFH4li//gkrXWfbD6feV8l6doRHlzljFWGJ2AP6iKaCJXyZmAUMOPtvbW7EXkbWO/pLEA==", + "dev": true + }, + "p-try": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", + "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", + "dev": true + }, + "package-json": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/package-json/-/package-json-6.5.0.tgz", + "integrity": "sha512-k3bdm2n25tkyxcjSKzB5x8kfVxlMdgsbPr0GkZcwHsLpba6cBjqCt1KlcChKEvxHIcTB1FVMuwoijZ26xex5MQ==", + "dev": true, + "requires": { + "got": "^9.6.0", + "registry-auth-token": "^4.0.0", + "registry-url": "^5.0.0", + "semver": "^6.2.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, + "pacote": { + "version": "9.4.0", + "resolved": "https://registry.npmjs.org/pacote/-/pacote-9.4.0.tgz", + "integrity": "sha512-WQ1KL/phGMkedYEQx9ODsjj7xvwLSpdFJJdEXrLyw5SILMxcTNt5DTxT2Z93fXuLFYJBlZJdnwdalrQdB/rX5w==", + "dev": true, + "requires": { + "bluebird": "^3.5.3", + "cacache": "^11.3.2", + "figgy-pudding": "^3.5.1", + "get-stream": "^4.1.0", + "glob": "^7.1.3", + "lru-cache": "^5.1.1", + "make-fetch-happen": "^4.0.1", + "minimatch": "^3.0.4", + "minipass": "^2.3.5", + "mississippi": "^3.0.0", + "mkdirp": "^0.5.1", + "normalize-package-data": "^2.4.0", + "npm-package-arg": "^6.1.0", + "npm-packlist": "^1.1.12", + "npm-pick-manifest": "^2.2.3", + "npm-registry-fetch": "^3.8.0", + "osenv": "^0.1.5", + "promise-inflight": "^1.0.1", + "promise-retry": "^1.1.1", + "protoduck": "^5.0.1", + "rimraf": "^2.6.2", + "safe-buffer": "^5.1.2", + "semver": "^5.6.0", + "ssri": "^6.0.1", + "tar": "^4.4.8", + "unique-filename": "^1.1.1", + "which": "^1.3.1" + }, + "dependencies": { + "cacache": { + "version": "11.3.2", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-11.3.2.tgz", + "integrity": "sha512-E0zP4EPGDOaT2chM08Als91eYnf8Z+eH1awwwVsngUmgppfM5jjJ8l3z5vO5p5w/I3LsiXawb1sW0VY65pQABg==", + "dev": true, + "requires": { + "bluebird": "^3.5.3", + "chownr": "^1.1.1", + "figgy-pudding": "^3.5.1", + "glob": "^7.1.3", + "graceful-fs": "^4.1.15", + "lru-cache": "^5.1.1", + "mississippi": "^3.0.0", + "mkdirp": "^0.5.1", + "move-concurrently": "^1.0.1", + "promise-inflight": "^1.0.1", + "rimraf": "^2.6.2", + "ssri": "^6.0.1", + "unique-filename": "^1.1.1", + "y18n": "^4.0.0" + } + }, + "get-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "dev": true, + "requires": { + "pump": "^3.0.0" + } + }, + "lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "requires": { + "yallist": "^3.0.2" + } + }, + "mississippi": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mississippi/-/mississippi-3.0.0.tgz", + "integrity": "sha512-x471SsVjUtBRtcvd4BzKE9kFC+/2TeWgKCgw0bZcw1b9l2X3QX5vCWgF+KaZaYm87Ss//rHnWryupDrgLvmSkA==", + "dev": true, + "requires": { + "concat-stream": "^1.5.0", + "duplexify": "^3.4.2", + "end-of-stream": "^1.1.0", + "flush-write-stream": "^1.0.0", + "from2": "^2.1.0", + "parallel-transform": "^1.1.0", + "pump": "^3.0.0", + "pumpify": "^1.3.3", + "stream-each": "^1.1.0", + "through2": "^2.0.0" + } + }, + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "ssri": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.2.tgz", + "integrity": "sha512-cepbSq/neFK7xB6A50KHN0xHDotYzq58wWCa5LeWqnPrHG8GzfEjO/4O8kpmcGW+oaxkvhEJCWgbgNk4/ZV93Q==", + "dev": true, + "requires": { + "figgy-pudding": "^3.5.1" + } + }, + "tar": { + "version": "4.4.19", + "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.19.tgz", + "integrity": "sha512-a20gEsvHnWe0ygBY8JbxoM4w3SJdhc7ZAuxkLqh+nvNQN2IOt0B5lLgM490X5Hl8FF0dl0tOf2ewFYAlIFgzVA==", + "dev": true, + "requires": { + "chownr": "^1.1.4", + "fs-minipass": "^1.2.7", + "minipass": "^2.9.0", + "minizlib": "^1.3.3", + "mkdirp": "^0.5.5", + "safe-buffer": "^5.2.1", + "yallist": "^3.1.1" + }, + "dependencies": { + "chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", + "dev": true + }, + "fs-minipass": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.7.tgz", + "integrity": "sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA==", + "dev": true, + "requires": { + "minipass": "^2.6.0" + } + }, + "minipass": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.9.0.tgz", + "integrity": "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==", + "dev": true, + "requires": { + "safe-buffer": "^5.1.2", + "yallist": "^3.0.0" + } + }, + "minizlib": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.3.3.tgz", + "integrity": "sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q==", + "dev": true, + "requires": { + "minipass": "^2.9.0" + } + }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true + }, + "yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true + } + } + }, + "yallist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.3.tgz", + "integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==", + "dev": true + } + } + }, + "pako": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.8.tgz", + "integrity": "sha512-6i0HVbUfcKaTv+EG8ZTr75az7GFXcLYk9UyLEg7Notv/Ma+z/UG3TCoz6GiNeOrn1E/e63I0X/Hpw18jHOTUnA==", + "dev": true + }, + "parallel-transform": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/parallel-transform/-/parallel-transform-1.1.0.tgz", + "integrity": "sha1-1BDwZbBdojCB/NEPKIVMKb2jOwY=", + "dev": true, + "requires": { + "cyclist": "~0.2.2", + "inherits": "^2.0.3", + "readable-stream": "^2.1.5" + } + }, + "parchment": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/parchment/-/parchment-1.1.4.tgz", + "integrity": "sha512-J5FBQt/pM2inLzg4hEWmzQx/8h8D0CiDxaG3vyp9rKrQRSDgBlhjdP5jQGgosEajXPSQouXGHOmVdgo7QmJuOg==" + }, + "parse-asn1": { + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.4.tgz", + "integrity": "sha512-Qs5duJcuvNExRfFZ99HDD3z4mAi3r9Wl/FOjEOijlxwCZs7E7mW2vjTpgQ4J8LpTF8x5v+1Vn5UQFejmWT11aw==", + "dev": true, + "requires": { + "asn1.js": "^4.0.0", + "browserify-aes": "^1.0.0", + "create-hash": "^1.1.0", + "evp_bytestokey": "^1.0.0", + "pbkdf2": "^3.0.3", + "safe-buffer": "^5.1.1" + } + }, + "parse-author": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/parse-author/-/parse-author-2.0.0.tgz", + "integrity": "sha1-00YL8d3Q367tQtp1QkLmX7aEqB8=", + "dev": true, + "requires": { + "author-regex": "^1.0.0" + } + }, + "parse-json": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", + "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", + "dev": true, + "requires": { + "error-ex": "^1.2.0" + } + }, + "parse5": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-4.0.0.tgz", + "integrity": "sha512-VrZ7eOd3T1Fk4XWNXMgiGBK/z0MG48BWG2uQNU4I72fkQuKUTZpl+u9k+CxEG0twMVzSmXEEz12z5Fnw1jIQFA==", + "dev": true + }, + "parseqs": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/parseqs/-/parseqs-0.0.5.tgz", + "integrity": "sha1-1SCKNzjkZ2bikbouoXNoSSGouJ0=", + "dev": true, + "requires": { + "better-assert": "~1.0.0" + } + }, + "parseuri": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/parseuri/-/parseuri-0.0.5.tgz", + "integrity": "sha1-gCBKUNTbt3m/3G6+J3jZDkvOMgo=", + "dev": true, + "requires": { + "better-assert": "~1.0.0" + } + }, + "parseurl": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.2.tgz", + "integrity": "sha1-/CidTtiZMRlGDBViUyYs3I3mW/M=", + "dev": true + }, + "pascalcase": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", + "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=", + "dev": true + }, + "path-browserify": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-0.0.0.tgz", + "integrity": "sha1-oLhwcpquIUAFt9UDLsLLuw+0RRo=", + "dev": true + }, + "path-dirname": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz", + "integrity": "sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA=", + "dev": true + }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "dev": true + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" + }, + "path-is-inside": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", + "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=", + "dev": true + }, + "path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", + "dev": true + }, + "path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=", + "dev": true + }, + "path-type": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", + "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", + "dev": true, + "requires": { + "pify": "^3.0.0" + } + }, + "pbkdf2": { + "version": "3.0.17", + "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.0.17.tgz", + "integrity": "sha512-U/il5MsrZp7mGg3mSQfn742na2T+1/vHDCG5/iTI3X9MKUuYUZVLQhyRsg06mCgDBTd57TxzgZt7P+fYfjRLtA==", + "dev": true, + "requires": { + "create-hash": "^1.1.2", + "create-hmac": "^1.1.4", + "ripemd160": "^2.0.1", + "safe-buffer": "^5.0.1", + "sha.js": "^2.4.8" + } + }, + "pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA=", + "dev": true + }, + "perfect-scrollbar": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/perfect-scrollbar/-/perfect-scrollbar-1.4.0.tgz", + "integrity": "sha512-/2Sk/khljhdrsamjJYS5NjrH+GKEHEwh7zFSiYyxROyYKagkE4kSn2zDQDRTOMo8mpT2jikxx6yI1dG7lNP/hw==" + }, + "performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=", + "dev": true + }, + "picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "dev": true + }, + "picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true + }, + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=" + }, + "pinkie": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", + "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=", + "dev": true + }, + "pinkie-promise": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", + "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", + "dev": true, + "requires": { + "pinkie": "^2.0.0" + } + }, + "pkg-dir": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-2.0.0.tgz", + "integrity": "sha1-9tXREJ4Z1j7fQo4L1X4Sd3YVM0s=", + "dev": true, + "requires": { + "find-up": "^2.1.0" + } + }, + "plist": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/plist/-/plist-3.0.1.tgz", + "integrity": "sha512-GpgvHHocGRyQm74b6FWEZZVRroHKE1I0/BTjAmySaohK+cUn+hZpbqXkc3KWgW3gQYkqcQej35FohcT0FRlkRQ==", + "dev": true, + "requires": { + "base64-js": "^1.2.3", + "xmlbuilder": "^9.0.7", + "xmldom": "0.1.x" + } + }, + "portfinder": { + "version": "1.0.20", + "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.20.tgz", + "integrity": "sha512-Yxe4mTyDzTd59PZJY4ojZR8F+E5e97iq2ZOHPz3HDgSvYC5siNad2tLooQ5y5QHyQhc3xVqvyk/eNA3wuoa7Sw==", + "dev": true, + "requires": { + "async": "^1.5.2", + "debug": "^2.2.0", + "mkdirp": "0.5.x" + }, + "dependencies": { + "async": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", + "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=", + "dev": true + } + } + }, + "positioning": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/positioning/-/positioning-1.4.0.tgz", + "integrity": "sha512-LbN+mgAXtcDdN46xMJ3yZwjndqqYJODaO5qKmU+MVMu5tL3K2dlm1Qha/zh1k2JAFym5HDaZpnPfO4gr91VTRw==" + }, + "posix-character-classes": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", + "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=", + "dev": true + }, + "postcss": { + "version": "7.0.14", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.14.tgz", + "integrity": "sha512-NsbD6XUUMZvBxtQAJuWDJeeC4QFsmWsfozWxCJPWf3M55K9iu2iMDaKqyoOdTJ1R4usBXuxlVFAIo8rZPQD4Bg==", + "dev": true, + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "postcss-import": { + "version": "12.0.1", + "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-12.0.1.tgz", + "integrity": "sha512-3Gti33dmCjyKBgimqGxL3vcV8w9+bsHwO5UrBawp796+jdardbcFl4RP5w/76BwNL7aGzpKstIfF9I+kdE8pTw==", + "dev": true, + "requires": { + "postcss": "^7.0.1", + "postcss-value-parser": "^3.2.3", + "read-cache": "^1.0.0", + "resolve": "^1.1.7" + } + }, + "postcss-load-config": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-2.0.0.tgz", + "integrity": "sha512-V5JBLzw406BB8UIfsAWSK2KSwIJ5yoEIVFb4gVkXci0QdKgA24jLmHZ/ghe/GgX0lJ0/D1uUK1ejhzEY94MChQ==", + "dev": true, + "requires": { + "cosmiconfig": "^4.0.0", + "import-cwd": "^2.0.0" + } + }, + "postcss-loader": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-3.0.0.tgz", + "integrity": "sha512-cLWoDEY5OwHcAjDnkyRQzAXfs2jrKjXpO/HQFcc5b5u/r7aa471wdmChmwfnv7x2u840iat/wi0lQ5nbRgSkUA==", + "dev": true, + "requires": { + "loader-utils": "^1.1.0", + "postcss": "^7.0.0", + "postcss-load-config": "^2.0.0", + "schema-utils": "^1.0.0" + } + }, + "postcss-value-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", + "dev": true + }, + "prepend-http": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-2.0.0.tgz", + "integrity": "sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc=", + "dev": true + }, + "pretty-bytes": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-1.0.4.tgz", + "integrity": "sha1-CiLoIQYJrTVUL4yNXSFZr/B1HIQ=", + "dev": true, + "requires": { + "get-stdin": "^4.0.1", + "meow": "^3.1.0" + } + }, + "process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=", + "dev": true + }, + "process-nextick-args": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", + "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==", + "dev": true + }, + "progress": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/progress/-/progress-1.1.8.tgz", + "integrity": "sha1-4mDHj2Fhzdmw5WzD4Khd4Xx6V74=", + "dev": true + }, + "progress-stream": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/progress-stream/-/progress-stream-1.2.0.tgz", + "integrity": "sha1-LNPP6jO6OonJwSHsM0er6asSX3c=", + "dev": true, + "requires": { + "speedometer": "~0.1.2", + "through2": "~0.2.3" + }, + "dependencies": { + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + }, + "readable-stream": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", + "dev": true + }, + "through2": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/through2/-/through2-0.2.3.tgz", + "integrity": "sha1-6zKE2k6jEbbMis42U3SKUqvyWj8=", + "dev": true, + "requires": { + "readable-stream": "~1.1.9", + "xtend": "~2.1.1" + } + }, + "xtend": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-2.1.2.tgz", + "integrity": "sha1-bv7MKk2tjmlixJAbM3znuoe10os=", + "dev": true, + "requires": { + "object-keys": "~0.4.0" + } + } + } + }, + "promise": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz", + "integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==", + "dev": true, + "optional": true, + "requires": { + "asap": "~2.0.3" + } + }, + "promise-inflight": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", + "integrity": "sha1-mEcocL8igTL8vdhoEputEsPAKeM=", + "dev": true + }, + "promise-retry": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-1.1.1.tgz", + "integrity": "sha1-ZznpaOMFHaIM5kl/srUPaRHfPW0=", + "dev": true, + "requires": { + "err-code": "^1.0.0", + "retry": "^0.10.0" + } + }, + "protobufjs": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-5.0.3.tgz", + "integrity": "sha512-55Kcx1MhPZX0zTbVosMQEO5R6/rikNXd9b6RQK4KSPcrSIIwoXTtebIczUrXlwaSrbz4x8XUVThGPob1n8I4QA==", + "requires": { + "ascli": "~1", + "bytebuffer": "~5", + "glob": "^7.0.5", + "yargs": "^3.10.0" + }, + "dependencies": { + "y18n": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz", + "integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE=" + }, + "yargs": { + "version": "3.32.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.32.0.tgz", + "integrity": "sha1-AwiOnr+edWtpdRYR0qXvWRSCyZU=", + "requires": { + "camelcase": "^2.0.1", + "cliui": "^3.0.3", + "decamelize": "^1.1.1", + "os-locale": "^1.4.0", + "string-width": "^1.0.1", + "window-size": "^0.1.4", + "y18n": "^3.2.0" + } + } + } + }, + "protoduck": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/protoduck/-/protoduck-5.0.1.tgz", + "integrity": "sha512-WxoCeDCoCBY55BMvj4cAEjdVUFGRWed9ZxPlqTKYyw1nDDTQ4pqmnIMAGfJlg7Dx35uB/M+PHJPTmGOvaCaPTg==", + "dev": true, + "requires": { + "genfun": "^5.0.0" + } + }, + "protractor": { + "version": "5.4.2", + "resolved": "https://registry.npmjs.org/protractor/-/protractor-5.4.2.tgz", + "integrity": "sha512-zlIj64Cr6IOWP7RwxVeD8O4UskLYPoyIcg0HboWJL9T79F1F0VWtKkGTr/9GN6BKL+/Q/GmM7C9kFVCfDbP5sA==", + "dev": true, + "requires": { + "@types/q": "^0.0.32", + "@types/selenium-webdriver": "^3.0.0", + "blocking-proxy": "^1.0.0", + "browserstack": "^1.5.1", + "chalk": "^1.1.3", + "glob": "^7.0.3", + "jasmine": "2.8.0", + "jasminewd2": "^2.1.0", + "optimist": "~0.6.0", + "q": "1.4.1", + "saucelabs": "^1.5.0", + "selenium-webdriver": "3.6.0", + "source-map-support": "~0.4.0", + "webdriver-js-extender": "2.1.0", + "webdriver-manager": "^12.0.6" + }, + "dependencies": { + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "dev": true + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + } + }, + "del": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/del/-/del-2.2.2.tgz", + "integrity": "sha1-wSyYHQZ4RshLyvhiz/kw2Qf/0ag=", + "dev": true, + "requires": { + "globby": "^5.0.0", + "is-path-cwd": "^1.0.0", + "is-path-in-cwd": "^1.0.0", + "object-assign": "^4.0.1", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0", + "rimraf": "^2.2.8" + } + }, + "globby": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-5.0.0.tgz", + "integrity": "sha1-69hGZ8oNuzMLmbz8aOrCvFQ3Dg0=", + "dev": true, + "requires": { + "array-union": "^1.0.1", + "arrify": "^1.0.0", + "glob": "^7.0.3", + "object-assign": "^4.0.1", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0" + } + }, + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + }, + "source-map-support": { + "version": "0.4.18", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.4.18.tgz", + "integrity": "sha512-try0/JqxPLF9nOjvSta7tVondkP5dwgyLDjVoyMDlmjugT2lRZ1OfsrYTkCd2hkDnJTKRbO/Rl3orm8vlsUzbA==", + "dev": true, + "requires": { + "source-map": "^0.5.6" + } + }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true + }, + "webdriver-manager": { + "version": "12.1.1", + "resolved": "https://registry.npmjs.org/webdriver-manager/-/webdriver-manager-12.1.1.tgz", + "integrity": "sha512-L9TEQmZs6JbMMRQI1w60mfps265/NCr0toYJl7p/R2OAk6oXAfwI6jqYP7EWae+d7Ad2S2Aj4+rzxoSjqk3ZuA==", + "dev": true, + "requires": { + "adm-zip": "^0.4.9", + "chalk": "^1.1.1", + "del": "^2.2.0", + "glob": "^7.0.3", + "ini": "^1.3.4", + "minimist": "^1.2.0", + "q": "^1.4.1", + "request": "^2.87.0", + "rimraf": "^2.5.2", + "semver": "^5.3.0", + "xml2js": "^0.4.17" + } + } + } + }, + "proxy-addr": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.4.tgz", + "integrity": "sha512-5erio2h9jp5CHGwcybmxmVqHmnCBZeewlfJ0pex+UW7Qny7OOZXTtH56TGNyBizkgiOwhJtMKrVzDTeKcySZwA==", + "dev": true, + "requires": { + "forwarded": "~0.1.2", + "ipaddr.js": "1.8.0" + } + }, + "prr": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", + "integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY=", + "dev": true + }, + "pseudomap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", + "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=", + "dev": true + }, + "psl": { + "version": "1.1.31", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.1.31.tgz", + "integrity": "sha512-/6pt4+C+T+wZUieKR620OpzN/LlnNKuWjy1iFLQ/UG35JqHlR/89MP1d96dUfkf6Dne3TuLQzOYEYshJ+Hx8mw==", + "dev": true + }, + "public-encrypt": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.3.tgz", + "integrity": "sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q==", + "dev": true, + "requires": { + "bn.js": "^4.1.0", + "browserify-rsa": "^4.0.0", + "create-hash": "^1.1.0", + "parse-asn1": "^5.0.0", + "randombytes": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "pump": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz", + "integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==", + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "pumpify": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-1.5.1.tgz", + "integrity": "sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ==", + "dev": true, + "requires": { + "duplexify": "^3.6.0", + "inherits": "^2.0.3", + "pump": "^2.0.0" + } + }, + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true + }, + "q": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/q/-/q-1.4.1.tgz", + "integrity": "sha1-VXBbzZPF82c1MMLCy8DCs63cKG4=", + "dev": true + }, + "qjobs": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/qjobs/-/qjobs-1.2.0.tgz", + "integrity": "sha512-8YOJEHtxpySA3fFDyCRxA+UUV+fA+rTWnuWvylOK/NCjhY+b4ocCtmu8TtsWb+mYeU+GCHf/S66KZF/AsteKHg==", + "dev": true + }, + "qs": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==", + "dev": true + }, + "querystring": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", + "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=" + }, + "querystring-es3": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/querystring-es3/-/querystring-es3-0.2.1.tgz", + "integrity": "sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM=", + "dev": true + }, + "quill": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/quill/-/quill-1.3.6.tgz", + "integrity": "sha512-K0mvhimWZN6s+9OQ249CH2IEPZ9JmkFuCQeHAOQax3EZ2nDJ3wfGh59mnlQaZV2i7u8eFarx6wAtvQKgShojug==", + "requires": { + "clone": "^2.1.1", + "deep-equal": "^1.0.1", + "eventemitter3": "^2.0.3", + "extend": "^3.0.1", + "parchment": "^1.1.4", + "quill-delta": "^3.6.2" + }, + "dependencies": { + "eventemitter3": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-2.0.3.tgz", + "integrity": "sha1-teEHm1n7XhuidxwKmTvgYKWMmbo=" + } + } + }, + "quill-delta": { + "version": "3.6.3", + "resolved": "https://registry.npmjs.org/quill-delta/-/quill-delta-3.6.3.tgz", + "integrity": "sha512-wdIGBlcX13tCHOXGMVnnTVFtGRLoP0imqxM696fIPwIf5ODIYUHIvHbZcyvGlZFiFhK5XzDC2lpjbxRhnM05Tg==", + "requires": { + "deep-equal": "^1.0.1", + "extend": "^3.0.2", + "fast-diff": "1.1.2" + } + }, + "randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "requires": { + "safe-buffer": "^5.1.0" + } + }, + "randomfill": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/randomfill/-/randomfill-1.0.4.tgz", + "integrity": "sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw==", + "dev": true, + "requires": { + "randombytes": "^2.0.5", + "safe-buffer": "^5.1.0" + } + }, + "range-parser": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz", + "integrity": "sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4=", + "dev": true + }, + "raw-body": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.3.tgz", + "integrity": "sha512-9esiElv1BrZoI3rCDuOuKCBRbuApGGaDPQfjSflGxdy4oyzqghxu6klEkkVIvBje+FF0BX9coEv8KqW6X/7njw==", + "dev": true, + "requires": { + "bytes": "3.0.0", + "http-errors": "1.6.3", + "iconv-lite": "0.4.23", + "unpipe": "1.0.0" + } + }, + "raw-loader": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/raw-loader/-/raw-loader-1.0.0.tgz", + "integrity": "sha512-Uqy5AqELpytJTRxYT4fhltcKPj0TyaEpzJDcGz7DFJi+pQOOi3GjR/DOdxTkTsF+NzhnldIoG6TORaBlInUuqA==", + "dev": true, + "requires": { + "loader-utils": "^1.1.0", + "schema-utils": "^1.0.0" + } + }, + "rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "dev": true, + "requires": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + } + }, + "rcedit": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/rcedit/-/rcedit-2.0.0.tgz", + "integrity": "sha512-XcFGyEBjhWSsud+R8elwQtGBbVkCf7tAiad+nXo5jc6l2rMf46NfGNwjnmBNneBIZDfq+Npf8lwP371JTONfrw==", + "dev": true + }, + "read-cache": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", + "integrity": "sha1-5mTvMRYRZsl1HNvo28+GtftY93Q=", + "dev": true, + "requires": { + "pify": "^2.3.0" + }, + "dependencies": { + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true + } + } + }, + "read-config-file": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/read-config-file/-/read-config-file-5.0.0.tgz", + "integrity": "sha512-jIKUu+C84bfnKxyJ5j30CxCqgXWYjZLXuVE/NYlMEpeni+dhESgAeZOZd0JZbg1xTkMmnCdxksDoarkOyfEsOg==", + "dev": true, + "requires": { + "dotenv": "^8.0.0", + "dotenv-expand": "^5.1.0", + "fs-extra": "^8.1.0", + "js-yaml": "^3.13.1", + "json5": "^2.1.0", + "lazy-val": "^1.0.4" + }, + "dependencies": { + "fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "dev": true, + "requires": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + } + }, + "graceful-fs": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.1.tgz", + "integrity": "sha512-b9usnbDGnD928gJB3LrCmxoibr3VE4U2SMo5PBuBnokWyDADTqDPXg4YpwKF1trpH+UbGp7QLicO3+aWEy0+mw==", + "dev": true + }, + "js-yaml": { + "version": "3.13.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", + "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "json5": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.1.0.tgz", + "integrity": "sha512-8Mh9h6xViijj36g7Dxi+Y4S6hNGV96vcJZr/SrlHh1LR/pEn/8j/+qIBbs44YKl69Lrfctp4QD+AdWLTMqEZAQ==", + "dev": true, + "requires": { + "minimist": "^1.2.0" + } + } + } + }, + "read-pkg": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", + "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=", + "dev": true, + "requires": { + "load-json-file": "^1.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^1.0.0" + }, + "dependencies": { + "path-type": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", + "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0" + } + }, + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true + } + } + }, + "read-pkg-up": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz", + "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=", + "dev": true, + "requires": { + "find-up": "^1.0.0", + "read-pkg": "^1.0.0" + }, + "dependencies": { + "find-up": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", + "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", + "dev": true, + "requires": { + "path-exists": "^2.0.0", + "pinkie-promise": "^2.0.0" + } + }, + "path-exists": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", + "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", + "dev": true, + "requires": { + "pinkie-promise": "^2.0.0" + } + } + } + }, + "readable-stream": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "readdirp": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz", + "integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.11", + "micromatch": "^3.1.10", + "readable-stream": "^2.0.2" + } + }, + "rechoir": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", + "integrity": "sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q=", + "dev": true, + "requires": { + "resolve": "^1.1.6" + } + }, + "redent": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-1.0.0.tgz", + "integrity": "sha1-z5Fqsf1fHxbfsggi3W7H9zDCr94=", + "dev": true, + "requires": { + "indent-string": "^2.1.0", + "strip-indent": "^1.0.1" + } + }, + "reflect-metadata": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.13.tgz", + "integrity": "sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg==", + "dev": true + }, + "regenerate": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.0.tgz", + "integrity": "sha512-1G6jJVDWrt0rK99kBjvEtziZNCICAuvIPkSiUFIQxVP06RCVpq3dmDo2oi6ABpYaDYaTRr67BEhL8r1wgEZZKg==", + "dev": true + }, + "regenerator-runtime": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", + "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==" + }, + "regex-not": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", + "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", + "dev": true, + "requires": { + "extend-shallow": "^3.0.2", + "safe-regex": "^1.1.0" + } + }, + "regexpu-core": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-1.0.0.tgz", + "integrity": "sha1-hqdj9Y7k18L2sQLkdkBQ3n7ZDGs=", + "dev": true, + "requires": { + "regenerate": "^1.2.1", + "regjsgen": "^0.2.0", + "regjsparser": "^0.1.4" + } + }, + "registry-auth-token": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-4.0.0.tgz", + "integrity": "sha512-lpQkHxd9UL6tb3k/aHAVfnVtn+Bcs9ob5InuFLLEDqSqeq+AljB8GZW9xY0x7F+xYwEcjKe07nyoxzEYz6yvkw==", + "dev": true, + "requires": { + "rc": "^1.2.8", + "safe-buffer": "^5.0.1" + } + }, + "registry-url": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-5.1.0.tgz", + "integrity": "sha512-8acYXXTI0AkQv6RAOjE3vOaIXZkT9wo4LOFbBKYQEEnnMNBpKqdUrI6S4NT0KPIo/WVvJ5tE/X5LF/TQUf0ekw==", + "dev": true, + "requires": { + "rc": "^1.2.8" + } + }, + "regjsgen": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.2.0.tgz", + "integrity": "sha1-bAFq3qxVT3WCP+N6wFuS1aTtsfc=", + "dev": true + }, + "regjsparser": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.1.5.tgz", + "integrity": "sha1-fuj4Tcb6eS0/0K4ijSS9lJ6tIFw=", + "dev": true, + "requires": { + "jsesc": "~0.5.0" + }, + "dependencies": { + "jsesc": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", + "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=", + "dev": true + } + } + }, + "remove-trailing-separator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", + "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=", + "dev": true + }, + "repeat-element": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.3.tgz", + "integrity": "sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g==", + "dev": true + }, + "repeat-string": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", + "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", + "dev": true + }, + "repeating": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/repeating/-/repeating-2.0.1.tgz", + "integrity": "sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo=", + "dev": true, + "requires": { + "is-finite": "^1.0.0" + } + }, + "request": { + "version": "2.88.0", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz", + "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==", + "dev": true, + "requires": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "har-validator": "~5.1.0", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.4.3", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" + } + }, + "request-progress": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/request-progress/-/request-progress-3.0.0.tgz", + "integrity": "sha1-TKdUCBx/7GP1BeT6qCWqBs1mnb4=", + "dev": true, + "requires": { + "throttleit": "^1.0.0" + }, + "dependencies": { + "throttleit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/throttleit/-/throttleit-1.0.0.tgz", + "integrity": "sha1-nnhYNtr0Z0MUWlmEtiaNgoUorGw=", + "dev": true + } + } + }, + "require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", + "dev": true + }, + "require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true + }, + "require-main-filename": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", + "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=", + "dev": true + }, + "requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=", + "dev": true + }, + "resize-observer-polyfill": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz", + "integrity": "sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==" + }, + "resolve": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.10.0.tgz", + "integrity": "sha512-3sUr9aq5OfSg2S9pNtPA9hL1FVEAjvfOC4leW0SNf/mpnaakz2a9femSd6LqAww2RaFctwyf1lCqnTHuF1rxDg==", + "dev": true, + "requires": { + "path-parse": "^1.0.6" + } + }, + "resolve-cwd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-2.0.0.tgz", + "integrity": "sha1-AKn3OHVW4nA46uIyyqNypqWbZlo=", + "dev": true, + "requires": { + "resolve-from": "^3.0.0" + } + }, + "resolve-from": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", + "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=", + "dev": true + }, + "resolve-url": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", + "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=", + "dev": true + }, + "responselike": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-1.0.2.tgz", + "integrity": "sha1-kYcg7ztjHFZCvgaPFa3lpG9Loec=", + "dev": true, + "requires": { + "lowercase-keys": "^1.0.0" + } + }, + "restore-cursor": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz", + "integrity": "sha1-n37ih/gv0ybU/RYpI9YhKe7g368=", + "dev": true, + "requires": { + "onetime": "^2.0.0", + "signal-exit": "^3.0.2" + } + }, + "ret": { + "version": "0.1.15", + "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", + "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", + "dev": true + }, + "retry": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.10.1.tgz", + "integrity": "sha1-52OI0heZLCUnUCQdPTlW/tmNj/Q=", + "dev": true + }, + "rfdc": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.1.2.tgz", + "integrity": "sha512-92ktAgvZhBzYTIK0Mja9uen5q5J3NRVMoDkJL2VMwq6SXjVCgqvQeVP2XAaUY6HT+XpQYeLSjb3UoitBryKmdA==", + "dev": true + }, + "rimraf": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", + "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "ripemd160": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz", + "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==", + "dev": true, + "requires": { + "hash-base": "^3.0.0", + "inherits": "^2.0.1" + } + }, + "run-async": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.3.0.tgz", + "integrity": "sha1-A3GrSuC91yDUFm19/aZP96RFpsA=", + "dev": true, + "requires": { + "is-promise": "^2.1.0" + } + }, + "run-queue": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/run-queue/-/run-queue-1.0.3.tgz", + "integrity": "sha1-6Eg5bwV9Ij8kOGkkYY4laUFh7Ec=", + "dev": true, + "requires": { + "aproba": "^1.1.1" + } + }, + "rxjs": { + "version": "6.3.3", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.3.3.tgz", + "integrity": "sha512-JTWmoY9tWCs7zvIk/CvRjhjGaOd+OVBM987mxFo+OW66cGpdKjZcpmc74ES1sB//7Kl/PAe8+wEakuhG4pcgOw==", + "requires": { + "tslib": "^1.9.0" + } + }, + "rxjs-compat": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/rxjs-compat/-/rxjs-compat-6.1.0.tgz", + "integrity": "sha512-x5L1KQy1RqDRpPadN5iDOx71TV9Wqmlmu6OOEn3tFFgaTCB0/N+Lmby/rZHgJ6JEPzzt0nD9Zv+kS53E5JIR5g==" + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "safe-regex": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", + "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", + "dev": true, + "requires": { + "ret": "~0.1.10" + } + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true + }, + "sanitize-filename": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/sanitize-filename/-/sanitize-filename-1.6.1.tgz", + "integrity": "sha1-YS2hyWRz+gLczaktzVtKsWSmdyo=", + "dev": true, + "requires": { + "truncate-utf8-bytes": "^1.0.0" + } + }, + "sass": { + "version": "1.49.0", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.49.0.tgz", + "integrity": "sha512-TVwVdNDj6p6b4QymJtNtRS2YtLJ/CqZriGg0eIAbAKMlN8Xy6kbv33FsEZSF7FufFFM705SQviHjjThfaQ4VNw==", + "dev": true, + "requires": { + "chokidar": ">=3.0.0 <4.0.0", + "immutable": "^4.0.0", + "source-map-js": ">=0.6.2 <2.0.0" + }, + "dependencies": { + "anymatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", + "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", + "dev": true, + "requires": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + } + }, + "binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "dev": true + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "requires": { + "fill-range": "^7.0.1" + } + }, + "chokidar": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "dev": true, + "requires": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "fsevents": "~2.3.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + } + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "optional": true + }, + "glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + }, + "is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "requires": { + "binary-extensions": "^2.0.0" + } + }, + "is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true + }, + "readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "requires": { + "picomatch": "^2.2.1" + } + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } + } + } + }, + "sass-graph": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/sass-graph/-/sass-graph-2.2.4.tgz", + "integrity": "sha1-E/vWPNHK8JCLn9k0dq1DpR0eC0k=", + "dev": true, + "optional": true, + "requires": { + "glob": "^7.0.0", + "lodash": "^4.0.0", + "scss-tokenizer": "^0.2.3", + "yargs": "^7.0.0" + } + }, + "sass-loader": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-7.1.0.tgz", + "integrity": "sha512-+G+BKGglmZM2GUSfT9TLuEp6tzehHPjAMoRRItOojWIqIGPloVCMhNIQuG639eJ+y033PaGTSjLaTHts8Kw79w==", + "dev": true, + "requires": { + "clone-deep": "^2.0.1", + "loader-utils": "^1.0.1", + "lodash.tail": "^4.1.1", + "neo-async": "^2.5.0", + "pify": "^3.0.0", + "semver": "^5.5.0" + } + }, + "saucelabs": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/saucelabs/-/saucelabs-1.5.0.tgz", + "integrity": "sha512-jlX3FGdWvYf4Q3LFfFWS1QvPg3IGCGWxIc8QBFdPTbpTJnt/v17FHXYVAn7C8sHf1yUXo2c7yIM0isDryfYtHQ==", + "dev": true, + "requires": { + "https-proxy-agent": "^2.2.1" + } + }, + "sax": { + "version": "0.5.8", + "resolved": "https://registry.npmjs.org/sax/-/sax-0.5.8.tgz", + "integrity": "sha1-1HLbIo6zMcJQaw6MFVJK25OdEsE=", + "dev": true + }, + "schema-utils": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz", + "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==", + "dev": true, + "requires": { + "ajv": "^6.1.0", + "ajv-errors": "^1.0.0", + "ajv-keywords": "^3.1.0" + } + }, + "scss-tokenizer": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/scss-tokenizer/-/scss-tokenizer-0.2.3.tgz", + "integrity": "sha1-jrBtualyMzOCTT9VMGQRSYR85dE=", + "dev": true, + "optional": true, + "requires": { + "js-base64": "^2.1.8", + "source-map": "^0.4.2" + }, + "dependencies": { + "source-map": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz", + "integrity": "sha1-66T12pwNyZneaAMti092FzZSA2s=", + "dev": true, + "optional": true, + "requires": { + "amdefine": ">=0.0.4" + } + } + } + }, + "select-hose": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", + "integrity": "sha1-Yl2GWPhlr0Psliv8N2o3NZpJlMo=", + "dev": true + }, + "selenium-webdriver": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/selenium-webdriver/-/selenium-webdriver-3.6.0.tgz", + "integrity": "sha512-WH7Aldse+2P5bbFBO4Gle/nuQOdVwpHMTL6raL3uuBj/vPG07k6uzt3aiahu352ONBr5xXh0hDlM3LhtXPOC4Q==", + "dev": true, + "requires": { + "jszip": "^3.1.3", + "rimraf": "^2.5.4", + "tmp": "0.0.30", + "xml2js": "^0.4.17" + }, + "dependencies": { + "tmp": { + "version": "0.0.30", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.30.tgz", + "integrity": "sha1-ckGdSovn1s51FI/YsyTlk6cRwu0=", + "dev": true, + "requires": { + "os-tmpdir": "~1.0.1" + } + } + } + }, + "selfsigned": { + "version": "1.10.4", + "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-1.10.4.tgz", + "integrity": "sha512-9AukTiDmHXGXWtWjembZ5NDmVvP2695EtpgbCsxCa68w3c88B+alqbmZ4O3hZ4VWGXeGWzEVdvqgAJD8DQPCDw==", + "dev": true, + "requires": { + "node-forge": "0.7.5" + } + }, + "semver": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.6.0.tgz", + "integrity": "sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg==", + "dev": true + }, + "semver-diff": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/semver-diff/-/semver-diff-2.1.0.tgz", + "integrity": "sha1-S7uEN8jTfksM8aaP1ybsbWRdbTY=", + "dev": true, + "requires": { + "semver": "^5.0.3" + } + }, + "semver-dsl": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/semver-dsl/-/semver-dsl-1.0.1.tgz", + "integrity": "sha1-02eN5VVeimH2Ke7QJTZq5fJzQKA=", + "dev": true, + "requires": { + "semver": "^5.3.0" + } + }, + "semver-intersect": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/semver-intersect/-/semver-intersect-1.4.0.tgz", + "integrity": "sha512-d8fvGg5ycKAq0+I6nfWeCx6ffaWJCsBYU0H2Rq56+/zFePYfT8mXkB3tWBSjR5BerkHNZ5eTPIk1/LBYas35xQ==", + "dev": true, + "requires": { + "semver": "^5.0.0" + } + }, + "send": { + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/send/-/send-0.16.2.tgz", + "integrity": "sha512-E64YFPUssFHEFBvpbbjr44NCLtI1AohxQ8ZSiJjQLskAdKuriYEP6VyGEsRDH8ScozGpkaX1BGvhanqCwkcEZw==", + "dev": true, + "requires": { + "debug": "2.6.9", + "depd": "~1.1.2", + "destroy": "~1.0.4", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "~1.6.2", + "mime": "1.4.1", + "ms": "2.0.0", + "on-finished": "~2.3.0", + "range-parser": "~1.2.0", + "statuses": "~1.4.0" + }, + "dependencies": { + "mime": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.4.1.tgz", + "integrity": "sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ==", + "dev": true + } + } + }, + "serialize-javascript": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-1.6.1.tgz", + "integrity": "sha512-A5MOagrPFga4YaKQSWHryl7AXvbQkEqpw4NNYMTNYUNV51bA8ABHgYFpqKx+YFFrw59xMV1qGH1R4AgoNIVgCw==", + "dev": true + }, + "serve-index": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz", + "integrity": "sha1-03aNabHn2C5c4FD/9bRTvqEqkjk=", + "dev": true, + "requires": { + "accepts": "~1.3.4", + "batch": "0.6.1", + "debug": "2.6.9", + "escape-html": "~1.0.3", + "http-errors": "~1.6.2", + "mime-types": "~2.1.17", + "parseurl": "~1.3.2" + } + }, + "serve-static": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.13.2.tgz", + "integrity": "sha512-p/tdJrO4U387R9oMjb1oj7qSMaMfmOyd4j9hOFoxZe2baQszgHcSWjuya/CiT5kgZZKRudHNOA0pYXOl8rQ5nw==", + "dev": true, + "requires": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.2", + "send": "0.16.2" + } + }, + "set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", + "dev": true + }, + "set-immediate-shim": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz", + "integrity": "sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E=", + "dev": true + }, + "set-value": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", + "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==", + "dev": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-extendable": "^0.1.1", + "is-plain-object": "^2.0.3", + "split-string": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=", + "dev": true + }, + "setprototypeof": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", + "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", + "dev": true + }, + "sha.js": { + "version": "2.4.11", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", + "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "shallow-clone": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-1.0.0.tgz", + "integrity": "sha512-oeXreoKR/SyNJtRJMAKPDSvd28OqEwG4eR/xc856cRGBII7gX9lvAqDxusPm0846z/w/hWYjI1NpKwJ00NHzRA==", + "dev": true, + "requires": { + "is-extendable": "^0.1.1", + "kind-of": "^5.0.0", + "mixin-object": "^2.0.1" + }, + "dependencies": { + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true + } + } + }, + "shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", + "dev": true, + "requires": { + "shebang-regex": "^1.0.0" + } + }, + "shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", + "dev": true + }, + "shelljs": { + "version": "0.8.5", + "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.5.tgz", + "integrity": "sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow==", + "dev": true, + "requires": { + "glob": "^7.0.0", + "interpret": "^1.0.0", + "rechoir": "^0.6.2" + } + }, + "signal-exit": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", + "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=" + }, + "single-line-log": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/single-line-log/-/single-line-log-1.1.2.tgz", + "integrity": "sha1-wvg/Jzo+GhbtsJlWYdoO1e8DM2Q=", + "dev": true, + "requires": { + "string-width": "^1.0.1" + } + }, + "slash": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz", + "integrity": "sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU=", + "dev": true + }, + "smart-buffer": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.0.2.tgz", + "integrity": "sha512-JDhEpTKzXusOqXZ0BUIdH+CjFdO/CR3tLlf5CN34IypI+xMmXW1uB16OOY8z3cICbJlDAVJzNbwBhNO0wt9OAw==", + "dev": true + }, + "snapdragon": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", + "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", + "dev": true, + "requires": { + "base": "^0.11.1", + "debug": "^2.2.0", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "map-cache": "^0.2.2", + "source-map": "^0.5.6", + "source-map-resolve": "^0.5.0", + "use": "^3.1.0" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + } + } + }, + "snapdragon-node": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", + "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", + "dev": true, + "requires": { + "define-property": "^1.0.0", + "isobject": "^3.0.0", + "snapdragon-util": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + } + } + }, + "snapdragon-util": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", + "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", + "dev": true, + "requires": { + "kind-of": "^3.2.0" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "socket.io": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-2.1.1.tgz", + "integrity": "sha512-rORqq9c+7W0DAK3cleWNSyfv/qKXV99hV4tZe+gGLfBECw3XEhBy7x85F3wypA9688LKjtwO9pX9L33/xQI8yA==", + "dev": true, + "requires": { + "debug": "~3.1.0", + "engine.io": "~3.2.0", + "has-binary2": "~1.0.2", + "socket.io-adapter": "~1.1.0", + "socket.io-client": "2.1.1", + "socket.io-parser": "~3.2.0" + }, + "dependencies": { + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + } + } + }, + "socket.io-adapter": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-1.1.1.tgz", + "integrity": "sha1-KoBeihTWNyEk3ZFZrUUC+MsH8Gs=", + "dev": true + }, + "socket.io-client": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-2.1.1.tgz", + "integrity": "sha512-jxnFyhAuFxYfjqIgduQlhzqTcOEQSn+OHKVfAxWaNWa7ecP7xSNk2Dx/3UEsDcY7NcFafxvNvKPmmO7HTwTxGQ==", + "dev": true, + "requires": { + "backo2": "1.0.2", + "base64-arraybuffer": "0.1.5", + "component-bind": "1.0.0", + "component-emitter": "1.2.1", + "debug": "~3.1.0", + "engine.io-client": "~3.2.0", + "has-binary2": "~1.0.2", + "has-cors": "1.1.0", + "indexof": "0.0.1", + "object-component": "0.0.3", + "parseqs": "0.0.5", + "parseuri": "0.0.5", + "socket.io-parser": "~3.2.0", + "to-array": "0.1.4" + }, + "dependencies": { + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + } + } + }, + "socket.io-parser": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-3.2.0.tgz", + "integrity": "sha512-FYiBx7rc/KORMJlgsXysflWx/RIvtqZbyGLlHZvjfmPTPeuD/I8MaW7cfFrj5tRltICJdgwflhfZ3NVVbVLFQA==", + "dev": true, + "requires": { + "component-emitter": "1.2.1", + "debug": "~3.1.0", + "isarray": "2.0.1" + }, + "dependencies": { + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "isarray": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz", + "integrity": "sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4=", + "dev": true + } + } + }, + "sockjs": { + "version": "0.3.19", + "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.19.tgz", + "integrity": "sha512-V48klKZl8T6MzatbLlzzRNhMepEys9Y4oGFpypBFFn1gLI/QQ9HtLLyWJNbPlwGLelOVOEijUbTTJeLLI59jLw==", + "dev": true, + "requires": { + "faye-websocket": "^0.10.0", + "uuid": "^3.0.1" + } + }, + "sockjs-client": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/sockjs-client/-/sockjs-client-1.3.0.tgz", + "integrity": "sha512-R9jxEzhnnrdxLCNln0xg5uGHqMnkhPSTzUZH2eXcR03S/On9Yvoq2wyUZILRUhZCNVu2PmwWVoyuiPz8th8zbg==", + "dev": true, + "requires": { + "debug": "^3.2.5", + "eventsource": "^1.0.7", + "faye-websocket": "~0.11.1", + "inherits": "^2.0.3", + "json3": "^3.3.2", + "url-parse": "^1.4.3" + }, + "dependencies": { + "debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "faye-websocket": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.1.tgz", + "integrity": "sha1-8O/hjE9W5PQK/H4Gxxn9XuYYjzg=", + "dev": true, + "requires": { + "websocket-driver": ">=0.5.1" + } + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "dev": true + } + } + }, + "socks": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.2.3.tgz", + "integrity": "sha512-+2r83WaRT3PXYoO/1z+RDEBE7Z2f9YcdQnJ0K/ncXXbV5gJ6wYfNAebYFYiiUjM6E4JyXnPY8cimwyvFYHVUUA==", + "dev": true, + "requires": { + "ip": "^1.1.5", + "smart-buffer": "4.0.2" + } + }, + "socks-proxy-agent": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-4.0.1.tgz", + "integrity": "sha512-Kezx6/VBguXOsEe5oU3lXYyKMi4+gva72TwJ7pQY5JfqUx2nMk7NXA6z/mpNqIlfQjWYVfeuNvQjexiTaTn6Nw==", + "dev": true, + "requires": { + "agent-base": "~4.2.0", + "socks": "~2.2.0" + } + }, + "source-list-map": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz", + "integrity": "sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw==", + "dev": true + }, + "source-map": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", + "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", + "dev": true + }, + "source-map-js": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", + "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "dev": true + }, + "source-map-loader": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/source-map-loader/-/source-map-loader-0.2.4.tgz", + "integrity": "sha512-OU6UJUty+i2JDpTItnizPrlpOIBLmQbWMuBg9q5bVtnHACqw1tn9nNwqJLbv0/00JjnJb/Ee5g5WS5vrRv7zIQ==", + "dev": true, + "requires": { + "async": "^2.5.0", + "loader-utils": "^1.1.0" + }, + "dependencies": { + "async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.2.tgz", + "integrity": "sha512-H1qVYh1MYhEEFLsP97cVKqCGo7KfCyTt6uEWqsTBr9SO84oK9Uwbyd/yCW+6rKJLHksBNUVWZDAjfS+Ccx0Bbg==", + "dev": true, + "requires": { + "lodash": "^4.17.11" + } + } + } + }, + "source-map-resolve": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.2.tgz", + "integrity": "sha512-MjqsvNwyz1s0k81Goz/9vRBe9SZdB09Bdw+/zYyO+3CuPk6fouTaxscHkgtE8jKvf01kVfl8riHzERQ/kefaSA==", + "dev": true, + "requires": { + "atob": "^2.1.1", + "decode-uri-component": "^0.2.0", + "resolve-url": "^0.2.1", + "source-map-url": "^0.4.0", + "urix": "^0.1.0" + } + }, + "source-map-support": { + "version": "0.5.10", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.10.tgz", + "integrity": "sha512-YfQ3tQFTK/yzlGJuX8pTwa4tifQj4QS2Mj7UegOu8jAz59MqIiMGPXxQhVQiIMNzayuUSF/jEuVnfFF5JqybmQ==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "source-map-url": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz", + "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=", + "dev": true + }, + "sourcemap-codec": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.4.tgz", + "integrity": "sha512-CYAPYdBu34781kLHkaW3m6b/uUSyMOC2R61gcYMWooeuaGtjof86ZA/8T+qVPPt7np1085CR9hmMGrySwEc8Xg==", + "dev": true + }, + "spdx-correct": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.0.tgz", + "integrity": "sha512-lr2EZCctC2BNR7j7WzJ2FpDznxky1sjfxvvYEyzxNyb6lZXHODmEoJeFu4JupYlkfha1KZpJyoqiJ7pgA1qq8Q==", + "dev": true, + "requires": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-exceptions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.2.0.tgz", + "integrity": "sha512-2XQACfElKi9SlVb1CYadKDXvoajPgBVPn/gOQLrTvHdElaVhr7ZEbqJaRnJLVNeaI4cMEAgVCeBMKF6MWRDCRA==", + "dev": true + }, + "spdx-expression-parse": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz", + "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==", + "dev": true, + "requires": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-license-ids": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.3.tgz", + "integrity": "sha512-uBIcIl3Ih6Phe3XHK1NqboJLdGfwr1UN3k6wSD1dZpmPsIkb8AGNbZYJ1fOBk834+Gxy8rpfDxrS6XLEMZMY2g==", + "dev": true + }, + "spdy": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/spdy/-/spdy-4.0.0.tgz", + "integrity": "sha512-ot0oEGT/PGUpzf/6uk4AWLqkq+irlqHXkrdbk51oWONh3bxQmBuljxPNl66zlRRcIJStWq0QkLUCPOPjgjvU0Q==", + "dev": true, + "requires": { + "debug": "^4.1.0", + "handle-thing": "^2.0.0", + "http-deceiver": "^1.2.7", + "select-hose": "^2.0.0", + "spdy-transport": "^3.0.0" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "dev": true + } + } + }, + "spdy-transport": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/spdy-transport/-/spdy-transport-3.0.0.tgz", + "integrity": "sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw==", + "dev": true, + "requires": { + "debug": "^4.1.0", + "detect-node": "^2.0.4", + "hpack.js": "^2.1.6", + "obuf": "^1.1.2", + "readable-stream": "^3.0.6", + "wbuf": "^1.7.3" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "dev": true + }, + "readable-stream": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.1.1.tgz", + "integrity": "sha512-DkN66hPyqDhnIQ6Jcsvx9bFjhw214O4poMBcIMgPVpQvNy9a0e0Uhg5SqySyDKAmUlwt8LonTBz1ezOnM8pUdA==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + } + } + }, + "speed-measure-webpack-plugin": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/speed-measure-webpack-plugin/-/speed-measure-webpack-plugin-1.3.0.tgz", + "integrity": "sha512-b9Yd0TrzceMVYSbuamM1sFsGM1oVfyFTM22gOoyLhymNvBVApuYpkdFOgYkKJpN/KhTpcCYcTGHg7X+FJ33Vvw==", + "dev": true, + "requires": { + "chalk": "^2.0.1" + } + }, + "speedometer": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/speedometer/-/speedometer-0.1.4.tgz", + "integrity": "sha1-mHbb0qFp0xFUAtSObqYynIgWpQ0=", + "dev": true + }, + "split-string": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", + "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", + "dev": true, + "requires": { + "extend-shallow": "^3.0.0" + } + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true + }, + "sshpk": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", + "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", + "dev": true, + "requires": { + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", + "tweetnacl": "~0.14.0" + } + }, + "ssri": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-5.3.0.tgz", + "integrity": "sha512-XRSIPqLij52MtgoQavH/x/dU1qVKtWUAAZeOHsR9c2Ddi4XerFy3mc1alf+dLJKl9EUIm/Ht+EowFkTUOA6GAQ==", + "dev": true, + "requires": { + "safe-buffer": "^5.1.1" + } + }, + "stat-mode": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/stat-mode/-/stat-mode-0.3.0.tgz", + "integrity": "sha512-QjMLR0A3WwFY2aZdV0okfFEJB5TRjkggXZjxP3A1RsWsNHNu3YPv8btmtc6iCFZ0Rul3FE93OYogvhOUClU+ng==", + "dev": true + }, + "static-extend": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", + "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=", + "dev": true, + "requires": { + "define-property": "^0.2.5", + "object-copy": "^0.1.0" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + } + } + }, + "stats-webpack-plugin": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/stats-webpack-plugin/-/stats-webpack-plugin-0.7.0.tgz", + "integrity": "sha512-NT0YGhwuQ0EOX+uPhhUcI6/+1Sq/pMzNuSCBVT4GbFl/ac6I/JZefBcjlECNfAb1t3GOx5dEj1Z7x0cAxeeVLQ==", + "dev": true, + "requires": { + "lodash": "^4.17.4" + } + }, + "stats.js": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/stats.js/-/stats.js-0.17.0.tgz", + "integrity": "sha1-scPcRtlEmLV4t/05hbgaznExzH0=" + }, + "statuses": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", + "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==", + "dev": true + }, + "stdout-stream": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/stdout-stream/-/stdout-stream-1.4.1.tgz", + "integrity": "sha512-j4emi03KXqJWcIeF8eIXkjMFN1Cmb8gUlDYGeBALLPo5qdyTfA9bOtl8m33lRoC+vFMkP3gl0WsDr6+gzxbbTA==", + "dev": true, + "optional": true, + "requires": { + "readable-stream": "^2.0.1" + } + }, + "stream-browserify": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.2.tgz", + "integrity": "sha512-nX6hmklHs/gr2FuxYDltq8fJA1GDlxKQCz8O/IM4atRqBH8OORmBNgfvW5gG10GT/qQ9u0CzIvr2X5Pkt6ntqg==", + "dev": true, + "requires": { + "inherits": "~2.0.1", + "readable-stream": "^2.0.2" + } + }, + "stream-each": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/stream-each/-/stream-each-1.2.3.tgz", + "integrity": "sha512-vlMC2f8I2u/bZGqkdfLQW/13Zihpej/7PmSiMQsbYddxuTsJp8vRe2x2FvVExZg7FaOds43ROAuFJwPR4MTZLw==", + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "stream-shift": "^1.0.0" + } + }, + "stream-http": { + "version": "2.8.3", + "resolved": "https://registry.npmjs.org/stream-http/-/stream-http-2.8.3.tgz", + "integrity": "sha512-+TSkfINHDo4J+ZobQLWiMouQYB+UVYFttRA94FpEzzJ7ZdqcL4uUUQ7WkdkI4DSozGmgBUE/a47L+38PenXhUw==", + "dev": true, + "requires": { + "builtin-status-codes": "^3.0.0", + "inherits": "^2.0.1", + "readable-stream": "^2.3.6", + "to-arraybuffer": "^1.0.0", + "xtend": "^4.0.0" + } + }, + "stream-shift": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.0.tgz", + "integrity": "sha1-1cdSgl5TZ+eG944Y5EXqIjoVWVI=", + "dev": true + }, + "streamroller": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/streamroller/-/streamroller-0.7.0.tgz", + "integrity": "sha512-WREzfy0r0zUqp3lGO096wRuUp7ho1X6uo/7DJfTlEi0Iv/4gT7YHqXDjKC2ioVGBZtE8QzsQD9nx1nIuoZ57jQ==", + "dev": true, + "requires": { + "date-format": "^1.2.0", + "debug": "^3.1.0", + "mkdirp": "^0.5.1", + "readable-stream": "^2.3.0" + }, + "dependencies": { + "debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "dev": true + } + } + }, + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "strip-bom": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", + "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", + "dev": true, + "requires": { + "is-utf8": "^0.2.0" + } + }, + "strip-eof": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", + "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=", + "dev": true + }, + "strip-indent": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-1.0.1.tgz", + "integrity": "sha1-DHlipq3vp7vUrDZkYKY4VSrhoKI=", + "dev": true, + "requires": { + "get-stdin": "^4.0.1" + } + }, + "strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", + "dev": true + }, + "style-loader": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-0.23.1.tgz", + "integrity": "sha512-XK+uv9kWwhZMZ1y7mysB+zoihsEj4wneFWAS5qoiLwzW0WzSqMrrsIy+a3zkQJq0ipFtBpX5W3MqyRIBF/WFGg==", + "dev": true, + "requires": { + "loader-utils": "^1.1.0", + "schema-utils": "^1.0.0" + } + }, + "stylus": { + "version": "0.54.5", + "resolved": "https://registry.npmjs.org/stylus/-/stylus-0.54.5.tgz", + "integrity": "sha1-QrlWCTHKcJDOhRWnmLqeaqPW3Hk=", + "dev": true, + "requires": { + "css-parse": "1.7.x", + "debug": "*", + "glob": "7.0.x", + "mkdirp": "0.5.x", + "sax": "0.5.x", + "source-map": "0.1.x" + }, + "dependencies": { + "glob": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.0.6.tgz", + "integrity": "sha1-IRuvr0nlJbjNkyYNFKsTYVKz9Xo=", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.2", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "source-map": { + "version": "0.1.43", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.1.43.tgz", + "integrity": "sha1-wkvBRspRfBRx9drL4lcbK3+eM0Y=", + "dev": true, + "requires": { + "amdefine": ">=0.0.4" + } + } + } + }, + "stylus-loader": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/stylus-loader/-/stylus-loader-3.0.2.tgz", + "integrity": "sha512-+VomPdZ6a0razP+zinir61yZgpw2NfljeSsdUF5kJuEzlo3khXhY19Fn6l8QQz1GRJGtMCo8nG5C04ePyV7SUA==", + "dev": true, + "requires": { + "loader-utils": "^1.0.2", + "lodash.clonedeep": "^4.5.0", + "when": "~3.6.x" + } + }, + "sumchecker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/sumchecker/-/sumchecker-2.0.2.tgz", + "integrity": "sha1-D0LBDl0F2l1C7qPlbDOZo31sWz4=", + "dev": true, + "requires": { + "debug": "^2.2.0" + } + }, + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + }, + "symbol-observable": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.2.0.tgz", + "integrity": "sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ==", + "dev": true + }, + "tapable": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.1.tgz", + "integrity": "sha512-9I2ydhj8Z9veORCw5PRm4u9uebCn0mcCa6scWoNcbZ6dAtoo2618u9UUzxgmsCOreJpqDDuv61LvwofW7hLcBA==", + "dev": true + }, + "tar": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tar/-/tar-2.2.1.tgz", + "integrity": "sha1-jk0qJWwOIYXGsYrWlK7JaLg8sdE=", + "dev": true, + "optional": true, + "requires": { + "block-stream": "*", + "fstream": "^1.0.2", + "inherits": "2" + } + }, + "temp-file": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/temp-file/-/temp-file-3.3.4.tgz", + "integrity": "sha512-qSZ5W5q54iyGnP8cNl49RE0jTJc5CrzNocux5APD5yIxcgonoMuMSbsZfaZy8rTGCYo0Xz6ySVv3adagZ8gffg==", + "dev": true, + "requires": { + "async-exit-hook": "^2.0.1", + "fs-extra": "^8.1.0" + }, + "dependencies": { + "fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "dev": true, + "requires": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + } + }, + "graceful-fs": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.1.tgz", + "integrity": "sha512-b9usnbDGnD928gJB3LrCmxoibr3VE4U2SMo5PBuBnokWyDADTqDPXg4YpwKF1trpH+UbGp7QLicO3+aWEy0+mw==", + "dev": true + } + } + }, + "term-size": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/term-size/-/term-size-1.2.0.tgz", + "integrity": "sha1-RYuDiH8oj8Vtb/+/rSYuJmOO+mk=", + "dev": true, + "requires": { + "execa": "^0.7.0" + }, + "dependencies": { + "cross-spawn": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", + "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", + "dev": true, + "requires": { + "lru-cache": "^4.0.1", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, + "execa": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-0.7.0.tgz", + "integrity": "sha1-lEvs00zEHuMqY6n68nrVpl/Fl3c=", + "dev": true, + "requires": { + "cross-spawn": "^5.0.1", + "get-stream": "^3.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + } + } + } + }, + "terser": { + "version": "3.16.1", + "resolved": "https://registry.npmjs.org/terser/-/terser-3.16.1.tgz", + "integrity": "sha512-JDJjgleBROeek2iBcSNzOHLKsB/MdDf+E/BOAJ0Tk9r7p9/fVobfv7LMJ/g/k3v9SXdmjZnIlFd5nfn/Rt0Xow==", + "dev": true, + "requires": { + "commander": "~2.17.1", + "source-map": "~0.6.1", + "source-map-support": "~0.5.9" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "terser-webpack-plugin": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-1.4.5.tgz", + "integrity": "sha512-04Rfe496lN8EYruwi6oPQkG0vo8C+HT49X687FZnpPF0qMAIHONI6HEXYPKDOE8e5HjXTyKfqRd/agHtH0kOtw==", + "dev": true, + "requires": { + "cacache": "^12.0.2", + "find-cache-dir": "^2.1.0", + "is-wsl": "^1.1.0", + "schema-utils": "^1.0.0", + "serialize-javascript": "^4.0.0", + "source-map": "^0.6.1", + "terser": "^4.1.2", + "webpack-sources": "^1.4.0", + "worker-farm": "^1.7.0" + }, + "dependencies": { + "bluebird": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", + "dev": true + }, + "cacache": { + "version": "12.0.4", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-12.0.4.tgz", + "integrity": "sha512-a0tMB40oefvuInr4Cwb3GerbL9xTj1D5yg0T5xrjGCGyfvbxseIXX7BAO/u/hIXdafzOI5JC3wDwHyf24buOAQ==", + "dev": true, + "requires": { + "bluebird": "^3.5.5", + "chownr": "^1.1.1", + "figgy-pudding": "^3.5.1", + "glob": "^7.1.4", + "graceful-fs": "^4.1.15", + "infer-owner": "^1.0.3", + "lru-cache": "^5.1.1", + "mississippi": "^3.0.0", + "mkdirp": "^0.5.1", + "move-concurrently": "^1.0.1", + "promise-inflight": "^1.0.1", + "rimraf": "^2.6.3", + "ssri": "^6.0.1", + "unique-filename": "^1.1.1", + "y18n": "^4.0.0" + } + }, + "commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true + }, + "find-cache-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.1.0.tgz", + "integrity": "sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==", + "dev": true, + "requires": { + "commondir": "^1.0.1", + "make-dir": "^2.0.0", + "pkg-dir": "^3.0.0" + } + }, + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "requires": { + "locate-path": "^3.0.0" + } + }, + "glob": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", + "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "requires": { + "yallist": "^3.0.2" + } + }, + "make-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", + "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", + "dev": true, + "requires": { + "pify": "^4.0.1", + "semver": "^5.6.0" + } + }, + "mississippi": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mississippi/-/mississippi-3.0.0.tgz", + "integrity": "sha512-x471SsVjUtBRtcvd4BzKE9kFC+/2TeWgKCgw0bZcw1b9l2X3QX5vCWgF+KaZaYm87Ss//rHnWryupDrgLvmSkA==", + "dev": true, + "requires": { + "concat-stream": "^1.5.0", + "duplexify": "^3.4.2", + "end-of-stream": "^1.1.0", + "flush-write-stream": "^1.0.0", + "from2": "^2.1.0", + "parallel-transform": "^1.1.0", + "pump": "^3.0.0", + "pumpify": "^1.3.3", + "stream-each": "^1.1.0", + "through2": "^2.0.0" + } + }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "requires": { + "p-limit": "^2.0.0" + } + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true + }, + "pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "dev": true + }, + "pkg-dir": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", + "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", + "dev": true, + "requires": { + "find-up": "^3.0.0" + } + }, + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "serialize-javascript": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-4.0.0.tgz", + "integrity": "sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw==", + "dev": true, + "requires": { + "randombytes": "^2.1.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "ssri": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.2.tgz", + "integrity": "sha512-cepbSq/neFK7xB6A50KHN0xHDotYzq58wWCa5LeWqnPrHG8GzfEjO/4O8kpmcGW+oaxkvhEJCWgbgNk4/ZV93Q==", + "dev": true, + "requires": { + "figgy-pudding": "^3.5.1" + } + }, + "terser": { + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-4.8.0.tgz", + "integrity": "sha512-EAPipTNeWsb/3wLPeup1tVPaXfIaU68xMnVdPafIL1TV05OhASArYyIfFvnvJCNrR2NIOvDVNNTFRa+Re2MWyw==", + "dev": true, + "requires": { + "commander": "^2.20.0", + "source-map": "~0.6.1", + "source-map-support": "~0.5.12" + } + }, + "webpack-sources": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.4.3.tgz", + "integrity": "sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ==", + "dev": true, + "requires": { + "source-list-map": "^2.0.0", + "source-map": "~0.6.1" + } + }, + "worker-farm": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/worker-farm/-/worker-farm-1.7.0.tgz", + "integrity": "sha512-rvw3QTZc8lAxyVrqcSGVm5yP/IJ2UcB3U0graE3LCFoZ0Yn2x4EoVSqJKdB/T5M+FLcRPjz4TDacRf3OCfNUzw==", + "dev": true, + "requires": { + "errno": "~0.1.7" + } + }, + "yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true + } + } + }, + "three": { + "version": "0.101.1", + "resolved": "https://registry.npmjs.org/three/-/three-0.101.1.tgz", + "integrity": "sha512-8ufimUVmRLtH+BTpEIbDjdGEKQOVWLMLgGynaKin1KbYTE136ZNOepJ8EgByi0tN43dQ7B1YrKLCJgXGy4bLmw==" + }, + "throttleit": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/throttleit/-/throttleit-0.0.2.tgz", + "integrity": "sha1-z+34jmDADdlpe2H90qg0OptoDq8=", + "dev": true + }, + "through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", + "dev": true + }, + "through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "dev": true, + "requires": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + }, + "thunky": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.0.3.tgz", + "integrity": "sha512-YwT8pjmNcAXBZqrubu22P4FYsh2D4dxRmnWBOL8Jk8bUcRUtc5326kx32tuTmFDAZtLOGEVNl8POAR8j896Iow==", + "dev": true + }, + "ticky": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ticky/-/ticky-1.0.1.tgz", + "integrity": "sha1-t8+nHnaPHJAAxJe5FRswlHxQ5G0=" + }, + "timers-browserify": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.10.tgz", + "integrity": "sha512-YvC1SV1XdOUaL6gx5CoGroT3Gu49pK9+TZ38ErPldOWW4j49GI1HKs9DV+KGq/w6y+LZ72W1c8cKz2vzY+qpzg==", + "dev": true, + "requires": { + "setimmediate": "^1.0.4" + } + }, + "tmp": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "dev": true, + "requires": { + "os-tmpdir": "~1.0.2" + } + }, + "tmp-promise": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/tmp-promise/-/tmp-promise-1.1.0.tgz", + "integrity": "sha512-8+Ah9aB1IRXCnIOxXZ0uFozV1nMU5xiu7hhFVUSxZ3bYu+psD4TzagCzVbexUCgNNGJnsmNDQlS4nG3mTyoNkw==", + "dev": true, + "requires": { + "bluebird": "^3.5.0", + "tmp": "0.1.0" + }, + "dependencies": { + "tmp": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.1.0.tgz", + "integrity": "sha512-J7Z2K08jbGcdA1kkQpJSqLF6T0tdQqpR2pnSUXsIchbPdTI9v3e85cLW0d6WDhwuAleOV71j2xWs8qMPfK7nKw==", + "dev": true, + "requires": { + "rimraf": "^2.6.3" + } + } + } + }, + "to-array": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/to-array/-/to-array-0.1.4.tgz", + "integrity": "sha1-F+bBH3PdTz10zaek/zI46a2b+JA=", + "dev": true + }, + "to-arraybuffer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz", + "integrity": "sha1-fSKbH8xjfkZsoIEYCDanqr/4P0M=", + "dev": true + }, + "to-fast-properties": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-1.0.3.tgz", + "integrity": "sha1-uDVx+k2MJbguIxsG46MFXeTKGkc=", + "dev": true + }, + "to-object-path": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", + "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "to-readable-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/to-readable-stream/-/to-readable-stream-1.0.0.tgz", + "integrity": "sha512-Iq25XBt6zD5npPhlLVXGFN3/gyR2/qODcKNNyTMd4vbm39HUaOiAM4PMq0eMVC/Tkxz+Zjdsc55g9yyz+Yq00Q==", + "dev": true + }, + "to-regex": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", + "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", + "dev": true, + "requires": { + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "regex-not": "^1.0.2", + "safe-regex": "^1.1.0" + } + }, + "to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", + "dev": true, + "requires": { + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" + } + }, + "tough-cookie": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", + "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==", + "dev": true, + "requires": { + "psl": "^1.1.24", + "punycode": "^1.4.1" + }, + "dependencies": { + "punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", + "dev": true + } + } + }, + "tree-kill": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.1.tgz", + "integrity": "sha512-4hjqbObwlh2dLyW4tcz0Ymw0ggoaVDMveUB9w8kFSQScdRLo0gxO9J7WFcUBo+W3C1TLdFIEwNOWebgZZ0RH9Q==", + "dev": true + }, + "trim-newlines": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-1.0.0.tgz", + "integrity": "sha1-WIeWa7WCpFA6QetST301ARgVphM=", + "dev": true + }, + "trim-right": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/trim-right/-/trim-right-1.0.1.tgz", + "integrity": "sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM=", + "dev": true + }, + "true-case-path": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/true-case-path/-/true-case-path-1.0.3.tgz", + "integrity": "sha512-m6s2OdQe5wgpFMC+pAJ+q9djG82O2jcHPOI6RNg1yy9rCYR+WD6Nbpl32fDpfC56nirdRy+opFa/Vk7HYhqaew==", + "dev": true, + "optional": true, + "requires": { + "glob": "^7.1.2" + } + }, + "truncate-utf8-bytes": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/truncate-utf8-bytes/-/truncate-utf8-bytes-1.0.2.tgz", + "integrity": "sha1-QFkjkJWS1W94pYGENLC3hInKXys=", + "dev": true, + "requires": { + "utf8-byte-length": "^1.0.1" + } + }, + "ts-node": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-7.0.1.tgz", + "integrity": "sha512-BVwVbPJRspzNh2yfslyT1PSbl5uIk03EZlb493RKHN4qej/D06n1cEhjlOJG69oFsE7OT8XjpTUcYf6pKTLMhw==", + "dev": true, + "requires": { + "arrify": "^1.0.0", + "buffer-from": "^1.1.0", + "diff": "^3.1.0", + "make-error": "^1.1.1", + "minimist": "^1.2.0", + "mkdirp": "^0.5.1", + "source-map-support": "^0.5.6", + "yn": "^2.0.0" + } + }, + "tslib": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.3.tgz", + "integrity": "sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ==" + }, + "tslint": { + "version": "5.11.0", + "resolved": "https://registry.npmjs.org/tslint/-/tslint-5.11.0.tgz", + "integrity": "sha1-mPMMAurjzecAYgHkwzywi0hYHu0=", + "dev": true, + "requires": { + "babel-code-frame": "^6.22.0", + "builtin-modules": "^1.1.1", + "chalk": "^2.3.0", + "commander": "^2.12.1", + "diff": "^3.2.0", + "glob": "^7.1.1", + "js-yaml": "^3.7.0", + "minimatch": "^3.0.4", + "resolve": "^1.3.2", + "semver": "^5.3.0", + "tslib": "^1.8.0", + "tsutils": "^2.27.2" + } + }, + "tsutils": { + "version": "2.29.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.29.0.tgz", + "integrity": "sha512-g5JVHCIJwzfISaXpXE1qvNalca5Jwob6FjI4AoPlqMusJ6ftFE7IkkFoMhVLRgK+4Kx3gkzb8UZK5t5yTTvEmA==", + "dev": true, + "requires": { + "tslib": "^1.8.1" + } + }, + "tty-browserify": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz", + "integrity": "sha1-oVe6QC2iTpv5V/mqadUk7tQpAaY=", + "dev": true + }, + "tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "dev": true, + "requires": { + "safe-buffer": "^5.0.1" + } + }, + "tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", + "dev": true + }, + "type-fest": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.3.1.tgz", + "integrity": "sha512-cUGJnCdr4STbePCgqNFbpVNCepa+kAVohJs1sLhxzdH+gnEoOd8VhbYa7pD3zZYGiURWM2xzEII3fQcRizDkYQ==", + "dev": true + }, + "type-is": { + "version": "1.6.16", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.16.tgz", + "integrity": "sha512-HRkVv/5qY2G6I8iab9cI7v1bOIdhm94dVjQCPFElW9W+3GeDOSHmy2EBYe4VTApuzolPcmgFTN3ftVJRKR2J9Q==", + "dev": true, + "requires": { + "media-typer": "0.3.0", + "mime-types": "~2.1.18" + } + }, + "typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", + "dev": true + }, + "typescript": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.2.4.tgz", + "integrity": "sha512-0RNDbSdEokBeEAkgNbxJ+BLwSManFy9TeXz8uW+48j/xhEXv1ePME60olyzw2XzUqUBNAYFeJadIqAgNqIACwg==", + "dev": true + }, + "ultron": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ultron/-/ultron-1.1.1.tgz", + "integrity": "sha512-UIEXBNeYmKptWH6z8ZnqTeS8fV74zG0/eRU9VGkpzz+LIJNs8W/zM/L+7ctCkRrgbNnnR0xxw4bKOr0cW0N0Og==", + "dev": true + }, + "union-value": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz", + "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==", + "dev": true, + "requires": { + "arr-union": "^3.1.0", + "get-value": "^2.0.6", + "is-extendable": "^0.1.1", + "set-value": "^2.0.1" + } + }, + "unique-filename": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.1.tgz", + "integrity": "sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ==", + "dev": true, + "requires": { + "unique-slug": "^2.0.0" + } + }, + "unique-slug": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-2.0.1.tgz", + "integrity": "sha512-n9cU6+gITaVu7VGj1Z8feKMmfAjEAQGhwD9fE3zvpRRa0wEIx8ODYkVGfSc94M2OX00tUFV8wH3zYbm1I8mxFg==", + "dev": true, + "requires": { + "imurmurhash": "^0.1.4" + } + }, + "unique-string": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-1.0.0.tgz", + "integrity": "sha1-nhBXzKhRq7kzmPizOuGHuZyuwRo=", + "requires": { + "crypto-random-string": "^1.0.0" + } + }, + "universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "dev": true + }, + "unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=", + "dev": true + }, + "unset-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", + "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=", + "dev": true, + "requires": { + "has-value": "^0.3.1", + "isobject": "^3.0.0" + }, + "dependencies": { + "has-value": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", + "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=", + "dev": true, + "requires": { + "get-value": "^2.0.3", + "has-values": "^0.1.4", + "isobject": "^2.0.0" + }, + "dependencies": { + "isobject": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", + "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", + "dev": true, + "requires": { + "isarray": "1.0.0" + } + } + } + }, + "has-values": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", + "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=", + "dev": true + } + } + }, + "upath": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/upath/-/upath-1.1.0.tgz", + "integrity": "sha512-bzpH/oBhoS/QI/YtbkqCg6VEiPYjSZtrHQM6/QnJS6OL9pKUFLqb3aFh4Scvwm45+7iAgiMkLhSbaZxUqmrprw==", + "dev": true + }, + "update-notifier": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-3.0.1.tgz", + "integrity": "sha512-grrmrB6Zb8DUiyDIaeRTBCkgISYUgETNe7NglEbVsrLWXeESnlCSP50WfRSj/GmzMPl6Uchj24S/p80nP/ZQrQ==", + "dev": true, + "requires": { + "boxen": "^3.0.0", + "chalk": "^2.0.1", + "configstore": "^4.0.0", + "has-yarn": "^2.1.0", + "import-lazy": "^2.1.0", + "is-ci": "^2.0.0", + "is-installed-globally": "^0.1.0", + "is-npm": "^3.0.0", + "is-yarn-global": "^0.3.0", + "latest-version": "^5.0.0", + "semver-diff": "^2.0.0", + "xdg-basedir": "^3.0.0" + } + }, + "uri-js": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", + "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", + "dev": true, + "requires": { + "punycode": "^2.1.0" + } + }, + "urix": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", + "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=", + "dev": true + }, + "url": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/url/-/url-0.11.0.tgz", + "integrity": "sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE=", + "requires": { + "punycode": "1.3.2", + "querystring": "0.2.0" + }, + "dependencies": { + "punycode": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", + "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=" + } + } + }, + "url-parse": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.4.tgz", + "integrity": "sha512-ITeAByWWoqutFClc/lRZnFplgXgEZr3WJ6XngMM/N9DMIm4K8zXPCZ1Jdu0rERwO84w1WC5wkle2ubwTA4NTBg==", + "dev": true, + "requires": { + "querystringify": "^2.1.1", + "requires-port": "^1.0.0" + }, + "dependencies": { + "querystringify": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", + "dev": true + } + } + }, + "url-parse-lax": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-3.0.0.tgz", + "integrity": "sha1-FrXK/Afb42dsGxmZF3gj1lA6yww=", + "dev": true, + "requires": { + "prepend-http": "^2.0.0" + } + }, + "use": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", + "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==", + "dev": true + }, + "useragent": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/useragent/-/useragent-2.3.0.tgz", + "integrity": "sha512-4AoH4pxuSvHCjqLO04sU6U/uE65BYza8l/KKBS0b0hnUPWi+cQ2BpeTEwejCSx9SPV5/U03nniDTrWx5NrmKdw==", + "dev": true, + "requires": { + "lru-cache": "4.1.x", + "tmp": "0.0.x" + } + }, + "utf8-byte-length": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/utf8-byte-length/-/utf8-byte-length-1.0.4.tgz", + "integrity": "sha1-9F8VDExm7uloGGUFq5P8u4rWv2E=", + "dev": true + }, + "util": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/util/-/util-0.11.1.tgz", + "integrity": "sha512-HShAsny+zS2TZfaXxD9tYj4HQGlBezXZMZuM/S5PKLLoZkShZiGk9o5CzukI1LVHZvjdvZ2Sj1aW/Ndn2NB/HQ==", + "dev": true, + "requires": { + "inherits": "2.0.3" + } + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", + "dev": true + }, + "utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=", + "dev": true + }, + "uuid": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", + "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==", + "dev": true + }, + "validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "dev": true, + "requires": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "validate-npm-package-name": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-3.0.0.tgz", + "integrity": "sha1-X6kS2B630MdK/BQN5zF/DKffQ34=", + "dev": true, + "requires": { + "builtins": "^1.0.3" + } + }, + "vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=", + "dev": true + }, + "verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", + "dev": true, + "requires": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } + }, + "vm-browserify": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-0.0.4.tgz", + "integrity": "sha1-XX6kW7755Kb/ZflUOOCofDV9WnM=", + "dev": true, + "requires": { + "indexof": "0.0.1" + } + }, + "void-elements": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-2.0.1.tgz", + "integrity": "sha1-wGavtYK7HLQSjWDqkjkulNXp2+w=", + "dev": true + }, + "watchpack": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.6.0.tgz", + "integrity": "sha512-i6dHe3EyLjMmDlU1/bGQpEw25XSjkJULPuAVKCbNRefQVq48yXKUpwg538F7AZTf9kyr57zj++pQFltUa5H7yA==", + "dev": true, + "requires": { + "chokidar": "^2.0.2", + "graceful-fs": "^4.1.2", + "neo-async": "^2.5.0" + } + }, + "wbuf": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/wbuf/-/wbuf-1.7.3.tgz", + "integrity": "sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA==", + "dev": true, + "requires": { + "minimalistic-assert": "^1.0.0" + } + }, + "web-animations-js": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/web-animations-js/-/web-animations-js-2.3.1.tgz", + "integrity": "sha1-Om2bwVGWN3qQ+OKAP6UmIWWwRRA=" + }, + "webdriver-js-extender": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/webdriver-js-extender/-/webdriver-js-extender-2.1.0.tgz", + "integrity": "sha512-lcUKrjbBfCK6MNsh7xaY2UAUmZwe+/ib03AjVOpFobX4O7+83BUveSrLfU0Qsyb1DaKJdQRbuU+kM9aZ6QUhiQ==", + "dev": true, + "requires": { + "@types/selenium-webdriver": "^3.0.0", + "selenium-webdriver": "^3.0.1" + } + }, + "webpack": { + "version": "4.29.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-4.29.0.tgz", + "integrity": "sha512-pxdGG0keDBtamE1mNvT5zyBdx+7wkh6mh7uzMOo/uRQ/fhsdj5FXkh/j5mapzs060forql1oXqXN9HJGju+y7w==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.7.11", + "@webassemblyjs/helper-module-context": "1.7.11", + "@webassemblyjs/wasm-edit": "1.7.11", + "@webassemblyjs/wasm-parser": "1.7.11", + "acorn": "^6.0.5", + "acorn-dynamic-import": "^4.0.0", + "ajv": "^6.1.0", + "ajv-keywords": "^3.1.0", + "chrome-trace-event": "^1.0.0", + "enhanced-resolve": "^4.1.0", + "eslint-scope": "^4.0.0", + "json-parse-better-errors": "^1.0.2", + "loader-runner": "^2.3.0", + "loader-utils": "^1.1.0", + "memory-fs": "~0.4.1", + "micromatch": "^3.1.8", + "mkdirp": "~0.5.0", + "neo-async": "^2.5.0", + "node-libs-browser": "^2.0.0", + "schema-utils": "^0.4.4", + "tapable": "^1.1.0", + "terser-webpack-plugin": "^1.1.0", + "watchpack": "^1.5.0", + "webpack-sources": "^1.3.0" + }, + "dependencies": { + "schema-utils": { + "version": "0.4.7", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-0.4.7.tgz", + "integrity": "sha512-v/iwU6wvwGK8HbU9yi3/nhGzP0yGSuhQMzL6ySiec1FSrZZDkhm4noOSWzrNFo/jEc+SJY6jRTwuwbSXJPDUnQ==", + "dev": true, + "requires": { + "ajv": "^6.1.0", + "ajv-keywords": "^3.1.0" + } + } + } + }, + "webpack-core": { + "version": "0.6.9", + "resolved": "https://registry.npmjs.org/webpack-core/-/webpack-core-0.6.9.tgz", + "integrity": "sha1-/FcViMhVjad76e+23r3Fo7FyvcI=", + "dev": true, + "requires": { + "source-list-map": "~0.1.7", + "source-map": "~0.4.1" + }, + "dependencies": { + "source-list-map": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-0.1.8.tgz", + "integrity": "sha1-xVCyq1Qn9rPyH1r+rYjE9Vh7IQY=", + "dev": true + }, + "source-map": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz", + "integrity": "sha1-66T12pwNyZneaAMti092FzZSA2s=", + "dev": true, + "requires": { + "amdefine": ">=0.0.4" + } + } + } + }, + "webpack-dev-middleware": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-3.5.1.tgz", + "integrity": "sha512-4dwCh/AyMOYAybggUr8fiCkRnjVDp+Cqlr9c+aaNB3GJYgRGYQWJ1YX/WAKUNA9dPNHZ6QSN2lYDKqjKSI8Vqw==", + "dev": true, + "requires": { + "memory-fs": "~0.4.1", + "mime": "^2.3.1", + "range-parser": "^1.0.3", + "webpack-log": "^2.0.0" + }, + "dependencies": { + "mime": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.0.tgz", + "integrity": "sha512-ikBcWwyqXQSHKtciCcctu9YfPbFYZ4+gbHEmE0Q8jzcTYQg5dHCr3g2wwAZjPoJfQVXZq6KXAjpXOTf5/cjT7w==", + "dev": true + } + } + }, + "webpack-dev-server": { + "version": "3.1.14", + "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-3.1.14.tgz", + "integrity": "sha512-mGXDgz5SlTxcF3hUpfC8hrQ11yhAttuUQWf1Wmb+6zo3x6rb7b9mIfuQvAPLdfDRCGRGvakBWHdHOa0I9p/EVQ==", + "dev": true, + "requires": { + "ansi-html": "0.0.7", + "bonjour": "^3.5.0", + "chokidar": "^2.0.0", + "compression": "^1.5.2", + "connect-history-api-fallback": "^1.3.0", + "debug": "^3.1.0", + "del": "^3.0.0", + "express": "^4.16.2", + "html-entities": "^1.2.0", + "http-proxy-middleware": "~0.18.0", + "import-local": "^2.0.0", + "internal-ip": "^3.0.1", + "ip": "^1.1.5", + "killable": "^1.0.0", + "loglevel": "^1.4.1", + "opn": "^5.1.0", + "portfinder": "^1.0.9", + "schema-utils": "^1.0.0", + "selfsigned": "^1.9.1", + "semver": "^5.6.0", + "serve-index": "^1.7.2", + "sockjs": "0.3.19", + "sockjs-client": "1.3.0", + "spdy": "^4.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^5.1.0", + "url": "^0.11.0", + "webpack-dev-middleware": "3.4.0", + "webpack-log": "^2.0.0", + "yargs": "12.0.2" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "camelcase": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", + "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=", + "dev": true + }, + "cliui": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-4.1.0.tgz", + "integrity": "sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ==", + "dev": true, + "requires": { + "string-width": "^2.1.1", + "strip-ansi": "^4.0.0", + "wrap-ansi": "^2.0.0" + }, + "dependencies": { + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + } + } + }, + "cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "dev": true, + "requires": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, + "debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "decamelize": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-2.0.0.tgz", + "integrity": "sha512-Ikpp5scV3MSYxY39ymh45ZLEecsTdv/Xj2CaQfI8RLMuwi7XvjX9H/fhraiSuU+C5w5NTDu4ZU72xNiZnurBPg==", + "dev": true, + "requires": { + "xregexp": "4.0.0" + } + }, + "execa": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", + "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", + "dev": true, + "requires": { + "cross-spawn": "^6.0.0", + "get-stream": "^4.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + } + }, + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "requires": { + "locate-path": "^3.0.0" + } + }, + "get-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "dev": true, + "requires": { + "pump": "^3.0.0" + } + }, + "invert-kv": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-2.0.0.tgz", + "integrity": "sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "lcid": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lcid/-/lcid-2.0.0.tgz", + "integrity": "sha512-avPEb8P8EGnwXKClwsNUgryVjllcRqtMYa49NTsbQagYuT1DcXnl1915oxWjoyGrXR6zH/Y0Zc96xWsPcoDKeA==", + "dev": true, + "requires": { + "invert-kv": "^2.0.0" + } + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "mime": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.0.tgz", + "integrity": "sha512-ikBcWwyqXQSHKtciCcctu9YfPbFYZ4+gbHEmE0Q8jzcTYQg5dHCr3g2wwAZjPoJfQVXZq6KXAjpXOTf5/cjT7w==", + "dev": true + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "dev": true + }, + "os-locale": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-3.1.0.tgz", + "integrity": "sha512-Z8l3R4wYWM40/52Z+S265okfFj8Kt2cC2MKY+xNi3kFs+XGI7WXu/I309QQQYbRW4ijiZ+yxs9pqEhJh0DqW3Q==", + "dev": true, + "requires": { + "execa": "^1.0.0", + "lcid": "^2.0.0", + "mem": "^4.0.0" + } + }, + "p-limit": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.1.0.tgz", + "integrity": "sha512-NhURkNcrVB+8hNfLuysU8enY5xn2KXphsHBaC2YmRNTZRc7RWusw6apSpdEj3jo4CMb6W9nrF6tTnsJsJeyu6g==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "requires": { + "p-limit": "^2.0.0" + } + }, + "p-try": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.0.0.tgz", + "integrity": "sha512-hMp0onDKIajHfIkdRk3P4CdCmErkYAxxDtP3Wx/4nZ3aGlau2VKh3mZpcuFkH27WQkL/3WBCPOktzA9ZOAnMQQ==", + "dev": true + }, + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + }, + "dependencies": { + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + } + } + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + }, + "webpack-dev-middleware": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-3.4.0.tgz", + "integrity": "sha512-Q9Iyc0X9dP9bAsYskAVJ/hmIZZQwf/3Sy4xCAZgL5cUkjZmUZLt4l5HpbST/Pdgjn3u6pE7u5OdGd1apgzRujA==", + "dev": true, + "requires": { + "memory-fs": "~0.4.1", + "mime": "^2.3.1", + "range-parser": "^1.0.3", + "webpack-log": "^2.0.0" + } + }, + "which-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", + "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", + "dev": true + }, + "yargs": { + "version": "12.0.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-12.0.2.tgz", + "integrity": "sha512-e7SkEx6N6SIZ5c5H22RTZae61qtn3PYUE8JYbBFlK9sYmh3DMQ6E5ygtaG/2BW0JZi4WGgTR2IV5ChqlqrDGVQ==", + "dev": true, + "requires": { + "cliui": "^4.0.0", + "decamelize": "^2.0.0", + "find-up": "^3.0.0", + "get-caller-file": "^1.0.1", + "os-locale": "^3.0.0", + "require-directory": "^2.1.1", + "require-main-filename": "^1.0.1", + "set-blocking": "^2.0.0", + "string-width": "^2.0.0", + "which-module": "^2.0.0", + "y18n": "^3.2.1 || ^4.0.0", + "yargs-parser": "^10.1.0" + } + }, + "yargs-parser": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-10.1.0.tgz", + "integrity": "sha512-VCIyR1wJoEBZUqk5PA+oOBF6ypbwh5aNB3I50guxAL/quggdfs4TtNHQrSazFA3fYZ+tEqfs0zIGlv0c/rgjbQ==", + "dev": true, + "requires": { + "camelcase": "^4.1.0" + } + } + } + }, + "webpack-log": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/webpack-log/-/webpack-log-2.0.0.tgz", + "integrity": "sha512-cX8G2vR/85UYG59FgkoMamwHUIkSSlV3bBMRsbxVXVUk2j6NleCKjQ/WE9eYg9WY4w25O9w8wKP4rzNZFmUcUg==", + "dev": true, + "requires": { + "ansi-colors": "^3.0.0", + "uuid": "^3.3.2" + } + }, + "webpack-merge": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-4.2.1.tgz", + "integrity": "sha512-4p8WQyS98bUJcCvFMbdGZyZmsKuWjWVnVHnAS3FFg0HDaRVrPbkivx2RYCre8UiemD67RsiFFLfn4JhLAin8Vw==", + "dev": true, + "requires": { + "lodash": "^4.17.5" + } + }, + "webpack-sources": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.3.0.tgz", + "integrity": "sha512-OiVgSrbGu7NEnEvQJJgdSFPl2qWKkWq5lHMhgiToIiN9w34EBnjYzSYs+VbL5KoYiLNtFFa7BZIKxRED3I32pA==", + "dev": true, + "requires": { + "source-list-map": "^2.0.0", + "source-map": "~0.6.1" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "webpack-subresource-integrity": { + "version": "1.1.0-rc.6", + "resolved": "https://registry.npmjs.org/webpack-subresource-integrity/-/webpack-subresource-integrity-1.1.0-rc.6.tgz", + "integrity": "sha512-Az7y8xTniNhaA0620AV1KPwWOqawurVVDzQSpPAeR5RwNbL91GoBSJAAo9cfd+GiFHwsS5bbHepBw1e6Hzxy4w==", + "dev": true, + "requires": { + "webpack-core": "^0.6.8" + } + }, + "websocket-driver": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.0.tgz", + "integrity": "sha1-DK+dLXVdk67gSdS90NP+LMoqJOs=", + "dev": true, + "requires": { + "http-parser-js": ">=0.4.0", + "websocket-extensions": ">=0.1.1" + } + }, + "websocket-extensions": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", + "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==", + "dev": true + }, + "when": { + "version": "3.6.4", + "resolved": "https://registry.npmjs.org/when/-/when-3.6.4.tgz", + "integrity": "sha1-RztRfsFZ4rhQBUl6E5g/CVQS404=", + "dev": true + }, + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "which-module": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-1.0.0.tgz", + "integrity": "sha1-u6Y8qGGUiZT/MHc2CJ47lgJsKk8=", + "dev": true + }, + "wide-align": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", + "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", + "dev": true, + "optional": true, + "requires": { + "string-width": "^1.0.2 || 2" + } + }, + "widest-line": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-2.0.1.tgz", + "integrity": "sha512-Ba5m9/Fa4Xt9eb2ELXt77JxVDV8w7qQrH0zS/TWSJdLyAwQjWoOzpzj5lwVftDz6n/EOu3tNACS84v509qwnJA==", + "dev": true, + "requires": { + "string-width": "^2.1.1" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + } + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + } + } + }, + "window-size": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.4.tgz", + "integrity": "sha1-+OGqHuWlPsW/FR/6CXQqatdpeHY=" + }, + "wordwrap": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", + "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=", + "dev": true + }, + "worker-farm": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/worker-farm/-/worker-farm-1.6.0.tgz", + "integrity": "sha512-6w+3tHbM87WnSWnENBUvA2pxJPLhQUg5LKwUQHq3r+XPhIM+Gh2R5ycbwPCyuGbNg+lPgdcnQUhuC02kJCvffQ==", + "dev": true, + "requires": { + "errno": "~0.1.7" + } + }, + "wrap-ansi": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", + "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", + "requires": { + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1" + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + }, + "write-file-atomic": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.4.2.tgz", + "integrity": "sha512-s0b6vB3xIVRLWywa6X9TOMA7k9zio0TMOsl9ZnDkliA/cfJlpHXAscj0gbHVJiTdIuAYpIyqS5GW91fqm6gG5g==", + "requires": { + "graceful-fs": "^4.1.11", + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.2" + } + }, + "ws": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-3.3.3.tgz", + "integrity": "sha512-nnWLa/NwZSt4KQJu51MYlCcSQ5g7INpOrOMt4XV8j4dqTXdmlUmSHQ8/oLC069ckre0fRsgfvsKwbTdtKLCDkA==", + "dev": true, + "requires": { + "async-limiter": "~1.0.0", + "safe-buffer": "~5.1.0", + "ultron": "~1.1.0" + } + }, + "xdg-basedir": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-3.0.0.tgz", + "integrity": "sha1-SWsswQnsqNus/i3HK2A8F8WHCtQ=" + }, + "xml2js": { + "version": "0.4.19", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.19.tgz", + "integrity": "sha512-esZnJZJOiJR9wWKMyuvSE1y6Dq5LCuJanqhxslH2bxM6duahNZ+HMpCLhBQGZkbX6xRf8x1Y2eJlgt2q3qo49Q==", + "requires": { + "sax": ">=0.6.0", + "xmlbuilder": "~9.0.1" + }, + "dependencies": { + "sax": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" + } + } + }, + "xmlbuilder": { + "version": "9.0.7", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz", + "integrity": "sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0=" + }, + "xmldom": { + "version": "0.1.27", + "resolved": "https://registry.npmjs.org/xmldom/-/xmldom-0.1.27.tgz", + "integrity": "sha1-1QH5ezvbQDr4757MIFcxh6rawOk=", + "dev": true + }, + "xmlhttprequest-ssl": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.5.tgz", + "integrity": "sha1-wodrBhaKrcQOV9l+gRkayPQ5iz4=", + "dev": true + }, + "xregexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/xregexp/-/xregexp-4.0.0.tgz", + "integrity": "sha512-PHyM+sQouu7xspQQwELlGwwd05mXUFqwFYfqPO0cC7x4fxyHnnuetmQr6CjJiafIDoH4MogHb9dOoJzR/Y4rFg==", + "dev": true + }, + "xtend": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", + "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=", + "dev": true + }, + "y18n": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", + "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", + "dev": true + }, + "yallist": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", + "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=", + "dev": true + }, + "yargs": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-7.1.0.tgz", + "integrity": "sha1-a6MY6xaWFyf10oT46gA+jWFU0Mg=", + "dev": true, + "requires": { + "camelcase": "^3.0.0", + "cliui": "^3.2.0", + "decamelize": "^1.1.1", + "get-caller-file": "^1.0.1", + "os-locale": "^1.4.0", + "read-pkg-up": "^1.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^1.0.1", + "set-blocking": "^2.0.0", + "string-width": "^1.0.2", + "which-module": "^1.0.0", + "y18n": "^3.2.1", + "yargs-parser": "^5.0.0" + }, + "dependencies": { + "camelcase": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz", + "integrity": "sha1-MvxLn82vhF/N9+c7uXysImHwqwo=", + "dev": true + }, + "y18n": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz", + "integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE=", + "dev": true + } + } + }, + "yargs-parser": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-5.0.0.tgz", + "integrity": "sha1-J17PDX/+Bcd+ZOfIbkzZS/DhIoo=", + "dev": true, + "requires": { + "camelcase": "^3.0.0" + }, + "dependencies": { + "camelcase": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz", + "integrity": "sha1-MvxLn82vhF/N9+c7uXysImHwqwo=", + "dev": true + } + } + }, + "yauzl": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.4.1.tgz", + "integrity": "sha1-lSj0QtqxsihOWLQ3m7GU4i4MQAU=", + "dev": true, + "requires": { + "fd-slicer": "~1.0.1" + } + }, + "yeast": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/yeast/-/yeast-0.1.2.tgz", + "integrity": "sha1-AI4G2AlDIMNy28L47XagymyKxBk=", + "dev": true + }, + "yn": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/yn/-/yn-2.0.0.tgz", + "integrity": "sha1-5a2ryKz0CPY4X8dklWhMiOavaJo=", + "dev": true + }, + "zone.js": { + "version": "0.8.29", + "resolved": "https://registry.npmjs.org/zone.js/-/zone.js-0.8.29.tgz", + "integrity": "sha512-mla2acNCMkWXBD+c+yeUrBUrzOxYMNFdQ6FGfigGGtEVBPJx07BQeJekjt9DmH1FtZek4E9rE1eRR9qQpxACOQ==" + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 00000000..04c66861 --- /dev/null +++ b/package.json @@ -0,0 +1,117 @@ +{ + "name": "truevision-designer", + "homepage": "https://www.truevision.ai", + "description": "Truevison designer is a tool to create environment and scenarios for training & testing autonomous vehicles", + "version": "2022.3.01", + "author": { + "name": "Truevision.ai", + "email": "himanshu@truevision.ai", + "url": "https://www.truevision.ai" + }, + "main": "main.js", + "scripts": { + "ng": "ng", + "start": "ng serve", + "start:dev": "npx ng serve --host 0.0.0.0 --poll", + "build": "ng build", + "build-watch": "ng build --watch", + "build-prod": "ng build --prod", + "build-prod-live": "ng build --prod --watch", + "test": "ng test", + "lint": "ng lint", + "e2e": "ng e2e", + "pack": "electron-builder --dir", + "dist": "electron-builder", + "postinstall": "electron-builder install-app-deps", + "linux": "ng build --prod --build-optimizer && electron-builder build --linux", + "windows": "ng build --prod --build-optimizer && electron-builder build --windows" + }, + "private": true, + "dependencies": { + "@agm/core": "1.0.0-beta.2", + "@angular/animations": "^7.2.6", + "@angular/cdk": "^7.3.3", + "@angular/common": "~7.2.0", + "@angular/compiler": "~7.2.0", + "@angular/core": "~7.2.0", + "@angular/flex-layout": "^7.0.0-beta.23", + "@angular/forms": "~7.2.0", + "@angular/http": "7.2.0", + "@angular/material": "^7.3.3", + "@angular/platform-browser": "~7.2.0", + "@angular/platform-browser-dynamic": "~7.2.0", + "@angular/router": "~7.2.0", + "@auth0/angular-jwt": "^3.0.0", + "@grpc/proto-loader": "^0.1.0", + "@ncstate/sat-popover": "^4.0.0", + "@ngu/carousel": "1.5.4", + "@ngx-translate/core": "10.0.1", + "@ngx-translate/http-loader": "3.0.1", + "@types/mixpanel-browser": "^2.23.1", + "angular-calendar": "0.26.4", + "angular-file-saver": "^1.1.3", + "async": "^1.5.2", + "chart.js": "2.5.0", + "classlist.js": "1.1.20150312", + "core-js": "^2.5.4", + "date-fns": "1.28.5", + "earcut": "^2.2.3", + "electron-splashscreen": "^1.0.0", + "fast-xml-parser": "^3.12.16", + "file-saver": "^2.0.2", + "google-protobuf": "^3.0.0", + "grpc": "^1.21.1", + "hammerjs": "^2.0.8", + "hopscotch": "0.3.1", + "lodash": "^4.17.21", + "minimist": "^1.2.5", + "mixpanel-browser": "^2.29.1", + "ng2-charts": "1.6.0", + "ng2-dragula": "1.3.1", + "ng2-file-upload": "1.2.1", + "ng2-validation": "4.2.0", + "ngx-color-picker": "^5.3.8", + "ngx-electron": "^2.1.1", + "ngx-pagination": "3.1.0", + "ngx-perfect-scrollbar": "6.1.0", + "ngx-quill": "2.0.4", + "perfect-scrollbar": "1.4.0", + "rxjs": "^6.3.3", + "rxjs-compat": "6.1.0", + "stats.js": "^0.17.0", + "three": "^0.101.1", + "tslib": "^1.9.0", + "web-animations-js": "2.3.1", + "zone.js": "~0.8.26" + }, + "devDependencies": { + "@angular-devkit/build-angular": "~0.13.0", + "@angular-devkit/core": "7.0.6", + "@angular/cli": "~7.3.3", + "@angular/compiler-cli": "~7.2.0", + "@angular/language-service": "~7.2.0", + "@types/jasmine": "~2.8.8", + "@types/jasminewd2": "~2.0.3", + "@types/node": "~8.9.4", + "@types/three": "^0.93.31", + "codelyzer": "~4.5.0", + "copy-webpack-plugin": "4.3.0", + "electron": "^5.0.3", + "electron-builder": "^21.2.0", + "electron-packager": "^14.0.0", + "jasmine-core": "~2.99.1", + "jasmine-spec-reporter": "~4.2.1", + "karma": "~4.0.0", + "karma-chrome-launcher": "~2.2.0", + "karma-coverage-istanbul-reporter": "~2.0.1", + "karma-jasmine": "~1.1.2", + "karma-jasmine-html-reporter": "^0.2.2", + "ngx-color": "^7.0.0", + "nwjs-builder-phoenix": "^1.15.0", + "protractor": "~5.4.0", + "sass": "^1.49.0", + "ts-node": "~7.0.0", + "tslint": "~5.11.0", + "typescript": "~3.2.2" + } +} diff --git a/src/app/app.component.css b/src/app/app.component.css new file mode 100644 index 00000000..bd9ce3fc --- /dev/null +++ b/src/app/app.component.css @@ -0,0 +1,11 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +.example-icon { + padding: 0 14px; +} + +.example-spacer { + flex: 1 1 auto; +} \ No newline at end of file diff --git a/src/app/app.component.html b/src/app/app.component.html new file mode 100644 index 00000000..f5ee029f --- /dev/null +++ b/src/app/app.component.html @@ -0,0 +1,22 @@ + + + +
+ + + + + + +
+ + diff --git a/src/app/app.component.ts b/src/app/app.component.ts new file mode 100644 index 00000000..9d61d630 --- /dev/null +++ b/src/app/app.component.ts @@ -0,0 +1,94 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { AfterViewInit, Component, OnInit, Renderer2 } from '@angular/core'; +import { Title } from '@angular/platform-browser'; +import { ActivatedRoute, NavigationEnd, Router, RouterEvent } from '@angular/router'; + +import { RoutePartsService } from './shared/services/route-parts.service'; +import { ThemeService } from './shared/services/theme.service'; + +import { filter, takeUntil } from 'rxjs/operators'; +import { LayoutService } from './shared/services/layout.service'; +import { AppService } from './core/services/app.service'; +import { AnalyticsService } from './core/analytics/analytics.service'; +import { environment } from '../environments/environment'; +import { Subject } from 'rxjs'; +import { TranslateService } from '@ngx-translate/core'; + +@Component( { + selector: 'app-root', + templateUrl: './app.component.html', + styleUrls: [ './app.component.css' ] +} ) +export class AppComponent implements OnInit, AfterViewInit { + + appTitle = 'Truevision'; + pageTitle = ''; + private destroyed$ = new Subject(); + + constructor ( + public title: Title, + private router: Router, + private activeRoute: ActivatedRoute, + private routePartsService: RoutePartsService, + private themeService: ThemeService, + private layout: LayoutService, + private renderer: Renderer2, + private appService: AppService, + private analytics: AnalyticsService, + private translate: TranslateService + ) { + } + + get production () { + return environment.production; + } + + ngOnInit () { + + this.analytics.init(); + + this.analytics.send( 'app-opened', {} ); + + this.changePageTitle(); + + this.trackPageChanges(); + + this.translate.setDefaultLang( 'en' ); + + } + + ngAfterViewInit () { + this.layout.applyMatTheme( this.renderer ); + } + + changePageTitle () { + this.router.events.pipe( filter( event => event instanceof NavigationEnd ) ).subscribe( ( routeChange ) => { + var routeParts = this.routePartsService.generateRouteParts( this.activeRoute.snapshot ); + if ( !routeParts.length ) + return this.title.setTitle( this.appTitle ); + // Extract title from parts; + this.pageTitle = routeParts + .reverse() + .map( ( part ) => part.title ) + .reduce( ( partA, partI ) => { + return `${ partA } > ${ partI }`; + } ); + this.pageTitle += ` | ${ this.appTitle }`; + this.title.setTitle( this.pageTitle ); + } ); + } + + private trackPageChanges () { + this.router.events + .pipe( + filter( ( event: RouterEvent ) => event instanceof NavigationEnd ), + takeUntil( this.destroyed$ ), + ) + .subscribe( ( event: NavigationEnd ) => { + this.analytics.trackPageView( event.url ); + } ); + } +} diff --git a/src/app/app.module.ts b/src/app/app.module.ts new file mode 100644 index 00000000..573ba6df --- /dev/null +++ b/src/app/app.module.ts @@ -0,0 +1,79 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { ErrorHandler, NgModule } from '@angular/core'; +import { BrowserModule, HAMMER_GESTURE_CONFIG } from '@angular/platform-browser'; +import { NoopAnimationsModule } from '@angular/platform-browser/animations'; +import { GestureConfig, MatTreeModule } from '@angular/material'; +import { PERFECT_SCROLLBAR_CONFIG, PerfectScrollbarConfigInterface, PerfectScrollbarModule } from 'ngx-perfect-scrollbar'; + +import { SharedModule } from './shared/shared.module'; +import { AppComponent } from './app.component'; +import { AppRoutingModule } from './app.routing'; + +import { HttpClient, HttpClientModule } from '@angular/common/http'; +import { TranslateLoader, TranslateModule } from '@ngx-translate/core'; +import { TranslateHttpLoader } from '@ngx-translate/http-loader'; +import { ErrorHandlerService } from './shared/services/error-handler.service'; + +import { TvMapModule } from './modules/tv-map/tv-map.module'; +import { ThreeJsModule } from './modules/three-js/three-js.module'; +import { CoreModule } from './core/core.module'; +import { NgxElectronModule } from 'ngx-electron'; +import { FormsModule } from '@angular/forms'; +import { SessionsModule } from './views/sessions/sessions.module'; +import { HashLocationStrategy, LocationStrategy } from '@angular/common'; + +import { SatPopoverModule } from '@ncstate/sat-popover'; +import { EditorModule } from './views/editor/editor.module'; + +// AoT requires an exported function for factories +export function HttpLoaderFactory ( httpClient: HttpClient ) { + return new TranslateHttpLoader( httpClient, './assets/i18n/', '.json' ); +} + +const DEFAULT_PERFECT_SCROLLBAR_CONFIG: PerfectScrollbarConfigInterface = { + suppressScrollX: true +}; + +@NgModule( { + imports: [ + AppRoutingModule, + MatTreeModule, + BrowserModule, + NoopAnimationsModule, + SharedModule, + HttpClientModule, + PerfectScrollbarModule, + FormsModule, + + CoreModule, + EditorModule, + TvMapModule, + ThreeJsModule, + NgxElectronModule, + + SessionsModule, + SatPopoverModule, + + TranslateModule.forRoot( { + loader: { + provide: TranslateLoader, + useFactory: HttpLoaderFactory, + deps: [ HttpClient ] + } + } ), + ], + declarations: [ AppComponent ], + providers: [ + { provide: ErrorHandler, useClass: ErrorHandlerService }, + { provide: HAMMER_GESTURE_CONFIG, useClass: GestureConfig }, + { provide: PERFECT_SCROLLBAR_CONFIG, useValue: DEFAULT_PERFECT_SCROLLBAR_CONFIG }, + { provide: LocationStrategy, useClass: HashLocationStrategy } + ], + bootstrap: [ AppComponent ], + exports: [] +} ) +export class AppModule { +} diff --git a/src/app/app.routing.ts b/src/app/app.routing.ts new file mode 100644 index 00000000..02a8dcfd --- /dev/null +++ b/src/app/app.routing.ts @@ -0,0 +1,79 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { NgModule } from '@angular/core'; +import { RouterModule, Routes } from '@angular/router'; +import { AuthGuard } from './core/guards/auth.guard'; +import { EditorLayoutComponent } from './views/editor/layout/editor-layout.component'; +import { EditorComponent } from './views/editor/editor.component'; +import { SplashComponent } from './shared/components/splash/splash.component'; +import { SignupComponent } from './views/sessions/signup/signup.component'; +import { SigninComponent } from './views/sessions/signin/signin.component'; +import { ForgotPasswordComponent } from './views/sessions/forgot-password/forgot-password.component'; +import { LockscreenComponent } from './views/sessions/lockscreen/lockscreen.component'; +import { NotFoundComponent } from './views/sessions/not-found/not-found.component'; +import { ErrorComponent } from './views/sessions/error/error.component'; + +const appRoutes: Routes = [ + { + path: '', + redirectTo: '/editor', + pathMatch: 'full' + }, + { + path: 'splash', + component: SplashComponent, + }, + { + path: 'editor', + canActivate: [ AuthGuard ], + component: EditorLayoutComponent, + children: [ + { + path: '', component: EditorComponent, + data: { + title: 'Editor', + breadcrumb: '' + } + }, + ] + }, + { + path: 'sessions', + children: [ { + path: 'signup', + component: SignupComponent, + data: { title: 'Signup' } + }, { + path: 'signin', + component: SigninComponent, + data: { title: 'Signin' } + }, { + path: 'forgot-password', + component: ForgotPasswordComponent, + data: { title: 'Forgot password' } + }, { + path: 'lockscreen', + component: LockscreenComponent, + data: { title: 'Lockscreen' } + }, { + path: '404', + component: NotFoundComponent, + data: { title: 'Not Found' } + }, { + path: 'error', + component: ErrorComponent, + data: { title: 'Error' } + } ] + }, + +]; + +@NgModule( { + imports: [ RouterModule.forRoot( appRoutes, { enableTracing: false, useHash: false } ) ], + exports: [ RouterModule ] +} ) +export class AppRoutingModule { + +} diff --git a/src/app/core/analytics/analytics.service.ts b/src/app/core/analytics/analytics.service.ts new file mode 100644 index 00000000..0f74c647 --- /dev/null +++ b/src/app/core/analytics/analytics.service.ts @@ -0,0 +1,98 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { Injectable } from '@angular/core'; +import { MixpanelService } from './mixpanel.service'; +import { environment } from '../../../environments/environment'; +import { AuthService } from '../services/auth.service'; +import { Environment } from '../utils/environment'; +import { Router, RouterEvent, NavigationEnd } from '@angular/router'; +import { filter, takeUntil } from 'rxjs/operators'; +import { Subject } from 'rxjs'; + +@Injectable( { + providedIn: 'root' +} ) +export class AnalyticsService { + + private destroyed$ = new Subject(); + + constructor ( private mixpanel: MixpanelService, private auth: AuthService, private router: Router ) { + + if ( Environment.production ) this.mixpanel.init( environment.mixpanel_id, this.auth.email ); + + if ( this.email != null ) this.setEmail( this.email ); + + this.auth.currentUser.subscribe( e => this.onUserChanged() ); + + } + + get email () { + + return this.auth.email; + + } + + init () { + + if ( Environment.production ) this.trackPageChanges(); + + } + + private trackPageChanges () { + + this.router.events + .pipe( + filter( ( event: RouterEvent ) => event instanceof NavigationEnd ), + takeUntil( this.destroyed$ ), + ) + .subscribe( ( event: NavigationEnd ) => { + this.trackPageView( event.url ); + } ); + + } + + onUserChanged (): void { + + if ( this.email != null ) this.setEmail( this.email ); + + } + + send ( event: string, options: any ) { + + if ( !Environment.production ) return; + + this.mixpanel.track( event, options ); + + } + + trackError ( error: Error ) { + + if ( !Environment.production ) return; + + this.mixpanel.track( 'error', { + name: error.name, + message: error.message, + stack: error.stack + } ); + + } + + setEmail ( email: string ) { + + if ( Environment.production ) this.mixpanel.setEmail( email ); + + } + + + trackPageView ( url: string ) { + + if ( !Environment.production ) return; + + this.mixpanel.track( 'pageview', { + url: url + } ); + + } +} diff --git a/src/app/core/analytics/mixpanel.service.ts b/src/app/core/analytics/mixpanel.service.ts new file mode 100644 index 00000000..8cae2867 --- /dev/null +++ b/src/app/core/analytics/mixpanel.service.ts @@ -0,0 +1,44 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { Injectable } from '@angular/core'; +import * as mixpanel from 'mixpanel-browser'; + +@Injectable( { + providedIn: 'root' +} ) +export class MixpanelService { + + + constructor () { + } + + /** + * Initialize mixpanel. + * + * @param {string} userToken + * @memberof MixpanelService + */ + init ( userToken: string, identifier: string = null ): void { + mixpanel.init( userToken ); + mixpanel.identify( identifier || userToken ); + } + + /** + * Push new action to mixpanel. + * + * @param {string} id Name of the action to track. + * @param {*} [action={}] Actions object with custom properties. + * @memberof MixpanelService + */ + track ( id: string, action: any = {} ): void { + mixpanel.track( id, action ); + } + + setEmail ( email: string ) { + mixpanel.people.set( { + $email: email + } ); + } +} diff --git a/src/app/core/analytics/track.directive.ts b/src/app/core/analytics/track.directive.ts new file mode 100644 index 00000000..087a7ab7 --- /dev/null +++ b/src/app/core/analytics/track.directive.ts @@ -0,0 +1,39 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { Directive, Input, HostListener } from '@angular/core'; +import { AnalyticsService } from './analytics.service'; + +@Directive( { + selector: '[appTrack]' +} ) +export class TrackDirective { + + @Input( 'appTrack' ) eventName: string; + + @Input() event: string; + @Input() action: string; + + constructor ( private analytics: AnalyticsService ) { } + + @HostListener( 'click' ) onMouseEnter () { + + const event = this.eventName || this.event; + + if ( this.action != null ) { + + const options = { + action: this.action + }; + + this.analytics.send( event, options ); + + } else { + + this.analytics.send( event, null ); + + } + } + +} diff --git a/src/app/core/asset-importer.service.ts b/src/app/core/asset-importer.service.ts new file mode 100644 index 00000000..6d4a73fd --- /dev/null +++ b/src/app/core/asset-importer.service.ts @@ -0,0 +1,198 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { Injectable } from '@angular/core'; +import { ElectronService } from 'ngx-electron'; +import * as THREE from 'three'; +import { SnackBar } from 'app/services/snack-bar.service'; +import { SceneService } from './services/scene.service'; + +import 'three/examples/js/loaders/OBJLoader'; +import 'three/examples/js/loaders/FBXLoader'; +import 'three/examples/js/loaders/ColladaLoader'; +import { FileService } from 'app/services/file.service'; + + +@Injectable( { + providedIn: 'root' +} ) +export class AssetImporterService { + + private fs; + private path; + + // causing bugs need to remove these + // private successCallback; + // private errorCallback; + + constructor ( private electronService: ElectronService ) { + } + + public import ( filepath: string, successCallback: Function, errorCallback: Function ) { + + this.fs = this.electronService.remote.require( 'fs' ); + this.path = this.electronService.remote.require( 'path' ); + + const fileExtension = this.path.extname( filepath ); + + switch ( fileExtension ) { + case '.obj': + this.importOBJ( filepath, successCallback, errorCallback ); + break; + + case '.dae': + this.importCollada( filepath, successCallback, errorCallback ); + break; + + case '.gltf': + this.importGLTF( filepath, successCallback, errorCallback ); + break; + + case '.glb': + this.importGLTF( filepath, successCallback, errorCallback ); + break; + + case '.fbx': + this.importFBX( filepath, successCallback, errorCallback ); + break; + + default: + SnackBar.error( 'unknown 3d format. Please use any of obj, dae, fbx formats' ); + break; + } + + } + + importOBJ ( filepath: string, success: Function, error: Function ) { + + var loader = new THREE.OBJLoader(); + + this.fs.readFile( filepath, 'utf-8', ( err, data ) => { + + if ( err ) { + error( 'An error ocurred reading the file :' + err.message ); + return; + } + + const group = loader.parse( data ); + + success( group ); + + } ) + + } + + importGLTF ( filepath: string, success: Function, error: Function ) { + + const loader = new THREE.GLTFLoader(); + + const dir = filepath.split( '/' ).slice( 0, -1 ).join( '/' ) + "/"; + + loader.load( `file:///${ filepath }`, ( gltf ) => { + + success( gltf.scene ); + + }, () => { + + // on progress + + }, ( err ) => { + + console.error( err ); + + error( err ); + + } ) + + // this.fs.readFile( filepath, 'ascii', ( err, data ) => { + + // if ( err ) { + // error( 'An error ocurred reading the file :' + err.message ); + // return; + // } + + // const dir = filepath.split( '/' ).slice( 0, -1 ).join( '/' ) + "/"; + + // loader.parse( data, `file:///${ dir }`, ( gltf ) => { + + // success( gltf.scene ); + + // }, ( err ) => { + + // error( err ) + + // } ); + + // } ) + + } + + importCollada ( filepath: string, success: Function, error: Function ) { + + var loader = new THREE.ColladaLoader(); + + this.fs.readFile( filepath, 'utf-8', ( err, data ) => { + + if ( err ) { + error( 'An error ocurred reading the file :' + err.message ); + return; + } + + const group = loader.parse( data ); + + success( group.scene ); + + } ); + + } + + importFBX ( filepath: string, success: Function, error: Function ) { + + SnackBar.error( "FBX files are not supported" ); + + // var loader = new THREE.FBXLoader(); + + // loader.load( 'assets/TrafficCone01.fbx', function ( object ) { + // // loader.load( 'assets/Box.fbx', function ( object ) { + + // object.traverse( ( child: any ) => { + + // if ( child.isMesh ) { + + // child.castShadow = true; + // child.receiveShadow = true; + + // } + + // } ); + + // SceneService.add( object ); + + // }, ( e ) => console.debug( e ), ( e ) => console.error( e ) ); + + // this.fs.readFile( filepath, 'utf-8', ( err, data ) => { + + // if ( err ) { + // error( 'An error ocurred reading the file :' + err.message ); + // return; + // } + + // try { + + // success( loader.parse( data, filepath ) ); + + // } catch ( error ) { + + // console.error( error ); + + // error( 'Could not import file, ' + error.message ); + + // } + + + // } ); + + } + +} diff --git a/src/app/core/commands/add-connection-command.ts b/src/app/core/commands/add-connection-command.ts new file mode 100644 index 00000000..cd331c89 --- /dev/null +++ b/src/app/core/commands/add-connection-command.ts @@ -0,0 +1,90 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { BaseCommand } from './base-command'; +import { TvRoad } from 'app/modules/tv-map/models/tv-road.model'; +import { TvLaneSide } from 'app/modules/tv-map/models/tv-common'; +import { SceneService } from '../services/scene.service'; +import { JunctionEntryObject } from 'app/modules/three-js/objects/junction-entry.object'; +import { TvJunction } from 'app/modules/tv-map/models/tv-junction'; +import { TvJunctionLaneLink } from 'app/modules/tv-map/models/tv-junction-lane-link'; +import { TvJunctionConnection } from 'app/modules/tv-map/models/tv-junction-connection'; +import { RoadFactory } from '../factories/road-factory.service'; +import { ManeuverTool } from '../tools/maneuver-tool'; +import { LanePathFactory } from '../factories/lane-path-factory.service'; + +export class AddConnectionCommand extends BaseCommand { + + private connectingRoad: TvRoad; + private connection: TvJunctionConnection; + private link: TvJunctionLaneLink; + + constructor ( + private entry: JunctionEntryObject, + private exit: JunctionEntryObject, + private junction: TvJunction, + private tool: ManeuverTool + ) { + + super(); + + } + + execute (): void { + + const laneWidth = this.entry.lane.getWidthValue( 0 ); + + this.connectingRoad = this.tool.createConnectingRoad( this.entry, this.exit, TvLaneSide.RIGHT, laneWidth, this.junction ); + + const result = this.tool.createConnections( this.junction, this.entry, this.connectingRoad, this.exit ); + + this.connection = result.connection; + + this.link = result.link; + + const lane = this.connectingRoad.getFirstLaneSection().getLaneById( -1 ); + + // tslint:disable-next-line: max-line-length + this.link.lanePath = LanePathFactory.createPathForLane( this.entry.road, this.connectingRoad, lane, result.connection, result.link ); + + this.tool.connectingRoad = this.connectingRoad; + + this.tool.lanePathObject = result.link.lanePath; + + RoadFactory.rebuildRoad( this.connectingRoad ); + + SceneService.add( result.link.lanePath ); + } + + undo (): void { + + this.openDrive.removeRoad( this.connectingRoad ); + + this.junction.removeConnection( this.connection, this.entry.road, this.exit.road ); + + this.tool.connectingRoad = null; + + this.tool.lanePathObject = null; + + SceneService.remove( this.link.lanePath ); + + } + + redo (): void { + + this.openDrive.addRoadInstance( this.connectingRoad ); + + this.junction.addConnection( this.connection ); + + this.tool.updateNeighbors( this.junction, this.entry, this.connectingRoad, this.exit ); + + this.tool.connectingRoad = this.connectingRoad; + + this.tool.lanePathObject = this.link.lanePath; + + RoadFactory.rebuildRoad( this.connectingRoad ); + + SceneService.add( this.link.lanePath ); + } +} \ No newline at end of file diff --git a/src/app/core/commands/add-lane-command.ts b/src/app/core/commands/add-lane-command.ts new file mode 100644 index 00000000..f0c7e520 --- /dev/null +++ b/src/app/core/commands/add-lane-command.ts @@ -0,0 +1,37 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { BaseCommand } from './base-command'; +import { TvLaneSection } from '../../modules/tv-map/models/tv-lane-section'; +import { TvLane } from '../../modules/tv-map/models/tv-lane'; +import { TvMapSourceFile } from '../../modules/tv-map/services/tv-map-source-file'; + +export class AddLaneCommand extends BaseCommand { + + constructor ( private laneSection: TvLaneSection, private lane: TvLane ) { + super(); + } + + execute (): void { + + this.laneSection.addLaneInstance( this.lane, true ); + + TvMapSourceFile.roadNetworkChanged.emit( this.openDrive ); + } + + undo (): void { + + this.laneSection.removeLaneById( this.lane.id ); + + TvMapSourceFile.roadNetworkChanged.emit( this.openDrive ); + + } + + redo (): void { + + this.execute(); + + } + +} \ No newline at end of file diff --git a/src/app/core/commands/add-lane-offset-command.ts b/src/app/core/commands/add-lane-offset-command.ts new file mode 100644 index 00000000..e1e0d3e8 --- /dev/null +++ b/src/app/core/commands/add-lane-offset-command.ts @@ -0,0 +1,43 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { BaseCommand } from './base-command'; +import { NodeFactoryService } from '../factories/node-factory.service'; +import { SceneService } from '../services/scene.service'; +import { SetInspectorCommand } from './set-inspector-command'; +import { LaneOffsetNode } from 'app/modules/three-js/objects/control-point'; + +export class AddLaneOffsetCommand extends BaseCommand { + + private command: SetInspectorCommand; + + constructor ( private node: LaneOffsetNode ) { + + super(); + + } + + execute (): void { + + this.node.road.addLaneOffsetInstance( this.node.laneOffset ); + + SceneService.add( this.node ); + + // ( new SetInspectorCommand( LaneOffsetInspector, new LaneOffsetInspectorData( this.node, this.node.road ) ) ).execute(); + } + + undo (): void { + + this.node.road.removeLaneOffset( this.node.laneOffset ); + + SceneService.remove( this.node ); + } + + redo (): void { + + this.execute(); + + } + +} \ No newline at end of file diff --git a/src/app/core/commands/add-object-command.ts b/src/app/core/commands/add-object-command.ts new file mode 100644 index 00000000..337c5312 --- /dev/null +++ b/src/app/core/commands/add-object-command.ts @@ -0,0 +1,35 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { BaseCommand } from './base-command'; +import { Object3D } from 'three'; +import { SceneService } from '../services/scene.service'; + +export class AddObjectCommand extends BaseCommand { + + constructor ( private object: Object3D ) { + + super(); + + } + + execute (): void { + + SceneService.add( this.object ); + + } + + undo (): void { + + SceneService.remove( this.object ); + + } + + redo (): void { + + this.execute(); + + } + +} diff --git a/src/app/core/commands/add-road-command.ts b/src/app/core/commands/add-road-command.ts new file mode 100644 index 00000000..ad62f2c0 --- /dev/null +++ b/src/app/core/commands/add-road-command.ts @@ -0,0 +1,53 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { OdBaseCommand } from './od-base-command'; +import { TvRoad } from '../../modules/tv-map/models/tv-road.model'; +import { AppInspector } from 'app/core/inspector'; +import { RoadInspector } from '../../views/inspectors/road-inspector/road-inspector.component'; +import { SceneService } from 'app/core/services/scene.service'; +import { RoadControlPoint } from 'app/modules/three-js/objects/road-control-point'; + +export class AddRoadCommand extends OdBaseCommand { + + constructor ( private road: TvRoad, private point: RoadControlPoint ) { + + super(); + + } + + execute (): void { + + AppInspector.setInspector( RoadInspector, { road: this.road, controlPoint: this.point } ); + + } + + undo (): void { + + this.road.spline.removeControlPoint( this.point ); + + SceneService.remove( this.point ); + + this.road.spline.hide(); + + this.openDrive.removeRoad( this.road ); + + AppInspector.clear(); + } + + redo (): void { + + this.road.spline.addControlPoint( this.point ); + + SceneService.add( this.point ); + + this.road.spline.show(); + + this.openDrive.addRoadInstance( this.road ); + + AppInspector.setInspector( RoadInspector, { road: this.road, controlPoint: this.point } ); + } + + +} \ No newline at end of file diff --git a/src/app/core/commands/add-road-geometry-command.ts b/src/app/core/commands/add-road-geometry-command.ts new file mode 100644 index 00000000..ce2ed890 --- /dev/null +++ b/src/app/core/commands/add-road-geometry-command.ts @@ -0,0 +1,38 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { OdBaseCommand } from './od-base-command'; +import { TvRoad } from '../../modules/tv-map/models/tv-road.model'; +import { TvAbstractRoadGeometry } from '../../modules/tv-map/models/geometries/tv-abstract-road-geometry'; +import { TvMapBuilder } from '../../modules/tv-map/builders/od-builder.service'; + +export class AddRoadGeometryCommand extends OdBaseCommand { + + constructor ( private road: TvRoad, private geometry: TvAbstractRoadGeometry ) { + super(); + } + + execute (): void { + + this.road.addGeometry( this.geometry ); + + TvMapBuilder.buildRoad( this.openDrive.gameObject, this.road ); + + } + + undo (): void { + + this.road.removeGeometryByUUID( this.geometry.uuid ); + + TvMapBuilder.buildRoad( this.openDrive.gameObject, this.road ); + } + + redo (): void { + + this.execute(); + + } + + +} \ No newline at end of file diff --git a/src/app/core/commands/add-road-object-command.ts b/src/app/core/commands/add-road-object-command.ts new file mode 100644 index 00000000..bdd48d3a --- /dev/null +++ b/src/app/core/commands/add-road-object-command.ts @@ -0,0 +1,42 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { TvRoadObject } from '../../modules/tv-map/models/tv-road-object'; +import { OdBaseCommand } from './od-base-command'; +import { SceneService } from '../services/scene.service'; +import { Object3D } from 'three'; + +export class AddRoadObjectCommand extends OdBaseCommand { + + constructor ( private roadId: number, private roadObject: TvRoadObject, private objects: Object3D[] = [] ) { + super(); + } + + execute (): void { + + SceneService.add( this.roadObject.mesh, false ); + + this.openDrive.getRoadById( this.roadId ).addRoadObjectInstance( this.roadObject ); + } + + undo (): void { + + SceneService.remove( this.roadObject.mesh, false ); + + this.openDrive.getRoadById( this.roadId ).removeRoadObjectById( this.roadObject.id ); + + this.objects.forEach( object => SceneService.remove( object ) ); + + } + + redo (): void { + + this.execute(); + + this.objects.forEach( object => SceneService.add( object ) ); + + } + + +} diff --git a/src/app/core/commands/add-road-point-command.ts b/src/app/core/commands/add-road-point-command.ts new file mode 100644 index 00000000..62cc49be --- /dev/null +++ b/src/app/core/commands/add-road-point-command.ts @@ -0,0 +1,41 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { OdBaseCommand } from './od-base-command'; +import { TvRoad } from '../../modules/tv-map/models/tv-road.model'; +import { RoadFactory } from 'app/core/factories/road-factory.service'; +import { AppInspector } from 'app/core/inspector'; +import { RoadInspector } from '../../views/inspectors/road-inspector/road-inspector.component'; +import { RoadControlPoint } from 'app/modules/three-js/objects/road-control-point'; + +export class AddRoadPointCommand extends OdBaseCommand { + + constructor ( private road: TvRoad, private newPoint: RoadControlPoint, private oldPoint: RoadControlPoint ) { + + super(); + + } + + execute (): void { + + AppInspector.setInspector( RoadInspector, { road: this.road, controlPoint: this.newPoint } ); + + } + + undo (): void { + + RoadFactory.removeControlPoint( this.road, this.newPoint ); + + AppInspector.setInspector( RoadInspector, { road: this.road, controlPoint: this.oldPoint } ); + + } + + redo (): void { + + RoadFactory.addControlPointNew( this.road, this.newPoint ); + + AppInspector.setInspector( RoadInspector, { road: this.road, controlPoint: this.newPoint } ); + } + +} \ No newline at end of file diff --git a/src/app/core/commands/add-roadmark-node.ts b/src/app/core/commands/add-roadmark-node.ts new file mode 100644 index 00000000..3c73c69f --- /dev/null +++ b/src/app/core/commands/add-roadmark-node.ts @@ -0,0 +1,51 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { BaseCommand } from './base-command'; +import { SceneService } from '../services/scene.service'; +import { TvLane } from 'app/modules/tv-map/models/tv-lane'; +import { OdRoadMarkBuilder } from 'app/modules/tv-map/builders/od-road-mark-builder'; +import { TvRoad } from 'app/modules/tv-map/models/tv-road.model'; +import { TvLaneRoadMark } from 'app/modules/tv-map/models/tv-lane-road-mark'; + +export class AddRoadmarkNodeCommand extends BaseCommand { + + private road: TvRoad; + + constructor ( private lane: TvLane, private roadMark: TvLaneRoadMark, private roadMarkbuilder: OdRoadMarkBuilder ) { + + super(); + + this.road = this.openDrive.getRoadById( this.lane.roadId ); + + } + + execute (): void { + + this.lane.addRoadMarkInstance( this.roadMark ); + + SceneService.add( this.roadMark.node ); + + this.roadMarkbuilder.buildRoad( this.road ); + + } + + undo (): void { + + const index = this.lane.roadMark.findIndex( roadmark => roadmark.uuid === this.roadMark.uuid ); + + this.lane.roadMark.splice( index, 1 ); + + SceneService.remove( this.roadMark.node ); + + this.roadMarkbuilder.buildRoad( this.road ); + } + + redo (): void { + + this.execute(); + + } + +} diff --git a/src/app/core/commands/add-signal-command.ts b/src/app/core/commands/add-signal-command.ts new file mode 100644 index 00000000..de493b06 --- /dev/null +++ b/src/app/core/commands/add-signal-command.ts @@ -0,0 +1,47 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { OdBaseCommand } from './od-base-command'; +import { TvRoadSignal } from '../../modules/tv-map/models/tv-road-signal.model'; +import { ObjectSelection } from 'app/core/selection'; +import { TvMapBuilder } from '../../modules/tv-map/builders/od-builder.service'; + +export class AddSignalCommand extends OdBaseCommand { + + constructor ( public signal: TvRoadSignal ) { + super(); + } + + get road () { + + return this.openDrive.getRoadById( this.signal.roadId ); + + } + + execute (): void { + + TvMapBuilder.makeRoadSignal( this.road, this.signal ); + + this.road.addSignal( this.signal ); + + // ObjectSelection.ActiveGameObject = this.signal.GameObject; + + } + + undo (): void { + + this.signal.gameObject.parent.remove( this.signal.gameObject ); + + this.road.removeSignal( this.signal ); + + ObjectSelection.removeActive(); + + } + + redo (): void { + + this.execute(); + + } +} diff --git a/src/app/core/commands/add-width-node-command.ts b/src/app/core/commands/add-width-node-command.ts new file mode 100644 index 00000000..b59b8a44 --- /dev/null +++ b/src/app/core/commands/add-width-node-command.ts @@ -0,0 +1,74 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { BaseCommand } from './base-command'; +import { TvRoad } from 'app/modules/tv-map/models/tv-road.model'; +import { LaneWidthNode } from 'app/modules/three-js/objects/control-point'; +import { LineType, OdLaneReferenceLineBuilder } from 'app/modules/tv-map/builders/od-lane-reference-line-builder'; +import { SceneService } from '../services/scene.service'; +import { TvMapBuilder } from 'app/modules/tv-map/builders/od-builder.service'; +import { SnackBar } from 'app/services/snack-bar.service'; + +export class AddWidthNodeCommand extends BaseCommand { + + constructor ( + private node: LaneWidthNode, + private laneHelper: OdLaneReferenceLineBuilder + ) { + + super(); + + } + + execute (): void { + + this.node.updateLaneWidthValues(); + + SceneService.add( this.node ); + + this.rebuild( this.node.road ); + + } + + undo (): void { + + const index = this.node.lane.width.findIndex( laneWidth => laneWidth.uuid === this.node.laneWidth.uuid ); + + if ( index === -1 ) SnackBar.error( "Unexpected error. Not able to find this node" ); + if ( index === -1 ) return; + + this.node.lane.width.splice( index, 1 ); + + this.node.updateLaneWidthValues(); + + SceneService.remove( this.node ); + + this.rebuild( this.node.road ); + + } + + redo (): void { + + this.node.lane.addWidthRecordInstance( this.node.laneWidth ); + + this.node.updateLaneWidthValues(); + + SceneService.add( this.node ); + + this.rebuild( this.node.road ); + + } + + rebuild ( road: TvRoad ): void { + + SceneService.removeWithChildren( road.gameObject, true ); + + TvMapBuilder.buildRoad( this.openDrive.gameObject, road ); + + // not sure which is better + // this.laneHelper.redraw(); + this.laneHelper.drawRoad( road, LineType.DASHED ); + } + +} diff --git a/src/app/core/commands/base-command.ts b/src/app/core/commands/base-command.ts new file mode 100644 index 00000000..883638bf --- /dev/null +++ b/src/app/core/commands/base-command.ts @@ -0,0 +1,23 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { ICommand, ICommandCallback } from './i-command'; +import { TvMapSourceFile } from '../../modules/tv-map/services/tv-map-source-file'; + + +export abstract class BaseCommand implements ICommand { + + callbacks?: ICommandCallback; + + abstract execute (): void; + + abstract undo (): void; + + abstract redo (): void; + + get openDrive () { + return TvMapSourceFile.openDrive; + } + +} diff --git a/src/app/core/commands/delete-link-command.ts b/src/app/core/commands/delete-link-command.ts new file mode 100644 index 00000000..dbde33b6 --- /dev/null +++ b/src/app/core/commands/delete-link-command.ts @@ -0,0 +1,79 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { BaseCommand } from './base-command'; +import { SceneService } from '../services/scene.service'; +import { LanePathObject, TvJunctionLaneLink } from 'app/modules/tv-map/models/tv-junction-lane-link'; +import { TvJunctionConnection } from 'app/modules/tv-map/models/tv-junction-connection'; +import { TvRoad } from 'app/modules/tv-map/models/tv-road.model'; +import { RoadFactory } from '../factories/road-factory.service'; +import { SnackBar } from 'app/services/snack-bar.service'; + +export class DeleteLinkCommand extends BaseCommand { + + private connectingRoad: TvRoad; + + constructor ( private connection: TvJunctionConnection, private link: TvJunctionLaneLink, private lanePathObject: LanePathObject ) { + + super(); + + } + + execute (): void { + + if ( !this.link.lanePath ) SnackBar.error( "Link mesh not found" ); + if ( !this.link.lanePath ) return; + + const index = this.connection.laneLink.findIndex( item => item.lanePath.id === this.link.lanePath.id ); + + if ( index === -1 ) SnackBar.error( "Link index not found" ); + if ( index === -1 ) return; + + this.connectingRoad = this.openDrive.getRoadById( this.connection.connectingRoad ); + + this.openDrive.removeRoad( this.connectingRoad ); + + this.link.hide(); + + SceneService.remove( this.link.lanePath ); + + this.connection.removeLinkAtIndex( index ); + + const junction = this.openDrive.getJunctionById( this.connectingRoad.junction ); + + if ( !junction ) SnackBar.error( "Junction not found"); + + const outgoingRoad = this.openDrive.getRoadById( this.connectingRoad.successor.elementId ); + + junction.removeConnection( this.connection, this.lanePathObject.incomingRoad, outgoingRoad ); + } + + undo (): void { + + if ( !this.link.lanePath ) return; + + this.link.lanePath.show(); + + SceneService.add( this.link.lanePath ); + + this.openDrive.addRoadInstance( this.connectingRoad ); + + this.connection.addLaneLink( this.link ); + + const junction = this.openDrive.getJunctionById( this.connectingRoad.junction ); + + if ( !junction ) console.error( "junction not found with id ", this.connectingRoad.junction ); + + junction.addConnection( this.connection ); + + RoadFactory.rebuildRoad( this.connectingRoad ); + } + + redo (): void { + + this.execute(); + + } + +} \ No newline at end of file diff --git a/src/app/core/commands/i-command.ts b/src/app/core/commands/i-command.ts new file mode 100644 index 00000000..81a2e17c --- /dev/null +++ b/src/app/core/commands/i-command.ts @@ -0,0 +1,25 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +export interface ICommand { + + callbacks?: ICommandCallback; + + execute (): void; + + undo (): void; + + redo (): void; + +} + +export interface ICommandCallback { + + onExecute (): void; + + onUndo (): void; + + onRedo (): void; + +} \ No newline at end of file diff --git a/src/app/core/commands/join-road-node-command.ts b/src/app/core/commands/join-road-node-command.ts new file mode 100644 index 00000000..8e556a94 --- /dev/null +++ b/src/app/core/commands/join-road-node-command.ts @@ -0,0 +1,70 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { OdBaseCommand } from './od-base-command'; +import { TvRoad } from '../../modules/tv-map/models/tv-road.model'; +import { AppInspector } from 'app/core/inspector'; +import { RoadInspector } from '../../views/inspectors/road-inspector/road-inspector.component'; +import { RoadFactory } from 'app/core/factories/road-factory.service'; +import { RoadNode } from 'app/modules/three-js/objects/road-node'; + +export class JoinRoadNodeCommand extends OdBaseCommand { + + private joiningRoad: TvRoad; + private lastInspector; + private lastInspectorData; + + constructor ( + private firstNode: RoadNode, + private secondNode: RoadNode + ) { + + super(); + + this.lastInspector = AppInspector.currentInspector; + this.lastInspectorData = AppInspector.currentInspectorData; + + } + + get firstRoad () { return this.openDrive.getRoadById( this.firstNode.roadId ) } + + get secondRoad () { return this.openDrive.getRoadById( this.secondNode.roadId ) } + + execute (): void { + + this.joiningRoad = RoadFactory.joinRoadNodes( this.firstRoad, this.firstNode, this.secondRoad, this.secondNode ); + + AppInspector.setInspector( RoadInspector, { road: this.joiningRoad, node: this.secondNode } ); + + } + + undo (): void { + + // remove from opendrive + // remove and clear the splines points + // remove the entire game object + + this.openDrive.roads.delete( this.joiningRoad.id ); + + this.openDrive.gameObject.remove( this.joiningRoad.gameObject ); + + RoadFactory.removeRoadConnections( this.firstRoad, this.joiningRoad ); + RoadFactory.removeRoadConnections( this.secondRoad, this.joiningRoad ); + + AppInspector.setInspector( this.lastInspector, this.lastInspectorData ); + } + + redo (): void { + + this.openDrive.roads.set( this.joiningRoad.id, this.joiningRoad ); + + this.openDrive.gameObject.add( this.joiningRoad.gameObject ); + + RoadFactory.makeRoadConnections( this.firstRoad, this.firstNode, this.secondRoad, this.secondNode, this.joiningRoad ); + + AppInspector.setInspector( RoadInspector, { road: this.joiningRoad, node: this.secondNode } ); + } + + +} \ No newline at end of file diff --git a/src/app/core/commands/multi-cmds-command.ts b/src/app/core/commands/multi-cmds-command.ts new file mode 100644 index 00000000..aa74222e --- /dev/null +++ b/src/app/core/commands/multi-cmds-command.ts @@ -0,0 +1,32 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { BaseCommand } from './base-command'; +import { ICommand } from './i-command'; + +export class MultiCmdsCommand extends BaseCommand { + + constructor ( private commands: ICommand[] ) { + super(); + } + + execute (): void { + + this.commands.forEach( command => command.execute() ); + + } + + undo (): void { + + this.commands.forEach( command => command.undo() ); + + } + + redo (): void { + + this.commands.forEach( command => command.redo() ); + + } + +} \ No newline at end of file diff --git a/src/app/core/commands/od-base-command.ts b/src/app/core/commands/od-base-command.ts new file mode 100644 index 00000000..60498646 --- /dev/null +++ b/src/app/core/commands/od-base-command.ts @@ -0,0 +1,21 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { BaseCommand } from 'app/core/commands/base-command'; +import { TvMapSourceFile } from '../../modules/tv-map/services/tv-map-source-file'; +import { TvRoad } from '../../modules/tv-map/models/tv-road.model'; + +export abstract class OdBaseCommand extends BaseCommand { + + abstract execute (): void; + + abstract undo (): void; + + abstract redo (): void; + + protected getRoad ( roadId: number ): TvRoad { + return TvMapSourceFile.openDrive.getRoadById( roadId ); + } + +} diff --git a/src/app/core/commands/remove-lane-command.ts b/src/app/core/commands/remove-lane-command.ts new file mode 100644 index 00000000..c8a4495d --- /dev/null +++ b/src/app/core/commands/remove-lane-command.ts @@ -0,0 +1,40 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { BaseCommand } from './base-command'; +import { TvLaneSection } from '../../modules/tv-map/models/tv-lane-section'; +import { TvLane } from '../../modules/tv-map/models/tv-lane'; +import { TvMapSourceFile } from '../../modules/tv-map/services/tv-map-source-file'; + +export class RemoveLaneCommand extends BaseCommand { + + constructor ( private laneSection: TvLaneSection, private lane: TvLane ) { + + super(); + + } + + execute (): void { + + this.laneSection.removeLaneById( this.lane.id ); + + TvMapSourceFile.roadNetworkChanged.emit( this.openDrive ); + + } + + undo (): void { + + this.laneSection.addLaneInstance( this.lane, true ); + + TvMapSourceFile.roadNetworkChanged.emit( this.openDrive ); + + } + + redo (): void { + + this.execute(); + + } + +} \ No newline at end of file diff --git a/src/app/core/commands/remove-roadmark-command.ts b/src/app/core/commands/remove-roadmark-command.ts new file mode 100644 index 00000000..4336876b --- /dev/null +++ b/src/app/core/commands/remove-roadmark-command.ts @@ -0,0 +1,71 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { BaseCommand } from './base-command'; +import { TvLaneRoadMark } from '../../modules/tv-map/models/tv-lane-road-mark'; +import { TvLane } from '../../modules/tv-map/models/tv-lane'; +import { OdRoadMarkBuilder } from '../../modules/tv-map/builders/od-road-mark-builder'; + +export class RemoveRoadmarkCommand extends BaseCommand { + + private index: number; + + private roadMarkBuilder = new OdRoadMarkBuilder( null ); + + constructor ( private roadmark: TvLaneRoadMark, private lane: TvLane ) { + + super(); + + } + + execute (): void { + + this.lane.gameObject.remove( this.roadmark.gameObject ); + + this.removeFromLane(); + + this.rebuild(); + } + + undo (): void { + + this.lane.gameObject.add( this.roadmark.gameObject ); + + this.lane.addRoadMarkInstance( this.roadmark ); + + this.rebuild(); + } + + redo (): void { + + this.execute(); + + } + + private rebuild () { + + this.openDrive.roads.forEach( road => { + + this.roadMarkBuilder.buildRoad( road ); + + } ); + + } + + private removeFromLane () { + + this.lane.getRoadMarks().forEach( ( ( value, i ) => { + + if ( value.sOffset === this.roadmark.sOffset ) { + + this.index = i; + + } + + } ) ); + + this.lane.getRoadMarks().splice( this.index, 1 ); + + } +} diff --git a/src/app/core/commands/remove-signal-command.ts b/src/app/core/commands/remove-signal-command.ts new file mode 100644 index 00000000..34e31da1 --- /dev/null +++ b/src/app/core/commands/remove-signal-command.ts @@ -0,0 +1,43 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { OdBaseCommand } from './od-base-command'; +import { TvRoadSignal } from '../../modules/tv-map/models/tv-road-signal.model'; +import { TvMapBuilder } from '../../modules/tv-map/builders/od-builder.service'; +import { ObjectSelection } from 'app/core/selection'; +import { AppInspector } from '../inspector'; + +export class RemoveSignalCommand extends OdBaseCommand { + + constructor ( public signal: TvRoadSignal ) { + super(); + } + + execute (): void { + + this.signal.gameObject.parent.remove( this.signal.gameObject ); + + this.getRoad( this.signal.roadId ).removeSignal( this.signal ); + + ObjectSelection.removeActive(); + + AppInspector.clear(); + } + + undo (): void { + + TvMapBuilder.makeRoadSignal( this.getRoad( this.signal.roadId ), this.signal ); + + this.getRoad( this.signal.roadId ).addSignal( this.signal ); + + ObjectSelection.ActiveGameObject = this.signal.gameObject; + + } + + redo (): void { + + this.execute(); + + } +} diff --git a/src/app/core/commands/remove-width-node-command.ts b/src/app/core/commands/remove-width-node-command.ts new file mode 100644 index 00000000..15b3f9d5 --- /dev/null +++ b/src/app/core/commands/remove-width-node-command.ts @@ -0,0 +1,79 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { BaseCommand } from './base-command'; +import { TvRoad } from 'app/modules/tv-map/models/tv-road.model'; +import { LaneWidthNode } from 'app/modules/three-js/objects/control-point'; +import { LineType, OdLaneReferenceLineBuilder } from 'app/modules/tv-map/builders/od-lane-reference-line-builder'; +import { SceneService } from '../services/scene.service'; +import { TvMapBuilder } from 'app/modules/tv-map/builders/od-builder.service'; +import { SnackBar } from 'app/services/snack-bar.service'; +import { SetInspectorCommand } from './set-inspector-command'; +import { LaneWidthInspector } from 'app/views/inspectors/lane-width-inspector/lane-width-inspector.component'; + +export class RemoveWidthNodeCommand extends BaseCommand { + + constructor ( + private node: LaneWidthNode, + private laneHelper?: OdLaneReferenceLineBuilder + ) { + + super(); + + if ( !laneHelper ) { + + this.laneHelper = new OdLaneReferenceLineBuilder(); + + } + + } + + execute (): void { + + const index = this.node.lane.width.findIndex( laneWidth => laneWidth.uuid === this.node.laneWidth.uuid ); + + if ( index === -1 ) SnackBar.error( "Unexpected error. Not able to find this node" ); + if ( index === -1 ) return; + + this.node.lane.width.splice( index, 1 ); + + this.node.updateLaneWidthValues(); + + SceneService.remove( this.node ); + + this.rebuild( this.node.road ); + + ( new SetInspectorCommand( LaneWidthInspector, { lane: this.node.lane } ) ).execute(); + } + + undo (): void { + + this.node.lane.addWidthRecordInstance( this.node.laneWidth ); + + this.node.updateLaneWidthValues(); + + SceneService.add( this.node ); + + this.rebuild( this.node.road ); + + ( new SetInspectorCommand( LaneWidthInspector, { lane: this.node.lane, node: this.node } ) ).execute(); + + } + + redo (): void { + + this.execute(); + + } + + rebuild ( road: TvRoad ): void { + + SceneService.removeWithChildren( road.gameObject, true ); + + TvMapBuilder.buildRoad( this.openDrive.gameObject, road ); + + this.laneHelper.drawRoad( road, LineType.DASHED, true ); + } + +} diff --git a/src/app/core/commands/set-inspector-command.ts b/src/app/core/commands/set-inspector-command.ts new file mode 100644 index 00000000..5b8e02be --- /dev/null +++ b/src/app/core/commands/set-inspector-command.ts @@ -0,0 +1,41 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { BaseCommand } from './base-command'; +import { AppInspector } from '../inspector'; +import { Type } from '@angular/core'; +import { IComponent } from '../game-object'; + +export class SetInspectorCommand extends BaseCommand { + + private oldInspector: Type; + private oldInspectorData: any; + + constructor ( public newInspector: Type, public newInspectorData: any ) { + + super(); + + this.oldInspector = AppInspector.currentInspector; + + this.oldInspectorData = AppInspector.currentInspectorData; + } + + execute (): void { + + AppInspector.setInspector( this.newInspector, this.newInspectorData ); + + } + + undo (): void { + + AppInspector.setInspector( this.oldInspector, this.oldInspectorData ); + + } + + redo (): void { + + this.execute(); + + } +} diff --git a/src/app/core/commands/set-lane-property-command.ts b/src/app/core/commands/set-lane-property-command.ts new file mode 100644 index 00000000..a14dd937 --- /dev/null +++ b/src/app/core/commands/set-lane-property-command.ts @@ -0,0 +1,38 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { BaseCommand } from './base-command'; +import { TvLane } from '../../modules/tv-map/models/tv-lane'; + +export class SetLanePropertyCommand extends BaseCommand { + + private readonly oldValue: any; + + constructor ( private lane: TvLane, private attribute: any, private newValue: any ) { + + super(); + + this.oldValue = lane[ attribute ]; + } + + execute (): void { + + this.lane[ this.attribute ] = this.newValue; + + } + + undo (): void { + + this.lane[ this.attribute ] = this.oldValue; + + } + + redo (): void { + + this.execute(); + + } + + +} \ No newline at end of file diff --git a/src/app/core/commands/set-roadmark-value-command.ts b/src/app/core/commands/set-roadmark-value-command.ts new file mode 100644 index 00000000..123ecb22 --- /dev/null +++ b/src/app/core/commands/set-roadmark-value-command.ts @@ -0,0 +1,57 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { BaseCommand } from './base-command'; +import { TvLaneRoadMark } from '../../modules/tv-map/models/tv-lane-road-mark'; +import { OdRoadMarkBuilder } from '../../modules/tv-map/builders/od-road-mark-builder'; + +export class SetRoadmarkValueCommand extends BaseCommand { + + private readonly oldValue: any; + + private roadMarkBuilder = new OdRoadMarkBuilder(); + + constructor ( + private roadmark: TvLaneRoadMark, + private attribute: any, + private newValue: any, + ) { + + super(); + + this.oldValue = this.roadmark[ this.attribute ]; + + } + + execute (): void { + + this.roadmark[ this.attribute ] = this.newValue; + + this.rebuild(); + + } + + undo (): void { + + this.roadmark[ this.attribute ] = this.oldValue; + + this.rebuild(); + + } + + redo (): void { + + this.execute(); + + } + + private rebuild () { + + this.openDrive.roads.forEach( road => { + + this.roadMarkBuilder.buildRoad( road ); + + } ); + } +} diff --git a/src/app/core/commands/set-tool-command.ts b/src/app/core/commands/set-tool-command.ts new file mode 100644 index 00000000..c1ce7303 --- /dev/null +++ b/src/app/core/commands/set-tool-command.ts @@ -0,0 +1,39 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { BaseCommand } from './base-command'; +import { ToolManager } from 'app/core/tools/tool-manager'; +import { BaseTool } from 'app/core/tools/base-tool'; + +export class SetToolCommand extends BaseCommand { + + private oldTool: BaseTool; + + constructor ( private newTool: BaseTool ) { + + super(); + + this.oldTool = ToolManager.currentTool; + + } + + execute (): void { + + ToolManager.currentTool = this.newTool; + + } + + undo (): void { + + ToolManager.currentTool = this.oldTool; + + } + + redo (): void { + + ToolManager.currentTool = this.newTool; + + } + +} \ No newline at end of file diff --git a/src/app/core/commands/show-lane-marking-command.ts b/src/app/core/commands/show-lane-marking-command.ts new file mode 100644 index 00000000..5c6cde1a --- /dev/null +++ b/src/app/core/commands/show-lane-marking-command.ts @@ -0,0 +1,123 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { BaseCommand } from './base-command'; +import { TvLane } from 'app/modules/tv-map/models/tv-lane'; +import { OdLaneReferenceLineBuilder } from 'app/modules/tv-map/builders/od-lane-reference-line-builder'; +import { NodeFactoryService } from '../factories/node-factory.service'; +import { SceneService } from '../services/scene.service'; +import { TvRoad } from 'app/modules/tv-map/models/tv-road.model'; +import { SnackBar } from 'app/services/snack-bar.service'; + +export class ShowLaneMarkingCommand extends BaseCommand { + + private road: TvRoad; + + private oldRoad: TvRoad; + + constructor ( private lane: TvLane, private oldLane: TvLane, private laneHelper: OdLaneReferenceLineBuilder ) { + + super(); + + if ( lane ) { + + this.road = this.openDrive.getRoadById( this.lane.roadId ); + + } + + if ( oldLane ) { + + this.oldRoad = this.openDrive.getRoadById( this.oldLane.roadId ); + + } + } + + execute (): void { + + if ( this.oldRoad ) { + + this.hideNodes( this.oldRoad ); + + } + + if ( this.road ) { + + this.showNodes( this.road ); + + } + } + + undo (): void { + + if ( this.road ) { + + this.hideNodes( this.road ); + + } + + if ( this.oldRoad ) { + + this.showNodes( this.oldRoad ); + + } + } + + redo (): void { + + this.execute(); + + } + + private showNodes ( road: TvRoad ) { + + if ( road.isJunction ) SnackBar.error( "LaneMark Editing on junction roads is currently not supported" ); + + if ( road.isJunction ) return; + + road.laneSections.forEach( laneSection => { + + laneSection.lanes.forEach( lane => { + + lane.getRoadMarks().forEach( roadmark => { + + if ( roadmark.node ) { + + roadmark.node.visible = true; + + } else { + + roadmark.node = NodeFactoryService.createRoadMarkNode( lane, roadmark ); + + SceneService.add( roadmark.node ); + + } + + } ) + + } ) + + } ); + + this.laneHelper.drawRoad( road ); + } + + private hideNodes ( road: TvRoad ) { + + road.laneSections.forEach( laneSection => { + + laneSection.lanes.forEach( lane => { + + lane.getRoadMarks().forEach( roadmark => { + + if ( roadmark.node ) roadmark.node.visible = false; + + } ); + + } ); + + } ); + + this.laneHelper.clear(); + } +} \ No newline at end of file diff --git a/src/app/core/commands/update-lane-offset-distance-command.ts b/src/app/core/commands/update-lane-offset-distance-command.ts new file mode 100644 index 00000000..c962bbda --- /dev/null +++ b/src/app/core/commands/update-lane-offset-distance-command.ts @@ -0,0 +1,69 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { BaseCommand } from './base-command'; +import { LaneWidthNode, LaneOffsetNode } from 'app/modules/three-js/objects/control-point'; +import { NodeFactoryService } from '../factories/node-factory.service'; +import { SceneService } from '../services/scene.service'; +import { OdLaneReferenceLineBuilder, LineType } from 'app/modules/tv-map/builders/od-lane-reference-line-builder'; +import { TvRoad } from 'app/modules/tv-map/models/tv-road.model'; +import { TvMapBuilder } from 'app/modules/tv-map/builders/od-builder.service'; + +export class UpdateLaneOffsetDistanceCommand extends BaseCommand { + + constructor ( + private node: LaneOffsetNode, + private newDistance: number, + private oldDistance?: number, + private laneHelper?: OdLaneReferenceLineBuilder + ) { + + super(); + + if ( !this.oldDistance ) { + + this.oldDistance = this.node.laneOffset.s; + + } + + } + + execute (): void { + + this.node.laneOffset.s = this.newDistance; + + NodeFactoryService.updateLaneOffsetNode( this.node ); + + this.rebuild( this.node.road ); + + + } + + undo (): void { + + this.node.laneOffset.s = this.oldDistance; + + NodeFactoryService.updateLaneOffsetNode( this.node ); + + this.rebuild( this.node.road ); + + } + + redo (): void { + + this.execute(); + + } + + rebuild ( road: TvRoad ): void { + + SceneService.removeWithChildren( road.gameObject, true ); + + TvMapBuilder.buildRoad( this.openDrive.gameObject, road ); + + this.laneHelper.drawRoad( road, LineType.DASHED, true ); + + } + +} diff --git a/src/app/core/commands/update-lane-offset-value-command.ts b/src/app/core/commands/update-lane-offset-value-command.ts new file mode 100644 index 00000000..7318e86e --- /dev/null +++ b/src/app/core/commands/update-lane-offset-value-command.ts @@ -0,0 +1,64 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { BaseCommand } from './base-command'; +import { LaneOffsetNode } from 'app/modules/three-js/objects/control-point'; +import { SceneService } from '../services/scene.service'; +import { OdLaneReferenceLineBuilder, LineType } from 'app/modules/tv-map/builders/od-lane-reference-line-builder'; +import { TvRoad } from 'app/modules/tv-map/models/tv-road.model'; +import { TvMapBuilder } from 'app/modules/tv-map/builders/od-builder.service'; + +export class UpdateLaneOffsetValueCommand extends BaseCommand { + + constructor ( + private node: LaneOffsetNode, + private newOffset: number, + private oldOffset?: number, + private laneHelper?: OdLaneReferenceLineBuilder + ) { + + super(); + + if ( !this.oldOffset ) { + + this.oldOffset = this.node.laneOffset.a; + + } + + } + + execute (): void { + + this.node.laneOffset.a = this.newOffset; + + this.rebuild( this.node.road ); + + + } + + undo (): void { + + this.node.laneOffset.a = this.oldOffset; + + this.rebuild( this.node.road ); + + } + + redo (): void { + + this.execute(); + + } + + rebuild ( road: TvRoad ): void { + + SceneService.removeWithChildren( road.gameObject, true ); + + TvMapBuilder.buildRoad( this.openDrive.gameObject, road ); + + this.laneHelper.drawRoad( road, LineType.DASHED, true ); + + } + +} diff --git a/src/app/core/commands/update-road-point-command.ts b/src/app/core/commands/update-road-point-command.ts new file mode 100644 index 00000000..a8997329 --- /dev/null +++ b/src/app/core/commands/update-road-point-command.ts @@ -0,0 +1,105 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { OdBaseCommand } from './od-base-command'; +import { TvRoad } from '../../modules/tv-map/models/tv-road.model'; +import { RoadFactory } from 'app/core/factories/road-factory.service'; +import { Vector3 } from 'three'; +import { RoadControlPoint } from 'app/modules/three-js/objects/road-control-point'; + +export class UpdateRoadPointCommand extends OdBaseCommand { + + constructor ( + private road: TvRoad, + private point: RoadControlPoint, + private newPosition: Vector3, + private oldPosition: Vector3 + ) { + + super(); + + this.newPosition = this.newPosition.clone(); + + this.oldPosition = this.oldPosition.clone(); + } + + execute (): void { + + // chanhe position of point + this.point.copyPosition( this.newPosition ); + + // update spline + this.road.spline.update(); + + // update geometry + RoadFactory.updateGeometry( this.road ); + + // build road + RoadFactory.rebuildRoad( this.road ); + + if ( this.road.successor && this.road.successor.elementType !== "junction" ) { + + const successor = this.openDrive.getRoadById( this.road.successor.elementId ); + + successor.updateGeometryFromSpline(); + + RoadFactory.rebuildRoad( successor ); + + } + + if ( this.road.predecessor && this.road.predecessor.elementType !== "junction" ) { + + const predecessor = this.openDrive.getRoadById( this.road.predecessor.elementId ); + + predecessor.updateGeometryFromSpline(); + + RoadFactory.rebuildRoad( predecessor ); + + } + + } + + undo (): void { + + // chanhe position of point + this.point.copyPosition( this.oldPosition ); + + // update spline + this.road.spline.update(); + + // update geometry + RoadFactory.updateGeometry( this.road ); + + // build road + RoadFactory.rebuildRoad( this.road ); + + if ( this.road.successor && this.road.successor.elementType !== "junction" ) { + + const successor = this.openDrive.getRoadById( this.road.successor.elementId ); + + successor.updateGeometryFromSpline(); + + RoadFactory.rebuildRoad( successor ); + + } + + if ( this.road.predecessor && this.road.predecessor.elementType !== "junction" ) { + + const predecessor = this.openDrive.getRoadById( this.road.predecessor.elementId ); + + predecessor.updateGeometryFromSpline(); + + RoadFactory.rebuildRoad( predecessor ); + + } + + } + + redo (): void { + + this.execute(); + + } + +} \ No newline at end of file diff --git a/src/app/core/commands/update-roadmark-node.ts b/src/app/core/commands/update-roadmark-node.ts new file mode 100644 index 00000000..4e6b8627 --- /dev/null +++ b/src/app/core/commands/update-roadmark-node.ts @@ -0,0 +1,46 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { BaseCommand } from './base-command'; +import { OdRoadMarkBuilder } from 'app/modules/tv-map/builders/od-road-mark-builder'; +import { Vector3 } from 'three'; +import { LaneRoadMarkNode } from 'app/modules/three-js/objects/control-point'; +import { NodeFactoryService } from '../factories/node-factory.service'; + +export class UpdateRoadmarkNodeCommand extends BaseCommand { + + constructor ( + private node: LaneRoadMarkNode, + private newPosition: Vector3, + private oldPosition: Vector3, + private roadMarkbuilder: OdRoadMarkBuilder + ) { + + super(); + + } + + execute (): void { + + NodeFactoryService.updateRoadMarkNodeByPosition( this.node, this.newPosition ); + + this.roadMarkbuilder.buildRoad( this.openDrive.getRoadById( this.node.lane.roadId ) ); + + } + + undo (): void { + + NodeFactoryService.updateRoadMarkNodeByPosition( this.node, this.oldPosition ); + + this.roadMarkbuilder.buildRoad( this.openDrive.getRoadById( this.node.lane.roadId ) ); + + } + + redo (): void { + + this.execute(); + + } + +} diff --git a/src/app/core/commands/update-width-node-distance-command.ts b/src/app/core/commands/update-width-node-distance-command.ts new file mode 100644 index 00000000..4b784d74 --- /dev/null +++ b/src/app/core/commands/update-width-node-distance-command.ts @@ -0,0 +1,73 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { BaseCommand } from './base-command'; +import { TvRoad } from 'app/modules/tv-map/models/tv-road.model'; +import { LaneWidthNode } from 'app/modules/three-js/objects/control-point'; +import { NodeFactoryService } from '../factories/node-factory.service'; +import { LineType, OdLaneReferenceLineBuilder } from 'app/modules/tv-map/builders/od-lane-reference-line-builder'; +import { SceneService } from '../services/scene.service'; +import { TvMapBuilder } from 'app/modules/tv-map/builders/od-builder.service'; + +export class UpdateWidthNodeDistanceCommand extends BaseCommand { + + constructor ( + private node: LaneWidthNode, + private newDistance: number, + private oldDistance?: number, + private laneHelper?: OdLaneReferenceLineBuilder + ) { + + super(); + + if ( !this.oldDistance ) { + + this.oldDistance = this.node.laneWidth.s; + + } + + } + + execute (): void { + + this.node.laneWidth.s = this.node.s = this.newDistance; + + this.node.updateLaneWidthValues(); + + NodeFactoryService.updateLaneWidthNodeLine( this.node ); + + this.rebuild( this.node.road ); + + + } + + undo (): void { + + this.node.laneWidth.s = this.node.s = this.oldDistance; + + this.node.updateLaneWidthValues(); + + NodeFactoryService.updateLaneWidthNodeLine( this.node ); + + this.rebuild( this.node.road ); + + } + + redo (): void { + + this.execute(); + + } + + rebuild ( road: TvRoad ): void { + + SceneService.removeWithChildren( road.gameObject, true ); + + TvMapBuilder.buildRoad( this.openDrive.gameObject, road ); + + this.laneHelper.drawRoad( road, LineType.DASHED, true ); + + } + +} diff --git a/src/app/core/commands/update-width-node-position-command.ts b/src/app/core/commands/update-width-node-position-command.ts new file mode 100644 index 00000000..69acd881 --- /dev/null +++ b/src/app/core/commands/update-width-node-position-command.ts @@ -0,0 +1,62 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { BaseCommand } from './base-command'; +import { TvRoad } from 'app/modules/tv-map/models/tv-road.model'; +import { Vector3 } from 'three'; +import { LaneWidthNode } from 'app/modules/three-js/objects/control-point'; +import { NodeFactoryService } from '../factories/node-factory.service'; +import { LineType, OdLaneReferenceLineBuilder } from 'app/modules/tv-map/builders/od-lane-reference-line-builder'; +import { SceneService } from '../services/scene.service'; +import { TvMapBuilder } from 'app/modules/tv-map/builders/od-builder.service'; + +export class UpdateWidthNodePositionCommand extends BaseCommand { + + constructor ( + private node: LaneWidthNode, + private newPosition: Vector3, + private oldPosition: Vector3, + private laneHelper: OdLaneReferenceLineBuilder + ) { + + super(); + + } + + execute (): void { + + NodeFactoryService.updateLaneWidthNode( this.node, this.newPosition ); + + this.node.updateLaneWidthValues(); + + this.rebuild( this.openDrive.getRoadById( this.node.roadId ) ); + } + + undo (): void { + + NodeFactoryService.updateLaneWidthNode( this.node, this.oldPosition ); + + this.node.updateLaneWidthValues(); + + this.rebuild( this.openDrive.getRoadById( this.node.roadId ) ); + + } + + redo (): void { + + this.execute(); + + } + + rebuild ( road: TvRoad ): void { + + SceneService.removeWithChildren( road.gameObject, true ); + + TvMapBuilder.buildRoad( this.openDrive.gameObject, road ); + + this.laneHelper.drawRoad( road, LineType.DASHED, true ); + + } + +} diff --git a/src/app/core/commands/update-width-node-value-command.ts b/src/app/core/commands/update-width-node-value-command.ts new file mode 100644 index 00000000..b985d5b1 --- /dev/null +++ b/src/app/core/commands/update-width-node-value-command.ts @@ -0,0 +1,73 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { BaseCommand } from './base-command'; +import { TvRoad } from 'app/modules/tv-map/models/tv-road.model'; +import { LaneWidthNode } from 'app/modules/three-js/objects/control-point'; +import { NodeFactoryService } from '../factories/node-factory.service'; +import { LineType, OdLaneReferenceLineBuilder } from 'app/modules/tv-map/builders/od-lane-reference-line-builder'; +import { SceneService } from '../services/scene.service'; +import { TvMapBuilder } from 'app/modules/tv-map/builders/od-builder.service'; + +export class UpdateWidthNodeValueCommand extends BaseCommand { + + + constructor ( + private node: LaneWidthNode, + private newWidth: number, + private oldWidth?: number, + private laneHelper?: OdLaneReferenceLineBuilder + ) { + + super(); + + if ( !this.oldWidth ) { + + this.oldWidth = this.node.laneWidth.a; + + } + + } + + execute (): void { + + this.node.laneWidth.a = this.newWidth; + + this.node.updateLaneWidthValues(); + + NodeFactoryService.updateLaneWidthNodeLine( this.node ); + + this.rebuild( this.node.road ); + + } + + undo (): void { + + this.node.laneWidth.a = this.oldWidth; + + this.node.updateLaneWidthValues(); + + NodeFactoryService.updateLaneWidthNodeLine( this.node ); + + this.rebuild( this.node.road ); + + } + + redo (): void { + + this.execute(); + + } + + rebuild ( road: TvRoad ): void { + + SceneService.removeWithChildren( road.gameObject, true ); + + TvMapBuilder.buildRoad( this.openDrive.gameObject, road ); + + this.laneHelper.drawRoad( road, LineType.DASHED, true ); + + } + +} diff --git a/src/app/core/components/abstract-field.component.ts b/src/app/core/components/abstract-field.component.ts new file mode 100644 index 00000000..5178790f --- /dev/null +++ b/src/app/core/components/abstract-field.component.ts @@ -0,0 +1,72 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { EventEmitter, Input, Output } from '@angular/core'; + +export abstract class AbstractFieldComponent { + + @Input() disabled: boolean = false; + + @Input() abstract value: any; + + @Input() label: string = ''; + + /** + * @deprecated use changed event instead + */ + @Output() valueChanged = new EventEmitter(); + + @Output() changed = new EventEmitter(); + + onModelChanged ( $event: any ) { + + this.value = $event; + + this.valueChanged.emit( this.value ); + + this.changed.emit( this.value ); + + } + + get isParameter (): boolean { + + return typeof this.value === 'string' && this.value.indexOf( '$' ) !== -1; + + } + + get fieldValue () { + + if ( this.isParameter ) { + + // return OscSourceFile.scenario.findParameter( this.value ).value; + + } + + return this.value; + } + + set fieldValue ( value ) { + + if ( this.isParameter ) { + + // OscSourceFile.scenario.findParameter( this.value ).value = value; + + } else { + + this.value = value; + + } + + } + + onFieldValueChanged ( $event: any ) { + + this.fieldValue = $event; + + if ( !this.isParameter ) this.valueChanged.emit( this.fieldValue ); + + if ( !this.isParameter ) this.changed.emit( this.fieldValue ); + + } +} diff --git a/src/app/core/components/base-inspector.component.ts b/src/app/core/components/base-inspector.component.ts new file mode 100644 index 00000000..c81005d5 --- /dev/null +++ b/src/app/core/components/base-inspector.component.ts @@ -0,0 +1,33 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { TvMapSourceFile } from '../../modules/tv-map/services/tv-map-source-file'; +import { HostListener } from '@angular/core'; + +enum KEY_CODE { + DELETE = 46 +} + +export abstract class BaseInspector { + + get openDrive () { + return TvMapSourceFile.openDrive; + } + + @HostListener( 'window:keydown', [ '$event' ] ) + baseOnKeyDown ( event: KeyboardEvent ) { + + if ( event.keyCode === KEY_CODE.DELETE ) { + + this.onDelete(); + + } + + } + + + onDelete () { + + } +} \ No newline at end of file diff --git a/src/app/core/components/mono-behaviour.ts b/src/app/core/components/mono-behaviour.ts new file mode 100644 index 00000000..f6c550d9 --- /dev/null +++ b/src/app/core/components/mono-behaviour.ts @@ -0,0 +1,130 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { AppService } from '../services/app.service'; +import { BaseEventData, PointerEventData, PointerMoveData } from 'app/events/pointer-event-data'; +import { Subscription } from 'rxjs'; + +export class MonoBehaviour { + + subscribed: boolean = false; + + pointerClickedSubscriber: Subscription; + pointerMovedSubscriber: Subscription; + pointerEnterSubscriber: Subscription; + pointerExitSubscriber: Subscription; + pointerUpSubscriber: Subscription; + pointerDownSubscriber: Subscription; + pointerLeaveSubscriber: Subscription; + pointerOutSubscriber: Subscription; + beginDragSubscriber: Subscription; + endDragSubscriber: Subscription; + dragSubscriber: Subscription; + dropSubscriber: Subscription; + selectSubscriber: Subscription; + deSelectSubscriber: Subscription; + + + constructor () { + + this.subscribeToEvents(); + + } + + subscribeToEvents () { + + if ( this.subscribed ) return; + + if ( !AppService.eventSystem ) return; + + this.pointerClickedSubscriber = AppService.eventSystem.pointerClicked.subscribe( e => this.onPointerClicked( e ) ); + this.pointerMovedSubscriber = AppService.eventSystem.pointerMoved.subscribe( e => this.onPointerMoved( e ) ); + this.pointerEnterSubscriber = AppService.eventSystem.pointerEnter.subscribe( e => this.onPointerEnter( e ) ); + this.pointerExitSubscriber = AppService.eventSystem.pointerExit.subscribe( e => this.onPointerExit( e ) ); + this.pointerUpSubscriber = AppService.eventSystem.pointerUp.subscribe( e => this.onPointerUp( e ) ); + this.pointerDownSubscriber = AppService.eventSystem.pointerDown.subscribe( e => this.onPointerDown( e ) ); + this.pointerLeaveSubscriber = AppService.eventSystem.pointerLeave.subscribe( e => this.onPointerLeave( e ) ); + this.pointerOutSubscriber = AppService.eventSystem.pointerOut.subscribe( e => this.onPointerOut( e ) ); + this.beginDragSubscriber = AppService.eventSystem.beginDrag.subscribe( e => this.onBeginDrag( e ) ); + this.endDragSubscriber = AppService.eventSystem.endDrag.subscribe( e => this.onEndDrag( e ) ); + this.dragSubscriber = AppService.eventSystem.drag.subscribe( e => this.onDrag( e ) ); + this.dropSubscriber = AppService.eventSystem.drop.subscribe( e => this.onDrop( e ) ); + this.selectSubscriber = AppService.eventSystem.select.subscribe( e => this.onSelect( e ) ); + this.deSelectSubscriber = AppService.eventSystem.deSelect.subscribe( e => this.onDeSelect( e ) ); + + this.subscribed = true; + } + + unsubscribeToEvents () { + + if ( !this.subscribed ) return; + + this.pointerClickedSubscriber.unsubscribe(); + this.pointerMovedSubscriber.unsubscribe(); + this.pointerEnterSubscriber.unsubscribe(); + this.pointerExitSubscriber.unsubscribe(); + this.pointerUpSubscriber.unsubscribe(); + this.pointerDownSubscriber.unsubscribe(); + this.pointerLeaveSubscriber.unsubscribe(); + this.pointerOutSubscriber.unsubscribe(); + this.beginDragSubscriber.unsubscribe(); + this.endDragSubscriber.unsubscribe(); + this.dragSubscriber.unsubscribe(); + this.dropSubscriber.unsubscribe(); + this.selectSubscriber.unsubscribe(); + this.deSelectSubscriber.unsubscribe(); + + this.subscribed = false; + } + + onPointerClicked ( pointerEventData: PointerEventData ): void { /*Debug.log( 'clicked' )*/ + } + + onPointerMoved ( pointerEventData: PointerMoveData ): void { /*Debug.log( 'moved' )*/ + } + + onPointerEnter ( pointerEventData: PointerEventData ): void { /*Debug.log( 'enter' )*/ + } + + onPointerExit ( pointerEventData: PointerEventData ): void { /*Debug.log( 'exit' )*/ + } + + onPointerDown ( pointerEventData: PointerEventData ): void { /*Debug.log( 'down' )*/ + } + + onPointerUp ( pointerEventData: PointerEventData ): void { /*Debug.log( 'up' )*/ + } + + onPointerOut ( pointerEventData: PointerEventData ): void { /*Debug.log( 'out' )*/ + } + + onPointerLeave ( pointerEventData: PointerEventData ): void { /*Debug.log( 'leave' )*/ + } + + onBeginDrag ( pointerEventData: PointerEventData ): void { /*Debug.log( 'begin-drag' )*/ + } + + onEndDrag ( pointerEventData: PointerEventData ): void { /*Debug.log( 'end-drag' )*/ + } + + onDrag ( pointerEventData: PointerEventData ): void { /*Debug.log( 'drag' )*/ + } + + onDrop ( pointerEventData: PointerEventData ): void { /*Debug.log( 'drop' )*/ + } + + onDeSelect ( baseEventData: BaseEventData ): any { + + // if ( baseEventData.object != null ) Debug.log( 'deselect', baseEventData.object.id ); + + } + + onSelect ( baseEventData: BaseEventData ): any { + + // Debug.log( 'selected', baseEventData.object.id ); + + } + + +} diff --git a/src/app/core/core.module.ts b/src/app/core/core.module.ts new file mode 100644 index 00000000..27777c86 --- /dev/null +++ b/src/app/core/core.module.ts @@ -0,0 +1,30 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; + +import { ApiService } from './services/api.service'; +import { HTTP_INTERCEPTORS } from '@angular/common/http'; +import { HttpTokenInterceptor } from './interceptors/http.token.interceptor'; +import { JwtService } from './services/jwt.service'; +import { MinPipe } from './pipes/min.pipe'; +import { MaxPipe } from './pipes/max.pipe'; +import { TimeOutInterceptor } from './interceptors/time-out-interceptor'; + +@NgModule( { + declarations: [ MinPipe, MaxPipe ], + imports: [ + CommonModule + ], + providers: [ + { provide: HTTP_INTERCEPTORS, useClass: HttpTokenInterceptor, multi: true }, + { provide: HTTP_INTERCEPTORS, useClass: TimeOutInterceptor, multi: true }, + ApiService, + JwtService + ], + exports: [ MinPipe, MaxPipe ] +} ) +export class CoreModule { +} diff --git a/src/app/core/editors/abstract-shape-editor.ts b/src/app/core/editors/abstract-shape-editor.ts new file mode 100644 index 00000000..a16629b0 --- /dev/null +++ b/src/app/core/editors/abstract-shape-editor.ts @@ -0,0 +1,434 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { AppService } from 'app/core/services/app.service'; +import * as THREE from 'three'; +import { Color, Object3D, PointsMaterial, Vector2, Vector3 } from 'three'; +import { MouseButton, PointerEventData } from 'app/events/pointer-event-data'; +import { COLOR } from 'app/shared/utils/colors.service'; +import { BaseEventData } from '../../events/pointer-event-data'; +import { EventEmitter } from '@angular/core'; +import { IShapeEditor } from './i-shape-editor'; +import { SceneService } from '../services/scene.service'; +import { KeyboardInput } from 'app/core/input'; +import { Subscription } from 'rxjs'; +import { Debug } from 'app/core/utils/debug'; +import { AnyControlPoint, NewDistanceNode } from '../../modules/three-js/objects/control-point'; +import { OdTextures } from 'app/modules/tv-map/builders/od.textures'; + +export abstract class AbstractShapeEditor implements IShapeEditor { + + public controlPointSelected = new EventEmitter(); + public controlPointUnselected = new EventEmitter(); + public controlPointHovered = new EventEmitter(); + + /** + * Fire when control point is added + */ + public controlPointAdded = new EventEmitter(); + + /** + * Fired everytime mouse is moving the control point + */ + public controlPointMoved = new EventEmitter(); + + /** + * Fired after mouse is up and control point position is updated + */ + public controlPointUpdated = new EventEmitter(); + + /** + * Fire when control point is removed + */ + public controlPointRemoved = new EventEmitter(); + + public curveGeometryChanged = new EventEmitter>(); + public curveGeometryAdded = new EventEmitter>(); + + public pickingEnabled: boolean = true; + + public currentPoint: AnyControlPoint; + + protected pointerDownAt: THREE.Vector3; + protected isDragging: boolean; + + protected DEFAULT_CONTROL_POINT_COLOR = COLOR.BLUE; + protected HOVERED_CONTROL_POINT_COLOR = COLOR.YELLOW; + protected SELECTED_CONTROL_POINT_COLOR = COLOR.RED; + + protected DEFAULT_LINE_COLOR = COLOR.RED; + protected HIGHLIGHT_LINE_COLOR = COLOR.BLUE; + + protected object: Object3D; + protected material = new THREE.LineBasicMaterial( { color: this.DEFAULT_LINE_COLOR, depthTest: false } ); + protected pointerIsDown: boolean; + + // subscribers + private pointerMovedSubscriber: Subscription; + private pointerClickedSubscriber: Subscription; + private pointerUpSubscriber: Subscription; + private pointerDownSubscriber: Subscription; + private selectSubscriber: Subscription; + private deSelectSubscriber: Subscription; + private pointerEnterSubscriber: Subscription; + private pointerExitSubscriber: Subscription; + private controlPointSelectedSubscriber: Subscription; + private controlPointUnselectedSubcriber: Subscription; + private controlPointHoveredSubcriber: Subscription; + private isEnabled: boolean = false; + private _controlPoints: AnyControlPoint[] = []; + + constructor () { + + this.enable(); + + } + + get controlPoints (): AnyControlPoint[] { + return this._controlPoints; + } + + set controlPoints ( value: AnyControlPoint[] ) { + this._controlPoints = value; + } + + public get controlPointCount (): number { + + return this._controlPoints.length; + + } + + public get controlPointPositions (): Vector3[] { + + const positions: Vector3[] = []; + + this._controlPoints.forEach( ( point ) => { + positions.push( point.position ); + } ); + + return positions; + } + + public get vector2ControlPoints (): Vector2[] { + + const positions: Vector2[] = []; + + this._controlPoints.forEach( ( point ) => { + positions.push( new Vector2( point.position.x, point.position.y ) ); + } ); + + return positions; + } + + protected get lastControlPoint () { + return this._controlPoints[ this._controlPoints.length - 1 ]; + }; + + public abstract draw (); + + destroy () { + + this.controlPoints.forEach( cp => this.unSelectControlPoint( cp ) ); + + this.removeAllControlPoints(); + + this.disable(); + + SceneService.remove( this.object ); + + } + + highlight () { + + this.material.color = new Color( this.HIGHLIGHT_LINE_COLOR ); + this.material.needsUpdate = true; + + } + + removeHighlight () { + + this.material.color = new Color( this.DEFAULT_LINE_COLOR ); + this.material.needsUpdate = true; + } + + enable () { + + if ( this.isEnabled ) return; + + const events = AppService.eventSystem; + + if ( events ) { + + this.pointerMovedSubscriber = events.pointerMoved.subscribe( e => this.onPointerMoved( e ) ); + this.pointerClickedSubscriber = events.pointerClicked.subscribe( e => this.onPointerClicked( e ) ); + this.pointerUpSubscriber = events.pointerUp.subscribe( e => this.onPointerUp( e ) ); + this.pointerDownSubscriber = events.pointerDown.subscribe( e => this.onPointerDown( e ) ); + this.selectSubscriber = events.select.subscribe( e => this.onSelect( e ) ); + this.deSelectSubscriber = events.deSelect.subscribe( e => this.onDeSelect( e ) ); + this.pointerEnterSubscriber = events.pointerEnter.subscribe( e => this.onPointerEnter( e ) ); + this.pointerExitSubscriber = events.pointerExit.subscribe( e => this.onPointerExit( e ) ); + + } + + + this.controlPointSelectedSubscriber = this.controlPointSelected.subscribe( e => this.onControlPointSelected( e ) ); + this.controlPointUnselectedSubcriber = this.controlPointUnselected.subscribe( e => this.onControlPointUnselected( e ) ); + this.controlPointHoveredSubcriber = this.controlPointHovered.subscribe( e => this.onControlPointHovered( e ) ); + + this.isEnabled = true; + } + + disable () { + + Debug.log( 'eventd disabled' ); + + this.pointerMovedSubscriber.unsubscribe(); + this.pointerClickedSubscriber.unsubscribe(); + this.pointerUpSubscriber.unsubscribe(); + this.pointerDownSubscriber.unsubscribe(); + this.selectSubscriber.unsubscribe(); + this.deSelectSubscriber.unsubscribe(); + this.pointerEnterSubscriber.unsubscribe(); + this.pointerExitSubscriber.unsubscribe(); + + this.controlPointSelectedSubscriber.unsubscribe(); + this.controlPointUnselectedSubcriber.unsubscribe(); + + this.isEnabled = false; + } + + onPointerClicked ( e: PointerEventData ): void { + + if ( this.isDragging ) return; + + } + + onSelect ( e: BaseEventData ) { + + if ( !this.pickingEnabled ) return; + + this.pickControlPoint( e ); + + } + + pickControlPoint ( e: BaseEventData ) { + + if ( e.object != null && e.object.type == 'Points' ) { + + this.unSelectControlPoint( this.currentPoint ); + + this.selectControlPoint( e.object as AnyControlPoint ); + + } else { + + this.unSelectControlPoint( this.currentPoint ); + } + + } + + selectControlPoint ( point: AnyControlPoint ) { + + if ( point != null ) { + + this.currentPoint = point; + + this.controlPointSelected.emit( this.currentPoint ); + + } + } + + unSelectControlPoint ( point: AnyControlPoint ) { + + if ( point != null ) { + + this.controlPointUnselected.emit( point ); + + this.currentPoint = null; + + } + + } + + onPointerUp ( e: PointerEventData ) { + + this.pointerIsDown = false; + + this.isDragging = false; + + if ( this.currentPoint != null ) this.controlPointUpdated.emit( this.currentPoint ); + } + + onPointerMoved ( e: PointerEventData ): void { + + if ( e.point != null && this.pointerIsDown && this._controlPoints.length > 1 ) { + + this.isDragging = true; + + if ( this.currentPoint != null ) this.currentPoint.copyPosition( e.point ); + + if ( this._controlPoints.length > 1 ) this.draw(); + + this.controlPointMoved.emit( this.currentPoint ); + } + + } + + onPointerExit ( e: any ): any { + + } + + onPointerEnter ( e: any ): any { + + } + + onPointerDown ( e: PointerEventData ): any { + + if ( e.button == MouseButton.RIGHT ) return; + + this.pointerIsDown = true; + + this.pointerDownAt = e.point; + + // if ( e.object != null && e.object.userData.is_selectable == true ) return; + + if ( e.button == MouseButton.LEFT && KeyboardInput.isShiftKeyDown && e.point != null ) { + + this.addControlPoint( e.point ); + + if ( this._controlPoints.length > 1 ) this.draw(); + } + + } + + addControlPoint ( position: THREE.Vector3, parent?: Object3D, size?: number ): AnyControlPoint { + + const point = this.createControlPoint( position, parent, size ); + + this._controlPoints.push( point ); + + this.controlPointAdded.emit( point ); + + return point; + } + + removeControlPoint ( point: AnyControlPoint ) { + + const index = this._controlPoints.indexOf( point ); + + this._controlPoints.splice( index, 1 ); + + this.controlPointRemoved.emit( point ); + + SceneService.remove( point ); + } + + removeAllControlPoints () { + + this._controlPoints.forEach( object => { + + object.visible = false; + + // SceneService.remove( object ); + + } ); + + this._controlPoints.splice( 0, this._controlPoints.length ); + + } + + protected createControlPoint ( position: Vector3, parent?: Object3D, size?: number ) { + + const dotGeometry = new THREE.Geometry(); + + dotGeometry.vertices.push( new THREE.Vector3( 0, 0, 0 ) ); + + const dotMaterial = new PointsMaterial( { + size: size || 10, + sizeAttenuation: false, + map: OdTextures.point, + alphaTest: 0.5, + transparent: true, + color: this.DEFAULT_CONTROL_POINT_COLOR, + depthTest: false + } ); + + const object = new AnyControlPoint( dotGeometry, dotMaterial ); + + object.setPosition( position.clone() ); + + object.userData.is_button = true; + object.userData.is_control_point = true; + object.userData.is_selectable = true; + + object.renderOrder = 3; + + SceneService.add( object ); + + return object; + + } + + public createDistanceNode ( roadId: number, laneId: number, s: number, t, position: Vector3, parent?: Object3D ) { + + const dotGeometry = new THREE.Geometry(); + + dotGeometry.vertices.push( new THREE.Vector3( 0, 0, 0 ) ); + + const dotMaterial = new PointsMaterial( { + size: 10, + sizeAttenuation: false, + map: OdTextures.point, + alphaTest: 0.5, + transparent: true, + color: this.DEFAULT_CONTROL_POINT_COLOR, + depthTest: false + } ); + + const object = new NewDistanceNode( roadId, laneId, s, t, dotGeometry, dotMaterial ); + + object.setPosition( position.clone() ); + + object.userData.is_button = true; + object.userData.is_control_point = true; + object.userData.is_selectable = true; + + object.renderOrder = 3; + + SceneService.add( object ); + + return object; + + } + + private onDeSelect ( e: any ) { + + } + + onControlPointHovered ( e: AnyControlPoint ) { + + e.onMouseOver(); + + } + + onControlPointUnhovered ( e: AnyControlPoint ) { + + e.onMouseOut(); + + } + + private onControlPointSelected ( e: AnyControlPoint ) { + + e.select(); + + AppService.three.disableControls(); + } + + private onControlPointUnselected ( e: AnyControlPoint ) { + + e.unselect(); + + AppService.three.enableControls(); + } + +} diff --git a/src/app/core/editors/box-editor.ts b/src/app/core/editors/box-editor.ts new file mode 100644 index 00000000..30a76193 --- /dev/null +++ b/src/app/core/editors/box-editor.ts @@ -0,0 +1,158 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { AbstractShapeEditor } from './abstract-shape-editor'; +import { MouseButton, PointerEventData, PointerMoveData } from 'app/events/pointer-event-data'; +import * as THREE from 'three'; +import { Line, BoxBufferGeometry, MeshBasicMaterial, Mesh } from 'three'; +import { SceneService } from '../services/scene.service'; +import { KeyboardInput } from 'app/core/input'; +import { AnyControlPoint } from 'app/modules/three-js/objects/control-point'; +import { AppService } from '../services/app.service'; +import { EventEmitter } from '@angular/core'; + +export interface BoxCreatedEvent { + mesh?: Mesh; + geometry?: BoxBufferGeometry; + height?: number; + width?: number; + length?: number; +} + +export class BoxEditor extends AbstractShapeEditor { + + boxGeometry = new BoxBufferGeometry(); + boxMaterial = new MeshBasicMaterial( { color: 'red' } ); + boxMesh: Mesh; + + height: number; + width: number; + length: number = 1; + + boxCreated = new EventEmitter(); + + boxes: Mesh[] = []; + + creating = false; + + constructor ( public keepAspectRatio: boolean = false ) { + + super(); + + } + + disable () { + + super.disable(); + + AppService.three.enableControls(); + + } + + draw () { + + this.boxGeometry = new BoxBufferGeometry( this.height, this.width, this.length ); + + if ( this.boxMesh ) SceneService.remove( this.boxMesh ); + + this.boxMesh = new Mesh( this.boxGeometry, this.boxMaterial ); + + this.boxMesh.position.copy( this.pointerDownAt ); + + SceneService.add( this.boxMesh ); + } + + onPointerDown ( e: PointerEventData ) { + + AppService.three.disableControls(); + + if ( e.button !== MouseButton.LEFT && !KeyboardInput.isShiftKeyDown ) return; + + this.pointerIsDown = true; + this.pointerDownAt = e.point; + + } + + onPointerUp ( e: PointerEventData ) { + + AppService.three.enableControls(); + + if ( e.button !== MouseButton.LEFT || !this.creating ) return; + + this.creating = false; + + this.pointerIsDown = false; + + if ( this.boxMesh ) SceneService.remove( this.boxMesh ); + + const cp = this.addControlPoint( this.boxMesh.position ); + + const box = new Mesh( this.boxGeometry, this.boxMaterial ); + + box.position.copy( this.boxMesh.position ); + + this.boxes.push( box ); + + this.boxCreated.emit( { + mesh: box, + geometry: this.boxGeometry, + height: this.length, + width: this.height, + length: this.width, + } ); + + cp.updated.subscribe( e => { + + box.position.copy( e.position ); + + } ); + + this.controlPointSelected.subscribe( e => { + + + } ); + + } + + onPointerMoved ( e: PointerMoveData ) { + + if ( this.pointerIsDown && KeyboardInput.isShiftKeyDown ) { + + this.creating = true; + + // this.height = this.pointerDownAt.distanceTo( e.point ); + // this.width = this.pointerDownAt.distanceTo( e.point ); + // this.depth = this.pointerDownAt.distanceTo( e.point ); + + const startPoint = this.pointerDownAt; + const endPoint = e.point; + + // const tmpPoint = startPoint.clone(); + + // tmpPoint.x = Math.min( startPoint.x, endPoint.x ); + // tmpPoint.y = Math.max( startPoint.y, endPoint.y ); + // endPoint.x = Math.max( startPoint.x, endPoint.x ); + // endPoint.y = Math.min( startPoint.y, endPoint.y ); + + + this.width = Math.abs( startPoint.y - endPoint.y ) * 2; + this.height = Math.abs( startPoint.x - endPoint.x ) * 2; + + this.draw(); + } + + if ( this.pointerIsDown && this.currentPoint != null ) { + + e.point.z = 0; + + this.currentPoint.copyPosition( e.point ); + + this.controlPointMoved.emit( this.currentPoint ); + + + } + + } + +} diff --git a/src/app/core/editors/cubic-bezier-curve-editor.ts b/src/app/core/editors/cubic-bezier-curve-editor.ts new file mode 100644 index 00000000..d552ee0f --- /dev/null +++ b/src/app/core/editors/cubic-bezier-curve-editor.ts @@ -0,0 +1,121 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { KeyboardInput } from '../input'; +import * as THREE from 'three'; +import { Curve, Object3D } from 'three'; +import { MouseButton, PointerEventData, PointerMoveData } from '../../events/pointer-event-data'; +import { AbstractShapeEditor } from './abstract-shape-editor'; +import { SceneService } from '../services/scene.service'; +import { AnyControlPoint } from '../../modules/three-js/objects/control-point'; + +export class CubicBezierCurveEditor extends AbstractShapeEditor { + + private curve: Curve; + private referenceLine: Object3D; + private v0: AnyControlPoint; + private v1: AnyControlPoint; + private v2: AnyControlPoint; + private v3: AnyControlPoint; + + constructor () { + + super(); + + } + + public draw () { + this.drawCurve(); + } + + onPointerMoved ( e: PointerMoveData ): void { + + if ( e.point != null && this.pointerIsDown && this.controlPoints.length > 1 ) { + + this.isDragging = true; + + if ( this.currentPoint != null ) this.currentPoint.copyPosition( e.point ); + + // this.lastControlPoint.position.copy( e.point ); + + if ( this.controlPoints.length > 1 ) this.drawCurve(); + + } + + } + + onPointerDown ( e: PointerEventData ) { + + this.pointerIsDown = true; + + this.pointerDownAt = e.point; + + if ( this.controlPoints.length >= 4 ) return; + + if ( e.object != null && e.object.userData.is_selectable == true ) return; + + if ( e.button == MouseButton.LEFT && KeyboardInput.isShiftKeyDown && e.point != null ) { + + this.addControlPoint( e.point ); + + if ( this.controlPoints.length > 1 ) this.draw(); + } + } + + drawCurve () { + + if ( this.referenceLine != null ) SceneService.remove( this.referenceLine, false ); + + this.updateBezierPoints(); + + this.curve = new THREE.CubicBezierCurve3( this.v0.position, this.v1.position, this.v2.position, this.v3.position ); + + let points = this.curve.getPoints( 50 ); + + let geometry = new THREE.BufferGeometry().setFromPoints( points ); + + let material = new THREE.LineBasicMaterial( { color: 0xff0000 } ); + + // Create the final object to add to the scene + this.referenceLine = new THREE.Line( geometry, material ); + + SceneService.add( this.referenceLine, false ); + + if ( this.controlPoints.length == 2 ) { + + this.curveGeometryAdded.emit( this.curve ); + + } else if ( this.controlPoints.length > 2 ) { + + this.curveGeometryChanged.emit( this.curve ); + + } + } + + private updateBezierPoints () { + + if ( this.controlPoints.length == 2 ) { + + this.v0 = this.controlPoints[ 0 ]; + this.v1 = this.controlPoints[ 0 ]; + this.v2 = this.controlPoints[ 1 ]; + this.v3 = this.controlPoints[ 1 ]; + + } else if ( this.controlPoints.length == 3 ) { + + this.v0 = this.controlPoints[ 0 ]; + this.v1 = this.controlPoints[ 0 ]; + this.v2 = this.controlPoints[ 1 ]; + this.v3 = this.controlPoints[ 2 ]; + + } else if ( this.controlPoints.length == 4 ) { + + this.v0 = this.controlPoints[ 0 ]; + this.v1 = this.controlPoints[ 1 ]; + this.v2 = this.controlPoints[ 2 ]; + this.v3 = this.controlPoints[ 3 ]; + + } + } +} diff --git a/src/app/core/editors/distance-node-editor.ts b/src/app/core/editors/distance-node-editor.ts new file mode 100644 index 00000000..03911688 --- /dev/null +++ b/src/app/core/editors/distance-node-editor.ts @@ -0,0 +1,138 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { AbstractShapeEditor } from './abstract-shape-editor'; +import { MouseButton, PointerEventData, PointerMoveData } from '../../events/pointer-event-data'; +import { KeyboardInput } from '../input'; +import { AnyControlPoint, NewDistanceNode } from 'app/modules/three-js/objects/control-point'; +import { TvMapQueries } from 'app/modules/tv-map/queries/tv-map-queries'; +import { TvPosTheta } from '../../modules/tv-map/models/tv-pos-theta'; + +export class DistanceNodeEditor extends AbstractShapeEditor { + + + constructor ( private maxControlPoints: number = 1000 ) { + + super(); + + } + + draw () { + + this.curveGeometryAdded.emit( null ); + + } + + + onPointerDown ( e: PointerEventData ) { + + if ( e.button == MouseButton.RIGHT ) return; + + this.pointerIsDown = true; + + this.pointerDownAt = e.point; + + if ( this.controlPoints.length >= this.maxControlPoints ) return; + + if ( e.object != null && e.object.userData.is_selectable === true ) return; + + if ( e.button === MouseButton.LEFT && KeyboardInput.isShiftKeyDown && e.point != null ) { + + e.point.z = 0; + + this.addControlPoint( e.point ); + + this.draw(); + } + } + + onPointerMoved ( e: PointerMoveData ): void { + + const direction = 'sCoordinate'; + + if ( e.point != null && this.pointerIsDown && this.controlPoints.length > 0 ) { + + this.isDragging = true; + + if ( this.currentPoint != null ) { + + const roadPos = new TvPosTheta(); + const lanePos = new TvPosTheta(); + + const node = ( this.currentPoint as NewDistanceNode ); + + e.point.z = 0; + + const position = e.point; + + // this gets the road and the s and t values + const road = TvMapQueries.getRoadByCoords( position.x, position.y, roadPos ); + + // this get the lane from road, s and t values + // roadPos is only used to read + const result = TvMapQueries.getLaneByCoords( position.x, position.y, roadPos ); + + if ( road ) { + + let finalPosition = null; + + if ( direction == 'sCoordinate' ) { + + // fixed t and only s-value will change + // finalPosition = OpenDriveQueries.getLanePosition( node.roadId, node.laneId, roadPos.s, node.t, lanePos ); + finalPosition = TvMapQueries.getLanePosition( node.roadId, node.laneId, roadPos.s, 0, lanePos ); + + } else if ( direction == 'tCoordinate' ) { + + // fixed s and only t-value will change + finalPosition = TvMapQueries.getLanePosition( node.roadId, node.laneId, node.s, roadPos.t, lanePos ); + + } + + node.s = roadPos.s; + + // node.t = roadPos.t; + + this.currentPoint.copyPosition( finalPosition ); + + this.controlPointMoved.emit( this.currentPoint ); + + } + } + } + } + + addControlPoint ( position: THREE.Vector3 ): AnyControlPoint { + + const roadPos = new TvPosTheta(); + const lanePos = new TvPosTheta(); + + // this gets the road and the s and t values + const road = TvMapQueries.getRoadByCoords( position.x, position.y, roadPos ); + + // cant create as road not found + if ( !road ) return; + + // this get the lane from road, s and t values + // roadPos is only used to read + const result = TvMapQueries.getLaneByCoords( position.x, position.y, roadPos ); + + // cant create as road or lane not found + if ( !result.road || !result.lane ) return; + + // now get the exact position in middle of the lane + const finalPosition = TvMapQueries.getLanePosition( road.id, result.lane.id, roadPos.s, 0, lanePos ); + + // create the distance node + const point = this.createDistanceNode( result.road.id, result.lane.id, roadPos.s, roadPos.t, position, road.gameObject ); + + point.copyPosition( finalPosition ); + + this.controlPoints.push( point ); + + this.controlPointAdded.emit( point ); + + return point; + } +} diff --git a/src/app/core/editors/i-shape-editor.ts b/src/app/core/editors/i-shape-editor.ts new file mode 100644 index 00000000..ab9b1139 --- /dev/null +++ b/src/app/core/editors/i-shape-editor.ts @@ -0,0 +1,8 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +export interface IShapeEditor { + + +} \ No newline at end of file diff --git a/src/app/core/editors/line-editor.ts b/src/app/core/editors/line-editor.ts new file mode 100644 index 00000000..2a450bc3 --- /dev/null +++ b/src/app/core/editors/line-editor.ts @@ -0,0 +1,87 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { AbstractShapeEditor } from './abstract-shape-editor'; +import { MouseButton, PointerEventData } from 'app/events/pointer-event-data'; +import * as THREE from 'three'; +import { Line } from 'three'; +import { SceneService } from '../services/scene.service'; +import { KeyboardInput } from 'app/core/input'; +import { AnyControlPoint } from 'app/modules/three-js/objects/control-point'; + +export class LineEditor extends AbstractShapeEditor { + + public curve: THREE.LineCurve3; + + private readonly maxPoints: number; + + constructor ( maxLines: number = 1 ) { + + super(); + + this.maxPoints = maxLines * 2; + + } + + public draw () { + + if ( this.object != null ) SceneService.remove( this.object, false ); + + this.curve = new THREE.LineCurve3( this.controlPointPositions[ 0 ], this.controlPointPositions[ 1 ] ); + + const points = this.curve.getPoints( 2 ); + + const material = new THREE.LineBasicMaterial( { color: '#afbeff' } ); + + const geometry = new THREE.BufferGeometry().setFromPoints( points ); + + this.object = new THREE.Line( geometry, material ); + + this.object.name = 'Line'; + + this.object.userData.is_selectable = false; + + this.object.renderOrder = 3; + + SceneService.add( this.object, false ); + + this.curveGeometryChanged.emit( this.curve ); + } + + onPointerDown ( e: PointerEventData ) { + + if ( e.button === MouseButton.RIGHT ) return; + + this.pointerIsDown = true; + + this.pointerDownAt = e.point; + + if ( this.controlPoints.length >= this.maxPoints ) return; + + if ( e.button === MouseButton.LEFT && KeyboardInput.isShiftKeyDown && e.point != null ) { + + this.addControlPoint( e.point ); + + if ( this.controlPoints.length > 1 ) this.draw(); + + } else if ( e.button === MouseButton.LEFT ) { + + for ( const i of e.intersections ) { + + if ( i.object.type === 'Points' ) { + + this.unSelectControlPoint( this.currentPoint ); + + this.selectControlPoint( i.object as AnyControlPoint ); + + break; + + } + + } + + } + } + +} diff --git a/src/app/core/editors/point-editor.ts b/src/app/core/editors/point-editor.ts new file mode 100644 index 00000000..eb8fddaa --- /dev/null +++ b/src/app/core/editors/point-editor.ts @@ -0,0 +1,95 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { AbstractShapeEditor } from './abstract-shape-editor'; +import { MouseButton, PointerEventData, PointerMoveData } from '../../events/pointer-event-data'; +import { KeyboardInput } from '../input'; +import { PickingHelper } from '../services/picking-helper.service'; + +export class PointEditor extends AbstractShapeEditor { + + + constructor ( private maxControlPoints: number = 1000 ) { + + super(); + + } + + draw () { + + this.curveGeometryAdded.emit( null ); + + } + + onPointerDown ( e: PointerEventData ) { + + if ( e.button == MouseButton.RIGHT ) return; + + this.pointerIsDown = true; + + this.pointerDownAt = e.point; + + if ( this.controlPoints.length >= this.maxControlPoints ) return; + + this.currentPoint = this.getNearestControlPoint( e ); + + if ( this.currentPoint ) { + + this.currentPoint.select(); + + this.controlPointSelected.emit( this.currentPoint ); + + } + + if ( e.object != null && e.object.userData.is_selectable === true ) return; + + if ( e.button === MouseButton.LEFT && KeyboardInput.isShiftKeyDown && e.point != null ) { + + e.point.z = 0; + + this.addControlPoint( e.point ); + + this.draw(); + } + } + + onPointerMoved ( e: PointerEventData ): void { + + this.controlPoints.forEach( cp => cp.onMouseOut() ); + + if ( e.point != null && this.pointerIsDown && this.controlPoints.length > 0 ) { + + this.isDragging = true; + + if ( this.currentPoint != null ) { + + e.point.z = 0; + + this.currentPoint.copyPosition( e.point ); + + this.controlPointMoved.emit( this.currentPoint ); + + } + + } else if ( e.point != null && !this.pointerIsDown && this.controlPoints.length > 0 ) { + + const controlPoint = this.getNearestControlPoint( e ); + + if ( controlPoint ) { + + controlPoint.onMouseOver(); + + } + + } + + } + + getNearestControlPoint ( e: PointerEventData ) { + + const maxDistance = Math.max( 0.5, e.approxCameraDistance * 0.01 ); + + return PickingHelper.findNearest( e.point, this.controlPoints, maxDistance ); + } +} diff --git a/src/app/core/editors/polygon-editor.ts b/src/app/core/editors/polygon-editor.ts new file mode 100644 index 00000000..acbcac07 --- /dev/null +++ b/src/app/core/editors/polygon-editor.ts @@ -0,0 +1,119 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { AbstractShapeEditor } from './abstract-shape-editor'; +import * as THREE from 'three'; +import { SceneService } from '../services/scene.service'; + +export class PolygonEditor extends AbstractShapeEditor { + + public draw () { + this.drawPolygon(); + } + + group: THREE.Group; + line: any; + + constructor () { + + super(); + + } + + public drawPolygon () { + + if ( this.group != null ) SceneService.remove( this.group ); + if ( this.line != null ) SceneService.remove( this.line ); + + this.group = new THREE.Group(); + + var splineShape = new THREE.Shape(); + + let v0 = this.vector2ControlPoints[ 0 ]; + + splineShape.moveTo( v0.x, v0.y ); + + splineShape.splineThru( this.vector2ControlPoints ); + + var extrudeSettings = { depth: 8, bevelEnabled: true, bevelSegments: 2, steps: 2, bevelSize: 1, bevelThickness: 1 }; + + this.addShape( splineShape, 0xf08000, 0, 0, 0, 0, 0, 0, 1 ); + + SceneService.add( this.group ); + + } + + addShape ( shape: THREE.Shape, color: number, x: number, y: number, z: number, rx: number, ry: number, rz: number, s: number ) { + + // flat shape with texture + // note: default UVs generated by THREE.ShapeBufferGeometry are simply the x- and y-coordinates of the vertices + const loader = new THREE.TextureLoader(); + const texture = loader.load( "assets/textures/terrain/grasslight-big.jpg" ); + texture.wrapS = texture.wrapT = THREE.RepeatWrapping; + texture.repeat.set( 25, 25 ); + texture.anisotropy = 16; + + let geometry1 = new THREE.ShapeBufferGeometry( shape ); + var mesh = new THREE.Mesh( geometry1, new THREE.MeshLambertMaterial( { map: texture } ) ); + mesh.position.set( x, y, z - 175 ); + mesh.rotation.set( rx, ry, rz ); + mesh.scale.set( s, s, s ); + this.group.add( mesh ); + + // // flat shape + // var geometry2 = new THREE.ShapeBufferGeometry( shape ); + // var mesh = new THREE.Mesh( geometry2, new THREE.MeshPhongMaterial( { color: color, side: THREE.DoubleSide } ) ); + // mesh.position.set( x, y, z - 125 ); + // mesh.rotation.set( rx, ry, rz ); + // mesh.scale.set( s, s, s ); + // this.group.add( mesh ); + + // // extruded shape + // var geometry3 = new THREE.ExtrudeBufferGeometry( shape, extrudeSettings ); + // var mesh = new THREE.Mesh( geometry3, new THREE.MeshPhongMaterial( { color: color, opacity: 0.8 } ) ); + // mesh.position.set( x, y, z - 10 ); + // mesh.rotation.set( rx, ry, rz ); + // mesh.scale.set( s, s, s ); + // this.group.add( mesh ); + + this.addLineShape( shape, color, x, y, z, rx, ry, rz, s ); + } + + addLineShape ( shape, color, x, y, z, rx, ry, rz, s ) { + + // lines + shape.autoClose = true; + var points = shape.getPoints(); + var spacedPoints = shape.getSpacedPoints( 50 ); + var geometryPoints = new THREE.BufferGeometry().setFromPoints( points ); + + // solid line + let line = this.line = new THREE.Line( geometryPoints, new THREE.LineBasicMaterial( { color: color } ) ); + line.position.set( x, y, z - 25 ); + line.rotation.set( rx, ry, rz ); + line.scale.set( s, s, s ); + this.group.add( line ); + + // // line from equidistance sampled points + // var line = new THREE.Line( geometrySpacedPoints, new THREE.LineBasicMaterial( { color: color } ) ); + // line.position.set( x, y, z + 25 ); + // line.rotation.set( rx, ry, rz ); + // line.scale.set( s, s, s ); + // this.group.add( line ); + + // // vertices from real points + // var particles = new THREE.Points( geometryPoints, new THREE.PointsMaterial( { color: color, size: 4 } ) ); + // particles.position.set( x, y, z + 75 ); + // particles.rotation.set( rx, ry, rz ); + // particles.scale.set( s, s, s ); + // this.group.add( particles ); + + // // equidistance sampled points + // var particles = new THREE.Points( geometrySpacedPoints, new THREE.PointsMaterial( { color: color, size: 4 } ) ); + // particles.position.set( x, y, z + 125 ); + // particles.rotation.set( rx, ry, rz ); + // particles.scale.set( s, s, s ); + // this.group.add( particles ); + } +} diff --git a/src/app/core/editors/polyline-editor.ts b/src/app/core/editors/polyline-editor.ts new file mode 100644 index 00000000..8a3d02e9 --- /dev/null +++ b/src/app/core/editors/polyline-editor.ts @@ -0,0 +1,42 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { AbstractShapeEditor } from './abstract-shape-editor'; +import * as THREE from 'three'; +import { CatmullRomCurve3 } from 'three'; +import { SceneService } from '../services/scene.service'; + +export class PolyLineEditor extends AbstractShapeEditor { + + private curve: CatmullRomCurve3; + + constructor () { + + super(); + + } + + public draw () { + this.drawSpline(); + } + + drawSpline () { + + if ( this.object != null ) SceneService.remove( this.object, false ); + + this.curve = new THREE.CatmullRomCurve3( this.controlPointPositions, false, 'catmullrom', 0 ); + + let geometry = new THREE.BufferGeometry().setFromPoints( this.curve.points ); + + // Create the final object to add to the scene + this.object = new THREE.Line( geometry, this.material ); + + this.object.renderOrder = 2; + + SceneService.add( this.object, false ); + + this.curveGeometryChanged.emit( this.curve ); + } + +} diff --git a/src/app/core/editors/shape-editor-factory.ts b/src/app/core/editors/shape-editor-factory.ts new file mode 100644 index 00000000..cb243e39 --- /dev/null +++ b/src/app/core/editors/shape-editor-factory.ts @@ -0,0 +1,14 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { LineEditor } from './line-editor'; + +export class ShapeEditorFactory { + + static createLineEditor (): LineEditor { + return new LineEditor(); + } + + +} diff --git a/src/app/core/editors/spline-curve-editor.ts b/src/app/core/editors/spline-curve-editor.ts new file mode 100644 index 00000000..f585dbbb --- /dev/null +++ b/src/app/core/editors/spline-curve-editor.ts @@ -0,0 +1,42 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { AbstractShapeEditor } from './abstract-shape-editor'; +import * as THREE from 'three'; +import { Curve, Object3D } from 'three'; +import { SceneService } from '../services/scene.service'; + +export class SplineCurveEditor extends AbstractShapeEditor { + + private curve: Curve; + private line: Object3D; + + constructor () { + + super(); + + } + + public draw () { + + if ( this.line != null ) SceneService.remove( this.line, false ); + + // Create a sine-like wave + this.curve = new THREE.CatmullRomCurve3( this.controlPointPositions, false, 'catmullrom', 0 ); + + // let points = this.curve.getPoints( 50 ); + + let geometry = new THREE.BufferGeometry().setFromPoints( this.controlPointPositions ); + + let material = new THREE.LineBasicMaterial( { color: 0xff0000 } ); + + // Create the final object to add to the scene + this.line = new THREE.Line( geometry, material ); + + this.line.renderOrder = 3; + + SceneService.add( this.line, false ); + } + +} diff --git a/src/app/core/factories/asset-factory.service.ts b/src/app/core/factories/asset-factory.service.ts new file mode 100644 index 00000000..b28b916b --- /dev/null +++ b/src/app/core/factories/asset-factory.service.ts @@ -0,0 +1,168 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { Injectable } from '@angular/core'; +import { Texture } from 'three'; +import { TvRoadSign } from 'app/modules/tv-map/models/tv-road-sign.model'; +import { TvMaterial } from 'app/modules/three-js/objects/tv-material.model'; +import { FileService } from 'app/services/file.service'; +import { SnackBar } from 'app/services/snack-bar.service'; +import { MetadataFactory } from './metadata-factory.service'; +import { PropModel } from '../models/prop-model.model'; +import { AssetDatabase } from 'app/services/asset-database'; +import { AppService } from '../services/app.service'; +import { TvMap } from 'app/modules/tv-map/models/tv-map.model'; +import { TvRoadMarking, MarkingTypes } from 'app/modules/tv-map/services/tv-marking.service'; + +@Injectable( { + providedIn: 'root' +} ) +export class AssetFactory { + + private static get fileService (): FileService { + + return AppService.file; + + } + + static getMeta ( guid: string ) { + + return AssetDatabase.getMetadata( guid ); + + } + + static createNewScene ( path: string, name: string = 'New Scene' ) { + + try { + + const scene = new TvMap(); + + const result = this.fileService.createFile( path, name, 'scene', AppService.exporter.export( scene ) ); + + const meta = MetadataFactory.createMetadata( result.fileName, 'scene', result.filePath ); + + AssetDatabase.setInstance( meta.guid, scene ); + + } catch ( error ) { + + SnackBar.error( error ); + + } + } + + static createNewFolder ( path: string, name: string = 'New Folder' ) { + + try { + + const result = this.fileService.createFolder( path, name ); + + const meta = MetadataFactory.createFolderMetadata( result.name, result.path ); + + AssetDatabase.setInstance( meta.guid, meta ); + + return result; + + } catch ( error ) { + + SnackBar.error( error ); + + } + + } + + static createNewMaterial ( path: string, name: string = 'NewMaterial' ) { + + try { + + const material = TvMaterial.new(); + + const result = this.fileService.createFile( path, material.name, 'material', material.toJSONString() ); + + const meta = MetadataFactory.createMetadata( result.fileName, 'material', result.filePath ); + + AssetDatabase.setInstance( meta.guid, material ); + + } catch ( error ) { + + SnackBar.error( error ); + + } + + } + + static createNewRoadMarking ( path: string, name: string = 'NewRoadMarking' ) { + + try { + + const marking = new TvRoadMarking( name, MarkingTypes.point, null ); + + const result = this.fileService.createFile( path, marking.name, TvRoadMarking.extension, marking.toJSONString() ); + + const meta = MetadataFactory.createMetadata( result.fileName, TvRoadMarking.extension, result.filePath ); + + AssetDatabase.setInstance( meta.guid, marking ); + + } catch ( error ) { + + SnackBar.error( error ); + + } + + } + + static updateRoadMarking ( path: string, marking: TvRoadMarking ) { + + try { + + this.fileService.fs.writeFileSync( path, marking.toJSONString() ); + + } catch ( error ) { + + SnackBar.error( error ); + + } + + } + + static updateMaterial ( path: string, material: TvMaterial ) { + + this.fileService.fs.writeFileSync( path, material.toJSONString() ); + + } + + static createNewSign ( name: string = 'NewSign', path: string ) { + + try { + + const sign = new TvRoadSign( name, null ); + + const result = this.fileService.createFile( path, sign.name, 'sign', sign.toJSONString() ); + + const meta = MetadataFactory.createMetadata( result.fileName, 'sign', result.filePath ); + + AssetDatabase.setInstance( meta.guid, sign ); + + } catch ( error ) { + + SnackBar.error( error ); + + } + + } + + static updatePropModelByGuid ( guid: string, prop: PropModel ): void { + + const meta = this.getMeta( guid ); + + this.fileService.fs.writeFileSync( meta.path, JSON.stringify( prop ) ); + } + + static updateTexture ( guid: string, texture: Texture ): void { + + const meta = this.getMeta( guid ); + + MetadataFactory.createTextureMetadata( meta.guid, meta.path, texture ); + } + +} diff --git a/src/app/core/factories/inspector-factory.service.ts b/src/app/core/factories/inspector-factory.service.ts new file mode 100644 index 00000000..3ce91e4e --- /dev/null +++ b/src/app/core/factories/inspector-factory.service.ts @@ -0,0 +1,90 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { Injectable, Type } from '@angular/core'; +import { AppInspector } from '../inspector'; +import { PropInstanceInspectorComponent } from 'app/views/inspectors/prop-instance-inspector/prop-instance-inspector.component'; +import { PropModelInspectorComponent } from 'app/views/inspectors/prop-model-inspector/prop-model-inspector.component'; +import { FileService } from 'app/services/file.service'; +import { IComponent } from '../game-object'; +import { MaterialInspector } from 'app/views/inspectors/material-inspector/material-inspector.component'; +import { TextureInspector } from 'app/views/inspectors/texture-inspector/texture-inspector.component'; +import { RoadSignInspector } from 'app/views/inspectors/road-sign-inspector/road-sign-inspector.component'; +import { RoadStyleInspector } from 'app/views/inspectors/road-style-inspector/road-style-inspector.component'; +import { RoadMarkingInspector } from 'app/views/inspectors/road-marking-inspector/road-marking-inspector.component'; +import { TvRoadMarking } from 'app/modules/tv-map/services/tv-marking.service'; + +export enum InspectorType { + prop_model_inspector = 'prop_model_inspector', + prop_instance_inspector = 'prop_instance_inspector', +} + +@Injectable( { + providedIn: 'root' +} ) +export class InspectorFactoryService { + + constructor () { } + + static setByType ( type: InspectorType, data: any ) { + + switch ( type ) { + + case InspectorType.prop_instance_inspector: + AppInspector.setInspector( PropInstanceInspectorComponent, data ); + break; + + case InspectorType.prop_model_inspector: + AppInspector.setInspector( PropModelInspectorComponent, data ); + break; + + default: + break; + } + + } + + static getInspectorByExtension ( extension ): Type { + + let inspector: Type; + + switch ( extension ) { + + case 'obj': inspector = PropModelInspectorComponent; break; + + case 'fbx': inspector = PropModelInspectorComponent; break; + + case 'gltf': inspector = PropModelInspectorComponent; break; + + case 'glb': inspector = PropModelInspectorComponent; break; + + case 'png': inspector = TextureInspector; break; + + case 'jpg': inspector = TextureInspector; break; + + case 'jpeg': inspector = TextureInspector; break; + + case 'svg': inspector = TextureInspector; break; + + case 'material': inspector = MaterialInspector; break; + + case 'sign': inspector = RoadSignInspector; break; + + case 'roadstyle': inspector = RoadStyleInspector; break; + + case TvRoadMarking.extension: inspector = RoadMarkingInspector; break; + + default: break; + } + + return inspector; + } + + static getInpectorByFilename ( filename: string ): Type { + + const extension = FileService.getExtension( filename ); + + return this.getInspectorByExtension( extension ); + } +} diff --git a/src/app/core/factories/lane-path-factory.service.ts b/src/app/core/factories/lane-path-factory.service.ts new file mode 100644 index 00000000..697787d1 --- /dev/null +++ b/src/app/core/factories/lane-path-factory.service.ts @@ -0,0 +1,214 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { Injectable } from '@angular/core'; +import { TvRoad } from 'app/modules/tv-map/models/tv-road.model'; +import { TvJunctionConnection } from 'app/modules/tv-map/models/tv-junction-connection'; +import { LanePathObject, TvJunctionLaneLink } from 'app/modules/tv-map/models/tv-junction-lane-link'; +import { BufferGeometry, Line, LineBasicMaterial, Shape } from 'three'; +import { AutoSplinePath, ExplicitSplinePath } from '../shapes/cubic-spline-curve'; +import { ExplicitSpline } from '../shapes/explicit-spline'; +import { AutoSpline } from '../shapes/auto-spline'; +import { AbstractSpline } from '../shapes/abstract-spline'; +import { TvLane } from 'app/modules/tv-map/models/tv-lane'; + +@Injectable( { + providedIn: 'root' +} ) +export class LanePathFactory { + + static create ( + incomingRoad: TvRoad, + connectingRoad: TvRoad, + connection: TvJunctionConnection, + link: TvJunctionLaneLink + ) { + + const pathObject = new LanePathObject( incomingRoad, connectingRoad, connection, link ); + + const lane = connectingRoad.getLaneSectionAt( 0 ).getLaneById( link.to ); + + const width = connectingRoad.getLaneSectionAt( 0 ).getWidthUptoCenter( lane, 0 ); + + const spline = connectingRoad.spline; + + const shape = new Shape(); shape.moveTo( 0, -0.3 ); shape.lineTo( 0, 0.3 ); + + if ( spline.controlPointPositions.length < 2 ) return; + + let offset = width; + + if ( lane.id < 0 ) offset *= -1; + + const path = this.getPath( spline, offset ); + + const lineMaterial = new LineBasicMaterial( { + color: 0x00ffff, + linewidth: 100, + opacity: 0.5, + transparent: true, + } ); + + const lineGeometry = new BufferGeometry().setFromPoints( path.getSpacedPoints( 50 ) ); + + pathObject.mesh = new Line( lineGeometry, lineMaterial ); + + pathObject.mesh.castShadow = true; + + pathObject.mesh.renderOrder = 3; + + pathObject.mesh.frustumCulled = false; + + pathObject.mesh[ 'tag' ] = LanePathObject.tag; + + pathObject.add( pathObject.mesh ); + + return pathObject; + } + + static createPathForLane ( + incomingRoad: TvRoad, + connectingRoad: TvRoad, + lane: TvLane, + connection: TvJunctionConnection, + link: TvJunctionLaneLink + ) { + + // console.trace( "create-path-lane", incomingRoad.id, connectingRoad.id, lane.id ); + + const pathObject = new LanePathObject( incomingRoad, connectingRoad, connection, link ); + + const width = connectingRoad.getFirstLaneSection().getWidthUptoCenter( lane, 0 ); + + const spline = connectingRoad.spline; + + const shape = new Shape(); shape.moveTo( 0, -0.3 ); shape.lineTo( 0, 0.3 ); + + if ( spline.controlPointPositions.length < 2 ) return; + + let offset = width; + + if ( lane.id < 0 ) offset *= -1; + + const path = this.getPath( spline, offset ); + + const lineMaterial = new LineBasicMaterial( { + color: 0x00ffff, + linewidth: 100, + opacity: 0.5, + transparent: true, + } ); + + const lineGeometry = new BufferGeometry().setFromPoints( path.getSpacedPoints( 50 ) ); + + pathObject.mesh = new Line( lineGeometry, lineMaterial ); + + pathObject.mesh.castShadow = true; + + pathObject.mesh.renderOrder = 3; + + pathObject.mesh.frustumCulled = false; + + pathObject.mesh[ 'tag' ] = LanePathObject.tag; + + pathObject.add( pathObject.mesh ); + + return pathObject; + } + + /** + * + * @param connectingRoad + * @deprecated dont use this + */ + static createFromConnectingRoad ( connectingRoad: TvRoad ) { + + const pathObject = new LanePathObject( null, connectingRoad, null, null ); + + let lane = connectingRoad.getFirstLaneSection().getLaneById( 1 ); + + if ( !lane ) lane = connectingRoad.getFirstLaneSection().getLaneById( -1 ); + + const width = connectingRoad.getFirstLaneSection().getWidthUptoCenter( lane, 0 ); + + const spline = connectingRoad.spline; + + const shape = new Shape(); shape.moveTo( 0, -0.3 ); shape.lineTo( 0, 0.3 ); + + if ( spline.controlPointPositions.length < 2 ) return; + + let offset = width; + + if ( lane.id < 0 ) offset *= -1; + + const path = this.getPath( spline, offset ); + + const lineMaterial = new LineBasicMaterial( { + color: 0x00ffff, + linewidth: 100, + opacity: 0.5, + transparent: true, + } ); + + const lineGeometry = new BufferGeometry().setFromPoints( path.getSpacedPoints( 50 ) ); + + pathObject.mesh = new Line( lineGeometry, lineMaterial ); + + pathObject.mesh.castShadow = true; + + pathObject.mesh.renderOrder = 3; + + pathObject.mesh.frustumCulled = false; + + pathObject.mesh[ 'tag' ] = LanePathObject.tag; + + pathObject.add( pathObject.mesh ); + + return pathObject; + } + + static update ( pathObject: LanePathObject ) { + + if ( !pathObject ) return; + + const connectingRoad = pathObject.connectingRoad; + + let lane = connectingRoad.getFirstLaneSection().getLaneById( 1 ); + + if ( !lane ) lane = connectingRoad.getFirstLaneSection().getLaneById( -1 ); + + const width = connectingRoad.getFirstLaneSection().getWidthUptoCenter( lane, 0 ); + + const spline = connectingRoad.spline; + + const shape = new Shape(); shape.moveTo( 0, -0.3 ); shape.lineTo( 0, 0.3 ); + + if ( spline.controlPointPositions.length < 2 ) return; + + let offset = width; + + if ( lane.id < 0 ) offset *= -1; + + const path = this.getPath( spline, offset ); + + pathObject.mesh.geometry.dispose(); + + pathObject.mesh.geometry = new BufferGeometry().setFromPoints( path.getSpacedPoints( 50 ) ); + + return pathObject; + } + + private static getPath ( spline: AbstractSpline, offset: number ) { + + if ( spline instanceof AutoSpline ) { + + return new AutoSplinePath( spline, offset ); + + } else if ( spline instanceof ExplicitSpline ) { + + return new ExplicitSplinePath( spline, offset ); + + } + } +} \ No newline at end of file diff --git a/src/app/core/factories/metadata-factory.service.ts b/src/app/core/factories/metadata-factory.service.ts new file mode 100644 index 00000000..616ca642 --- /dev/null +++ b/src/app/core/factories/metadata-factory.service.ts @@ -0,0 +1,236 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { Injectable, Type } from '@angular/core'; +import { Metadata } from '../models/metadata.model'; +import { Vector3, Texture, MeshStandardMaterial, RepeatWrapping, UVMapping, ImageLoader, TextureLoader } from 'three'; +import { FileService } from 'app/services/file.service'; +import { FileNode } from 'app/views/editor/project-browser/file-node.model'; +import { SnackBar } from 'app/services/snack-bar.service'; +import { AssetDatabase } from 'app/services/asset-database'; +import { AppService } from '../services/app.service'; +import * as THREE from 'three'; +import { TvRoadMarking } from 'app/modules/tv-map/services/tv-marking.service'; +import { RoadStyle } from 'app/services/road-style.service'; + +@Injectable( { + providedIn: 'root' +} ) +export class MetadataFactory { + + private static get fileService (): FileService { + + return AppService.file; + + } + + static saveMetadataFile ( file: FileNode | string, metadata: Metadata ): void { + + try { + + let path = null; + + if ( typeof ( file ) === 'string' ) path = file; + + if ( typeof ( file ) === 'object' ) path = file.path; + + if ( !path.includes( '.meta' ) ) path = path + '.meta'; + + this.fileService.fs.writeFileSync( path, JSON.stringify( metadata, null, 2 ) ); + + } catch ( error ) { + + console.error( error ); + + SnackBar.error( "Error in writing .meta file. Please Reimport the asset.", "", 5000 ); + } + + } + + static createMetadata ( fileName: string, ext: string, path: string ): Metadata { + + const extension = ext || FileService.getExtension( path ); + + const guid = THREE.Math.generateUUID(); + + let metadata: Metadata; + + switch ( extension ) { + + case 'scene': metadata = this.createSceneMetadata( fileName, guid, path ); break; + + case 'obj': metadata = this.createModelMetadata( fileName, guid, path ); break; + + case 'fbx': metadata = this.createModelMetadata( fileName, guid, path ); break; + + case 'gltf': metadata = this.createModelMetadata( fileName, guid, path ); break; + + case 'glb': metadata = this.createModelMetadata( fileName, guid, path ); break; + + case 'xodr': metadata = this.createOpenDriveMetadata( fileName, guid, path ); break; + + case 'png': metadata = this.createTextureMetaInternal( guid, path ); break; + case 'jpg': metadata = this.createTextureMetaInternal( guid, path ); break; + case 'jpeg': metadata = this.createTextureMetaInternal( guid, path ); break; + case 'svg': metadata = this.createTextureMetaInternal( guid, path ); break; + + case 'material': metadata = this.createMaterialMetadata( fileName, guid, path ); break; + + case 'sign': metadata = this.createSignMetadata( fileName, guid, path ); break; + + case TvRoadMarking.extension: metadata = this.createRoadMarkingMetadata( fileName, guid, path ); break; + + case RoadStyle.extension: metadata = this.createRoadStyleMetadata( fileName, guid, path ); break; + + } + + if ( metadata ) this.saveMetadataFile( path, metadata ); + + if ( metadata ) AssetDatabase.setMetadata( guid, metadata ); + + return metadata; + } + + static createRoadMarkingMetadata ( name: string, guid: string, path: string ): Metadata { + + return { + guid: guid, + importer: "RoadMarkingImporter", + data: {}, + path: path, + }; + + } + + static createRoadStyleMetadata ( name: string, guid: string, path: string ): Metadata { + + return { + guid: guid, + importer: RoadStyle.importer, + data: {}, + path: path, + }; + + } + + private static createTextureMetaInternal ( guid: string, path: string ): Metadata { + + const texture = this.loadTexture( path ); + + const metadata = this.createTextureMetadata( guid, path, texture ); + + AssetDatabase.setInstance( metadata.guid, texture ); + + return metadata; + } + + static createFolderMetadata ( name: string, path: string ): Metadata { + + const guid = THREE.Math.generateUUID(); + + const metadata = { guid: guid, isFolder: true, path: path, importer: null, data: null }; + + this.saveMetadataFile( path, metadata ); + + AssetDatabase.setMetadata( guid, metadata ); + + return metadata; + } + + static createSceneMetadata ( name: string, guid: string, path: string ) { + + return { + guid: guid, + importer: 'SceneImporter', + data: {}, + path: path + }; + + } + + static createModelMetadata ( name: string, guid: string, path: string ) { + + return { + guid: guid, + importer: 'ModelImporter', + data: { name: name, rotationVariance: new Vector3( 0, 0, 0 ), scaleVariance: new Vector3( 0, 0, 0 ) }, + path: path + }; + + } + + static createOpenDriveMetadata ( name: string, guid: string, path: string ) { + + return { + guid: guid, + importer: "OpenDriveImporter", + data: {}, + path: path + }; + + } + + static createTextureMetadata ( guid: string, path: string, texture: Texture ) { + + const data = texture.toJSON( undefined ); + + const version = data.metadata.version || 4.5; + + data.metadata = null; + + return { + guid: guid, + version: version, + type: "Texture", + importer: "TextureImporter", + data: data, + path: path + }; + + } + + static createMaterialMetadata ( name: string, guid: string, path: string ) { + + return { + guid: guid, + importer: "MaterialImporter", + data: {}, + path: path, + }; + + } + + static createSignMetadata ( name: string, guid: string, path: string ) { + + return { + guid: guid, + importer: "SignImporter", + data: {}, + path: path, + }; + + } + + static loadTexture ( path: string ): Texture { + + try { + + const texture = new TextureLoader().load( path ); + + texture.wrapS = RepeatWrapping; + texture.wrapT = RepeatWrapping; + texture.mapping = UVMapping; + texture.repeat.set( 1, 1 ); + + return texture; + + } catch ( error ) { + + SnackBar.error( error ); + + return null; + + } + } +} diff --git a/src/app/core/factories/node-factory.service.ts b/src/app/core/factories/node-factory.service.ts new file mode 100644 index 00000000..29b757e1 --- /dev/null +++ b/src/app/core/factories/node-factory.service.ts @@ -0,0 +1,400 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { Injectable } from '@angular/core'; +import { AnyControlPoint, LaneOffsetNode, LaneRoadMarkNode, LaneWidthNode } from 'app/modules/three-js/objects/control-point'; +import { RoadNode } from 'app/modules/three-js/objects/road-node'; +import { TvMapQueries } from 'app/modules/tv-map/queries/tv-map-queries'; +import { BufferGeometry, LineBasicMaterial, LineSegments, Vector3 } from 'three'; +import { COLOR } from 'app/shared/utils/colors.service'; +import { TvPosTheta } from 'app/modules/tv-map/models/tv-pos-theta'; +import { TvLaneWidth } from 'app/modules/tv-map/models/tv-lane-width'; +import { TvRoadLaneOffset } from 'app/modules/tv-map/models/tv-road-lane-offset'; +import { TvRoad } from 'app/modules/tv-map/models/tv-road.model'; +import { Maths } from 'app/utils/maths'; +import { SceneService } from '../services/scene.service'; +import { TvLane } from 'app/modules/tv-map/models/tv-lane'; +import { TvLaneRoadMark } from 'app/modules/tv-map/models/tv-lane-road-mark'; +import { SnackBar } from 'app/services/snack-bar.service'; + + +@Injectable( { + providedIn: 'root' +} ) +export class NodeFactoryService { + + constructor () { + } + + static createLaneWidthNodeByPosition ( road: TvRoad, lane: TvLane, point: Vector3 ): LaneWidthNode { + + const posTheta = new TvPosTheta(); + + // getting position on track in s/t coordinates + TvMapQueries.getRoadByCoords( point.x, point.y, posTheta ); + + // get the exisiting lane width at s + // and clone the lane width + const laneWidth = lane.getLaneWidthAt( posTheta.s ).clone( posTheta.s ); + + // add the with back to lane to + lane.addWidthRecordInstance( laneWidth ); + + // make mesh for the lane width node + return laneWidth.mesh = this.createLaneWidthNode( road, lane, laneWidth.s, laneWidth ); + } + + static createLaneWidthNode ( road: TvRoad, lane: TvLane, s: number, laneWidth: TvLaneWidth ): LaneWidthNode { + + const node = new LaneWidthNode( road, lane, s, laneWidth ); + + const offset = laneWidth.getValue( s ) * 0.5; + + const start = TvMapQueries.getLanePosition( road.id, lane.id, s, -offset ); + const end = TvMapQueries.getLanePosition( road.id, lane.id, s, offset ); + + ///////////////////////////////////////// + + node.point = AnyControlPoint.create( 'point', end ); + + node.point.tag = LaneWidthNode.pointTag; + + node.add( node.point ); + + ///////////////////////////////////////// + + const lineGeometry = new BufferGeometry().setFromPoints( [ start, end ] ); + + node.line = new LineSegments( lineGeometry, new LineBasicMaterial( { color: COLOR.DARKBLUE, opacity: 0.35 } ) ); + + node.line[ 'tag' ] = LaneWidthNode.lineTag; + + node.line.renderOrder = 3; + + node.add( node.line ); + + ////////////////////////////////////////// + + // group.position.copy( center ); + + return node; + } + + // update s value of lane-width as per the restriction + static updateLaneWidthNode ( node: LaneWidthNode, point: Vector3, direction = 'sCoordinate' ): void { + + const index = node.lane.getLaneWidthVector().findIndex( i => i.uuid === node.laneWidth.uuid ); + + if ( index === -1 ) SnackBar.error( "Unexpected error. Not able to find this node" ); + if ( index === -1 ) return; + + if ( index === 0 ) SnackBar.error( "First node cannot be edited. Please add a new node." ); + if ( index === 0 ) return; + + const minS = node.lane.width[ index - 1 ].s + 0.1; + + // TODO: mke this the max s value as per lane section + let maxS = Number.MAX_SAFE_INTEGER; + + if ( index + 1 < node.lane.width.length ) { + + maxS = node.lane.width[ index + 1 ].s - 0.1; + + } + + const newPosition = new TvPosTheta(); + + const road = TvMapQueries.getRoadByCoords( point.x, point.y, newPosition ); + + // we are getting another road s value to ignore + if ( node.lane.roadId !== road.id ) return; + + // our desired s value should lie between the previous node and the next node + const adjustedS = Maths.clamp( newPosition.s, minS, maxS ); + + // update s offset as per the new position on road + node.laneWidth.s = adjustedS; + + // const offset = node.lane.getWidthValue( adjustedS ) * 0.5; + + const offset = node.laneWidth.getValue( adjustedS ) * 0.5; + + let sCoordinate = null; + + if ( direction === 'sCoordinate' ) { + + sCoordinate = adjustedS; + + } else if ( direction === 'tCoordinate' ) { + + sCoordinate = node.s; + + } + + const start = TvMapQueries.getLanePosition( node.roadId, node.laneId, sCoordinate, -offset ); + const end = TvMapQueries.getLanePosition( node.roadId, node.laneId, sCoordinate, offset ); + + + // TODO: can be improved + node.line.geometry.dispose(); + node.line.geometry = new BufferGeometry().setFromPoints( [ start, end ] ); + + node.point.position.copy( end ); + + node.s = node.laneWidth.s = adjustedS; + + // const roadPos = new OdPosTheta(); + // const lanePos = new OdPosTheta(); + + // // this gets the road and the s and t values + // // const road = OpenDriveQueries.getRoadByCoords( point.x, point.y, roadPos ); + + // // this get the lane from road, s and t values + // // roadPos is only used to read + // // const result = OpenDriveQueries.getLaneByCoords( position.x, position.y, roadPos ); + + // if ( node.s < 0.1 ) return; + + // if ( road ) { + + // let finalPosition = null; + // let sCoordinate = null; + + // if ( direction == 'sCoordinate' ) { + + // sCoordinate = roadPos.s; + + // } else if ( direction == 'tCoordinate' ) { + + // sCoordinate = node.s; + + // } + + // const offset = laneWidth.getValue( roadPos.s ) * 0.5; + + // const start = OpenDriveQueries.getLanePosition( node.roadId, node.laneId, sCoordinate, -offset ); + // const end = OpenDriveQueries.getLanePosition( node.roadId, node.laneId, sCoordinate, offset ); + + // // TODO: can be improved + // node.line.geometry.dispose(); + // node.line.geometry = new BufferGeometry().setFromPoints( [ start, end ] ) + + // node.point.position.copy( end ); + + // node.s = node.laneWidth.s = roadPos.s; + // } + } + + /** + * Updates the lane width line position + * @param node LaneWidthNode + */ + static updateLaneWidthNodeLine ( node: LaneWidthNode ) { + + const laneWidth = node.laneWidth; + + if ( node.s < 0 ) node.s = 0; + + const offset = laneWidth.getValue( node.s ) * 0.5; + + const start = TvMapQueries.getLanePosition( node.roadId, node.laneId, node.s, -offset ); + const end = TvMapQueries.getLanePosition( node.roadId, node.laneId, node.s, offset ); + + // TODO: can be improved + node.line.geometry.dispose(); + node.line.geometry = new BufferGeometry().setFromPoints( [ start, end ] ); + + node.point.position.copy( end ); + } + + static createLaneOffsetNode ( road: TvRoad, laneOffset: TvRoadLaneOffset ): LaneOffsetNode { + + const node = new LaneOffsetNode( road, laneOffset ); + + const offset = laneOffset.getValue( laneOffset.s ); + + const start = TvMapQueries.findRoadById( road.id ).getPositionAt( laneOffset.s, 0 ); + + // const end = OpenDriveQueries.findRoadById( roadId ).getPositionAt( s, 0 ); + + ///////////////////////////////////////// + + node.point = AnyControlPoint.create( "point", start.toVector3() ); + + node.point.tag = LaneOffsetNode.pointTag; + + node.add( node.point ); + + ///////////////////////////////////////// + + // const lineGeometry = new BufferGeometry().setFromPoints( [ start, end ] ); + + // node.line = new LineSegments( lineGeometry, new LineBasicMaterial( { color: COLOR.DARKBLUE, opacity: 0.35 } ) ); + + // node.line[ 'tag' ] = LaneOffsetNode.lineTag; + + // node.line.renderOrder = 3; + + // node.add( node.line ); + + ////////////////////////////////////////// + + // group.position.copy( center ); + + return node; + } + + /** + * Updates the position of the node + * @param node + */ + static updateLaneOffsetNode ( node: LaneOffsetNode ): LaneOffsetNode { + + const offset = node.laneOffset.getValue( node.laneOffset.s ); + + const position = TvMapQueries.findRoadById( node.roadId ).getPositionAt( node.laneOffset.s, 0 ); + + node.point.copyPosition( position.toVector3() ); + + return node; + } + + static createRoadMarkNode ( lane: TvLane, roadmark: TvLaneRoadMark ): LaneRoadMarkNode { + + const node = new LaneRoadMarkNode( lane, roadmark ); + + const offset = lane.getWidthValue( roadmark.s ) * 0.5; + + const position = TvMapQueries.getLanePosition( lane.roadId, lane.id, roadmark.s, offset ); + + node.point = AnyControlPoint.create( 'point', position ); + + node.point.tag = LaneRoadMarkNode.pointTag; + + node.add( node.point ); + + return node; + } + + static updateRoadMarkNodeByPosition ( node: LaneRoadMarkNode, point: Vector3 ) { + + const index = node.lane.getRoadMarks().findIndex( roadmark => roadmark.uuid === node.roadmark.uuid ); + + if ( index === -1 ) SnackBar.error( "Unexpected error. Not able to find this node" ); + if ( index === -1 ) return node; + + if ( index === 0 ) SnackBar.error( "First node cannot be edited. Please add a new node." ); + if ( index === 0 ) return node; + + const minS = node.lane.roadMark[ index - 1 ].s + 0.1; + + // TODO: mke this the max s value as per lane section + let maxS = Number.MAX_SAFE_INTEGER; + + if ( index + 1 < node.lane.roadMark.length ) { + + maxS = node.lane.roadMark[ index + 1 ].s - 0.1; + + } + + const newPosition = new TvPosTheta(); + + const road = TvMapQueries.getRoadByCoords( point.x, point.y, newPosition ); + + // we are getting another road s value to ignore + if ( node.lane.roadId !== road.id ) return node; + + // our desired s value should lie between the previous node and the next node + const adjustedS = Maths.clamp( newPosition.s, minS, maxS ); + + // update s offset as per the new position on road + node.roadmark.sOffset = adjustedS; + + const offset = node.lane.getWidthValue( adjustedS ) * 0.5; + + const finalPosition = TvMapQueries.getLanePosition( node.lane.roadId, node.lane.id, adjustedS, offset ); + + node.point.copyPosition( finalPosition ); + + return node; + } + + static createRoadNodes ( road: TvRoad ): void { + + road.startNode = this.createRoadNode( road.id, "start" ); + road.endNode = this.createRoadNode( road.id, "end" ); + + } + + static updateRoadNodes ( road: TvRoad ): void { + + if ( !road.startNode ) + road.startNode = this.createRoadNode( road.id, "start" ); + else + this.updateRoadNode( road.startNode ); + + + if ( !road.endNode ) + road.endNode = this.createRoadNode( road.id, "end" ); + else + this.updateRoadNode( road.endNode ); + } + + static createRoadNode ( roadId: number, distance: 'start' | 'end' ) { + + const road = TvMapQueries.findRoadById( roadId ); + + let s: number; + + if ( distance == 'start' ) s = 0; + + else if ( distance == 'end' ) s = road.length - Maths.Epsilon; + + const node = new RoadNode( roadId, distance, s ); + + const result = TvMapQueries.getRoadWidthAt( road.id, s ); + + const start = TvMapQueries.getRoadPosition( road.id, s, result.leftSideWidth ); + const end = TvMapQueries.getRoadPosition( road.id, s, -result.rightSideWidth ); + + const lineGeometry = new BufferGeometry().setFromPoints( [ start.toVector3(), end.toVector3() ] ); + + node.line = new LineSegments( lineGeometry, new LineBasicMaterial( { + color: RoadNode.defaultColor, + opacity: RoadNode.defaultOpacity, + linewidth: RoadNode.defaultWidth, + } ) ); + + node.line[ 'tag' ] = RoadNode.lineTag; + + node.line.renderOrder = 3; + + node.add( node.line ); + + SceneService.add( node ); + + return node; + } + + static updateRoadNode ( node: RoadNode ): void { + + const road = TvMapQueries.findRoadById( node.roadId ); + + let s: number; + + if ( node.distance == 'start' ) s = 0; + + else if ( node.distance == 'end' ) s = road.length - Maths.Epsilon; + + const result = TvMapQueries.getRoadWidthAt( node.roadId, s ); + + const start = TvMapQueries.getRoadPosition( node.roadId, s, result.leftSideWidth ); + const end = TvMapQueries.getRoadPosition( node.roadId, s, -result.rightSideWidth ); + + // TODO: can be improved + node.line.geometry.dispose(); + node.line.geometry = new BufferGeometry().setFromPoints( [ start.toVector3(), end.toVector3() ] ); + + } +} diff --git a/src/app/core/factories/road-factory.service.ts b/src/app/core/factories/road-factory.service.ts new file mode 100644 index 00000000..968a1d4f --- /dev/null +++ b/src/app/core/factories/road-factory.service.ts @@ -0,0 +1,388 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { TvRoad } from 'app/modules/tv-map/models/tv-road.model'; +import { Vector3 } from 'three'; +import { SceneService } from '../services/scene.service'; +import { TvMapSourceFile } from 'app/modules/tv-map/services/tv-map-source-file'; +import { TvMapBuilder } from 'app/modules/tv-map/builders/od-builder.service'; +import { NodeFactoryService } from './node-factory.service'; +import { AppInspector } from '../inspector'; +import { RoadInspector } from 'app/views/inspectors/road-inspector/road-inspector.component'; +import { RoadNode } from 'app/modules/three-js/objects/road-node'; +import { TvContactPoint, TvLaneSide, TvRoadType } from 'app/modules/tv-map/models/tv-common'; +import { TvPosTheta } from 'app/modules/tv-map/models/tv-pos-theta'; +import { Maths } from 'app/utils/maths'; +import { TvLaneSection } from 'app/modules/tv-map/models/tv-lane-section'; +import { ExplicitSpline } from '../shapes/explicit-spline'; +import { RoadControlPoint } from 'app/modules/three-js/objects/road-control-point'; + +export class RoadFactory { + + static get openDrive () { + return TvMapSourceFile.openDrive; + } + + static createRoad ( position: Vector3 ) { + + const road = this.openDrive.addDefaultRoadWithType( TvRoadType.TOWN, 40 ); + + const point = this.addControlPoint( road, position ); + + return { road, point }; + } + + static removeRoad ( road: TvRoad ) { + + this.openDrive.removeRoad( road ); + + } + + static updateGeometryAndRebuild ( road: TvRoad ): void { + + this.updateGeometry( road ); + + this.rebuildRoad( road ); + } + + static updateGeometry ( road: TvRoad ) { + + road.updateGeometryFromSpline(); + + } + + static rebuildRoad ( road: TvRoad ) { + + this.openDrive.gameObject.remove( road.gameObject ); + + TvMapBuilder.buildRoad( this.openDrive.gameObject, road ); + + if ( !road.isJunction ) NodeFactoryService.updateRoadNodes( road ); + + } + + static addControlPoint ( road: TvRoad, position: Vector3 ): RoadControlPoint { + + let point: RoadControlPoint; + + // TODO: Fix addControlPointAtNew + + if ( road.spline instanceof ExplicitSpline ) { + + point = road.spline.addControlPointAtNew( position ); + + } else { + + point = new RoadControlPoint( road, position, "cp", 0, 0 ); + + point.mainObject = point.userData.road = road; + + this.addControlPointNew( road, point ); + + } + + return point; + } + + static addControlPointNew ( road: TvRoad, point: RoadControlPoint ) { + + // TODO: spline should take this responsibility + SceneService.add( point ); + + road.addControlPoint( point ); + + AppInspector.setInspector( RoadInspector, { + road: road, + controlPoint: point + } ); + + if ( road.spline.controlPoints.length > 1 ) { + this.updateGeometryAndRebuild( road ); + }; + + } + + static removeControlPoint ( road: TvRoad, cp: RoadControlPoint ) { + + road.spline.removeControlPoint( cp ); + + SceneService.remove( cp ); + + if ( road.spline.controlPoints.length < 1 ) { + + this.openDrive.gameObject.remove( road.gameObject ); + + // nothing to update, will throw error + // road.spline.update(); + + road.clearGeometries(); + + + } else if ( road.spline.controlPoints.length === 1 ) { + + this.openDrive.gameObject.remove( road.gameObject ); + + road.spline.update(); + + road.clearGeometries(); + + this.clearNodes( road ); + + } else if ( road.spline.controlPoints.length > 1 ) { + + this.updateGeometryAndRebuild( road ); + + } + } + + static clearNodes ( road: TvRoad ) { + + if ( road.startNode ) { + + SceneService.remove( road.startNode ); + + road.startNode = null; + + } + + if ( road.endNode ) { + + SceneService.remove( road.endNode ); + + road.endNode = null; + } + } + + static joinRoadNodes ( firstRoad: TvRoad, firstNode: RoadNode, secondRoad: TvRoad, secondNode: RoadNode ): TvRoad { + + const distance = 20; + + let laneSection: TvLaneSection, firstRoadS: number; + + let firstPoint: RoadControlPoint, lastPoint: RoadControlPoint; + + let secondPosition: TvPosTheta, thirdPosition: TvPosTheta, fourPosition: TvPosTheta, fivePosition: TvPosTheta; + + if ( firstNode.distance === "start" ) { + + firstRoadS = 0; + + laneSection = firstRoad.getLaneSectionAt( 0 ).cloneAtS( 0, firstRoadS ); + + secondPosition = firstRoad.startPosition().clone().rotateDegree( 180 ).moveForward( distance ); + thirdPosition = firstRoad.startPosition().clone().rotateDegree( 180 ).moveForward( distance + 20 ).addLateralOffset( 5 ); + + } else { + + firstRoadS = firstRoad.length - Maths.Epsilon + + laneSection = firstRoad.getLaneSectionAt( firstRoadS ).cloneAtS( 0, firstRoadS ); + + secondPosition = firstRoad.endPosition().clone().moveForward( distance ); + thirdPosition = firstRoad.endPosition().clone().moveForward( distance + 20 ).addLateralOffset( 5 ); + + } + + if ( secondNode.distance === "start" ) { + + fourPosition = secondRoad.startPosition().clone().rotateDegree( 180 ).moveForward( distance + 20 ).addLateralOffset( 5 ); + fivePosition = secondRoad.startPosition().clone().rotateDegree( 180 ).moveForward( distance ) + + } else { + + fourPosition = secondRoad.endPosition().clone().moveForward( distance + 20 ).addLateralOffset( 5 ); + fivePosition = secondRoad.endPosition().clone().moveForward( distance ); + + } + + // Make control points required for road geometry + const joiningRoad = this.openDrive.addDefaultRoad(); + + if ( firstRoad.hasType ) { + + const roadType = firstRoad.getRoadTypeAt( firstRoadS ); + + joiningRoad.setType( roadType.type, roadType.speed.max, roadType.speed.unit ); + + } else { + + joiningRoad.setType( TvRoadType.TOWN, 40 ); + + } + + + // remove lane section as will create copy of first road + joiningRoad.clearLaneSections(); + + joiningRoad.addLaneSectionInstance( laneSection ); + + if ( firstNode.distance === "start" ) { + + firstPoint = new RoadControlPoint( joiningRoad, firstRoad.spline.getFirstPoint().position.clone(), "cp", 0, 0 ) + + } else { + + firstPoint = new RoadControlPoint( joiningRoad, firstRoad.spline.getLastPoint().position.clone(), "cp", 0, 0 ) + + } + + if ( secondNode.distance === "start" ) { + + lastPoint = new RoadControlPoint( joiningRoad, secondRoad.spline.getFirstPoint().position.clone(), "cp", 0, 0 ); + + } else { + + lastPoint = new RoadControlPoint( joiningRoad, secondRoad.spline.getLastPoint().position.clone(), "cp", 0, 0 ); + + } + + const secondPoint = new RoadControlPoint( joiningRoad, secondPosition.toVector3(), "cp", 0, 0 ); + const thirdPoint = new RoadControlPoint( joiningRoad, thirdPosition.toVector3(), "cp", 0, 0 ); + const fourthPoint = new RoadControlPoint( joiningRoad, fourPosition.toVector3(), "cp", 0, 0 ); + const fifthPoint = new RoadControlPoint( joiningRoad, fivePosition.toVector3(), "cp", 0, 0 ); + + SceneService.add( firstPoint ); + SceneService.add( secondPoint ); + SceneService.add( thirdPoint ); + SceneService.add( fifthPoint ); + SceneService.add( fourthPoint ); + SceneService.add( lastPoint ); + + joiningRoad.spline.addControlPoint( firstPoint ); + joiningRoad.spline.addControlPoint( secondPoint ); + joiningRoad.spline.addControlPoint( thirdPoint ); + joiningRoad.spline.addControlPoint( fourthPoint ); + joiningRoad.spline.addControlPoint( fifthPoint ); + joiningRoad.spline.addControlPoint( lastPoint ); + + + joiningRoad.updateGeometryFromSpline(); + + ///////////////////////////////////////////////////////////////////////////////// + + + // TODO: add more logic to smoothen the geometry + + // for ( let i = 0; i < roadC.geometries.length; i++ ) { + + // const geometry = roadC.geometries[ i ]; + + // if ( geometry.geometryType === OdGeometryType.ARC ) { + + // const arcGeometry = geometry as OdArcGeometry; + + // if ( arcGeometry.attr_curvature < 0.01 ) { + + // // first geometry so update second point + // if ( i < 1 ) { + + // } + + // } + // } + + // } + + ///////////////////////////////////////////////////////////////////////////////// + // + // Update the road connections + + + this.makeRoadConnections( firstRoad, firstNode, secondRoad, secondNode, joiningRoad ); + + ///////////////////////////////////////////////////////////////////////////////// + + + TvMapBuilder.buildRoad( this.openDrive.gameObject, joiningRoad ); + + return joiningRoad; + } + + static makeRoadConnections ( firstRoad: TvRoad, firstNode: RoadNode, secondRoad: TvRoad, secondNode: RoadNode, joiningRoad: TvRoad ) { + + if ( firstNode.distance === "start" ) { + + // link will be negative as joining roaad will in opposite direction + + firstRoad.setPredecessor( "road", joiningRoad.id, TvContactPoint.START ); + firstRoad.getFirstLaneSection().lanes.forEach( lane => { + if ( lane.side !== TvLaneSide.CENTER ) lane.setPredecessor( -lane.id ); + } ); + + joiningRoad.setPredecessor( "road", firstRoad.id, TvContactPoint.START ); + joiningRoad.getFirstLaneSection().lanes.forEach( lane => { + if ( lane.side !== TvLaneSide.CENTER ) lane.setPredecessor( -lane.id ); + } ) + + } else { + + // links will be in same direction + + firstRoad.setSuccessor( "road", joiningRoad.id, TvContactPoint.START ); + firstRoad.getLastLaneSection().lanes.forEach( lane => { + if ( lane.side !== TvLaneSide.CENTER ) lane.setSuccessor( lane.id ); + } ); + + joiningRoad.setPredecessor( "road", firstRoad.id, TvContactPoint.END ); + joiningRoad.getFirstLaneSection().lanes.forEach( lane => { + if ( lane.side !== TvLaneSide.CENTER ) lane.setPredecessor( lane.id ); + } ) + + } + + if ( secondNode.distance === "start" ) { + + secondRoad.setPredecessor( "road", joiningRoad.id, TvContactPoint.END ); + secondRoad.getFirstLaneSection().lanes.forEach( lane => { + if ( lane.side !== TvLaneSide.CENTER ) lane.setPredecessor( lane.id ); + } ) + + joiningRoad.setSuccessor( "road", secondRoad.id, TvContactPoint.START ); + joiningRoad.getLastLaneSection().lanes.forEach( lane => { + if ( lane.side !== TvLaneSide.CENTER ) lane.setSuccessor( lane.id ); + } ) + + } else { + + secondRoad.setSuccessor( "road", joiningRoad.id, TvContactPoint.END ); + secondRoad.getLastLaneSection().lanes.forEach( lane => { + if ( lane.side !== TvLaneSide.CENTER ) lane.setSuccessor( -lane.id ); + } ) + + joiningRoad.setSuccessor( "road", secondRoad.id, TvContactPoint.END ); + joiningRoad.getLastLaneSection().lanes.forEach( lane => { + if ( lane.side !== TvLaneSide.CENTER ) lane.setSuccessor( -lane.id ); + } ) + + } + } + + static removeRoadConnections ( firstRoad: TvRoad, secondRoad: TvRoad ) { + + if ( firstRoad.predecessor && firstRoad.predecessor.elementId === secondRoad.id ) { + + firstRoad.predecessor = null; + + } + + if ( firstRoad.successor && firstRoad.successor.elementId === secondRoad.id ) { + + firstRoad.successor = null; + + } + + if ( secondRoad.predecessor && secondRoad.predecessor.elementId === firstRoad.id ) { + + secondRoad.predecessor = null; + + } + + if ( secondRoad.successor && secondRoad.successor.elementId === firstRoad.id ) { + + secondRoad.successor = null; + + } + + } +} \ No newline at end of file diff --git a/src/app/core/game-object.ts b/src/app/core/game-object.ts new file mode 100644 index 00000000..754ec8bc --- /dev/null +++ b/src/app/core/game-object.ts @@ -0,0 +1,134 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { ElementRef, Type } from '@angular/core'; +import * as THREE from 'three'; +import { BufferGeometry, Euler, Geometry, Material, Vector3 } from 'three'; +import { ITvObject, TvObjectType } from '../modules/tv-map/interfaces/i-tv-object'; + +export interface IComponent { + data: any; +} + +export class ComponentItem { + constructor ( public component: Type, public data: any ) { + } +} + +export interface IComponentEditor { + componentContent: ElementRef; +} + +export class Component { + +} + +export class Transform extends Component { + + public position: Vector3 = new Vector3; + public eulerAngles: Euler = new Euler; + public scale: Vector3 = new Vector3; + +} + +export class SomeNewComponent extends Component { + + public position: any; + +} + +export class GameObject extends THREE.Mesh implements ITvObject { + + OpenDriveType: TvObjectType; + + getType (): TvObjectType { + return this.OpenDriveType; + } + + public IsGameObject = true; + public detectRaycast = true; + private active: boolean; + private tag: string; + private transform: Transform; + private components: Component[] = []; + + constructor ( name?: string, geometry?: Geometry | BufferGeometry, material?: Material | Material[] ) { + + super( geometry, material ); + + this.name = name; + this.transform = new Transform; + this.transform.position = this.position; + this.transform.eulerAngles = this.rotation; + this.transform.scale = this.scale; + + this.userData.is_selectable = true; + + // AppService.engine.add( this ); + + } + + get Active () { + return this.active; + } + + set Active ( value ) { + this.active = value; + } + + get Tag () { + return this.tag; + } + + set Tag ( value ) { + this.tag = value; + } + + get Transform () { + return this.transform; + } + + set Transform ( value ) { + this.transform = value; + } + + public addComponent ( componentType: Type ): T { + + const obj = new componentType; + + this.components.push( obj ); + + return obj; + + } + + public getComponent ( componentType: Type ): Component { + + for ( let i = 0; i < this.components.length; i++ ) { + + const element: Component = this.components[i]; + + if ( element instanceof componentType ) { + + return element; + + } + } + + } + +} + +export class Cube extends GameObject { + + constructor ( width: number = 1, height: number = 1, length: number = 1 ) { + super( + 'Cube', + new THREE.BoxBufferGeometry( width, height, length ), + new THREE.MeshBasicMaterial( { color: 0x000000 } + ) + ); + } + +} diff --git a/src/app/core/guards/auth.guard.ts b/src/app/core/guards/auth.guard.ts new file mode 100644 index 00000000..b8632e5d --- /dev/null +++ b/src/app/core/guards/auth.guard.ts @@ -0,0 +1,40 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { Injectable } from '@angular/core'; +import { CanActivate, Router } from '@angular/router'; +import { AuthService } from '../services/auth.service'; +import { JwtService } from '../services/jwt.service'; +import { User } from '../models/user'; + +@Injectable( { + providedIn: 'root' +} ) +export class AuthGuard implements CanActivate { + + private user: User; + + constructor ( public auth: AuthService, private jwt: JwtService, public router: Router ) { + + this.auth.currentUser.subscribe( user => this.user = user ); + + } + + canActivate (): boolean { + + if ( this.jwt.hasToken() && !this.jwt.isTokenExpired() ) { + + return true; + + } else { + + this.router.navigate( [ '/sessions/signin' ] ); + + return false; + + } + + } + +} diff --git a/src/app/core/helpers/road-plan-helper.spec.ts b/src/app/core/helpers/road-plan-helper.spec.ts new file mode 100644 index 00000000..5b6a6be9 --- /dev/null +++ b/src/app/core/helpers/road-plan-helper.spec.ts @@ -0,0 +1,80 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { TestBed } from '@angular/core/testing'; +import { RoadPlanHelper } from './road-plan-helper'; +import { Vector3 } from 'three'; +import { Maths } from '../../utils/maths'; + + +describe( 'RoadPlanHelper test', () => { + + let helper: RoadPlanHelper; + + beforeEach( () => TestBed.configureTestingModule( {} ) ); + + beforeEach( () => { + helper = new RoadPlanHelper( null ); + } ); + + it( 'should give correct angle', () => { + + let p1, p2, p3: Vector3; + + p1 = new Vector3( 0, 0, 0 ); + p2 = new Vector3( 10, 0, 0 ); + + //////////////////////////////////////////////////////////////// + + p3 = new Vector3( 5, 5, 0 ); + + expect( Math.round( helper.calculateAngle( p1, p2, p3 ) ) ).toBe( 45 ); + + //////////////////////////////////////////////////////////////// + + p3 = new Vector3( 10, 10, 0 ); + + expect( Math.round( helper.calculateAngle( p1, p2, p3 ) ) ).toBe( 90 ); + + //////////////////////////////////////////////////////////////// + + p3 = new Vector3( 15, 5, 0 ); + + expect( Math.round( helper.calculateAngle( p1, p2, p3 ) ) ).toBe( 135 ); + + } ); + + it( 'should give correct area for triangle', () => { + + let p1, p2, p3: Vector3; + + const height = 10; + const base = 10; + + p1 = new Vector3( 0, 0, 0 ); + p2 = new Vector3( 0, height, 0 ); + p3 = new Vector3( base, height, 0 ); + + expect( Maths.areaOfTriangle( p1, p2, p3 ) ).toBe( 50 ); + + } ); + + it( 'should give correct height for triangle', () => { + + let p1, p2, p3: Vector3; + + const height = 8; + const base = 15; + + p1 = new Vector3( 0, 0, 0 ); + p2 = new Vector3( 0, height, 0 ); + p3 = new Vector3( base, height, 0 ); + + expect( Maths.heightOfTriangle( p1, p2, p3 ) ).toBe( 7.0588235294117645 ); + + + } ); + + +} ); diff --git a/src/app/core/helpers/road-plan-helper.ts b/src/app/core/helpers/road-plan-helper.ts new file mode 100644 index 00000000..5c851792 --- /dev/null +++ b/src/app/core/helpers/road-plan-helper.ts @@ -0,0 +1,233 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { AbstractShapeEditor } from '../editors/abstract-shape-editor'; +import { TvLineGeometry } from '../../modules/tv-map/models/geometries/tv-line-geometry'; +import { Object3D, Vector2, Vector3 } from 'three'; +import { TvMap } from '../../modules/tv-map/models/tv-map.model'; +import { TvRoad } from '../../modules/tv-map/models/tv-road.model'; +import { TvAbstractRoadGeometry } from '../../modules/tv-map/models/geometries/tv-abstract-road-geometry'; +import { TvArcGeometry } from '../../modules/tv-map/models/geometries/tv-arc-geometry'; +import { Maths } from '../../utils/maths'; +import { TvMapBuilder } from '../../modules/tv-map/builders/od-builder.service'; +import { TvMapSourceFile } from '../../modules/tv-map/services/tv-map-source-file'; +import { SceneService } from '../services/scene.service'; +import { TvSide } from '../../modules/tv-map/models/tv-common'; +import { TvSpiralGeometry } from 'app/modules/tv-map/models/geometries/tv-spiral-geometry'; + +export class RoadPlanHelper { + + constructor ( private shapeEditor: AbstractShapeEditor ) { + + } + + setControlPointUserData ( object: Object3D, roadId, geometry, index ): void { + + object.userData.roadId = roadId; + object.userData.geometry = geometry; + object.userData.index = index; + + } + + createRoadControlPoints ( openDrive: TvMap ): void { + + // create control points for all geometries + openDrive.roads.forEach( road => { + + road.geometries.forEach( geometry => { + + // if line + if ( geometry instanceof TvLineGeometry ) { + + const start = this.shapeEditor.addControlPoint( geometry.startV3 ); + const end = this.shapeEditor.addControlPoint( geometry.endV3 ); + + this.setControlPointUserData( start, road.id, geometry, 0 ); + this.setControlPointUserData( end, road.id, geometry, 1 ); + + } else if ( geometry instanceof TvArcGeometry ) { + + this.createArcControlPoints( road, geometry ); + } + + + } ); + + } ); + + } + + updateRoadGeometry ( point: Object3D, road: TvRoad, geometry: TvAbstractRoadGeometry, index: number ): void { + + if ( geometry instanceof TvLineGeometry ) { + + this.updateLineGeometry( point, road, geometry as TvLineGeometry, index ); + + } else if ( geometry instanceof TvArcGeometry ) { + + this.updateArcGeometry( point, road, geometry as TvArcGeometry, index ); + + } + + } + + createLineGeometry ( controlPoint1: Object3D, controlPoint2: Object3D, road: TvRoad ) { + + const start = controlPoint1.position; + const end = controlPoint2.position; + + const hdg = Math.atan2( end.y - start.y, end.x - start.x ); + const length = road.length = start.distanceTo( end ); + + const geometry = new TvLineGeometry( 0, start.x, start.y, hdg, length ); + // const geometry = road.addGeometryLine( 0, start.x, start.y, hdg, length ); + + this.setControlPointUserData( controlPoint1, road.id, geometry, 0 ); + this.setControlPointUserData( controlPoint2, road.id, geometry, 1 ); + + // OdBuilder.makeRoad( OdSourceFile.openDrive.gameObject, road ); + + return geometry; + } + + createSpiralGeometry ( controlPoint1: Object3D, controlPoint2: Object3D, road: TvRoad ) { + + const start = controlPoint1.position; + const end = controlPoint2.position; + + const hdg = Math.atan2( end.y - start.y, end.x - start.x ); + const length = road.length = start.distanceTo( end ); + + const geometry = new TvSpiralGeometry( 0, start.x, start.y, hdg, length, 0.005, -0.001 ); + // const geometry = road.addGeometryLine( 0, start.x, start.y, hdg, length ); + + this.setControlPointUserData( controlPoint1, road.id, geometry, 0 ); + this.setControlPointUserData( controlPoint2, road.id, geometry, 1 ); + + // OdBuilder.makeRoad( OdSourceFile.openDrive.gameObject, road ); + + return geometry; + } + + createArcGeometry ( cp1: Object3D, cp2: Object3D, cp3: Object3D, road: TvRoad ) { + + const start = cp1.position; + const middle = cp2.position; + const end = cp3.position; + + const hdg = Math.atan2( middle.y - start.y, middle.x - start.x ); + const length = road.length = start.distanceTo( end ); + + const height = Maths.heightOfTriangle( start, middle, end ); + const base = start.distanceTo( end ); + + const radius = ( height / 2 ) + ( ( base * base ) / ( 8 * height ) ); + let curvature = 1 / radius; + + // make the curvature negative for right side i.e. for clockwise + if ( Maths.direction( start, middle, end ) == TvSide.RIGHT ) curvature *= -1; + + const geometry = new TvArcGeometry( 0, start.x, start.y, hdg, length, curvature ); + + road.addGeometry( geometry ); + + this.setControlPointUserData( cp1, road.id, geometry, 0 ); + this.setControlPointUserData( cp2, road.id, geometry, 1 ); + this.setControlPointUserData( cp3, road.id, geometry, 2 ); + + geometry.cp1 = cp1; + geometry.cp2 = cp2; + geometry.cp3 = cp3; + + TvMapBuilder.buildRoad( TvMapSourceFile.openDrive.gameObject, road ); + } + + // TODO: move this in MathUtils + public calculateAngle ( p1: Vector3, p2: Vector3, p3: Vector3 ) { + + const A = new Vector2( p1.x, p1.y ); + const B = new Vector2( p2.x, p2.y ); + const C = new Vector2( p3.x, p3.y ); + + const a = A.distanceTo( B ); + const b = B.distanceTo( C ); + const c = A.distanceTo( C ); + + return Maths.Rad2Deg * Math.acos( ( a * a + b * b - c * c ) / ( 2 * a * b ) ); + } + + public updateArcGeometry ( point: Object3D, road: TvRoad, geometry: TvArcGeometry, index: number ) { + + let start: Vector3 = geometry.cp1.position; + let middle: Vector3 = geometry.cp2.position; + let end: Vector3 = geometry.cp3.position; + + const height = Maths.heightOfTriangle( start, middle, end ); + const base = start.distanceTo( end ); + + const radius = ( height / 2 ) + ( ( base * base ) / ( 8 * height ) ); + + let curvature = 1 / radius; + + // make the curvature negative for right side i.e. for clockwise + if ( Maths.direction( start, middle, end ) == TvSide.RIGHT ) curvature *= -1; + + geometry.x = start.x; + geometry.y = start.y; + geometry.curvature = curvature; + geometry.hdg = Math.atan2( middle.y - start.y, middle.x - start.x ); + geometry.length = road.length = start.distanceTo( end ); + + this.removeOldAndCreateNew( road ); + } + + private updateLineGeometry ( point: Object3D, road: TvRoad, geometry: TvLineGeometry, index: number ): void { + + let start: Vector3; + let end: Vector3; + + if ( index == 0 ) { + + start = point.position; + end = geometry.endV3; + + } else if ( index == 1 ) { + + start = geometry.startV3; + end = point.position; + + } + + geometry.x = start.x; + + geometry.y = start.y; + + geometry.hdg = Math.atan2( end.y - start.y, end.x - start.x ); + + geometry.length = road.length = start.distanceTo( end ); + + this.removeOldAndCreateNew( road ); + } + + private createArcControlPoints ( road: TvRoad, geometry: TvArcGeometry ) { + + const p1 = geometry.cp1 ? geometry.cp1.position : geometry.startV3; + const p2 = geometry.cp2 ? geometry.cp2.position : geometry.middleV3; + const p3 = geometry.cp3 ? geometry.cp3.position : geometry.endV3; + + const start = geometry.cp1 = this.shapeEditor.addControlPoint( p1 ); + const middle = geometry.cp2 = this.shapeEditor.addControlPoint( p2 ); + const end = geometry.cp3 = this.shapeEditor.addControlPoint( p3 ); + + this.setControlPointUserData( start, road.id, geometry, 0 ); + this.setControlPointUserData( middle, road.id, geometry, 1 ); + this.setControlPointUserData( end, road.id, geometry, 2 ); + + } + + private removeOldAndCreateNew ( road: TvRoad ) { + SceneService.removeWithChildren( road.gameObject, true ); + TvMapBuilder.buildRoad( TvMapSourceFile.openDrive.gameObject, road ); + } +} diff --git a/src/app/core/input.ts b/src/app/core/input.ts new file mode 100644 index 00000000..917358a2 --- /dev/null +++ b/src/app/core/input.ts @@ -0,0 +1,37 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { EventEmitter } from '@angular/core'; + +export class KeyboardInput { + + public static keyUp = new EventEmitter(); + public static keyDown = new EventEmitter(); + + public static isShiftKeyDown: boolean; + + private static isKeyDown: boolean; + private static keyCode: number; + + static OnKeyDown ( e: KeyboardEvent ) { + + this.isKeyDown = true; + this.keyCode = e.keyCode; + + this.keyUp.emit( e ); + + this.isShiftKeyDown = e.shiftKey; + } + + static OnKeyUp ( e: KeyboardEvent ) { + + this.isKeyDown = false; + this.keyCode = e.keyCode; + + this.keyDown.emit( e ); + + this.isShiftKeyDown = e.shiftKey; + } + +} \ No newline at end of file diff --git a/src/app/core/inspector.ts b/src/app/core/inspector.ts new file mode 100644 index 00000000..38b093f1 --- /dev/null +++ b/src/app/core/inspector.ts @@ -0,0 +1,55 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { EventEmitter, Type } from '@angular/core'; +import { ComponentItem, IComponent } from './game-object'; + +export class AppInspector { + + public static inspectorChanged = new EventEmitter(); + public static inspectorCleared = new EventEmitter(); + + /** + * fired when new instance of inspector is created + */ + public static inspectorCreated = new EventEmitter(); + + private static componentItem: ComponentItem; + + public static currentInspector: Type; + public static currentInspectorData: any; + + public static setInspector ( component: Type, data: any ) { + + if ( !component ) this.clear(); + + if ( !component ) return; + + this.currentInspector = component; + + this.currentInspectorData = data; + + this.componentItem = new ComponentItem( component, data ); + + this.inspectorChanged.emit( this.componentItem ); + + } + + public static getInspector (): ComponentItem { + + return this.componentItem; + + } + + static clear () { + + this.currentInspector = null; + + this.currentInspectorData = null; + + this.inspectorCleared.emit( null ); + + } + +} diff --git a/src/app/core/interceptors/http.token.interceptor.ts b/src/app/core/interceptors/http.token.interceptor.ts new file mode 100644 index 00000000..c933fb53 --- /dev/null +++ b/src/app/core/interceptors/http.token.interceptor.ts @@ -0,0 +1,29 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { Injectable, Injector } from '@angular/core'; +import { HttpEvent, HttpInterceptor, HttpHandler, HttpRequest } from '@angular/common/http'; +import { Observable } from 'rxjs'; +import { JwtService } from '../services/jwt.service'; + +@Injectable() +export class HttpTokenInterceptor implements HttpInterceptor { + constructor ( private jwtService: JwtService ) { } + + intercept ( req: HttpRequest, next: HttpHandler ): Observable> { + const headersConfig = { + 'Content-Type': 'application/json', + 'Accept': 'application/json' + }; + + const token = this.jwtService.getToken(); + + if ( token ) { + headersConfig[ 'Authorization' ] = `Bearer ${ token }`; + } + + const request = req.clone( { setHeaders: headersConfig } ); + return next.handle( request ); + } +} \ No newline at end of file diff --git a/src/app/core/interceptors/time-out-interceptor.ts b/src/app/core/interceptors/time-out-interceptor.ts new file mode 100644 index 00000000..fda1d005 --- /dev/null +++ b/src/app/core/interceptors/time-out-interceptor.ts @@ -0,0 +1,19 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { HttpInterceptor, HttpRequest, HttpHandler, HttpEvent } from '@angular/common/http'; +import { Observable } from 'rxjs'; +import { timeout } from 'rxjs/operators'; + +export class TimeOutInterceptor implements HttpInterceptor { + + private defaultTimeout = 5000; + + intercept ( req: HttpRequest, next: HttpHandler ): Observable> { + + const timeoutValue = Number( req.headers.get( 'timeout' ) ) || this.defaultTimeout; + + return next.handle( req ).pipe( timeout( timeoutValue ) ); + } +} diff --git a/src/app/core/interfaces/shortcuts.ts b/src/app/core/interfaces/shortcuts.ts new file mode 100644 index 00000000..272e3d29 --- /dev/null +++ b/src/app/core/interfaces/shortcuts.ts @@ -0,0 +1,17 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +export interface IKeyboardShortcut { + + check ( e: KeyboardEvent ): boolean; + + execute (): void; +} + +export abstract class AbstractKeyboardShortcut implements IKeyboardShortcut { + + abstract check ( e: KeyboardEvent ): boolean; + abstract execute (): void; + +} \ No newline at end of file diff --git a/src/app/core/models/file.ts b/src/app/core/models/file.ts new file mode 100644 index 00000000..1ce05765 --- /dev/null +++ b/src/app/core/models/file.ts @@ -0,0 +1,26 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { FileUtils } from 'app/services/file-utils'; + +export class IFile { + + constructor ( + public name?: string, + public path?: string, + public contents?: string, + public type?: string, + public online?: boolean, + public updatedAt?: Date + ) { + } + + get directory () { + return FileUtils.getDirectoryFromPath( this.path ); + } + + get filename () { + return FileUtils.getFilenameFromPath( this.path ); + } +} diff --git a/src/app/core/models/metadata.model.ts b/src/app/core/models/metadata.model.ts new file mode 100644 index 00000000..41f245e8 --- /dev/null +++ b/src/app/core/models/metadata.model.ts @@ -0,0 +1,19 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +export interface Metadata { + guid: string; + path: string; + isFolder?: boolean; + importer: string; + data: any; + preview?: any; +} + +export interface DynamicMeta { + guid: string; + path: string; + importer: string; + data: T; +} diff --git a/src/app/core/models/prop-instance.model.ts b/src/app/core/models/prop-instance.model.ts new file mode 100644 index 00000000..1acb9c24 --- /dev/null +++ b/src/app/core/models/prop-instance.model.ts @@ -0,0 +1,14 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { Object3D } from 'three'; + +export class PropInstance { + + constructor ( public guid: string, public object: Object3D ) { + + } + + +} diff --git a/src/app/core/models/prop-model.model.ts b/src/app/core/models/prop-model.model.ts new file mode 100644 index 00000000..c245aacc --- /dev/null +++ b/src/app/core/models/prop-model.model.ts @@ -0,0 +1,15 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { Object3D, Vector3 } from 'three'; + +/** + * Prop class instance holds info about the prop + */ +export class PropModel { + + constructor ( public guid: string, public rotationVariance: Vector3, public scaleVariance: Vector3 ) { + + } +} diff --git a/src/app/core/models/user.ts b/src/app/core/models/user.ts new file mode 100644 index 00000000..c4793fed --- /dev/null +++ b/src/app/core/models/user.ts @@ -0,0 +1,6 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +export class User { +} diff --git a/src/app/core/pipes/max.pipe.ts b/src/app/core/pipes/max.pipe.ts new file mode 100644 index 00000000..26697b28 --- /dev/null +++ b/src/app/core/pipes/max.pipe.ts @@ -0,0 +1,16 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { Pipe, PipeTransform } from '@angular/core'; + +@Pipe({ + name: 'max' +}) +export class MaxPipe implements PipeTransform { + + transform(value: any, args?: any): any { + return null; + } + +} diff --git a/src/app/core/pipes/min.pipe.ts b/src/app/core/pipes/min.pipe.ts new file mode 100644 index 00000000..63c12bf2 --- /dev/null +++ b/src/app/core/pipes/min.pipe.ts @@ -0,0 +1,23 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { Pipe, PipeTransform } from '@angular/core'; + +@Pipe( { + name: 'min' +} ) +export class MinPipe implements PipeTransform { + + // transform(value: any, args?: any): any { + // return null; + // } + + transform ( value: number, min: number ): number { + + var rtuenr = Math.max( value, min );; + + return rtuenr; + + } +} diff --git a/src/app/core/pipes/search.pipe.ts b/src/app/core/pipes/search.pipe.ts new file mode 100644 index 00000000..c7b3bad0 --- /dev/null +++ b/src/app/core/pipes/search.pipe.ts @@ -0,0 +1,27 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { Pipe, PipeTransform } from '@angular/core'; + +@Pipe( { + name: 'search' +} ) +export class SearchPipe implements PipeTransform { + + transform ( items: string[], query: string ): string[] { + + if ( !items ) return []; + + if ( !query ) return items; + + query = query.toLowerCase(); + + return items.filter( it => { + + return it.toLowerCase().includes( query ); + + } ); + } + +} diff --git a/src/app/core/player.service.ts b/src/app/core/player.service.ts new file mode 100644 index 00000000..678beda9 --- /dev/null +++ b/src/app/core/player.service.ts @@ -0,0 +1,106 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { EventEmitter, Injectable } from '@angular/core'; +import { Time } from './time'; +import { ToolManager } from './tools/tool-manager'; + +export interface PlayerUpdateData { + time: number; + delta: number; +} + +@Injectable( { + providedIn: 'root' +} ) +export class PlayerService { + + playerStarted = new EventEmitter(); + playerResumed = new EventEmitter(); + playerTick = new EventEmitter(); + playerPaused = new EventEmitter(); + playerStopped = new EventEmitter(); + + private prevTime: number; + private handle: number; + private paused: boolean; + + constructor () { + } + + play () { + + ToolManager.disable(); + + this.prevTime = performance.now(); + + if ( this.paused ) { + + this.playerResumed.emit(); + + } else { + + this.playerStarted.emit(); + + } + + this.paused = false; + + const self = this; + + ( function tick () { + + self.handle = requestAnimationFrame( tick ); + + self.tick(); + + }() ); + } + + pause () { + + this.playerPaused.emit(); + + this.paused = true; + + cancelAnimationFrame( this.handle ); + } + + stop () { + + cancelAnimationFrame( this.handle ); + + this.playerStopped.emit(); + + this.paused = false; + + Time.reset(); + + ToolManager.enable(); + } + + tick () { + + const time = performance.now(); + + try { + + Time.deltaTime = Time.fixedDeltaTime / 1000; + Time.deltaTimeInMs = Time.fixedDeltaTime; + Time.time += Time.fixedDeltaTime; + Time.frameCount++; + Time.realTimeSinceStart = time; + + this.playerTick.emit( { time: time, delta: Time.fixedDeltaTime } ); + + } catch ( e ) { + + console.error( ( e.message || e ), ( e.stack || '' ) ); + + } + + this.prevTime = time; + + } +} diff --git a/src/app/core/selection.ts b/src/app/core/selection.ts new file mode 100644 index 00000000..df3a3402 --- /dev/null +++ b/src/app/core/selection.ts @@ -0,0 +1,64 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { GameObject, Transform } from './game-object'; +import { EventEmitter } from '@angular/core'; + +export class ObjectSelection { + + static readonly selectionChange = new EventEmitter(); + + private static activeObject: THREE.Object3D; + private static activeGameObject: GameObject; + private static activeTransform: Transform; + + static get ActiveObject () { + + return this.activeObject + + }; + + static set ActiveObject ( object ) { + + this.activeObject = object + this.activeTransform.position = object.position; + + this.selectionChange.emit( this.activeGameObject ); + + }; + + static get ActiveGameObject () { + + return this.activeGameObject; + + } + + static set ActiveGameObject ( gameObject ) { + + + this.activeGameObject = gameObject + this.activeTransform = gameObject.Transform + + this.selectionChange.emit( this.activeGameObject ); + + } + + static get ActiveTransform () { + + return this.activeTransform; + + } + + static removeActive (): any { + + this.activeObject = null; + this.activeGameObject = null; + this.activeTransform = null; + + this.selectionChange.emit( null ); + } + + + +} \ No newline at end of file diff --git a/src/app/core/services/IEngine.ts b/src/app/core/services/IEngine.ts new file mode 100644 index 00000000..25f6a5af --- /dev/null +++ b/src/app/core/services/IEngine.ts @@ -0,0 +1,20 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import * as THREE from 'three'; + +export interface IEngine { + + add ( object: THREE.Object3D, raycasting?: boolean ): void; + + remove ( object: THREE.Object3D, raycasting?: boolean ): void; + + focus ( object: THREE.Object3D ): void; + + reset (): void; + + select ( object: THREE.Object3D ): void; + + deselect ( object: THREE.Object3D ): void; +} \ No newline at end of file diff --git a/src/app/core/services/abstract-reader.ts b/src/app/core/services/abstract-reader.ts new file mode 100644 index 00000000..c49533db --- /dev/null +++ b/src/app/core/services/abstract-reader.ts @@ -0,0 +1,70 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +export abstract class AbstractReader { + + public static readAsOptionalArray ( items: any, callbackFn: ( xml: any, count: number ) => void ) { + + if ( items != null ) { + + if ( Array.isArray( items ) ) { + + for ( const item of items ) { + + callbackFn( item, items.length ); + + } + + } else { + + callbackFn( items, 1 ); + + } + + } + + } + + public static readAsOptionalElement ( xml: any, callbackFn: ( xml: any ) => void ) { + + if ( xml != null ) { + + callbackFn( xml ); + + } + + } + + public readAsOptionalArray ( items: any, callbackFn: ( xml: any, count: number ) => void ) { + + if ( items != null ) { + + if ( Array.isArray( items ) ) { + + for ( const item of items ) { + + callbackFn( item, items.length ); + + } + + } else { + + callbackFn( items, 1 ); + + } + + } + + } + + public readAsOptionalElement ( xml: any, callbackFn: ( xml: any ) => void ) { + + if ( xml != null ) { + + callbackFn( xml ); + + } + + } +} diff --git a/src/app/core/services/api.service.ts b/src/app/core/services/api.service.ts new file mode 100644 index 00000000..5de445c2 --- /dev/null +++ b/src/app/core/services/api.service.ts @@ -0,0 +1,48 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { Injectable } from '@angular/core'; +import { environment } from '../../../environments/environment'; +import { HttpClient, HttpParams } from '@angular/common/http'; +import { Observable } from 'rxjs'; + +import { throwError } from 'rxjs'; +import { catchError } from 'rxjs/operators'; + +@Injectable() +export class ApiService { + + constructor ( + private http: HttpClient + ) { } + + private formatErrors ( error: any ) { + return throwError( error.error ); + } + + get ( path: string, params: HttpParams = new HttpParams() ): Observable { + return this.http.get( `${ environment.api_url }${ path }`, { params } ) + .pipe( catchError( this.formatErrors ) ); + } + + put ( path: string, body: object = {} ): Observable { + return this.http.put( + `${ environment.api_url }${ path }`, + JSON.stringify( body ) + ).pipe( catchError( this.formatErrors ) ); + } + + post ( path: string, body: object = {} ): Observable { + return this.http.post( + `${ environment.api_url }${ path }`, + JSON.stringify( body ) + ).pipe( catchError( this.formatErrors ) ); + } + + delete ( path ): Observable { + return this.http.delete( + `${ environment.api_url }${ path }` + ).pipe( catchError( this.formatErrors ) ); + } +} diff --git a/src/app/core/services/app-info.service.ts b/src/app/core/services/app-info.service.ts new file mode 100644 index 00000000..6997975c --- /dev/null +++ b/src/app/core/services/app-info.service.ts @@ -0,0 +1,16 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { ElectronService } from 'ngx-electron'; + +export class AppInfo { + + static electron: ElectronService; + + static get isElectronApp (): boolean { + + return this.electron.isElectronApp; + + } +} \ No newline at end of file diff --git a/src/app/core/services/app.service.ts b/src/app/core/services/app.service.ts new file mode 100644 index 00000000..c1195da8 --- /dev/null +++ b/src/app/core/services/app.service.ts @@ -0,0 +1,67 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { Injectable } from '@angular/core'; +import { ElectronService } from 'ngx-electron'; +import { EventSystem } from '../../events/event-system.service'; +import { ThreeService } from '../../modules/three-js/three.service'; +import { SnackBar } from '../../services/snack-bar.service'; +import { AuthService } from './auth.service'; +import { SceneService } from './scene.service'; +import { AssetLoaderService } from 'app/services/asset-loader.service'; +import { FileService } from 'app/services/file.service'; +import { AppInfo } from './app-info.service'; +import { SceneExporterService } from 'app/services/scene-exporter.service'; + +@Injectable( { + providedIn: 'root' +} ) +export class AppService { + + static homeUrl = ''; + static loginUrl = '/sessions/signin'; + + static eventSystem: EventSystem; + static three: ThreeService; + static electron: ElectronService; + static assets: AssetLoaderService; + static file: FileService; + static exporter: SceneExporterService; + + constructor ( + private eventSystem: EventSystem, + private electron: ElectronService, + private scene: SceneService, + private snackBar: SnackBar, + private three: ThreeService, + public auth: AuthService, + public assets: AssetLoaderService, + public files: FileService, + sceneExporter: SceneExporterService, + ) { + + AppService.eventSystem = eventSystem; + AppService.three = three; + AppService.electron = electron; + AppService.assets = assets; + AppService.file = files; + + AppService.exporter = sceneExporter; + + AppInfo.electron = electron; + } + + static get isElectronApp (): boolean { + + return this.electron.isElectronApp; + + } + + public exit () { + + this.electron.remote.app.exit( 0 ); + + } + +} diff --git a/src/app/core/services/auth.service.ts b/src/app/core/services/auth.service.ts new file mode 100644 index 00000000..87cd926d --- /dev/null +++ b/src/app/core/services/auth.service.ts @@ -0,0 +1,131 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { Injectable } from '@angular/core'; +import { ApiService } from './api.service'; + +import { map } from 'rxjs/operators'; +import { JwtService } from './jwt.service'; +import { BehaviorSubject, Observable } from 'rxjs'; +import { User } from '../models/user'; + +@Injectable( { + providedIn: 'root' +} ) +export class AuthService { + + private currentUserSubject: BehaviorSubject; + public currentUser: Observable; + + constructor ( private api: ApiService, private jwt: JwtService ) { + + this.currentUserSubject = new BehaviorSubject( JSON.parse( localStorage.getItem( 'currentUser' ) ) ); + this.currentUser = this.currentUserSubject.asObservable(); + + } + + public get email (): string { + + if ( window.localStorage.getItem( 'email' ) ) { + return window.localStorage.getItem( 'email' ); + } + + return null; + } + + public refresh () { + + return this.api.post( '/auth/refresh' ).pipe( map( response => { + + if ( response && response.token ) { + + this.jwt.saveToken( response.token ); + + window.localStorage.setItem( 'currentUser', JSON.stringify( response ) ); + + this.currentUserSubject.next( response ); + + } + + return response; + + } ) ); + + } + + public logout () { + + window.localStorage.clear(); + + this.jwt.destroyToken(); + + } + + public login ( email: string, password: string ) { + + return this.api.post( '/auth/login', { email, password } ) + + .pipe( map( response => { + + // login successful if there's a jwt token in the response + if ( response && response.token ) { + + this.jwt.saveToken( response.token ); + + window.localStorage.setItem( 'currentUser', JSON.stringify( response ) ); + window.localStorage.setItem( 'email', email ); + + this.currentUserSubject.next( response ); + + } + + return response; + + } ) ); + + } + + public register ( + name: string, + email: string, + password: string, + confirmPassword: string, + agreed: boolean + ) { + + return this.api.post( '/auth/register', { + name, + email, + password, + password_confirmation: confirmPassword, + agreed + } ) + + .pipe( map( response => { + + // login successful if there's a jwt token in the response + if ( response && response.token ) { + + this.jwt.saveToken( response.token ); + + window.localStorage.setItem( 'currentUser', JSON.stringify( response ) ); + window.localStorage.setItem( 'email', email ); + + this.currentUserSubject.next( response ); + + } + + return response; + + } ) ); + + } + + public forgotPassword ( email: string ) { + + return this.api.post( '/auth/forgot-password', { email } ); + + } + +} diff --git a/src/app/core/services/drawing.service.ts b/src/app/core/services/drawing.service.ts new file mode 100644 index 00000000..79bba3fc --- /dev/null +++ b/src/app/core/services/drawing.service.ts @@ -0,0 +1,231 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { Injectable } from '@angular/core'; +import { ThreeService } from 'app/modules/three-js/three.service'; +import * as THREE from 'three'; +import { Vector2 } from 'three'; +import { COLOR } from 'app/shared/utils/colors.service'; + +@Injectable( { + providedIn: 'root' +} ) +export class DrawingService { + + ORANGE_COLOR = 0xFF4500; + + DEFAULT_BOX_COLOR = 0xff0000; + HIGHTLIGHT_BOX_COLOR = 0x00ff00; + CROSSHAIR_COLOR = 0x00ff00; + DASHED_LINE_COLOR = 0xff00ff; + + lastBoundingBox: THREE.Object3D; + lastSolidBox: THREE.Mesh; + boxMaterial = new THREE.MeshBasicMaterial( { color: this.ORANGE_COLOR, opacity: 0.2, transparent: true } ); + lineDashedMaterial = new THREE.LineDashedMaterial( { color: this.DASHED_LINE_COLOR, linewidth: 10, dashSize: 3, gapSize: 3 } ); + + boxes: Map = new Map(); + + lastLine: THREE.Line; + + constructor ( private engineService: ThreeService ) { + + // this.editor.undoAddedAnnotation.subscribe( e => this.engineService.remove( this.lastBoundingBox, true ) ); + + } + + getLastSolidBox (): THREE.Mesh { + return this.lastSolidBox; + } + + getLastBoundingBox (): THREE.Object3D { + return this.lastBoundingBox; + } + + public drawSolidBox ( start: THREE.Vector3, end: THREE.Vector3, color: string = null ): THREE.Object3D { + + this.engineService.remove( this.lastBoundingBox, true ); + + var box = this.getDimensions( start, end ); + + var boxGeometry = new THREE.PlaneBufferGeometry(); + + var boxMaterial = (new THREE.MeshBasicMaterial()).copy( this.boxMaterial ); + + boxMaterial.color.set( color ? color : '#000000' ); + + this.lastSolidBox = new THREE.Mesh( boxGeometry, boxMaterial ); + + this.lastSolidBox.geometry.scale( box.width, box.height, 1 ); + + if ( end.y < start.y ) box.height *= -1; + if ( end.x < start.x ) box.width *= -1; + + this.lastSolidBox.position.set( start.x + (box.width / 2), start.y + (box.height / 2), 0.1 ); + + this.engineService.add( this.lastSolidBox, true ); + + this.addBoxInfo( this.lastSolidBox, start, end, box ); + + // this.updateControlPoints( start, end, this.lastBox ); + + this.drawBoundingBox( start, end ); + + this.drawDeletButton( start.x, start.y, this.lastSolidBox ); + + return this.lastSolidBox; + } + + public drawBoundingBox ( start: THREE.Vector3, end: THREE.Vector3, color: string = null ): THREE.Object3D { + + var box = this.getDimensions( start, end ); + + var geometry = new THREE.PlaneBufferGeometry( box.width, box.height ); + var edges = new THREE.EdgesGeometry( geometry ); + var material = new THREE.LineBasicMaterial( { color: color ? color : 0xff00ff } ); + + this.lastBoundingBox = new THREE.LineSegments( edges, material ); + + // this.lastBoundingBox.computeLineDistances(); + + geometry.scale( box.width, box.height, 1 ); + + if ( end.y < start.y ) box.height *= -1; + if ( end.x < start.x ) box.width *= -1; + + // this.lastBoundingBox.position.set( start.x + ( box.width / 2 ), start.y + ( box.height / 2 ), 0.1 ); + + this.lastSolidBox.add( this.lastBoundingBox ); + + // this.engineService.add( this.lastBoundingBox, true ); + + this.addBoxInfo( this.lastBoundingBox, start, end, box ); + + return this.lastBoundingBox; + } + + private addBoxInfo ( object: THREE.Object3D, start: THREE.Vector3, end: THREE.Vector3, box: any ): any { + + object.userData.is_annotation = true; + object.userData.type = 'box'; + + object.userData.width = box.width; + object.userData.height = box.height; + + object.userData.startX = start.x; + object.userData.startY = start.y; + + object.userData.endX = end.x; + object.userData.endY = end.y; + } + + public drawLine ( vertices: Vector2[] ) { + + this.engineService.remove( this.lastLine, true ); + + let material = new THREE.LineBasicMaterial( { color: 0xFF0000 } ); + + var geometry = new THREE.Geometry(); + + for ( let i = 0; i < vertices.length; i++ ) { + + const item = vertices[i]; + + const position = new THREE.Vector3( item.x, item.y, 0.2 ); + + geometry.vertices.push( position ); + } + + this.lastLine = new THREE.Line( geometry, material ); + + for ( let i = 0; i < vertices.length; i++ ) { + + const item = vertices[i]; + + const position = new THREE.Vector3( item.x, item.y, 0.2 ); + + this.drawSpritePoint( position, this.lastLine ); + } + + this.engineService.add( this.lastLine, true ); + } + + private lastButtonSprite: THREE.Sprite; + + public drawDeletButton ( x, y, parent ) { + + // this.engineService.remove( this.lastButtonSprite, true ); + + // var button = new DeleteButton( x, y, parent ); + + // this.engineService.add( button.object, true ); + + // this.lastButtonSprite = button.object; + } + + drawSpritePoint ( position: any, parent: any ) { + + var dotGeometry = new THREE.Geometry(); + dotGeometry.vertices.push( new THREE.Vector3( 0, 0, 0 ) ); + + var dotMaterial = new THREE.PointsMaterial( { + size: 15, + sizeAttenuation: true, + map: this.diskSprite, + alphaTest: 0.5, + transparent: true, + color: COLOR.MAGENTA + } ); + + // dotMaterial.color.setHSL( 1.0, 0.3, 0.7 ); + + var object = new THREE.Points( dotGeometry, dotMaterial ); + + object.position.set( position.x, position.y, position.z ); + + // this.engineService.add( object ); + this.lastLine.add( object ); + + // extra data + object.userData.is_button = true; + object.userData.is_control_point = true; + + return object; + + } + + drawPoint ( position: any, parent: any ): any { + + var geometry = new THREE.CircleBufferGeometry( 5, 32 ); + var object = new THREE.Mesh( geometry, new THREE.MeshBasicMaterial( { color: 0x00FF00 } ) ); + + object.position.set( position.x, position.y, position.z + 0.2 ); + + // this.engineService.add( object ); + this.lastLine.add( object ); + + // extra data + object.userData.is_button = true; + object.userData.is_control_point = true; + + return object; + } + + private diskSprite = new THREE.TextureLoader().load( 'assets/disc.png' ); + + private getDimensions ( p1, p2 ): any { + + let width = Math.abs( p1.x - p2.x ); + let height = Math.abs( p1.y - p2.y ); + + width = Math.max( width, 10 ); + height = Math.max( height, 10 ); + + return { + width: width, + height: height + }; + } + +} diff --git a/src/app/core/services/editor-events.ts b/src/app/core/services/editor-events.ts new file mode 100644 index 00000000..8974168f --- /dev/null +++ b/src/app/core/services/editor-events.ts @@ -0,0 +1,16 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { EventEmitter, Output } from '@angular/core'; + +export class EditorEvents { + + @Output() static onZoomIn = new EventEmitter(); + @Output() static onZoomOut = new EventEmitter(); + @Output() static onZoomReset = new EventEmitter(); + + @Output() static sceneRendered = new EventEmitter(); + @Output() static sceneCreated = new EventEmitter(); + +} diff --git a/src/app/core/services/file-api.service.ts b/src/app/core/services/file-api.service.ts new file mode 100644 index 00000000..307058c9 --- /dev/null +++ b/src/app/core/services/file-api.service.ts @@ -0,0 +1,41 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { Injectable } from '@angular/core'; +import { ApiService } from './api.service'; +import { IFile } from '../models/file'; + +@Injectable( { + providedIn: 'root' +} ) +export class FileApiService { + + constructor ( private api: ApiService ) { } + + // get file + getFile ( name: string, type: string ) { + + return this.api.get( `/files?type=${ type }&name=${ name }` ); + + } + + // get list of files + getFileList ( type: string ) { + + return this.api.get( `/files/list?type=${ type }` ); + + } + + save ( file: IFile ) { + + return this.api.put( '/files', { file } ); + + } + + ping () { + + return this.api.get( `/ping` ); + + } +} diff --git a/src/app/core/services/input.service.ts b/src/app/core/services/input.service.ts new file mode 100644 index 00000000..d0ef24d1 --- /dev/null +++ b/src/app/core/services/input.service.ts @@ -0,0 +1,21 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { Injectable, Output, EventEmitter } from '@angular/core'; + +@Injectable( { + providedIn: 'root' +} ) +export class InputService { + + public isShiftKeyDown: boolean; + + @Output() shiftKeyDown = new EventEmitter(); + + constructor () { + + this.shiftKeyDown.subscribe( value => this.isShiftKeyDown = value ); + + } +} diff --git a/src/app/core/services/jwt.service.ts b/src/app/core/services/jwt.service.ts new file mode 100644 index 00000000..d4b32867 --- /dev/null +++ b/src/app/core/services/jwt.service.ts @@ -0,0 +1,50 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { Injectable } from '@angular/core'; + +import { JwtHelperService } from '@auth0/angular-jwt'; + +@Injectable() +export class JwtService { + + private KEY = 'jwtToken'; + + private helper: JwtHelperService; + + constructor () { + this.helper = new JwtHelperService(); + } + + get token () { return this.getToken(); } + + get decodedToken () { return this.helper.decodeToken( this.token ); } + + get expirationDate () { return this.helper.getTokenExpirationDate( this.token ); } + + getToken (): string { + return window.localStorage[ this.KEY ]; + } + + saveToken ( token: string ) { + window.localStorage[ this.KEY ] = token; + } + + destroyToken () { + window.localStorage.removeItem( 'jwtToken' ); + } + + hasToken () { + return ( + this.token != null && + this.token !== undefined && + this.token !== '' && + this.token !== 'null' + ); + } + + isTokenExpired (): boolean { + return this.helper.isTokenExpired( this.token ); + } +} diff --git a/src/app/core/services/open-drive-api.service.ts b/src/app/core/services/open-drive-api.service.ts new file mode 100644 index 00000000..119cde5f --- /dev/null +++ b/src/app/core/services/open-drive-api.service.ts @@ -0,0 +1,25 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { Injectable } from '@angular/core'; +import { IFile } from '../models/file'; +import { Observable } from 'rxjs'; +import { FileApiService } from './file-api.service'; + +@Injectable( { + providedIn: 'root' +} ) +export class OpenDriveApiService { + + constructor ( private files: FileApiService ) { + } + + getOpenDrive ( name: string ): Observable { + return this.files.getFile( name, 'tv-map' ); + } + + getAll (): Observable { + return this.files.getFileList( 'tv-map' ); + } +} diff --git a/src/app/core/services/open-scenario-api.service.ts b/src/app/core/services/open-scenario-api.service.ts new file mode 100644 index 00000000..84aebfdc --- /dev/null +++ b/src/app/core/services/open-scenario-api.service.ts @@ -0,0 +1,29 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { Injectable } from '@angular/core'; +import { IFile } from '../models/file'; +import { Observable } from 'rxjs'; +import { FileApiService } from './file-api.service'; + +@Injectable( { + providedIn: 'root' +} ) +export class OpenScenarioApiService { + + constructor ( private files: FileApiService ) { + } + + getOpenScenario ( name: string ): Observable { + + return this.files.getFile( name, 'open-scenario' ); + + } + + saveOpenScenario ( file: IFile ): Observable { + + return this.files.save( file ); + + } +} diff --git a/src/app/core/services/picking-helper.service.ts b/src/app/core/services/picking-helper.service.ts new file mode 100644 index 00000000..6e84070c --- /dev/null +++ b/src/app/core/services/picking-helper.service.ts @@ -0,0 +1,88 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { MouseButton, PointerEventData } from 'app/events/pointer-event-data'; +import { TvLane } from 'app/modules/tv-map/models/tv-lane'; +import { ObjectTypes } from 'app/modules/tv-map/models/tv-common'; +import { Object3D, Vector3 } from 'three'; +import { BaseControlPoint } from 'app/modules/three-js/objects/control-point'; + +export class PickingHelper { + + public static checkControlPointInteraction ( event: PointerEventData, tag: string, maxDistance = 0.5 ): BaseControlPoint { + + let hasInteracted = false; + + let currentMin = Number.MAX_VALUE; + let controlPoint: BaseControlPoint = null; + + for ( let i = 0; i < event.intersections.length; i++ ) { + + const intersection = event.intersections[ i ]; + + if ( event.button === MouseButton.LEFT && intersection.object && intersection.object[ 'tag' ] == tag ) { + + hasInteracted = true; + + if ( intersection.distanceToRay < currentMin && intersection.distanceToRay < maxDistance ) { + + currentMin = intersection.distanceToRay; + + controlPoint = intersection.object as BaseControlPoint; + + } + } + } + + return controlPoint; + } + + public static findNearest ( position: Vector3, objects: T[], maxDistance = 0.5 ): T { + + let nearestDistance = Number.MAX_VALUE; + let nearestObject: T = null; + + for ( let i = 0; i < objects.length; i++ ) { + + const object = objects[ i ]; + + const distance = position.distanceTo( object.position ); + + if ( distance < nearestDistance && distance < maxDistance ) { + + nearestDistance = distance; + + nearestObject = object; + + } + } + + return nearestObject; + } + + public static checkLaneObjectInteraction ( event: PointerEventData, tag?: string ): TvLane { + + const laneTag = tag || ObjectTypes.LANE; + + let lane = null; + + for ( let i = 0; i < event.intersections.length; i++ ) { + + const intersection = event.intersections[ i ]; + + if ( intersection.object && intersection.object[ 'tag' ] === laneTag ) { + + if ( intersection.object.userData.lane ) { + + lane = intersection.object.userData.lane as TvLane; + + break; + } + } + } + + return lane; + } + +} \ No newline at end of file diff --git a/src/app/core/services/scene.service.ts b/src/app/core/services/scene.service.ts new file mode 100644 index 00000000..43592142 --- /dev/null +++ b/src/app/core/services/scene.service.ts @@ -0,0 +1,185 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { Injectable } from '@angular/core'; +import * as THREE from 'three'; +import { Mesh, Object3D } from 'three'; +import { GameObject } from '../game-object'; + +@Injectable( { + providedIn: 'root' +} ) +export class SceneService { + + public static scene: THREE.Scene = new THREE.Scene; + public static objects: Object3D[] = []; + public static sceneHelpers: Object3D[] = []; + + constructor () { + + + } + + public static get children () { + return this.scene.children; + } + + static addHelper ( object: Object3D ): void { + + this.scene.add( object ); + + this.sceneHelpers.push( object ); + } + + static removeHelper ( object: Object3D ): void { + + if ( object == null ) return; + + this.scene.remove( object ); + + for ( let i = 0; i < this.sceneHelpers.length; i++ ) { + + const element = this.sceneHelpers[ i ]; + + if ( element.id === object.id ) { + + this.sceneHelpers.splice( i, 1 ); + + break; + } + } + } + + static add ( object: Object3D, raycasting: boolean = true ): void { + + this.scene.add( object ); + + if ( raycasting ) this.objects.push( object ); + + } + + static deselect ( object: Object3D ): void { + + throw new Error( 'Not implemented' ); + + } + + static focus ( object: Object3D ): void { + + throw new Error( 'Not implemented' ); + + } + + static remove ( object: Object3D, raycasting: boolean = true ): void { + + if ( object == null ) return; + + this.scene.remove( object ); + + if ( raycasting ) { + + for ( let i = 0; i < this.objects.length; i++ ) { + + const element = this.objects[ i ]; + + if ( element.id === object.id ) { + + this.objects.splice( i, 1 ); + + break; + + } + } + } + } + + static reset (): void { + + while ( this.scene.children.length > 0 ) { + + let obj = this.scene.children[ 0 ]; + + this.scene.remove( obj ); + + this.disposeHierarchy( obj, SceneService.disposeNode ); + + } + + this.objects = []; + } + + static select ( object: Object3D ): void { + + throw new Error( 'Not implemented' ); + + } + + public static disposeHierarchy ( node, callback ) { + + for ( let i = node.children.length - 1; i >= 0; i-- ) { + + const child = node.children[ i ]; + + this.disposeHierarchy( child, callback ); + + callback( child ); + + } + } + + static removeWithChildren ( object: Object3D, raycasting: boolean = false ) { + + while ( object.children.length > 0 ) { + + this.disposeNode( object.children[ 0 ] ); + + } + + this.remove( object, raycasting ); + } + + private static disposeNode ( node ) { + + if ( node instanceof Mesh ) { + + node.parent.remove( node ); + node.parent = undefined; + + if ( node.geometry ) { + + node.geometry.dispose(); + + } + + let material: any = node.material; + + if ( material ) { + + if ( material.map ) material.map.dispose(); + if ( material.lightMap ) material.lightMap.dispose(); + if ( material.bumpMap ) material.bumpMap.dispose(); + if ( material.normalMap ) material.normalMap.dispose(); + if ( material.specularMap ) material.specularMap.dispose(); + if ( material.envMap ) material.envMap.dispose(); + + material.dispose(); + } + + } else if ( node instanceof Object3D ) { + + node.parent.remove( node ); + node.parent = undefined; + + } else if ( node instanceof GameObject ) { + + node.parent.remove( node ); + node.parent = undefined; + + } else { + + throw new Error( 'unknown type' ); + + } + } +} diff --git a/src/app/core/shapes/ParametricPolynomial.ts b/src/app/core/shapes/ParametricPolynomial.ts new file mode 100644 index 00000000..b641e4e8 --- /dev/null +++ b/src/app/core/shapes/ParametricPolynomial.ts @@ -0,0 +1,44 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { ARC_SEGMENTS, PARACUBICFACTOR } from "./spline-config"; +import { Vector3, BufferGeometry, BufferAttribute, Line, LineBasicMaterial } from 'three'; +class ParametricPolynomial { + private curveType; + private mesh; + constructor ( private points ) { + const geometry = new BufferGeometry(); + geometry.addAttribute( 'position', new BufferAttribute( new Float32Array( ARC_SEGMENTS * 3 ), 3 ) ); + this.curveType = 'cubic'; + this.mesh = new Line( geometry, new LineBasicMaterial( { color: 0x0000ff, opacity: 0.85 } ) ); + this.mesh.castShadow = true; + this.mesh.renderOrder = 1; + this.mesh.frustumCulled = false; + } + update () { + const position = this.mesh.geometry.attributes.position; + const point = new Vector3(); + for ( let i = 0; i < ARC_SEGMENTS; i++ ) { + const t = i / ( ARC_SEGMENTS - 1 ); + this.getPoint( t, point ); + position.setXYZ( i, point.x, point.y, point.z ); + } + position.needsUpdate = true; + } + getPoint ( t, rettarget ) { + const retpoint = rettarget || new Vector3(); + const p1 = this.points[ 0 ]; + const p2 = this.points[ 3 ]; + const t1 = new Vector3().subVectors( this.points[ 1 ], p1 ).multiplyScalar( PARACUBICFACTOR ); + const t2 = new Vector3().subVectors( p2, this.points[ 2 ] ).multiplyScalar( PARACUBICFACTOR ); + // tslint:disable-next-line: one-variable-per-declaration + const s = t, s2 = s * s, s3 = s2 * s; + const h1 = new Vector3().setScalar( 2 * s3 - 3 * s2 + 1 ).multiply( p1 ); + const h2 = new Vector3().setScalar( -2 * s3 + 3 * s2 ).multiply( p2 ); + const h3 = new Vector3().setScalar( s3 - 2 * s2 + s ).multiply( t1 ); + const h4 = new Vector3().setScalar( s3 - s2 ).multiply( t2 ); + retpoint.copy( h1 ).add( h2 ).add( h3 ).add( h4 ); + return retpoint; + } +} diff --git a/src/app/core/shapes/PolyLine.ts b/src/app/core/shapes/PolyLine.ts new file mode 100644 index 00000000..a48074be --- /dev/null +++ b/src/app/core/shapes/PolyLine.ts @@ -0,0 +1,57 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { Vector3, BufferGeometry, BufferAttribute, Line, LineBasicMaterial } from 'three'; +import { COLOR } from 'app/shared/utils/colors.service'; +import { MAX_CTRL_POINTS } from "./spline-config"; +import { BaseControlPoint } from 'app/modules/three-js/objects/control-point'; + +export class PolyLine { + + curveType; + + mesh; + + constructor ( public points: BaseControlPoint[] ) { + + const geometry = new BufferGeometry(); + + geometry.addAttribute( 'position', new BufferAttribute( new Float32Array( MAX_CTRL_POINTS * 3 ), 3 ) ); + + this.curveType = 'polyline'; + + this.mesh = new Line( geometry, new LineBasicMaterial( { color: COLOR.WHITE, opacity: 0.35, linewidth: 2 } ) ); + + this.mesh.castShadow = true; + + this.mesh.renderOrder = 1; + + this.mesh.frustumCulled = false; + } + + addPoint ( point: BaseControlPoint ) { + + this.points.push( point ); + + } + + // Should be called once after curve control points get updated + update () { + + if ( this.points.length <= 1 ) return; + + const position = this.mesh.geometry.attributes.position; + + // fill the whole buffer + for ( let i = 0; i < MAX_CTRL_POINTS; i++ ) { + + const point = i >= this.points.length ? this.points[ this.points.length - 1 ] : this.points[ i ]; + + position.setXYZ( i, point.position.x, point.position.y, point.position.z ); + + } + + position.needsUpdate = true; + } +} diff --git a/src/app/core/shapes/Spiral.ts b/src/app/core/shapes/Spiral.ts new file mode 100644 index 00000000..9645baff --- /dev/null +++ b/src/app/core/shapes/Spiral.ts @@ -0,0 +1,33 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { ARC_SEGMENTS } from "./spline-config"; +import { BufferGeometry, BufferAttribute, Line, LineBasicMaterial } from 'three'; +class Spiral { + curveType; + mesh; + constructor ( private points ) { + const geometry = new BufferGeometry(); + geometry.addAttribute( 'position', new BufferAttribute( new Float32Array( ARC_SEGMENTS * 3 ), 3 ) ); + this.curveType = 'spiral'; + this.mesh = new Line( geometry, new LineBasicMaterial( { color: 0x00ff00, opacity: 0.35 } ) ); + this.mesh.castShadow = true; + this.mesh.renderOrder = 1; + this.mesh.frustumCulled = false; + } + update (): void { + // // determine params + // var sd = SPIRAL.vec2Angle( this.points[ 1 ].x - this.points[ 0 ].x, this.points[ 1 ].z - this.points[ 0 ].z ); + // var ed = SPIRAL.vec2Angle( this.points[ 3 ].x - this.points[ 2 ].x, this.points[ 3 ].z - this.points[ 2 ].z ); + // var k, dk, L, iter; + // [ k, dk, L, iter ] = SPIRAL.buildClothoid( this.points[ 0 ].x * 100, this.points[ 0 ].z * 100, sd, this.points[ 3 ].x * 100, this.points[ 3 ].z * 100, ed ); + // //console.log(k,dk,L); + // var spiralarc = SPIRAL.clothoid_1( this.points[ 0 ].x * 100, this.points[ 0 ].z * 100, this.points[ 0 ].y, sd, k, k + dk * L, L, this.points[ 3 ].y, ARC_SEGMENTS - 1 ) + // var position = this.mesh.geometry.attributes.position; + // for ( var i = 0; i < ARC_SEGMENTS; i++ ) { + // position.setXYZ( i, spiralarc[ i ][ 0 ] / 100, spiralarc[ i ][ 2 ], spiralarc[ i ][ 1 ] / 100 ); + // } + // position.needsUpdate = true; + } +} diff --git a/src/app/core/shapes/SplineData.ts b/src/app/core/shapes/SplineData.ts new file mode 100644 index 00000000..a41a547f --- /dev/null +++ b/src/app/core/shapes/SplineData.ts @@ -0,0 +1,104 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { Vector2, Vector4 } from 'three'; + +class SplineData { + + public xb: Vector4; + + public yb: Vector4; + + constructor ( p0: Vector2, p1: Vector2, p2: Vector2, p3: Vector2 ) { + + this.xb = new Vector4( p0.x, p1.x, p2.x, p3.x ); + + this.yb = new Vector4( p0.y, p1.y, p2.y, p3.y ); + + } + +} + +// Optimised for t=0.5 +function Split ( spline: Vector4 ) { + + const q0 = ( spline.x + spline.y ) * 0.5; // x + y / 2 + + const q1 = ( spline.y + spline.z ) * 0.5; // y + z / 2 + + const q2 = ( spline.z + spline.w ) * 0.5; // z + w / 2 + + const r0 = ( q0 + q1 ) * 0.5; // x + 2y + z / 4 + + const r1 = ( q1 + q2 ) * 0.5; // y + 2z + w / 4 + + const s0 = ( r0 + r1 ) * 0.5; // q0 + 2q1 + q2 / 4 = x+y + 2(y+z) + z+w / 8 = x + 3y + 3z + w + + const sx = spline.x; // support aliasing + + const sw = spline.w; + + return [ new Vector4( sx, q0, r0, s0 ), new Vector4( s0, r1, q2, sw ) ]; + +} + +export function HermiteSpline ( p0: Vector2, p1: Vector2, v0: Vector2, v1: Vector2 ): SplineData { + + const pb1 = new Vector2().copy( v0 ).multiplyScalar( 1.0 / 3.0 ).add( p0 ); + + const pb2 = new Vector2().copy( v1 ).multiplyScalar( -1.0 / 3.0 ).add( p1 ); + + return new SplineData( p0, pb1, pb2, p1 ); + +} + +function sqr ( x ) { return x * x; } + +function LengthEstimate ( s: SplineData ) { + + // Our convex hull is p0, p1, p2, p3, so p0_p3 is our minimum possible length, and p0_p1 + p1_p2 + p2_p3 our maximum. + + const d03 = sqr( s.xb.x - s.xb.w ) + sqr( s.yb.x - s.yb.w ); + + const d01 = sqr( s.xb.x - s.xb.y ) + sqr( s.yb.x - s.yb.y ); + + const d12 = sqr( s.xb.y - s.xb.z ) + sqr( s.yb.y - s.yb.z ); + + const d23 = sqr( s.xb.z - s.xb.w ) + sqr( s.yb.z - s.yb.w ); + + let minLength = Math.sqrt( d03 ); + + let maxLength = Math.sqrt( d01 ) + Math.sqrt( d12 ) + Math.sqrt( d23 ); + + minLength *= 0.5; + + maxLength *= 0.5; + + return [ minLength + maxLength, maxLength - minLength ]; + +} + +export function Length ( s: SplineData, maxError /*float*/ ) { + + let length, error; + + [ length, error ] = LengthEstimate( s ); + + if ( error > maxError ) { + + const s0 = new SplineData( new Vector2(), new Vector2(), new Vector2(), new Vector2() ); + + const s1 = new SplineData( new Vector2(), new Vector2(), new Vector2(), new Vector2() ); + + [ s0.xb, s1.xb ] = Split( s.xb ); + + [ s0.yb, s1.yb ] = Split( s.yb ); + + return Length( s0, maxError ) + Length( s1, maxError ); + + } + + return length; + +} \ No newline at end of file diff --git a/src/app/core/shapes/TangentLine.ts b/src/app/core/shapes/TangentLine.ts new file mode 100644 index 00000000..e693634f --- /dev/null +++ b/src/app/core/shapes/TangentLine.ts @@ -0,0 +1,109 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { Vector3, BufferGeometry, BufferAttribute, LineSegments, LineBasicMaterial } from 'three'; +import { COLOR } from 'app/shared/utils/colors.service'; +import { MAX_CTRL_POINTS, CURVE_Y } from "./spline-config"; + +export class TangentLine { + + public radiuses: any[]; + + public mesh: LineSegments; + + private hdgs: any[] = []; + + private geometry: BufferGeometry; + + private curveType: string = 'tangent'; + + constructor ( public points: Vector3[] ) { + + this.geometry = new BufferGeometry(); + + this.geometry.addAttribute( 'position', new BufferAttribute( new Float32Array( MAX_CTRL_POINTS * 2 * 3 ), 3 ) ); + + this.mesh = new LineSegments( this.geometry, new LineBasicMaterial( { color: COLOR.DARKBLUE, opacity: 0.35 } ) ); + + this.mesh.castShadow = true; + + this.mesh.renderOrder = 3; + + this.mesh.frustumCulled = false; + + } + + update ( hdgs: any[], points?: any[] ) { + + this.hdgs = hdgs; + + const position = this.geometry.attributes.position as BufferAttribute; + + const pos = new Vector3(); + + for ( let i = 0; i < points.length; i++ ) { + + pos.set( Math.cos( this.hdgs[ i ][ 0 ] ), Math.sin( this.hdgs[ i ][ 0 ] ), CURVE_Y ) + + .multiplyScalar( this.hdgs[ i ][ 1 ] ) + + .add( points[ i ] ); + + position.setXYZ( i * 2 + 0, pos.x, pos.y, pos.z ); + + pos.set( Math.cos( this.hdgs[ i ][ 0 ] ), Math.sin( this.hdgs[ i ][ 0 ] ), CURVE_Y ) + + .multiplyScalar( -this.hdgs[ i ][ 2 ] ) + + .add( points[ i ] ); + + position.setXYZ( i * 2 + 1, pos.x, pos.y, pos.z ); + + } + + for ( let i = points.length; i < MAX_CTRL_POINTS; i++ ) { + + position.setXYZ( i * 2 + 0, pos.x, pos.y, pos.z ); + + position.setXYZ( i * 2 + 1, pos.x, pos.y, pos.z ); + + } + + position.needsUpdate = true; + + } + + updateOneSegment ( idx, point ) { + + const position = this.geometry.attributes.position as BufferAttribute; + + const pos1 = new Vector3(); + + const pos2 = new Vector3(); + + pos1.set( Math.cos( this.hdgs[ idx ][ 0 ] ), Math.sin( this.hdgs[ idx ][ 0 ] ), CURVE_Y ) + + .multiplyScalar( this.hdgs[ idx ][ 1 ] ) + + .add( point ); + + position.setXYZ( idx * 2 + 0, pos1.x, pos1.y, pos1.z ); + + pos2.set( Math.cos( this.hdgs[ idx ][ 0 ] ), Math.sin( this.hdgs[ idx ][ 0 ] ), CURVE_Y ) + + .multiplyScalar( -this.hdgs[ idx ][ 2 ] ) + + .add( point ); + + position.setXYZ( idx * 2 + 1, pos2.x, pos2.y, pos2.z ); + + position.needsUpdate = true; + + return [ pos1, pos2 ]; + + } + +} + + diff --git a/src/app/core/shapes/abstract-spline.ts b/src/app/core/shapes/abstract-spline.ts new file mode 100644 index 00000000..80350ac4 --- /dev/null +++ b/src/app/core/shapes/abstract-spline.ts @@ -0,0 +1,183 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import * as THREE from 'three'; +import { Vector2, Vector3 } from 'three'; +import { SceneService } from '../services/scene.service'; + +import { TvAbstractRoadGeometry } from 'app/modules/tv-map/models/geometries/tv-abstract-road-geometry'; +import { EventEmitter } from '@angular/core'; +import { RoadControlPoint } from 'app/modules/three-js/objects/road-control-point'; +import { BaseControlPoint } from 'app/modules/three-js/objects/control-point'; + +export abstract class AbstractSpline { + + abstract type: string; + + abstract init (): void; + + abstract hide (): void; + + abstract show (): void; + + abstract update (): void; + + abstract exportGeometries (): TvAbstractRoadGeometry[]; + + // tcboxgeometry = new THREE.BoxBufferGeometry( 0.7, 0.3, 0.7 ); + protected controlPointAdded = new EventEmitter(); + protected controlPointRemoved = new EventEmitter(); + + public controlPoints: BaseControlPoint[] = []; + + protected meshAddedInScene: boolean; + + constructor ( public closed = true, public tension = 0.5 ) { + + this.init(); + + } + + get scene () { return SceneService.scene; } + + get controlPointPositions (): Vector3[] { + return this.controlPoints.map( point => point.position ); + } + + clear () { + + throw new Error( "Method not implemented." ); + + } + + addControlPoint ( cp: BaseControlPoint ) { + + this.controlPoints.push( cp ); + + } + + addControlPointAtNew ( position: Vector3 ): RoadControlPoint { + + throw new Error( "method not implemented" ); + + } + + getFirstPoint () { + + return this.controlPoints[ 0 ]; + + } + + getSecondPoint () { + + try { + + return this.controlPoints[ 1 ]; + + } catch ( error ) { + + } + + } + + getLastPoint () { + + return this.controlPoints[ this.controlPoints.length - 1 ]; + + } + + getSecondLastPoint () { + + try { + + return this.controlPoints[ this.controlPoints.length - 2 ]; + + } catch ( error ) { + + } + + } + + removeControlPoint ( cp: BaseControlPoint ) { + + const index = this.controlPoints.findIndex( p => p.id === cp.id ); + + this.controlPoints.splice( index, 1 ); + } + + getArcParams ( p1: Vector2, p2: Vector2, dir1: Vector2, dir2: Vector2 ): number[] { + + const distance = p1.distanceTo( p2 ); + + const normalisedDotProduct = new THREE.Vector2() + .copy( dir1 ) + .normalize() + .dot( new THREE.Vector2().copy( dir2 ).normalize() ); + + const alpha = Math.acos( normalisedDotProduct ); + + const r = distance / 2 / Math.sin( alpha / 2 ); + + const length = r * alpha; + + const ma = dir1.x, mb = dir1.y, mc = -mb, md = ma; + + const det = 1 / ( ma * md - mb * mc ); + + const mia = det * md, mib = -mb * det, mic = -mc * det, mid = ma * det; + + const p2proj = new THREE.Vector2().subVectors( p2, p1 ); + + p2proj.set( p2proj.x * mia + p2proj.y * mic, p2proj.x * mib + p2proj.y * mid ); + + return [ r, alpha, length, Math.sign( p2proj.y ) ]; + } + + updateControlPoint ( cp: BaseControlPoint, id: number, cpobjidx?: any ) { + + cp[ 'tag' ] = "cp"; cp[ 'tagindex' ] = id; + + cp.userData.is_button = true; + cp.userData.is_control_point = true; + cp.userData.is_selectable = true; + + if ( cpobjidx == undefined ) + this.controlPoints.push( cp ); + else + this.controlPoints.splice( cpobjidx, 0, cp ); + } + + /** + * + * @deprecated dont use this make another internal for any sub class + * @param tag + * @param id + * @param cpobjidx + */ + createControlPoint ( tag: "cp" | "tpf" | "tpb", id: number, cpobjidx?: any ): BaseControlPoint { + + // let cptobj = new THREE.Mesh( this.tcboxgeometry, new THREE.MeshLambertMaterial( { color: Math.random() * 0xffffff } ) ); + let controlPointObject = new RoadControlPoint( null, new Vector3(), tag, id, cpobjidx ); + + controlPointObject[ 'tag' ] = tag; controlPointObject[ 'tagindex' ] = id; + + controlPointObject.userData.is_button = true; + controlPointObject.userData.is_control_point = true; + controlPointObject.userData.is_selectable = true; + + this.scene.add( controlPointObject ); + + if ( cpobjidx == undefined ) + this.controlPoints.push( controlPointObject ); + else + this.controlPoints.splice( cpobjidx, 0, controlPointObject ); + + this.controlPointAdded.emit( controlPointObject ); + + return controlPointObject; + } + +} + + diff --git a/src/app/core/shapes/arc-spline-curve.spec.ts b/src/app/core/shapes/arc-spline-curve.spec.ts new file mode 100644 index 00000000..423f7301 --- /dev/null +++ b/src/app/core/shapes/arc-spline-curve.spec.ts @@ -0,0 +1,37 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { TestBed } from '@angular/core/testing'; +import { Vector3 } from 'three'; +import { CustomSplineCurve } from './arc-spline-curve'; + + +describe( 'ArcSplineCurve test', () => { + + let curve: CustomSplineCurve; + + beforeEach( () => TestBed.configureTestingModule( {} ) ); + + beforeEach( () => { + curve = new CustomSplineCurve(); + } ); + + it( 'should give correct angle', () => { + + // tslint:disable-next-line: one-variable-per-declaration + let p1, p2, p3: Vector3; + + p1 = new Vector3( 0, 0, 0 ); + p2 = new Vector3( 10, 0, 0 ); + p3 = new Vector3( 20, 10, 0 ); + + curve.addPoints( [ p1, p2, p3 ] ); + + const geomtries = curve.geometries; + + + } ); + + +} ); diff --git a/src/app/core/shapes/arc-spline-curve.ts b/src/app/core/shapes/arc-spline-curve.ts new file mode 100644 index 00000000..cd11fa87 --- /dev/null +++ b/src/app/core/shapes/arc-spline-curve.ts @@ -0,0 +1,167 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { Curve, Vector3 } from 'three'; +import { Maths } from 'app/utils/maths'; +import { TvLineGeometry } from 'app/modules/tv-map/models/geometries/tv-line-geometry'; +import { TvArcGeometry } from 'app/modules/tv-map/models/geometries/tv-arc-geometry'; +import { TvSide } from 'app/modules/tv-map/models/tv-common'; +import { TvPosTheta } from '../../modules/tv-map/models/tv-pos-theta'; +import { TvAbstractRoadGeometry } from 'app/modules/tv-map/models/geometries/tv-abstract-road-geometry'; + +export class CustomSplineCurve { + + private mGeometries: TvAbstractRoadGeometry[] = []; + + constructor ( private points: Vector3[] = [] ) { + } + + get geometries () { + return this.mGeometries; + } + + addPoint ( p: Vector3 ) { + + this.points.push( p ); + + this.compute(); + + } + + addPoints ( points: Vector3[] ) { + + points.forEach( point => { + this.points.push( point ); + } ); + + this.compute(); + } + + compute () { + + if ( this.points.length < 3 ) return; + + const reversed = [ ...this.points ].reverse(); + + for ( let i = 0; i < reversed.length; i++ ) { + + // break if we don't have 3 points remaining + if ( ( reversed.length - i ) < 3 ) break; + + const p1 = reversed[ i + 2 ]; + const p2 = reversed[ i + 1 ]; + const p3 = reversed[ i ]; + + const x = p1.x; + const y = p1.y; + + const firstSegment = p1.distanceTo( p2 ); + const secondSegment = p2.distanceTo( p3 ); + + // hdg of first line segment + const hdg = Math.atan2( p2.y - p1.y, p2.x - p1.x ); + + // hdg of second line segment + const hdg2 = Math.atan2( p3.y - p2.y, p3.x - p2.x ); + + let arcStartingPosition: Vector3; + + // line + arc + if ( firstSegment >= secondSegment ) { + + const t = ( firstSegment - secondSegment ) / firstSegment; + + const lineLength = firstSegment - secondSegment; + + arcStartingPosition = Maths.linearInterpolationVector3( p1, p2, t ); + + const line = new TvLineGeometry( 0, x, y, hdg, lineLength ); + + const end = line.end; + + const arc = this.createArcGeometry( line.s2, end.x, end.y, arcStartingPosition, hdg, p3, hdg2, p2 ); + + this.mGeometries.push( line ); + this.mGeometries.push( arc ); + + // arc + line + } else if ( secondSegment > firstSegment ) { + + const t = firstSegment / secondSegment; + + const lineLength = secondSegment - firstSegment; + + const lineStartingPosition = Maths.linearInterpolationVector3( p2, p3, t ); + + const arc = this.createArcGeometry( 0, x, y, p1, hdg, lineStartingPosition, hdg2, p2 ); + + const pos = new TvPosTheta(); + + arc.getCoords( arc.s2, pos ); + + const line = new TvLineGeometry( arc.s2, pos.x, pos.y, pos.hdg, lineLength ); + + this.mGeometries.push( arc ); + this.mGeometries.push( line ); + } + } + } + + private createArcGeometry ( s: number, x: number, y: number, p1: Vector3, hdg: number, p3: Vector3, hdg2: number, p2: Vector3 ) { + + const res = this.getRadius( p1, hdg, p3, hdg2 ); + + const radius = res.radius; + const center = res.center; + + let curvature = 1 / radius; + + const doCurvature = Maths.angle( center, p1, p3 ); + + const arcLength = doCurvature * radius; + + // make the curvature negative for right side i.e. for clockwise + const side = Maths.direction( p2, p3, p1 ); + if ( side === TvSide.RIGHT ) curvature *= -1; + + return new TvArcGeometry( s, x, y, hdg, arcLength, curvature ); + } + + private getRadius ( A: Vector3, line1Hdg: number, C: Vector3, line2Hdg: number ) { + + const B = new Vector3( + A.x + Math.cos( line1Hdg + Maths.M_PI_2 ) * 1, + A.y + Math.sin( line1Hdg + Maths.M_PI_2 ) * 1 + ); + + const D = new Vector3( + C.x + Math.cos( line2Hdg + Maths.M_PI_2 ) * 1, + C.y + Math.sin( line2Hdg + Maths.M_PI_2 ) * 1 + ); + + const center = Maths.lineLineIntersection( A, B, C, D ); + + const radius = A.distanceTo( center ); + + return { + radius, + center + }; + + } + +} + + +export class BSplineCurve3 extends Curve { + + constructor ( private points?: Vector3[], private closed?: boolean, private curveType?: string, private tension?: number ) { + super(); + } + + getPoint ( t: number ): Vector3 { + + return new Vector3(); + } +} \ No newline at end of file diff --git a/src/app/core/shapes/auto-spline.spec.ts b/src/app/core/shapes/auto-spline.spec.ts new file mode 100644 index 00000000..ef36a579 --- /dev/null +++ b/src/app/core/shapes/auto-spline.spec.ts @@ -0,0 +1,547 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +// describe( 'blah test', () => { + +// it( 'should run', () => { + + +// // taking example values for a,b,c,d +// // +// const p0 = new Vector3( 0, 0, 0 ); // first point +// const p1 = new Vector3( 100, 0, 0 ); // first tangent +// const p2 = new Vector3( 0, 100, 0 ); // second tangent +// const p3 = new Vector3( 100, 100, 0 ); // second point + +// // const t1 = Math.atan2( p1.y, p1.x ); +// // const t2 = Math.atan2( p2.y, p2.x ); + +// const s1 = 0; +// const s2 = 300; + +// const length = s2 - s1; + +// const pp0 = 3.5; +// const pp1 = 15; +// const pd0 = 0; +// const pd1 = 0; + +// let a = pp0; +// let b = pd0; +// let c = ( -3 * pp0 ) + ( 3 * pp1 ) + ( -2 * pd0 ) + ( -1 * pd1 ); +// let d = ( 2 * pp0 ) + ( -2 * pp1 ) + ( 1 * pd0 ) + ( 1 * pd1 ); + +// b /= length; +// c /= length * length; +// d /= length * length * length; + +// console.log( a, b, c, d ); + +// // console.log( t1, t2 ); + +// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +// // const B = new Matrix4().set( +// // 1, 0, 0, 0, +// // 1, 1, 1, 1, +// // 0, 1, 0, 0, +// // 0, 1, 2, 3 +// // ); + +// // const B_inverse = new Matrix4().getInverse( B ); + +// // console.log( B ); +// // console.log( B_inverse ); + +// // // const A_coeffs = new Matrix4().set( +// // // 0, 0, 0, 0, +// // // 0, 0, 0, 0, +// // // 0.01, 0, 0, 0, +// // // 0, 0, 0, 0 +// // // ); + +// // const G_x = new Matrix4().set( +// // 100, 0, 0, 0, +// // 0, 0, 0, 0, +// // 1, 0, 0, 0, +// // 0, 0, 0, 0, +// // ); + +// // const A = B_inverse.multiply( G_x ); + +// // console.log( "====================================================" ); +// // console.log( B ); +// // console.log( B_inverse ); +// // console.log( G_x ); +// // console.log( "========= coeffs ===================================" ); +// // console.log( A ); + +// // return; + +// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +// // const B = new Matrix4().set( +// // 0, 0, 0, 1, +// // 0.125, 0.25, 0.5, 1, +// // 0.75, 1, 1, 0, +// // 1, 1, 1, 1 +// // ); + +// // const B_inverse = new Matrix4().getInverse( B ); + +// // const G_x = new Matrix4().set( +// // 0, 0, 0, 0, +// // 10, 0, 0, 0, +// // 0, 0, 0, 0, +// // 0, 0, 0, 0 +// // ); + +// // const A = B_inverse.multiply( G_x ) + +// // console.log( A ); + +// // console.log( B.multiply( A ) ); + +// // return; + +// // const p0 = new Vector3( 0, 0, 0 ); +// // const p1 = new Vector3( 50, 0, 0 ); +// // const p2 = new Vector3( 50, 100, 0 ); +// // const p3 = new Vector3( 100, 100, 0 ); + +// // // const h1 = new Vector4( 2, -2, 1, 1 ); +// // // const h2 = new Vector4( -3, -3, -2, 1 ); +// // // const h3 = new Vector4( 0, 0, 1, 0 ); +// // // const h4 = new Vector4( 1, 0, 0, 0 ); + +// // // const hh1 = new Vector4().set(2, -2, 1, 1).applyMatrix4(); + +// // const m4 = new Matrix4(); + +// // // hermite curve +// // // m4.set( +// // // 2, -2, 1, 1, +// // // -3, 3, -2, 1, +// // // 0, 0, 1, 0, +// // // 1, 0, 0, 0 +// // // ); + +// // // // hermite curve +// // // m4.set( +// // // 2, -2, 1, 1, +// // // -3, 3, -2, 1, +// // // 0, 0, 1, 0, +// // // 1, 0, 0, 0 +// // // ); + +// // // m4.set( +// // // 0, 0, 0, 1, +// // // 1, 1, 1, 1, +// // // 0, 0, 1, 0, +// // // 3, 2, 1, 0 +// // // ); + +// // // curve with midpoint +// // m4.set( +// // -4, 0, -4, 4, +// // 8, -4, 6, -4, +// // -5, 4, -2, 1, +// // 1, 0, 0, 0 +// // ); + +// // const m5 = new Matrix4().set( +// // 0, 0, 0, 0, +// // 1, 0, 0, 0, +// // 0, 0, 0, 0, +// // 0, 0, 0, 0 +// // ); + +// // const m6 = new Matrix4().set( +// // 1, 0, 0, 0, +// // 0, 0, 0, 0, +// // 0, 0, 0, 0, +// // 0, 0, 0, 0 +// // ); + + +// // console.log( m4.multiply( m5 ) ); + +// // const points = [ p0, p1, p2, p3 ]; + +// // const curve = ( new ParametricPolynomial( points ) ); + +// // curve.update(); + +// // console.log( curve.getCoefficents() ); +// // console.log( curve.getCoefficent() ); + +// } ); + +// } ) + +// // describe( 'AutoSpline test', () => { + +// // let curve: AutoSpline; + +// // beforeEach( () => TestBed.configureTestingModule( {} ) ); + +// // beforeEach( () => { +// // curve = new AutoSpline(); +// // } ); + +// // interface DOT { +// // id: number, +// // roadId: number, +// // laneId: number, +// // direction: 'forward' | 'backward', +// // type: 'start' | 'end', +// // position: Vector3 +// // }; + +// // interface CONNECTION { +// // id: number, +// // incomingRoad: number, +// // outgoingRoad: number, +// // point: 'start' | 'end', +// // links: LINK[] +// // } + +// // interface LINK { +// // from: number, +// // to: number, +// // } + +// // // TODO: +// // // 1. it should give correct lines, arc based points + +// // // 2. it should give correct points based on lines, arcs +// // // it( 'it should give correct points based on lines', () => { + +// // // const road = new OdRoad( 'road', 10, 1, 0 ); + +// // // road.addPlanView(); + +// // // // road.addGeometryLine( 0, 0, 0, 1, 10 ); +// // // road.addGeometryArc( 0, 0, 0, 0, 10, 0.001 ); + +// // // // road.getGeometryCoords(0, o) + +// // // const odPosTheta = new OdPosTheta(); + +// // // const points: Vector2[] = []; + +// // // for ( let s = 0; s < road.length; s++ ) { + +// // // road.getGeometryCoords( s, odPosTheta ); + +// // // // console.log( odPosTheta ); + +// // // points.push( odPosTheta.toVector2() ); +// // // } + +// // // const planView = new OdPlaneView(); + +// // // let prevHdg = null; +// // // let prevCurvature = null; + +// // // for ( let i = 2; i < points.length; i++ ) { + +// // // const p0 = points[ i - 2 ]; +// // // const p1 = points[ i - 1 ]; +// // // const p2 = points[ i ]; + +// // // const d = p0.distanceTo( p1 ); + +// // // const hdg = new THREE.Vector2().subVectors( p1, p0 ).angle(); +// // // const hdg2 = new THREE.Vector2().subVectors( p2, p1 ).angle(); + +// // // const curvature = Math.fround( hdg - prevHdg ); +// // // const curvature2 = Math.fround( hdg2 - hdg ); + +// // // // same hdg, same curvature == line +// // // // diff hdg, same curvature == arc + +// // // // line +// // // if ( Maths.approxEquals( hdg, hdg2 ) ) { + +// // // // continue adding to the road +// // // console.log( 'is line', hdg, hdg2 ); + +// // // } else { + +// // // // create new road +// // // console.log( 'is curved', curvature, hdg, hdg2 ); + +// // // } + +// // // console.log( d, hdg ); + +// // // prevHdg = hdg; +// // // prevCurvature = curvature; +// // // } + +// // // } ); + +// // // it( 'it should give correct connection count for 2x2 lane road intersection', () => { + +// // // const roads = []; + +// // // const road1 = new OdRoad( 'road1', 100, 1, -1 ); +// // // const road2 = new OdRoad( 'road1', 100, 2, -1 ); + +// // // roads.push( road1 ); +// // // roads.push( road2 ); + +// // // const dots: DOT[] = [] + +// // // let id = 1; + +// // // roads.forEach( road => { + +// // // // add 2 lanes on each road +// // // road.addLaneSection( 0, false ); + +// // // const laneSection = road.getLastAddedLaneSection(); + +// // // laneSection.addLane( LaneSide.LEFT, 1, OdLaneType.driving, true, true ); +// // // laneSection.addLane( LaneSide.CENTER, 0, OdLaneType.driving, true, true ); +// // // laneSection.addLane( LaneSide.RIGHT, -1, OdLaneType.driving, true, true ); + +// // // road1.laneSections[ 0 ].getRightLanes().forEach( lane => { +// // // dots.push( { id: id++, roadId: road.id, laneId: lane.id, type: 'start', direction: 'backward', position: null } ) +// // // dots.push( { id: id++, roadId: road.id, laneId: lane.id, type: 'end', direction: 'backward', position: null } ) +// // // } ) + +// // // road1.laneSections[ 0 ].getLeftLanes().forEach( lane => { +// // // dots.push( { id: id++, roadId: road.id, laneId: lane.id, type: 'start', direction: 'forward', position: null } ) +// // // dots.push( { id: id++, roadId: road.id, laneId: lane.id, type: 'end', direction: 'forward', position: null } ) +// // // } ) + +// // // } ); + +// // // const connections = getConnections( dots ); + +// // // expect( connections.length ).toBe( 12 ); + +// // // } ) + +// // it( 'it should give correct connection count for 4x4 highway lane road intersection', () => { + +// // const roads = []; + +// // const road1 = new OdRoad( 'road1', 100, 1, -1 ); +// // const road2 = new OdRoad( 'road1', 100, 2, -1 ); + +// // roads.push( road1 ); +// // roads.push( road2 ); + +// // const dots: DOT[] = [] + +// // let id = 1; + +// // roads.forEach( road => { + +// // // add 2 lanes on each road +// // road.addLaneSection( 0, false ); + +// // const laneSection = road.getLastAddedLaneSection(); + +// // laneSection.addLane( LaneSide.LEFT, 3, OdLaneType.driving, true, true ); +// // laneSection.addLane( LaneSide.LEFT, 4, OdLaneType.driving, true, true ); +// // laneSection.addLane( LaneSide.LEFT, 2, OdLaneType.driving, true, true ); +// // laneSection.addLane( LaneSide.LEFT, 1, OdLaneType.driving, true, true ); + +// // // road1.laneSections[ 0 ].getRightLanes().forEach( lane => { +// // // dots.push( { id: id++, roadId: road.id, laneId: lane.id, type: 'start', direction: 'backward', position: null } ) +// // // dots.push( { id: id++, roadId: road.id, laneId: lane.id, type: 'end', direction: 'backward', position: null } ) +// // // } ) + +// // road.laneSections[ 0 ].getLeftLanes().forEach( lane => { +// // dots.push( { id: id++, roadId: road.id, laneId: lane.id, type: 'start', direction: 'forward', position: null } ) +// // dots.push( { id: id++, roadId: road.id + 2, laneId: lane.id, type: 'end', direction: 'forward', position: null } ) +// // } ) + +// // } ); + +// // roads.push( new OdRoad( 'road3', 100, 3, -1 ) ); +// // roads.push( new OdRoad( 'road4', 100, 4, -1 ) ); + +// // const connections = getConnections( dots, roads ); + + + +// // expect( connections.length ).toBe( 10 ); + +// // } ) + +// // function getConnections ( points: DOT[], roads: OdRoad[] ) { + +// // const connections: CONNECTION[] = []; + +// // const startPoints = points.filter( d => d.type === 'start' ); +// // const endPoints = points.filter( d => d.type === 'end' ); + + +// // const blah: [][] = []; + +// // // left turn +// // roads.forEach( road => { + +// // const roadStartPoints = points.filter( d => d.type == 'start' && d.roadId == road.id ); + +// // // start connections +// // roadStartPoints.forEach( roadStartPoint => { + +// // // straight connection +// // // 1 connection for same lane and road to +// // const pointOnSameLaneRoad = points.find( other => { +// // return other.roadId === roadStartPoint.roadId + 2 && // + 2 because continusing road is at + 2 +// // other.laneId == roadStartPoint.laneId && +// // other.id != roadStartPoint.id && +// // other.type != roadStartPoint.type +// // } ) + +// // if ( pointOnSameLaneRoad ) { + +// // const links: LINK[] = [] + +// // connections.push( { +// // id: 0, +// // incomingRoad: roadStartPoint.roadId, +// // outgoingRoad: pointOnSameLaneRoad.roadId, +// // point: 'start', +// // links: [] +// // } ); +// // } + +// // } ); + +// // // console.log( road.id ); + +// // // // find lane with highest id +// // // const leftMostPoint = startPoints.filter( p => p.roadId == road.id ).reduce( ( x, y ) => { +// // // return x.laneId > y.laneId ? x : y; +// // // } ) + +// // // const leftMostPointOnNextRoad = endPoints.filter( p => p.roadId != road.id ).reduce( ( x, y ) => { +// // // return x.laneId > y.laneId ? x : y; +// // // } ) + +// // // find lane with lowest id +// // // const rightMostPoint = startPoints.filter( p => p.roadId == road.id ).reduce( ( x, y ) => { +// // // return x.laneId < y.laneId ? x : y; +// // // } ) + +// // // console.log( 'left', leftMostPoint, leftMostPointOnNextRoad ); +// // // console.log( 'right', rightMostPoint ); + +// // } ); + +// // // startPoints.forEach( point => { + +// // // // left turn +// // // const pointForLeftTurn = dots.find( other => { +// // // return other.direction == point.direction && +// // // other.roadId != point.roadId && +// // // other.laneId == point.laneId && +// // // other.type != point.type +// // // } ) + +// // // connections.push( { +// // // incomingRoad: pointForLeftTurn.roadId, +// // // outgoingRoad: pointForLeftTurn.laneId, +// // // point: pointForLeftTurn.type, +// // // } ); + +// // // } ) + +// // // for ( let i = 0; i < startPoints.length; i++ ) { + +// // // const startingDot = startPoints[ i ]; + +// // // // console.log( dot, sameDot ) + +// // // // if direction +// // // // match start with end; ignore end +// // // // start from right +// // // // total dots are lanes * 2 +// // // // not from same road + +// // // // dots from other roads +// // // const otherDots = dots.filter( d => d.roadId != startingDot.roadId ); + +// // // otherDots.forEach( other => { + +// // // const laneConnectExits = connections.find( connection => { +// // // return connection.road == startingDot.roadId && +// // // connection.lane == startingDot.laneId +// // // } ); + +// // // if ( +// // // other.type != startingDot.type && +// // // ) { +// // // connections.push( { +// // // road: other.roadId, +// // // lane: other.laneId, +// // // type: other.type, +// // // } ); +// // // } + +// // // } ); + +// // // } + +// // return connections; +// // } + + +// // } ); + +// ///////////////////////////////////////// +// // PSE + +// // // left road 1 +// // 4F S +// // 3F S +// // 2F S +// // 1F S + +// // // right road 3 +// // 4F E +// // 3F E +// // 2F E +// // 1F E + +// // // top road 4 +// // 4F E +// // 3F E +// // 2F E +// // 1F E + +// // // bottom road 2 +// // 4F S +// // 3F S +// // 2F S +// // 1F S + +// // =========================================================== + +// // in auto : +// // max 1 left turn +// // and +// // max 1 right turn per road +// // for outgoing traffic + +// // left turns +// // --------- +// // left most (highest id) lane of current road +// // with left most (highest) lane of next road +// // start point with endpoint + + +// // right turns +// // ----------- +// // right most lane (lowest id) of current road +// // with right most (lowest id) lane of next road +// // start point with endpoint diff --git a/src/app/core/shapes/auto-spline.ts b/src/app/core/shapes/auto-spline.ts new file mode 100644 index 00000000..90440497 --- /dev/null +++ b/src/app/core/shapes/auto-spline.ts @@ -0,0 +1,288 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { Vector2, Vector3 } from 'three'; +import { TvAbstractRoadGeometry } from 'app/modules/tv-map/models/geometries/tv-abstract-road-geometry'; +import { TvLineGeometry } from 'app/modules/tv-map/models/geometries/tv-line-geometry'; +import { TvArcGeometry } from 'app/modules/tv-map/models/geometries/tv-arc-geometry'; +import { PolyLine } from './PolyLine'; +import { RoundLine } from './round-line'; +import { AbstractSpline } from './abstract-spline'; +import { TvRoad } from 'app/modules/tv-map/models/tv-road.model'; +import { RoadControlPoint } from 'app/modules/three-js/objects/road-control-point'; +import { TvPosTheta } from 'app/modules/tv-map/models/tv-pos-theta'; + + +export class AutoSpline extends AbstractSpline { + + public type = 'auto'; + + public polyline: PolyLine; + + public roundline: RoundLine; + + constructor ( private road?: TvRoad ) { + + super(); + + } + + get hdgs () { return this.controlPoints.map( ( cp: RoadControlPoint ) => cp.hdg ) } + + init () { + + this.polyline = new PolyLine( this.controlPoints ); + + this.roundline = new RoundLine( this.controlPoints ); + + if ( this.meshAddedInScene ) return; + + this.scene.add( this.polyline.mesh ); + + this.scene.add( this.roundline.mesh ); + + this.meshAddedInScene = true; + + } + + hide (): void { + + this.controlPoints.forEach( i => i.visible = false ); + + this.polyline.mesh.visible = false; + + this.roundline.mesh.visible = false; + + } + + show (): void { + + this.controlPoints.forEach( i => i.visible = true ); + + this.polyline.mesh.visible = true; + + this.roundline.mesh.visible = true; + + } + + addControlPoint ( cp: RoadControlPoint ) { + + // this.polyline.addPoint( cp ); + + // this.roundline.addPoint( cp ); + + super.addControlPoint( cp ); + + } + + update () { + + this.updateHdgs(); + + this.polyline.update(); + + this.roundline.update(); + + + } + + updateHdgs () { + + const hdgs = []; + + let hdg, p1, p2, currentPoint, previousPoint; + + for ( let i = 1; i < this.controlPoints.length; i++ ) { + + previousPoint = this.controlPoints[ i - 1 ]; + currentPoint = this.controlPoints[ i ]; + + p1 = new Vector2( currentPoint.position.x, currentPoint.position.y ); + p2 = new Vector2( previousPoint.position.x, previousPoint.position.y ); + + hdg = new Vector2().subVectors( p1, p2 ).angle(); + + previousPoint[ 'hdg' ] = hdg; + + hdgs.push( hdg ); + } + + // setting hdg for the last point + if ( hdg != null ) { + + currentPoint[ 'hdg' ] = hdg; + + } + + } + + clear () { + + this.controlPoints.splice( 0, this.controlPoints.length ); + + this.scene.remove( this.polyline.mesh ); + + this.scene.remove( this.roundline.mesh ); + + } + + exportGeometries (): TvAbstractRoadGeometry[] { + + let totalLength = 0; + + const points = this.roundline.points as RoadControlPoint[]; + + const radiuses = this.roundline.radiuses; + + const geometries: TvAbstractRoadGeometry[] = []; + + let s = totalLength; + + for ( let i = 1; i < points.length; i++ ) { + + let x, y, hdg, length; + + const previous = points[ i - 1 ].position; + const current = points[ i ].position; + + const p1 = new Vector2( previous.x, previous.y ); + + const p2 = new Vector2( current.x, current.y ); + + const d = p1.distanceTo( p2 ); + + // line between p1 and p2 + if ( d - radiuses[ i - 1 ] - radiuses[ i ] > 0.001 ) { + + [ x, y ] = new Vector2() + .subVectors( p2, p1 ) + .normalize() + .multiplyScalar( radiuses[ i - 1 ] ) + .add( p1 ) + .toArray(); + + // hdg = new Vector2().subVectors( p2, p1 ).angle(); + hdg = points[ i - 1 ].hdg; + + length = d - radiuses[ i - 1 ] - radiuses[ i ]; + + s = totalLength; + + totalLength += length; + + geometries.push( new TvLineGeometry( s, x, y, hdg, length ) ); + + } + + // arc for p2 + if ( radiuses[ i ] > 0 ) { // first and last point can't have zero radiuses + + const next = points[ i + 1 ].position; + + const dir1 = new Vector2( current.x - previous.x, current.y - previous.y ).normalize(); + + const dir2 = new Vector2( next.x - current.x, next.y - current.y ).normalize(); + + const pp1 = new Vector2() + .subVectors( p1, p2 ) + .normalize() + .multiplyScalar( radiuses[ i ] ) + .add( p2 ); + + const pp2 = new Vector2() + .subVectors( ( new Vector2( next.x, next.y ) ), p2 ) + .normalize() + .multiplyScalar( radiuses[ i ] ) + .add( p2 ); + + x = pp1.x; + + y = pp1.y; + + hdg = dir1.angle(); + + let r, alpha, sign; + + [ r, alpha, length, sign ] = this.getArcParams( pp1, pp2, dir1, dir2 ); + + if ( r != Infinity ) { + + s = totalLength; + + totalLength += length; + + const curvature = ( sign > 0 ? 1 : -1 ) * ( 1 / r ); // sign < for mirror image + + geometries.push( new TvArcGeometry( s, x, y, hdg, length, curvature ) ); + + + } else { + + s = totalLength; + + length = pp1.distanceTo( pp2 ); + + totalLength += length; + + geometries.push( new TvLineGeometry( s, x, y, hdg, length ) ); + + console.warn( "radius is infinity" ); + + } + + + } + + } + + return geometries; + } + + addControlPointAt ( position: Vector3 ): RoadControlPoint { + + const index = this.controlPoints.length; + + const point = new RoadControlPoint( this.road, position, "cp", index, index ); + + this.controlPoints.push( point ); + + this.update(); + + return point; + } + + getPoint ( t: number, offset = 0 ): Vector3 { + + const geometries = this.exportGeometries(); + + const length = geometries.map( g => g.length ).reduce( ( a, b ) => a + b ); + + const s = length * t; + + const geometry = geometries.find( g => s >= g.s && s <= g.s2 ); + + const posTheta = new TvPosTheta(); + + geometry.getCoords( s, posTheta ); + + posTheta.addLateralOffset( offset ); + + return posTheta.toVector3(); + } + + getLength () { + + const geometries = this.exportGeometries(); + + let length = 0; + + for ( let i = 0; i < geometries.length; i++ ) { + + length += geometries[ i ].length; + + } + + return length; + } +} diff --git a/src/app/core/shapes/catmull-rom-spline.spec.ts b/src/app/core/shapes/catmull-rom-spline.spec.ts new file mode 100644 index 00000000..e2273969 --- /dev/null +++ b/src/app/core/shapes/catmull-rom-spline.spec.ts @@ -0,0 +1,40 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { TestBed } from '@angular/core/testing'; +import { Vector3 } from 'three'; +import { GameObject } from '../game-object'; +import { CatmullRomSpline } from './catmull-rom-spline'; +import { AnyControlPoint } from 'app/modules/three-js/objects/control-point'; + + +describe( 'CatmullRomSpline test', () => { + + let spline: CatmullRomSpline; + + beforeEach( () => TestBed.configureTestingModule( {} ) ); + + beforeEach( () => { + spline = new CatmullRomSpline( false ); + } ); + + it( 'should give correct positions', () => { + + spline.add( AnyControlPoint.create( "", new Vector3( 0, 0, 0 ) ) ) + + spline.add( AnyControlPoint.create( "", new Vector3( 50, 0, 0 ) ) ) + + spline.add( AnyControlPoint.create( "", new Vector3( 100, 0, 0 ) ) ) + + const points = spline.getPoints( 10 ); + + // 11 becuase 1 is at the start as well + expect( points.length ).toBe( 11 ); + + expect( spline.getLength() ).toBe( 100 ); + + } ); + + +} ); diff --git a/src/app/core/shapes/catmull-rom-spline.ts b/src/app/core/shapes/catmull-rom-spline.ts new file mode 100644 index 00000000..ff8e5673 --- /dev/null +++ b/src/app/core/shapes/catmull-rom-spline.ts @@ -0,0 +1,150 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { BufferGeometry, CatmullRomCurve3, Line, LineBasicMaterial, Vector3, LineLoop } from 'three'; +import { TvAbstractRoadGeometry } from 'app/modules/tv-map/models/geometries/tv-abstract-road-geometry'; +import { AnyControlPoint } from 'app/modules/three-js/objects/control-point'; +import { AbstractSpline } from './abstract-spline'; +import { COLOR } from 'app/shared/utils/colors.service'; + +export class CatmullRomSpline extends AbstractSpline { + + public type: string = 'catmullrom'; + + public curveType: string = 'curve'; + + public curve: CatmullRomCurve3; + + public mesh: Line; + + constructor ( closed = true, type = 'catmullrom', tension = 0.5 ) { + + super( closed, tension ); + + } + + init (): void { + + this.curve = new CatmullRomCurve3( this.controlPointPositions, this.closed, this.type || 'catmullrom', this.tension ); + + const geometry = new BufferGeometry(); + + // Create the final object to add to the scene + if ( this.closed ) { + + this.mesh = new LineLoop( geometry, new LineBasicMaterial( { color: COLOR.RED, opacity: 0.35 } ) ); + + } else { + + this.mesh = new Line( geometry, new LineBasicMaterial( { color: COLOR.RED, opacity: 0.35 } ) ); + + } + + this.mesh.castShadow = true; + + this.mesh.renderOrder = 1; + + this.mesh.frustumCulled = false; + + this.scene.add( this.mesh ); + } + + + hide (): void { + + this.controlPoints.forEach( i => i.visible = false ); + + this.mesh.visible = false; + + } + + show (): void { + + this.controlPoints.forEach( i => i.visible = true ); + + this.mesh.visible = true; + + } + + hideAllTangents () { + + this.controlPoints.forEach( ( cp: AnyControlPoint ) => { + + this.hideTangenAt(); + + } ); + + } + + showcontrolPoints () { + + this.controlPoints.forEach( co => co.visible = true ); + + } + + hidecontrolPoints () { + + this.controlPoints.forEach( co => co.visible = false ); + + } + + showTangentsAt () { + + } + + hideTangenAt () { + + } + + update (): void { + + if ( this.controlPoints.length < 2 ) return; + + // if ( !this.curve ) { + // this.curve = new CatmullRomCurve3( + // this.controlPointPositions, + // this.closed, + // this.type, + // this.tension + // ); + // } + + this.curve.points = this.controlPointPositions; + + this.curve.updateArcLengths(); + + this.mesh.geometry.dispose(); + + this.mesh.geometry = new BufferGeometry().setFromPoints( this.curve.getPoints( 100 ) ); + + } + + add ( point: AnyControlPoint ) { + + this.controlPoints.push( point ); + + this.curve.points.push( point.position ); + + this.update(); + } + + exportGeometries (): TvAbstractRoadGeometry[] { + + throw new Error( 'Method not implemented.' ); + + } + + getLength () { + + return this.curve.getLength(); + + } + + getPoints ( spacing = 10 ): Vector3[] { + + return this.curve.getPoints( spacing ); + + } + +} \ No newline at end of file diff --git a/src/app/core/shapes/cubic-spline-curve.ts b/src/app/core/shapes/cubic-spline-curve.ts new file mode 100644 index 00000000..55c91217 --- /dev/null +++ b/src/app/core/shapes/cubic-spline-curve.ts @@ -0,0 +1,357 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { Curve, CurvePath, Vector3, CatmullRomCurve3 } from 'three'; +import { AnyControlPoint } from 'app/modules/three-js/objects/control-point'; +import { ExplicitSpline } from './explicit-spline'; +import { AutoSpline } from './auto-spline'; + + +function CubicBezierP0 ( t, p ) { + + const k = 1 - t; + return k * k * k * p; + +} + +function CubicBezierP1 ( t, p ) { + + const k = 1 - t; + return 3 * k * k * t * p; + +} + +function CubicBezierP2 ( t, p ) { + + return 3 * ( 1 - t ) * t * t * p; + +} + +function CubicBezierP3 ( t, p ) { + + return t * t * t * p; + +} + +function CubicBezier ( t, p0, p1, p2, p3 ) { + + return CubicBezierP0( t, p0 ) + CubicBezierP1( t, p1 ) + CubicBezierP2( t, p2 ) + + CubicBezierP3( t, p3 ); + +} + +export class CubicSplineCurve extends Curve { + + constructor ( + public points: Vector3[] = [], + public closed?: boolean, + public curveType?: string, + public tension?: number + ) { + super(); + } + + getPoint ( t: number, optionalTarget?: Vector3 ): Vector3 { + + const point = optionalTarget || new Vector3(); + + const points = this.points; + const p = ( points.length - 1 ) * t; + + const intPoint = Math.floor( p ); + const weight = p - intPoint; + + const p0 = points[ intPoint === 0 ? intPoint : intPoint - 1 ]; + const p1 = points[ intPoint ]; + const p2 = points[ intPoint > points.length - 2 ? points.length - 1 : intPoint + 1 ]; + const p3 = points[ intPoint > points.length - 3 ? points.length - 1 : intPoint + 2 ]; + + point.set( + CubicBezier( weight, p0.x, p1.x, p2.x, p3.x ), + CubicBezier( weight, p0.y, p1.y, p2.y, p3.y ), + CubicBezier( weight, p0.z, p1.z, p2.z, p3.z ), + ); + + return point; + } + + +} + +export class HermiteSplineCurve extends CurvePath { + + constructor ( + public v0: Vector3, public v1: Vector3, public v2: Vector3, public v3: Vector3 + ) { + super(); + } + + getPoint ( t: number, optionalTarget?: Vector3 ): Vector3 { + + const point = optionalTarget || new Vector3(); + + const p1 = this.v0; + const p2 = this.v3; + const t1 = new Vector3().subVectors( this.v1, this.v0 ).multiplyScalar( 3.5 ); + const t2 = new Vector3().subVectors( this.v3, this.v2 ).multiplyScalar( 3.5 ); + + const s = t, s2 = s * s, s3 = s2 * s; + const h1 = new Vector3().setScalar( 2 * s3 - 3 * s2 + 1 ).multiply( p1 ); + const h2 = new Vector3().setScalar( -2 * s3 + 3 * s2 ).multiply( p2 ); + const h3 = new Vector3().setScalar( s3 - 2 * s2 + s ).multiply( t1 ); + const h4 = new Vector3().setScalar( s3 - s2 ).multiply( t2 ); + + point.copy( h1 ).add( h2 ).add( h3 ).add( h4 ); + + return point; + } + + getCoefficients () { + + + } + +} + +export class LineArcSplineCurve extends CurvePath { + + public radiuses: number[]; + + /** NOT WORKING CURRENTLY */ + constructor ( + public points: AnyControlPoint[] + ) { + super(); + } + + getPoint ( t: number, optionalTarget?: Vector3 ): Vector3 { + + this.calcRadius(); + + const point = optionalTarget || new Vector3(); + + const ARC_TESSEL_HALF = 24 / 2; + + let vertexIndex = 0; + let currentPoint = null; + let prevPoint = null; + let nextPoint = null; + let radius = null; + + const i = 1; + + currentPoint = this.points[ i ]; + + radius = this.radiuses[ i ]; + + if ( radius == 0 ) { + + point.copy( currentPoint ); + + } else { + + prevPoint = this.points[ i - 1 ].position; + nextPoint = this.points[ i + 1 ].position; + + const p1 = new Vector3() + .subVectors( prevPoint, currentPoint ) + .normalize() + .multiplyScalar( radius ) + .add( currentPoint ); + + const p0 = currentPoint; + + const p2 = new Vector3() + .subVectors( nextPoint, currentPoint ) + .normalize() + .multiplyScalar( radius ) + .add( currentPoint ); + + + // for ( let ii = 0; ii < ARC_TESSEL_HALF; ii++ ) { + + // pos.lerpVectors( p1, p0, ii / ARC_TESSEL_HALF ); + + // pos.copy( this.arcInterpolation( currentPoint, prevPoint, nextPoint, radius, pos ) ); + + // } + + // for ( let ii = 0; ii < ARC_TESSEL_HALF; ii++ ) { + + // pos.lerpVectors( p0, p2, ii / ARC_TESSEL_HALF ); + + // pos.copy( this.arcInterpolation( currentPoint, prevPoint, nextPoint, radius, pos ) ); + + // } + + point.lerpVectors( p1, p0, t ); + + point.copy( this.arcInterpolation( currentPoint, prevPoint, nextPoint, radius, point ) ); + + point.lerpVectors( p0, p2, t ); + + point.copy( this.arcInterpolation( currentPoint, prevPoint, nextPoint, radius, point ) ); + + + } + + return point; + } + + calcRadius (): void { + + if ( this.points.length === 0 ) return; + + // init all radiuses to Infinity + this.radiuses = new Array( this.points.length ); + + // store lengths from one point to another for the whole spline + const lengths = new Array( this.points.length - 1 ); + + let currentPoint: AnyControlPoint = null; + let nextPoint: AnyControlPoint = null; + + this.points.forEach( ( currentPoint, i ) => { + + // set the radius at each point 0 by default + this.radiuses[ i ] = 0; + + // set the lengths until the last point + if ( i < this.points.length - 1 ) { + + nextPoint = this.points[ i + 1 ]; + + lengths[ i ] = currentPoint.position.distanceTo( nextPoint.position ); + } + + } ) + + // foreach point except the first one + for ( let i = 1; i < this.points.length - 1; i++ ) { + + this.radiuses[ i ] = Math.min( lengths[ i - 1 ], lengths[ i ] ); + + } + + for ( let updated = true; updated; ) { + + updated = false; + + for ( let i = 1; i < this.points.length - 1; i++ ) { + + const leftR = this.radiuses[ i - 1 ] + this.radiuses[ i ] > lengths[ i - 1 ] ? lengths[ i - 1 ] / 2 : this.radiuses[ i ]; + + const rightR = this.radiuses[ i + 1 ] + this.radiuses[ i ] > lengths[ i ] ? lengths[ i ] / 2 : this.radiuses[ i ]; + + const minR = Math.min( leftR, rightR ); + + if ( minR != this.radiuses[ i ] ) { + updated = true; + this.radiuses[ i ] = minR; + } + } + } + } + + arcInterpolation ( currentPoint: Vector3, prevPoint: Vector3, nextPoint: Vector3, radius: number, v: Vector3 ) { + + const va = new Vector3() + .subVectors( prevPoint, currentPoint ) + .normalize() + .multiplyScalar( radius ); + + const vb = new Vector3() + .subVectors( nextPoint, currentPoint ) + .normalize() + .multiplyScalar( radius ); + + // const t = ( va.x * va.x + va.y * va.y + va.z * va.z ) / ( va.x * va.x + va.y * va.y + va.z * va.z + vb.x * va.x + vb.y * va.y + vb.z * va.z ); + const t = ( va.x * va.x + va.z * va.z + va.y * va.y ) + / ( va.x * va.x + va.z * va.z + va.y * va.y + vb.x * va.x + vb.z * va.z + vb.y * va.y ); + + // center of circle + const p = new Vector3().addVectors( va, vb ).multiplyScalar( t ).add( currentPoint ); + + // radius of circle + const r = new Vector3().addVectors( currentPoint, va ).distanceTo( p ); + + // project to circle + return new Vector3().subVectors( v, p ).normalize().multiplyScalar( r ).add( p ); + } +} + +export class CatmullRomPath extends CurvePath { + + private catmull: CatmullRomCurve3; + + constructor ( + public points: Vector3[] = [], + public closed?: boolean, + public curveType?: string, + public tension?: number + ) { + + super(); + + this.catmull = new CatmullRomCurve3( points, closed, curveType, tension ); + } + + getPoint ( t: number, optionalTarget?: Vector3 ): Vector3 { + + return this.catmull.getPoint( t ); + + } + + getLength () { + + return this.catmull.getLength(); + + } +} + +export class ExplicitSplinePath extends CurvePath { + + constructor ( + private spline: ExplicitSpline, + private offset: number = 0, + ) { + + super(); + } + + getPoint ( t: number, optionalTarget?: Vector3 ): Vector3 { + + return this.spline.getPoint( t, this.offset ); + + } + + getLength () { + + return this.spline.getLength(); + + } +} + +export class AutoSplinePath extends CurvePath { + + constructor ( + private spline: AutoSpline, + private offset: number = 0, + ) { + + super(); + } + + getPoint ( t: number, optionalTarget?: Vector3 ): Vector3 { + + return this.spline.getPoint( t, this.offset ); + + } + + getLength () { + + return this.spline.getLength(); + + } +} diff --git a/src/app/core/shapes/explicit-spline.spec.ts b/src/app/core/shapes/explicit-spline.spec.ts new file mode 100644 index 00000000..9357f37c --- /dev/null +++ b/src/app/core/shapes/explicit-spline.spec.ts @@ -0,0 +1,52 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { Shape } from 'three'; +import { ExplicitSpline } from './explicit-spline'; +import { ExplicitSplinePath } from './cubic-spline-curve'; +import { TvRoad } from 'app/modules/tv-map/models/tv-road.model'; +import { TvMapSourceFile } from 'app/modules/tv-map/services/tv-map-source-file'; +import { TvGeometryType } from 'app/modules/tv-map/models/tv-common'; +import { Maths } from 'app/utils/maths'; + + +describe( 'ExplicitSpline Test', () => { + + let road: TvRoad; + let spline: ExplicitSpline; + + beforeEach( () => { + + road = TvMapSourceFile.openDrive.addDefaultRoad(); + + spline = road.spline = new ExplicitSpline(); + + } ); + + it( 'should work as curve path', () => { + + road.addGeometryLine( 0, 0, 0, 0, 100 ); + + const shape = new Shape(); shape.moveTo( 0, -0.25 ); shape.lineTo( 0, 0.25 ); + + spline.addFromFile( 0, road.startPosition().toVector3(), 0, TvGeometryType.LINE ); + spline.addFromFile( 1, road.endPosition().toVector3(), 0, TvGeometryType.LINE ); + + const path = new ExplicitSplinePath( spline ); + // const path = new CatmullRomPath( [ road.startPosition().toVector3(), road.endPosition().toVector3() ] ); + + expect( Maths.approxEquals( path.getLength(), 100, 0.01 ) ).toBe( true ); + + // const extrudeSettings = { + // steps: path.getLength() * 2, + // bevelEnabled: false, + // extrudePath: path + // }; + + // const geometry = new ExtrudeGeometry( shape, extrudeSettings ); + + } ); + + +} ); \ No newline at end of file diff --git a/src/app/core/shapes/explicit-spline.ts b/src/app/core/shapes/explicit-spline.ts new file mode 100644 index 00000000..4e492f58 --- /dev/null +++ b/src/app/core/shapes/explicit-spline.ts @@ -0,0 +1,458 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { AbstractSpline } from './abstract-spline'; +import { TvAbstractRoadGeometry } from 'app/modules/tv-map/models/geometries/tv-abstract-road-geometry'; +import { AnyControlPoint } from 'app/modules/three-js/objects/control-point'; +import { BufferAttribute, BufferGeometry, Line, LineBasicMaterial, Vector2, Vector3 } from 'three'; + +import * as SPIRAL from './spiral-math.js'; +import { TvGeometryType } from 'app/modules/tv-map/models/tv-common'; +import { TvLineGeometry } from 'app/modules/tv-map/models/geometries/tv-line-geometry'; +import { TvArcGeometry } from 'app/modules/tv-map/models/geometries/tv-arc-geometry'; +import { TvSpiralGeometry } from 'app/modules/tv-map/models/geometries/tv-spiral-geometry'; +import { TvParamPoly3Geometry } from 'app/modules/tv-map/models/geometries/tv-param-poly3-geometry'; +import { HermiteSpline, Length } from './SplineData'; +import { CURVE_TESSEL, CURVE_Y, PARACUBICFACTOR } from './spline-config'; +import { TvRoad } from 'app/modules/tv-map/models/tv-road.model'; +import { RoadControlPoint } from 'app/modules/three-js/objects/road-control-point'; +import { SceneService } from '../services/scene.service'; +import { TvPosTheta } from 'app/modules/tv-map/models/tv-pos-theta'; + +export class ExplicitSpline extends AbstractSpline { + + type: string = 'explicit'; + + private segments: Line[] = [] + + // no need for now + // private tangentLines: Line[] = []; + + get hdgs () { + + return this.controlPoints.map( ( cp: RoadControlPoint ) => [ cp.hdg, 7, 7 ] ); + + } + + get segTypes () { + + return this.controlPoints.map( ( cp: RoadControlPoint ) => cp.segmentType ); + + } + + constructor ( private road?: TvRoad ) { + + super(); + + } + + init (): void { + + // nothing to init + + } + + hide (): void { + + this.controlPoints.forEach( i => i.hide() ); + + this.segments.forEach( segment => segment.visible = false ); + + } + + show (): void { + + this.controlPoints.forEach( i => i.show() ); + + this.segments.forEach( segment => segment.visible = true ); + + } + + update (): void { + + for ( let i = 0; i < this.segments.length; i++ ) { + + this.updateSegment( i ); + + } + + } + + clear () { + + this.controlPoints.splice( 0, 1 ); + + this.segments.forEach( segment => segment.visible = false ); + + } + + exportGeometries (): TvAbstractRoadGeometry[] { + + return this.exportFromSpline( this.segTypes, this.hdgs, this.controlPointPositions ); + + } + + exportFromSpline ( segTypes: number[], hdgs: number[][], points: Vector3[] ): TvAbstractRoadGeometry[] { + + let totalLength = 0; + const geometries: TvAbstractRoadGeometry[] = []; + + for ( let i = 0; i < this.segments.length; i++ ) { + + const hdg1 = hdgs[ i ]; + const hdg2 = hdgs[ i + 1 ]; + + const dir1 = new Vector2( Math.cos( hdg1[ 0 ] ), Math.sin( hdg1[ 0 ] ) ); + const dir2 = new Vector2( Math.cos( hdg2[ 0 ] ), Math.sin( hdg2[ 0 ] ) ); + + // using y instead of z + const p1 = new Vector2( points[ i ].x, points[ i ].y ); + const p2 = new Vector2( points[ i + 1 ].x, points[ i + 1 ].y ); + + const distance = p1.distanceTo( p2 ); + + let s = totalLength, x: number, y: number, hdg: number, length: number; + + if ( segTypes[ i ] === TvGeometryType.LINE ) { + + x = p1.x; y = p1.y; + + hdg = hdg1[ 0 ]; + + length = distance; + + geometries.push( new TvLineGeometry( s, x, y, hdg, length ) ); + + } else if ( segTypes[ i ] === TvGeometryType.ARC ) { + + x = p1.x; y = p1.y; + + hdg = hdg1[ 0 ]; + + let radius, alpha, sign;[ radius, alpha, length, sign ] = this.getArcParams( p1, p2, dir1, dir2 ); + + // world z is flipped so inverse the sign + // const curvature = + ( sign < 0 ? 1 : -1 ) + 1 / r; + let curvature = ( sign > 0 ? 1 : -1 ) * ( 1 / radius ); // sign < for mirror image + + // if radius if infinite then curvature should be the least possible value + // so its almost close to a line but still an arc + if ( radius === Infinity ) curvature = Number.MIN_VALUE; + + // because its alsmot a line we can take the arc length as the simple distance between the points + if ( radius === Infinity ) length = distance; + + geometries.push( new TvArcGeometry( s, x, y, hdg, length, curvature ) ); + + } else if ( segTypes[ i ] === TvGeometryType.SPIRAL ) { + + let k, dk, _L, iter;[ k, dk, _L, iter ] = SPIRAL.buildClothoid( + p1.x, + p1.y, + SPIRAL.vec2Angle( dir1.x, dir1.y ), + p2.x, + p2.y, + SPIRAL.vec2Angle( dir2.x, dir2.y ) + ); + + x = p1.x; y = p1.y; + + hdg = hdg1[ 0 ]; + + length = _L; + + const curvStart = k; + + const curvEnd = ( k + dk * _L ); + + geometries.push( new TvSpiralGeometry( s, x, y, hdg, length, curvStart, curvEnd ) ); + + } else if ( segTypes[ i ] === TvGeometryType.PARAMPOLY3 ) { + + const ma = dir1.x, mb = dir1.y, mc = -mb, md = ma; + + const det = 1 / ( ma * md - mb * mc ); + + const mia = det * md, mib = -mb * det, mic = -mc * det, mid = ma * det; + + const dir2proj = new Vector2( + dir2.x * mia + dir2.y * mic, + dir2.x * mib + dir2.y * mid + ); + + /*flip y axis*/ + dir2proj.y = -dir2proj.y; + + const p2proj = new Vector2().subVectors( p2, p1 ); + + p2proj.set( p2proj.x * mia + p2proj.y * mic, p2proj.x * mib + p2proj.y * mid ); + + /*flip y axis*/ + p2proj.y = -p2proj.y; + + x = p1.x; y = p1.y; + + hdg = hdg1[ 0 ]; + + length = distance; //TODO fix this + + const t1 = new Vector2( 1, 0 ).multiplyScalar( PARACUBICFACTOR * hdgs[ i ][ 1 ] ); + const t2 = new Vector2( dir2proj.x, dir2proj.y ).multiplyScalar( PARACUBICFACTOR * hdgs[ i + 1 ][ 2 ] ); + + const hs = HermiteSpline( new Vector2( 0, 0 ), p2proj, t1, t2 ); + + length = Length( hs, 0.001 ); + + const f3 = new Vector2( -2 * p2proj.x + 1 * t1.x + 1 * t2.x, -2 * p2proj.y + 1 * t1.y + 1 * t2.y ); + const f2 = new Vector2( 3 * p2proj.x - 2 * t1.x - 1 * t2.x, 3 * p2proj.y - 2 * t1.y - 1 * t2.y ); + const f1 = new Vector2( 1 * t1.x, 1 * t1.y ); + + const aU = 0; + const bU = f1.x; + const cU = f2.x; + const dU = f3.x; + + const aV = 0; + const bV = f1.y; + const cV = f2.y; + const dV = f3.y; + + geometries.push( new TvParamPoly3Geometry( s, x, y, hdg, length, aU, bU, cU, dU, aV, bV, cV, dV ) ); + } + + totalLength += length; + + } + + return geometries; + } + + /** + * + * @param cp + * @deprecated + */ + addControlPoint ( cp: AnyControlPoint ) { + + cp.visible = false; + + this.addControlPointAtNew( cp.position ); + + } + + addControlPointAtNew ( position: Vector3 ) { + + const index = this.segTypes.length; + + const previousPoint = this.controlPoints[ index - 1 ] as RoadControlPoint; + + let hdg: number = 0; + + if ( previousPoint ) { + + // need to set previous point to spiral to avoid bugs + previousPoint.segmentType = TvGeometryType.SPIRAL; + + hdg = SPIRAL.vec2Angle( previousPoint.position.x, previousPoint.position.y ); + } + + return this.addFromFile( index, position, hdg, TvGeometryType.SPIRAL ); + } + + addFromFile ( index: number, position: Vector3, hdg: number, segType: TvGeometryType ) { + + // this.segTypes.push( segType ); + + const controlPoint = new RoadControlPoint( this.road, position, 'cp', index, index ); + + controlPoint.segmentType = segType; + + // TODO: move this in spline mesh or somewhere else + SceneService.add( controlPoint ); + + this.controlPoints.push( controlPoint ); + + controlPoint.hdg = hdg; + + controlPoint.addDefaultTangents( hdg, 1, 1 ); + + // update tangent line + // this.tangent.update( this.hdgs, this.tangentLines ); + + if ( index > 0 ) { + + // add empty curve mesh + this.addSegment( index - 1 ); + + // calculate curve mesh + this.updateSegment( index - 1 ); + + } + + return controlPoint; + } + + addSegment ( index: number ) { + + const geometry = new BufferGeometry(); + + geometry.attributes.position = new BufferAttribute( new Float32Array( CURVE_TESSEL * 3 ), 3 ); + + const line = new Line( geometry, new LineBasicMaterial( { color: 0x0000ff, opacity: 0.35, linewidth: 2 } ) ); + + line[ 'tag' ] = 'curve'; + + line[ 'tagindex' ] = index; + + line.castShadow = true; + + line.renderOrder = 3; + + line.frustumCulled = false; + + this.segments.push( line ); + + this.scene.add( line ); + + // this.tangentLines.push( line ); + } + + updateSegment ( idx: number ) { + + const mesh = this.segments[ idx ]; + const posattr = ( mesh.geometry as BufferGeometry ).attributes.position as BufferAttribute; + + const dir1 = new Vector2( Math.cos( this.hdgs[ idx ][ 0 ] ), Math.sin( this.hdgs[ idx ][ 0 ] ) ); + const dir2 = new Vector2( Math.cos( this.hdgs[ idx + 1 ][ 0 ] ), Math.sin( this.hdgs[ idx + 1 ][ 0 ] ) ); + + const p1 = this.controlPointPositions[ idx ]; + const p2 = this.controlPointPositions[ idx + 1 ]; + + const L = p1.distanceTo( p2 ); + + if ( this.segTypes[ idx ] == TvGeometryType.LINE ) { + + posattr.setXYZ( 0, p1.x, p1.y, p1.z ); + posattr.setXYZ( 1, p2.x, p2.y, p2.z ); + + for ( let ii = 2; ii < CURVE_TESSEL; ii++ ) { + posattr.setXYZ( ii, p2.x, p2.y, p2.z ); + } + + posattr.needsUpdate = true; + + } else if ( this.segTypes[ idx ] == TvGeometryType.ARC ) { + + const alpha = Math.acos( dir1.dot( dir2 ) ); + + // let advx = new Vector2( p2.x - p1.x, p2.z - p1.z ).normalize(); + const advx = new Vector2( p2.x - p1.x, p2.y - p1.y ).normalize(); + const advy = new Vector2( -advx.y, advx.x ); + + // get inverse matrix of p1 local space transform + const ma = dir1.x, mb = dir1.y, mc = -mb, md = ma; + const det = 1 / ( ma * md - mb * mc ); + const mia = det * md, mib = -mb * det, mic = -mc * det, mid = ma * det; + + // project p2 into p1 local space + const arcdir = new Vector2( advx.x * mia + advx.y * mic, advx.x * mib + advx.y * mid ); + + // flip y axis according to arcdir (world z is flipped so inverse the sign) + advy.multiplyScalar( -Math.sign( arcdir.y ) ); + + for ( let ii = 0; ii < CURVE_TESSEL; ii++ ) { + + const theta = ii / ( CURVE_TESSEL - 1 ) * alpha; + + const gamma = ( alpha - theta ) / 2; + + const d = L * Math.sin( theta / 2 ) / Math.sin( alpha / 2 ); + + // posattr.setXYZ( ii, p1.x + advx.x * Math.cos( gamma ) * d + advy.x * Math.sin( gamma ) * d, CURVE_Y, p1.z + advx.y * Math.cos( gamma ) * d + advy.y * Math.sin( gamma ) * d ); + posattr.setXYZ( ii, p1.x + advx.x * Math.cos( gamma ) * d + advy.x * Math.sin( gamma ) * d, p1.y + advx.y * Math.cos( gamma ) * d + advy.y * Math.sin( gamma ) * d, CURVE_Y ); + } + + posattr.needsUpdate = true; + + } else if ( this.segTypes[ idx ] == TvGeometryType.SPIRAL ) { + + const sd = SPIRAL.vec2Angle( dir1.x, dir1.y ); + const ed = SPIRAL.vec2Angle( dir2.x, dir2.y ); + + // axis issue + // let k, dk, _L, iter;[ k, dk, _L, iter ] = SPIRAL.buildClothoid( p1.x * 100, p1.z * 100, sd, p2.x * 100, p2.z * 100, ed ); + let k, dk, _L, iter;[ k, dk, _L, iter ] = SPIRAL.buildClothoid( p1.x * 100, p1.y * 100, sd, p2.x * 100, p2.y * 100, ed ); + + // axis issue + // let spiralarc = SPIRAL.clothoid_1( p1.x * 100, p1.z * 100, p1.y, sd, k, k + dk * _L, _L, p2.y, CURVE_TESSEL - 1 ) + const spiralarc = SPIRAL.clothoid_1( p1.x * 100, p1.y * 100, p1.y, sd, k, k + dk * _L, _L, p2.y, CURVE_TESSEL - 1 ) + + for ( let ii = 0; ii < CURVE_TESSEL; ii++ ) { + // axis issue + // posattr.setXYZ( ii, spiralarc[ ii ][ 0 ] / 100, spiralarc[ ii ][ 2 ], spiralarc[ ii ][ 1 ] / 100 ); + // posattr.setXYZ( ii, spiralarc[ ii ][ 0 ] / 100, spiralarc[ ii ][ 1 ] / 100, spiralarc[ ii ][ 2 ] ); + posattr.setXYZ( ii, spiralarc[ ii ][ 0 ] / 100, spiralarc[ ii ][ 1 ] / 100, 0 ); + } + + posattr.needsUpdate = true; + + } else if ( this.segTypes[ idx ] == TvGeometryType.PARAMPOLY3 ) { + + const t1 = new Vector3( dir1.x, dir1.y, CURVE_Y ).multiplyScalar( PARACUBICFACTOR * this.hdgs[ idx ][ 1 ] ); + const t2 = new Vector3( dir2.x, dir2.y, CURVE_Y ).multiplyScalar( PARACUBICFACTOR * this.hdgs[ idx + 1 ][ 2 ] ); + + const posfoo = new Vector3(); + + for ( let ii = 0; ii < CURVE_TESSEL; ii++ ) { + const s = ii / ( CURVE_TESSEL - 1 ), s2 = s * s, s3 = s2 * s; + const h1 = new Vector3().setScalar( 2 * s3 - 3 * s2 + 1 ).multiply( p1 ); + const h2 = new Vector3().setScalar( -2 * s3 + 3 * s2 ).multiply( p2 ); + const h3 = new Vector3().setScalar( s3 - 2 * s2 + s ).multiply( t1 ); + const h4 = new Vector3().setScalar( s3 - s2 ).multiply( t2 ); + posfoo.copy( h1 ).add( h2 ).add( h3 ).add( h4 ); + posattr.setXYZ( ii, posfoo.x, posfoo.y, posfoo.z ); + } + + posattr.needsUpdate = true; + } + } + + /** + * returns position on the curve + * @param t A position on the curve. Must be in the range [ 0, 1 ]. + */ + getPoint ( t: number, offset = 0 ): Vector3 { + + const geometries = this.exportGeometries(); + + const length = geometries.map( g => g.length ).reduce( ( a, b ) => a + b ); + + const s = length * t; + + const geometry = geometries.find( g => s >= g.s && s <= g.s2 ); + + const posTheta = new TvPosTheta(); + + geometry.getCoords( s, posTheta ); + + posTheta.addLateralOffset( offset ); + + return posTheta.toVector3(); + } + + getLength () { + + const geometries = this.exportGeometries(); + + let length = 0; + + for ( let i = 0; i < geometries.length; i++ ) { + + length += geometries[ i ].length; + + } + + return length; + } +} diff --git a/src/app/core/shapes/parametric-spline.ts b/src/app/core/shapes/parametric-spline.ts new file mode 100644 index 00000000..0dc539d8 --- /dev/null +++ b/src/app/core/shapes/parametric-spline.ts @@ -0,0 +1,808 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { BufferAttribute, BufferGeometry, Group, Line, LineBasicMaterial, Vector2, Vector3 } from 'three'; +import { TvAbstractRoadGeometry } from 'app/modules/tv-map/models/geometries/tv-abstract-road-geometry'; +import { AnyControlPoint } from 'app/modules/three-js/objects/control-point'; +import { TangentLine } from './TangentLine'; +import { AbstractSpline } from './abstract-spline'; +import { CURVE_TESSEL, CURVE_Y, PARACUBICFACTOR } from './spline-config'; +import { HermiteSpline, Length } from './SplineData'; + +export class ParametricSpline extends AbstractSpline { + + public type: string = 'parametric'; + + public mesh: Group; + + /** + * the main control points of the curve, not tangent points + */ + public controlObjects: AnyControlPoint[] = []; + + private tangent: TangentLine; + + // private polyline: PolyLine; + + // private roundline: RoundLine; + + private hdgs: any[] = []; + + /** + * Holds reference to the line segments forming the whole curve + */ + private segments: Line[] = []; + + constructor ( private parent: any ) { + + super(); + + } + + init (): void { + + this.mesh = new Group(); + + this.tangent = new TangentLine( this.controlPointPositions ); + + // this.polyline = new PolyLine( this.controlPointPositions ); + + // this.roundline = new RoundLine( this.controlPointPositions ); + + if ( this.meshAddedInScene ) return; + + this.mesh.add( this.tangent.mesh ); + + this.scene.add( this.mesh ); + + // this.scene.add( this.polyline.mesh ); + + // this.scene.add( this.roundline.mesh ); + + this.meshAddedInScene = true; + + } + + + hide (): void { + + this.controlPoints.forEach( i => i.visible = false ); + + this.tangent.mesh.visible = false; + + // this.polyline.mesh.visible = false; + + // this.roundline.mesh.visible = false; + + } + + show (): void { + + this.controlPoints.forEach( i => i.visible = true ); + + this.tangent.mesh.visible = true; + + // this.polyline.mesh.visible = true; + + // this.roundline.mesh.visible = true; + + } + + hideAllTangents () { + + this.controlObjects.forEach( ( cp: AnyControlPoint ) => { + + this.hideTangenAt( cp.tagindex ); + + } ); + + } + + showControlObjects () { + + this.controlObjects.forEach( co => co.visible = true ); + + } + + hideControlObjects () { + + this.controlObjects.forEach( co => co.visible = false ); + + } + + showTangentsAt ( id: number ) { + + this.controlPoints[ this.controlObjects.length + id * 2 + 0 ].visible = true; + this.controlPoints[ this.controlObjects.length + id * 2 + 1 ].visible = true; + + this.tangent.updateOneSegment( id, this.controlObjects[ id ] ); + + } + + hideTangenAt ( id: number ) { + + this.controlPoints[ this.controlObjects.length + id * 2 + 0 ].visible = false; + this.controlPoints[ this.controlObjects.length + id * 2 + 1 ].visible = false; + + } + + update (): void { + + } + + updateSpine ( cp: AnyControlPoint ) { + + if ( cp.tag == "cp" || cp.tag == "tpf" || cp.tag == "tpb" ) { + + let ptidx = cp.tagindex; + + if ( cp.tag == "cp" ) { + + // do nothing for now + + } + + else if ( cp.tag == "tpf" ) { + + const delta = new Vector3().subVectors( + this.controlPoints[ this.controlObjects.length + ptidx * 2 ].position, + this.controlPoints[ ptidx ].position + ); + + this.hdgs[ ptidx ][ 0 ] = Math.atan2( delta.y, delta.x ); + + this.hdgs[ ptidx ][ 1 ] = delta.length(); + + } + + // tslint:disable-next-line: one-line + else if ( cp.tag == "tpb" ) { + + const delta = new Vector3().subVectors( + this.controlPoints[ this.controlObjects.length + ptidx * 2 + 1 ].position, + this.controlPoints[ ptidx ].position + ); + + this.hdgs[ ptidx ][ 0 ] = Math.PI + Math.atan2( delta.y, delta.x ); + + this.hdgs[ ptidx ][ 2 ] = delta.length(); + + } + + let pa: Vector3, pb: Vector3; + + [ pa, pb ] = this.tangent.updateOneSegment( ptidx, this.controlPoints[ ptidx ].position ); + + this.controlPoints[ this.controlObjects.length + ptidx * 2 ].position.copy( pa ); + + this.controlPoints[ this.controlObjects.length + ptidx * 2 + 1 ].position.copy( pb ); + + if ( ptidx < this.segments.length ) { + + this.updateSegment( ptidx ); + + } + + if ( ptidx - 1 >= 0 ) { + + this.updateSegment( ptidx - 1 ); + + } + + } + + } + + exportGeometries (): TvAbstractRoadGeometry[] { + + throw Error( 'method nor implew' ); + + } + + exportFromSpline (): TvAbstractRoadGeometry[] { + + throw Error( 'method nor implew' ); + } + + add ( position: AnyControlPoint, heading: number, newIndex: number ): AnyControlPoint { + + const controlPointObject = this.createControlPoint( "cp", newIndex, newIndex ); + + this.controlObjects.push( controlPointObject ); + + controlPointObject.copyPosition( position.position ); + + this.hdgs.push( [ heading, 7, 7 ] ); + + const tgX = Math.cos( heading ); + + const tgY = Math.sin( heading ); + + const frontTangentPosition = new Vector3( tgX, tgY, CURVE_Y ) + .multiplyScalar( this.hdgs[ newIndex ][ 1 ] ) + .add( this.controlPointPositions[ newIndex ] ); + + const backTangentPosition = new Vector3( tgX, tgY, CURVE_Y ) + .multiplyScalar( -this.hdgs[ newIndex ][ 2 ] ) + .add( this.controlPointPositions[ newIndex ] ); + + const frontTanget = this.createControlPoint( "tpf", newIndex, newIndex + 1 + newIndex * 2 ); + + frontTanget.position.copy( frontTangentPosition ); + + const backTanget = this.createControlPoint( "tpb", newIndex, newIndex + 1 + newIndex * 2 + 1 ); + + backTanget.position.copy( backTangentPosition ); + + // update tangent line + this.tangent.update( this.hdgs, this.controlObjects ); + + if ( newIndex > 0 ) { + + // add empty curve mesh + this.addNewSegment( newIndex - 1 ); + + // calculate curve mesh + this.updateSegment( newIndex - 1 ); + + } + + this.tangent.updateOneSegment( newIndex, controlPointObject ); + + return controlPointObject; + } + + addNewSegment ( idx ) { + + const buffgeo = new BufferGeometry(); + + buffgeo.attributes.position = new BufferAttribute( new Float32Array( CURVE_TESSEL * 3 ), 3 ); + + const curvemesh = new Line( buffgeo, new LineBasicMaterial( { color: 0x0000ff, opacity: 0.35, linewidth: 2 } ) ); + + curvemesh[ 'tag' ] = 'curve'; + + curvemesh[ 'tagindex' ] = idx; + + curvemesh.userData.parent = this.parent; + + this.segments.push( curvemesh ); + + this.scene.add( curvemesh ); + + // this.controlPointLines.push( curvemesh ); + + } + + updateSegment ( idx: number ) { + + const curvemesh = this.segments[ idx ]; + + if ( !curvemesh ) { + + console.error( "curve not found", idx, this.segments ); + + return; + + } else { + + } + + + const posattr = ( curvemesh.geometry as BufferGeometry ).attributes.position as BufferAttribute; + + const dir1 = new Vector2( Math.cos( this.hdgs[ idx ][ 0 ] ), Math.sin( this.hdgs[ idx ][ 0 ] ) ); + const dir2 = new Vector2( Math.cos( this.hdgs[ idx + 1 ][ 0 ] ), Math.sin( this.hdgs[ idx + 1 ][ 0 ] ) ); + + const p1 = this.controlPointPositions[ idx ]; + const p2 = this.controlPointPositions[ idx + 1 ]; + + const t1 = new Vector3( dir1.x, dir1.y, CURVE_Y ).multiplyScalar( PARACUBICFACTOR * this.hdgs[ idx ][ 1 ] ); + const t2 = new Vector3( dir2.x, dir2.y, CURVE_Y ).multiplyScalar( PARACUBICFACTOR * this.hdgs[ idx + 1 ][ 2 ] ); + + const posfoo = new Vector3(); + + for ( let ii = 0; ii < CURVE_TESSEL; ii++ ) { + + const s = ii / ( CURVE_TESSEL - 1 ), s2 = s * s, s3 = s2 * s; + + const h1 = new Vector3().setScalar( 2 * s3 - 3 * s2 + 1 ).multiply( p1 ); + + const h2 = new Vector3().setScalar( -2 * s3 + 3 * s2 ).multiply( p2 ); + + const h3 = new Vector3().setScalar( s3 - 2 * s2 + s ).multiply( t1 ); + + const h4 = new Vector3().setScalar( s3 - s2 ).multiply( t2 ); + + posfoo.copy( h1 ).add( h2 ).add( h3 ).add( h4 ); + + posattr.setXYZ( ii, posfoo.x, posfoo.y, posfoo.z ); + + } + + posattr.needsUpdate = true; + + } + + getPoint ( s: number, target?: Vector3 ) { + + for ( let i = 0; i < this.segments.length; i++ ) { + + const segment = this.segments[ i ]; + const segmentLength = this.getSegmentLength( i ); + + if ( s <= segmentLength ) { + + + return this.getPointInSegment( ( segmentLength - s ) / segmentLength, i ); + + break; + } + + } + } + + Hermite_Interpolate ( y0, y1, y2, y3, mu, tension, bias ) { + + let m0, m1, mu2, mu3; + + let a0, a1, a2, a3; + + mu2 = mu * mu; + + mu3 = mu2 * mu; + + m0 = ( y1 - y0 ) * ( 1 + bias ) * ( 1 - tension ) / 2; + + m0 += ( y2 - y1 ) * ( 1 - bias ) * ( 1 - tension ) / 2; + + m1 = ( y2 - y1 ) * ( 1 + bias ) * ( 1 - tension ) / 2; + + m1 += ( y3 - y2 ) * ( 1 - bias ) * ( 1 - tension ) / 2; + + a0 = 2 * mu3 - 3 * mu2 + 1; + + a1 = mu3 - 2 * mu2 + mu; + + a2 = mu3 - mu2; + + a3 = -2 * mu3 + 3 * mu2; + + return ( a0 * y1 + a1 * m0 + a2 * m1 + a3 * y2 ); + } + + + // getPoints ( spacing = 5 ) { + + // const positions = []; + + // for ( let i = 0; i < this.segments.length; i++ ) { + + // const segment = this.segments[ i ]; + // const segmentLength = this.getSegmentLength( i ); + + // const p1 = this.controlPointPositions[ i ]; + // const p2 = this.controlPointPositions[ i + 1 ]; + // const t1 = this.controlPoints[ this.controlObjects.length + i * 2 + 0 ].position; + // const t2 = this.controlPoints[ this.controlObjects.length + ( i + 1 ) * 2 + 1 ].position; + + // for ( let t = 0; t < segmentLength; t++ ) { + + // // const x = this.Hermite_Interpolate( t2.x, p2.x, p1.x, t1.x, t / segmentLength, 0, 0 ); + // // const y = this.Hermite_Interpolate( t2.y, p2.y, p1.y, t1.y, t / segmentLength, 0, 0 ); + + // const x = this.Hermite_Interpolate( t1.x, p2.x, p1.x, t2.x, t / segmentLength, 0, 0 ); + // const y = this.Hermite_Interpolate( t1.y, p2.y, p1.y, t2.y, t / segmentLength, 0, 0 ); + + // positions.push( new Vector3( x, y, 0 ) ); + + // } + // } + + // return positions; + // } + + getPoints ( spacing = 10 ) { + + const positions = []; + + for ( let i = 0; i < this.segments.length; i++ ) { + + const segment = this.segments[ i ]; + const segmentLength = this.getSegmentLength( i ); + + const dir1 = new Vector2( Math.cos( this.hdgs[ i ][ 0 ] ), Math.sin( this.hdgs[ i ][ 0 ] ) ); + const dir2 = new Vector2( Math.cos( this.hdgs[ i + 1 ][ 0 ] ), Math.sin( this.hdgs[ i + 1 ][ 0 ] ) ); + + const p1 = this.controlPointPositions[ i ]; + const p2 = this.controlPointPositions[ i + 1 ]; + + const t1 = new Vector3( dir1.x, dir1.y, CURVE_Y ).multiplyScalar( PARACUBICFACTOR * this.hdgs[ i ][ 1 ] ); + const t2 = new Vector3( dir2.x, dir2.y, CURVE_Y ).multiplyScalar( PARACUBICFACTOR * this.hdgs[ i + 1 ][ 2 ] ); + + for ( let u = 0; u < segmentLength; u += spacing ) { + + const t = this.getUtoTmapping( u / segmentLength, 10 ); + + const s = t, s2 = s * s, s3 = s2 * s; + + const h1 = new Vector3().setScalar( 2 * s3 - 3 * s2 + 1 ).multiply( p1 ); + const h2 = new Vector3().setScalar( -2 * s3 + 3 * s2 ).multiply( p2 ); + const h3 = new Vector3().setScalar( s3 - 2 * s2 + s ).multiply( t1 ); + const h4 = new Vector3().setScalar( s3 - s2 ).multiply( t2 ); + + const position = new Vector3().copy( h1 ).add( h2 ).add( h3 ).add( h4 ); + + positions.push( position ); + + } + } + + return positions; + } + + + getUtoTmapping ( u, distance ) { + + const arcLengths = this.getLengths( 10 ); + + let i = 0; + const il = arcLengths.length; + + let targetArcLength; // The targeted u distance value to get + + if ( distance ) { + + targetArcLength = distance; + + } else { + + targetArcLength = u * arcLengths[ il - 1 ]; + + } + + // binary search for the index with largest value smaller than target u distance + + let low = 0, high = il - 1, comparison; + + while ( low <= high ) { + + // less likely to overflow, though probably not issue here, JS doesn't really have integers, all numbers are floats + i = Math.floor( low + ( high - low ) / 2 ); + + comparison = arcLengths[ i ] - targetArcLength; + + if ( comparison < 0 ) { + + low = i + 1; + + } else if ( comparison > 0 ) { + + high = i - 1; + + } else { + + high = i; + break; + + // DONE + + } + + } + + i = high; + + if ( arcLengths[ i ] === targetArcLength ) { + + return i / ( il - 1 ); + + } + + // we could get finer grain at lengths, or use simple interpolation between two points + + const lengthBefore = arcLengths[ i ]; + const lengthAfter = arcLengths[ i + 1 ]; + + const segmentLength = lengthAfter - lengthBefore; + + // determine where we are between the 'before' and 'after' points + + const segmentFraction = ( targetArcLength - lengthBefore ) / segmentLength; + + // add that fractional amount to t + + const t = ( i + segmentFraction ) / ( il - 1 ); + + return t; + + } + + + // getPoints ( spacing = 5 ) { + + // const positions = []; + + // for ( let i = 0; i < this.segments.length; i++ ) { + + // const position = new Vector3(); + + // const segment = this.segments[ i ]; + // const segmentLength = this.getSegmentLength( i ); + + // const hdg1 = this.hdgs[ i ]; + // const hdg2 = this.hdgs[ i + 1 ]; + + // const dir1 = new Vector2( Math.cos( hdg1[ 0 ] ), Math.sin( hdg1[ 0 ] ) ); + // const dir2 = new Vector2( Math.cos( hdg2[ 0 ] ), Math.sin( hdg2[ 0 ] ) ); + + // const p1 = new Vector2( this.controlObjects[ i ].position.x, this.controlObjects[ i ].position.y ); + // const p2 = new Vector2( this.controlObjects[ i + 1 ].position.x, this.controlObjects[ i + 1 ].position.y ); + + // const ma = dir1.x, mb = dir1.y, mc = -mb, md = ma; + + // const det = 1 / ( ma * md - mb * mc ); + + // const mia = det * md, mib = -mb * det, mic = -mc * det, mid = ma * det; + + // const dir2proj = new Vector2( + // dir2.x * mia + dir2.y * mic, + // dir2.x * mib + dir2.y * mid + // ); + + // /*flip y axis*/ + // dir2proj.y = -dir2proj.y; + + // const p2proj = new Vector2().subVectors( p2, p1 ); + + // p2proj.set( p2proj.x * mia + p2proj.y * mic, p2proj.x * mib + p2proj.y * mid ); + + // /*flip y axis*/ + // p2proj.y = -p2proj.y; + + // const hdg = hdg1[ 0 ]; + + // const t1 = new Vector2( 1, 0 ).multiplyScalar( PARACUBICFACTOR * this.hdgs[ i ][ 1 ] ); + // const t2 = new Vector2( dir2proj.x, dir2proj.y ).multiplyScalar( PARACUBICFACTOR * this.hdgs[ i + 1 ][ 2 ] ); + + // const hs = HermiteSpline( new Vector2( 0, 0 ), p2proj, t1, t2 ); + // const length = Length( hs, 0.001 ); + + // const f3 = new Vector2( -2 * p2proj.x + 1 * t1.x + 1 * t2.x, -2 * p2proj.y + 1 * t1.y + 1 * t2.y ); + // const f2 = new Vector2( 3 * p2proj.x - 2 * t1.x - 1 * t2.x, 3 * p2proj.y - 2 * t1.y - 1 * t2.y ); + // const f1 = new Vector2( 1 * t1.x, 1 * t1.y ); + + // const aU = 0; + // const bU = f1.x; + // const cU = f2.x; + // const dU = f3.x; + + // const aV = 0; + // const bV = f1.y; + // const cV = f2.y; + // const dV = f3.y; + + // const steps = segmentLength / spacing; + + // for ( let ii = 0; ii < segmentLength; ii += steps ) { + + // // normalised p between 0 to 1 + // const p = ii / length; + + // const uLocal = + // ( aU ) + + // ( bU * p ) + + // ( cU * p * p ) + + // ( dU * p * p * p ); + + // const vLocal = + // ( aV ) + + // ( bV * p ) + + // ( cV * p * p ) + + // ( dV * p * p * p ); + + // // Derivate to get heading change + // // const dCoeffsU = ( new Vector3( bU, cU, dU ) ).multiply( new Vector3( 1, 2, 3 ) ); + // // const dCoeffsV = ( new Vector3( bV, cV, dV ) ).multiply( new Vector3( 1, 2, 3 ) ); + + // // const dx = this.polyeval( p, dCoeffsU ); + // // const dy = this.polyeval( p, dCoeffsV ); + + // // const tangent = Math.atan2( dy, dx ); + + // // apply rotation with respect to start + // const xnew = p1.x + uLocal; + // const ynew = p1.y + vLocal; + + // position.set( xnew, ynew, 0 ); + + // positions.push( position ); + // } + // } + + // return positions; + // } + + polyeval ( t: number, v: Vector3 ): number { + + return ( v.x ) + ( v.y * t ) + ( v.z * t * t ); + } + + getPointInSegment ( t: number, id: number, rettarget?: Vector3 ): Vector3 { + + const p1 = this.controlPointPositions[ id ]; + const p2 = this.controlPointPositions[ id + 1 ]; + const p3 = this.controlPoints[ this.controlObjects.length + id * 2 + 0 ].position; + const p4 = this.controlPoints[ this.controlObjects.length + ( id + 1 ) * 2 + 1 ].position; + + const s = t, s2 = s * s, s3 = s2 * s; + + const h1 = new Vector3().setScalar( 2 * s3 - 3 * s2 + 1 ).multiply( p1 ); + const h2 = new Vector3().setScalar( -2 * s3 + 3 * s2 ).multiply( p2 ); + const h3 = new Vector3().setScalar( s3 - 2 * s2 + s ).multiply( p3 ); + const h4 = new Vector3().setScalar( s3 - s2 ).multiply( p4 ); + + const posfoo = new Vector3(); + + posfoo.copy( h1 ).add( h2 ).add( h3 ).add( h4 ) + + return posfoo; + + // const dir1 = new Vector2( Math.cos( this.hdgs[ id ][ 0 ] ), Math.sin( this.hdgs[ id ][ 0 ] ) ); + // const dir2 = new Vector2( Math.cos( this.hdgs[ id + 1 ][ 0 ] ), Math.sin( this.hdgs[ id + 1 ][ 0 ] ) ); + + // const p1 = this.controlPointPositions[ id ]; + // const p2 = this.controlPointPositions[ id + 1 ]; + + // const t1 = new Vector3( dir1.x, dir1.y, CURVE_Y ).multiplyScalar( PARACUBICFACTOR * this.hdgs[ id ][ 1 ] ); + // const t2 = new Vector3( dir2.x, dir2.y, CURVE_Y ).multiplyScalar( PARACUBICFACTOR * this.hdgs[ id + 1 ][ 2 ] ); + + // // const t1Pos = this.controlPoints[ this.controlObjects.length + id * 2 + 0 ].position; + // // const t2Pos = this.controlPoints[ this.controlObjects.length + ( id + 1 ) * 2 + 1 ].position; + + // // const t1 = new Vector3().subVectors( t1Pos, p1 ).multiplyScalar( PARACUBICFACTOR ); + // // const t2 = new Vector3().subVectors( p2, t2Pos ).multiplyScalar( PARACUBICFACTOR ); + + // const posfoo = new Vector3(); + + // // for ( let ii = 0; ii < CURVE_TESSEL; ii++ ) { + + // const s = t, s2 = s * s, s3 = s2 * s; + + // const h1 = new Vector3().setScalar( 2 * s3 - 3 * s2 + 1 ).multiply( p1 ); + + // const h2 = new Vector3().setScalar( -2 * s3 + 3 * s2 ).multiply( p2 ); + + // const h3 = new Vector3().setScalar( s3 - 2 * s2 + s ).multiply( t1 ); + + // const h4 = new Vector3().setScalar( s3 - s2 ).multiply( t2 ); + + // posfoo.copy( h1 ).add( h2 ).add( h3 ).add( h4 ); + + // return posfoo; + + // // posattr.setXYZ( ii, posfoo.x, posfoo.y, posfoo.z ); + + // } + + + // const retpoint = rettarget || new Vector3(); + + // const p1 = this.controlObjects[ i ].position; + // const p2 = this.controlObjects[ i + 1 ].position; + + // const t1Pos = this.controlPointPositions[ this.controlObjects.length + i * 2 + 0 ]; + // const t2Pos = this.controlPointPositions[ this.controlObjects.length + ( i + 1 ) * 2 + 1 ]; + + // const t1 = new Vector3().subVectors( t1Pos, p1 ).multiplyScalar( PARACUBICFACTOR ); + // const t2 = new Vector3().subVectors( p2, t2Pos ).multiplyScalar( PARACUBICFACTOR ); + + // const s = t, s2 = s * s, s3 = s2 * s; + + // const h1 = new Vector3().setScalar( 2 * s3 - 3 * s2 + 1 ).multiply( p1 ); + + // const h2 = new Vector3().setScalar( -2 * s3 + 3 * s2 ).multiply( p2 ); + + // const h3 = new Vector3().setScalar( s3 - 2 * s2 + s ).multiply( t1 ); + + // const h4 = new Vector3().setScalar( s3 - s2 ).multiply( t2 ); + + // retpoint.copy( h1 ).add( h2 ).add( h3 ).add( h4 ); + + // return retpoint; + + } + + getSegmentLength ( i: number ): number { + + const p1 = new Vector2( this.controlObjects[ i ].position.x, this.controlObjects[ i ].position.y ); + const p2 = new Vector2( this.controlObjects[ i + 1 ].position.x, this.controlObjects[ i + 1 ].position.y ); + + const hdg1 = this.hdgs[ i ]; + const hdg2 = this.hdgs[ i + 1 ]; + + const dir1 = new Vector2( Math.cos( hdg1[ 0 ] ), Math.sin( hdg1[ 0 ] ) ); + const dir2 = new Vector2( Math.cos( hdg2[ 0 ] ), Math.sin( hdg2[ 0 ] ) ); + + const ma = dir1.x, mb = dir1.y, mc = -mb, md = ma; + + const det = 1 / ( ma * md - mb * mc ); + + const mia = det * md, mib = -mb * det, mic = -mc * det, mid = ma * det; + + const dir2proj = new Vector2( + dir2.x * mia + dir2.y * mic, + dir2.x * mib + dir2.y * mid + ); + + /*flip y axis*/ + dir2proj.y = -dir2proj.y; + + const p2proj = new Vector2().subVectors( p2, p1 ); + + p2proj.set( p2proj.x * mia + p2proj.y * mic, p2proj.x * mib + p2proj.y * mid ); + + /*flip y axis*/ + p2proj.y = -p2proj.y; + + const x = p1.x; const y = p1.y; + + const hdg = hdg1[ 0 ]; + + const t1 = new Vector2( 1, 0 ).multiplyScalar( PARACUBICFACTOR * this.hdgs[ i ][ 1 ] ); + const t2 = new Vector2( dir2proj.x, dir2proj.y ).multiplyScalar( PARACUBICFACTOR * this.hdgs[ i + 1 ][ 2 ] ); + + const hs = HermiteSpline( new Vector2( 0, 0 ), p2proj, t1, t2 ); + const length = Length( hs, 0.001 ); + + return length; + } + + getLength (): number { + + let totalLength = 0; + + for ( let i = 0; i < this.segments.length; i++ ) { + + const length = this.getSegmentLength( i ); + + totalLength += length; + + } + + return totalLength; + } + + getLengths ( divisions ) { + + const cache = []; + let current, last = this.getPoint( 0 ); + let sum = 0; + + cache.push( 0 ); + + for ( let p = 1; p <= divisions; p++ ) { + + current = this.getPoint( p / divisions ); + sum += current.distanceTo( last ); + cache.push( sum ); + last = current; + + } + + return cache; + + // const lenghts = []; + + // lenghts.push( 0 ); + + // for ( let i = 0; i < this.segments.length; i++ ) { + + // lenghts.push( this.getSegmentLength( i ) ); + + // } + + // return lenghts; + } +} \ No newline at end of file diff --git a/src/app/core/shapes/round-line.ts b/src/app/core/shapes/round-line.ts new file mode 100644 index 00000000..a6acade1 --- /dev/null +++ b/src/app/core/shapes/round-line.ts @@ -0,0 +1,227 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { Vector3, BufferAttribute, BufferGeometry, Line, LineBasicMaterial } from 'three'; +import { COLOR } from 'app/shared/utils/colors.service'; +import { MAX_CTRL_POINTS, ARC_TESSEL } from "./spline-config"; +import { BaseControlPoint } from 'app/modules/three-js/objects/control-point'; + +export class RoundLine { + + public radiuses: number[]; + + curveType; + + mesh; + + constructor ( public points: BaseControlPoint[] ) { + + const geometry = new BufferGeometry(); + + geometry.addAttribute( 'position', new BufferAttribute( new Float32Array( MAX_CTRL_POINTS * ARC_TESSEL * 3 ), 3 ) ); + + this.curveType = 'roundline'; + + this.mesh = new Line( geometry, new LineBasicMaterial( { color: COLOR.RED, opacity: 0.35 } ) ); + + this.mesh.castShadow = true; + + this.mesh.renderOrder = 3; + + this.mesh.frustumCulled = false; + + } + + addPoint ( point: BaseControlPoint ) { + + this.points.push( point ); + + } + + calcRadius (): void { + + if ( this.points.length === 0 ) return; + + // init all radiuses to Infinity + this.radiuses = new Array( this.points.length ); + + // store lengths from one point to another for the whole spline + const lengths = new Array( this.points.length - 1 ); + + let currentPoint: BaseControlPoint = null; + + let nextPoint: BaseControlPoint = null; + + this.points.forEach( ( currentPoint, i ) => { + + // set the radius at each point 0 by default + this.radiuses[ i ] = 0; + + // set the lengths until the last point + if ( i < this.points.length - 1 ) { + + nextPoint = this.points[ i + 1 ]; + + lengths[ i ] = currentPoint.position.distanceTo( nextPoint.position ); + + } + + } ); + + // // foreach point + // for ( let i = 0; i < this.points.length; i++ ) { + // // set the radius at each point 0 by default + // this.radiuses[ i ] = 0; + // // set the lengths until the last point + // if ( i < this.points.length - 1 ) { + // currentPoint = this.points[ i ]; + // nextPoint = this.points[ i + 1 ]; + // lengths[ i ] = currentPoint.distanceTo( nextPoint ); + // } + // } + + // foreach point except the first one + for ( let i = 1; i < this.points.length - 1; i++ ) { + + this.radiuses[ i ] = Math.min( lengths[ i - 1 ], lengths[ i ] ); + + } + + for ( let updated = true; updated; ) { + + updated = false; + + for ( let i = 1; i < this.points.length - 1; i++ ) { + + const leftR = this.radiuses[ i - 1 ] + this.radiuses[ i ] > lengths[ i - 1 ] ? lengths[ i - 1 ] / 2 : this.radiuses[ i ]; + + const rightR = this.radiuses[ i + 1 ] + this.radiuses[ i ] > lengths[ i ] ? lengths[ i ] / 2 : this.radiuses[ i ]; + + const minR = Math.min( leftR, rightR ); + + if ( minR != this.radiuses[ i ] ) { + + updated = true; + + this.radiuses[ i ] = minR; + + } + } + } + } + + update (): void { + + if ( this.points.length <= 1 ) return; + + this.calcRadius(); + + const position: BufferAttribute = this.mesh.geometry.attributes.position; + + const pos = new Vector3(); + + const ARC_TESSEL_HALF = ARC_TESSEL / 2; + + let vertexIndex = 0; + + let currentPoint: Vector3 = null; + + let prevPoint: Vector3 = null; + + let nextPoint: Vector3 = null; + + let radius = null; + + // foreach point + + for ( let i = 0; i < this.points.length; i++ ) { + + currentPoint = this.points[ i ].position; + + radius = this.radiuses[ i ]; + + if ( radius == 0 ) { + + pos.copy( currentPoint ); + + position.setXYZ( vertexIndex++, pos.x, pos.y, pos.z ); + + } else { + + prevPoint = this.points[ i - 1 ].position; + + nextPoint = this.points[ i + 1 ].position; + + const p1 = new Vector3() + .subVectors( prevPoint, currentPoint ) + .normalize() + .multiplyScalar( radius ) + .add( currentPoint ); + + const p0 = currentPoint; + + const p2 = new Vector3() + .subVectors( nextPoint, currentPoint ) + .normalize() + .multiplyScalar( radius ) + .add( currentPoint ); + + for ( let ii = 0; ii < ARC_TESSEL_HALF; ii++ ) { + + pos.lerpVectors( p1, p0, ii / ARC_TESSEL_HALF ); + + pos.copy( this.arcInterpolation( currentPoint, prevPoint, nextPoint, radius, pos ) ); + + position.setXYZ( vertexIndex++, pos.x, pos.y, pos.z ); + + } + + for ( let ii = 0; ii < ARC_TESSEL_HALF; ii++ ) { + + pos.lerpVectors( p0, p2, ii / ARC_TESSEL_HALF ); + + pos.copy( this.arcInterpolation( currentPoint, prevPoint, nextPoint, radius, pos ) ); + + position.setXYZ( vertexIndex++, pos.x, pos.y, pos.z ); + + } + } + } + + // repeat last point + for ( let i = vertexIndex; i < MAX_CTRL_POINTS * ARC_TESSEL; i++ ) { + + position.setXYZ( i, pos.x, pos.y, pos.z ); + + } + + position.needsUpdate = true; + } + + arcInterpolation ( currentPoint: Vector3, prevPoint: Vector3, nextPoint: Vector3, radius: number, v: Vector3 ) { + + const va = new Vector3() + .subVectors( prevPoint, currentPoint ) + .normalize() + .multiplyScalar( radius ); + + const vb = new Vector3() + .subVectors( nextPoint, currentPoint ) + .normalize() + .multiplyScalar( radius ); + + // const t = ( va.x * va.x + va.y * va.y + va.z * va.z ) / ( va.x * va.x + va.y * va.y + va.z * va.z + vb.x * va.x + vb.y * va.y + vb.z * va.z ); + const t = ( va.x * va.x + va.z * va.z + va.y * va.y ) + / ( va.x * va.x + va.z * va.z + va.y * va.y + vb.x * va.x + vb.z * va.z + vb.y * va.y ); + + // center of circle + const p = new Vector3().addVectors( va, vb ).multiplyScalar( t ).add( currentPoint ); + + // radius of circle + const r = new Vector3().addVectors( currentPoint, va ).distanceTo( p ); + + // project to circle + return new Vector3().subVectors( v, p ).normalize().multiplyScalar( r ).add( p ); + } +} diff --git a/src/app/core/shapes/spiral-math.js b/src/app/core/shapes/spiral-math.js new file mode 100644 index 00000000..650b19b7 --- /dev/null +++ b/src/app/core/shapes/spiral-math.js @@ -0,0 +1,731 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +var SPIRAL = { + /** S(x) for small x numerator. */ + SN: [ + -2.99181919401019853726E3, + 7.08840045257738576863E5, + -6.29741486205862506537E7, + 2.54890880573376359104E9, + -4.42979518059697779103E10, + 3.18016297876567817986E11 + ], + + /** S(x) for small x denominator. */ + SD: [ + 2.81376268889994315696E2, + 4.55847810806532581675E4, + 5.17343888770096400730E6, + 4.19320245898111231129E8, + 2.24411795645340920940E10, + 6.07366389490084639049E11 + ], + + /** C(x) for small x numerator. */ + CN: [ + -4.98843114573573548651E-8, + 9.50428062829859605134E-6, + -6.45191435683965050962E-4, + 1.88843319396703850064E-2, + -2.05525900955013891793E-1, + 9.99999999999999998822E-1 + ], + + /** C(x) for small x denominator. */ + CD: [ + 3.99982968972495980367E-12, + 9.15439215774657478799E-10, + 1.25001862479598821474E-7, + 1.22262789024179030997E-5, + 8.68029542941784300606E-4, + 4.12142090722199792936E-2, + 1.00000000000000000118E0 + ], + + /** Auxiliary function f(x) numerator. */ + FN: [ + 4.21543555043677546506E-1, + 1.43407919780758885261E-1, + 1.15220955073585758835E-2, + 3.45017939782574027900E-4, + 4.63613749287867322088E-6, + 3.05568983790257605827E-8, + 1.02304514164907233465E-10, + 1.72010743268161828879E-13, + 1.34283276233062758925E-16, + 3.76329711269987889006E-20 + ], + + /** Auxiliary function f(x) denominator. */ + FD: [ + 7.51586398353378947175E-1, + 1.16888925859191382142E-1, + 6.44051526508858611005E-3, + 1.55934409164153020873E-4, + 1.84627567348930545870E-6, + 1.12699224763999035261E-8, + 3.60140029589371370404E-11, + 5.88754533621578410010E-14, + 4.52001434074129701496E-17, + 1.25443237090011264384E-20 + ], + + /** Auxiliary function g(x) numerator. */ + GN: [ + 5.04442073643383265887E-1, + 1.97102833525523411709E-1, + 1.87648584092575249293E-2, + 6.84079380915393090172E-4, + 1.15138826111884280931E-5, + 9.82852443688422223854E-8, + 4.45344415861750144738E-10, + 1.08268041139020870318E-12, + 1.37555460633261799868E-15, + 8.36354435630677421531E-19, + 1.86958710162783235106E-22 + ], + + /** Auxiliary function g(x) denominator. */ + GD: [ + 1.47495759925128324529E0, + 3.37748989120019970451E-1, + 2.53603741420338795122E-2, + 8.14679107184306179049E-4, + 1.27545075667729118702E-5, + 1.04314589657571990585E-7, + 4.60680728146520428211E-10, + 1.10273215066240270757E-12, + 1.38796531259578871258E-15, + 8.39158816283118707363E-19, + 1.86958710162783236342E-22 + ], + + /** + * Compute a polynomial in x. + * @param x double; x + * @param coef double[]; coefficients + * @return polynomial in x + */ + polevl: function(x, coef) { + var result = coef[0]; + for (var i = 0; i < coef.length; i++) { + result = result * x + coef[i]; + } + return result; + }, + + /** + * Compute a polynomial in x. + * @param x double; x + * @param coef double[]; coefficients + * @return polynomial in x + */ + p1evl: function(x, coef) { + var result = x + coef[0]; + for (var i = 0; i < coef.length; i++) { + result = result * x + coef[i]; + } + return result; + }, + + /** + * Approximate the Fresnel function. + * @param xxa double; the xxa parameter + * @return double[]; array with two double values c and s + */ + fresnel: function(xxa) { + var x = Math.abs(xxa); + var x2 = x * x; + var cc, ss; + + if (x2 < 2.5625) { + var t = x2 * x2; + ss = x * x2 * this.polevl(t, this.SN) / this.p1evl(t, this.SD); + cc = x * this.polevl(t, this.CN) / this.polevl(t, this.CD); + } + else if (x > 36974.0) { + cc = 0.5; + ss = 0.5; + } + else { + var t = Math.PI * x2; + var u = 1.0 / (t * t); + t = 1.0 / t; + var f = 1.0 - u * this.polevl(u, this.FN) / this.p1evl(u, this.FD); + var g = t * this.polevl(u, this.GN) / this.p1evl(u, this.GD); + + t = Math.PI * 0.5 * x2; + var c = Math.cos(t); + var s = Math.sin(t); + t = Math.PI * x; + cc = 0.5 + (f * s - g * c) / t; + ss = 0.5 - (f * c + g * s) / t; + } + + if (xxa < 0.0) { + cc = -cc; + ss = -ss; + } + return [cc, ss]; + }, + + /** + * Approximate one point of the "standard" spiral (curvature at start is 0). + * @param s run-length along spiral + * @param cDot first derivative of curvature [1/m2] + * @param initialCurvature double; curvature at start + * @return double[x,y,t]; array of three double values containing x, y, and tangent direction + */ + odrSpiral: function(s, cDot, initialCurvature) { + var a = Math.sqrt(Math.PI / Math.abs(cDot)); + + var xy = this.fresnel(initialCurvature + s / a); + return [xy[0] * a, xy[1] * a * Math.sign(cDot), s * s * cDot * 0.5]; + }, + + /** + * Approximate a clothoid that starts in the x-direction. + * @param initialCurvature double; curvature at start + * @param curvatureDerivative double; rate of curvature change along the clothoid + * @param length double; total length of the clothoid + * @param numSegments int; number of segments used to approximate (the number of points is one higher than this) + * @return double[[x,y,z]]; the line points array; the z-component of each point is set to 0 + */ + clothoid_0: function(initialCurvature, curvatureDerivative, length, numSegments) { + var points = new Array(numSegments + 1); + // get start tangent + var offset = this.odrSpiral(initialCurvature / curvatureDerivative, curvatureDerivative, initialCurvature); + // make it to align with x-axis + var sinRot = Math.sin(-offset[2]); + var cosRot = Math.cos(-offset[2]); + for (var i = 0; i <= numSegments; i++) { + var xyd = this.odrSpiral(i * length / numSegments + initialCurvature / curvatureDerivative, curvatureDerivative, initialCurvature); + var dx = xyd[0] - offset[0]; + var dy = xyd[1] - offset[1]; + points[i] = [dx * cosRot - dy * sinRot, dx * sinRot + dy * cosRot , 0]; + } + return points; + }, + + /** + * Approximate a clothoid that starts at a given point in the given direction and curvature. Elevation is linearly + * interpolated over the length of the clothoid. + * @param x1 double; x-coordinate of the start point + * @param y1 double; y-coordinate of the start point + * @param z1 double; z-component at start of the curve + * @param startDirection double; rotation in radians at the start of the curve + * @param startCurvature double; curvature at the start of the clothoid + * @param endCurvature double; curvature at the end of the clothoid + * @param length double; length of the clothoid + * @param z2 double; z-component at end of the curve + * @param numSegments int; number of segments used to approximate (the number of points is one higher than this) + * @return double[[x,y,z]]; the line points array; the clothoid + */ + clothoid_1: function(x1, y1, z1, startDirection, startCurvature, endCurvature, length, z2, numSegments) { + var result = this.clothoid_0(startCurvature, (endCurvature - startCurvature) / length, length, numSegments); + var sinRot = Math.sin(startDirection); + var cosRot = Math.cos(startDirection); + var list = new Array(result.length); + var elevationPerStep = (z2 - z1) / (result.length - 1); + for (var i = 0; i < result.length; i++) { + var p = result[i]; + list[i] = [x1 + cosRot * p[0] - sinRot * p[1], y1 + sinRot * p[0]+cosRot * p[1] , z1 + i * elevationPerStep]; + } + return list; + }, + + /** + * Approximate a clothoid with curvature 0 at start. + * @param start double[x,y,z]; starting point of the clothoid + * @param startDirection double; start direction of the clothoid + * @param endCurvature double; curvature at the end of the clothoid [1/m] + * @param length double; length of the clothoid + * @param z2 double; elevation at end of the clothoid + * @param numSegments int; number of segments used to approximate (the number of points is one higher than this) + * @return double[[x,y,z]]; the line points array; the clothoid + */ + clothoid_2: function(start, startDirection, endCurvature, length, z2, numSegments) { + return clothoid_1(start[0], start[1], start[2], startDirection, 0, endCurvature, length, z2, numSegments); + }, + + /** + * Approximate a clothoid. + * @param start double[x,y,z]; starting point of the clothoid + * @param startDirection double; start direction of the clothoid + * @param startCurvature double; curvature at the start of the clothoid [1/m] + * @param endCurvature double; curvature at the end of the clothoid [1/m] + * @param length double; length of the clothoid + * @param z2 double; elevation at end of the clothoid + * @param numSegments int; number of segments used to approximate (the number of points is one higher than this) + * @return double[[x,y,z]]; the line points array; the clothoid + */ + clothoid_3: function(start, startDirection, startCurvature, endCurvature, length, z2, numSegments) { + return clothoid_1(start[0], start[1], start[2], startDirection, startCurvature, endCurvature, length, z2, numSegments); + }, + + /** + * Approximate a clothoid with curvature 0 at start. + * @param start double[x,y,z]; starting point of the clothoid + * @param startDirection double; start direction of the clothoid + * @param endCurvature double; curvature at the end of the clothoid + * @param length double; length of the clothoid + * @param z2 double; elevation at end of the clothoid + * @param numSegments int; number of segments used to approximate (the number of points is one higher than this) + * @return double[[x,y,z]]; the line points array; the clothoid + */ + clothoid_4: function(start, startDirection, endCurvature, length, z2, numSegments) { + return clothoid_3(start, startDirection, 0, endCurvature, length, z2, numSegments); + }, + + /** + * vec2Angle: get direction of vector2d in radians, which is angle between x axis and the vector + * @param xy double; vector coordinate, not need to be normalized + * @return double; [0,2*PI] + */ + vec2Angle: function(x,y) { + var d=Math.sqrt(x*x+y*y); + x/=d; y/=d + if(y>=0) return Math.acos(x); + else return 2*Math.PI-Math.acos(x); + }, + + /** + * guessA: Find guess for zeros of function g(A) + * @param {*} double; phi0 + * @param {*} double; phi1 + * @return double; + */ + guessA: function (phi0, phi1) { + var CF = [2.989696028701907, 0.716228953608281, -0.458969738821509, -0.502821153340377, 0.261062141752652, -0.045854475238709]; + var X = phi0 / Math.PI; + var Y = phi1 / Math.PI; + var xy = X * Y; + return (phi0 + phi1) * (CF[0] + xy * (CF[1] + xy * CF[2]) + (CF[3] + xy * CF[4]) * (X * X + Y * Y) + CF[5] * (X * X * X * X + Y * Y * Y * Y)); + }, + + /** + * findA: Find a zero of function g(A) defined as + * g(A) = \int_0^1 \sin( A*t^2+(delta-A)*t+phi0 ) dt + * @param {*} double; Aguess + * @param {*} double; delta + * @param {*} double; phi0 + * @param {*} double; tol + * @return [double,double] + */ + findA: function (Aguess, delta, phi0, tol) { + var A = Aguess; + for (var iter = 1; iter <= 500; iter++) { + [intC, intS] = this.GeneralizedFresnelCS(3, 2 * A, delta - A, phi0); + var f = intS[0]; + var df = intC[2] - intC[1]; + A = A - f / df; + if (Math.abs(f) < tol) + break; + } + if (Math.abs(f) > tol * 10) { + console.log('Newton iteration fails, f = ', f); + console.log('Aguess = ', Aguess, ', A = ', A, ', delta = ', delta, ', phi0 = ', phi0); + } + return [A, iter] + }, + /** + * normalizeAngle: normalize angle in the range [-pi,pi] + * @param {*}double; phi_in + * @return double; normalized angle + */ + normalizeAngle: function (phi_in) { + var phi = phi_in; + while (phi > Math.PI) + phi = phi - 2 * Math.PI; + while (phi < -Math.PI) + phi = phi + 2 * Math.PI; + return phi + }, + /** + * buildClothoid: Compute parameters of the G1 Hermite clothoid fitting + * + * x0, y0 = coodinate of initial point + * theta0 = orientation (angle) of the clothoid at initial point + * x1, y1 = coodinate of final point + * theta1 = orientation (angle) of the clothoid at final point + * + * On output: + * L = the lenght of the clothoid curve from initial to final point + * k = curvature at initial point + * dk = derivative of curvature respect to arclength, notice that curvature at final point is k+dk*L + * iter = Newton Iterations used to solve the interpolation problem + */ + buildClothoid: function( x0, y0, theta0, x1, y1, theta1 ) { + var dx = x1 - x0; + var dy = y1 - y0; + var r = Math.sqrt(dx * dx + dy * dy); + var phi = Math.atan2(dy, dx); + + var phi0 = this.normalizeAngle(theta0 - phi); + var phi1 = this.normalizeAngle(theta1 - phi); + var delta = phi1 - phi0; + + // initial point + var Aguess = this.guessA(phi0, phi1); + + // Newton iteration + [A, iter] = this.findA(Aguess, delta, phi0, 1e-12); + + // final operation + [h, g] = this.GeneralizedFresnelCS(1, 2 * A, delta - A, phi0); + var L = r / h; + + if (L > 0) { + var k = (delta - A) / L; + var dk = 2 * A / (L * L); + } + else { + console.error('negative length'); + } + + + return [ k, dk, L, iter ] + }, + + /** + * + * @param {*} int; nk + * @param {*} double; a + * @param {*} double; b + * @param {*} double; c + */ + GeneralizedFresnelCS: function (nk, a, b, c) { + var epsi = 1e-3; // best thresold + if (Math.abs(a) < epsi) // case `a` small + [X, Y] = this.evalXYaSmall(nk, a, b, 3); + else + [X, Y] = this.evalXYaLarge(nk, a, b); + var cc = Math.cos(c); + var ss = Math.sin(c); + for (var k = 0; k < nk; k++) { + var xx = X[k]; + var yy = Y[k]; + X[k] = xx * cc - yy * ss; + Y[k] = xx * ss + yy * cc; + } + return [X,Y] + }, + + FresnelCSk: function (nk, t) { + var C = new Array(nk); + var S = new Array(nk); + [C[0], S[0]] = this.FresnelCS(t); + if (nk > 1) { + var tt = Math.PI / 2 * t * t; + var ss = Math.sin(tt); + var cc = Math.cos(tt); + C[1] = ss / Math.PI; + S[1] = (1 - cc) / Math.PI; + if (nk > 2) { + C[2] = (t * ss - S[0]) / Math.PI; + S[2] = (C[0] - t * cc) / Math.PI; + } + } + return [C,S] + }, + evalXYaLarge: function (nk, a, b) { + var X = new Array(nk); + var Y = new Array(nk); + var s = Math.sign(a); + var z = Math.sqrt(Math.abs(a) / Math.PI); + var ell = s * b / Math.sqrt(Math.abs(a) * Math.PI); + var g = -0.5 * s * b *b / Math.abs(a); + [Cl, Sl] = this.FresnelCSk(nk, ell); + [Cz, Sz] = this.FresnelCSk(nk, ell + z); + var dC =new Array(Cz.length); /*= Cz - Cl;*/for(var i=0;i 1) { + cg = cg / z; + sg = sg / z; + DC = dC[1] - ell * dC[0]; + DS = dS[1] - ell * dS[0]; + X[1] = cg * DC - s * sg * DS; + Y[1] = sg * DC + s * cg * DS; + if (nk > 2) { + cg = cg / z; + sg = sg / z; + DC = dC[2] + ell * (ell * dC[0] - 2 * dC[1]); + DS = dS[2] + ell * (ell * dS[0] - 2 * dS[1]); + X[2] = cg * DC - s * sg * DS; + Y[2] = sg * DC + s * cg * DS; + } + } + return [X, Y]; + }, + + rLommel: function (mu, nu, b) { + var tmp = 1 / ((mu + nu + 1) * (mu - nu + 1)); + var res = tmp; + for (var n = 0; n < 100; n++) { + tmp = tmp * (-b / (2 * n + mu - nu + 1)) * (b / (2 * n + mu + nu + 1)); + res = res + tmp; + if (Math.abs(tmp) < Math.abs(res) * 1e-50) + break; + } + return res; + }, + evalXYazero: function (nk, b) { + var X = new Array(nk); + var Y = new Array(nk); + var sb = Math.sin(b); + var cb = Math.cos(b); + var b2 = b * b; + if (Math.abs(b) < 1e-3) { + X[0] = 1 - (b2 / 6) * (1 - (b2 / 20) * (1 - (b2 / 42))); + Y[0] = (b / 2) * (1 - (b2 / 12) * (1 - (b2 / 30))); + } + else { + X[0] = sb / b; + Y[0] = (1 - cb) / b; + } + // use recurrence in the stable part + var m = Math.min(Math.max(1, Math.floor(2 * b)), nk); + for (var k = 1; k <= m - 1; k++) { + X[k] = (sb - k * Y[k-1]) / b; + Y[k] = (k * X[k-1] - cb) / b; + } + // use Lommel for the unstable part + if (m < nk) { + var A = b * sb; + var D = sb - b * cb; + var B = b * D; + var C = -b2 * sb; + var rLa = this.rLommel(m + 1 / 2, 3 / 2, b); + var rLd = this.rLommel(m + 1 / 2, 1 / 2, b); + for (var k = m; k <= nk - 1; k++) { + var rLb = this.rLommel(k + 3 / 2, 1 / 2, b); + var rLc = this.rLommel(k + 3 / 2, 3 / 2, b); + X[k] = (k * A * rLa + B * rLb + cb) / (1 + k); + Y[k] = (C * rLc + sb) / (2 + k) + D * rLd; + rLa = rLc; + rLd = rLb; + } + } + return [X, Y]; + }, + evalXYaSmall: function (nk, a, b, p) { + [X0, Y0] = this.evalXYazero(nk + 4 * p + 2, b); + + var X = new Array(nk); + var Y = new Array(nk); + var tmpX = new Array(p + 1); + var tmpY = new Array(p + 1); + var sum=function(arr) { + var sumation=0.0; + for(var i=0;i eps) { + twofn = twofn + 2.0; + fact = fact * twofn * (twofn - 1.0); + denterm = denterm + 4.0; + numterm = numterm * t; + term = numterm / (fact * denterm); + sum = sum + term; + ratio = Math.abs(term / sum); + } + + FresnelC = x * sum; + + // Sine integral series + twofn = 1.0; + fact = 1.0; + denterm = 3.0; + numterm = 1.0; + sum = 1.0 / 3.0; + ratio = 10.0; + + while (ratio > eps) { + twofn = twofn + 2.0; + fact = fact * twofn * (twofn - 1.0); + denterm = denterm + 4.0; + numterm = numterm * t; + term = numterm / (fact * denterm); + sum = sum + term; + ratio = Math.abs(term / sum); + } + + FresnelS = (Math.PI / 2) * sum * x * x * x; + } + else if (x < 6.0) { + // Rational approximation for f + var sumn = 0.0; + var sumd = fd[11]; + for (var k = 10; k >= 0; k--) { + sumn = fn[k] + x * sumn; + sumd = fd[k] + x * sumd; + } + var f = sumn / sumd; + // Rational approximation for g + sumn = 0.0; + sumd = gd[11]; + for (var k = 10; k >= 0; k--) { + sumn = gn[k] + x * sumn; + sumd = gd[k] + x * sumd; + } + var g = sumn / sumd; + var U = (Math.PI / 2) * x * x; + var SinU = Math.sin(U); + var CosU = Math.cos(U); + FresnelC = 0.5 + f * SinU - g * CosU; + FresnelS = 0.5 - f * CosU - g * SinU; + } + else { + // x >= 6; asymptotic expansions for f and g + t = -Math.pow(Math.PI * x * x, -2.0); + // Expansion for f + var numterm = -1.0; + var term = 1.0; + var sum = 1.0; + var oldterm = 1.0; + var ratio = 10.0; + var eps10 = 0.1 * eps; + + while (ratio > eps10) { + numterm = numterm + 4.0; + term = term * numterm * (numterm - 2.0) * t; + sum = sum + term; + absterm = Math.abs(term); + ratio = Math.abs(term / sum); + if (oldterm < absterm) { + console.log('\n\n !!In FresnelCS f not converged to eps'); + ratio = eps10; + } + oldterm = absterm; + } + + var f = sum / (Math.PI * x); + // Expansion for g + numterm = -1.0; + term = 1.0; + sum = 1.0; + oldterm = 1.0; + ratio = 10.0; + eps10 = 0.1 * eps; + + while (ratio > eps10) { + numterm = numterm + 4.0; + term = term * numterm * (numterm + 2.0) * t; + sum = sum + term; + absterm = Math.abs(term); + ratio = Math.abs(term / sum); + if (oldterm < absterm) { + console.log('\n\n!!In FresnelCS g not converged to eps'); + ratio = eps10; + } + oldterm = absterm; + } + var g = sum / ((Math.PI * x) * (Math.PI * x) * x); + var U = (Math.PI / 2) * x * x; + var SinU = Math.sin(U); + var CosU = Math.cos(U); + FresnelC = 0.5 + f * SinU - g * CosU; + FresnelS = 0.5 - f * CosU - g * SinU; + } + if (y < 0) { + FresnelC = -FresnelC; + FresnelS = -FresnelS; + } + return [FresnelC, FresnelS]; + } +} + +module.exports = SPIRAL \ No newline at end of file diff --git a/src/app/core/shapes/spline-config.ts b/src/app/core/shapes/spline-config.ts new file mode 100644 index 00000000..7c31f056 --- /dev/null +++ b/src/app/core/shapes/spline-config.ts @@ -0,0 +1,11 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +export const MAX_CTRL_POINTS = 100; +export const ARC_TESSEL = 24; +export const CURVE_TESSEL = 48; +export const CURVE_Y = 0; +export const ARC_SEGMENTS = 2; +export const PARACUBICFACTOR = 3.5; +export const TANGENT_HANDLE_LENGTH = 7; diff --git a/src/app/core/time.ts b/src/app/core/time.ts new file mode 100644 index 00000000..0205a698 --- /dev/null +++ b/src/app/core/time.ts @@ -0,0 +1,63 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +export class Time { + + /** + * Time passed in simulation in milliseconds or ms + */ + static time: number = 0; + + /** + * Fixed time-step for physics/simulation update + * in millisecond + * 20ms = 50 updates per second + */ + static fixedDeltaTime: number = 20; + + /** + * Time since last graphic update + */ + static deltaTime: number = 0; + + /** + * Time since last graphic update in ms + */ + static deltaTimeInMs: number = 0; + + /** + * Graphics frames count + */ + static frameCount: number = 0; + + static realTimeSinceStart: number = 0; + + /** + * Returns rounded second + */ + static get seconds () { + + return Math.round( Time.time * 0.001 ); + + } + + /** + * Returns time in seconds without rounding + */ + static get inSeconds () { + + return Time.time * 0.001; + + } + + static reset () { + + this.deltaTime = 0; + this.deltaTimeInMs = 0; + this.time = 0; + this.frameCount = 0; + this.realTimeSinceStart = 0; + + } +} diff --git a/src/app/core/tools/_road-plan-arc-tool.ts b/src/app/core/tools/_road-plan-arc-tool.ts new file mode 100644 index 00000000..970ebcb0 --- /dev/null +++ b/src/app/core/tools/_road-plan-arc-tool.ts @@ -0,0 +1,83 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +// import { RoadPlanTool } from './road-plan-tool'; +// import { Points } from 'three'; +// import { OdAbstractRoadGeometry } from '../../modules/tv-map/models/geometries/od-abstract-road-geometry'; +// import { ControlPoint } from 'app/modules/three-js/objects/control-point'; +// import { OdArcGeometry } from 'app/modules/tv-map/models/geometries/od-arc-geometry'; +// import { Maths } from 'app/utils/maths'; + +// export class RoadPlanArcTool extends RoadPlanTool { + +// private cps: ControlPoint[] = []; + +// protected onControlPointSelected ( e: ControlPoint ) { + +// try { + +// const road = this.openDrive.getRoadById( e.userData.roadId ); + +// if ( road == null ) return; + +// this.selectRoad( road ); + +// // this.cps.push( e ); + +// } catch ( e ) { + +// // console.error( e ); + +// } + +// } + +// protected onControlPointAdded ( e: ControlPoint ) { + +// this.cps.push( e ); + +// if ( this.cps.length % 3 === 0 ) { + +// const count = this.cps.length; +// const points = this.cps; +// const cp1 = points[ count - 3 ]; +// const cp2 = points[ count - 2 ]; +// const cp3 = points[ count - 1 ]; + +// const road = this.createRoad(); + +// this.addDefaultLanes( road ); + +// this.helper.createArcGeometry( cp1, cp2, cp3, road ); + +// this.selectRoad( road ); + +// this.cps.splice( 0, this.cps.length ); +// } +// } + +// protected onControlPointMoved ( e: ControlPoint ) { + +// try { + +// const road = this.openDrive.getRoadById( e.userData.roadId ); + +// if ( road == null ) return; + +// const geometry = e.userData.geometry as OdAbstractRoadGeometry; + +// const index = e.userData.index; + +// this.helper.updateRoadGeometry( e, road, geometry, index ); + +// const arc = geometry as OdArcGeometry; + +// } catch ( e ) { + +// // console.error( e ); + +// } + +// } +// } \ No newline at end of file diff --git a/src/app/core/tools/_road-plan-line-tool.ts b/src/app/core/tools/_road-plan-line-tool.ts new file mode 100644 index 00000000..76cfad3e --- /dev/null +++ b/src/app/core/tools/_road-plan-line-tool.ts @@ -0,0 +1,106 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +// import { RoadPlanTool } from './road-plan-tool'; +// import { Points } from 'three'; +// import { OdAbstractRoadGeometry } from '../../modules/tv-map/models/geometries/od-abstract-road-geometry'; +// import { MultiCmdsCommand } from 'app/modules/tv-map/commands/multi-cmds-command'; +// import { AddRoadCommand } from 'app/modules/tv-map/commands/add-road-command'; +// import { AddRoadGeometryCommand } from 'app/modules/tv-map/commands/add-road-geometry-command'; +// import { Geometry } from 'ngx-perfect-scrollbar'; +// import { CommandHistory } from 'app/services/command-history'; +// import { OdBuilder } from 'app/modules/tv-map/builders/od-builder.service'; + +// export class RoadPlanLineTool extends RoadPlanTool { + +// private cps: Points[] = []; + +// protected onControlPointSelected ( e: any ) { + +// try { + +// const road = this.openDrive.getRoadById( e.userData.roadId ); + +// if ( road == null ) return; + +// this.selectRoad( road ); + +// } catch ( e ) { + +// // console.error( e ); + +// } + +// } + +// protected onControlPointAdded ( e: Points ) { + +// this.cps.push( e ); + +// // if ( this.cps.length % 2 === 0 ) { + +// // const count = this.cps.length; +// // const points = this.cps; +// // const cp1 = points[ count - 2 ]; +// // const cp2 = points[ count - 1 ]; + +// // const road = this.createRoad(); + +// // this.addDefaultLanes( road ); + +// // const geometry = this.helper.createLineGeometry( cp1, cp2, road ); + +// // const addRoad = new AddRoadCommand( road ); +// // const addRoadGeom = new AddRoadGeometryCommand( road, geometry ); + +// // const multiCmd = new MultiCmdsCommand( [ addRoad, addRoadGeom ] ); + +// // CommandHistory.execute( multiCmd ); + +// // this.selectRoad( road ); + +// // this.cps.splice( 0, count ); +// // } + +// // if ( this.shapeEditor.controlPointCount % 3 === 0 ) { +// // +// // const count = this.shapeEditor.controlPointCount; +// // const points = this.shapeEditor.controlPoints; +// // const cp1 = points[ count - 3 ]; +// // const cp2 = points[ count - 2 ]; +// // const cp3 = points[ count - 1 ]; +// // +// // const road = this.createRoad(); +// // +// // this.addDefaultLanes( road ); +// // +// // this.helper.createArcGeometry( cp1, cp2, cp3, road ); +// // +// // this.selectRoad( road ); +// // +// // } +// } + +// protected onControlPointMoved ( e: Points ) { + +// try { + +// const road = this.openDrive.getRoadById( e.userData.roadId ); + +// if ( road == null ) return; + +// const geometry = e.userData.geometry as OdAbstractRoadGeometry; + +// const index = e.userData.index; + +// this.helper.updateRoadGeometry( e, road, geometry, index ); + +// } catch ( e ) { + +// // console.error( e ); + +// } + +// } +// } \ No newline at end of file diff --git a/src/app/core/tools/_road-plan-spline-tool.ts b/src/app/core/tools/_road-plan-spline-tool.ts new file mode 100644 index 00000000..454668f0 --- /dev/null +++ b/src/app/core/tools/_road-plan-spline-tool.ts @@ -0,0 +1,290 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +// import { RoadPlanTool } from './road-plan-tool'; +// import { Points, Vector3, Vector2, Line, Line3, ArrowHelper } from 'three'; +// import { OdAbstractRoadGeometry } from '../../modules/tv-map/models/geometries/od-abstract-road-geometry'; +// import { ControlPoint } from 'app/modules/three-js/objects/control-point'; +// import { OdArcGeometry } from 'app/modules/tv-map/models/geometries/od-arc-geometry'; +// import { Maths } from 'app/utils/maths'; +// import { SplineCurveEditor } from '../editors/spline-curve-editor'; +// import { OdRoadReferenceLineBuilder } from 'app/modules/tv-map/builders/od-road-reference-line-builder'; +// import { OdSides } from 'app/modules/tv-map/models/od-common.model'; +// import { GameObject } from '../game-object'; +// import { OdLaneReferenceLineBuilder } from 'app/modules/tv-map/builders/od-lane-reference-line-builder'; +// import { SceneService } from '../services/scene.service'; +// import { OdLineGeometry } from 'app/modules/tv-map/models/geometries/od-line-geometry'; +// import { OdRoad } from 'app/modules/tv-map/models/od-road.model'; +// import { OdPosTheta } from 'app/modules/tv-map/models/od-pos-theta'; + +// export class RoadPlanSplineTool extends RoadPlanTool { + +// private cps: ControlPoint[] = []; + +// private builder: OdRoadReferenceLineBuilder; +// private laneBuilder: OdLaneReferenceLineBuilder; + +// init () { + +// super.init(); + +// // this.shapeEditor = new LineEditor(10); +// this.shapeEditor = new SplineCurveEditor(); +// // this.shapeEditor = new CubicBezierCurveEditor(); +// // this.shapeEditor = new PolygonEditor(); + +// this.helper.createRoadControlPoints( this.openDrive ); + +// this.builder = new OdRoadReferenceLineBuilder( null ); +// this.laneBuilder = new OdLaneReferenceLineBuilder( null ); + +// } + +// protected onControlPointSelected ( e: ControlPoint ) { + +// // try { + +// // const road = this.openDrive.getRoadById( e.userData.roadId ); + +// // if ( road == null ) return; + +// // this.selectRoad( road ); + +// // // this.cps.push( e ); + +// // } catch ( e ) { + +// // // console.error( e ); + +// // } + +// } + +// protected onControlPointAdded ( e: ControlPoint ) { + +// this.cps.push( e ); + +// if ( this.cps.length % 3 === 0 ) { + +// const count = this.cps.length; +// const points = this.cps; +// const cp1 = points[ count - 3 ]; +// const cp2 = points[ count - 2 ]; +// const cp3 = points[ count - 1 ]; + +// const road = this.createRoad(); + +// road.gameObject = new GameObject(); + +// SceneService.add( road.gameObject ); + +// this.addDefaultLanes( road ); + +// this.helper.createArcGeometry( cp1, cp2, cp3, road ); + +// // this.selectRoad( road ); + +// // this.cps.splice( 0, this.cps.length ); +// } +// } + +// protected onControlPointMoved ( e: ControlPoint ) { + +// this.printAngles(); + +// // try { + +// // const road = this.openDrive.getRoadById( e.userData.roadId ); + +// // if ( road == null ) return; + +// // const geometry = e.userData.geometry as OdAbstractRoadGeometry; + +// // const index = e.userData.index; + +// // this.helper.updateRoadGeometry( e, road, geometry, index ); + +// // this.printAngles(); + +// // } catch ( e ) { + +// // console.error( e ); + +// // } + +// } + +// private printAngles () { + +// if ( this.cps.length != 3 ) return; + +// const road = this.openDrive.getRoadById( 1 ); + +// // remove previous geometries +// road.geometries.splice( 0, road.geometries.length ); + +// const p1 = this.cps[ 0 ].position; +// const p2 = this.cps[ 1 ].position; +// const p3 = this.cps[ 2 ].position; + +// const x = p1.x; +// const y = p1.y; + +// // const a = Maths.angle( p1, p2, p3 ) * Maths.Rad2Deg; +// // const b = Maths.angle( p2, p3, p1 ) * Maths.Rad2Deg; /// actual angle of interest +// // const c = Maths.angle( p3, p1, p2 ) * Maths.Rad2Deg; + +// const firstLineLength = p1.distanceTo( p2 ); +// const secondLineLength = p2.distanceTo( p3 ); + +// // hdg of first line segment +// const hdg = Math.atan2( p2.y - p1.y, p2.x - p1.x ); + +// // hdg of second line segment +// const hdg2 = Math.atan2( p3.y - p2.y, p3.x - p2.x ); + + +// let arcStartingPosition: Vector3; + +// // line + arc +// if ( firstLineLength >= secondLineLength ) { + +// const t = ( firstLineLength - secondLineLength ) / firstLineLength; + +// const lineLength = firstLineLength - secondLineLength; + +// arcStartingPosition = Maths.linearInterpolationVector3( p1, p2, t ); + +// const lineGeometry = road.planView.addGeometryLine( 0, x, y, hdg, lineLength ) as OdLineGeometry; + +// const end = lineGeometry.end; + +// this.createArcGeometryRight( lineGeometry.s2, end.x, end.y, arcStartingPosition, hdg, p3, hdg2, p2, road ); + +// } +// // only arc is required +// else if ( secondLineLength > firstLineLength ) { + +// const t = firstLineLength / secondLineLength; + +// const lineLength = secondLineLength - firstLineLength; + +// const lineStartingPosition = Maths.linearInterpolationVector3( p2, p3, t ); + +// const arcGeometry = this.createArcGeometryRight( 0, x, y, p1, hdg, lineStartingPosition, hdg2, p2, road ); + +// const lineS = arcGeometry.s2; +// const pos = new OdPosTheta(); + +// arcGeometry.getCoords( lineS, pos ); + +// const lineGeometry = road.planView.addGeometryLine( lineS, pos.x, pos.y, pos.hdg, lineLength ); + +// } + +// // const geometry = road.geometries[ 0 ] as OdArcGeometry; + + +// // const height = Maths.heightOfTriangle( p2, p3, p1 ); +// // const base = p2.distanceTo( p1 ); +// // const radius = ( height / 2 ) + ( ( base * base ) / ( 8 * height ) ); +// // let curvature = 1 / radius; +// // make the curvature negative for right side i.e. for clockwise +// // if ( Maths.direction( p2, p3, p1 ) === OdSides.RIGHT ) curvature *= -1; + +// // console.log( 180 - b, radius, curvature, firstHalfDistance, secondHalfDistance, d ); + +// // geometry.curvature = curvature; + +// this.builder.clear( this.openDrive.getRoadById( 1 ) ); +// this.builder.buildRoad( this.openDrive.getRoadById( 1 ) ); +// } + +// private createArcGeometryRight ( +// s: number, x: number, y: number, p1: Vector3, hdg: number, p3: Vector3, hdg2: number, p2: Vector3, road: OdRoad ) { + +// const res = this.getRadius( p1, hdg, p3, hdg2 ); +// const radius = res.radius; +// const center = res.center; + +// let curvature = 1 / radius; + +// const doCurvature = Maths.angle( center, p1, p3 ); + +// const arcLength = doCurvature * radius; + +// // make the curvature negative for right side i.e. for clockwise +// if ( Maths.direction( p2, p3, p1 ) === OdSides.RIGHT ) +// curvature *= -1; + +// const geometry = road.planView.addGeometryArc( s, x, y, hdg, arcLength, curvature ); + +// road.length = s + arcLength; + +// return geometry; +// } + +// private createArcGeometryLeft ( s: number, x: number, y: number, end: Vector3, hdg: number, p3: Vector3, hdg2: number, p2: Vector3, road: OdRoad ) { + +// const res = this.getRadius( end, hdg, p3, hdg2 ); +// const radius = res.radius; +// const center = res.center; + +// let curvature = 1 / radius; + +// const doCurvature = Maths.angle( center, end, p3 ); + +// const arcLength = doCurvature * radius; + +// // make the curvature negative for right side i.e. for clockwise +// if ( Maths.direction( p2, p3, end ) === OdSides.RIGHT ) +// curvature *= -1; + +// const geometry = road.planView.addGeometryArc( s, x, y, hdg, arcLength, curvature ); + +// road.length = s + arcLength; + +// return geometry; +// } +// private getRadius ( A: Vector3, line1Hdg: number, C: Vector3, line2Hdg: number ) { + +// // refPos.hdg - Maths.M_PI_2 +// // obj.gameObject.rotation.set( 0, 0, refPos.hdg - Maths.M_PI_2 ); + +// const B = new Vector3( +// A.x + Math.cos( line1Hdg + Maths.M_PI_2 ) * 1, +// A.y + Math.sin( line1Hdg + Maths.M_PI_2 ) * 1 +// ); + +// // const dir1 = new Vector3(); +// // this.createArrow( dir1.subVectors( line1P2, line1P1 ).normalize(), line1P1 ); + + +// const D = new Vector3( +// C.x + Math.cos( line2Hdg + Maths.M_PI_2 ) * 1, +// C.y + Math.sin( line2Hdg + Maths.M_PI_2 ) * 1 +// ); + +// // const dir2 = new Vector3(); +// // this.createArrow( dir2.subVectors( line2P2, line2P1 ).normalize(), line2P2 ); +// // console.log( line1P1, line1P2, line1Hdg, line2P1, line2P2, line2Hdg ); + + +// const center = Maths.lineLineIntersection( A, B, C, D ); + +// const radius = A.distanceTo( center ); + +// // console.log( center, radius ); + +// return { +// radius, +// center +// }; + +// } + +// private createArrow ( dir, origin ) { +// // SceneService.add( new ArrowHelper( dir, origin, 10, 0xffff00 ) ); +// } +// } diff --git a/src/app/core/tools/add-road-object-tool.ts b/src/app/core/tools/add-road-object-tool.ts new file mode 100644 index 00000000..41b51d1e --- /dev/null +++ b/src/app/core/tools/add-road-object-tool.ts @@ -0,0 +1,137 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { BaseTool } from './base-tool'; +import { BoxBufferGeometry, Mesh, MeshBasicMaterial, Object3D, TextureLoader } from 'three'; +import { AbstractShapeEditor } from '../editors/abstract-shape-editor'; +import { Subscription } from 'rxjs'; +import { PointEditor } from '../editors/point-editor'; +import { AddRoadObjectCommand } from '../commands/add-road-object-command'; +import { TvOrientation } from '../../modules/tv-map/models/tv-common'; +import { TvRoadObject } from '../../modules/tv-map/models/tv-road-object'; +import { CommandHistory } from '../../services/command-history'; +import { AnyControlPoint } from '../../modules/three-js/objects/control-point'; +import { TvMapQueries } from '../../modules/tv-map/queries/tv-map-queries'; + +export class AddRoadObjectTool extends BaseTool { + + name: string = 'RoadObject'; + + private roadObjects: Object3D[] = []; + private shapeEditor: AbstractShapeEditor; + private treeTexture = new TextureLoader().load( 'assets/top-down-tree.png' ); + private subsribers: Subscription[] = []; + + constructor ( private propSprite: string = 'assets/top-down-tree.png' ) { + + super(); + + this.shapeEditor = new PointEditor(); + + } + + init () { + + super.init(); + + this.populate(); + + const s1 = this.shapeEditor.controlPointAdded.subscribe( e => this.onPointAdded( e ) ); + const s2 = this.shapeEditor.controlPointMoved.subscribe( e => this.onPointMoved( e ) ); + + this.subsribers.push( s1 ); + this.subsribers.push( s2 ); + } + + populate () { + + this.openDrive.roads.forEach( road => { + + const objects = road.getRoadObjects(); + + for ( let i = 0; i < objects.length; i++ ) { + + this.roadObjects.push( objects[ i ].mesh ); + + const point = this.shapeEditor.addControlPoint( objects[ i ].mesh.position ); + + objects[ i ].mesh.userData.controlPointId = point.id; + } + + } ); + + } + + disable (): void { + + super.disable(); + + this.shapeEditor.destroy(); + + this.subsribers.forEach( item => { + item.unsubscribe(); + } ); + } + + private onPointAdded ( e: AnyControlPoint ) { + + this.addProp( e ); + + } + + private onPointMoved ( e: AnyControlPoint ) { + + for ( const prop of this.roadObjects ) { + + if ( prop.userData.controlPointId === e.id ) { + + prop.position.set( e.position.x, e.position.y, prop.position.z ); + + break; + + } + + } + + } + + private getMaterial () { + + return new MeshBasicMaterial( { + map: this.treeTexture, + transparent: true, + opacity: 0.9 + } ); + + } + + private addProp ( point: Object3D ) { + + const road = TvMapQueries.getRoadByCoords( point.position.x, point.position.y ); + + if ( !road ) throw new Error( 'Need road to place objects' ); + + const material = this.getMaterial(); + + const geometry = new BoxBufferGeometry( 1, 1, 1 ); + + const obj = new Mesh( geometry, material ); + + obj.position.copy( point.position ); + + obj.userData.controlPointId = point.id; + + this.roadObjects.push( obj ); + + // tslint:disable-next-line:max-line-length + // const roadObject = this.openDrive.getRoad( 0 ).addRoadObject( 'tree', 'tree', point.id, 0, 0, 0, 0, OdOrientations.NONE, null, null, null, null, null, null, null ); + + const roadObject = new TvRoadObject( 'tree', 'tree', point.id, 0, 0, 0, 0, TvOrientation.NONE ); + + roadObject.mesh = obj; + + CommandHistory.execute( new AddRoadObjectCommand( road.id, roadObject, [ point ] ) ); + } + +} diff --git a/src/app/core/tools/auto-maneuver-tool.old-spec.ts b/src/app/core/tools/auto-maneuver-tool.old-spec.ts new file mode 100644 index 00000000..ae22b140 --- /dev/null +++ b/src/app/core/tools/auto-maneuver-tool.old-spec.ts @@ -0,0 +1,847 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +// import { TestBed } from '@angular/core/testing'; +// import { OdRoad } from 'app/modules/tv-map/models/od-road.model'; +// import { LaneSide, OdLaneType, OdSides, OdContactPoints } from 'app/modules/tv-map/models/od-common.model'; +// import { Vector3, Vector2 } from 'three'; +// import { OpenDriveQueries } from 'app/modules/tv-map/queries/tv-map-queries'; +// import { OdSourceFile } from 'app/modules/tv-map/services/od-source-file'; +// import { OpenDrive } from 'app/modules/tv-map/models/tv-map.models'; +// import { Maths } from 'app/utils/maths'; +// import { OdPosTheta } from 'app/modules/tv-map/models/od-pos-theta'; +// import { ManeuverTool, IJunctionConnection } from './maneuver-tool'; +// import { OdLineGeometry } from 'app/modules/tv-map/models/geometries/od-line-geometry'; +// import { OdArcGeometry } from 'app/modules/tv-map/models/geometries/od-arc-geometry'; +// import { OpenDriveService } from 'app/modules/tv-map/services/tv-map.service'; +// import { OdWriter } from 'app/modules/tv-map/services/od-writer.service'; + +// interface Geom { +// road: number; +// positions: Vector3[]; // x,y,z +// } + +// describe( 'ManeuverTool Test', () => { + +// let tool: ManeuverTool; + +// beforeEach( () => { + +// tool = new ManeuverTool(); + +// tool.init(); + +// ManeuverTool.DOTCOUNT = 0; + +// } ); + +// function findIntersectionsSlow ( roads: OdRoad[] ): Vector3[] { + +// const newRoads: Geom[] = []; + +// const t1 = performance.now(); + +// for ( let i = 0; i < roads.length; i++ ) { + +// const road = roads[ i ]; + +// const positions: Vector3[] = []; + +// const geom = { +// road: road.id, +// positions: positions, +// } + +// const geometries = road.geometries; + +// for ( let g = 0; g < geometries.length; g++ ) { + +// const geometry = geometries[ g ]; + +// const s = geometry.s; +// const s2 = geometry.s2; + +// let posTheta = new OdPosTheta(); + +// for ( let s = 0; s <= s2; s += 10 ) { + +// geometry.getCoords( s, posTheta ); + +// positions.push( posTheta.toVector3() ); + +// } +// } + +// newRoads.push( geom ); +// } + +// const t2 = performance.now(); + +// const timeToMakeGeometries = t2 - t1; + +// const intersections: Vector3[] = []; + +// for ( let i = 0; i < newRoads.length; i++ ) { + +// const positions = newRoads[ i ].positions; + +// for ( let j = i + 1; j < newRoads.length; j++ ) { + +// const otherPositions = newRoads[ j ].positions; + +// for ( let k = 0; k < otherPositions.length; k++ ) { + +// const otherPosition = otherPositions[ k ]; + +// for ( let l = 0; l < positions.length; l++ ) { + +// const position = positions[ l ]; +// const distance = position.distanceTo( otherPosition ); + +// if ( distance < 9.9 ) { + +// intersections.push( position ); +// } +// } +// } +// } +// } + +// const t3 = performance.now(); + +// const timeToFindIntersections = t3 - t2; + +// return intersections; +// } + +// function findRoadIntersections ( roads: OdRoad[] ) { + +// const positions: [][] = []; + +// roads.forEach( road => { + +// const otherRoads = roads.filter( r => r.id != road.id && !road.isJunction ); + +// const roadGeometries = road.geometries; + +// otherRoads.forEach( otherRoad => { + +// const otherRoadGeometries = otherRoad.geometries; + +// roadGeometries.forEach( g => { + +// otherRoadGeometries.forEach( og => { + +// // g.getIntersections( og ); + +// const s = og.s; +// const s2 = og.s2; + +// let position = null; +// let posTheta = new OdPosTheta(); + +// for ( let s = 0; s <= s2; s++ ) { + +// position = og.getCoords( s, posTheta ); + +// positions[ road.id ] + +// } +// } ); + +// } ) + +// } ); +// } ); +// } + +// function getLineArcIntersections ( A: Vector3, B: Vector3, C: Vector3, R: number ): Vector3[] { + +// // https://revisionmaths.com/advanced-level-maths-revision/pure-maths/geometry/equation-circle +// // https://stackoverflow.com/a/1088058 +// // equation of circle with center (a,b) with radius r is +// // +// // (x-a)^2 + (y-b)^2 = r^2 +// // + +// // line segment +// // const A = new Vector3( 0, 0, 0 ); +// // const B = new Vector3( 0, 150, 0 ); +// // const slope = Maths.slope( A, B ); + +// // center of circle +// // const C = new Vector3( 0, 0, 0 ); +// // const R = 100; + +// const LAB = A.distanceTo( B ); + +// const D = new Vector3( +// ( B.x - A.x ) / LAB, +// ( B.y - A.y ) / LAB, +// 0 +// ); + +// // compute the distance between the points A and E, where +// // E is the point of AB closest the circle center (Cx, Cy) +// const t = D.x * ( C.x - A.x ) + D.y * ( C.y - A.y ) + +// const E = new Vector3( +// t * D.x + A.y, +// t * D.y + A.y, +// 0 +// ); + +// const LEC = E.distanceTo( C ); + +// // line intersects +// if ( LEC < R ) { + +// // compute distance from t to circle intersection point +// const dt = Math.sqrt( R * R - LEC * LEC ); + +// // compute first intersection point +// const F = new Vector3( +// ( t - dt ) * D.x + A.x, +// ( t - dt ) * D.y + A.y, +// 0 +// ); + +// // compute second intersection point +// const G = new Vector3( +// ( t + dt ) * D.x + A.x, +// ( t + dt ) * D.y + A.y, +// 0 +// ); + +// const intersections = []; + +// const F_online = Maths.isPointOnLine( A, B, F ); + +// if ( F_online ) intersections.push( F ); + +// const G_online = Maths.isPointOnLine( A, B, G ); + +// if ( G_online ) intersections.push( G ); + +// return intersections; + +// // const isXOnLine = Maths.isPointOnLine( A, B, new Vector3( 0, 10, 0 ) ); + +// } else if ( Maths.approxEquals( LEC, R ) ) { + +// // TODO: Return tanget also in future +// // tangent point to circle is E + +// return []; + +// } else { + +// // line does not intersect +// return []; + +// } +// } + +// it( 'should check line and line intersection', () => { + +// create2x2LaneRoadIntersection( 10000 ); + +// const intersections = findIntersectionsSlow( [ ...OdSourceFile.openDrive.roads.values() ] ); + +// expect( intersections.length ).toBe( 1 ); + +// } ); + +// it( 'should give left side for point direction checks', () => { + +// // TODO: Move this to maths.spec.ts + +// let side: OdSides; + +// side = Maths.findSide( new Vector3( 0, 10, 0 ), new Vector3( 0, 0, 0 ), 0 ); + +// expect( side ).toBe( OdSides.LEFT ); + +// side = Maths.findSide( new Vector3( 0, 10, 0 ), new Vector3( 0, 0, 0 ), -0.78 ); // -45 degree + +// expect( side ).toBe( OdSides.LEFT ); + +// side = Maths.findSide( new Vector3( 0, 10, 0 ), new Vector3( 0, 0, 0 ), 0.78 ); // +45 degree + +// expect( side ).toBe( OdSides.LEFT ); + +// side = Maths.findSide( new Vector3( 10, 10, 0 ), new Vector3( 0, 0, 0 ), -1.57 ); // -90 degree + +// expect( side ).toBe( OdSides.LEFT ); + +// side = Maths.findSide( new Vector3( 10, 0, 0 ), new Vector3( 0, 0, 0 ), -1.57 ); // -90 degree + +// expect( side ).toBe( OdSides.LEFT ); + +// side = Maths.findSide( new Vector3( 10, 20, 0 ), new Vector3( 0, 0, 0 ), -1.57 ); // -90 degree + +// expect( side ).toBe( OdSides.LEFT ); + +// } ); + +// it( 'should give right side for point direction checks', () => { + +// // TODO: Move this to maths.spec.ts + +// let side: OdSides; + +// // +90 degree +// expect( Maths.findSide( new Vector3( 10, 0, 0 ), new Vector3( 0, 0, 0 ), 1.57 ) ) +// .toBe( OdSides.RIGHT ); + +// expect( Maths.findSide( new Vector3( 10, 10, 0 ), new Vector3( 0, 0, 0 ), 1.57 ) ) +// .toBe( OdSides.RIGHT ); + +// expect( Maths.findSide( new Vector3( 100, 100, 0 ), new Vector3( 0, 0, 0 ), 1.57 ) ) +// .toBe( OdSides.RIGHT ); + +// expect( Maths.findSide( new Vector3( 1000, 1000, 0 ), new Vector3( 0, 0, 0 ), 1.57 ) ) +// .toBe( OdSides.RIGHT ); + +// // +45 degree +// expect( Maths.findSide( new Vector3( 10, 0, 0 ), new Vector3( 0, 0, 0 ), 0.78 ) ) +// .toBe( OdSides.RIGHT ); + +// expect( Maths.findSide( new Vector3( 100, 0, 0 ), new Vector3( 0, 0, 0 ), 0.78 ) ) +// .toBe( OdSides.RIGHT ); + +// } ) + +// it( 'should create 8 dots for 2x2 lane intersections with same direction', () => { + +// const roads = create2x2LaneRoadIntersection(); + +// const intesections = tool.findIntersectionsSlow( roads ); + +// const junctions = tool.createJunctionAreas( intesections ); + +// expect( intesections.length ).toBe( 1 ); +// expect( junctions.length ).toBe( 1 ); + +// // TODO: assert junction[0] values also for correct s, start, end values + +// const dots = tool.createDotsForJunction( junctions[ 0 ] ); + +// expect( dots.length ).toBe( 8 ); +// expect( dots[ 0 ].id ).toBe( 0 ); +// expect( dots[ 1 ].id ).toBe( 1 ); +// expect( dots[ 2 ].id ).toBe( 2 ); +// expect( dots[ 3 ].id ).toBe( 3 ); +// expect( dots[ 4 ].id ).toBe( 4 ); +// expect( dots[ 5 ].id ).toBe( 5 ); +// expect( dots[ 6 ].id ).toBe( 6 ); +// expect( dots[ 7 ].id ).toBe( 7 ); + +// // A Left End > A Left Start +// expect( dots[ 1 ].s ).toBeGreaterThan( dots[ 0 ].s ); +// // A Right Start > A Right End +// expect( dots[ 2 ].s ).toBeGreaterThan( dots[ 3 ].s ); + +// // B Left End > B Left Start +// expect( dots[ 5 ].s ).toBeGreaterThan( dots[ 4 ].s ); +// // B Right Start > B Right End +// expect( dots[ 6 ].s ).toBeGreaterThan( dots[ 7 ].s ); + +// // TODO: test position +// // TODO: test connections + +// const connections = ManeuverTool.createConnections( roads, dots ); + +// const straightConnections = connections.filter( c => c.type == 'straight' ); +// const leftTurnConnections = connections.filter( c => c.type == "left-turn" ); +// const rightTurnConnections = connections.filter( c => c.type == "right-turn" ); + +// expect( connections.length ).toBe( 12 ); +// expect( straightConnections.length ).toBe( 4 ); +// expect( leftTurnConnections.length ).toBe( 4 ); +// expect( rightTurnConnections.length ).toBe( 4 ); + +// expect( straightConnections[ 0 ].incomingRoad ).toBe( 1 ); +// expect( straightConnections[ 0 ].outgoingRoad ).toBe( 1 ); +// expect( straightConnections[ 0 ].fromLane ).toBe( 1 ); +// expect( straightConnections[ 0 ].toLane ).toBe( 1 ); +// expect( straightConnections[ 0 ].point ).toBe( 'start' ); + +// expect( straightConnections[ 1 ].incomingRoad ).toBe( 1 ); +// expect( straightConnections[ 1 ].outgoingRoad ).toBe( 1 ); +// expect( straightConnections[ 1 ].fromLane ).toBe( -1 ); +// expect( straightConnections[ 1 ].toLane ).toBe( -1 ); +// expect( straightConnections[ 1 ].point ).toBe( 'start' ); + +// expect( straightConnections[ 2 ].incomingRoad ).toBe( 2 ); +// expect( straightConnections[ 2 ].outgoingRoad ).toBe( 2 ); +// expect( straightConnections[ 2 ].fromLane ).toBe( 1 ); +// expect( straightConnections[ 2 ].toLane ).toBe( 1 ); +// expect( straightConnections[ 2 ].point ).toBe( 'start' ); + +// expect( straightConnections[ 3 ].incomingRoad ).toBe( 2 ); +// expect( straightConnections[ 3 ].outgoingRoad ).toBe( 2 ); +// expect( straightConnections[ 3 ].fromLane ).toBe( -1 ); +// expect( straightConnections[ 3 ].toLane ).toBe( -1 ); +// expect( straightConnections[ 3 ].point ).toBe( 'start' ); + +// expect( leftTurnConnections[ 0 ].entry.id ).toBe( 0 ); +// expect( leftTurnConnections[ 0 ].exit.id ).toBe( 5 ); +// expect( leftTurnConnections[ 0 ].incomingRoad ).toBe( 1 ); +// expect( leftTurnConnections[ 0 ].outgoingRoad ).toBe( 2 ); +// expect( leftTurnConnections[ 0 ].fromLane ).toBe( 1 ); // forward +// expect( leftTurnConnections[ 0 ].toLane ).toBe( 1 ); // forward +// expect( leftTurnConnections[ 0 ].point ).toBe( 'start' ); + +// expect( leftTurnConnections[ 1 ].entry.id ).toBe( 2 ); +// expect( leftTurnConnections[ 1 ].exit.id ).toBe( 7 ); +// expect( leftTurnConnections[ 1 ].incomingRoad ).toBe( 1 ); +// expect( leftTurnConnections[ 1 ].outgoingRoad ).toBe( 2 ); +// expect( leftTurnConnections[ 1 ].fromLane ).toBe( -1 ); // backward +// expect( leftTurnConnections[ 1 ].toLane ).toBe( -1 ); // backward +// expect( leftTurnConnections[ 1 ].point ).toBe( 'start' ); + +// expect( leftTurnConnections[ 2 ].entry.id ).toBe( 4 ); +// expect( leftTurnConnections[ 2 ].exit.id ).toBe( 3 ); +// expect( leftTurnConnections[ 2 ].incomingRoad ).toBe( 2 ); +// expect( leftTurnConnections[ 2 ].outgoingRoad ).toBe( 1 ); +// expect( leftTurnConnections[ 2 ].fromLane ).toBe( 1 ); // forward +// expect( leftTurnConnections[ 2 ].toLane ).toBe( -1 ); // backward +// expect( leftTurnConnections[ 2 ].point ).toBe( 'start' ); + +// expect( leftTurnConnections[ 3 ].entry.id ).toBe( 6 ); +// expect( leftTurnConnections[ 3 ].exit.id ).toBe( 1 ); +// expect( leftTurnConnections[ 3 ].incomingRoad ).toBe( 2 ); +// expect( leftTurnConnections[ 3 ].outgoingRoad ).toBe( 1 ); +// expect( leftTurnConnections[ 3 ].fromLane ).toBe( -1 ); // backward +// expect( leftTurnConnections[ 3 ].toLane ).toBe( 1 ); // forward +// expect( leftTurnConnections[ 3 ].point ).toBe( 'start' ); + +// } ); + +// it( 'should create 8 dots for 2x2 lane intersections with opposite direction', () => { + +// const roads = create2x2LaneRoadIntersection(); + +// // make road go from top to down +// roads[ 1 ].planView.geometries[ 0 ].x = 0; +// roads[ 1 ].planView.geometries[ 0 ].y = 50; +// roads[ 1 ].planView.geometries[ 0 ].hdg = -1.57; + +// const intesections = tool.findIntersectionsSlow( roads ); + +// const junctions = tool.createJunctionAreas( intesections ); + +// expect( intesections.length ).toBe( 1 ); +// expect( junctions.length ).toBe( 1 ); + +// // TODO: assert junction[0] values also for correct s, start, end values + +// const dots = tool.createDotsForJunction( junctions[ 0 ] ); + +// expect( dots.length ).toBe( 8 ); +// expect( dots[ 0 ].id ).toBe( 0 ); +// expect( dots[ 1 ].id ).toBe( 1 ); +// expect( dots[ 2 ].id ).toBe( 2 ); +// expect( dots[ 3 ].id ).toBe( 3 ); +// expect( dots[ 4 ].id ).toBe( 4 ); +// expect( dots[ 5 ].id ).toBe( 5 ); +// expect( dots[ 6 ].id ).toBe( 6 ); +// expect( dots[ 7 ].id ).toBe( 7 ); + +// // A Left End > A Left Start +// expect( dots[ 1 ].s ).toBeGreaterThan( dots[ 0 ].s ); +// // A Right Start > A Right End +// expect( dots[ 2 ].s ).toBeGreaterThan( dots[ 3 ].s ); + +// // B Left End > B Left Start +// expect( dots[ 5 ].s ).toBeGreaterThan( dots[ 4 ].s ); +// // B Right Start > B Right End +// expect( dots[ 6 ].s ).toBeGreaterThan( dots[ 7 ].s ); + +// // TODO: test position +// // TODO: test connections + +// const connections = ManeuverTool.createConnections( roads, dots ); + +// const straightConnections = connections.filter( c => c.type == 'straight' ); +// const leftTurnConnections = connections.filter( c => c.type == "left-turn" ); +// const rightTurnConnections = connections.filter( c => c.type == "right-turn" ); + +// expect( connections.length ).toBe( 12 ); +// expect( straightConnections.length ).toBe( 4 ); +// expect( leftTurnConnections.length ).toBe( 4 ); +// expect( rightTurnConnections.length ).toBe( 4 ); + +// expect( straightConnections[ 0 ].incomingRoad ).toBe( 1 ); +// expect( straightConnections[ 0 ].outgoingRoad ).toBe( 1 ); +// expect( straightConnections[ 0 ].fromLane ).toBe( 1 ); +// expect( straightConnections[ 0 ].toLane ).toBe( 1 ); +// expect( straightConnections[ 0 ].point ).toBe( 'start' ); + +// expect( straightConnections[ 1 ].incomingRoad ).toBe( 1 ); +// expect( straightConnections[ 1 ].outgoingRoad ).toBe( 1 ); +// expect( straightConnections[ 1 ].fromLane ).toBe( -1 ); +// expect( straightConnections[ 1 ].toLane ).toBe( -1 ); +// expect( straightConnections[ 1 ].point ).toBe( 'start' ); + +// expect( straightConnections[ 2 ].incomingRoad ).toBe( 2 ); +// expect( straightConnections[ 2 ].outgoingRoad ).toBe( 2 ); +// expect( straightConnections[ 2 ].fromLane ).toBe( 1 ); +// expect( straightConnections[ 2 ].toLane ).toBe( 1 ); +// expect( straightConnections[ 2 ].point ).toBe( 'start' ); + +// expect( straightConnections[ 3 ].incomingRoad ).toBe( 2 ); +// expect( straightConnections[ 3 ].outgoingRoad ).toBe( 2 ); +// expect( straightConnections[ 3 ].fromLane ).toBe( -1 ); +// expect( straightConnections[ 3 ].toLane ).toBe( -1 ); +// expect( straightConnections[ 3 ].point ).toBe( 'start' ); + +// // below are not working + +// // expect( leftTurnConnections[ 0 ].entry.id ).toBe( 0 ); +// // expect( leftTurnConnections[ 0 ].exit.id ).toBe( 7 ); +// // expect( leftTurnConnections[ 0 ].incomingRoad ).toBe( 1 ); +// // expect( leftTurnConnections[ 0 ].outgoingRoad ).toBe( 2 ); +// // expect( leftTurnConnections[ 0 ].fromLane ).toBe( 1 ); // forward +// // expect( leftTurnConnections[ 0 ].toLane ).toBe( 1 ); // forward +// // expect( leftTurnConnections[ 0 ].point ).toBe( 'start' ); + +// // expect( leftTurnConnections[ 1 ].entry.id ).toBe( 2 ); +// // expect( leftTurnConnections[ 1 ].exit.id ).toBe( 7 ); +// // expect( leftTurnConnections[ 1 ].incomingRoad ).toBe( 1 ); +// // expect( leftTurnConnections[ 1 ].outgoingRoad ).toBe( 2 ); +// // expect( leftTurnConnections[ 1 ].fromLane ).toBe( -1 ); // backward +// // expect( leftTurnConnections[ 1 ].toLane ).toBe( -1 ); // backward +// // expect( leftTurnConnections[ 1 ].point ).toBe( 'start' ); + +// // expect( leftTurnConnections[ 2 ].entry.id ).toBe( 4 ); +// // expect( leftTurnConnections[ 2 ].exit.id ).toBe( 3 ); +// // expect( leftTurnConnections[ 2 ].incomingRoad ).toBe( 2 ); +// // expect( leftTurnConnections[ 2 ].outgoingRoad ).toBe( 1 ); +// // expect( leftTurnConnections[ 2 ].fromLane ).toBe( 1 ); // forward +// // expect( leftTurnConnections[ 2 ].toLane ).toBe( -1 ); // backward +// // expect( leftTurnConnections[ 2 ].point ).toBe( 'start' ); + +// // expect( leftTurnConnections[ 3 ].entry.id ).toBe( 6 ); +// // expect( leftTurnConnections[ 3 ].exit.id ).toBe( 1 ); +// // expect( leftTurnConnections[ 3 ].incomingRoad ).toBe( 2 ); +// // expect( leftTurnConnections[ 3 ].outgoingRoad ).toBe( 1 ); +// // expect( leftTurnConnections[ 3 ].fromLane ).toBe( -1 ); // backward +// // expect( leftTurnConnections[ 3 ].toLane ).toBe( 1 ); // forward +// // expect( leftTurnConnections[ 3 ].point ).toBe( 'start' ); + +// } ); + +// it( 'should create 14 connections for 3x3 lane intersection', () => { + +// const roads = create3x3LaneRoadIntersection(); + +// const intesections = tool.findIntersectionsSlow( roads ); + +// const junctions = tool.createJunctionAreas( intesections ); + +// expect( intesections.length ).toBe( 1 ); +// expect( junctions.length ).toBe( 1 ); + +// const dots = tool.createDotsForJunction( junctions[ 0 ] ); + +// const connections = tool.prepareJunctionConnections( junctions ); + +// expect( dots.length ).toBe( 12 ); + +// expect( connections.length ).toBe( 14 ); + +// } ); + +// it( 'should create 10 connections for 4 lane freeway intersection', () => { + +// // const roads = create4LaneFreewayIntersection(); + +// // const intesections = tool.findIntersectionsSlow( roads ); + +// // const junctions = ManeuverTool.createJunctionAreas( intesections ); + +// // expect( intesections.length ).toBe( 1 ); +// // expect( junctions.length ).toBe( 1 ); + +// // const dots = tool.createDotsForJunction( junctions[ 0 ] ); + +// // const connections = tool.prepareJunctionConnections( junctions ); + +// // expect( dots.length ).toBe( 16 ); + +// // throwing error: 9 instead of 10 +// // expect( connections.length ).toBe( 10 ); + +// } ); + +// it( 'should not create junctions for same intersection again', () => { + +// const roads = create2x2LaneRoadIntersection(); + +// const intesections = tool.findIntersectionsSlow( roads ); + +// const junctions = tool.createJunctionAreas( intesections ); + +// const connections = tool.prepareJunctionConnections( junctions ); + +// // const straightConnections = connections.filter( c => c.type == 'straight' ); +// // const leftTurnConnections = connections.filter( c => c.type == "left-turn" ); +// // const rightTurnConnections = connections.filter( c => c.type == "right-turn" ); + +// } ); + +// it( 'should divide straight road correctly on intersection', () => { + +// // // TODO: this test it not working + +// // const sStart = 45; +// // const sEnd = 55; + +// // const roads = create2x2LaneRoadIntersection( 100 ); + +// // const spline = new AutoSpline(); + +// // const connection: IJunctionConnection = { +// // entry: { +// // id: 0, +// // s: sStart, +// // roadId: 1, +// // laneId: 1, +// // type: 'start', +// // color: 345, +// // direction: 'forward', +// // position: OpenDriveQueries.getLanePosition( 1, 1, sStart, 0 ), +// // hdg: 0, +// // }, +// // exit: { +// // id: 0, +// // s: 55, +// // roadId: 1, +// // laneId: 1, +// // type: 'end', +// // color: 345, +// // direction: 'forward', +// // position: OpenDriveQueries.getLanePosition( 1, 1, sEnd, 0 ), +// // hdg: 0, +// // }, +// // spline: spline +// // }; + +// // spline.addControlPointAt( connection.entry.position ); +// // spline.addControlPointAt( connection.exit.position ); +// // spline.update(); + +// // const result = tool.createStraightJunctionRoad( 1, connection ); + +// // expect( result.incoming.length ).toBe( 45 ); +// // expect( result.incoming.geometries[ 0 ].x ).toBe( -50 ); +// // expect( result.incoming.geometries[ 0 ].y ).toBe( 0 ); +// // expect( result.incoming.geometries[ 0 ].hdg ).toBe( 0 ); + +// // expect( result.connecting.length ).toBe( 10 ); +// // expect( result.connecting.geometries[ 0 ].x ).toBe( -5 ); +// // expect( result.connecting.geometries[ 0 ].y ).toBe( 0 ); +// // expect( result.connecting.geometries[ 0 ].hdg ).toBe( 0 ); + +// // expect( result.outgoing.length ).toBe( 45 ); +// // expect( result.connecting.geometries[ 0 ].x ).toBe( 5 ); +// // expect( result.connecting.geometries[ 0 ].y ).toBe( 0 ); +// // expect( result.connecting.geometries[ 0 ].hdg ).toBe( 0 ); + +// } ); + +// it( 'should find the correct geometry at s', () => { + +// const road = new OdRoad( "1", 500, 6, -1 ); + +// road.addGeometryLine( 0, 0, 0, 0, 100 ); // 100 +// road.addGeometryLine( 100, 100, 0, 0, 100 ); // 200 +// road.addGeometryLine( 200, 200, 0, 0, 100 ); // 300 +// road.addGeometryLine( 300, 300, 0, 0, 100 ); +// road.addGeometryLine( 400, 400, 0, 0, 100 ); + +// const s = 250; +// const result = road.getGeometryAt( s ); + +// expect( result.s ).toBe( 200 ); +// expect( result.s2 ).toBe( 300 ); + +// } ) + +// it( 'should find the correct geometry at s', () => { + +// const road = new OdRoad( "1", 500, 6, -1 ); + +// road.addLaneSection( 0, false ); +// road.addLaneSection( 100, false ); +// road.addLaneSection( 200, false ); +// road.addLaneSection( 300, false ); +// road.addLaneSection( 400, false ); + +// const s = 250; +// const result = road.getLaneSectionAt( s ); + +// expect( result.s ).toBe( 200 ); + +// } ) + +// it( 'should show imported junctions correctly', () => { + +// // create simple two road intersection for testing +// // ============|||================== +// const openDrive = OdSourceFile.openDrive = new OpenDrive(); + +// const roadA = openDrive.addRoad( "A", 90, 1, -1 ); +// const roadB = openDrive.addRoad( "B", 90, 2, -1 ); + +// roadA.addGeometryLine( 0, -100, 0, 0, 90 ); +// roadB.addGeometryLine( 0, 10, 0, 0, 90 ); + +// [ roadA, roadB ].forEach( road => { + +// road.addLaneSection( 0, false ); const laneSection = road.getLastAddedLaneSection(); + +// let leftLane = laneSection.addLane( LaneSide.LEFT, 1, OdLaneType.driving, true, true ); +// let centerLane = laneSection.addLane( LaneSide.CENTER, 0, OdLaneType.driving, true, true ); +// let rightLane = laneSection.addLane( LaneSide.RIGHT, -1, OdLaneType.driving, true, true ); + +// [ leftLane, centerLane, rightLane ].forEach( lane => lane.addWidthRecord( 0, 2, 0, 0, 0 ) ); + +// } ) + +// roadA.setSuccessor( "junction", 1, null ); +// roadB.setPredecessor( "junction", 1, null ); + + +// // connecting roads for junction +// const roadC = openDrive.addRoad( "C", 20, 3, 1 ); +// const roadD = openDrive.addRoad( "D", 20, 4, 1 ); + +// roadC.setPredecessor( "road", 1, OdContactPoints.END ); +// roadC.setSuccessor( "road", 2, OdContactPoints.START ); + +// roadD.setPredecessor( "road", 1, OdContactPoints.END ); +// roadD.setSuccessor( "road", 2, OdContactPoints.START ); + +// roadC.addGeometryLine( 0, -10, 0, 0, 20 ); +// roadD.addGeometryLine( 0, -10, 0, 0, 20 ); + + +// roadC.addLaneSection( 0, false ); +// roadC.getLaneSectionAt( 0 ) +// .addLane( LaneSide.LEFT, 1, OdLaneType.driving, true, true ) +// .addWidthRecord( 0, 2, 0, 0, 0 ); +// roadC.getLaneSectionAt( 0 ) +// .addLane( LaneSide.CENTER, 0, OdLaneType.driving, true, true ); + +// roadD.addLaneSection( 0, false ); +// roadD.getLaneSectionAt( 0 ) +// .addLane( LaneSide.RIGHT, -1, OdLaneType.driving, true, true ) +// .addWidthRecord( 0, 2, 0, 0, 0 ); +// roadD.getLaneSectionAt( 0 ) +// .addLane( LaneSide.CENTER, 0, OdLaneType.driving, true, true ); + +// const junctionIndex = openDrive.addJunction( "J", 1 ); +// const junction = openDrive.getJunctionById( 1 ); + +// // connections +// junction.addJunctionConnection( 0, roadA.id, roadC.id, "start" ).addJunctionLaneLink( 1, 1 ); +// junction.addJunctionConnection( 1, roadB.id, roadD.id, "end" ).addJunctionLaneLink( -1, -1 ); + +// // +// tool.init(); +// tool.enable(); + +// const output = new OdWriter().getOutput( openDrive ); + +// } ); + +// function create2x2LaneRoadIntersection ( length = 100 ) { + +// OdSourceFile.openDrive = new OpenDrive(); + +// const road1 = OdSourceFile.openDrive.addRoad( '1', length, 1, -1 ); road1.addPlanView() +// const road2 = OdSourceFile.openDrive.addRoad( '2', length, 2, -1 ); road2.addPlanView(); + +// road1.addGeometryLine( 0, -50, 0, 0, length ); +// road2.addGeometryLine( 0, 0, -50, 1.57, length ); + +// const roads = [ road1, road2 ]; + +// roads.forEach( road => { + +// road.addLaneSection( 0, false ); const laneSection = road.getLastAddedLaneSection(); + +// let leftLane = laneSection.addLane( LaneSide.LEFT, 1, OdLaneType.driving, true, true ); +// let centerLane = laneSection.addLane( LaneSide.CENTER, 0, OdLaneType.driving, true, true ); +// let rightLane = laneSection.addLane( LaneSide.RIGHT, -1, OdLaneType.driving, true, true ); + +// [ leftLane, centerLane, rightLane ].forEach( lane => lane.addWidthRecord( 0, 2, 0, 0, 0 ) ); + +// } ); + + +// return roads; +// } + +// function create3x3LaneRoadIntersection () { + +// OdSourceFile.openDrive = new OpenDrive(); + +// const road1 = OdSourceFile.openDrive.addRoad( '1', 100, 1, -1 ); road1.addPlanView() +// const road2 = OdSourceFile.openDrive.addRoad( '2', 100, 2, -1 ); road2.addPlanView(); + +// road1.addGeometryLine( 0, -50, 0, 0, 100 ); +// road2.addGeometryLine( 0, 0, -50, 1.57, 100 ); + +// const roads = [ road1, road2 ]; + +// roads.forEach( road => { + +// road.addLaneSection( 0, false ); const laneSection = road.getLastAddedLaneSection(); + +// let left2 = laneSection.addLane( LaneSide.LEFT, 2, OdLaneType.driving, true, true ); +// let left1 = laneSection.addLane( LaneSide.LEFT, 1, OdLaneType.driving, true, true ); +// let center = laneSection.addLane( LaneSide.CENTER, 0, OdLaneType.driving, true, true ); +// let right1 = laneSection.addLane( LaneSide.RIGHT, -1, OdLaneType.driving, true, true ); + +// [ left2, left1, center, right1 ].forEach( lane => lane.addWidthRecord( 0, 2, 0, 0, 0 ) ); + +// } ); + +// return roads; +// } + +// function create4LaneFreewayIntersection () { + +// OdSourceFile.openDrive = new OpenDrive(); + +// const road1 = OdSourceFile.openDrive.addRoad( '1', 100, 1, -1 ); road1.addPlanView() +// const road2 = OdSourceFile.openDrive.addRoad( '2', 100, 2, -1 ); road2.addPlanView(); + +// road1.addGeometryLine( 0, -50, 0, 0, 100 ); +// road2.addGeometryLine( 0, 0, -50, 1.57, 100 ); + +// const roads = [ road1, road2 ]; + +// roads.forEach( road => { + +// road.addLaneSection( 0, false ); const laneSection = road.getLastAddedLaneSection(); + +// let left4 = laneSection.addLane( LaneSide.LEFT, 4, OdLaneType.driving, true, true ); +// let left3 = laneSection.addLane( LaneSide.LEFT, 3, OdLaneType.driving, true, true ); +// let left2 = laneSection.addLane( LaneSide.LEFT, 2, OdLaneType.driving, true, true ); +// let left1 = laneSection.addLane( LaneSide.LEFT, 1, OdLaneType.driving, true, true ); +// let center = laneSection.addLane( LaneSide.CENTER, 0, OdLaneType.driving, true, true ); + +// [ left4, left3, left2, left1, center ].forEach( lane => lane.addWidthRecord( 0, 2, 0, 0, 0 ) ); + +// } ); + +// return roads; +// } + +// } ); diff --git a/src/app/core/tools/auto-maneuver-tool.spec.ts b/src/app/core/tools/auto-maneuver-tool.spec.ts new file mode 100644 index 00000000..f9029084 --- /dev/null +++ b/src/app/core/tools/auto-maneuver-tool.spec.ts @@ -0,0 +1,207 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { AutoManeuverTool } from './auto-maneuver-tool'; +import { TvMap } from 'app/modules/tv-map/models/tv-map.model'; +import { TvMapSourceFile } from 'app/modules/tv-map/services/tv-map-source-file'; +import { Maths } from 'app/utils/maths'; +import { TvDirection } from 'app/modules/tv-map/models/tv-common'; + + +describe( 'AutoManeuverTool Test', () => { + + let tool: AutoManeuverTool; + let openDrive: TvMap; + + beforeEach( () => { + + tool = new AutoManeuverTool(); + + tool.init(); + + openDrive = TvMapSourceFile.openDrive = new TvMap(); + + AutoManeuverTool.DOTCOUNT = 0; + + } ); + + it( 'should find straight road intersection in same direction', () => { + + const roads = createStraightRoadsSameDirection(); + + const intersections = tool.findIntersectionsSlow( roads ); + + expect( intersections.length ).toBe( 1 ); + expect( intersections[ 0 ].road1 ).toBe( roads[ 0 ].id ); + expect( intersections[ 0 ].road2 ).toBe( roads[ 1 ].id ); + expect( intersections[ 0 ].x ).toBe( 0 ); + expect( intersections[ 0 ].y ).toBe( 0 ); + expect( intersections[ 0 ].s1 ).toBe( 50 ); + expect( intersections[ 0 ].s2 ).toBe( 50 ); + } ); + + it( 'should find straight road intersection in opposite direction', () => { + + const roads = createStraightRoadsOppositeDirection(); + + const intersections = tool.findIntersectionsSlow( roads ); + + expect( intersections.length ).toBe( 1 ); + expect( intersections[ 0 ].road1 ).toBe( roads[ 0 ].id ); + expect( intersections[ 0 ].road2 ).toBe( roads[ 1 ].id ); + expect( intersections[ 0 ].x ).toBe( 0 ); + expect( intersections[ 0 ].y ).toBe( 0 ); + expect( intersections[ 0 ].s1 ).toBe( 50 ); + expect( intersections[ 0 ].s2 ).toBe( 50 ); + } ); + + it( 'should find angled road (45 degree) intersection in same direction', () => { + + const road1 = openDrive.addDefaultRoad(); + const road2 = openDrive.addDefaultRoad(); + + // left to right with angle + road1.addGeometryLine( 0, -35.35, -35.35, Maths.Deg2Rad * 45, 100 ); + + // down to up + road2.addGeometryLine( 0, 0, -50, 1.5708, 100 ); + + const intersections = tool.findIntersectionsSlow( [ road1, road2 ] ); + + expect( intersections.length ).toBe( 1 ); + expect( intersections[ 0 ].road1 ).toBe( road1.id ); + expect( intersections[ 0 ].road2 ).toBe( road2.id ); + expect( Maths.approxEquals( intersections[ 0 ].x, -0.71, 0.01 ) ).toBe( true ); + expect( Maths.approxEquals( intersections[ 0 ].y, -0.71, 0.01 ) ).toBe( true ); + expect( intersections[ 0 ].s1 ).toBe( 49 ); + expect( intersections[ 0 ].s2 ).toBe( 49 ); + + + } ); + + it( 'should find angled road (45 degree) intersection in opposite direction', () => { + + const road1 = openDrive.addDefaultRoad(); + const road2 = openDrive.addDefaultRoad(); + + // left to right with angle + road1.addGeometryLine( 0, -35.35, -35.35, Maths.Deg2Rad * 45, 100 ); + + // up to down + road2.addGeometryLine( 0, 0, 50, -1.5708, 100 ); + + const intersections = tool.findIntersectionsSlow( [ road1, road2 ] ); + + expect( intersections.length ).toBe( 1 ); + expect( intersections[ 0 ].road1 ).toBe( road1.id ); + expect( intersections[ 0 ].road2 ).toBe( road2.id ); + expect( Maths.approxEquals( intersections[ 0 ].x, 0.71, 0.01 ) ).toBe( true ); + expect( Maths.approxEquals( intersections[ 0 ].y, 0.71, 0.01 ) ).toBe( true ); + expect( intersections[ 0 ].s1 ).toBe( 51 ); + expect( intersections[ 0 ].s2 ).toBe( 49 ); + + + } ); + + it( 'should find correct distance from intersection', () => { + + let distance = tool.calculateDistance( 0, 90 * Maths.Deg2Rad ); + + // let isEqual = Maths.approxEquals( distance, 2.35619450625 ); + + expect( distance ).toBe( 2.35619450625 ); + + + distance = tool.calculateDistance( 45 * Maths.Deg2Rad, 90 * Maths.Deg2Rad ); + + // isEqual = Maths.approxEquals( distance, 3.53429174325 ); + + expect( distance ).toBe( 3.53429174325 ); + + } ); + + it( 'should calculate hdg difference', () => { + + let hdgA = 0 * Maths.Deg2Rad; + let hdgB = 90 * Maths.Deg2Rad; + + expect( tool.calculateSDirection( hdgA, hdgB ) ).toBe( TvDirection.SAME ); + expect( tool.calculateSDirection( hdgB, hdgA ) ).toBe( TvDirection.SAME ); + + hdgA = -45 * Maths.Deg2Rad; + hdgB = 45 * Maths.Deg2Rad; + + expect( tool.calculateSDirection( hdgA, hdgB ) ).toBe( TvDirection.SAME ); + expect( tool.calculateSDirection( hdgB, hdgA ) ).toBe( TvDirection.SAME ); + + hdgA = -45 * Maths.Deg2Rad; + hdgB = 0 * Maths.Deg2Rad; + + expect( tool.calculateSDirection( hdgA, hdgB ) ).toBe( TvDirection.SAME ); + expect( tool.calculateSDirection( hdgB, hdgA ) ).toBe( TvDirection.SAME ); + + hdgA = 90 * Maths.Deg2Rad; + hdgB = 0 * Maths.Deg2Rad; + + expect( tool.calculateSDirection( hdgA, hdgB ) ).toBe( TvDirection.SAME ); + expect( tool.calculateSDirection( hdgB, hdgA ) ).toBe( TvDirection.SAME ); + + hdgA = 100 * Maths.Deg2Rad; + hdgB = 0 * Maths.Deg2Rad; + + expect( tool.calculateSDirection( hdgA, hdgB ) ).toBe( TvDirection.OPPOSITE ); + expect( tool.calculateSDirection( hdgB, hdgA ) ).toBe( TvDirection.OPPOSITE ); + + hdgA = 135 * Maths.Deg2Rad; + hdgB = 0 * Maths.Deg2Rad; + + expect( tool.calculateSDirection( hdgA, hdgB ) ).toBe( TvDirection.OPPOSITE ); + expect( tool.calculateSDirection( hdgB, hdgA ) ).toBe( TvDirection.OPPOSITE ); + + hdgA = 135 * Maths.Deg2Rad; + hdgB = 90 * Maths.Deg2Rad; + + expect( tool.calculateSDirection( hdgA, hdgB ) ).toBe( TvDirection.SAME ); + expect( tool.calculateSDirection( hdgB, hdgA ) ).toBe( TvDirection.SAME ); + + } ); + + it( 'should display links' ); + + it( 'should delete link' ); + + it( 'should create link' ); + + it( 'should show link control points' ); + + function createStraightRoadsSameDirection () { + + const road1 = openDrive.addDefaultRoad(); + const road2 = openDrive.addDefaultRoad(); + + // left to right + road1.addGeometryLine( 0, -50, 0, 0, 100 ); + + // down to up + road2.addGeometryLine( 0, 0, -50, 1.5708, 100 ); + + return [ road1, road2 ]; + } + + function createStraightRoadsOppositeDirection () { + + const road1 = openDrive.addDefaultRoad(); + const road2 = openDrive.addDefaultRoad(); + + // left to right + road1.addGeometryLine( 0, -50, 0, 0, 100 ); + + // up to down + road2.addGeometryLine( 0, 0, 50, -1.5708, 100 ); + + return [ road1, road2 ]; + } + +} ); + diff --git a/src/app/core/tools/auto-maneuver-tool.ts b/src/app/core/tools/auto-maneuver-tool.ts new file mode 100644 index 00000000..2915e4e3 --- /dev/null +++ b/src/app/core/tools/auto-maneuver-tool.ts @@ -0,0 +1,1423 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { BaseTool } from './base-tool'; +import { TvRoad } from 'app/modules/tv-map/models/tv-road.model'; +import * as THREE from 'three'; +import { Mesh, PointsMaterial, Vector3 } from 'three'; +import { TvPosTheta } from 'app/modules/tv-map/models/tv-pos-theta'; +import { TvMapQueries } from 'app/modules/tv-map/queries/tv-map-queries'; +import { PointEditor } from '../editors/point-editor'; +import { AbstractShapeEditor } from '../editors/abstract-shape-editor'; +import { TvMapSourceFile } from 'app/modules/tv-map/services/tv-map-source-file'; +import { Maths } from 'app/utils/maths'; +import { TvDirection, TvLaneType, TvSide } from 'app/modules/tv-map/models/tv-common'; +import { SceneService } from '../services/scene.service'; +import { CatmullRomPath, HermiteSplineCurve } from '../shapes/cubic-spline-curve'; +import { OdTextures } from 'app/modules/tv-map/builders/od.textures'; +import { TvJunctionConnection } from 'app/modules/tv-map/models/tv-junction-connection'; +import { LanePathObject, TvJunctionLaneLink } from 'app/modules/tv-map/models/tv-junction-lane-link'; +// import { JunctionDot } from "app/modules/three-js/objects/junction-dot"; +import { AbstractSpline } from '../shapes/abstract-spline'; +import { AutoSpline } from '../shapes/auto-spline'; +import { LanePathFactory } from '../factories/lane-path-factory.service'; +import { MouseButton, PointerEventData } from 'app/events/pointer-event-data'; +import { KeyboardInput } from '../input'; +import { CommandHistory } from 'app/services/command-history'; +import { SetInspectorCommand } from '../commands/set-inspector-command'; +import { LaneLinkInspector } from 'app/views/inspectors/lane-link-inspector/lane-link-inspector.component'; +import { JunctionEntryObject } from 'app/modules/three-js/objects/junction-entry.object'; +import { MultiCmdsCommand } from '../commands/multi-cmds-command'; +import { RoadControlPoint } from 'app/modules/three-js/objects/road-control-point'; +import { PickingHelper } from '../services/picking-helper.service'; +import { SetValueCommand } from 'app/modules/three-js/commands/set-value-command'; +import { UpdateRoadPointCommand } from '../commands/update-road-point-command'; +import { TvRoadCoord } from 'app/modules/tv-map/models/tv-lane-coord'; + +export interface JunctionDot { + id: number, + s: number, + roadId: number, + laneId: number, + type: 'start' | 'end', + direction: 'forward' | 'backward', + position: Vector3, + hdg: number, + sDirection?: 'same' | 'opposite', + color: number +} + +export interface IJunctionConnection { + type?: 'straight' | 'left-turn' | 'right-turn', + incomingRoad?: number, + outgoingRoad?: number, + fromLane?: number, + toLane?: number, + point?: 'start' | 'end', + entry?: JunctionDot, + exit?: JunctionDot, + spline?: AbstractSpline, + mesh?: Mesh +} + +export interface TempIntersection { + x: number, + y: number, + s1: number, + s2: number, + road1: number, + road2: number, + coordA?: TvRoadCoord, + coordB?: TvRoadCoord, +} + +/** + * Tool to show,edit junction maneuvers + * + * 1. Shows all junction areas on enable/init + * a. highlight junction area on hover + * b. select junction area on click + * P + * 2. When Junction Clicked + * a. show dots, maneuvers + * b. show rebuild maneuver button in inspector + * + * 3. Dots can be highlighted and clicked + * a. when clicked, dots show nothing + * + * 4. Maneuvers can be clicked + * a. when clicked show, inspector with turn type etc info + * b. when clicked show 4 control points that move in +- s direction + * c. maneuevers have distance node for start and end which affects maneuver path + */ +export class AutoManeuverTool extends BaseTool { + + public static DOTCOUNT = 0; + + name: string = 'ManeuverTool'; + + pointEditor: AbstractShapeEditor; + + private connections: IJunctionConnection[] = []; + + private pointerDown = false; + + private pointerDownAt: Vector3; + + private roadChanged = false; + + private connectingRoad: TvRoad; + + private roadControlPoint: RoadControlPoint; + + private lanePathObject: LanePathObject; + + init () { + + this.pointEditor = new PointEditor(); + + } + + enable () { + + this.loadJunctions(); + + const intersections = this.findIntersectionsSlow( [ ...this.openDrive.roads.values() ] ); + + intersections.forEach( intersection => { + + const results = this.calculateJunctionDistances( intersection.coordA, intersection.coordB ); + + results.forEach( result => { + + this.divideRoad( result ); + + } ); + + } ); + + // const junctions = this.createJunctionAreas( intersections ); + + // this.connections = this.prepareJunctionConnections( junctions ); + + // this.connections.forEach( connection => { + // this.makePath( connection ); + // } ) + } + + divideRoad ( result ) { + + // const startSCoord = result.start.s; + // const startRoad = this.openDrive.getRoadById( result.start.roadId ); + // const startPosition = startRoad.getRoadPosition( result.start.s ); + // const p1 = new RoadControlPoint( startRoad, startPosition.toVector3() ); + + // // start position become the limit/length for the road + // startRoad.length = startSCoord; + + // const geometry = startRoad.getGeometryAt( startSCoord ); + + // geometry.length = startSCoord - geometry.s; + + + // const endRoad = this.openDrive.getRoadById( result.end.roadId ); + // const endPosition = endRoad.getRoadPosition( result.end.s ); + // const p2 = new RoadControlPoint( endRoad, endPosition.toVector3() ); + + + // SceneService.add( p1 ); + // SceneService.add( p2 ); + } + + disable () { + + this.pointEditor.removeAllControlPoints(); + + this.connections.forEach( connection => { + + if ( connection.spline ) { + + connection.spline.hide(); + + } + + if ( connection.mesh ) { + + connection.mesh.visible = false; + } + + } ); + + this.hideLinks(); + } + + onPointerDown ( e: PointerEventData ) { + + if ( e.button === MouseButton.RIGHT || e.button === MouseButton.MIDDLE ) return; + + this.pointerDown = true; + + this.pointerDownAt = e.point.clone(); + + const shiftKeyDown = KeyboardInput.isShiftKeyDown; + + let hasInteracted = false; + + if ( !shiftKeyDown && !hasInteracted ) hasInteracted = this.checkRoadControlPointInteraction( e ); + + if ( !shiftKeyDown && !hasInteracted ) hasInteracted = this.checkJunctionEntryInteraction( e ); + + if ( !shiftKeyDown && !hasInteracted ) hasInteracted = this.checkPathInteraction( e ); + + if ( !hasInteracted ) { + + const commands = []; + + commands.push( new SetInspectorCommand( null, null ) ); + + CommandHistory.execute( new MultiCmdsCommand( commands ) ); + } + } + + onPointerUp ( e: PointerEventData ) { + + if ( this.connectingRoad && this.roadControlPoint && this.roadChanged ) { + + const updateRoadPointCommand = new UpdateRoadPointCommand( + this.connectingRoad, + this.roadControlPoint, + this.roadControlPoint.position, + this.pointerDownAt + ); + + CommandHistory.execute( updateRoadPointCommand ); + + LanePathFactory.update( this.lanePathObject ); + } + + this.pointerDown = false; + + this.roadChanged = false; + + this.pointerDownAt = null; + } + + onPointerMoved ( e: PointerEventData ) { + + if ( this.pointerDown && this.roadControlPoint && this.connectingRoad ) { + + this.roadControlPoint.copyPosition( e.point ); + + this.connectingRoad.spline.update(); + + this.roadChanged = true; + + } + + } + + checkPathInteraction ( event: PointerEventData ): boolean { + + if ( event.button !== MouseButton.LEFT ) return; + + let hasInteracted = false; + + for ( let i = 0; i < event.intersections.length; i++ ) { + + const intersection = event.intersections[ i ]; + + if ( intersection.object[ 'tag' ] === LanePathObject.tag ) { + + hasInteracted = true; + + this.lanePathObject = intersection.object.parent as LanePathObject; + + const commands = []; + + this.connectingRoad = this.lanePathObject.connectingRoad; + + commands.push( new SetInspectorCommand( LaneLinkInspector, { + link: this.lanePathObject.link, + connection: this.lanePathObject.connection + } ) ); + + CommandHistory.execute( new MultiCmdsCommand( commands ) ); + + break; + } + } + + return hasInteracted; + } + + static createDots ( road: TvRoad, point: Vector3, sCoord?: number, startSCoord?: number, endSCoord?: number ) { + + const dots: JunctionDot[] = []; + + const laneSection = road.getLaneSectionAt( sCoord ); + + laneSection.getLeftLanes().filter( l => l.type == TvLaneType.driving ).forEach( lane => { + + const startRef = new TvPosTheta(); + const startPosition = TvMapQueries.getLanePosition( road.id, lane.id, startSCoord, 0, startRef ); + + // start dot + dots.push( { + id: this.DOTCOUNT++, + color: 0x69F0AE, + s: startSCoord, + roadId: road.id, + laneId: lane.id, + type: 'start', + direction: 'forward', + position: startPosition, + hdg: startRef.hdg + } ); + + const endRef = new TvPosTheta(); + const endPosition = TvMapQueries.getLanePosition( road.id, lane.id, endSCoord, 0, endRef ); + + // end dot + dots.push( { + id: this.DOTCOUNT++, + color: 0xFF5252, + s: endSCoord, + roadId: road.id, + laneId: lane.id, + type: 'end', + direction: 'forward', + position: endPosition, + hdg: endRef.hdg, + } ); + + } ); + + laneSection.getRightLanes().filter( l => l.type == TvLaneType.driving ).forEach( lane => { + + const startRef = new TvPosTheta(); + const startPosition = TvMapQueries.getLanePosition( road.id, lane.id, endSCoord, 0, startRef ); + + // start dot + dots.push( { + id: this.DOTCOUNT++, + color: 0x69F0AE, + s: endSCoord, + roadId: road.id, + laneId: lane.id, + type: 'start', + direction: 'backward', + position: startPosition, + hdg: startRef.hdg + Math.PI, // add 180 degree + } ); + + const endRef = new TvPosTheta(); + const endPosition = TvMapQueries.getLanePosition( road.id, lane.id, startSCoord, 0, endRef ); + + // end dot + dots.push( { + id: this.DOTCOUNT++, + color: 0xFF5252, + s: startSCoord, + roadId: road.id, + laneId: lane.id, + type: 'end', + direction: 'backward', + position: endPosition, + hdg: endRef.hdg + Math.PI, // add 180 degree + } ); + + } ); + + return dots; + } + + checkRoadControlPointInteraction ( e: PointerEventData ): boolean { + + if ( !this.connectingRoad || !this.connectingRoad.spline ) return; + + if ( !e.point ) return; + + const maxDistance = Math.max( 0.5, e.approxCameraDistance * 0.01 ); + + const roadControlPoints = []; + + this.connectingRoad.spline.controlPoints.forEach( ( cp: RoadControlPoint ) => { + + roadControlPoints.push( cp ); + + if ( cp.frontTangent ) roadControlPoints.push( cp.frontTangent ); + + if ( cp.backTangent ) roadControlPoints.push( cp.backTangent ); + + } ); + + const roadControlPoint = PickingHelper.findNearest( e.point, roadControlPoints, maxDistance ); + + if ( roadControlPoint ) { + + const commands = []; + + // commands.push( new SetInspectorCommand( RoadInspector, { + // road: this.connectingRoad, + // controlPoint: roadControlPoint + // } ) ); + + commands.push( new SetValueCommand( this, 'roadControlPoint', roadControlPoint ) ); + + // if ( this.node ) commands.push( new SetValueCommand( this, 'node', null ) ); + + CommandHistory.execute( new MultiCmdsCommand( commands ) ); + + } else if ( !this.roadControlPoint && this.roadControlPoint ) { + + this.roadControlPoint.unselect(); + + this.roadControlPoint = null; + + } + + return roadControlPoint != null; + } + + hideLinks () { + + this.openDrive.junctions.forEach( junction => { + + junction.connections.forEach( connection => { + + const incomingRoad = this.openDrive.getRoadById( connection.incomingRoad ); + const connectingRoad = this.openDrive.getRoadById( connection.connectingRoad ); + + connection.laneLink.forEach( link => { + + link.hide(); + + } ); + + } ); + + } ); + } + + loadJunctions (): void { + + this.openDrive.junctions.forEach( junction => { + + junction.connections.forEach( connection => { + + const incomingRoad = this.openDrive.getRoadById( connection.incomingRoad ); + const connectingRoad = this.openDrive.getRoadById( connection.connectingRoad ); + + connection.laneLink.forEach( link => { + + this.makeJunctionConnectionPath( incomingRoad, connectingRoad, connection, link ); + + } ); + + } ); + + } ); + + } + + static createTurnConnectionPaths ( entry: JunctionDot, exit: JunctionDot ): AbstractSpline { + + const autoSpline = new AutoSpline(); + + autoSpline.addControlPointAt( entry.position ); + + if ( entry.direction == 'forward' && exit.direction == 'forward' ) { + + const entry2 = TvMapQueries.getLanePosition( entry.roadId, entry.laneId, entry.s + 2.5, 0 ); + autoSpline.addControlPointAt( entry2 ); + + const entry3 = TvMapQueries.getLanePosition( exit.roadId, exit.laneId, exit.s - 2.5, 0 ); + autoSpline.addControlPointAt( entry3 ); + + } else if ( entry.direction == 'backward' && exit.direction == 'backward' ) { + + const entry2 = TvMapQueries.getLanePosition( entry.roadId, entry.laneId, entry.s - 2.5, 0 ); + autoSpline.addControlPointAt( entry2 ); + + const entry3 = TvMapQueries.getLanePosition( exit.roadId, exit.laneId, exit.s + 2.5, 0 ); + autoSpline.addControlPointAt( entry3 ); + + } else if ( entry.direction == 'forward' && exit.direction == 'backward' ) { + + const entry2 = TvMapQueries.getLanePosition( entry.roadId, entry.laneId, entry.s + 2.5, 0 ); + autoSpline.addControlPointAt( entry2 ); + + const entry3 = TvMapQueries.getLanePosition( exit.roadId, exit.laneId, exit.s + 2.5, 0 ); + autoSpline.addControlPointAt( entry3 ); + + } else if ( entry.direction == 'backward' && exit.direction == 'forward' ) { + + const entry2 = TvMapQueries.getLanePosition( entry.roadId, entry.laneId, entry.s - 2.5, 0 ); + autoSpline.addControlPointAt( entry2 ); + + const entry3 = TvMapQueries.getLanePosition( exit.roadId, exit.laneId, exit.s - 2.5, 0 ); + autoSpline.addControlPointAt( entry3 ); + + } + + autoSpline.addControlPointAt( exit.position ); + + autoSpline.update(); + + // console.log( entry, exit, autoSpline.exportGeometries() ); + + return autoSpline; + } + + makePath ( connection: IJunctionConnection ) { + + const spline = connection.spline; + + const shape = new THREE.Shape(); + shape.moveTo( 0, -0.25 ); + shape.lineTo( 0, 0.25 ); + + // const arcLine = new LineArcSplineCurve( spline.controlPoints ); + + const path = new CatmullRomPath( spline.controlPointPositions ); + + // const randomPoints = []; + // for ( let i = 0; i < 10; i++ ) { + // randomPoints.push( new THREE.Vector3( ( i - 4.5 ) * 50, THREE.Math.randFloat( - 50, 50 ), 0 ) ); + // } + // const path = new THREE.CatmullRomSpline( randomPoints ); + + const extrudeSettings = { + steps: path.getLength() * 2, + bevelEnabled: false, + extrudePath: path + }; + + const geometry = new THREE.ExtrudeGeometry( shape, extrudeSettings ); + const material = new THREE.MeshBasicMaterial( { + color: 0x00ff00, + wireframe: false, + opacity: 0.5, + transparent: true, + depthTest: false, + map: OdTextures.asphalt + } ); + + connection.mesh = new THREE.Mesh( geometry, material ); + + connection.mesh.renderOrder = 3; + + SceneService.add( connection.mesh ); + + } + + private curve: THREE.Mesh; + + updateCurve () { + + const shape = new THREE.Shape(); + shape.moveTo( 0, 0 ); + shape.lineTo( 0, 12 ); + + const p0 = this.pointEditor.controlPointPositions[ 0 ]; + const p1 = this.pointEditor.controlPointPositions[ 1 ]; + const p2 = this.pointEditor.controlPointPositions[ 2 ]; + const p3 = this.pointEditor.controlPointPositions[ 3 ]; + + const a = 0; + const b = 0; + const c = 0; + const d = 0; + + + const bezier = new THREE.CubicBezierCurve3( p0, p1, p2, p3 ); + const hermite = new HermiteSplineCurve( p0, p1, p2, p3 ); + + const extrudeSettings = { + steps: 200, + bevelEnabled: false, + extrudePath: hermite + }; + + this.curve.geometry.dispose(); + + this.curve.geometry = new THREE.ExtrudeGeometry( shape, extrudeSettings ); + } + + makeSampleCurve () { + + const shape = new THREE.Shape(); + shape.moveTo( 0, 0 ); + shape.lineTo( 0, 12 ); + + const p0 = new Vector3( 0, 0, 0 ); + const p1 = new Vector3( 100, 0, 0 ); + const p2 = new Vector3( 150, 50, 0 ); + const p3 = new Vector3( 150, 100, 0 ); + + this.pointEditor.addControlPoint( p0 ); + this.pointEditor.addControlPoint( p1 ); + this.pointEditor.addControlPoint( p2 ); + this.pointEditor.addControlPoint( p3 ); + + // const m = new Matrix4().set( + // 0, 0, -3, 3, + // -3, 3, 0, 0, + // 0, 0, 0, 1, + // 1, 0, 0, 0 + // ); + + // const m = new Matrix4().set( + // 1, 0, 0, 0, + // 0, 0, 0, 1, + // -3, 3, 0, 0, + // 0, 0, -3, 3, + // ); + + // // console.log( 'm', m ); + + // const m_inv = new Matrix4().getInverse( m ); + + // console.log( 'm-inv', m_inv ); + + // const g = new Matrix4().set( + // 0, 0, 0, 0, + // 100, 0, 0, 0, + // 150, 0, 0, 0, + // 150, 0, 0, 0 + // ); + + const bezier = new THREE.CubicBezierCurve3( p0, p1, p2, p3 ); + const hermite = new HermiteSplineCurve( p0, p1, p2, p3 ); + + // console.log( m_inv.multiply( g ), bezier.getLength() ); + + const extrudeSettings = { + steps: 200, + bevelEnabled: false, + extrudePath: hermite + }; + + const geometry = new THREE.ExtrudeGeometry( shape, extrudeSettings ); + const material = new THREE.MeshBasicMaterial( { color: 0x00ff00, wireframe: true } ); + const mesh = this.curve = new THREE.Mesh( geometry, material ); + + SceneService.add( mesh ); + + } + + static createStraightConnections ( road: TvRoad, dots: JunctionDot[] = [] ) { + + const connections: IJunctionConnection[] = []; + + const entries = dots.filter( d => d.roadId == road.id ).filter( d => d.type == 'start' ); + + entries.forEach( entry => { + + const exits = dots.filter( d => d.roadId == road.id ).filter( d => d.laneId == entry.laneId ).filter( d => d.type == 'end' ); + + if ( exits.length > 0 ) { + + const exit = exits[ 0 ]; + + const spline = this.createStraightConnectionPath( entry, exit ); + + connections.push( { + type: 'straight', + incomingRoad: entry.roadId, + outgoingRoad: exit.roadId, + fromLane: entry.laneId, + toLane: exit.laneId, + point: 'start', + entry: entry, + exit: exit, + spline: spline + } ); + } + + } ); + + return connections; + } + + static createLeftTurnConnections ( road: TvRoad, dots: JunctionDot[] = [] ) { + + const connections: IJunctionConnection[] = []; + + const directions = [ 'forward', 'backward' ]; + + directions.forEach( direction => { + + let entry: JunctionDot = null; + + let entries = dots.filter( d => d.roadId == road.id ) + .filter( d => d.direction == direction ) + .filter( d => d.type == 'start' ); + + if ( entries.length > 0 && direction == 'forward' ) { + + // find left most lane + entry = entries.reduce( ( x, y ) => { + return x.laneId > y.laneId ? x : y; + } ); + + } else if ( entries.length > 0 && direction == 'backward' ) { + + // find left most lane + entry = entries.reduce( ( x, y ) => { + return x.laneId < y.laneId ? x : y; + } ); + + } + + if ( entry ) { + + const exits = dots.filter( d => d.roadId != road.id ) + .filter( d => d.type == 'end' ) + .filter( d => Maths.findSide( d.position, entry.position, entry.hdg ) == TvSide.LEFT ); + + if ( exits.length > 0 ) { + + const exit = exits.reduce( ( a, b ) => { + return a.position.distanceTo( entry.position ) < b.position.distanceTo( entry.position ) ? a : b; + } ); + + const spline = this.createTurnConnectionPaths( entry, exit ); + + connections.push( { + type: 'left-turn', + incomingRoad: entry.roadId, + outgoingRoad: exit.roadId, + fromLane: entry.laneId, + toLane: exit.laneId, + point: 'start', + entry: entry, + exit: exit, + spline: spline + } ); + } + } + + } ); + + return connections; + } + + findIntersectionsSlow ( roads: TvRoad[] ): TempIntersection[] { + + const step = 1; + + const pointCache: any[] = []; + + // const t1 = performance.now(); + + for ( let i = 0; i < roads.length; i++ ) { + + const road = roads[ i ]; + + const positions: any[] = []; + + const geom = { + road: road.id, + positions: positions, + }; + + const geometries = road.geometries; + + for ( let g = 0; g < geometries.length; g++ ) { + + const geometry = geometries[ g ]; + + const posTheta = new TvPosTheta(); + + for ( let s = geometry.s; s <= geometry.s2; s += step ) { + + geometry.getCoords( s, posTheta ); + + positions.push( { x: posTheta.x, y: posTheta.y, z: 0, s: s } ); + + } + } + + pointCache.push( geom ); + } + + // const t2 = performance.now(); + + // const timeToMakeGeometries = t2 - t1; console.log( "step-1", timeToMakeGeometries ); + + const intersections: TempIntersection[] = []; + + for ( let i = 0; i < pointCache.length; i++ ) { + + const points = pointCache[ i ].positions; + + for ( let j = i + 1; j < pointCache.length; j++ ) { + + const otherPoints = pointCache[ j ].positions; + + for ( let k = 0; k < otherPoints.length; k++ ) { + + const otherPoint = otherPoints[ k ]; + + for ( let l = 0; l < points.length; l++ ) { + + const point = points[ l ]; + + // const distance = position.distanceTo( otherPosition ); + + const distance = Math.sqrt( + ( point.x - otherPoint.x ) * ( point.x - otherPoint.x ) + + ( point.y - otherPoint.y ) * ( point.y - otherPoint.y ) + ); + + if ( distance < ( step * 0.9 ) ) { + + intersections.push( { + x: point.x, + y: point.y, + s1: point.s, + s2: otherPoint.s, + road1: pointCache[ i ].road, + road2: pointCache[ j ].road, + coordA: new TvRoadCoord( pointCache[ i ].road, point.s ), + coordB: new TvRoadCoord( pointCache[ j ].road, otherPoint.s ), + } ); + + // skip a few steps + l += step * 2; + k += step * 2; + } + } + } + } + } + + // const t3 = performance.now(); + + // const timeToFindIntersections = t3 - t2; console.log( "step2", timeToFindIntersections ); + + return intersections; + } + + calculateDistance ( roadAHdg: number, roadBHdg: number ) { + + // const roadA = this.openDrive.getRoadById( coordA.roadId ); + // const roadB = this.openDrive.getRoadById( coordB.roadId ); + + // const roadAHdg = roadA.getRoadPosition( coordA.s ).hdg; + // const roadBHdg = roadB.getRoadPosition( coordB.s ).hdg; + + let hdgDifference: number; + + if ( roadAHdg > roadBHdg ) + hdgDifference = roadAHdg - roadBHdg; + else + hdgDifference = roadBHdg - roadAHdg; + + const absDifference = Math.abs( hdgDifference ); + + // const absDifference = Maths.clamp( Math.abs( hdgDifference ), 0, Maths.M_PI_2 ); + + // const sDirection = this.calculateSDirection( roadAHdg, roadBHdg ); + + const distanceFromJunction = Math.abs( Maths.M_PI - absDifference ) * 1.5; + + return distanceFromJunction; + } + + calculateSDirection ( hdgA, hdgB ): TvDirection { + + let difference; + + if ( hdgA > hdgB ) { + + difference = hdgB - hdgA; + + } else { + + difference = hdgA - hdgB; + + } + + return Math.abs( difference ) > Maths.M_PI_2 ? + TvDirection.OPPOSITE : + TvDirection.SAME; + } + + toPositiveAngle ( angle: number ) { + + angle = angle % ( Math.PI * 2 ); + + while ( angle < 0 ) { + + angle += Math.PI * 2; + + } + + return angle; + } + + prepareJunctionConnections ( junctions: any[] ): IJunctionConnection[] { + + const connections: IJunctionConnection[] = []; + + for ( let i = 0; i < junctions.length; i++ ) { + + const junction = junctions[ i ]; + + // point of intersection + this.pointEditor.addControlPoint( junction.position ); + + const dots = this.createDotsForJunction( junction ); + + // ManeuverTool.createConnections( [ junction.road1, junction.road2 ], dots ).forEach( connection => { + + // connections.push( connection ); + + // } ); + + } + + return connections; + } + + createDotsForJunction ( junction ) { + + const dots: JunctionDot[] = []; + + const road1_dots = AutoManeuverTool.createDots( + junction.road1, + junction.position, + junction.road1_s, + junction.road1_StartS, + junction.road1_EndS + ); + + const road2_dots = AutoManeuverTool.createDots( + junction.road2, + junction.position, + junction.road2_s, + junction.road2_StartS, + junction.road2_EndS + ); + + road1_dots.forEach( dot => dots.push( dot ) ); + + road2_dots.forEach( dot => dots.push( dot ) ); + + dots.forEach( dot => { + + dot.sDirection = junction.sDirection; + + const cp = this.pointEditor.addControlPoint( dot.position, null, 20 ); + + ( cp.material as PointsMaterial ).color.set( dot.color ); + + } ); + + return dots; + } + + calculateJunctionDistances ( coordA: TvRoadCoord, coordB: TvRoadCoord ) { + + const result: { start: TvRoadCoord, end: TvRoadCoord }[] = []; + + const coords: TvRoadCoord[] = []; + + const roadA = this.openDrive.getRoadById( coordA.roadId ); + const roadB = this.openDrive.getRoadById( coordB.roadId ); + + const roadALeftWidth = roadA.getLeftSideWidth( coordA.s ); + const roadARightWidth = roadA.getRightsideWidth( coordA.s ); + + const roadBLeftWidth = roadB.getLeftSideWidth( coordB.s ); + const roadBRightWidth = roadB.getRightsideWidth( coordB.s ); + + const roadAHdg = roadA.getRoadPosition( coordA.s ).hdg; + const roadBHdg = roadB.getRoadPosition( coordB.s ).hdg; + + let roadAStart, roadAEnd, roadBStart, roadBEnd; + + const distanceFromJunction = this.calculateDistance( roadAHdg, roadBHdg ); + + const sDirection = this.calculateSDirection( roadAHdg, roadBHdg ); + + if ( sDirection === TvDirection.SAME ) { + + roadAStart = coordA.s - roadBLeftWidth - distanceFromJunction; + roadAEnd = coordA.s + roadBRightWidth + distanceFromJunction; + + roadBStart = coordB.s - roadALeftWidth - distanceFromJunction; + roadBEnd = coordB.s + roadARightWidth + distanceFromJunction; + + } else { + + roadAStart = coordA.s - roadBLeftWidth - distanceFromJunction; + roadAEnd = coordA.s + roadBRightWidth + distanceFromJunction; + + roadBStart = coordB.s - roadALeftWidth - distanceFromJunction; + roadBEnd = coordB.s + roadARightWidth + distanceFromJunction; + + } + + result.push( { + start: new TvRoadCoord( coordA.roadId, roadAStart ), + end: new TvRoadCoord( coordA.roadId, roadAEnd ), + } ); + + coords.push( new TvRoadCoord( coordA.roadId, roadAStart ) ); + coords.push( new TvRoadCoord( coordA.roadId, roadAEnd ) ); + + result.push( { + start: new TvRoadCoord( coordB.roadId, roadBStart ), + end: new TvRoadCoord( coordB.roadId, roadBEnd ), + } ); + + coords.push( new TvRoadCoord( coordB.roadId, roadBStart ) ); + coords.push( new TvRoadCoord( coordB.roadId, roadBEnd ) ); + + return result; + } + + static createConnections ( roads: TvRoad[], dots: JunctionDot[] ): IJunctionConnection[] { + + const connections: IJunctionConnection[] = []; + + roads.forEach( road => { + + this.createStraightConnections( road, dots ) + .forEach( connection => connections.push( connection ) ); + + this.createLeftTurnConnections( road, dots ) + .forEach( connection => connections.push( connection ) ); + + this.createRightTurnConnections( road, dots ) + .forEach( connection => connections.push( connection ) ); + + } ); + + return connections; + } + + static createRightTurnConnections ( road: TvRoad, dots: JunctionDot[] = [] ) { + + const connections: IJunctionConnection[] = []; + + const directions = [ 'forward', 'backward', ]; + + directions.forEach( direction => { + + let entry = null; + + let entries = dots.filter( d => d.roadId == road.id ) + .filter( d => d.direction == direction ) + .filter( d => d.type == 'start' ); + + if ( entries.length > 0 && direction == 'forward' ) { + + // find right most lane + entry = entries.reduce( ( x, y ) => { + return x.laneId < y.laneId ? x : y; + } ); + + } else if ( entries.length > 0 && direction == 'backward' ) { + + // find right most lane + entry = entries.reduce( ( x, y ) => { + return x.laneId > y.laneId ? x : y; + } ); + + } + + if ( entry ) { + + const exits = dots + .filter( d => d.roadId != road.id ) + .filter( d => d.type == 'end' ) + .filter( d => Maths.findSide( d.position, entry.position, entry.hdg ) == TvSide.RIGHT ); + + if ( exits.length > 0 ) { + + // get the nearest exit by lowest distance from entry + const exit = exits.reduce( ( a, b ) => { + return a.position.distanceTo( entry.position ) < b.position.distanceTo( entry.position ) ? a : b; + } ); + + const spline = this.createTurnConnectionPaths( entry, exit ); + + connections.push( { + type: 'right-turn', + incomingRoad: entry.roadId, + outgoingRoad: exit.roadId, + fromLane: entry.laneId, + toLane: exit.laneId, + point: 'start', + entry: entry, + exit: exit, + spline: spline + } ); + } + } + + } ); + + return connections; + } + + checkJunctionEntryInteraction ( event: PointerEventData ): boolean { + + if ( event.button !== MouseButton.LEFT ) return; + + let hasInteracted = false; + + for ( let i = 0; i < event.intersections.length; i++ ) { + + const intersection = event.intersections[ i ]; + + if ( intersection.object[ 'tag' ] === JunctionEntryObject.tag ) { + + hasInteracted = true; + + console.log( 'junction-entry' ); + + break; + } + } + + return hasInteracted; + } + + createJunctionAreas ( intersections: any[] ) { + + const junctions: { + position, + sDirection, + road1, + road1_s, + road1_hdg, + road1_StartS, + road1_EndS, + road2, + road2_s, + road2_hdg, + road2_StartS, + road2_EndS + }[] = []; + + for ( let i = 0; i < intersections.length; i++ ) { + + const intersection = intersections[ i ]; + + const position = new Vector3( intersection.x, intersection.y, 0 ); + + const road_1 = TvMapSourceFile.openDrive.getRoadById( intersection.road1 ); + const road_2 = TvMapSourceFile.openDrive.getRoadById( intersection.road2 ); + + const road1_s = intersection.s1; + const road2_s = intersection.s2; + + let road1_LeftWidth = 0; + let road1_RightWidth = 0; + + road_1.getLaneSectionAt( road1_s ).getLeftLanes().forEach( lane => { + road1_LeftWidth += lane.getWidthValue( road1_s ); + } ); + road_1.getLaneSectionAt( road1_s ).getRightLanes().forEach( lane => { + road1_RightWidth += lane.getWidthValue( road1_s ); + } ); + + let road2_LeftWidth = 0; + let road2_RightWidth = 0; + + road_2.getLaneSectionAt( road2_s ).getLeftLanes().forEach( lane => { + road2_LeftWidth += lane.getWidthValue( road2_s ); + } ); + road_2.getLaneSectionAt( road2_s ).getRightLanes().forEach( lane => { + road2_RightWidth += lane.getWidthValue( road2_s ); + } ); + + let hdg1 = road_1.getPositionAt( road1_s, 0 ).hdg * Maths.Rad2Deg; + let hdg2 = road_2.getPositionAt( road2_s, 0 ).hdg * Maths.Rad2Deg; + + let difference = ( hdg2 - hdg1 ) % 360; + + let road1_StartS, road1_EndS, road2_StartS, road2_EndS; + + let sDirection; + + // same + if ( difference > 0 && difference < 180 ) { + + sDirection = 'same'; + + let distanceFromJunction = Math.abs( 90 - difference ) * 0.2 + 2.5; + + road1_StartS = road1_s - road2_LeftWidth - distanceFromJunction; + road1_EndS = road1_s + road2_RightWidth + distanceFromJunction; + + road2_StartS = road2_s - road1_LeftWidth - distanceFromJunction; + road2_EndS = road2_s + road1_RightWidth + distanceFromJunction; + + } else { + + sDirection = 'opposite'; + + let distanceFromJunction = Math.abs( 90 - Math.abs( difference ) ) * 0.2 + 2.5; + + road1_StartS = road1_s - road2_LeftWidth - distanceFromJunction; + road1_EndS = road1_s + road2_RightWidth + distanceFromJunction; + + road2_StartS = road2_s - road1_LeftWidth - distanceFromJunction; + road2_EndS = road2_s + road1_RightWidth + distanceFromJunction; + + } + + junctions.push( { + position, + sDirection, + road1: road_1, + road1_s, + road1_hdg: hdg1, + road1_StartS, + road1_EndS, + road2: road_2, + road2_s, + road2_hdg: hdg2, + road2_StartS, + road2_EndS + } ); + } + + return junctions; + } + + static createStraightConnectionPath ( entry: JunctionDot, exit: JunctionDot ): AbstractSpline { + + const spline = new AutoSpline(); + + spline.addControlPointAt( entry.position ); + spline.addControlPointAt( exit.position ); + + spline.update(); + + return spline; + } + + makeJunctionConnectionPath ( + incomingRoad: TvRoad, + connectingRoad: TvRoad, + connection: TvJunctionConnection, + link: TvJunctionLaneLink + ) { + + // 1. find connection positions which are basically dots + // 2. find whether this connection is straight,left,right based on start->end position relation + + let start: Vector3, end: Vector3; + + if ( connection.contactPoint === 'start' ) { + + start = TvMapQueries.getLanePosition( + connectingRoad.id, + link.to, + 0, + ); + + end = TvMapQueries.getLanePosition( + connectingRoad.id, + link.to, + connectingRoad.length, + ); + + + } else { + + start = TvMapQueries.getLanePosition( + connectingRoad.id, + link.to, + connectingRoad.length, + ); + + end = TvMapQueries.getLanePosition( + connectingRoad.id, + link.to, + 0, + ); + + } + + // SceneService.add( new JunctionEntryObject( "start-dot", start, OdContactPoints.START, incomingRoad, link.from ) ); + // SceneService.add( new JunctionEntryObject( "start-dot", end, OdContactPoints.END, incomingRoad, link.from ) ); + + if ( !link.lanePath ) { + + link.lanePath = LanePathFactory.create( incomingRoad, connectingRoad, connection, link ); + + SceneService.add( link.lanePath ); + + } else { + + link.lanePath.visible = true; + + } + + + // ////////////////////////////////////////////////////////////// + + // const spline = connectingRoad.spline; + + // const shape = new THREE.Shape(); shape.moveTo( 0, -0.3 ); shape.lineTo( 0, 0.3 ); + + // if ( spline.controlPointPositions.length < 2 ) return; + + // let offset = targetLaneWidth; + + // if ( targetLane.id < 0 ) offset *= -1; + + // const path = new ExplicitSplinePath( spline as ExplicitSpline, offset ); + + // // const extrudeSettings = { + // // steps: path.getLength() * 2, + // // bevelEnabled: false, + // // extrudePath: path + // // }; + + // // const geometry = new THREE.ExtrudeGeometry( shape, extrudeSettings ); + // // const material = new THREE.MeshBasicMaterial( { + // // color: 0x00ff00, + // // wireframe: false, + // // opacity: 0.5, + // // transparent: true, + // // map: OdTextures.asphalt + // // } ); + + // // link.mesh = new THREE.Mesh( geometry, material ); + + // // link.mesh.renderOrder = 3; + + // // SceneService.add( link.mesh ); + + // const lineMaterial = new LineBasicMaterial( { + // color: 0x00ff00, + // linewidth: 100, + // opacity: 0.5, + // transparent: true, + // } ); + + // const lineGeometry = new BufferGeometry().setFromPoints( path.getSpacedPoints( 50 ) ); + + // link.mesh = new Line( lineGeometry, lineMaterial ); + + // link.mesh.castShadow = true; + + // link.mesh.renderOrder = 3; + + // link.mesh.frustumCulled = false; + + // SceneService.add( link.mesh ); + } + + // create multiple roads + createStraightIntersectionRoad ( incomingRoad: TvRoad, sStart, sIntersection, sEnd, spline: AbstractSpline ) { + + + } + + /** + * + * @param junctionId + * @param connection + * @deprecated not working + */ + createStraightJunctionRoad ( junctionId: number, connection: IJunctionConnection ) { + + const incomingRoad = this.openDrive.getRoadById( connection.entry.roadId ); + const incomingLane = connection.entry.laneId; + + const originalLenth = incomingRoad.length; + + // change geometry of road 1 + const startPos = new TvPosTheta(); + const endPos = new TvPosTheta(); + + incomingRoad.getGeometryCoordsAt( connection.entry.s, 0, startPos ); + incomingRoad.getGeometryCoordsAt( connection.exit.s, 0, endPos ); + + const laneSection = incomingRoad.getLaneSectionAt( connection.entry.s ); + const geometry = incomingRoad.getGeometryAt( connection.entry.s ); + + // split + incomingRoad.split( connection.exit.s ); + + // connecting road + + const connectingRoadLength = connection.exit.s - connection.entry.s; + + const connectingRoad = new TvRoad( 'Connecting', connectingRoadLength, this.openDrive.roads.size, -1 ); + + connection.spline.exportGeometries().forEach( g => connectingRoad.addGeometry( g ) ); + + + // outgoing road + + const outgoingRoadLength = originalLenth - connection.exit.s; + + const outgoingRoad = new TvRoad( 'Outgoing', outgoingRoadLength, this.openDrive.roads.size, -1 ); + + + // update incoming finally + + incomingRoad.length = connection.entry.s; + + geometry.length = geometry.length - outgoingRoadLength - connectingRoadLength; + + return { + incoming: incomingRoad, + connecting: connectingRoad, + outgoing: outgoingRoad + }; + + } +} diff --git a/src/app/core/tools/base-point-tool.ts b/src/app/core/tools/base-point-tool.ts new file mode 100644 index 00000000..16795ab5 --- /dev/null +++ b/src/app/core/tools/base-point-tool.ts @@ -0,0 +1,6 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +export class BasePointTool { +} diff --git a/src/app/core/tools/base-road-plan-tool.ts b/src/app/core/tools/base-road-plan-tool.ts new file mode 100644 index 00000000..05aa54f0 --- /dev/null +++ b/src/app/core/tools/base-road-plan-tool.ts @@ -0,0 +1,405 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { BaseTool } from './base-tool'; +import { AbstractShapeEditor } from '../editors/abstract-shape-editor'; +import { TvRoad } from '../../modules/tv-map/models/tv-road.model'; +import { TvColors, TvLaneSide, TvLaneType, TvRoadMarkTypes, TvRoadMarkWeights } from '../../modules/tv-map/models/tv-common'; +import { TvMapSourceFile } from '../../modules/tv-map/services/tv-map-source-file'; +import { RoadInspector } from '../../views/inspectors/road-inspector/road-inspector.component'; +import { AppInspector } from '../inspector'; +import { PointEditor } from '../editors/point-editor'; +import { TvAbstractRoadGeometry } from '../../modules/tv-map/models/geometries/tv-abstract-road-geometry'; +import { RoadPlanHelper } from '../helpers/road-plan-helper'; +import { PointerEventData } from '../../events/pointer-event-data'; +import { Subscription } from 'rxjs'; +import { AnyControlPoint } from '../../modules/three-js/objects/control-point'; +import { KeyboardInput } from '../input'; + +export class BaseRoadPlanTool extends BaseTool { + + name: string = 'RoadPlan'; + + protected controlPointSelectedSubscriber: Subscription; + protected controlPointUnselectedSubscriber: Subscription; + protected controlPointAddedSubscriber: Subscription; + protected controlPointMovedSubscriber: Subscription; + protected controlPointUpdatedSubscriber: Subscription; + + onKeyDownSub: Subscription; + + protected shapeEditor: AbstractShapeEditor; + protected road: TvRoad; + protected roadAdded: boolean; + protected helper: RoadPlanHelper; + + constructor () { + + super(); + + this.shapeEditor = new PointEditor(); + } + + init () { + + super.init(); + + } + + enable (): void { + + super.enable(); + + this.controlPointSelectedSubscriber = this.shapeEditor.controlPointSelected.subscribe( e => this.onControlPointSelected( e ) ); + this.controlPointUnselectedSubscriber = this.shapeEditor.controlPointUnselected.subscribe( e => this.onControlPointUnselected( e ) ); + this.controlPointAddedSubscriber = this.shapeEditor.controlPointAdded.subscribe( e => this.onControlPointAdded( e ) ); + this.controlPointMovedSubscriber = this.shapeEditor.controlPointMoved.subscribe( e => this.onControlPointMoved( e ) ); + this.controlPointUpdatedSubscriber = this.shapeEditor.controlPointUpdated.subscribe( e => this.onControlPointUpdated( e ) ); + + this.onKeyDownSub = KeyboardInput.keyDown.subscribe( e => { + + if ( e.key === "Delete" ) { + + this.unselectRoad( this.road ); + + this.road.remove( TvMapSourceFile.openDrive.gameObject ); + + TvMapSourceFile.openDrive.deleteRoad( this.road.id ); + + this.road = null; + + } + + } ) + } + + disable (): void { + + super.disable(); + + this.controlPointSelectedSubscriber.unsubscribe(); + this.controlPointUnselectedSubscriber.unsubscribe(); + this.controlPointAddedSubscriber.unsubscribe(); + this.controlPointMovedSubscriber.unsubscribe(); + this.controlPointUpdatedSubscriber.unsubscribe(); + + if ( this.onKeyDownSub ) this.onKeyDownSub.unsubscribe(); + + this.shapeEditor.destroy(); + + } + + onPointerClicked ( e: PointerEventData ) { + + let hasInteracted = false; + + this.checkControlPointIntersection( e.intersections, ( point ) => { + + hasInteracted = true; + + this.shapeEditor.selectControlPoint( point as AnyControlPoint ); + + } ); + + if ( hasInteracted ) return; + + this.checkRoadIntersection( e.intersections, ( object ) => { + + hasInteracted = true; + + this.selectRoad( object.userData.road ); + + } ); + + if ( hasInteracted ) return; + + // Finally, If no objects were intersected then clear the inspector + if ( !KeyboardInput.isShiftKeyDown ) this.clickedOutside(); + } + + protected clickedOutside () { + + this.road == null; + + AppInspector.clear() + + // this.shapeEditor.removeAllControlPoints(); + + } + + onPointerMoved ( e: PointerEventData ) { + + let hoveringPoint: AnyControlPoint = null; + + let selectectPoint: AnyControlPoint = this.shapeEditor.currentPoint; + + this.checkControlPointIntersection( e.intersections, cp => hoveringPoint = cp ); + + let points: AnyControlPoint[] = []; + + if ( selectectPoint ) { + + points = this.shapeEditor.controlPoints.filter( cp => cp.id != selectectPoint.id ); + + } else { + + points = this.shapeEditor.controlPoints; + + } + + if ( hoveringPoint ) { + + if ( !selectectPoint || selectectPoint.id != hoveringPoint.id ) + this.shapeEditor.onControlPointHovered( hoveringPoint ); + + points = points.filter( i => i.id != hoveringPoint.id ); + + } + + points.forEach( p => this.shapeEditor.onControlPointUnhovered( p ) ); + + } + + protected onControlPointSelected ( e: AnyControlPoint ) { } + + protected onControlPointUnselected ( e: AnyControlPoint ) { } + + protected onControlPointAdded ( e: AnyControlPoint ) { } + + protected onControlPointMoved ( e: AnyControlPoint ) { } + + protected onControlPointUpdated ( e: AnyControlPoint ) { } + + protected getRoadIdFromControlPoint ( e: AnyControlPoint ) { + return e.userData.roadId; + } + + protected setRoadIdOnControlPoint ( e: AnyControlPoint, road: TvRoad ) { + e.userData.roadId = road.id + } + + // + // // TODO: Handle multiple openDrive geometry type + // // currently only handles bezier cube + // onRoadGeometryChanged ( e: THREE.Curve ) { + // + // // this.lineGeometry( e as LineCurve3 ); + // + // // this.addParamPoly3( e as CubicBezierCurve3 ); + // + // } + // + // onRoadGeometryAdded ( e: THREE.Curve ) { + // + // + // } + // + // lineGeometry ( line: LineCurve3 ) { + // + // // return; + // + // // TODO: Do this from openDrive class + // // this.road = new OdRoad( 'road', 0, 1, -1 ); + // + // const p1 = line.v1; + // const p2 = line.v2; + // const hdg = Math.atan2( p2.y - p1.y, p2.x - p1.x ); + // const length = line.getLength(); + // + // + // if ( !this.roadAdded ) { + // + // const roadId = this.openDrive.getRoadCount() + 2; + // + // this.road = this.openDrive.addRoad( '', length, roadId, -1 ); + // + // this.road.addPlanView(); + // + // this.addDefaultLanes( this.road ); + // + // const s = 0; + // + // this.road.planView.addGeometryLine( s, p1.x, p1.y, hdg, length ); + // + // this.roadAdded = true; + // + // } else { + // + // this.road.attr_length = length; + // + // const last = this.road.planView.geometries[ this.road.planView.geometries.length - 1 ]; + // + // last.x = p1.x; + // last.y = p1.y; + // last.hdg = hdg; + // last.length = length; + // + // // this.road.planView.addGeometryLine( s, p1.x, p1.y, hdg, length ); + // } + // + // OdSourceFile.roadNetworkChanged.emit( OdSourceFile.openDrive ); + // } + + public addDefaultLanes ( road: TvRoad ) { + + road.addLaneSection( 0, false ); + + const laneSection = road.getLastAddedLaneSection(); + + const leftLane3 = laneSection.addLane( TvLaneSide.LEFT, 3, TvLaneType.sidewalk, true, true ); + const leftLane2 = laneSection.addLane( TvLaneSide.LEFT, 2, TvLaneType.shoulder, true, true ); + const leftLane1 = laneSection.addLane( TvLaneSide.LEFT, 1, TvLaneType.driving, true, true ); + const centerLane = laneSection.addLane( TvLaneSide.CENTER, 0, TvLaneType.driving, true, true ); + const rightLane1 = laneSection.addLane( TvLaneSide.RIGHT, -1, TvLaneType.driving, true, true ); + const rightLane2 = laneSection.addLane( TvLaneSide.RIGHT, -2, TvLaneType.shoulder, true, true ); + const rightLane3 = laneSection.addLane( TvLaneSide.RIGHT, -3, TvLaneType.sidewalk, true, true ); + + leftLane1.addRoadMarkRecord( 0, TvRoadMarkTypes.NONE, TvRoadMarkWeights.STANDARD, TvColors.STANDARD, 0.15, 'none', 0 ); + centerLane.addRoadMarkRecord( 0, TvRoadMarkTypes.BROKEN, TvRoadMarkWeights.STANDARD, TvColors.STANDARD, 0.15, 'none', 0 ); + rightLane1.addRoadMarkRecord( 0, TvRoadMarkTypes.NONE, TvRoadMarkWeights.STANDARD, TvColors.STANDARD, 0.15, 'none', 0 ); + + laneSection.getLaneVector().forEach( lane => { + + if ( lane.side !== TvLaneSide.CENTER ) { + + if ( lane.type == TvLaneType.driving ) lane.addWidthRecord( 0, 3.6, 0, 0, 0 ); + + else if ( lane.type == TvLaneType.sidewalk ) lane.addWidthRecord( 0, 2, 0, 0, 0 ); + + else lane.addWidthRecord( 0, 0.5, 0, 0, 0 ); + + } + + } ); + + this.selectRoad( road ); + + } + + protected selectRoad ( road: TvRoad ) { + + this.unselectRoad( this.road ); + + this.road = road; + + // skip if the same road is selected + // if ( road.id == this.road.id ) return; + + let controlPoint = this.shapeEditor.currentPoint; + + let node = null; + + AppInspector.setInspector( RoadInspector, { road, controlPoint, node } ); + + this.showRoadControlPoints( road, 'auto' ); + } + + protected unselectRoad ( road: TvRoad ) { + + if ( !road ) return; + + this.road.spline.hide(); + + } + + protected selectRoadControlPoint ( controlPoint?: AnyControlPoint, node?: any ) { + + // skip if the same control point is selected + // if ( this.shapeEditor.currentPoint.id == controlPoint.id ) return; + + let road = this.road; + + AppInspector.setInspector( RoadInspector, { road, controlPoint, node } ); + } + + protected showRoadControlPoints ( road: TvRoad, type: 'auto' | 'explicit' = 'auto' ) { + + road.getPlanView() + + if ( type == 'auto' ) { + + // + + } + + } + + // private addParamPoly3 ( curve: CubicBezierCurve3 ) { + // + // const x = curve.v0.x; + // const y = curve.v0.y; + // + // const hdg = 0; + // const length = curve.getLength(); + // + // const a = new Vector3(); + // const b = curve.v1; + // const c = curve.v2; + // const d = curve.v3; + // + // if ( !this.roadAdded ) { + // + // const roadId = this.openDrive.getRoadCount() + 2; + // + // this.road = this.openDrive.addRoad( '', length, roadId, -1 ); + // + // this.road.addPlanView(); + // + // this.addDefaultLanes( this.road ); + // + // const s = 0; + // + // this.road.planView.addGeometryParamPoly3( 0, x, y, hdg, length, a.x, b.x, c.x, d.x, a.y, b.y, c.y, d.y ); + // // this.road.planView.addGeometryPoly3( 0, x, y, hdg, length, a.y, b.y, c.y, d.y ); + // + // this.roadAdded = true; + // + // } else { + // + // this.road.attr_length = length; + // + // const last = this.road.planView.geometries[ this.road.planView.geometries.length - 1 ] as OdParamPoly3Geometry; + // + // last.x = x; + // last.y = y; + // last.hdg = hdg; + // last.length = length; + // + // last.aU = a.x; + // last.bU = b.x; + // last.cU = c.x; + // last.dU = d.x; + // + // last.aV = a.y; + // last.bV = b.y; + // last.cV = c.y; + // last.dV = d.y; + // + // // this.road.planView.addGeometryLine( s, p1.x, p1.y, hdg, length ); + // } + // + // OdSourceFile.roadNetworkChanged.emit( OdSourceFile.openDrive ); + // + // } + + + protected createRoad () { + + const roadId = this.openDrive.getRoadCount() + 1; + const road = this.openDrive.addRoad( '', 0, roadId, -1 ); + + road.addPlanView(); + + this.addDefaultLanes( road ); + + return road; + } + + private addGeometry ( road: TvRoad, geometry: TvAbstractRoadGeometry ) { + + road.planView.addGeometry( geometry ); + + TvMapSourceFile.roadNetworkChanged.emit( TvMapSourceFile.openDrive ); + } +} diff --git a/src/app/core/tools/base-tool.ts b/src/app/core/tools/base-tool.ts new file mode 100644 index 00000000..023020a1 --- /dev/null +++ b/src/app/core/tools/base-tool.ts @@ -0,0 +1,143 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { IComponent } from 'app/core/game-object'; +import { MonoBehaviour } from '../components/mono-behaviour'; +import { AppInspector } from 'app/core/inspector'; +import { Type } from '@angular/core'; +import { TvMapSourceFile } from '../../modules/tv-map/services/tv-map-source-file'; +import { IEditorState } from './i-editor-state'; +import { Color, Intersection, Mesh, MeshBasicMaterial, Object3D } from 'three'; +import { ObjectTypes } from '../../modules/tv-map/models/tv-common'; +import { AnyControlPoint } from '../../modules/three-js/objects/control-point'; + +export abstract class BaseTool extends MonoBehaviour implements IEditorState { + + abstract name: string; + + // highlighting variables + private previousColor = new Color(); + private previousMaterial: MeshBasicMaterial; + + constructor () { + + super(); + + this.clearInspector(); + + } + + get openDrive () { + + return TvMapSourceFile.openDrive; + + } + + init () { + + } + + enable (): void { + + this.subscribeToEvents(); + + } + + disable (): void { + + this.unsubscribeToEvents(); + + this.removeHighlight(); + + } + + clearInspector () { + + AppInspector.clear(); + + } + + setInspector ( component: Type, data: any ) { + + AppInspector.setInspector( component, data ); + + } + + protected checkRoadIntersection ( intersections: Intersection[], callback: ( object: Object3D ) => void ): void { + + this.checkIntersection( ObjectTypes.LANE, intersections, ( obj ) => { + + callback( obj.parent.parent ); + + } ); + + } + + + protected checkLaneIntersection ( intersections: Intersection[], callback: ( object: Object3D ) => void ) { + + this.checkIntersection( ObjectTypes.LANE, intersections, callback ); + + } + + protected checkVehicleIntersection ( intersections: Intersection[], callback: ( object: Object3D ) => void ) { + + this.checkIntersection( ObjectTypes.VEHICLE, intersections, callback ); + + } + + protected checkControlPointIntersection ( intersections: Intersection[], callback: ( object: AnyControlPoint ) => void ) { + + for ( const i of intersections ) { + + if ( i.object != null && i.object.type == 'Points' ) { + + callback( i.object as AnyControlPoint ); + + break; + } + } + } + + protected checkIntersection ( tag: string, intersections: Intersection[], callback: ( object: Object3D ) => void ): void { + + for ( const i of intersections ) { + + if ( i.object[ 'tag' ] == tag ) { + + callback( i.object ); + + break; + } + + } + + } + + protected highlight ( object: Mesh ) { + + const material = ( object.material as MeshBasicMaterial ); + + // clone because we want a new instance + this.previousColor = material.color.clone(); + + // set the current material property to highlighted color + material.color.add( new Color( 0, 0, 0.2 ) ); + // material.color.addScalar(0.1) + // material.opacity = 0.6; + + // dont clone we want the same instance + this.previousMaterial = material; + } + + protected removeHighlight () { + + if ( this.previousMaterial == null ) return; + + this.previousMaterial.color.copy( this.previousColor ); + + } + +} + diff --git a/src/app/core/tools/i-editor-state.ts b/src/app/core/tools/i-editor-state.ts new file mode 100644 index 00000000..44bf1e23 --- /dev/null +++ b/src/app/core/tools/i-editor-state.ts @@ -0,0 +1,41 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { BaseEventData, PointerEventData, PointerMoveData } from '../../events/pointer-event-data'; + +export interface IEditorState { + + enable (): void; + + disable (): void; + + onPointerClicked ( e: PointerEventData ): void; + + onPointerUp ( e: PointerEventData ): void; + + onPointerDown ( e: PointerEventData ): void; + + onPointerEnter ( e: PointerEventData ): void; + + onPointerExit ( e: PointerEventData ): void; + + onPointerOut ( e: PointerEventData ): void; + + onPointerLeave ( e: PointerEventData ): void; + + onBeginDrag ( e: PointerEventData ): void; + + onEndDrag ( e: PointerEventData ): void; + + onDrag ( e: PointerEventData ): void; + + onDrop ( e: PointerEventData ): void; + + onPointerMoved ( e: PointerMoveData ): void; + + onSelect ( e: BaseEventData ): void; + + onDeSelect ( e: BaseEventData ): void; + +} diff --git a/src/app/core/tools/lane-add-tool.ts b/src/app/core/tools/lane-add-tool.ts new file mode 100644 index 00000000..3f443c30 --- /dev/null +++ b/src/app/core/tools/lane-add-tool.ts @@ -0,0 +1,161 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { BaseTool } from './base-tool'; +import { MouseButton, PointerEventData } from '../../events/pointer-event-data'; +import { TvLane } from '../../modules/tv-map/models/tv-lane'; +import { TvLaneSide } from '../../modules/tv-map/models/tv-common'; +import { KeyboardInput } from '../input'; +import { PickingHelper } from '../services/picking-helper.service'; +import { LineType, OdLaneReferenceLineBuilder } from 'app/modules/tv-map/builders/od-lane-reference-line-builder'; +import { COLOR } from 'app/shared/utils/colors.service'; +import { TvRoad } from 'app/modules/tv-map/models/tv-road.model'; +import { SceneService } from '../services/scene.service'; +import { TvMapBuilder } from 'app/modules/tv-map/builders/od-builder.service'; +import { AppInspector } from '../inspector'; +import { LaneInspectorComponent } from 'app/views/inspectors/lane-type-inspector/lane-inspector.component'; + +export class LaneAddTool extends BaseTool { + + public name: string = 'AddLane'; + + private lane: TvLane; + + private laneHelper = new OdLaneReferenceLineBuilder( null, LineType.SOLID, COLOR.MAGENTA ); + + constructor () { + + super(); + + } + + enable (): void { + + super.enable(); + + } + + disable (): void { + + super.disable(); + + if ( this.laneHelper ) this.laneHelper.clear(); + } + + public onPointerDown ( e: PointerEventData ) { + + if ( e.button == MouseButton.RIGHT || e.button == MouseButton.MIDDLE ) return; + + const shiftKeyDown = KeyboardInput.isShiftKeyDown; + + let hasInteracted = false; + + if ( shiftKeyDown && !hasInteracted ) hasInteracted = this.checkReferenceLineInteraction( e ); + + if ( !hasInteracted ) hasInteracted = this.checkLaneObjectInteraction( e ); + + } + + private checkLaneObjectInteraction ( e: PointerEventData ): boolean { + + const newLane = PickingHelper.checkLaneObjectInteraction( e ); + + if ( this.lane && newLane == null ) { + + // clear + + this.laneHelper.clear(); + + this.lane = null; + + AppInspector.clear(); + + } else if ( this.lane && newLane && this.lane.gameObject.id != newLane.gameObject.id ) { + + // clear and select new + + this.laneHelper.clear(); + + this.lane = newLane; + + const newRoad = this.openDrive.getRoadById( newLane.roadId ); + + this.laneHelper.drawRoad( newRoad, LineType.SOLID ); + + AppInspector.setInspector( LaneInspectorComponent, newLane ); + + } else if ( !this.lane && newLane ) { + + // select new + + this.lane = newLane; + + const newRoad = this.openDrive.getRoadById( newLane.roadId ); + + this.laneHelper.drawRoad( newRoad, LineType.SOLID ); + + AppInspector.setInspector( LaneInspectorComponent, newLane ); + + } else if ( !this.lane && newLane == null ) { + + // clear + + AppInspector.clear(); + + } + + return newLane != null; + } + + private checkReferenceLineInteraction ( e: PointerEventData ) { + + let hasInteracted = false; + + for ( let i = 0; i < e.intersections.length; i++ ) { + + const intersection = e.intersections[ i ]; + + if ( e.button === MouseButton.LEFT && intersection.object && intersection.object[ 'tag' ] == this.laneHelper.tag ) { + + hasInteracted = true; + + if ( intersection.object.userData.lane ) { + + this.cloneLane( intersection.object.userData.lane as TvLane ); + + } + + break; + } + } + + return hasInteracted; + } + + private cloneLane ( lane: TvLane ): void { + + const road = this.openDrive.getRoadById( lane.roadId ); + + const laneSection = road.getLaneSectionById( lane.laneSectionId ); + + const newLaneId = lane.side === TvLaneSide.LEFT ? lane.id + 1 : lane.id - 1; + + const newLane = lane.clone( newLaneId ); + + laneSection.addLaneInstance( newLane, true ); + + this.rebuild( road ); + } + + private rebuild ( road: TvRoad ): void { + + if ( !road ) return; + + SceneService.removeWithChildren( road.gameObject, true ); + + TvMapBuilder.buildRoad( this.openDrive.gameObject, road ); + + this.laneHelper.redraw( LineType.SOLID ); + } +} diff --git a/src/app/core/tools/lane-direction-tool.ts b/src/app/core/tools/lane-direction-tool.ts new file mode 100644 index 00000000..d96e1919 --- /dev/null +++ b/src/app/core/tools/lane-direction-tool.ts @@ -0,0 +1,70 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { BaseTool } from './base-tool'; +import { PointerEventData } from '../../events/pointer-event-data'; +import { Mesh, Object3D } from 'three'; +import { TvLane } from '../../modules/tv-map/models/tv-lane'; +import { OdLaneDirectionBuilder } from '../../modules/tv-map/builders/od-lane-direction-builder'; + +export class LaneDirectionTool extends BaseTool { + + name: string = 'LaneDirection'; + + private laneDirectionHelper: OdLaneDirectionBuilder; + + constructor () { + + super(); + + } + + init () { + + super.init(); + + this.laneDirectionHelper = new OdLaneDirectionBuilder( null ); + } + + + disable (): void { + + super.disable(); + + this.laneDirectionHelper.clear(); + } + + onPointerDown ( e: PointerEventData ) { + + super.onPointerDown( e ); + + let laneFound = false; + + this.checkLaneIntersection( e.intersections, ( object: Object3D ) => { + + laneFound = true; + + this.selectLane( object as Mesh ); + + } ); + + if ( !laneFound ) { + + this.laneDirectionHelper.clear(); + this.clearInspector(); + } + } + + private selectLane ( object: Mesh ) { + + let lane = (object.userData.lane as TvLane); + + this.laneDirectionHelper.setRoad( this.openDrive.getRoadById( lane.roadId ) ); + + this.laneDirectionHelper.create(); + + // AppInspector.setInspector( LaneTypeInspectorComponent, data ); + } + +} diff --git a/src/app/core/tools/lane-marking-tool.spec.ts b/src/app/core/tools/lane-marking-tool.spec.ts new file mode 100644 index 00000000..f48e8c04 --- /dev/null +++ b/src/app/core/tools/lane-marking-tool.spec.ts @@ -0,0 +1,139 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { LaneMarkingTool } from './lane-marking-tool'; +import { PointerEventData, MouseButton } from 'app/events/pointer-event-data'; +import { Vector3 } from 'three'; +import { TvMap } from 'app/modules/tv-map/models/tv-map.model'; +import { OdRoadBuilder } from 'app/modules/tv-map/builders/od-road-builder'; +import { TvMapBuilder } from 'app/modules/tv-map/builders/od-builder.service'; +import { ViewportComponent } from 'app/modules/three-js/viewport/viewport.component'; +import { ThemeService } from 'app/shared/services/theme.service'; +import { EventSystem } from 'app/events/event-system.service'; +import { InputService } from '../services/input.service'; +import { ThreeService } from 'app/modules/three-js/three.service'; +import { TvRoad } from 'app/modules/tv-map/models/tv-road.model'; +import { TvMapSourceFile } from 'app/modules/tv-map/services/tv-map-source-file'; +import { AppInspector } from '../inspector'; + + +fdescribe( 'LaneMarkingTool Test', () => { + + let tool: LaneMarkingTool; + let map: TvMap; + let road: TvRoad; + + beforeEach( () => { + + tool = new LaneMarkingTool(); + + map = TvMapSourceFile.openDrive = new TvMap(); + + road = map.addDefaultRoad(); + + road.addGeometryLine( 0, 0, 0, 0, 100 ); + + TvMapBuilder.buildMap( map ); + + } ); + + it( 'should do nothing on right-click', () => { + + tool.onPointerDown( PointerEventData.new( MouseButton.RIGHT, new Vector3() ) ); + + expect( tool.lane ).toBeUndefined(); + expect( tool.controlPoint ).toBeUndefined(); + expect( tool.node ).toBeUndefined(); + expect( tool.roadMark ).toBeUndefined(); + + expect( tool.pointerDown ).toBeTruthy(); + expect( tool.pointerDownAt ).toBeUndefined(); + + } ); + + it( 'should set right tool variables on left-click', () => { + + tool.onPointerDown( PointerEventData.new( MouseButton.LEFT, new Vector3() ) ); + + expect( tool.lane ).toBeNull(); + expect( tool.controlPoint ).toBeNull(); + expect( tool.node ).toBeNull(); + + expect( tool.pointerDown ).toBeTruthy(); + expect( tool.pointerDownAt ).toBeDefined(); + + } ); + + it( 'should select lane & highlight reference lines', () => { + + const selectedLane = road.getLaneSectionAt( 0 ).getLeftLanes()[ 0 ]; + + const intersection = { + distance: 1, + distanceToRay: 1, + point: new Vector3(), + object: selectedLane.gameObject, + } + + tool.onPointerDown( PointerEventData.new( MouseButton.LEFT, new Vector3(), [ intersection ] ) ); + + expect( tool.lane ).toBeDefined(); + expect( tool.lane.id ).toEqual( selectedLane.id ); + + expect( tool.controlPoint ).toBeUndefined(); + expect( tool.node ).toBeUndefined(); + + expect( tool.pointerDown ).toBeTruthy(); + expect( tool.pointerDownAt ).toBeDefined(); + + road.getLaneSectionAt( 0 ).lanes.forEach( lane => { + + // now lets check if the lane lines are visible on all the lanes + expect( lane.startLine ).toBeDefined(); + expect( lane.startLine.visible ).toBeTruthy(); + expect( lane.startLine.isSelected ).toBeFalsy(); + + } ); + + expect( AppInspector.currentInspector ).toBeNull(); + + } ); + + it( 'should select lane & highlight reference lines', () => { + + const selectedLane = road.getLaneSectionAt( 0 ).getLeftLanes()[ 0 ]; + + const laneIntersection = { + distance: 1, + distanceToRay: 1, + point: new Vector3(), + object: selectedLane.gameObject, + } + + tool.onPointerDown( PointerEventData.new( MouseButton.LEFT, new Vector3(), [ laneIntersection ] ) ); + + expect( tool.lane ).toBeDefined(); + expect( tool.lane.id ).toEqual( selectedLane.id ); + + // at this point lane is selected and reference lines should be visible + + const lineIntersection = { + distance: 1, + distanceToRay: 1, + point: new Vector3(), + object: selectedLane.startLine, + } + + tool.onPointerDown( PointerEventData.new( MouseButton.LEFT, new Vector3(), [ lineIntersection ] ) ); + + // at this point line lane line should be selected + + expect( tool.lane.startLine.isSelected ).toBe( true ); + + expect( tool.roadMark ).toBeDefined(); + + + } ); + +} ); \ No newline at end of file diff --git a/src/app/core/tools/lane-marking-tool.ts b/src/app/core/tools/lane-marking-tool.ts new file mode 100644 index 00000000..7b61c406 --- /dev/null +++ b/src/app/core/tools/lane-marking-tool.ts @@ -0,0 +1,383 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { BaseTool } from './base-tool'; +import { Vector3 } from 'three'; +import { MouseButton, PointerEventData } from '../../events/pointer-event-data'; +import { TvLane } from '../../modules/tv-map/models/tv-lane'; +import { LaneRoadmarkInspectorComponent } from '../../views/inspectors/lane-roadmark-inspector/lane-roadmark-inspector.component'; +import { TvLaneRoadMark } from '../../modules/tv-map/models/tv-lane-road-mark'; +import { TvPosTheta } from '../../modules/tv-map/models/tv-pos-theta'; +import { KeyboardInput } from '../input'; +import { OdRoadMarkBuilder } from '../../modules/tv-map/builders/od-road-mark-builder'; +import { AnyControlPoint, LaneRoadMarkNode } from '../../modules/three-js/objects/control-point'; +import { OdLaneReferenceLineBuilder } from '../../modules/tv-map/builders/od-lane-reference-line-builder'; +import { TvMapQueries } from '../../modules/tv-map/queries/tv-map-queries'; +import { PickingHelper } from '../services/picking-helper.service'; +import { LaneInspectorComponent } from 'app/views/inspectors/lane-type-inspector/lane-inspector.component'; +import { SceneService } from '../services/scene.service'; +import { NodeFactoryService } from '../factories/node-factory.service'; +import { TvRoad } from 'app/modules/tv-map/models/tv-road.model'; +import { SetValueCommand } from 'app/modules/three-js/commands/set-value-command'; +import { ShowLaneMarkingCommand } from '../commands/show-lane-marking-command'; +import { CommandHistory } from 'app/services/command-history'; +import { MultiCmdsCommand } from '../commands/multi-cmds-command'; +import { SetInspectorCommand } from '../commands/set-inspector-command'; +import { AddRoadmarkNodeCommand } from '../commands/add-roadmark-node'; +import { UpdateRoadmarkNodeCommand } from '../commands/update-roadmark-node'; + +export class LaneMarkingTool extends BaseTool { + + public name: string = 'LaneMarking'; + + public pointerDown: boolean; + + public pointerDownAt: Vector3; + + public pointerObject: any; + + public markingDistanceChanged: boolean; + + private lane: TvLane; + + private roadMark: TvLaneRoadMark; + + private controlPoint: AnyControlPoint; + + private node: LaneRoadMarkNode; + + private roadMarkBuilder = new OdRoadMarkBuilder(); + + private laneHelper = new OdLaneReferenceLineBuilder( null ); + + constructor () { + + super(); + + } + + init () { + + super.init(); + + } + + enable (): void { + + super.enable(); + + } + + disable (): void { + + super.disable(); + + if ( this.laneHelper ) this.laneHelper.clear(); + + this.openDrive.roads.forEach( road => this.hideNodes( road ) ); + } + + public onPointerDown ( e: PointerEventData ) { + + this.pointerDown = true; + + if ( e.button === MouseButton.RIGHT || e.button === MouseButton.MIDDLE ) return; + + this.pointerDownAt = e.point ? e.point.clone() : null; + + const shiftKeyDown = KeyboardInput.isShiftKeyDown; + + let hasInteracted = false; + + if ( !shiftKeyDown && !hasInteracted ) hasInteracted = this.checkNodePointInteraction( e ); + + if ( !hasInteracted ) hasInteracted = this.checkReferenceLineInteraction( e ); + + if ( !hasInteracted ) hasInteracted = this.checkLaneObjectInteraction( e ); + + if ( !hasInteracted ) { + + this.node = null; + + this.lane = null; + + this.controlPoint = null; + + } + } + + public onPointerUp ( e: PointerEventData ) { + + if ( this.markingDistanceChanged && this.node ) { + + // e.point is null for some reason, so use node position + // const newPosition = e.point.clone(); + + // use the current positino of the node as the new position + const newPosition = this.node.point.position.clone(); + + // old position is the location where the mouse was last down + const oldPosition = this.pointerDownAt.clone(); + + const command = new UpdateRoadmarkNodeCommand( this.node, newPosition, oldPosition, this.roadMarkBuilder ); + + CommandHistory.execute( command ); + + } + + this.pointerDown = false; + + this.pointerDownAt = null; + + this.pointerObject = null; + + this.markingDistanceChanged = false; + } + + public onPointerMoved ( e: PointerEventData ) { + + if ( this.pointerDown && this.node && this.node.point.isSelected ) { + + this.markingDistanceChanged = true; + + NodeFactoryService.updateRoadMarkNodeByPosition( this.node, e.point ); + + } + } + + private checkNodePointInteraction ( e: PointerEventData ): boolean { + + const maxDistance = Math.max( 0.5, e.approxCameraDistance * 0.01 ); + + // first chceck for control point interactions + // doing in 2 loop to prioritise control points + const controlPoint = PickingHelper.checkControlPointInteraction( e, LaneRoadMarkNode.pointTag, maxDistance ); + + if ( controlPoint ) { + + const node = controlPoint.parent as LaneRoadMarkNode; + + const roadMark = node.roadmark; + + CommandHistory.executeAll( [ + + new SetValueCommand( this, 'controlPoint', controlPoint ), + + new SetValueCommand( this, 'node', node ), + + new SetInspectorCommand( LaneRoadmarkInspectorComponent, roadMark ) + + ] ); + + } else if ( !this.controlPoint && this.controlPoint ) { + + CommandHistory.executeAll( [ + + new SetValueCommand( this, 'controlPoint', null ), + + new SetValueCommand( this, 'node', null ), + + new SetInspectorCommand( null, null ) + + ] ); + + } + + return controlPoint != null; + + } + + private checkLaneObjectInteraction ( e: PointerEventData ): boolean { + + const newLane = PickingHelper.checkLaneObjectInteraction( e ); + + if ( this.lane && newLane == null ) { + + // clear + + const commands = []; + + commands.push( new ShowLaneMarkingCommand( null, this.lane, this.laneHelper ) ); + + commands.push( new SetInspectorCommand( null, null ) ); + + commands.push( new SetValueCommand( this, 'lane', null ) ); + + CommandHistory.execute( new MultiCmdsCommand( commands ) ); + + } else if ( this.lane && newLane && this.lane.gameObject.id !== newLane.gameObject.id && this.lane.roadId != newLane.roadId ) { + + // clear and select new + + const commands = []; + + commands.push( new ShowLaneMarkingCommand( newLane, this.lane, this.laneHelper ) ); + + commands.push( new SetInspectorCommand( LaneInspectorComponent, newLane ) ); + + commands.push( new SetValueCommand( this, 'lane', newLane ) ); + + CommandHistory.execute( new MultiCmdsCommand( commands ) ); + + } else if ( !this.lane && newLane ) { + + // select new + + const commands = []; + + commands.push( new SetValueCommand( this, 'lane', newLane ) ); + + commands.push( new ShowLaneMarkingCommand( newLane, this.lane, this.laneHelper ) ); + + commands.push( new SetInspectorCommand( LaneInspectorComponent, newLane ) ); + + CommandHistory.execute( new MultiCmdsCommand( commands ) ); + + } else if ( !this.lane && newLane == null ) { + + // clear + + CommandHistory.execute( new SetInspectorCommand( null, null ) ); + + } + + return newLane != null; + } + + private checkReferenceLineInteraction ( e: PointerEventData ) { + + const isShiftKeyDown = KeyboardInput.isShiftKeyDown; + + let hasInteracted = false; + + for ( let i = 0; i < e.intersections.length; i++ ) { + + const intersection = e.intersections[ i ]; + + if ( e.button === MouseButton.LEFT && intersection.object && intersection.object[ 'tag' ] == this.laneHelper.tag ) { + + hasInteracted = true; + + if ( intersection.object.userData.lane ) { + + this.lane = intersection.object.userData.lane; + + if ( isShiftKeyDown ) { + + this.addRoadmarkNodeAt( e.point ); + + } else { + + this.selectRoadMarkAt( e.point, this.lane ); + + } + + } + + break; + } + } + + return hasInteracted; + } + + private addRoadmarkNodeAt ( position: Vector3 ) { + + if ( !this.lane ) return; + + const posTheta = new TvPosTheta(); + + // getting position on track in s/t coordinates + TvMapQueries.getRoadByCoords( position.x, position.y, posTheta ); + + // get the exisiting lane road mark at s and clone it + const roadMark = this.lane.getRoadMarkAt( posTheta.s ).clone( posTheta.s ); + + roadMark.node = NodeFactoryService.createRoadMarkNode( this.lane, roadMark ); + + CommandHistory.executeAll( [ + + new AddRoadmarkNodeCommand( this.lane, roadMark, this.roadMarkBuilder ), + + new SetValueCommand( this, 'roadMark', roadMark ), + + new SetInspectorCommand( LaneRoadmarkInspectorComponent, roadMark ), + + ] ) + + } + + private selectRoadMarkAt ( position: Vector3, lane: TvLane ) { + + const posTheta = new TvPosTheta(); + + TvMapQueries.getRoadByCoords( position.x, position.y, posTheta ); + + const roadMark = lane.getRoadMarkAt( posTheta.s ); + + CommandHistory.executeAll( [ + + new SetValueCommand( this, 'roadMark', roadMark ), + + new SetInspectorCommand( LaneRoadmarkInspectorComponent, roadMark ), + + ] ); + + } + + private rebuild ( roadId: number ) { + + // TODO may only road->lane need to be built + + const road = this.openDrive.getRoadById( roadId ); + + this.roadMarkBuilder.buildRoad( road ); + } + + private showNodes ( road: TvRoad ) { + + road.laneSections.forEach( laneSection => { + + laneSection.lanes.forEach( lane => { + + lane.getRoadMarks().forEach( roadmark => { + + if ( roadmark.node ) { + + roadmark.node.visible = true; + + } else { + + roadmark.node = NodeFactoryService.createRoadMarkNode( lane, roadmark ); + + SceneService.add( roadmark.node ); + + } + + } ) + + } ) + + } ); + + } + + private hideNodes ( road: TvRoad ) { + + road.laneSections.forEach( laneSection => { + + laneSection.lanes.forEach( lane => { + + lane.getRoadMarks().forEach( roadmark => { + + if ( roadmark.node ) roadmark.node.visible = false; + + } ); + + } ); + + } ); + + } +} diff --git a/src/app/core/tools/lane-offset-tool.ts b/src/app/core/tools/lane-offset-tool.ts new file mode 100644 index 00000000..a03e4254 --- /dev/null +++ b/src/app/core/tools/lane-offset-tool.ts @@ -0,0 +1,542 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { BaseTool } from './base-tool'; +import { MouseButton, PointerEventData } from '../../events/pointer-event-data'; +import { Object3D, Vector3 } from 'three'; +import { TvLane } from '../../modules/tv-map/models/tv-lane'; +import { TvRoad } from 'app/modules/tv-map/models/tv-road.model'; +import { AnyControlPoint, LaneOffsetNode, LaneWidthNode } from 'app/modules/three-js/objects/control-point'; +import { SceneService } from '../services/scene.service'; +import { Subscription } from 'rxjs'; +import { KeyboardInput } from '../input'; +import { NodeFactoryService } from '../factories/node-factory.service'; +import { TvMapBuilder } from 'app/modules/tv-map/builders/od-builder.service'; +import { AppInspector } from '../inspector'; +import { TvPosTheta } from 'app/modules/tv-map/models/tv-pos-theta'; +import { LineType, OdLaneReferenceLineBuilder } from 'app/modules/tv-map/builders/od-lane-reference-line-builder'; +import { PickingHelper } from '../services/picking-helper.service'; +import { COLOR } from 'app/shared/utils/colors.service'; +import { LaneOffsetInspector, LaneOffsetInspectorData } from 'app/views/inspectors/lane-offset-inspector/lane-offset-inspector.component'; +import { TvMapQueries } from '../../modules/tv-map/queries/tv-map-queries'; +import { UpdateLaneOffsetDistanceCommand } from '../commands/update-lane-offset-distance-command'; +import { CommandHistory } from 'app/services/command-history'; +import { UpdateLaneOffsetValueCommand } from '../commands/update-lane-offset-value-command'; +import { AddLaneOffsetCommand } from '../commands/add-lane-offset-command'; +import { SetInspectorCommand } from '../commands/set-inspector-command'; +import { SetValueCommand } from 'app/modules/three-js/commands/set-value-command'; + +export class LaneOffsetTool extends BaseTool { + + public name: string = 'LaneOffset'; + + private distanceSub: Subscription; + private offsetSub: Subscription; + + private laneWidthChanged: boolean = false; + private pointerDown: boolean = false; + private pointerObject: Object3D; + + private lane: TvLane; + private controlPoint: AnyControlPoint; + private node: LaneOffsetNode; + + private laneHelper = new OdLaneReferenceLineBuilder( null, LineType.SOLID, COLOR.MAGENTA ); + + constructor () { + + super(); + + } + + init () { + + + } + + enable () { + + super.enable(); + + // this.distanceSub = LaneOffsetInspector.distanceChanged.subscribe( distance => { + + // const road = this.openDrive.getRoadById( data.node.roadId ); + + // road.updateLaneOffsetValues(); + + // NodeFactoryService.updateLaneOffsetNode( data.node ); + + // this.rebuild( road ); + + // } ); + + this.distanceSub = LaneOffsetInspector.distanceChanged.subscribe( distance => { + + CommandHistory.execute( new UpdateLaneOffsetDistanceCommand( this.node, distance, null, this.laneHelper ) ); + + } ); + + + this.offsetSub = LaneOffsetInspector.offsetChanged.subscribe( offset => { + + CommandHistory.execute( new UpdateLaneOffsetValueCommand( this.node, offset, null, this.laneHelper ) ); + + } ); + + } + + disable () { + + super.disable(); + + if ( this.laneHelper ) this.laneHelper.clear(); + + this.distanceSub.unsubscribe(); + + this.openDrive.roads.forEach( road => this.hideNodes( road ) ); + } + + public onPointerDown ( e: PointerEventData ) { + + if ( e.button == MouseButton.RIGHT || e.button == MouseButton.MIDDLE ) return; + + this.pointerDown = true; + + const shiftKeyDown = KeyboardInput.isShiftKeyDown; + + let hasInteracted = false; + + // check for control point interactions first + if ( !shiftKeyDown && !hasInteracted ) hasInteracted = this.checkNodePointInteraction( e ); + + // // check for line segment interactions + // if ( !shiftKeyDown && !hasInteracted ) hasInteracted = this.checkNodeLineInteraction( e ); + + // check for lane game object interactions + if ( !shiftKeyDown && !hasInteracted ) hasInteracted = this.checkLaneObjectInteraction( e ); + + // if ( !hasInteracted ) hasInteracted = this.checkReferenceLineInteraction( e ); + + // if ( hasInteracted ) { + + // if ( this.controlPoint ) { + + // AppInspector.setInspector( LaneWidthInspectorComponent, { + + // node: this.controlPoint.parent as LaneWidthNode + + // } ); + + // } + + // } else { + + // // no interaction with line, lane, points etc + // if ( this.lane ) { + + // this.hideNodes( this.openDrive.getRoadById( this.lane.roadId ) ); + + // this.laneHelper.clear(); + + // this.lane = null; + + // } + + // } + + } + + public onPointerClicked ( e: PointerEventData ) { + + if ( e.button === MouseButton.LEFT && KeyboardInput.isShiftKeyDown && e.point != null ) { + + this.addNode( e.point ); + + } + } + + public onPointerUp () { + + // if ( this.laneWidthChanged && this.lane ) { + + // const newPosition = new TvPosTheta(); + + // const road = TvMapQueries.getRoadByCoords( this.node.point.position.x, this.node.point.position.y, newPosition ); + + // // new road should be same + // if ( road.id === this.node.road.id ) { + + // const command = new UpdateLaneOffsetDistanceCommand( this.node, newPosition.s, null, this.laneHelper ); + + // CommandHistory.execute( command ); + + // } + + // } + + this.pointerDown = false; + + this.pointerObject = null; + + this.laneWidthChanged = false; + } + + public onPointerMoved ( e: PointerEventData ) { + + if ( this.pointerDown && this.node ) { + + this.laneWidthChanged = true; + + const newPosition = new TvPosTheta(); + + const road = TvMapQueries.getRoadByCoords( e.point.x, e.point.y, newPosition ); + + // new road should be same + if ( road.id === this.node.road.id ) { + + const command = ( new UpdateLaneOffsetDistanceCommand( this.node, newPosition.s, null, this.laneHelper ) ); + + command.execute(); + + } + + } + + + // if ( this.pointerDown && this.pointerObject && this.pointerObject[ 'tag' ] == LaneWidthNode.pointTag ) { + + // this.laneWidthChanged = true; + + // NodeFactoryService.updateLaneWidthNode( this.pointerObject.parent as LaneWidthNode, e.point ); + + // this.updateLaneWidth( this.pointerObject.parent as LaneWidthNode ); + + // if ( this.lane ) this.laneHelper.redraw( LineType.DASHED ); + + // } else if ( this.pointerDown && this.pointerObject && this.pointerObject[ 'tag' ] == LaneWidthNode.lineTag ) { + + // this.laneWidthChanged = true; + + // NodeFactoryService.updateLaneWidthNode( this.pointerObject.parent as LaneWidthNode, e.point ); + + // this.updateLaneWidth( this.pointerObject.parent as LaneWidthNode ); + + // if ( this.lane ) this.laneHelper.redraw( LineType.DASHED ); + + // } + } + + private checkNodePointInteraction ( e: PointerEventData ): boolean { + + // // first chceck for control point interactions + // // doing in 2 loop to prioritise control points + const controlPoint = PickingHelper.checkControlPointInteraction( e, LaneOffsetNode.pointTag, 1.0 ); + + if ( controlPoint ) { + + const node = controlPoint.parent as LaneOffsetNode; + + const road = this.openDrive.getRoadById( node.roadId ); + + CommandHistory.executeMany( + + new SetValueCommand( this, 'controlPoint', controlPoint ), + + new SetValueCommand( this, 'node', node ), + + new SetInspectorCommand( LaneOffsetInspector, new LaneOffsetInspectorData( node, road ) ), + + ); + + } else if ( this.controlPoint ) { + + CommandHistory.executeMany( + + new SetValueCommand( this, 'controlPoint', null ), + + new SetValueCommand( this, 'node', null ), + + new SetInspectorCommand( null, null ), + + ); + + } + + // 4 scenarios + // old and new both present then check is new or not + // old and new is null + // old is null and new + // old is null and new is null + + // if ( this.controlPoint && controlPoint && this.controlPoint.id != controlPoint.id ) { + + // this.controlPoint.unselect(); + + // this.controlPoint = controlPoint; + + // this.controlPoint.select(); + + // AppInspector.setInspector( LaneOffsetInspector, new LaneOffsetInspectorData( this.controlPoint.parent as LaneOffsetNode ) ); + + // } else if ( this.controlPoint && !controlPoint ) { + + // this.controlPoint.unselect(); + + // this.controlPoint = null; + + // AppInspector.clear(); + + // } else if ( !this.controlPoint && controlPoint ) { + + // this.controlPoint = controlPoint; + + // this.controlPoint.select(); + + // AppInspector.setInspector( LaneOffsetInspector, new LaneOffsetInspectorData( this.controlPoint.parent as LaneOffsetNode ) ); + + // } else if ( !this.controlPoint && !controlPoint ) { + + // AppInspector.clear(); + + // } + + return controlPoint != null; + } + + private checkNodeLineInteraction ( e: PointerEventData ) { + + // let hasInteracted = false; + + // // check for line segment interactions + // for ( let i = 0; i < e.intersections.length; i++ ) { + + // const intersection = e.intersections[ i ]; + + // if ( e.button === MouseButton.LEFT && intersection.object && intersection.object[ 'tag' ] == 'width-line' ) { + + // hasInteracted = true; + + // // const road = intersection.object.userData.road; + + // // // check if old or a new road is selected + // // if ( !this.road || this.road.id != road.id ) { + + // // this.road = road; + + // // this.road.spline.show(); + + // // } + + // break; + // } + // } + + // return hasInteracted; + } + + private checkLaneObjectInteraction ( e: PointerEventData ): boolean { + + const newLane = PickingHelper.checkLaneObjectInteraction( e ); + + if ( newLane ) { + + const road = this.openDrive.getRoadById( newLane.roadId ); + + CommandHistory.executeMany( + + new SetValueCommand( this, 'lane', newLane ), + + new SetInspectorCommand( LaneOffsetInspector, new LaneOffsetInspectorData( null, road ) ) + + ); + + } else if ( this.lane ) { + + CommandHistory.executeMany( + + new SetValueCommand( this, 'lane', null ), + + new SetInspectorCommand( null, null ), + + ); + + } + + // lane exists new not found -> 1 clear + // lane exists new found -> 2 change + // lane does not exist new found -> set new + // lane does not exist and new not found => nothing + + // if ( this.lane && newLane == null ) { + + // this.hideNodes( this.openDrive.getRoadById( this.lane.roadId ) ); + + // this.laneHelper.clear(); + + // this.lane = null; + + // } else if ( this.lane && newLane && this.lane.roadId != newLane.roadId ) { + + // this.hideNodes( this.openDrive.getRoadById( this.lane.roadId ) ); + + // this.laneHelper.clear(); + + // this.lane = newLane; + + // const newRoad = this.openDrive.getRoadById( newLane.roadId ); + + // this.showNodes( newRoad ); + + // this.laneHelper.drawRoad( newRoad, LineType.SOLID ); + + // } else if ( !this.lane && newLane ) { + + // this.lane = newLane; + + // const newRoad = this.openDrive.getRoadById( newLane.roadId ); + + // this.showNodes( newRoad ); + + // this.laneHelper.drawRoad( newRoad, LineType.SOLID ); + + + // } else if ( !this.lane && newLane == null ) { + + // // do nothing + + // } + + return newLane != null; + } + + private checkReferenceLineInteraction ( e: PointerEventData ) { + + // let hasInteracted = false; + + // this.checkIntersection( this.laneHelper.tag, e.intersections, ( obj ) => { + + // hasInteracted = true; + + // this.laneHelper.onLineSelected( obj as Line ); + + // } ); + + // return hasInteracted; + } + + private addNode ( position: Vector3 ): void { + + if ( !this.lane ) return; + + const road = this.openDrive.getRoadById( this.lane.roadId ); + + const posTheta = new TvPosTheta(); + + // getting position on track in s/t coordinates + TvMapQueries.getRoadByCoords( + position.x, + position.y, + posTheta + ); + + const laneOffset = road.getLaneOffsetAt( posTheta.s ).clone( posTheta.s ); + + const node = NodeFactoryService.createLaneOffsetNode( road, laneOffset ); + + CommandHistory.executeMany( + + new AddLaneOffsetCommand( node ), + + new SetInspectorCommand( LaneOffsetInspector, new LaneOffsetInspectorData( node, road ) ), + + ); + + // // getting position on track in s/t coordinates + // TvMapQueries.getRoadByCoords( + // position.x, + // position.y, + // posTheta + // ); + + // // // get the exisiting lane Offset at s + // // // and clone the lane Offset + // const newLaneOffset = road.getLaneOffsetAt( posTheta.s ).clone( posTheta.s ); + + // // // add the with back to lane to + // // this.lane.addOffsetRecordInstance( newLaneOffset ); + // road.addLaneOffsetInstance( newLaneOffset ); + + // // // make mesh for the lane Offset node + // // const laneOffsetNode = newLaneOffset.mesh = NodeFactoryService.createLaneOffsetNode( + // // this.lane.roadId, this.lane.id, newLaneOffset.s, newLaneOffset + // // ); + // const node = newLaneOffset.mesh = NodeFactoryService.createLaneOffsetNode( road, newLaneOffset ); + + // // // add it to scene to enable rendering + // // SceneService.add( newLaneOffset.mesh ); + // SceneService.add( node ); + + // // set the node in inpsector + // AppInspector.setInspector( LaneOffsetInspector, new LaneOffsetInspectorData( node, road ) ); + + // this.rebuild( road ); + + } + + private updateLaneWidth ( node: LaneWidthNode ) { + + // if ( !node ) return; + + // if ( !this.lane ) return; + + // const road = this.openDrive.getRoadById( node.roadId ); + + // road.getLaneSectionAt( node.s ).updateLaneWidthValues( this.lane ); + } + + private hideNodes ( road: TvRoad ): void { + + road.getLaneOffsets().forEach( laneOffset => { + + if ( laneOffset.mesh ) { + + laneOffset.mesh.visible = false; + + } + + } ); + + } + + private showNodes ( road: TvRoad ) { + + road.getLaneOffsets().forEach( laneOffset => { + + if ( laneOffset.mesh ) { + + laneOffset.mesh.visible = true; + + } else { + + laneOffset.mesh = NodeFactoryService.createLaneOffsetNode( road, laneOffset ); + + SceneService.add( laneOffset.mesh ); + + } + + } ); + + } + + private rebuild ( road: TvRoad ): void { + + if ( !this.lane ) return; + + SceneService.removeWithChildren( road.gameObject, true ); + + TvMapBuilder.buildRoad( this.openDrive.gameObject, road ); + + this.laneHelper.redraw( LineType.SOLID ); + } +} diff --git a/src/app/core/tools/lane-tool.ts b/src/app/core/tools/lane-tool.ts new file mode 100644 index 00000000..4481e700 --- /dev/null +++ b/src/app/core/tools/lane-tool.ts @@ -0,0 +1,73 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { PointerEventData } from '../../events/pointer-event-data'; +import { Mesh, Object3D } from 'three'; +import { TvLane } from '../../modules/tv-map/models/tv-lane'; +import { BaseTool } from './base-tool'; +import { AppInspector } from '../inspector'; +import { LaneInspectorComponent } from '../../views/inspectors/lane-type-inspector/lane-inspector.component'; +import { OdLaneDirectionBuilder } from '../../modules/tv-map/builders/od-lane-direction-builder'; + +export class LaneTool extends BaseTool { + + name: string = 'LaneTool'; + + init () { + + super.init(); + + this.laneDirectionHelper = new OdLaneDirectionBuilder( null ); + } + + disable (): void { + + super.disable(); + + this.laneDirectionHelper.clear(); + } + + private laneDirectionHelper: OdLaneDirectionBuilder; + + onPointerDown ( e: PointerEventData ) { + + super.onPointerDown( e ); + + let laneFound = false; + + this.checkLaneIntersection( e.intersections, ( object: Object3D ) => { + + laneFound = true; + + // this.removeHighlight(); + + // this.highlight( object as Mesh ); + + this.selectLane( object as Mesh ); + + } ); + + if ( !laneFound ) { + + // this.removeHighlight(); + + this.clearInspector(); + } + } + + private selectLane ( object: Mesh ) { + + let lane = ( object.userData.lane as TvLane ); + + if ( lane == null ) return; + + AppInspector.setInspector( LaneInspectorComponent, lane ); + + this.laneDirectionHelper.clear(); + + const road = this.openDrive.getRoadById( lane.roadId ); + + this.laneDirectionHelper.drawSingleLane( road, lane ); + } +} diff --git a/src/app/core/tools/lane-width-tool.ts b/src/app/core/tools/lane-width-tool.ts new file mode 100644 index 00000000..c4b00477 --- /dev/null +++ b/src/app/core/tools/lane-width-tool.ts @@ -0,0 +1,359 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { BaseTool } from './base-tool'; +import { MouseButton, PointerEventData } from '../../events/pointer-event-data'; +import { Vector3 } from 'three'; +import { TvLane } from '../../modules/tv-map/models/tv-lane'; +import { TvRoad } from 'app/modules/tv-map/models/tv-road.model'; +import { AnyControlPoint, LaneWidthNode } from 'app/modules/three-js/objects/control-point'; +import { SceneService } from '../services/scene.service'; +import { Subscription } from 'rxjs'; +import { KeyboardInput } from '../input'; +import { ObjectTypes } from 'app/modules/tv-map/models/tv-common'; +import { NodeFactoryService } from '../factories/node-factory.service'; +import { LaneWidthInspector } from 'app/views/inspectors/lane-width-inspector/lane-width-inspector.component'; +import { LineType, OdLaneReferenceLineBuilder } from 'app/modules/tv-map/builders/od-lane-reference-line-builder'; +import { PickingHelper } from '../services/picking-helper.service'; +import { CommandHistory } from 'app/services/command-history'; +import { SetValueCommand } from 'app/modules/three-js/commands/set-value-command'; +import { SetInspectorCommand } from '../commands/set-inspector-command'; +import { UpdateWidthNodePositionCommand } from '../commands/update-width-node-position-command'; +import { AddWidthNodeCommand } from '../commands/add-width-node-command'; +import { UpdateWidthNodeValueCommand } from '../commands/update-width-node-value-command'; + +export class LaneWidthTool extends BaseTool { + + public name: string = 'LaneWidth'; + + private widthChangeSub: Subscription; + + private laneWidthChanged: boolean = false; + private pointerDown: boolean = false; + private pointerDownAt: Vector3; + + private lane: TvLane; + private controlPoint: AnyControlPoint; + private widthNode: LaneWidthNode; + + private laneHelper: OdLaneReferenceLineBuilder; + + + constructor () { + + super(); + + } + + init () { + + + } + + enable () { + + super.enable(); + + this.laneHelper = new OdLaneReferenceLineBuilder( null, LineType.DASHED ); + + // this.widthChangeSub = LaneWidthInspector.widthChanged.subscribe( ( data: LaneWidthInspectorData ) => { + + // data.node.road.getLaneSectionAt( data.node.s ).updateLaneWidthValues( data.node.lane ); + + // NodeFactoryService.updateLaneWidthNodeLine( data.node ); + + // this.rebuild( data.node.road ); + + // } ); + + this.widthChangeSub = LaneWidthInspector.widthChanged.subscribe( width => { + + CommandHistory.execute( new UpdateWidthNodeValueCommand( this.widthNode, width, null, this.laneHelper ) ); + + } ); + + + + } + + disable () { + + super.disable(); + + if ( this.laneHelper ) this.laneHelper.clear(); + + if ( this.widthChangeSub ) this.widthChangeSub.unsubscribe(); + + this.openDrive.roads.forEach( road => LaneWidthTool.hideNodes( road ) ); + } + + public onPointerDown ( e: PointerEventData ) { + + if ( e.button === MouseButton.RIGHT || e.button === MouseButton.MIDDLE ) return; + + this.pointerDown = true; + + this.pointerDownAt = e.point; + + const shiftKeyDown = KeyboardInput.isShiftKeyDown; + + let hasInteracted = false; + + // check for control point interactions first + if ( !shiftKeyDown && !hasInteracted ) hasInteracted = this.checkNodePointInteraction( e ); + + // check for line segment interactions + // if ( !shiftKeyDown && !hasInteracted ) hasInteracted = this.checkNodeLineInteraction( e ); + + // if ( !hasInteracted ) hasInteracted = this.checkReferenceLineInteraction( e ); + + // check for lane game object interactions + if ( !hasInteracted ) hasInteracted = this.checkLaneObjectInteraction( e ); + + if ( !hasInteracted ) { + + // // no interaction with line, lane, points etc + // if ( this.lane ) { + + // LaneWidthTool.hideNodes( this.openDrive.getRoadById( this.lane.roadId ) ); + + // this.laneHelper.clear(); + + // this.lane = null; + + // } + + } + + } + + public onPointerClicked ( e: PointerEventData ) { + + if ( e.button === MouseButton.LEFT && KeyboardInput.isShiftKeyDown && e.point != null ) { + + this.addNode( e.point ); + + } + } + + public onPointerUp ( e ) { + + if ( this.laneWidthChanged && this.widthNode ) { + + const newPosition = this.widthNode.point.position.clone(); + + const oldPosition = this.pointerDownAt.clone(); + + CommandHistory.execute( new UpdateWidthNodePositionCommand( this.widthNode, newPosition, oldPosition, this.laneHelper ) ) + + } + + this.pointerDown = false; + + this.pointerDownAt = null; + + this.laneWidthChanged = false; + } + + public onPointerMoved ( e: PointerEventData ) { + + if ( this.pointerDown && this.widthNode ) { + + this.laneWidthChanged = true; + + NodeFactoryService.updateLaneWidthNode( this.widthNode, e.point ); + + this.widthNode.updateLaneWidthValues(); + + // this.updateLaneWidth( this.pointerObject.parent as LaneWidthNode ); + + // if ( this.lane ) this.laneHelper.redraw( LineType.DASHED ); + + } + + // else if ( this.pointerDown && this.pointerObject && this.pointerObject[ 'tag' ] == LaneWidthNode.lineTag ) { + + // this.laneWidthChanged = true; + + // NodeFactoryService.updateLaneWidthNode( this.pointerObject.parent as LaneWidthNode, e.point ); + + // this.updateLaneWidth( this.pointerObject.parent as LaneWidthNode ); + + // if ( this.lane ) this.laneHelper.redraw( LineType.DASHED ); + + // } + } + + private checkNodePointInteraction ( e: PointerEventData ): boolean { + + // first chceck for control point interactions + // doing in 2 loop to prioritise control points + const controlPoint = PickingHelper.checkControlPointInteraction( e, LaneWidthNode.pointTag ); + + if ( controlPoint ) { + + const laneWidthNode = controlPoint.parent as LaneWidthNode; + + CommandHistory.executeMany( + + new SetValueCommand( this, 'controlPoint', controlPoint ), + + new SetValueCommand( this, 'widthNode', laneWidthNode ), + + new SetInspectorCommand( LaneWidthInspector, { node: laneWidthNode } ), + + ); + + } else if ( this.controlPoint ) { + + CommandHistory.executeMany( + + new SetValueCommand( this, 'controlPoint', null ), + + new SetInspectorCommand( LaneWidthInspector, { node: null, lane: this.lane } ), + + ); + + } + + return controlPoint != null; + } + + + private checkLaneObjectInteraction ( e: PointerEventData ) { + + let hasInteracted = false; + + for ( let i = 0; i < e.intersections.length; i++ ) { + + const intersection = e.intersections[ i ]; + + // tslint:disable-next-line: no-string-literal + if ( intersection.object && intersection.object[ 'tag' ] === ObjectTypes.LANE ) { + + hasInteracted = true; + + if ( intersection.object.userData.lane ) { + + const newLane = intersection.object.userData.lane as TvLane; + + + + // check if old or a new lane is selected + if ( !this.lane || this.lane.id !== newLane.id || this.lane.roadId !== newLane.roadId ) { + + CommandHistory.executeMany( + + new SetValueCommand( this, 'lane', newLane ), + + new SetInspectorCommand( LaneWidthInspector, { lane: newLane } ), + + ); + + } + } + + break; + } + } + + if ( !hasInteracted && !this.controlPoint ) { + + CommandHistory.executeMany( + + new SetValueCommand( this, 'lane', null ), + + new SetInspectorCommand( null, null ), + + ); + + this.laneHelper.clear(); + } + + return hasInteracted; + } + + // private checkReferenceLineInteraction ( e: PointerEventData ) { + + // let hasInteracted = false; + + // this.checkIntersection( this.laneHelper.tag, e.intersections, ( obj ) => { + + // hasInteracted = true; + + // this.laneHelper.onLineSelected( obj as Line ); + + // } ); + + // return hasInteracted; + // } + + private addNode ( position: Vector3 ): void { + + if ( !this.lane ) return; + + const road = this.openDrive.getRoadById( this.lane.roadId ); + + const laneWidthNode = NodeFactoryService.createLaneWidthNodeByPosition( road, this.lane, position ); + + CommandHistory.executeMany( + + new SetValueCommand( this, 'widthNode', laneWidthNode ), + + new AddWidthNodeCommand( laneWidthNode, this.laneHelper ), + + new SetInspectorCommand( LaneWidthInspector, { node: laneWidthNode } ), + + ); + } + + + // tslint:disable-next-line: member-ordering + static hideNodes ( road: TvRoad ): void { + + road.laneSections.forEach( laneSection => { + + laneSection.lanes.forEach( lane => { + + lane.getLaneWidthVector().forEach( laneWidth => { + + if ( laneWidth.mesh ) laneWidth.mesh.visible = false; + + } ); + + } ); + + } ); + + } + + // tslint:disable-next-line: member-ordering + static showNodes ( road: TvRoad ) { + + road.laneSections.forEach( laneSection => { + + laneSection.lanes.forEach( lane => { + + lane.getLaneWidthVector().forEach( laneWidth => { + + if ( laneWidth.mesh ) { + + laneWidth.mesh.visible = true; + + } else { + + laneWidth.mesh = NodeFactoryService.createLaneWidthNode( road, lane, laneWidth.s, laneWidth ); + + SceneService.add( laneWidth.mesh ); + + } + + } ) + + } ) + + } ); + } + +} diff --git a/src/app/core/tools/maneuver-tool.spec.ts b/src/app/core/tools/maneuver-tool.spec.ts new file mode 100644 index 00000000..bb4b38e9 --- /dev/null +++ b/src/app/core/tools/maneuver-tool.spec.ts @@ -0,0 +1,449 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { TvMap } from 'app/modules/tv-map/models/tv-map.model'; +import { TvMapSourceFile } from 'app/modules/tv-map/services/tv-map-source-file'; +import { TvContactPoint, TvLaneSide } from 'app/modules/tv-map/models/tv-common'; +import { JunctionEntryObject } from 'app/modules/three-js/objects/junction-entry.object'; +import { ManeuverTool } from './maneuver-tool'; + + +describe( 'ManeuverTool Test', () => { + + let tool: ManeuverTool; + let openDrive: TvMap; + + beforeEach( () => { + + tool = new ManeuverTool(); + + tool.init(); + + openDrive = TvMapSourceFile.openDrive = new TvMap(); + + } ); + + it( 'should return correct entry exit side', () => { + + // start-end <-> start:end + // 1L <--- <---- 1L + // -1R ---> ----> -1R + + const roadA = openDrive.addDefaultRoad(); + roadA.addGeometryLine( 0, 0, 0, 0, 10 ); + + const roadB = openDrive.addDefaultRoad(); + roadB.addGeometryLine( 0, 15, 0, 0, 10 ); + + const positionA = roadA.endPosition().toVector3(); + const positionB = roadB.startPosition().toVector3(); + + const aL2 = new JunctionEntryObject( "", positionA, TvContactPoint.END, roadA, roadA.getFirstLaneSection().getLaneById( 2 ) ); + const aL1 = new JunctionEntryObject( "", positionA, TvContactPoint.END, roadA, roadA.getFirstLaneSection().getLaneById( 1 ) ); + const aR1 = new JunctionEntryObject( "", positionA, TvContactPoint.END, roadA, roadA.getFirstLaneSection().getLaneById( -1 ) ); + const aR2 = new JunctionEntryObject( "", positionA, TvContactPoint.END, roadA, roadA.getFirstLaneSection().getLaneById( -2 ) ); + + const bL2 = new JunctionEntryObject( "", positionB, TvContactPoint.START, roadB, roadB.getFirstLaneSection().getLaneById( 2 ) ); + const bL1 = new JunctionEntryObject( "", positionB, TvContactPoint.START, roadB, roadB.getFirstLaneSection().getLaneById( 1 ) ); + const bR1 = new JunctionEntryObject( "", positionB, TvContactPoint.START, roadB, roadB.getFirstLaneSection().getLaneById( -1 ) ); + const bR2 = new JunctionEntryObject( "", positionB, TvContactPoint.START, roadB, roadB.getFirstLaneSection().getLaneById( -2 ) ); + + let result = tool.findEntryExitSide( aL1, bL1 ); + + expect( result.entry.id ).toBe( bL1.id ); + expect( result.exit.id ).toBe( aL1.id ); + expect( result.side ).toBe( TvLaneSide.RIGHT ); + + result = tool.findEntryExitSide( bL1, aL1 ); + + expect( result.entry.id ).toBe( bL1.id ); + expect( result.exit.id ).toBe( aL1.id ); + expect( result.side ).toBe( TvLaneSide.RIGHT ); + + result = tool.findEntryExitSide( aR1, bR1 ); + + expect( result.entry.id ).toBe( aR1.id ); + expect( result.exit.id ).toBe( bR1.id ); + expect( result.side ).toBe( TvLaneSide.RIGHT ); + + result = tool.findEntryExitSide( bR1, aR1 ); + + expect( result.entry.id ).toBe( aR1.id ); + expect( result.exit.id ).toBe( bR1.id ); + expect( result.side ).toBe( TvLaneSide.RIGHT ); + + } ); + + it( 'should return true for existing connection ', () => { + + const roadA = openDrive.addDefaultRoad(); + roadA.addGeometryLine( 0, 0, 0, 0, 10 ); + + const roadB = openDrive.addDefaultRoad(); + roadB.addGeometryLine( 0, 15, 0, 0, 10 ); + + const roadC = openDrive.addDefaultRoad(); + roadC.addGeometryLine( 0, 15, 10, 0, 10 ); + + const positionA = roadA.endPosition().toVector3(); + const positionB = roadB.startPosition().toVector3(); + const positionC = roadC.startPosition().toVector3(); + + const aL2 = new JunctionEntryObject( "", positionA, TvContactPoint.END, roadA, roadA.getFirstLaneSection().getLaneById( 2 ) ); + const aL1 = new JunctionEntryObject( "", positionA, TvContactPoint.END, roadA, roadA.getFirstLaneSection().getLaneById( 1 ) ); + const aR1 = new JunctionEntryObject( "", positionA, TvContactPoint.END, roadA, roadA.getFirstLaneSection().getLaneById( -1 ) ); + const aR2 = new JunctionEntryObject( "", positionA, TvContactPoint.END, roadA, roadA.getFirstLaneSection().getLaneById( -2 ) ); + + const bL2 = new JunctionEntryObject( "", positionB, TvContactPoint.START, roadB, roadB.getFirstLaneSection().getLaneById( 2 ) ); + const bL1 = new JunctionEntryObject( "", positionB, TvContactPoint.START, roadB, roadB.getFirstLaneSection().getLaneById( 1 ) ); + const bR1 = new JunctionEntryObject( "", positionB, TvContactPoint.START, roadB, roadB.getFirstLaneSection().getLaneById( -1 ) ); + const bR2 = new JunctionEntryObject( "", positionB, TvContactPoint.START, roadB, roadB.getFirstLaneSection().getLaneById( -2 ) ); + + const cL2 = new JunctionEntryObject( "", positionC, TvContactPoint.START, roadC, roadC.getFirstLaneSection().getLaneById( 2 ) ); + const cL1 = new JunctionEntryObject( "", positionC, TvContactPoint.START, roadC, roadC.getFirstLaneSection().getLaneById( 1 ) ); + const cR1 = new JunctionEntryObject( "", positionC, TvContactPoint.START, roadC, roadC.getFirstLaneSection().getLaneById( -1 ) ); + const cR2 = new JunctionEntryObject( "", positionC, TvContactPoint.START, roadC, roadC.getFirstLaneSection().getLaneById( -2 ) ); + + const entryExitSide = tool.findEntryExitSide( aL1, bL1 ); + + const laneWidth = aL1.lane.getWidthValue( 0 ); + + const entry = entryExitSide.entry; + const exit = entryExitSide.exit; + const side = entryExitSide.side; + + const junction = tool.findJunction( entry, exit ); + + const connectingRoad = tool.createConnectingRoad( entry, exit, side, laneWidth, junction ); + + let result = tool.hasConnection( junction, entry, exit ); + + expect( result.connectionFound ).toBe( false ); + + tool.createConnections( junction, entry, connectingRoad, exit ); + + result = tool.hasConnection( junction, entry, exit ); + + expect( result.connectionFound ).toBe( true ); + + result = tool.hasConnection( junction, exit, entry ); + + expect( result.connectionFound ).toBe( false ); + + expect( openDrive.roads.size ).toBe( 4 ); + + expect( junction.connections.size ).toBe( 1 ); + + result = tool.hasConnection( junction, aR2, bR2 ); + + // // false because till now only left side connection has been created + // // if we check for right side connection it should return false + // expect( result.connectionFound ).toBe( false ); + + // result = tool.hasConnection( junction, aL2, bL2 ); + + // // true because left side connection already exists + // expect( result.connectionFound ).toBe( true ); + + // // create another connection road a and c left side + // tool.createConnections( junction, aL1, tool.createConnectingRoad( aL1, cL1, side, laneWidth, junction ), cL1 ); + + // expect( openDrive.roads.size ).toBe( 5 ); + + // expect( junction.connections.size ).toBe( 4 ); + + // // [] + + // // expect(result.) + + } ) + + it( 'should connect road for connection from left to right', () => { + + // start-end <-> start:end + // 1L <--- <---- 1L + // -1R ---> ----> -1R + + const roadA = openDrive.addDefaultRoad(); + roadA.addGeometryLine( 0, 0, 0, 0, 10 ); + + const roadB = openDrive.addDefaultRoad(); + roadB.addGeometryLine( 0, 15, 0, 0, 10 ); + + const positionA = roadA.endPosition().toVector3(); + const positionB = roadB.startPosition().toVector3(); + + const leftA = new JunctionEntryObject( "", positionA, TvContactPoint.END, roadA, roadA.getFirstLaneSection().getLaneById( 1 ) ); + const rightA = new JunctionEntryObject( "", positionA, TvContactPoint.END, roadA, roadA.getFirstLaneSection().getLaneById( -1 ) ); + + const leftB = new JunctionEntryObject( "", positionB, TvContactPoint.START, roadB, roadB.getFirstLaneSection().getLaneById( 1 ) ); + const rightB = new JunctionEntryObject( "", positionB, TvContactPoint.START, roadB, roadB.getFirstLaneSection().getLaneById( -1 ) ); + + let nodes = tool.getSplinePositions( leftA, leftB, TvLaneSide.LEFT ); + expect( nodes.start.x ).toBe( 10 ); + expect( nodes.end.x ).toBe( 15 ); + expect( nodes.side ).toBe( TvLaneSide.LEFT ); + + nodes = tool.getSplinePositions( leftB, leftA, TvLaneSide.RIGHT ); + expect( nodes.start.x ).toBe( 15 ); + expect( nodes.end.x ).toBe( 10 ); + expect( nodes.side ).toBe( TvLaneSide.RIGHT ); + + nodes = tool.getSplinePositions( rightA, rightB, TvLaneSide.RIGHT ); + expect( nodes.start.x ).toBe( 10 ); + expect( nodes.end.x ).toBe( 15 ); + expect( nodes.side ).toBe( TvLaneSide.RIGHT ); + + nodes = tool.getSplinePositions( rightB, rightA, TvLaneSide.LEFT ); + expect( nodes.start.x ).toBe( 15 ); + expect( nodes.end.x ).toBe( 10 ); + expect( nodes.side ).toBe( TvLaneSide.LEFT ); + + // this will create junctions and connections + tool.connectJunctionObject( leftA, leftB ); + tool.connectJunctionObject( rightA, rightB ); + + //////////////////////////////////////////////////////////////////////// + // connecting road successor/predecssor tests + + const roadC = openDrive.roads.get( 3 ); + const roadD = openDrive.roads.get( 4 ); + + expect( !roadC ).toBe( false ); + expect( !roadD ).toBe( false ); + + expect( roadC.successor.elementType ).toBe( "road" ); + expect( roadC.successor.elementId ).toBe( roadA.id ); + expect( roadC.successor.contactPoint ).toBe( TvContactPoint.END ); + + expect( roadC.predecessor.elementType ).toBe( "road" ); + expect( roadC.predecessor.elementId ).toBe( roadB.id ); + expect( roadC.predecessor.contactPoint ).toBe( TvContactPoint.START ); + + expect( roadD.successor.elementType ).toBe( "road" ); + expect( roadD.successor.elementId ).toBe( roadB.id ); + expect( roadD.successor.contactPoint ).toBe( TvContactPoint.START ); + + expect( roadD.predecessor.elementType ).toBe( "road" ); + expect( roadD.predecessor.elementId ).toBe( roadA.id ); + expect( roadD.predecessor.contactPoint ).toBe( TvContactPoint.END ); + + //////////////////////////////////////////////////////////////////////// + // incoming/outgoing road successor/predecessor relation + + expect( openDrive.junctions.size ).toBe( 1 ); + + const junction = openDrive.junctions.get( 1 ); + + expect( junction.connections.size ).toBe( 2 ); + + expect( roadA.successor.elementType ).toBe( "junction" ) + expect( roadA.successor.elementId ).toBe( junction.id ) + + expect( roadB.predecessor.elementType ).toBe( "junction" ) + expect( roadB.predecessor.elementId ).toBe( junction.id ) + + //////////////////////////////////////////////////////////////////////// + // junction connection tests + let connection = junction.getJunctionConnection( 1 ); + let link = connection.laneLink[ 0 ]; + + expect( !connection ).toBe( false ); + expect( connection.incomingRoad ).toBe( 2 ); + expect( connection.connectingRoad ).toBe( 3 ); + expect( connection.contactPoint ).toBe( TvContactPoint.START ); + expect( link.from ).toBe( +1 ); + expect( link.to ).toBe( -1 ); + + connection = junction.getJunctionConnection( 2 ); + link = connection.laneLink[ 0 ]; + + expect( !connection ).toBe( false ); + expect( connection.incomingRoad ).toBe( 1 ); + expect( connection.connectingRoad ).toBe( 4 ); + expect( connection.contactPoint ).toBe( TvContactPoint.START ); + expect( link.from ).toBe( -1 ); + expect( link.to ).toBe( -1 ); + + + //////////////////////////////////////////////////////////////////////// + // connection road lane links + //////////////////////////////////////////////////////////////////////// + + const laneC = roadC.getFirstLaneSection().getLaneById( -1 ); + + expect( !laneC ).toBe( false ); + expect( laneC.predecessor ).toBe( 1 ); + expect( laneC.succcessor ).toBe( 1 ); + + const laneD = roadD.getFirstLaneSection().getLaneById( -1 ); + + expect( !laneD ).toBe( false ); + expect( laneD.predecessor ).toBe( -1 ); + expect( laneD.succcessor ).toBe( -1 ); + + } ); + + it( 'should connect road for connection from right to left', () => { + + // start-end <-> start:end + // 1L <--- <---- 1L + // -1R ---> ----> -1R + + const roadA = openDrive.addDefaultRoad(); + roadA.addGeometryLine( 0, 0, 0, 0, 10 ); + + const roadB = openDrive.addDefaultRoad(); + roadB.addGeometryLine( 0, 15, 0, 0, 10 ); + + const positionA = roadA.endPosition().toVector3(); + const positionB = roadB.startPosition().toVector3(); + + const leftA = new JunctionEntryObject( "", positionA, TvContactPoint.END, roadA, roadA.getFirstLaneSection().getLaneById( 1 ) ); + const rightA = new JunctionEntryObject( "", positionA, TvContactPoint.END, roadA, roadA.getFirstLaneSection().getLaneById( -1 ) ); + + const leftB = new JunctionEntryObject( "", positionB, TvContactPoint.START, roadB, roadB.getFirstLaneSection().getLaneById( 1 ) ); + const rightB = new JunctionEntryObject( "", positionB, TvContactPoint.START, roadB, roadB.getFirstLaneSection().getLaneById( -1 ) ); + + let nodes = tool.getSplinePositions( leftA, leftB, TvLaneSide.LEFT ); + expect( nodes.start.x ).toBe( 10 ); + expect( nodes.end.x ).toBe( 15 ); + expect( nodes.side ).toBe( TvLaneSide.LEFT ); + + nodes = tool.getSplinePositions( leftB, leftA, TvLaneSide.RIGHT ); + expect( nodes.start.x ).toBe( 15 ); + expect( nodes.end.x ).toBe( 10 ); + expect( nodes.side ).toBe( TvLaneSide.RIGHT ); + + nodes = tool.getSplinePositions( rightA, rightB, TvLaneSide.RIGHT ); + expect( nodes.start.x ).toBe( 10 ); + expect( nodes.end.x ).toBe( 15 ); + expect( nodes.side ).toBe( TvLaneSide.RIGHT ); + + nodes = tool.getSplinePositions( rightB, rightA, TvLaneSide.LEFT ); + expect( nodes.start.x ).toBe( 15 ); + expect( nodes.end.x ).toBe( 10 ); + expect( nodes.side ).toBe( TvLaneSide.LEFT ); + + // this will create junctions and connections + tool.connectJunctionObject( leftB, leftA ); + tool.connectJunctionObject( rightB, rightA ); + + //////////////////////////////////////////////////////////////////////// + // connecting road successor/predecssor tests + + const roadC = openDrive.roads.get( 3 ); + const roadD = openDrive.roads.get( 4 ); + + expect( roadC.successor.elementType ).toBe( "road" ); + expect( roadC.successor.elementId ).toBe( roadA.id ); + expect( roadC.successor.contactPoint ).toBe( TvContactPoint.END ); + + expect( roadC.predecessor.elementType ).toBe( "road" ); + expect( roadC.predecessor.elementId ).toBe( roadB.id ); + expect( roadC.predecessor.contactPoint ).toBe( TvContactPoint.START ); + + expect( roadD.successor.elementType ).toBe( "road" ); + expect( roadD.successor.elementId ).toBe( roadB.id ); + expect( roadD.successor.contactPoint ).toBe( TvContactPoint.START ); + + expect( roadD.predecessor.elementType ).toBe( "road" ); + expect( roadD.predecessor.elementId ).toBe( roadA.id ); + expect( roadD.predecessor.contactPoint ).toBe( TvContactPoint.END ); + + //////////////////////////////////////////////////////////////////////// + // incoming/outgoing road successor/predecessor relation + + expect( openDrive.junctions.size ).toBe( 1 ); + + const junction = openDrive.junctions.get( 1 ); + + expect( junction.connections.size ).toBe( 2 ); + + expect( roadA.successor.elementType ).toBe( "junction" ) + expect( roadA.successor.elementId ).toBe( junction.id ) + + expect( roadB.predecessor.elementType ).toBe( "junction" ) + expect( roadB.predecessor.elementId ).toBe( junction.id ) + + //////////////////////////////////////////////////////////////////////// + // junction connection tests + let connection = junction.getJunctionConnection( 1 ); + let link = connection.laneLink[ 0 ]; + + expect( !connection ).toBe( false ); + expect( connection.incomingRoad ).toBe( 2 ); + expect( connection.connectingRoad ).toBe( 3 ); + expect( connection.contactPoint ).toBe( TvContactPoint.START ); + expect( link.from ).toBe( +1 ); + expect( link.to ).toBe( -1 ); + + connection = junction.getJunctionConnection( 2 ); + link = connection.laneLink[ 0 ]; + + expect( !connection ).toBe( false ); + expect( connection.incomingRoad ).toBe( 1 ); + expect( connection.connectingRoad ).toBe( 4 ); + expect( connection.contactPoint ).toBe( TvContactPoint.START ); + expect( link.from ).toBe( -1 ); + expect( link.to ).toBe( -1 ); + + + //////////////////////////////////////////////////////////////////////// + // connection road lane links + //////////////////////////////////////////////////////////////////////// + + const laneC = roadC.getFirstLaneSection().getLaneById( -1 ); + + expect( !laneC ).toBe( false ); + expect( laneC.predecessor ).toBe( 1 ); + expect( laneC.succcessor ).toBe( 1 ); + + const laneD = roadD.getFirstLaneSection().getLaneById( -1 ); + + expect( !laneD ).toBe( false ); + expect( laneD.predecessor ).toBe( -1 ); + expect( laneD.succcessor ).toBe( -1 ); + + + } ); + + // it( 'should connect road for connection', () => { + + // // start-end <-> end:start + // // 1L <--- <---- -1R + // // -1R ---> ----> 1L + + // const roadA = openDrive.addDefaultRoad(); + // roadA.addGeometryLine( 0, 0, 0, 0, 10 ); + + // const roadB = openDrive.addDefaultRoad(); + // roadB.addGeometryLine( 0, 25, 0, -Math.PI, 10 ); + + // const positionA = roadA.endPosition().toVector3(); + // const positionB = roadB.startPosition().toVector3(); + + // let a = new JunctionEntryObject( "", positionA, OdContactPoints.END, roadA, roadA.getFirstLaneSection().getLaneById( 1 ) ); + // let b = new JunctionEntryObject( "", positionB, OdContactPoints.END, roadB, roadB.getFirstLaneSection().getLaneById( 1 ) ); + + // let nodes = tool.findStartEndForRoad( a, b ); + + // expect( nodes ).toBeUndefined(); + + // ///////////////////////////////////////////////////////////////////////////// + + // a = new JunctionEntryObject( "", positionA, OdContactPoints.END, roadA, roadA.getFirstLaneSection().getLaneById( -1 ) ); + + // nodes = tool.findStartEndForRoad( a, b ); + + // expect( nodes.start.x ).toBe( 15 ); + // expect( nodes.end.x ).toBe( 10 ); + + // } ); + + it( 'should connect right road for connection' ); + + + +} ); \ No newline at end of file diff --git a/src/app/core/tools/maneuver-tool.ts b/src/app/core/tools/maneuver-tool.ts new file mode 100644 index 00000000..07ec7806 --- /dev/null +++ b/src/app/core/tools/maneuver-tool.ts @@ -0,0 +1,1084 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { BaseTool } from './base-tool'; +import { MouseButton, PointerEventData } from 'app/events/pointer-event-data'; +import { CommandHistory } from 'app/services/command-history'; +import { LanePathFactory } from '../factories/lane-path-factory.service'; +import { KeyboardInput } from '../input'; +import { SetInspectorCommand } from '../commands/set-inspector-command'; +import { MultiCmdsCommand } from '../commands/multi-cmds-command'; +import { UpdateRoadPointCommand } from '../commands/update-road-point-command'; +import { Vector3 } from 'three'; +import { TvRoad } from 'app/modules/tv-map/models/tv-road.model'; +import { RoadControlPoint } from 'app/modules/three-js/objects/road-control-point'; +import { LanePathObject, TvJunctionLaneLink } from 'app/modules/tv-map/models/tv-junction-lane-link'; +import { LaneLinkInspector } from 'app/views/inspectors/lane-link-inspector/lane-link-inspector.component'; +import { JunctionEntryObject } from 'app/modules/three-js/objects/junction-entry.object'; +import { PickingHelper } from '../services/picking-helper.service'; +import { SetValueCommand } from 'app/modules/three-js/commands/set-value-command'; +import { TvContactPoint, TvElementType, TvLaneSide, TvLaneType } from 'app/modules/tv-map/models/tv-common'; +import { TvMapQueries } from 'app/modules/tv-map/queries/tv-map-queries'; +import { SceneService } from '../services/scene.service'; +import { OdLaneDirectionBuilder } from 'app/modules/tv-map/builders/od-lane-direction-builder'; +import { TvPosTheta } from 'app/modules/tv-map/models/tv-pos-theta'; +import { AutoSpline } from '../shapes/auto-spline'; +import { RoadFactory } from '../factories/road-factory.service'; +import { TvJunction } from 'app/modules/tv-map/models/tv-junction'; +import { TvJunctionConnection } from 'app/modules/tv-map/models/tv-junction-connection'; +import { SnackBar } from 'app/services/snack-bar.service'; +import { RoadControlPointInspector } from 'app/views/inspectors/road-control-point-inspector/road-control-point-inspector.component'; +import { JunctionEntryInspector } from 'app/views/inspectors/junction-entry-inspector/junction-entry-inspector.component'; +import { AddConnectionCommand } from '../commands/add-connection-command'; + +const DEFAULT_SIDE = TvLaneSide.RIGHT; + +export class ManeuverTool extends BaseTool { + + name: string = 'ManeuverTool'; + + public connectingRoad: TvRoad; + + public roadControlPoint: RoadControlPoint; + + public lanePathObject: LanePathObject; + + private pointerDown = false; + + private pointerDownAt: Vector3; + + private roadChanged = false; + + private junctionEntryObjects = []; + private lanePathObjects = []; + + private laneDirectionHelper = new OdLaneDirectionBuilder( null ); + + private junctionEntryObject: JunctionEntryObject; + + init () { + + + } + + enable () { + + super.enable(); + + this.junctionEntryObject = null; + this.lanePathObject = null; + this.connectingRoad = null; + this.roadControlPoint = null; + + this.showJunctionEntries(); + + this.showLanePathObjects(); + } + + disable () { + + super.disable(); + + this.junctionEntryObject = null; + this.lanePathObject = null; + this.connectingRoad = null; + this.roadControlPoint = null; + + this.hideJunctionEntries(); + + this.hideLanePathObjects(); + + this.laneDirectionHelper.clear(); + } + + onPointerDown ( e: PointerEventData ) { + + if ( e.button === MouseButton.RIGHT || e.button === MouseButton.MIDDLE ) return; + + this.pointerDown = true; + + this.pointerDownAt = e.point.clone(); + + const shiftKeyDown = KeyboardInput.isShiftKeyDown; + + let hasInteracted = false; + + if ( !shiftKeyDown && !hasInteracted ) hasInteracted = this.checkRoadControlPointInteraction( e ); + + if ( !shiftKeyDown && !hasInteracted ) hasInteracted = this.checkJunctionEntryInteraction( e ); + + if ( !shiftKeyDown && !hasInteracted ) hasInteracted = this.checkPathInteraction( e ); + + if ( !hasInteracted ) { + + const commands = []; + + commands.push( new SetInspectorCommand( null, null ) ); + + CommandHistory.execute( new MultiCmdsCommand( commands ) ); + + if ( this.connectingRoad ) { + + this.connectingRoad.hideNodes(); + this.connectingRoad.spline.hide(); + } + + // if ( this.lanePathObject ) this.lanePathObject.visible = false; + } + } + + onPointerUp ( e: PointerEventData ) { + + if ( this.connectingRoad && this.roadControlPoint && this.roadChanged ) { + + const updateRoadPointCommand = new UpdateRoadPointCommand( + this.connectingRoad, + this.roadControlPoint, + this.roadControlPoint.position, + this.pointerDownAt + ); + + CommandHistory.execute( updateRoadPointCommand ); + + LanePathFactory.update( this.lanePathObject ); + } + + this.pointerDown = false; + + this.roadChanged = false; + + this.pointerDownAt = null; + } + + onPointerMoved ( e: PointerEventData ) { + + if ( this.pointerDown && this.roadControlPoint && this.connectingRoad ) { + + this.roadControlPoint.copyPosition( e.point ); + + this.connectingRoad.spline.update(); + + this.roadChanged = true; + + } + + } + + checkPathInteraction ( event: PointerEventData ): boolean { + + if ( event.button !== MouseButton.LEFT ) return; + + let hasInteracted = false; + + for ( let i = 0; i < event.intersections.length; i++ ) { + + const intersection = event.intersections[ i ]; + + // tslint:disable-next-line: no-string-literal + if ( intersection.object[ 'tag' ] === LanePathObject.tag ) { + + hasInteracted = true; + + this.lanePathObject = intersection.object.parent as LanePathObject; + + const commands = []; + + this.connectingRoad = this.lanePathObject.connectingRoad; + + commands.push( new SetInspectorCommand( LaneLinkInspector, this.lanePathObject ) ); + + CommandHistory.execute( new MultiCmdsCommand( commands ) ); + + break; + } + } + + return hasInteracted; + } + + checkJunctionEntryInteraction ( event: PointerEventData ): boolean { + + if ( event.button !== MouseButton.LEFT ) return; + + let hasInteracted = false; + + for ( let i = 0; i < event.intersections.length; i++ ) { + + const intersection = event.intersections[ i ]; + + // tslint:disable-next-line: no-string-literal + if ( intersection.object[ 'tag' ] === JunctionEntryObject.tag ) { + + hasInteracted = true; + + const junctionObject = intersection.object as JunctionEntryObject; + + const tryToConnect = this.junctionEntryObject && junctionObject; + + if ( tryToConnect ) { + + this.connectJunctionObject( this.junctionEntryObject, junctionObject ); + + // const junctionEntryObject = created ? null : junctionObject; + + // CommandHistory.executeAll( [ + + // new SetValueCommand( this, 'junctionEntryObject', junctionEntryObject ), + + // new SetInspectorCommand( JunctionEntryInspector, junctionObject ), + + // ] ); + + } else { + + CommandHistory.executeAll( [ + + new SetValueCommand( this, 'junctionEntryObject', junctionObject ), + + new SetInspectorCommand( JunctionEntryInspector, junctionObject ), + + ] ); + + } + + break; + } + } + + if ( !hasInteracted ) { + + CommandHistory.executeAll( [ + + new SetValueCommand( this, 'junctionEntryObject', null ), + + new SetInspectorCommand( null, null ) + + ] ); + + } + + return hasInteracted; + } + + checkRoadControlPointInteraction ( e: PointerEventData ): boolean { + + // return if no connectingRoad is selected + if ( !this.connectingRoad || !this.connectingRoad.spline ) return false; + + if ( !e.point ) return; + + const maxDistance = Math.max( 0.5, e.approxCameraDistance * 0.01 ); + + const roadControlPoints = []; + + this.connectingRoad.spline.controlPoints.forEach( ( cp: RoadControlPoint ) => { + + roadControlPoints.push( cp ); + + if ( cp.frontTangent ) roadControlPoints.push( cp.frontTangent ); + + if ( cp.backTangent ) roadControlPoints.push( cp.backTangent ); + + } ); + + const roadControlPoint = PickingHelper.findNearest( e.point, roadControlPoints, maxDistance ); + + if ( roadControlPoint ) { + + CommandHistory.executeAll( [ + + new SetValueCommand( this, 'roadControlPoint', roadControlPoint ), + + new SetInspectorCommand( RoadControlPointInspector, roadControlPoint ), + + ] ); + + } else { + + CommandHistory.executeAll( [ + + new SetValueCommand( this, 'roadControlPoint', null ), + + new SetInspectorCommand( null, null ), + + ] ); + + } + + return roadControlPoint != null; + } + + connectJunctionObject ( a: JunctionEntryObject, b: JunctionEntryObject ): void { + + if ( a == null || b == null ) return; + + if ( a.id === b.id ) { + + CommandHistory.execute( new SetValueCommand( this, 'junctionEntryObject', null ) ); + + SnackBar.error( "Select a different entry/exit" ); + + return; + } + + const entryExitSide = this.findEntryExitSide( a, b ); + + if ( !entryExitSide ) { + + CommandHistory.execute( new SetValueCommand( this, 'junctionEntryObject', null ) ); + + SnackBar.error( "Cannot connect" ); + + return; + } + + const entry = entryExitSide.entry; + const exit = entryExitSide.exit; + + // if ( this.connectingRoad ) { + + // this.connectingRoad.hideNodes(); + // this.connectingRoad.spline.hide(); + // } + + try { + + const junction = this.findJunction( a, b ); + + const result = this.hasConnection( junction, entry, exit ); + + if ( result.connectionFound && result.laneLinkFound ) { + + CommandHistory.execute( new SetValueCommand( this, 'junctionEntryObject', null ) ); + + SnackBar.error( "Connection already exists" ); + + } else if ( result.connectionFound && !result.laneLinkFound ) { + + CommandHistory.execute( new SetValueCommand( this, 'junctionEntryObject', null ) ); + + // this.createLink( result.connection, entry, exit, side, laneWidth, junction ); + + // SnackBar.success( "Connection created" ); + + } else if ( !result.connectionFound && !result.laneLinkFound ) { + + this.createNewConnection( entry, exit, junction ); + + SnackBar.success( "Connection created" ); + } + + } catch ( error ) { + + console.log( error ); + + return; + } + + } + + createNewConnection ( entry: JunctionEntryObject, exit: JunctionEntryObject, junction: TvJunction ) { + + // for connection + // create road + // create connection and link + // set neighbours + // set lane sucessor/predessor + // create lane path + + // const laneWidth = entry.lane.getWidthValue( 0 ); + + // const connectingRoad = this.createConnectingRoad( entry, exit, DEFAULT_SIDE, laneWidth, junction ); + + // const result = this.createConnections( junction, entry, connectingRoad, exit ); + + // if ( result == null ) return; + + // const connection = result.connection; + + // const link = result.link; + + // // TODO: find the connecting lane + // const lane = connectingRoad.getFirstLaneSection().getLaneById( -1 ); + + // const lanePathObject = result.link.mesh = LanePathFactory.createPathForLane( + // entry.road, connectingRoad, lane, result.connection, result.link + // ); + + // this.lanePathObjects.push( this.lanePathObject ); + + // SceneService.add( lanePathObject ); + + // RoadFactory.rebuildRoad( connectingRoad ); + + // this.connectingRoad = connectingRoad; + + + // set value command - connectingRoad + // set value command - lanePathObject + // set + + + CommandHistory.executeAll( [ + + // new SetValueCommand( this, 'lanePathObject', lanePathObject ), + + // new SetValueCommand( this, 'connectingRoad', connectingRoad ), + + new SetValueCommand( this, 'junctionEntryObject', null ), + + new SetInspectorCommand( null, null ), + + new AddConnectionCommand( entry, exit, junction, this ), + + ] ); + + + + + + } + + /** + * + * @deprecated currently not being used + * @param connection + * @param entry + * @param exit + * @param side + * @param laneWidth + * @param junction + */ + createLink ( + connection: TvJunctionConnection, + entry: JunctionEntryObject, + exit: JunctionEntryObject, + side: TvLaneSide, + laneWidth: number, + junction: TvJunction + ) { + + const connectionRoad = this.openDrive.getRoadById( connection.connectingRoad ); + + const laneSection = connectionRoad.getFirstLaneSection(); + + // -ve because its always for right side + const newLaneId = -1 * ( laneSection.getRightLaneCount() + 1 ); + + const nodes = this.getSplinePositions( entry, exit, side ); + + const connectingLane = laneSection.addLane( nodes.side, newLaneId, entry.lane.type, entry.lane.level, true ); + + connectingLane.addWidthRecord( 0, laneWidth, 0, 0, 0 ); + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + connectionRoad.setPredecessor( "road", entry.road.id, entry.contact ); + + connectionRoad.setSuccessor( "road", exit.road.id, exit.contact ); + + connectingLane.setPredecessor( entry.lane.id ); + + connectingLane.setSuccessor( exit.lane.id ); + + const link = connection.addNewLink( entry.lane.id, connectingLane.id ); + + if ( entry.contact === TvContactPoint.START ) { + + entry.road.setPredecessor( "junction", junction.id ); + + } else if ( entry.contact === TvContactPoint.END ) { + + entry.road.setSuccessor( "junction", junction.id ); + + } + + if ( exit.contact === TvContactPoint.START ) { + + exit.road.setPredecessor( "junction", junction.id ); + + } else if ( exit.contact === TvContactPoint.END ) { + + exit.road.setSuccessor( "junction", junction.id ); + + } + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + this.lanePathObject = LanePathFactory.createPathForLane( entry.road, this.connectingRoad, connectingLane, connection, link ); + + this.lanePathObjects.push( this.lanePathObject ); + + SceneService.add( this.lanePathObject ); + + RoadFactory.rebuildRoad( this.connectingRoad ); + + } + + createConnectingRoad ( entry, exit, side, laneWidth, junction ) { + + const spline = this.createSpline( entry, exit, side ) + + const connectingRoad = this.openDrive.addConnectingRoad( DEFAULT_SIDE, laneWidth, junction.id ); + + connectingRoad.spline = spline; + + connectingRoad.updateGeometryFromSpline(); + + connectingRoad.spline.hide(); + + return connectingRoad; + } + + createSpline ( entry, exit, side ) { + + const nodes = this.getSplinePositions( entry, exit, side ); + + const spline = new AutoSpline(); + + SceneService.add( spline.addControlPointAt( nodes.start ) ); + SceneService.add( spline.addControlPointAt( nodes.a2.toVector3() ) ); + SceneService.add( spline.addControlPointAt( nodes.b2.toVector3() ) ); + SceneService.add( spline.addControlPointAt( nodes.end ) ); + + spline.controlPoints.forEach( ( cp: RoadControlPoint ) => cp.allowChange = false ); + + return spline; + } + + findEntryExitSide ( a: JunctionEntryObject, b: JunctionEntryObject ) { + + const isAEntry = + ( a.lane.direction === "forward" && a.contact === TvContactPoint.END ) || + ( a.lane.direction === "backward" && a.contact === TvContactPoint.START ); + + const isBEntry = + ( b.lane.direction === "forward" && b.contact === TvContactPoint.END ) || + ( b.lane.direction === "backward" && b.contact === TvContactPoint.START ); + + const isAExit = + ( a.lane.direction === "forward" && a.contact === TvContactPoint.START ) || + ( a.lane.direction === "backward" && a.contact === TvContactPoint.END ); + + const isBExit = + ( b.lane.direction === "forward" && b.contact === TvContactPoint.START ) || + ( b.lane.direction === "backward" && b.contact === TvContactPoint.END ); + + + if ( isAEntry && isBExit ) { + + return { + entry: a, + exit: b, + side: TvLaneSide.RIGHT + } + + // return [ a, b, LaneSide.LEFT ]; + + } else if ( isAExit && isBEntry ) { + + return { + entry: b, + exit: a, + side: TvLaneSide.RIGHT + } + + // return [ b, a, LaneSide.RIGHT ]; + + } else { + + a.unselect(); + + b.unselect(); + + return; + } + } + + // hasConnection ( junction: OdJunction, a: JunctionEntryObject, b: JunctionEntryObject, result?: any ) { + + // let connectionFound = false; + // let connection: OdJunctionConnection = null; + // let laneLinkFound = false; + // let laneLink: OdJunctionLaneLink = null; + + // this.openDrive.roads.forEach( road => { + + // if ( road.isJunction ) { + + // const connectionFound = + // road.predecessor.elementId === a.road.id && + // road.successor.elementId === b.road.id; + + // const connectingLane = road.getFirstLaneSection().getLaneVector() + // .find( lane => lane.predecessor === a.lane.id && lane.succcessor === b.lane.id ); + + // // const connection = [ ...junction.connections.values() ] + // // .find( i => i.incomingRoad === a.road.id && i.connectingRoad === road.id ); + + // // const linkSide = link.from > 0 ? LaneSide.LEFT : LaneSide.RIGHT; + // // const aSide = a.lane.id > 0 ? LaneSide.LEFT : LaneSide.RIGHT; + // // const laneSideMatches = linkSide === aSide; + + // if ( connectionFound && connectingLane != null ) { + + // // connection and lane both found + + + // } else if ( connectionFound && connectingLane == null ) { + + // // create lane-link + + // } else if ( !connectionFound && connectingLane == null ) { + + // // create connection and lane-link + + // } + + // } + + // } ); + + // return { connectionFound, connection, laneLinkFound, laneLink }; + // } + + // hasConnection ( junction: OdJunction, a: JunctionEntryObject, b: JunctionEntryObject, result?: any ) { + + // let connectionFound = false; + // let connection: OdJunctionConnection = null; + // let laneLinkFound = false; + // let laneLink: OdJunctionLaneLink = null; + + // for ( const item of junction.connections ) { + + // if ( item[ 1 ].incomingRoad === a.road.id ) { + + // const incomingRoad = a.road; + + // const connectingRoad = this.openDrive.getRoadById( item[ 1 ].connectingRoad ); + + // const outgoingRoad = b.road; + + // const outgoingRoadMatches = + // connectingRoad.successor.elementId === outgoingRoad.id || + // connectingRoad.predecessor.elementId === outgoingRoad.id; + + // if ( outgoingRoadMatches ) { + + // for ( const link of item[ 1 ].laneLink ) { + + // const linkSide = link.from > 0 ? LaneSide.LEFT : LaneSide.RIGHT; + + // const aSide = a.lane.id > 0 ? LaneSide.LEFT : LaneSide.RIGHT; + + // const laneSideMatches = linkSide === aSide; + + // if ( laneSideMatches ) { + + // connectionFound = true; + + // connection = result = item[ 1 ]; + + // const sameLaneId = Math.abs( a.lane.id ) === Math.abs( b.lane.id ); + + // // const connectingLane = connectingRoad.getFirstLaneSection().getLaneById( link.to ); + + // const connectingLane = connectingRoad + // .getFirstLaneSection() + // .getLaneVector() + // .find( i => i.predecessor === a.lane.id && i.succcessor === b.lane.id ); + + // laneLinkFound = link.from === a.lane.id && connectingLane != null; + + // laneLink = link; + + // // we dont want to break the loop unless we have found the right link + // if ( laneLinkFound ) break; + // } + // } + // } + + // if ( connectionFound ) break; + // } + // } + + // return { connectionFound, connection, laneLinkFound, laneLink }; + // } + + hasConnection ( junction: TvJunction, a: JunctionEntryObject, b: JunctionEntryObject, result?: any ) { + + let connectionFound = false; + let connection: TvJunctionConnection = null; + let laneLinkFound = false; + let laneLink: TvJunctionLaneLink = null; + + for ( const item of junction.connections ) { + + if ( item[ 1 ].incomingRoad === a.road.id ) { + + const incomingRoad = a.road; + + const connectingRoad = this.openDrive.getRoadById( item[ 1 ].connectingRoad ); + + const outgoingRoad = b.road; + + const outgoingRoadMatches = + connectingRoad.successor.elementId === outgoingRoad.id || + connectingRoad.predecessor.elementId === outgoingRoad.id; + + if ( outgoingRoadMatches ) { + + for ( const link of item[ 1 ].laneLink ) { + + connection = result = item[ 1 ]; + + const connectingLane = connectingRoad + .getFirstLaneSection() + .getLaneVector() + .find( i => i.predecessor === a.lane.id && i.succcessor === b.lane.id ); + + // basically if the same lane link is found only then it will + // return as connection found + // for now we will create a new connection for every lane + // TODO: fix this to combine lane links in 1 connection + connectionFound = laneLinkFound = link.from === a.lane.id && connectingLane != null; + + laneLink = link; + + // we dont want to break the loop unless we have found the right link + if ( laneLinkFound ) break; + } + } + + if ( connectionFound ) break; + } + } + + return { connectionFound, connection, laneLinkFound, laneLink }; + } + + // start position is always at the entry + // end position is always at the exit + getSplinePositions ( entry: JunctionEntryObject, exit: JunctionEntryObject, laneSide: TvLaneSide ) { + + const as = entry.contact === TvContactPoint.START ? 0 : entry.road.length; + const aPosTheta = new TvPosTheta(); + const aPosition = TvMapQueries.getLaneStartPosition( entry.road.id, entry.lane.id, as, 0, aPosTheta ); + + const bs = exit.contact === TvContactPoint.START ? 0 : exit.road.length; + const bPosTheta = new TvPosTheta(); + const bPosition = TvMapQueries.getLaneStartPosition( exit.road.id, exit.lane.id, bs, 0, bPosTheta ); + + let a2: TvPosTheta; + let b2: TvPosTheta; + + const distance = aPosition.distanceTo( bPosition ) * 0.3; + + if ( entry.contact === TvContactPoint.START && exit.contact === TvContactPoint.START ) { + + a2 = aPosTheta.moveForward( -distance ); + b2 = bPosTheta.moveForward( -distance ); + + } else if ( entry.contact === TvContactPoint.START && exit.contact === TvContactPoint.END ) { + + a2 = aPosTheta.moveForward( -distance ); + b2 = bPosTheta.moveForward( +distance ); + + } else if ( entry.contact === TvContactPoint.END && exit.contact === TvContactPoint.END ) { + + a2 = aPosTheta.moveForward( +distance ); + b2 = bPosTheta.moveForward( +distance ); + + } else if ( entry.contact === TvContactPoint.END && exit.contact === TvContactPoint.START ) { + + a2 = aPosTheta.moveForward( +distance ); + b2 = bPosTheta.moveForward( -distance ); + + } + + return { + side: laneSide, + start: aPosition, + startPos: aPosTheta, + end: bPosition, + endPos: bPosTheta, + a2: a2, + b2: b2, + } + } + + findJunction ( a: JunctionEntryObject, b: JunctionEntryObject ) { + + // the nearest junction + let nearestJunction: TvJunction = null; + let nearestDistance = Number.MAX_VALUE; + + this.openDrive.junctions.forEach( junction => { + + if ( !junction.position && junction.connections.size > 0 ) { + + const firstconnection = [ ...junction.connections.values() ][ 0 ] + + const connectionRoad = this.openDrive.getRoadById( firstconnection.connectingRoad ); + + if ( firstconnection.contactPoint === TvContactPoint.START ) { + + junction.position = connectionRoad.startPosition().toVector3(); + + } else { + + junction.position = connectionRoad.endPosition().toVector3(); + + } + + } + + if ( junction.position ) { + + const aDistance = junction.position.distanceTo( a.position ); + const bDistance = junction.position.distanceTo( b.position ); + + if ( aDistance < nearestDistance ) { + + nearestDistance = aDistance; + + nearestJunction = junction; + + } + + if ( bDistance < nearestDistance ) { + + nearestDistance = bDistance; + + nearestJunction = junction; + } + + } + + } ); + + if ( !nearestJunction ) nearestJunction = this.openDrive.addNewJunction(); + + // todo make it the mid point between a and b + nearestJunction.position = a.position; + + return nearestJunction; + } + + createConnections ( + junction: TvJunction, incoming: JunctionEntryObject, connectingRoad: TvRoad, outgoing: JunctionEntryObject + ) { + + this.updateNeighbors( junction, incoming, connectingRoad, outgoing ); + + let connection = [ ...junction.connections.values() ] + .find( i => i.connectingRoad === connectingRoad.id && i.incomingRoad === incoming.road.id ); + + if ( !connection ) { + connection = junction.addNewConnection( incoming.road.id, connectingRoad.id, TvContactPoint.START, outgoing.road.id ); + } + + let connectingLane = connectingRoad.getFirstLaneSection().getLaneById( -Math.abs( incoming.lane.id ) ); + + if ( !connectingLane ) connectingLane = connectingRoad.getFirstLaneSection().getLaneById( -1 ); + + if ( !connectingLane ) throw new Error( "connection lane not found" ); + + if ( !connectingLane ) return; + + const link = connection.addNewLink( incoming.lane.id, connectingLane.id ); + + connectingLane.setPredecessor( incoming.lane.id ); + + connectingLane.setSuccessor( outgoing.lane.id ); + + return { connection, link }; + } + + updateNeighbors ( + junction: TvJunction, + incoming: JunctionEntryObject, + connectingRoad: TvRoad, + outgoing: JunctionEntryObject + ): void { + + connectingRoad.setPredecessor( "road", incoming.road.id, incoming.contact ); + + connectingRoad.setSuccessor( "road", outgoing.road.id, outgoing.contact ); + + if ( incoming.contact === TvContactPoint.START ) { + + incoming.road.setPredecessor( "junction", junction.id ); + + } else if ( incoming.contact === TvContactPoint.END ) { + + incoming.road.setSuccessor( "junction", junction.id ); + + } + + if ( outgoing.contact === TvContactPoint.START ) { + + outgoing.road.setPredecessor( "junction", junction.id ); + + } else if ( outgoing.contact === TvContactPoint.END ) { + + outgoing.road.setSuccessor( "junction", junction.id ); + + } + } + + showJunctionEntries () { + + this.openDrive.roads.forEach( road => { + + if ( !road.isJunction ) { + + if ( !road.predecessor || road.predecessor.elementType === TvElementType.junction ) { + + this.showStartEntries( road ); + + } + + if ( !road.successor || road.successor.elementType === TvElementType.junction ) { + + this.showEndEntries( road ); + + } + } + + } ); + + } + + // showLanePathObjects () { + + // this.openDrive.roads.forEach( road => { + + // if ( road.isJunction ) { + + // road.laneSections.forEach( section => { + + // section.getLaneVector().forEach( lane => { + + // if ( lane.id !== 0 && lane.type === OdLaneType.driving ) { + + // // TODO: pass connection and lane link as well + // const lanePathObject = LanePathFactory.createPathForLane( null, road, lane, null, null ); + + // this.lanePathObjects.push( lanePathObject ); + + // SceneService.add( lanePathObject ); + + // } + + // } ) + + // } ); + + // } + + // } ); + + // } + + showLanePathObjects () { + + this.openDrive.junctions.forEach( junction => { + + junction.connections.forEach( connection => { + + const incomingRoad = this.openDrive.getRoadById( connection.incomingRoad ); + const connectingRoad = this.openDrive.getRoadById( connection.connectingRoad ); + + connection.laneLink.forEach( link => { + + const connectingLane = connectingRoad.getFirstLaneSection().getLaneById( link.to ); + + link.lanePath = LanePathFactory.createPathForLane( incomingRoad, connectingRoad, connectingLane, connection, link ); + + SceneService.add( link.lanePath ); + + } ); + + } ); + + } ); + + } + + showStartEntries ( road: TvRoad ) { + + road.getFirstLaneSection().getLaneVector().forEach( lane => { + + if ( lane.id !== 0 && lane.type === TvLaneType.driving ) { + + this.laneDirectionHelper.drawSingleLane( road, lane ); + + const position = TvMapQueries.getLanePosition( road.id, lane.id, 0 ); + + const name = `road-${ road.id }-lane-${ lane.id }-${ TvContactPoint.START }`; + + const obj = new JunctionEntryObject( name, position, TvContactPoint.START, road, lane ); + + this.junctionEntryObjects.push( obj ); + + SceneService.add( obj ); + + } + + } ); + } + + showEndEntries ( road: TvRoad ) { + + road.getLastLaneSection().getLaneVector().forEach( lane => { + + if ( lane.id !== 0 && lane.type === TvLaneType.driving ) { + + this.laneDirectionHelper.drawSingleLane( road, lane ); + + const position = TvMapQueries.getLanePosition( road.id, lane.id, road.length ); + + const name = `road-${ road.id }-lane-${ lane.id }-${ TvContactPoint.END }`; + + const obj = new JunctionEntryObject( name, position, TvContactPoint.END, road, lane ); + + this.junctionEntryObjects.push( obj ); + + SceneService.add( obj ); + + } + + } ); + } + + hideJunctionEntries () { + + this.junctionEntryObjects.forEach( obj => SceneService.remove( obj ) ); + + this.junctionEntryObjects.splice( 0, this.junctionEntryObjects.length ); + + } + + hideLanePathObjects () { + + this.lanePathObjects.forEach( obj => SceneService.remove( obj ) ); + + this.lanePathObjects.splice( 0, this.lanePathObjects.length ); + + this.openDrive.junctions.forEach( junction => { + + junction.connections.forEach( connection => { + + connection.laneLink.forEach( link => { + + if ( link.lanePath ) SceneService.remove( link.lanePath ); + + } ); + + } ); + + } ); + } +} \ No newline at end of file diff --git a/src/app/core/tools/marking-line-tool.ts b/src/app/core/tools/marking-line-tool.ts new file mode 100644 index 00000000..19b5e687 --- /dev/null +++ b/src/app/core/tools/marking-line-tool.ts @@ -0,0 +1,218 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { BaseMarkingTool } from './marking-point-tool'; +import { AbstractShapeEditor } from '../editors/abstract-shape-editor'; +import { Subscription } from 'rxjs'; +import { TvRoadSignal } from 'app/modules/tv-map/models/tv-road-signal.model'; +import { LineEditor } from '../editors/line-editor'; +import * as THREE from 'three'; +import { + CurvePath, + ExtrudeBufferGeometry, + ExtrudeGeometryOptions, + Mesh, + MeshBasicMaterial, + Shape, + TextureLoader, + Vector2, + Vector3 +} from 'three'; + +export class MarkingLineTool extends BaseMarkingTool { + + name: string = 'MarkingLineTool'; + + private shapeEditor: AbstractShapeEditor; + + private controlPointAddedSubscriber: Subscription; + + private hasSignal = false; + private selectedSignal: TvRoadSignal; + + private cpSubscriptions: Subscription[] = []; + + private mesh: Mesh; + + static texture = new TextureLoader().load( `assets/markings/crosswalk-marking.png` ); + + constructor () { + + super(); + + } + + init () { + + super.init(); + + this.shapeEditor = new LineEditor(); + + // this.createControlPoints(); + + MarkingLineTool.texture.wrapS = THREE.RepeatWrapping; + MarkingLineTool.texture.wrapT = THREE.RepeatWrapping; + MarkingLineTool.texture.mapping = THREE.UVMapping; + MarkingLineTool.texture.repeat.set( 1, 1 ); + MarkingLineTool.texture.anisotropy = 5; + + + } + + enable () { + + super.enable(); + + // this.controlPointAddedSubscriber = this.shapeEditor.controlPointAdded.subscribe( e => this.onControlPointAdded( e ) ); + + this.shapeEditor.curveGeometryAdded.subscribe( e => this.onGeometryAdded( e ) ); + this.shapeEditor.curveGeometryChanged.subscribe( e => this.onGeometryChanged( e ) ); + + } + + disable (): void { + + super.disable(); + + // this.controlPointAddedSubscriber.unsubscribe(); + + this.shapeEditor.destroy(); + + // this.unsubscribeFromControlPoints(); + + } + + getShape (): Shape { + + const shape = new Shape(); + + shape.moveTo( 0, -2.5 ); + + shape.lineTo( 0, 2.5 ); + + return shape; + + } + + getGeometry ( line: THREE.LineCurve3 ) { + + const shape = this.getShape(); + + const path = new CurvePath(); + + path.add( line ); + + const generator = new CustomUvGenerator(); + + const geometry = new ExtrudeBufferGeometry( shape, { + depth: 16, + extrudePath: path, + UVGenerator: generator + } ); + + geometry.attributes.faces; + + return geometry; + } + + onGeometryAdded ( line: THREE.LineCurve3 ) { + + const mesh = this.mesh = new Mesh( this.getGeometry( line ), this.getMaterial() ); + + this.openDrive.gameObject.add( mesh ); + + } + + onGeometryChanged ( line: THREE.LineCurve3 ) { + + if ( this.mesh ) this.openDrive.gameObject.remove( this.mesh ); + + const mesh = this.mesh = new Mesh( this.getGeometry( line ), this.getMaterial() ); + + this.openDrive.gameObject.add( mesh ); + } + + + getMaterial () { + + return new MeshBasicMaterial( { + map: MarkingLineTool.texture, + transparent: true, + alphaTest: 0.1, + wireframe: false + } ); + + } + +} + +export class CustomUvGenerator implements THREE.UVGenerator { + + generateTopUV ( geometry, vertices, indexA, indexB, indexC ) { + + const a_x = vertices[ indexA * 3 ]; + const a_y = vertices[ indexA * 3 + 1 ]; + const b_x = vertices[ indexB * 3 ]; + const b_y = vertices[ indexB * 3 + 1 ]; + const c_x = vertices[ indexC * 3 ]; + const c_y = vertices[ indexC * 3 + 1 ]; + + const res = [ + new Vector2( a_x, a_y ), + new Vector2( b_x, b_y ), + new Vector2( c_x, c_y ) + ]; + + return res; + } + + generateSideWallUV ( geometry, vertices, indexA, indexB, indexC, indexD ) { + + const a_x = vertices[ indexA * 3 ]; + const a_y = vertices[ indexA * 3 + 1 ]; + const a_z = vertices[ indexA * 3 + 2 ]; + const b_x = vertices[ indexB * 3 ]; + const b_y = vertices[ indexB * 3 + 1 ]; + const b_z = vertices[ indexB * 3 + 2 ]; + const c_x = vertices[ indexC * 3 ]; + const c_y = vertices[ indexC * 3 + 1 ]; + const c_z = vertices[ indexC * 3 + 2 ]; + const d_x = vertices[ indexD * 3 ]; + const d_y = vertices[ indexD * 3 + 1 ]; + const d_z = vertices[ indexD * 3 + 2 ]; + + if ( Math.abs( a_y - b_y ) < 0.01 ) { + + return [ + new Vector2( a_x, 1 - a_z ), + new Vector2( b_x, 1 - b_z ), + new Vector2( c_x, 1 - c_z ), + new Vector2( d_x, 1 - d_z ) + ]; + + } else { + + return [ + new Vector2( a_y, 1 - a_z ), + new Vector2( b_y, 1 - b_z ), + new Vector2( c_y, 1 - c_z ), + new Vector2( d_y, 1 - d_z ) + ]; + + } + + } + +} + + +export class ExtrudePlaneGeometry { + + constructor ( private shapes: Shape | Shape[], private options?: ExtrudeGeometryOptions ) { + + // const path = options ? options.extrudePath : ; + + } + +} \ No newline at end of file diff --git a/src/app/core/tools/marking-point-tool.ts b/src/app/core/tools/marking-point-tool.ts new file mode 100644 index 00000000..631409c1 --- /dev/null +++ b/src/app/core/tools/marking-point-tool.ts @@ -0,0 +1,244 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { BaseTool } from './base-tool'; +import { Subscription } from 'rxjs'; +import { TvRoadSignal } from 'app/modules/tv-map/models/tv-road-signal.model'; +import { AbstractShapeEditor } from '../editors/abstract-shape-editor'; +import { PointEditor } from '../editors/point-editor'; +import { PointerEventData } from 'app/events/pointer-event-data'; +import { TvObjectType } from 'app/modules/tv-map/interfaces/i-tv-object'; +import { Mesh, MeshBasicMaterial, Object3D, PlaneBufferGeometry, TextureLoader } from 'three'; +import { OdSignalInspectorComponent } from 'app/views/inspectors/signal-inspector/signal-inspector.component'; +import { AnyControlPoint } from 'app/modules/three-js/objects/control-point'; +import { TvPosTheta } from 'app/modules/tv-map/models/tv-pos-theta'; +import { TvOrientation } from 'app/modules/tv-map/models/tv-common'; +import { SnackBar } from 'app/services/snack-bar.service'; +import { MarkingTypes, TvMarkingService, TvRoadMarking } from 'app/modules/tv-map/services/tv-marking.service'; +import { TvRoadObject } from 'app/modules/tv-map/models/tv-road-object'; +import { TvMapQueries } from '../../modules/tv-map/queries/tv-map-queries'; + +export abstract class BaseMarkingTool extends BaseTool { + +} + +export class MarkingPointTool extends BaseMarkingTool { + + name: string = 'MarkingPointTool'; + + private shapeEditor: AbstractShapeEditor; + private hasSignal = false; + private selectedSignal: TvRoadSignal; + private cpSubscriptions: Subscription[] = []; + + private controlPointAddedSubscriber: Subscription; + private controlPointSelectedSubscriber: Subscription; + + private currentMarking: TvRoadMarking; + + constructor () { + + super(); + + } + + init () { + + super.init(); + + this.shapeEditor = new PointEditor(); + + this.createControlPoints(); + + } + + enable () { + + super.enable(); + + this.controlPointAddedSubscriber = this.shapeEditor.controlPointAdded.subscribe( e => this.onControlPointAdded( e ) ); + this.controlPointSelectedSubscriber = this.shapeEditor.controlPointSelected.subscribe( e => this.onControlPointSelected( e ) ); + + } + + disable (): void { + + super.disable(); + + this.controlPointAddedSubscriber.unsubscribe(); + + this.shapeEditor.destroy(); + + this.unsubscribeFromControlPoints(); + + } + + get marking () { + + return TvMarkingService.currentMarking; + + } + + // onPointerDown ( e: PointerEventData ) { + + // super.onPointerDown( e ); + + // this.hasSignal = false; + + // for ( const i of e.intersections ) { + + // if ( i.object[ 'OpenDriveType' ] != null && i.object[ 'OpenDriveType' ] == TvObjectType.SIGNAL ) { + + // this.hasSignal = true; + + // this.inspectSignal( i.object ); + + // break; + + // } + // } + + // if ( !this.hasSignal ) { + + // this.clearInspector(); + + // } + // } + + // private inspectSignal ( object: Object3D ) { + + // this.selectedSignal = ( object.userData.data as TvRoadSignal ); + + // this.setInspector( OdSignalInspectorComponent, this.selectedSignal ); + // } + + private onControlPointAdded ( point: AnyControlPoint ) { + + if ( !this.marking ) SnackBar.error( "Select a marking from project browser" ); + + if ( !this.marking ) return; + + const pose = new TvPosTheta(); + + pose.x = point.position.x; + + pose.y = point.position.y; + + const road = TvMapQueries.getRoadByCoords( pose.x, pose.y, pose ); + + if ( !road ) SnackBar.error( "Marking can be added only on road mesh" ); + + if ( !road ) this.shapeEditor.removeControlPoint( point ); + + if ( !road ) return; + + if ( this.marking && this.marking.type === MarkingTypes.point ) { + + // const id = road.getRoadObjectCount() + 1; + + // const marking = this.marking.name; + + // const texture = new TextureLoader().load( `assets/markings/${ marking }.png` ); + + // const material = new MeshBasicMaterial( { map: texture, alphaTest: 0.1 } ); + + // const geometry = new PlaneBufferGeometry( 1, 1 ); + + // const mesh = new Mesh( geometry, material ); + + const marking = point.mainObject = this.marking.clone(); + + marking.mesh.position.setX( point.position.x ); + + marking.mesh.position.setY( point.position.y ); + + this.openDrive.gameObject.add( marking.mesh ); + + // const roadObject = new TvRoadObject( 'marking', 'arrow-forward', id, pose.s, pose.t, 0, 0, TvOrientation.MINUS ); + + // roadObject.mesh = mesh; + + this.sync( point, marking ); + + // road.addRoadObjectInstance( roadObject ); + + } else { + + this.shapeEditor.removeControlPoint( point ); + + SnackBar.show( 'Please select a sign first' ); + + } + + } + + private onControlPointSelected ( point: AnyControlPoint ) { + + console.log( point.mainObject ); + + } + + private onConrolPointUpdated ( point: AnyControlPoint ) { + + if ( point.mainObject instanceof TvRoadMarking ) { + + this.currentMarking = point.mainObject; + + point.mainObject.mesh.position.setX( point.position.x ); + + point.mainObject.mesh.position.setY( point.position.y ); + + } + + } + + private sync ( point: AnyControlPoint, object: TvRoadMarking ): void { + + // const subscription = point.updated.subscribe( e => { + + // object.mesh.position.setX( e.position.x ); + + // object.mesh.position.setY( e.position.y ); + + // } ); + + // this.cpSubscriptions.push( subscription ); + } + + private createControlPoints () { + + // this.forEachRoadObject( object => { + + // const cp = this.shapeEditor.addControlPoint( object.mesh.position ); + + // this.sync( cp, object ); + + // } ); + + } + + private unsubscribeFromControlPoints () { + + this.cpSubscriptions.forEach( sub => { + + sub.unsubscribe(); + + } ); + + } + + private forEachRoadObject ( callback: ( object: TvRoadObject ) => void ) { + + // this.openDrive.roads.forEach( road => { + + // road.objects.object.forEach( object => { + + // if ( object.mesh ) callback( object ); + + // } ); + + // } ); + + } +} diff --git a/src/app/core/tools/misc-shape-tools.ts b/src/app/core/tools/misc-shape-tools.ts new file mode 100644 index 00000000..f0dfa6d8 --- /dev/null +++ b/src/app/core/tools/misc-shape-tools.ts @@ -0,0 +1,70 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { BaseRoadPlanTool } from './base-road-plan-tool'; +import { AnyControlPoint } from 'app/modules/three-js/objects/control-point'; +import { CatmullRomCurve3, BufferGeometry, LineBasicMaterial, Line, Vector2, Vector3, CubicBezierCurve3 } from 'three'; +import { SceneService } from '../services/scene.service'; +import { CubicSplineCurve } from '../shapes/cubic-spline-curve'; + +export class MiscShapeTool extends BaseRoadPlanTool { + + private cps: AnyControlPoint[] = []; + + private curve: any; + private line: any; + + init () { + + super.init(); + + } + + reraw () { + + if (this.cps.length < 4) return; + + if ( this.line != null ) SceneService.remove( this.line, false ); + + const positions: Vector3[] = this.cps.map( value => value.position ); + + // Create a sine-like wave + this.curve = new CubicBezierCurve3( positions[0], positions[1], positions[2], positions[3] ); + + const points = this.curve.getPoints( 50 ); + + const geometry = new BufferGeometry().setFromPoints( points ); + + const material = new LineBasicMaterial( { color: 0xff0000 } ); + + // Create the final object to add to the scene + this.line = new Line( geometry, material ); + + this.line.renderOrder = 3; + + SceneService.add( this.line, false ); + + } + + protected onControlPointSelected ( cp: AnyControlPoint ) { + + } + + protected onControlPointAdded ( cp: AnyControlPoint ) { + + this.cps.push( cp ); + + // this.curve.points.push( cp.position ); + + this.reraw(); + + } + + protected onControlPointMoved ( e: AnyControlPoint ) { + + this.reraw(); + + } + +} diff --git a/src/app/core/tools/parking-box-tool.ts b/src/app/core/tools/parking-box-tool.ts new file mode 100644 index 00000000..37d0f322 --- /dev/null +++ b/src/app/core/tools/parking-box-tool.ts @@ -0,0 +1,138 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { BaseTool } from './base-tool'; +import { BoxCreatedEvent, BoxEditor } from '../editors/box-editor'; +import { TvRoadObject } from 'app/modules/tv-map/models/tv-road-object'; +import { TvOrientation } from 'app/modules/tv-map/models/tv-common'; +import { TvPosTheta } from 'app/modules/tv-map/models/tv-pos-theta'; +import { PointerEventData } from 'app/events/pointer-event-data'; +import { Object3D } from 'three'; +import { CommandHistory } from 'app/services/command-history'; +import { AddRoadObjectCommand } from 'app/core/commands/add-road-object-command'; +import { Subscription } from 'rxjs'; +import { RoadObjectInspectorComponent } from 'app/views/inspectors/road-object-inspector/road-object-inspector.component'; +import { TvMapQueries } from '../../modules/tv-map/queries/tv-map-queries'; + +export abstract class BaseParkingTool extends BaseTool { + +} + +export class ParkingBoxTool extends BaseParkingTool { + + name = 'ParkingBoxTool'; + + init () { + + super.init(); + + this.shapeEditor = new BoxEditor(); + + } + + enable () { + + super.enable(); + + this.boxCreatedSub = this.shapeEditor.boxCreated.subscribe( e => this.onBoxCreated( e ) ); + + } + + disable (): void { + + super.disable(); + + this.boxCreatedSub.unsubscribe(); + + this.shapeEditor.destroy(); + } + + shapeEditor: BoxEditor; + boxCreatedSub: Subscription; + selectedBox: Object3D; + selectedObject: TvRoadObject; + objects: TvRoadObject[] = []; + + onBoxCreated ( e: BoxCreatedEvent ) { + + const position = e.mesh.position; + + const theta = new TvPosTheta(); + + const road = TvMapQueries.getRoadByCoords( position.x, position.y, theta ); + + const type = 'parkingSpace'; + const name = ''; + const id = 1; + const s = theta.s; + const t = theta.t; + const z = 0; + const validLength = 0.0; + const orientation = TvOrientation.NONE; + const length = e.length; + const width = e.width; + const height = e.height; + const radius = null; + const hdg = 0; + const pitch = 0; + const roll = 0; + + const object = new TvRoadObject( + type, name, id, s, t, z, validLength, orientation, length, width, radius, height, hdg, pitch, roll + ); + + object.mesh = e.mesh; + + this.objects.push( object ); + + CommandHistory.execute( new AddRoadObjectCommand( road.id, object ) ); + + } + + onPointerClicked ( e: PointerEventData ) { + + let found = false; + + for ( const intersection of e.intersections ) { + + for ( const object of this.objects ) { + + if ( object.mesh.id === intersection.object.id ) { + + this.selectedBox = intersection.object; + + this.selectedObject = object; + + // AppService.three.select( i.object ); + + found = true; + + // this.shapeEditor.disable(); + + this.setInspector( RoadObjectInspectorComponent, object ); + + break; + } + + } + + } + + if ( !found ) { + + this.selectedBox = null; + + this.selectedObject = null; + + this.clearInspector(); + + // AppService.three.deselect(); + + // this.shapeEditor.enable(); + } + + + } + +} diff --git a/src/app/core/tools/prop-curve-tool.ts b/src/app/core/tools/prop-curve-tool.ts new file mode 100644 index 00000000..8228c9d3 --- /dev/null +++ b/src/app/core/tools/prop-curve-tool.ts @@ -0,0 +1,279 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { BaseTool } from './base-tool'; +import { PointEditor } from '../editors/point-editor'; +import { AnyControlPoint } from 'app/modules/three-js/objects/control-point'; +import { Subscription } from 'rxjs'; +import { AppInspector } from '../inspector'; +import { PointerEventData } from 'app/events/pointer-event-data'; +import { KeyboardInput } from '../input'; +import { PropCurve } from "app/modules/tv-map/models/prop-curve"; +import { + PropCurveInspectorComponent, + PropCurveInspectorData +} from 'app/views/inspectors/prop-curve-inspector/prop-curve-inspector.component'; +import { PropService } from 'app/services/prop-service'; +import { SnackBar } from 'app/services/snack-bar.service'; +import { PropModel } from '../models/prop-model.model'; + +export class PropCurveTool extends BaseTool { + + public name: string = 'PropCurveTool'; + + public shapeEditor: PointEditor; + + private cpSubscriptions: Subscription[] = []; + + private cpAddedSub: Subscription; + private cpMovedSub: Subscription; + private cpUpdatedSub: Subscription; + private cpSelectedSub: Subscription; + private cpUnselectedSub: Subscription; + + // private splines: ParmetricSpline[] = []; + // private selectedSpline: ParmetricSpline; + + private curve: PropCurve; + private point: AnyControlPoint; + + private get curves () { return this.openDrive.propCurves; } + + private get spline () { return this.curve ? this.curve.spline : null; } + + constructor () { + + super(); + + } + + private get prop (): PropModel { + + const prop = PropService.getProp(); + + if ( prop ) { + + return new PropModel( prop.guid, prop.data.rotationVariance, prop.data.scaleVariance ); + + } + + } + + public init () { + + super.init(); + + this.shapeEditor = new PointEditor( 100 ); + } + + public enable () { + + super.enable(); + + this.curves.forEach( curve => { + + curve.spline.show(); + + curve.spline.controlPoints.forEach( point => { + + this.shapeEditor.controlPoints.push( point ); + + } ); + + } ); + + this.cpAddedSub = this.shapeEditor.controlPointAdded + .subscribe( ( cp: AnyControlPoint ) => this.onControlPointAdded( cp ) ); + + this.cpMovedSub = this.shapeEditor.controlPointMoved + .subscribe( ( cp: AnyControlPoint ) => this.onControlPointMoved( cp ) ); + + this.cpUpdatedSub = this.shapeEditor.controlPointUpdated + .subscribe( ( cp: AnyControlPoint ) => this.onControlPointUpdated( cp ) ); + + this.cpSelectedSub = this.shapeEditor.controlPointSelected + .subscribe( ( cp: AnyControlPoint ) => this.onControlPointSelected( cp ) ); + + this.cpUnselectedSub = this.shapeEditor.controlPointUnselected + .subscribe( ( cp: AnyControlPoint ) => this.onControlPointUnselected() ); + + } + + public disable (): void { + + super.disable(); + + this.curves.forEach( curve => curve.spline.hide() ); + + this.cpAddedSub.unsubscribe(); + this.cpMovedSub.unsubscribe(); + this.cpUpdatedSub.unsubscribe(); + this.cpSelectedSub.unsubscribe(); + this.cpUnselectedSub.unsubscribe(); + + this.shapeEditor.destroy(); + + this.unsubscribeFromControlPoints(); + } + + public onPointerClicked ( e: PointerEventData ) { + + let hasInteracted = false; + + // first check for any point intersections + for ( const i of e.intersections ) { + + if ( i.object != null && i.object.type === 'Points' ) { + + hasInteracted = true; + + this.point = ( i.object as AnyControlPoint ); + + this.curve = this.point.mainObject as PropCurve; + + this.showInspector( this.curve, this.point ); + + break; + } + + } + + // if not point intersections then check for curve intersections + for ( const i of e.intersections ) { + + if ( i.object != null && i.object[ 'tag' ] === 'curve' && !hasInteracted ) { + + hasInteracted = true; + + this.curve = i.object.userData.parent; + + this.point = null; + + this.showInspector( this.curve, this.point ); + + break; + + } + } + + if ( hasInteracted ) return; + + // Finally, If no objects were intersected then clear the inspector + if ( !KeyboardInput.isShiftKeyDown ) { + + // unselect the curve in second click + if ( !this.point ) { + + this.curve = null; + + this.showInspector( this.curve, this.point ); + + } else { + + // in first click, remove focus from control point and hide tangent + this.point = null; + + this.showInspector( this.curve, this.point ); + } + } + } + + private onControlPointSelected ( cp: AnyControlPoint ) { + + this.point = cp; + + if ( cp.mainObject ) { + + this.curve = cp.mainObject; + + this.showInspector( this.curve, this.point ); + + } + } + + private onControlPointUnselected () { + + } + + private showInspector ( curve: PropCurve, point: AnyControlPoint ) { + + if ( curve == null && point == null ) { + + AppInspector.clear(); + + } else { + + const data = new PropCurveInspectorData( point, curve ); + + AppInspector.setInspector( PropCurveInspectorComponent, data ); + } + } + + private onControlPointAdded ( cp: AnyControlPoint ) { + + if ( !this.prop ) SnackBar.error( "Select a prop from the project browser" ); + + if ( !this.prop ) this.shapeEditor.removeControlPoint( cp ); + + if ( !this.prop ) return; + + if ( !this.curve ) { + + if ( this.spline ) this.spline.hide(); + + this.curve = new PropCurve( this.prop.guid ); + + this.curves.push( this.curve ); + } + + cp.mainObject = this.curve; + + this.curve.addControlPoint( cp ); + + this.showInspector( this.curve, this.point ); + + this.updateCurveProps(); + } + + private onControlPointUpdated ( cp: AnyControlPoint ) { + + if ( cp.mainObject ) { + + this.curve = cp.mainObject; + + this.curve.update(); + + this.updateCurveProps(); + + } + } + + private onControlPointMoved ( cp: AnyControlPoint ) { + + if ( cp.mainObject ) { + + this.curve = cp.mainObject; + + this.curve.update(); + + } + + } + + private unsubscribeFromControlPoints () { + + this.cpSubscriptions.forEach( sub => { + + sub.unsubscribe(); + + } ); + + } + + private updateCurveProps () { + + PropService.updateCurveProps( this.curve ); + + } +} \ No newline at end of file diff --git a/src/app/core/tools/prop-point-tool.ts b/src/app/core/tools/prop-point-tool.ts new file mode 100644 index 00000000..63e000e8 --- /dev/null +++ b/src/app/core/tools/prop-point-tool.ts @@ -0,0 +1,168 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { BaseTool } from './base-tool'; +import { PointEditor } from '../editors/point-editor'; +import { AnyControlPoint } from 'app/modules/three-js/objects/control-point'; +import { Subscription } from 'rxjs'; +import { TvMapQueries } from 'app/modules/tv-map/queries/tv-map-queries'; +import { SnackBar } from 'app/services/snack-bar.service'; +import { AppInspector } from '../inspector'; +import { PropInstance } from '../models/prop-instance.model'; +import { InspectorFactoryService, InspectorType } from '../factories/inspector-factory.service'; +import { ProjectBrowserService } from 'app/views/editor/project-browser/project-browser.service'; +import { ModelImporterService } from 'app/services/model-importer.service'; +import { PropService } from 'app/services/prop-service'; +import { AssetDatabase } from 'app/services/asset-database'; + +/** + * Prop point tool + * + * Steps + * 1. Select a prop (fbx, gltf) from library browser + * 2. SHIFT + LEFT-CLICK to drop it in the scene + * + * Requires an instance of prop to be able to drop them in the scene + * + * + */ +export class PropPointTool extends BaseTool { + + public name: string = 'PropPointTool'; + + public shapeEditor: PointEditor; + + private cpSubscriptions: Subscription[] = []; + + private controlPointAddedSubscriber: Subscription; + private controlPointSelectedSubscriber: Subscription; + private controlPointUnselectedSubscriber: Subscription; + + constructor ( private modelImporter?: ModelImporterService ) { + + super(); + + } + + get prop (): PropInstance { + + const prop = PropService.getProp(); + + if ( prop ) { + + return new PropInstance( prop.guid, AssetDatabase.getInstance( prop.guid ) ); + + } + + } + + init () { + + super.init(); + + this.shapeEditor = new PointEditor( 100 ); + + } + + enable () { + + super.enable(); + + this.openDrive.props.forEach( ( prop: PropInstance ) => { + + const cp = this.shapeEditor.addControlPoint( prop.object.position ); + + this.sync( cp, prop ); + + } ); + + this.controlPointAddedSubscriber = this.shapeEditor.controlPointAdded.subscribe( + + point => this.onControlPointAdded( point ) + + ); + + this.controlPointSelectedSubscriber = this.shapeEditor.controlPointSelected.subscribe( + + point => this.onControlPointSelected( point ) + + ); + + this.controlPointUnselectedSubscriber = this.shapeEditor.controlPointUnselected.subscribe( + + point => this.onControlPointUnselected( point ) + + ); + + } + + disable (): void { + + super.disable(); + + this.controlPointAddedSubscriber.unsubscribe(); + this.controlPointSelectedSubscriber.unsubscribe(); + this.controlPointUnselectedSubscriber.unsubscribe(); + + this.shapeEditor.destroy(); + + this.unsubscribeFromControlPoints(); + } + + private onControlPointSelected ( point: AnyControlPoint ) { + + InspectorFactoryService.setByType( InspectorType.prop_instance_inspector, point.mainObject ); + + } + + private onControlPointUnselected ( point: AnyControlPoint ) { + + AppInspector.clear(); + + } + + private onControlPointAdded ( point: AnyControlPoint ) { + + if ( !this.prop ) SnackBar.error( "Select a prop from the project browser" ); + + if ( !this.prop ) this.shapeEditor.removeControlPoint( point ); + + if ( !this.prop ) return; + + const prop = new PropInstance( this.prop.guid, this.prop.object.clone() ); + + prop.object.position.set( point.position.x, point.position.y, point.position.z ); + + this.sync( point, prop ); + + this.openDrive.gameObject.add( prop.object ); + + TvMapQueries.openDrive.props.push( prop ); + + } + + private sync ( point: AnyControlPoint, prop: PropInstance ): void { + + const subscription = point.updated.subscribe( e => { + + prop.object.position.copy( e.position ); + + } ); + + point.mainObject = prop; + + this.cpSubscriptions.push( subscription ); + } + + private unsubscribeFromControlPoints () { + + this.cpSubscriptions.forEach( sub => { + + sub.unsubscribe(); + + } ); + + } + +} \ No newline at end of file diff --git a/src/app/core/tools/prop-polygon-tool.ts b/src/app/core/tools/prop-polygon-tool.ts new file mode 100644 index 00000000..30a1e643 --- /dev/null +++ b/src/app/core/tools/prop-polygon-tool.ts @@ -0,0 +1,225 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { BaseTool } from './base-tool'; +import { PointEditor } from '../editors/point-editor'; +import { AnyControlPoint } from 'app/modules/three-js/objects/control-point'; +import { Subscription } from 'rxjs'; +import { PointerEventData } from 'app/events/pointer-event-data'; +import { KeyboardInput } from '../input'; +import { PropPolygon } from 'app/modules/tv-map/models/prop-polygons'; +import { PropService } from 'app/services/prop-service'; +import { SnackBar } from 'app/services/snack-bar.service'; +import { AppInspector } from '../inspector'; +import { PropPolygonInspectorData, PropPolygonInspectorComponent } from 'app/views/inspectors/prop-polygon-inspector/prop-polygon-inspector.component'; + +export class PropPolygonTool extends BaseTool { + + public name: string = 'PropPolygonTool'; + + public shapeEditor: PointEditor; + + private cpSubscriptions: Subscription[] = []; + + private cpAddedSub: Subscription; + private cpMovedSub: Subscription; + private cpUpdatedSub: Subscription; + private cpSelectedSub: Subscription; + private cpUnselectedSub: Subscription; + private keyDownSub: Subscription; + + private polygon: PropPolygon; + private point: AnyControlPoint; + + constructor () { + + super(); + + } + + public init () { + + super.init(); + + this.shapeEditor = new PointEditor( 100 ); + } + + public enable () { + + super.enable(); + + this.openDrive.propPolygons.forEach( polygon => { + + polygon.showControlPoints(); + + polygon.showCurve(); + + polygon.spline.controlPoints.forEach( cp => { + + cp.mainObject = polygon; + + this.shapeEditor.controlPoints.push( cp ) + + } ) + } ); + + this.keyDownSub = KeyboardInput.keyDown + .subscribe( e => this.onDeletePressed( e ) ); + + this.cpAddedSub = this.shapeEditor.controlPointAdded + .subscribe( ( cp: AnyControlPoint ) => this.onControlPointAdded( cp ) ); + + this.cpMovedSub = this.shapeEditor.controlPointMoved + .subscribe( () => this.onControlPointMoved() ); + + this.cpUpdatedSub = this.shapeEditor.controlPointUpdated + .subscribe( () => this.onControlPointUpdated() ); + + this.cpSelectedSub = this.shapeEditor.controlPointSelected + .subscribe( ( cp: AnyControlPoint ) => this.onControlPointSelected( cp ) ); + + this.cpUnselectedSub = this.shapeEditor.controlPointUnselected + .subscribe( () => this.onControlPointUnselected() ); + + } + + public disable (): void { + + super.disable(); + + this.openDrive.propPolygons.forEach( polygon => { + + polygon.hideCurve(); + polygon.hideControlPoints(); + + } ); + + this.keyDownSub.unsubscribe(); + this.cpAddedSub.unsubscribe(); + this.cpMovedSub.unsubscribe(); + this.cpUpdatedSub.unsubscribe(); + this.cpSelectedSub.unsubscribe(); + this.cpUnselectedSub.unsubscribe(); + + this.shapeEditor.destroy(); + } + + public onPointerClicked ( e: PointerEventData ) { + + for ( let i = 0; i < e.intersections.length; i++ ) { + + const intersection = e.intersections[ i ]; + + if ( intersection.object && intersection.object[ 'tag' ] === PropPolygon.tag ) { + + this.polygon = intersection.object.userData.polygon; + + this.polygon.showControlPoints(); + + this.showInspector( this.polygon ); + + break; + } + } + } + + private onControlPointSelected ( cp: AnyControlPoint ) { + + this.point = cp; + + this.polygon = cp.mainObject; + + this.showInspector( this.polygon, this.point ); + } + + private onControlPointUnselected () { + + this.polygon = null; + + this.point = null; + + } + + private onControlPointAdded ( cp: AnyControlPoint ) { + + const prop = PropService.getProp(); + + if ( !prop ) SnackBar.error( "Select a prop from the project browser" ); + + if ( !prop ) this.shapeEditor.removeControlPoint( cp ); + + if ( !prop ) return; + + if ( !this.polygon ) { + + this.polygon = new PropPolygon( prop.guid ); + + this.openDrive.propPolygons.push( this.polygon ); + + } + + this.point = cp; + + cp.mainObject = this.polygon; + + this.polygon.spline.addControlPoint( cp ); + + this.polygon.update(); + + this.showInspector( this.polygon, this.point ); + } + + private onControlPointUpdated () { + + this.polygon.update(); + + this.showInspector( this.polygon, this.point ); + } + + private onControlPointMoved () { + + this.polygon.spline.update() + + this.showInspector( this.polygon, this.point ); + + } + + private onDeletePressed ( e: KeyboardEvent ) { + + if ( e.key === "Delete" && this.polygon ) { + + this.polygon.delete(); + + const index = this.openDrive.surfaces.findIndex( s => s.id === this.polygon.id ); + + if ( index > -1 ) { + + this.openDrive.surfaces.splice( index, 1 ); + + } + + this.polygon = null; + + delete this.polygon; + } + + } + + private showInspector ( polygon?: PropPolygon, point?: AnyControlPoint ) { + + if ( polygon == null && point == null ) { + + AppInspector.clear(); + + } else { + + const data = new PropPolygonInspectorData( point, polygon ); + + AppInspector.setInspector( PropPolygonInspectorComponent, data ); + } + } + + + +} diff --git a/src/app/core/tools/road-sign-tool.ts b/src/app/core/tools/road-sign-tool.ts new file mode 100644 index 00000000..bd38f594 --- /dev/null +++ b/src/app/core/tools/road-sign-tool.ts @@ -0,0 +1,246 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { BaseTool } from './base-tool'; +import { PointerEventData } from 'app/events/pointer-event-data'; +import { Object3D, Points } from 'three'; +import { TvObjectType } from '../../modules/tv-map/interfaces/i-tv-object'; +import { TvRoadSignal } from '../../modules/tv-map/models/tv-road-signal.model'; +import { OdSignalInspectorComponent } from '../../views/inspectors/signal-inspector/signal-inspector.component'; +import { AbstractShapeEditor } from '../editors/abstract-shape-editor'; +import { PointEditor } from '../editors/point-editor'; +import { OdSignalBuilder } from '../../modules/tv-map/builders/od-signal-builder'; +import { TvPosTheta } from '../../modules/tv-map/models/tv-pos-theta'; +import { SnackBar } from '../../services/snack-bar.service'; +import { TvSignService } from '../../modules/tv-map/services/tv-sign.service'; +import { CommandHistory } from '../../services/command-history'; +import { AddSignalCommand } from '../commands/add-signal-command'; +import { TvDynamicTypes, TvOrientation } from '../../modules/tv-map/models/tv-common'; +import { SceneService } from '../services/scene.service'; +import { Subscription } from 'rxjs'; +import { AnyControlPoint } from '../../modules/three-js/objects/control-point'; +import { TvMapQueries } from '../../modules/tv-map/queries/tv-map-queries'; + +export class RoadSignTool extends BaseTool { + + name: string = 'RoadSignTool'; + + init () { + + super.init(); + + this.showControlPoints(); + + } + + disable (): void { + + super.disable(); + + this.controlPointAddedSubscriber.unsubscribe(); + this.controlPointMovedSubscriber.unsubscribe(); + + this.shapeEditor.destroy(); + + this.hideControlPoints(); + } + + private signFactory = new OdSignalBuilder(); + private controlPointAddedSubscriber: Subscription; + private hasSignal = false; + private selectedSignal: TvRoadSignal; + private shapeEditor: AbstractShapeEditor; + private controlPointMovedSubscriber: Subscription; + + constructor ( private signService: TvSignService ) { + + super(); + + this.shapeEditor = new PointEditor(); + + this.controlPointAddedSubscriber = this.shapeEditor.controlPointAdded.subscribe( e => this.onControlPointAdded( e ) ); + this.controlPointMovedSubscriber = this.shapeEditor.controlPointMoved.subscribe( e => this.onControlPointMoved( e ) ); + } + + get sign () { + return this.signService.currentSign; + } + + onPointerDown ( e: PointerEventData ) { + + super.onPointerDown( e ); + + this.hasSignal = false; + + for ( const i of e.intersections ) { + + if ( i.object[ 'OpenDriveType' ] != null && i.object[ 'OpenDriveType' ] == TvObjectType.SIGNAL ) { + + this.hasSignal = true; + + this.inspectSignal( i.object ); + + break; + + } + } + + if ( !this.hasSignal ) { + + this.clearInspector(); + + } + } + + private inspectSignal ( object: Object3D ) { + + this.selectedSignal = ( object.userData.data as TvRoadSignal ); + + this.setInspector( OdSignalInspectorComponent, this.selectedSignal ); + } + + private onControlPointAdded ( point: AnyControlPoint ) { + + const pose = new TvPosTheta(); + + pose.x = point.position.x; + pose.y = point.position.y; + + if ( this.sign ) { + + const road = TvMapQueries.getRoadByCoords( pose.x, pose.y, pose ); + + if ( road == null ) throw new Error( 'Could not find any road' ); + + const id = road.getRoadSignalCount() + 1; + + const signal = new TvRoadSignal( pose.s, pose.t, id, '', TvDynamicTypes.NO, TvOrientation.MINUS ); + + signal.roadId = road.id; + signal.height = 4; + signal.controlPoint = point; + signal.signShape = this.sign.shape; + signal.name = this.sign.name; + signal.text = this.sign.name; + + signal.addUserData( 'asset_name', this.sign.name ); + signal.addUserData( 'sign_shape', this.sign.shape as string ); + + CommandHistory.execute( new AddSignalCommand( signal ) ); + + } else { + + this.shapeEditor.removeControlPoint( point ); + + SnackBar.show( 'Please select a sign first' ); + + } + + } + + + // OLD CODE JUST FOR REFERENCE + // const ray = new Ray(); + // + // ray.origin.copy( e.position ); + // + // ray.direction.copy( pose.toDirectionVector() ); + // + // // console.log( pose, ray ); + // + // SceneService.add( new ArrowHelper( ray.direction, ray.origin, 300, 0xff0000 ) ); + // + // // e.add( signal.GameObject ); + // + // // const p1 = pose.toVector3(); + // // const p2 = p1.clone().add( pose.toDirectionVector().multiplyScalar( 10 ) ); + // + // // console.log( p1, p2 ); + // + // // console.log( signal.GameObject.position.dot( ray.direction ) ); + // // console.log( this.isLeft( p1, p2, signal.GameObject.position ) ); + // + // // const tmp1 = new OdPosTheta(); + // // const tmp2 = new OdPosTheta(); + // // + // // road.getGeometryCoords( pose.s, tmp1 ); + // // road.getGeometryCoords( pose.s + Maths.Epsilon, tmp2 ); + // // + // // this.directionOfPoint( tmp1.toVector3(), tmp2.toVector3(), e.position ); + // + // const a = pose.toVector3(); + // const b = e.position; + // + // var dot = a.x * b.x + a.y * b.y; + // if ( dot > 0 ) + // console.log( '<90 degrees' ); + // else if ( dot < 0 ) + // console.log( '>90 degrees' ); + // else + // console.log( '90 degrees' ); + + + private showControlPoints () { + + this.forEachControlPoint( controlPoint => { + + // add control points to shape editor to sync and avoid errors + this.shapeEditor.controlPoints.push( controlPoint ); + + controlPoint.visible = true; + + SceneService.add( controlPoint ); + + } ); + + } + + private hideControlPoints () { + + this.forEachControlPoint( controlPoint => { + + controlPoint.visible = false; + + SceneService.remove( controlPoint ); + + } ); + + } + + private onControlPointMoved ( point: Points ) { + + for ( const road of this.openDrive.roads ) { + + for ( const map of road[ 1 ].signals ) { + + const signal = map[ 1 ]; + + if ( signal.controlPoint && signal.controlPoint.id == point.id ) { + + signal.gameObject.position.copy( point.position ); + + break; + + } + + } + + } + + } + + private forEachControlPoint ( callback: ( controlPoint: AnyControlPoint ) => void ) { + + this.openDrive.roads.forEach( road => { + + road.signals.forEach( signal => { + + if ( signal.controlPoint ) callback( signal.controlPoint ); + + } ); + + } ); + + } +} diff --git a/src/app/core/tools/road-tool.spec.ts b/src/app/core/tools/road-tool.spec.ts new file mode 100644 index 00000000..c5252429 --- /dev/null +++ b/src/app/core/tools/road-tool.spec.ts @@ -0,0 +1,199 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { TvMap } from 'app/modules/tv-map/models/tv-map.model'; +import { NodeFactoryService } from '../factories/node-factory.service'; +import { TvRoad } from 'app/modules/tv-map/models/tv-road.model'; +import { Vector3 } from 'three'; +import { RoadTool } from './road-tool'; +import { TvMapSourceFile } from 'app/modules/tv-map/services/tv-map-source-file'; +import { RoadFactory } from '../factories/road-factory.service'; +import { RoadControlPoint } from 'app/modules/three-js/objects/road-control-point'; +import { TvContactPoint, TvRoadType } from 'app/modules/tv-map/models/tv-common'; + +describe( 'RoadTool', () => { + + let openDrive: TvMap; + + let roadTool: RoadTool; + + beforeEach( () => { + + openDrive = TvMapSourceFile.openDrive = new TvMap(); + + roadTool = new RoadTool(); + + } ) + + it( 'should maintain hdg for connected roads', () => { + + // c is th joining road + let roadA: TvRoad, roadB: TvRoad, roadC: TvRoad; + + let roadAHdg, roadBHdg, roadCHdg; + + [ roadA, roadC, roadB ] = createRoads(); + + roadAHdg = roadA.spline.controlPoints.map( ( p: RoadControlPoint ) => p.hdg ); + roadBHdg = roadB.spline.controlPoints.map( ( p: RoadControlPoint ) => p.hdg ); + roadCHdg = roadC.spline.controlPoints.map( ( p: RoadControlPoint ) => p.hdg ); + + roadA.spline.getLastPoint().copyPosition( new Vector3( 120, 0, 0 ) ); + + roadA.updateGeometryFromSpline(); + + roadAHdg = roadA.spline.controlPoints.map( ( p: RoadControlPoint ) => p.hdg ); + roadBHdg = roadB.spline.controlPoints.map( ( p: RoadControlPoint ) => p.hdg ); + roadCHdg = roadC.spline.controlPoints.map( ( p: RoadControlPoint ) => p.hdg ); + + // nothing happening in this test for now + expect( true ).toBe( true ); + + } ); + + it( 'should join 2 road nodes correctly', () => { + + // start |----------------> + + // start |----------------> + + let roadA, roadB, joiningRoad; + + [ roadA, roadB, joiningRoad ] = createRoads(); + + expect( !joiningRoad ).toBe( false ); + + expect( joiningRoad.spline.controlPoints.length ).toBe( 6 ); + + } ); + + function createRoads () { + + const roadA = openDrive.addDefaultRoad(); + + roadA.spline.addControlPoint( new RoadControlPoint( roadA, new Vector3( 0, 0, 0 ) ) ); + roadA.spline.addControlPoint( new RoadControlPoint( roadA, new Vector3( 50, 5, 0 ) ) ); + roadA.spline.addControlPoint( new RoadControlPoint( roadA, new Vector3( 100, 0, 0 ) ) ); + + roadA.updateGeometryFromSpline(); + + NodeFactoryService.updateRoadNodes( roadA ); + + const roadB = openDrive.addDefaultRoad(); + + roadB.spline.addControlPoint( new RoadControlPoint( roadB, new Vector3( 0, 50, 0 ) ) ); + roadB.spline.addControlPoint( new RoadControlPoint( roadB, new Vector3( 55, 50, 0 ) ) ); + roadB.spline.addControlPoint( new RoadControlPoint( roadB, new Vector3( 100, 50, 0 ) ) ); + + roadB.updateGeometryFromSpline(); + + NodeFactoryService.updateRoadNodes( roadB ); + + const joiningRoad = RoadFactory.joinRoadNodes( roadA, roadA.endNode, roadB, roadB.endNode ); + + return [ roadA, roadB, joiningRoad ]; + } + + function suc_pre () { + const road1 = this.openDrive.addDefaultRoadWithType( TvRoadType.TOWN ); + + road1.addControlPointAt( new Vector3( 0, 0, 0 ) ); + road1.addControlPointAt( new Vector3( 0, 50, 0 ) ); + road1.addControlPointAt( new Vector3( 80, 50, 0 ) ); + + road1.updateGeometryFromSpline(); + + RoadFactory.rebuildRoad( road1 ); + + const road2 = this.openDrive.addDefaultRoadWithType( TvRoadType.TOWN ); + + road2.addControlPointAt( new Vector3( 80, 50, 0 ) ); + road2.addControlPointAt( new Vector3( 160, 50, 0 ) ); + road2.addControlPointAt( new Vector3( 160, 0, 0 ) ); + + road2.updateGeometryFromSpline(); + + RoadFactory.rebuildRoad( road2 ); + + road1.setSuccessor( "road", road2.id, TvContactPoint.START ); + road2.setPredecessor( "road", road1.id, TvContactPoint.END ); + } + + function suc_suc () { + const road1 = this.openDrive.addDefaultRoadWithType( TvRoadType.TOWN ); + + road1.addControlPointAt( new Vector3( 0, 0, 0 ) ); + road1.addControlPointAt( new Vector3( 0, 50, 0 ) ); + road1.addControlPointAt( new Vector3( 80, 50, 0 ) ); + + road1.updateGeometryFromSpline(); + + RoadFactory.rebuildRoad( road1 ); + + const road2 = this.openDrive.addDefaultRoadWithType( TvRoadType.TOWN ); + + road2.addControlPointAt( new Vector3( 160, 0, 0 ) ); + road2.addControlPointAt( new Vector3( 160, 50, 0 ) ); + road2.addControlPointAt( new Vector3( 80, 50, 0 ) ); + + road2.updateGeometryFromSpline(); + + RoadFactory.rebuildRoad( road2 ); + + road1.setSuccessor( "road", road2.id, TvContactPoint.END ); + road2.setSuccessor( "road", road1.id, TvContactPoint.END ); + } + + function pre_pre () { + const road1 = this.openDrive.addDefaultRoadWithType( TvRoadType.TOWN ); + + road1.addControlPointAt( new Vector3( 80, 50, 0 ) ); + road1.addControlPointAt( new Vector3( 0, 50, 0 ) ); + road1.addControlPointAt( new Vector3( 0, 0, 0 ) ); + + road1.updateGeometryFromSpline(); + + RoadFactory.rebuildRoad( road1 ); + + const road2 = this.openDrive.addDefaultRoadWithType( TvRoadType.TOWN ); + + road2.addControlPointAt( new Vector3( 80, 50, 0 ) ); + road2.addControlPointAt( new Vector3( 160, 50, 0 ) ); + road2.addControlPointAt( new Vector3( 160, 0, 0 ) ); + + road2.updateGeometryFromSpline(); + + RoadFactory.rebuildRoad( road2 ); + + road1.setPredecessor( "road", road2.id, TvContactPoint.START ); + road2.setPredecessor( "road", road1.id, TvContactPoint.START ); + } + + function pre_suc () { + const road1 = this.openDrive.addDefaultRoadWithType( TvRoadType.TOWN ); + + road1.addControlPointAt( new Vector3( 80, 50, 0 ) ); + road1.addControlPointAt( new Vector3( 0, 50, 0 ) ); + road1.addControlPointAt( new Vector3( 0, 0, 0 ) ); + + road1.updateGeometryFromSpline(); + + RoadFactory.rebuildRoad( road1 ); + + const road2 = this.openDrive.addDefaultRoadWithType( TvRoadType.TOWN ); + + road2.addControlPointAt( new Vector3( 160, 0, 0 ) ); + road2.addControlPointAt( new Vector3( 160, 50, 0 ) ); + road2.addControlPointAt( new Vector3( 80, 50, 0 ) ); + + road2.updateGeometryFromSpline(); + + RoadFactory.rebuildRoad( road2 ); + + road1.setPredecessor( "road", road2.id, TvContactPoint.END ); + road2.setSuccessor( "road", road1.id, TvContactPoint.START ); + } + + +} ); diff --git a/src/app/core/tools/road-tool.ts b/src/app/core/tools/road-tool.ts new file mode 100644 index 00000000..796941c4 --- /dev/null +++ b/src/app/core/tools/road-tool.ts @@ -0,0 +1,616 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { BaseTool } from './base-tool'; +import { MouseButton, PointerEventData } from 'app/events/pointer-event-data'; +import { ObjectTypes, TvContactPoint } from 'app/modules/tv-map/models/tv-common'; +import { TvLane } from 'app/modules/tv-map/models/tv-lane'; +import { TvRoad } from 'app/modules/tv-map/models/tv-road.model'; +import { RoadInspector } from 'app/views/inspectors/road-inspector/road-inspector.component'; +import { Vector3 } from 'three'; +import { KeyboardInput } from '../input'; +import { PickingHelper } from '../services/picking-helper.service'; +import { RoadNode } from 'app/modules/three-js/objects/road-node'; +import { TvMapBuilder } from 'app/modules/tv-map/builders/od-builder.service'; +import { NodeFactoryService } from '../factories/node-factory.service'; +import { RoadFactory } from '../factories/road-factory.service'; +import { CommandHistory } from 'app/services/command-history'; +import { AddRoadPointCommand } from 'app/core/commands/add-road-point-command'; +import { SetValueCommand } from 'app/modules/three-js/commands/set-value-command'; +import { MultiCmdsCommand } from 'app/core/commands/multi-cmds-command'; +import { AddRoadCommand } from 'app/core/commands/add-road-command'; +import { SetInspectorCommand } from '../commands/set-inspector-command'; +import { UpdateRoadPointCommand } from 'app/core/commands/update-road-point-command'; +import { JoinRoadNodeCommand } from '../commands/join-road-node-command'; +import { RoadControlPoint } from 'app/modules/three-js/objects/road-control-point'; + +export class RoadTool extends BaseTool { + + public name: string = 'RoadTool'; + + private road: TvRoad; + private controlPoint: RoadControlPoint; + private node: RoadNode; + + private roadChanged: boolean = false; + private pointerDown: boolean = false; + private pointerDownAt: Vector3; + + constructor () { + + super(); + + } + + init () { + + + } + + enable () { + + super.enable(); + + this.openDrive.roads.forEach( road => { + + if ( !road.isJunction ) { + + NodeFactoryService.updateRoadNodes( road ); + + road.showNodes(); + + } + + } ); + + } + + disable () { + + super.disable(); + + this.openDrive.roads.forEach( road => road.hideNodes() ); + + if ( this.road ) this.hideRoad( this.road ); + + if ( this.controlPoint ) this.controlPoint.unselect(); + + if ( this.node ) this.node.unselected(); + } + + onPointerDown ( e: PointerEventData ) { + + if ( e.button == MouseButton.RIGHT || e.button == MouseButton.MIDDLE ) return; + + this.pointerDown = true; + this.pointerDownAt = e.point.clone(); + + const shiftKeyDown = KeyboardInput.isShiftKeyDown; + + let hasInteracted = false; + + if ( !hasInteracted ) hasInteracted = this.checkControlPointInteraction( e ); + + if ( !hasInteracted ) hasInteracted = this.checkRoadNodeInteraction( e ); + + if ( !hasInteracted && !shiftKeyDown ) hasInteracted = this.checkLaneObjectInteraction( e ); + + if ( !hasInteracted ) { + + if ( e.button === MouseButton.LEFT && shiftKeyDown && e.point != null ) { + + this.addControlPoint( e.point ); + + } else { + + if ( this.road || this.controlPoint || this.node ) { + + const setInspectorCommand = new SetInspectorCommand( null, null ); + + const setRoadCommand = new SetValueCommand( this, 'road', null ); + + const setPointCommand = new SetValueCommand( this, 'controlPoint', null ); + + const setNodeCommand = new SetValueCommand( this, 'node', null ); + + CommandHistory.execute( new MultiCmdsCommand( [ + setInspectorCommand, + setRoadCommand, + setPointCommand, + setNodeCommand + ] ) ); + + } + + } + + // // no interaction with line, lane, points etc + // if ( this.road && !this.controlPoint && !shiftKeyDown ) { + + // AppInspector.clear(); + + // this.hideRoad( this.road ); + + // this.road = null; + // } + + // if ( this.controlPoint && !shiftKeyDown ) { + + // this.controlPoint.unselect(); + + // this.controlPoint = null; + + // } + + } + + // AppInspector.setInspector( RoadInspector, { + // road: this.road, + // controlPoint: this.controlPoint + // } ); + } + + onPointerClicked ( e: PointerEventData ) { + + // no need to do chck creation logic here, as it caused bugs, moved it in onPointerDown + + // if ( e.button === MouseButton.LEFT && KeyboardInput.isShiftKeyDown && e.point != null ) { + + // this.checkRoadNodeInteraction( e ); + + // this.addControlPoint( e.point ); + + // } + } + + onPointerUp ( e: PointerEventData ) { + + if ( this.roadChanged && this.road && this.road.spline.controlPoints.length >= 2 ) { + + const updateRoadPointCommand = new UpdateRoadPointCommand( + this.road, this.controlPoint, this.controlPoint.position, this.pointerDownAt + ); + + CommandHistory.execute( updateRoadPointCommand ); + + } + + this.pointerDown = false; + + this.pointerDownAt = null; + + this.roadChanged = false; + } + + onPointerMoved ( e: PointerEventData ) { + + if ( this.pointerDown && this.controlPoint && this.controlPoint.isSelected && this.road ) { + + this.controlPoint.copyPosition( e.point ); + + this.road.spline.update(); + + this.roadChanged = true; + + this.updateSuccessor( this.road, this.controlPoint ); + + this.updatePredecessor( this.road, this.controlPoint ); + } + + } + + private updateSuccessor ( road: TvRoad, currentPoint: RoadControlPoint ) { + + const P1 = road.spline.getSecondLastPoint() as RoadControlPoint; + const P2 = road.spline.getLastPoint() as RoadControlPoint; + + if ( road.successor && road.successor.elementType !== "junction" + && ( P1.id === currentPoint.id || P2.id === currentPoint.id ) ) { + + const successor = this.openDrive.getRoadById( road.successor.elementId ); + + if ( !successor ) return; + + successor.spline.show(); + + let P3: RoadControlPoint; + + let P4: RoadControlPoint; + + let newP4: RoadControlPoint; + + let distance: number; + + if ( road.successor.contactPoint === TvContactPoint.START ) { + + P3 = successor.spline.controlPoints[ 0 ] as RoadControlPoint; + + P4 = successor.spline.controlPoints[ 1 ] as RoadControlPoint; + + distance = P3.position.distanceTo( P4.position ); + + P3.copyPosition( P2.position ); + + P2.hdg = P1.hdg; + + P3.hdg = P2.hdg + Math.PI; + + newP4 = P2.moveForward( distance ); + + P4.copyPosition( newP4.position ); + + successor.spline.update(); + + } else { + + P3 = successor.spline.getLastPoint() as RoadControlPoint; + + P4 = successor.spline.getSecondLastPoint() as RoadControlPoint; + + distance = P3.position.distanceTo( P4.position ); + + P3.copyPosition( P2.position ); + + P2.hdg = P1.hdg; + + P3.hdg = P2.hdg + Math.PI; + + newP4 = P2.moveForward( distance ); + + P4.copyPosition( newP4.position ); + + successor.spline.update(); + } + } + } + + private updatePredecessor ( road: TvRoad, currentPoint: RoadControlPoint ) { + + const P1 = road.spline.controlPoints[ 1 ] as RoadControlPoint; + const P2 = road.spline.controlPoints[ 0 ] as RoadControlPoint; + + if ( road.predecessor && road.predecessor.elementType !== "junction" + && ( P1.id === currentPoint.id || P2.id === currentPoint.id ) ) { + + const predecessor = this.openDrive.getRoadById( road.predecessor.elementId ); + + if ( !predecessor ) return; + + predecessor.spline.show(); + + let P3: RoadControlPoint; + + let P4: RoadControlPoint; + + let newP4: RoadControlPoint; + + let distance: number; + + if ( road.predecessor.contactPoint === TvContactPoint.START ) { + + P3 = predecessor.spline.controlPoints[ 0 ] as RoadControlPoint; + + P4 = predecessor.spline.controlPoints[ 1 ] as RoadControlPoint; + + distance = P3.position.distanceTo( P4.position ); + + P3.copyPosition( P2.position ); + + P3.hdg = P4.hdg = P2.hdg + Math.PI; + + newP4 = P3.moveForward( distance ); + + P4.copyPosition( newP4.position ); + + predecessor.spline.update(); + + } else { + + P3 = predecessor.spline.getLastPoint() as RoadControlPoint; + + P4 = predecessor.spline.getSecondLastPoint() as RoadControlPoint; + + distance = P3.position.distanceTo( P4.position ); + + P3.copyPosition( P2.position ); + + P3.hdg = P4.hdg = P2.hdg + Math.PI; + + newP4 = P3.moveForward( distance ); + + P4.copyPosition( newP4.position ); + + predecessor.spline.update(); + + } + } + } + + private checkControlPointInteraction ( e: PointerEventData ): boolean { + + if ( !this.road || !this.road.spline ) return; + + if ( !e.point ) return; + + // // first chceck for control point interactions + // // doing in 2 loop to prioritise control points + // let controlPoint = PickingHelper.checkControlPointInteraction( e, ControlPoint.roadTag, 1 ); + + const maxDistance = Math.max( 0.5, e.approxCameraDistance * 0.01 ); + + const controlPoints = []; + + this.road.spline.controlPoints.forEach( ( cp: RoadControlPoint ) => { + + controlPoints.push( cp ); + + if ( cp.frontTangent ) controlPoints.push( cp.frontTangent ); + + if ( cp.backTangent ) controlPoints.push( cp.backTangent ); + + } ); + + const controlPoint = PickingHelper.findNearest( e.point, controlPoints, maxDistance ); + + if ( controlPoint ) { + + CommandHistory.executeAll( [ + + new SetInspectorCommand( RoadInspector, { road: this.road, controlPoint } ), + + new SetValueCommand( this, 'controlPoint', controlPoint ), + + new SetValueCommand( this, 'node', null ) + + ] ); + + } else if ( this.controlPoint ) { + + CommandHistory.executeAll( [ + + new SetValueCommand( this, 'controlPoint', null ), + + new SetInspectorCommand( null, null ), + + ] ); + + } + + return controlPoint != null; + } + + private checkLaneObjectInteraction ( e: PointerEventData ): boolean { + + let hasInteracted = false; + + for ( let i = 0; i < e.intersections.length; i++ ) { + + const intersection = e.intersections[ i ]; + + // tslint:disable-next-line: no-string-literal + if ( intersection.object && intersection.object[ 'tag' ] === ObjectTypes.LANE ) { + + hasInteracted = true; + + if ( intersection.object.userData.lane ) { + + const lane = intersection.object.userData.lane as TvLane; + + const road = this.openDrive.getRoadById( lane.roadId ); + + if ( road.isJunction ) continue; + + // check if old or a new lane is selected + if ( !this.road || this.road.id !== road.id ) { + + const commands = []; + + commands.push( new SetInspectorCommand( RoadInspector, { road } ) ); + + commands.push( new SetValueCommand( this, 'road', road ) ); + + commands.push( new SetValueCommand( this, 'controlPoint', null ) ); + + commands.push( new SetValueCommand( this, 'node', null ) ); + + CommandHistory.execute( new MultiCmdsCommand( commands ) ); + } + } + + break; + } + } + + return hasInteracted; + } + + private checkRoadNodeInteraction ( e: PointerEventData ): boolean { + + let hasInteracted = false; + + // for now ignore the shift key + const shiftKeyDown = true; //KeyboardInput.isShiftKeyDown; + + for ( let i = 0; i < e.intersections.length; i++ ) { + + const intersection = e.intersections[ i ]; + + if ( intersection.object && intersection.object[ 'tag' ] === RoadNode.lineTag ) { + + hasInteracted = true; + + const node = intersection.object.parent as RoadNode; + + if ( shiftKeyDown && this.node && this.node.roadId !== node.roadId ) { + + // node with node then + + // two roads need to joined + // we take both nodes and use them as start and end points + // for a new road + // new road will have 4 more points, so total 6 points + + this.joinNodeWithNode( this.node, node ); + + } else if ( shiftKeyDown && this.controlPoint ) { + + // control point with node + // modify the control point road and join it the the node road + // console.log( "only join roads", this.road.id, node.roadId ); + + + // another scenario of first node selected then controlpoint + // in this + // create new road and normally but with node as the first point + // and second point will be forward distance of x distance + // then 3rd point will be created wherever the point was selected + + } else { + + // this only selects the node + + const road = this.openDrive.getRoadById( node.roadId ); + + CommandHistory.executeAll( [ + + new SetInspectorCommand( RoadInspector, { road, node } ), + + new SetValueCommand( this, 'node', node ), + + new SetValueCommand( this, 'road', road ), + + new SetValueCommand( this, 'conrolPoint', null ), + + ] ); + + } + + + break; + } + } + + if ( !hasInteracted && this.node ) { + + this.node.unselected(); + + this.node = null; + + } + + return hasInteracted; + } + + private updateRoadGeometry ( road: TvRoad ): void { + + road.spline.update(); + + this.openDrive.gameObject.remove( road.gameObject ); + + // remove old geometries + road.clearGeometries(); + + // TODO: can be improved + road.spline.exportGeometries().forEach( geometry => { + + road.addGeometry( geometry ); + + } ); + + TvMapBuilder.buildRoad( this.openDrive.gameObject, road ); + + NodeFactoryService.updateRoadNodes( this.road ); + } + + private hideRoad ( road: TvRoad ): void { + + road.spline.hide(); + + } + + private showRoad ( road: TvRoad ): void { + + road.spline.show(); + + } + + private addControlPoint ( position: Vector3 ) { + + // // unselect old if exists + // if ( this.controlPoint ) this.controlPoint.unselect(); + + if ( !this.road ) { + + const result = RoadFactory.createRoad( position ); + + const addRoadCommand = new AddRoadCommand( result.road, result.point ); + + const setRoadCommand = new SetValueCommand( this, 'road', result.road ); + + const setPointCommand = new SetValueCommand( this, 'controlPoint', result.point ); + + CommandHistory.execute( new MultiCmdsCommand( [ addRoadCommand, setRoadCommand, setPointCommand ] ) ); + + } else { + + const controlPoint = RoadFactory.addControlPoint( this.road, position ); + + const setPointCommand = new SetValueCommand( this, 'controlPoint', controlPoint ); + + const addPointCommand = new AddRoadPointCommand( this.road, controlPoint, this.controlPoint ); + + CommandHistory.execute( new MultiCmdsCommand( [ addPointCommand, setPointCommand ] ) ); + } + + // if ( !this.road ) this.road = this.openDrive.addDefaultRoad(); + + + // // set new + // this.controlPoint = ControlPoint.create( "", position ); + + // // TODO: spline should take this responsibility + // SceneService.add( this.controlPoint ); + + // this.controlPoint.mainObject = this.controlPoint.userData.road = this.road; + + // this.road.spline.addControlPoint( this.controlPoint ); + + // this.controlPoint.onSelected(); + + // if ( this.road.spline.controlPoints.length < 2 ) return; + + // this.updateRoadGeometry( this.road ); + + // AppInspector.setInspector( RoadInspector, { + // road: this.road, + // controlPoint: this.controlPoint + // } ); + } + + private joinNodeWithNode ( firstNode: RoadNode, secondNode: RoadNode ) { + + // console.log( "crate new road to join ", firstNode.roadId, secondNode.roadId ); + + const commands = []; + + commands.push( new SetValueCommand( this, 'node', null ) ); + + commands.push( new SetValueCommand( this, 'road', null ) ); + + commands.push( new SetValueCommand( this, 'conrolPoint', null ) ); + + commands.push( new JoinRoadNodeCommand( firstNode, secondNode ) ); + + CommandHistory.execute( new MultiCmdsCommand( commands ) ); + + } + + private joinPointWithNode () { + + } + + private joinNodeWithPoint () { + + } +} \ No newline at end of file diff --git a/src/app/core/tools/surface-tool.ts b/src/app/core/tools/surface-tool.ts new file mode 100644 index 00000000..b46427ba --- /dev/null +++ b/src/app/core/tools/surface-tool.ts @@ -0,0 +1,190 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { BaseTool } from './base-tool'; +import { PointEditor } from '../editors/point-editor'; +import { AnyControlPoint } from 'app/modules/three-js/objects/control-point'; +import { Subscription } from 'rxjs'; +import { PointerEventData } from 'app/events/pointer-event-data'; +import { CatmullRomSpline } from '../shapes/catmull-rom-spline'; +import { TvSurface } from 'app/modules/tv-map/models/tv-surface.model'; +import { KeyboardInput } from '../input'; + +export class SurfaceTool extends BaseTool { + + public name: string = 'SurfaceTool'; + + public shapeEditor: PointEditor; + + private cpSubscriptions: Subscription[] = []; + + private cpAddedSub: Subscription; + private cpMovedSub: Subscription; + private cpUpdatedSub: Subscription; + private cpSelectedSub: Subscription; + private cpUnselectedSub: Subscription; + private keyDownSub: Subscription; + + private surface: TvSurface; + + constructor () { + + super(); + + } + + public init () { + + super.init(); + + this.shapeEditor = new PointEditor( 100 ); + } + + public enable () { + + super.enable(); + + this.openDrive.surfaces.forEach( surface => { + + surface.update(); + + surface.showControlPoints(); + + surface.showCurve(); + + surface.spline.controlPoints.forEach( cp => { + + cp.mainObject = surface; + + this.shapeEditor.controlPoints.push( cp ) + + } ) + } ); + + this.keyDownSub = KeyboardInput.keyDown + .subscribe( e => this.onDeletePressed( e ) ); + + this.cpAddedSub = this.shapeEditor.controlPointAdded + .subscribe( ( cp: AnyControlPoint ) => this.onControlPointAdded( cp ) ); + + this.cpMovedSub = this.shapeEditor.controlPointMoved + .subscribe( () => this.onControlPointMoved() ); + + this.cpUpdatedSub = this.shapeEditor.controlPointUpdated + .subscribe( () => this.onControlPointUpdated() ); + + this.cpSelectedSub = this.shapeEditor.controlPointSelected + .subscribe( ( cp: AnyControlPoint ) => this.onControlPointSelected( cp ) ); + + this.cpUnselectedSub = this.shapeEditor.controlPointUnselected + .subscribe( () => this.onControlPointUnselected() ); + + } + + public disable (): void { + + super.disable(); + + this.openDrive.surfaces.forEach( surface => { + + surface.hideCurve(); + surface.hideControlPoints(); + + } ); + + this.keyDownSub.unsubscribe(); + this.cpAddedSub.unsubscribe(); + this.cpMovedSub.unsubscribe(); + this.cpUpdatedSub.unsubscribe(); + this.cpSelectedSub.unsubscribe(); + this.cpUnselectedSub.unsubscribe(); + + this.shapeEditor.destroy(); + } + + public onPointerClicked ( e: PointerEventData ) { + + for ( let i = 0; i < e.intersections.length; i++ ) { + + const intersection = e.intersections[ i ]; + + if ( intersection.object && intersection.object[ 'tag' ] === TvSurface.tag ) { + + this.surface = intersection.object.userData.surface; + + this.surface.showControlPoints(); + + break; + } + } + } + + private onControlPointSelected ( cp: AnyControlPoint ) { + + this.surface = cp.mainObject; + + this.surface.showControlPoints(); + + } + + private onControlPointUnselected () { + + this.surface = null; + + } + + private onControlPointAdded ( cp: AnyControlPoint ) { + + if ( !this.surface ) { + + this.surface = new TvSurface( 'grass', new CatmullRomSpline() ) + + this.openDrive.surfaces.push( this.surface ); + + } + + cp.mainObject = this.surface; + + this.surface.spline.addControlPoint( cp ); + + this.surface.update(); + + } + + private onControlPointUpdated () { + + this.surface.spline.update(); + + this.surface.update(); + + } + + private onControlPointMoved () { + + this.surface.spline.update(); + + } + + private onDeletePressed ( e: KeyboardEvent ) { + + if ( e.key === "Delete" && this.surface ) { + + this.surface.delete(); + + const index = this.openDrive.surfaces.findIndex( s => s.id == this.surface.id ); + + if ( index > -1 ) { + + this.openDrive.surfaces.splice( index, 1 ); + + } + + this.surface = null; + + delete this.surface; + } + + } + +} diff --git a/src/app/core/tools/tool-manager.ts b/src/app/core/tools/tool-manager.ts new file mode 100644 index 00000000..355a0d95 --- /dev/null +++ b/src/app/core/tools/tool-manager.ts @@ -0,0 +1,70 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { EventEmitter } from '@angular/core'; +import { BaseTool } from './base-tool'; + +export class ToolManager { + + public static toolChanged = new EventEmitter(); + + private static tool: BaseTool; + + static get currentTool (): BaseTool { + + return this.tool; + + } + + static set currentTool ( value: BaseTool ) { + + if ( !value ) { + + this.clear(); + + } else { + + // dont do anything if the same tool is already being used + if ( this.tool && this.tool.name === value.name ) return; + + this.destroyPreviousState(); + + this.tool = value; + + this.tool.init(); + + this.tool.enable(); + + this.toolChanged.emit( value ); + + } + + } + + static clear () { + + this.destroyPreviousState(); + + this.toolChanged.emit( null ); + } + + static disable () { + + if ( this.tool != null ) this.tool.disable(); + + } + + static enable () { + + if ( this.tool != null ) this.tool.enable(); + + } + + private static destroyPreviousState () { + + this.disable(); + + delete this.tool; + } +} diff --git a/src/app/core/utils/console.ts b/src/app/core/utils/console.ts new file mode 100644 index 00000000..e621eba6 --- /dev/null +++ b/src/app/core/utils/console.ts @@ -0,0 +1,74 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { environment } from '../../../environments/environment'; + +enum ConsoleMessageType { + info, + warn, + error, +} + +class ConsoleMessage { + constructor ( public type: ConsoleMessageType, public message: any, public counter = 0 ) { } +} + +export class TvConsole { + + static messages: ConsoleMessage[] = []; + + static get lastMessage () { return this.messages[ this.messages.length - 1 ]; } + + static clear () { + + this.messages.splice( 0, this.messages.length ); + + } + + static info ( message: string ) { + + // if same message is being printed then just increase the counter + if ( this.lastMessage.type === ConsoleMessageType.info && this.lastMessage.message === message ) { + + this.lastMessage.counter += 1; + + } else { + + this.messages.push( new ConsoleMessage( ConsoleMessageType.info, message ) ); + + } + } + + + static warn ( message: string ) { + + // if same message is being printed then just increase the counter + if ( this.lastMessage.type === ConsoleMessageType.warn && this.lastMessage.message === message ) { + + this.lastMessage.counter += 1; + + } else { + + this.messages.push( new ConsoleMessage( ConsoleMessageType.warn, message ) ); + + } + + } + + + static error ( message: string ) { + + // if same message is being printed then just increase the counter + if ( this.lastMessage.type === ConsoleMessageType.error && this.lastMessage.message === message ) { + + this.lastMessage.counter += 1; + + } else { + + this.messages.push( new ConsoleMessage( ConsoleMessageType.error, message ) ); + + } + + } +} diff --git a/src/app/core/utils/debug.ts b/src/app/core/utils/debug.ts new file mode 100644 index 00000000..0e3ef526 --- /dev/null +++ b/src/app/core/utils/debug.ts @@ -0,0 +1,41 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { environment } from '../../../environments/environment'; +import { Mesh, MeshBasicMaterial, SphereBufferGeometry, Vector2, Vector3 } from 'three'; +import { SceneService } from '../services/scene.service'; + +export class Debug { + + private static sphereAdded: boolean; + private static sphere: Mesh; + + static log ( message?: any, ...optionalParams: any[] ) { + + if ( !environment.production ) { + + console.log( message, optionalParams ); + + } + } + + static drawSphere ( position: Vector3 | Vector2 ) { + + if ( !this.sphereAdded ) { + + const geometry = new SphereBufferGeometry(); + const material = new MeshBasicMaterial(); + + this.sphere = new Mesh( geometry, material ); + + SceneService.add( this.sphere ); + + this.sphereAdded = true; + } + + this.sphere.position.setX( position.x ); + this.sphere.position.setY( position.y ); + + } +} diff --git a/src/app/core/utils/environment.ts b/src/app/core/utils/environment.ts new file mode 100644 index 00000000..1d1c67b6 --- /dev/null +++ b/src/app/core/utils/environment.ts @@ -0,0 +1,27 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { environment } from '../../../environments/environment'; + +export class Environment { + + static get production (): boolean { + + return environment.production; + + } + + static get oscEnabled (): boolean { + + return environment.osc_enabled; + + } + + static get websiteUrl (): string { + + return environment.web_url; + + } + +} diff --git a/src/app/core/utils/threejs-utils.ts b/src/app/core/utils/threejs-utils.ts new file mode 100644 index 00000000..7d1bf478 --- /dev/null +++ b/src/app/core/utils/threejs-utils.ts @@ -0,0 +1,12 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import * as THREE from 'three'; +import { Mesh } from 'three'; + +export class ThreeJsUtils { + + + +} diff --git a/src/app/events/event-system-handler.ts b/src/app/events/event-system-handler.ts new file mode 100644 index 00000000..a4c9fd19 --- /dev/null +++ b/src/app/events/event-system-handler.ts @@ -0,0 +1,12 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +export interface IEventSystemHandler { + +} + +export class EventSystemHandler implements IEventSystemHandler { + + +} diff --git a/src/app/events/event-system.service.ts b/src/app/events/event-system.service.ts new file mode 100644 index 00000000..53c91075 --- /dev/null +++ b/src/app/events/event-system.service.ts @@ -0,0 +1,43 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { EventEmitter, Injectable, Output } from '@angular/core'; +import { BaseEventData, PointerEventData, PointerMoveData } from './pointer-event-data'; + +@Injectable( { + providedIn: 'root' +} ) +export class EventSystem { + + @Output() pointerClicked = new EventEmitter(); + @Output() pointerMoved = new EventEmitter(); + @Output() pointerUp = new EventEmitter(); + @Output() pointerDown = new EventEmitter(); + @Output() pointerEnter = new EventEmitter(); + @Output() pointerExit = new EventEmitter(); + @Output() pointerOut = new EventEmitter(); + @Output() pointerLeave = new EventEmitter(); + @Output() beginDrag = new EventEmitter(); + @Output() endDrag = new EventEmitter(); + @Output() drag = new EventEmitter(); + @Output() drop = new EventEmitter(); + @Output() select = new EventEmitter(); + @Output() deSelect = new EventEmitter(); + + + constructor () { + + // this.pointerClicked.subscribe( e => Debug.log( 'pointerClicked', e ) ); + // this.pointerMoved.subscribe( e => Debug.log( 'pointerMoved', e ) ); + // this.pointerUp.subscribe( e => Debug.log( 'pointerUp', e ) ); + // this.pointerDown.subscribe( e => Debug.log( 'pointerDown', e ) ); + // this.pointerEnter.subscribe( e => Debug.log( 'pointerEnter', e ) ); + // this.pointerExit.subscribe( e => Debug.log( 'pointerExit', e ) ); + // this.select.subscribe( e => Debug.log( 'select', e ) ); + // this.deSelect.subscribe( e => Debug.log( 'deSelect', e ) ); + // this.beginDrag.subscribe( e => Debug.log( 'beginDrag', e ) ); + // this.drag.subscribe( e => Debug.log( 'drag', e ) ); + + } +} diff --git a/src/app/events/pointer-event-data.ts b/src/app/events/pointer-event-data.ts new file mode 100644 index 00000000..b33a7506 --- /dev/null +++ b/src/app/events/pointer-event-data.ts @@ -0,0 +1,33 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import {Face3, Intersection, Object3D, Vector2, Vector3} from 'three/three-core'; + +export class BaseEventData { + object: Object3D; +} + +export enum MouseButton { + LEFT = 0, + MIDDLE = 1, + RIGHT = 2 +} + +export class PointerMoveData { + point: Vector3; +} + +export class PointerEventData extends BaseEventData { + distance: number; + distanceToRay?: number; + point: Vector3; + index?: number; + face?: Face3 | null; + faceIndex?: number; + uv?: Vector2; + button: MouseButton; + intersections?: Intersection[]; + approxCameraDistance?: number +} + diff --git a/src/app/modules/three-js/EnableThreeExamples.js b/src/app/modules/three-js/EnableThreeExamples.js new file mode 100644 index 00000000..3f704223 --- /dev/null +++ b/src/app/modules/three-js/EnableThreeExamples.js @@ -0,0 +1,5 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +THREE = require( 'three' ); diff --git a/src/app/modules/three-js/commands/set-material-map-command.ts b/src/app/modules/three-js/commands/set-material-map-command.ts new file mode 100644 index 00000000..5924e013 --- /dev/null +++ b/src/app/modules/three-js/commands/set-material-map-command.ts @@ -0,0 +1,40 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { BaseCommand } from 'app/core/commands/base-command'; +import { Material, Texture } from 'three'; + +export class SetMaterialMapCommand extends BaseCommand { + + private oldMap: Texture | null; + + constructor ( private material: Material, private mapName, private newMap: Texture, private materialSlot ) { + + super(); + + this.oldMap = this.material[ mapName ]; + } + + execute (): void { + + this.material[ this.mapName ] = this.newMap; + this.material[ 'needsUpdate' ] = true; + + } + + undo (): void { + + this.material[ this.mapName ] = this.oldMap; + this.material[ 'needsUpdate' ] = true; + + } + + redo (): void { + + this.execute(); + + } + + +} diff --git a/src/app/modules/three-js/commands/set-position-command.ts b/src/app/modules/three-js/commands/set-position-command.ts new file mode 100644 index 00000000..77fa3ed9 --- /dev/null +++ b/src/app/modules/three-js/commands/set-position-command.ts @@ -0,0 +1,58 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { BaseCommand } from 'app/core/commands/base-command'; + +export class SetPositionCommand extends BaseCommand { + + private oldPosition: THREE.Vector3; + + constructor ( + private object: THREE.Object3D, + private newPosition: THREE.Vector3, + private optionalOldPosition: THREE.Vector3 = null + ) { + + super(); + + if ( object !== null && newPosition !== null ) { + + this.oldPosition = object.position.clone(); + this.newPosition = newPosition.clone(); + + } + + if ( optionalOldPosition !== null ) { + + this.oldPosition = optionalOldPosition.clone(); + + } + + } + + execute (): void { + + this.object.position.copy( this.newPosition ); + this.object.updateMatrixWorld( true ); + + // this.editor.signals.objectChanged.dispatch( this.object ); + + } + + undo (): void { + + this.object.position.copy( this.oldPosition ); + this.object.updateMatrixWorld( true ); + + // this.editor.signals.objectChanged.dispatch( this.object ); + + } + + redo (): void { + + this.object.position.copy( this.newPosition ); + this.object.updateMatrixWorld( true ); + + } +} diff --git a/src/app/modules/three-js/commands/set-rotation-command.ts b/src/app/modules/three-js/commands/set-rotation-command.ts new file mode 100644 index 00000000..00857fd8 --- /dev/null +++ b/src/app/modules/three-js/commands/set-rotation-command.ts @@ -0,0 +1,55 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { BaseCommand } from '../../../core/commands/base-command'; +import { Euler, Object3D } from 'three'; + +export class SetRotationCommand extends BaseCommand { + + private readonly oldRotation: Euler; + + constructor ( + private object: Object3D, + private newRotation: Euler, + private optionalOldRotation: Euler = null + ) { + + super(); + + if ( object !== null && newRotation !== null ) { + + this.oldRotation = object.rotation.clone(); + this.newRotation = newRotation.clone(); + + } + + if ( optionalOldRotation !== null ) { + + this.oldRotation = optionalOldRotation.clone(); + + } + + } + + execute (): void { + + this.object.rotation.copy( this.newRotation ); + this.object.updateMatrixWorld( true ); + + } + + undo (): void { + + this.object.rotation.copy( this.oldRotation ); + this.object.updateMatrixWorld( true ); + + } + + redo (): void { + + this.execute(); + + } + +} \ No newline at end of file diff --git a/src/app/modules/three-js/commands/set-scale-command.ts b/src/app/modules/three-js/commands/set-scale-command.ts new file mode 100644 index 00000000..c66929d2 --- /dev/null +++ b/src/app/modules/three-js/commands/set-scale-command.ts @@ -0,0 +1,55 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { BaseCommand } from '../../../core/commands/base-command'; +import { Object3D, Vector3 } from 'three'; + +export class SetScaleCommand extends BaseCommand { + + private readonly oldScale: Vector3; + + constructor ( + private object: Object3D, + private newScale: Vector3, + private optionalOldScale: Vector3 = null + ) { + + super(); + + if ( object !== null && newScale !== null ) { + + this.oldScale = object.scale.clone(); + this.newScale = newScale.clone(); + + } + + if ( optionalOldScale !== null ) { + + this.oldScale = optionalOldScale.clone(); + + } + + } + + execute (): void { + + this.object.scale.copy( this.newScale ); + this.object.updateMatrixWorld( true ); + + } + + undo (): void { + + this.object.scale.copy( this.oldScale ); + this.object.updateMatrixWorld( true ); + + } + + redo (): void { + + this.execute(); + + } + +} \ No newline at end of file diff --git a/src/app/modules/three-js/commands/set-value-command.ts b/src/app/modules/three-js/commands/set-value-command.ts new file mode 100644 index 00000000..ef701dd9 --- /dev/null +++ b/src/app/modules/three-js/commands/set-value-command.ts @@ -0,0 +1,36 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { BaseCommand } from 'app/core/commands/base-command'; + +export class SetValueCommand extends BaseCommand { + + private oldValue: any; + + constructor ( private object: Object, private attributeName, private newValue: any ) { + + super(); + + // store the old value + this.oldValue = ( object !== undefined ) ? object[ attributeName ] : undefined; + } + + execute (): void { + + this.object[ this.attributeName ] = this.newValue; + + } + + undo (): void { + + this.object[ this.attributeName ] = this.oldValue; + + } + + redo (): void { + + this.execute(); + + } +} diff --git a/src/app/modules/three-js/inspectors/material-inspector/_material-inspector.component.html b/src/app/modules/three-js/inspectors/material-inspector/_material-inspector.component.html new file mode 100644 index 00000000..ac4d6be7 --- /dev/null +++ b/src/app/modules/three-js/inspectors/material-inspector/_material-inspector.component.html @@ -0,0 +1,17 @@ + + +
+ + Material
+ + + + + + + + +
\ No newline at end of file diff --git a/src/app/modules/three-js/inspectors/material-inspector/_material-inspector.component.ts b/src/app/modules/three-js/inspectors/material-inspector/_material-inspector.component.ts new file mode 100644 index 00000000..ea6cfe37 --- /dev/null +++ b/src/app/modules/three-js/inspectors/material-inspector/_material-inspector.component.ts @@ -0,0 +1,67 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +// import { Component, Input, OnInit } from '@angular/core'; +// import { IComponent } from '../../../../core/game-object'; +// import * as THREE from 'three'; +// import { Material, MeshBasicMaterial, Texture } from 'three'; +// import { CommandHistory } from '../../../../services/command-history'; +// import { SetMaterialMapCommand } from '../../commands/set-material-map-command'; + +// @Component( { +// selector: 'app-material-inspector', +// templateUrl: './material-inspector.component.html', +// } ) +// /** +// * @deprecated +// */ +// export class MaterialInspectorComponent implements OnInit, IComponent { + +// @Input() data: Material | Material[]; + +// /** +// * Color Picker position +// */ +// @Input() cpPosition: string = 'top'; + + +// get material (): MeshBasicMaterial { +// return this.data as MeshBasicMaterial; +// } + +// get color (): any { +// return '#' + this.material.color.getHexString(); +// } + +// set color ( value: any ) { +// this.material.color.setStyle( value ); +// } + +// ngOnInit (): void { +// } + +// onEventLog ( event: string, data: string ) { + +// // console.log( event, data ); + +// // this.data.color.setStyle( data[ 'color' ] ); + +// // console.log( '#' + this.data.color.getHexString() ); +// // console.log( '#' + this.data.color.getHex() ); +// // console.log( '#' + this.data.color.getStyle() ); + +// } + +// onAlbedoChanged ( texture: Texture ) { + +// texture.wrapS = THREE.RepeatWrapping; +// texture.wrapT = THREE.RepeatWrapping; +// texture.mapping = THREE.UVMapping; +// texture.repeat.set( 1, 1 ); +// texture.anisotropy = 5; + +// CommandHistory.execute( new SetMaterialMapCommand( this.material, 'map', texture, 1 ) ); + +// } +// } diff --git a/src/app/modules/three-js/inspectors/texture-field/texture-field.component.css b/src/app/modules/three-js/inspectors/texture-field/texture-field.component.css new file mode 100644 index 00000000..707523f7 --- /dev/null +++ b/src/app/modules/three-js/inspectors/texture-field/texture-field.component.css @@ -0,0 +1,4 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + diff --git a/src/app/modules/three-js/inspectors/texture-field/texture-field.component.html b/src/app/modules/three-js/inspectors/texture-field/texture-field.component.html new file mode 100644 index 00000000..23b8d0cd --- /dev/null +++ b/src/app/modules/three-js/inspectors/texture-field/texture-field.component.html @@ -0,0 +1,8 @@ + + + +{{ label }} diff --git a/src/app/modules/three-js/inspectors/texture-field/texture-field.component.ts b/src/app/modules/three-js/inspectors/texture-field/texture-field.component.ts new file mode 100644 index 00000000..8f0383e4 --- /dev/null +++ b/src/app/modules/three-js/inspectors/texture-field/texture-field.component.ts @@ -0,0 +1,163 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { Component, ElementRef, EventEmitter, Input, OnInit, Output, ViewChild } from '@angular/core'; +import * as THREE from 'three'; +import { Texture } from 'three'; + +@Component( { + selector: 'app-texture-field', + templateUrl: './texture-field.component.html', + styleUrls: [ './texture-field.component.css' ] +} ) +/** + * @deprecated + */ +export class TextureFieldComponent implements OnInit { + + @Input() data: Texture | null; + @Input() label: string = 'Texture'; + + @Output() changed = new EventEmitter(); + + @ViewChild( 'canvas' ) canvas: ElementRef; + + private input: any; + + constructor () { + } + + get canvasEl (): HTMLCanvasElement { + return this.canvas.nativeElement; + } + + get texture () { + return this.data; + } + + set texture ( value ) { + this.data = value; + } + + ngOnInit (): void { + + const dom = document.createElement( 'span' ); + const form = document.createElement( 'form' ); + + this.input = document.createElement( 'input' ); + this.input.type = 'file'; + this.input.addEventListener( 'change', ( event: any ) => { + + this.loadFile( event.target.files[ 0 ] ); + + } ); + + form.appendChild( this.input ); + + if ( this.data != null ) this.setValue( this.texture ); + } + + loadFile ( file ) { + + if ( file.type.match( 'image.*' ) ) { + + const reader = new FileReader(); + + if ( file.type === 'image/targa' ) { + + // reader.addEventListener( 'load', function ( event ) { + // + // // var canvas = new THREE.TGALoader().parse( event.target.result ); + // // + // // var texture = new THREE.CanvasTexture( canvas, mapping ); + // // texture.sourceFile = file.name; + // // + // // scope.setValue( texture ); + // // + // // if ( scope.onChangeCallback ) scope.onChangeCallback( texture ); + // + // }, false ); + // + // reader.readAsArrayBuffer( file ); + // + } else { + + reader.addEventListener( 'load', ( event: any ) => { + + const image = document.createElement( 'img' ); + + image.addEventListener( 'load', ( event: any ) => { + + const texture = new Texture( image, THREE.UVMapping ); + texture.sourceFile = file.name; + texture.format = file.type === 'image/jpeg' ? THREE.RGBFormat : THREE.RGBAFormat; + texture.needsUpdate = true; + + this.setValue( texture ); + + this.changed.emit( texture ); + + }, false ); + + image.src = event.target.result; + + }, false ); + + reader.readAsDataURL( file ); + + } + + } + + // form.reset(); + + } + + onClick () { + + this.input.click(); + + } + + private setValue ( texture: Texture ) { + + const canvas = this.canvasEl; + const context = canvas.getContext( '2d' ); + + if ( texture !== null ) { + + const image = texture.image; + + if ( image !== undefined && image.width > 0 ) { + + canvas.title = texture.sourceFile; + + const scale = canvas.width / image.width; + context.drawImage( image, 0, 0, image.width * scale, image.height * scale ); + + } else { + + canvas.title = texture.sourceFile + ' (error)'; + context.clearRect( 0, 0, canvas.width, canvas.height ); + + } + + } else { + + canvas.title = 'empty'; + + if ( context !== null ) { + + // Seems like context can be null if the canvas is not visible + + context.clearRect( 0, 0, canvas.width, canvas.height ); + + } + + } + + this.texture = texture; + + } +} diff --git a/src/app/modules/three-js/objects/control-point.ts b/src/app/modules/three-js/objects/control-point.ts new file mode 100644 index 00000000..498a5d8d --- /dev/null +++ b/src/app/modules/three-js/objects/control-point.ts @@ -0,0 +1,213 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { BufferGeometry, Color, Geometry, Group, LineSegments, Material, Points, PointsMaterial, Vector3 } from 'three'; +import { EventEmitter } from '@angular/core'; +import { COLOR } from 'app/shared/utils/colors.service'; +import { OdTextures } from 'app/modules/tv-map/builders/od.textures'; +import { TvLaneWidth } from 'app/modules/tv-map/models/tv-lane-width'; +import { TvRoadLaneOffset } from 'app/modules/tv-map/models/tv-road-lane-offset'; +import { TvLaneRoadMark } from 'app/modules/tv-map/models/tv-lane-road-mark'; +import { TvRoad } from 'app/modules/tv-map/models/tv-road.model'; +import { TvLane } from '../../tv-map/models/tv-lane'; + +export abstract class BaseControlPoint extends Points { + + public mainObject: any; + + public tag: string; + public tagindex: number; + + public updated = new EventEmitter(); + + protected DEFAULT_CONTROL_POINT_COLOR = COLOR.BLUE; + protected HOVERED_CONTROL_POINT_COLOR = COLOR.YELLOW; + protected SELECTED_CONTROL_POINT_COLOR = COLOR.RED; + + public isSelected: boolean; + + constructor ( geometry?: Geometry | BufferGeometry, material?: Material | Material[] ) { + + super( geometry, material ); + + } + + setPosition ( position: Vector3 ) { + + this.position.copy( position ); + + this.updated.emit( this ); + } + + copyPosition ( position: Vector3 ) { + + this.setPosition( position.clone() ); + + } + + show (): void { + + this.visible = true; + + } + + hide (): void { + + this.visible = false; + + } + + onMouseOver () { + + ( this.material as PointsMaterial ).color = new Color( this.HOVERED_CONTROL_POINT_COLOR ); + ( this.material as PointsMaterial ).needsUpdate = true; + + } + + onMouseOut () { + + ( this.material as PointsMaterial ).color = new Color( this.DEFAULT_CONTROL_POINT_COLOR ); + ( this.material as PointsMaterial ).needsUpdate = true; + + } + + select () { + + this.isSelected = true; + + ( this.material as PointsMaterial ).color = new Color( this.SELECTED_CONTROL_POINT_COLOR ); + ( this.material as PointsMaterial ).needsUpdate = true; + + } + + unselect () { + + this.isSelected = false; + + ( this.material as PointsMaterial ).color = new Color( this.DEFAULT_CONTROL_POINT_COLOR ); + ( this.material as PointsMaterial ).needsUpdate = true; + + } + +} + +export class DistanceNode extends BaseControlPoint { + + constructor ( public s: number, geometry?: Geometry | BufferGeometry, material?: Material ) { + super( geometry, material ); + } + +} +export class NewDistanceNode extends BaseControlPoint { + + constructor ( public roadId, public laneId, public s: number, public t: number, geometry?: Geometry | BufferGeometry, material?: Material ) { + super( geometry, material ); + } + +} + +export class LaneWidthNode extends Group { + + public static readonly tag = 'width-node'; + public static readonly pointTag = 'width-point'; + public static readonly lineTag = 'width-line'; + + public line: LineSegments; + public point: AnyControlPoint; + + constructor ( public road: TvRoad, public lane: TvLane, public s: number, public laneWidth: TvLaneWidth ) { + + super(); + + } + + get roadId () { return this.road.id } + + get laneId () { return this.lane.id } + + updateLaneWidthValues () { + + this.road.getLaneSectionAt( this.s ).updateLaneWidthValues( this.lane ); + + } + +} + +export class LaneOffsetNode extends Group { + + public static readonly tag = 'offset-node'; + public static readonly pointTag = 'offset-point'; + public static readonly lineTag = 'offset-line'; + + public line: LineSegments; + public point: AnyControlPoint; + + constructor ( public road: TvRoad, public laneOffset: TvRoadLaneOffset ) { + + super(); + + } + + get roadId () { return this.road.id } +} + +export class LaneRoadMarkNode extends Group { + + public static readonly tag = 'roadmark-node'; + public static readonly pointTag = 'roadmark-point'; + public static readonly lineTag = 'roadmark-line'; + + public line: LineSegments; + public point: AnyControlPoint; + + constructor ( public lane: TvLane, public roadmark: TvLaneRoadMark ) { + + super(); + + } +} + + +/** + * @deprecated avoid using this use BaseControlPoint or use an exact implementation + */ +export class AnyControlPoint extends BaseControlPoint { + + static roadTag = 'road'; + + static create ( name = "", position?: Vector3 ) { + + const dotGeometry = new Geometry(); + + dotGeometry.vertices.push( new Vector3( 0, 0, 0 ) ); + + const texture = OdTextures.point; + + const dotMaterial = new PointsMaterial( { + size: 10, + sizeAttenuation: false, + map: texture, + alphaTest: 0.5, + transparent: true, + color: COLOR.BLUE, + depthTest: false + } ); + + const cp = new AnyControlPoint( dotGeometry, dotMaterial ); + + if ( position ) cp.copyPosition( position ); + + cp.userData.is_button = true; + cp.userData.is_control_point = true; + cp.userData.is_selectable = true; + + cp.tag = this.roadTag; + + cp.renderOrder = 3; + + return cp; + } + +} + diff --git a/src/app/modules/three-js/objects/editor-controls.ts b/src/app/modules/three-js/objects/editor-controls.ts new file mode 100644 index 00000000..0b2f5c96 --- /dev/null +++ b/src/app/modules/three-js/objects/editor-controls.ts @@ -0,0 +1,52 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import * as THREE from 'three'; +import { Camera, Vector3 } from 'three'; +import { IViewportController } from './i-viewport-controller'; + +import 'three/examples/js/controls/EditorControls'; + +export class EditorControls extends THREE.EditorControls implements IViewportController { + + + static getNew ( camera: Camera, canvas: HTMLCanvasElement ) { + + return new EditorControls( camera, canvas ); + } + + setTarget ( position: Vector3 ): void { + this.center = position; + } + + getTarget (): Vector3 { + return undefined; + } + + setCamera ( camera: Camera ): void { + this[ 'object' ] = camera; + } + + setEnabled ( enabled: boolean ): void { + this.enabled = enabled; + } + + setPanEnabled ( enabled: boolean ): void { + } + + setZoomEnabled ( enabled: boolean ): void { + } + + setRotateEnabled ( enabled: boolean ): void { + } + + setScreenSpaceEnabled ( enabled: boolean ): void { + } + + update (): void { + } + + reset (): void { + } +} diff --git a/src/app/modules/three-js/objects/i-viewport-controller.ts b/src/app/modules/three-js/objects/i-viewport-controller.ts new file mode 100644 index 00000000..fe65109e --- /dev/null +++ b/src/app/modules/three-js/objects/i-viewport-controller.ts @@ -0,0 +1,30 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { Camera, Vector3 } from 'three'; + +export interface IViewportController { + + enabled: boolean; + + setTarget ( position: Vector3 ): void; + + getTarget (): Vector3; + + setCamera ( camera: Camera ): void; + + setEnabled ( enabled: boolean ): void; + + setPanEnabled ( enabled: boolean ): void; + + setZoomEnabled ( enabled: boolean ): void; + + setRotateEnabled ( enabled: boolean ): void; + + setScreenSpaceEnabled ( enabled: boolean ): void; + + update (): void; + + reset (): void; +} \ No newline at end of file diff --git a/src/app/modules/three-js/objects/junction-entry.object.ts b/src/app/modules/three-js/objects/junction-entry.object.ts new file mode 100644 index 00000000..dbf92b39 --- /dev/null +++ b/src/app/modules/three-js/objects/junction-entry.object.ts @@ -0,0 +1,76 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { Color, Geometry, PointsMaterial, Vector3 } from 'three'; +import { COLOR } from 'app/shared/utils/colors.service'; +import { OdTextures } from 'app/modules/tv-map/builders/od.textures'; +import { TvRoad } from 'app/modules/tv-map/models/tv-road.model'; +import { BaseControlPoint } from './control-point'; +import { TvContactPoint } from 'app/modules/tv-map/models/tv-common'; +import { TvLane } from '../../tv-map/models/tv-lane'; + +export class JunctionEntryObject extends BaseControlPoint { + + public static tag = 'junction-dot'; + + public contact: TvContactPoint; + + public road: TvRoad; + + public lane: TvLane; + + constructor ( name: string, position: Vector3, contact: TvContactPoint, road: TvRoad, lane?: TvLane ) { + + const geometry = new Geometry(); + + geometry.vertices.push( new Vector3( 0, 0, 0 ) ); + + const texture = OdTextures.point; + + const material = new PointsMaterial( { + size: 20, + sizeAttenuation: false, + map: texture, + alphaTest: 0.5, + transparent: true, + color: COLOR.SKYBLUE, + depthTest: true + } ); + + super( geometry, material ); + + this.contact = contact; + + this.road = road; + + this.lane = lane; + + this.name = name; + + if ( position ) this.copyPosition( position ); + + this.tag = JunctionEntryObject.tag; + + this.renderOrder = 3; + + } + + select () { + + this.isSelected = true; + + ( this.material as PointsMaterial ).color = new Color( COLOR.RED ); + ( this.material as PointsMaterial ).needsUpdate = true; + + } + + unselect () { + + this.isSelected = false; + + ( this.material as PointsMaterial ).color = new Color( COLOR.SKYBLUE ); + ( this.material as PointsMaterial ).needsUpdate = true; + + } +} diff --git a/src/app/modules/three-js/objects/orbit-controls.ts b/src/app/modules/three-js/objects/orbit-controls.ts new file mode 100644 index 00000000..4643b24c --- /dev/null +++ b/src/app/modules/three-js/objects/orbit-controls.ts @@ -0,0 +1,55 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import * as THREE from 'three'; +import { Camera, Vector3 } from 'three'; +import { IViewportController } from './i-viewport-controller'; + +import 'three/examples/js/controls/OrbitControls'; + +export class OrbitControls extends THREE.OrbitControls implements IViewportController { + + static getNew ( camera, canvas ): OrbitControls { + + const controls = new OrbitControls( camera, canvas ); + + controls.screenSpacePanning = true; + controls.enableRotate = false; + controls.enableKeys = false; + + return controls; + } + + setTarget ( position: Vector3 ): void { + this.target = position; + } + + getTarget (): Vector3 { + return this.target; + } + + setCamera ( camera: Camera ): void { + this.object = camera; + } + + setEnabled ( enabled: boolean ): void { + this.enabled = enabled; + } + + setPanEnabled ( enabled: boolean ): void { + this.enablePan = enabled; + } + + setZoomEnabled ( enabled: boolean ): void { + this.enableZoom = enabled; + } + + setRotateEnabled ( enabled: boolean ): void { + this.enableRotate = enabled; + } + + setScreenSpaceEnabled ( enabled: boolean ): void { + this.screenSpacePanning = enabled; + } +} diff --git a/src/app/modules/three-js/objects/road-control-point.ts b/src/app/modules/three-js/objects/road-control-point.ts new file mode 100644 index 00000000..af96efe6 --- /dev/null +++ b/src/app/modules/three-js/objects/road-control-point.ts @@ -0,0 +1,207 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { BaseControlPoint } from './control-point'; +import { TvRoad } from 'app/modules/tv-map/models/tv-road.model'; +import { BufferAttribute, BufferGeometry, Geometry, Line, LineBasicMaterial, PointsMaterial, Vector3 } from 'three'; +import { OdTextures } from 'app/modules/tv-map/builders/od.textures'; +import { COLOR } from 'app/shared/utils/colors.service'; +import { RoadTangentPoint } from './road-tangent-point'; +import { CURVE_Y } from 'app/core/shapes/spline-config'; +import { SceneService } from 'app/core/services/scene.service'; +import { TvGeometryType } from 'app/modules/tv-map/models/tv-common'; + +export class RoadControlPoint extends BaseControlPoint { + + public static readonly tag = 'road-control-point'; + + public frontTangent: RoadTangentPoint; + public backTangent: RoadTangentPoint; + + public tangentLine: Line; + public tangentLineGeometry: BufferGeometry; + public tangentLineMaterial = new LineBasicMaterial( { + color: 0x0000ff, + linewidth: 2 + } ); + + public hdg: number = 0; + public segmentType: TvGeometryType; + + public allowChange: boolean = true; + + // tag, tagindex, cpobjidx are not used anywhere in new fixed workflow + // can add hdg here and + // remove segmentType from here to spline directly + constructor ( + public road: TvRoad, + position: Vector3, + tag = 'cp', + tagindex?: number, + cpobjidx?: number, + ) { + + super( null, null ); + + this.geometry = new Geometry(); + + this.geometry.vertices.push( new Vector3( 0, 0, 0 ) ); + + const texture = OdTextures.point; + + this.material = new PointsMaterial( { + size: 10, + sizeAttenuation: false, + map: texture, + alphaTest: 0.5, + transparent: true, + color: COLOR.BLUE, + depthTest: false + } ); + + if ( position ) this.copyPosition( position ); + + this.userData.is_button = true; + this.userData.is_control_point = true; + this.userData.is_selectable = true; + + this.tag = tag; + this.tagindex = tagindex; + + this.renderOrder = 3; + + } + + copyPosition ( position: Vector3 ) { + + if ( !this.allowChange ) return; + + super.copyPosition( position ); + + if ( this.frontTangent ) { + + this.segmentType = TvGeometryType.SPIRAL; + + const frontPosition = new Vector3( Math.cos( this.hdg ), Math.sin( this.hdg ), CURVE_Y ) + .multiplyScalar( this.frontTangent.length ) + .add( this.position ); + + this.frontTangent.copyPosition( frontPosition ); + + } + + if ( this.backTangent ) { + + this.segmentType = TvGeometryType.SPIRAL; + + const backPosition = new Vector3( Math.cos( this.hdg ), Math.sin( this.hdg ), CURVE_Y ) + .multiplyScalar( -this.backTangent.length ) + .add( this.position ); + + this.backTangent.copyPosition( backPosition ); + + } + + if ( this.frontTangent || this.backTangent ) { + + this.updateTangentLine(); + + } + + } + + show () { + + super.show(); + + if ( this.frontTangent ) this.frontTangent.show(); + + if ( this.backTangent ) this.backTangent.show(); + + if ( this.tangentLine ) this.tangentLine.visible = true; + } + + hide () { + + super.hide(); + + if ( this.frontTangent ) this.frontTangent.hide(); + + if ( this.backTangent ) this.backTangent.hide(); + + if ( this.tangentLine ) this.tangentLine.visible = false; + + } + + addDefaultTangents ( hdg: number, frontLength: number, backLength: number ) { + + const frontPosition = new Vector3( Math.cos( hdg ), Math.sin( hdg ), CURVE_Y ) + .multiplyScalar( frontLength ) + .add( this.position ) + + const backPosition = new Vector3( Math.cos( hdg ), Math.sin( hdg ), CURVE_Y ) + .multiplyScalar( -backLength ) + .add( this.position ); + + this.frontTangent = new RoadTangentPoint( + this.road, + frontPosition, + "tpf", + this.tagindex, + this.tagindex + 1 + this.tagindex * 2, + this, + ); + + this.backTangent = new RoadTangentPoint( + this.road, + backPosition, + "tpb", + this.tagindex, + this.tagindex + 1 + this.tagindex * 2 + 1, + this, + ); + + // TODO: move this maybe somewhere else + + SceneService.add( this.frontTangent ); + + SceneService.add( this.backTangent ); + + this.tangentLineGeometry = new BufferGeometry().setFromPoints( [ this.frontTangent.position, this.backTangent.position ] ); + + this.tangentLine = new Line( this.tangentLineGeometry, this.tangentLineMaterial ); + + this.tangentLine.castShadow = true; + + this.tangentLine.renderOrder = 3; + + this.tangentLine.frustumCulled = false; + + SceneService.add( this.tangentLine ); + } + + updateTangentLine () { + + if ( this.tangentLine && this.backTangent ) { + + const buffer = this.tangentLineGeometry.attributes.position as BufferAttribute; + + buffer.setXYZ( 0, this.frontTangent.position.x, this.frontTangent.position.y, this.frontTangent.position.z ); + + buffer.setXYZ( 1, this.backTangent.position.x, this.backTangent.position.y, this.backTangent.position.z ); + + buffer.needsUpdate = true; + } + + } + + moveForward ( s: number ): RoadControlPoint { + + const x = this.position.x + Math.cos( this.hdg ) * s; + const y = this.position.y + Math.sin( this.hdg ) * s; + + return new RoadControlPoint( this.road, new Vector3( x, y, 0 ), this.tag, this.tagindex, this.tagindex ); + } + +} diff --git a/src/app/modules/three-js/objects/road-node.ts b/src/app/modules/three-js/objects/road-node.ts new file mode 100644 index 00000000..64d04ff5 --- /dev/null +++ b/src/app/modules/three-js/objects/road-node.ts @@ -0,0 +1,45 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { Color, Group, LineSegments, LineBasicMaterial } from 'three'; +import { COLOR } from 'app/shared/utils/colors.service'; + +export class RoadNode extends Group { + + public static readonly tag = 'road-node'; + public static readonly lineTag = 'road-node-line'; + + public static defaultColor = COLOR.MAGENTA; + public static defaultOpacity = 0.35; + public static defaultWidth = 5; + + public line: LineSegments; + public isSelected = false; + + constructor ( public roadId: number, public distance: 'start' | 'end', public s?: number ) { + + super(); + + } + + get material () { return this.line.material as LineBasicMaterial; } + + selected () { + + this.isSelected = true; + + this.material.color = new Color( COLOR.RED ); + + this.renderOrder = 5; + } + + unselected () { + + this.isSelected = false; + + this.material.color = new Color( RoadNode.defaultColor ); + + this.renderOrder = 3; + } +} diff --git a/src/app/modules/three-js/objects/road-tangent-point.ts b/src/app/modules/three-js/objects/road-tangent-point.ts new file mode 100644 index 00000000..48efaaaa --- /dev/null +++ b/src/app/modules/three-js/objects/road-tangent-point.ts @@ -0,0 +1,118 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { BaseControlPoint } from './control-point'; +import { TvRoad } from 'app/modules/tv-map/models/tv-road.model'; +import { Geometry, PointsMaterial, Vector3 } from 'three'; +import { OdTextures } from 'app/modules/tv-map/builders/od.textures'; +import { COLOR } from 'app/shared/utils/colors.service'; +import { RoadControlPoint } from './road-control-point'; +import { CURVE_Y } from 'app/core/shapes/spline-config'; +import { TvGeometryType } from 'app/modules/tv-map/models/tv-common'; + +export class RoadTangentPoint extends BaseControlPoint { + + // public static readonly tag = 'road-tangent-point'; + + public length = 1; + + // tag, tagindex, cpobjidx are not used anywhere in new fixed workflow + constructor ( + public road: TvRoad, + position: Vector3, + public tag: 'tpf' | 'tpb', + public tagindex: number, + public cpobjidx: number, + public controlPoint: RoadControlPoint + ) { + + super( null, null ); + + this.geometry = new Geometry(); + + this.geometry.vertices.push( new Vector3( 0, 0, 0 ) ); + + const texture = OdTextures.point; + + this.material = new PointsMaterial( { + size: 10, + sizeAttenuation: false, + map: texture, + alphaTest: 0.5, + transparent: true, + color: COLOR.BLUE, + depthTest: false + } ); + + if ( position ) this.position.copy( position.clone() ); + + this.userData.is_button = true; + this.userData.is_control_point = true; + this.userData.is_selectable = true; + + this.tag = tag; + + this.tagindex = tagindex; + + this.renderOrder = 3; + + } + + copyPosition ( position: Vector3 ) { + + super.copyPosition( position ); + + this.controlPoint.segmentType = TvGeometryType.SPIRAL; + + if ( this.tag === "tpf" && this.controlPoint.frontTangent ) { + + const delta = new Vector3().subVectors( + this.controlPoint.frontTangent.position, + this.controlPoint.position + ); + + this.controlPoint.hdg = Math.atan2( delta.y, delta.x ); + + this.length = delta.length(); + + } else if ( this.tag === "tpb" && this.controlPoint.backTangent ) { + + const delta = new Vector3().subVectors( + this.controlPoint.backTangent.position, + this.controlPoint.position + ); + + this.controlPoint.hdg = Math.PI + Math.atan2( delta.y, delta.x ); + + this.length = delta.length(); + + } + + if ( this.controlPoint.frontTangent ) { + + this.controlPoint.frontTangent.position.set( + Math.cos( this.controlPoint.hdg ), + Math.sin( this.controlPoint.hdg ), + CURVE_Y + ).multiplyScalar( this.controlPoint.frontTangent.length ) + .add( this.controlPoint.position ); + + } + + if ( this.controlPoint.backTangent ) { + + this.controlPoint.backTangent.position.set( + Math.cos( this.controlPoint.hdg ), + Math.sin( this.controlPoint.hdg ), + CURVE_Y + ).multiplyScalar( -this.controlPoint.backTangent.length ) + .add( this.controlPoint.position ); + + } + + this.controlPoint.updateTangentLine(); + + } + +} diff --git a/src/app/modules/three-js/objects/tv-material.model.ts b/src/app/modules/three-js/objects/tv-material.model.ts new file mode 100644 index 00000000..6a90ca67 --- /dev/null +++ b/src/app/modules/three-js/objects/tv-material.model.ts @@ -0,0 +1,337 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { MeshStandardMaterial, MeshStandardMaterialParameters, Math, Texture } from 'three'; +import { AppService } from 'app/core/services/app.service'; + +export class TvMaterial extends MeshStandardMaterial { + + public mapGuid: string; + public roughnessMapGuid: string; + public normalMapGuid: string; + public aoMapGuid: string; + public displacementMapGuid: string; + + + constructor ( public guid: string, parameters?: MeshStandardMaterialParameters ) { + super( parameters ) + } + + static new ( name = 'NewMaterial' ) { + + return new TvMaterial( Math.generateUUID(), { name: name } ); + + } + + static parseString ( value: string ): TvMaterial { + + return this.parseJSON( JSON.parse( value ) ); + + } + + static parseJSON ( json: any ): TvMaterial { + + function getTexture ( guid ) { + + return AppService.assets.getInstance( guid ) as Texture; + + } + + var material = new TvMaterial( Math.generateUUID() ); + + if ( json.uuid !== undefined ) material.uuid = json.uuid; + if ( json.name !== undefined ) material.name = json.name; + if ( json.color !== undefined ) material.color.setHex( json.color ); + if ( json.roughness !== undefined ) material.roughness = json.roughness; + if ( json.metalness !== undefined ) material.metalness = json.metalness; + if ( json.emissive !== undefined ) material.emissive.setHex( json.emissive ); + // if ( json.specular !== undefined ) material.specular.setHex( json.specular ); + // if ( json.shininess !== undefined ) material.shininess = json.shininess; + // if ( json.clearCoat !== undefined ) material.clearCoat = json.clearCoat; + // if ( json.clearCoatRoughness !== undefined ) material.clearCoatRoughness = json.clearCoatRoughness; + if ( json.vertexColors !== undefined ) material.vertexColors = json.vertexColors; + if ( json.fog !== undefined ) material.fog = json.fog; + if ( json.flatShading !== undefined ) material.flatShading = json.flatShading; + if ( json.blending !== undefined ) material.blending = json.blending; + // if ( json.combine !== undefined ) material.combine = json.combine; + if ( json.side !== undefined ) material.side = json.side; + if ( json.opacity !== undefined ) material.opacity = json.opacity; + if ( json.transparent !== undefined ) material.transparent = json.transparent; + if ( json.alphaTest !== undefined ) material.alphaTest = json.alphaTest; + if ( json.depthTest !== undefined ) material.depthTest = json.depthTest; + if ( json.depthWrite !== undefined ) material.depthWrite = json.depthWrite; + if ( json.colorWrite !== undefined ) material.colorWrite = json.colorWrite; + if ( json.wireframe !== undefined ) material.wireframe = json.wireframe; + if ( json.wireframeLinewidth !== undefined ) material.wireframeLinewidth = json.wireframeLinewidth; + // if ( json.wireframeLinecap !== undefined ) material.wireframeLinecap = json.wireframeLinecap; + // if ( json.wireframeLinejoin !== undefined ) material.wireframeLinejoin = json.wireframeLinejoin; + + // if ( json.rotation !== undefined ) material.rotation = json.rotation; + + // if ( json.linewidth !== 1 ) material.linewidth = json.linewidth; + // if ( json.dashSize !== undefined ) material.dashSize = json.dashSize; + // if ( json.gapSize !== undefined ) material.gapSize = json.gapSize; + // if ( json.scale !== undefined ) material.scale = json.scale; + + if ( json.polygonOffset !== undefined ) material.polygonOffset = json.polygonOffset; + if ( json.polygonOffsetFactor !== undefined ) material.polygonOffsetFactor = json.polygonOffsetFactor; + if ( json.polygonOffsetUnits !== undefined ) material.polygonOffsetUnits = json.polygonOffsetUnits; + + if ( json.skinning !== undefined ) material.skinning = json.skinning; + if ( json.morphTargets !== undefined ) material.morphTargets = json.morphTargets; + if ( json.dithering !== undefined ) material.dithering = json.dithering; + + if ( json.visible !== undefined ) material.visible = json.visible; + if ( json.userData !== undefined ) material.userData = json.userData; + + // maps + if ( json.mapGuid !== undefined ) { + material.mapGuid = json.mapGuid; + material.map = getTexture( json.mapGuid ); + } + + if ( json.roughnessMapGuid !== undefined ) { + material.roughnessMapGuid = json.roughnessMapGuid; + material.roughnessMap = getTexture( json.roughnessMapGuid ); + } + + if ( json.normalMapGuid !== undefined ) { + material.normalMapGuid = json.normalMapGuid; + material.normalMap = getTexture( json.normalMapGuid ); + } + + if ( json.aoMapGuid !== undefined ) { + material.aoMapGuid = json.aoMapGuid; + material.aoMap = getTexture( json.aoMapGuid ); + } + + if ( json.displacementMapGuid !== undefined ) { + material.displacementMapGuid = json.displacementMapGuid; + material.displacementMap = getTexture( json.displacementMapGuid ); + } + + + // if ( json.alphaMap !== undefined ) { + + // material.alphaMap = getTexture( json.alphaMap ); + // material.transparent = true; + + // } + + // if ( json.bumpMap !== undefined ) material.bumpMap = getTexture( json.bumpMap ); + // if ( json.bumpScale !== undefined ) material.bumpScale = json.bumpScale; + + // if ( json.normalMap !== undefined ) material.normalMap = getTexture( json.normalMap ); + + // if ( json.displacementMap !== undefined ) material.displacementMap = getTexture( json.displacementMap ); + // if ( json.displacementScale !== undefined ) material.displacementScale = json.displacementScale; + // if ( json.displacementBias !== undefined ) material.displacementBias = json.displacementBias; + + // if ( json.roughnessMap !== undefined ) material.roughnessMap = getTexture( json.roughnessMap ); + // if ( json.metalnessMap !== undefined ) material.metalnessMap = getTexture( json.metalnessMap ); + + // if ( json.emissiveMap !== undefined ) material.emissiveMap = getTexture( json.emissiveMap ); + // if ( json.emissiveIntensity !== undefined ) material.emissiveIntensity = json.emissiveIntensity; + + // // if ( json.specularMap !== undefined ) material.specularMap = getTexture( json.specularMap ); + + // if ( json.envMap !== undefined ) material.envMap = getTexture( json.envMap ); + // if ( json.envMapIntensity !== undefined ) material.envMapIntensity = json.envMapIntensity; + + // // if ( json.reflectivity !== undefined ) material.reflectivity = json.reflectivity; + + // if ( json.lightMap !== undefined ) material.lightMap = getTexture( json.lightMap ); + // if ( json.lightMapIntensity !== undefined ) material.lightMapIntensity = json.lightMapIntensity; + + // if ( json.aoMap !== undefined ) material.aoMap = getTexture( json.aoMap ); + // if ( json.aoMapIntensity !== undefined ) material.aoMapIntensity = json.aoMapIntensity; + + // // if ( json.gradientMap !== undefined ) material.gradientMap = getTexture( json.gradientMap ); + + return material; + } + + toJSON ( meta?: any ): any { + + var isRoot = ( meta === undefined || typeof meta === 'string' ); + + if ( isRoot ) { + + meta = { + textures: {}, + images: {} + }; + + } + + var data: any = { + version: 0.1, + }; + + // standard Material serialization + data.uuid = this.uuid; + data.type = this.type; + + if ( this.name !== '' ) data.name = this.name; + + if ( this.color ) data.color = this.color.getHex(); + + if ( this.roughness !== undefined ) data.roughness = this.roughness; + if ( this.metalness !== undefined ) data.metalness = this.metalness; + + if ( this.emissive ) data.emissive = this.emissive.getHex(); + if ( this.emissiveIntensity !== 1 ) data.emissiveIntensity = this.emissiveIntensity; + + // if ( this.specular && this.specular.isColor ) data.specular = this.specular.getHex(); + // if ( this.shininess !== undefined ) data.shininess = this.shininess; + // if ( this.clearCoat !== undefined ) data.clearCoat = this.clearCoat; + // if ( this.clearCoatRoughness !== undefined ) data.clearCoatRoughness = this.clearCoatRoughness; + + if ( this.mapGuid ) data.mapGuid = this.mapGuid; + + if ( this.roughnessMapGuid ) data.roughnessMapGuid = this.roughnessMapGuid; + + if ( this.normalMapGuid ) data.normalMapGuid = this.normalMapGuid; + + if ( this.aoMapGuid ) data.aoMapGuid = this.aoMapGuid; + + if ( this.displacementMapGuid ) data.displacementMapGuid = this.displacementMapGuid; + + // if ( this.alphaMap && this.alphaMap.isTexture ) data.alphaMap = this.alphaMap.toJSON( meta ).uuid; + // if ( this.lightMap && this.lightMap.isTexture ) data.lightMap = this.lightMap.toJSON( meta ).uuid; + + // if ( this.aoMap && this.aoMap.isTexture ) { + + // data.aoMap = this.aoMap.toJSON( meta ).uuid; + // data.aoMapIntensity = this.aoMapIntensity; + + // } + + // if ( this.bumpMap && this.bumpMap.isTexture ) { + + // data.bumpMap = this.bumpMap.toJSON( meta ).uuid; + // data.bumpScale = this.bumpScale; + + // } + + // if ( this.normalMap && this.normalMap.isTexture ) { + + // data.normalMap = this.normalMap.toJSON( meta ).uuid; + // data.normalMapType = this.normalMapType; + // data.normalScale = this.normalScale.toArray(); + + // } + + // if ( this.displacementMap && this.displacementMap.isTexture ) { + + // data.displacementMap = this.displacementMap.toJSON( meta ).uuid; + // data.displacementScale = this.displacementScale; + // data.displacementBias = this.displacementBias; + + // } + + // if ( this.roughnessMap && this.roughnessMap.isTexture ) data.roughnessMap = this.roughnessMap.toJSON( meta ).uuid; + // if ( this.metalnessMap && this.metalnessMap.isTexture ) data.metalnessMap = this.metalnessMap.toJSON( meta ).uuid; + + // if ( this.emissiveMap && this.emissiveMap.isTexture ) data.emissiveMap = this.emissiveMap.toJSON( meta ).uuid; + // if ( this.specularMap && this.specularMap.isTexture ) data.specularMap = this.specularMap.toJSON( meta ).uuid; + + // if ( this.envMap && this.envMap.isTexture ) { + + // data.envMap = this.envMap.toJSON( meta ).uuid; + // data.reflectivity = this.reflectivity; // Scale behind envMap + + // if ( this.combine !== undefined ) data.combine = this.combine; + // if ( this.envMapIntensity !== undefined ) data.envMapIntensity = this.envMapIntensity; + + // } + + // if ( this.gradientMap && this.gradientMap.isTexture ) { + + // data.gradientMap = this.gradientMap.toJSON( meta ).uuid; + + // } + + // if ( this.size !== undefined ) data.size = this.size; + // if ( this.sizeAttenuation !== undefined ) data.sizeAttenuation = this.sizeAttenuation; + + // if ( this.blending !== NormalBlending ) data.blending = this.blending; + // if ( this.flatShading === true ) data.flatShading = this.flatShading; + // if ( this.side !== FrontSide ) data.side = this.side; + // if ( this.vertexColors !== NoColors ) data.vertexColors = this.vertexColors; + + // if ( this.opacity < 1 ) data.opacity = this.opacity; + // if ( this.transparent === true ) data.transparent = this.transparent; + + data.depthFunc = this.depthFunc; + data.depthTest = this.depthTest; + data.depthWrite = this.depthWrite; + + // // rotation (SpriteMaterial) + // if ( this.rotation !== 0 ) data.rotation = this.rotation; + + // if ( this.polygonOffset === true ) data.polygonOffset = true; + // if ( this.polygonOffsetFactor !== 0 ) data.polygonOffsetFactor = this.polygonOffsetFactor; + // if ( this.polygonOffsetUnits !== 0 ) data.polygonOffsetUnits = this.polygonOffsetUnits; + + // if ( this.linewidth !== 1 ) data.linewidth = this.linewidth; + // if ( this.dashSize !== undefined ) data.dashSize = this.dashSize; + // if ( this.gapSize !== undefined ) data.gapSize = this.gapSize; + // if ( this.scale !== undefined ) data.scale = this.scale; + + // if ( this.dithering === true ) data.dithering = true; + + // if ( this.alphaTest > 0 ) data.alphaTest = this.alphaTest; + // if ( this.premultipliedAlpha === true ) data.premultipliedAlpha = this.premultipliedAlpha; + + // if ( this.wireframe === true ) data.wireframe = this.wireframe; + // if ( this.wireframeLinewidth > 1 ) data.wireframeLinewidth = this.wireframeLinewidth; + // if ( this.wireframeLinecap !== 'round' ) data.wireframeLinecap = this.wireframeLinecap; + // if ( this.wireframeLinejoin !== 'round' ) data.wireframeLinejoin = this.wireframeLinejoin; + + // if ( this.morphTargets === true ) data.morphTargets = true; + // if ( this.skinning === true ) data.skinning = true; + + // if ( this.visible === false ) data.visible = false; + + if ( JSON.stringify( this.userData ) !== '{}' ) data.userData = this.userData; + + // TODO: Copied from Object3D.toJSON + + function extractFromCache ( cache ) { + + var values = []; + + for ( var key in cache ) { + + var data = cache[ key ]; + delete data.metadata; + values.push( data ); + + } + + return values; + + } + + if ( isRoot ) { + + var textures = extractFromCache( meta.textures ); + var images = extractFromCache( meta.images ); + + if ( textures.length > 0 ) data.textures = textures; + if ( images.length > 0 ) data.images = images; + + } + + return data; + + } + + toJSONString (): string { + + return JSON.stringify( this.toJSON(), null, 2 ); + + } +} \ No newline at end of file diff --git a/src/app/modules/three-js/pointer-guides/pointer-guides.component.html b/src/app/modules/three-js/pointer-guides/pointer-guides.component.html new file mode 100644 index 00000000..4b7973b3 --- /dev/null +++ b/src/app/modules/three-js/pointer-guides/pointer-guides.component.html @@ -0,0 +1,4 @@ + + diff --git a/src/app/modules/three-js/pointer-guides/pointer-guides.component.ts b/src/app/modules/three-js/pointer-guides/pointer-guides.component.ts new file mode 100644 index 00000000..4831fd71 --- /dev/null +++ b/src/app/modules/three-js/pointer-guides/pointer-guides.component.ts @@ -0,0 +1,96 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { Component, OnInit } from '@angular/core'; +import * as THREE from 'three'; +import { ThreeService } from 'app/modules/three-js/three.service'; +import { AppService } from 'app/core/services/app.service'; +import { PointerMoveData } from 'app/events/pointer-event-data'; + +@Component( { + selector: 'app-pointer-guides', + templateUrl: './pointer-guides.component.html' +} ) +export class PointerGuidesComponent implements OnInit { + + draw: boolean = false; + + private CROSSHAIR_COLOR = 0x000000; + + private lineMaterial = new THREE.LineBasicMaterial( { + color: this.CROSSHAIR_COLOR, + linewidth: 10, + } ); + + private lineGeometry = new THREE.BufferGeometry(); + private verticalLine = new THREE.Line( this.lineGeometry, this.lineMaterial ); + private horizontalLine = new THREE.Line( this.lineGeometry, this.lineMaterial ); + + constructor ( + private engineService: ThreeService, + ) { + } + + ngOnInit () { + + if ( this.draw ) { + + AppService.eventSystem.pointerMoved.subscribe( e => this.onPointerMoved( e ) ); + + } + + } + + onPointerMoved ( e: PointerMoveData ): any { + + this.updateLineHelpers( e ); + + } + + updateLineHelpers ( data: PointerMoveData ): void { + + this.updateCursorType(); + + this.engineService.remove( this.verticalLine ); + this.engineService.remove( this.horizontalLine ); + + // DRAW IF CURSOR IS ON IMAGE + if ( data ) { + + const point = data.point; + + var geometry = new THREE.Geometry(); + geometry.vertices.push( new THREE.Vector3( point.x, -10000, 1 ) ); + geometry.vertices.push( new THREE.Vector3( point.x, 10000, 1 ) ); + this.horizontalLine = new THREE.Line( geometry, this.lineMaterial ); + this.horizontalLine.computeLineDistances(); + + var geometry = new THREE.Geometry(); + geometry.vertices.push( new THREE.Vector3( -10000, point.y, 1 ) ); + geometry.vertices.push( new THREE.Vector3( 10000, point.y, 1 ) ); + this.verticalLine = new THREE.Line( geometry, this.lineMaterial ); + this.verticalLine.computeLineDistances(); + + this.engineService.add( this.verticalLine ); + this.engineService.add( this.horizontalLine ); + + } + + } + + updateCursorType (): any { + + // this.engineService.canvas.style.cursor = 'crosshair'; + + // if ( this.inputService.isShiftKeyDown ) { + // // this.engineService.canvas.style.cursor = 'grab'; + // } else if ( this.engineService.cursorOnBox && !this.drawing ) { + // this.engineService.canvas.style.cursor = 'move'; + // } else { + // this.engineService.canvas.style.cursor = 'crosshair'; + // } + + } + +} diff --git a/src/app/modules/three-js/primitives.ts b/src/app/modules/three-js/primitives.ts new file mode 100644 index 00000000..9c58e0a5 --- /dev/null +++ b/src/app/modules/three-js/primitives.ts @@ -0,0 +1,124 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { COLOR } from '../../shared/utils/colors.service'; +import * as THREE from 'three'; +import { Mesh, Object3D, PlaneBufferGeometry, Vector2, Vector3 } from 'three'; +import { SceneService } from '../../core/services/scene.service'; + +export enum PrimitiveType { + CUBE = 1, + PLANE = 2, +} + +export class BoundingBoxObject { + + public width: number = 10; + public height: number = 10; + public geometry = new PlaneBufferGeometry( this.width, this.height ); + private color = COLOR.DARKGRAY; + private opacity = 0.4; + private material = new THREE.MeshBasicMaterial( { + color: this.color, + opacity: this.opacity, + transparent: true + } ); + public mesh = new Mesh( this.geometry, this.material ); + + // BORDER + private borderGeometry = new THREE.EdgesGeometry( this.geometry ); + private borderMaterial = new THREE.LineBasicMaterial( { color: this.color } ); + private border = new THREE.LineSegments( this.borderGeometry, this.borderMaterial ); + private borderAdded = false; + + constructor ( start?: Vector3, end?: Vector3 ) { + + this.mesh.userData.is_annotation = true; + this.mesh.userData.uuid = this.mesh.uuid; + + SceneService.add( this.mesh, true ); + + if ( start && end ) this.updateGeometry( start, end ); + } + + static addBoxInfo ( box: Object3D, start: Vector3, end: THREE.Vector3, size: Vector2 ): any { + + box.userData.is_annotation = true; + box.userData.type = 'box'; + + box.userData.width = size.x; + box.userData.height = size.y; + + box.userData.startX = start.x; + box.userData.startY = start.y; + + box.userData.endX = end.x; + box.userData.endY = end.y; + } + + static getDimensions ( p1: Vector3, p2: Vector3 ): Vector2 { + + let width = Math.abs( p1.x - p2.x ); + let height = Math.abs( p1.y - p2.y ); + + width = Math.round( Math.max( width, 10 ) ); + height = Math.round( Math.max( height, 10 ) ); + + return new Vector2( + width, + height + ); + } + + setColor ( hexColorString: string ): any { + this.material.color.set( hexColorString ); + this.borderMaterial.color.set( hexColorString ); + } + + updateGeometry ( start: Vector3, end: Vector3 ) { + + let size = BoundingBoxObject.getDimensions( start, end ); + + this.width = size.x; + this.height = size.y; + + this.geometry = new PlaneBufferGeometry( size.x, size.y, 1, 1 ); + + this.mesh.geometry.dispose(); + + this.mesh.geometry = this.geometry; + + this.setPosition( this.mesh, start, end, size ); + + this.updateBorderGeometry( start, end, size ); + + BoundingBoxObject.addBoxInfo( this.mesh, start, end, size ); + } + + updateBorderGeometry ( start, end, size ) { + + this.borderGeometry = new THREE.EdgesGeometry( this.geometry ); + + this.border.geometry.dispose(); + this.border.geometry = this.borderGeometry; + + if ( !this.borderAdded ) { + this.mesh.add( this.border ); + this.borderAdded = true; + } + } + + setPosition ( object, start: Vector3, end: Vector3, dimensions: Vector2 ) { + + if ( end.x < start.x ) dimensions.x *= -1; + if ( end.y < start.y ) dimensions.y *= -1; + + object.position.set( start.x + (dimensions.x / 2), start.y + (dimensions.y / 2), 0.1 ); + } + + destroy () { + SceneService.remove( this.mesh, true ); + } + +} diff --git a/src/app/modules/three-js/three-js.module.ts b/src/app/modules/three-js/three-js.module.ts new file mode 100644 index 00000000..4ac23733 --- /dev/null +++ b/src/app/modules/three-js/three-js.module.ts @@ -0,0 +1,27 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { ViewportComponent } from './viewport/viewport.component'; +import { PointerGuidesComponent } from './pointer-guides/pointer-guides.component'; +import { SharedModule } from 'app/shared/shared.module'; +import { ColorPickerModule } from 'ngx-color-picker'; +import { MatInputModule } from '@angular/material'; +import { TextureFieldComponent } from './inspectors/texture-field/texture-field.component'; + +@NgModule( { + declarations: [ ViewportComponent, PointerGuidesComponent, TextureFieldComponent ], + imports: [ + CommonModule, + SharedModule, + ColorPickerModule, + MatInputModule + ], + exports: [ + ViewportComponent, + ] +} ) +export class ThreeJsModule { +} diff --git a/src/app/modules/three-js/three.service.ts b/src/app/modules/three-js/three.service.ts new file mode 100644 index 00000000..cb40054f --- /dev/null +++ b/src/app/modules/three-js/three.service.ts @@ -0,0 +1,567 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { Injectable } from '@angular/core'; + +import * as THREE from 'three'; +import { Material, Object3D, OrthographicCamera, PerspectiveCamera, WebGLRenderer } from 'three'; +import './EnableThreeExamples'; +import 'three/examples/js/controls/DragControls'; +import 'three/examples/js/controls/TransformControls'; + +import 'three/examples/js/postprocessing/EffectComposer'; +import 'three/examples/js/postprocessing/RenderPass'; +import 'three/examples/js/postprocessing/ShaderPass'; +import 'three/examples/js/postprocessing/OutlinePass'; +import 'three/examples/js/shaders/CopyShader'; + +import { Maths } from 'app/utils/maths'; +import { SetPositionCommand } from './commands/set-position-command'; +import { CommandHistory } from 'app/services/command-history'; +import { IEngine } from '../../core/services/IEngine'; +import { SceneService } from '../../core/services/scene.service'; +import { IViewportController } from './objects/i-viewport-controller'; +import { OrbitControls } from './objects/orbit-controls'; +import { COLOR } from 'app/shared/utils/colors.service'; + +@Injectable( { + providedIn: 'root' +} ) +export class ThreeService implements IEngine { + + + public canvas: HTMLCanvasElement; + public renderer: THREE.WebGLRenderer; + public mouse: THREE.Vector2 = new THREE.Vector2; + public image: THREE.Mesh; + + public static controls: IViewportController; + + public canvasWidth: number; + public canvasHeight: number; + public leftOffset: number; + public topOffset: number; + public ORTHO_DRIVER = 4; + + private currentCameraIndex = 0; + private cameras: THREE.Camera[] = []; + private composer: THREE.EffectComposer; + private transformControls: THREE.TransformControls; + private light: THREE.AmbientLight; + private objectPositionOnDown: THREE.Vector3 = null; + static bgForClicks: THREE.Mesh; + + constructor () { + + } + + public get camera () { + return this.cameras[ this.currentCameraIndex ]; + } + + setupScene ( canvas: HTMLCanvasElement, renderer: WebGLRenderer ): void { + + this.canvas = canvas; + this.renderer = renderer; + + // const box = this.getCanvasBounds(); + // this.CANVAS_WIDTH = box.width === 0 ? this.CANVAS_WIDTH : box.width; + // this.CANVAS_HEIGHT = box.height === 0 ? this.CANVAS_HEIGHT : box.height;CO + SceneService.scene.background = new THREE.Color( COLOR.BLACK );// new THREE.Color( 0x2e2e2e ); + + this.createCameras(); + + this.createSceneHelpers(); + } + + addDirectionalLight () { + + const directionaLight = new THREE.DirectionalLight( '0xffffff', 1 ); + + directionaLight.position.set( 5, 10, 7.5 ); + + SceneService.addHelper( directionaLight ); + + SceneService.addHelper( directionaLight.target ); + + const ambientLight = new THREE.AmbientLight( 0xE6E6E6, 1 ); + + SceneService.addHelper( ambientLight ); + + } + + createControls (): void { + + ThreeService.controls = OrbitControls.getNew( this.camera, this.canvas ); + // ThreeService.controls = EditorControls.getNew( this.camera, this.canvas ); + + } + + createTransformControls (): void { + + const self: ThreeService = this; + + this.transformControls = new THREE.TransformControls( this.camera, this.canvas ); + + this.transformControls.addEventListener( 'dragging-changed', function ( event ) { + + ThreeService.controls.enabled = !event.value; + + } ); + + this.transformControls.addEventListener( 'mouseDown', () => { + + var object = self.transformControls.object; + + + this.objectPositionOnDown = object.position.clone(); + // this.objectRotationOnDown = object.rotation.clone(); + // this.objectScaleOnDown = object.scale.clone(); + + ThreeService.controls.enabled = false; + + } ); + + this.transformControls.addEventListener( 'mouseUp', function () { + + var object = self.transformControls.object; + + if ( object !== undefined ) { + + switch ( self.transformControls.getMode() ) { + + case 'translate': + + if ( !self.objectPositionOnDown.equals( object.position ) ) { + + CommandHistory.execute( new SetPositionCommand( object, object.position, self.objectPositionOnDown ) ); + + } + + break; + + default: + break; + } + + } + + ThreeService.controls.enabled = true; + + } ); + + + SceneService.addHelper( this.transformControls ); + + } + + createGridHelper (): void { + + var gridHelper = new THREE.GridHelper( 1000, 100 ); + + ( gridHelper.material as Material ).transparent = true; + ( gridHelper.material as Material ).opacity = 0.2; + ( gridHelper.material as Material ).needsUpdate = false; + + // to adjust with up Z + gridHelper.rotateX( Maths.Deg2Rad * 90 ); + + SceneService.addHelper( gridHelper ); + + } + + createSceneHelpers (): any { + + this.addDirectionalLight(); + + this.setupPostProcessing(); + + this.createControls(); + + this.createDragControls(); + + this.createGridHelper(); + + this.createTransformControls(); + + this.createBackgroundPlaneForClicks(); + + this.addAxesHelper(); + } + + createBackgroundPlaneForClicks () { + + ThreeService.bgForClicks = new THREE.Mesh( new THREE.PlaneBufferGeometry( 10000, 10000 ), new THREE.MeshBasicMaterial( { + color: 0xFFFFFF, + transparent: true, + opacity: 0 + } ) ); + + ThreeService.bgForClicks.name = 'bgForClicks'; + + SceneService.add( ThreeService.bgForClicks, true ); + } + + setupPostProcessing (): any { + + // // TODO : Move this to component, listen to resize to fix blurriness + // + // // postprocessing + // this.composer = new THREE.EffectComposer( this.renderer ); + // + // let renderPass = new THREE.RenderPass( this.scene, this.camera ); + // + // + // let copyPass = new THREE.ShaderPass( THREE.CopyShader ); + // copyPass.renderToScreen = false; + // + // let res = new Vector2( this.CANVAS_WIDTH, this.CANVAS_HEIGHT ); + // let outlinePass = new OutlinePass( res, this.scene, this.camera ); + // + // this.composer.addPass( renderPass ); + // // this.composer.addPass( copyPass ); + // // this.composer.addPass( outlinePass ); + + } + + createDragControls (): any { + + + } + + // addImageSprite (): any { + + // const self: ThreeService = this; + + // // const image = "assets/checkered.png"; + // const image = 'assets/buildings.jpeg'; + + // var map = new THREE.TextureLoader().load( image, function ( texture ) { + + // var material = new THREE.SpriteMaterial( { map: texture, color: 0xffffff } ); + // var sprite = new THREE.Sprite( material ); + + // sprite.scale.set( texture.image.width, 2622, 1 ); + + // sprite.position.set( texture.image.width / 2, texture.image.height / 2, 0 ); + // // self.camera.position.set( texture.image.width / 2, texture.image.height / 2, 20 ); + // // self.camera.lookAt( new THREE.Vector3( texture.image.width / 2, texture.image.height / 2, 0 ) ); + // // self.camera.up.set( 0, 1, 0 ); + // // self.camera.lookAt( 1311, 1311, 0 ); + // // self.camera.position.set( 600, 0, 0 ); + // // self.camera.position.z = 20; + + // self.camera.updateProjectionMatrix(); + + // self.add( sprite, true ); + + // } ); + + // } + + // addImageScreen (): any { + // + // const self: ThreeService = this; + // + // // const url = "http://true-label.test/images/checkered.png"; + // // const url = "https://picsum.photos/1200/900"; + // // const url = "assets/highway.jpg"; + // const url = 'assets/pedestrians/Frame_ (1).png'; + // // const url = "assets/buildings.jpeg"; + // // const url = "assets/sample.png"; + // + // // load a texture, set wrap mode to repeat + // const textureLoader = new THREE.TextureLoader(); + // + // textureLoader.crossOrigin = ''; + // + // textureLoader.load( url, function ( texture ) { + // + // texture.wrapS = THREE.RepeatWrapping; + // texture.wrapT = THREE.RepeatWrapping; + // + // var geometry = new THREE.PlaneGeometry( texture.image.width, texture.image.height ); + // var material = new THREE.MeshBasicMaterial( { map: texture, side: THREE.DoubleSide } ); + // + // self.image = new THREE.Mesh( geometry, material ); + // + // self.image.userData.is_image = true; + // + // self.image.position.set( texture.image.width / 2, texture.image.height / 2, 0 ); + // + // self.camera.position.set( texture.image.width / 2, texture.image.height / 2, 20 ); + // + // self.orbitControls.target.set( texture.image.width / 2, texture.image.height / 2, 0 ); + // + // self.camera.updateProjectionMatrix(); + // + // self.add( self.image, true ); + // + // } ); + // } + + animate (): void { + // window.addEventListener( 'DOMContentLoaded', () => { + // this.render(); + // } ); + + // window.addEventListener( 'resize', () => { + // this.resize(); + // } ); + } + + // render () { + // requestAnimationFrame( () => { + // this.render(); + // } ); + + // this.orbitControls.update(); + + // // update the picking ray with the camera and mouse position + // this.raycaster.setFromCamera( this.mouse, this.camera ); + + // // calculate objects intersecting the picking ray + // let intersects = this.raycaster.intersectObjects( this.raycastableObjects, false ); + + // // intersects = this.raycaster.intersectObjects( this.unclickableObjects, false ); + + // // Debug.log( intersects.length ); + + // // this.cursorOnBox = false; + + // if ( intersects.length > 0 ) { + + // // Debug.log( intersects.length ); + + // // this.sphereInter.visible = true; + // // this.sphereInter.position.copy( intersects[0].point ); + + // this.pointerPosition = intersects[0].point; + + // this.cursorOnImage = true; + // this.pixelPosition.x = intersects[0].point.x; + // this.pixelPosition.y = intersects[0].point.y; + + // if ( intersects[0].object.userData.is_annotation ) { + // // this.dragControls['enabled'] = true; + // this.cursorOnBox = true; + // this.objectInFocus = intersects[0].object; + // this.editorService.mouseOverAnnotationObject.emit( intersects[0].object.id ); + + // } else if ( intersects[0].object.userData.is_button ) { + + // this.cursorOnBox = false; + + // // this.dragControls['enabled'] = false; + + // if ( intersects[0].object.userData.is_delete_button ) this.editorService.mouseOverDeleteButton.emit( 1 ); + + // } else { + + // // this.dragControls['enabled'] = false; + // this.cursorOnBox = false; + // this.objectInFocus = null; + + // } + + // // Debug.log( this.cursorOnBox ); + + // } else { + + // // this.sphereInter.visible = false; + + // this.cursorOnImage = false; + // this.pixelPosition.x = 0; + // this.pixelPosition.y = 0; + + // } + + // // for ( var i = 0; i < intersects.length; i++ ) { + // // intersects[i].object.material.color.set( 0xff0000 ); + // // } + + // // this.renderer.render( this.scene, this.camera ); + + // } + + + /** + * + * @param object + * @param raycasting + * @deprecated use SceneService.add instead + */ + add ( object: THREE.Object3D, raycasting = false ): any { + + SceneService.add( object, raycasting ); + } + + remove ( object: THREE.Object3D, raycasting = false ): any { + + SceneService.remove( object, raycasting ); + + } + + public focus ( obj: THREE.Object3D ) { + + // move the camera on top of the object + this.camera.position.setX( obj.position.x ); + this.camera.position.setY( obj.position.y ); + + // focus the camera on the object + this.camera.lookAt( obj.position ); + + // change the target position for controls + ThreeService.controls.setTarget( obj.position ); + ThreeService.controls.update(); + + ( this.camera as any ).updateProjectionMatrix(); + } + + reset () { + + SceneService.reset(); + + } + + /** + * Public methods + */ + + public select ( obj: Object3D ) { + + this.transformControls.attach( obj ); + + } + + public deselect () { + + this.transformControls.detach(); + + } + + public changeCamera () { + + if ( this.currentCameraIndex + 1 >= this.cameras.length ) { + + this.currentCameraIndex = 0; + + } else { + + this.currentCameraIndex++; + + } + + this.transformControls.detach(); + this.transformControls.object = this.camera; + + ThreeService.controls.setCamera( this.camera ); + + if ( this.camera[ 'isOrthographicCamera' ] ) { + + ThreeService.controls.setScreenSpaceEnabled( true ); + ThreeService.controls.setRotateEnabled( false ); + + } else if ( this.camera[ 'isPerspectiveCamera' ] ) { + + ThreeService.controls.setScreenSpaceEnabled( false ); + ThreeService.controls.setRotateEnabled( true ); + + } + + ThreeService.controls.reset(); + } + + onWindowResized () { + + const width = this.canvasWidth; + const height = this.canvasHeight; + + this.cameras.forEach( camera => { + + if ( camera[ 'isOrthographicCamera' ] ) { + + ( camera as OrthographicCamera ).left = width / -this.ORTHO_DRIVER; + ( camera as OrthographicCamera ).right = width / this.ORTHO_DRIVER; + ( camera as OrthographicCamera ).top = height / this.ORTHO_DRIVER; + ( camera as OrthographicCamera ).bottom = height / -this.ORTHO_DRIVER; + + ( camera as OrthographicCamera ).updateProjectionMatrix(); + + } else if ( camera[ 'isPerspectiveCamera' ] ) { + + ( camera as PerspectiveCamera ).aspect = width / height; + + ( camera as PerspectiveCamera ).updateProjectionMatrix(); + } + + + } ); + + } + + enableControls () { + + ThreeService.controls.enabled = true; + + } + + disableControls () { + + ThreeService.controls.enabled = false; + + } + + private createCameras () { + + // higher near value >= 10 reduces the z fighting that + // happens in rendering road markings + const near = 1; + const far = 100000; + + const width = 791.88; + const height = 606; + const otherDivider = 4; + + const left = width / -otherDivider; + const right = width / otherDivider; + const top = height / otherDivider; + const bottom = height / -otherDivider; + + const orthographicCamera = new THREE.OrthographicCamera( left, right, top, bottom, near, far ); + orthographicCamera.position.set( 0, 0, 50 ); + orthographicCamera.up.set( 0, 0, 1 ); + + const perspectiveCamera = new THREE.PerspectiveCamera( 50, width / height, near, far ); + perspectiveCamera.position.set( 0, 5, 10 ); + perspectiveCamera.up.set( 0, 0, 1 ); + + this.cameras.push( orthographicCamera ); + this.cameras.push( perspectiveCamera ); + + for ( let i = 0; i < this.cameras.length; i++ ) { + + this.cameras[ i ].lookAt( 0, 0, 0 ); + + SceneService.addHelper( this.cameras[ i ] ); + } + + + if ( this.camera[ 'isOrthographicCamera' ] ) { + + ( this.camera as OrthographicCamera ).updateProjectionMatrix(); + + } else if ( this.camera[ 'isPerspectiveCamera' ] ) { + + ( this.camera as PerspectiveCamera ).updateProjectionMatrix(); + + } + } + + private addAxesHelper () { + + SceneService.addHelper( new THREE.AxesHelper( 3000 ) ); + + } +} diff --git a/src/app/modules/three-js/viewport/viewport.component.html b/src/app/modules/three-js/viewport/viewport.component.html new file mode 100644 index 00000000..b46577b9 --- /dev/null +++ b/src/app/modules/three-js/viewport/viewport.component.html @@ -0,0 +1,15 @@ + + +
+ + + +
+ {{ fps }} FPS +
+
\ No newline at end of file diff --git a/src/app/modules/three-js/viewport/viewport.component.ts b/src/app/modules/three-js/viewport/viewport.component.ts new file mode 100644 index 00000000..8f4f3cc3 --- /dev/null +++ b/src/app/modules/three-js/viewport/viewport.component.ts @@ -0,0 +1,635 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { AfterViewInit, Component, ElementRef, HostListener, Input, OnDestroy, OnInit, ViewChild } from '@angular/core'; +import * as THREE from 'three'; +import { Intersection, Object3D, WebGLRenderer, OrthographicCamera, PerspectiveCamera } from 'three'; +import { ThreeService } from 'app/modules/three-js/three.service'; +import { InputService } from 'app/core/services/input.service'; +import { EventSystem } from 'app/events/event-system.service'; +import { MouseButton, PointerEventData } from 'app/events/pointer-event-data'; +import { SceneService } from '../../../core/services/scene.service'; +import { ImporterService } from 'app/services/importer.service'; + +import * as Stats from 'stats.js'; + +@Component( { + selector: 'app-viewport', + templateUrl: './viewport.component.html', + styles: [ + `.app-viewport-stats { + top: 12px; + right: 10px; + color: white; + height: 38px; + padding: 0px 8px !important; + position: absolute; + width: auto !important; + }` + ] +} ) +export class ViewportComponent implements OnInit, AfterViewInit, OnDestroy { + + @Input( 'directionalLight' ) directionalLightEnabled: boolean = false; + @Input( 'showPointerCoordinates' ) showPointerCoordinates: boolean = false; + + public beginTime; + public prevTime; + public frames = 0; + public fps; + + CANVAS_WIDTH = 600; + CANVAS_HEIGHT = 600; + + OFFSET_LEFT: number = 0; + OFFSET_TOP: number = 0; + + selected: Object3D; + intersections: THREE.Intersection[] = []; + lastIntersection: THREE.Intersection; + + @ViewChild( 'viewport' ) elementRef: ElementRef; + + raycaster = new THREE.Raycaster; + mouse: THREE.Vector2 = new THREE.Vector2(); + + private animationId; + private renderer: WebGLRenderer; + private ORTHO_DRIVER = 4; + private stats: Stats; + + + constructor ( + private threeService: ThreeService, + private inputService: InputService, + private eventSystem: EventSystem, + private importer: ImporterService, + ) { + this.render = this.render.bind( this ); + } + + get canvas (): HTMLCanvasElement { + return this.elementRef.nativeElement; + } + + ngOnInit () { + + + } + + // was used when multiple canvas instances were used + ngOnDestroy (): void { + + this.canvas.remove(); + + this.renderer.dispose(); + + cancelAnimationFrame( this.animationId ); + } + + ngAfterViewInit (): void { + + this.prevTime = ( performance || Date ).now(); + this.beginTime = ( performance || Date ).now() + + if ( this.detectWebgl() ) { + + this.setupRenderer(); + + setTimeout( () => { + + this.setCanvasSize(); + + }, 300 ); + + } else { + + alert( 'Your Browser does not support WebGL. Please visit from a browser which supports WebGL.' ); + } + + } + + setupRenderer () { + + this.renderer = new THREE.WebGLRenderer( { alpha: false, antialias: true, precision: 'highp', stencil: false } ); + this.renderer.setPixelRatio( window.devicePixelRatio ); + this.renderer.setClearColor( 0xffffff, 1 ); + this.renderer.autoClear = true; + + this.raycaster = new THREE.Raycaster(); + this.raycaster.linePrecision = 0.25; + this.raycaster.far = 10000; + + //////////////////////////////////// + + this.renderer.setSize( this.CANVAS_WIDTH, this.CANVAS_HEIGHT ); + + this.canvas.appendChild( this.renderer.domElement ); + + //////////////////////////////////// + + this.threeService.setupScene( this.canvas, this.renderer ); + + this.render(); + + // const self: ViewportComponent = this; + + /** + * Commented the below as this was causing slow rendering + */ + // ( function render () { + + // self.animationId = requestAnimationFrame( render ); + + // self.render(); + + // }() ); + + // self.render(); + + this.handleEditorEvents(); + this.handlePointerEvents(); + + if ( this.directionalLightEnabled ) this.threeService.addDirectionalLight(); + + } + + handleEditorEvents (): any { + + // this.editorService.onZoomIn.subscribe( e => { + // if ( this.threeService.camera.zoom >= 2.0 ) return; + // this.threeService.camera.zoom += 0.1; + // this.threeService.camera.updateProjectionMatrix(); + // } ); + // + // this.editorService.onZoomOut.subscribe( e => { + // if ( this.threeService.camera.zoom <= 0.20 ) return; + // this.threeService.camera.zoom -= 0.1; + // this.threeService.camera.updateProjectionMatrix(); + // } ); + // + // this.editorService.onZoomReset.subscribe( e => { + // this.threeService.camera.zoom = 1; + // this.threeService.camera.updateProjectionMatrix(); + // } ); + + } + + render () { + + // this seems a faster want to call render function + requestAnimationFrame( this.render ); + + this.frameBegin(); + + this.renderer.render( SceneService.scene, this.threeService.camera ); + + ThreeService.controls.update(); + + this.frameEnd(); + } + + frameBegin () { + + this.beginTime = ( performance || Date ).now(); + + } + + frameEnd () { + + this.frames++; + + const time = ( performance || Date ).now(); + + if ( time >= this.prevTime + 1000 ) { + + this.fps = Math.round( ( this.frames * 1000 ) / ( time - this.prevTime ) ); + + this.prevTime = time; + + this.frames = 0; + } + + } + + findIntersections ( recursive: boolean = true ): void { + + this.raycaster.setFromCamera( this.mouse, this.threeService.camera ); + + this.intersections = this.raycaster.intersectObjects( SceneService.objects, recursive ); + + if ( this.intersections.length > 0 ) { + + // if new object then fire enter event + if ( + this.lastIntersection != null && + this.lastIntersection.object.id != this.intersections[ 0 ].object.id && + this.intersections[ 0 ].object[ 'detectRaycast' ] == true + ) { + + this.eventSystem.pointerExit.emit( this.convertToPointerData( 0, this.lastIntersection ) ); + this.eventSystem.pointerEnter.emit( this.convertToPointerData( 0, this.intersections[ 0 ] ) ); + + } + + if ( this.intersections[ 0 ].object[ 'detectRaycast' ] == true ) { + + this.lastIntersection = this.intersections[ 0 ]; + + } + + // if ( this.threeIntersection.object.userData.is_annotation ) { + // this.editorService.mouseOverAnnotationObject.emit( this.threeIntersection ); + // } + + // if ( this.threeIntersection.object.userData.is_button ) { + // this.editorService.mouseOverButton.emit( this.threeIntersection ); + // } + + + } else { + + if ( this.lastIntersection != null ) { + + this.eventSystem.pointerExit.emit( this.convertToPointerData( 0, this.lastIntersection ) ); + + } + + this.lastIntersection = null; + + } + } + + private lastTime: number = Date.now(); + private minTime: number = 100; + + onMouseMove ( event: MouseEvent ) { + + // TODO: implement GPU picking + // https://threejs.org/examples/webgl_interactive_cubes_gpu.html + // https://stackoverflow.com/questions/48691642/three-js-raycaster-find-intersections-as-mouse-moves + // https://github.com/brianxu/GPUPicker + + this.mouse.x = ( ( event.clientX - this.OFFSET_LEFT ) / this.CANVAS_WIDTH ) * 2 - 1; + this.mouse.y = -( ( event.clientY - this.OFFSET_TOP ) / this.CANVAS_HEIGHT ) * 2 + 1; + + this.raycaster.setFromCamera( this.mouse, this.threeService.camera ); + + // logic to limit the number of time raycasting will be done + // return if less time has passed + if ( ( Date.now() - this.lastTime ) < this.minTime ) return; + + this.lastTime = Date.now(); + + + // this.mouse.x = (event.clientX / window.innerWidth) * 2 - 1; + // this.mouse.y = -(event.clientY / window.innerHeight) * 2 + 1; + + // this.editorService.move.emit( this.mouse ); + + // this.eventSystem.pointerMoved.emit( { this.INTERSECTIONS[0] } ); + + this.intersections = this.raycaster.intersectObjects( [ ThreeService.bgForClicks ], false ); + + if ( this.intersections.length > 0 ) { + this.eventSystem.pointerMoved.emit( this.convertToPointerData( 0, this.intersections[ 0 ] ) ); + } + + // TODO: no need to find intersections on mouse move + return; + + this.findIntersections( false ); + + if ( this.intersections.length > 0 ) { + this.eventSystem.pointerMoved.emit( this.convertToPointerData( 0, this.intersections[ 0 ] ) ); + } else { + // this.eventSystem.pointerMoved.emit( { point: new THREE.Vector3 } ); + } + + } + + onMouseClick ( event: MouseEvent ) { + + // this.findIntersections(); + + switch ( event.button ) { + + // left + case 0: + this.fireSelectionEvents(); + this.fireClickedEvent( event.button ); + break; + + // middle click + case 1: + break; + + // right + case 2: + break; + + } + + } + + onMouseDown ( event: MouseEvent ) { + + this.findIntersections( true ); + + event.preventDefault(); + + // this.eventSystem.pointerDown.emit( new PointerEventData ); + + switch ( event.button ) { + + // left + case 0: + this.fireSelectionEvents(); + if ( this.intersections.length > 0 ) { + this.eventSystem.pointerDown.emit( + this.convertToPointerData( event.button, this.intersections[ 0 ] ) + ); + } else { + this.eventSystem.pointerDown.emit( this.convertToPointerData( event.button, null ) ); + } + break; + + // middle + case 1: + // this.eventSystem.pointerDown.emit( this.convertToPointerData( event.button, null ) ); + break; + + // right + case 2: + this.eventSystem.pointerDown.emit( this.convertToPointerData( event.button, null ) ); + break; + + } + + } + + onMouseUp ( event: MouseEvent ) { + + this.eventSystem.pointerUp.emit( this.convertToPointerData( event.button, null ) ); + + } + + onMouseEnter ( event: Event ) { + + this.eventSystem.pointerEnter.emit( new PointerEventData ); + + } + + onMouseExit ( event: MouseEvent ) { + + this.eventSystem.pointerExit.emit( new PointerEventData ); + + } + + onMouseLeave ( $event: Event ) { + + this.eventSystem.pointerLeave.emit( new PointerEventData ); + + } + + onMouseOut ( e: MouseEvent ) { + + this.eventSystem.pointerOut.emit( new PointerEventData ); + + } + + //Dragover listener + @HostListener( 'dragover', [ '$event' ] ) + onDragOver ( evt ) { + evt.preventDefault(); + evt.stopPropagation(); + } + + //Dragleave listener + @HostListener( 'dragleave', [ '$event' ] ) + onDragLeave ( evt ) { + evt.preventDefault(); + evt.stopPropagation(); + } + + //Drop listener + @HostListener( 'drop', [ '$event' ] ) + onDrop ( $event: DragEvent ) { + + $event.preventDefault(); + $event.stopPropagation(); + + this.mouse.x = ( ( $event.clientX - this.OFFSET_LEFT ) / this.CANVAS_WIDTH ) * 2 - 1; + this.mouse.y = -( ( $event.clientY - this.OFFSET_TOP ) / this.CANVAS_HEIGHT ) * 2 + 1; + + this.findIntersections(); + + this.eventSystem.drop.emit( this.convertToPointerData( 0, this.intersections[ 0 ] ) ); + + let position = null; + + if ( this.intersections.length > 0 ) { + + this.eventSystem.pointerMoved.emit( this.convertToPointerData( 0, this.intersections[ 0 ] ) ); + + position = this.intersections[ 0 ].point; + + } + + + this.importer.importViaPath( $event.dataTransfer.getData( "path" ), "", position ); + } + + @HostListener( 'window: resize', [ '$event' ] ) + resize () { + + this.setCanvasSize(); + + // let width = this.CANVAS_WIDTH; + // let height = this.CANVAS_HEIGHT; + // + // // const box = this.threeService.getCanvasBounds(); + // + // // this.CANVAS_WIDTH = width = box.width; + // // this.CANVAS_HEIGHT = height = box.height; + // + // this.renderingService.renderer.setSize( width, height ); + // + // // this.renderer.setViewport( box.left, -box.top, width, height ); + // this.renderingService.renderer.setViewport( 0, -64, width, height ); + // + // this.threeService.oCamera.left = width / -2; + // this.threeService.oCamera.right = width / 2; + // this.threeService.oCamera.top = height / 2; + // this.threeService.oCamera.bottom = height / -2; + // + // this.threeService.oCamera.updateProjectionMatrix(); + + } + + handlePointerEvents (): any { + + + } + + fireSelectionEvents () { + + if ( this.intersections.length > 0 ) { + + let object = null; + + this.intersections.forEach( ( i: Intersection ) => { + + if ( i.object != null && i.object.userData.is_selectable == true ) { + + object = i.object; + + return false; + } + + } ); + + // NO SELECTION EVENTS ON OUR ANNOTATION IMAGE + // if ( object.userData.is_image ) return; + + // new object is selected, so first deselect + if ( this.selected != null && object != this.selected ) { + + this.eventSystem.deSelect.emit( { object: this.selected } ); + + this.eventSystem.select.emit( { object: object } ); + + this.selected = object; + + } else if ( this.selected == null ) { + + this.eventSystem.select.emit( { object: object } ); + + this.selected = object; + + } else if ( object == this.selected ) { + + // do nothing + + } + + // no object is selected + } else { + + this.eventSystem.deSelect.emit( { object: this.selected } ); + + } + + } + + fireClickedEvent ( button: number ): any { + + if ( this.intersections.length > 0 ) { + + this.eventSystem.pointerClicked.emit( + this.convertToPointerData( button, this.intersections[ 0 ] ) + ); + + } + + } + + convertToPointerData ( button: number, i: THREE.Intersection ): PointerEventData { + + let p = new PointerEventData(); + + if ( i != null ) { + + p.distance = i.distance; + p.distanceToRay = i.distanceToRay; + p.index = i.index; + p.face = i.face; + p.faceIndex = i.faceIndex; + p.object = i.object; + p.point = i.point; + p.uv = i.uv; + p.intersections = this.intersections; + + if ( this.threeService.camera instanceof OrthographicCamera ) { + + // approximation, not accurate + p.approxCameraDistance = ( 1 / this.threeService.camera.zoom ) * this.ORTHO_DRIVER * this.threeService.camera.position.z; + + } else if ( this.threeService.camera instanceof PerspectiveCamera ) { + + p.approxCameraDistance = i.distance; + + } + + } else { + + p.intersections = []; + + } + + // ADDITIONAL + if ( button == 0 ) { + p.button = MouseButton.LEFT; + } else if ( button == 1 ) { + p.button = MouseButton.MIDDLE; + } else if ( button == 2 ) { + p.button = MouseButton.RIGHT; + } + + return p; + } + + detectWebgl () { + + try { + + // Create canvas element. The canvas is not added to the + // document itself, so it is never displayed in the + // browser window. + const canvas = document.createElement( 'canvas' ); + + // Get WebGLRenderingContext from canvas element. + const gl = canvas.getContext( 'webgl' ) || canvas.getContext( 'experimental-webgl' ); + + // Report the result. + if ( gl && gl instanceof WebGLRenderingContext ) { + + return true; + + } else { + + return false; + } + + } catch ( e ) { + + return false; + + } + } + + onContextMenu ( $event: MouseEvent ) { + + + } + + private setCanvasSize () { + + const container = this.renderer.domElement.parentElement.parentElement; + + const box = container.getBoundingClientRect(); + + const width = this.threeService.canvasWidth = this.CANVAS_WIDTH = container.clientWidth; + const height = this.threeService.canvasHeight = this.CANVAS_HEIGHT = container.clientHeight; + + this.OFFSET_LEFT = this.threeService.leftOffset = box.left; + this.OFFSET_TOP = this.threeService.topOffset = box.top; + + this.renderer.setViewport( -box.left, -box.top, width, height ); + this.renderer.setSize( width, height ); + + this.threeService.onWindowResized(); + + } +} diff --git a/src/app/modules/tv-map/builders/od-builder-config.ts b/src/app/modules/tv-map/builders/od-builder-config.ts new file mode 100644 index 00000000..bf4c00a3 --- /dev/null +++ b/src/app/modules/tv-map/builders/od-builder-config.ts @@ -0,0 +1,36 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +export class OdBuilderConfig { + + public static ROAD_STEP = 1.0; + + public static JUNCTION_STEP = 3.0; + + // Junction elevation shift, so that the junction tracks are drawn above the road + public static JUNCTION_ELEVATION_SHIFT = 0.05; + + // Epsilon constant for length and s related precision + public static LENGTH_EPS = 0.0000001; + + // Epsilon constant for Height related precision + public static HEIGHT_EPS = 1.0e-6; + + // Texture scaling along the road (along s) + // S is divided by this constant to define the number of tiling of the texture + public static TEXTURE_LENGTH_SCALING = 4.0; + + // widths for the two types of weight values + public static STD_ROADMARK_WIDTH = 0.15; + public static BOLD_ROADMARK_WIDTH = 0.3; + + // elevation shift, so that the road mark is drawn above the road + // 0.001 increases the z-fighting, and requires camera near value of 10 + // 0.01 works well with camera near value of 1 + public static ROADMARK_ELEVATION_SHIFT = 0.01; + + // broken mark tiling + private static ROADMARK_BROKEN_TILING = 3.0; + +} \ No newline at end of file diff --git a/src/app/modules/tv-map/builders/od-builder.service.ts b/src/app/modules/tv-map/builders/od-builder.service.ts new file mode 100644 index 00000000..45c3baa4 --- /dev/null +++ b/src/app/modules/tv-map/builders/od-builder.service.ts @@ -0,0 +1,318 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { TvMap } from '../models/tv-map.model'; +import { GameObject } from 'app/core/game-object'; +import { TvRoad } from '../models/tv-road.model'; +import { TvLaneSection } from '../models/tv-lane-section'; +import { TvRoadSignal } from '../models/tv-road-signal.model'; +import { TvRoadObject } from '../models/tv-road-object'; +import * as THREE from 'three'; +import { BufferGeometry, Material, MeshBasicMaterial, Vector2, Vector3 } from 'three'; +import { TvObjectType } from '../interfaces/i-tv-object'; +import { TvPosTheta } from '../models/tv-pos-theta'; +import { Maths } from 'app/utils/maths'; +import { TvLane } from '../models/tv-lane'; +import { ObjectTypes, TvLaneSide, TvLaneType } from '../models/tv-common'; +import { OdBuilderConfig } from './od-builder-config'; +import { TvMapSourceFile } from '../services/tv-map-source-file'; +import { SceneService } from '../../../core/services/scene.service'; +import { TvSignalHelper } from '../services/tv-signal-helper'; +import { OdRoadMarkBuilder } from './od-road-mark-builder'; +import { MeshGeometryData } from '../models/mesh-geometry.data'; +import { Vertex } from '../models/vertex'; +import { OdSignalBuilder } from './od-signal-builder'; +import { AssetDatabase } from 'app/services/asset-database'; +import { OdMaterials } from './od-materials.service'; + +export class TvMapBuilder { + + private static signalFactory = new OdSignalBuilder; + private static roadMarkBuilder = new OdRoadMarkBuilder( null ); + + private static JUNCTION_ELEVATION_SHIFT = 0.005; + + constructor ( public openDrive?: TvMap ) { + + } + + public static buildMap ( openDrive?: TvMap ): GameObject { + + TvMapSourceFile.clearOpenDrive(); + + SceneService.remove( openDrive.gameObject ); + + openDrive.gameObject = null; + openDrive.gameObject = new GameObject( 'OpenDrive' ); + + openDrive.roads.forEach( road => { + + this.buildRoad( openDrive.gameObject, road ); + + } ); + + SceneService.add( openDrive.gameObject ); + + return openDrive.gameObject; + + } + + static buildRoad ( parent: GameObject, road: TvRoad ): any { + + road.gameObject = null; + road.gameObject = new GameObject( 'Road:' + road.id ); + road.gameObject.Tag = ObjectTypes.ROAD; + road.gameObject.userData.road = road; + + road.lanes.computeLaneSectionEnd( road ); + + // ( new OdRoadReferenceLineHelper( road ) ).create(); + // ( new OdLaneReferenceLineHelper( road ) ).create(); + + // OdBuilder.makeRoadReferenceLine( road ); + + // const offset = road.lanes.getLaneOffset(); + const laneSections = road.lanes.getLaneSections(); + + for ( let i = 0; i < laneSections.length; i++ ) { + + TvMapBuilder.buildLaneSection( road, laneSections[ i ] ); + + } + + this.roadMarkBuilder.buildRoad( road ); + + ( new TvSignalHelper( road ) ).create(); + + parent.add( road.gameObject ); + + } + + static buildLaneSection ( road: TvRoad, laneSection: TvLaneSection ): void { + + laneSection.gameObject = null; + laneSection.gameObject = new GameObject( 'LaneSection' ); + + road.gameObject.add( laneSection.gameObject ); + + TvMapBuilder.buildLanes( laneSection.getLeftLanes().reverse(), road, laneSection ); + + TvMapBuilder.createCenterLane( laneSection.getLastCenterLane(), laneSection, road ); + + TvMapBuilder.buildLanes( laneSection.getRightLanes(), road, laneSection ); + + // console.timeEnd( 'lane-build-time' ); + } + + static createCenterLane ( lane: TvLane, laneSection: TvLaneSection, road: TvRoad ) { + + this.createLaneGameObject( lane, new BufferGeometry(), new MeshBasicMaterial(), laneSection ); + + } + + static buildLanes ( lanes: TvLane[], road: TvRoad, laneSection: TvLaneSection ): any { + + // console.time( 'lane-build-time' ); + + for ( let i = 0; i < lanes.length; i++ ) { + + const lane = lanes[ i ]; + + TvMapBuilder.buildLane( lane, laneSection, road ); + + } + + } + + static buildLane ( lane: TvLane, laneSection: TvLaneSection, road: TvRoad ): any { + + let roadStep = OdBuilderConfig.ROAD_STEP; + let posTheta = new TvPosTheta; + + let cumulativeWidth = 0; + + lane.meshData = null; + lane.meshData = new MeshGeometryData; + + lane.markMeshData = null; + lane.markMeshData = new MeshGeometryData; + + const laneSectionLength = laneSection.lastSCoordinate - laneSection.s; + + let step = 0; + + for ( let sCoordinate = laneSection.s; sCoordinate < laneSection.lastSCoordinate; sCoordinate += roadStep ) { + + step += roadStep; + + cumulativeWidth = laneSection.getWidthUptoStart( lane, step ); + + road.getGeometryCoords( sCoordinate, posTheta ); + + this.makeLaneVertices( sCoordinate, posTheta, lane, road, cumulativeWidth, step ); + + } + + // add last s geometry to close any gaps + let lastSCoordinate = laneSection.lastSCoordinate - Maths.Epsilon; + + cumulativeWidth = laneSection.getWidthUptoStart( lane, laneSectionLength ); + + road.getGeometryCoords( lastSCoordinate, posTheta ); + + this.makeLaneVertices( lastSCoordinate, posTheta, lane, road, cumulativeWidth, laneSectionLength ); + + this.createLaneMeshFromGeometry( road, lane, laneSection ); + + } + + static makeLaneVertices ( sCoordinate: number, pos: TvPosTheta, lane: TvLane, road: TvRoad, cumulativeWidth: number, laneSectionS: number ) { + + const width = lane.getWidthValue( laneSectionS ); + const height = lane.getHeightValue( laneSectionS ); + const elevation = road.getElevationValue( laneSectionS ); + + const cosHdgPlusPiO2 = Maths.cosHdgPlusPiO2( lane.side, pos.hdg ); + const sinHdgPlusPiO2 = Maths.sinHdgPlusPiO2( lane.side, pos.hdg ); + + const v1 = new Vertex(); + const p1X = cosHdgPlusPiO2 * cumulativeWidth; + const p1Y = sinHdgPlusPiO2 * cumulativeWidth; + v1.Position = new Vector3( pos.x + p1X, pos.y + p1Y, elevation ); + v1.TexCoord = new Vector2( 0, sCoordinate ); + + const v2 = new Vertex(); + const p2X = cosHdgPlusPiO2 * ( cumulativeWidth + width ); + const p2Y = sinHdgPlusPiO2 * ( cumulativeWidth + width ); + v2.Position = new Vector3( pos.x + p2X, pos.y + p2Y, elevation + height.getOuter() ); + v2.TexCoord = new Vector2( width + height.getOuter(), sCoordinate ); + + if ( lane.side == TvLaneSide.RIGHT ) { + + this.addVertex( lane.meshData, v1 ); + this.addVertex( lane.meshData, v2 ); + + } else { + + this.addVertex( lane.meshData, v2 ); + this.addVertex( lane.meshData, v1 ); + + } + + } + + static addVertex ( meshData: MeshGeometryData, v1: Vertex ) { + + meshData.vertices.push( v1.Position.x, v1.Position.y, v1.Position.z ); + meshData.normals.push( v1.Normal.x, v1.Normal.y, v1.Normal.z ); + meshData.texCoords.push( v1.TexCoord.x, v1.TexCoord.y ); + meshData.indices.push( meshData.currentIndex++ ); + + } + + + static createMeshIndices ( geom: MeshGeometryData ): void { + + let index = 0; + + for ( let i = 0; i < ( geom.indices.length / 2 ) - 1; i++ ) { + + geom.triangles.push( index ); + geom.triangles.push( index + 1 ); + geom.triangles.push( index + 2 ); + + geom.triangles.push( index + 1 ); + geom.triangles.push( index + 3 ); + geom.triangles.push( index + 2 ); + + index += 2; + } + } + + static makeRoadSignal ( road: TvRoad, signal: TvRoadSignal ): any { + + TvMapBuilder.signalFactory.createSignalGameObject( road, signal ); + + } + + static makeObject ( road: TvRoad, object: TvRoadObject ): any { + + var gameObject = new GameObject( 'Object:' + object.attr_id ); + + road.gameObject.add( gameObject ); + + } + + private static createLaneMeshFromGeometry ( road: TvRoad, lane: TvLane, laneSection: TvLaneSection ) { + + this.createMeshIndices( lane.meshData ); + + const geometry = new THREE.BufferGeometry(); + const vertices = new Float32Array( lane.meshData.vertices ); + const normals = new Float32Array( lane.meshData.normals ); + const faces = new Float32Array( lane.meshData.texCoords ); + + geometry.setIndex( lane.meshData.triangles ); + geometry.addAttribute( 'position', new THREE.BufferAttribute( vertices, 3 ) ); + geometry.addAttribute( 'normal', new THREE.Float32BufferAttribute( normals, 3 ) ); + geometry.addAttribute( 'uv', new THREE.Float32BufferAttribute( faces, 2 ) ); + + geometry.computeBoundingBox(); + geometry.computeVertexNormals(); + + // const material = OdMaterials.getLaneMaterial( lane ); + const material = this.getLaneMaterial( road, lane ); + + TvMapBuilder.createLaneGameObject( lane, geometry, material, laneSection ); + + } + + public static getLaneMaterial ( road: TvRoad, lane: TvLane ): Material { + + let material: Material; + let guid: string; + + if ( lane.type == TvLaneType.driving ) { + + guid = road.drivingMaterialGuid; + + } else if ( lane.type == TvLaneType.border ) { + + guid = road.borderMaterialGuid; + + } else if ( lane.type == TvLaneType.sidewalk ) { + + guid = road.sidewalkMaterialGuid; + + } else if ( lane.type == TvLaneType.shoulder ) { + + guid = road.shoulderMaterialGuid; + + } + + // find by guid + if ( guid ) material = AssetDatabase.getInstance( guid ); + + // if no material found then use in built + if ( !material ) material = OdMaterials.getLaneMaterial( lane ) as Material; + + return material; + } + + private static createLaneGameObject ( + lane: TvLane, + geometry: THREE.BufferGeometry, + material: THREE.Material | THREE.Material[], + laneSection: TvLaneSection + ) { + + lane.gameObject = new GameObject( 'Lane:' + lane.id, geometry, material ); + lane.gameObject.Tag = TvObjectType[ TvObjectType.LANE ]; + lane.gameObject.OpenDriveType = TvObjectType.LANE; + lane.gameObject.userData.data = lane; + lane.gameObject.userData.lane = lane; + + laneSection.gameObject.add( lane.gameObject ); + } +} diff --git a/src/app/modules/tv-map/builders/od-lane-direction-builder.ts b/src/app/modules/tv-map/builders/od-lane-direction-builder.ts new file mode 100644 index 00000000..80f875ac --- /dev/null +++ b/src/app/modules/tv-map/builders/od-lane-direction-builder.ts @@ -0,0 +1,133 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { TvRoad } from '../models/tv-road.model'; +import { ArrowHelper, Object3D, Vector3 } from 'three'; +import { TvLane } from '../models/tv-lane'; +import { TvLaneSection } from '../models/tv-lane-section'; +import { TvPosTheta } from '../models/tv-pos-theta'; +import { TvLaneSide, TvLaneType } from '../models/tv-common'; +import { Maths } from '../../../utils/maths'; +import { SceneService } from 'app/core/services/scene.service'; + +export class OdLaneDirectionBuilder { + + private stepValue = 5; + private arrows: Object3D[] = []; + + constructor ( private road: TvRoad ) { + + } + + setRoad ( value: TvRoad ): void { + + this.clear(); + + this.road = value; + + } + + create () { + + const container = this.road.getLanes(); + + container.computeLaneSectionEnd( this.road ); + + for ( let i = 0; i < this.road.lanes.laneSections.length; i++ ) { + + const laneSection = this.road.lanes.laneSections[ i ]; + + laneSection.getLeftLanes().forEach( lane => this.drawLane( lane, laneSection ) ); + + laneSection.getRightLanes().forEach( lane => this.drawLane( lane, laneSection ) ); + } + + } + + clear () { + + this.arrows.forEach( arrow => SceneService.removeHelper( arrow ) ); + + } + + drawSingleLane ( road: TvRoad, lane: TvLane ) { + + this.road = road; + + for ( let i = 0; i < this.road.lanes.laneSections.length; i++ ) { + + const laneSection = this.road.lanes.laneSections[ i ]; + + this.drawLane( lane, laneSection ); + } + } + + private createArrow ( origin: Vector3, direction: Vector3 ) { + + // var dir = new Vector3( 0, 1, 0 ); + + // normalize the direction vector (convert to vector of length 1) + direction.normalize(); + + const length = 2.5; + const hex = 0xffff00; + + const headLength = 0.2 * length; + const headWidth = 0.75 * headLength; + + origin.setZ( origin.z + 0.1 ); + + const arrowHelper = new ArrowHelper( direction, origin, length, hex, headLength, headWidth ); + + arrowHelper.renderOrder = 3; + + this.arrows.push( arrowHelper ); + + // add to helper to avoid raycasting + SceneService.addHelper( arrowHelper ); + } + + private drawLane ( lane: TvLane, laneSection: TvLaneSection ) { + + if ( lane.type !== TvLaneType.driving ) return; + + let s = laneSection.s; + + const posTheta = new TvPosTheta(); + + let laneOffset = 0; + + let width = 0; + + while ( s <= laneSection.lastSCoordinate ) { + + laneOffset = this.road.lanes.getLaneOffsetValue( s ); + + width = laneSection.getWidthUptoCenter( lane, s ); + + this.road.getGeometryCoords( s, posTheta ); + + posTheta.addLateralOffset( laneOffset ); + + if ( lane.side === TvLaneSide.RIGHT ) { + + width *= -1; + + } else if ( lane.side === TvLaneSide.LEFT ) { + + // to show arrow in traffic direction + // TODO: Make traffic direction editable from editor + posTheta.hdg += Maths.M_PI; + width *= -1; + } + + posTheta.addLateralOffset( width ); + + this.createArrow( posTheta.toVector3(), posTheta.toDirectionVector() ); + + s += this.stepValue; + } + + } +} diff --git a/src/app/modules/tv-map/builders/od-lane-reference-line-builder.ts b/src/app/modules/tv-map/builders/od-lane-reference-line-builder.ts new file mode 100644 index 00000000..a04b7a49 --- /dev/null +++ b/src/app/modules/tv-map/builders/od-lane-reference-line-builder.ts @@ -0,0 +1,346 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { TvRoad } from '../models/tv-road.model'; +import * as THREE from 'three'; +import { Color, Line, LineBasicMaterial, LineDashedMaterial, Material, Object3D, Vector3 } from 'three'; +import { TvPosTheta } from '../models/tv-pos-theta'; +import { TvLaneSection } from '../models/tv-lane-section'; +import { TvLane } from '../models/tv-lane'; +import { TvLaneSide } from '../models/tv-common'; +import { Maths } from 'app/utils/maths'; + +export enum LineType { + SOLID = 'solid', + DASHED = 'dashed', + BOTH = 'both', +} + +const DEFAULT_LINE_COLOR = new Color( 0, 0, 1 ); +const HIGHLIGHT_LINE_COLOR = new Color( 0, 1, 0 ); +const SELECTED_LINE_COLOR = new Color( 1, 0, 0 ); + +const DEFAULT_SOLID_WIDTH = 1; +const HIGHLIGHT_SOLID_WIDTH = 2; + +const DEFAULT_DASHED_WIDTH = 2; +const HIGHLIGHT_DASHED_WIDTH = 3; + + +export class OdLaneReferenceLineBuilder { + + public lines: Object3D[] = []; + + public readonly tag: string = 'lane-reference-line'; + + private basicMaterial = new THREE.LineBasicMaterial( { + color: DEFAULT_LINE_COLOR, + linewidth: DEFAULT_SOLID_WIDTH + } ); + + private dashedMaterial = new LineDashedMaterial( { + color: DEFAULT_LINE_COLOR, + opacity: 0.35, + linewidth: DEFAULT_DASHED_WIDTH, + scale: 1, + dashSize: 0.2, + gapSize: 0.1, + } ); + + private mouseOverLine: Line; + + private selectedLine: Line; + + constructor ( private road?: TvRoad, private lineType: LineType = LineType.SOLID, private color?: number ) { + + } + + public setType ( lineType: LineType ) { + + this.lineType = lineType; + + } + + public create () { + + const container = this.road.getLanes(); + + container.computeLaneSectionEnd( this.road ); + + this.drawRoad( this.road ); + } + + public redraw ( type: LineType = LineType.SOLID ): void { + + const road = this.road; + + this.clear(); + + this.drawRoad( road, type ); + } + + + public drawRoad ( road: TvRoad, type: LineType = LineType.SOLID, redraw = false ) { + + if ( road == null ) return; + + // TODO: remove this logic as the responsibility should not be here + // simply return if the road is already selected + if ( !redraw && this.road && this.road.id === road.id ) return; + + this.clear(); + + this.road = road; + + for ( let i = 0; i < road.lanes.laneSections.length; i++ ) { + + const laneSection = road.lanes.laneSections[ i ]; + + laneSection.lanes.forEach( lane => { + + const points: TvPosTheta[] = []; + + this.makeLanePoints( laneSection, lane, points ); + + this.drawLine( lane, this.convertToVector3List( points ), type ); + + } ) + + } + } + + public clear () { + + if ( this.road && this.road.gameObject ) { + + this.lines.forEach( line => { + + this.road.gameObject.remove( line ); + + } ); + + this.road = null; + } + + } + + onMouseOverLine ( line: Line ) { + + if ( this.selectedLine && line.id === this.selectedLine.id ) return; + + // // not reqquired it seems + // // dont higlight if this line is already selected + // if ( this.mouseOverLine && this.selectedLine && line.id === this.selectedLine.id ) return; + + // if same line is being asked to highlight then simple return and do nothing + if ( this.mouseOverLine && this.mouseOverLine.id == line.id ) return; + + // // not reqquired it seems + // simpley return if mouse is over the selected line + // if ( this.mouseOverLine && this.selectedLine && this.selectedLine.id == this.mouseOverLine.id ) return; + + // else remove the previously highlighted line if present + this.onMouseOutLine(); + + // make this the new highlighted line + this.mouseOverLine = line; + + // set the current material property to highlighted color + ( line.computeLineDistances() ); + + ( line.material as LineBasicMaterial ).color.set( HIGHLIGHT_LINE_COLOR ); + + if ( this.lineType == LineType.SOLID ) { + + ( line.material as LineBasicMaterial ).linewidth = HIGHLIGHT_SOLID_WIDTH; + + } else { + + ( line.material as LineBasicMaterial ).linewidth = HIGHLIGHT_DASHED_WIDTH; + } + + ( line.material as LineBasicMaterial ).needsUpdate = true; + } + + onMouseOutLine () { + + if ( !this.mouseOverLine ) return; + + // simpley return if mouse was over the selected line + if ( this.mouseOverLine && this.selectedLine && this.mouseOverLine.id == this.selectedLine.id ) return; + + // set the current material property to highlighted color + ( this.mouseOverLine.computeLineDistances() ); + + ( this.mouseOverLine.material as LineBasicMaterial ).color.set( DEFAULT_LINE_COLOR ); + + if ( this.lineType == LineType.SOLID ) { + + ( this.mouseOverLine.material as LineBasicMaterial ).linewidth = DEFAULT_SOLID_WIDTH; + + } else { + + ( this.mouseOverLine.material as LineBasicMaterial ).linewidth = DEFAULT_DASHED_WIDTH; + } + + ( this.mouseOverLine.material as LineBasicMaterial ).needsUpdate = true; + + this.mouseOverLine = null; + } + + onLineSelected ( line: Line ) { + + if ( this.selectedLine && this.selectedLine.id == line.id ) return; + + this.onLineUnselected(); + + this.selectedLine = line; + + ( line.computeLineDistances() ); + + ( line.material as LineBasicMaterial ).color.set( SELECTED_LINE_COLOR ); + + if ( this.lineType == LineType.SOLID ) { + + ( line.material as LineBasicMaterial ).linewidth = HIGHLIGHT_SOLID_WIDTH; + + } else { + + ( line.material as LineBasicMaterial ).linewidth = HIGHLIGHT_DASHED_WIDTH; + } + + ( line.material as LineBasicMaterial ).needsUpdate = true; + + } + + onLineUnselected () { + + if ( !this.selectedLine ) return; + + ( this.selectedLine.computeLineDistances() ); + + ( this.selectedLine.material as LineBasicMaterial ).color.set( DEFAULT_LINE_COLOR ); + + if ( this.lineType == LineType.SOLID ) { + + ( this.selectedLine.material as LineBasicMaterial ).linewidth = DEFAULT_SOLID_WIDTH; + + } else { + + ( this.selectedLine.material as LineBasicMaterial ).linewidth = DEFAULT_DASHED_WIDTH; + } + + ( this.selectedLine.material as LineBasicMaterial ).needsUpdate = true; + + this.selectedLine = null; + + } + + private drawLine ( lane: TvLane, points: Vector3[], type: LineType = LineType.SOLID ) { + + const geometry = new THREE.BufferGeometry().setFromPoints( points ); + + const material = this.getLineMaterial( type ); + + if ( this.color ) material.color.set( this.color ); + + const line = new THREE.Line( geometry, material ); + + ( line.material as Material ).depthTest = false; + + line.computeLineDistances(); + + line.name = 'LaneWidthLine'; + + line.userData.is_selectable = true; + + line.userData.lane = lane; + + line.renderOrder = 5; + + line[ 'tag' ] = this.tag; + + this.lines.push( line ); + + this.road.gameObject.add( line ); + } + + private getLineMaterial ( type: LineType ) { + + if ( type == LineType.SOLID ) { + + return new LineBasicMaterial().copy( this.basicMaterial ); + + } else if ( type == LineType.DASHED ) { + + return new LineDashedMaterial().copy( this.dashedMaterial ); + + } else if ( type == LineType.BOTH ) { + + return new LineDashedMaterial().copy( this.dashedMaterial ); + + } else { + + console.warn( "unknown line type" ); + + return new LineBasicMaterial().copy( this.basicMaterial ); + + } + } + + private convertToVector3List ( poses: TvPosTheta[] ): Vector3[] { + + const tmp: Vector3[] = []; + + poses.forEach( pose => { + + tmp.push( new Vector3( pose.x, pose.y, 0 ) ); + + } ); + + return tmp; + } + + private makeLanePoints ( laneSection: TvLaneSection, lane: TvLane, points: TvPosTheta[] = [] ) { + + let s = laneSection.s; + + while ( s <= laneSection.lastSCoordinate ) { + + this.makeLanePointsLoop( s, laneSection, lane, points ); + + s++; + } + + s = laneSection.lastSCoordinate - Maths.Epsilon; + + this.makeLanePointsLoop( s, laneSection, lane, points ); + } + + private makeLanePointsLoop ( s, laneSection: TvLaneSection, lane: TvLane, points: TvPosTheta[] = [] ) { + + const posTheta = new TvPosTheta(); + + // const laneOffset = this.road.lanes.getLaneOffsetValue( s ); + + let width = laneSection.getWidthUptoEnd( lane, s ); + + this.road.getGeometryCoords( s, posTheta ); + + // posTheta.addLateralOffset( laneOffset ); + + // If right side lane then make the offset negative + if ( lane.side === TvLaneSide.RIGHT ) { + width *= -1; + } + + posTheta.addLateralOffset( width ); + + points.push( posTheta ); + + } + + + +} diff --git a/src/app/modules/tv-map/builders/od-materials.service.ts b/src/app/modules/tv-map/builders/od-materials.service.ts new file mode 100644 index 00000000..18f5454d --- /dev/null +++ b/src/app/modules/tv-map/builders/od-materials.service.ts @@ -0,0 +1,26 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { MeshBasicMaterial } from 'three'; +import { TvLane } from '../models/tv-lane'; +import { OdTextures } from './od.textures'; +import { COLOR } from '../../../shared/utils/colors.service'; + +export class OdMaterials { + + static getLaneMaterial ( lane: TvLane, forceNew = false ) { + + if ( !forceNew && lane.gameObject != null && lane.gameObject.material != null ) { + + return lane.gameObject.material; + + } + + return new MeshBasicMaterial( { + map: OdTextures.getLaneTexture( lane ), + color: COLOR.WHITE, + wireframe: false, + } ); + } +} diff --git a/src/app/modules/tv-map/builders/od-road-builder.ts b/src/app/modules/tv-map/builders/od-road-builder.ts new file mode 100644 index 00000000..9400a1cc --- /dev/null +++ b/src/app/modules/tv-map/builders/od-road-builder.ts @@ -0,0 +1,14 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { TvRoad } from '../models/tv-road.model'; + +export class OdRoadBuilder { + + static build ( road: TvRoad ) { + + + } + +} \ No newline at end of file diff --git a/src/app/modules/tv-map/builders/od-road-mark-builder.ts b/src/app/modules/tv-map/builders/od-road-mark-builder.ts new file mode 100644 index 00000000..4199bf56 --- /dev/null +++ b/src/app/modules/tv-map/builders/od-road-mark-builder.ts @@ -0,0 +1,452 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { TvRoad } from '../models/tv-road.model'; +import * as THREE from 'three'; +import { Vector2, Vector3 } from 'three'; +import { TvPosTheta } from '../models/tv-pos-theta'; +import { TvLaneSection } from '../models/tv-lane-section'; +import { TvLane } from '../models/tv-lane'; +import { Maths } from 'app/utils/maths'; +import { GameObject } from '../../../core/game-object'; +import { ObjectTypes, TvLaneSide, TvRoadMarkTypes } from '../models/tv-common'; +import { TvLaneRoadMark } from '../models/tv-lane-road-mark'; +import { COLOR } from '../../../shared/utils/colors.service'; +import { MeshGeometryData } from '../models/mesh-geometry.data'; +import { Vertex } from '../models/vertex'; +import { OdBuilderConfig } from './od-builder-config'; +import { SceneService } from '../../../core/services/scene.service'; + +export class OdRoadMarkBuilder { + + private _texture: any; + + constructor ( private road: TvRoad = null ) { + + } + + private get texture () { + + if ( this._texture ) return this._texture; + + const texture = new THREE.TextureLoader().load( 'assets/flat-roadmarks.png' ); + + texture.wrapS = THREE.RepeatWrapping; + texture.wrapT = THREE.RepeatWrapping; + texture.mapping = THREE.UVMapping; + texture.repeat.set( 1, 1 ); + + texture.anisotropy = 5; + + this._texture = texture; + + return this._texture; + } + + public create (): void { + + this.buildRoad( this.road ); + + } + + + public buildRoad ( road: TvRoad ): void { + + this.road = road; + + for ( let i = 0; i < road.lanes.laneSections.length; i++ ) { + + const laneSection = road.lanes.laneSections[ i ]; + + laneSection.getLaneVector().forEach( lane => { + + this.processLane( laneSection, lane ); + + } ); + + } + } + + private createMeshIndices ( geom: MeshGeometryData ): void { + + let index = 0; + + for ( let i = 0; i < ( geom.indices.length / 2 ) - 1; i++ ) { + + geom.triangles.push( index ); + geom.triangles.push( index + 1 ); + geom.triangles.push( index + 2 ); + + geom.triangles.push( index + 1 ); + geom.triangles.push( index + 3 ); + geom.triangles.push( index + 2 ); + + index += 2; + } + } + + + private processLane ( laneSection: TvLaneSection, lane: TvLane ) { + + const roadMarks = lane.getRoadMarks(); + + this.clearPreviousMesh( roadMarks, lane ); + + this.updateLastCoordinate( roadMarks, laneSection, lane ); + + for ( const mark of roadMarks ) { + + const mesh = new MeshGeometryData(); + + let sOffset = laneSection.s + mark.sOffset; + + let relativeS = mark.sOffset; + + for ( let s = 0; s < mark.length; s += OdBuilderConfig.ROAD_STEP ) { + + this.createVertex( sOffset + s, mark, laneSection, lane, mesh, relativeS + s ); + + } + + // one last entry to nearest to the end + this.createVertex( ( sOffset + mark.length ) - Maths.Epsilon, mark, laneSection, lane, mesh, ( relativeS + mark.length ) - Maths.Epsilon ); + + this.drawRoadMark( mark, mesh, lane ); + + } + + // for ( let s = laneSection.s; s <= laneSection.lastSCoordinate; s++ ) { + // + // this.makeLanePointsLoop( s, laneSection, lane, mesh ); + // + // } + + // last entry to close any issues + // const s = laneSection.lastSCoordinate - Maths.Epsilon; + + // this.makeLanePointsLoop( s, laneSection, lane, points ); + } + + private createVertex ( s, roadMark: TvLaneRoadMark, laneSection: TvLaneSection, lane: TvLane, mesh: MeshGeometryData, laneSectionS: number ) { + + const posTheta = new TvPosTheta(); + + const cumulativeWidth = this.getCumulativeWidth( laneSectionS, lane, laneSection ); + + // if ( cumulativeWidth > 100 ) console.log( laneSectionS, this.road.id, laneSection.s, lane.id, cumulativeWidth ); + + this.road.getGeometryCoords( s, posTheta ); + + // const laneOffset = this.road.lanes.getLaneOffsetAt( s ); + // posTheta.addLateralOffset( laneOffset ); + // let laneWidth = laneSection.getWidthUptoEnd( lane, s ); + + const height = lane.getHeightValue( laneSectionS ); + const elevation = this.road.getElevationValue( laneSectionS ); + + // console.log( roadMark.getHeight() ); + + const cosHdgPlusPiO2 = Maths.cosHdgPlusPiO2( lane.side, posTheta.hdg ); + const sinHdgPlusPiO2 = Maths.sinHdgPlusPiO2( lane.side, posTheta.hdg ); + + const laneBorderX = posTheta.x + ( cosHdgPlusPiO2 * cumulativeWidth ); + const laneBorderY = posTheta.y + ( sinHdgPlusPiO2 * cumulativeWidth ); + + // console.log( `LaneSection: ${laneSection.id} Lane: ${lane.id} Width: ${cumulativeWidth} S: ${s} LS: ${laneSectionS}` ); + + const roadMarkWidth = roadMark.getWidth(); + + const x1 = laneBorderX + ( cosHdgPlusPiO2 * roadMarkWidth * 0.5 ); + const y1 = laneBorderY + ( sinHdgPlusPiO2 * roadMarkWidth * 0.5 ); + + const x2 = laneBorderX - ( cosHdgPlusPiO2 * roadMarkWidth * 0.5 ); + const y2 = laneBorderY - ( sinHdgPlusPiO2 * roadMarkWidth * 0.5 ); + + ////////////////////////////////////////////////////////////// + + let roadMarkColor, type; + + const roadMarkTexWidth = 0.5; + let roadMarkTexModifierMin1 = 0; + let roadMarkTexModifierMax1 = 0; + + let roadMarkTexModifierMin2 = 0; + let roadMarkTexModifierMax2 = 0; + + // Define the color of the current road mark: + if ( roadMark.getColor() === ( 'standard' ) || roadMark.getColor() === ( 'white' ) ) { + roadMarkColor = [ 255, 255, 255 ]; + } else if ( roadMark.getColor() === 'yellow' ) { + roadMarkColor = [ 255, 255, 0 ]; + } else { + roadMarkColor = [ 255, 255, 255 ]; + } + + // Set the tex coords modifiers based on the type of the road mark + if ( roadMark.getType() === TvRoadMarkTypes.NONE ) { + type = -1; // debug only + return false; + } + + if ( roadMark.getType() === TvRoadMarkTypes.SOLID ) { + + type = 0; + roadMarkTexModifierMax1 = roadMarkTexWidth; + + } else if ( roadMark.getType() === TvRoadMarkTypes.BROKEN ) { + + type = 0; + roadMarkTexModifierMin1 = roadMarkTexWidth; + roadMarkTexModifierMax1 = 1; + + } else if ( roadMark.getType() === TvRoadMarkTypes.SOLID_SOLID ) { + + type = 1; + roadMarkTexModifierMax1 = roadMarkTexWidth; + roadMarkTexModifierMax2 = roadMarkTexWidth; + + } else if ( roadMark.getType() === TvRoadMarkTypes.SOLID_BROKEN ) { + + type = 2; + roadMarkTexModifierMin1 = 0; + roadMarkTexModifierMax1 = roadMarkTexWidth; + roadMarkTexModifierMin2 = roadMarkTexWidth; + roadMarkTexModifierMax2 = 1; + + } else if ( roadMark.getType() === TvRoadMarkTypes.BROKEN_SOLID ) { + + type = 3; + roadMarkTexModifierMin1 = roadMarkTexWidth; + roadMarkTexModifierMax1 = 1; + roadMarkTexModifierMin2 = 0; + roadMarkTexModifierMax2 = roadMarkTexWidth; + + } + + // =============================== + // Add Vertices + // =============================== + let texX; + const texY = s / TvLaneRoadMark.ROADMARK_BROKEN_TILING; + + // // shift the x,y in case it is a 2 line mark + // if ( type > 0 ) { + // + // x1 = laneBorderX + cosHdgPlusPiO2 * 1.5 * width; + // y1 = laneBorderY + sinHdgPlusPiO2 * 1.5 * width; + // + // x2 = laneBorderX + cosHdgPlusPiO2 * 0.25 * width; + // y2 = laneBorderY + sinHdgPlusPiO2 * 0.25 * width; + // + // x3 = laneBorderX - cosHdgPlusPiO2 * 0.25 * width; + // y3 = laneBorderY - sinHdgPlusPiO2 * 0.25 * width; + // + // x4 = laneBorderX - cosHdgPlusPiO2 * 1.5 * width; + // y4 = laneBorderY - sinHdgPlusPiO2 * 1.5 * width; + // } + + + /////////////////////////////////////////////////////////////// + + // First vertex + texX = roadMarkTexModifierMin1; + + const v1 = new Vertex(); + v1.Position = new Vector3( x1, y1, elevation ); + v1.TexCoord = new Vector2( texX, texY ); + + + // Second vertex + texX = roadMarkTexModifierMax1; + + const v2 = new Vertex(); + v2.Position = new Vector3( x2, y2, elevation + height.getOuter() ); + v2.TexCoord = new Vector2( texX, texY ); + + if ( lane.side == TvLaneSide.LEFT ) { + this.addVertex( mesh, v1 ); + this.addVertex( mesh, v2 ); + } else { + this.addVertex( mesh, v2 ); + this.addVertex( mesh, v1 ); + } + + // if ( type > 0 ) { + // + // // First vertex of the second line + // texX = roadMarkTexModifierMin2; + // + // roadMarksGeometry.vertices.push( y3, z, x3 ); + // roadMarksGeometry.normals.push( 0, 0, 1 ); + // roadMarksGeometry.colors.push( roadMarkColor ); + // roadMarksGeometry.texCoords.push( texX, texY ); + // roadMarksGeometry.currentIndex++; + // + // // Second vertex of the second line + // texX = roadMarkTexModifierMax2; + // + // roadMarksGeometry.vertices.push( y4, z, x4 ); + // roadMarksGeometry.normals.push( 0, 0, 1 ); + // roadMarksGeometry.colors.push( roadMarkColor ); + // roadMarksGeometry.texCoords.push( texX, texY ); + // roadMarksGeometry.currentIndex++; + // + // } + } + + private addVertex ( meshData: MeshGeometryData, v1: Vertex ) { + meshData.vertices.push( v1.Position.x, v1.Position.y, v1.Position.z + OdBuilderConfig.ROADMARK_ELEVATION_SHIFT ); + meshData.normals.push( v1.Normal.x, v1.Normal.y, v1.Normal.z ); + meshData.texCoords.push( v1.TexCoord.x, v1.TexCoord.y ); + meshData.indices.push( meshData.currentIndex++ ); + } + + private drawRoadMark ( roadMark: TvLaneRoadMark, mesh: MeshGeometryData, lane: TvLane ) { + + this.createMeshIndices( mesh ); + + const material = this.getMaterial( roadMark ); + + const geometry = this.getGeometry( mesh ); + + roadMark.gameObject = new GameObject( 'RoadMark:', geometry, material ); + + roadMark.gameObject.Tag = ObjectTypes.LANE_MARKING; + + roadMark.gameObject.userData.data = lane; + + roadMark.gameObject.userData.lane = lane; + + roadMark.gameObject.userData.roadMark = roadMark; + + lane.gameObject.add( roadMark.gameObject ); + } + + private getMaterial ( roadMark: TvLaneRoadMark ) { + + let color = COLOR.WHITE; + + switch ( roadMark.color ) { + + case 'standard': + color = COLOR.WHITE; + break; + + case 'white': + color = COLOR.WHITE; + break; + + case 'yellow': + color = COLOR.YELLOW; + break; + + case 'red': + color = COLOR.RED; + break; + + default: + color = COLOR.WHITE; + break; + + } + + return new THREE.MeshBasicMaterial( { + color: color, + map: this.texture, + transparent: true, + alphaTest: 0.1, + wireframe: false, + side: THREE.FrontSide + } ); + } + + private getGeometry ( mesh: MeshGeometryData ) { + + const geometry = new THREE.BufferGeometry(); + const vertices = new Float32Array( mesh.vertices ); + const colors = new Float32Array( mesh.colors ); + const normals = new Float32Array( mesh.normals ); + const faces = new Float32Array( mesh.texCoords ); + + geometry.setIndex( mesh.triangles ); + + geometry.addAttribute( 'position', new THREE.BufferAttribute( vertices, 3 ) ); + geometry.addAttribute( 'color', new THREE.Float32BufferAttribute( colors, 3 ) ); + geometry.addAttribute( 'normal', new THREE.Float32BufferAttribute( normals, 3 ) ); + geometry.addAttribute( 'uv', new THREE.Float32BufferAttribute( faces, 2 ) ); + + geometry.computeBoundingBox(); + geometry.computeVertexNormals(); + + return geometry; + } + + private getCumulativeWidth ( s, lane: TvLane, laneSection: TvLaneSection ) { + + let width = 0; + + switch ( lane.side ) { + + case TvLaneSide.LEFT: + width = laneSection.getWidthUptoEnd( lane, s ); + break; + + case TvLaneSide.CENTER: + width = 0; + break; + + case TvLaneSide.RIGHT: + width = laneSection.getWidthUptoEnd( lane, s ); + break; + + } + + return width; + } + + private clearPreviousMesh ( roadMarks: TvLaneRoadMark[], lane: TvLane ) { + + roadMarks.forEach( mark => { + + if ( mark.gameObject ) { + + SceneService.remove( mark.gameObject ); + + lane.gameObject.remove( mark.gameObject ); + + } + + mark.gameObject = null; + + } ); + + } + + private updateLastCoordinate ( roadMarks: TvLaneRoadMark[], laneSection: TvLaneSection, lane: TvLane ) { + + // setting the last coordinate + roadMarks.forEach( ( mark, index ) => { + + if ( index < roadMarks.length - 1 ) { + + mark.lastSCoordinate = roadMarks[ index + 1 ].sOffset; + + } else { + + mark.lastSCoordinate = laneSection.length; + + } + + // console.log( laneSection.roadId, laneSection.id, lane.id, mark.sOffset, mark.lastSCoordinate ); + + } ); + + // console.log( roadMarks, laneSection ); + } + + private processRoadMark ( roadmark: TvLaneRoadMark, road: TvRoad, laneSection: TvLaneSection, lane: TvLane, mesh: MeshGeometryData ) { + + + } +} diff --git a/src/app/modules/tv-map/builders/od-road-reference-line-builder.ts b/src/app/modules/tv-map/builders/od-road-reference-line-builder.ts new file mode 100644 index 00000000..1ff53573 --- /dev/null +++ b/src/app/modules/tv-map/builders/od-road-reference-line-builder.ts @@ -0,0 +1,102 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { TvRoad } from '../models/tv-road.model'; +import { TvPosTheta } from '../models/tv-pos-theta'; +import * as THREE from 'three'; +import { BufferGeometry, Line, Vector3 } from 'three'; +import { Maths } from 'app/utils/maths'; +import { COLOR } from '../../../shared/utils/colors.service'; + +export class OdRoadReferenceLineBuilder { + + // private points: OdPosTheta[] = []; + + private material = new THREE.LineBasicMaterial( { color: COLOR.RED } ); + + line: any; + private cache: Map = new Map(); + + constructor ( private road: TvRoad ) { + + } + + public clear ( road: TvRoad ) { + + // if ( this.cache.has( road.id ) ) { + + // road.GameObject.remove( this.cache.get( road.id ) ); + + // this.cache.deconste( road.id ); + + // } + + } + + public create () { + + this.buildRoad( this.road ); + + } + + public buildRoad ( road: TvRoad ) { + + this.road = road; + + const tmp = new TvPosTheta(); + const points: TvPosTheta[] = []; + + for ( let s = 0; s <= this.road.length; s++ ) { + + road.getGeometryCoords( s, tmp ); + points.push( new TvPosTheta( tmp.x, tmp.y, tmp.hdg ) ); + + } + + // last entry + road.getGeometryCoords( this.road.length - Maths.Epsilon, tmp ); + points.push( new TvPosTheta( tmp.x, tmp.y, tmp.hdg ) ); + + this.drawLine( road, points ); + + } + + private drawLine ( road: TvRoad, positions: TvPosTheta[] ) { + + // this.curve = new THREE.CatmullRomCurve3( this.getVector3Points() ); + + // const points = this.curve.getPoints( 50 ); + + const points = this.convertToVector3( positions ); + + const geometry = new BufferGeometry().setFromPoints( points ); + + const object = new Line( geometry, this.material ); + + object.name = 'Line'; + + object.userData.is_selectable = false; + + if ( this.line ) road.gameObject.remove( this.line ); + + this.line = object; + + road.gameObject.add( this.line ); + + // this.cache.set( road.id, object ); + } + + private convertToVector3 ( points: TvPosTheta[] ): Vector3[] { + + const tmp: Vector3[] = []; + + points.forEach( point => { + + tmp.push( new Vector3( point.x, point.y, 0 ) ); + + } ); + + return tmp; + } +} diff --git a/src/app/modules/tv-map/builders/od-signal-builder.ts b/src/app/modules/tv-map/builders/od-signal-builder.ts new file mode 100644 index 00000000..fdedf61c --- /dev/null +++ b/src/app/modules/tv-map/builders/od-signal-builder.ts @@ -0,0 +1,261 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { GameObject } from '../../../core/game-object'; +import { BoxGeometry, CylinderBufferGeometry, CylinderGeometry, FrontSide, MeshBasicMaterial, TextureLoader, Vector3 } from 'three'; +import { TvObjectType } from '../interfaces/i-tv-object'; +import { TvDynamicTypes, TvOrientation, TvUnit } from '../models/tv-common'; +import { TvMapSourceFile } from '../services/tv-map-source-file'; +import { TvRoadSignal } from '../models/tv-road-signal.model'; +import { TvRoad } from '../models/tv-road.model'; +import { COLOR } from '../../../shared/utils/colors.service'; +import { Maths } from '../../../utils/maths'; +import { SignShapeType } from '../services/tv-sign.service'; +import { SnackBar } from '../../../services/snack-bar.service'; + +export class OdSignalBuilder { + + constructor () { + } + + createSignalGameObject ( road: TvRoad, signal: TvRoadSignal, shape?: SignShapeType ) { + + const position = road.getPositionAt( signal.s, signal.t ); + + this.createPole( position.toVector3(), signal, road, shape ); + + } + + createSignal ( + assetName: string, + signShape: SignShapeType, + roadId: number, + s: number, + t: number, + name: string = '', + dynamic: TvDynamicTypes = TvDynamicTypes.NO, + orientation: TvOrientation = TvOrientation.MINUS, + zOffset: number = 0, + country: string = 'OpenDrive', + type: string = '-1', + subtype: string = '-1', + value: number = null, + unit: TvUnit = null, + height: number = 4, + width: number = null, + text: string = null, + hOffset: number = null, + pitch: number = null, + roll: number = null + ): TvRoadSignal { + + const road = TvMapSourceFile.openDrive.getRoadById( roadId ); + + const id = road.getRoadSignalCount() + 1; + + const signal = road.addRoadSignal( + s, t, id, name, dynamic, orientation, zOffset, + country, type, subtype, + value, unit, + height, width, text, + hOffset, pitch, roll + ); + + signal.addUserData( 'asset_name', assetName ); + signal.addUserData( 'sign_shape', signShape as string ); + + return signal; + } + + + createPole ( position: Vector3, signal: TvRoadSignal, road: TvRoad, shape?: SignShapeType ) { + + const assetName = signal.assetName ? signal.assetName.attr_value : 'default'; + + // if shape is supplied + // else check for shape in signal data + const signShape = shape ? shape : signal.signShape ? signal.signShape : 'default'; + + const poleWidth = 0.05; + const poleHeight = signal.height; + + const geometry = new CylinderBufferGeometry( poleWidth, poleWidth, poleHeight, 32 ); + const material = new MeshBasicMaterial( { color: COLOR.DARKGRAY } ); + const pole = new GameObject( 'Signal', geometry, material ); + + let sign: GameObject; + + switch ( signShape ) { + + case SignShapeType.circle: + sign = this.createSphericalSignal( assetName, signal ); + break; + + case SignShapeType.square: + sign = this.createSquareSignal( assetName, signal ); + break; + + case SignShapeType.diamond: + sign = this.createSquareSignal( assetName, signal ); + break; + + case SignShapeType.triangle: + sign = this.createSquareSignal( assetName, signal ); + break; + + case SignShapeType.triangle_inverted: + sign = this.createSquareSignal( assetName, signal ); + break; + + case SignShapeType.square_tilted: + sign = this.createTiltedSquareSignal( assetName, signal ); + break; + + case SignShapeType.rectangle: + sign = this.createRectangleSignal( assetName, signal ); + break; + case 'default': + break; + + default: + SnackBar.show( 'Sign shape type not specified' ); + console.error( 'Sign shape type not specified' ); + break; + } + + if ( !sign ) return; + + pole.add( sign ); + + sign.position.set( 0, poleHeight * 0.5, poleWidth ); + + pole.rotateX( 90 * Maths.Deg2Rad ); + + + pole.position.setZ( pole.position.z + ( poleHeight * 0.5 ) ); + + signal.gameObject = new GameObject( 'Signal' ); + signal.gameObject.add( pole ); + signal.gameObject.position.setX( position.x ); + signal.gameObject.position.setY( position.y ); + + road.gameObject.add( signal.gameObject ); + } + + createRectangleSignal ( sign: string, signal: TvRoadSignal ): GameObject { + + const geometry = new BoxGeometry( 1, 1.5, 0.05 ); + + const signMaterial = this.getSignMaterial( sign ); + + return this.createObject( geometry, signMaterial, signal ); + + } + + createSquareSignal ( sign: string, signal: TvRoadSignal ): GameObject { + + const geometry = new BoxGeometry( 1, 1, 0.05 ); + + const signMaterial = this.getSignMaterial( sign ); + + const gameObject = this.createObject( geometry, signMaterial, signal ); + + return gameObject; + } + + createTiltedSquareSignal ( sign: string, signal: TvRoadSignal ): GameObject { + + const geometry = new CylinderGeometry( 0.5, 0.5, 0.05, 4, 1 ); + + const signMaterial = this.getSignMaterial( sign ); + + const gameObject = this.createObject( geometry, signMaterial, signal ); + + gameObject.rotateX( 90 * Maths.Deg2Rad ); + gameObject.rotateY( 90 * Maths.Deg2Rad ); + + return gameObject; + } + + createSphericalSignal ( sign: string, signal: TvRoadSignal ): GameObject { + + const geometry = new CylinderGeometry( 0.5, 0.5, 0.05, 32, 1 ); + + const signMaterial = this.getSignMaterial( sign ); + + const gameObject = this.createObject( geometry, signMaterial, signal ); + + gameObject.rotateX( 90 * Maths.Deg2Rad ); + gameObject.rotateY( 90 * Maths.Deg2Rad ); + + return gameObject; + } + + private createObject ( geometry, signMaterial, signal: TvRoadSignal ) { + + const gameObject = new GameObject( 'Signal', geometry, signMaterial ); + + gameObject.Tag = TvObjectType[ TvObjectType.SIGNAL ]; + + gameObject.OpenDriveType = TvObjectType.SIGNAL; + + gameObject.userData.data = signal; + + return gameObject; + } + + private getBlankMaterial () { + const blankTexture = new TextureLoader().load( `assets/textures/blank.png` ); + const blankMaterial = new MeshBasicMaterial( { map: blankTexture, transparent: true, alphaTest: 0.1, side: FrontSide } ); + } + + private getMetalMaterial () { + const metalTexture = new TextureLoader().load( `assets/signs/metal.png` ); + const metalMaterial = new MeshBasicMaterial( { map: metalTexture, transparent: true, alphaTest: 0.1, side: FrontSide } ); + } + + private getBackMaterial () { + const signBackTexture = new TextureLoader().load( `assets/signs/back_circle.png` ); + const signBackMaterial = new MeshBasicMaterial( { map: signBackTexture, transparent: true, alphaTest: 0.1, side: FrontSide } ); + return signBackMaterial; + } + + private getSignMaterial ( sign: string ) { + const signTexture = new TextureLoader().load( `assets/signs/${sign}.png` ); + const signMaterial = new MeshBasicMaterial( { map: signTexture, transparent: true, alphaTest: 0.1, side: FrontSide } ); + return signMaterial; + } + + // createPlaneSignal ( sign: string, signal: OdRoadSignal ): GameObject { + // + // const geometry = new PlaneBufferGeometry( 1, 1, 1 ); + // + // let boxMaterial: MeshBasicMaterial; + // + // const gameObject = new GameObject( 'Signal', geometry, boxMaterial ); + // + // new TextureLoader().load( `assets/signs/${sign}.png`, function ( texture ) { + // + // gameObject.material = new MeshBasicMaterial( { map: texture, transparent: true, opacity: 0.9 } ); + // + // ( gameObject.material as Material ).needsUpdate = true; + // + // } ); + // + // // var position = road.getPositionAt( signal.attr_s, signal.attr_t ); + // + // // gameObject.position.set( position.x, position.y, 0 ); + // + // gameObject.Tag = OpenDriveObjectType[ OpenDriveObjectType.SIGNAL ]; + // + // gameObject.OpenDriveType = OpenDriveObjectType.SIGNAL; + // + // gameObject.userData.data = signal; + // + // return gameObject; + // + // } + + +} diff --git a/src/app/modules/tv-map/builders/od.textures.ts b/src/app/modules/tv-map/builders/od.textures.ts new file mode 100644 index 00000000..7e345c9e --- /dev/null +++ b/src/app/modules/tv-map/builders/od.textures.ts @@ -0,0 +1,82 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { TvLane } from '../models/tv-lane'; +import { TvLaneType } from '../models/tv-common'; +import * as THREE from 'three'; +import { Texture } from 'three'; + +export class OdTextures { + + static uv_grid = new THREE.TextureLoader().load( 'assets/uv_grid_opengl.jpg' ); + static asphalt = new THREE.TextureLoader().load( 'assets/flat-asphalt.png' ); + static border = new THREE.TextureLoader().load( 'assets/flat-asphalt.png' ); + static sidewalk = new THREE.TextureLoader().load( 'assets/sidewalk.jpg' ); + static stop = new THREE.TextureLoader().load( 'assets/flat-asphalt.png' ); + static shoulder = new THREE.TextureLoader().load( 'assets/flat-asphalt.png' ); + static biking = new THREE.TextureLoader().load( 'assets/flat-asphalt.png' ); + static restricted = new THREE.TextureLoader().load( 'assets/flat-asphalt.png' ); + static parking = new THREE.TextureLoader().load( 'assets/flat-asphalt.png' ); + static terrain = new THREE.TextureLoader().load( 'assets/grass.jpg' ); + + // misc + static point = new THREE.TextureLoader().load( 'assets/point.png' ); + static arrow = new THREE.TextureLoader().load( 'assets/arrow.svg' ); + + static getLaneTexture ( lane: TvLane ): Texture { + + let texture: Texture; + + switch ( lane.type ) { + + case TvLaneType.none: + texture = this.asphalt; + break; + + case TvLaneType.driving: + texture = this.asphalt; + break; + + case TvLaneType.stop: + texture = this.stop; + break; + + case TvLaneType.shoulder: + texture = this.shoulder; + break; + + case TvLaneType.biking: + texture = this.biking; + break; + + case TvLaneType.sidewalk: + texture = this.sidewalk; + break; + + case TvLaneType.border: + texture = this.border; + break; + + case TvLaneType.restricted: + texture = this.restricted; + break; + + case TvLaneType.parking: + texture = this.parking; + break; + + default: + texture = this.terrain; + break; + } + + texture.wrapS = THREE.RepeatWrapping; + texture.wrapT = THREE.RepeatWrapping; + texture.mapping = THREE.UVMapping; + texture.repeat.set( 1, 1 ); + texture.anisotropy = 5; + + return texture; + } +} diff --git a/src/app/modules/tv-map/dialogs/new-road-dialog/new-road-dialog.component.css b/src/app/modules/tv-map/dialogs/new-road-dialog/new-road-dialog.component.css new file mode 100644 index 00000000..707523f7 --- /dev/null +++ b/src/app/modules/tv-map/dialogs/new-road-dialog/new-road-dialog.component.css @@ -0,0 +1,4 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + diff --git a/src/app/modules/tv-map/dialogs/new-road-dialog/new-road-dialog.component.html b/src/app/modules/tv-map/dialogs/new-road-dialog/new-road-dialog.component.html new file mode 100644 index 00000000..b7b50f5f --- /dev/null +++ b/src/app/modules/tv-map/dialogs/new-road-dialog/new-road-dialog.component.html @@ -0,0 +1,33 @@ + + + + +
+ +
Create New Road
+ +
Or
+ +
Open From Computer
+ +
+
Open From Computer
+ +
+ +
+ + + + +
Recent Files
+ + folder +
{{file.path}}
+
{{file.type}}
+
+
+ +
\ No newline at end of file diff --git a/src/app/modules/tv-map/dialogs/new-road-dialog/new-road-dialog.component.ts b/src/app/modules/tv-map/dialogs/new-road-dialog/new-road-dialog.component.ts new file mode 100644 index 00000000..94de9261 --- /dev/null +++ b/src/app/modules/tv-map/dialogs/new-road-dialog/new-road-dialog.component.ts @@ -0,0 +1,95 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { Component, OnInit } from '@angular/core'; +import { MatDialogRef } from '@angular/material'; +import { ElectronService } from 'ngx-electron'; +import { RecentFileService } from 'app/services/recent-file.service'; +import { IFile } from 'app/core/models/file'; +import { MainFileService } from 'app/services/main-file.service'; + +@Component( { + selector: 'app-new-road-dialog', + templateUrl: './new-road-dialog.component.html', + styleUrls: [ './new-road-dialog.component.css' ] +} ) +export class NewRoadDialogComponent implements OnInit { + + constructor ( + private dialogRef: MatDialogRef, + private electron: ElectronService, + private recentFileService: RecentFileService, + private mainFileService: MainFileService + ) { + } + + get recentFiles () { + + return this.recentFileService.recentFiles; + + } + + get isElectronApp () { + + return this.electron.isElectronApp; + + } + + ngOnInit () { + + // console.log( this.recentFileService.recentFiles ); + + } + + createNew () { + + this.mainFileService.newFile(); + + // this.oscService.newFile(); + + this.dialogRef.close(); + + } + + openFromComputer () { + + this.mainFileService.showOpenWindow(); + + this.dialogRef.close(); + + } + + openFile ( file: IFile ) { + + this.mainFileService.openFromPath( file.path, () => { + + this.dialogRef.close(); + + } ); + + } + + public fileChange ( event ) { + + const self = this; + + const reader = new FileReader(); + + if ( event.target.files && event.target.files.length > 0 ) { + + const file = event.target.files[ 0 ]; + + reader.readAsText( file ); + + reader.onload = ( data ) => { + + self.mainFileService.importViaContent( reader.result as string ); + + self.dialogRef.close(); + + }; + } + + } +} diff --git a/src/app/modules/tv-map/interfaces/i-tv-object.ts b/src/app/modules/tv-map/interfaces/i-tv-object.ts new file mode 100644 index 00000000..27e0f485 --- /dev/null +++ b/src/app/modules/tv-map/interfaces/i-tv-object.ts @@ -0,0 +1,19 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +export interface ITvObject { + + OpenDriveType: TvObjectType; + + getType (): TvObjectType; +} + +export enum TvObjectType { + ROAD = 1, + LANE = 2, + ROADMARK = 3, + SIGNAL = 4, + OBJECT = 5, + VEHICLE = 6, +} \ No newline at end of file diff --git a/src/app/modules/tv-map/models/geometries/tv-abstract-road-geometry.ts b/src/app/modules/tv-map/models/geometries/tv-abstract-road-geometry.ts new file mode 100644 index 00000000..0573c7da --- /dev/null +++ b/src/app/modules/tv-map/models/geometries/tv-abstract-road-geometry.ts @@ -0,0 +1,254 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { TvGeometryType, TvSide } from '../tv-common'; +import { TvPosTheta } from '../tv-pos-theta'; +import { Curve, Vector2, Vector3 } from 'three'; +import { Maths } from '../../../../utils/maths'; +import { _Math } from 'three/src/math/Math'; + +export abstract class TvAbstractRoadGeometry { + + public readonly uuid: string; + + public attr_S; + public attr_x; + public attr_y; + public attr_hdg; + public attr_length; + protected _s2; + protected _geometryType: TvGeometryType; + + constructor ( s: number, x: number, y: number, hdg: number, length: number ) { + + this.attr_S = s; + this.attr_x = x; + this.attr_y = y; + this.attr_hdg = hdg; + this.attr_length = length; + + this._s2 = s + length; + + this.uuid = _Math.generateUUID(); + } + + get s2 () { + + return this._s2; + + } + + get startV3 (): Vector3 { + + return new Vector3( this.x, this.y, 0 ); + + } + + get geometryType (): TvGeometryType { + return this._geometryType; + } + + set geometryType ( type: TvGeometryType ) { + this._geometryType = type; + } + + get s () { + + return this.attr_S; + + } + + set s ( value: number ) { + + this.attr_S = value; + + this._s2 = this.attr_S + this.attr_length; + } + + get x () { + + return this.attr_x; + + } + + set x ( value: number ) { + + this.attr_x = value; + + } + + get y () { + + return this.attr_y; + + } + + set y ( value: number ) { + + this.attr_y = value; + + } + + get hdg () { + + return this.attr_hdg; + + } + + set hdg ( value: number ) { + + this.attr_hdg = value; + + this.computeVars(); + } + + get length () { + + return this.attr_length; + + } + + set length ( value: number ) { + + this.attr_length = value; + + this.computeVars(); + } + + static getTypeAsString ( geometryType: TvGeometryType ): string { + + if ( geometryType === TvGeometryType.LINE ) { + + return 'line'; + + } else if ( geometryType === TvGeometryType.ARC ) { + + return 'arc'; + + } else if ( geometryType === TvGeometryType.SPIRAL ) { + + return 'spiral'; + + } else if ( geometryType === TvGeometryType.POLY3 ) { + + return 'poly3'; + + } else if ( geometryType === TvGeometryType.PARAMPOLY3 ) { + + return 'paramPoly3'; + + } + + } + + setBase ( s: number, x: number, y: number, hdg: number, length: number, recalculate: boolean ) { + + this.attr_S = s; + this.attr_x = x; + this.attr_y = y; + this.attr_hdg = hdg; + this.attr_length = length; + + this._s2 = s + length; + + if ( recalculate ) { + this.computeVars(); + } + } + + checkInterval ( sCheck: number ): boolean { + + if ( ( sCheck >= this.attr_S ) && ( sCheck <= this.s2 ) ) { + + return true; + + } + + return false; + } + + abstract getCoords ( sCheck, posTheta: TvPosTheta ); + + abstract computeVars (); + + abstract getCurve (): Curve; + + public updateControlPoints () { + + } + + public getNearestPointFrom ( x: number, y: number, posTheta?: TvPosTheta ): Vector2 { + + return this.loopToGetNearestPoint( x, y, posTheta ); + + } + + protected loopToGetNearestPoint ( x: number, y: number, refPosTheta?: TvPosTheta ): Vector2 { + + let nearestPoint: Vector2 = null; + + const point = new Vector2( x, y ); + + // const curve = this.getCurve(); + + const tmpPosTheta = new TvPosTheta(); + + let minDistance = Number.MAX_SAFE_INTEGER; + + // const curveLength = curve.getLength(); + + for ( let s = this.s; s <= this.s2; s++ ) { + + this.getCoords( s, tmpPosTheta ); + + const distance = tmpPosTheta.toVector2().distanceTo( point ); + + if ( distance < minDistance ) { + + minDistance = distance; + nearestPoint = tmpPosTheta.toVector2(); + + if ( refPosTheta ) { + + refPosTheta.x = x; + refPosTheta.y = y; + refPosTheta.s = s; + refPosTheta.t = distance; + refPosTheta.hdg = tmpPosTheta.hdg; + } + } + } + + if ( nearestPoint == null ) { + + throw new Error( 'could not find the nearest point' ); + + } else { + + if ( refPosTheta ) { + + // calculating the lane side for correct value of t + + const tmp1 = new TvPosTheta(); + const tmp2 = new TvPosTheta(); + + this.getCoords( refPosTheta.s, tmp1 ); + this.getCoords( refPosTheta.s + 1, tmp2 ); + + const side = Maths.direction( tmp1.toVector3(), tmp2.toVector3(), refPosTheta.toVector3() ); + + if ( side == TvSide.RIGHT ) refPosTheta.t *= -1; + + } + + } + + return nearestPoint; + } + + polyeval ( t: number, v: Vector3 ): number { + + return ( v.x ) + ( v.y * t ) + ( v.z * t * t ); + } +} diff --git a/src/app/modules/tv-map/models/geometries/tv-arc-geometry.spec.ts b/src/app/modules/tv-map/models/geometries/tv-arc-geometry.spec.ts new file mode 100644 index 00000000..f399380f --- /dev/null +++ b/src/app/modules/tv-map/models/geometries/tv-arc-geometry.spec.ts @@ -0,0 +1,72 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { TvRoad } from '../tv-road.model'; +import { TvPosTheta } from '../tv-pos-theta'; +import { TvMap } from '../tv-map.model'; +import { TvMapQueries } from '../../queries/tv-map-queries'; +import { TvMapSourceFile } from '../../services/tv-map-source-file'; + +describe( 'OdArcGeometry', () => { + + let openDrive: TvMap; + let road: TvRoad; + let pose = new TvPosTheta(); + + beforeEach( () => { + + openDrive = new TvMap(); + + TvMapSourceFile.openDrive = openDrive; + + road = new TvRoad( '', 10, 1, -1 ); + + road.addPlanView(); + + } ); + + it( 'should give nearest point on arc', () => { + + // add roads + // 3 arc roads + // ==========|==========|========== + const road1 = openDrive.addRoad( '', 10, 1, -1 ); + const road2 = openDrive.addRoad( '', 10, 2, -1 ); + const road3 = openDrive.addRoad( '', 10, 3, -1 ); + + road1.addPlanView(); + road2.addPlanView(); + road3.addPlanView(); + + road1.addGeometryArc( 0, 0, 0, 0, 10, 0 ); + road2.addGeometryArc( 0, 10, 0, 0, 10, 0 ); + road3.addGeometryArc( 0, 20, 0, 0, 10, 0 ); + + let roadResult = TvMapQueries.getRoadByCoords( 0, 0 ); + expect( roadResult ).not.toBeNull(); + expect( roadResult.id ).toBe( 1 ); + + roadResult = TvMapQueries.getRoadByCoords( 1, 9 ); + expect( roadResult ).not.toBeNull(); + expect( roadResult.id ).toBe( 1 ); + + roadResult = TvMapQueries.getRoadByCoords( 11, 0 ); + expect( roadResult ).not.toBeNull(); + expect( roadResult.id ).toBe( 2 ); + + roadResult = TvMapQueries.getRoadByCoords( 11, 10 ); + expect( roadResult ).not.toBeNull(); + expect( roadResult.id ).toBe( 2 ); + + roadResult = TvMapQueries.getRoadByCoords( 21, 0 ); + expect( roadResult ).not.toBeNull(); + expect( roadResult.id ).toBe( 3 ); + + roadResult = TvMapQueries.getRoadByCoords( 21, 10 ); + expect( roadResult ).not.toBeNull(); + expect( roadResult.id ).toBe( 3 ); + + } ); + +} ); diff --git a/src/app/modules/tv-map/models/geometries/tv-arc-geometry.ts b/src/app/modules/tv-map/models/geometries/tv-arc-geometry.ts new file mode 100644 index 00000000..e4bfad65 --- /dev/null +++ b/src/app/modules/tv-map/models/geometries/tv-arc-geometry.ts @@ -0,0 +1,292 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { TvGeometryType } from '../tv-common'; +import { Curve, Object3D, SplineCurve, Vector2, Vector3 } from 'three'; +import { TvPosTheta } from '../tv-pos-theta'; +import { Maths } from '../../../../utils/maths'; +import { TvAbstractRoadGeometry } from './tv-abstract-road-geometry'; + +export class TvArcGeometry extends TvAbstractRoadGeometry { + + public attr_curvature; + + public cp1: Object3D; + public cp2: Object3D; + public cp3: Object3D; + + private curve: Curve; + + constructor ( s: number, x: number, y: number, hdg: number, length: number, curvature: number ) { + + super( s, x, y, hdg, length ); + + this._geometryType = TvGeometryType.ARC; + + if ( curvature == 0 ) { + this.attr_curvature = Maths.Epsilon; + } else { + this.attr_curvature = curvature; + } + + this.computeVars(); + } + + get curvature (): number { + return this.attr_curvature; + } + + set curvature ( value: number ) { + + this.attr_curvature = value; + + this.computeVars(); + + } + + get radius (): number { + + return 1.0 / Math.abs( this.attr_curvature ); + + } + + get centre (): Vector3 { + + const clockwise = this.attr_curvature < 0; + + const circleX = this.x - this.radius * Math.cos( this.hdg - Maths.M_PI_2 ) * ( clockwise ? -1 : 1 ); + const circleY = this.y - this.radius * Math.sin( this.hdg - Maths.M_PI_2 ) * ( clockwise ? -1 : 1 ); + + return new Vector3( circleX, circleY, 0 ); + } + + get startV3 (): Vector3 { + + return new Vector3( this.x, this.y, 0 ); + + } + + get middleV3 (): Vector3 { + + const pos = new TvPosTheta(); + + this.getCoords( this.s2 / 2, pos ); + + return pos.toVector3(); + } + + get endV3 (): Vector3 { + + const pos = new TvPosTheta(); + + this.getCoords( this.s2, pos ); + + return pos.toVector3(); + + } + + get headingEnd (): Vector3 { + + // find the end of the chord line + const x = this.attr_x + Math.cos( this.attr_hdg ) * this.s2; + const y = this.attr_y + Math.sin( this.attr_hdg ) * this.s2; + + return new Vector3( x, y, 0 ); + + } + + updateControlPoints () { + + if ( this.cp1 ) this.cp1.position.copy( this.startV3 ); + if ( this.cp2 ) this.cp2.position.copy( this.middleV3 ); + if ( this.cp3 ) this.cp3.position.copy( this.endV3 ); + + } + + getCoords ( s, odPosTheta: TvPosTheta ) { + + // calculate the first geometry element for the returning geometries + var ds = s - this.s; + var curvature = this.curvature; + var theta = ds * curvature; + var radius = 1 / Math.abs( this.curvature ); + var rotation = this.hdg - Math.sign( curvature ) * Math.PI / 2; + + const retX = this.x - radius * Math.cos( rotation ) + radius * Math.cos( rotation + theta ); + const retY = this.y - radius * Math.sin( rotation ) + radius * Math.sin( rotation + theta ); + + odPosTheta.x = retX; + odPosTheta.y = retY; + odPosTheta.hdg = this.hdg + theta; + + return this.geometryType; + } + + computeVars () { + + this._s2 = this.s + this.length; + + // this.startAngle = this.hdg; + // + // this.radius = 0.0; + // + // // if curvature is 0, radius is also 0, otherwise, radius is 1/curvature + // if ( Math.abs( this.attr_curvature ) > 1.00e-15 ) { + // + // // radius = Math.abs( 1.0 / this.attr_curvature ); + // + // this.radius = 1.0 / Math.abs( this.attr_curvature ); + // + // } + + // this.clockwise = this.attr_curvature < 0; + // + // this.arcAngle = this.length * this.curvature; + // + // this.circleX = this.x - this.radius * Math.cos( this.startAngle - Maths.M_PI_2 ) * (this.clockwise ? -1 : 1); + // this.circleY = this.y - this.radius * Math.sin( this.startAngle - Maths.M_PI_2 ) * (this.clockwise ? -1 : 1); + + // // calculate the start angle for the arc plot + // // if ( this.attr_curvature <= 0 ) { + // if ( this.attr_curvature < 0 ) { + // + // this.startAngle = this.attr_hdg + Maths.M_PI_2; + // + // } else { + // + // this.startAngle = this.attr_hdg - Maths.M_PI_2; + // + // } + // + // const cos = Math.cos( this.startAngle - Maths.M_PI ); + // const sin = Math.sin( this.startAngle - Maths.M_PI ); + // + // this.circleX = this.attr_x + cos * this.radius; + // this.circleY = this.attr_y + sin * this.radius; + } + + findS ( x, y ) { + + // not working just for reference + + // // calculate the first geometry element for the returning geometries + // // var ds = s - this.s; + // var curvature = this.curvature; + // // var theta = ds * curvature; + // var radius = 1 / Math.abs( this.curvature ); + // var rotation = this.hdg - Math.sign( curvature ) * Math.PI / 2; + + // let c = ( ( x - this.x ) + ( radius * Math.cos( rotation ) ) ) / radius; + // let a = Math.cos( rotation ); + // let b = Math.sin( rotation ); + + // let alphaCos = Math.acos( a / ( Math.sqrt( a * a + b * b ) ) ); // positive + // let alphaSin = Math.asin( b / ( Math.sqrt( a * a + b * b ) ) ); // negative + + // let thetaCos = Math.asin( c / ( Math.sqrt( a * a + b * b ) ) ) - alphaCos; + // let thetaSin = Math.asin( c / ( Math.sqrt( a * a + b * b ) ) ) - alphaSin; + + // let thetaNew = Math.asin( c / ( Math.sqrt( a * a + b * b ) ) ); + + // let theta = x * this.curvature; + + // let thetaCosCorrect = Maths.approxEquals( theta, thetaCos ); + // let thetaSinCorrect = Maths.approxEquals( theta, thetaSin ); + // let thetaNewCorrect = Maths.approxEquals( theta, thetaNew ); + + // let actualS = theta / curvature; + // let cosS = theta / curvature; + // let sinS = theta / curvature; + // let foundS = thetaNew / curvature; + + // let foundSCorrect = Maths.approxEquals( actualS, foundS ); + + } + + getCurve (): Curve { + + if ( this.curve != null ) return this.curve; + + const points: Vector2[] = []; + const posTheta = new TvPosTheta(); + + for ( let sCoordinate = this.s; sCoordinate <= this.s2; sCoordinate++ ) { + + this.getCoords( sCoordinate, posTheta ); + points.push( posTheta.toVector2() ); + + } + + this.getCoords( this.s2 - Maths.Epsilon, posTheta ); + points.push( posTheta.toVector2() ); + + return this.curve = new SplineCurve( points ); + } + + // getCoords ( sCheck, odPosTheta: OdPosTheta ) { + // // s from the beginning of the segment + // const currentLength = sCheck - this.attr_S; + // let endAngle = this.startAngle; + // let radius = 0.0; + // + // let retX, retY, retHdg; + // + // // if curvature is 0, radius is also 0, so don't add anything to the initial radius, + // // otherwise, radius is 1/curvature so the central angle can be calculated and added to the initial direction + // if ( Math.abs( this.attr_curvature ) > 1.00e-15 ) { + // + // endAngle += currentLength / (1.0 / this.attr_curvature); + // + // radius = Math.abs( 1.0 / this.attr_curvature ); + // + // } + // + // const cos = Math.cos( endAngle ); + // const sin = Math.sin( endAngle ); + // + // retX = this.circleX + cos * radius; + // retY = this.circleY + sin * radius; + // + // + // // heading at the given position + // if ( this.attr_curvature <= 0 ) { + // + // retHdg = endAngle - Maths.M_PI_2; + // + // } else { + // + // retHdg = endAngle + Maths.M_PI_2; + // + // } + // + // // // tangent to arc (road direction) + // // const theta = this.clockwise ? this.startAngle - sCheck / this.radius : this.startAngle + sCheck / this.radius; + // // + // // // angle arc subtends at center + // // const arcTheta = theta - Maths.M_PI_2;11 + // // + // // const cos = Math.cos( theta ); + // // const sin = Math.sin( theta ); + // // + // // // lateralOffset is perpendicular to road + // // // NA + // // const r = this.radius - 0 * (this.clockwise ? -1 : 1); + // // + // // odPosTheta.x = this.circleX + r * Math.cos( arcTheta ) * (this.clockwise ? -1 : 1); + // // + // // odPosTheta.y = this.circleY + r * Math.sin( arcTheta ) * (this.clockwise ? -1 : 1); + // // + // // odPosTheta.hdg = + + setAll ( s: number, x: number, y: number, hdg: number, length: number, curvature: number ) { + + this.setBase( s, x, y, hdg, length, false ); + + this.attr_curvature = curvature; + + this.computeVars(); + + } + +} diff --git a/src/app/modules/tv-map/models/geometries/tv-line-geometry.spec.ts b/src/app/modules/tv-map/models/geometries/tv-line-geometry.spec.ts new file mode 100644 index 00000000..a6629ebc --- /dev/null +++ b/src/app/modules/tv-map/models/geometries/tv-line-geometry.spec.ts @@ -0,0 +1,188 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { TvRoad } from '../tv-road.model'; +import { TvPosTheta } from '../tv-pos-theta'; +import { TvMap } from '../tv-map.model'; +import { TvMapQueries } from '../../queries/tv-map-queries'; +import { TvMapSourceFile } from '../../services/tv-map-source-file'; + +describe( 'OdLineGeometry', () => { + + let openDrive: TvMap; + let road: TvRoad; + let pose = new TvPosTheta(); + + beforeEach( () => { + + openDrive = new TvMap(); + + TvMapSourceFile.openDrive = openDrive; + + road = new TvRoad( '', 10, 1, -1 ); + + road.addPlanView(); + + } ); + + it( 'should give correct coordinates with 0-degree hdg', () => { + + road.addGeometryLine( 0, 0, 1, 0, 10 ); + + const posTheta = new TvPosTheta(); + + road.getGeometryCoords( 0, posTheta ); + + expect( posTheta.x ).toBe( 0 ); + expect( posTheta.y ).toBe( 1 ); + expect( posTheta.hdg ).toBe( 0 ); + + road.getGeometryCoords( 10, posTheta ); + + expect( posTheta.x ).toBe( 10 ); + expect( posTheta.y ).toBe( 1 ); + expect( posTheta.hdg ).toBe( 0 ); + + } ); + + it( 'should give correct coordinates with 90degree hdg', () => { + + const hdg = 90 * (Math.PI / 180); + + road.addGeometryLine( 0, 1, 0, hdg, 10 ); + + const posTheta = new TvPosTheta(); + + road.getGeometryCoords( 0, posTheta ); + + expect( posTheta.x ).toBe( 1 ); + expect( posTheta.y ).toBe( 0 ); + expect( posTheta.hdg ).toBe( hdg ); + + road.getGeometryCoords( 10, posTheta ); + + expect( Math.round( posTheta.x ) ).toBe( 1 ); + expect( Math.round( posTheta.y ) ).toBe( 10 ); + expect( posTheta.hdg ).toBe( hdg ); + + } ); + + it( 'should give correct coordinates with 180 degree hdg', () => { + + const hdg = 180 * (Math.PI / 180); + + road.addGeometryLine( 0, 0, 0, hdg, 10 ); + + const posTheta = new TvPosTheta(); + + road.getGeometryCoords( 0, posTheta ); + + expect( Math.round( posTheta.x ) ).toBe( 0 ); + expect( posTheta.y ).toBe( 0 ); + expect( posTheta.hdg ).toBe( hdg ); + + road.getGeometryCoords( 10, posTheta ); + + expect( Math.round( posTheta.x ) ).toBe( -10 ); + expect( Math.round( posTheta.y ) ).toBe( 0 ); + expect( posTheta.hdg ).toBe( hdg ); + + } ); + + it( 'should give correct coordinates for s and t with 0 degree hdg', () => { + + const hdg = 0; + const x = 0; + const y = 0; + const s = 0; + const length = 10; + + let t = 0; + + road.addGeometryLine( s, x, y, hdg, length ); + + road.getGeometryCoordsAt( s, t, pose ); + + expect( Math.round( pose.x ) ).toBe( 0 ); + expect( pose.y ).toBe( 0 ); + + t = 1; + + road.getGeometryCoordsAt( s, t, pose ); + + expect( Math.round( pose.x ) ).toBe( 0 ); + expect( pose.y ).toBe( t ); + + } ); + + it( 'should give correct coordinates for s and t with 90 degree hdg', () => { + + const hdg = 90 * (Math.PI / 180); + const x = 0; + const y = 0; + const s = 0; + const length = 10; + + let t = 0; + + road.addGeometryLine( s, x, y, hdg, length ); + + road.getGeometryCoordsAt( s, t, pose ); + + expect( Math.round( pose.x ) ).toBe( 0 ); + expect( Math.round( pose.y ) ).toBe( 0 ); + + t = 1; + + road.getGeometryCoordsAt( s, t, pose ); + + expect( Math.round( pose.x ) ).toBe( -1 ); + expect( Math.round( pose.y ) ).toBe( 0 ); + + } ); + + it( 'should give nearest point on line', () => { + + // add roads + // 3 straight road + // ==========|==========|========== + const road1 = openDrive.addRoad( '', 10, 1, -1 ); + const road2 = openDrive.addRoad( '', 10, 2, -1 ); + const road3 = openDrive.addRoad( '', 10, 3, -1 ); + + road1.addPlanView(); + road2.addPlanView(); + road3.addPlanView(); + + road1.addGeometryLine( 0, 0, 0, 0, 10 ); + road2.addGeometryLine( 0, 10, 0, 0, 10 ); + road3.addGeometryLine( 0, 20, 0, 0, 10 ); + + let roadResult = TvMapQueries.getRoadByCoords( 0, 0 ); + expect( roadResult ).not.toBeNull(); + expect( roadResult.id ).toBe( 1 ); + + roadResult = TvMapQueries.getRoadByCoords( 1, 10 ); + expect( roadResult ).not.toBeNull(); + expect( roadResult.id ).toBe( 1 ); + + roadResult = TvMapQueries.getRoadByCoords( 11, 0 ); + expect( roadResult ).not.toBeNull(); + expect( roadResult.id ).toBe( 2 ); + + roadResult = TvMapQueries.getRoadByCoords( 11, 10 ); + expect( roadResult ).not.toBeNull(); + expect( roadResult.id ).toBe( 2 ); + + roadResult = TvMapQueries.getRoadByCoords( 21, 0 ); + expect( roadResult ).not.toBeNull(); + expect( roadResult.id ).toBe( 3 ); + + roadResult = TvMapQueries.getRoadByCoords( 21, 10 ); + expect( roadResult ).not.toBeNull(); + expect( roadResult.id ).toBe( 3 ); + + } ); + +} ); diff --git a/src/app/modules/tv-map/models/geometries/tv-line-geometry.ts b/src/app/modules/tv-map/models/geometries/tv-line-geometry.ts new file mode 100644 index 00000000..b6a0e1c0 --- /dev/null +++ b/src/app/modules/tv-map/models/geometries/tv-line-geometry.ts @@ -0,0 +1,227 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { TvGeometryType } from '../tv-common'; +import { Curve, LineCurve, Vector2, Vector3 } from 'three'; +import { TvPosTheta } from '../tv-pos-theta'; +import { TvAbstractRoadGeometry } from './tv-abstract-road-geometry'; +import { Maths } from 'app/utils/maths'; +import { TvArcGeometry } from './tv-arc-geometry'; + +export class TvLineGeometry extends TvAbstractRoadGeometry { + + private cosTheta: number; + private sinTheta: number; + + constructor ( s: number, x: number, y: number, hdg: number, length: number ) { + + super( s, x, y, hdg, length ); + + this._geometryType = TvGeometryType.LINE; + + this.computeVars(); + + } + + get start (): Vector2 { + + return new Vector2( this.x, this.y ); + + } + + get end (): Vector2 { + + // find the end of the chord line + const x = this.attr_x + Math.cos( this.attr_hdg ) * this.s2; + const y = this.attr_y + Math.sin( this.attr_hdg ) * this.s2; + + return new Vector2( x, y ); + + } + + get endV3 (): Vector3 { + + // find the end of the chord line + const x = this.attr_x + Math.cos( this.attr_hdg ) * this.s2; + const y = this.attr_y + Math.sin( this.attr_hdg ) * this.s2; + + return new Vector3( x, y, 0 ); + + } + + getCurve (): Curve { + + return new LineCurve( this.start, this.end ); + + } + + computeVars () { + + this._s2 = this.s + this.length; + + this.sinTheta = Math.sin( this.hdg ); + this.cosTheta = Math.cos( this.hdg ); + + } + + clone () { + + } + + setAll ( s: number, x: number, y: number, hdg: number, length: number ) { + + this.setBase( s, x, y, hdg, length, false ); + + this.computeVars(); + } + + getCoords ( sCheck, odPosTheta: TvPosTheta ): TvGeometryType { + + const lateralOffset = 0; + + const newLength = sCheck - this.attr_S; + + // find the end of the chord line + odPosTheta.x = this.attr_x + Math.cos( this.attr_hdg ) * newLength; + odPosTheta.y = this.attr_y + Math.sin( this.attr_hdg ) * newLength; + odPosTheta.hdg = this.attr_hdg; + + // odPosTheta.x = this.x + sCheck * this.cosTheta - lateralOffset * this.sinTheta; + // odPosTheta.y = this.y + sCheck * this.sinTheta + lateralOffset * this.cosTheta; + // odPosTheta.hdg = this.hdg; + + return this.geometryType; + + } + + // closestPointToPointParmeter ( point: Vector3 ) { + // + // const _startP = new Vector3(); + // const _startEnd = new Vector3(); + // + // const start = new Vector3( this.start.x, this.start.y, 0 ); + // const end = new Vector3( this.end.x, this.end.y, 0 ); + // + // _startP.subVectors( point, start ); + // _startEnd.subVectors( end, start ); + // + // var startEnd2 = _startEnd.dot( _startEnd ); + // var startEnd_startP = _startEnd.dot( _startP ); + // + // const t = startEnd_startP / startEnd2; + // + // return t; + // } + + // getNearestPointFrom ( x2: number, y2: number ): Vector2 { + // + // return this.loopToGetNearestPoint( x2, y2 ); + // + // // const point = new Vector3( x2, y2, 0 ); + // // + // // const t = this.closestPointToPointParmeter( point ); + // // + // // const start = new Vector3( this.start.x, this.start.y, 0 ); + // // + // // const delta = new Vector3(); + // // + // // const res = delta.multiplyScalar( t ).add( start ); + // // + // // return new Vector2( res.x, res.y ); + // } + + getStCoordinates ( posTheta: TvPosTheta ) { + + const objPosition = new Vector2( posTheta.x, posTheta.y ); + + // get nearest point on road from this new point + const nearestPointOnRoad = this.getNearestPointFrom( objPosition.x, objPosition.y ); + + // Debug.log( nearestPointOnRoad ); + + // s value is simply the distance from start to the nearestPointOnRoad + const s = ( new Vector2( this.x, this.y ) ).distanceTo( nearestPointOnRoad ); + + // TODO: Find negative t value as well + // t value is simple the distance from nearPointOnRoad to object position + const t = nearestPointOnRoad.distanceTo( objPosition ); + + posTheta.s = s; + posTheta.t = t; + posTheta.hdg = this.hdg; + + return new Vector2( s, t ); + } + + findS ( x, y ) { + + // not working just for reference + + // const s1 = ( x - this.x ) / Math.cos( this.hdg ); + // const s2 = ( y - this.y ) / Math.sin( this.hdg ); + + // let s1Valid = false; + // let s2Valid = false; + + // if ( isNaN( s1 ) || ( s1 >= 0 && s1 <= this.length ) ) { + // s1Valid = true; + // } + + // if ( isNaN( s2 ) || ( s2 >= 0 && s2 <= this.length ) ) { + // s2Valid = true; + // } + + // return [ s1, s2, s1Valid && s2Valid, s1Valid, s2Valid ]; + } + + /** + * + * @param geometry + * @deprecated not working currently + */ + public getIntersections ( geometry: TvAbstractRoadGeometry ): Vector3[] { + + // throw new Error( "Method not implemented." ); + + if ( geometry instanceof TvLineGeometry ) { + + const x1 = this.x; + const y1 = this.y; + const t1 = this.hdg; + + const x2 = geometry.x; + const y2 = geometry.y; + const t2 = geometry.hdg; + + const sX = ( x2 - x1 ) / Math.cos( t2 ) - Math.cos( t1 ); + const sY = ( y2 - y1 ) / Math.sin( t2 ) - Math.sin( t1 ); + + // const intersection = Maths.lineLineIntersection( this.startV3, this.endV3, geometry.startV3, geometry.endV3 ); + + // if ( intersection && + // Maths.isPointOnLine( this.startV3, this.endV3, intersection ) && + // Maths.isPointOnLine( geometry.startV3, geometry.endV3, intersection ) + // ) { + + // return [ intersection ]; + + // } + + } else if ( geometry instanceof TvArcGeometry ) { + + + return Maths.getLineArcIntersections( this.startV3, this.endV3, geometry.centre, geometry.radius ); + + } else { + + // console.error( "Intersection with ", geometry.geometryType, "not implemented" ); + + // throw new Error( "Method not implemented." ); + + } + + return []; + } + +} diff --git a/src/app/modules/tv-map/models/geometries/tv-param-poly3-geometry.spec.ts b/src/app/modules/tv-map/models/geometries/tv-param-poly3-geometry.spec.ts new file mode 100644 index 00000000..5fbb81b0 --- /dev/null +++ b/src/app/modules/tv-map/models/geometries/tv-param-poly3-geometry.spec.ts @@ -0,0 +1,60 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { TvRoad } from '../tv-road.model'; +import { TvPosTheta } from '../tv-pos-theta'; + +describe( 'OdParamPoly3Geometry', () => { + + let road: TvRoad; + let pose: TvPosTheta; + + beforeEach( () => { + + pose = new TvPosTheta(); + + road = new TvRoad( '', 100, 1, -1 ); + + road.addPlanView(); + + } ); + + it( 'should give correct coordinates with 0-degree hdg', () => { + + const s = 0; + const x = 0; + const y = 0; + const hdg = 0; + const length = 100; + + const aU = 0; + const bU = 0; + const cU = 40; + const dU = 50; + + const aV = 0; + const bV = 0; + const cV = 10; + const dV = 0; + + road.addGeometryParamPoly( s, x, y, hdg, length, aU, bU, cU, dU, aV, bV, cV, dV ); + + road.getGeometryCoords( 0, pose ); + expect( pose.x ).toBe( 0 ); + expect( pose.y ).toBe( 0 ); + expect( pose.hdg ).toBe( 0 ); + + road.getGeometryCoords( 10, pose ); + expect( pose.x ).toBe( 0.45 ); + expect( pose.y ).toBe( 0.1 ); + expect( pose.hdg ).toBe( 0.20749622643520266 ); + + road.getGeometryCoords( 20, pose ); + expect( pose.x ).toBe( 2.0 ); + expect( pose.y ).toBe( 0.4 ); + expect( pose.hdg ).toBe( 0.17985349979247828 ); + + } ); + +} ); diff --git a/src/app/modules/tv-map/models/geometries/tv-param-poly3-geometry.ts b/src/app/modules/tv-map/models/geometries/tv-param-poly3-geometry.ts new file mode 100644 index 00000000..d7f085fa --- /dev/null +++ b/src/app/modules/tv-map/models/geometries/tv-param-poly3-geometry.ts @@ -0,0 +1,176 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { TvAbstractRoadGeometry } from './tv-abstract-road-geometry'; +import { TvPosTheta } from '../tv-pos-theta'; +import { Curve, SplineCurve, Vector2, Vector3 } from 'three'; +import { TvGeometryType } from '../tv-common'; + +export class TvParamPoly3Geometry extends TvAbstractRoadGeometry { + + private sinTheta; + private cosTheta; + private curve: Curve; + + constructor ( + public s: number, + public x: number, + public y: number, + public hdg: number, + public length: number, + public aU: number, public bU: number, public cU: number, public dU: number, + public aV: number, public bV: number, public cV: number, public dV: number + ) { + super( s, x, y, hdg, length ); + + this._geometryType = TvGeometryType.PARAMPOLY3; + + this.computeVars(); + + // this.getCurve(); + } + + get v0 () { + return new Vector2( this.aU, this.aV ); + } + + get v1 () { + return new Vector2( this.bU, this.bV ); + } + + get v2 () { + return new Vector2( this.cU, this.cV ); + } + + get v3 () { + return new Vector2( this.dU, this.dV ); + } + + computeVars () { + + this.sinTheta = Math.sin( this.hdg ); + this.cosTheta = Math.cos( this.hdg ); + + } + + getCoords ( sCheck, posTheta: TvPosTheta ) { + + // normalised p between 0 to 1 + const du = sCheck - this.s; + const p = ( du / this.length ); + + const uLocal = + ( this.aU ) + + ( this.bU * p ) + + ( this.cU * p * p ) + + ( this.dU * p * p * p ); + + const vLocal = + (this.aV) + + (this.bV * p) + + (this.cV * p * p) + + (this.dV * p * p * p); + + // apply rotation with respect to start + const xnew = uLocal * this.cosTheta - vLocal * this.sinTheta; + const ynew = uLocal * this.sinTheta + vLocal * this.cosTheta; + + // Derivate to get heading change + const dCoeffsU = (new Vector3( this.bU, this.cU, this.dU )).multiply( new Vector3( 1, 2, 3 ) ); + const dCoeffsV = (new Vector3( this.bV, this.cV, this.dV )).multiply( new Vector3( 1, 2, 3 ) ); + + const dx = this.polyeval( p, dCoeffsU ); + const dy = this.polyeval( p, dCoeffsV ); + + const tangent = Math.atan2( dy, dx ); + + // apply tranformation with respect to start + posTheta.x = this.x + xnew; + posTheta.y = this.y + ynew; + posTheta.hdg = this.hdg + tangent; + + // if ( this.curve != null ) { + // + // const du = sCheck - this.s; + // + // const p = du / this.length; + // + // const point = this.curve.getPointAt( p ); + // + // posTheta.x = point.x; + // posTheta.y = point.y; + // + // const tangent = this.curve.getTangent( p ); + // + // posTheta.hdg = tangent.angle(); + // + // return this.geometryType; + // } + // + // return; + + // if ( this.curve != null ) { + + // const tangent = this.curve.getTangent( p ); + + // posTheta.hdg = tangent.angle(); + + // } else { + + // posTheta.hdg = this.hdg; + + // } + + return this.geometryType; + } + + getCurve (): Curve { + + // const v0 = this.v0; + // const v1 = this.v1; + // const v2 = this.v2; + // const v3 = this.v3; + // + // return new CubicBezierCurve( v0, v1, v2, v3 ); + + const resolution = 1; + const s = this.sinTheta; + const c = this.cosTheta; + const pMax = this.length; + const pStep = pMax / Math.ceil( this.length / resolution ); + + const points: Vector2[] = []; + + for ( let p = 0; p <= pMax + pStep; p += pStep ) { + + const x = + (this.aU) + + (this.bU * p) + + (this.cU * p * p) + + (this.dU * p * p * p); + + const y = + (this.aV) + + (this.bV * p) + + (this.cV * p * p) + + (this.dV * p * p * p); + + const xnew = x * c - y * s; + const ynew = x * s + y * c; + + points.push( new Vector2( this.x + xnew, this.y + ynew ) ); + + } + + // for ( let sCoordinate = this.s; sCoordinate < this.s2; sCoordinate++ ) { + + // this.getCoords( sCoordinate, posTheta ); + // points.push( posTheta.toVector2() ); + + // } + + return this.curve = new SplineCurve( points ); + + } +} diff --git a/src/app/modules/tv-map/models/geometries/tv-poly3-geometry.spec.ts b/src/app/modules/tv-map/models/geometries/tv-poly3-geometry.spec.ts new file mode 100644 index 00000000..16ec5e58 --- /dev/null +++ b/src/app/modules/tv-map/models/geometries/tv-poly3-geometry.spec.ts @@ -0,0 +1,60 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { TvRoad } from '../tv-road.model'; +import { TvPosTheta } from '../tv-pos-theta'; + +describe( 'OdPoly3Geometry', () => { + + let road: TvRoad; + let pose: TvPosTheta; + + beforeEach( () => { + + pose = new TvPosTheta(); + + road = new TvRoad( '', 100, 1, -1 ); + + road.addPlanView(); + + } ); + + it( 'should give correct coordinates with 0-degree hdg', () => { + + const s = 0; + const x = 0; + const y = 0; + const hdg = 0; + const length = 10; + + const a = 0; + const b = 0; + const c = 1; + const d = 0; + + road.addGeometryPoly( s, x, y, hdg, length, a, b, c, d ); + + road.getGeometryCoords( 0, pose ); + expect( pose.x ).toBe( 0 ); + expect( pose.y ).toBe( 0 ); + expect( pose.hdg ).toBe( 0 ); + + road.getGeometryCoords( 1, pose ); + expect( pose.x ).toBe( 1 ); + expect( pose.y ).toBe( 1 ); + expect( pose.hdg ).toBe( 2 ); + + road.getGeometryCoords( 2, pose ); + expect( pose.x ).toBe( 2 ); + expect( pose.y ).toBe( 4 ); + expect( pose.hdg ).toBe( 4 ); + + road.getGeometryCoords( 3, pose ); + expect( pose.x ).toBe( 3 ); + expect( pose.y ).toBe( 9 ); + expect( pose.hdg ).toBe( 6 ); + + } ); + +} ); diff --git a/src/app/modules/tv-map/models/geometries/tv-poly3-geometry.ts b/src/app/modules/tv-map/models/geometries/tv-poly3-geometry.ts new file mode 100644 index 00000000..0d3711b1 --- /dev/null +++ b/src/app/modules/tv-map/models/geometries/tv-poly3-geometry.ts @@ -0,0 +1,94 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { TvGeometryType } from '../tv-common'; +import { Curve, SplineCurve, Vector2, Vector3 } from 'three'; +import { TvPosTheta } from '../tv-pos-theta'; +import { TvAbstractRoadGeometry } from './tv-abstract-road-geometry'; + +export class TvPoly3Geometry extends TvAbstractRoadGeometry { + + public attr_a: number; + public attr_b: number; + public attr_c: number; + public attr_d: number; + + private staringPoint; + private sinTheta; + private cosTheta; + private curve: Curve; + + constructor ( s: number, x: number, y: number, hdg: number, length: number, a: number, b: number, c: number, d: number ) { + + super( s, x, y, hdg, length ); + + this._geometryType = TvGeometryType.POLY3; + + this.attr_a = a; + this.attr_b = b; + this.attr_c = c; + this.attr_d = d; + + this.computeVars(); + } + + getCoords ( sCheck: any, posTheta: TvPosTheta ) { + + const vLocal = this.getBezierValue( sCheck ); + + const x = sCheck; + const y = vLocal; + + const xnew = x * this.cosTheta - y * this.sinTheta; + const ynew = x * this.sinTheta + y * this.cosTheta; + + // Derivate to get heading change + const dCoeffs = (new Vector3( this.attr_b, this.attr_c, this.attr_d )).multiply( new Vector3( 1, 2, 3 ) ); + const tangent = this.polyeval( sCheck, dCoeffs ); + + posTheta.x = this.x + xnew; + posTheta.y = this.y + ynew; + + posTheta.hdg = this.hdg + tangent; + + return this.geometryType; + } + + getCurve (): Curve { + + const points: Vector2[] = []; + const posTheta = new TvPosTheta(); + + for ( let sCoordinate = this.s; sCoordinate < this.s2; sCoordinate++ ) { + + this.getCoords( sCoordinate, posTheta ); + points.push( posTheta.toVector2() ); + + } + + return this.curve = new SplineCurve( points ); + + } + + computeVars () { + + + this.sinTheta = Math.sin( this.hdg ); + this.cosTheta = Math.cos( this.hdg ); + + + // throw new Error("Method not implemented."); + } + + getBezierValue ( sCheck ): number { + + const du = sCheck - this.s; + + return (this.attr_a) + + (this.attr_b * du) + + (this.attr_c * du * du) + + (this.attr_d * du * du * du); + } + +} diff --git a/src/app/modules/tv-map/models/geometries/tv-spiral-geometry.ts b/src/app/modules/tv-map/models/geometries/tv-spiral-geometry.ts new file mode 100644 index 00000000..ecc92d7c --- /dev/null +++ b/src/app/modules/tv-map/models/geometries/tv-spiral-geometry.ts @@ -0,0 +1,273 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { Maths } from '../../../../utils/maths'; +import { TvGeometryType } from '../tv-common'; +import { SpiralUtils } from 'app/utils/spiral-utils'; +import { TvPosTheta } from '../tv-pos-theta'; +import { TvAbstractRoadGeometry } from './tv-abstract-road-geometry'; + +export class TvSpiralGeometry extends TvAbstractRoadGeometry { + + public attr_curvStart; + public attr_curvEnd; + + private sqrtPiO2 = Math.sqrt( Maths.M_PI_2 ); + private mA; + private mCurvature; + private mDenormalizeFactor; + private endX; + private endY; + private normalDirection; + + private differenceAngle; + private mRotCos; + private mRotSin; + + constructor ( s: number, x: number, y: number, hdg: number, length: number, curvStart: number, curvEnd: number ) { + + super( s, x, y, hdg, length ); + + this.attr_curvStart = curvStart; + this.attr_curvEnd = curvEnd; + + this._geometryType = TvGeometryType.SPIRAL; + + } + + computeVars () { } + + /** + * Gets the coordinates at the sample S offset + * @param sCheck + * @param odPosTheta + */ + getCoords ( sCheck, odPosTheta: TvPosTheta ) { + + const dist = Maths.clamp( sCheck - this.s, 0.0, this.length ); + + const curveEnd = this.attr_curvEnd; + const curveStart = this.attr_curvStart; + const curveDot = ( curveEnd - curveStart ) / this.length; + const s_o = curveStart / curveDot; + const s = s_o + dist; + + const xyt = SpiralUtils.odrSpiral( s, curveDot, 0, 0, 0 ); + + const xyt_o = SpiralUtils.odrSpiral( s_o, curveDot, 0, 0, 0 ); + + const x = xyt.x - xyt_o.x; + const y = xyt.y - xyt_o.y; + const t = xyt.t - xyt_o.t; + + const angle = this.hdg - xyt_o.t; + + // Translate the curve to the required position & rotate it + const retX = this.x + x * Math.cos( angle ) - y * Math.sin( angle ); + const retY = this.y + y * Math.cos( angle ) + x * Math.sin( angle ); + + odPosTheta.x = retX; + odPosTheta.y = retY; + odPosTheta.hdg = this.hdg + t; + + return this.geometryType; + } + + getCurve (): import( "three" ).Curve { + throw new Error( "Method not implemented." ); + } + +} + + +// old version for reference +// export class OdSpiralGeometry extends OdAbstractRoadGeometry { + +// public attr_curvStart; +// public attr_curvEnd; + +// private sqrtPiO2 = Math.sqrt( Maths.M_PI_2 ); +// private mA; +// private mCurvature; +// private mDenormalizeFactor; +// private endX; +// private endY; +// private normalDirection; + +// private differenceAngle; +// private mRotCos; +// private mRotSin; + +// constructor ( s: number, x: number, y: number, hdg: number, length: number, curvStart: number, curvEnd: number ) { + +// super( s, x, y, hdg, length ); + +// this.attr_curvStart = curvStart; +// this.attr_curvEnd = curvEnd; + +// this._geometryType = OdGeometryType.SPIRAL; + +// this.computeVars(); +// } + +// computeVars () { + +// this.mA = 0; + +// // if the curvatureEnd is the non-zero curvature, then the motion is in normal direction along the spiral +// if ( (Math.abs( this.attr_curvEnd ) > Maths.Epsilon) && (Math.abs( this.attr_curvStart ) <= Maths.Epsilon) ) { + +// this.normalDirection = true; + +// this.mCurvature = this.attr_curvEnd; + +// // Calculate the normalization term : +// // a = 1.0/sqrt(2*End_Radius*Total_Curve_Length) +// this.mA = 1.0 / Math.sqrt( 2 * (1.0 / Math.abs( this.mCurvature )) * this.length ); + +// this.mDenormalizeFactor = 1.0 / this.mA; + +// // Calculate the sine and cosine of the heading angle used to +// // rotate the spiral according to the heading +// this.mRotCos = Math.cos( this.attr_hdg ); +// this.mRotSin = Math.sin( this.attr_hdg ); + +// } else { + +// this.normalDirection = false; + +// this.mCurvature = this.attr_curvStart; + +// this.mA = 1.0 / Math.sqrt( 2 * 1.0 / Math.abs( this.mCurvature ) * this.length ); + +// // Because we move in the inverse direction, we need to rotate the curve according to the heading +// // around the last point of the normalized spiral +// // Calculate the total length, normalize it and divide by sqrtPiO2, then, calculate the position of the final point. + +// const L = (( this._s2 - this.s ) * this.mA) / this.sqrtPiO2; + +// const res = SpiralUtils.fresnel( L, this.endY, this.endX ); + +// this.endX = res.x; +// this.endY = res.y; + +// // Invert the curve if the curvature is negative +// if ( this.mCurvature < 0 ) { +// this.endY = -this.endY; +// } + +// // Denormalisation factor +// this.mDenormalizeFactor = 1.0 / this.mA; + +// // Find the x,y coords of the final point fo the curve in local curve coordinates +// this.endX *= this.mDenormalizeFactor * this.sqrtPiO2; +// this.endY *= this.mDenormalizeFactor * this.sqrtPiO2; + +// // Calculate the tangent angle +// this.differenceAngle = L * L * ( this.sqrtPiO2 * this.sqrtPiO2 ); + +// let diffAngle; + +// // Calculate the tangent and heading angle difference that will be used to rotate the spiral +// if ( this.mCurvature < 0 ) { + +// diffAngle = this.hdg - this.differenceAngle - Math.PI; + +// } else { + +// diffAngle = this.hdg + this.differenceAngle - Math.PI; + +// } + +// // Calculate the sine and cosine of the different angle +// this.mRotCos = Math.cos( diffAngle ); +// this.mRotSin = Math.sin( diffAngle ); + +// } + + +// } + +// /** +// * Gets the coordinates at the sample S offset +// * @param sCheck +// * @param odPosTheta +// */ +// getCoords ( sCheck, odPosTheta: OdPosTheta ) { + +// let l = 0.0; +// let tmpX = 0.0, tmpY = 0.0; + +// let retX, retY, retHdg; + +// // Depending on the moving direction, calculate the length of the curve from its beginning to the current point and normalize +// // it by multiplying with the "a" normalization term +// // Cephes lib for solving Fresnel Integrals, uses cos/sin (PI/2 * X^2) format in its function. +// // So, in order to use the function, transform the argument (which is just L) by dividing it +// // by the sqrt(PI/2) factor and multiply the results by it. +// if ( this.normalDirection ) { + +// l = (( sCheck - this.s ) * this.mA) / this.sqrtPiO2; + +// } else { + +// l = (( this._s2 - sCheck ) * this.mA) / this.sqrtPiO2; + +// } + +// // Solve the Fresnel Integrals +// const res = SpiralUtils.fresnel( l, tmpY, tmpX ); +// tmpY = res.y; +// tmpX = res.x; + +// // If the curvature is negative, invert the curve on the Y axis +// if ( this.mCurvature < 0 ) { +// tmpY = -tmpY; +// } + +// // Denormalize the results and multiply by the sqrt(PI/2) term +// tmpX *= this.mDenormalizeFactor * this.sqrtPiO2; +// tmpY *= this.mDenormalizeFactor * this.sqrtPiO2; + +// // Calculate the heading at the found position. +// // Kill the sqrt(PI/2) term that was added to the L +// l = ( sCheck - this.s ) * this.mA; + +// let tangentAngle = l * l; + +// if ( this.mCurvature < 0 ) { +// tangentAngle = -tangentAngle; +// } + +// retHdg = this.hdg + tangentAngle; + + +// if ( !this.normalDirection ) { + +// // If we move in the inverse direction, translate the spiral +// // in order to rotate around its final point +// tmpX -= this.endX; +// tmpY -= this.endY; + +// // also invert the spiral in the y-axis +// tmpY = -tmpY; +// } + +// // Translate the curve to the required position & rotate it +// retX = this.x + tmpX * this.mRotCos - tmpY * this.mRotSin; +// retY = this.y + tmpY * this.mRotCos + tmpX * this.mRotSin; + + +// odPosTheta.x = retX; +// odPosTheta.y = retY; +// odPosTheta.hdg = retHdg; + +// return this.geometryType; +// } + +// getCurve (): import( "three" ).Curve { +// throw new Error( "Method not implemented." ); +// } + +// } diff --git a/src/app/modules/tv-map/models/geometries/tv-spline-geometry.ts b/src/app/modules/tv-map/models/geometries/tv-spline-geometry.ts new file mode 100644 index 00000000..e7a2c679 --- /dev/null +++ b/src/app/modules/tv-map/models/geometries/tv-spline-geometry.ts @@ -0,0 +1,37 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { TvAbstractRoadGeometry } from './tv-abstract-road-geometry'; +import { TvGeometryType } from '../tv-common'; +import { TvPosTheta } from '../tv-pos-theta'; +import { Curve, Vector2 } from 'three'; + +export class TvSplineGeometry extends TvAbstractRoadGeometry { + + constructor ( s: number, x: number, y: number, hdg: number, length: number ) { + + super( s, x, y, hdg, length ); + + this._geometryType = TvGeometryType.SPLINE; + } + + getCoords ( sCheck: any, posTheta: TvPosTheta ) { + + throw new Error( 'Method not implemented.' ); + + } + + computeVars () { + + throw new Error( "Method not implemented." ); + + } + + getCurve (): Curve { + + throw new Error( "Method not implemented." ); + + } + +} diff --git a/src/app/modules/tv-map/models/lane.data.ts b/src/app/modules/tv-map/models/lane.data.ts new file mode 100644 index 00000000..fd43b668 --- /dev/null +++ b/src/app/modules/tv-map/models/lane.data.ts @@ -0,0 +1,30 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { GameObject } from '../../../core/game-object'; + +class LaneData { + + public parent: GameObject; + public cumulativeWidth: number; + + public elevation: number; + public elevationAccum: number; + + public cosAngleLeft: number; + public cosAngleRight: number; + public sinAngleLeft: number; + public sinAngleRight: number; + public laneOffset: number; + + public Clear (): void { + + this.cumulativeWidth = 0; + this.elevationAccum = 0; + this.cosAngleLeft = 0; + this.cosAngleRight = 0; + this.sinAngleLeft = 0; + this.sinAngleRight = 0; + } +} diff --git a/src/app/modules/tv-map/models/mesh-geometry.data.ts b/src/app/modules/tv-map/models/mesh-geometry.data.ts new file mode 100644 index 00000000..8c9e75bf --- /dev/null +++ b/src/app/modules/tv-map/models/mesh-geometry.data.ts @@ -0,0 +1,14 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +export class MeshGeometryData { + public vertices: any[] = []; + public triangles = []; + public currentIndex = 0; + public indices: number[] = []; + public colors: number[] = []; + public normals: number[] = []; + public texCoords: number[] = []; + public material: any; +} diff --git a/src/app/modules/tv-map/models/prop-curve.ts b/src/app/modules/tv-map/models/prop-curve.ts new file mode 100644 index 00000000..fddab151 --- /dev/null +++ b/src/app/modules/tv-map/models/prop-curve.ts @@ -0,0 +1,47 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { PropModel } from 'app/core/models/prop-model.model'; +import { AbstractSpline } from 'app/core/shapes/abstract-spline'; +import { CatmullRomSpline } from 'app/core/shapes/catmull-rom-spline'; +import { AnyControlPoint } from 'app/modules/three-js/objects/control-point'; +import { Object3D } from 'three'; + +export class PropCurve { + + public reverse: boolean = false; + + public spacing: number = 5.0; + + public rotation: number = 0.0; + + public positionVariance: number = 0.0; + + public props: Object3D[] = []; + + constructor ( public propGuid: string, public spline?: CatmullRomSpline, public headings: number[] = [] ) { + + if ( !this.spline ) { + + this.spline = new CatmullRomSpline( false, 'catmullrom', 0.001 ); + + this.spline.init(); + + } + + } + + update () { + + this.spline.update(); + + } + + addControlPoint ( cp: AnyControlPoint ) { + + ( this.spline as CatmullRomSpline ).add( cp ); + + this.update(); + } +} diff --git a/src/app/modules/tv-map/models/prop-polygons.ts b/src/app/modules/tv-map/models/prop-polygons.ts new file mode 100644 index 00000000..9e447162 --- /dev/null +++ b/src/app/modules/tv-map/models/prop-polygons.ts @@ -0,0 +1,120 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { CatmullRomSpline } from 'app/core/shapes/catmull-rom-spline'; +import { AnyControlPoint } from 'app/modules/three-js/objects/control-point'; +import { Object3D, Mesh, Shape, ShapeBufferGeometry, MeshBasicMaterial, Vector2, ShapeUtils, BufferGeometryUtils, BufferAttribute, Vector3 } from 'three'; +import { GameObject } from 'app/core/game-object'; +import * as THREE from 'three'; +import { PropService } from 'app/services/prop-service'; + +export class PropPolygon { + + public static index = 0; + + public static tag: string = 'prop-polygon'; + + public id: number; + + public props: Object3D[] = []; + + public mesh: Mesh; + + constructor ( public propGuid: string, public spline?: CatmullRomSpline, public density = 0.5 ) { + + if ( !this.spline ) { + + this.spline = new CatmullRomSpline( true, 'catmullrom', 0.001 ); + + } + + this.id = PropPolygon.index++; + + // make a blank shape to avoid any errors + this.mesh = this.makeMesh( new Shape() ); + } + + makeMesh ( shape: Shape ): Mesh { + + const geometry = new ShapeBufferGeometry( shape ); + + const groundMaterial = new MeshBasicMaterial( {} ); + + const mesh = new GameObject( PropPolygon.tag, geometry, groundMaterial ); + + mesh.position.set( 0, 0, -0.1 ); + + mesh.Tag = PropPolygon.tag; + + mesh.userData.polygon = this; + + return mesh; + } + + update () { + + this.spline.update(); + + if ( this.spline.controlPoints.length < 3 ) return; + + const points: Vector2[] = this.spline.curve.getPoints( 50 ).map( + p => new Vector2( p.x, p.y ) + ); + + const shape = new Shape(); + + const first = points.shift(); + + shape.moveTo( first.x, first.y ); + + shape.splineThru( points ); + + this.mesh.geometry.dispose(); + + const geometry = this.mesh.geometry = new ShapeBufferGeometry( shape ); + + geometry.computeBoundingBox(); + + PropService.updateCurvePolygonProps( this ); + } + + addControlPoint ( cp: AnyControlPoint ) { + + ( this.spline as CatmullRomSpline ).add( cp ); + + this.update(); + } + + delete () { + + this.hideControlPoints(); + + this.hideCurve(); + + } + + hideControlPoints () { + + this.spline.hidecontrolPoints(); + + } + + hideCurve () { + + this.spline.hide(); + + } + + showCurve () { + + this.spline.show(); + + } + + showControlPoints () { + + this.spline.showcontrolPoints(); + + } +} diff --git a/src/app/modules/tv-map/models/third-order-polynom.ts b/src/app/modules/tv-map/models/third-order-polynom.ts new file mode 100644 index 00000000..da7606d2 --- /dev/null +++ b/src/app/modules/tv-map/models/third-order-polynom.ts @@ -0,0 +1,119 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { Math } from 'three'; + +export class ThirdOrderPolynom { + + public readonly uuid: string; + + protected _s: number; + protected _a: number; + protected _b: number; + protected _c: number; + protected _d: number; + + + /** + * Constructor that initializes the polynom with base properties + * + * @param {number} s + * @param {number} a + * @param {number} b + * @param {number} c + * @param {number} d + */ + constructor ( s: number, a: number, b: number, c: number, d: number ) { + + this._s = s; + this._a = a; + this._b = b; + this._c = c; + this._d = d; + + this.uuid = Math.generateUUID(); + } + + get s (): number { + return this._s; + } + + set s ( value: number ) { + this._s = value; + } + + get d (): number { + return this._d; + } + + set d ( value: number ) { + this._d = value; + } + + get c (): number { + return this._c; + } + + set c ( value: number ) { + this._c = value; + } + + get b (): number { + return this._b; + } + + set b ( value: number ) { + this._b = value; + } + + get a (): number { + return this._a; + } + + set a ( value: number ) { + this._a = value; + } + + setValues ( s, a, b, c, d ) { + + this.s = s; + this.a = a; + this.b = b; + this.c = c; + this.d = d; + + } + + + /** + * Method to check if sample S is inside the record interval + * + * @param sOther + * @returns {boolean} + */ + checkInterval ( sOther ): boolean { + + return sOther >= this._s; + + } + + /** + * Returns the value at sample S + * + * @param sCheck + * @returns {number} + */ + getValue ( sCheck: number ): number { + + if ( isNaN( sCheck ) ) console.error( "s in not a number" ); + + const ds = sCheck - this._s; + + return ( this._a ) + + ( this._b * ds ) + + ( this._c * ds * ds ) + + ( this._d * ds * ds * ds ); + } + +} diff --git a/src/app/modules/tv-map/models/tv-common.ts b/src/app/modules/tv-map/models/tv-common.ts new file mode 100644 index 00000000..28b8e567 --- /dev/null +++ b/src/app/modules/tv-map/models/tv-common.ts @@ -0,0 +1,241 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +export class TvUserData { + constructor ( public attr_code: string, public attr_value ) { + } +} + +export enum TvElementType { + road = 'road', + junction = 'junction' +} + +export enum TvLaneType { + none = 'none', + driving = 'driving', + stop = 'stop', + shoulder = 'shoulder', + biking = 'biking', + sidewalk = 'sidewalk', + border = 'border', + restricted = 'restricted', + parking = 'parking', + bidirectional = 'bidirectional', + median = 'median', + special1 = 'special1', + special2 = 'special2', + special3 = 'special3', + roadWorks = 'roadWorks', + tram = 'tram', + rail = 'rail', + entry = 'entry', + exit = 'exit', + offRamp = 'offRamp', + onRamp = 'onRamp', +} + +export enum TvRoadMarkTypes { + NONE = 'none', + SOLID = 'solid', + BROKEN = 'broken', + SOLID_SOLID = 'solid solid', + SOLID_BROKEN = 'solid broken', + BROKEN_SOLID = 'broken solid', + BROKEN_BROKEN = 'broken broken', + BOTTS_DOTS = 'botts dots', + GRASS = 'grass', + CURB = 'curb', +} + +export enum TvRoadMarkWeights { + STANDARD = 'standard', + BOLD = 'bold' +} + +export enum TvColors { + STANDARD = 'standard', + BLUE = 'blue', + GREEN = 'green', + RED = 'red', + WHITE = 'white', + YELLOW = 'yellow', +} + +export enum TvParkingSpaceAccess { + ALL = 'all', + CAR = 'car', + WOMEN = 'women', + HANDICAPPED = 'handicapped', + BUS = 'bus', + TRUCK = 'truck', + ELECTRIC = 'electric', + RESIDENTS = 'residents' +} + +export enum TvParkingSpaceMarkingSides { + FRONT = 'front', + REAR = 'rear', + LEFT = 'left', + RIGHT = 'right', +} + +export enum TvBridgeTypes { + CONCRETE = 'concrete', + STEEL = 'steel', + BRICK = 'brick', + WOOD = 'wood', +} + +export enum TvTunnelTypes { + STANDARD = 'standard', + UNDERPASS = 'underpass' +} + +export enum TvOrientation { + PLUS = '+', + MINUS = '-', + NONE = 'none' +} + +export enum TvUnit { + METER = 'm', + KM = 'km', + FEET = 'ft', + MILE = 'mile', + METER_PER_SECOND = 'm/s', + MILES_PER_HOUR = 'mph', + KM_PER_HOUR = 'km/h', + KG = 'kg', + T = 't', + PERCENT = '%' +} + +export enum TvDirection { + SAME = 'same', + OPPOSITE = 'opposite' +} + +export enum TravelDirection { + forward = 'forward', + backward = 'backward', + bidirectional = 'bidirectional', + undirected = 'undirected' +} + + +export enum TvRoadType { + UNKNOWN = 'unknown', + RURAL = 'rural', + MOTORWAY = 'motorway', + TOWN = 'town', + LOW_SPEED = 'lowSpeed', + PEDESTRIAN = 'pedestrian', + BICYCLE = 'bicycle', +} + +export enum TvSide { + LEFT = 'left', + RIGHT = 'right' +} + +export enum TvContactPoint { + START = 'start', + END = 'end' +} + +export enum TvStationTypes { + SMALL = 'small', + MEDIUM = 'medium', + LARGE = 'large' +} + +export enum TvJunctionGroupTypes { + ROUNDABOUT = 'roundabout', + UNKNOWN = 'unknown' +} + +export enum TvDynamicTypes { + YES = 'yes', + NO = 'no' +} + +export enum TvActionMode { + SELECT, + DROP, + EDIT +} + +export enum TvEditingMode { + ANY, + ROAD, + LANE, + ROADMARK, + SIGNAL, + OBJECT, +} + +export enum TvGeometryType { + LINE = 1, + ARC = 2, + SPIRAL = 3, + POLY3 = 4, + PARAMPOLY3 = 5, + SPLINE = 6, +} + +export enum TvLaneSide { + LEFT, + CENTER, + RIGHT +} + +export enum ObjectTypes { + ROAD = 'ROAD', + LANE = 'LANE', + LANE_MARKING = 'LANE_MARKING', + VEHICLE = 'vehicle' +} + +export class EnumHelper { + + static stringToOdUnits ( value ): TvUnit { + + switch ( value ) { + + case 'm': + return TvUnit.METER; break; + + case 'km': + return TvUnit.KM; break; + + case 'ft': + return TvUnit.FEET; break; + + case 'mile': + return TvUnit.MILE; break; + + case 'm/s': + return TvUnit.METER_PER_SECOND; break; + + case 'mph': + return TvUnit.MILES_PER_HOUR; break; + + case 'km/h': + return TvUnit.KM_PER_HOUR; break; + + case 'kg': + return TvUnit.KG; break; + + case 't': + return TvUnit.T; break; + + case '%': + return TvUnit.PERCENT; break; + + default: + console.error( "unknown unit" ); break; + } + } +} \ No newline at end of file diff --git a/src/app/modules/tv-map/models/tv-controller.ts b/src/app/modules/tv-map/models/tv-controller.ts new file mode 100644 index 00000000..c93f0f30 --- /dev/null +++ b/src/app/modules/tv-map/models/tv-controller.ts @@ -0,0 +1,81 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +export class TvController { + + private _controls: TvControllerControl[] = []; + private _id: number; + private _name: string; + private _sequence: number; + + constructor ( id: number, name: string, sequence?: number ) { + this._id = id; + this._name = name; + this._sequence = sequence; + } + + get controls (): TvControllerControl[] { + return this._controls; + } + + set controls ( value: TvControllerControl[] ) { + this._controls = value; + } + + get id (): number { + return this._id; + } + + set id ( value: number ) { + this._id = value; + } + + get name (): string { + return this._name; + } + + set name ( value: string ) { + this._name = value; + } + + get sequence (): number { + return this._sequence; + } + + set sequence ( value: number ) { + this._sequence = value; + } + + addControl ( odControllerControl: TvControllerControl ) { + this.controls.push( odControllerControl ); + } +} + +export class TvControllerControl { + + private _signalId: number; + private _type: string; + + constructor ( signalId: number, type: string ) { + this._signalId = signalId; + this._type = type; + } + + get type (): string { + return this._type; + } + + set type ( value: string ) { + this._type = value; + } + + get signalId (): number { + return this._signalId; + } + + set signalId ( value: number ) { + this._signalId = value; + } + +} diff --git a/src/app/modules/tv-map/models/tv-elevation-profile.ts b/src/app/modules/tv-map/models/tv-elevation-profile.ts new file mode 100644 index 00000000..21f08ba3 --- /dev/null +++ b/src/app/modules/tv-map/models/tv-elevation-profile.ts @@ -0,0 +1,26 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { TvElevation } from './tv-elevation'; + +export class TvElevationProfile { + + public elevation: TvElevation[] = []; + + constructor () { + + } + + getElevations (): TvElevation[] { + return this.elevation; + } + + getElevation ( i: number ): any { + return this.elevation[ i ]; + } + + getElevationCount (): number { + return this.elevation.length; + } +} diff --git a/src/app/modules/tv-map/models/tv-elevation.ts b/src/app/modules/tv-map/models/tv-elevation.ts new file mode 100644 index 00000000..725428dc --- /dev/null +++ b/src/app/modules/tv-map/models/tv-elevation.ts @@ -0,0 +1,8 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { ThirdOrderPolynom } from './third-order-polynom'; + +export class TvElevation extends ThirdOrderPolynom { +} \ No newline at end of file diff --git a/src/app/modules/tv-map/models/tv-junction-connection.ts b/src/app/modules/tv-map/models/tv-junction-connection.ts new file mode 100644 index 00000000..7d955e6f --- /dev/null +++ b/src/app/modules/tv-map/models/tv-junction-connection.ts @@ -0,0 +1,131 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { TvContactPoint } from './tv-common'; +import { TvJunctionLaneLink } from './tv-junction-lane-link'; +import { TvRoad } from './tv-road.model'; +import { Math } from 'three'; + +export class TvJunctionConnection { + + public readonly uuid: string; + + public laneLink: TvJunctionLaneLink[] = []; + + private lastAddedJunctionLaneLinkIndex: number; + + constructor ( + public id: number, + public incomingRoad: number, + public connectingRoad: number, + public contactPoint: TvContactPoint, + public outgoingRoad?: number + ) { + this.uuid = Math.generateUUID(); + } + + /** + * Add a lane link record + * + * @param {number} from + * @param {number} to + * @returns {number} + */ + public addJunctionLaneLink ( from: number, to: number ) { + + const instance = new TvJunctionLaneLink( from, to ); + + this.addLaneLink( instance ); + + this.lastAddedJunctionLaneLinkIndex = this.laneLink.length - 1; + + return this.lastAddedJunctionLaneLinkIndex; + + } + + addNewLink ( from: number, to: number ) { + + const link = new TvJunctionLaneLink( from, to ); + + this.addLaneLink( link ); + + return link; + } + + getJunctionLaneLinkCount (): number { + + return this.laneLink.length; + + } + + getJunctionLaneLink ( index: number ): TvJunctionLaneLink { + + return this.laneLink[ index ]; + + } + + public cloneJunctionLaneLink ( index ) { + + // TODO + + } + + public deleteJunctionLaneLink ( index ) { + + this.laneLink.splice( index, 1 ); + + } + + public getLastAddedJunctionLaneLink (): TvJunctionLaneLink { + + return this.laneLink[ this.lastAddedJunctionLaneLinkIndex ]; + + } + + public addLaneLink ( laneLink: TvJunctionLaneLink ) { + + this.laneLink.push( laneLink ); + + } + + getConnectingRoad (): TvRoad { + return undefined; + } + + getToLaneId ( laneId: number ): number { + + for ( const link of this.laneLink ) { + + if ( link.from == laneId ) { + + return link.to; + + } + + } + + return null; + } + + getFromLaneId ( laneId: number ): number { + + for ( const link of this.laneLink ) { + + if ( link.to == laneId ) { + + return link.from; + + } + + } + + return null; + } + + removeLinkAtIndex ( index: number ) { + + this.laneLink.splice( index, 1 ); + + } +} diff --git a/src/app/modules/tv-map/models/tv-junction-controller.ts b/src/app/modules/tv-map/models/tv-junction-controller.ts new file mode 100644 index 00000000..b944bcd2 --- /dev/null +++ b/src/app/modules/tv-map/models/tv-junction-controller.ts @@ -0,0 +1,41 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +export class TvJunctionController { + + public attr_id: number; + public attr_type: string; + public attr_sequence: number; + + constructor ( id: number, type: string, sequence?: number ) { + this.attr_id = id; + this.attr_type = type; + this.attr_sequence = sequence; + } + + get id (): number { + return this.attr_id; + } + + set id ( value ) { + this.attr_id = value; + } + + get type (): string { + return this.attr_type; + } + + set type ( value ) { + this.attr_type = value; + } + + get sequence (): number { + return this.attr_sequence; + } + + set sequence ( value ) { + this.attr_sequence = value; + } + +} diff --git a/src/app/modules/tv-map/models/tv-junction-lane-link.ts b/src/app/modules/tv-map/models/tv-junction-lane-link.ts new file mode 100644 index 00000000..aea7079c --- /dev/null +++ b/src/app/modules/tv-map/models/tv-junction-lane-link.ts @@ -0,0 +1,87 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { Color, Line, LineBasicMaterial, Mesh, Object3D } from 'three'; +import { TvRoad } from './tv-road.model'; +import { TvJunctionConnection } from './tv-junction-connection'; + +export class TvJunctionLaneLink { + + public lanePath: LanePathObject; + + /** + * + * @param from ID of the incoming lane + * @param to ID of the connecting lane + */ + constructor ( public from: number, public to: number ) { + + } + + show () { + + if ( this.lanePath ) this.lanePath.hide(); + + } + + hide () { + + if ( this.lanePath ) this.lanePath.show(); + + } +} + +export class LanePathObject extends Object3D { + + public static tag = 'lane-path'; + + public mesh: Mesh | Line; + + public isSelected: boolean; + + constructor ( + public incomingRoad: TvRoad, + public connectingRoad: TvRoad, + public connection: TvJunctionConnection, + public link: TvJunctionLaneLink + ) { + super(); + } + + get material () { return this.mesh.material as LineBasicMaterial } + + select () { + + this.isSelected = true; + + // red + this.material.color = new Color( 0xff0000 ); + this.material.needsUpdate = true; + + } + + unselect () { + + this.isSelected = false; + + // green + this.material.color = new Color( 0x00ffff ); + this.material.needsUpdate = true; + + } + + hide () { + + this.visible = false; + this.mesh.visible = false; + + } + + show () { + + this.visible = true; + this.mesh.visible = true; + + } +} diff --git a/src/app/modules/tv-map/models/tv-junction-priority.ts b/src/app/modules/tv-map/models/tv-junction-priority.ts new file mode 100644 index 00000000..52723f50 --- /dev/null +++ b/src/app/modules/tv-map/models/tv-junction-priority.ts @@ -0,0 +1,30 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +export class TvJunctionPriority { + + public attr_high: number; + public attr_low: number; + + constructor ( high: number, low: number ) { + this.attr_high = high; + this.attr_low = low; + } + + get high () { + return this.attr_high; + } + + set high ( value: number ) { + this.attr_high = value; + } + + get low () { + return this.attr_low; + } + + set low ( value: number ) { + this.attr_low = value; + } +} diff --git a/src/app/modules/tv-map/models/tv-junction.ts b/src/app/modules/tv-map/models/tv-junction.ts new file mode 100644 index 00000000..112847f1 --- /dev/null +++ b/src/app/modules/tv-map/models/tv-junction.ts @@ -0,0 +1,396 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { Maths } from '../../../utils/maths'; +import { TvJunctionConnection } from './tv-junction-connection'; +import { TvJunctionPriority } from './tv-junction-priority'; +import { TvJunctionController } from './tv-junction-controller'; +import { Vector3 } from 'three'; +import { TvContactPoint } from './tv-common'; +import { TvRoad } from './tv-road.model'; + +export class TvJunction { + + public position?: Vector3; + + private _priorities: TvJunctionPriority[] = []; + private _controllers: TvJunctionController[] = []; + private _connections: Map = new Map(); + private _name: string; + private _id: number; + private lastAddedJunctionConnectionIndex: number; + private lastAddedJunctionPriorityIndex: number; + private lastAddedJunctionControllerIndex: number; + + constructor ( name: string, id: number ) { + this._name = name; + this._id = id; + } + + get controllers (): TvJunctionController[] { + return this._controllers; + } + + set controllers ( value: TvJunctionController[] ) { + this._controllers = value; + } + + get priorities (): TvJunctionPriority[] { + return this._priorities; + } + + set priorities ( value: TvJunctionPriority[] ) { + this._priorities = value; + } + + get connections (): Map { + return this._connections; + } + + set connections ( value: Map ) { + this._connections = value; + } + + get id (): number { + return this._id; + } + + set id ( value: number ) { + this._id = value; + } + + get name (): string { + return this._name; + } + + set name ( value: string ) { + this._name = value; + } + + /** + * Adds a junction connection to the junction + * + * @param id ID within the junction + * @param incomingRoad ID of the incoming road + * @param connectingRoad ID of the connecting path + * @param contactPoint Contact point on the connecting road (start or end) + */ + public addJunctionConnection ( id, incomingRoad, connectingRoad, contactPoint, outgoingRoad? ): TvJunctionConnection { + + const connection = new TvJunctionConnection( id, incomingRoad, connectingRoad, contactPoint, outgoingRoad ); + + this._connections.set( id, connection ); + + return connection; + } + + /** + * Adds a junction connection to the junction + * + * @param incomingRoad ID of the incoming road + * @param connectingRoad ID of the connecting path + * @param contactPoint Contact point on the connecting road (start or end) + */ + public addNewConnection ( + incomingRoad: number, + connectingRoad: number, + contactPoint: TvContactPoint, + outgoingRoad?: number + ): TvJunctionConnection { + + const id = this.connections.size + 1; + + const connection = this.addJunctionConnection( id, incomingRoad, connectingRoad, contactPoint, outgoingRoad ); + + return connection; + } + + removeConnectionByUuid ( uuid: string ) { + + throw new Error( "method not implemented" ); + + } + + removeConnectionById ( id: number ): boolean { + + return this.connections.delete( id ); + + } + + removeConnection ( connection: TvJunctionConnection, incomingRoad: TvRoad, outgoingRoad: TvRoad ) { + + if ( this.removeConnectionById( connection.id ) ) { + + this.removeJunctionRelation( incomingRoad ); + + this.removeJunctionRelation( outgoingRoad ); + + } + + } + + /** + * Adds a priority parameter to the junction + * + * @param {number} high ID of the connecting road with higher priority + * @param {number} low ID of the connecting road with lower priority + * @returns {number} + */ + public addJunctionPriority ( high: number, low: number ): TvJunctionPriority { + + const priority = new TvJunctionPriority( high, low ); + + this._priorities.push( priority ); + + return priority; + } + + /** + * Adds a controller to the junction + * + * @param {number} id ID of the controller to add + * @param {string} type Type of control + * @returns {number} + */ + public addJunctionController ( id: number, type: string ): TvJunctionController { + + const controller = new TvJunctionController( id, type ); + + this._controllers.push( controller ); + + return controller; + } + + public closeJunctionConnection ( index ) { + + // TODO: + + } + + public closeJunctionPriority ( index ) { + + // TODO: + + } + + public closeJunctionController ( index ) { + + // TODO: + + } + + public deleteJunctionConnection ( id: number ): void { + + this._connections.delete( id ); + + } + + public deleteJunctionPriority ( index: number ): void { + + this._priorities.splice( index, 1 ); + + } + + public deleteJunctionController ( index: number ): void { + + this._controllers.splice( index, 1 ); + + } + + public getJunctionPriorities (): TvJunctionPriority[] { + + return this._priorities; + + } + + public getJunctionControllers (): TvJunctionController[] { + + return this._controllers; + + } + + public getJunctionConnectionCount (): number { + + return this._connections.size; + + } + + public getJunctionPriorityCount (): number { + + return this._priorities.length; + + } + + public getJunctionControllerCount (): number { + + return this._controllers.length; + + } + + + public getJunctionConnection ( id: number ) { + + return this.connections.get( id ); + + } + + public getJunctionPriority ( index: number ) { + + if ( index < this._priorities.length && this._priorities.length > 0 ) { + + return this._priorities[ index ]; + + } + + return null; + } + + public getJunctionController ( index: number ) { + + if ( index < this._controllers.length && this._controllers.length > 0 ) { + + return this._controllers[ index ]; + + } + + return null; + } + + public getLastAddedJunctionConnection () { + + if ( this.lastAddedJunctionConnectionIndex < this._connections.size ) { + + return this._connections[ this.lastAddedJunctionConnectionIndex ]; + + } + + return null; + } + + public getLastAddedJunctionPriority () { + + if ( this.lastAddedJunctionPriorityIndex < this._priorities.length ) { + + return this._priorities[ this.lastAddedJunctionPriorityIndex ]; + + } + + return null; + } + + public getLastAddedJunctionController () { + + if ( this.lastAddedJunctionConnectionIndex < this._controllers.length ) { + + return this._controllers[ this.lastAddedJunctionConnectionIndex ]; + + } + + return null; + } + + addConnection ( connection: TvJunctionConnection ) { + + this._connections.set( connection.id, connection ); + + } + + addPriority ( priority: TvJunctionPriority ) { + + this._priorities.push( priority ); + + } + + addController ( controller: TvJunctionController ) { + + this._controllers.push( controller ); + + } + + getRandomConnectionFor ( incomingRoadId: number, laneId?: number ): TvJunctionConnection { + + const connections = [ ...this.connections.values() ].filter( connection => { + + if ( laneId ) { + + return ( connection.incomingRoad === incomingRoadId && connection.getToLaneId( laneId ) ); + + } else { + + return ( connection.incomingRoad === incomingRoadId ); + + } + + } ); + + const randomIndex = Maths.randomNumberBetween( 0, connections.length - 1 ); + + return connections[ randomIndex ]; + } + + private removeJunctionRelation ( road: TvRoad ): void { + + let hasConnections = false; + + for ( const connection of this.connections ) { + + if ( connection[ 1 ].incomingRoad === road.id ) { + + hasConnections = true; + + } else if ( connection[ 1 ].outgoingRoad === road.id ) { + + hasConnections = true; + + } + } + + if ( !hasConnections ) { + + if ( road.successor && road.successor.elementType === "junction" && road.successor.elementId === this.id ) { + + road.successor = null; + + } else if ( road.predecessor && road.predecessor.elementType === "junction" && road.predecessor.elementId === this.id ) { + + road.predecessor = null; + + } + + } + } + + private addJunctionRelation ( road: TvRoad ): void { + + // let hasConnections = false; + + // for ( const connection of this.connections ) { + + // if ( connection[ 1 ].incomingRoad === road.id ) { + + // hasConnections = true; + + // } else if ( connection[ 1 ].outgoingRoad === road.id ) { + + // hasConnections = true; + + // } + // } + + // if ( !hasConnections ) { + + // if ( road.successor && road.successor.elementType === "junction" && road.successor.elementId === this.id ) { + + // road.setSuccessor(); + + // } else if ( road.predecessor && road.predecessor.elementType === "junction" && road.predecessor.elementId === this.id ) { + + // road.predecessor = null; + + // } + + // } + } +} + diff --git a/src/app/modules/tv-map/models/tv-lane-access.ts b/src/app/modules/tv-map/models/tv-lane-access.ts new file mode 100644 index 00000000..c63d9661 --- /dev/null +++ b/src/app/modules/tv-map/models/tv-lane-access.ts @@ -0,0 +1,30 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +export class TvLaneAccess { + + public attr_sOffset; + public attr_restriction; + + constructor ( sOffset: number, restriction: string ) { + this.attr_sOffset = sOffset; + this.attr_restriction = restriction; + } + + get sOffset () { + return this.attr_sOffset; + } + + set sOffset ( value ) { + this.attr_sOffset = value; + } + + get restriction () { + return this.attr_restriction; + } + + set restriction ( value ) { + this.attr_restriction = value; + } +} \ No newline at end of file diff --git a/src/app/modules/tv-map/models/tv-lane-border.ts b/src/app/modules/tv-map/models/tv-lane-border.ts new file mode 100644 index 00000000..25c59fe0 --- /dev/null +++ b/src/app/modules/tv-map/models/tv-lane-border.ts @@ -0,0 +1,8 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { ThirdOrderPolynom } from './third-order-polynom'; + +export class TvLaneBorder extends ThirdOrderPolynom { +} \ No newline at end of file diff --git a/src/app/modules/tv-map/models/tv-lane-coord.ts b/src/app/modules/tv-map/models/tv-lane-coord.ts new file mode 100644 index 00000000..04b4b986 --- /dev/null +++ b/src/app/modules/tv-map/models/tv-lane-coord.ts @@ -0,0 +1,60 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +export class TvCoord { + + constructor ( x, y, z, h, p, r ) { + } + + static getDist2d ( a: TvCoord, b: TvCoord ) { + + } + + static getDist3d ( a: TvCoord, b: TvCoord ) { + + } +} + +export class TvLaneCoord { + + // total 4 properties + // road-id + // lane-section-id + // s + // lane-Id + // lane-offset + + constructor ( public roadId: number, public sectionId: number, public laneId: number, public s: number, public offset: number ) { + + } + + init () { + + } + + addTrackCoord ( value: TvRoadCoord ) { + + } + +} + +export class TvRoadCoord { + + constructor ( public roadId, public s: number, public t: number = 0, public z: number = 0, public h?, public p?, public r? ) { + + } + + init () { + + } + + add ( value: TvRoadCoord ) { + } +} + +export class TvGeoCoord { + + constructor ( lat, long, z, h, p, r ) { + } +} diff --git a/src/app/modules/tv-map/models/tv-lane-height.ts b/src/app/modules/tv-map/models/tv-lane-height.ts new file mode 100644 index 00000000..71a4cfb0 --- /dev/null +++ b/src/app/modules/tv-map/models/tv-lane-height.ts @@ -0,0 +1,48 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +export class TvLaneHeight { + public attr_sOffset; + public attr_inner = 0; + public attr_outer = 0; + + constructor ( sOffset: number, inner: number, outer: number ) { + this.attr_sOffset = sOffset; + this.attr_inner = inner; + this.attr_outer = outer; + } + + get sOffset () { + return this.attr_sOffset; + } + + set sOffset ( value ) { + this.attr_sOffset = value; + } + + getOuter () { + return this.attr_outer; + } + + get outer () { + return this.attr_outer; + } + + setOuter ( value ) { + this.attr_outer = value; + } + + getInner () { + return this.attr_inner; + } + + get inner () { + return this.attr_inner; + } + + setInner ( value ) { + this.attr_inner = value; + } + +} \ No newline at end of file diff --git a/src/app/modules/tv-map/models/tv-lane-material.ts b/src/app/modules/tv-map/models/tv-lane-material.ts new file mode 100644 index 00000000..8a3a0d78 --- /dev/null +++ b/src/app/modules/tv-map/models/tv-lane-material.ts @@ -0,0 +1,55 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +export class TvLaneMaterial { + + private _sOffset: number; + private _surface: string; + private _friction: number; + private _roughness: number; + + constructor ( sOffset: number, surface: string, friction: number, roughness: number ) { + this._sOffset = sOffset; + this._surface = surface; + this._friction = friction; + this._roughness = roughness; + } + + get s () { + return this.sOffset; + } + + get sOffset () { + return this._sOffset; + } + + set sOffset ( value ) { + this._sOffset = value; + } + + get surface () { + return this._surface; + } + + set surface ( value ) { + this._surface = value; + } + + get friction () { + return this._friction; + } + + set friction ( value ) { + this._friction = value; + } + + get roughness () { + return this._roughness; + } + + set roughness ( value ) { + this._roughness = value; + } + +} diff --git a/src/app/modules/tv-map/models/tv-lane-road-mark.ts b/src/app/modules/tv-map/models/tv-lane-road-mark.ts new file mode 100644 index 00000000..fd304c90 --- /dev/null +++ b/src/app/modules/tv-map/models/tv-lane-road-mark.ts @@ -0,0 +1,188 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { TvColors, TvRoadMarkTypes, TvRoadMarkWeights } from './tv-common'; +import { GameObject } from '../../../core/game-object'; +import { TvLane } from './tv-lane'; +import { LaneRoadMarkNode } from '../../three-js/objects/control-point'; +import { Math } from 'three'; + +export class TvLaneRoadMark { + + public readonly uuid: string; + + // widths for the two types of weight values + public static STD_ROADMARK_WIDTH = 0.15; + public static BOLD_ROADMARK_WIDTH = 0.3; + // elevation shift, so that the road mark is drawn above the road + public static ROADMARK_ELEVATION_SHIFT = 0.01; + // broken mark tiling + public static ROADMARK_BROKEN_TILING = 3.0; + public gameObject: GameObject; + public attr_sOffset: number; + public attr_type: TvRoadMarkTypes; + public attr_weight: TvRoadMarkWeights; + public attr_color: TvColors; + public attr_material: string; + public attr_width: number; + public attr_laneChange: string; + public attr_height: number = 0; + public lastSCoordinate: number; + public readonly lane: TvLane; + public node: LaneRoadMarkNode; + + constructor ( + sOffset: number, + type: TvRoadMarkTypes, + weight: TvRoadMarkWeights, + color: TvColors, + width: number, + laneChange: string, + height: number, + lane: TvLane + ) { + + this.uuid = Math.generateUUID(); + + this.attr_sOffset = sOffset; + this.attr_type = type; + this.attr_weight = weight; + this.attr_color = color; + this.attr_width = width; + this.attr_laneChange = laneChange; + this.attr_height = height; + + this.lane = lane; + } + + get length () { + return this.lastSCoordinate - this.sOffset; + } + + get s () { + return this.sOffset; + } + + get sOffset () { + return this.attr_sOffset; + } + + set sOffset ( value ) { + this.attr_sOffset = value; + } + + get type () { + return this.attr_type; + } + + set type ( value: TvRoadMarkTypes ) { + this.attr_type = value; + } + + get weight (): TvRoadMarkWeights { + return this.attr_weight; + } + + set weight ( value ) { + this.attr_weight = value; + } + + get color () { + return this.attr_color; + } + + set color ( value: TvColors ) { + this.attr_color = value; + } + + get material () { + return this.attr_material; + } + + get width () { + return this.attr_width; + } + + set width ( value: number ) { + this.attr_width = value; + } + + get laneChange () { + return this.attr_laneChange; + } + + get height () { + return this.attr_height; + } + + getType () { + return this.attr_type; + } + + setType ( value ) { + this.attr_type = value; + } + + getWeight () { + return this.attr_weight; + } + + setWeight ( value ) { + this.attr_weight = value; + } + + getColor () { + return this.attr_color; + } + + setColor ( value ) { + this.attr_color = value; + } + + getMaterial () { + return this.attr_material; + } + + setMaterial ( value ) { + this.attr_material = value; + } + + getWidth () { + return this.attr_width; + } + + setWidth ( value ) { + this.attr_width = value; + } + + getLaneChange () { + return this.attr_laneChange; + } + + setLaneChange ( value ) { + this.attr_laneChange = value; + } + + getHeight () { + return this.attr_height; + } + + setHeight ( value ) { + this.attr_height = value; + } + + clone ( s?: number ) { + + return new TvLaneRoadMark( + s || this.sOffset, + this.type, + this.weight, + this.color, + this.width, + this.laneChange, + this.height, + this.lane + ); + } +} diff --git a/src/app/modules/tv-map/models/tv-lane-section-sample.ts b/src/app/modules/tv-map/models/tv-lane-section-sample.ts new file mode 100644 index 00000000..265dc1e0 --- /dev/null +++ b/src/app/modules/tv-map/models/tv-lane-section-sample.ts @@ -0,0 +1,202 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { TvLaneHeight } from './tv-lane-height'; +import { TvLaneRoadMark } from './tv-lane-road-mark'; + + +export class TvLaneSectionSample { + + private mLeftTypeVector: string[] = []; + private mLeftWidthVector: number[] = []; + private mLeftHeightVector: TvLaneHeight[] = []; + private mLeftRoadMarkVector: TvLaneRoadMark[] = []; + private mLeftLevelVector: boolean[] = []; + + private mRightTypeVector: string[] = []; + private mRightWidthVector: number[] = []; + private mRightHeightVector: TvLaneHeight[] = []; + private mRightRoadMarkVector: TvLaneRoadMark[] = []; + private mRightLevelVector: boolean[] = []; + + + /** + * Lane Section Sample. Holds all the lane information at a certain S value including lane widths, levels, + * heights, etc + * + * + * + * + */ + constructor () { + + } + + + addLeftType ( type: string ): void { + this.mLeftTypeVector.push( type ); + } + + addLeftWidth ( width: number ): void { + this.mLeftWidthVector.push( width ); + } + + addLeftHeight ( height: TvLaneHeight ): void { + this.mLeftHeightVector.push( height ); + } + + addLeftRoadMark ( roadMark: TvLaneRoadMark ): void { + this.mLeftRoadMarkVector.push( roadMark ); + } + + addLeftLevel ( level: boolean ): void { + this.mLeftLevelVector.push( level ); + } + + + addRightType ( type: string ): void { + this.mRightTypeVector.push( type ); + } + + addRightWidth ( width: number ): void { + this.mRightWidthVector.push( width ); + } + + addRightHeight ( height: TvLaneHeight ): void { + this.mRightHeightVector.push( height ); + } + + addRightRoadMark ( roadMark: TvLaneRoadMark ): void { + this.mRightRoadMarkVector.push( roadMark ); + } + + addRightLevel ( level: boolean ): void { + this.mRightLevelVector.push( level ); + } + + addLeftRecord ( type: string, width: number, height: TvLaneHeight, roadMark: TvLaneRoadMark, level: boolean ): void { + this.addLeftType( type ); + this.addLeftWidth( width ); + this.addLeftHeight( height ); + this.addLeftRoadMark( roadMark ); + this.addLeftLevel( level ); + } + + addRightRecord ( type: string, width: number, height: TvLaneHeight, roadMark: TvLaneRoadMark, level: boolean ): void { + this.addRightType( type ); + this.addRightWidth( width ); + this.addRightHeight( height ); + this.addRightRoadMark( roadMark ); + this.addRightLevel( level ); + } + + // LEFT + + getLeftType ( i: number ): string { + return this.mLeftTypeVector[ i ]; + } + + getLeftWidth ( i: number ): number { + return this.mLeftWidthVector[ i ]; + } + + getLeftHeight ( i: number ): TvLaneHeight { + return this.mLeftHeightVector[ i ]; + } + + getLeftRoadMark ( i: number ): TvLaneRoadMark { + return this.mLeftRoadMarkVector[ i ]; + } + + getLeftLevel ( i: number ): boolean { + return this.mLeftLevelVector[ i ]; + } + + // RIGHT + + getRightType ( i: number ): string { + return this.mRightTypeVector[ i ]; + } + + getRightWidth ( i: number ): number { + return this.mRightWidthVector[ i ]; + } + + getRightHeight ( i: number ): TvLaneHeight { + return this.mRightHeightVector[ i ]; + } + + getRightRoadMark ( i: number ): TvLaneRoadMark { + return this.mRightRoadMarkVector[ i ]; + } + + getRightLevel ( i: number ): boolean { + return this.mRightLevelVector[ i ]; + } + + getLeftVectorsSize (): number { + return this.mLeftWidthVector.length; + } + + getRightVectorsSize (): number { + return this.mRightWidthVector.length; + } + + getLeftTypeVector (): string[] { + return this.mLeftTypeVector; + } + + getLeftWidthVector (): number[] { + return this.mLeftWidthVector; + } + + getLeftHeigthVector (): TvLaneHeight[] { + return this.mLeftHeightVector; + } + + getLeftRoadMarkVector (): TvLaneRoadMark[] { + return this.mLeftRoadMarkVector; + } + + getLeftLevelVector (): boolean[] { + return this.mLeftLevelVector; + } + + getRightTypeVector (): string[] { + return this.mRightTypeVector; + } + + getRightWidthVector (): number[] { + return this.mRightWidthVector; + } + + getRightHeigthVector (): TvLaneHeight[] { + return this.mRightHeightVector; + } + + getRightRoadMarkVector (): TvLaneRoadMark[] { + return this.mRightRoadMarkVector; + } + + getRightLevelVector (): boolean[] { + return this.mRightLevelVector; + } + + clearVectors (): void { + + this.mLeftTypeVector.splice( 0, this.mLeftTypeVector.length ); + this.mLeftWidthVector.splice( 0, this.mLeftWidthVector.length ); + this.mLeftHeightVector.splice( 0, this.mLeftHeightVector.length ); + this.mLeftRoadMarkVector.splice( 0, this.mLeftRoadMarkVector.length ); + this.mLeftLevelVector.splice( 0, this.mLeftLevelVector.length ); + + this.mRightTypeVector.splice( 0, this.mRightTypeVector.length ); + this.mRightWidthVector.splice( 0, this.mRightWidthVector.length ); + this.mRightHeightVector.splice( 0, this.mRightHeightVector.length ); + this.mRightRoadMarkVector.splice( 0, this.mRightRoadMarkVector.length ); + this.mRightLevelVector.splice( 0, this.mRightLevelVector.length ); + + } + +} diff --git a/src/app/modules/tv-map/models/tv-lane-section.spec.ts b/src/app/modules/tv-map/models/tv-lane-section.spec.ts new file mode 100644 index 00000000..d6c1f97c --- /dev/null +++ b/src/app/modules/tv-map/models/tv-lane-section.spec.ts @@ -0,0 +1,162 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { TvLaneSide, TvLaneType } from './tv-common'; +import { TvLaneSection } from './tv-lane-section'; +import { TvLane } from './tv-lane'; +import { TvMapSourceFile } from '../services/tv-map-source-file'; +import { TvMap } from './tv-map.model'; + +describe( 'OpenDrive LaneSection', () => { + + let laneSection: TvLaneSection; + + let leftOne: TvLane; + let leftTwo: TvLane; + let leftThree: TvLane; + let rightOne: TvLane; + let rightTwo: TvLane; + let rightThree: TvLane; + let rightFour: TvLane; + + beforeEach( () => { + + laneSection = new TvLaneSection( 1, 0, true, 1 ); + + laneSection.addLane( TvLaneSide.LEFT, 2, TvLaneType.driving, true, true ); + leftTwo = laneSection.getLastAddedLane(); + + laneSection.addLane( TvLaneSide.LEFT, 3, TvLaneType.driving, true, true ); + leftThree = laneSection.getLastAddedLane(); + + laneSection.addLane( TvLaneSide.LEFT, 1, TvLaneType.driving, true, true ); + leftOne = laneSection.getLastAddedLane(); + + laneSection.addLane( TvLaneSide.CENTER, 0, TvLaneType.driving, true, true ); + + laneSection.addLane( TvLaneSide.RIGHT, -1, TvLaneType.driving, true, true ); + rightOne = laneSection.getLastAddedLane(); + + laneSection.addLane( TvLaneSide.RIGHT, -3, TvLaneType.driving, true, true ); + rightThree = laneSection.getLastAddedLane(); + + laneSection.addLane( TvLaneSide.RIGHT, -2, TvLaneType.driving, true, true ); + rightTwo = laneSection.getLastAddedLane(); + + laneSection.addLane( TvLaneSide.RIGHT, -4, TvLaneType.driving, true, true ); + rightFour = laneSection.getLastAddedLane(); + + laneSection.getLaneVector().forEach( lane => { + + if ( lane.side !== TvLaneSide.CENTER ) { + + lane.addWidthRecord( 0, 2, 0, 0, 0 ); + + } + + } ); + + } ); + + it( 'should give correct width for first left lanes', () => { + + const start = laneSection.getWidthUptoStart( leftOne, 0 ); + const center = laneSection.getWidthUptoCenter( leftOne, 0 ); + const end = laneSection.getWidthUptoEnd( leftOne, 0 ); + + expect( start ).toBe( 0 ); + expect( center ).toBe( 1 ); + expect( end ).toBe( 2 ); + + } ); + + it( 'should give correct width for second left lanes', () => { + + const start = laneSection.getWidthUptoStart( leftTwo, 0 ); + const center = laneSection.getWidthUptoCenter( leftTwo, 0 ); + const end = laneSection.getWidthUptoEnd( leftTwo, 0 ); + + expect( start ).toBe( 2 ); + expect( center ).toBe( 3 ); + expect( end ).toBe( 4 ); + + } ); + + it( 'should give correct width for first right lanes', () => { + + const start = laneSection.getWidthUptoStart( rightOne, 0 ); + const center = laneSection.getWidthUptoCenter( rightOne, 0 ); + const end = laneSection.getWidthUptoEnd( rightOne, 0 ); + + expect( start ).toBe( 0 ); + expect( center ).toBe( 1 ); + expect( end ).toBe( 2 ); + + } ); + + it( 'should give correct width for second right lanes', () => { + + const start = laneSection.getWidthUptoStart( rightTwo, 0 ); + const center = laneSection.getWidthUptoCenter( rightTwo, 0 ); + const end = laneSection.getWidthUptoEnd( rightTwo, 0 ); + + expect( start ).toBe( 2 ); + expect( center ).toBe( 3 ); + expect( end ).toBe( 4 ); + + } ); + + it( 'should give correct count for total lanes', () => { + expect( laneSection.getLaneCount() ).toBe( 8 ); + } ); + + it( 'should give correct count for left lanes', () => { + expect( laneSection.getLeftLaneCount() ).toBe( 3 ); + } ); + + it( 'should give correct count for right lanes', () => { + expect( laneSection.getRightLaneCount() ).toBe( 4 ); + } ); + + it( 'should give correct length for laneSection', () => { + + TvMapSourceFile.openDrive = new TvMap(); + + const road = TvMapSourceFile.openDrive.addRoad( "", 100, 1, -1 ); + + const section1 = road.addGetLaneSection( 0, false ); + const section2 = road.addGetLaneSection( 40, false ); + const section3 = road.addGetLaneSection( 50, false ); + + expect( section1.roadId ).toBe( 1 ); + expect( section2.roadId ).toBe( 1 ); + expect( section3.roadId ).toBe( 1 ); + + expect( road.getLaneSectionLength( section1 ) ).toBe( 40 ); + expect( road.getLaneSectionLength( section2 ) ).toBe( 10 ); + expect( road.getLaneSectionLength( section3 ) ).toBe( 50 ); + + expect( section1.length ).toBe( 40 ); + expect( section2.length ).toBe( 10 ); + expect( section3.length ).toBe( 50 ); + + } ); + + it( 'should copy lane correctly', () => { + + // 3 left 1 center 4 right + expect( laneSection.lanes.size ).toBe( 8 ); + + const newLane = leftTwo.clone( 1 ); + + laneSection.addLaneInstance( newLane, true ); + + expect( laneSection.lanes.size ).toBe( 9 ); + + expect( laneSection.lanes.get( 3 ).id ).toBe( 3 ); + expect( laneSection.lanes.get( 4 ).id ).toBe( 4 ); + + } ); + +} ); diff --git a/src/app/modules/tv-map/models/tv-lane-section.ts b/src/app/modules/tv-map/models/tv-lane-section.ts new file mode 100644 index 00000000..4a67b05d --- /dev/null +++ b/src/app/modules/tv-map/models/tv-lane-section.ts @@ -0,0 +1,823 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { TvLaneSide, TvLaneType } from './tv-common'; +import { TvLane } from './tv-lane'; +import { TvLaneSectionSample } from './tv-lane-section-sample'; +import { GameObject } from 'app/core/game-object'; +import { TvLaneRoadMark } from './tv-lane-road-mark'; +import { TvLaneHeight } from './tv-lane-height'; +import { Debug } from 'app/core/utils/debug'; +import { Math } from 'three'; + +export class TvLaneSection { + + public readonly id: number; + public readonly uuid: string; + + public gameObject: GameObject; + public roadId: number; + public attr_s: number; + public attr_singleSide: boolean; + // old property + public lastSCoordinate: number; + + // public left: OdRoadLaneSectionContainer; + // public center: OdRoadLaneSectionContainer; + // public right: OdRoadLaneSectionContainer; + private lastAddedLaneIndex: number; + private laneMap: Map = new Map(); + + private _length: number; + + public get lanes () { + return this.laneMap; + } + + constructor ( id: number, s: number, singleSide: boolean, roadId: number ) { + this.uuid = Math.generateUUID(); + this.id = id; + this.attr_s = s; + this.attr_singleSide = singleSide; + this.roadId = roadId; + } + + get s () { + return this.attr_s; + } + + get length () { + return this._length; + // return this.lastSCoordinate - this.s; + } + + set length ( value: number ) { + this._length = value; + } + + // private laneVector: OdLane[] = []; + private get laneVector (): TvLane[] { + return [ ...this.laneMap.values() ]; + } + + updateMeshGeometry ( offset: number ): any { + + this.getLeftLanes().reverse().forEach( ( lane, i ) => { + + Debug.log( i, lane ); + + } ); + + + } + + getWidthUptoStart ( lane: TvLane, sCoordinate: number ): number { + + let width = 0; + let lanes: TvLane[] = []; + + if ( lane.side == TvLaneSide.RIGHT ) { + + lanes = this.getRightLanes(); + + } else if ( lane.side == TvLaneSide.LEFT ) { + + lanes = this.getLeftLanes().reverse(); + + } else { + + width = 0; + + return width; + + } + + for ( let i = 0; i < lanes.length; i++ ) { + + // TODO: Check if this correct + + var element = lanes[ i ]; + + if ( element.id == lane.id ) break; + + width += element.getWidthValue( sCoordinate ); + } + + // console.log(`upto-start lane-id: ${lane.id} s: ${sCoordinate} width: ${width}`); + + return width; + + } + + getWidthUptoEnd ( lane: TvLane, sCoordinate: number ): number { + + let width = 0; + let lanes: TvLane[] = []; + + if ( lane.side == TvLaneSide.RIGHT ) { + + lanes = this.getRightLanes(); + + } else if ( lane.side == TvLaneSide.LEFT ) { + + lanes = this.getLeftLanes().reverse(); + + } else { + + return width = 0; + + } + + for ( let i = 0; i < lanes.length; i++ ) { + + // TODO: Check if this correct + + var element = lanes[ i ]; + + width += element.getWidthValue( sCoordinate ); + + if ( element.id == lane.id ) break; + } + + // console.log(`upto-end lane-id: ${lane.id} s: ${sCoordinate} width: ${width}`); + + return width; + + } + + getWidthUptoCenter ( lane: TvLane, sCoordinate: number ): number { + + let cumulativeWidth = 0; + let lanes: TvLane[] = []; + + if ( lane.side == TvLaneSide.RIGHT ) { + + lanes = this.getRightLanes(); + + } else if ( lane.side == TvLaneSide.LEFT ) { + + lanes = this.getLeftLanes().reverse(); + + } else { + + return 0; + } + + for ( let i = 0; i < lanes.length; i++ ) { + + let element = lanes[ i ]; + + let width = element.getWidthValue( sCoordinate ); + + cumulativeWidth += width; + + if ( element.id == lane.id ) { + + cumulativeWidth -= width / 2; + break; + } + } + + return cumulativeWidth; + + } + + /** + * Add a lane to the lane section + * + * @param {TvLaneSide} laneSide side the side of the road to which the lane will be added + * @param {number} id of the lane + * @param {string} type of the lane (Section 6.5 of the OpenDRIVE specification) + * @param {boolean} level Level parameter of the road + * @param {boolean} sort Defines if the lanes should be sorted when added. True by default + */ + addLane ( laneSide: TvLaneSide, id: number, type: TvLaneType, level: boolean, sort: boolean ) { + + const newLane = new TvLane( laneSide, id, type, level, this.roadId, this.id ); + + this.addLaneInstance( newLane, sort ); + + return newLane; + + // return; + // + // let counter = 0; + // + // if ( sort ) { + // + // switch ( laneSide ) { + // + // case LaneSide.RIGHT: + // + // this.laneVector.push( new OdLane( laneSide, id, type, level, this.roadId ) ); + // + // this.lastAddedLaneIndex = this.laneVector.length - 1; + // + // break; + // + // case LaneSide.CENTER: + // + // const size = this.getLaneCount(); + // + // if ( size > 0 ) { + // + // for ( let i = 0; i < size; i++ ) { + // + // if ( this.laneVector[ i ].getId() < 0 ) { + // + // counter = i; + // + // this.laneVector[ counter ] = new OdLane( laneSide, id, type, level, this.roadId ); + // + // this.lastAddedLaneIndex = counter; + // + // } + // + // } + // + // } else { + // + // counter = 0; + // + // this.laneVector.push( new OdLane( laneSide, id, type, level, this.roadId ) ); + // + // this.lastAddedLaneIndex = counter; + // } + // + // break; + // + // case LaneSide.LEFT: + // + // // add to the beginning of the array + // this.laneVector.unshift( new OdLane( laneSide, id, type, level, this.roadId ) ); + // + // this.lastAddedLaneIndex = counter; + // + // break; + // + // } + // } else { + // + // this.laneVector.push( new OdLane( laneSide, id, type, level, this.roadId ) ); + // + // this.lastAddedLaneIndex = this.laneVector.length - 1; + // + // return this.lastAddedLaneIndex; + // } + } + + /** + * Delete lane at provided index + * + * @param index + */ + deleteLane ( index ) { + this.laneVector.splice( index, 1 ); + } + + /** + * Delete the outside left lane + */ + deleteLeftLane () { + + // Remove first element of array + this.laneVector.shift(); + } + + /** + * Delete the outside right lane + */ + deleteRightLane () { + + // Remove last element of array + this.laneVector.pop(); + } + + getLastLane (): TvLane { + + if ( this.laneVector.length > 0 ) { + return this.laneVector[ this.laneVector.length - 1 ]; + } + + return null; + } + + getLastAddedLane (): TvLane { + + if ( this.laneMap.has( this.lastAddedLaneIndex ) ) { + return this.laneMap.get( this.lastAddedLaneIndex ); + } + + // TODO : remove this + if ( this.lastAddedLaneIndex < this.laneVector.length ) { + return this.laneVector[ this.lastAddedLaneIndex ]; + } + + return null; + } + + getLastLeftLane (): TvLane { + + if ( this.laneVector.length > 0 ) { + + if ( this.laneVector[ 0 ].getSide() === TvLaneSide.LEFT ) { + + return this.laneVector[ 0 ]; + + } else { + + return null; + } + } + + return null; + } + + getLastRightLane (): TvLane { + + if ( this.laneVector.length > 0 ) { + + const index = this.laneVector.length - 1; + + if ( this.laneVector[ index ].getSide() === TvLaneSide.RIGHT ) { + + return this.laneVector[ index ]; + + } else { + + return null; + } + } + + return null; + } + + getLastCenterLane (): TvLane { + + const size = this.getLaneCount(); + + for ( let i = 0; i < size; i++ ) { + + if ( this.laneVector[ i ].getSide() === TvLaneSide.CENTER ) { + + return this.laneVector[ i ]; + + } + } + + return null; + } + + getLane ( index ): TvLane { + + if ( this.laneVector.length > 0 && index < this.laneVector.length ) { + return this.laneVector[ index ]; + } + + return null; + } + + getLaneCount () { + return this.laneVector.length; + } + + getLaneVector () { + return this.laneVector; + } + + /** + * Get the lane section s-offset + */ + getS () { + return this.attr_s; + } + + setS ( value ) { + this.attr_s = value; + } + + /** + * Get the lane section final s-offset which is the + * s-offset of the last record of the lane section + */ + getS2 () { + + let maxSValue = 0; + + const size = this.getLaneCount(); + + for ( let i = 0; i < size; i++ ) { + + const lane = this.getLane( i ); + + const width = lane.getLaneWidth( i ); + if ( width != null ) { + if ( width.s > maxSValue ) { + maxSValue = width.s; + } + } + + const roadMark = lane.getLaneRoadMark( i ); + if ( roadMark != null ) { + if ( roadMark.sOffset > maxSValue ) { + maxSValue = roadMark.sOffset; + } + } + + const material = lane.getLaneMaterial( i ); + if ( material != null ) { + if ( material.sOffset > maxSValue ) { + maxSValue = material.sOffset; + } + } + + const visibility = lane.getLaneVisibility( i ); + if ( visibility != null ) { + if ( visibility.sOffset > maxSValue ) { + maxSValue = visibility.sOffset; + } + } + + const speed = lane.getLaneSpeed( i ); + if ( speed != null ) { + if ( speed.sOffset > maxSValue ) { + maxSValue = speed.sOffset; + } + } + + const access = lane.getLaneAccess( i ); + if ( access != null ) { + if ( access.sOffset > maxSValue ) { + maxSValue = access.sOffset; + } + } + + const height = lane.getLaneHeight( i ); + if ( height != null ) { + if ( height.sOffset > maxSValue ) { + maxSValue = height.sOffset; + } + } + } + + return this.getS() + maxSValue; + } + + /** + * Check if the tested s-offset is inside the lane section interval + * @param sCheck A double s-offset value that has to be checked + * @returns {boolean} Return true if the s-offset value belongs to current lane section, false otherwise + */ + checkInterval ( sCheck ): boolean { + + if ( sCheck >= this.attr_s ) { + return true; + } + + return false; + } + + /** + * Return the lane-0 index in the lanes vector + */ + getZeroLaneIndex () { + + for ( let i = 0; i < this.getLaneCount(); i++ ) { + + if ( this.laneVector[ i ].getId() === 0 ) { + + return i; + + } + + } + + return 0; + } + + getLeftLaneCount () { + + const idGreaterThanZero = ( a, b ) => a[ 0 ] > 0; + + const leftLanes = new Map( [ ...this.laneMap.entries() ].filter( idGreaterThanZero ) ); + + return leftLanes.size; + + // let count = 0; + // + // for ( let i = 0; i < this.getLaneCount(); i++ ) { + // + // if ( this.laneVector[ i ].getSide() === LaneSide.LEFT ) { + // + // count++; + // + // } + // } + // + // return count; + } + + getLeftLanes (): TvLane[] { + + const idGreaterThanZero = ( a, b ) => a[ 0 ] > 0; + + const leftLanes = new Map( [ ...this.laneMap.entries() ].filter( idGreaterThanZero ) ); + + return [ ...leftLanes.values() ]; + + // const lanes = []; + // + // for ( let i = 0; i < this.getLaneCount(); i++ ) { + // + // if ( this.laneVector[ i ].getSide() === LaneSide.LEFT ) { + // + // lanes.push( this.laneVector[ i ] ); + // + // } + // } + // + // return lanes; + } + + getCenterLaneCount () { + + let count = 0; + + for ( let i = 0; i < this.getLaneCount(); i++ ) { + + if ( this.laneVector[ i ].getSide() === TvLaneSide.CENTER ) { + + count++; + + } + } + + return count; + } + + getRightLaneCount () { + + const idLessThanZero = ( a, b ) => a[ 0 ] < 0; + + const rightLanes = new Map( [ ...this.laneMap.entries() ].filter( idLessThanZero ) ); + + return rightLanes.size; + + // let count = 0; + // + // for ( let i = 0; i < this.getLaneCount(); i++ ) { + // + // if ( this.laneVector[ i ].getSide() === LaneSide.RIGHT ) { + // + // count++; + // + // } + // } + // + // return count; + } + + getRightLanes (): TvLane[] { + + const idLessThanZero = ( a, b ) => a[ 0 ] < 0; + + const rightLanes = new Map( [ ...this.laneMap.entries() ].filter( idLessThanZero ) ); + + return [ ...rightLanes.values() ]; + + // const lanes = []; + // + // for ( let i = 0; i < this.getLaneCount(); i++ ) { + // + // if ( this.laneVector[ i ].getSide() === LaneSide.RIGHT ) { + // + // lanes.push( this.laneVector[ i ] ); + // + // } + // } + // + // return lanes; + } + + /** + * Fill a special structure with all the lane / lane section data that is sampled at a provided s-offset position along the road + * @param sCheck s-offset along the road at which to sample the lane section + * @param laneSectionSample The structure that has to be filled with the sampled data + * @return Returns true if the operation was successful. + */ + fillLaneSectionSample ( sCheck: number, laneSectionSample: TvLaneSectionSample ) { + + laneSectionSample.clearVectors(); + + + const leftMax = 0; + const rightMax = this.getLaneCount() - 1; + + sCheck -= this.getS(); + + let level: boolean; + let type: string; + let height: TvLaneHeight; + let roadMark: TvLaneRoadMark; + let width = 0; + + if ( this.getLeftLaneCount() > 0 ) { + + const zeroLaneIndex = this.getZeroLaneIndex(); + + for ( let i = zeroLaneIndex; i >= leftMax; i-- ) { + + const lane = this.getLane( i ); + + type = lane.getType(); + level = lane.getLevel(); + height = lane.getHeightValue( sCheck ); + roadMark = lane.getRoadMark( sCheck ); + width = lane.getWidthValue( sCheck ); // and accumulate the width + + laneSectionSample.addLeftRecord( type, width, height, roadMark, level ); + } + } + + if ( this.getRightLaneCount() > 0 ) { + + for ( let i = this.getZeroLaneIndex(); i <= rightMax; i++ ) { + + const lane = this.getLane( i ); + + type = lane.getType(); + level = lane.getLevel(); + height = lane.getHeightValue( sCheck ); + roadMark = lane.getRoadMark( sCheck ); + width = lane.getWidthValue( sCheck ); + + laneSectionSample.addRightRecord( type, width, height, roadMark, level ); + + } + } + + return true; + } + + getLaneOffset ( lane: TvLane ) { + + let offsetFromCenter = 0; + + if ( this.laneVector.length > 0 ) { + + for ( let i = 0; i < this.laneVector.length; i++ ) { + + const element = this.laneVector[ i ]; + + if ( element.getSide() === lane.getSide() ) { + + offsetFromCenter += element.getWidthValue( 0 ); + + } + } + } + + return offsetFromCenter; + } + + getLaneById ( laneId: number ): TvLane { + + let lane = null; + + if ( this.laneVector.length > 0 ) { + + for ( let i = 0; i < this.laneVector.length; i++ ) { + + const element = this.laneVector[ i ]; + + if ( element.id === laneId ) { + + lane = element; + break; + + } + } + } + + return lane; + } + + addLaneInstance ( newLane: TvLane, sort: boolean ): void { + + if ( this.laneMap.has( newLane.id ) ) { + + const lanes = [ ...this.laneMap.entries() ]; + + this.laneMap.clear(); + + for ( let i = 0; i < lanes.length; i++ ) { + + const lane = lanes[ i ][ 1 ]; + + // shift left lanes + if ( lane.id >= newLane.id && newLane.id > 0 ) lane.setId( lane.id + 1 ); + + // shift right lanes + if ( lane.id <= newLane.id && newLane.id < 0 ) lane.setId( lane.id - 1 ); + + this.laneMap.set( lane.id, lane ); + + } + + } + + this.laneMap.set( newLane.id, newLane ); + + this.lastAddedLaneIndex = newLane.id; + + if ( sort ) { + + const inDescOrder = ( a, b ) => a[ 1 ].id > b[ 1 ].id ? -1 : 1; + + this.laneMap = new Map( [ ...this.laneMap.entries() ].sort( inDescOrder ) ); + + } + } + + removeLaneById ( laneId: number ) { + + this.laneMap.delete( laneId ); + + const inDescOrder = ( a, b ) => a[ 0 ] > b[ 0 ] ? -1 : 1; + + this.laneMap = new Map( [ ...this.laneMap.entries() ].sort( inDescOrder ) ); + + } + + updateLaneWidthValues ( lane: TvLane ): void { + + const widthSections = lane.getLaneWidthVector(); + + for ( let i = 0; i < widthSections.length; i++ ) { + + const current = widthSections[ i ]; + + let pp0, pp1, pd0, pd1, length; + + if ( ( i + 1 ) < widthSections.length ) { + + const next = widthSections[ i + 1 ]; + + // next s cannot be less than current so we need to clamp it + if ( next.s <= current.s ) { + + next.s = current.s + 0.1; + + } + + length = next.s - current.s; + + pp0 = current.a; // width at start + pp1 = next.a; // width at end + pd0 = current.b; // tangent at start + pd1 = next.b; // tangent at end + + } else { + + // take lane section length + length = this.length - current.s; + + pp0 = current.a; // width at start + pp1 = current.a; // width at end + pd0 = current.b; // tangent at start + pd1 = current.b; // tangent at end + + } + + let a = pp0; + let b = pd0; + let c = ( -3 * pp0 ) + ( 3 * pp1 ) + ( -2 * pd0 ) + ( -1 * pd1 ); + let d = ( 2 * pp0 ) + ( -2 * pp1 ) + ( 1 * pd0 ) + ( 1 * pd1 ); + + b /= length; + c /= length * length; + d /= length * length * length; + + current.a = a; + current.b = b; + current.c = c; + current.d = d; + + } + + } + + cloneAtS ( id?: number, s?: number, side?: boolean, roadId?: number ): TvLaneSection { + + const laneSection = new TvLaneSection( id || 0, 0, side || this.attr_singleSide, roadId || 0 ); + + this.laneMap.forEach( lane => { + + laneSection.laneMap.set( lane.id, lane.cloneAtS( lane.id, s || 0 ) ); + + } ); + + return laneSection; + } +} diff --git a/src/app/modules/tv-map/models/tv-lane-speed.ts b/src/app/modules/tv-map/models/tv-lane-speed.ts new file mode 100644 index 00000000..76c2f6c2 --- /dev/null +++ b/src/app/modules/tv-map/models/tv-lane-speed.ts @@ -0,0 +1,39 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +export class TvLaneSpeed { + public attr_sOffset; + public attr_max; + public attr_unit; + + constructor ( sOffset: number, max: number, unit: string ) { + this.attr_sOffset = sOffset; + this.attr_max = max; + this.attr_unit = unit; + } + + get sOffset () { + return this.attr_sOffset; + } + + set sOffset ( value ) { + this.attr_sOffset = value; + } + + get max () { + return this.attr_max; + } + + set max ( value ) { + this.attr_max = value; + } + + get unit () { + return this.attr_unit; + } + + set unit ( value ) { + this.attr_unit = value; + } +} \ No newline at end of file diff --git a/src/app/modules/tv-map/models/tv-lane-visibility.ts b/src/app/modules/tv-map/models/tv-lane-visibility.ts new file mode 100644 index 00000000..80e61da8 --- /dev/null +++ b/src/app/modules/tv-map/models/tv-lane-visibility.ts @@ -0,0 +1,60 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +export class TvLaneVisibility { + public attr_sOffset; + public attr_forward; + public attr_back; + public attr_left; + public attr_right; + + constructor ( sOffset: number, forward: number, back: number, left: number, right: number ) { + this.attr_sOffset = sOffset; + this.attr_forward = forward; + this.attr_back = back; + this.attr_left = left; + this.attr_right = right; + } + + get sOffset () { + return this.attr_sOffset; + } + + set sOffset ( value ) { + this.attr_sOffset = value; + } + + get forward () { + return this.attr_forward; + } + + set forward ( value ) { + this.attr_forward = value; + } + + get back () { + return this.attr_back; + } + + set back ( value ) { + this.attr_back = value; + } + + get left () { + return this.attr_left; + } + + set left ( value ) { + this.attr_left = value; + } + + get right () { + return this.attr_right; + } + + set right ( value ) { + this.attr_right = value; + } + +} \ No newline at end of file diff --git a/src/app/modules/tv-map/models/tv-lane-width.ts b/src/app/modules/tv-map/models/tv-lane-width.ts new file mode 100644 index 00000000..99b7609f --- /dev/null +++ b/src/app/modules/tv-map/models/tv-lane-width.ts @@ -0,0 +1,45 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { ThirdOrderPolynom } from './third-order-polynom'; +import { Math, Object3D } from 'three'; + +export class TvLaneWidth extends ThirdOrderPolynom { + + public mesh?: Object3D; + + private _laneId: number; + private _roadId: number; + + constructor ( s: number, a: number, b: number, c: number, d: number, laneId?: number, roadId?: number ) { + + super( s, a, b, c, d ); + + this._laneId = laneId; + this._roadId = roadId; + + } + + get laneId () { + return this._laneId; + } + + set laneId ( value ) { + this._laneId = value; + } + + get roadId () { + return this._roadId; + } + + set roadId ( value ) { + this._roadId = value; + } + + clone ( s?: number ) { + + return new TvLaneWidth( s || this.s, this.a, this.b, this.c, this.d, this._laneId, this._roadId ); + + } +} diff --git a/src/app/modules/tv-map/models/tv-lane.ts b/src/app/modules/tv-map/models/tv-lane.ts new file mode 100644 index 00000000..3dd8eb09 --- /dev/null +++ b/src/app/modules/tv-map/models/tv-lane.ts @@ -0,0 +1,1220 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { TravelDirection, TvColors, TvLaneSide, TvLaneType, TvRoadMarkTypes, TvRoadMarkWeights } from './tv-common'; +import { TvLaneRoadMark } from './tv-lane-road-mark'; +import { TvRoadLaneSectionLaneLink } from './tv-road-lane-section-lane-link'; +import { TvLaneWidth } from './tv-lane-width'; +import { TvLaneBorder } from './tv-lane-border'; +import { TvLaneMaterial } from './tv-lane-material'; +import { TvLaneVisibility } from './tv-lane-visibility'; +import { TvLaneSpeed } from './tv-lane-speed'; +import { TvLaneAccess } from './tv-lane-access'; +import { TvLaneHeight } from './tv-lane-height'; +import { GameObject } from 'app/core/game-object'; +import { MeshGeometryData } from './mesh-geometry.data'; +import { TvUtils } from './tv-utils'; +import { Math } from 'three'; + +export class TvLane { + + public readonly uuid: string; + + public gameObject: GameObject; + public meshData: MeshGeometryData; + public markMeshData: MeshGeometryData; + public attr_id: number; + public attr_type: TvLaneType; + public attr_level; + public link: TvRoadLaneSectionLaneLink; + public width: TvLaneWidth[] = []; + public border: TvLaneBorder[] = []; + public roadMark: TvLaneRoadMark[] = []; + public material: TvLaneMaterial[] = []; + public visibility: TvLaneVisibility[] = []; + public speed: TvLaneSpeed[] = []; + public access: TvLaneAccess[] = []; + public height: TvLaneHeight[] = []; + + private _roadId: number; + private _sectionId: number; + + private _side: TvLaneSide; + private _predecessorExists: boolean; + private _successorExists: boolean; + private _predecessor: number; + private _successor: number; + + private lastAddedLaneWidth: number; + private lastAddedLaneRoadMark: number; + private lastAddedLaneMaterial: number; + private lastAddedLaneVisibility: number; + private lastAddedLaneSpeed: number; + private lastAddedLaneAccess: number; + private lastAddedLaneHeight: number; + + + constructor ( laneSide: TvLaneSide, id: number, type: TvLaneType, level: boolean, roadId?: number, sectionId?: number ) { + + this._side = laneSide; + + this.uuid = Math.generateUUID(); + this.attr_id = id; + this.attr_type = type; + this.attr_level = level; + this.roadId = roadId; + this._sectionId = sectionId; + } + + get roadId () { + return this._roadId; + } + + set roadId ( value ) { + this._roadId = value; + } + + get laneSectionId () { + return this._sectionId; + } + + set laneSectionId ( value ) { + this._sectionId = value; + } + + get direction () { + + if ( this.side === TvLaneSide.LEFT ) { + + return TravelDirection.backward; + + } else if ( this.side === TvLaneSide.RIGHT ) { + + return TravelDirection.forward; + + } else if ( this.side === TvLaneSide.CENTER ) { + + return TravelDirection.undirected; + + } else { + + return TravelDirection.undirected; + + } + + } + + // updateMeshGeometry (): any { + + // let posTheta = new OdPosTheta; + // let road = OdEditorComponent.openDrive.getRoadById( this.roadId ); + // let laneSection = road.getLaneSection( 0 ); + // let cumulativeWidth = 0; + + // this.meshData = null; + // this.meshData = new MeshGeometryData; + + // for ( let sCoordinate = laneSection.s; sCoordinate < laneSection.lastSCoordinate; sCoordinate += OdBuilderConfig.ROAD_STEP ) { + + // cumulativeWidth = laneSection.getCumulativeWidth( this, sCoordinate ); + + // road.getGeometryCoords( sCoordinate, posTheta ); + + // this.makeLaneVertices( sCoordinate, posTheta, lane, road, cumulativeWidth ); + + // } + + // cumulativeWidth = laneSection.getCumulativeWidth( lane, laneSection.lastSCoordinate ); + + // this.makeLaneVertices( laneSection.lastSCoordinate - Maths.Epsilon, posTheta, lane, road, 0 ); + + // var geometry = new BufferGeometry(); + + // const vertices = new Float32Array( this.meshData.vertices ); + // const colors = new Float32Array( this.meshData.colors ); + + // this.createMeshIndices( this.meshData ); + + // geometry.setIndex( this.meshData.triangles ); + + // geometry.addAttribute( 'position', new BufferAttribute( vertices, 3 ) ); + // geometry.addAttribute( 'color', new BufferAttribute( colors, 3 ) ); + + // var material = new MeshBasicMaterial( { color: OdColorFactory.getLaneColor( lane ), transparent: true, opacity: 1 } ); + + // this.gameObject = new GameObject( "Lane:" + this.id, geometry, material ); + // this.gameObject.Tag = OpenDriveObjectType[OpenDriveObjectType.LANE]; + // this.gameObject.OpenDriveType = OpenDriveObjectType.LANE; + // this.gameObject.userData.data = lane; + + // laneSection.gameObject.add( this.gameObject ); + + // } + + // private mwidth; mheight; elevation; cosHdgPlusPiO2; sinHdgPlusPiO2; + + // private makeLaneVertices ( sCoordinate: number, pos: OdPosTheta, lane: OdLane, road: OdRoad, cumulativeWidth: number ) { + + // this.mwidth = lane.getWidthValue( sCoordinate ); + // this.mheight = lane.getHeightValue( sCoordinate ); + // this.elevation = road.getElevationValue( sCoordinate ); + + // this.cosHdgPlusPiO2 = Maths.cosHdgPlusPiO2( lane.side, pos.hdg ); + // this.sinHdgPlusPiO2 = Maths.sinHdgPlusPiO2( lane.side, pos.hdg ); + + // var v1 = new Vertex(); + // var p1X = this.cosHdgPlusPiO2 * cumulativeWidth; + // var p1Y = this.sinHdgPlusPiO2 * cumulativeWidth; + // v1.Position = new Vector3( pos.x + p1X, pos.y + p1Y, this.elevation ); + // v1.TexCoord = new Vector2( 0, sCoordinate ); + + // var v2 = new Vertex(); + // var p2X = cosHdgPlusPiO2 * ( cumulativeWidth + width ); + // var p2Y = sinHdgPlusPiO2 * ( cumulativeWidth + width ); + // v2.Position = new Vector3( pos.x + p2X, pos.y + p2Y, elevation + height.getOuter() ); + // v2.TexCoord = new Vector2( width + height.getOuter(), sCoordinate ) + + // if ( lane.side == LaneSide.RIGHT ) { + // this.addVertex( lane.meshData, v1 ); + // this.addVertex( lane.meshData, v2 ); + // } else { + // this.addVertex( lane.meshData, v2 ); + // this.addVertex( lane.meshData, v1 ); + // } + + // // Debug.log( v1.Position, v2.Position ); + // } + + // addVertex ( meshData: MeshGeometryData, v1: Vertex ) { + + // meshData.vertices.push( v1.Position.x, v1.Position.y, v1.Position.z ); + // meshData.normals.push( v1.Normal.x, v1.Normal.y, v1.Normal.z ); + // meshData.texCoords.push( v1.TexCoord.x, v1.TexCoord.y ); + // meshData.colors.push( 0, 1, 0 ); + // meshData.indices.push( meshData.currentIndex++ ); + + // } + + get predecessorExists (): boolean { + return this._predecessorExists; + } + + set predecessorExists ( value: boolean ) { + this._predecessorExists = value; + } + + get successorExists (): boolean { + return this._successorExists; + } + + set successorExists ( value: boolean ) { + this._successorExists = value; + } + + get side (): TvLaneSide { + return this._side; + } + + get sideAsString (): string { + + switch ( this.side ) { + + case TvLaneSide.LEFT: + return 'left' + break; + + case TvLaneSide.CENTER: + return 'center' + break; + + case TvLaneSide.RIGHT: + return 'right' + break; + + default: + break; + } + } + + set side ( value ) { + + const som = '' + value; + const val = parseFloat( som ); + + if ( val === 0 ) { + this._side = TvLaneSide.LEFT; + } else if ( val === 1 ) { + this._side = TvLaneSide.CENTER; + } else if ( val === 2 ) { + this._side = TvLaneSide.RIGHT; + } + } + + get id (): number { + return Number( this.attr_id ); + } + + get type (): TvLaneType { + return this.attr_type; + } + + set type ( value: TvLaneType ) { + this.attr_type = value; + } + + get level (): boolean { + return this.attr_level; + } + + get predecessor () { + return this._predecessor; + } + + set predecessor ( laneId: number ) { + this.setPredecessor( laneId ); + } + + + // + // Methods used to add child records to the respective lane records + // + + get succcessor () { + return this._successor; + } + + set succcessor ( laneId: number ) { + this.setPredecessor( laneId ); + } + + setSide ( side: TvLaneSide ) { + this._side = side; + } + + setId ( id: number ) { + this.attr_id = id; + } + + setType ( type: TvLaneType ) { + this.attr_type = type; + } + + setLevel ( level: boolean ) { + this.attr_level = level; + } + + setPredecessor ( laneId: number ) { + + this._predecessor = laneId; + this._predecessorExists = true; + + } + + // + // CLONE METHODS + // + + setSuccessor ( laneId: number ) { + + this._successor = laneId; + this._successorExists = true; + + } + + removePredecessor () { + + this._predecessor = null; + this._predecessorExists = false; + + } + + removeSuccessor () { + + this._successor = null; + this._successorExists = false; + + } + + addWidthRecord ( s: number, a: number, b: number, c: number, d: number ) { + + return this.addWidthRecordInstance( new TvLaneWidth( s, a, b, c, d, this.id, this._roadId ) ); + + } + + addRoadMarkRecord ( sOffset: number, type: TvRoadMarkTypes, weight: TvRoadMarkWeights, color: TvColors, width: number, laneChange: string, height: number ) { + + return this.addRoadMarkInstance( new TvLaneRoadMark( sOffset, type, weight, color, width, laneChange, height, this ) ); + + } + + addMaterialRecord ( sOffset: number, surface: string, friction: number, roughness: number ) { + + const index = this.checkLaneMaterialInterval( sOffset ) + 1; + + if ( index > this.getLaneMaterialCount() ) { + + this.material.push( new TvLaneMaterial( sOffset, surface, friction, roughness ) ); + + } else { + + this.material[ index ] = ( new TvLaneMaterial( sOffset, surface, friction, roughness ) ); + + } + + this.lastAddedLaneMaterial = index; + + return index; + } + + addVisibilityRecord ( sOffset: number, forward: number, back: number, left: number, right: number ) { + + const index = this.checkLaneVisibilityInterval( sOffset ) + 1; + + if ( index > this.getLaneVisibilityCount() ) { + + this.visibility.push( new TvLaneVisibility( sOffset, forward, back, left, right ) ); + + } else { + + this.visibility[ index ] = new TvLaneVisibility( sOffset, forward, back, left, right ); + + } + + this.lastAddedLaneVisibility = index; + + return index; + + } + + // + // DELETE METHODS + // + + addSpeedRecord ( sOffset: number, max: number, unit: string ) { + + const index = this.checkLaneSpeedInterval( sOffset ) + 1; + + if ( index > this.getLaneSpeedCount() ) { + + this.speed.push( new TvLaneSpeed( sOffset, max, unit ) ); + + } else { + + this.speed[ index ] = new TvLaneSpeed( sOffset, max, unit ); + + } + + this.lastAddedLaneSpeed = index; + + return index; + } + + addAccessRecord ( sOffset: number, restriction: string ) { + + const index = this.checkLaneAccessInterval( sOffset ) + 1; + + if ( index > this.getLaneAccessCount() ) { + + this.access.push( new TvLaneAccess( sOffset, restriction ) ); + + } else { + + this.access[ index ] = new TvLaneAccess( sOffset, restriction ); + + } + + this.lastAddedLaneAccess = index; + + return index; + } + + addHeightRecord ( sOffset: number, inner: number, outer: number ) { + + const index = this.checkLaneHeightInterval( sOffset ) + 1; + + if ( index > this.getLaneHeightCount() ) { + + this.height.push( new TvLaneHeight( sOffset, inner, outer ) ); + + } else { + + this.height[ index ] = new TvLaneHeight( sOffset, inner, outer ); + + } + + this.lastAddedLaneHeight = index; + + return index; + } + + cloneLaneWidth ( index: number ) { + + if ( index < this.width.length - 1 ) { + + this.width[ index + 1 ] = this.width[ index ]; + + } else if ( index === this.width.length - 1 ) { + + this.width.push( this.width[ index ] ); + + } + + this.lastAddedLaneWidth = index + 1; + + return this.lastAddedLaneWidth; + } + + cloneLaneRoadMark ( index: number ) { + + if ( index < this.roadMark.length - 1 ) { + + this.roadMark[ index + 1 ] = ( this.roadMark[ index ] ); + + } else if ( index === this.roadMark.length - 1 ) { + + this.roadMark.push( this.roadMark[ index ] ); + + } + + this.lastAddedLaneRoadMark = index + 1; + + return this.lastAddedLaneRoadMark; + } + + cloneLaneMaterial ( index: number ) { + + if ( index < this.material.length - 1 ) { + + this.material[ index + 1 ] = ( this.material[ index ] ); + + } else if ( index === this.material.length - 1 ) { + + this.material.push( this.material[ index ] ); + + } + + this.lastAddedLaneMaterial = index + 1; + + return this.lastAddedLaneMaterial; + } + + cloneLaneVisibility ( index: number ) { + + if ( index < this.visibility.length - 1 ) { + + this.visibility[ index + 1 ] = ( this.visibility[ index ] ); + + } else if ( index === this.visibility.length - 1 ) { + + this.visibility.push( this.visibility[ index ] ); + + } + + this.lastAddedLaneVisibility = index + 1; + + return this.lastAddedLaneVisibility; + } + + cloneLaneSpeed ( index: number ) { + + if ( index < this.speed.length - 1 ) { + + this.speed[ index + 1 ] = ( this.speed[ index ] ); + + } else if ( index === this.speed.length - 1 ) { + + this.speed.push( this.speed[ index ] ); + + } + + this.lastAddedLaneSpeed = index + 1; + + return this.lastAddedLaneSpeed; + } + + cloneLaneAccess ( index: number ) { + + if ( index < this.access.length - 1 ) { + + this.access[ index + 1 ] = ( this.access[ index ] ); + + } else if ( index === this.access.length - 1 ) { + + this.access.push( this.access[ index ] ); + + } + + this.lastAddedLaneAccess = index + 1; + + return this.lastAddedLaneAccess; + } + + cloneLaneHeight ( index: number ) { + + if ( index < this.height.length - 1 ) { + + this.height[ index + 1 ] = ( this.height[ index ] ); + + } else if ( index === this.height.length - 1 ) { + + this.height.push( this.height[ index ] ); + + } + + this.lastAddedLaneHeight = index + 1; + + return this.lastAddedLaneHeight; + } + + deleteLaneWidth ( index: number ) { + this.width.splice( index, 1 ); + } + + deleteLaneRoadMark ( index: number ) { + this.roadMark.splice( index, 1 ); + } + + deleteLaneMaterial ( index: number ) { + this.material.splice( index, 1 ); + } + + deleteLaneVisibility ( index: number ) { + this.visibility.splice( index, 1 ); + } + + deleteLaneSpeed ( index: number ) { + this.speed.splice( index, 1 ); + } + + deleteLaneAccess ( index: number ) { + this.access.splice( index, 1 ); + } + + deleteLaneHeight ( index: number ) { + this.height.splice( index, 1 ); + } + + getSide (): TvLaneSide { + return this._side; + } + + getId (): number { + return Number( this.attr_id ); + } + + getType (): string { + return this.attr_type; + } + + getLevel (): boolean { + return this.attr_level; + } + + isPredecessorSet () { + return this._predecessorExists; + } + + getPredecessor () { + return this._predecessor; + } + + isSuccessorSet () { + return this._successorExists; + } + + getSuccessor () { + return this._successor; + } + + // + // GET POINTER TO RECORDS + + // + getLaneWidthVector (): TvLaneWidth[] { + return this.width; + } + + getLaneRoadMarkVector (): TvLaneRoadMark[] { + return this.roadMark; + } + + getLaneMaterialVector (): TvLaneMaterial[] { + return this.material; + } + + getLaneVisibilityVector (): TvLaneVisibility[] { + return this.visibility; + } + + getLaneSpeedVector (): TvLaneSpeed[] { + return this.speed; + } + + getLaneAccessVector (): TvLaneAccess[] { + return this.access; + } + + getLaneHeightVector (): TvLaneHeight[] { + return this.height; + } + + + // + // GET ELEMENT AT INDEX + // + + getLaneWidth ( index ): TvLaneWidth { + + if ( this.width.length > 0 && index < this.width.length ) { + return this.width[ index ]; + } + + return null; + } + + getLaneRoadMark ( index ): TvLaneRoadMark { + + if ( this.roadMark.length > 0 && index < this.roadMark.length ) { + return this.roadMark[ index ]; + } + + return null; + } + + getLaneMaterial ( index ): TvLaneMaterial { + + if ( this.material.length > 0 && index < this.material.length ) { + return this.material[ index ]; + } + + return null; + } + + getLaneVisibility ( index ): TvLaneVisibility { + + if ( this.visibility.length > 0 && index < this.visibility.length ) { + return this.visibility[ index ]; + } + + return null; + } + + getLaneSpeed ( index ): TvLaneSpeed { + + if ( this.speed.length > 0 && index < this.speed.length ) { + return this.speed[ index ]; + } + + return null; + } + + getLaneAccess ( index ): TvLaneAccess { + + if ( this.access.length > 0 && index < this.access.length ) { + return this.access[ index ]; + } + + return null; + } + + getLaneHeight ( index ): TvLaneHeight { + + if ( this.height.length > 0 && index < this.height.length ) { + return this.height[ index ]; + } + + return null; + } + + // + // GET COUNT OF ELEMENTS + // + + getLaneWidthCount (): number { + return this.width.length; + } + + getLaneRoadMarkCount (): number { + return this.roadMark.length; + } + + getLaneMaterialCount (): number { + return this.material.length; + } + + getLaneVisibilityCount (): number { + return this.visibility.length; + } + + getLaneSpeedCount (): number { + return this.speed.length; + } + + getLaneAccessCount (): number { + return this.access.length; + } + + getLaneHeightCount (): number { + return this.height.length; + } + + // + // GET LAST ELEMENT + // + + getLastLaneWidth () { + + if ( this.width.length > 0 ) { + return this.width[ this.width.length - 1 ]; + } + + return null; + } + + getLastLaneRoadMark () { + + if ( this.roadMark.length > 0 ) { + return this.roadMark[ this.roadMark.length - 1 ]; + } + + return null; + } + + getLastLaneMaterial () { + + if ( this.material.length > 0 ) { + return this.material[ this.material.length - 1 ]; + } + + return null; + } + + getLastLaneVisibility () { + + if ( this.visibility.length > 0 ) { + return this.visibility[ this.visibility.length - 1 ]; + } + + return null; + } + + getLastLaneSpeed () { + + if ( this.speed.length > 0 ) { + return this.speed[ this.speed.length - 1 ]; + } + + return null; + } + + getLastLaneAccess () { + + if ( this.access.length > 0 ) { + return this.access[ this.access.length - 1 ]; + } + + return null; + } + + getLastLaneHeight () { + + if ( this.height.length > 0 ) { + return this.height[ this.height.length - 1 ]; + } + + return null; + } + + /** + * Get the last added elements of a certain vectors + * (their position might not be at the end of the vector) + */ + + getLastAddedLaneWidth () { + if ( this.lastAddedLaneWidth < this.width.length ) { + return this.width[ this.lastAddedLaneWidth ]; + } + return null; + } + + getLastAddedLaneRoadMark () { + if ( this.lastAddedLaneRoadMark < this.roadMark.length ) { + return this.roadMark[ this.lastAddedLaneRoadMark ]; + } + return null; + } + + getLastAddedLaneMaterial () { + if ( this.lastAddedLaneMaterial < this.material.length ) { + return this.material[ this.lastAddedLaneMaterial ]; + } + return null; + } + + getLastAddedLaneVisibility () { + if ( this.lastAddedLaneVisibility < this.visibility.length ) { + return this.visibility[ this.lastAddedLaneVisibility ]; + } + return null; + } + + getLastAddedLaneSpeed () { + if ( this.lastAddedLaneSpeed < this.speed.length ) { + return this.speed[ this.lastAddedLaneSpeed ]; + } + return null; + } + + getLastAddedLaneAccess () { + if ( this.lastAddedLaneAccess < this.access.length ) { + return this.access[ this.lastAddedLaneAccess ]; + } + return null; + } + + getLastAddedLaneHeight () { + if ( this.lastAddedLaneHeight < this.height.length ) { + return this.height[ this.lastAddedLaneHeight ]; + } + return null; + } + + /** + * Check the intervals and return the index of the records that applies to the provided s-offset + */ + + getLaneWidthIndex ( sCheck: number ): number { + + let result = null; + + for ( let i = 0; i < this.width.length; i++ ) { + + if ( sCheck >= this.width[ i ].s ) result = i; + + } + + return result; + } + + checkLaneRoadMarkInterval ( sCheck: number ): number { + + let res = -1; + + for ( let i = 0; i < this.roadMark.length; i++ ) { + + if ( sCheck >= this.roadMark[ i ].sOffset ) { + + res = i; + + } else { + + break; + + } + + } + + return res; + } + + checkLaneMaterialInterval ( sCheck: number ): number { + + let res = -1; + + for ( let i = 0; i < this.material.length; i++ ) { + + if ( sCheck >= this.material[ i ].sOffset ) { + + res = i; + + } else { + + break; + + } + + } + + return res; + } + + checkLaneVisibilityInterval ( sCheck: number ): number { + + let res = -1; + + for ( let i = 0; i < this.visibility.length; i++ ) { + + if ( sCheck >= this.visibility[ i ].sOffset ) { + + res = i; + + } else { + + break; + + } + + } + + return res; + } + + checkLaneSpeedInterval ( sCheck: number ): number { + + let res = -1; + + for ( let i = 0; i < this.speed.length; i++ ) { + + if ( sCheck >= this.speed[ i ].sOffset ) { + + res = i; + + } else { + + break; + + } + + } + + return res; + } + + checkLaneAccessInterval ( sCheck: number ): number { + + let res = -1; + + for ( let i = 0; i < this.access.length; i++ ) { + + if ( sCheck >= this.access[ i ].sOffset ) { + + res = i; + + } else { + + break; + + } + + } + + return res; + } + + checkLaneHeightInterval ( s_value: number ): number { + + let res = -1; + + for ( let i = 0; i < this.height.length; i++ ) { + + if ( s_value >= this.height[ i ].sOffset ) { + + res = i; + + } else { + + break; + + } + + } + + return res; + } + + /** + * Evaluate the record and the return the width value + * @param sCheck + */ + getWidthValue ( sCheck ): number { + + const widthEntry = this.getLaneWidthAt( sCheck ); + + if ( widthEntry == null ) return 0; + + return widthEntry.getValue( sCheck ); + } + + /** + * Evaluate the record and return the height object + * @param sCheck + */ + getHeightValue ( sCheck ): TvLaneHeight { + + const laneHeight = new TvLaneHeight( 0, 0, 0 ); + + const index = this.checkLaneHeightInterval( sCheck ); + + if ( index >= 0 ) { + + const currentHeight = this.getLaneHeight( index ); + + laneHeight.setInner( currentHeight.getInner() ); + laneHeight.setOuter( currentHeight.getOuter() ); + + } + + return laneHeight; + } + + /** + * Evaluate the road marks records and return the road + * mark object corresponding to the provided s-offset + * @param sCheck + */ + getRoadMark ( sCheck ): TvLaneRoadMark { + + let result = TvUtils.checkIntervalArray( this.roadMark, sCheck ); + + if ( result == null ) { + + console.warn( 'roadmark not found using default' ); + + result = new TvLaneRoadMark( 0, TvRoadMarkTypes.SOLID, TvRoadMarkWeights.STANDARD, TvColors.WHITE, 0, '', 0, this ); + + } + + return result; + + // const laneRoadMark = new OdLaneRoadMark( 0, OdRoadMarkTypes.SOLID, OdRoadMarkWeights.STANDARD, OdColors.WHITE, 0, '', 0 ); + // + // const index = this.checkLaneRoadMarkInterval( sCheck ); + // + // if ( index >= 0 ) { + // + // const currentRoadMark = this.roadMark[ index ]; + // + // laneRoadMark.setType( currentRoadMark.getType() ); + // laneRoadMark.setWeight( currentRoadMark.getWeight() ); + // laneRoadMark.setMaterial( currentRoadMark.getMaterial() ); + // laneRoadMark.setWidth( currentRoadMark.getWidth() ); + // laneRoadMark.setHeight( currentRoadMark.getHeight() ); + // laneRoadMark.setLaneChange( currentRoadMark.getLaneChange() ); + // + // } + // + // return laneRoadMark; + } + + /** + * Returns the color for mesh of the lane + * @returns {string} + */ + getColor (): string { + + if ( this.attr_type === 'driving' ) { + + return '#aeaeae'; + + } else if ( this.attr_type === 'border' ) { + + return '#118400'; + + } else if ( this.attr_type === 'stop' ) { + + return '#848484'; + } + + return '#ff00ab'; + } + + // clones the entire lane + clone ( id?: number ): TvLane { + + const laneId = id || this.id; + + const newLane = new TvLane( this.side, laneId, this.type, this.level, this.roadId, this.laneSectionId ); + + this.getLaneWidthVector().forEach( width => { + newLane.addWidthRecord( width.s, width.a, width.b, width.c, width.d ); + } ); + + this.getLaneRoadMarkVector().forEach( roadMark => { + newLane.addRoadMarkRecord( roadMark.sOffset, roadMark.type, roadMark.weight, roadMark.color, roadMark.width, roadMark.laneChange, roadMark.height ); + } ); + + return newLane; + } + + // clones only the lane at s and avoid multiple entries for width, height etc + cloneAtS ( id?: number, s?: number ): TvLane { + + const laneId = id || this.id; + + const newLane = new TvLane( this.side, laneId, this.type, this.level, this.roadId, this.laneSectionId ); + + const width = this.getLaneWidthAt( s || 0 ); + + if ( width ) { + + newLane.addWidthRecord( width.s, width.a, width.b, width.c, width.d ); + + } + + const roadMark = this.getRoadMark( s || 0 ); + + if ( roadMark ) { + + newLane.addRoadMarkRecord( + roadMark.sOffset, + roadMark.type, + roadMark.weight, + roadMark.color, + roadMark.width, + roadMark.laneChange, + roadMark.height + ); + + } + + return newLane; + + } + + public getLaneWidthAt ( s: number ): TvLaneWidth { + + return TvUtils.checkIntervalArray( this.width, s ); + + } + + public getRoadMarkAt ( s: number ): TvLaneRoadMark { + + return TvUtils.checkIntervalArray( this.roadMark, s ); + + } + + getRoadMarks () { + + return this.roadMark; + + } + + addDefaultRoadMark () { + + this.addRoadMarkRecord( 0, TvRoadMarkTypes.SOLID, TvRoadMarkWeights.STANDARD, TvColors.WHITE, 0.15, 'none', 0 ); + + } + + addRoadMarkInstance ( roadmark: TvLaneRoadMark ) { + + this.roadMark.push( roadmark ); + + this.roadMark.sort( ( a, b ) => a.sOffset > b.sOffset ? 1 : -1 ); + + // console.log( this.roadMark ); + + // this.lastAddedLaneRoadMark = index; + // + // return index; + } + + addWidthRecordInstance ( laneWidth: TvLaneWidth ) { + + this.width.push( laneWidth ); + + this.width.sort( ( a, b ) => a.s > b.s ? 1 : -1 ); + + } +} + diff --git a/src/app/modules/tv-map/models/tv-lateral-profile-crossfall.ts b/src/app/modules/tv-map/models/tv-lateral-profile-crossfall.ts new file mode 100644 index 00000000..fbd2c69b --- /dev/null +++ b/src/app/modules/tv-map/models/tv-lateral-profile-crossfall.ts @@ -0,0 +1,6 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +export class TvLateralProfileCrossfall { +} \ No newline at end of file diff --git a/src/app/modules/tv-map/models/tv-lateral-profile-shape.ts b/src/app/modules/tv-map/models/tv-lateral-profile-shape.ts new file mode 100644 index 00000000..df1727c6 --- /dev/null +++ b/src/app/modules/tv-map/models/tv-lateral-profile-shape.ts @@ -0,0 +1,6 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +export class TvLateralProfileShape { +} \ No newline at end of file diff --git a/src/app/modules/tv-map/models/tv-lateral-profile-super-elevation.ts b/src/app/modules/tv-map/models/tv-lateral-profile-super-elevation.ts new file mode 100644 index 00000000..0c4b5923 --- /dev/null +++ b/src/app/modules/tv-map/models/tv-lateral-profile-super-elevation.ts @@ -0,0 +1,6 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +export class TvLateralProfileSuperElevation { +} \ No newline at end of file diff --git a/src/app/modules/tv-map/models/tv-lateral.profile.ts b/src/app/modules/tv-map/models/tv-lateral.profile.ts new file mode 100644 index 00000000..47aa5740 --- /dev/null +++ b/src/app/modules/tv-map/models/tv-lateral.profile.ts @@ -0,0 +1,13 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { TvLateralProfileSuperElevation } from './tv-lateral-profile-super-elevation'; +import { TvLateralProfileCrossfall } from './tv-lateral-profile-crossfall'; +import { TvLateralProfileShape } from './tv-lateral-profile-shape'; + +export class TvLateralProfile { + public superelelevation: TvLateralProfileSuperElevation[] = []; + public crossfall: TvLateralProfileCrossfall[] = []; + public shape: TvLateralProfileShape[] = []; +} \ No newline at end of file diff --git a/src/app/modules/tv-map/models/tv-map-header.ts b/src/app/modules/tv-map/models/tv-map-header.ts new file mode 100644 index 00000000..7f1f0646 --- /dev/null +++ b/src/app/modules/tv-map/models/tv-map-header.ts @@ -0,0 +1,81 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +export class TvMapHeader { + + public attr_revMajor: number; + public attr_revMinor: number; + public attr_name: string; + public attr_version: number; + public attr_date: string; + public attr_north: number; + public attr_south: number; + public attr_east: number; + public attr_west: number; + public attr_vendor: string; + + constructor ( + revMajor: number, + revMinor: number, + name: string, + version: number, + date: string, + north: number, + south: number, + east: number, + west: number, + vendor: string + ) { + this.attr_revMajor = revMajor; + this.attr_revMinor = revMinor; + this.attr_name = name; + this.attr_version = version; + this.attr_date = date; + this.attr_north = north; + this.attr_south = south; + this.attr_east = east; + this.attr_west = west; + this.attr_vendor = vendor; + } + + get revMajor (): number { + return this.attr_revMajor; + } + + get revMinor (): number { + return this.attr_revMinor; + } + + get name (): string { + return this.attr_name; + } + + get version (): number { + return this.attr_version; + } + + get date (): string { + return this.attr_date; + } + + get north (): number { + return this.attr_north; + } + + get south (): number { + return this.attr_south; + } + + get east (): number { + return this.attr_east; + } + + get west (): number { + return this.attr_west; + } + + get vendor (): string { + return this.attr_vendor; + } +} \ No newline at end of file diff --git a/src/app/modules/tv-map/models/tv-map.model.spec.ts b/src/app/modules/tv-map/models/tv-map.model.spec.ts new file mode 100644 index 00000000..2b341bb2 --- /dev/null +++ b/src/app/modules/tv-map/models/tv-map.model.spec.ts @@ -0,0 +1,118 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { TvLaneSide, TvLaneType } from './tv-common'; +import { TvLaneSection } from './tv-lane-section'; +import { TvLane } from './tv-lane'; +import { TvMap } from './tv-map.model'; +import { TvRoad } from './tv-road.model'; +import { TvPosTheta } from './tv-pos-theta'; +import { TvMapQueries } from '../queries/tv-map-queries'; +import { TvMapSourceFile } from '../services/tv-map-source-file'; + +describe( 'OpenDrive Model', () => { + + let openDrive: TvMap; + let road: TvRoad; + let laneSection: TvLaneSection; + + let leftOne: TvLane; + let leftTwo: TvLane; + let leftThree: TvLane; + let rightOne: TvLane; + let rightTwo: TvLane; + let rightThree: TvLane; + let rightFour: TvLane; + + beforeEach( () => { + + openDrive = new TvMap(); + + TvMapSourceFile.openDrive = openDrive; + + road = openDrive.addRoad( '', 10, 1, -1 ); + + road.addPlanView(); + + laneSection = new TvLaneSection( 1, 0, true, 1 ); + + road.getLaneSections().push( laneSection ); + + laneSection.addLane( TvLaneSide.LEFT, 2, TvLaneType.driving, true, true ); + leftTwo = laneSection.getLastAddedLane(); + + laneSection.addLane( TvLaneSide.LEFT, 3, TvLaneType.driving, true, true ); + leftThree = laneSection.getLastAddedLane(); + + laneSection.addLane( TvLaneSide.LEFT, 1, TvLaneType.driving, true, true ); + leftOne = laneSection.getLastAddedLane(); + + laneSection.addLane( TvLaneSide.CENTER, 0, TvLaneType.driving, true, true ); + + laneSection.addLane( TvLaneSide.RIGHT, -1, TvLaneType.driving, true, true ); + rightOne = laneSection.getLastAddedLane(); + + laneSection.addLane( TvLaneSide.RIGHT, -3, TvLaneType.driving, true, true ); + rightThree = laneSection.getLastAddedLane(); + + laneSection.addLane( TvLaneSide.RIGHT, -2, TvLaneType.driving, true, true ); + rightTwo = laneSection.getLastAddedLane(); + + laneSection.addLane( TvLaneSide.RIGHT, -4, TvLaneType.driving, true, true ); + rightFour = laneSection.getLastAddedLane(); + + laneSection.getLaneVector().forEach( lane => { + + if ( lane.side !== TvLaneSide.CENTER ) { + + lane.addWidthRecord( 0, 2, 0, 0, 0 ); + + } + + } ); + + } ); + + it( 'should give correct road id ', () => { + + road.addGeometryLine( 0, 0, 0, 0, 10 ); + + const result = TvMapQueries.getRoadByCoords( 1, 1 ); + + expect( result.id ).toBe( 1 ); + + } ); + + it( 'should give correct lane id for left lane', () => { + + road.addGeometryLine( 0, 0, 0, 0, 10 ); + + const posTheta = new TvPosTheta(); + + const result = TvMapQueries.getLaneByCoords( 1, 1, posTheta ); + + expect( result.road.id ).toBe( 1 ); + expect( result.lane.id ).toBe( 1 ); + + const result2 = TvMapQueries.getLaneByCoords( 1, 3, posTheta ); + + expect( result2.road.id ).toBe( 1 ); + expect( result2.lane.id ).toBe( 2 ); + + } ); + + it( 'should give correct lane id for right lane', () => { + + road.addGeometryLine( 0, 0, 0, 0, 10 ); + + const posTheta = new TvPosTheta(); + + const result = TvMapQueries.getLaneByCoords( 1, -1, posTheta ); + + expect( result.road.id ).toBe( 1 ); + expect( result.lane.id ).toBe( -1 ); + + } ); + +} ); diff --git a/src/app/modules/tv-map/models/tv-map.model.ts b/src/app/modules/tv-map/models/tv-map.model.ts new file mode 100644 index 00000000..58c7a755 --- /dev/null +++ b/src/app/modules/tv-map/models/tv-map.model.ts @@ -0,0 +1,355 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { TvJunction } from './tv-junction'; +import { TvRoad } from './tv-road.model'; +import { TvController } from './tv-controller'; +import { GameObject } from 'app/core/game-object'; +import { TvMapHeader } from './tv-map-header'; +import { TvRoadLinkChild } from './tv-road-link-child'; +import { TvJunctionConnection } from './tv-junction-connection'; +import { PropInstance } from 'app/core/models/prop-instance.model'; +import { TvSurface } from './tv-surface.model'; +import { TvLaneSide, TvLaneType, TvRoadType } from './tv-common'; +import { RoadStyleService } from 'app/services/road-style.service'; +import { PropCurve } from './prop-curve'; +import { SceneService } from 'app/core/services/scene.service'; +import { PropPolygon } from './prop-polygons'; + +export class TvMap { + + public props: PropInstance[] = []; + public propCurves: PropCurve[] = []; + public propPolygons: PropPolygon[] = []; + public surfaces: TvSurface[] = []; + + public gameObject: GameObject = new GameObject( 'OpenDrive' ); + public header: TvMapHeader = new TvMapHeader( 1, 4, 'Untitled', 1, Date(), 1, 0, 0, 0, 'truevision.ai' ); + + private _roads: Map = new Map(); + private _junctions: Map = new Map(); + private _controllers: Map = new Map(); + + + get junctions (): Map { + return this._junctions; + } + + set junctions ( value: Map ) { + this._junctions = value; + } + + get roads (): Map { + return this._roads; + } + + set roads ( value: Map ) { + this._roads = value; + } + + get controllers (): Map { + return this._controllers; + } + + set controllers ( value: Map ) { + this._controllers = value; + } + + update () { + + + } + + public getHeader (): TvMapHeader { + return this.header; + } + + public addRoad ( name: string, length: number, id: number, junction: number ): TvRoad { + + const index = this.getRoadCount(); + + const road = new TvRoad( name, length, id, junction ); + + this.addRoadInstance( road ); + + + return road; + } + + addDefaultRoadWithType ( type: TvRoadType, maxSpeed = 40 ) { + + const road = this.addDefaultRoad(); + + road.setType( type, maxSpeed ); + + return road; + } + + addDefaultRoad (): TvRoad { + + const road = this.addRoad( `${ this.roads.size + 1 }`, 0, this.roads.size + 1, -1 ); + + const roadStyle = RoadStyleService.getRoadStyle( road.id ); + + // const laneOffset = road.addLaneOffset( 0, 0, 0, 0, 0 ); + const laneOffset = road.addLaneOffsetInstance( roadStyle.laneOffset ); + + // const laneSection = road.addGetLaneSection( 0 ); + const laneSection = road.addLaneSectionInstance( roadStyle.laneSection ); + + // const leftLane3 = laneSection.addLane( TvLaneSide.LEFT, 3, TvLaneType.sidewalk, true, true ); + // const leftLane2 = laneSection.addLane( TvLaneSide.LEFT, 2, TvLaneType.shoulder, true, true ); + // const leftLane1 = laneSection.addLane( TvLaneSide.LEFT, 1, TvLaneType.driving, true, true ); + // const centerLane = laneSection.addLane( TvLaneSide.CENTER, 0, TvLaneType.driving, true, true ); + // const rightLane1 = laneSection.addLane( TvLaneSide.RIGHT, -1, TvLaneType.driving, true, true ); + // const rightLane2 = laneSection.addLane( TvLaneSide.RIGHT, -2, TvLaneType.shoulder, true, true ); + // const rightLane3 = laneSection.addLane( TvLaneSide.RIGHT, -3, TvLaneType.sidewalk, true, true ); + + // leftLane1.addRoadMarkRecord( 0, TvRoadMarkTypes.NONE, TvRoadMarkWeights.STANDARD, TvColors.STANDARD, 0.15, 'none', 0 ); + // centerLane.addRoadMarkRecord( 0, TvRoadMarkTypes.BROKEN, TvRoadMarkWeights.STANDARD, TvColors.STANDARD, 0.15, 'none', 0 ); + // rightLane1.addRoadMarkRecord( 0, TvRoadMarkTypes.NONE, TvRoadMarkWeights.STANDARD, TvColors.STANDARD, 0.15, 'none', 0 ); + + // laneSection.getLaneVector().forEach( lane => { + + // if ( lane.side !== TvLaneSide.CENTER ) { + + // if ( lane.type == TvLaneType.driving ) lane.addWidthRecord( 0, 3.6, 0, 0, 0 ); + + // else if ( lane.type == TvLaneType.sidewalk ) lane.addWidthRecord( 0, 2, 0, 0, 0 ); + + // else lane.addWidthRecord( 0, 0.5, 0, 0, 0 ); + + // } + + // } ); + + return road; + } + + addConnectingRoad ( side: TvLaneSide, width: number, junctionId: number ): TvRoad { + + const road = this.addRoad( `${ this.roads.size + 1 }`, 0, this.roads.size + 1, junctionId ); + + + const laneSection = road.addGetLaneSection( 0 ); + + if ( side === TvLaneSide.LEFT ) + laneSection.addLane( TvLaneSide.LEFT, 1, TvLaneType.driving, true, true ); + + if ( side === TvLaneSide.RIGHT ) + laneSection.addLane( TvLaneSide.RIGHT, -1, TvLaneType.driving, true, true ); + + laneSection.addLane( TvLaneSide.CENTER, 0, TvLaneType.driving, true, true ); + + laneSection.getLaneVector().forEach( lane => { + + if ( lane.side !== TvLaneSide.CENTER ) { + + if ( lane.type === TvLaneType.driving ) lane.addWidthRecord( 0, width, 0, 0, 0 ); + + } + + } ); + + return road; + } + + addConnectingRoadLane () { + + } + + + addRoadInstance ( road: TvRoad ) { + + this._roads.set( road.id, road ); + + } + + public addNewJunction ( junctionName?: string ): TvJunction { + + const id = this.junctions.size + 1; + + const name = junctionName || `${ id }`; + + + return this.junctions.get( id ); + } + + public addJunction ( name, id ): number { + + const index = this.getJunctionCount(); + + this._junctions.set( id, new TvJunction( name, id ) ); + + + return index; + } + + public addController ( id: number, name: string, sequence: number ): TvController { + + const controller = new TvController( id, name, sequence ); + + this._controllers.set( id, controller ); + + return controller; + } + + public getControllerCount (): number { + + return this._controllers.size; + + } + + public getController ( index: number ) { + + return this._controllers.get( index ); + + } + + public removeRoad ( road: TvRoad ) { + + road.remove( this.gameObject ); + + this.roads.delete( road.id ); + } + + public deleteRoad ( id: number ): void { + + this.roads.delete( id ); + + } + + public deleteJunction ( id ): void { + + this._junctions.delete( id ); + + } + + getRoadById ( roadId: number ): TvRoad { + + if ( this._roads.has( roadId ) ) { + + return this._roads.get( roadId ); + + } else { + + console.error( `${ roadId } road-id not found` ); + // throw new Error( 'RoadID not found.' ); + + } + + } + + public getRoadCount (): number { + + return this._roads.size; + + } + + public getJunctionById ( id ): TvJunction { + + return this._junctions.get( id ); + + } + + public getJunctionCount (): number { + + return this._junctions.size; + + } + + /** + * Clears the OpenDrive structure, could be used to start a new document + */ + public clear () { + + this._roads.clear(); + + this._junctions.clear(); + + this.props.splice( 0, this.props.length ); + + this.propCurves.splice( 0, this.propCurves.length ); + + this.surfaces.splice( 0, this.surfaces.length ); + } + + public setHeader ( + revMajor: number, + revMinor: number, + name: string, + version: number, + date: string, + north: number, + south: number, + east: number, + west: number, + vendor: string + ) { + + this.header = new TvMapHeader( revMajor, revMinor, name, version, date, north, south, east, west, vendor ); + + } + + addJunctionInstance ( junction: TvJunction ) { + + this._junctions.set( junction.id, junction ); + + } + + addControllerInstance ( odController: TvController ) { + this.controllers.set( odController.id, odController ); + } + + destroy () { + + this.roads.forEach( road => road.remove( this.gameObject ) ); + + this.surfaces.forEach( surface => this.gameObject.remove( surface.mesh ) ); + + this.propCurves.forEach( curve => { + + this.gameObject.remove( curve.spline.mesh ); + + curve.props.forEach( prop => SceneService.remove( prop ) ); + + } ); + + this.propPolygons.forEach( polygon => { + + this.gameObject.remove( polygon.spline.mesh ); + + polygon.props.forEach( prop => SceneService.remove( prop ) ); + + } ); + + this.props.forEach( prop => this.gameObject.remove( prop.object ) ); + + this.clear(); + } + + private getNextRoad ( road: TvRoad, connection: TvJunctionConnection, child: TvRoadLinkChild ) { + + if ( child.elementType == 'road' ) { + + connection = null; + + return this.getRoadById( child.elementId ); + + } else if ( child.elementType == 'junction' ) { + + const junction = this.getJunctionById( child.elementId ); + + connection = junction.getRandomConnectionFor( road.id ); + + return this.getRoadById( connection.connectingRoad ); + + } else { + + console.error( 'unknown successor type' ); + + } + + } +} diff --git a/src/app/modules/tv-map/models/tv-plane-view.ts b/src/app/modules/tv-map/models/tv-plane-view.ts new file mode 100644 index 00000000..2c04b8eb --- /dev/null +++ b/src/app/modules/tv-map/models/tv-plane-view.ts @@ -0,0 +1,168 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { TvAbstractRoadGeometry } from './geometries/tv-abstract-road-geometry'; +import { TvSpiralGeometry } from './geometries/tv-spiral-geometry'; +import { TvPosTheta } from './tv-pos-theta'; +import { Vector2 } from 'three'; +import { TvLineGeometry } from './geometries/tv-line-geometry'; +import { TvArcGeometry } from './geometries/tv-arc-geometry'; +import { TvPoly3Geometry } from './geometries/tv-poly3-geometry'; +import { TvParamPoly3Geometry } from './geometries/tv-param-poly3-geometry'; + +export class TvPlaneView { + + // public geometry: ODGeometry[] = []; + + public geometries: TvAbstractRoadGeometry[] = []; + + + constructor () { + + } + + addGeometry ( geometry: TvAbstractRoadGeometry ) { + + this.geometries.push( geometry ); + + } + + addGeometryLine ( s: number, x: number, y: number, hdg: number, length: number ) { + + const geometry = new TvLineGeometry( s, x, y, hdg, length ); + + this.geometries.push( geometry ); + + return geometry; + } + + addGeometrySpiral ( s, x, y, hdg, length, curvStart, curvEnd ) { + + // const geometry = new ODGeometry(); + // geometry.spiral = new OdSpiralGeometry( s, x, y, hdg, length, curvStart, curvEnd ); + + this.geometries.push( new TvSpiralGeometry( s, x, y, hdg, length, curvStart, curvEnd ) ); + } + + addGeometryArc ( s, x, y, hdg, length, curvature: number ) { + + const geometry = new TvArcGeometry( s, x, y, hdg, length, curvature ); + + this.geometries.push( geometry ); + + return geometry; + } + + addGeometryPoly3 ( s, x, y, hdg, length, a, b, c, d ) { + + this.geometries.push( new TvPoly3Geometry( s, x, y, hdg, length, a, b, c, d ) ); + + } + + addGeometryParamPoly3 ( s, x, y, hdg, length, aU, bU, cU, dU, aV, bV, cV, dV ) { + + this.geometries.push( new TvParamPoly3Geometry( s, x, y, hdg, length, aU, bU, cU, dU, aV, bV, cV, dV ) ); + + } + + /** + * Getter for the overall planView/ road length (sum of all geometry record lengths) + */ + getBlockLength () { + + let total = 0; + + for ( let i = 0; i < this.geometries.length; i++ ) { + + total += this.geometries[ i ].length; + + } + + return total; + } + + recalculate ( s, x, y, hdg ) { + + + for ( let i = 0; i < this.geometries.length; i++ ) { + + // const element = this.geometry[i]; + + // TODO: + + } + } + + + getLastS2 () { + + if ( this.geometries.length > 0 ) { + + return this.geometries[ this.geometries.length - 1 ].s2; + + } else { + + return 0; + + } + } + + /** + * Gets the coordinates and heading at the end of the last geometry + * @param sCheck + * @param posTheta + */ + getCoords ( sCheck: number, posTheta: TvPosTheta ): number { + + // go through all the geometries + for ( let i = 0; i < this.geometries.length; i++ ) { + + const geometry = this.geometries[ i ]; + + // if the sCheck belongs to one of the geometries + if ( geometry.checkInterval( sCheck ) ) { + + geometry.getCoords( sCheck, posTheta ); + + return geometry.geometryType; + } + } + } + + /** + * Gets the nearest point on reference line from the given co-ordinates + * @param x + * @param y + */ + getNearestPointFrom ( x: number, y: number ): Vector2 { + + let curentMinDistance = 100000000; + let nearestPoint: Vector2; + + // go through all the geometries + for ( let i = 0; i < this.geometries.length; i++ ) { + + const geometry = this.geometries[ i ]; + + const point = geometry.getNearestPointFrom( x, y ); + const distance = this.distance( point, x, y ); + + // check if the distance is smaller then curent min distance + if ( distance < curentMinDistance ) { + + curentMinDistance = distance; + + nearestPoint = point; + + } + } + + return nearestPoint; + } + + distance ( pos: Vector2, x, y ): number { + return Math.sqrt( ( x - pos.x ) * ( x - pos.x ) + ( y - pos.y ) * ( y - pos.y ) ); + } + +} diff --git a/src/app/modules/tv-map/models/tv-pos-theta.ts b/src/app/modules/tv-map/models/tv-pos-theta.ts new file mode 100644 index 00000000..c02e2326 --- /dev/null +++ b/src/app/modules/tv-map/models/tv-pos-theta.ts @@ -0,0 +1,138 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +// Class representing a position in space plus a direction. +import { Vector2, Vector3 } from 'three'; +import { Maths } from '../../../utils/maths'; + +export class TvPosTheta { + + private _x: number; + private _s: number; + private _t: number; + private _y: number; + private _hdg: number; + + constructor ( x?: number, y?: number, hdg?: number, s?: number, t?: number ) { + this._x = x; + this._y = y; + this._hdg = hdg; + this._s = s; + this._t = t; + } + + get x () { + return this._x; + } + + set x ( value ) { + this._x = value; + } + + get s () { + return this._s; + } + + set s ( value ) { + this._s = value; + } + + get t () { + return this._t; + } + + set t ( value ) { + this._t = value; + } + + get y () { + return this._y; + } + + set y ( value ) { + this._y = value; + } + + get hdg () { + return this._hdg; + } + + set hdg ( value ) { + this._hdg = value; + } + + toVector3 (): Vector3 { + return new Vector3( this.x, this.y, 0 ); + } + + toVector2 (): Vector2 { + return new Vector2( this.x, this.y ); + } + + toDirectionVector (): Vector3 { + + const direction = new Vector3(); + + direction.x = Math.cos( this.hdg ) * Math.cos( 0 ); + direction.y = Math.sin( this.hdg ) * Math.cos( 0 ); + direction.z = 0; // Math.sin( pose.hdg ); + + return direction; + } + + rotateDegree ( degree: number ) { + + this.hdg = this.hdg + Maths.Deg2Rad * degree; + + return this; + } + + rotateRadian ( radians: number ) { + + this.hdg = this.hdg + radians; + + return this; + } + + moveForward ( s: number ): TvPosTheta { + + const x = this.x + Math.cos( this.hdg ) * s; + const y = this.y + Math.sin( this.hdg ) * s; + + return this.clone( x, y, this.hdg, this.s + s, this.t ); + } + + // offset means t + addLateralOffset ( offset: number ) { + + // // find the end of the chord line + // this.x = this.x + Math.cos( this.hdg ) * laneOffset; + // this.y = this.y + Math.sin( this.hdg ) * laneOffset; + + // // cosine and sine for the tangent (lateral position in track coords) + // double cosHdgPlusPiO2 = -1 * Math.Cos(hdg + OpenDriveXmlParserV2.M_PI_2); + // double sinHdgPlusPiO2 = -1 * Math.Sin(hdg + OpenDriveXmlParserV2.M_PI_2); + // + // x += cosHdgPlusPiO2 * (cumulativeWidth + 0); + // y += sinHdgPlusPiO2 * (cumulativeWidth + 0); + + // changed after testing + // let cosHdgPlusPiO2 = -1 * Math.cos( this.hdg + Maths.M_PI_2 ); + // let sinHdgPlusPiO2 = -1 * Math.sin( this.hdg + Maths.M_PI_2 ); + + let cosHdgPlusPiO2 = Math.cos( this.hdg + Maths.M_PI_2 ); + let sinHdgPlusPiO2 = Math.sin( this.hdg + Maths.M_PI_2 ); + + this.x += cosHdgPlusPiO2 * ( offset ); + this.y += sinHdgPlusPiO2 * ( offset ); + + return this; + } + + clone ( x?: number, y?: number, hdg?: number, s?: number, t?: number ) { + + return new TvPosTheta( x || this.x, y || this.y, hdg || this.hdg, s || this.s, t || this.t ); + + } +} diff --git a/src/app/modules/tv-map/models/tv-road-lane-offset.ts b/src/app/modules/tv-map/models/tv-road-lane-offset.ts new file mode 100644 index 00000000..9af2f34b --- /dev/null +++ b/src/app/modules/tv-map/models/tv-road-lane-offset.ts @@ -0,0 +1,18 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { ThirdOrderPolynom } from './third-order-polynom'; +import { Object3D } from 'three'; + +export class TvRoadLaneOffset extends ThirdOrderPolynom { + + public mesh?: Object3D; + + clone ( s?: number ) { + + return new TvRoadLaneOffset( s || this.s, this.a, this.b, this.c, this.d ); + + } + +} \ No newline at end of file diff --git a/src/app/modules/tv-map/models/tv-road-lane-section-container.ts b/src/app/modules/tv-map/models/tv-road-lane-section-container.ts new file mode 100644 index 00000000..5de0c6a7 --- /dev/null +++ b/src/app/modules/tv-map/models/tv-road-lane-section-container.ts @@ -0,0 +1,9 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { TvLane } from './tv-lane'; + +export class TvRoadLaneSectionContainer { + public lane: TvLane[]; +} \ No newline at end of file diff --git a/src/app/modules/tv-map/models/tv-road-lane-section-lane-link.ts b/src/app/modules/tv-map/models/tv-road-lane-section-lane-link.ts new file mode 100644 index 00000000..6166ecb7 --- /dev/null +++ b/src/app/modules/tv-map/models/tv-road-lane-section-lane-link.ts @@ -0,0 +1,6 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +export class TvRoadLaneSectionLaneLink { +} \ No newline at end of file diff --git a/src/app/modules/tv-map/models/tv-road-lanes.spec.ts b/src/app/modules/tv-map/models/tv-road-lanes.spec.ts new file mode 100644 index 00000000..9736721f --- /dev/null +++ b/src/app/modules/tv-map/models/tv-road-lanes.spec.ts @@ -0,0 +1,49 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { TvRoad } from './tv-road.model'; +import { TvPosTheta } from './tv-pos-theta'; + +describe( 'OdRoadLanes', () => { + + let road: TvRoad; + let pose: TvPosTheta; + + beforeEach( () => { + + pose = new TvPosTheta(); + + road = new TvRoad( '', 30, 1, -1 ); + + road.addPlanView(); + + const s = 0; + const x = 0; + const y = 0; + const hdg = 0; + const length = 30; + + road.addGeometryLine( s, x, y, hdg, length ); + + } ); + + it( 'should give correct getLaneOffsetAt s', () => { + + road.addLaneSection( 0, true ); + road.lanes.addLaneOffsetRecord( 0, 0, 0, 0, 0 ); + road.lanes.addLaneOffsetRecord( 10, 1, 0, 0, 0 ); + road.lanes.addLaneOffsetRecord( 20, 2, 0, 0, 0 ); + + expect( road.lanes.getLaneOffsetValue( 0 ) ).toBe( 0 ); + expect( road.lanes.getLaneOffsetValue( 1 ) ).toBe( 0 ); + expect( road.lanes.getLaneOffsetValue( 10 ) ).toBe( 1 ); + expect( road.lanes.getLaneOffsetValue( 11 ) ).toBe( 1 ); + expect( road.lanes.getLaneOffsetValue( 20 ) ).toBe( 2 ); + expect( road.lanes.getLaneOffsetValue( 21 ) ).toBe( 2 ); + expect( road.lanes.getLaneOffsetValue( 30 ) ).toBe( 2 ); + + + } ); + +} ); diff --git a/src/app/modules/tv-map/models/tv-road-lanes.ts b/src/app/modules/tv-map/models/tv-road-lanes.ts new file mode 100644 index 00000000..e1a91abd --- /dev/null +++ b/src/app/modules/tv-map/models/tv-road-lanes.ts @@ -0,0 +1,146 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { TvRoadLaneOffset } from './tv-road-lane-offset'; +import { TvLaneSection } from './tv-lane-section'; +import { TvRoad } from './tv-road.model'; +import { TvUtils } from './tv-utils'; +import { isNullOrUndefined } from 'util'; + +export class TvRoadLanes { + + public laneSections: TvLaneSection[] = []; + + private laneOffsets: TvRoadLaneOffset[] = []; + + getLaneSections (): TvLaneSection[] { + return this.laneSections; + } + + getLaneOffsets (): TvRoadLaneOffset[] { + return this.laneOffsets; + } + + addLaneOffsetRecord ( s: number, a: number, b: number, c: number, d: number ): any { + + this.laneOffsets.push( new TvRoadLaneOffset( s, a, b, c, d ) ); + + this.laneOffsets.sort( ( a, b ) => a.s > b.s ? 1 : -1 ); + + } + + addLaneOffsetInstance ( laneOffset: TvRoadLaneOffset ): void { + + this.laneOffsets.push( laneOffset ); + + this.laneOffsets.sort( ( a, b ) => a.s > b.s ? 1 : -1 ); + } + + updateLaneOffsetValues ( roadLength: number ) { + + for ( let i = 0; i < this.laneOffsets.length && this.laneOffsets.length > 1; i++ ) { + + const current = this.laneOffsets[ i ]; + + let pp0, pp1, pd0, pd1, length; + + if ( ( i + 1 ) < this.laneOffsets.length ) { + + const next = this.laneOffsets[ i + 1 ]; + + // next s cannot be less than current so we need to clamp it + if ( next.s <= current.s ) { + + next.s = current.s + 0.1; + + } + + length = next.s - current.s; + + pp0 = current.a; // offset at start + pp1 = next.a; // offset at end + pd0 = current.b; // tangent at start + pd1 = next.b; // tangent at end + + } else { + + // take lane section length + length = roadLength; + + pp0 = current.a; // offset at start + pp1 = current.a; // offset at end + pd0 = current.b; // tangent at start + pd1 = current.b; // tangent at end + + } + + let a = pp0; + let b = pd0; + let c = ( -3 * pp0 ) + ( 3 * pp1 ) + ( -2 * pd0 ) + ( -1 * pd1 ); + let d = ( 2 * pp0 ) + ( -2 * pp1 ) + ( 1 * pd0 ) + ( 1 * pd1 ); + + b /= length; + c /= length * length; + d /= length * length * length; + + current.a = a; + current.b = b; + current.c = c; + current.d = d; + + } + + } + + computeLaneSectionEnd ( road: TvRoad ) { + + // Compute lastSCoordinate for all laneSections + for ( let i = 0; i < this.laneSections.length; i++ ) { + + const currentLaneSection = this.laneSections[ i ]; + + // by default it is equal to road lenght + let lastSCoordinate = 0; + + // if next laneSection exists lets use its sCoordinate + if ( i + 1 < this.laneSections.length ) { + + const nextLaneSection = this.laneSections[ i + 1 ]; + lastSCoordinate = nextLaneSection.attr_s; + + } else { + + + lastSCoordinate = road.length; + } + + currentLaneSection.lastSCoordinate = lastSCoordinate; + } + } + + getLaneOffsetValue ( s: number ): number { + + if ( isNullOrUndefined( s ) ) console.error( "s is undefined" ); + + let offset = 0; + + const hasEntry = this.getLaneOffsetEntryAt( s ); + + if ( hasEntry ) offset = hasEntry.getValue( s ); + + return offset; + } + + getLaneOffsetEntryAt ( s: number ): TvRoadLaneOffset { + + return TvUtils.checkIntervalArray( this.laneOffsets, s ); + + } + + getLaneSectionAt ( s: number ): TvLaneSection { + + return TvUtils.checkIntervalArray( this.laneSections, s ); + + } +} diff --git a/src/app/modules/tv-map/models/tv-road-link-child.ts b/src/app/modules/tv-map/models/tv-road-link-child.ts new file mode 100644 index 00000000..3960ec10 --- /dev/null +++ b/src/app/modules/tv-map/models/tv-road-link-child.ts @@ -0,0 +1,42 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { TvContactPoint } from './tv-common'; + +export class TvRoadLinkChild { + + public attr_elementType: string; + public attr_elementId: number; + public attr_contactPoint: TvContactPoint; + + constructor ( elementType: string, elementId: number, contactPoint: TvContactPoint ) { + this.attr_elementType = elementType; + this.attr_elementId = elementId; + this.attr_contactPoint = contactPoint; + } + + get elementType () { + return this.attr_elementType; + } + + set elementType ( value ) { + this.attr_elementType = value; + } + + get elementId () { + return this.attr_elementId; + } + + set elementId ( value ) { + this.attr_elementId = value; + } + + get contactPoint () { + return this.attr_contactPoint; + } + + set contactPoint ( value ) { + this.attr_contactPoint = value; + } +} diff --git a/src/app/modules/tv-map/models/tv-road-link-neighbor.ts b/src/app/modules/tv-map/models/tv-road-link-neighbor.ts new file mode 100644 index 00000000..2b8e6a7d --- /dev/null +++ b/src/app/modules/tv-map/models/tv-road-link-neighbor.ts @@ -0,0 +1,15 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +export class TvRoadLinkNeighbor { + public attr_side; + public attr_elementId; + public attr_direction; + + constructor ( side: string, elementId: string, direction: string ) { + this.attr_side = side; + this.attr_elementId = elementId; + this.attr_direction = direction; + } +} \ No newline at end of file diff --git a/src/app/modules/tv-map/models/tv-road-object.ts b/src/app/modules/tv-map/models/tv-road-object.ts new file mode 100644 index 00000000..60d0a4b5 --- /dev/null +++ b/src/app/modules/tv-map/models/tv-road-object.ts @@ -0,0 +1,452 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { + TvBridgeTypes, + TvColors, + TvOrientation, + TvParkingSpaceAccess, + TvParkingSpaceMarkingSides, + TvRoadMarkTypes, + TvTunnelTypes, + TvUserData +} from './tv-common'; +import { Mesh } from 'three'; + +export class TvObjectContainer { + public object: TvRoadObject[] = []; + public objectReference: TvRoadObjectReference[] = []; + public tunnel: TvRoadTunnel[] = []; + public bridge: TvRoadBridge[] = []; +} + +export class TvRoadObject { + + public attr_type: string; + public attr_name: string; + public attr_id: number; + public attr_s: number; + public attr_t: number; + public attr_zOffset: number; + public attr_validLength: number; + public attr_orientation: any; + public attr_length: number; + public attr_width: number; + public attr_radius: number; + public attr_height: number; + public attr_hdg: number; + public attr_pitch: number; + public attr_roll: number; + + public repeat: TvObjectRepeat[] = []; + public outline: TvObjectOutline; + public material: TvObjectMaterial; + public validity: TvLaneValidity[] = []; + public parkingSpace: TvParkingSpace; + public userData: TvUserData[] = []; + // public gameObject: Object3D; + private lastAddedRepeatObjectIndex: number; + + public mesh: Mesh; + + constructor ( + type: string, + name: string, + id: number, + s: number, + t: number, + zOffset: number, + validLength: number, + orientation: TvOrientation, + length: number = null, + width: number = null, + radius: number = null, + height: number = null, + hdg: number = null, + pitch: number = null, + roll: number = null + ) { + this.attr_type = type; + this.attr_name = name; + this.attr_id = id; + this.attr_s = s; + this.attr_t = t; + this.attr_zOffset = zOffset; + this.attr_validLength = validLength; + this.attr_orientation = orientation; + this.attr_length = length; + this.attr_width = width; + this.attr_radius = radius; + this.attr_height = height; + this.attr_hdg = hdg; + this.attr_pitch = pitch; + this.attr_roll = roll; + } + + get type (): string { + return this.attr_type; + } + + get name (): string { + return this.attr_name; + } + + get id (): number { + return this.attr_id; + } + + get s (): number { + return this.attr_s; + } + + get t (): number { + return this.attr_t; + } + + get zOffset (): number { + return this.attr_zOffset; + } + + get validLength (): number { + return this.attr_validLength; + } + + get orientation (): any { + return this.attr_orientation; + } + + get length (): number { + return this.attr_length; + } + + get width (): number { + return this.attr_width; + } + + get radius (): number { + return this.attr_radius; + } + + get height (): number { + return this.attr_height; + } + + get hdg (): number { + return this.attr_hdg; + } + + get pitch (): number { + return this.attr_pitch; + } + + get roll (): number { + return this.attr_roll; + } + + getRepeatCount (): number { + return this.repeat.length; + } + + getRepeatList (): TvObjectRepeat[] { + return this.repeat; + } + + getRepeat ( i: number ): TvObjectRepeat { + return this.repeat[ i ]; + } + + getValidityCount (): number { + return this.validity.length; + } + + getValidityList (): TvLaneValidity[] { + return this.validity; + } + + getValidity ( i: number ): TvLaneValidity { + return this.validity[ i ]; + } + + addRepeat ( + s: number, length: number, distance: number, tStart: number, tEnd: number, + widthStart: number, widthEnd: number, heightStart: number, heightEnd: number, + zOffsetStart: number, zOffsetEnd: number + ): void { + + this.repeat.push( + new TvObjectRepeat( s, length, distance, tStart, tEnd, widthStart, widthEnd, heightStart, heightEnd, zOffsetStart, zOffsetEnd ) + ); + + this.lastAddedRepeatObjectIndex = this.repeat.length - 1; + + } + + +} + +export class TvObjectOutline { + public cornerRoad: TvCornerRoad[] = []; + public cornerLocal: TvCornerLocal[] = []; + + getCornerLocal ( i: number ): TvCornerLocal { + return this.cornerLocal[ i ]; + } + + getCornerLocalCount (): number { + return this.cornerLocal.length; + } + + getCornerRoad ( i: number ): TvCornerRoad { + return this.cornerRoad[ i ]; + } + + getCornerRoadCount (): number { + return this.cornerRoad.length; + } +} + +export class TvParkingSpace { + public attr_access: TvParkingSpaceAccess; + public attr_restriction: string; + + // MAX 4 ENTRIES ALLOWED + public marking: TvParkingSpaceMarking[] = []; + + getMarkingCount (): number { + return this.marking.length; + } + + getMarkingList (): TvParkingSpaceMarking[] { + return this.marking; + } + + getMarking ( i: number ): TvParkingSpaceMarking { + return this.marking[ i ]; + } +} + +export class TvParkingSpaceMarking { + public attr_side: TvParkingSpaceMarkingSides; + public attr_type: TvRoadMarkTypes; + public attr_width: number; + public attr_color: TvColors; +} + +/** + * For objects like patches which are within the road surface (and, typically, coplanar to the surface) and + * which represent a local deviation from the standard road material, a description of the material + * properties is required. This description supercedes the one provided by the Road Material record and, + * again, is valid only within the outline of the parent road object. + */ +export class TvObjectMaterial { + public attr_surface: string; + public attr_friction: number; + public attr_roughness: number; +} + +/** + * Defines a corner point on the object’s outline in road co-ordinates.. + */ +export class TvCornerRoad { + public attr_s: number; + public attr_t: number; + public attr_dz: number; + public attr_height: number; +} + +/** + * Defines a corner point on the object’s outline relative to the object's pivot point in local u/v coordinates. + * The pivot point and the orientation of the object are given by the s/t/heading arguments + * of the entry. + */ +export class TvCornerLocal { + public attr_u: number; + public attr_v: number; + public attr_z: number; + + // height of the object at this corner + public attr_height: number; +} + +export class TvObjectRepeat { + + public attr_s: number; + public attr_length: number; + public attr_distance: number; + public attr_tStart: number; + public attr_tEnd: number; + public attr_widthStart: number; + public attr_widthEnd: number; + public attr_heightStart: number; + public attr_heightEnd: number; + public attr_zOffsetStart: number; + public attr_zOffsetEnd: number; + + constructor ( s: number, length: number, distance: number, tStart: number, tEnd: number, + widthStart: number, widthEnd: number, heightStart: number, heightEnd: number, + zOffsetStart: number, zOffsetEnd: number ) { + + this.attr_s = s; + this.attr_length = length; + this.attr_distance = distance; + this.attr_tStart = tStart; + this.attr_tEnd = tEnd; + this.attr_widthStart = widthStart; + this.attr_widthEnd = widthEnd; + this.attr_heightStart = heightStart; + this.attr_heightEnd = heightEnd; + this.attr_zOffsetStart = zOffsetStart; + this.attr_zOffsetEnd = zOffsetEnd; + + } + + get s (): number { + return this.attr_s; + } + + set s ( value: number ) { + this.attr_s = value; + } + + get length (): number { + return this.attr_length; + } + + set length ( value: number ) { + this.attr_length = value; + } + + get distance (): number { + return this.attr_distance; + } + + set distance ( value: number ) { + this.attr_distance = value; + } + + get tStart (): number { + return this.attr_tStart; + } + + set tStart ( value: number ) { + this.attr_tStart = value; + } + + get tEnd (): number { + return this.attr_tEnd; + } + + set tEnd ( value: number ) { + this.attr_tEnd = value; + } + + get widthStart (): number { + return this.attr_widthStart; + } + + set widthStart ( value: number ) { + this.attr_widthStart = value; + } + + get widthEnd (): number { + return this.attr_widthEnd; + } + + set widthEnd ( value: number ) { + this.attr_widthEnd = value; + } + + get heightStart (): number { + return this.attr_heightStart; + } + + set heightStart ( value: number ) { + this.attr_heightStart = value; + } + + get heightEnd (): number { + return this.attr_heightEnd; + } + + set heightEnd ( value: number ) { + this.attr_heightEnd = value; + } + + get zOffsetStart (): number { + return this.attr_zOffsetStart; + } + + set zOffsetStart ( value: number ) { + this.attr_zOffsetStart = value; + } + + get zOffsetEnd (): number { + return this.attr_zOffsetEnd; + } + + set zOffsetEnd ( value: number ) { + this.attr_zOffsetEnd = value; + } +} + +export class TvRoadObjectReference { + + public attr_s; + public attr_t; + public attr_id; + public attr_zOffset; + public attr_validLength; + public attr_orientation; + + public validity: TvLaneValidity[] = []; +} + +// The tunnel record is – like an object record – applied to the entire cross +// section of the road within the given range unless a lane validity record with +// further restrictions is provided as child record +export class TvRoadTunnel { + + public attr_s: number; + public attr_length: number; + public attr_name: string; + public attr_id: string; + public attr_type: TvTunnelTypes; + + // degree of artificial tunnel lighting + public attr_lighting: number; + + // degree of daylight intruding the tunnel + public attr_daylight: number; + + public validity: TvLaneValidity[] = []; + +} + +export class TvRoadBridge { + + public attr_s: number; + public attr_length: number; + public attr_name: string; + public attr_id: string; + public attr_type: TvBridgeTypes; + + public validity: TvLaneValidity[] = []; +} + +export class TvLaneValidity { + + // NOTE: For single-lane-validity of the object, provide identical values for fromLane and toLane. + + // minimum ID of the lanes for which the object is valid + public attr_fromLane: number; + + // maximum ID of the lanes for which the object is valid + public attr_toLane: number; + + constructor ( from: number, to: number ) { + this.attr_fromLane = from; + this.attr_toLane = to; + } +} + diff --git a/src/app/modules/tv-map/models/tv-road-sign.model.ts b/src/app/modules/tv-map/models/tv-road-sign.model.ts new file mode 100644 index 00000000..8cebdd9d --- /dev/null +++ b/src/app/modules/tv-map/models/tv-road-sign.model.ts @@ -0,0 +1,34 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { Material } from 'three'; + +export class TvRoadSign { + + public static readonly tag = "road-sign"; + + public static index = 0; + + public id: number; + + constructor ( + public name: string, + public material: Material, + public width: number = 1.0, + public height: number = 1.0, + public preserveAspectRatio = true, + public pixelsPerMeter = 256, + public roundedCorners = false, + public clampTexture = false + ) { + this.id = TvRoadSign.index++; + } + + toJSONString (): string { + + return JSON.stringify( this ); + + } + +} diff --git a/src/app/modules/tv-map/models/tv-road-signal.model.ts b/src/app/modules/tv-map/models/tv-road-signal.model.ts new file mode 100644 index 00000000..03e61722 --- /dev/null +++ b/src/app/modules/tv-map/models/tv-road-signal.model.ts @@ -0,0 +1,190 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { TvLaneValidity } from './tv-road-object'; +import { TvDynamicTypes, TvOrientation, TvUnit, TvUserData } from './tv-common'; +import { GameObject } from 'app/core/game-object'; +import { SignShapeType } from '../services/tv-sign.service'; +import { AnyControlPoint } from '../../three-js/objects/control-point'; + +export class TvRoadSignal { + + public s: number; + public t: number; + public id: number; + public name: string; + public dynamic: TvDynamicTypes; + public orientations: TvOrientation; + public zOffset: number; + public country: string; + public type: string; + public subtype: string; + public value: number; + public unit: TvUnit; + public height: number; + public width: number; + public text: string; + public hOffset: number; + public pitch: number; + public roll: number; + public validities: TvLaneValidity[] = []; + public dependencies: TvSignalDependency[] = []; + public signalReferences: TvSignalReference[] = []; + public roadId: number; + + public controlPoint?: AnyControlPoint; + + private _gameObject: GameObject; + private _userData: Map = new Map(); + private _signShape: SignShapeType; + + constructor ( + s: number, + t: number, + id: number, + name: string, + dynamic: TvDynamicTypes, + orientation: TvOrientation, + zOffset?: number, + country?: string, + type?: string, + subtype?: string, + value?: number, + unit?: TvUnit, + height?: number, + width?: number, + text?: string, + hOffset?: number, + pitch?: number, + roll?: number + ) { + + this.s = s; + this.t = t; + this.id = id; + this.name = name; + this.dynamic = dynamic; + this.orientations = orientation; + this.zOffset = zOffset; + this.country = country; + this.type = type; + this.subtype = subtype; + this.value = value; + this.unit = unit; + this.height = height; + this.width = width; + this.text = text; + this.hOffset = hOffset; + this.pitch = pitch; + this.roll = roll; + + } + + set userData ( values: TvUserData[] ) { + values.forEach( data => this._userData.set( data.attr_code, data ) ); + } + + get userDataMap () { + return this._userData; + } + + get assetName () { + return this._userData.get( 'asset_name' ); + } + + get signShape () { + return this._signShape; + } + + set signShape ( value ) { + this._signShape = value; + } + + get gameObject () { + return this._gameObject; + } + + set gameObject ( value ) { + this._gameObject = value; + } + + getUserData () { + return this._userData; + } + + addValidity ( fromLane: number, toLane: number ): void { + this.validities.push( new TvLaneValidity( fromLane, toLane ) ); + } + + getValidity ( index: number ): TvLaneValidity { + return this.validities[ index ]; + } + + getValidityCount (): number { + return this.validities.length; + } + + addDependency ( id: number, type: string ) { + this.dependencies.push( new TvSignalDependency( id, type ) ); + } + + getDependency ( index: number ): TvSignalDependency { + return this.dependencies[ index ]; + } + + getDependencyCount (): number { + return this.dependencies.length; + } + + getSignalReference ( index: number ): TvSignalReference { + return this.signalReferences[ index ]; + } + + getSignalReferenceCount (): number { + return this.signalReferences.length; + } + + addUserData ( key: string, value: string ) { + this._userData.set( key, new TvUserData( key, value ) ); + } +} + +/** + * The signal dependency record provides signals with a means to control other signals. Signs can e.g. + * restrict other signs for various types of vehicles, warning lights can be turned on when a traffic light + * goes red etc. The signal dependency record is an optional child record of the signal record. A signal + * may have multiple dependency records. + */ +export class TvSignalDependency { + public id: number; + public type: string; + + constructor ( id: number, type: string ) { + this.id = id; + this.type = type; + } + +} + +/** + * Depending on the way roads (especially in junctions) are laid out for different applications, it may be + * necessary to refer to the same (i.e. the identical) sign from multiple roads. In order to prevent + * inconsistencies by multiply defining an entire signal entry, the user only needs to define the complete + * signal entry once and can refer to this complete record by means of the signal reference record. + */ +export class TvSignalReference { + public s: number; + public t: number; + public id: number; + public orientations: TvOrientation; + public laneValidities: TvLaneValidity[] = []; + + getValidityCount (): number { + return this.laneValidities.length; + } + + getValidity ( i: number ): TvLaneValidity { + return this.laneValidities[ i ]; + } +} diff --git a/src/app/modules/tv-map/models/tv-road-type.class.ts b/src/app/modules/tv-map/models/tv-road-type.class.ts new file mode 100644 index 00000000..8e55ff6c --- /dev/null +++ b/src/app/modules/tv-map/models/tv-road-type.class.ts @@ -0,0 +1,76 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { TvRoadType, TvUnit } from './tv-common'; + + +export class TvRoadTypeClass { + + public readonly speed: TvRoadSpeed; + + constructor ( + public s: number, + public type: TvRoadType, + maxSpeed: number = 40, + speedUnit: TvUnit = TvUnit.MILES_PER_HOUR + ) { + this.speed = new TvRoadSpeed( maxSpeed, speedUnit ); + } + + static stringToTypes ( value ): TvRoadType { + + if ( value === 'unknown' ) { + + return TvRoadType.UNKNOWN; + + } else if ( value === 'rural' ) { + + return TvRoadType.RURAL; + + } else if ( value === 'motorway' ) { + + return TvRoadType.MOTORWAY; + + } else if ( value === 'town' ) { + + return TvRoadType.TOWN; + + } else if ( value === 'lowSpeed' ) { + + return TvRoadType.LOW_SPEED; + + } else if ( value === 'pedestrian' ) { + + return TvRoadType.PEDESTRIAN; + + } else if ( value === 'bicycle' ) { + + return TvRoadType.BICYCLE; + + } else { + + // console.error( "Unknown value for road type" ); + + return TvRoadType.UNKNOWN; + } + + } +} + +export class TvRoadSpeed { + + constructor ( + public max: number, + public unit: TvUnit + ) { + } + + inkmph (): number { + + console.error( 'converionf from mph to kmph not happening' ); + + return this.max; + + } +} diff --git a/src/app/modules/tv-map/models/tv-road.link.ts b/src/app/modules/tv-map/models/tv-road.link.ts new file mode 100644 index 00000000..6d46ddd3 --- /dev/null +++ b/src/app/modules/tv-map/models/tv-road.link.ts @@ -0,0 +1,24 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { TvRoadLinkChild } from './tv-road-link-child'; +import { TvRoadLinkNeighbor } from './tv-road-link-neighbor'; + +export class TvRoadLink { + public predecessor: TvRoadLinkChild; + public successor: TvRoadLinkChild; + public neighbor: TvRoadLinkNeighbor[] = []; + + getNeighbors (): TvRoadLinkNeighbor[] { + return this.neighbor; + } + + getNeighborCount (): number { + return this.neighbor.length; + } + + getNeighbour ( i: number ): TvRoadLinkNeighbor { + return this.neighbor[ i ]; + } +} \ No newline at end of file diff --git a/src/app/modules/tv-map/models/tv-road.model.spec.ts b/src/app/modules/tv-map/models/tv-road.model.spec.ts new file mode 100644 index 00000000..79c09b1f --- /dev/null +++ b/src/app/modules/tv-map/models/tv-road.model.spec.ts @@ -0,0 +1,101 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { TvRoad } from './tv-road.model'; +import { TvPosTheta } from './tv-pos-theta'; + +describe( 'OdRoad', () => { + + let road: TvRoad; + let pose: TvPosTheta; + + beforeEach( () => { + + pose = new TvPosTheta(); + + road = new TvRoad( '', 30, 1, -1 ); + + road.addPlanView(); + + // 3 straight road lines + road.addGeometryLine( 0, 0, 0, 0, 10 ); + road.addGeometryLine( 10, 0, 0, 1, 10 ); + road.addGeometryLine( 20, 0, 0, 2, 10 ); + + } ); + + it( 'should give correct geometry', () => { + + expect( road.getGeometryBlockCount() ).toBe( 3 ); + + expect( road.getGeometryBlock( 0 ).hdg ).toBe( 0 ); + expect( road.getGeometryBlock( 1 ).hdg ).toBe( 1 ); + expect( road.getGeometryBlock( 2 ).hdg ).toBe( 2 ); + + + } ); + + it( 'should give correct geometry index', () => { + + road.getGeometryCoords( 0, pose ); + expect( pose.hdg ).toBe( 0 ); + + road.getGeometryCoords( 10, pose ); + expect( pose.hdg ).toBe( 1 ); + + road.getGeometryCoords( 20, pose ); + expect( pose.hdg ).toBe( 2 ); + + } ); + + it( 'should give correct lane section', () => { + + road.addLaneSection( 0, false ); + road.addLaneSection( 10, false ); + road.addLaneSection( 20, false ); + + + expect( road.getLaneSectionAt( 0 ).s ).toBe( 0 ); + expect( road.getLaneSectionAt( 9 ).s ).toBe( 0 ); + expect( road.getLaneSectionAt( 10 ).s ).toBe( 10 ); + expect( road.getLaneSectionAt( 11 ).s ).toBe( 10 ); + expect( road.getLaneSectionAt( 20 ).s ).toBe( 20 ); + expect( road.getLaneSectionAt( 21 ).s ).toBe( 20 ); + expect( road.getLaneSectionAt( 30 ).s ).toBe( 20 ); + + } ); + + + it( 'should give correct lane offset', () => { + + road.addLaneOffset( 0, 0, 0, 0, 0 ); + road.addLaneOffset( 10, 10, 0, 0, 0 ); + road.addLaneOffset( 20, 20, 0, 0, 0 ); + + expect( road.getLaneOffsetAt( 0 ).a ).toBe( 0 ); + expect( road.getLaneOffsetAt( 1 ).a ).toBe( 0 ); + expect( road.getLaneOffsetAt( 9 ).a ).toBe( 0 ); + expect( road.getLaneOffsetAt( 10 ).a ).toBe( 10 ); + expect( road.getLaneOffsetAt( 11 ).a ).toBe( 10 ); + expect( road.getLaneOffsetAt( 20 ).a ).toBe( 20 ); + + } ); + + + it( 'should give correct lane offset value', () => { + + road.addLaneOffset( 0, 0, 0, 0, 0 ); + road.addLaneOffset( 10, 10, 0, 0, 0 ); + road.addLaneOffset( 20, 20, 0, 0, 0 ); + + expect( road.getLaneOffsetValue( 0 ) ).toBe( 0 ); + expect( road.getLaneOffsetValue( 1 ) ).toBe( 0 ); + expect( road.getLaneOffsetValue( 9 ) ).toBe( 0 ); + expect( road.getLaneOffsetValue( 10 ) ).toBe( 10 ); + expect( road.getLaneOffsetValue( 11 ) ).toBe( 10 ); + expect( road.getLaneOffsetValue( 20 ) ).toBe( 20 ); + + } ); + +} ); diff --git a/src/app/modules/tv-map/models/tv-road.model.ts b/src/app/modules/tv-map/models/tv-road.model.ts new file mode 100644 index 00000000..f75c6705 --- /dev/null +++ b/src/app/modules/tv-map/models/tv-road.model.ts @@ -0,0 +1,1199 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { TvLaneSection } from './tv-lane-section'; +import { TvRoadLanes } from './tv-road-lanes'; +import { TvPlaneView } from './tv-plane-view'; +import { TvAbstractRoadGeometry } from './geometries/tv-abstract-road-geometry'; +import { TvPosTheta } from './tv-pos-theta'; +import { TvObjectContainer, TvRoadObject } from './tv-road-object'; +import { TvRoadSignal } from './tv-road-signal.model'; +import { TvContactPoint, TvDynamicTypes, TvOrientation, TvRoadType, TvUnit } from './tv-common'; +import { GameObject } from 'app/core/game-object'; +import { TvRoadLinkChild } from './tv-road-link-child'; +import { TvRoadLinkNeighbor } from './tv-road-link-neighbor'; +import { TvRoadLink } from './tv-road.link'; +import { TvElevation } from './tv-elevation'; +import { TvElevationProfile } from './tv-elevation-profile'; +import { TvLateralProfile } from './tv-lateral.profile'; +import { TvLane } from './tv-lane'; +import { TvJunctionConnection } from './tv-junction-connection'; +import { TvLineGeometry } from './geometries/tv-line-geometry'; +import { TvArcGeometry } from './geometries/tv-arc-geometry'; +import { TvUtils } from './tv-utils'; +import { TvRoadTypeClass } from './tv-road-type.class'; +import { SnackBar } from 'app/services/snack-bar.service'; +import { AbstractSpline } from 'app/core/shapes/abstract-spline'; +import { AutoSpline } from 'app/core/shapes/auto-spline'; +import { TvRoadLaneOffset } from './tv-road-lane-offset'; +import { RoadNode } from 'app/modules/three-js/objects/road-node'; +import { Maths } from 'app/utils/maths'; +import { RoadControlPoint } from 'app/modules/three-js/objects/road-control-point'; +import { Vector3, Math as MathUtils } from 'three'; +import { SceneService } from 'app/core/services/scene.service'; +import { EventEmitter } from '@angular/core'; + +export class TvRoad { + + public readonly uuid: string; + + public updated = new EventEmitter(); + + // auto will be the default spline for now + public spline: AbstractSpline; + + public startNode: RoadNode; + public endNode: RoadNode; + + public type: TvRoadTypeClass[] = []; + public elevationProfile: TvElevationProfile = new TvElevationProfile; + public lateralProfile: TvLateralProfile; + public lanes = new TvRoadLanes(); + + public drivingMaterialGuid: string; + public sidewalkMaterialGuid: string; + public borderMaterialGuid: string; + public shoulderMaterialGuid: string; + + /** + * @deprecated use predecessor, successor directly + */ + private link: TvRoadLink; + + private _objects: TvObjectContainer = new TvObjectContainer(); + private _signals: Map = new Map(); + private _planView = new TvPlaneView; + private _predecessor: TvRoadLinkChild; + private _successor: TvRoadLinkChild; + private _neighbors: TvRoadLinkNeighbor[] = []; + private _name: string; + private _length: number; + private _id: number; + private _junction: number; + private _gameObject: GameObject; + private lastAddedLaneSectionIndex: number; + private lastAddedRoadObjectIndex: number; + private lastAddedRoadSignalIndex: number; + + constructor ( name: string, length: number, id: number, junction: number ) { + + this.uuid = MathUtils.generateUUID(); + this._name = name; + this._length = length; + this._id = id; + this._junction = junction; + + this.spline = new AutoSpline( this ); + + } + + get signals (): Map { + return this._signals; + } + + set signals ( value: Map ) { + this._signals = value; + } + + get objects (): TvObjectContainer { + return this._objects; + } + + set objects ( value: TvObjectContainer ) { + this._objects = value; + } + + get planView (): TvPlaneView { + return this._planView; + } + + set planView ( value: TvPlaneView ) { + this._planView = value; + } + + get neighbors (): TvRoadLinkNeighbor[] { + return this._neighbors; + } + + set neighbors ( value: TvRoadLinkNeighbor[] ) { + this._neighbors = value; + } + + get successor (): TvRoadLinkChild { + return this._successor; + } + + set successor ( value: TvRoadLinkChild ) { + this._successor = value; + } + + get predecessor (): TvRoadLinkChild { + return this._predecessor; + } + + set predecessor ( value: TvRoadLinkChild ) { + this._predecessor = value; + } + + get junction (): number { + return this._junction; + } + + set junction ( value: number ) { + this._junction = value; + } + + get id (): number { + return this._id; + } + + set id ( value: number ) { + this._id = value; + } + + get length (): number { + return this._length; + } + + set length ( value: number ) { + this._length = value; + } + + get name (): string { + return this._name; + } + + set name ( value: string ) { + this._name = value; + } + + get gameObject () { + return this._gameObject; + } + + set gameObject ( value ) { + this._gameObject = value; + } + + get isJunction (): boolean { + return this._junction !== -1; + } + + get geometries () { + return this._planView.geometries; + } + + get laneSections () { + return this.lanes.laneSections; + } + + get hasType (): boolean { return this.type.length > 0 } + + onSuccessorUpdated ( successor: TvRoad ) { + + console.log( "successor of", this.id, "updated" ); + + } + + onPredecessorUpdated ( predecessor: TvRoad ) { + + console.log( "predecssor of", this.id, "updated" ); + + } + + setPredecessor ( elementType: string, elementId: number, contactPoint?: TvContactPoint ) { + + if ( this._predecessor == null ) { + + this._predecessor = new TvRoadLinkChild( elementType, elementId, contactPoint ); + + } + } + + getPositionAt ( s: number, t: number = 0 ): TvPosTheta { + + const pose = new TvPosTheta; + + this.getGeometryCoordsAt( s, t, pose ); + + return pose; + } + + getRoadPosition ( s: number ) { + + return this.getPositionAt( s, 0 ); + + } + + endPosition () { + + return this.getRoadPosition( this.length - Maths.Epsilon ); + + } + + endCoord () { + + return this.getPositionAt( this.length - Maths.Epsilon, 0 ); + + } + + startPosition () { + + return this.getRoadPosition( 0 ); + + } + + setType ( type: TvRoadType, maxSpeed: number = 40, unit: TvUnit = TvUnit.MILES_PER_HOUR ) { + + this.type.push( new TvRoadTypeClass( 0, type, maxSpeed, unit ) ); + + } + + setSuccessor ( elementType: string, elementId: number, contactPoint?: TvContactPoint ) { + + if ( this._successor == null ) { + + this._successor = new TvRoadLinkChild( elementType, elementId, contactPoint ); + + } + } + + setNeighbor ( side: string, elementId: string, direction: string ) { + + console.error( "neighbor not supported" ); + + // const neighbor = new OdRoadLinkNeighbor( side, elementId, direction ); + // + // this.link.neighbor.push( neighbor ); + // + // if ( this.neighbor1 === null ) { + // + // this.neighbor1 = neighbor; + // + // } + } + + getPlanView (): TvPlaneView { + + return this._planView; + + } + + addPlanView () { + + if ( this._planView == null ) { + + this._planView = new TvPlaneView(); + + } + } + + addElevation ( s: number, a: number, b: number, c: number, d: number ) { + + const index = this.checkElevationInterval( s ) + 1; + + if ( index > this.getElevationCount() ) { + + this.elevationProfile.elevation.push( new TvElevation( s, a, b, c, d ) ); + + } else { + + this.elevationProfile.elevation[ index ] = new TvElevation( s, a, b, c, d ); + + } + } + + checkElevationInterval ( s: number ): number { + + let res = -1; + + // Go through all the road type records + for ( let i = 0; i < this.elevationProfile.elevation.length; i++ ) { + + if ( this.elevationProfile.elevation[ i ].checkInterval( s ) ) { + + res = i; + + } else { + + break; + + } + } + + // return the result: 0 to MaxInt as the index to the + // record containing s_check or -1 if nothing found + return res; + } + + getElevationCount () { + + return this.elevationProfile.elevation.length; + + } + + getElevationValue ( s: number ) { + + const elevation = this.getElevationAt( s ); + + if ( elevation == null ) return 0; + + // console.log( value ); + + return elevation.getValue( s ); + } + + checkSuperElevationInterval ( s: number ) { + // TODO + } + + checkCrossfallInterval ( s: number ) { + // TODO + } + + addElevationProfile () { + + if ( this.elevationProfile == null ) { + + this.elevationProfile = new TvElevationProfile(); + + } + } + + /** + * + * @param s + * @param singleSide + * @deprecated use addGetLaneSection + */ + addLaneSection ( s: number, singleSide: boolean ) { + + // TODO: Check for interval + + // this.lanes = new OdRoadLanes(); + + const laneSectionId = this.lanes.laneSections.length + 1; + + this.lanes.laneSections.push( new TvLaneSection( laneSectionId, s, singleSide, this.id ) ); + + this.updateLaneSections(); + + this.lastAddedLaneSectionIndex = this.lanes.laneSections.length - 1; + + return this.lastAddedLaneSectionIndex; + } + + addLaneSectionInstance ( laneSection: TvLaneSection ) { + + laneSection.roadId = this.id; + + laneSection.lanes.forEach( lane => { + + lane.roadId = this.id; + + lane.laneSectionId = laneSection.id; + + } ); + + this.laneSections.push( laneSection ); + + } + + clearLaneSections () { + + this.laneSections.splice( 0, this.laneSections.length ); + + } + + addGetLaneSection ( s: number, singleSide: boolean = false ): TvLaneSection { + + const laneSectionId = this.lanes.laneSections.length + 1; + + const laneSection = new TvLaneSection( laneSectionId, s, singleSide, this.id ); + + this.lanes.laneSections.push( laneSection ); + + this.updateLaneSections(); + + this.lastAddedLaneSectionIndex = this.lanes.laneSections.length - 1; + + return laneSection; + } + + getLaneSectionCount () { + + return this.lanes.laneSections.length; + + } + + getFirstLaneSection () { + + return this.laneSections[ 0 ]; + + } + + getLastLaneSection () { + + return this.laneSections[ this.laneSections.length - 1 ]; + + } + + getLaneSection ( i: number ) { + + return this.lanes.laneSections[ i ]; + + } + + getLaneSectionLength ( section: TvLaneSection ) { + + // find next section higher than requested section + const next = this.laneSections.find( s => s.s > section.s ); + + return next ? + next.s - section.s : + this.length - section.s; + } + + getLastAddedLaneSection () { + + return this.lanes.laneSections[ this.lastAddedLaneSectionIndex ]; + + } + + getTypes (): TvRoadTypeClass[] { + + return this.type; + + } + + addSignal ( signal: TvRoadSignal ): void { + + this._signals.set( signal.id, signal ); + + } + + removeSignal ( signal: TvRoadSignal ): any { + + this.removeSignalById( signal.id ); + + } + + removeSignalById ( signalId: number ): boolean { + + return this.signals.delete( signalId ); + + } + + getRoadSignalCount (): number { + + return this._signals.size; + + } + + getRoadSignal ( id: number ) { + + return this._signals.get( id ); + + } + + getRoadSignalById ( id: number ): TvRoadSignal { + + return this._signals.get( id ); + + } + + getRoadObjects (): TvRoadObject[] { + + return this._objects.object; + + } + + getRoadObject ( i: number ): TvRoadObject { + + return this._objects.object[ i ]; + + } + + getRoadObjectCount (): number { + + return this._objects.object.length; + + } + + getElevationProfile (): TvElevationProfile { + + return this.elevationProfile; + + } + + getLaneSections (): TvLaneSection[] { + + return this.lanes.laneSections; + + } + + getLanes (): TvRoadLanes { + + return this.lanes; + + } + + recalculateGeometry () { + + // Goes through geometry blocks and recalculates their coordinates and + // headings starting with the second record + // so the second geometry will start at the coordinates where the first one ended + + let length = 0; + const lGeometryVectorSize = this._planView.geometries.length; + + if ( lGeometryVectorSize > 0 ) { + + const s = 0; + const posTheta = new TvPosTheta( 0, 0, 0 ); + + const abstractRoadGeometry = this._planView.geometries[ 0 ]; + + length += this._planView.getBlockLength(); + + abstractRoadGeometry.getCoords( s, posTheta ); + } + } + + getGeometryCoords ( s: number, odPosTheta: TvPosTheta ): number { + + if ( s == null || s == undefined ) console.error( 's is undefined' ); + + if ( s > this.length || s < 0 ) console.warn( 's is greater than road length or less than 0' ); + + const geometry = this.getGeometryAt( s ); + + if ( geometry == null ) + throw new Error( `geometry not found at s = ${ s }` ); + + const geometryType = geometry.getCoords( s, odPosTheta ); + + if ( !geometryType ) console.error( "geometry type not found" ); + + const laneOffset = this.getLaneOffsetValue( s ); + + odPosTheta.addLateralOffset( laneOffset ); + + return geometryType; + + // const index = this.checkGeometryInterval( sCheck ); + // + // if ( index === -999 ) { + // throw new Error( 'geometry index not found ' ); + // } + // + // // Check the block and get coords. + // const res = this.planView.geometries[ index ].getCoords( sCheck, odPosTheta ); + // + // const laneOffset = this.lanes.getLaneOffsetValue( sCheck ); + // + // odPosTheta.addLateralOffset( laneOffset ); + // + // // If the returned value is one of the geometry types (for 0=line,1=arc and 2=spiral) + // // then the result has been found and parameters filled, so, return the value + // if ( res > 0 ) { + // return res; + // } + // + // // if s_check does not belong to the road, return -999 + // return -999; + } + + getGeometryCoordsAt ( sCheck, t, odPosTheta: TvPosTheta ): number { + + const res = this.getGeometryCoords( sCheck, odPosTheta ); + + odPosTheta.addLateralOffset( t ); + + // If the returned value is one of the geometry types (for 0=line,1=arc and 2=spiral) + // then the result has been found and parameters filled, so, return the value + if ( res > 0 ) { + return res; + } + + // if s_check does not belong to the road, return -999 + return -999; + } + + getGeometryBlockCount (): number { + + return this._planView.geometries.length; + + } + + getGeometryBlock ( i: number ): TvAbstractRoadGeometry { + + return this._planView.geometries[ i ]; + + } + + getRoadLength () { + + return this._length; + + } + + // TODO: Fix this + getSuperElevationValue ( s: number ): number { + + return null; + + } + + // TODO: Fix this + getCrossfallValue ( s: number, angleLeft: number, angleRight: number ): number { + + return null; + + } + + // fillLaneSectionSample ( s: number, laneSectionSample: OdLaneSectionSample ) { + // + // const index = this.checkLaneSectionInterval( s ); + // + // if ( index >= 0 ) { + // + // this.lanes.laneSection[ index ].fillLaneSectionSample( s, laneSectionSample ); + // + // } + // } + + addRoadSignal ( + s: number, + t: number, + id: number, + name: string, + dynamic: TvDynamicTypes, + orientation: TvOrientation, + zOffset: number, + country: string, + type: string, + subtype: string, + value: number, + unit: TvUnit, + height: number, + width: number, + text: string, + hOffset: number, + pitch: number, + roll: number + ) { + + const signal = new TvRoadSignal( + s, t, id, name, + dynamic, orientation, zOffset, + country, type, subtype, + value, unit, + height, width, text, + hOffset, pitch, roll + ); + + signal.roadId = this.id; + + this._signals.set( id, signal ); + + return signal; + } + + getLastAddedRoadObject (): TvRoadObject { + return this._objects.object[ this.lastAddedRoadObjectIndex ]; + } + + addRoadObject ( + type: string, + name: string, + id: number, + s: number, + t: number, + zOffset: number, + validLength: number, + orientation: TvOrientation, + length: number, + width: number, + radius: number, + height: number, + hdg: number, + pitch: number, + roll: number + ): TvRoadObject { + + const obj = new TvRoadObject( + type, + name, + id, + s, + t, + zOffset, + validLength, + orientation, + length, + width, + radius, + height, + hdg, + pitch, + roll + ); + + this.addRoadObjectInstance( obj ); + + return obj; + } + + addRoadObjectInstance ( roadObject: TvRoadObject ) { + + this._objects.object.push( roadObject ); + + this.lastAddedRoadObjectIndex = this._objects.object.length - 1; + } + + removeRoadObjectById ( id: number ) { + + for ( let i = 0; i < this._objects.object.length; i++ ) { + + const element = this._objects.object[ i ]; + + if ( element.attr_id == id ) { + + this._objects.object.splice( i, 1 ); + break; + + } + } + } + + getLaneWidth ( sCoordinate: number, laneId: number ): number { + + // TODO: Fix lanesection finding + const laneSection = this.lanes.getLaneSectionAt( sCoordinate ); + + const lane = laneSection.getLaneById( laneId ); + + return lane.getWidthValue( sCoordinate ); + } + + getLaneSectionAt ( s: number ): TvLaneSection { + + return this.lanes.getLaneSectionAt( s ); + + } + + // todo move this lanes + updateLaneOffsetValues (): void { + + this.lanes.updateLaneOffsetValues( this.length ); + + } + + addLaneOffset ( s: number, a: number, b: number, c: number, d: number ) { + + this.lanes.addLaneOffsetRecord( s, a, b, c, d ); + + } + + addLaneOffsetInstance ( laneOffset: TvRoadLaneOffset ): void { + + this.lanes.addLaneOffsetInstance( laneOffset ); + + this.lanes.updateLaneOffsetValues( this.length ); + + } + + removeLaneOffset ( laneOffset: TvRoadLaneOffset ): void { + + const index = this.lanes.getLaneOffsets().findIndex( i => i.uuid === laneOffset.uuid ); + + if ( index !== -1 ) { + + this.lanes.getLaneOffsets().splice( index, 1 ); + + } + + this.lanes.updateLaneOffsetValues( this.length ); + } + + + getLaneOffsetAt ( s: number ) { + + return this.lanes.getLaneOffsetEntryAt( s ); + + } + + getLaneOffsets () { + + return this.lanes.getLaneOffsets(); + + } + + getLaneOffsetValue ( s: number ): number { + + return this.lanes.getLaneOffsetValue( s ); + + } + + getLaneSectionById ( id: number ) { + + return this.lanes.laneSections.find( laneSection => { + + return laneSection.id === id; + + } ); + + } + + getWidthUptoStart ( s: number, lane: TvLane ) { + + const laneSection = this.getLaneSectionAt( s ); + + return laneSection.getWidthUptoStart( lane, s ); + + } + + getWidthUptoCenter ( s: number, lane: TvLane ) { + + const laneSection = this.getLaneSectionAt( s ); + + return laneSection.getWidthUptoCenter( lane, s ); + + } + + getWidthUptoEnd ( s: number, lane: TvLane ) { + + const laneSection = this.getLaneSectionAt( s ); + + return laneSection.getWidthUptoEnd( lane, s ); + + } + + getSuccessorRoad ( connection: TvJunctionConnection ) { + + if ( this._successor.elementType == 'road' ) { + + } else if ( this._successor.elementType == 'junction' ) { + + } else { + + } + + } + + addGeometry ( geometry: TvAbstractRoadGeometry ) { + + if ( !this.planView ) this.addPlanView(); + + this.geometries.push( geometry ); + + this.length += geometry.length; + + this.updateLaneSections(); + } + + addGeometryLine ( s: number, x: number, y: number, hdg: number, length: number ): TvLineGeometry { + + this.length += length; + + this.updateLaneSections(); + + return this._planView.addGeometryLine( s, x, y, hdg, length ); + + } + + addGeometryArc ( s: number, x: number, y: number, hdg: number, length: number, curvature: number ): TvArcGeometry { + + this.length += length; + + this.updateLaneSections(); + + return this._planView.addGeometryArc( s, x, y, hdg, length, curvature ); + + } + + addGeometryParamPoly ( + s: number, x: number, y: number, hdg: number, length: number, + aU: number, bU: number, cU: number, dU: number, + aV: number, bV: number, cV: number, dV: number + ) { + + this.length += length; + + this.updateLaneSections(); + + return this._planView.addGeometryParamPoly3( s, x, y, hdg, length, aU, bU, cU, dU, aV, bV, cV, dV ); + + } + + addGeometryPoly ( s: number, x: number, y: number, hdg: number, length: number, a: number, b: number, c: number, d: number ) { + + this.length += length; + + this.updateLaneSections(); + + return this._planView.addGeometryPoly3( s, x, y, hdg, length, a, b, c, d ); + + } + + clearGeometries () { + + this.geometries.splice( 0, this.geometries.length ); + + this.length = 0; + + this.updateLaneSections(); + + } + + public removeGeometryByUUID ( uuid: string ) { + + this.length += length; + + // find the index of geometry and remove it from the road + this.geometries.splice( this.geometries.findIndex( geom => geom.uuid === uuid ), 1 ); + + } + + public getRoadTypeAt ( s: number ): TvRoadTypeClass { + + if ( !this.hasType ) return; + + return TvUtils.checkIntervalArray( this.type, s ) as TvRoadTypeClass; + } + + public findMaxSpeedAt ( s: number, laneId?: number ) { + + let maxSpeed = null; + + // get max-speed as per road + const type = TvUtils.checkIntervalArray( this.type, s ) as TvRoadTypeClass; + + const maxSpeedAsPerRoad = type ? type.speed.inkmph() : Number.POSITIVE_INFINITY; + + // check if lane-speed record exists + if ( laneId ) { + + // const laneSpeedRecord = this.getLaneSectionAt( s ).getLaneById( laneId ).getLaneSpeedAt( s ); + const laneSpeedRecord = Number.MAX_VALUE; + + maxSpeed = Math.min( maxSpeedAsPerRoad, laneSpeedRecord ); + + } else { + + maxSpeed = maxSpeedAsPerRoad; + + } + + return maxSpeed; + } + + // private checkGeometryInterval ( sCheck: any ) { + // + // let index = -999; + // + // for ( let i = 0; i < this._planView.geometries.length; i++ ) { + // + // const odGeometry = this._planView.geometries[ i ]; + // + // if ( odGeometry.s <= sCheck ) { + // index = i; + // } + // + // // if ( ( sCheck >= odGeometry.s ) && ( sCheck <= odGeometry.s2 ) ) { + // // return i; + // // } + // } + // + // return index; + // } + + // private checkLaneSectionInterval ( s: number ) { + // + // let res = -1; + // + // for ( let i = 0; i < this.lanes.laneSection.length; i++ ) { + // + // // check if the s belongs to the current record + // if ( this.lanes.laneSection[ i ].checkInterval( s ) ) { + // + // res = i; + // + // } else { + // + // break; + // + // } + // + // } + // + // return res; + // + // } + + public getGeometryAt ( s: number ): TvAbstractRoadGeometry { + + return TvUtils.checkIntervalArray( this.geometries, s ); + + } + + /** + * Remove any existing road model from the scene and its children + */ + public remove ( parent: GameObject ) { + + if ( this.spline ) this.spline.hide(); + + parent.remove( this.gameObject ); + + this.laneSections.forEach( laneSection => { + + if ( this.gameObject ) this.gameObject.remove( laneSection.gameObject ); + + if ( this.gameObject ) laneSection.lanes.forEach( lane => laneSection.gameObject.remove( lane.gameObject ) ); + + } ); + } + + /** + * @deprecated currently not working need to fix + * @param s s-coordinate + */ + public split ( s: number ) { + + // TODO: not working, fix and complete + + const newRoad: TvRoad = new TvRoad( "New", 0, 0, -1 ); + + // divide geometry and clone other sections + + const geometry = this.getGeometryAt( s ); + + const laneSection = this.getLaneSectionAt( s ); + + const newLength = this.length - s; + + const posTheta = new TvPosTheta(); + + geometry.getCoords( s, posTheta ); + + if ( geometry instanceof TvLineGeometry ) { + + const newG = new TvLineGeometry( s, posTheta.x, posTheta.y, posTheta.hdg, newLength ); + + } else if ( geometry instanceof TvArcGeometry ) { + + const newG = new TvArcGeometry( s, posTheta.x, posTheta.y, posTheta.hdg, newLength, geometry.curvature ); + + } else { + + SnackBar.error( 'Cannot split this geometry' ); + + } + + // divide laneSection + + const newLaneSection = newRoad.addLaneSection( 0, false ); + + // const laneSectionsAfterS = + + } + + showNodes (): any { + + if ( this.startNode ) this.startNode.visible = true; + if ( this.endNode ) this.endNode.visible = true; + + } + + hideNodes (): void { + + if ( this.startNode ) this.startNode.visible = false; + if ( this.endNode ) this.endNode.visible = false; + + } + + addControlPoint ( point: RoadControlPoint ) { + + this.spline.addControlPoint( point ); + + SceneService.add( point ); + } + + addControlPointAt ( position: Vector3 ) { + + this.addControlPoint( new RoadControlPoint( this, position, 'cp', 0, 0 ) ); + + } + + updateGeometryFromSpline () { + + // make length 0 because geometry will update road length again + this.length = 0; + + this.spline.update(); + + this.clearGeometries(); + + this.spline.exportGeometries().forEach( geometry => { + + this.addGeometry( geometry ); + + } ); + + this.updated.emit( this ); + } + + getLeftSideWidth ( s: number ) { + + let width = 0; + + this.getLaneSectionAt( s ).getLeftLanes().forEach( lane => { + width += lane.getWidthValue( s ); + } ); + + return width; + } + + getRightsideWidth ( s: number ) { + + let width = 0; + + this.getLaneSectionAt( s ).getRightLanes().forEach( lane => { + width += lane.getWidthValue( s ); + } ); + + return width; + + } + + private updateLaneSections () { + + const sections = this.getLaneSections(); + + if ( sections.length == 0 ) return; + + // update first, not required + // if ( sections.length == 1 ) sections[ 0 ].length = this.length; + + for ( let i = 1; i < sections.length; i++ ) { + + const current = sections[ i ]; + const previous = sections[ i - 1 ]; + + previous.length = current.s - previous.s; + } + + // update last + sections[ sections.length - 1 ].length = this.length - sections[ sections.length - 1 ].s + } + + private getElevationAt ( s: number ): TvElevation { + + return TvUtils.checkIntervalArray( this.elevationProfile.elevation, s ); + + } + +} diff --git a/src/app/modules/tv-map/models/tv-surface.model.ts b/src/app/modules/tv-map/models/tv-surface.model.ts new file mode 100644 index 00000000..07880406 --- /dev/null +++ b/src/app/modules/tv-map/models/tv-surface.model.ts @@ -0,0 +1,151 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { CatmullRomSpline } from 'app/core/shapes/catmull-rom-spline'; +import * as THREE from 'three'; +import { Mesh, Shape, ShapeBufferGeometry, Vector2 } from 'three'; +import { OdTextures } from '../builders/od.textures'; +import { SceneService } from 'app/core/services/scene.service'; +import { TvMapSourceFile } from '../services/tv-map-source-file'; +import { GameObject } from 'app/core/game-object'; + +export class TvSurface { + + public static readonly tag = 'surface'; + + public static index = 0; + + public mesh: Mesh; + + public id: number; + + public textureDensity = 100; + + constructor ( + public materialGuid: string, + public spline: CatmullRomSpline, + public offset: Vector2 = new Vector2( 0, 0 ), + public scale: Vector2 = new Vector2( 1, 1 ), + public rotation: number = 0.0, + public height: number = 0.0 + ) { + + this.init(); + } + + init (): void { + + this.id = TvSurface.index++; + + // make a blank shape to avoid any errors + this.mesh = this.makeMesh( new Shape() ); + + // TODO: we can probably avoid doing this here + // add the surface mesh to opendrive object to make it available + // for exporting in 3d format easily + TvMapSourceFile.openDrive.gameObject.add( this.mesh ); + + // add the spline mesh direcly to scene and not opendrive + // this helps avoid exporting it in the 3d file + SceneService.add( this.spline.mesh ); + + // update the surface if >=3 points are present + if ( this.spline.controlPoints.length > 2 ) this.update(); + + } + + update (): void { + + this.spline.update(); + + if ( this.spline.controlPoints.length < 3 ) return; + + const points: Vector2[] = this.spline.curve.getPoints( 50 ).map( + p => new Vector2( p.x, p.y ) + ); + + const shape = new Shape(); + + const first = points.shift(); + + shape.moveTo( first.x, first.y ); + + shape.splineThru( points ); + + this.mesh.geometry.dispose(); + + this.mesh.geometry = new ShapeBufferGeometry( shape ); + + const uvAttribute = this.mesh.geometry.attributes.uv; + + for ( let i = 0; i < uvAttribute.count; i++ ) { + + const u = uvAttribute.getX( i ); + const v = uvAttribute.getY( i ); + + uvAttribute.setXY( i, u * this.textureDensity, v * this.textureDensity ); + + } + } + + makeMesh ( shape: Shape ): Mesh { + + const geometry = new ShapeBufferGeometry( shape ); + + const texture = OdTextures.terrain.clone(); + texture.wrapS = texture.wrapT = THREE.RepeatWrapping; + texture.repeat.set( 0.008, 0.008 ); + texture.anisotropy = 16; + texture.encoding = THREE.sRGBEncoding; + + const groundMaterial = new THREE.MeshLambertMaterial( { map: texture } ); + + const mesh = new GameObject( "Surface", geometry, groundMaterial ); + + mesh.position.set( 0, 0, -0.1 ); + + mesh.Tag = TvSurface.tag; + + mesh.userData.surface = this; + + groundMaterial.map.needsUpdate = true; + + return mesh; + } + + showCurve (): void { + + this.spline.show(); + + } + + hideCurve (): void { + + this.spline.hide(); + + } + + showControlPoints (): void { + + this.spline.showcontrolPoints(); + + } + + hideControlPoints (): void { + + this.spline.hidecontrolPoints(); + + } + + delete (): void { + + this.hideControlPoints(); + + this.hideCurve(); + + this.mesh.visible = false; + + } + +} diff --git a/src/app/modules/tv-map/models/tv-utils.ts b/src/app/modules/tv-map/models/tv-utils.ts new file mode 100644 index 00000000..1f254480 --- /dev/null +++ b/src/app/modules/tv-map/models/tv-utils.ts @@ -0,0 +1,77 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { ThirdOrderPolynom } from './third-order-polynom'; + +export class TvUtils { + + static checkInterval ( items: Map, s: number, sort: boolean = false ): ThirdOrderPolynom { + + let array = []; + + if ( sort ) { + + const inDescOrder = ( a, b ) => a[ 0 ] > b[ 0 ] ? -1 : 1; + + array = [ ...items.entries() ].sort( inDescOrder ); + + } else { + + array = Array.from( items.entries() ); + + } + + const checkInterval = ( a, b ) => a[ 0 ] > s ? -1 : 1; + + return array.sort( checkInterval ).pop(); + } + + static checkIntervalArray ( items: { s: number }[], s: number ): any { + + let result = null; + + for ( let i = 0; i < items.length; i++ ) { + + const item = items[ i ]; + + if ( s >= item.s ) result = item; + + } + + return result; + + // less efficient map copy method + // const array = Array.from( items.entries() ); + // + // // filter items that are equal and greater than the given s + // const checkInterval = ( a ) => a[ 0 ] >= s ? -1 : 1; + // + // try { + // + // // get the last item from the list + // return array.filter( checkInterval ).pop()[ 1 ]; + // + // } catch ( e ) { + // + // console.error( e ); + // console.error( items, s ); + // + // } + + } + + static getRandomMapItem ( map: Map ): any { + + let items = Array.from( map ); + + return items[ Math.floor( Math.random() * items.length ) ][ 1 ]; + + } + + static getRandomArrayItem ( items: any[] ): any { + + return items[ Math.floor( Math.random() * items.length ) ]; + + } +} diff --git a/src/app/modules/tv-map/models/vertex.ts b/src/app/modules/tv-map/models/vertex.ts new file mode 100644 index 00000000..76aea0da --- /dev/null +++ b/src/app/modules/tv-map/models/vertex.ts @@ -0,0 +1,12 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { Vector2, Vector3, Vector4 } from 'three'; + +export class Vertex { + public Position: Vector3; + public Normal = new Vector3( 0, 0, 1 ); + public Color = new Vector4( 1, 1, 1, 1 ); + public TexCoord: Vector2; +} diff --git a/src/app/modules/tv-map/queries/tv-map-queries.spec.ts b/src/app/modules/tv-map/queries/tv-map-queries.spec.ts new file mode 100644 index 00000000..6a8db37e --- /dev/null +++ b/src/app/modules/tv-map/queries/tv-map-queries.spec.ts @@ -0,0 +1,52 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { TvMapSourceFile } from '../services/tv-map-source-file'; +import { TvRoad } from '../models/tv-road.model'; +import { TvMap } from '../models/tv-map.model'; +import { TvMapQueries } from './tv-map-queries'; +import { TvLaneSide, TvLaneType } from '../models/tv-common'; + +describe( 'OpenDriveQueries', () => { + + let road: TvRoad; + + beforeEach( () => { + + TvMapSourceFile.openDrive = new TvMap(); + + road = TvMapSourceFile.openDrive.addDefaultRoad(); + + road.addGeometryLine( 0, 0, 0, 0, 10 ); + + } ); + + it( 'should give correct left side width of road', () => { + + const result = TvMapQueries.getRoadWidthAt( road.id, 0 ); + + expect( result.totalWidth ).toBe( 12.2 ); + + expect( result.leftSideWidth ).toBe( 6.1 ); + + expect( result.rightSideWidth ).toBe( 6.1 ); + + } ); + + + it( 'should give correct position for road start node', () => { + + const s = 0; + + // add left lane with 2 width + road.getLaneSectionAt( 0 ).addLane( TvLaneSide.LEFT, 4, TvLaneType.driving, false, true ).addWidthRecord( 0, 2, 0, 0, 0 ); + + const result = TvMapQueries.getRoadWidthAt( road.id, s ); + + const start = TvMapQueries.getRoadPosition( road.id, s, -result.leftSideWidth ); + const end = TvMapQueries.getRoadPosition( road.id, s, result.rightSideWidth ); + + } ); + +} ); diff --git a/src/app/modules/tv-map/queries/tv-map-queries.ts b/src/app/modules/tv-map/queries/tv-map-queries.ts new file mode 100644 index 00000000..19f036f4 --- /dev/null +++ b/src/app/modules/tv-map/queries/tv-map-queries.ts @@ -0,0 +1,465 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { TvMapSourceFile } from '../services/tv-map-source-file'; +import { TvPosTheta } from '../models/tv-pos-theta'; +import { TvRoad } from '../models/tv-road.model'; +import { TvAbstractRoadGeometry } from '../models/geometries/tv-abstract-road-geometry'; +import { Vector2, Vector3 } from 'three'; +import { Maths } from '../../../utils/maths'; +import { TvLane } from '../models/tv-lane'; +import { TvLaneSection } from '../models/tv-lane-section'; +import { TvCoord, TvLaneCoord } from '../models/tv-lane-coord'; +import { TvLaneSide, TvLaneType } from '../models/tv-common'; +import { TvUtils } from '../models/tv-utils'; +import { TvMap } from '../models/tv-map.model'; + +export abstract class TvBaseQueries { + + static get openDrive () { + return TvMapSourceFile.openDrive; + } + + static get roads () { + return this.openDrive.roads; + } + +} + +export class TvMapQueries extends TvBaseQueries { + + static getRoadWidthAt ( roadId: number, s: number ) { + + let leftWidth = 0, rightWidth = 0; + + this.findRoadById( roadId ) + .getLaneSectionAt( s ) + .getLeftLanes() + .forEach( lane => leftWidth += lane.getWidthValue( s ) ); + + this.findRoadById( roadId ) + .getLaneSectionAt( s ) + .getRightLanes() + .forEach( lane => rightWidth += lane.getWidthValue( s ) ); + + return { + totalWidth: leftWidth + rightWidth, + leftSideWidth: leftWidth, + rightSideWidth: rightWidth, + } + } + + static findRoadById ( id: number ): TvRoad { + + return this.openDrive.roads.get( id ); + + } + + static getSignalById ( id: number ) { + + let res = null; + + for ( const road of this.roads ) { + + for ( const signal of road[ 1 ].signals ) { + + if ( signal[ 1 ].id === id ) { + + res = signal; + break; + + } + + } + + if ( res ) break; + } + + return res; + } + + static getRoadPosition ( roadId: number, s: number, t: number ): TvPosTheta { + + return this.findRoadById( roadId ).getPositionAt( s, t ); + + } + + static getRoadByCoords ( x: number, y: number, posTheta?: TvPosTheta, ...roadIdsToIgnore ): TvRoad { + + // console.time( 'get-road' ); + + const tmpPosTheta = new TvPosTheta(); + + let nearestRoad: TvRoad = null; + + let nearestGeometry: TvAbstractRoadGeometry = null; + + let nearestPosition: Vector2 = null; + + let minDistance = Number.MAX_SAFE_INTEGER; + + const point = new Vector2( x, y ); + + const roadCount = this.roads.size; + + let road: TvRoad; + + for ( const keyValue of this.roads ) { + + road = keyValue[ 1 ]; + + let skip = false; + + for ( const id of roadIdsToIgnore ) if ( road.id === id ) skip = true; + + if ( skip ) continue; + + road.geometries.forEach( geometry => { + + const nearestPoint = geometry.getNearestPointFrom( x, y, tmpPosTheta ); + + const distance = point.distanceTo( nearestPoint ); + + if ( distance < minDistance ) { + + minDistance = distance; + nearestRoad = road; + nearestGeometry = geometry; + nearestPosition = nearestPoint; + + if ( posTheta ) { + posTheta.x = tmpPosTheta.x; + posTheta.y = tmpPosTheta.y; + posTheta.hdg = tmpPosTheta.hdg; + posTheta.s = tmpPosTheta.s; + posTheta.t = tmpPosTheta.t; + } + } + + } ); + + } + + // console.timeEnd( 'get-road' ); + + return nearestRoad; + + } + + static getLaneStartPosition ( roadId: number, laneId: number, sCoordinate: number, offset: number = 0, refPos?: TvPosTheta ): Vector3 { + + const posTheta = new TvPosTheta(); + + const road = this.roads.get( roadId ); + + if ( road === undefined ) throw new Error( `Road with ID: ${ roadId } not found` ); + + road.getGeometryCoords( sCoordinate, posTheta ); + + const laneSection = road.getLaneSectionAt( sCoordinate ); + + const lane = laneSection.getLaneById( laneId ); + + const tDirection = laneId > 0 ? 1 : -1; + + const cumulativeWidth = laneSection.getWidthUptoStart( lane, sCoordinate ); + + const cosTheta = Math.cos( posTheta.hdg + Maths.M_PI_2 ) * tDirection; + const sinTheta = Math.sin( posTheta.hdg + Maths.M_PI_2 ) * tDirection; + + posTheta.x += cosTheta * ( cumulativeWidth + offset ); + posTheta.y += sinTheta * ( cumulativeWidth + offset ); + + if ( refPos ) { + + refPos.x = posTheta.x; + refPos.y = posTheta.y; + refPos.s = posTheta.s; + refPos.t = posTheta.t; + refPos.hdg = posTheta.hdg; + + } + + return new Vector3( + posTheta.x, + posTheta.y, + 0 + ); + + } + + static getLanePosition ( roadId: number, laneId: number, sCoordinate: number, offset: number = 0, refPos?: TvPosTheta ): Vector3 { + + const posTheta = new TvPosTheta(); + + const road = this.roads.get( roadId ); + + if ( road === undefined ) throw new Error( `Road with ID: ${ roadId } not found` ); + + road.getGeometryCoords( sCoordinate, posTheta ); + + const laneSection = road.getLaneSectionAt( sCoordinate ); + + if ( !laneSection || laneSection == null ) { + throw new Error( `LaneSection not found for road: ${ roadId } at ${ sCoordinate }` ); + } + + const lane = laneSection.getLaneById( laneId ); + + if ( !lane || lane === undefined || lane == null ) { + throw new Error( `Lane not found for road ${ roadId } at ${ sCoordinate } with id:${ laneId }` ); + } + + const tDirection = laneId > 0 ? 1 : -1; + + const cumulativeWidth = laneSection.getWidthUptoCenter( lane, sCoordinate ); + + const cosTheta = Math.cos( posTheta.hdg + Maths.M_PI_2 ) * tDirection; + const sinTheta = Math.sin( posTheta.hdg + Maths.M_PI_2 ) * tDirection; + + posTheta.x += cosTheta * ( cumulativeWidth + offset ); + posTheta.y += sinTheta * ( cumulativeWidth + offset ); + + if ( refPos ) { + + refPos.x = posTheta.x; + refPos.y = posTheta.y; + refPos.s = posTheta.s; + refPos.t = posTheta.t; + refPos.hdg = posTheta.hdg; + + } + + return new Vector3( + posTheta.x, + posTheta.y, + 0 + ); + + } + + static getLaneByCoords ( x: number, y: number, posTheta: TvPosTheta, ...roadIdsToIgnore ): { road: TvRoad, lane: TvLane } { + + let resultLane: TvLane = null; + + const resultRoad = TvMapQueries.getRoadByCoords( x, y, posTheta, roadIdsToIgnore ); + + const s = posTheta.s; + const t = posTheta.t; + + // find laneSection + const laneSections = resultRoad.getLaneSections(); + + for ( const laneSection of laneSections ) { + + // TODO: Fix this, checkInteral does not check properly + if ( laneSection.checkInterval( s ) ) { + + // t is positive of left + // left lanes are positive int + // right lanes are negative int + + let lanes: TvLane[] = []; + + // positive t means left side + if ( t > 0 ) { + + lanes = laneSection.getLeftLanes().reverse(); + + } else if ( t < 0 ) { + + lanes = laneSection.getRightLanes(); + + } + + let cumulativeWidth = 0; + + for ( const lane of lanes ) { + + const width = lane.getWidthValue( s ); + + cumulativeWidth += width; + + if ( cumulativeWidth > Math.abs( t ) ) { + + resultLane = lane; + break; + + } + + } + + break; + } + + } + + return { + road: resultRoad, + lane: resultLane + }; + } + + static getMaxTrackPos () { + } + + static getCurvature () { + } + + static getLaneCurvature ( a, b, c, d ) { + } + + static getDeltaLaneDir () { + } + + static getLaneWidth ( lane: TvLane, s: number ) { + } + + // static getLaneWidth2 ( lane: OdLane, s, a, b ) + + static getLaneWidthAndRoadMark ( lane: TvLane, s: number ) { + + } + + static getLaneHeight () { + } + + static getNextJunction ( trackCoord, bool, juncHeader, double, roadHeader ) { + + } + + static getObjects ( trackCoord, bool, double, resultInfostruct: [] ) { + + } + + static getPitch ( road: TvRoad, trackCoord, double ) { } + static getPitchAndZ ( road: TvRoad, trackCoord, double ) { } + + static getPitchAndPitchDot ( road: TvRoad, trackCoord, double ) { + } + + static getPitchDot ( road: TvRoad, trackCoord, double ) { + } + + static getRoll ( road: TvRoad, trackCoord, double ) { + } + + static getRoll2 ( trackCoord, double ) { + } + + static getRollAndRollDot ( road: TvRoad, trackCoord, double ) { + } + + static getRollDot ( road: TvRoad, trackCoord, double ) { + } + + static getSignals ( trackCoord, bool, double, resultInfostruct: [] ) { + } + + static getTrackAngles ( trackCoord, coord: TvCoord ) { + } + + static getTrackAnglesDot ( trackCoord, coord: TvCoord ) { + } + + static getTrackHeading ( trackCoord, double ) { + } + + static getTrackWidth ( trackCoord ) { + } + + static inTunnel ( laneCoord ) { + } + + static onBridge ( laneCoord ) { + } + + static lane2curvature ( laneCoord ) { + } + + static lane2laneHeight ( laneCoord ) { + } + + static lane2laneWidth ( laneCoord ) { + } + + static lane2roadMark ( laneCoord ) { + } + static lane2track ( laneCoord ) { } + static lane2validLane ( laneCoord ) { } + static lane2validLane2 ( laneCoord, bool ) { } + + static s2elevation ( road, s ) { } + static s2superelevation ( road, s ) { } + static s2surface ( road, s, ushort ) { } + + static laneId2Node () { } + static laneSpeedFromRoadType ( laneCoord, double ) { } + + + static getRoadMark ( lane: TvLane, double ) { + } + + static getRoadType ( laneCoord ) { } + + static getFootPoint () { + } + + static getTolerance () { + } + + static setRoadData ( a ) { + } + + static getRoadData () { + } + + static getLaneFromId ( laneSection: TvLaneSection, id ) { + } + + static getLaneOnNextRoad ( laneCoord: TvLaneCoord, road: TvRoad ) { + } + + static getLaneOnPreviousRoad ( laneCoord: TvLaneCoord, road: TvRoad ) { + } + + static getLaneSpeed () { } + + static getRandomRoad ( openDrive: TvMap ): TvRoad { + + return TvUtils.getRandomArrayItem( [ ...openDrive.roads.values() ] ) as TvRoad; + + } + + static getRandomLaneSection ( road: TvRoad ): TvLaneSection { + + return TvUtils.getRandomArrayItem( road.lanes.laneSections ) as TvLaneSection; + + } + + static getRandomLane ( laneSection: TvLaneSection, laneType: TvLaneType ): TvLane { + + const lanes = [ ...laneSection.lanes.values() ]; + + const filteredLanes = lanes.filter( lane => { + if ( lane.type === laneType && lane.side !== TvLaneSide.CENTER ) return true; + } ); + + return TvUtils.getRandomArrayItem( filteredLanes ) as TvLane; + } + + static getRandomLocationOnRoads ( roads: TvRoad[], laneType: TvLaneType ) { + + const road = TvUtils.getRandomArrayItem( roads ) as TvRoad; + + const laneSection = this.getRandomLaneSection( road ); + + const lane = this.getRandomLane( laneSection, laneType ); + + // get random s on lane-section + const s = Maths.randomNumberBetween( laneSection.s + 1, laneSection.lastSCoordinate - 1 ); + + return new TvLaneCoord( road.id, laneSection.id, lane.id, s, 0 ); + } +} diff --git a/src/app/modules/tv-map/services/open-drive-parser.service.spec.ts b/src/app/modules/tv-map/services/open-drive-parser.service.spec.ts new file mode 100644 index 00000000..fbce02e8 --- /dev/null +++ b/src/app/modules/tv-map/services/open-drive-parser.service.spec.ts @@ -0,0 +1,71 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { TestBed } from '@angular/core/testing'; +import { OpenDriverParser } from './open-drive-parser.service'; + + +describe( 'OpenDrive Parsing', () => { + + let parser: OpenDriverParser; + + beforeEach( () => TestBed.configureTestingModule( {} ) ); + + beforeEach( () => { + parser = new OpenDriverParser( ); + } ); + + it( 'should parse header correctly', () => { + + const headerXml = { + attr_revMajor: '1', + attr_revMinor: '4', + attr_name: 'himanshu', + attr_version: '1', + attr_date: 'Date', + attr_north: '1.0000000000000000e+00', + attr_south: '1.0000000000000000e+00', + attr_east: '1.0000000000000000e+00', + attr_west: '1.0000000000000000e+00', + attr_vendor: 'Truevision.ai', + }; + + parser.readHeader( headerXml ); + + const header = parser.OpenDrive.header; + + expect( header.attr_revMajor ).toBe( 1 ); + expect( header.attr_revMinor ).toBe( 4 ); + expect( header.attr_name ).toBe( headerXml.attr_name ); + expect( header.attr_version ).toBe( 1 ); + expect( header.attr_date ).toBe( headerXml.attr_date ); + expect( header.attr_north ).toBe( 1 ); + expect( header.attr_south ).toBe( 1 ); + expect( header.attr_east ).toBe( 1 ); + expect( header.attr_west ).toBe( 1 ); + expect( header.attr_vendor ).toBe( headerXml.attr_vendor ); + + } ); + + it( 'should parse road correctly', () => { + + const xml = { + attr_id: '1', + attr_name: 'road', + attr_length: '100', + attr_junction: '-1' + }; + + const road = ( parser as any ).readRoad( xml ); + + expect( road ).toBeDefined(); + expect( road.id ).toBe( 1 ); + expect( road.name ).toBe( xml.attr_name ); + expect( road.length ).toBe( 100 ); + expect( road.junction ).toBe( -1 ); + + } ); + + +} ); diff --git a/src/app/modules/tv-map/services/open-drive-parser.service.ts b/src/app/modules/tv-map/services/open-drive-parser.service.ts new file mode 100644 index 00000000..609f7de0 --- /dev/null +++ b/src/app/modules/tv-map/services/open-drive-parser.service.ts @@ -0,0 +1,1013 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { TvMap } from '../models/tv-map.model'; +import { TvRoad } from '../models/tv-road.model'; +import { TvPlaneView } from '../models/tv-plane-view'; +import { TvJunction } from '../models/tv-junction'; +import { TvRoadObject } from '../models/tv-road-object'; +import { TvRoadSignal } from '../models/tv-road-signal.model'; +import { TvLaneSection } from '../models/tv-lane-section'; +import { TvLane } from '../models/tv-lane'; +import { EnumHelper, TvContactPoint, TvGeometryType, TvLaneSide, TvRoadType, TvUnit, TvUserData } from '../models/tv-common'; +import { AbstractReader } from '../../../core/services/abstract-reader'; +import { SignShapeType } from './tv-sign.service'; +import { TvJunctionLaneLink } from '../models/tv-junction-lane-link'; +import { TvJunctionConnection } from '../models/tv-junction-connection'; +import { TvJunctionPriority } from '../models/tv-junction-priority'; +import { TvJunctionController } from '../models/tv-junction-controller'; +import { TvController, TvControllerControl } from '../models/tv-controller'; +import { TvRoadTypeClass } from '../models/tv-road-type.class'; +import { Injectable } from '@angular/core'; +import { TvAbstractRoadGeometry } from '../models/geometries/tv-abstract-road-geometry'; +import { ExplicitSpline } from 'app/core/shapes/explicit-spline'; + +@Injectable( { + providedIn: 'root' +} ) +export class OpenDriverParser extends AbstractReader { + + public openDrive: TvMap = new TvMap(); + public xmlElement: string; + + constructor () { + super(); + } + + get OpenDrive () { + return this.openDrive; + } + + parse ( xmlElement: string ): TvMap { + + this.xmlElement = xmlElement; + + const defaultOptions = { + attributeNamePrefix: 'attr_', + attrNodeName: false, + textNodeName: 'value', + ignoreAttributes: false, + supressEmptyNode: false, + format: true, + }; + + const Parser = require( 'fast-xml-parser' ); + const data: any = Parser.parse( this.xmlElement, defaultOptions ); + + this.readFile( data ); + + return this.openDrive; + } + + /** + * Reads the data from the OpenDrive structure to a file + */ + readFile ( xmlString ) { + + const xmlElement = xmlString.OpenDRIVE; + + this.readHeader( xmlElement.header ); + + this.readRoads( xmlElement ); + + this.readAsOptionalArray( xmlElement.controller, xml => { + + this.openDrive.addControllerInstance( this.readController( xml ) ); + + } ); + + this.readAsOptionalArray( xmlElement.junction, ( xml ) => { + + this.openDrive.addJunctionInstance( this.readJunction( xml ) ); + + } ); + } + + /** + * The following methods are used to read the data from the XML file and fill in the the OpenDrive structure + * Methods follow the hierarchical structure and are called automatically when ReadFile is executed + */ + readHeader ( xmlElement ) { + + const revMajor = parseFloat( xmlElement.attr_revMajor ); + const revMinor = parseFloat( xmlElement.attr_revMinor ); + const name = xmlElement.attr_name; + const version = parseFloat( xmlElement.attr_version ); + const date = xmlElement.attr_date; + const north = parseFloat( xmlElement.attr_north ); + const south = parseFloat( xmlElement.attr_south ); + const east = parseFloat( xmlElement.attr_east ); + const west = parseFloat( xmlElement.attr_west ); + const vendor = xmlElement.attr_vendor; + + this.openDrive.setHeader( revMajor, revMinor, name, version, date, north, south, east, west, vendor ); + } + + readRoad ( xml: any ) { + + const name = xml.attr_name; + const length = parseFloat( xml.attr_length ); + const id = parseInt( xml.attr_id, 10 ); + const junction = parseFloat( xml.attr_junction ); + + const road = this.openDrive.addRoad( name, length, id, junction ); + + if ( xml.link != null ) { + + this.readRoadLinks( road, xml.link ); + + } + + // Get type + this.readRoadTypes( road, xml ); + + if ( xml.planView != null ) { + + this.readPlanView( road, xml.planView ); + + road.spline = this.makeSplineFromGeometry( road, road.planView.geometries ); + + road.updateGeometryFromSpline(); + } + + if ( xml.elevationProfile != null ) this.readElevationProfile( road, xml.elevationProfile ); + + if ( xml.lateralProfile != null ) this.readLateralProfile( road, xml.lateralProfile ); + + if ( xml.lanes != null ) this.readLanes( road, xml.lanes ); + + // if ( xml.objects != null && xml.objects !== '' ) this.readObjects( road, xml.objects ); + + if ( xml.signals != null && xml.signals !== '' ) this.readSignals( road, xml.signals ); + + if ( xml.surface != null && xml.surface !== '' ) this.readSurface( road, xml.surface ); + + return road; + } + + public makeSplineFromGeometry ( road: TvRoad, geometries: TvAbstractRoadGeometry[] ): ExplicitSpline { + + const spline = new ExplicitSpline( road ); + + if ( geometries.length === 0 ) return spline; + + let lastGeometry: TvAbstractRoadGeometry; + + for ( let i = 0; i < geometries.length; i++ ) { + + lastGeometry = geometries[ i ]; + + spline.addFromFile( i, lastGeometry.startV3, lastGeometry.hdg, lastGeometry.geometryType ); + } + + // // for last geometry + // const lastGeometry = geometries[ geometries.length - 1 ]; + + const lastCoord = road.endCoord() + + spline.addFromFile( geometries.length, lastCoord.toVector3(), lastCoord.hdg, lastGeometry.geometryType ); + + spline.hide(); + + spline.controlPoints.forEach( cp => cp.userData.roadId = road.id ); + + return spline; + } + + public readRoads ( xmlElement: any ) { + + if ( xmlElement.road == null ) { + + throw new Error( 'no roads found' ); + + } + + this.readAsOptionalArray( xmlElement.road, ( xml ) => { + + this.readRoad( xml ); + + } ); + + } + + public readRoadLinks ( road: TvRoad, xmlElement: any ) { + + if ( xmlElement.predecessor != null ) { + + this.readRoadLink( road, xmlElement.predecessor, 0 ); + + } + + if ( xmlElement.successor != null ) { + + this.readRoadLink( road, xmlElement.successor, 1 ); + + } + + if ( xmlElement.neighbor != null ) { + + if ( Array.isArray( xmlElement.neighbor ) ) { + + for ( let i = 0; i < xmlElement.neighbor.length; i++ ) { + + this.readRoadLink( road, xmlElement.neighbor[ i ], 2 ); + + } + + } else { + + this.readRoadLink( road, xmlElement.neighbor, 2 ); + + } + } + } + + public readRoadLink ( road: TvRoad, xmlElement: any, type: number ) { + + if ( type === 0 ) { + + const elementType = xmlElement.attr_elementType; + const elementId = parseFloat( xmlElement.attr_elementId ); + const contactPoint = this.readContactPoint( xmlElement.attr_contactPoint ); + + road.setPredecessor( elementType, elementId, contactPoint ); + + } else if ( type === 1 ) { + + const elementType = xmlElement.attr_elementType; + const elementId = parseFloat( xmlElement.attr_elementId ); + const contactPoint = this.readContactPoint( xmlElement.attr_contactPoint ); + + road.setSuccessor( elementType, elementId, contactPoint ); + + } else if ( type === 2 ) { + + console.error( 'neighbour not supported' ); + + // const side = xmlElement.attr_side; + // const elementId = xmlElement.attr_elementId; + // const direction = xmlElement.attr_direction; + // + // road.setNeighbor( side, elementId, direction ); + + } + + } + + public readContactPoint ( value: string ): TvContactPoint { + + if ( value === 'start' ) { + + return TvContactPoint.START; + + } else if ( value === 'end' ) { + + return TvContactPoint.END; + + } else { + + return null; + + } + + } + + public readRoadTypes ( road: TvRoad, xmlElement: any ) { + + if ( !xmlElement.type ) console.warn( "no road type tag not present" ) + + this.readAsOptionalArray( xmlElement.type, xml => { + + const s = parseFloat( xml.attr_s ); + + const roadType = TvRoadTypeClass.stringToTypes( xml.attr_type ); + + let maxSpeed = 0; + + let unit = TvUnit.MILES_PER_HOUR; + + this.readAsOptionalElement( xml.speed, xml => { + + maxSpeed = parseFloat( xml.attr_max ); + + unit = EnumHelper.stringToOdUnits( xml.attr_unit ); + + } ); + + road.type.push( new TvRoadTypeClass( s, roadType, maxSpeed, unit ) ); + + } ); + + // add default if no road type inserted + if ( road.type.length === 0 ) { + + road.setType( TvRoadType.TOWN, 40, TvUnit.MILES_PER_HOUR ); + + } + + } + + public readPlanView ( road: TvRoad, xmlElement: any ) { + + if ( xmlElement.geometry != null ) { + + if ( Array.isArray( xmlElement.geometry ) ) { + + for ( let i = 0; i < xmlElement.geometry.length; i++ ) { + + this.readGeometryType( road, xmlElement.geometry[ i ] ); + + } + + } else { + + this.readGeometryType( road, xmlElement.geometry ); + + } + } + } + + public readGeometryType ( road: TvRoad, xmlElement: any ) { + + if ( xmlElement.line != null ) { + + this.readGeometryBlock( road, xmlElement, TvGeometryType.LINE ); + + } else if ( xmlElement.arc != null ) { + + this.readGeometryBlock( road, xmlElement, TvGeometryType.ARC ); + + } else if ( xmlElement.spiral != null ) { + + this.readGeometryBlock( road, xmlElement, TvGeometryType.SPIRAL ); + + } else if ( xmlElement.poly3 != null ) { + + this.readGeometryBlock( road, xmlElement, TvGeometryType.POLY3 ); + + } else if ( xmlElement.paramPoly3 != null ) { + + this.readGeometryBlock( road, xmlElement, TvGeometryType.PARAMPOLY3 ); + + } else { + + console.error( 'unknown geometry type' ); + + } + } + + public readGeometryBlock ( road: TvRoad, xmlElement: any, geometryType: TvGeometryType ) { + + const s = parseFloat( xmlElement.attr_s ); + const x = parseFloat( xmlElement.attr_x ); + const y = parseFloat( xmlElement.attr_y ); + const hdg = parseFloat( xmlElement.attr_hdg ); + const length = parseFloat( xmlElement.attr_length ); + + road.addPlanView(); + + const planView = road.getPlanView(); + + this.readGeometry( planView, xmlElement, geometryType ); + } + + public readGeometry ( planView: TvPlaneView, xmlElement: any, geometryType: TvGeometryType ) { + + const s = parseFloat( xmlElement.attr_s ); + const x = parseFloat( xmlElement.attr_x ); + const y = parseFloat( xmlElement.attr_y ); + let hdg = parseFloat( xmlElement.attr_hdg ); + const length = parseFloat( xmlElement.attr_length ); + + // unsure of this, but works well so far + // hdg += Maths.M_PI_2; + + // NO NEED FOR THIS + // because of threejs co-ordinate system + // x will become y and y will become x + // const x = parsedX * -1; + // const y = parsedY; + + switch ( geometryType ) { + + case TvGeometryType.LINE: + + planView.addGeometryLine( s, x, y, hdg, length ); + + break; + + case TvGeometryType.SPIRAL: + + const curvStart = parseFloat( xmlElement.spiral.attr_curvStart ); + const curvEnd = parseFloat( xmlElement.spiral.attr_curvEnd ); + + planView.addGeometrySpiral( s, x, y, hdg, length, curvStart, curvEnd ); + + break; + + case TvGeometryType.ARC: + + const curvature = parseFloat( xmlElement.arc.attr_curvature ); + + planView.addGeometryArc( s, x, y, hdg, length, curvature ); + + break; + + case TvGeometryType.POLY3: + + const a = parseFloat( xmlElement.poly3.attr_a ); + const b = parseFloat( xmlElement.poly3.attr_b ); + const c = parseFloat( xmlElement.poly3.attr_c ); + const d = parseFloat( xmlElement.poly3.attr_d ); + + planView.addGeometryPoly3( s, x, y, hdg, length, a, b, c, d ); + + break; + + case TvGeometryType.PARAMPOLY3: + + const aU = parseFloat( xmlElement.paramPoly3.attr_aU ); + const bU = parseFloat( xmlElement.paramPoly3.attr_bU ); + const cU = parseFloat( xmlElement.paramPoly3.attr_cU ); + const dU = parseFloat( xmlElement.paramPoly3.attr_dU ); + + const aV = parseFloat( xmlElement.paramPoly3.attr_aV ); + const bV = parseFloat( xmlElement.paramPoly3.attr_bV ); + const cV = parseFloat( xmlElement.paramPoly3.attr_cV ); + const dV = parseFloat( xmlElement.paramPoly3.attr_dV ); + + planView.addGeometryParamPoly3( s, x, y, hdg, length, aU, bU, cU, dU, aV, bV, cV, dV ); + + break; + + default: + console.error( 'unknown type' ); + break; + + } + + } + + public readController ( xmlElement: any ): TvController { + + const id = parseFloat( xmlElement.attr_id ); + const name = xmlElement.attr_name; + const sequence = xmlElement.attr_sequence ? parseFloat( xmlElement.attr_sequence ) : null; + + const controller = new TvController( id, name, sequence ); + + this.readAsOptionalArray( xmlElement.control, xml => { + + controller.addControl( this.readControl( xml ) ); + + } ); + + return controller; + } + + public readJunction ( xmlElement: any ): TvJunction { + + const name = xmlElement.attr_name; + const id = parseInt( xmlElement.attr_id ); + + const junction = new TvJunction( name, id ); + + this.readAsOptionalArray( xmlElement.connection, xml => { + + junction.addConnection( this.readJunctionConnection( xml ) ); + + } ); + + this.readAsOptionalArray( xmlElement.priority, xml => { + + junction.addPriority( this.readJunctionPriority( xml ) ); + + } ); + + this.readAsOptionalArray( xmlElement.controller, xml => { + + junction.addController( this.readJunctionController( xml ) ); + + } ); + + return junction; + } + + public readJunctionConnection ( xmlElement: any ) { + + const id = parseInt( xmlElement.attr_id ); + const incomingRoad = parseInt( xmlElement.attr_incomingRoad ); + const connectingRoad = parseInt( xmlElement.attr_connectingRoad ); + const contactPoint = this.readContactPoint( xmlElement.attr_contactPoint ); + + const junctionConnection = new TvJunctionConnection( id, incomingRoad, connectingRoad, contactPoint ); + + this.readAsOptionalArray( xmlElement.laneLink, xml => { + + junctionConnection.addLaneLink( this.readJunctionConnectionLaneLink( xml ) ); + + } ); + + return junctionConnection; + } + + public readJunctionConnectionLaneLink ( xmlElement: any ): TvJunctionLaneLink { + + const from = parseInt( xmlElement.attr_from ); + const to = parseInt( xmlElement.attr_to ); + + return new TvJunctionLaneLink( from, to ); + } + + public readJunctionPriority ( xmlElement: any ): TvJunctionPriority { + + const high = parseInt( xmlElement.attr_high ); + const low = parseInt( xmlElement.attr_low ); + + return new TvJunctionPriority( high, low ); + } + + public readJunctionController ( xmlElement: any ): TvJunctionController { + + const id = parseInt( xmlElement.attr_id ); + const type = xmlElement.attr_type; + const sequence = parseInt( xmlElement.attr_sequence ); + + return new TvJunctionController( id, type, sequence ); + } + + public readElevationProfile ( road: TvRoad, xmlElement: any ) { + + road.addElevationProfile(); + + this.readAsOptionalArray( xmlElement.elevation, xml => { + + const s = parseFloat( xml.attr_s ); + const a = parseFloat( xml.attr_a ); + const b = parseFloat( xml.attr_b ); + const c = parseFloat( xml.attr_c ); + const d = parseFloat( xml.attr_d ); + + road.addElevation( s, a, b, c, d ); + + } ); + + } + + public readLateralProfile ( road: TvRoad, xmlElement: any ) { + + } + + public readLanes ( road: TvRoad, xmlElement: any ) { + + this.readAsOptionalArray( xmlElement.laneSection, ( xml ) => { + + this.readLaneSection( road, xml ); + + } ); + + this.readAsOptionalArray( xmlElement.laneOffset, ( xml ) => { + + this.readLaneOffset( road, xml ); + + } ); + + + // if ( xmlElement.laneSection != null ) { + // + // if ( Array.isArray( xmlElement.laneSection ) ) { + // + // for ( let i = 0; i < xmlElement.laneSection.length; i++ ) { + // + // this.readLaneSections( road, xmlElement.laneSection[i] ); + // + // } + // + // } else { + // + // this.readLaneSections( road, xmlElement.laneSection ); + // + // } + // } + } + + public readObjects ( road: TvRoad, xmlElement: any ) { + + if ( xmlElement != null && xmlElement !== '' ) { + + if ( Array.isArray( xmlElement.object ) ) { + + for ( let i = 0; i < xmlElement.object.length; i++ ) { + + this.readObject( road, xmlElement.object[ i ] ); + + } + } else { + + this.readObject( road, xmlElement.object ); + + } + } + } + + public readObject ( road: TvRoad, xmlElement: any ) { + + const type = xmlElement.attr_type; + const name = xmlElement.attr_name; + const id = parseFloat( xmlElement.attr_id ); + const s = parseFloat( xmlElement.attr_s ); + const t = parseFloat( xmlElement.attr_t ); + const zOffset = parseFloat( xmlElement.attr_zOffset ); + const validLength = parseFloat( xmlElement.attr_validLength ); + const orientation = xmlElement.attr_orientation; + const length = parseFloat( xmlElement.attr_length ); + const width = parseFloat( xmlElement.attr_width ); + const radius = parseFloat( xmlElement.attr_radius ); + const height = parseFloat( xmlElement.attr_height ); + const hdg = parseFloat( xmlElement.attr_hdg ); + const pitch = parseFloat( xmlElement.attr_pitch ); + const roll = parseFloat( xmlElement.attr_roll ); + + road.addRoadObject( + type, name, id, + s, t, zOffset, + validLength, + orientation, + length, width, radius, height, + hdg, pitch, roll + ); + + const roadObject = road.getLastAddedRoadObject(); + + roadObject.userData = this.readUserData( xmlElement ); + + this.readRoadObjectRepeatArray( roadObject, xmlElement ); + } + + public readRoadObjectRepeatArray ( roadObject: TvRoadObject, xmlElement: any ): void { + + if ( xmlElement.repeat != null && xmlElement.repeat !== '' ) { + + if ( Array.isArray( xmlElement.repeat ) ) { + + for ( let i = 0; i < xmlElement.repeat.length; i++ ) { + + this.readRoadObjectRepeat( roadObject, xmlElement.repeat[ i ] ); + + } + + } else { + + this.readRoadObjectRepeat( roadObject, xmlElement ); + + } + + } + + } + + public readRoadObjectRepeat ( roadObject: TvRoadObject, xmlElement: any ): void { + + const s = parseFloat( xmlElement.attr_s ); + const length = parseFloat( xmlElement.attr_length ); + const distance = parseFloat( xmlElement.attr_distance ); + const tStart = parseFloat( xmlElement.attr_tStart ); + const tEnd = parseFloat( xmlElement.attr_tEnd ); + const widthStart = parseFloat( xmlElement.attr_widthStart ); + const widthEnd = parseFloat( xmlElement.attr_widthEnd ); + const heightStart = parseFloat( xmlElement.attr_heightStart ); + const heightEnd = parseFloat( xmlElement.attr_heightEnd ); + const zOffsetStart = parseFloat( xmlElement.attr_zOffsetStart ); + const zOffsetEnd = parseFloat( xmlElement.attr_zOffsetEnd ); + + roadObject.addRepeat( s, length, distance, tStart, tEnd, widthStart, widthEnd, heightStart, heightEnd, zOffsetStart, zOffsetEnd ); + + } + + public readSignals ( road: TvRoad, xmlElement: any ) { + + this.readAsOptionalArray( xmlElement.signal, ( xml ) => { + + this.readSignal( road, xml ); + + } ); + + } + + public readSignal ( road: TvRoad, xmlElement: any ) { + + const s = parseFloat( xmlElement.attr_s ); + const t = xmlElement.attr_t; + const id = xmlElement.attr_id; + const name = xmlElement.attr_name; + const dynamic = xmlElement.attr_dynamic; + const orientation = xmlElement.attr_orientation; + const zOffset = xmlElement.attr_zOffset; + const country = xmlElement.attr_country; + const type = xmlElement.attr_type; + const subtype = xmlElement.attr_subtype; + const value = xmlElement.attr_value; + const unit = xmlElement.attr_unit; + const height = xmlElement.attr_height; + const width = xmlElement.attr_width; + const text = xmlElement.attr_text; + const hOffset = xmlElement.attr_hOffset; + const pitch = xmlElement.attr_pitch; + const roll = xmlElement.attr_roll; + + const roadSignal = road.addRoadSignal( s, + t, + id, + name, + dynamic, + orientation, + zOffset, + country, + type, + subtype, + value, + unit, + height, + width, + text, + hOffset, + pitch, + roll + ); + + roadSignal.roadId = road.id; + + this.readSignalValidity( roadSignal, xmlElement ); + + this.readSignalDependency( roadSignal, xmlElement ); + + roadSignal.userData = this.readUserData( xmlElement ); + + if ( roadSignal.userDataMap.has( 'sign_shape' ) ) { + + const signShape = roadSignal.userDataMap.get( 'sign_shape' ); + + roadSignal.signShape = SignShapeType[ signShape.attr_value ] as SignShapeType; + + } + } + + public readSignalValidity ( signal: TvRoadSignal, xmlElement: any ): void { + + if ( xmlElement.validity != null && xmlElement.validity !== '' ) { + + if ( Array.isArray( xmlElement.validity ) ) { + + for ( let i = 0; i < xmlElement.validity.length; i++ ) { + + const validity = xmlElement.validity[ i ]; + + signal.addValidity( parseFloat( validity.attr_fromLane ), parseFloat( validity.attr_toLane ) ); + + } + + } else { + + const validity = xmlElement.validity; + + signal.addValidity( parseFloat( validity.attr_fromLane ), parseFloat( validity.attr_toLane ) ); + + } + } + } + + public readSignalDependency ( signal: TvRoadSignal, xmlElement: any ): void { + + if ( xmlElement.dependency != null && xmlElement.dependency !== '' ) { + + if ( Array.isArray( xmlElement.dependency ) ) { + + for ( let i = 0; i < xmlElement.dependency.length; i++ ) { + + const dependency = xmlElement.dependency[ i ]; + + signal.addDependency( parseFloat( dependency.attr_id ), dependency.attr_type ); + + } + + } else { + + const dependency = xmlElement.dependency; + + signal.addDependency( parseFloat( dependency.attr_id ), dependency.attr_type ); + + } + } + } + + public readSurface ( road: TvRoad, xmlElement: any ) { + + } + + public readLaneSection ( road: TvRoad, xmlElement: any ) { + + const s = parseFloat( xmlElement.attr_s ); + const singleSide = xmlElement.attr_singleSide == 'true'; + + road.addLaneSection( s, singleSide ); + + const laneSection = road.getLastAddedLaneSection(); + + this.readAsOptionalElement( xmlElement.left, xml => { + this.readAsOptionalArray( xml.lane, xml => { + this.readLane( laneSection, xml, TvLaneSide.LEFT ); + } ); + } ); + + this.readAsOptionalElement( xmlElement.center, xml => { + this.readAsOptionalArray( xml.lane, xml => { + this.readLane( laneSection, xml, TvLaneSide.CENTER ); + } ); + } ); + + this.readAsOptionalElement( xmlElement.right, xml => { + this.readAsOptionalArray( xml.lane, xml => { + this.readLane( laneSection, xml, TvLaneSide.RIGHT ); + } ); + } ); + + } + + public readLane ( laneSection: TvLaneSection, xmlElement: any, laneSide: TvLaneSide ) { + + const id = parseFloat( xmlElement.attr_id ); + const type = xmlElement.attr_type; + const level = xmlElement.attr_level; + + laneSection.addLane( laneSide, id, type, level, false ); + + const lane = laneSection.getLastAddedLane(); + + if ( xmlElement.link != null ) { + + const predecessorXml = xmlElement.link.predecessor; + const successorXml = xmlElement.link.successor; + + if ( predecessorXml != null ) { + + lane.setPredecessor( parseInt( predecessorXml.attr_id ) ); + + } + + if ( successorXml != null ) { + + lane.setSuccessor( parseInt( successorXml.attr_id ) ); + + } + } + + // Read Width + this.readAsOptionalArray( xmlElement.width, xml => this.readLaneWidth( lane, xml ) ); + + // Read RoadMark + this.readAsOptionalArray( xmlElement.roadMark, xml => this.readLaneRoadMark( lane, xml ) ); + + // Read material + this.readAsOptionalArray( xmlElement.material, xml => this.readLaneMaterial( lane, xml ) ); + + // Read visibility + this.readAsOptionalArray( xmlElement.visibility, xml => this.readLaneVisibility( lane, xml ) ); + + // Read speed + this.readAsOptionalArray( xmlElement.speed, xml => this.readLaneSpeed( lane, xml ) ); + + // Read access + this.readAsOptionalArray( xmlElement.access, xml => this.readLaneAccess( lane, xml ) ); + + // Read height + this.readAsOptionalArray( xmlElement.height, xml => this.readLaneHeight( lane, xml ) ); + + } + + public readLaneWidth ( lane: TvLane, xmlElement: any ) { + + const sOffset = parseFloat( xmlElement.attr_sOffset ); + + const a = parseFloat( xmlElement.attr_a ); + const b = parseFloat( xmlElement.attr_b ); + const c = parseFloat( xmlElement.attr_c ); + const d = parseFloat( xmlElement.attr_d ); + + lane.addWidthRecord( sOffset, a, b, c, d ); + + } + + public readLaneRoadMark ( lane: TvLane, xmlElement: any ) { + + const sOffset = parseFloat( xmlElement.attr_sOffset ); + const type = xmlElement.attr_type; + const weight = xmlElement.attr_weight; + const color = xmlElement.attr_color; + const width = parseFloat( xmlElement.attr_width ); + const laneChange = xmlElement.attr_laneChange; + const height = xmlElement.attr_height; + + lane.addRoadMarkRecord( sOffset, type, weight, color, width, laneChange, height ); + + } + + public readLaneMaterial ( lane: TvLane, xmlElement: any ) { + + const sOffset = parseFloat( xmlElement.attr_sOffset ); + const surface = xmlElement.attr_surface; + const friction = parseFloat( xmlElement.attr_friction ); + const roughness = parseFloat( xmlElement.attr_roughness ); + + lane.addMaterialRecord( sOffset, surface, friction, roughness ); + + } + + public readLaneVisibility ( lane: TvLane, xmlElement: any ) { + + const sOffset = parseFloat( xmlElement.attr_sOffset ); + const forward = parseFloat( xmlElement.attr_forward ); + const back = parseFloat( xmlElement.attr_back ); + const left = parseFloat( xmlElement.attr_left ); + const right = parseFloat( xmlElement.attr_right ); + + lane.addVisibilityRecord( sOffset, forward, back, left, right ); + + } + + public readLaneSpeed ( lane: TvLane, xmlElement: any ) { + + const sOffset = parseFloat( xmlElement.attr_sOffset ); + const max = parseFloat( xmlElement.attr_max ); + const unit = xmlElement.attr_unit; + + lane.addSpeedRecord( sOffset, max, unit ); + + } + + public readLaneAccess ( lane: TvLane, xmlElement: any ) { + + const sOffset = parseFloat( xmlElement.attr_sOffset ); + const restriction = xmlElement.attr_restriction; + + lane.addAccessRecord( sOffset, restriction ); + + } + + public readLaneHeight ( lane: TvLane, xmlElement: any ) { + + const sOffset = parseFloat( xmlElement.attr_sOffset ); + const inner = parseFloat( xmlElement.attr_inner ); + const outer = parseFloat( xmlElement.attr_outer ); + + lane.addHeightRecord( sOffset, inner, outer ); + + } + + public readUserData ( xmlElement: any ): TvUserData[] { + + const response: TvUserData[] = []; + + if ( xmlElement.userData != null ) { + + if ( Array.isArray( xmlElement.userData ) ) { + + for ( let i = 0; i < xmlElement.userData.length; i++ ) { + + const userData = xmlElement.userData[ i ]; + + response.push( new TvUserData( userData.attr_code, userData.attr_value ) ); + + } + + } else { + + response.push( new TvUserData( xmlElement.userData.attr_code, xmlElement.userData.attr_value ) ); + + } + + } + + return response; + + } + + public readLaneOffset ( road: TvRoad, xml: any ) { + + const s = parseFloat( xml.attr_s ); + const a = parseFloat( xml.attr_a ); + const b = parseFloat( xml.attr_b ); + const c = parseFloat( xml.attr_c ); + const d = parseFloat( xml.attr_d ); + + road.addLaneOffset( s, a, b, c, d ); + } + + public readControl ( xml: any ): TvControllerControl { + + const signalId = parseFloat( xml.attr_signalId ); + const type = xml.attr_type; + + return new TvControllerControl( signalId, type ); + } +} diff --git a/src/app/modules/tv-map/services/open-drive-writer.service.ts b/src/app/modules/tv-map/services/open-drive-writer.service.ts new file mode 100644 index 00000000..689ada3e --- /dev/null +++ b/src/app/modules/tv-map/services/open-drive-writer.service.ts @@ -0,0 +1,1020 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { Injectable } from '@angular/core'; +import { TvMap } from '../models/tv-map.model'; +import { TvRoad } from '../models/tv-road.model'; +import { TvAbstractRoadGeometry } from '../models/geometries/tv-abstract-road-geometry'; +import { TvLaneSection } from '../models/tv-lane-section'; +import { TvLane } from '../models/tv-lane'; +import { TvObjectOutline, TvRoadObject } from '../models/tv-road-object'; +import { TvJunction } from '../models/tv-junction'; +import { TvGeometryType, TvLaneSide, TvUserData } from '../models/tv-common'; +import { TvLaneRoadMark } from '../models/tv-lane-road-mark'; +import { TvLaneWidth } from '../models/tv-lane-width'; +import { TvLaneMaterial } from '../models/tv-lane-material'; +import { TvLaneVisibility } from '../models/tv-lane-visibility'; +import { TvLaneSpeed } from '../models/tv-lane-speed'; +import { TvLaneAccess } from '../models/tv-lane-access'; +import { TvLaneHeight } from '../models/tv-lane-height'; +import { TvArcGeometry } from '../models/geometries/tv-arc-geometry'; +import { TvSpiralGeometry } from '../models/geometries/tv-spiral-geometry'; +import { TvPoly3Geometry } from '../models/geometries/tv-poly3-geometry'; +import { TvParamPoly3Geometry } from '../models/geometries/tv-param-poly3-geometry'; +import { TvJunctionConnection } from '../models/tv-junction-connection'; + +@Injectable( { + providedIn: 'root' +} ) +export class OdWriter { + + public xmlDocument: Object; + public openDrive: TvMap; + + public constructor () { + + } + + public getOutput ( openDrive: TvMap ): string { + + this.openDrive = openDrive; + + const defaultOptions = { + attributeNamePrefix: 'attr_', + attrNodeName: false, + ignoreAttributes: false, + supressEmptyNode: true, + format: true, + trimValues: true, + }; + + const Parser = require( 'fast-xml-parser' ).j2xParser; + const parser = new Parser( defaultOptions ); + + this.writeFile( '' ); + + const data = parser.parse( this.xmlDocument ); + + // if ( Environment.production ) { + + // const blob: any = new Blob( [data], { type: 'application/xml' } ); + + // // Debug.log( blob ); + + // // TODO : Export OpenDRIVE + // // saveAs( blob, 'openDrive.xodr' ); + + // } else { + + // Debug.log( data ); + + // } + + return data; + + } + + public writeLaneLinks ( laneNode: any, lane: TvLane ) { + + // not link for center lanes + if ( lane.side === TvLaneSide.CENTER ) return; + + if ( lane.predecessorExists != null ) + laneNode.link[ 'predecessor' ] = { attr_id: lane.predecessor }; + + if ( lane.successorExists != null ) + laneNode.link[ 'successor' ] = { attr_id: lane.succcessor }; + + } + + /** + * Writes the data from the OpenDrive structure to a file + */ + public writeFile ( fileName: string ) { + + const rootNode = { + header: {}, + road: [], + junction: [] + }; + + this.xmlDocument = { + 'OpenDRIVE': rootNode + }; + + this.writeHeader( rootNode.header ); + + this.openDrive.roads.forEach( road => { + + this.writeRoad( rootNode, road ); + + } ); + + this.writeControllers( rootNode ); + + this.openDrive.junctions.forEach( junction => { + + this.writeJunction( rootNode, junction ); + + } ); + } + + /** + * The following methods are used to create the XML representation of the OpenDrive structure + * Methods follow the same hierarchical structure and are called automatically when WriteFile + * is executed + */ + public writeHeader ( xmlNode ) { + + const header = this.openDrive.getHeader(); + + // Add all attributes + xmlNode.attr_revMajor = header.revMajor; + xmlNode.attr_revMinor = header.revMinor; + xmlNode.attr_name = header.name; + xmlNode.attr_version = header.version; + xmlNode.attr_date = header.date; + xmlNode.attr_north = header.north; + xmlNode.attr_south = header.south; + xmlNode.attr_east = header.east; + xmlNode.attr_west = header.west; + xmlNode.attr_vendor = header.vendor; + + } + + public writeRoad ( xmlNode, road: TvRoad ) { + + const nodeRoad = { + attr_name: road.name, + attr_length: road.length, + attr_id: road.id, + attr_junction: road.junction, + }; + + xmlNode.road.push( nodeRoad ); + + this.writeRoadLinks( nodeRoad, road ); + + this.writeRoadType( nodeRoad, road ); + + this.writePlanView( nodeRoad, road ); + + this.writeElevationProfile( nodeRoad, road ); + + this.writeLateralProfile( nodeRoad, road ); + + this.writeLanes( nodeRoad, road ); + + this.writeObjects( nodeRoad, road ); + + this.writeSignals( nodeRoad, road ); + } + + public writeRoadLinks ( xmlNode, road: TvRoad ) { + + xmlNode.link = {}; + + if ( road.predecessor != null ) { + + if ( road.predecessor.elementType === 'junction' ) { + + xmlNode.link.predecessor = { + attr_elementType: road.predecessor.attr_elementType, + attr_elementId: road.predecessor.attr_elementId, + }; + + } else { + + xmlNode.link.predecessor = { + attr_elementType: road.predecessor.elementType, + attr_elementId: road.predecessor.elementId, + attr_contactPoint: road.predecessor.contactPoint, + }; + + } + } + + if ( road.successor != null ) { + + if ( road.successor.elementType === 'junction' ) { + + xmlNode.link.successor = { + attr_elementType: road.successor.attr_elementType, + attr_elementId: road.successor.attr_elementId, + }; + + } else { + + xmlNode.link.successor = { + attr_elementType: road.successor.elementType, + attr_elementId: road.successor.elementId, + attr_contactPoint: road.successor.contactPoint, + }; + + } + } + + // // dont support neighbour + // if ( road.link != null ) { + // + // for ( let i = 0; i < road.link.getNeighborCount(); i++ ) { + // + // const neighbor = road.link.getNeighbour( i ); + // + // xmlNode.link.neighbor.push( { + // attr_side: neighbor.attr_side, + // attr_elementId: neighbor.attr_elementId, + // attr_direction: neighbor.attr_direction, + // } ); + // + // } + // } + + } + + public writeRoadType ( xmlNode, road: TvRoad ) { + + if ( road.type.length > 0 ) { + + xmlNode.type = []; + + road.getTypes().forEach( type => { + + const typeXml = { + attr_s: type.s, + attr_type: type.type, + }; + + if ( type.speed ) { + typeXml[ 'speed' ] = {}; + typeXml[ 'speed' ][ 'attr_max' ] = type.speed.max; + typeXml[ 'speed' ][ 'attr_unit' ] = type.speed.unit; + } + + xmlNode.type.push( typeXml ); + + } ); + } + + } + + public writePlanView ( xmlNode, road: TvRoad ) { + + xmlNode.planView = { + geometry: [] + }; + + const geometryCount = road.getGeometryBlockCount(); + + for ( let i = 0; i < geometryCount; i++ ) { + this.writeGeometryBlock( xmlNode.planView, road.getGeometryBlock( i ) ); + } + } + + public writeGeometryBlock ( xmlNode, geometryBlock: TvAbstractRoadGeometry ) { + + const nodeGeometry = { + attr_s: geometryBlock.s.toExponential( 16 ), + attr_x: geometryBlock.x.toExponential( 16 ), + attr_y: geometryBlock.y.toExponential( 16 ), + attr_hdg: geometryBlock.hdg.toExponential( 16 ), + attr_length: geometryBlock.length.toExponential( 16 ), + }; + + xmlNode.geometry.push( nodeGeometry ); + + // TODO: ADD POLY3 GEOMETRY + switch ( geometryBlock.geometryType ) { + + case TvGeometryType.LINE: + + nodeGeometry[ 'line' ] = null; + + break; + + case TvGeometryType.ARC: + + const arc = geometryBlock as TvArcGeometry; + + nodeGeometry[ 'arc' ] = {}; + nodeGeometry[ 'arc' ][ 'attr_curvature' ] = arc.curvature; + + break; + + case TvGeometryType.SPIRAL: + + const sprial = geometryBlock as TvSpiralGeometry; + + // TODO: ADD REST OF THE VALUES + nodeGeometry[ 'spiral' ] = {}; + nodeGeometry[ 'spiral' ][ 'attr_curvStart' ] = sprial.attr_curvStart; + nodeGeometry[ 'spiral' ][ 'attr_curvEnd' ] = sprial.attr_curvEnd; + + break; + + case TvGeometryType.POLY3: + + const poly3 = geometryBlock as TvPoly3Geometry; + + nodeGeometry[ 'poly3' ] = {}; + nodeGeometry[ 'poly3' ][ 'attr_a' ] = poly3.attr_a; + nodeGeometry[ 'poly3' ][ 'attr_b' ] = poly3.attr_b; + nodeGeometry[ 'poly3' ][ 'attr_c' ] = poly3.attr_c; + nodeGeometry[ 'poly3' ][ 'attr_d' ] = poly3.attr_d; + + break; + + case TvGeometryType.PARAMPOLY3: + + const paramPoly3 = geometryBlock as TvParamPoly3Geometry; + + nodeGeometry[ 'paramPoly3' ] = {}; + + nodeGeometry[ 'paramPoly3' ][ 'attr_aU' ] = paramPoly3.aU; + nodeGeometry[ 'paramPoly3' ][ 'attr_bU' ] = paramPoly3.bU; + nodeGeometry[ 'paramPoly3' ][ 'attr_cU' ] = paramPoly3.cU; + nodeGeometry[ 'paramPoly3' ][ 'attr_dU' ] = paramPoly3.dU; + + nodeGeometry[ 'paramPoly3' ][ 'attr_aV' ] = paramPoly3.aV; + nodeGeometry[ 'paramPoly3' ][ 'attr_bV' ] = paramPoly3.bV; + nodeGeometry[ 'paramPoly3' ][ 'attr_cV' ] = paramPoly3.cV; + nodeGeometry[ 'paramPoly3' ][ 'attr_dV' ] = paramPoly3.dV; + + break; + } + + } + + public writeGeometry ( xmlNode, roadGeometry, geometryType ) { + } + + public writeElevationProfile ( xmlNode, road: TvRoad ) { + + const elevationProfile = road.getElevationProfile(); + + if ( elevationProfile != null ) { + + xmlNode.elevationProfile = { + elevation: [] + }; + + if ( elevationProfile.getElevationCount() == 0 ) road.addElevation( 0, 0, 0, 0, 0 ); + + for ( let i = 0; i < elevationProfile.getElevationCount(); i++ ) { + + const element = elevationProfile.getElevation( i ); + + xmlNode.elevationProfile.elevation.push( { + attr_s: element.s, + attr_a: element.a, + attr_b: element.b, + attr_c: element.c, + attr_d: element.d, + } ); + + } + + } + + } + + public writeLateralProfile ( xmlNode, road: TvRoad ) { + } + + public writeLanes ( xmlNode, road: TvRoad ) { + + xmlNode.lanes = { + laneOffset: [], + laneSection: [] + }; + + this.writeLaneOffset( xmlNode.lanes, road ); + + for ( let i = 0; i < road.getLaneSectionCount(); i++ ) { + this.writeLaneSections( xmlNode.lanes, road.getLaneSection( i ) ); + } + } + + public writeLaneOffset ( xmlNode, road: TvRoad ) { + + const laneOffsets = road.getLaneOffsets(); + + if ( laneOffsets.length === 0 ) road.addLaneOffset( 0, 0, 0, 0, 0 ); + + if ( laneOffsets != null ) { + + xmlNode.laneOffset = []; + + laneOffsets.forEach( laneOffset => { + + xmlNode.laneOffset.push( { + attr_s: laneOffset.s, + attr_a: laneOffset.a, + attr_b: laneOffset.b, + attr_c: laneOffset.c, + attr_d: laneOffset.d, + } ); + + } ); + + } + } + + public writeLaneSections ( xmlNode, laneSection: TvLaneSection ) { + + const leftLanes = { + lane: [] + }; + const centerLanes = { + lane: [] + }; + const rightLanes = { + lane: [] + }; + + for ( let i = 0; i < laneSection.getLaneCount(); i++ ) { + + const lane = laneSection.getLane( i ); + const side = lane.getSide(); + + if ( side === TvLaneSide.LEFT ) { + + this.writeLane( leftLanes, lane ); + + } else if ( side === TvLaneSide.RIGHT ) { + + this.writeLane( rightLanes, lane ); + + } else if ( side === TvLaneSide.CENTER ) { + + this.writeLane( centerLanes, lane ); + + } + } + + const laneSectionNode = { + attr_s: laneSection.s, + }; + + if ( leftLanes.lane.length > 0 ) laneSectionNode[ "left" ] = leftLanes; + + if ( centerLanes.lane.length > 0 ) laneSectionNode[ "center" ] = centerLanes; + + if ( rightLanes.lane.length > 0 ) laneSectionNode[ "right" ] = rightLanes; + + xmlNode.laneSection.push( laneSectionNode ); + + } + + public writeLane ( xmlNode, lane: TvLane ): any { + + const laneNode = { + attr_id: lane.id, + attr_type: lane.type, + attr_level: lane.level, + link: {}, + width: [], + roadMark: [], + material: [], + visibility: [], + speed: [], + access: [], + height: [] + }; + + this.writeLaneLinks( laneNode, lane ); + + for ( let i = 0; i < lane.getLaneWidthCount(); i++ ) { + this.writeLaneWidth( laneNode, lane.getLaneWidth( i ) ); + } + + for ( let i = 0; i < lane.getLaneRoadMarkCount(); i++ ) { + this.writeLaneRoadMark( laneNode, lane.getLaneRoadMark( i ) ); + } + + for ( let i = 0; i < lane.getLaneMaterialCount(); i++ ) { + this.writeLaneMaterial( laneNode, lane.getLaneMaterial( i ) ); + } + + for ( let i = 0; i < lane.getLaneVisibilityCount(); i++ ) { + this.writeLaneVisibility( laneNode, lane.getLaneVisibility( i ) ); + } + + for ( let i = 0; i < lane.getLaneSpeedCount(); i++ ) { + this.writeLaneSpeed( laneNode, lane.getLaneSpeed( i ) ); + } + + for ( let i = 0; i < lane.getLaneAccessCount(); i++ ) { + this.writeLaneAccess( laneNode, lane.getLaneAccess( i ) ); + } + + for ( let i = 0; i < lane.getLaneHeightCount(); i++ ) { + this.writeLaneHeight( laneNode, lane.getLaneHeight( i ) ); + } + + xmlNode.lane.push( laneNode ); + + return laneNode; + } + + public writeLaneWidth ( xmlNode, laneWidth: TvLaneWidth ) { + + xmlNode.width.push( { + attr_sOffset: laneWidth.s, + attr_a: laneWidth.a, + attr_b: laneWidth.b, + attr_c: laneWidth.c, + attr_d: laneWidth.d, + } ); + } + + public writeLaneRoadMark ( xmlNode, laneRoadMark: TvLaneRoadMark ) { + + xmlNode.roadMark.push( { + attr_sOffset: laneRoadMark.sOffset, + attr_type: laneRoadMark.type, + attr_weight: laneRoadMark.weight, + attr_color: laneRoadMark.color, + attr_material: laneRoadMark.material, + attr_width: laneRoadMark.width, + attr_laneChange: laneRoadMark.laneChange, + attr_height: laneRoadMark.height, + } ); + } + + public writeLaneMaterial ( xmlNode, laneMaterial: TvLaneMaterial ) { + + xmlNode.material.push( { + attr_sOffset: laneMaterial.sOffset, + attr_surface: laneMaterial.surface, + attr_friction: laneMaterial.friction, + attr_roughness: laneMaterial.roughness, + } ); + } + + public writeLaneVisibility ( xmlNode, laneVisibility: TvLaneVisibility ) { + + xmlNode.visibility.push( { + attr_sOffset: laneVisibility.sOffset, + attr_forward: laneVisibility.forward, + attr_back: laneVisibility.back, + attr_left: laneVisibility.left, + attr_right: laneVisibility.right, + } ); + } + + public writeLaneSpeed ( xmlNode, laneSpeed: TvLaneSpeed ) { + + xmlNode.speed.push( { + attr_sOffset: laneSpeed.sOffset, + attr_max: laneSpeed.max, + attr_unit: laneSpeed.unit, + } ); + } + + public writeLaneAccess ( xmlNode, laneAccess: TvLaneAccess ) { + + xmlNode.access.push( { + attr_sOffset: laneAccess.sOffset, + attr_restriction: laneAccess.restriction, + } ); + } + + public writeLaneHeight ( xmlNode, laneHeight: TvLaneHeight ) { + + xmlNode.height.push( { + attr_sOffset: laneHeight.sOffset, + attr_inner: laneHeight.inner, + attr_outer: laneHeight.outer, + } ); + } + + public writeObjects ( xmlNode, road: TvRoad ) { + + xmlNode.objects = { + object: [] + }; + + for ( let i = 0; i < road.getRoadObjectCount(); i++ ) { + + const roadObject = road.getRoadObject( i ); + + this.writeObject( xmlNode.objects, roadObject ); + + } + } + + public writeObject ( xmlNode, roadObject: TvRoadObject ) { + + const nodeRoadObject = { + + // Attributes + attr_type: roadObject.type, + attr_name: roadObject.name, + attr_id: roadObject.id, + attr_s: roadObject.s, + attr_t: roadObject.t, + + // Elements + repeat: [], + validity: [], + userData: [], + }; + + roadObject.zOffset ? nodeRoadObject[ 'attr_zOffset' ] = roadObject.zOffset : null; + roadObject.validLength ? nodeRoadObject[ 'attr_validLength' ] = roadObject.validLength : null; + roadObject.orientation ? nodeRoadObject[ 'attr_orientation' ] = roadObject.orientation : null; + roadObject.length ? nodeRoadObject[ 'attr_length' ] = roadObject.length : null; + roadObject.width ? nodeRoadObject[ 'attr_width' ] = roadObject.width : null; + roadObject.radius ? nodeRoadObject[ 'attr_radius' ] = roadObject.radius : null; + roadObject.height ? nodeRoadObject[ 'attr_height' ] = roadObject.height : null; + roadObject.hdg ? nodeRoadObject[ 'attr_hdg' ] = roadObject.hdg : null; + roadObject.pitch ? nodeRoadObject[ 'attr_pitch' ] = roadObject.pitch : null; + roadObject.roll ? nodeRoadObject[ 'attr_roll' ] = roadObject.roll : null; + + this.writeObjectRepeat( nodeRoadObject, roadObject ); + + this.writeObjectOutline( nodeRoadObject, roadObject.outline ); + + this.writeObjectMaterial( nodeRoadObject, roadObject ); + + this.writeObjectValidity( nodeRoadObject, roadObject ); + + this.writeObjectParkingSpace( nodeRoadObject, roadObject ); + + this.writeUserDataFromArray( nodeRoadObject.userData, roadObject.userData ); + + xmlNode.object.push( nodeRoadObject ); + } + + public writeObjectRepeat ( xmlNode, roadObject: TvRoadObject ) { + + xmlNode.repeat = []; + + for ( let i = 0; i < roadObject.getRepeatCount(); i++ ) { + + const repeat = roadObject.getRepeat( i ); + + xmlNode.repeat.push( { + attr_s: repeat.s, + attr_length: repeat.length, + attr_distance: repeat.distance, + attr_tStart: repeat.tStart, + attr_tEnd: repeat.tEnd, + attr_widthStart: repeat.widthStart, + attr_widthEnd: repeat.widthEnd, + attr_heightStart: repeat.heightStart, + attr_heightEnd: repeat.heightEnd, + attr_zOffsetStart: repeat.zOffsetStart, + attr_zOffsetEnd: repeat.zOffsetEnd, + } ); + } + } + + public writeObjectOutline ( xmlNode, objectOutline: TvObjectOutline ) { + + if ( objectOutline != null ) { + + xmlNode[ 'outline' ] = {}; + + xmlNode[ 'outline' ] = { + cornerRoad: [], + cornerLocal: [] + }; + + for ( let i = 0; i < objectOutline.getCornerRoadCount(); i++ ) { + + const cornerRoad = objectOutline.getCornerRoad( i ); + + // TODO: ACCESS VIA GETTERS & SETTERS + xmlNode.cornerRoad.push( { + attr_s: cornerRoad.attr_s, + attr_t: cornerRoad.attr_t, + attr_dz: cornerRoad.attr_dz, + attr_heigh: cornerRoad.attr_height, + } ); + } + + for ( let i = 0; i < objectOutline.getCornerLocalCount(); i++ ) { + + const cornerLocal = objectOutline.getCornerLocal( i ); + + // TODO: ACCESS VIA GETTERS & SETTERS + xmlNode.cornerLocal.push( { + attr_u: cornerLocal.attr_u, + attr_v: cornerLocal.attr_v, + attr_z: cornerLocal.attr_z, + attr_height: cornerLocal.attr_height, + } ); + } + } + } + + public writeObjectMaterial ( xmlNode, roadObject: TvRoadObject ) { + + if ( roadObject.material != null ) { + + xmlNode[ 'material' ] = {}; + + // TODO: ACCESS VIA GETTERS & SETTERS + xmlNode.material = { + attr_surface: roadObject.material.attr_surface, + attr_friction: roadObject.material.attr_friction, + attr_roughness: roadObject.material.attr_roughness, + }; + } + } + + public writeObjectValidity ( xmlNode, roadObject: TvRoadObject ) { + + xmlNode.validity = []; + + for ( let i = 0; i < roadObject.getValidityCount(); i++ ) { + + const validity = roadObject.getValidity( i ); + + // TODO: ACCESS VIA GETTERS & SETTER + xmlNode.validity.push( { + attr_fromLane: validity.attr_fromLane, + attr_toLane: validity.attr_toLane + } ); + } + } + + public writeObjectParkingSpace ( xmlNode, roadObject: TvRoadObject ) { + + if ( roadObject.parkingSpace != null ) { + + xmlNode[ 'parkingSpace' ] = {}; + + // TODO: ACCESS VIA GETTERS & SETTERS + xmlNode.parkingSpace = { + attr_access: roadObject.parkingSpace.attr_access, + attr_restriction: roadObject.parkingSpace.attr_restriction, + marking: [] + }; + + for ( let i = 0; i < roadObject.parkingSpace.getMarkingCount(); i++ ) { + + const marking = roadObject.parkingSpace.getMarking( i ); + + // TODO: ACCESS VIA GETTERS & SETTERS + xmlNode.parkingSpace.marking.push( { + attr_side: marking.attr_side, + attr_type: marking.attr_type, + attr_width: marking.attr_width, + attr_color: marking.attr_color, + } ); + } + } + } + + public writeSignals ( xmlNode, road: TvRoad ) { + + xmlNode.signals = { + signal: [] + }; + + road.signals.forEach( ( signal, signalId ) => { + + const nodeSignal = { + + // TODO: ACCESS VIA GETTERS & SETTERS + // Attributes + attr_s: signal.s, + attr_t: signal.t, + attr_id: signal.id, + attr_name: signal.name, + attr_dynamic: signal.dynamic, + attr_orientation: signal.orientations, + attr_zOffset: signal.zOffset, + attr_country: signal.country, + attr_type: signal.type, + attr_subtype: signal.subtype, + + // Elements + validity: [], + dependency: [], + signalReference: [], + userData: [], + }; + + if ( signal.value != null ) nodeSignal[ 'attr_value' ] = signal.value; + if ( signal.unit != null ) nodeSignal[ 'attr_unit' ] = signal.unit; + if ( signal.height != null ) nodeSignal[ 'attr_height' ] = signal.height; + if ( signal.width != null ) nodeSignal[ 'attr_width' ] = signal.width; + if ( signal.text != null ) nodeSignal[ 'attr_text' ] = signal.text; + if ( signal.hOffset != null ) nodeSignal[ 'attr_hOffset' ] = signal.hOffset; + if ( signal.pitch != null ) nodeSignal[ 'attr_pitch' ] = signal.pitch; + if ( signal.roll != null ) nodeSignal[ 'attr_roll' ] = signal.roll; + + signal.userDataMap.delete( 'sign_shape' ); + signal.userDataMap.set( 'sign_shape', new TvUserData( 'sign_shape', signal.signShape ) ); + + this.writeUserDataFromMap( nodeSignal.userData, signal.getUserData() ); + + for ( let j = 0; j < signal.getValidityCount(); j++ ) { + + const validity = signal.getValidity( j ); + + // TODO: ACCESS VIA GETTERS & SETTER + nodeSignal.validity.push( { + attr_fromLane: validity.attr_fromLane, + attr_toLane: validity.attr_toLane + } ); + } + + for ( let j = 0; j < signal.getDependencyCount(); j++ ) { + + const dependency = signal.getDependency( j ); + + nodeSignal.dependency.push( { + attr_id: dependency.id, + attr_type: dependency.type + } ); + } + + for ( let j = 0; j < signal.getSignalReferenceCount(); j++ ) { + + const signalReference = signal.getSignalReference( j ); + + const nodeSignalReference = { + attr_s: signalReference.s, + attr_t: signalReference.t, + attr_id: signalReference.id, + attr_orientation: signalReference.orientations, + validity: [] + }; + + for ( let k = 0; k < signalReference.getValidityCount(); k++ ) { + + const validity = signalReference.getValidity( k ); + + // TODO: ACCESS VIA GETTERS & SETTER + nodeSignalReference.validity.push( { + attr_fromLane: validity.attr_fromLane, + attr_toLane: validity.attr_toLane + } ); + } + + nodeSignal.signalReference.push( nodeSignalReference ); + } + + xmlNode.signals.signal.push( nodeSignal ); + + } ); + + } + + public writeSurface ( xmlNode, road: TvRoad ) { + } + + public writeControllers ( xmlNode ) { + + xmlNode.controller = []; + + this.openDrive.controllers.forEach( controller => { + + const nodeController = { + attr_id: controller.id, + attr_name: controller.name, + control: [] + }; + + if ( controller.sequence ) { + nodeController[ 'attr_sequence' ] = controller.sequence; + } + + controller.controls.forEach( control => { + + nodeController.control.push( { + attr_signalId: control.signalId, + attr_type: control.type + } ); + + } ); + + xmlNode.controller.push( nodeController ); + + } ); + } + + public writeJunction ( xmlNode, junction: TvJunction ) { + + if ( junction.connections.size === 0 ) return; + + const nodeJunction = { + attr_id: junction.id, + attr_name: junction.name, + connection: [], + priority: [], + controller: [] + }; + + this.writeJunctionConnection( nodeJunction, junction ); + + this.writeJunctionPriority( nodeJunction, junction ); + + this.writeJunctionController( nodeJunction, junction ); + + xmlNode.junction.push( nodeJunction ); + } + + public writeJunctionConnection ( xmlNode, junction: TvJunction ) { + + junction.connections.forEach( connection => { + + const nodeConnection = { + attr_id: connection.id, + attr_incomingRoad: connection.incomingRoad, + attr_connectingRoad: connection.connectingRoad, + laneLink: [] + }; + + if ( connection.contactPoint != null ) { + + nodeConnection[ 'attr_contactPoint' ] = connection.contactPoint; + + } + + this.writeJunctionConnectionLaneLink( nodeConnection, connection ); + + xmlNode.connection.push( nodeConnection ); + + } ); + + } + + public writeJunctionConnectionLaneLink ( xmlNode, junctionConnection: TvJunctionConnection ) { + + for ( let i = 0; i < junctionConnection.getJunctionLaneLinkCount(); i++ ) { + + const laneLink = junctionConnection.getJunctionLaneLink( i ); + + xmlNode.laneLink.push( { + attr_from: laneLink.from, + attr_to: laneLink.to, + } ); + } + } + + public writeJunctionPriority ( xmlNode, junction: TvJunction ) { + + for ( let i = 0; i < junction.getJunctionPriorityCount(); i++ ) { + + const priority = junction.getJunctionPriority( i ); + + xmlNode.priority.push( { + attr_high: priority.high, + attr_low: priority.low, + } ); + } + } + + public writeJunctionController ( xmlNode, junction: TvJunction ) { + + for ( let i = 0; i < junction.getJunctionControllerCount(); i++ ) { + + const controller = junction.getJunctionController( i ); + + xmlNode.priority.push( { + attr_id: controller.id, + attr_type: controller.type, + attr_sequence: controller.sequence, + } ); + } + } + + public writeUserDataFromArray ( xmlNode, userData: TvUserData[] ) { + + for ( let i = 0; i < userData.length; i++ ) { + + const data = userData[ i ]; + + xmlNode.push( { + attr_code: data.attr_code, + attr_value: data.attr_value + } ); + + } + } + + public writeUserDataFromMap ( xmlNode, userData: Map ) { + + userData.forEach( userData => { + + xmlNode.push( { + + attr_code: userData.attr_code, + attr_value: userData.attr_value + + } ); + + } ); + + } + +} + + diff --git a/src/app/modules/tv-map/services/tv-carla-exporter.ts b/src/app/modules/tv-map/services/tv-carla-exporter.ts new file mode 100644 index 00000000..7cd4bebf --- /dev/null +++ b/src/app/modules/tv-map/services/tv-carla-exporter.ts @@ -0,0 +1,36 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { OdWriter } from './open-drive-writer.service'; +import { TvLane } from '../models/tv-lane'; + +export class TvCarlaExporter extends OdWriter { + + // override default + public writeLane ( xmlNode, lane: TvLane ) { + + return super.writeLane( xmlNode, lane ); + + // // below logic is probably not required + // // carla tv-map importnig process does not parse any user data with travel direction + + // const laneXmlNode = super.writeLane( xmlNode, lane ); + + // if ( lane.side == LaneSide.CENTER ) return; + + // let direction = "undirected"; + + // if ( lane.side == LaneSide.LEFT && lane.type == OdLaneType.driving ) direction = "backward"; + + // if ( lane.side === LaneSide.RIGHT && lane.type == OdLaneType.driving ) direction = "forward"; + + // laneXmlNode.userData = { + // vectorLane: { + // attr_travelDir: direction + // } + // } + + } + +} diff --git a/src/app/modules/tv-map/services/tv-map-source-file.ts b/src/app/modules/tv-map/services/tv-map-source-file.ts new file mode 100644 index 00000000..4eb273ca --- /dev/null +++ b/src/app/modules/tv-map/services/tv-map-source-file.ts @@ -0,0 +1,36 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { TvMap } from '../models/tv-map.model'; +import { EventEmitter } from '@angular/core'; +import { IFile } from '../../../core/models/file'; + +export class TvMapSourceFile { + + static roadNetworkChanged = new EventEmitter(); + static currentFile: IFile; + + private static _openDrive: TvMap = new TvMap; + + static get openDrive (): TvMap { + return this._openDrive; + } + + static set openDrive ( value: TvMap ) { + this._openDrive = value; + this.roadNetworkChanged.emit( value ); + } + + static clearOpenDrive () { + // console.error( 'method not implemented' ); + } + + static clearScene () { + // console.error( 'method not implemented' ); + } + + static redraw () { + this.roadNetworkChanged.emit( this.openDrive ); + } +} diff --git a/src/app/modules/tv-map/services/tv-map.service.ts b/src/app/modules/tv-map/services/tv-map.service.ts new file mode 100644 index 00000000..5cddc685 --- /dev/null +++ b/src/app/modules/tv-map/services/tv-map.service.ts @@ -0,0 +1,245 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { Injectable } from '@angular/core'; +import { FileService } from '../../../services/file.service'; +import { OpenDriverParser } from './open-drive-parser.service'; +import { TvMapSourceFile } from './tv-map-source-file'; +import { IFile } from '../../../core/models/file'; +import { TvMapBuilder } from '../builders/od-builder.service'; +import { OdWriter } from './open-drive-writer.service'; +import { FileApiService } from 'app/core/services/file-api.service'; +import { SnackBar } from 'app/services/snack-bar.service'; +import { TvMap } from '../models/tv-map.model'; +import { ElectronService } from 'ngx-electron'; + +import { saveAs } from 'file-saver'; +import { ToolManager } from 'app/core/tools/tool-manager'; +import { AppInspector } from 'app/core/inspector'; +import { CommandHistory } from 'app/services/command-history'; + +@Injectable( { + providedIn: 'root' +} ) +export class TvMapService { + + constructor ( + private fileService: FileService, + private writer: OdWriter, + private fileApiService: FileApiService, + private electron: ElectronService, + private openDriveParser: OpenDriverParser + ) { + + // not reqiured now because open scenario not being used + // OdSourceFile.roadNetworkChanged.subscribe( ( e ) => { + // OdBuilder.makeOpenDrive( this.openDrive ); + // } ); + + } + + public get currentFile () { + return TvMapSourceFile.currentFile; + } + + public set currentFile ( value ) { + TvMapSourceFile.currentFile = value; + } + + public get openDrive () { + return TvMapSourceFile.openDrive; + } + + public set openDrive ( value ) { + TvMapSourceFile.openDrive = value; + } + + /** + * @deprecated + */ + newFile () { + + if ( this.openDrive ) this.openDrive.destroy(); + + this.currentFile = new IFile( 'untitled.xml' ); + + this.openDrive = new TvMap(); + + } + + /** + * @deprecated + */ + async open () { + + const filepaths = await this.fileService.showAsyncDialog(); + + if ( filepaths == null || filepaths.length == 0 ) return; + + const contents = await this.fileService.readAsync( filepaths[ 0 ] ); + + if ( this.openDrive ) this.openDrive.destroy(); + + this.openDrive = this.openDriveParser.parse( contents ); + + ToolManager.clear(); + + AppInspector.clear(); + + CommandHistory.clear(); + + // set to currently file pah + this.currentFile = new IFile( 'untitled.xml' ); + + TvMapBuilder.buildMap( this.openDrive ); + + } + + public import ( file: IFile, callbackFn = null ) { + + ToolManager.clear(); + + AppInspector.clear(); + + CommandHistory.clear(); + + if ( this.openDrive != null ) this.openDrive.destroy(); + + this.currentFile = file; + + let parser = new OpenDriverParser(); + + this.openDrive = parser.parse( file.contents ); + + TvMapBuilder.buildMap( this.openDrive ); + + if ( callbackFn != null ) callbackFn(); + + // Important! removes garbage + parser = undefined; + + SnackBar.success( 'File Imported' ); + } + + public importFromPath ( filepath: string, callbackFn = null ) { + + this.fileService.readFile( filepath, 'xml', ( file: IFile ) => { + + this.import( file, callbackFn ); + + } ); + + } + + public importContent ( contents: string ) { + + const file = new IFile(); + + file.name = 'Untitled.xml'; + file.contents = contents; + + this.import( file ); + + } + + /** + * @deprecated + */ + save () { + + if ( this.currentFile == null ) { + + throw new Error( 'Create file before saving' ); + + } + + this.currentFile.contents = this.writer.getOutput( this.openDrive ); + + if ( this.currentFile.online ) { + + this.saveOnline( this.currentFile ); + + } else { + + this.saveLocally( this.currentFile ); + + } + + } + + getOutput () { + + return this.writer.getOutput( this.openDrive ); + + } + + /** + * + * @deprecated + * @param file + */ + saveLocally ( file: IFile ) { + + // path exists means it was imported locally + if ( this.currentFile.path != null ) { + + this.fileService.saveFile( file.path, file.contents, ( file: IFile ) => { + + this.currentFile.path = file.path; + this.currentFile.name = file.name; + + SnackBar.success( 'File Saved!' ); + + } ); + + } else { + + this.saveAs(); + + } + } + + saveLocallyAt ( path: string ) { + + const contents = this.getOutput(); + + this.fileService.saveFile( path, contents, ( file: IFile ) => { + + this.currentFile.path = file.path; + this.currentFile.name = file.name; + + } ); + } + + saveOnline ( file: IFile ) { + + this.fileApiService.save( file ).subscribe( res => { + + SnackBar.success( 'File Saved (Online)!' ); + + } ); + + } + + saveAs () { + + const contents = this.writer.getOutput( this.openDrive ); + + if ( this.electron.isElectronApp ) { + + this.fileService.saveAsFile( null, contents, ( file: IFile ) => { + + this.currentFile.path = file.path; + this.currentFile.name = file.name; + + } ); + + } else { + + saveAs( new Blob( [ contents ] ), 'road.xodr' ); + + } + + } +} diff --git a/src/app/modules/tv-map/services/tv-marking.service.ts b/src/app/modules/tv-map/services/tv-marking.service.ts new file mode 100644 index 00000000..5c1abf5a --- /dev/null +++ b/src/app/modules/tv-map/services/tv-marking.service.ts @@ -0,0 +1,106 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { EventEmitter, Injectable } from '@angular/core'; +import { Math, Mesh, Shape, ShapeBufferGeometry, BoxBufferGeometry, PlaneBufferGeometry, Texture } from 'three'; +import * as THREE from 'three'; +import { AssetDatabase } from 'app/services/asset-database'; +import { GameObject } from 'app/core/game-object'; + +export enum MarkingTypes { + point = 'point', + curve = 'curve', +} + +export class TvRoadMarking { + + public static extension = 'roadmarking'; + + public static tag = 'roadmarking'; + + public mesh: Mesh; + + constructor ( public name: string, public type: MarkingTypes, public textureGuid: string ) { + + this.mesh = this.makeMesh( new Shape() ); + + } + + static new (): TvRoadMarking { + + return new TvRoadMarking( 'NewRoadMarking', MarkingTypes.point, null ); + + } + + static importFromString ( contents: string ): TvRoadMarking { + + const json = JSON.parse( contents ); + + return new TvRoadMarking( json.name, json.type, json.textureGuid ); + + } + + makeMesh ( shape: Shape ): Mesh { + + const geometry = new PlaneBufferGeometry(); + + const texture = AssetDatabase.getInstance( this.textureGuid ); + + // texture.wrapS = texture.wrapT = THREE.RepeatWrapping; + // texture.repeat.set( 0.008, 0.008 ); + // texture.anisotropy = 16; + // texture.encoding = THREE.sRGBEncoding;> + + const material = new THREE.MeshLambertMaterial( { map: texture, transparent: true, alphaTest: 0.1 } ); + + const mesh = new GameObject( this.name, geometry, material ); + + mesh.position.set( 0, 0, 0.01 ); + + mesh.Tag = TvRoadMarking.tag; + + mesh.userData.roadmarking = this; + + return mesh; + } + + + toJSONString (): any { + + return JSON.stringify( { + name: this.name, + type: this.type, + textureGuid: this.textureGuid, + }, null, 2 ); + } + + clone () { + + return new TvRoadMarking( this.name, this.type, this.textureGuid ); + + } + +} + + +export class TvMarkingService { + + public static markingChanged = new EventEmitter(); + + private static selectedMarking: TvRoadMarking; + + static get currentMarking () { + + return this.selectedMarking; + + } + + static set currentMarking ( value ) { + + this.selectedMarking = value; + + this.markingChanged.emit( value ); + + } +} diff --git a/src/app/modules/tv-map/services/tv-shortcuts.service.ts b/src/app/modules/tv-map/services/tv-shortcuts.service.ts new file mode 100644 index 00000000..6ff796d4 --- /dev/null +++ b/src/app/modules/tv-map/services/tv-shortcuts.service.ts @@ -0,0 +1,65 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { Injectable } from '@angular/core'; +import { IKeyboardShortcut } from 'app/core/interfaces/shortcuts'; +import { TvShorcutFactory } from '../shortcuts/od-shortcut-factory'; + +@Injectable( { + providedIn: 'root' +} ) +export class OdShortcutService { + + private static mShortcuts: IKeyboardShortcut[] = []; + + constructor () { + + TvShorcutFactory.shortcuts.forEach( ( shortcut ) => { + + OdShortcutService.mShortcuts.push( new shortcut ); + + } ); + + } + + get shortcuts (): IKeyboardShortcut[] { + + return OdShortcutService.mShortcuts; + } + + static get shortcuts (): IKeyboardShortcut[] { + + return OdShortcutService.mShortcuts; + } + + public register ( shortcut: IKeyboardShortcut ) { + + OdShortcutService.mShortcuts.push( shortcut ); + + } + + public static register ( shortcut: IKeyboardShortcut ): any { + + this.mShortcuts.push( shortcut ); + + } + + public handleKeyDown ( e: KeyboardEvent ): any { + + this.shortcuts.forEach( ( shortcut ) => { + + if ( shortcut.check( e ) ) { + + shortcut.execute(); + + return false; + + } + + } ); + + } + + +} \ No newline at end of file diff --git a/src/app/modules/tv-map/services/tv-sign.service.ts b/src/app/modules/tv-map/services/tv-sign.service.ts new file mode 100644 index 00000000..3746fbb0 --- /dev/null +++ b/src/app/modules/tv-map/services/tv-sign.service.ts @@ -0,0 +1,60 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { EventEmitter, Injectable } from '@angular/core'; + +export interface ISign { + name: string; + shape: SignShapeType; +} + +export enum SignShapeType { + circle = 'circle', + square = 'square', + diamond = 'diamond', + triangle = 'triangle', + triangle_inverted = 'triangle_inverted', + square_tilted = 'square_tilted', + rectangle = 'rectangle' +} + +@Injectable( { + providedIn: 'root' +} ) +export class TvSignService { + + public signChanged = new EventEmitter(); + + private selectedSign: ISign; + + constructor () { + } + + get currentSign () { + + return this.selectedSign; + + } + + set currentSign ( value ) { + + this.selectedSign = value; + this.signChanged.emit( value ); + + } + + get signs (): ISign[] { + return [ + { name: 'stop', shape: SignShapeType.diamond }, + { name: 'no_entry', shape: SignShapeType.circle }, + { name: 'two_way_traffic', shape: SignShapeType.circle }, + { name: 'road_closed', shape: SignShapeType.rectangle }, + { name: 'sharp_left_curve_ahead', shape: SignShapeType.square_tilted }, + { name: 'sharp_right_curve_ahead', shape: SignShapeType.square_tilted }, + { name: 'slow', shape: SignShapeType.square_tilted }, + { name: 'chevron_left', shape: SignShapeType.rectangle }, + { name: 'chevron_right', shape: SignShapeType.rectangle }, + ]; + } +} diff --git a/src/app/modules/tv-map/services/tv-signal-helper.ts b/src/app/modules/tv-map/services/tv-signal-helper.ts new file mode 100644 index 00000000..fd83738f --- /dev/null +++ b/src/app/modules/tv-map/services/tv-signal-helper.ts @@ -0,0 +1,33 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { TvRoad } from '../models/tv-road.model'; +import { TvRoadSignal } from '../models/tv-road-signal.model'; +import { OdSignalBuilder } from '../builders/od-signal-builder'; + +export class TvSignalHelper { + + private signalFactory = new OdSignalBuilder(); + + constructor ( private road: TvRoad ) { + + } + + public create () { + + this.road.signals.forEach( signal => { + + this.createSignal( signal ); + + } ); + + } + + createSignal ( signal: TvRoadSignal ) { + + this.signalFactory.createSignalGameObject( this.road, signal ); + + + } +} diff --git a/src/app/modules/tv-map/shortcuts/new-file-shortcut.ts b/src/app/modules/tv-map/shortcuts/new-file-shortcut.ts new file mode 100644 index 00000000..a59bcae4 --- /dev/null +++ b/src/app/modules/tv-map/shortcuts/new-file-shortcut.ts @@ -0,0 +1,20 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { TvBaseShortcut } from './tv-base-shortcut'; + +export class NewFileShortcut extends TvBaseShortcut { + + check ( e: KeyboardEvent ): boolean { + // CMD + N + return ( e.metaKey == true && e.keyCode == 78 ); + } + + execute (): void { + + // createNewFile(); + + } + +} diff --git a/src/app/modules/tv-map/shortcuts/od-shortcut-factory.ts b/src/app/modules/tv-map/shortcuts/od-shortcut-factory.ts new file mode 100644 index 00000000..a617d29e --- /dev/null +++ b/src/app/modules/tv-map/shortcuts/od-shortcut-factory.ts @@ -0,0 +1,22 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { SaveShortcut } from './save-file-shortcut'; +import { NewFileShortcut } from './new-file-shortcut'; +import { UndoShortcut } from './undo-shortcut'; +import { RedoShortcut } from './redo-shortcut'; +import { SaveAsShortcut } from './save-as-shortcut'; + +export class TvShorcutFactory { + + static shortcuts: Array = [ + NewFileShortcut, + SaveAsShortcut, + SaveShortcut, + RedoShortcut, + UndoShortcut, + ]; + + +} \ No newline at end of file diff --git a/src/app/modules/tv-map/shortcuts/redo-shortcut.ts b/src/app/modules/tv-map/shortcuts/redo-shortcut.ts new file mode 100644 index 00000000..d51217c4 --- /dev/null +++ b/src/app/modules/tv-map/shortcuts/redo-shortcut.ts @@ -0,0 +1,19 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { TvBaseShortcut } from './tv-base-shortcut'; +import { CommandHistory } from '../../../services/command-history'; + +export class RedoShortcut extends TvBaseShortcut { + + check ( e: KeyboardEvent ): boolean { + // CMD + shift + Z + return ( e.metaKey && e.shiftKey && e.keyCode == 90 ); + } + + execute (): void { + CommandHistory.redo(); + } + +} diff --git a/src/app/modules/tv-map/shortcuts/save-as-shortcut.ts b/src/app/modules/tv-map/shortcuts/save-as-shortcut.ts new file mode 100644 index 00000000..e984d906 --- /dev/null +++ b/src/app/modules/tv-map/shortcuts/save-as-shortcut.ts @@ -0,0 +1,20 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { TvBaseShortcut } from './tv-base-shortcut'; + +export class SaveAsShortcut extends TvBaseShortcut { + + check ( e: KeyboardEvent ): boolean { + // CMD + shift + S + return ( e.metaKey && e.shiftKey && e.keyCode == 83 ); + } + + execute (): void { + + // saveFileAs(); + + } + +} diff --git a/src/app/modules/tv-map/shortcuts/save-file-shortcut.ts b/src/app/modules/tv-map/shortcuts/save-file-shortcut.ts new file mode 100644 index 00000000..993fc2ea --- /dev/null +++ b/src/app/modules/tv-map/shortcuts/save-file-shortcut.ts @@ -0,0 +1,18 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { TvBaseShortcut } from './tv-base-shortcut'; + +export class SaveShortcut extends TvBaseShortcut { + + check ( e: KeyboardEvent ): boolean { + // CMD + S + return ( e.keyCode == 83 && e.metaKey == true ); + } + + execute (): void { + // save + } + +} diff --git a/src/app/modules/tv-map/shortcuts/tv-base-shortcut.ts b/src/app/modules/tv-map/shortcuts/tv-base-shortcut.ts new file mode 100644 index 00000000..2ad3454b --- /dev/null +++ b/src/app/modules/tv-map/shortcuts/tv-base-shortcut.ts @@ -0,0 +1,21 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { AbstractKeyboardShortcut } from 'app/core/interfaces/shortcuts'; + +export abstract class TvBaseShortcut extends AbstractKeyboardShortcut { + + abstract check ( e: KeyboardEvent ): boolean; + + abstract execute (): void; + + constructor () { + + super(); + + } + +} + + diff --git a/src/app/modules/tv-map/shortcuts/undo-shortcut.ts b/src/app/modules/tv-map/shortcuts/undo-shortcut.ts new file mode 100644 index 00000000..5394aad4 --- /dev/null +++ b/src/app/modules/tv-map/shortcuts/undo-shortcut.ts @@ -0,0 +1,19 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { TvBaseShortcut } from './tv-base-shortcut'; +import { CommandHistory } from '../../../services/command-history'; + +export class UndoShortcut extends TvBaseShortcut { + + check ( e: KeyboardEvent ): boolean { + // CMD + Z + return ( e.metaKey == true && e.keyCode == 90 ); + } + + execute (): void { + CommandHistory.undo(); + } + +} diff --git a/src/app/modules/tv-map/tv-map.module.ts b/src/app/modules/tv-map/tv-map.module.ts new file mode 100644 index 00000000..c9d4a7f6 --- /dev/null +++ b/src/app/modules/tv-map/tv-map.module.ts @@ -0,0 +1,142 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { SharedModule } from 'app/shared/shared.module'; +import { PerfectScrollbarModule } from 'ngx-perfect-scrollbar'; +import { RouterModule } from '@angular/router'; +import { + MatButtonModule, + MatCardModule, + MatDividerModule, + MatFormFieldModule, + MatGridListModule, + MatIconModule, + MatInputModule, + MatMenuModule, + MatSelectModule, + MatSidenavModule, + MatTabsModule, + MatToolbarModule, + MatTooltipModule +} from '@angular/material'; +import { ThreeJsModule } from '../three-js/three-js.module'; +import { FormsModule } from '@angular/forms'; +import { OdSignalInspectorComponent } from '../../views/inspectors/signal-inspector/signal-inspector.component'; +import { CoreModule } from 'app/core/core.module'; +import { LaneWidthInspector } from '../../views/inspectors/lane-width-inspector/lane-width-inspector.component'; +import { FlexLayoutModule } from '@angular/flex-layout'; +import { NewRoadDialogComponent } from './dialogs/new-road-dialog/new-road-dialog.component'; +import { RoadInspector } from '../../views/inspectors/road-inspector/road-inspector.component'; +import { LaneRoadmarkInspectorComponent } from '../../views/inspectors/lane-roadmark-inspector/lane-roadmark-inspector.component'; +import { LaneInspectorComponent } from '../../views/inspectors/lane-type-inspector/lane-inspector.component'; +import { SatPopoverModule } from '@ncstate/sat-popover'; +import { TranslateModule } from '@ngx-translate/core'; +import { ShapeInspectorComponent } from '../../views/inspectors/shape-inspector/shape-inspector.component'; +import { TransformInspectorComponent } from '../../views/inspectors/transform-inspector/transform-inspector.component'; +import { RoadObjectInspectorComponent } from '../../views/inspectors/road-object-inspector/road-object-inspector.component'; +import { PropModelInspectorComponent } from '../../views/inspectors/prop-model-inspector/prop-model-inspector.component'; +import { PropInstanceInspectorComponent } from '../../views/inspectors/prop-instance-inspector/prop-instance-inspector.component'; +import { PropCurveInspectorComponent } from '../../views/inspectors/prop-curve-inspector/prop-curve-inspector.component'; +import { LaneOffsetInspector } from '../../views/inspectors/lane-offset-inspector/lane-offset-inspector.component'; +import { TextureInspector } from '../../views/inspectors/texture-inspector/texture-inspector.component'; +import { MaterialInspector } from '../../views/inspectors/material-inspector/material-inspector.component'; +import { ObjectPreviewComponent } from '../../views/inspectors/object-preview/object-preview.component'; +import { RoadSignInspector } from '../../views/inspectors/road-sign-inspector/road-sign-inspector.component'; +import { MaterialFieldComponent } from '../../views/fields/material-field/material-field.component'; +import { ColorPickerModule } from 'ngx-color-picker'; +import { TextureFieldComponent } from '../../views/fields/texture-field/texture-field.component'; +import { LaneLinkInspector } from '../../views/inspectors/lane-link-inspector/lane-link-inspector.component'; +import { RoadControlPointInspector } from '../../views/inspectors/road-control-point-inspector/road-control-point-inspector.component'; +import { JunctionEntryInspector } from '../../views/inspectors/junction-entry-inspector/junction-entry-inspector.component'; +import { RoadStyleInspector } from 'app/views/inspectors/road-style-inspector/road-style-inspector.component'; +import { PropPolygonInspectorComponent } from 'app/views/inspectors/prop-polygon-inspector/prop-polygon-inspector.component'; +import { RoadMarkingInspector } from 'app/views/inspectors/road-marking-inspector/road-marking-inspector.component'; + + +@NgModule( { + declarations: [ + OdSignalInspectorComponent, + LaneInspectorComponent, + LaneWidthInspector, + NewRoadDialogComponent, + RoadInspector, + LaneRoadmarkInspectorComponent, + ShapeInspectorComponent, + TransformInspectorComponent, + RoadObjectInspectorComponent, + PropModelInspectorComponent, + PropInstanceInspectorComponent, + PropCurveInspectorComponent, + PropPolygonInspectorComponent, + LaneOffsetInspector, + TextureInspector, + MaterialInspector, + ObjectPreviewComponent, + RoadSignInspector, + RoadMarkingInspector, + MaterialFieldComponent, + TextureFieldComponent, + LaneLinkInspector, + RoadControlPointInspector, + JunctionEntryInspector, + RoadStyleInspector, + ], + imports: [ + CommonModule, + SharedModule, + PerfectScrollbarModule, + ThreeJsModule, + CoreModule, + FlexLayoutModule, + + MatSidenavModule, + MatToolbarModule, + MatButtonModule, + MatMenuModule, + MatDividerModule, + MatIconModule, + MatTabsModule, + MatCardModule, + MatGridListModule, + MatFormFieldModule, + MatInputModule, + MatSelectModule, + MatTooltipModule, + FormsModule, + SatPopoverModule, + TranslateModule, + ColorPickerModule, + ], + exports: [ + TransformInspectorComponent + ], + entryComponents: [ + OdSignalInspectorComponent, + LaneInspectorComponent, + LaneWidthInspector, + NewRoadDialogComponent, + RoadInspector, + LaneRoadmarkInspectorComponent, + ShapeInspectorComponent, + RoadObjectInspectorComponent, + PropModelInspectorComponent, + PropInstanceInspectorComponent, + PropCurveInspectorComponent, + PropPolygonInspectorComponent, + LaneOffsetInspector, + TextureInspector, + MaterialInspector, + RoadSignInspector, + RoadMarkingInspector, + LaneLinkInspector, + RoadControlPointInspector, + JunctionEntryInspector, + ObjectPreviewComponent, + RoadStyleInspector, + ] +} ) +export class TvMapModule { +} diff --git a/src/app/services/app-links.ts b/src/app/services/app-links.ts new file mode 100644 index 00000000..bcce69e6 --- /dev/null +++ b/src/app/services/app-links.ts @@ -0,0 +1,37 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { Environment } from 'app/core/utils/environment'; + +export class AppLinks { + + static get base () { return Environment.websiteUrl; } + + static get contactUsLink () { return this.base + 'contact'; } + + static get createAccountLink () { return this.base + 'contact'; } + + static get forgotPasswordLink () { return this.base + 'password/reset'; } + + static get truevisionManualLink () { return this.base + 'docs/simulator'; } + + static get documentationLink () { return this.base + 'designer/docs'; } + + static get scriptingReference () { return this.base + 'docs/scripting-reference'; } + + static get roadEditorManualLink () { return this.base + 'docs/road-editor'; } + + static get scenarioEditorManualLink () { return this.base + 'docs/scenario-editor'; } + + static get vehicleEditorManualLink () { return this.base + 'docs/vehicle-editor'; } + + static get sensorEditorManualLink () { return this.base + 'docs/sensor-editor'; } + + static get gradingEditorManualLink () { return this.base + 'docs/grading-editor'; } + + static get reportBugLink () { return this.base + 'docs/grading-editor'; } + + static get aboutTruevisionLink () { return this.base + 'docs/grading-editor'; } + +} diff --git a/src/app/services/asset-database.ts b/src/app/services/asset-database.ts new file mode 100644 index 00000000..00d8352b --- /dev/null +++ b/src/app/services/asset-database.ts @@ -0,0 +1,132 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { Metadata } from 'app/core/models/metadata.model'; +import { FileUtils } from './file-utils'; +import { FileNode } from 'app/views/editor/project-browser/file-node.model'; + +export class AssetDatabase { + + private static metadata: Map = new Map(); + + // private static previewCache: Map = new Map(); + + private static instances: Map = new Map(); + + static setMetadata ( guid: string, metadata: Metadata ) { + + this.metadata.set( guid, metadata ); + + } + + static getMetadata ( guid: string ): Metadata { + + return this.metadata.get( guid ); + + } + + static getAssetNameByGuid ( guid: string ): string { + + if ( !guid ) return; + + const metadata = this.getMetadata( guid ) + + if ( metadata ) + return FileUtils.getFilenameFromPath( metadata.path ); + } + + static deleteFile ( file: FileNode ) { + + if ( file.type == 'directory' ) { + + + + } else { + + } + + } + + static deleteAssetByGuid ( guid: string ) { + + const metadata = this.getMetadata( guid ); + + // file or folder + if ( metadata.isFolder ) { + + // if folder remove all files under the folder + + } else { + + // remove metadata from memory + // remove meta file + // remove instance from memory + // remove instance file + + } + } + + static getMetadataAll () { + + return this.metadata; + + } + + static removeMetadata ( guid: string ) { + + return this.metadata.delete( guid ); + + } + + static setInstance ( guid: string, instance: any ) { + + this.instances.set( guid, instance ); + + } + + static getInstance ( guid: string ): T { + + if ( guid != null && this.instances.has( guid ) ) { + + return this.instances.get( guid ); + + } else { + + console.error( `requested ${ guid } instance does not exist` ); + } + } + + static getInstanceType ( guid: string ): T { + + return this.getInstance( guid ); + + } + + static removeInstance ( guid: string ) { + + this.instances.delete( guid ); + + } + + static setPreview ( guid: string, preview: string ) { + + } + + static remove ( guid: string ) { + + try { + + this.metadata.delete( guid ); + + this.instances.delete( guid ); + + + } catch ( error ) { + + console.error( error ); + + } + + } +} \ No newline at end of file diff --git a/src/app/services/asset-loader.service.ts b/src/app/services/asset-loader.service.ts new file mode 100644 index 00000000..a7aebda5 --- /dev/null +++ b/src/app/services/asset-loader.service.ts @@ -0,0 +1,549 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { Injectable } from '@angular/core'; +import { FileService } from './file.service'; +import { FileNode } from 'app/views/editor/project-browser/file-node.model'; +import { + Vector3, + Object3D, + Vector2, + Texture, + MeshStandardMaterial, + TextureLoader, + MaterialLoader, + RepeatWrapping, + LinearFilter, + LinearMipMapLinearFilter, + RGBAFormat, + UnsignedByteType, + LinearEncoding +} from 'three'; +import { Metadata } from '../core/models/metadata.model'; +import { SnackBar } from './snack-bar.service'; +import { ImporterService } from './importer.service'; +import { ModelImporterService } from './model-importer.service'; +import { MetadataFactory } from 'app/core/factories/metadata-factory.service'; +import { TvMaterial } from 'app/modules/three-js/objects/tv-material.model'; +import { AssetDatabase } from './asset-database'; +import { AssetFactory } from 'app/core/factories/asset-factory.service'; +import { RoadStyle, RoadStyleService } from './road-style.service'; +import { RoadStyleImporter } from './road-style-importer'; +import { TvRoadMarking } from 'app/modules/tv-map/services/tv-marking.service'; + +@Injectable( { + providedIn: 'root' +} ) +export class AssetLoaderService { + + // public previewCache: Map = new Map(); + + // public assetInstances: Map = new Map(); + + /** + * This class is responsible to loading, caching, importing, reading .meta files. + * + * @param fileService FileService + */ + constructor ( private fileService: FileService, public modelImporterService: ModelImporterService ) { + } + + private get projectDir () { + return this.fileService.projectFolder; + } + + init () { + + this.createProjectFolder(); + + this.loadDefaultAssets(); + + this.loadDirectory( this.fileService.readPathContentsSync( this.projectDir ) ); + + this.loadTextures(); + + this.loadMaterials(); + + this.loadModels(); + + this.loadRoadStyles(); + + this.loadRoadMarkings(); + } + + createProjectFolder () { + + try { + + // create Truevision Folder in user documents if it does not exist + if ( !this.fileService.fs.existsSync( this.projectDir ) ) { + + this.fileService.createFolder( this.fileService.userDocumentFolder, 'Truevision' ); + + AssetFactory.createNewFolder( this.projectDir, 'Materials' ); + AssetFactory.createNewFolder( this.projectDir, 'Props' ); + AssetFactory.createNewFolder( this.projectDir, 'Roads' ); + AssetFactory.createNewFolder( this.projectDir, 'RoadStyles' ); + AssetFactory.createNewFolder( this.projectDir, 'RoadMarkings' ); + AssetFactory.createNewFolder( this.projectDir, 'Signs' ); + AssetFactory.createNewFolder( this.projectDir, 'Scenes' ); + AssetFactory.createNewFolder( this.projectDir, 'Textures' ); + + this.createDefaultAssets(); + + + } + + } catch ( error ) { + + console.log( error ); + + throw new Error( "Error in setting up default project folder" ); + + } + + } + + createDefaultAssets () { + + this.createRoadStyleAssets(); + + this.createPropAssets(); + + this.createRoadMarkingAssets(); + + this.createBaseAssets( "Materials" ); + + } + + createRoadMarkingAssets () { + + this.createBaseAssets( 'RoadMarkings' ); + + } + + createPropAssets () { + + this.createBaseAssets( 'Props' ); + + } + + createRoadStyleAssets () { + + this.createBaseAssets( 'RoadStyles' ); + + } + createBaseAssets ( folder: string ) { + + try { + + let path = null; + + if ( this.fileService.electronService.remote.app.isPackaged ) { + + const appPath = this.fileService.electronService.remote.app.getAppPath(); + + path = this.fileService.resolve( appPath, `./default-project/${ folder }` ); + + } else { + + path = this.fileService.join( this.fileService.currentDirectory, `/default-project/${ folder }` ); + + } + + this.fileService.readPathContentsSync( path ).forEach( file => { + + const destinationFolder = this.fileService.join( this.projectDir, `/${ folder }/` ); + + const destinationPath = this.fileService.join( destinationFolder, file.name ); + + if ( file.name.includes( '.meta' ) ) { + + const metadata = this.fetchMetaFile( file ); + + metadata.path = destinationPath.replace( '.meta', '' ); + + MetadataFactory.saveMetadataFile( destinationPath, metadata ); + + } else { + + this.fileService.fs.copyFileSync( file.path, destinationPath ); + + } + + } ); + + } catch ( error ) { + + console.error( error ); + + throw new Error( "Error in creating assets" ); + + } + + } + + loadTextures () { + + AssetDatabase.getMetadataAll().forEach( meta => { + + if ( meta.importer === 'TextureImporter' ) { + + const data = meta.data; + + const texture = new TextureLoader().load( meta.path ); + + texture.uuid = data.uuid; + texture.name = data.name; + texture.mapping = data.mapping || 300; + + if ( data.repeat ) texture.repeat.set( data.repeat[ 0 ], data.repeat[ 1 ] ); + if ( data.offset ) texture.offset.set( data.offset[ 0 ], data.offset[ 1 ] ); + if ( data.center ) texture.center.set( data.center[ 0 ], data.center[ 1 ] ); + + if ( data.wrap ) { + texture.wrapS = data[ 'wrap' ][ 0 ]; + texture.wrapT = data[ 'wrap' ][ 1 ]; + } + + texture.rotation = data.rotation || 0; + texture.encoding = data.encoding || LinearEncoding; + texture.minFilter = data.minFilter || LinearMipMapLinearFilter; + texture.magFilter = data.magFilter || LinearFilter; + texture.anisotropy = data.anisotropy || 1; + texture.flipY = data.flipY || true; + texture.premultiplyAlpha = data.premultiplyAlpha || false; + texture.unpackAlignment = data.unpackAlignment || 4; + texture.format = data.format || RGBAFormat; + texture.type = data.type || UnsignedByteType; + + AssetDatabase.setInstance( meta.guid, texture ); + } + + } ); + + } + + loadMaterials () { + + AssetDatabase.getMetadataAll().forEach( meta => { + + if ( meta.importer == 'MaterialImporter' && meta.guid != 'defaultMaterial' ) { + + const material = TvMaterial.parseString( this.fileService.fs.readFileSync( meta.path, 'utf-8' ) ); + + AssetDatabase.setInstance( meta.guid, material ); + + } + + } ); + + } + + loadModels () { + + AssetDatabase.getMetadataAll().forEach( meta => { + + if ( meta.importer == 'ModelImporter' ) { + + // TODO: Async can also be an option + this.modelImporterService.load( meta.path, ( obj ) => { + + AssetDatabase.setInstance( meta.guid, obj ); + + }, meta ); + + } + + } ); + + } + + loadDefaultAssets () { + + const defaultMaterial = new MeshStandardMaterial( { name: 'DefaultMaterial' } ); + + const meta = MetadataFactory.createMaterialMetadata( 'DefaultMaterial', 'defaultMaterial', 'Default.material' ); + + AssetDatabase.setMetadata( meta.guid, meta ); + + AssetDatabase.setInstance( meta.guid, defaultMaterial ); + + } + + loadRoadStyles () { + + AssetDatabase.getMetadataAll().forEach( meta => { + + if ( meta.importer == 'RoadStyleImporter' ) { + + this.fileService.readAsync( meta.path ).then( contents => { + + const roadStyle = RoadStyleImporter.importFromString( contents ); + + AssetDatabase.setInstance( meta.guid, roadStyle ); + + } ); + } + + } ); + } + + loadRoadMarkings () { + + AssetDatabase.getMetadataAll().forEach( meta => { + + if ( meta.importer === 'RoadMarkingImporter' ) { + + this.fileService.readAsync( meta.path ).then( contents => { + + const marking = TvRoadMarking.importFromString( contents ); + + AssetDatabase.setInstance( meta.guid, marking ); + + } ); + } + + } ); + + } + + /** + * + * @param guid + * @deprecated use AssetCache instead + */ + find ( guid: string ): Metadata { + + return AssetDatabase.getMetadata( guid ); + + } + + // updateAsset ( guid: string, data: any ) { + + // try { + + // const metadata = this.find( guid ); + + // this.writeMetafile( metadata.path, metadata ); + + // } catch ( error ) { + + // SnackBar.error( "Error in updating asset" ); + + // } + + // } + + + // reimportProject () { + + // this.reimportFiles( this.fileService.readPathContentsSync( this.projectDir ) ); + + // } + + // reimportFiles ( files: any[] ) { + + // files.forEach( file => { + + // const extension = FileService.getExtension( file.name ); + + // if ( file.type === 'file' && extension != 'meta' ) { + + // this.reimport( file ); + + // } + + // if ( file.type === 'directory' ) { + + // const guid = Math.random().toString( 36 ).substring( 7 ); + + // const metadata = { guid: guid, isFolder: true, path: file.path, importer: null, data: null }; + + // this.writeMetafile( file, metadata ); + + // this.addMetafileInCache( guid, metadata ); + + // this.reimportFiles( this.fileService.readPathContentsSync( file.path ) ); + + // } + + // } ); + // } + + // reimport ( file: { path: string, name: string }, extension?: string ) { + + // const metadata = MetadataFactory.createMetadata( file.name, extension, file.path ); + + // if ( metadata ) { + + // // this.writeMetafile( file.path, metadata ); + + // this.addMetafileInCache( metadata.guid, metadata ); + + // } + // } + + // writeMetafile ( file: FileNode | string, metadata: Metadata ) { + + // try { + + // let path = null; + + // if ( typeof ( file ) === 'string' ) path = file; + + // if ( typeof ( file ) === 'object' ) path = file.path; + + // if ( !path.includes( '.meta' ) ) path = path + '.meta'; + + // this.fileService.fs.writeFileSync( path, JSON.stringify( metadata ) ); + + // } catch ( error ) { + + // console.error( error ); + + // SnackBar.error( "Error in writing .meta file. Please Reimport the asset.", "", 5000 ); + // } + + // } + + loadDirectory ( files: any[] ) { + + files.forEach( file => { + + if ( file.type === 'file' && FileService.getExtension( file.name ) === 'meta' ) this.loadMetadata( file ); + + if ( file.type === 'directory' ) { + this.loadDirectory( this.fileService.readPathContentsSync( file.path ) ); + } + + } ); + + } + + loadMetadata ( file ): void { + + // TODO: improve import pipeline + // import texture + // import materials + // import models + // import props, signs etc + + try { + + const metadata = this.fetchMetaFile( file ); + + AssetDatabase.setMetadata( metadata.guid, metadata ); + + // this.loadInstance( metadata.importer, metadata.guid, metadata.path ); + + } catch ( error ) { + + console.error( error, file ); + + } + + } + + loadModelFile ( guid: string, callback: ( object: Object3D ) => void ): void { + + try { + + const metadata = this.find( guid ); + + this.modelImporterService.load( metadata.path, ( obj ) => { + + callback( obj ); + + }, metadata ); + + } catch ( error ) { + + console.error( error ); + + } + + } + + // loadInstance ( importer: string, guid: string, path: string ): any { + + // let instance = null; + + // try { + + // switch ( importer ) { + + // case 'TextureImporter': instance = new TextureLoader().load( path ); break; + + // case 'MaterialImporter': + // { + // instance = TvMaterial.parseString( this.fileService.fs.readFileSync( path, 'utf-8' ) ); + // } + // break; + + // default: break; + // } + + // } catch ( error ) { + + // console.error( error ); + + // } + + // if ( instance ) AssetCache.setInstance( guid, instance ); + + // return instance; + // } + + fetchMetaFile ( file: FileNode | string ): Metadata { + + try { + + let path = null; + + if ( typeof ( file ) === 'string' ) path = file; + + if ( typeof ( file ) === 'object' ) path = file.path; + + if ( !path.includes( '.meta' ) ) path = path + '.meta'; + + return JSON.parse( this.fileService.fs.readFileSync( path, 'utf-8' ) ); + + } catch ( error ) { + + console.error( error ); + + // SnackBar.error( "Error in reading .meta file. Please Reimport the asset.", "", 5000 ); + } + + } + + hasMetaFile ( file: FileNode | string ): boolean { + + try { + + let path = null; + + if ( typeof ( file ) === 'string' ) path = file; + + if ( typeof ( file ) === 'object' ) path = file.path; + + if ( !path.includes( '.meta' ) ) path = path + '.meta'; + + return this.fileService.fs.existsSync( path ); + + } catch ( error ) { + + return false; + + } + } + + getInstance ( guid: string ): any { + + return AssetDatabase.getInstance( guid ); + + } + +} diff --git a/src/app/services/command-history.ts b/src/app/services/command-history.ts new file mode 100644 index 00000000..10d71f58 --- /dev/null +++ b/src/app/services/command-history.ts @@ -0,0 +1,99 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { ICommand } from '../core/commands/i-command'; +import { Environment } from 'app/core/utils/environment'; +import { MultiCmdsCommand } from 'app/core/commands/multi-cmds-command'; + +export class CommandHistory { + + private static get debug () { + return true && !Environment.production; + } + + private static undos: ICommand[] = []; + + private static redos: ICommand[] = []; + + static clear (): void { + + if ( this.debug ) console.debug( 'clear history' ); + + this.undos.splice( 0, this.undos.length ); + + this.redos.splice( 0, this.redos.length ); + + } + + static execute ( cmd: ICommand ): void { + + if ( this.debug ) console.debug( 'execute ', cmd ); + + this.undos.push( cmd ); + + cmd.execute(); + + cmd.callbacks ? cmd.callbacks.onExecute() : null; + + // clear all redos on a new action + this.redos.splice( 0, this.redos.length ); + + } + + static executeAll ( cmds: ICommand[] ): void { + + this.execute( new MultiCmdsCommand( cmds ) ); + + } + + static executeMany ( ...cmds: ICommand[] ) { + + this.execute( new MultiCmdsCommand( cmds ) ); + + } + + static undo (): void { + + if ( this.undos.length > 0 ) { + + const cmd = this.undos.pop(); + + if ( this.debug ) console.debug( 'undo ', cmd ); + + cmd.undo(); + + cmd.callbacks ? cmd.callbacks.onUndo() : null; + + this.redos.push( cmd ); + + + } else { + + if ( this.debug ) console.debug( 'nothing to undo ' ); + + } + + } + + static redo (): any { + + if ( this.redos.length > 0 ) { + + const cmd = this.redos.pop(); + + if ( this.debug ) console.debug( 'redo ', cmd ); + + cmd.redo(); + + cmd.callbacks ? cmd.callbacks.onRedo() : null; + + this.undos.push( cmd ); + + } else { + + if ( this.debug ) console.debug( 'nothing to redo ' ); + + } + } +} diff --git a/src/app/services/exporter.service.ts b/src/app/services/exporter.service.ts new file mode 100644 index 00000000..defb191f --- /dev/null +++ b/src/app/services/exporter.service.ts @@ -0,0 +1,116 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { Injectable } from '@angular/core'; +import { TvMapSourceFile } from 'app/modules/tv-map/services/tv-map-source-file'; + +import * as THREE from 'three'; +import { saveAs } from 'file-saver'; + +import { TvCarlaExporter } from 'app/modules/tv-map/services/tv-carla-exporter'; +import { IFile } from 'app/core/models/file'; +import { TvMapService } from 'app/modules/tv-map/services/tv-map.service'; +import { FileService } from './file.service'; +import { ElectronService } from 'ngx-electron'; +import { SceneExporterService } from './scene-exporter.service'; +import { CommandHistory } from './command-history'; +import { SetToolCommand } from 'app/core/commands/set-tool-command'; + + +@Injectable( { + providedIn: 'root' +} ) +export class ExporterService { + + constructor ( + private odService: TvMapService, + private fileService: FileService, + private electron: ElectronService, + private sceneExporter: SceneExporterService + ) { } + + exportScene () { + + this.clearTool(); + + this.sceneExporter.saveAs(); + + } + + exportOpenDrive () { + + this.clearTool(); + + this.odService.saveAs(); + } + + exportGLB ( filename = 'road.glb' ) { + + this.clearTool(); + + const exporter = new THREE.GLTFExporter(); + + exporter.parse( TvMapSourceFile.openDrive.gameObject, ( buffer: any ) => { + + const blob = new Blob( [ buffer ], { type: 'application/octet-stream' } ); + + saveAs( blob, filename ); + + // forceIndices: true, forcePowerOfTwoTextures: true + // to allow compatibility with facebook + }, { binary: true, forceIndices: true, forcePowerOfTwoTextures: true } ); + + } + + exportGTLF () { + + this.clearTool(); + + const options = {}; + + const exporter = new THREE.GLTFExporter(); + + exporter.parse( TvMapSourceFile.openDrive.gameObject, ( result ) => { + + const text = JSON.stringify( result, null, 2 ); + + const filename = 'road.gltf'; + + saveAs( new Blob( [ text ], { type: 'text/plain' } ), filename ); + + }, options ); + + } + + exportCARLA () { + + this.clearTool(); + + const exporter = new TvCarlaExporter(); + + const contents = exporter.getOutput( this.odService.openDrive ); + + if ( this.electron.isElectronApp ) { + + this.fileService.saveAsFile( null, contents, ( file: IFile ) => { + + this.odService.currentFile.path = file.path; + this.odService.currentFile.name = file.name; + + } ); + + } else { + + saveAs( new Blob( [ contents ] ), 'road.xodr' ); + + } + + } + + private clearTool () { + + CommandHistory.execute( new SetToolCommand( null ) ); + + } +} diff --git a/src/app/services/file-utils.ts b/src/app/services/file-utils.ts new file mode 100644 index 00000000..9183c3f3 --- /dev/null +++ b/src/app/services/file-utils.ts @@ -0,0 +1,41 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { AppInfo } from 'app/core/services/app-info.service'; + +export class FileUtils { + + static getDirectoryFromPath ( path: string ): string { + + if ( !path ) return; + + // split by slash or back-slash then remove the last item and add + // slash or blash at the end + + if ( AppInfo.electron.isWindows ) + return path.split( '\\' ).slice( 0, -1 ).join( '\\' ) + + if ( AppInfo.electron.isLinux ) + return path.split( '/' ).slice( 0, -1 ).join( '/' ); + + console.error( "unknown platform" ); + + } + + static getFilenameFromPath ( path: string ): string { + + if ( !path ) return; + + // if windows, split by backslash and return the last item + if ( AppInfo.electron.isWindows ) + return path.split( '\\' ).pop(); + + // if linux, split by slash and return the last item + if ( AppInfo.electron.isLinux ) + return path.split( '/' ).pop(); + + console.error( "unknown platform" ); + } + +} \ No newline at end of file diff --git a/src/app/services/file.service.ts b/src/app/services/file.service.ts new file mode 100644 index 00000000..1d82ad25 --- /dev/null +++ b/src/app/services/file.service.ts @@ -0,0 +1,479 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { EventEmitter, Injectable, NgZone } from '@angular/core'; +import { ElectronService } from 'ngx-electron'; +import { IFile } from '../core/models/file'; +import { FileUtils } from './file-utils'; +import { SnackBar } from './snack-bar.service'; + +@Injectable( { + providedIn: 'root' +} ) +export class FileService { + + static electron: ElectronService; + + public fileImported = new EventEmitter(); + public fileSaved = new EventEmitter(); + + public fs: any; + private path: any; + private util: any; + + constructor ( public electronService: ElectronService, private ngZone: NgZone ) { + + FileService.electron = electronService; + + if ( this.electronService.isElectronApp ) { + + this.fs = this.electronService.remote.require( 'fs' ); + this.path = this.electronService.remote.require( 'path' ); + this.util = this.electronService.remote.require( 'util' ); + + } + + } + + get userDocumentFolder () { return this.electronService.remote.app.getPath( 'documents' ); } + + get currentDirectory () { return this.electronService.ipcRenderer.sendSync( "current-directory" ); } + + get projectFolder () { + + if ( this.electronService.isWindows ) { + + return this.userDocumentFolder + "\\Truevision" + + } else if ( this.electronService.isLinux ) { + + return this.userDocumentFolder + "/Truevision" + + } else { + + throw new Error( "Unsupported platform. Please contact support for more details." ); + + } + } + + static openFile ( onImported: ( files: any ) => void = null, onRead: ( content: string ) => void = null ) { + + // TODO : Test one time creation + const form = document.createElement( 'form' ); + const input = document.createElement( 'input' ); + + input.type = 'file'; + + form.appendChild( input ); + + input.addEventListener( 'change', ( event: any ) => { + + onImported( event.target.files ); + + const reader = new FileReader(); + + reader.addEventListener( 'load', ( event: any ) => { + + onRead( event.target.result ); + + }, false ); + + reader.readAsText( event.target.files[ 0 ] ); + + } ); + + input.click(); + + } + + static getExtension ( filename: string ): string { + + if ( this.electron.isWindows ) { + + const array = filename.split( '.' ); + + return array[ array.length - 1 ]; + } + + const regEx = /(?:\.([^.]+))?$/; + + const extension = regEx.exec( filename )[ 1 ]; + + return extension; + } + + async showAsyncDialog (): Promise { + + const options = { + title: 'Select file', + buttonLabel: 'Import', + filters: [ + { + name: null, + } + ], + message: 'Select file' + }; + + return Promise.resolve( this.electronService.remote.dialog.showOpenDialog( null, {} ) ); + } + + async readAsync ( path ) { + + return Promise.resolve( this.fs.readFileSync( path, 'utf-8' ) ); + + } + + import ( path?: string, type: string = 'default', extensions = [ 'xml' ], callbackFn: any = null ) { + + if ( !this.electronService.isElectronApp ) throw new Error( 'Error: cannot import' ); + + const options = { + title: 'Select file', + buttonLabel: 'Import', + defaultPath: path || this.projectFolder, + filters: [ + { + name: null, + extensions + } + ], + message: 'Select file' + }; + + this.electronService.remote.dialog.showOpenDialog( null, options, ( filepaths ) => { + + if ( filepaths != null ) this.readFile( filepaths[ 0 ], type, callbackFn ); + + } ); + } + + /** + * + * @deprecated use import + */ + importFile ( path?: string, type: string = 'default', extensions = [ 'xml' ] ) { + + this.import( path, type, extensions, null ); + + } + + readFile ( path: string, type: string = 'default', callbackFn: any = null ) { + + this.fs.readFile( path, 'utf-8', ( err, data ) => { + + if ( err ) { + alert( 'An error ocurred reading the file :' + err.message ); + return; + } + + const file = new IFile(); + + file.path = path; + file.contents = data; + file.type = type; + file.updatedAt = new Date() + + // if ( callbackFn != null ) callbackFn( file ); + + // Need to call the callback function from ngZone to trigger change detection in Angular + if ( callbackFn != null ) this.ngZone.run( () => callbackFn( file ) ); + + this.fileImported.emit( file ); + + } ); + + } + + saveFile ( defaultPath: string, contents: string, callbackFn: any = null ): any { + + this.writeFile( defaultPath, contents, callbackFn ); + + } + + saveFileWithExtension ( directory: string = null, contents: string, extension: string, callbackFn: any = null ) { + + if ( directory == null ) directory = this.projectFolder; + + const options = { + defaultPath: directory + }; + + this.electronService.remote.dialog.showSaveDialog( null, options, ( fullPath ) => { + + if ( fullPath != null ) { + + // append the extension if not present in the path + if ( !fullPath.includes( `.${ extension }` ) ) { + + fullPath = fullPath + "." + extension; + + } + + this.writeFile( fullPath, contents, callbackFn ); + + } else { + + console.error( "Could not save file" ); + + } + + } ); + + } + + saveAsFile ( directory: string = null, contents: string, callbackFn: any = null ): any { + + if ( directory == null ) directory = this.projectFolder; + + const options = { + defaultPath: directory + }; + + this.electronService.remote.dialog.showSaveDialog( null, options, ( path ) => { + + if ( path != null ) { + + this.writeFile( path, contents, callbackFn ); + + + } else { + + // alert( "you didnt save file" ); + + } + + } ); + + } + + writeFile ( filepath, content, callbackFn: any = null ) { + + this.fs.writeFile( filepath, content, ( err, data ) => { + + if ( err ) { + + console.error( 'An error ocurred creating the file ' + err.message ); + + return; + + } else { + + const file = new IFile( null, filepath, content, null, null, new Date() ); + + this.fileSaved.emit( file ); + + if ( callbackFn != null ) callbackFn( file ); + + } + + } ); + + } + + listFiles ( path, callback ) { + + this.fs.readdir( path, ( err, files ) => { + + if ( err ) { + + console.log( 'Error getting directory information.' ); + + } else { + + callback( files ); + + } + + } ); + + } + + deleteFolderSync ( path: string ) { + + if ( this.fs.existsSync( path ) ) { + + this.fs.rmdirSync( path, { recursive: true } ); + + } else { + + console.error( "folder does not exists" ); + + } + + } + + deleteFileSync ( path: string ) { + + this.fs.unlinkSync( path ); + + } + + createFolder ( path: string, name: string = 'New Folder' ) { + + try { + + let folderName = name; + + let folderPath = this.join( path, folderName ); + + let count = 1; + + while ( this.fs.existsSync( folderPath ) && count <= 20 ) { + + folderName = `${ name } (${ count++ })`; + + folderPath = this.join( path, folderName ); + + } + + this.fs.mkdirSync( folderPath ); + + return { + name: folderName, + path: folderPath + } + + } catch ( error ) { + + // throw new Error( `Error in creating project at ${ path }` ); + + } + + } + + createFile ( path: string, name: string = 'New Untitled', extension: string, contents: any ) { + + let slash = null; + + if ( this.electronService.isWindows ) slash = "\\"; + + if ( this.electronService.isLinux ) slash = "/"; + + let filePath = `${ path }${ slash }${ name }.${ extension }`; + let fileName = name; + + if ( this.fs.existsSync( filePath ) ) { + + let count = 1; + + fileName = `${ name }(${ count })`; + filePath = `${ path }${ slash }${ fileName }.${ extension }`; + + while ( this.fs.existsSync( filePath ) ) { + + fileName = `${ name }(${ count++ })`; + filePath = `${ path }${ slash }${ fileName }.${ extension }`; + + } + + } + + this.fs.writeFileSync( filePath, contents ); + + return { fileName, filePath }; + } + + + readPathContents ( dirpath ) { + return new Promise( resolve => { + this.fs.readdir( dirpath, this.handled( files => { + Promise.all( files.map( file => { + const itemPath = this.path.join( dirpath, file ); + return this.getItemProperties( itemPath ); + } ) ).then( resolve ); + } ) ); + } ); + } + + readPathContentsSync ( dirpath ) { + + const files = this.fs.readdirSync( dirpath ); + + const items = []; + + files.forEach( file => { + + const itemPath = this.path.join( dirpath, file ); + + items.push( this.getItemProperties( itemPath ) ); + + } ); + + return items; + } + + getItemProperties ( itemPath ) { + + const stats = this.fs.statSync( itemPath ); + + const name = FileService.getFilenameFromPath( itemPath ); + + return { + name: name, + type: this.getItemType( stats ), + path: itemPath, + size: stats.size, + mtime: stats.mtime + }; + + return new Promise( resolve => { + this.fs.stat( itemPath, this.handled( stats => resolve( { + name: itemPath.split( '/' ).pop(), + type: this.getItemType( stats ), + path: itemPath, + size: stats.size, + mtime: stats.mtime + } ) ) ); + } ); + } + + static getFilenameFromPath ( path: string ): string { + + return FileUtils.getFilenameFromPath( path ); + + } + + getItemType ( item ) { + if ( item.isFile() ) { + return 'file'; + } else if ( item.isDirectory() ) { + return 'directory'; + } else if ( item.isBlockDevice() ) { + return 'blockdevice'; + } else if ( item.isCharacterDevice() ) { + return 'characterdevice'; + } else if ( item.isSymbolicLink() ) { + return 'symlink'; + } else if ( item.isFIFO() ) { + return 'fifo'; + } else if ( item.isSocket() ) { + return 'socket'; + } + return ''; + } + + handled ( callback ) { + return function handledCallback ( err, ...args ) { + if ( err ) { + throw err; + } + callback( ...args ); + }; + } + + resolve ( relativePath: string, filename: string ): string { + + const dirname = this.path.dirname( relativePath ); + + return this.path.resolve( dirname, filename ); + + } + + join ( path, filename ): string { + + return this.path.join( path, filename ); + + } +} diff --git a/src/app/services/importer.service.ts b/src/app/services/importer.service.ts new file mode 100644 index 00000000..b766fadc --- /dev/null +++ b/src/app/services/importer.service.ts @@ -0,0 +1,199 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { Injectable } from '@angular/core'; +import { TvMapService } from 'app/modules/tv-map/services/tv-map.service'; +import { FileService } from './file.service'; +import { SnackBar } from './snack-bar.service'; +// import { SceneImporterService } from './scene-importer.service'; +import 'three/examples/js/loaders/GLTFLoader.js'; +import 'three/examples/js/loaders/OBJLoader.js'; +import 'three/examples/js/controls/OrbitControls'; + +import { Vector3 } from 'three'; +import { ModelImporterService } from './model-importer.service'; +import { AssetLoaderService } from './asset-loader.service'; +import { SceneImporterService } from './scene-importer.service'; +import { MetadataFactory } from 'app/core/factories/metadata-factory.service'; +import { ToolManager } from 'app/core/tools/tool-manager'; +import { PropPointTool } from 'app/core/tools/prop-point-tool'; +import { PropService } from './prop-service'; +import { PropInstance } from 'app/core/models/prop-instance.model'; +import { AssetDatabase } from './asset-database'; + +@Injectable( { + providedIn: 'root' +} ) +export class ImporterService { + + /** + * This class is responsible for importing all supported files + * + * @param od + * @param osc + * @param three + * @param assetImporter + * @param sceneImporter + * @param assetService + */ + constructor ( + private od: TvMapService, + private sceneImporter: SceneImporterService, + private modelImporter: ModelImporterService, + private assetService: AssetLoaderService, + private fileService: FileService + ) { } + + importViaPath ( path: string, filename?: string, position?: Vector3 ) { + + const extension = FileService.getExtension( path ); + + const metadata = this.assetService.fetchMetaFile( path ); + + switch ( extension ) { + + case 'xodr': + this.importOpenDrive( path ); + break; + + case 'gltf': + this.modelImporter.import( path, filename, extension, position, metadata ); + break; + + case 'glb': + + PropService.setProp( metadata ); + + if ( ToolManager.currentTool instanceof PropPointTool ) { + + ToolManager.currentTool.shapeEditor.addControlPoint( position ); + + } else { + + ToolManager.currentTool = new PropPointTool(); + + ( ToolManager.currentTool as PropPointTool ).shapeEditor.addControlPoint( position ); + + } + + // this.modelImporter.import( path, filename, extension, position, metadata ); + + break; + + case 'obj': + this.modelImporter.import( path, filename, extension, position, metadata ); + break; + + case 'fbx': + this.modelImporter.import( path, filename, extension, position, metadata ); + break; + + case 'prop': + // alert( 'import prop ' + path ); + break; + + case 'scene': + this.importScene( path ); + break; + + case 'roadstyle': + console.error( "method not implemented" ); + break; + + default: + console.error( `unknown file type: ${ extension }` ); + SnackBar.error( 'Unknown file! Not able to import' ); + break; + } + + } + + onFileDropped ( file: File, folderPath: string ): any { + + if ( !file ) SnackBar.error( "Incorrect file. Cannot import" ); + if ( !file ) return; + + const extension = FileService.getExtension( file.name ); + + const destinationPath = this.fileService.join( folderPath, file.name ); + + let copied = false; + + switch ( extension ) { + + case 'gltf': copied = this.copyFileInFolder( file.path, destinationPath, extension ); break; + + case 'glb': copied = this.copyFileInFolder( file.path, destinationPath, extension ); break; + + case 'obj': copied = this.copyFileInFolder( file.path, destinationPath, extension ); break; + + // case 'fbx': copied = this.copyFileInFolder( file.path, destinationPath, extension ); break; + + case 'jpg': copied = this.copyFileInFolder( file.path, destinationPath, extension ); break; + + case 'jpeg': copied = this.copyFileInFolder( file.path, destinationPath, extension ); break; + + case 'png': copied = this.copyFileInFolder( file.path, destinationPath, extension ); break; + + case 'svg': copied = this.copyFileInFolder( file.path, destinationPath, extension ); break; + + default: SnackBar.error( `${ extension } file cannot be imported` ); break; + } + + if ( copied ) { + + MetadataFactory.createMetadata( file.name, extension, destinationPath ); + + } + } + + copyFileInFolder ( sourcePath: string, destinationPath: string, ext?: string ): boolean { + + if ( !destinationPath ) SnackBar.error( "folderPath incorrect" ); + if ( !destinationPath ) return; + + try { + + // console.log( 'import as texture in ', this.selectedFolder ); + // const extension = ext || FileService.getExtension( file.name ); + + // const source = sourcePath.path; + // const destination = destinationPath + '\\' + sourcePath.name; + + // const newFilePath = this.fileService.join( destinationPath, sourcePath.name ); + + this.fileService.fs.copyFileSync( sourcePath, destinationPath ); + + // this.fileService.fs.copyFile( source, destination, ( err ) => { + + // if ( err ) SnackBar.error( err ); + + // this.reimport( { path: destination, name: file.name }, extension ); + + // } ); + + return true; + + } catch ( error ) { + + SnackBar.error( error ); + + } + } + + + importScene ( path: string ) { + + this.sceneImporter.importFromPath( path ); + + } + + importOpenDrive ( filepath: string ) { + + this.od.importFromPath( filepath ); + + } + + +} diff --git a/src/app/services/main-file.service.ts b/src/app/services/main-file.service.ts new file mode 100644 index 00000000..4b0182fe --- /dev/null +++ b/src/app/services/main-file.service.ts @@ -0,0 +1,135 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { Injectable } from '@angular/core'; +import { SnackBar } from './snack-bar.service'; +import { TvMapSourceFile } from 'app/modules/tv-map/services/tv-map-source-file'; +import { SceneExporterService } from './scene-exporter.service'; +import { IFile } from 'app/core/models/file'; +import { FileService } from './file.service'; +import { AppService } from 'app/core/services/app.service'; +import { SceneImporterService } from './scene-importer.service'; +import { TvMap } from 'app/modules/tv-map/models/tv-map.model'; +import { TvMapBuilder } from 'app/modules/tv-map/builders/od-builder.service'; +import { ToolManager } from 'app/core/tools/tool-manager'; +import { AppInspector } from 'app/core/inspector'; +import { ThreeService } from 'app/modules/three-js/three.service'; +import { CommandHistory } from './command-history'; + +@Injectable( { + providedIn: 'root' +} ) +export class MainFileService { + + constructor ( + public sceneExporter: SceneExporterService, + public sceneImporter: SceneImporterService, + public fileService: FileService, + public threeService: ThreeService + ) { } + + get currentFile () { return TvMapSourceFile.currentFile; } + + set currentFile ( value ) { TvMapSourceFile.currentFile = value; } + + get openDrive () { return TvMapSourceFile.openDrive; } + + set openDrive ( value ) { TvMapSourceFile.openDrive = value; } + + + importViaContent ( content: string ) { + + this.sceneImporter.importFromString( content ); + + } + + newFile () { + + ToolManager.clear(); + + AppInspector.clear(); + + CommandHistory.clear(); + + if ( this.openDrive ) this.openDrive.destroy(); + + this.currentFile = new IFile( 'untitled.xml' ); + + this.openDrive = new TvMap(); + + TvMapBuilder.buildMap( this.openDrive ); + } + + showOpenWindow ( path?: string ) { + + if ( AppService.isElectronApp ) { + + this.fileService.import( path, 'tv-map', [ 'xml', 'xosc' ], ( file: IFile ) => { + + this.sceneImporter.importFromFile( file ); + + } ); + + } else { + + throw new Error( 'not implemented' ); + + } + + } + + openFromPath ( path: string, callback?: Function ) { + + if ( AppService.isElectronApp ) { + + this.fileService.readFile( path, 'xml', ( file: IFile ) => { + + this.sceneImporter.importFromString( file.contents ); + + if ( callback ) callback(); + + } ); + + } + + } + + save () { + + if ( this.currentFile == null ) throw new Error( 'Create file before saving' ); + + this.currentFile.contents = this.sceneExporter.export( this.openDrive ); + + this.saveLocally( this.currentFile ); + + } + + saveAs () { + + this.sceneExporter.saveAs(); + + } + + saveLocally ( file: IFile ) { + + // path exists means it was imported locally + if ( this.currentFile.path != null ) { + + this.fileService.saveFile( file.path, file.contents, ( file: IFile ) => { + + this.currentFile.path = file.path; + this.currentFile.name = file.name; + + SnackBar.success( 'File Saved!' ); + + } ); + + } else { + + this.saveAs(); + + } + } + +} diff --git a/src/app/services/menu.service.ts b/src/app/services/menu.service.ts new file mode 100644 index 00000000..6d4ff6fc --- /dev/null +++ b/src/app/services/menu.service.ts @@ -0,0 +1,72 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { Injectable } from '@angular/core'; +import { ElectronService } from 'ngx-electron'; +import { Menu, MenuItem, MenuItemConstructorOptions } from 'electron'; + +export enum ContextMenuType { + VIEWPORT = 0, + GAME_OBJECT = 1, + HIERARCHY = 3, +} + +@Injectable( { + providedIn: 'root' +} ) +export class MenuService { + + private menus: Map = new Map(); + + constructor ( private electron: ElectronService ) { + + } + + registerContextMenu ( type: ContextMenuType, template: Array<(MenuItemConstructorOptions) | (MenuItem)> ) { + + if ( !this.electron.isElectronApp ) return; + + const menu = this.electron.remote.Menu.buildFromTemplate( template ); + + this.menus.set( type, menu ); + } + + showContextMenu ( type: ContextMenuType ) { + + if ( !this.electron.isElectronApp ) return; + + if ( this.menus.has( type ) ) { + + const menu = this.menus.get( type ); + + menu.popup( { + window: this.electron.remote.getCurrentWindow() + } ); + } + } + + private sampleRegister () { + + // this.menu = electron.remote.require( 'menu' ); + + // const menu = electron.remote.Menu.buildFromTemplate( [ { + // label: 'File', + // click: () => { + // console.log( 'clicked' ); + // } + // } ] ); + + // const menu = new Menu(); + // + // menu.append( new MenuItem( { + // label: 'File', + // click: () => { + // console.log( 'clicked' ); + // } + // } ) ); + + // this.registerContextMenu( ContextMenuType.VIEWPORT, menu ); + + } +} diff --git a/src/app/services/model-importer.service.ts b/src/app/services/model-importer.service.ts new file mode 100644 index 00000000..ef217011 --- /dev/null +++ b/src/app/services/model-importer.service.ts @@ -0,0 +1,163 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { Injectable } from '@angular/core'; +import { Object3D, Vector3, Euler, Scene } from 'three'; +import { SnackBar } from './snack-bar.service'; +import { AssetImporterService } from 'app/core/asset-importer.service'; +import { Metadata } from 'app/core/models/metadata.model'; +import { FileService } from './file.service'; +import { SceneService } from 'app/core/services/scene.service'; + +@Injectable( { + providedIn: 'root' +} ) +export class ModelImporterService { + + constructor ( private assetImporter: AssetImporterService ) { } + + public import ( path: string, filename?: string, extension?: string, position?: Vector3, metadata?: Metadata ) { + + // const metadata = this.assetService.fetchMetaFile( path ); + + this.load( path, ( object ) => { + + if ( position ) object.position.set( position.x, position.y, position.z ); + + SceneService.add( object ); + + }, metadata, extension ) + + } + + public load ( path: string, callback: ( object: Object3D ) => void, metadata: Metadata, extension?: string ): void { + + // if ( !metadata ) metadata = this.assetService.fetchMetaFile( path ); + + if ( !extension ) extension = FileService.getExtension( path ); + + switch ( extension ) { + + case 'gltf': + this.load3DFile( path, callback ); + break; + + case 'glb': + this.load3DFile( path, callback ); + break; + + case 'obj': + this.load3DFile( path, callback ); + break; + + case 'fbx': + this.load3DFile( path, callback ); + break; + + default: + console.error( 'unknown file type' ); + SnackBar.error( 'Not able to import' ); + break; + } + + } + + private load3DFile ( path: string, callback: ( object: Object3D ) => void ): void { + + this.assetImporter.import( path, ( object ) => { + + callback( object ); + + }, ( e ) => { + + SnackBar.error( e ) + + } ); + + } + + private onModelImported ( path: string, object: Object3D, position?: Vector3, metadata?: Metadata ) { + + // const rotationVariance = metadata ? metadata.data.rotationVariance : new Vector3( 0, 0, 0 ); + + // const scaleVariance = metadata ? metadata.data.scaleVariance : new Vector3( 0, 0, 0 ); + + // if ( !( ToolManager.currentTool instanceof PropPointTool ) ) { + + // ToolManager.currentTool = new PropPointTool( { guid: metadata.guid, object: object } ); + + // } + + // const randomRotation = new Vector3( + // Maths.randomFloatBetween( -rotationVariance.x, rotationVariance.x ), + // Maths.randomFloatBetween( -rotationVariance.y, rotationVariance.y ), + // Maths.randomFloatBetween( -rotationVariance.z, rotationVariance.z ), + // ); + + // const randomScale = new Vector3( + // object.scale.x + Maths.randomFloatBetween( -scaleVariance.x, scaleVariance.x ), + // object.scale.y + Maths.randomFloatBetween( -scaleVariance.y, scaleVariance.y ), + // object.scale.z + Maths.randomFloatBetween( -scaleVariance.z, scaleVariance.z ), + // ); + + // // console.log( randomRotation, randomScale ); + + // object.rotation.setFromVector3( randomRotation ); + + // object.scale.set( randomScale.x, randomScale.y, randomScale.z ); + + // if ( position ) ( ToolManager.currentTool as PropPointTool ).shapeEditor.addControlPoint( position ); + + // InspectorFactoryService.setByType( InspectorType.prop_instance_inspector, { guid: metadata.guid, object: object } ); + + // AppInspector.setInspector( PropInstanceInspectorComponent, { guid: metadata.guid, object: object } ); + } + + private importOBJ ( path: string, filename?: string, extension?: string, position?: Vector3, metadata?: Metadata ) { + + // const meta = this.assetService.fetchMetaFile( path ); + + this.assetImporter.import( path, ( object: Object3D ) => { + + this.onModelImported( path, object, position, metadata ); + + }, ( e ) => { + + SnackBar.error( e ); + + } ); + + } + + private importGLTF ( path: string, filename?: string, extension?: string, position?: Vector3 ) { + + this.assetImporter.import( path, ( object ) => { + + this.onModelImported( path, object, position ); + + }, ( e ) => { + + SnackBar.error( e ) + + } ); + + } + + /** + * @deprecated not working right now + */ + private importFBX ( path: string, filename?: string, extension?: string, position?: Vector3 ) { + + this.assetImporter.import( path, ( object ) => { + + this.onModelImported( path, object, position ); + + }, ( e ) => { + + SnackBar.error( e ) + + } ); + + } +} diff --git a/src/app/services/prop-service.spec.ts b/src/app/services/prop-service.spec.ts new file mode 100644 index 00000000..e41217f3 --- /dev/null +++ b/src/app/services/prop-service.spec.ts @@ -0,0 +1,70 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { TestBed } from '@angular/core/testing'; +import { Vector3, ShapeUtils, Triangle } from 'three'; +import { AnyControlPoint } from 'app/modules/three-js/objects/control-point'; +import { PropPolygon } from 'app/modules/tv-map/models/prop-polygons'; +import { PropService } from './prop-service'; +import * as THREE from 'three'; +import earcut from 'earcut'; + + +describe( 'PropService test', () => { + + beforeEach( () => TestBed.configureTestingModule( {} ) ); + + beforeEach( () => { } ); + + it( 'should place props in polygon correctly', () => { + + const polygon = new PropPolygon( 'blank' ); + + // triangulating a polygon with 3d coords0 + const triangles = earcut( [ 10, 0, 1, 0, 50, 2, 60, 60, 3, 70, 10, 4 ], null, 3 ); + + const faces = []; + + for ( let i = 0; i < triangles.length; i += 3 ) { + + faces.push( triangles.slice( i, i + 3 ) ); + + } + + function randomInTriangle ( v1, v2, v3 ) { + var r1 = Math.random(); + var r2 = Math.sqrt( Math.random() ); + var a = 1 - r2; + var b = r2 * ( 1 - r1 ); + var c = r1 * r2; + return ( v1.clone().multiplyScalar( a ) ).add( v2.clone().multiplyScalar( b ) ).add( v3.clone().multiplyScalar( c ) ); + } + + + faces.forEach( face => { + + const t = new Triangle( face[ 0 ], face[ 1 ], face[ 2 ] ); + + const area = t.getArea(); + + const count = area / polygon.density; + + for ( let i = 0; i < count; i++ ) { + + const p = randomInTriangle( face[ 0 ], face[ 1 ], face[ 2 ] ); + + + + } + + } ) + + // const ts = THREE.ShapeUtils.triangulate( [], false ); + + // PropService.updateCurvePolygonProps( polygon ); + + } ); + + +} ); diff --git a/src/app/services/prop-service.ts b/src/app/services/prop-service.ts new file mode 100644 index 00000000..02387ee3 --- /dev/null +++ b/src/app/services/prop-service.ts @@ -0,0 +1,157 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { PropInstance } from 'app/core/models/prop-instance.model'; +import { PropModel } from 'app/core/models/prop-model.model'; +import { DynamicMeta } from 'app/core/models/metadata.model'; +import { PropCurve } from "app/modules/tv-map/models/prop-curve"; +import { SceneService } from 'app/core/services/scene.service'; +import { CatmullRomSpline } from 'app/core/shapes/catmull-rom-spline'; +import { AssetDatabase } from './asset-database'; +import { Object3D, BufferGeometry, BufferAttribute, Vector3, Triangle } from 'three'; +import { Maths } from 'app/utils/maths'; +import { PropPolygon } from 'app/modules/tv-map/models/prop-polygons'; +import * as THREE from 'three'; +import earcut from 'earcut'; + +export class PropService { + + private static prop?: DynamicMeta; + + static setProp ( prop: DynamicMeta ) { + + this.prop = prop; + + } + + static getProp (): DynamicMeta { + + return this.prop; + + } + + static updateCurveProps ( curve: PropCurve ) { + + if ( !curve ) return; + + if ( curve.spline.controlPoints.length < 2 ) return; + + const length = ( curve.spline as CatmullRomSpline ).getLength(); + + if ( length <= 0 ) return; + + curve.props.forEach( prop => SceneService.remove( prop ) ); + + curve.props.splice( 0, curve.props.length ); + + const spline = curve.spline as CatmullRomSpline; + + const instance = AssetDatabase.getInstance( curve.propGuid ) as Object3D; + + for ( let i = 0; i < length; i += curve.spacing ) { + + const t = spline.curve.getUtoTmapping( 0, i ); + + const position = spline.curve.getPoint( t ); + + const prop = instance.clone(); + + // apply random position variance + position.setX( position.x + Maths.randomFloatBetween( -curve.positionVariance, curve.positionVariance ) ); + position.setY( position.y + Maths.randomFloatBetween( -curve.positionVariance, curve.positionVariance ) ); + + // apply random rotation variance + prop.rotateX( Maths.randomFloatBetween( -curve.rotation, curve.rotation ) ); + prop.rotateY( Maths.randomFloatBetween( -curve.rotation, curve.rotation ) ); + prop.rotateZ( Maths.randomFloatBetween( -curve.rotation, curve.rotation ) ); + + prop.position.copy( position ); + + curve.props.push( prop ); + + SceneService.add( prop ); + + } + } + + static updateCurvePolygonProps ( polygon: PropPolygon ) { + + polygon.props.forEach( p => SceneService.remove( p ) ); + + polygon.props.splice( 0, polygon.props.length ); + + const instance = AssetDatabase.getInstance( polygon.propGuid ) as Object3D; + + + instance.up.set( 0, 0, 1 ); + + instance.updateMatrixWorld( true ); + + + + const vertices = []; + + polygon.spline.controlPointPositions.forEach( p => { + vertices.push( p.x ); + vertices.push( p.y ); + } ); + + // triangulating a polygon with 2d coords0 + const triangles = earcut( vertices ); + + const faces = []; + + for ( let i = 0; i < triangles.length; i += 3 ) { + + faces.push( triangles.slice( i, i + 3 ) ); + + } + + function randomInTriangle ( v1, v2, v3 ) { + + const r1 = Math.random(); + + const r2 = Math.sqrt( Math.random() ); + + const a = 1 - r2; + + const b = r2 * ( 1 - r1 ); + + const c = r1 * r2; + + return ( v1.clone().multiplyScalar( a ) ).add( v2.clone().multiplyScalar( b ) ).add( v3.clone().multiplyScalar( c ) ); + } + + + faces.forEach( face => { + + const v0 = polygon.spline.controlPointPositions[ face[ 0 ] ]; + const v1 = polygon.spline.controlPointPositions[ face[ 1 ] ]; + const v2 = polygon.spline.controlPointPositions[ face[ 2 ] ]; + + const t = new Triangle( v0, v1, v2 ); + + const area = t.getArea(); + + let count = area * polygon.density * polygon.density * polygon.density * 0.5; + + count = Maths.clamp( count, 0, 1000 ); + + for ( let i = 0; i < count; i++ ) { + + const position = randomInTriangle( v0, v1, v2 ); + + const prop = instance.clone(); + + prop.position.copy( position ); + + polygon.props.push( prop ); + + SceneService.add( prop ); + + } + + } ) + } +} \ No newline at end of file diff --git a/src/app/services/recent-file.service.ts b/src/app/services/recent-file.service.ts new file mode 100644 index 00000000..251e123a --- /dev/null +++ b/src/app/services/recent-file.service.ts @@ -0,0 +1,146 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { Injectable } from '@angular/core'; +import { StorageService } from './storage.service'; +import { FileService } from './file.service'; +import { IFile } from '../core/models/file'; + +@Injectable( { + providedIn: 'root' +} ) +export class RecentFileService { + + private readonly LAST_OPENED_FILE = 'current_file'; + private readonly RECENT_FILES = 'recent_files'; + + private mRecentFiles: IFile[] = []; + private mLastFile: IFile; + + constructor ( + private storage: StorageService, + private files: FileService + ) { + + this.loadStorageData(); + + this.files.fileImported.subscribe( ( file: IFile ) => this.addToRecentFiles( file ) ); + + this.files.fileSaved.subscribe( ( file: IFile ) => this.addToRecentFiles( file ) ); + + } + + get lastOpenedFile (): IFile { + + return this.mLastFile; + + } + + get currentFile (): IFile { + + return this.mLastFile; + + } + + set currentFile ( file: IFile ) { + + this.mLastFile = file; + + this.saveLastOpenedFile(); + + } + + get recentFiles (): IFile[] { + + return this.mRecentFiles.reverse(); + + } + + addToRecentFiles ( file: IFile ): void { + + this.currentFile = file; + + if ( !this.fileExists( file ) ) this.mRecentFiles.push( file ); + + this.saveRecentFiles(); + + } + + addPathToRecentFiles ( path: string ) { + + this.mRecentFiles.push( new IFile( null, path ) ); + + this.saveRecentFiles(); + } + + private saveLastOpenedFile (): void { + + this.storage.store( this.LAST_OPENED_FILE, JSON.stringify( this.currentFile ) ); + + } + + private loadStorageData (): void { + + this.loadLastFileFromStorage(); + this.loadRecentFilesFromStorage(); + + } + + private loadLastFileFromStorage (): void { + + var jsonString = this.storage.get( this.LAST_OPENED_FILE ); + + if ( jsonString != null ) { + + this.mLastFile = JSON.parse( jsonString ); + } + } + + private loadRecentFilesFromStorage (): void { + + var jsonString = this.storage.get( this.RECENT_FILES ); + + if ( jsonString != null ) { + + try { + + const files = JSON.parse( jsonString ); + + files.forEach( file => { + this.mRecentFiles.push( new IFile( file.name, file.path, file.contents, file.type, file.online, file.updatedAt ) ); + } ); + + } catch ( error ) { + + console.error( error ); + + } + + } else { + + this.saveRecentFiles(); + } + + } + + private saveRecentFiles (): void { + + this.storage.store( this.RECENT_FILES, JSON.stringify( this.mRecentFiles ) ); + + } + + private fileExists ( find: IFile ): boolean { + + if ( this.recentFiles.findIndex( file => file.path === find.path ) == -1 ) { + + return false; + + } else { + + return true; + + } + + } +} diff --git a/src/app/services/road-style-exporter.service.ts b/src/app/services/road-style-exporter.service.ts new file mode 100644 index 00000000..2181e922 --- /dev/null +++ b/src/app/services/road-style-exporter.service.ts @@ -0,0 +1,196 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { Injectable } from '@angular/core'; +import { TvMapSourceFile } from 'app/modules/tv-map/services/tv-map-source-file'; +import { PropInstance } from 'app/core/models/prop-instance.model'; +import { OdWriter } from 'app/modules/tv-map/services/open-drive-writer.service'; +import { Euler, Vector3 } from 'three'; +import { TvMap } from 'app/modules/tv-map/models/tv-map.model'; +import { TvRoad } from 'app/modules/tv-map/models/tv-road.model'; +import { TvRoadTypeClass } from 'app/modules/tv-map/models/tv-road-type.class'; +import { AbstractSpline } from 'app/core/shapes/abstract-spline'; +import { AutoSpline } from 'app/core/shapes/auto-spline'; +import { FileService } from './file.service'; +import { ElectronService } from 'ngx-electron'; + +import { saveAs } from 'file-saver'; +import { IFile } from 'app/core/models/file'; +import { TvSurface } from 'app/modules/tv-map/models/tv-surface.model'; +import { ExplicitSpline } from 'app/core/shapes/explicit-spline'; +import { SnackBar } from './snack-bar.service'; +import { RoadControlPoint } from 'app/modules/three-js/objects/road-control-point'; +import { TvJunction } from 'app/modules/tv-map/models/tv-junction'; +import { RoadStyle } from './road-style.service'; +import { TvLaneSection } from 'app/modules/tv-map/models/tv-lane-section'; +import { TvLaneSide } from 'app/modules/tv-map/models/tv-common'; +import { TvLane } from 'app/modules/tv-map/models/tv-lane'; + +const Parser = require( 'fast-xml-parser' ).j2xParser; + +export interface Scene { + + road: { guid: string }, + + props: { guid: string, position: Vector3, rotation: Euler, scale: Vector3 }[], + + propCurves: [] +} + +@Injectable( { + providedIn: 'root' +} ) +export class RoadExporterService { + + private readonly extension = 'roadstyle'; + + constructor ( + private openDriveWriter: OdWriter, + private fileService: FileService, + private electron: ElectronService + ) { } + + exportRoadStyle ( road: RoadStyle ) { + + const xmlNode = { + version: 1, + laneOffset: null, + laneSection: null, + }; + + this.writeLaneOffset( xmlNode, road ); + + this.writeLaneSection( xmlNode, road.laneSection ); + + return xmlNode; + } + + writeLaneSection ( xmlNode: any, laneSection: TvLaneSection ) { + + const leftLanes = { + lane: [] + }; + const centerLanes = { + lane: [] + }; + const rightLanes = { + lane: [] + }; + + for ( let i = 0; i < laneSection.getLaneCount(); i++ ) { + + const lane = laneSection.getLane( i ); + const side = lane.getSide(); + + if ( side === TvLaneSide.LEFT ) { + + this.writeLane( leftLanes, lane ); + + } else if ( side === TvLaneSide.RIGHT ) { + + this.writeLane( rightLanes, lane ); + + } else if ( side === TvLaneSide.CENTER ) { + + this.writeLane( centerLanes, lane ); + + } + } + + const laneSectionNode = { + attr_s: laneSection.s, + }; + + if ( leftLanes.lane.length > 0 ) laneSectionNode[ "left" ] = leftLanes; + + if ( centerLanes.lane.length > 0 ) laneSectionNode[ "center" ] = centerLanes; + + if ( rightLanes.lane.length > 0 ) laneSectionNode[ "right" ] = rightLanes; + + xmlNode.laneSection = laneSectionNode; + } + + writeLane ( xmlNode, lane: TvLane ): any { + + const laneNode = { + attr_id: lane.id, + attr_type: lane.type, + attr_level: lane.level, + // link: {}, + width: [], + roadMark: [], + // material: [], + // visibility: [], + // speed: [], + // access: [], + // height: [] + }; + + for ( let i = 0; i < lane.getLaneWidthCount(); i++ ) { + this.openDriveWriter.writeLaneWidth( laneNode, lane.getLaneWidth( i ) ); + } + + for ( let i = 0; i < lane.getLaneRoadMarkCount(); i++ ) { + this.openDriveWriter.writeLaneRoadMark( laneNode, lane.getLaneRoadMark( i ) ); + } + + // NOTE: below lane properties can be added as needed + + // for ( let i = 0; i < lane.getLaneMaterialCount(); i++ ) { + // this.openDriveWriter.writeLaneMaterial( laneNode, lane.getLaneMaterial( i ) ); + // } + + // for ( let i = 0; i < lane.getLaneVisibilityCount(); i++ ) { + // this.openDriveWriter.writeLaneVisibility( laneNode, lane.getLaneVisibility( i ) ); + // } + + // for ( let i = 0; i < lane.getLaneSpeedCount(); i++ ) { + // this.openDriveWriter.writeLaneSpeed( laneNode, lane.getLaneSpeed( i ) ); + // } + + // for ( let i = 0; i < lane.getLaneAccessCount(); i++ ) { + // this.openDriveWriter.writeLaneAccess( laneNode, lane.getLaneAccess( i ) ); + // } + + // for ( let i = 0; i < lane.getLaneHeightCount(); i++ ) { + // this.openDriveWriter.writeLaneHeight( laneNode, lane.getLaneHeight( i ) ); + // } + + xmlNode.lane.push( laneNode ); + + return laneNode; + } + + writeLaneOffset ( xmlNode, road: RoadStyle ) { + + xmlNode.laneOffset = { + attr_s: road.laneOffset.s, + attr_a: road.laneOffset.a, + attr_b: road.laneOffset.b, + attr_c: road.laneOffset.c, + attr_d: road.laneOffset.d, + }; + + return xmlNode; + } + + exportTypes ( types: TvRoadTypeClass[] ) { + + return types.map( type => { + + return { + attr_s: type.s, + attr_type: type.type, + speed: { + attr_max: type.speed.max, + attr_unit: type.speed.unit + }, + } + + } ); + + } + + +} diff --git a/src/app/services/road-style-importer.ts b/src/app/services/road-style-importer.ts new file mode 100644 index 00000000..3222da32 --- /dev/null +++ b/src/app/services/road-style-importer.ts @@ -0,0 +1,172 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { TvMapSourceFile } from 'app/modules/tv-map/services/tv-map-source-file'; +import { TvMap } from 'app/modules/tv-map/models/tv-map.model'; +import { SnackBar } from './snack-bar.service'; +import { AbstractReader } from 'app/core/services/abstract-reader'; +import { RoadStyle } from './road-style.service'; +import { TvRoadLaneOffset } from 'app/modules/tv-map/models/tv-road-lane-offset'; +import { TvLaneSection } from 'app/modules/tv-map/models/tv-lane-section'; +import { TvLaneSide } from '../modules/tv-map/models/tv-common'; +import { TvLane } from '../modules/tv-map/models/tv-lane'; +import { TvLaneRoadMark } from '../modules/tv-map/models/tv-lane-road-mark'; +import { TvLaneWidth } from '../modules/tv-map/models/tv-lane-width'; + +export class RoadStyleImporter extends AbstractReader { + + get openDrive (): TvMap { + return TvMapSourceFile.openDrive; + } + + set openDrive ( value ) { + TvMapSourceFile.openDrive = value; + } + + constructor () { + super(); + } + + static importFromString ( contents: string ): RoadStyle { + + const roadStyleFile: any = JSON.parse( contents ); + + // check for main elements first before parsing + const version = roadStyleFile.version; + + if ( !version ) SnackBar.error( 'Cannot read road-style version. Please check file before importing', 'OK', 5000 ); + if ( !version ) return; + + return this.importRoadStyle( roadStyleFile ); + } + + static importRoadStyle ( json: any ): RoadStyle { + + const roadStyle = new RoadStyle(); + + roadStyle.laneOffset = new TvRoadLaneOffset( + json.laneOffset.attr_s || 0, + json.laneOffset.attr_a || 0, + json.laneOffset.attr_b || 0, + json.laneOffset.attr_c || 0, + json.laneOffset.attr_d || 0 + ); + + roadStyle.laneSection = this.importLaneSection( json ); + + return roadStyle; + } + + static importLaneSection ( json: any ): TvLaneSection { + + const laneSection = new TvLaneSection( 0, 0, true, 1 ); + + this.readAsOptionalElement( json.laneSection.left, xml => { + this.readAsOptionalArray( xml.lane, xml => { + this.readLane( laneSection, xml, TvLaneSide.LEFT ); + } ); + } ); + + this.readAsOptionalElement( json.laneSection.center, xml => { + this.readAsOptionalArray( xml.lane, xml => { + this.readLane( laneSection, xml, TvLaneSide.CENTER ); + } ); + } ); + + this.readAsOptionalElement( json.laneSection.right, xml => { + this.readAsOptionalArray( xml.lane, xml => { + this.readLane( laneSection, xml, TvLaneSide.RIGHT ); + } ); + } ); + + return laneSection; + } + + static readLane ( laneSection: TvLaneSection, xmlElement: any, laneSide: TvLaneSide ) { + + const id = parseFloat( xmlElement.attr_id ); + const type = xmlElement.attr_type; + const level = xmlElement.attr_level; + + laneSection.addLane( laneSide, id, type, level, false ); + + const lane = laneSection.getLastAddedLane(); + + if ( xmlElement.link != null ) { + + const predecessorXml = xmlElement.link.predecessor; + const successorXml = xmlElement.link.successor; + + if ( predecessorXml != null ) { + + // tslint:disable-next-line:radix + lane.setPredecessor( parseInt( predecessorXml.attr_id ) ); + + } + + if ( successorXml != null ) { + + // tslint:disable-next-line:radix + lane.setSuccessor( parseInt( successorXml.attr_id ) ); + + } + } + + // Read Width + this.readAsOptionalArray( xmlElement.width, xml => { + + lane.addWidthRecordInstance( this.readLaneWidth( lane, xml ) ); + + } ); + + // Read RoadMark + this.readAsOptionalArray( xmlElement.roadMark, xml => { + + lane.addRoadMarkInstance( this.readLaneRoadMark( lane, xml ) ); + + } ); + + // // Read material + // this.readAsOptionalArray( xmlElement.material, xml => this.readLaneMaterial( lane, xml ) ); + // + // // Read visibility + // this.readAsOptionalArray( xmlElement.visibility, xml => this.readLaneVisibility( lane, xml ) ); + // + // // Read speed + // this.readAsOptionalArray( xmlElement.speed, xml => this.readLaneSpeed( lane, xml ) ); + // + // // Read access + // this.readAsOptionalArray( xmlElement.access, xml => this.readLaneAccess( lane, xml ) ); + // + // // Read height + // this.readAsOptionalArray( xmlElement.height, xml => this.readLaneHeight( lane, xml ) ); + + } + + static readLaneWidth ( lane: TvLane, json: any ) { + + const sOffset = parseFloat( json.attr_sOffset ); + + const a = parseFloat( json.attr_a ); + const b = parseFloat( json.attr_b ); + const c = parseFloat( json.attr_c ); + const d = parseFloat( json.attr_d ); + + return new TvLaneWidth( sOffset, a, b, c, d ); + } + + static readLaneRoadMark ( lane: TvLane, json: any ) { + + const sOffset = parseFloat( json.attr_sOffset ); + const type = json.attr_type; + const weight = json.attr_weight; + const color = json.attr_color; + const width = parseFloat( json.attr_width ); + const laneChange = json.attr_laneChange; + const height = json.attr_height; + + return new TvLaneRoadMark( sOffset, type, weight, color, width, laneChange, height, lane ); + } + +} diff --git a/src/app/services/road-style.service.ts b/src/app/services/road-style.service.ts new file mode 100644 index 00000000..64827131 --- /dev/null +++ b/src/app/services/road-style.service.ts @@ -0,0 +1,346 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { TvRoadLaneOffset } from 'app/modules/tv-map/models/tv-road-lane-offset'; +import { TvLaneSection } from 'app/modules/tv-map/models/tv-lane-section'; +import { TvLaneSide, TvLaneType, TvRoadMarkTypes, TvRoadMarkWeights, TvColors } from 'app/modules/tv-map/models/tv-common'; + +export class RoadStyle { + + public static extension = 'roadstyle'; + + public static importer = 'RoadStyleImporter'; + + public laneOffset: TvRoadLaneOffset; + + public laneSection: TvLaneSection; + + constructor ( roadId?: number ) { + + this.laneOffset = new TvRoadLaneOffset( 0, 0, 0, 0, 0 ); + + this.laneSection = new TvLaneSection( 0, 0, true, roadId ); + + this.laneSection.roadId = roadId; + + } + + clone ( roadId: number ): RoadStyle { + + const style = new RoadStyle( roadId ); + + style.laneOffset = this.laneOffset.clone(); + + style.laneSection = this.laneSection.cloneAtS() + + return style; + } + + +} + +export class RoadStyleService { + + private static style?: RoadStyle; + + static setCurrentStyle ( style: RoadStyle ) { + + this.style = style; + + } + + static getRoadStyle ( roadId?: number ): RoadStyle { + + if ( this.style ) { + + return this.style.clone( roadId ); + } + + return this.getDefaultRoadStyle( roadId ); + } + + static getDefaultRoadStyle ( roadId?: number ): RoadStyle { + + const roadStyle = new RoadStyle(); + + roadStyle.laneOffset = new TvRoadLaneOffset( 0, 0, 0, 0, 0 ); + + roadStyle.laneSection = new TvLaneSection( 0, 0, true, roadId ); + + const leftLane3 = roadStyle.laneSection.addLane( TvLaneSide.LEFT, 3, TvLaneType.sidewalk, true, true ); + const leftLane2 = roadStyle.laneSection.addLane( TvLaneSide.LEFT, 2, TvLaneType.shoulder, true, true ); + const leftLane1 = roadStyle.laneSection.addLane( TvLaneSide.LEFT, 1, TvLaneType.driving, true, true ); + const centerLane = roadStyle.laneSection.addLane( TvLaneSide.CENTER, 0, TvLaneType.driving, true, true ); + const rightLane1 = roadStyle.laneSection.addLane( TvLaneSide.RIGHT, -1, TvLaneType.driving, true, true ); + const rightLane2 = roadStyle.laneSection.addLane( TvLaneSide.RIGHT, -2, TvLaneType.shoulder, true, true ); + const rightLane3 = roadStyle.laneSection.addLane( TvLaneSide.RIGHT, -3, TvLaneType.sidewalk, true, true ); + + leftLane1.addRoadMarkRecord( 0, TvRoadMarkTypes.NONE, TvRoadMarkWeights.STANDARD, TvColors.STANDARD, 0.15, 'none', 0 ); + centerLane.addRoadMarkRecord( 0, TvRoadMarkTypes.BROKEN, TvRoadMarkWeights.STANDARD, TvColors.STANDARD, 0.15, 'none', 0 ); + rightLane1.addRoadMarkRecord( 0, TvRoadMarkTypes.NONE, TvRoadMarkWeights.STANDARD, TvColors.STANDARD, 0.15, 'none', 0 ); + + roadStyle.laneSection.getLaneVector().forEach( lane => { + + if ( lane.side !== TvLaneSide.CENTER ) { + + if ( lane.type == TvLaneType.driving ) lane.addWidthRecord( 0, 3.6, 0, 0, 0 ); + + else if ( lane.type == TvLaneType.sidewalk ) lane.addWidthRecord( 0, 2, 0, 0, 0 ); + + else lane.addWidthRecord( 0, 0.5, 0, 0, 0 ); + + } + + } ); + + return roadStyle; + } + + // static getCountryRoadStyle (): RoadStyle { + + // const roadStyle = new RoadStyle(); + + // roadStyle.laneSection = new TvLaneSection( 1, 0, true, -1 ); + + // const leftLane2 = roadStyle.laneSection.addLane( TvLaneSide.LEFT, 2, TvLaneType.shoulder, true, true ); + // const leftLane1 = roadStyle.laneSection.addLane( TvLaneSide.LEFT, 1, TvLaneType.driving, true, true ); + // const centerLane = roadStyle.laneSection.addLane( TvLaneSide.CENTER, 0, TvLaneType.driving, true, true ); + // const rightLane1 = roadStyle.laneSection.addLane( TvLaneSide.RIGHT, -1, TvLaneType.driving, true, true ); + // const rightLane2 = roadStyle.laneSection.addLane( TvLaneSide.RIGHT, -2, TvLaneType.shoulder, true, true ); + + // leftLane1.addRoadMarkRecord( 0, TvRoadMarkTypes.SOLID, TvRoadMarkWeights.STANDARD, TvColors.STANDARD, 0.15, 'none', 0 ); + // centerLane.addRoadMarkRecord( 0, TvRoadMarkTypes.NONE, TvRoadMarkWeights.STANDARD, TvColors.STANDARD, 0.15, 'none', 0 ); + // rightLane1.addRoadMarkRecord( 0, TvRoadMarkTypes.SOLID, TvRoadMarkWeights.STANDARD, TvColors.STANDARD, 0.15, 'none', 0 ); + + // roadStyle.laneSection.getLaneVector().forEach( lane => { + + // if ( lane.side !== TvLaneSide.CENTER ) { + + // if ( lane.type == TvLaneType.driving ) lane.addWidthRecord( 0, 3.6, 0, 0, 0 ); + + // else if ( lane.type == TvLaneType.sidewalk ) lane.addWidthRecord( 0, 2, 0, 0, 0 ); + + // else lane.addWidthRecord( 0, 0.5, 0, 0, 0 ); + + // } + + // } ); + + // return roadStyle; + // } + + // static getHighwayPassingRoadStyle (): RoadStyle { + + // const roadStyle = new RoadStyle(); + + // roadStyle.laneSection = new TvLaneSection( 1, 0, true, -1 ); + + // const leftLane2 = roadStyle.laneSection.addLane( TvLaneSide.LEFT, 2, TvLaneType.shoulder, true, true ); + // const leftLane1 = roadStyle.laneSection.addLane( TvLaneSide.LEFT, 1, TvLaneType.driving, true, true ); + // const centerLane = roadStyle.laneSection.addLane( TvLaneSide.CENTER, 0, TvLaneType.driving, true, true ); + // const rightLane1 = roadStyle.laneSection.addLane( TvLaneSide.RIGHT, -1, TvLaneType.driving, true, true ); + // const rightLane2 = roadStyle.laneSection.addLane( TvLaneSide.RIGHT, -2, TvLaneType.shoulder, true, true ); + + // leftLane1.addRoadMarkRecord( 0, TvRoadMarkTypes.SOLID, TvRoadMarkWeights.STANDARD, TvColors.STANDARD, 0.15, 'none', 0 ); + // centerLane.addRoadMarkRecord( 0, TvRoadMarkTypes.BROKEN, TvRoadMarkWeights.STANDARD, TvColors.YELLOW, 0.15, 'none', 0 ); + // rightLane1.addRoadMarkRecord( 0, TvRoadMarkTypes.SOLID, TvRoadMarkWeights.STANDARD, TvColors.STANDARD, 0.15, 'none', 0 ); + + // roadStyle.laneSection.getLaneVector().forEach( lane => { + + // if ( lane.side !== TvLaneSide.CENTER ) { + + // if ( lane.type == TvLaneType.driving ) lane.addWidthRecord( 0, 3.6, 0, 0, 0 ); + + // else if ( lane.type == TvLaneType.sidewalk ) lane.addWidthRecord( 0, 2, 0, 0, 0 ); + + // else lane.addWidthRecord( 0, 0.5, 0, 0, 0 ); + + // } + + // } ); + + // return roadStyle; + // } + + // static getHighwayRoadStyle (): RoadStyle { + + // const roadStyle = new RoadStyle(); + + // roadStyle.laneSection = new TvLaneSection( 1, 0, true, -1 ); + + // const leftLane2 = roadStyle.laneSection.addLane( TvLaneSide.LEFT, 2, TvLaneType.shoulder, true, true ); + // const leftLane1 = roadStyle.laneSection.addLane( TvLaneSide.LEFT, 1, TvLaneType.driving, true, true ); + // const centerLane = roadStyle.laneSection.addLane( TvLaneSide.CENTER, 0, TvLaneType.driving, true, true ); + // const rightLane1 = roadStyle.laneSection.addLane( TvLaneSide.RIGHT, -1, TvLaneType.driving, true, true ); + // const rightLane2 = roadStyle.laneSection.addLane( TvLaneSide.RIGHT, -2, TvLaneType.shoulder, true, true ); + + // leftLane1.addRoadMarkRecord( 0, TvRoadMarkTypes.SOLID, TvRoadMarkWeights.STANDARD, TvColors.STANDARD, 0.15, 'none', 0 ); + // centerLane.addRoadMarkRecord( 0, TvRoadMarkTypes.SOLID_SOLID, TvRoadMarkWeights.STANDARD, TvColors.YELLOW, 0.15, 'none', 0 ); + // rightLane1.addRoadMarkRecord( 0, TvRoadMarkTypes.SOLID, TvRoadMarkWeights.STANDARD, TvColors.STANDARD, 0.15, 'none', 0 ); + + // roadStyle.laneSection.getLaneVector().forEach( lane => { + + // if ( lane.side !== TvLaneSide.CENTER ) { + + // if ( lane.type == TvLaneType.driving ) lane.addWidthRecord( 0, 3.6, 0, 0, 0 ); + + // else if ( lane.type == TvLaneType.sidewalk ) lane.addWidthRecord( 0, 2, 0, 0, 0 ); + + // else lane.addWidthRecord( 0, 0.5, 0, 0, 0 ); + + // } + + // } ); + + // return roadStyle; + // } + + // static getFreewayRoadStyle (): RoadStyle { + + // const roadStyle = new RoadStyle(); + + // roadStyle.laneSection = new TvLaneSection( 1, 0, true, -1 ); + + // const leftLane6 = roadStyle.laneSection + // .addLane( TvLaneSide.LEFT, 6, TvLaneType.shoulder, true, true ); + + // const leftLane5 = roadStyle.laneSection + // .addLane( TvLaneSide.LEFT, 5, TvLaneType.driving, true, true ) + // .addRoadMarkRecord( 0, TvRoadMarkTypes.SOLID, TvRoadMarkWeights.STANDARD, TvColors.STANDARD, 0.15, 'none', 0 ); + + // const leftLane4 = roadStyle.laneSection + // .addLane( TvLaneSide.LEFT, 4, TvLaneType.driving, true, true ) + // .addRoadMarkRecord( 0, TvRoadMarkTypes.BROKEN, TvRoadMarkWeights.STANDARD, TvColors.STANDARD, 0.15, 'none', 0 ); + + // const leftLane3 = roadStyle.laneSection + // .addLane( TvLaneSide.LEFT, 3, TvLaneType.driving, true, true ) + // .addRoadMarkRecord( 0, TvRoadMarkTypes.BROKEN, TvRoadMarkWeights.STANDARD, TvColors.STANDARD, 0.15, 'none', 0 ); + + // const leftLane2 = roadStyle.laneSection + // .addLane( TvLaneSide.LEFT, 2, TvLaneType.driving, true, true ) + // .addRoadMarkRecord( 0, TvRoadMarkTypes.BROKEN, TvRoadMarkWeights.STANDARD, TvColors.STANDARD, 0.15, 'none', 0 ); + + // const leftLane1 = roadStyle.laneSection + // .addLane( TvLaneSide.LEFT, 1, TvLaneType.shoulder, true, true ) + // .addRoadMarkRecord( 0, TvRoadMarkTypes.SOLID, TvRoadMarkWeights.STANDARD, TvColors.YELLOW, 0.15, 'none', 0 ); + + // const centerLane = roadStyle.laneSection + // .addLane( TvLaneSide.CENTER, 0, TvLaneType.none, true, true ) + // .addRoadMarkRecord( 0, TvRoadMarkTypes.NONE, TvRoadMarkWeights.STANDARD, TvColors.STANDARD, 0.15, 'none', 0 ); + + // const rightLane1 = roadStyle.laneSection + // .addLane( TvLaneSide.RIGHT, -1, TvLaneType.shoulder, true, true ) + // .addRoadMarkRecord( 0, TvRoadMarkTypes.SOLID, TvRoadMarkWeights.STANDARD, TvColors.YELLOW, 0.15, 'none', 0 ); + + // const rightLane2 = roadStyle.laneSection + // .addLane( TvLaneSide.RIGHT, -2, TvLaneType.driving, true, true ) + // .addRoadMarkRecord( 0, TvRoadMarkTypes.BROKEN, TvRoadMarkWeights.STANDARD, TvColors.STANDARD, 0.15, 'none', 0 ); + + // const rightLane3 = roadStyle.laneSection + // .addLane( TvLaneSide.RIGHT, -3, TvLaneType.driving, true, true ) + // .addRoadMarkRecord( 0, TvRoadMarkTypes.BROKEN, TvRoadMarkWeights.STANDARD, TvColors.STANDARD, 0.15, 'none', 0 ); + + // const rightLane4 = roadStyle.laneSection + // .addLane( TvLaneSide.RIGHT, -4, TvLaneType.driving, true, true ) + // .addRoadMarkRecord( 0, TvRoadMarkTypes.BROKEN, TvRoadMarkWeights.STANDARD, TvColors.STANDARD, 0.15, 'none', 0 ); + + // const rightLane5 = roadStyle.laneSection + // .addLane( TvLaneSide.RIGHT, -5, TvLaneType.driving, true, true ) + // .addRoadMarkRecord( 0, TvRoadMarkTypes.SOLID, TvRoadMarkWeights.STANDARD, TvColors.STANDARD, 0.15, 'none', 0 ); + + // const rightLane6 = roadStyle.laneSection + // .addLane( TvLaneSide.RIGHT, -6, TvLaneType.shoulder, true, true ); + + // roadStyle.laneSection.getLaneVector().forEach( lane => { + + // if ( lane.side !== TvLaneSide.CENTER ) { + + // if ( lane.type == TvLaneType.driving ) lane.addWidthRecord( 0, 3.6, 0, 0, 0 ); + + // else if ( lane.type == TvLaneType.shoulder ) lane.addWidthRecord( 0, 3.6, 0, 0, 0 ); + + // else lane.addWidthRecord( 0, 0.5, 0, 0, 0 ); + + // } + + // } ); + + // return roadStyle; + // } + + // static getFreewayOneWayRoadStyle (): RoadStyle { + + // const roadStyle = new RoadStyle(); + + // roadStyle.laneSection = new TvLaneSection( 1, 0, true, -1 ); + + // const centerLane = roadStyle.laneSection + // .addLane( TvLaneSide.CENTER, 0, TvLaneType.none, true, true ) + // .addRoadMarkRecord( 0, TvRoadMarkTypes.NONE, TvRoadMarkWeights.STANDARD, TvColors.STANDARD, 0.15, 'none', 0 ); + + // const rightLane1 = roadStyle.laneSection + // .addLane( TvLaneSide.RIGHT, -1, TvLaneType.shoulder, true, true ) + // .addRoadMarkRecord( 0, TvRoadMarkTypes.SOLID, TvRoadMarkWeights.STANDARD, TvColors.YELLOW, 0.15, 'none', 0 ); + + // const rightLane2 = roadStyle.laneSection + // .addLane( TvLaneSide.RIGHT, -2, TvLaneType.driving, true, true ) + // .addRoadMarkRecord( 0, TvRoadMarkTypes.BROKEN, TvRoadMarkWeights.STANDARD, TvColors.STANDARD, 0.15, 'none', 0 ); + + // const rightLane3 = roadStyle.laneSection + // .addLane( TvLaneSide.RIGHT, -3, TvLaneType.driving, true, true ) + // .addRoadMarkRecord( 0, TvRoadMarkTypes.BROKEN, TvRoadMarkWeights.STANDARD, TvColors.STANDARD, 0.15, 'none', 0 ); + + // const rightLane4 = roadStyle.laneSection + // .addLane( TvLaneSide.RIGHT, -4, TvLaneType.driving, true, true ) + // .addRoadMarkRecord( 0, TvRoadMarkTypes.BROKEN, TvRoadMarkWeights.STANDARD, TvColors.STANDARD, 0.15, 'none', 0 ); + + // const rightLane5 = roadStyle.laneSection + // .addLane( TvLaneSide.RIGHT, -5, TvLaneType.driving, true, true ) + // .addRoadMarkRecord( 0, TvRoadMarkTypes.SOLID, TvRoadMarkWeights.STANDARD, TvColors.STANDARD, 0.15, 'none', 0 ); + + // const rightLane6 = roadStyle.laneSection + // .addLane( TvLaneSide.RIGHT, -6, TvLaneType.shoulder, true, true ); + + // roadStyle.laneSection.getLaneVector().forEach( lane => { + + // if ( lane.side !== TvLaneSide.CENTER ) { + + // if ( lane.type == TvLaneType.driving ) lane.addWidthRecord( 0, 3.6, 0, 0, 0 ); + + // else if ( lane.type == TvLaneType.shoulder ) lane.addWidthRecord( 0, 3.6, 0, 0, 0 ); + + // else lane.addWidthRecord( 0, 0.5, 0, 0, 0 ); + + // } + + // } ); + + // return roadStyle; + // } + + // static getOneWayRoadStyle (): RoadStyle { + + // const roadStyle = new RoadStyle(); + + // roadStyle.laneSection = new TvLaneSection( 1, 0, true, -1 ); + + // const l1 = roadStyle.laneSection + // .addLane( TvLaneSide.LEFT, 1, TvLaneType.sidewalk, true, true ) + // .addWidthRecord( 0, 2.0, 0, 0, 0 ); + + // const centerLane = roadStyle.laneSection + // .addLane( TvLaneSide.CENTER, 0, TvLaneType.none, true, true ); + + // const r1 = roadStyle.laneSection + // .addLane( TvLaneSide.RIGHT, -1, TvLaneType.driving, true, true ) + // .addWidthRecord( 0, 3.6, 0, 0, 0 ); + + // const r2 = roadStyle.laneSection + // .addLane( TvLaneSide.RIGHT, -2, TvLaneType.sidewalk, true, true ) + // .addWidthRecord( 0, 2.0, 0, 0, 0 ); + + // return roadStyle; + // } + +} \ No newline at end of file diff --git a/src/app/services/scene-exporter.service.ts b/src/app/services/scene-exporter.service.ts new file mode 100644 index 00000000..97e9a0a5 --- /dev/null +++ b/src/app/services/scene-exporter.service.ts @@ -0,0 +1,398 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { Injectable } from '@angular/core'; +import { TvMapSourceFile } from 'app/modules/tv-map/services/tv-map-source-file'; +import { PropInstance } from 'app/core/models/prop-instance.model'; +import { OdWriter } from 'app/modules/tv-map/services/open-drive-writer.service'; +import { Euler, Vector3 } from 'three'; +import { TvMap } from 'app/modules/tv-map/models/tv-map.model'; +import { TvRoad } from 'app/modules/tv-map/models/tv-road.model'; +import { TvRoadTypeClass } from 'app/modules/tv-map/models/tv-road-type.class'; +import { AbstractSpline } from 'app/core/shapes/abstract-spline'; +import { AutoSpline } from 'app/core/shapes/auto-spline'; +import { FileService } from './file.service'; +import { ElectronService } from 'ngx-electron'; + +import { saveAs } from 'file-saver'; +import { IFile } from 'app/core/models/file'; +import { TvSurface } from 'app/modules/tv-map/models/tv-surface.model'; +import { ExplicitSpline } from 'app/core/shapes/explicit-spline'; +import { SnackBar } from './snack-bar.service'; +import { RoadControlPoint } from 'app/modules/three-js/objects/road-control-point'; +import { TvJunction } from 'app/modules/tv-map/models/tv-junction'; +import { PropCurve } from 'app/modules/tv-map/models/prop-curve'; +import { PropPolygon } from 'app/modules/tv-map/models/prop-polygons'; + +const Parser = require( 'fast-xml-parser' ).j2xParser; + +export interface Scene { + + road: { guid: string }, + + props: { guid: string, position: Vector3, rotation: Euler, scale: Vector3 }[], + + propCurves: [] +} + +@Injectable( { + providedIn: 'root' +} ) +export class SceneExporterService { + + private readonly extension = 'scene'; + + private openDrive: TvMap; + + constructor ( + private openDriveWriter: OdWriter, + private fileService: FileService, + private electron: ElectronService + ) { } + + get currentFile (): IFile { return TvMapSourceFile.currentFile; } + + set currentFile ( value: IFile ) { TvMapSourceFile.currentFile = value; } + + export ( openDrive?: TvMap ): string { + + const defaultOptions = { + attributeNamePrefix: 'attr_', + attrNodeName: false, + ignoreAttributes: false, + supressEmptyNode: true, + format: true, + trimValues: true, + }; + + const parser = new Parser( defaultOptions ); + + this.openDrive = openDrive || TvMapSourceFile.openDrive; + + const scene = this.exportMap( this.openDrive ); + + const xmlDocument = parser.parse( scene ); + + return xmlDocument; + } + + saveAs () { + + const contents = this.export(); + + if ( this.electron.isElectronApp ) { + + this.fileService.saveFileWithExtension( + + this.fileService.projectFolder, contents, this.extension, ( file: IFile ) => { + + this.currentFile.path = file.path; + this.currentFile.name = file.name; + + } ); + + } else { + + saveAs( new Blob( [ contents ] ), `road.${ this.extension }` ); + + } + + } + + exportMap ( openDrive: TvMap ) { + + return { + version: 0.1, + road: this.exportRoads( [ ...this.openDrive.roads.values() ] ), + prop: this.exportProps( openDrive.props ), + propCurve: this.exportPropCurves( openDrive.propCurves ), + propPolygon: this.exportPropPolygons( openDrive.propPolygons ), + surface: this.exportSurfaces( openDrive.surfaces ), + junction: this.exportJunctions( [ ...openDrive.junctions.values() ] ), + }; + + } + + exportRoads ( roads: TvRoad[] ) { + + return roads.map( road => this.exportRoad( road ) ); + + } + + exportRoad ( road: TvRoad ) { + + const xml = { + attr_id: road.id, + attr_name: road.name, + attr_length: road.length, + attr_junction: road.junction, + drivingMaterialGuid: road.drivingMaterialGuid, + sidewalkMaterialGuid: road.sidewalkMaterialGuid, + borderMaterialGuid: road.borderMaterialGuid, + shoulderMaterialGuid: road.shoulderMaterialGuid, + spline: this.exportRoadSpline( road.spline ), + } + + this.openDriveWriter.writeRoadLinks( xml, road ); + + this.openDriveWriter.writeRoadType( xml, road ); + + // no need as geometry is being stored via spline + // this.openDriveWriter.writePlanView( xml, road ); + + this.openDriveWriter.writeElevationProfile( xml, road ); + + this.openDriveWriter.writeLateralProfile( xml, road ); + + this.openDriveWriter.writeLanes( xml, road ); + + // TODO: maybe not required here + this.openDriveWriter.writeObjects( xml, road ); + + // TODO: maybe not required here + this.openDriveWriter.writeSignals( xml, road ); + + + return xml; + } + + exportRoadSpline ( spline: AbstractSpline ) { + + if ( spline instanceof AutoSpline ) { + + return { + attr_type: spline.type, + point: spline.controlPointPositions.map( point => ( { + attr_x: point.x, + attr_y: point.y, + attr_z: point.z + } ) ) + } + + + } else if ( spline instanceof ExplicitSpline ) { + + return { + attr_type: spline.type, + point: spline.controlPoints.map( ( point: RoadControlPoint ) => ( { + attr_x: point.position.x, + attr_y: point.position.y, + attr_z: point.position.z, + attr_hdg: point.hdg, + attr_type: point.segmentType, + } ) ) + } + + } else { + + SnackBar.error( "Not able to export this spline type" ); + + } + + } + + exportTypes ( types: TvRoadTypeClass[] ) { + + return types.map( type => { + + return { + attr_s: type.s, + attr_type: type.type, + speed: { + attr_max: type.speed.max, + attr_unit: type.speed.unit + }, + } + + } ); + + } + + exportJunctions ( junctions: TvJunction[] ) { + + return junctions.map( junction => this.exportJunction( junction ) ); + + } + + exportJunction ( junction: TvJunction ) { + + const xml = { + attr_id: junction.id, + attr_name: junction.name, + position: { + attr_x: junction.position ? junction.position.x : 0, + attr_y: junction.position ? junction.position.y : 0, + attr_z: junction.position ? junction.position.z : 0, + }, + connection: [], + priority: [], + controller: [] + }; + + this.openDriveWriter.writeJunctionConnection( xml, junction ); + + // TODO: Add controller and priority as well + + // this.openDriveWriter.writeJunctionController( xml, junction ); + + // this.openDriveWriter.writeJunctionPriority( xml, junction ); + + return xml; + } + + exportProps ( props: PropInstance[] ) { + + return props.map( prop => this.exportProp( prop ) ); + + } + + exportProp ( prop: PropInstance ) { + + return { + attr_guid: prop.guid, + position: { + attr_x: prop.object.position.x, + attr_y: prop.object.position.y, + attr_z: prop.object.position.z, + }, + rotation: { + attr_x: prop.object.rotation.x, + attr_y: prop.object.rotation.y, + attr_z: prop.object.rotation.z, + }, + scale: { + attr_x: prop.object.scale.x, + attr_y: prop.object.scale.y, + attr_z: prop.object.scale.z, + } + } + + } + + exportPropCurves ( curves: PropCurve[] ) { + + return curves.map( curve => this.exportPropCurve( curve ) ); + + } + + exportPropCurve ( curve: PropCurve ) { + + return { + attr_rotation: curve.rotation, + attr_positionVariance: curve.positionVariance, + attr_reverse: curve.reverse, + attr_guid: curve.propGuid, + props: curve.props.map( prop => ( { + attr_guid: curve.propGuid, + position: { + attr_x: prop.position.x, + attr_y: prop.position.y, + attr_z: prop.position.z, + }, + rotation: { + attr_x: prop.rotation.x, + attr_y: prop.rotation.y, + attr_z: prop.rotation.z, + }, + scale: { + attr_x: prop.scale.x, + attr_y: prop.scale.y, + attr_z: prop.scale.z, + } + } ) ), + spline: { + attr_type: curve.spline.type, + attr_closed: curve.spline.closed, + attr_tension: curve.spline.tension, + point: curve.spline.controlPointPositions.map( p => ( { + attr_x: p.x, + attr_y: p.y, + attr_z: p.z, + } ) ) + } + }; + + } + + exportPropPolygons ( polygons: PropPolygon[] ) { + + return polygons.map( polygon => this.exportPropPolygon( polygon ) ); + + } + + exportPropPolygon ( polygon: PropPolygon ) { + + return { + attr_guid: polygon.propGuid, + attr_density: polygon.density, + props: polygon.props.map( prop => ( { + attr_guid: polygon.propGuid, + position: { + attr_x: prop.position.x, + attr_y: prop.position.y, + attr_z: prop.position.z, + }, + rotation: { + attr_x: prop.rotation.x, + attr_y: prop.rotation.y, + attr_z: prop.rotation.z, + }, + scale: { + attr_x: prop.scale.x, + attr_y: prop.scale.y, + attr_z: prop.scale.z, + } + } ) ), + spline: { + attr_type: polygon.spline.type, + attr_closed: polygon.spline.closed, + attr_tension: polygon.spline.tension, + point: polygon.spline.controlPointPositions.map( p => ( { + attr_x: p.x, + attr_y: p.y, + attr_z: p.z, + } ) ) + } + }; + + } + + exportSurfaces ( surfaces: TvSurface[] ) { + + return surfaces.map( surface => this.exportSurface( surface ) ); + + } + + exportSurface ( surface: TvSurface ) { + + return { + attr_height: surface.height, + attr_rotation: surface.rotation, + material: { + attr_guid: surface.materialGuid + }, + offset: { + attr_x: surface.offset.x, + attr_y: surface.offset.y, + }, + scale: { + attr_x: surface.scale.x, + attr_y: surface.scale.y, + }, + spline: { + attr_type: surface.spline.type, + attr_closed: surface.spline.closed, + attr_tension: surface.spline.tension, + point: surface.spline.controlPointPositions.map( p => ( { + attr_x: p.x, + attr_y: p.y, + attr_z: p.z, + } ) ) + } + } + + } + + +} diff --git a/src/app/services/scene-importer.service.ts b/src/app/services/scene-importer.service.ts new file mode 100644 index 00000000..60395bbe --- /dev/null +++ b/src/app/services/scene-importer.service.ts @@ -0,0 +1,608 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { Injectable } from '@angular/core'; +import { FileService } from './file.service'; +import { SnackBar } from './snack-bar.service'; +import { TvMapService } from 'app/modules/tv-map/services/tv-map.service'; +import { AssetLoaderService } from './asset-loader.service'; +import { PropInstance } from 'app/core/models/prop-instance.model'; +import { AbstractReader } from 'app/core/services/abstract-reader'; +import { TvRoad } from 'app/modules/tv-map/models/tv-road.model'; +import { Euler, Vector2, Vector3, Object3D } from 'three'; +import { AutoSpline } from 'app/core/shapes/auto-spline'; +import { AnyControlPoint } from 'app/modules/three-js/objects/control-point'; +import { OpenDriverParser } from 'app/modules/tv-map/services/open-drive-parser.service'; +import { TvMapBuilder } from 'app/modules/tv-map/builders/od-builder.service'; +import { TvMap } from 'app/modules/tv-map/models/tv-map.model'; +import { SceneService } from 'app/core/services/scene.service'; +import { IFile } from 'app/core/models/file'; +import { TvMapSourceFile } from 'app/modules/tv-map/services/tv-map-source-file'; +import { ModelImporterService } from './model-importer.service'; +import { TvSurface } from 'app/modules/tv-map/models/tv-surface.model'; +import { CatmullRomSpline } from 'app/core/shapes/catmull-rom-spline'; +import { ToolManager } from 'app/core/tools/tool-manager'; +import { AppInspector } from 'app/core/inspector'; +import { RoadControlPoint } from 'app/modules/three-js/objects/road-control-point'; +import { ExplicitSpline } from 'app/core/shapes/explicit-spline'; +import { AbstractSpline } from 'app/core/shapes/abstract-spline'; +import { CommandHistory } from './command-history'; +import { PropCurve } from 'app/modules/tv-map/models/prop-curve'; +import { PropModel } from 'app/core/models/prop-model.model'; +import { AssetDatabase } from './asset-database'; +import { PropPolygon } from 'app/modules/tv-map/models/prop-polygons'; + +const Parser = require( 'fast-xml-parser' ); + +@Injectable( { + providedIn: 'root' +} ) +export class SceneImporterService extends AbstractReader { + + constructor ( + private fileService: FileService, + private openDriveService: TvMapService, + private assets: AssetLoaderService, + private odParser: OpenDriverParser, + private modelImporter: ModelImporterService + ) { + super(); + } + + get openDrive (): TvMap { return TvMapSourceFile.openDrive; } + + set openDrive ( value ) { TvMapSourceFile.openDrive = value; } + + importFromPath ( path: string ) { + + try { + + this.fileService.readFile( path, "scene", ( file ) => { + + this.importFromFile( file ); + + } ) + + } catch ( error ) { + + SnackBar.error( error ); + + } + + } + + importFromFile ( file: IFile ): void { + + try { + + if ( this.importFromString( file.contents ) ) { + + TvMapSourceFile.currentFile = file; + + } + + } catch ( error ) { + + SnackBar.error( error ); + + console.error( error ); + + } + + } + + + importFromString ( contents: string ): boolean { + + const defaultOptions = { + attributeNamePrefix: 'attr_', + attrNodeName: false, + textNodeName: 'value', + ignoreAttributes: false, + supressEmptyNode: false, + format: true, + }; + + const scene: any = Parser.parse( contents, defaultOptions ); + + // check for main elements first before parsing + const version = scene.version; + const guid = scene.guid; + + if ( !version ) SnackBar.error( "Cannot read scene version. Please check scene file before importing", "OK", 5000 ); + if ( !version ) return; + + this.prepareToImport(); + + this.importScene( scene ); + + return true; + } + + private prepareToImport () { + + ToolManager.clear(); + + AppInspector.clear(); + + CommandHistory.clear(); + + this.openDrive.destroy(); + + this.openDrive = new TvMap(); + + } + + private importScene ( xml: any ): void { + + this.readAsOptionalArray( xml.road, xml => { + + this.openDrive.addRoadInstance( this.importRoad( xml ) ); + + } ); + + this.openDrive.roads.forEach( road => road.updateGeometryFromSpline() ); + + this.openDrive.roads.forEach( road => { + + if ( road.isJunction ) { + + road.spline.controlPoints.forEach( ( cp: RoadControlPoint ) => cp.allowChange = false ); + + } + + // if ( road.successor && road.successor.elementType === "road" ) { + + // const successor = this.openDrive.getRoadById( road.successor.elementId ); + + // successor.updated.subscribe( i => road.onSuccessorUpdated( i ) ); + // } + + // if ( road.predecessor && road.predecessor.elementType === "road" ) { + + // const predecessor = this.openDrive.getRoadById( road.predecessor.elementId ); + + // predecessor.updated.subscribe( i => road.onPredecessorUpdated( i ) ); + // } + + } ); + + this.readAsOptionalArray( xml.prop, xml => { + + this.importProp( xml ); + + } ); + + this.readAsOptionalArray( xml.propCurve, xml => { + + this.openDrive.propCurves.push( this.importPropCurve( xml ) ); + + } ); + + this.readAsOptionalArray( xml.propPolygon, xml => { + + this.openDrive.propPolygons.push( this.importPropPolygon( xml ) ); + + } ); + + this.readAsOptionalArray( xml.surface, xml => { + + this.openDrive.surfaces.push( this.importSurface( xml ) ); + + } ); + + this.readAsOptionalArray( xml.junction, xml => { + + const junction = this.odParser.readJunction( xml ); + + if ( xml.position ) { + + junction.position = new Vector3( + parseFloat( xml.position.attr_x ), + parseFloat( xml.position.attr_y ), + parseFloat( xml.position.attr_z ), + ); + } + + this.openDrive.addJunctionInstance( junction ); + + } ); + + + this.openDrive.roads.forEach( road => { + + TvMapBuilder.buildRoad( this.openDrive.gameObject, road ); + + } ) + + SceneService.add( this.openDrive.gameObject ); + + } + + private importProp ( xml ) { + + const instance = AssetDatabase.getInstance( xml.attr_guid ); + + if ( !instance ) SnackBar.error( `Object not found` ); + + if ( !instance ) return; + + const prop = instance.clone(); + + const position = new Vector3( + parseFloat( xml.position.attr_x ), + parseFloat( xml.position.attr_y ), + parseFloat( xml.position.attr_z ), + ) + + const rotation = new Euler( + parseFloat( xml.rotation.attr_x ), + parseFloat( xml.rotation.attr_y ), + parseFloat( xml.rotation.attr_z ), + ) + + const scale = new Vector3( + parseFloat( xml.scale.attr_x ), + parseFloat( xml.scale.attr_y ), + parseFloat( xml.scale.attr_z ), + ) + + prop.position.copy( position ); + + prop.rotation.copy( rotation ); + + prop.scale.copy( scale ); + + this.openDrive.gameObject.add( prop ); + + this.openDrive.props.push( new PropInstance( xml.attr_guid, prop ) ); + + } + + private importRoad ( xml: any ): TvRoad { + + if ( !xml.spline ) throw new Error( "Incorrect road" ); + + const name = xml.attr_name || 'untitled'; + const length = parseFloat( xml.attr_length ); + const id = parseInt( xml.attr_id ); + const junction = parseInt( xml.attr_junction ) || -1; + + const road = new TvRoad( name, length, id, junction ); + + road.drivingMaterialGuid = xml.drivingMaterialGuid; + road.sidewalkMaterialGuid = xml.sidewalkMaterialGuid; + road.borderMaterialGuid = xml.borderMaterialGuid; + road.shoulderMaterialGuid = xml.shoulderMaterialGuid; + + road.spline = this.importSpline( xml.spline, road ); + + this.odParser.readRoadTypes( road, xml ); + + if ( xml.link != null ) this.odParser.readRoadLinks( road, xml.link ); + + if ( xml.elevationProfile != null ) this.odParser.readElevationProfile( road, xml.elevationProfile ); + + if ( xml.lateralProfile != null ) this.odParser.readLateralProfile( road, xml.lateralProfile ); + + if ( xml.lanes != null ) this.odParser.readLanes( road, xml.lanes ); + + // if ( xml.objects != null && xml.objects !== '' ) this.readObjects( road, xml.objects ); + + // if ( xml.signals != null && xml.signals !== '' ) this.readSignals( road, xml.signals ); + + // if ( xml.surface != null && xml.surface !== '' ) this.readSurface( road, xml.surface ); + + return road + } + + private importSurface ( xml: any ): TvSurface { + + const height = parseFloat( xml.attr_height ) || 0.0; + + const rotation = parseFloat( xml.attr_rotation ) || 0.0; + + const material = xml.material.attr_guid || 'grass'; + + const offset = new Vector2( + parseFloat( xml.offset.attr_x ), + parseFloat( xml.offset.attr_y ), + ); + + const scale = new Vector2( + parseFloat( xml.scale.attr_x ), + parseFloat( xml.scale.attr_y ), + ); + + const spline = this.importCatmullSpline( xml.spline ); + + const surface = new TvSurface( material, spline, offset, scale, rotation, height ); + + spline.controlPoints.forEach( p => p.mainObject = surface ); + + return surface; + } + + private importSpline ( xml: any, road: TvRoad ): AbstractSpline { + + const type = xml.attr_type; + + if ( type === 'auto' ) { + + return this.importAutoSpline( xml, road ); + + } else if ( type === 'explicit' ) { + + return this.importExplicitSpline( xml, road ); + + } else { + + throw new Error( "unknown spline type" ); + + } + + } + + private importExplicitSpline ( xml: any, road: TvRoad ): ExplicitSpline { + + const spline = new ExplicitSpline( road ); + + let index = 0; + + this.readAsOptionalArray( xml.point, xml => { + + const position = new Vector3( + parseFloat( xml.attr_x ), + parseFloat( xml.attr_y ), + parseFloat( xml.attr_z ), + ); + + const hdg = parseFloat( xml.attr_hdg ); + + const segType = +xml.attr_type; + + spline.addFromFile( index, position, hdg, segType ); + + index++ + + } ); + + // to not show any lines or control points + spline.hide(); + + return spline; + } + + private importAutoSpline ( xml, road: TvRoad ): AutoSpline { + + const spline = new AutoSpline( road ); + + let index = 0; + + this.readAsOptionalArray( xml.point, xml => { + + const controlPoint = new RoadControlPoint( road, new Vector3( + parseFloat( xml.attr_x ), + parseFloat( xml.attr_y ), + parseFloat( xml.attr_z ), + ), "cp", index, index ); + + index++; + + controlPoint.mainObject = controlPoint.userData.road = road; + + SceneService.add( controlPoint ); + + spline.addControlPoint( controlPoint ); + + } ); + + // to not show any lines or control points + spline.hide(); + + return spline; + } + + private importCatmullSpline ( xml: any ): CatmullRomSpline { + + const type = xml.attr_type || "catmullrom"; + const closed = xml.attr_closed === "true"; + const tension = parseFloat( xml.attr_tension ) || 0.5; + + const spline = new CatmullRomSpline( closed, type, tension ); + + this.readAsOptionalArray( xml.point, xml => { + + const controlPoint = AnyControlPoint.create(); + + controlPoint.position.set( + parseFloat( xml.attr_x ), + parseFloat( xml.attr_y ), + parseFloat( xml.attr_z ) + ) + + spline.addControlPoint( controlPoint ); + + SceneService.add( controlPoint ); + + } ); + + // to make the line and other calculations + spline.update(); + + // to not show any lines or control points + spline.hide(); + + return spline; + } + + private importOpenDrive ( road: any ) { + + // guid + if ( typeof road === 'string' ) { + + try { + + const meta = this.assets.find( road ); + + this.openDriveService.importFromPath( meta.guid ); + + } catch ( error ) { + + SnackBar.error( error ); + + } + + } + + if ( typeof road === 'object' ) { + + } + } + + private importProps ( props: any ): any[] { + + const instances: any[] = []; + + if ( Array.isArray( props ) ) { + + props.forEach( ( prop: any ) => { + + instances.push( { + guid: prop.guid, + position: prop.position, + rotation: prop.rotation, + scale: prop.scale + } ); + + } ); + + } + + return instances; + } + + private importVector3 ( vector3: any ) { + + return { + + } + + } + + private importPropCurve ( xml: any ): PropCurve { + + const guid = xml.attr_guid; + + const meta = AssetDatabase.getMetadata( guid ); + + if ( !meta ) return; + + const spline = this.importCatmullSpline( xml.spline ); + + const curve = new PropCurve( guid, spline ); + + spline.controlPoints.forEach( p => p.mainObject = curve ); + + curve.reverse = xml.attr_reverse === 'true' ? true : false; + + curve.rotation = parseFloat( xml.attr_rotation ) || 0; + + curve.positionVariance = parseFloat( xml.attr_positionVariance ) || 0; + + this.readAsOptionalArray( xml.props, propXml => { + + const instance = AssetDatabase.getInstance( propXml.attr_guid ) as Object3D; + + const prop = instance.clone(); + + const position = new Vector3( + parseFloat( propXml.position.attr_x ), + parseFloat( propXml.position.attr_y ), + parseFloat( propXml.position.attr_z ), + ) + + const propRotation = new Euler( + parseFloat( propXml.rotation.attr_x ), + parseFloat( propXml.rotation.attr_y ), + parseFloat( propXml.rotation.attr_z ), + ) + + const scale = new Vector3( + parseFloat( propXml.scale.attr_x ), + parseFloat( propXml.scale.attr_y ), + parseFloat( propXml.scale.attr_z ), + ) + + prop.position.copy( position ); + + prop.rotation.copy( propRotation ); + + prop.scale.copy( scale ); + + curve.props.push( prop ); + + SceneService.add( prop ); + + } ); + + return curve; + } + + private importPropPolygon ( xml: any ): PropPolygon { + + const guid = xml.attr_guid; + + const density = parseFloat( xml.attr_density ) || 0.5; + + const metadata = AssetDatabase.getMetadata( guid ); + + if ( !metadata ) return; + + const spline = this.importCatmullSpline( xml.spline ); + + const polygon = new PropPolygon( guid, spline, density ); + + spline.controlPoints.forEach( p => p.mainObject = p.userData.polygon = polygon ); + + this.readAsOptionalArray( xml.props, propXml => { + + const instance = AssetDatabase.getInstance( propXml.attr_guid ) as Object3D; + + const prop = instance.clone(); + + const position = new Vector3( + parseFloat( propXml.position.attr_x ), + parseFloat( propXml.position.attr_y ), + parseFloat( propXml.position.attr_z ), + ) + + const propRotation = new Euler( + parseFloat( propXml.rotation.attr_x ), + parseFloat( propXml.rotation.attr_y ), + parseFloat( propXml.rotation.attr_z ), + ) + + const scale = new Vector3( + parseFloat( propXml.scale.attr_x ), + parseFloat( propXml.scale.attr_y ), + parseFloat( propXml.scale.attr_z ), + ) + + prop.position.copy( position ); + + prop.rotation.copy( propRotation ); + + prop.scale.copy( scale ); + + polygon.props.push( prop ); + + SceneService.add( prop ); + + } ); + + return polygon; + } + +} diff --git a/src/app/services/snack-bar.service.ts b/src/app/services/snack-bar.service.ts new file mode 100644 index 00000000..03fe62ba --- /dev/null +++ b/src/app/services/snack-bar.service.ts @@ -0,0 +1,79 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { Injectable } from '@angular/core'; +import { MatSnackBar, MatSnackBarRef, SimpleSnackBar } from '@angular/material'; +import { MatSnackBarVerticalPosition } from '@angular/material/snack-bar/typings/snack-bar-config'; +import { AnalyticsService } from 'app/core/analytics/analytics.service'; + +@Injectable( { + providedIn: 'root' +} ) +export class SnackBar { + + private static snackBar: MatSnackBar; + private static verticalPosition: MatSnackBarVerticalPosition = 'bottom'; + private static analytics: AnalyticsService; + + constructor ( private snackBar: MatSnackBar, private analytics: AnalyticsService ) { + + SnackBar.snackBar = snackBar; + SnackBar.analytics = analytics; + + } + + /** + * @deprecated use show instead + */ + static open ( message: string = '', action: string = '', duration: number = 2000 ): MatSnackBarRef { + + return this.show( message, action, duration ); + + } + + static show ( message: string = '', action: string = '', duration: number = 2000 ): MatSnackBarRef { + + return this.snackBar.open( message, action, { + duration: duration, + verticalPosition: this.verticalPosition, + horizontalPosition: 'right' + } ); + + } + + static success ( message: string = '', action: string = '', duration: number = 2000 ): MatSnackBarRef { + + return this.snackBar.open( message, action, { + duration: duration, + verticalPosition: this.verticalPosition, + horizontalPosition: 'right', + panelClass: [ 'green-snackbar' ] + } ); + + } + + static error ( message: string = '', action: string = '', duration: number = 2000 ): MatSnackBarRef { + + if ( this.analytics ) this.analytics.trackError( { name: message, message: message, stack: message } ); + + return this.snackBar.open( message, action, { + duration: duration, + verticalPosition: this.verticalPosition, + horizontalPosition: 'right', + panelClass: [ 'red-snackbar' ] + } ); + + } + + open ( message: string = '', action: string = '', duration: number = 2000 ): MatSnackBarRef { + + return this.snackBar.open( message, action, { + duration: duration, + verticalPosition: SnackBar.verticalPosition, + horizontalPosition: 'right' + } ); + + } + +} diff --git a/src/app/services/storage.service.ts b/src/app/services/storage.service.ts new file mode 100644 index 00000000..00ddb27a --- /dev/null +++ b/src/app/services/storage.service.ts @@ -0,0 +1,35 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { Injectable, EventEmitter } from '@angular/core'; + +@Injectable( { + providedIn: 'root' +} ) +export class StorageService { + + public store ( key: string, value: string ): void { + + window.localStorage.setItem( key, value ); + + } + + public get ( key: string ) { + + return window.localStorage.getItem( key ); + + } + + public delete ( key: string ) { + + return window.localStorage.removeItem( key ); + + } + + public deleteAll ( key: string ) { + + return window.localStorage.clear(); + + } +} diff --git a/src/app/shared/animations/egret-animations.ts b/src/app/shared/animations/egret-animations.ts new file mode 100644 index 00000000..7b1e9019 --- /dev/null +++ b/src/app/shared/animations/egret-animations.ts @@ -0,0 +1,62 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { + sequence, + trigger, + animate, + style, + group, + query, + transition, + animateChild, + state, + animation, + useAnimation, + stagger +} from "@angular/animations"; + +const reusable = animation( + [ + style({ + opacity: "{{opacity}}", + transform: "scale({{scale}}) translate3d({{x}}, {{y}}, {{z}})" + }), + animate("{{duration}} {{delay}} cubic-bezier(0.0, 0.0, 0.2, 1)", style("*")) + ], + { + params: { + duration: "200ms", + delay: "0ms", + opacity: "0", + scale: "1", + x: "0", + y: "0", + z: "0" + } + } +); + +export const egretAnimations = [ + trigger("animate", [transition("void => *", [useAnimation(reusable)])]), + + trigger("fadeInOut", [ + state( + "0", + style({ + opacity: 0, + display: "none" + }) + ), + state( + "1", + style({ + opacity: 1, + display: "block" + }) + ), + transition("0 => 1", animate("300ms")), + transition("1 => 0", animate("300ms")) + ]) +]; diff --git a/src/app/shared/components/breadcrumb/breadcrumb.component.html b/src/app/shared/components/breadcrumb/breadcrumb.component.html new file mode 100644 index 00000000..2fcec1b7 --- /dev/null +++ b/src/app/shared/components/breadcrumb/breadcrumb.component.html @@ -0,0 +1,16 @@ + + + + + \ No newline at end of file diff --git a/src/app/shared/components/breadcrumb/breadcrumb.component.scss b/src/app/shared/components/breadcrumb/breadcrumb.component.scss new file mode 100644 index 00000000..96867174 --- /dev/null +++ b/src/app/shared/components/breadcrumb/breadcrumb.component.scss @@ -0,0 +1,4 @@ +/*! + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + diff --git a/src/app/shared/components/breadcrumb/breadcrumb.component.ts b/src/app/shared/components/breadcrumb/breadcrumb.component.ts new file mode 100644 index 00000000..f094f17d --- /dev/null +++ b/src/app/shared/components/breadcrumb/breadcrumb.component.ts @@ -0,0 +1,67 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { Component, OnInit, OnDestroy } from '@angular/core'; +import { Router, NavigationEnd, ActivatedRoute, ActivatedRouteSnapshot } from '@angular/router'; +import { RoutePartsService } from '../../../shared/services/route-parts.service'; +import { LayoutService } from '../../../shared/services/layout.service'; +import { Subscription } from "rxjs"; +import { filter } from 'rxjs/operators'; + +@Component({ + selector: 'app-breadcrumb', + templateUrl: './breadcrumb.component.html', + styleUrls: ['./breadcrumb.component.scss'] +}) +export class BreadcrumbComponent implements OnInit, OnDestroy { + routeParts:any[]; + routerEventSub: Subscription; + // public isEnabled: boolean = true; + constructor( + private router: Router, + private routePartsService: RoutePartsService, + private activeRoute: ActivatedRoute, + public layout: LayoutService + ) { + this.routerEventSub = this.router.events + .pipe(filter(event => event instanceof NavigationEnd)) + .subscribe((routeChange) => { + this.routeParts = this.routePartsService.generateRouteParts(this.activeRoute.snapshot); + // generate url from parts + this.routeParts.reverse().map((item, i) => { + item.breadcrumb = this.parseText(item); + item.urlSegments.forEach((urlSegment, j) => { + if(j === 0) + return item.url = `${urlSegment.path}`; + item.url += `/${urlSegment.path}` + }); + if(i === 0) { + return item; + } + // prepend previous part to current part + item.url = `${this.routeParts[i - 1].url}/${item.url}`; + return item; + }); + }); + } + + ngOnInit() {} + ngOnDestroy() { + if(this.routerEventSub) { + this.routerEventSub.unsubscribe() + } + } + + parseText(part) { + if(!part.breadcrumb) { + return '' + } + part.breadcrumb = part.breadcrumb.replace(/{{([^{}]*)}}/g, function (a, b) { + var r = part.params[b]; + return typeof r === 'string' ? r : a; + }); + return part.breadcrumb; + } + +} diff --git a/src/app/shared/components/button-loading/button-loading.component.html b/src/app/shared/components/button-loading/button-loading.component.html new file mode 100644 index 00000000..269b9481 --- /dev/null +++ b/src/app/shared/components/button-loading/button-loading.component.html @@ -0,0 +1,11 @@ + + + \ No newline at end of file diff --git a/src/app/shared/components/button-loading/button-loading.component.scss b/src/app/shared/components/button-loading/button-loading.component.scss new file mode 100644 index 00000000..96867174 --- /dev/null +++ b/src/app/shared/components/button-loading/button-loading.component.scss @@ -0,0 +1,4 @@ +/*! + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + diff --git a/src/app/shared/components/button-loading/button-loading.component.ts b/src/app/shared/components/button-loading/button-loading.component.ts new file mode 100644 index 00000000..8f208b89 --- /dev/null +++ b/src/app/shared/components/button-loading/button-loading.component.ts @@ -0,0 +1,25 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { Component, OnInit, Input } from '@angular/core'; + +@Component({ + selector: 'button-loading', + templateUrl: './button-loading.component.html', + styleUrls: ['./button-loading.component.scss'] +}) +export class ButtonLoadingComponent implements OnInit { + + @Input('loading') loading: boolean; + @Input('btnClass') btnClass: string; + @Input('loadingText') loadingText = 'Please wait'; + @Input('type') type: 'button' | 'submit' = 'submit'; + @Input('color') color: 'primary' | 'accent' | 'warn'; + + constructor() { } + + ngOnInit() { + } + +} diff --git a/src/app/shared/components/customizer/customizer.component.html b/src/app/shared/components/customizer/customizer.component.html new file mode 100644 index 00000000..0f052c5f --- /dev/null +++ b/src/app/shared/components/customizer/customizer.component.html @@ -0,0 +1,120 @@ + + +
+ +
+
+ + +
+ + + + + +
+
+ +
{{this.layoutConf | json}}
+
+ + help +
+
+ + +

Customize the template then copy configuration json.

+
+
Layouts
+ + Top Navigation + Side Navigation + +
+ + +
+
Header Colors
+
+ Fixed Header +
+ +
+
+ check +
+
+
+ +
+
Sidebar colors
+
+
+ check +
+
+
+ + + +
+
Material Themes
+
+
+ check +
+
+
+ +
+
Breadcrumb
+
+ Use breadcrumb +
+ Breadcrumb types + + Simple + Simple with title + +
+ +
+
Navigation
+ + + {{type.name}} + + +
+ +
+ RTL +
+
+
+
\ No newline at end of file diff --git a/src/app/shared/components/customizer/customizer.component.scss b/src/app/shared/components/customizer/customizer.component.scss new file mode 100644 index 00000000..55c6ea5b --- /dev/null +++ b/src/app/shared/components/customizer/customizer.component.scss @@ -0,0 +1,75 @@ +/*! + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +.handle { + position: fixed; + bottom: 30px; + right: 30px; + z-index: 99; +} +#app-customizer { + position: fixed; + bottom: 0px; + top: 0; + right: 0; + min-width: 180px; + max-width: 280px; + z-index: 999; + .title { + text-transform: uppercase; + font-size: 12px; + font-weight: bold; + margin: 0 0 1rem; + } + .mat-card { + margin: 0; + border-radius: 0; + } + .mat-card-content { + padding: 1rem 1.5rem 2rem; + max-height: calc(100vh - 80px); + } +} +.pos-rel { + position: relative; + z-index: 99; + .olay { + position: absolute; + width: 100%; + height: 100%; + background: rgba(0, 0, 0, .5); + z-index: 100; + } +} + +.colors { + display: flex; + flex-wrap: wrap; + .color { + position: relative; + width: 36px; + height: 36px; + display: inline-block; + border-radius: 50%; + margin: 8px; + text-align: center; + box-shadow: 0 4px 20px 1px rgba(0,0,0,.06), 0 1px 4px rgba(0,0,0,.03); + cursor: pointer; + .active-icon { + position: absolute; + left: 0; + right: 0; + margin: auto; + top: 6px; + } + } +} + +// [dir="rtl"] { +// .handle {} +// #app-customizer { +// right: auto; +// left: 0; +// } +// } \ No newline at end of file diff --git a/src/app/shared/components/customizer/customizer.component.ts b/src/app/shared/components/customizer/customizer.component.ts new file mode 100644 index 00000000..3e9f1674 --- /dev/null +++ b/src/app/shared/components/customizer/customizer.component.ts @@ -0,0 +1,80 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { Component, OnInit, Input, Renderer2 } from "@angular/core"; +import { NavigationService } from "../../../shared/services/navigation.service"; +import { LayoutService } from "../../../shared/services/layout.service"; +import PerfectScrollbar from "perfect-scrollbar"; +import { CustomizerService } from "app/shared/services/customizer.service"; +import { ThemeService, ITheme } from "app/shared/services/theme.service"; + +@Component({ + selector: "app-customizer", + templateUrl: "./customizer.component.html", + styleUrls: ["./customizer.component.scss"] +}) +export class CustomizerComponent implements OnInit { + isCustomizerOpen: boolean = false; + viewMode: 'options' | 'json' = 'options'; + sidenavTypes = [ + { + name: "Default Menu", + value: "default-menu" + }, + { + name: "Separator Menu", + value: "separator-menu" + }, + { + name: "Icon Menu", + value: "icon-menu" + } + ]; + sidebarColors: any[]; + topbarColors: any[]; + + layoutConf; + selectedMenu: string = "icon-menu"; + selectedLayout: string; + isTopbarFixed = false; + isRTL = false; + egretThemes: ITheme[]; + + constructor( + private navService: NavigationService, + private layout: LayoutService, + private themeService: ThemeService, + public customizer: CustomizerService, + private renderer: Renderer2 + ) {} + + ngOnInit() { + this.layoutConf = this.layout.layoutConf; + this.selectedLayout = this.layoutConf.navigationPos; + this.isTopbarFixed = this.layoutConf.topbarFixed; + this.isRTL = this.layoutConf.dir === "rtl"; + this.egretThemes = this.themeService.egretThemes; + } + changeTheme(theme) { + // this.themeService.changeTheme(theme); + this.layout.publishLayoutChange({matTheme: theme.name}) + } + changeLayoutStyle(data) { + this.layout.publishLayoutChange({ navigationPos: this.selectedLayout }); + } + changeSidenav(data) { + this.navService.publishNavigationChange(data.value); + } + toggleBreadcrumb(data) { + this.layout.publishLayoutChange({ useBreadcrumb: data.checked }); + } + toggleTopbarFixed(data) { + this.layout.publishLayoutChange({ topbarFixed: data.checked }); + } + toggleDir(data) { + let dir = data.checked ? "rtl" : "ltr"; + this.layout.publishLayoutChange({ dir: dir }); + } + +} diff --git a/src/app/shared/components/footer-bottom/footer-bottom.component.css b/src/app/shared/components/footer-bottom/footer-bottom.component.css new file mode 100644 index 00000000..707523f7 --- /dev/null +++ b/src/app/shared/components/footer-bottom/footer-bottom.component.css @@ -0,0 +1,4 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + diff --git a/src/app/shared/components/footer-bottom/footer-bottom.component.html b/src/app/shared/components/footer-bottom/footer-bottom.component.html new file mode 100644 index 00000000..4b7973b3 --- /dev/null +++ b/src/app/shared/components/footer-bottom/footer-bottom.component.html @@ -0,0 +1,4 @@ + + diff --git a/src/app/shared/components/footer-bottom/footer-bottom.component.ts b/src/app/shared/components/footer-bottom/footer-bottom.component.ts new file mode 100644 index 00000000..c1fe495e --- /dev/null +++ b/src/app/shared/components/footer-bottom/footer-bottom.component.ts @@ -0,0 +1,131 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { Component, HostListener, OnInit } from '@angular/core'; +import { ActivatedRoute, Router } from '@angular/router'; + +@Component( { + selector: 'app-footer-bottom', + templateUrl: './footer-bottom.component.html', + styleUrls: ['./footer-bottom.component.css'] +} ) +export class FooterBottomComponent implements OnInit { + + showSlider: boolean; + + sliderValue: number = 1; + sliderMin: number = 1; + sliderMax: number = 1; + sliderStep: number = 1; + + constructor ( + private route: ActivatedRoute, + private router: Router, + ) { + } + + ngOnInit () { + + // this.router.events.subscribe( e => { + // if ( e instanceof NavigationEnd ) { + // Debug.log( e ); + // } + // } ); + + + + } + + selectAttachment ( frame: number ): any { + + // this.editorService.attachmentChanged.emit( frame ); + + } + + resetSlider (): void { + + this.showSlider = false; + this.sliderMin = 1; + this.sliderMax = 1; + + } + + setSliderValues ( task: any ): void { + + this.showSlider = task.attachments.length > 1; + this.sliderMin = 1; + this.sliderMax = task.attachments.length; + + } + + ngOnDestroy () { + + } + + onSliderMoved ( e ) { + + // Debug.log( 'moved', this.sliderValue ); + + } + + onSliderValueChanged ( e ) { + + this.selectAttachment( this.sliderValue ); + + } + + onNextClick () { + + if ( this.sliderValue >= this.sliderMax ) return; + + this.sliderValue += 1; + + this.selectAttachment( this.sliderValue ); + + // Debug.log( 'on next', this.sliderValue ); + + } + + onBackClick () { + + if ( this.sliderValue <= this.sliderMin ) return; + + this.sliderValue -= 1; + + this.selectAttachment( this.sliderValue ); + + // Debug.log( 'on back', this.sliderValue ); + } + + onSubmit () { + + // this.editorService.submitBtnClicked.emit(); + + } + + @HostListener( 'document:keydown', ['$event'] ) + onKeyDown ( event: KeyboardEvent ) { + + // Debug.log( 'keydown', event ); + + switch ( event.code ) { + case 'BracketLeft': + this.onBackClick(); + break; + + case 'BracketRight': + this.onNextClick(); + break; + } + + } + + // @HostListener( 'document:keypress', ['$event'] ) + // onKeyPress ( event: KeyboardEvent ) { + + // Debug.log( 'keypress', event ); + + // } + +} diff --git a/src/app/shared/components/header-side/header-side.component.ts b/src/app/shared/components/header-side/header-side.component.ts new file mode 100644 index 00000000..261c3940 --- /dev/null +++ b/src/app/shared/components/header-side/header-side.component.ts @@ -0,0 +1,80 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { Component, OnInit, EventEmitter, Input, Output, Renderer2 } from '@angular/core'; +import { ThemeService } from '../../services/theme.service'; +import { LayoutService } from '../../services/layout.service'; +import { TranslateService } from '@ngx-translate/core'; + +@Component({ + selector: 'app-header-side', + templateUrl: './header-side.template.html' +}) +export class HeaderSideComponent implements OnInit { + @Input() notificPanel; + public availableLangs = [{ + name: 'EN', + code: 'en', + flag: 'flag-icon-us' + }, { + name: 'ES', + code: 'es', + flag: 'flag-icon-es' + }] + currentLang = this.availableLangs[0]; + + public egretThemes; + public layoutConf:any; + constructor( + private themeService: ThemeService, + private layout: LayoutService, + public translate: TranslateService, + private renderer: Renderer2 + ) {} + ngOnInit() { + this.egretThemes = this.themeService.egretThemes; + this.layoutConf = this.layout.layoutConf; + this.translate.use(this.currentLang.code); + } + setLang(lng) { + this.currentLang = lng; + this.translate.use(lng.code); + } + changeTheme(theme) { + // this.themeService.changeTheme(theme); + } + toggleNotific() { + this.notificPanel.toggle(); + } + toggleSidenav() { + if(this.layoutConf.sidebarStyle === 'closed') { + return this.layout.publishLayoutChange({ + sidebarStyle: 'full' + }) + } + this.layout.publishLayoutChange({ + sidebarStyle: 'closed' + }) + } + + toggleCollapse() { + // compact --> full + if(this.layoutConf.sidebarStyle === 'compact') { + return this.layout.publishLayoutChange({ + sidebarStyle: 'full', + sidebarCompactToggle: false + }, {transitionClass: true}) + } + + // * --> compact + this.layout.publishLayoutChange({ + sidebarStyle: 'compact', + sidebarCompactToggle: true + }, {transitionClass: true}) + + } + + onSearch(e) { + } +} \ No newline at end of file diff --git a/src/app/shared/components/header-side/header-side.template.html b/src/app/shared/components/header-side/header-side.template.html new file mode 100644 index 00000000..f63a20a3 --- /dev/null +++ b/src/app/shared/components/header-side/header-side.template.html @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/app/shared/components/header-top/header-top.component.html b/src/app/shared/components/header-top/header-top.component.html new file mode 100644 index 00000000..e7d6b519 --- /dev/null +++ b/src/app/shared/components/header-top/header-top.component.html @@ -0,0 +1,137 @@ + + +
+
+
+ +
+ +
+ + + + + + + + {{ lang.name }} + + + + + + +
+
+ check +
+
+
+
+ + + + + + + + + + + + + +
+
+
\ No newline at end of file diff --git a/src/app/shared/components/header-top/header-top.component.ts b/src/app/shared/components/header-top/header-top.component.ts new file mode 100644 index 00000000..b7b24cb5 --- /dev/null +++ b/src/app/shared/components/header-top/header-top.component.ts @@ -0,0 +1,82 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { Component, OnInit, Input, OnDestroy, Renderer2 } from '@angular/core'; +import { NavigationService } from "../../../shared/services/navigation.service"; +import { Subscription } from 'rxjs'; +import { ThemeService } from '../../../shared/services/theme.service'; +import { TranslateService } from '@ngx-translate/core'; +import { LayoutService } from '../../services/layout.service'; + +@Component({ + selector: 'app-header-top', + templateUrl: './header-top.component.html' +}) +export class HeaderTopComponent implements OnInit, OnDestroy { + layoutConf: any; + menuItems:any; + menuItemSub: Subscription; + egretThemes: any[] = []; + currentLang = 'en'; + availableLangs = [{ + name: 'English', + code: 'en', + }, { + name: 'Spanish', + code: 'es', + }] + @Input() notificPanel; + constructor( + private layout: LayoutService, + private navService: NavigationService, + public themeService: ThemeService, + public translate: TranslateService, + private renderer: Renderer2 + ) { } + + ngOnInit() { + this.layoutConf = this.layout.layoutConf; + this.egretThemes = this.themeService.egretThemes; + this.menuItemSub = this.navService.menuItems$ + .subscribe(res => { + res = res.filter(item => item.type !== 'icon' && item.type !== 'separator'); + let limit = 4 + let mainItems:any[] = res.slice(0, limit) + if(res.length <= limit) { + return this.menuItems = mainItems + } + let subItems:any[] = res.slice(limit, res.length - 1) + mainItems.push({ + name: 'More', + type: 'dropDown', + tooltip: 'More', + icon: 'more_horiz', + sub: subItems + }) + this.menuItems = mainItems + }) + } + ngOnDestroy() { + this.menuItemSub.unsubscribe() + } + setLang() { + this.translate.use(this.currentLang) + } + changeTheme(theme) { + this.layout.publishLayoutChange({matTheme: theme.name}) + } + toggleNotific() { + this.notificPanel.toggle(); + } + toggleSidenav() { + if(this.layoutConf.sidebarStyle === 'closed') { + return this.layout.publishLayoutChange({ + sidebarStyle: 'full' + }) + } + this.layout.publishLayoutChange({ + sidebarStyle: 'closed' + }) + } +} diff --git a/src/app/shared/components/layouts/admin-layout/admin-layout.component.ts b/src/app/shared/components/layouts/admin-layout/admin-layout.component.ts new file mode 100644 index 00000000..df39d9cc --- /dev/null +++ b/src/app/shared/components/layouts/admin-layout/admin-layout.component.ts @@ -0,0 +1,115 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { Component, OnInit, AfterViewInit, ViewChild, HostListener, ChangeDetectionStrategy } from '@angular/core'; +import { + Router, + NavigationEnd, + RouteConfigLoadStart, + RouteConfigLoadEnd, + ResolveStart, + ResolveEnd +} from '@angular/router'; +import { Subscription } from "rxjs"; +import { MediaObserver } from "@angular/flex-layout"; +import { TranslateService } from '@ngx-translate/core'; +import { ThemeService } from '../../../services/theme.service'; +import { LayoutService } from '../../../services/layout.service'; +import { filter } from 'rxjs/operators'; + +@Component({ + selector: 'app-admin-layout', + templateUrl: './admin-layout.template.html', + changeDetection: ChangeDetectionStrategy.Default +}) +export class AdminLayoutComponent implements OnInit, AfterViewInit { + public isModuleLoading: Boolean = false; + private moduleLoaderSub: Subscription; + private layoutConfSub: Subscription; + private routerEventSub: Subscription; + + public scrollConfig = {} + public layoutConf: any = {}; + + constructor( + private router: Router, + public translate: TranslateService, + public themeService: ThemeService, + private layout: LayoutService + ) { + // Close sidenav after route change in mobile + this.routerEventSub = router.events.pipe(filter(event => event instanceof NavigationEnd)) + .subscribe((routeChange: NavigationEnd) => { + this.layout.adjustLayout({ route: routeChange.url }); + }); + + // Translator init + const browserLang: string = translate.getBrowserLang(); + translate.use(browserLang.match(/en|fr/) ? browserLang : 'en'); + } + ngOnInit() { + // this.layoutConf = this.layout.layoutConf; + this.layoutConfSub = this.layout.layoutConf$.subscribe((layoutConf) => { + this.layoutConf = layoutConf; + }) + // FOR MODULE LOADER FLAG + this.moduleLoaderSub = this.router.events.subscribe(event => { + if(event instanceof RouteConfigLoadStart || event instanceof ResolveStart) { + this.isModuleLoading = true; + } + if(event instanceof RouteConfigLoadEnd || event instanceof ResolveEnd) { + this.isModuleLoading = false; + } + }); + } + @HostListener('window:resize', ['$event']) + onResize(event) { + this.layout.adjustLayout(event); + } + + ngAfterViewInit() { + + } + + + scrollToTop(selector: string) { + if(document) { + let element = document.querySelector(selector); + element.scrollTop = 0; + } + } + ngOnDestroy() { + if(this.moduleLoaderSub) { + this.moduleLoaderSub.unsubscribe(); + } + if(this.layoutConfSub) { + this.layoutConfSub.unsubscribe(); + } + if(this.routerEventSub) { + this.routerEventSub.unsubscribe(); + } + } + closeSidebar() { + this.layout.publishLayoutChange({ + sidebarStyle: 'closed' + }) + } + + sidebarMouseenter(e) { + if(this.layoutConf.sidebarStyle === 'compact') { + this.layout.publishLayoutChange({sidebarStyle: 'full'}, {transitionClass: true}); + } + } + + sidebarMouseleave(e) { + // Debug.log(this.layoutConf); + if ( + this.layoutConf.sidebarStyle === 'full' && + this.layoutConf.sidebarCompactToggle + ) { + this.layout.publishLayoutChange({sidebarStyle: 'compact'}, {transitionClass: true}); + } + } + +} \ No newline at end of file diff --git a/src/app/shared/components/layouts/admin-layout/admin-layout.template.html b/src/app/shared/components/layouts/admin-layout/admin-layout.template.html new file mode 100644 index 00000000..7b88df48 --- /dev/null +++ b/src/app/shared/components/layouts/admin-layout/admin-layout.template.html @@ -0,0 +1,83 @@ + + +
+ + + + + + + + + + + + + + + + + + + + +
+ + + + +
+ +
+
+
+
+
+
+ + + + +
+
+ + + + + +
+ +
+
+
+
+ + + + + \ No newline at end of file diff --git a/src/app/shared/components/layouts/auth-layout/auth-layout.component.html b/src/app/shared/components/layouts/auth-layout/auth-layout.component.html new file mode 100644 index 00000000..3c1333e4 --- /dev/null +++ b/src/app/shared/components/layouts/auth-layout/auth-layout.component.html @@ -0,0 +1,5 @@ + + + \ No newline at end of file diff --git a/src/app/shared/components/layouts/auth-layout/auth-layout.component.ts b/src/app/shared/components/layouts/auth-layout/auth-layout.component.ts new file mode 100644 index 00000000..8b804c7d --- /dev/null +++ b/src/app/shared/components/layouts/auth-layout/auth-layout.component.ts @@ -0,0 +1,18 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { Component, OnInit } from '@angular/core'; + +@Component({ + selector: 'app-auth-layout', + templateUrl: './auth-layout.component.html' +}) +export class AuthLayoutComponent implements OnInit { + + constructor() { } + + ngOnInit() { + } + +} diff --git a/src/app/shared/components/notifications/notifications.component.html b/src/app/shared/components/notifications/notifications.component.html new file mode 100644 index 00000000..2c58dba1 --- /dev/null +++ b/src/app/shared/components/notifications/notifications.component.html @@ -0,0 +1,22 @@ + + +
+
Notifications
+
+ + + + {{n.icon}} + +
+

{{n.message}}

+ {{n.time}} +
+
+
+
+ \ No newline at end of file diff --git a/src/app/shared/components/notifications/notifications.component.ts b/src/app/shared/components/notifications/notifications.component.ts new file mode 100644 index 00000000..7dd8dc85 --- /dev/null +++ b/src/app/shared/components/notifications/notifications.component.ts @@ -0,0 +1,50 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { Component, OnInit, ViewChild, Input } from '@angular/core'; +import { MatSidenav } from '@angular/material'; +import { Router, NavigationEnd } from '@angular/router'; + +@Component({ + selector: 'app-notifications', + templateUrl: './notifications.component.html' +}) +export class NotificationsComponent implements OnInit { + @Input() notificPanel; + + // Dummy notifications + notifications = [{ + message: 'New contact added', + icon: 'assignment_ind', + time: '1 min ago', + route: '/inbox', + color: 'primary' + }, { + message: 'New message', + icon: 'chat', + time: '4 min ago', + route: '/chat', + color: 'accent' + }, { + message: 'Server rebooted', + icon: 'settings_backup_restore', + time: '12 min ago', + route: '/charts', + color: 'warn' + }] + + constructor(private router: Router) {} + + ngOnInit() { + this.router.events.subscribe((routeChange) => { + if (routeChange instanceof NavigationEnd) { + this.notificPanel.close(); + } + }); + } + clearAll(e) { + e.preventDefault(); + this.notifications = []; + } +} diff --git a/src/app/shared/components/object-inspector/object-inspector.component.css b/src/app/shared/components/object-inspector/object-inspector.component.css new file mode 100644 index 00000000..707523f7 --- /dev/null +++ b/src/app/shared/components/object-inspector/object-inspector.component.css @@ -0,0 +1,4 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + diff --git a/src/app/shared/components/object-inspector/object-inspector.component.html b/src/app/shared/components/object-inspector/object-inspector.component.html new file mode 100644 index 00000000..347b8bf6 --- /dev/null +++ b/src/app/shared/components/object-inspector/object-inspector.component.html @@ -0,0 +1,9 @@ + + + + + + + \ No newline at end of file diff --git a/src/app/shared/components/object-inspector/object-inspector.component.ts b/src/app/shared/components/object-inspector/object-inspector.component.ts new file mode 100644 index 00000000..9d800b7f --- /dev/null +++ b/src/app/shared/components/object-inspector/object-inspector.component.ts @@ -0,0 +1,61 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { Component, ComponentFactoryResolver, OnInit, ViewChild } from '@angular/core'; +import { ComponentContainerDirective } from '../../directives/component-container.directive'; +import { ComponentItem, IComponent } from '../../../core/game-object'; +import { AppInspector } from '../../../core/inspector'; + +@Component( { + selector: 'app-object-inspector', + templateUrl: './object-inspector.component.html', + styleUrls: [ './object-inspector.component.css' ] +} ) +export class ObjectInspectorComponent implements OnInit { + + @ViewChild( ComponentContainerDirective ) componentContainer: ComponentContainerDirective; + + constructor ( + private componentFactoryResolver: ComponentFactoryResolver, + ) { + + AppInspector.inspectorChanged.subscribe( ( e: ComponentItem ) => { + this.loadInspector( e ); + } ); + + AppInspector.inspectorCleared.subscribe( () => { + this.clearInspector(); + } ) + } + + loadInspector ( component: ComponentItem ) { + + const componentFactory = this.componentFactoryResolver.resolveComponentFactory( component.component ); + + const viewContainerRef = this.componentContainer.viewContainerRef; + + viewContainerRef.clear(); + + const componentRef = viewContainerRef.createComponent( componentFactory ); + + const componentInstance = componentRef.instance as IComponent; + + componentInstance.data = component.data; + + AppInspector.inspectorCreated.emit( componentInstance ); + } + + clearInspector () { + + this.componentContainer.viewContainerRef.clear(); + + } + + + ngOnInit () { + + + } + +} diff --git a/src/app/shared/components/prop-browser/prop-browser.component.html b/src/app/shared/components/prop-browser/prop-browser.component.html new file mode 100644 index 00000000..442c0ac3 --- /dev/null +++ b/src/app/shared/components/prop-browser/prop-browser.component.html @@ -0,0 +1,27 @@ + + +
+ + + + + + + + + + + + + {{ model }} + + + + + + +
\ No newline at end of file diff --git a/src/app/shared/components/prop-browser/prop-browser.component.ts b/src/app/shared/components/prop-browser/prop-browser.component.ts new file mode 100644 index 00000000..06b983db --- /dev/null +++ b/src/app/shared/components/prop-browser/prop-browser.component.ts @@ -0,0 +1,61 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { Component, OnInit } from '@angular/core'; +import { SearchPipe } from '../../../core/pipes/search.pipe'; +import { TvSignService } from 'app/modules/tv-map/services/tv-sign.service'; + +@Component( { + selector: 'app-prop-browser', + templateUrl: './prop-browser.component.html', + providers: [ SearchPipe ] +} ) +/** + * @deprecated dont need this prop browser as assets are placed in the project browser + */ +export class PropBrowserComponent implements OnInit { + + query: string; + + selectedName: string; + + constructor ( private searchPipe: SearchPipe, private signService: TvSignService ) { + } + + get signs (): string[] { + return this.signService.signs.map( value => { + return value.name; + } ); + } + + ngOnInit () { + } + + onMouseDown ( name: string ): void { + + const sign = this.signService.signs.filter( value => { + return value.name == name; + } )[ 0 ]; + + // + // var texture = new TextureLoader().load( 'assets/signs/' + model + '.png' ); + // var material = new MeshBasicMaterial( { map: texture, transparent: true, opacity: 0.9 } ); + // var geometry = new BoxGeometry( 1, 1, 1 ); + // + // var sign = new Mesh( geometry, material ); + // + // SceneService.add( sign ); + + this.selectedName = name; + + this.signService.currentSign = { + name: sign.name, + shape: sign.shape + }; + } + + onModelChanged ( $event: any ) { + + } +} diff --git a/src/app/shared/components/sidebar-side/sidebar-side.component.html b/src/app/shared/components/sidebar-side/sidebar-side.component.html new file mode 100644 index 00000000..42473982 --- /dev/null +++ b/src/app/shared/components/sidebar-side/sidebar-side.component.html @@ -0,0 +1,88 @@ + + + \ No newline at end of file diff --git a/src/app/shared/components/sidebar-side/sidebar-side.component.ts b/src/app/shared/components/sidebar-side/sidebar-side.component.ts new file mode 100644 index 00000000..27798cdb --- /dev/null +++ b/src/app/shared/components/sidebar-side/sidebar-side.component.ts @@ -0,0 +1,59 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { Component, OnInit, OnDestroy, AfterViewInit } from "@angular/core"; +import { NavigationService } from "../../../shared/services/navigation.service"; +import { ThemeService } from "../../services/theme.service"; +import { Subscription } from "rxjs"; +import { ILayoutConf, LayoutService } from "app/shared/services/layout.service"; + +@Component({ + selector: "app-sidebar-side", + templateUrl: "./sidebar-side.component.html" +}) +export class SidebarSideComponent implements OnInit, OnDestroy, AfterViewInit { + public menuItems: any[]; + public hasIconTypeMenuItem: boolean; + public iconTypeMenuTitle: string; + private menuItemsSub: Subscription; + public layoutConf: ILayoutConf; + + constructor( + private navService: NavigationService, + public themeService: ThemeService, + private layout: LayoutService + ) {} + + ngOnInit() { + this.iconTypeMenuTitle = this.navService.iconTypeMenuTitle; + this.menuItemsSub = this.navService.menuItems$.subscribe(menuItem => { + this.menuItems = menuItem; + //Checks item list has any icon type. + this.hasIconTypeMenuItem = !!this.menuItems.filter( + item => item.type === "icon" + ).length; + }); + this.layoutConf = this.layout.layoutConf; + } + ngAfterViewInit() {} + ngOnDestroy() { + if (this.menuItemsSub) { + this.menuItemsSub.unsubscribe(); + } + } + toggleCollapse() { + if ( + this.layoutConf.sidebarCompactToggle + ) { + this.layout.publishLayoutChange({ + sidebarCompactToggle: false + }); + } else { + this.layout.publishLayoutChange({ + // sidebarStyle: "compact", + sidebarCompactToggle: true + }); + } + } +} diff --git a/src/app/shared/components/sidebar-top/sidebar-top.component.html b/src/app/shared/components/sidebar-top/sidebar-top.component.html new file mode 100644 index 00000000..6fd0cda0 --- /dev/null +++ b/src/app/shared/components/sidebar-top/sidebar-top.component.html @@ -0,0 +1,9 @@ + + + \ No newline at end of file diff --git a/src/app/shared/components/sidebar-top/sidebar-top.component.ts b/src/app/shared/components/sidebar-top/sidebar-top.component.ts new file mode 100644 index 00000000..afc3c018 --- /dev/null +++ b/src/app/shared/components/sidebar-top/sidebar-top.component.ts @@ -0,0 +1,43 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { Component, OnInit, OnDestroy, AfterViewInit } from '@angular/core'; +// import PerfectScrollbar from 'perfect-scrollbar'; +import { NavigationService } from "../../../shared/services/navigation.service"; +import { Subscription } from "rxjs"; + +@Component({ + selector: 'app-sidebar-top', + templateUrl: './sidebar-top.component.html' +}) +export class SidebarTopComponent implements OnInit, OnDestroy, AfterViewInit { + // private sidebarPS: PerfectScrollbar; + public menuItems: any[]; + private menuItemsSub: Subscription; + constructor( + private navService: NavigationService + ) { } + + ngOnInit() { + this.menuItemsSub = this.navService.menuItems$.subscribe(menuItem => { + this.menuItems = menuItem.filter(item => item.type !== 'icon' && item.type !== 'separator'); + }); + } + ngAfterViewInit() { + // setTimeout(() => { + // this.sidebarPS = new PerfectScrollbar('#sidebar-top-scroll-area', { + // suppressScrollX: true + // }) + // }) + } + ngOnDestroy() { + // if(this.sidebarPS) { + // this.sidebarPS.destroy(); + // } + if( this.menuItemsSub ) { + this.menuItemsSub.unsubscribe() + } + } + +} diff --git a/src/app/shared/components/sidenav/sidenav.component.ts b/src/app/shared/components/sidenav/sidenav.component.ts new file mode 100644 index 00000000..7906a2fd --- /dev/null +++ b/src/app/shared/components/sidenav/sidenav.component.ts @@ -0,0 +1,33 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { Component, OnInit, Input } from '@angular/core'; + +@Component({ + selector: 'app-sidenav', + templateUrl: './sidenav.template.html' +}) +export class SidenavComponent { + @Input('items') public menuItems: any[] = []; + @Input('hasIconMenu') public hasIconTypeMenuItem: boolean; + @Input('iconMenuTitle') public iconTypeMenuTitle: string; + + constructor() {} + ngOnInit() {} + + // Only for demo purpose + addMenuItem() { + this.menuItems.push({ + name: 'ITEM', + type: 'dropDown', + tooltip: 'Item', + icon: 'done', + state: 'material', + sub: [ + {name: 'SUBITEM', state: 'cards'}, + {name: 'SUBITEM', state: 'buttons'} + ] + }); + } +} \ No newline at end of file diff --git a/src/app/shared/components/sidenav/sidenav.template.html b/src/app/shared/components/sidenav/sidenav.template.html new file mode 100755 index 00000000..c45eec5b --- /dev/null +++ b/src/app/shared/components/sidenav/sidenav.template.html @@ -0,0 +1,80 @@ + + + diff --git a/src/app/shared/components/splash/splash.component.css b/src/app/shared/components/splash/splash.component.css new file mode 100644 index 00000000..e69de29b diff --git a/src/app/shared/components/splash/splash.component.html b/src/app/shared/components/splash/splash.component.html new file mode 100644 index 00000000..45d707a8 --- /dev/null +++ b/src/app/shared/components/splash/splash.component.html @@ -0,0 +1,3 @@ +
+ +
diff --git a/src/app/shared/components/splash/splash.component.spec.ts b/src/app/shared/components/splash/splash.component.spec.ts new file mode 100644 index 00000000..c36545ee --- /dev/null +++ b/src/app/shared/components/splash/splash.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { SplashComponent } from './splash.component'; + +describe( 'SplashComponent', () => { + let component: SplashComponent; + let fixture: ComponentFixture; + + beforeEach( async( () => { + TestBed.configureTestingModule( { + declarations: [ SplashComponent ] + } ) + .compileComponents(); + } ) ); + + beforeEach( () => { + fixture = TestBed.createComponent( SplashComponent ); + component = fixture.componentInstance; + fixture.detectChanges(); + } ); + + it( 'should create', () => { + expect( component ).toBeTruthy(); + } ); +} ); diff --git a/src/app/shared/components/splash/splash.component.ts b/src/app/shared/components/splash/splash.component.ts new file mode 100644 index 00000000..fd23aa45 --- /dev/null +++ b/src/app/shared/components/splash/splash.component.ts @@ -0,0 +1,16 @@ +import { Component, OnInit } from '@angular/core'; + +@Component( { + selector: 'app-splash', + templateUrl: './splash.component.html', + styleUrls: [ './splash.component.css' ] +} ) +export class SplashComponent implements OnInit { + + constructor () { + } + + ngOnInit () { + } + +} diff --git a/src/app/shared/dialogs/import-file-dialog/import-file-dialog.component.html b/src/app/shared/dialogs/import-file-dialog/import-file-dialog.component.html new file mode 100644 index 00000000..b6045fd7 --- /dev/null +++ b/src/app/shared/dialogs/import-file-dialog/import-file-dialog.component.html @@ -0,0 +1,46 @@ + + +

Import File From

+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + +
Name {{element.name}} Updated At {{element.updated_at}} Created At {{element.created_at}}
+ + +
+ + + +
+
+ +
+ + +
\ No newline at end of file diff --git a/src/app/shared/dialogs/import-file-dialog/import-file-dialog.component.ts b/src/app/shared/dialogs/import-file-dialog/import-file-dialog.component.ts new file mode 100644 index 00000000..643e586b --- /dev/null +++ b/src/app/shared/dialogs/import-file-dialog/import-file-dialog.component.ts @@ -0,0 +1,102 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { Component, Inject, OnInit } from '@angular/core'; +import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material'; +import { IFile } from '../../../core/models/file'; +import { FileApiService } from 'app/core/services/file-api.service'; + +@Component( { + selector: 'app-import-file-dialog', + templateUrl: './import-file-dialog.component.html', +} ) +export class ImportFileDialogComponent implements OnInit { + + public selectedFile: IFile; + + public files: IFile[] = []; + + public columns: string[] = [ 'name', 'updated_at', 'created_at' ]; + + // @ViewChild( 'fileInput' ) fileInput: ElementRef; + + constructor ( + public dialogRef: MatDialogRef, + @Inject( MAT_DIALOG_DATA ) public data: any, + private fileService: FileApiService + ) { + + } + + ngOnInit (): void { + + this.fileService.getFileList( 'tv-map' ).subscribe( res => { + + if ( Array.isArray( res ) ) { + + this.files = res; + + } + + } ); + + } + + selectFile ( file: IFile ) { + + this.selectedFile = file; + + } + + public fileChange ( event ) { + + const self = this; + + const reader = new FileReader(); + + if ( event.target.files && event.target.files.length > 0 ) { + + const file = event.target.files[ 0 ]; + + reader.readAsText( file ); + + reader.onload = ( data ) => { + + self.selectedFile = new IFile( 'Untitled.xml' ); + self.selectedFile.contents = reader.result as string; + self.selectedFile.online = false; + + }; + } + + } + + public onImport () { + + if ( this.selectedFile != null ) { + + this.fileService.getFile( this.selectedFile.name, this.selectedFile.type ).subscribe( file => { + + this.dialogRef.close( file ); + + } ); + + } + + } + + public onCancel () { + + this.dialogRef.close(); + + } + + // public openFileDialog (): void { + // + // const event = new MouseEvent( 'click', { bubbles: false } ); + // + // this.fileInput.nativeElement.dispatchEvent( event ); + // + // } +} diff --git a/src/app/shared/directives/component-container.directive.ts b/src/app/shared/directives/component-container.directive.ts new file mode 100644 index 00000000..163e1577 --- /dev/null +++ b/src/app/shared/directives/component-container.directive.ts @@ -0,0 +1,14 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { Directive, ViewContainerRef } from '@angular/core'; + +@Directive( { + selector: '[appComponentContainer]' +} ) +export class ComponentContainerDirective { + + constructor ( public viewContainerRef: ViewContainerRef ) { } + +} diff --git a/src/app/shared/directives/dropdown-anchor.directive.ts b/src/app/shared/directives/dropdown-anchor.directive.ts new file mode 100644 index 00000000..f954de53 --- /dev/null +++ b/src/app/shared/directives/dropdown-anchor.directive.ts @@ -0,0 +1,23 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { Directive, HostListener, Inject } from '@angular/core'; +import { DropdownLinkDirective } from './dropdown-link.directive'; + +@Directive({ + selector: '[appDropdownToggle]' +}) +export class DropdownAnchorDirective { + + protected navlink: DropdownLinkDirective; + + constructor( @Inject(DropdownLinkDirective) navlink: DropdownLinkDirective) { + this.navlink = navlink; + } + + @HostListener('click', ['$event']) + onClick(e: any) { + this.navlink.toggle(); + } +} diff --git a/src/app/shared/directives/dropdown-link.directive.ts b/src/app/shared/directives/dropdown-link.directive.ts new file mode 100644 index 00000000..e7418ac2 --- /dev/null +++ b/src/app/shared/directives/dropdown-link.directive.ts @@ -0,0 +1,50 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { + Directive, HostBinding, Inject, Input, OnInit, OnDestroy +} from '@angular/core'; + +import { AppDropdownDirective } from './dropdown.directive'; + +@Directive({ + selector: '[appDropdownLink]' +}) +export class DropdownLinkDirective { + + @Input() public group: any; + + @HostBinding('class.open') + @Input() + get open(): boolean { + return this._open; + } + + set open(value: boolean) { + this._open = value; + if (value) { + this.nav.closeOtherLinks(this); + } + } + + protected _open: boolean; + protected nav: AppDropdownDirective; + + public constructor(@Inject(AppDropdownDirective) nav: AppDropdownDirective) { + this.nav = nav; + } + + public ngOnInit(): any { + this.nav.addLink(this); + } + + public ngOnDestroy(): any { + this.nav.removeGroup(this); + } + + public toggle(): any { + this.open = !this.open; + } + +} diff --git a/src/app/shared/directives/dropdown.directive.ts b/src/app/shared/directives/dropdown.directive.ts new file mode 100644 index 00000000..c440454d --- /dev/null +++ b/src/app/shared/directives/dropdown.directive.ts @@ -0,0 +1,59 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { Directive } from '@angular/core'; +import { Router, NavigationEnd } from '@angular/router'; +import { DropdownLinkDirective } from './dropdown-link.directive'; +import { Subscription } from 'rxjs'; +import { filter } from 'rxjs/operators'; + +@Directive({ + selector: '[appDropdown]' +}) +export class AppDropdownDirective { + protected navlinks: Array = []; + + private _router: Subscription; + + public closeOtherLinks(openLink: DropdownLinkDirective): void { + this.navlinks.forEach((link: DropdownLinkDirective) => { + if (link !== openLink) { + link.open = false; + } + }); + } + + public addLink(link: DropdownLinkDirective): void { + this.navlinks.push(link); + } + + public removeGroup(link: DropdownLinkDirective): void { + const index = this.navlinks.indexOf(link); + if (index !== -1) { + this.navlinks.splice(index, 1); + } + } + + public getUrl() { + return this.router.url; + } + + public ngOnInit(): any { + this._router = this.router.events.pipe(filter(event => event instanceof NavigationEnd)).subscribe((event: NavigationEnd) => { + this.navlinks.forEach((link: DropdownLinkDirective) => { + if (link.group) { + const routeUrl = this.getUrl(); + const currentUrl = routeUrl.split('/'); + if (currentUrl.indexOf( link.group ) > 0) { + link.open = true; + this.closeOtherLinks(link); + } + } + }); + }); + } + + constructor( private router: Router) {} + +} diff --git a/src/app/shared/directives/egret-side-nav-toggle.directive.ts b/src/app/shared/directives/egret-side-nav-toggle.directive.ts new file mode 100644 index 00000000..d9b84f3f --- /dev/null +++ b/src/app/shared/directives/egret-side-nav-toggle.directive.ts @@ -0,0 +1,49 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { Directive, Host, Self, Optional, OnDestroy, OnInit } from '@angular/core'; +import { MediaChange, MediaObserver } from "@angular/flex-layout"; +import { Subscription } from "rxjs"; +import { MatSidenav } from '@angular/material'; + + +@Directive({ + selector: '[EgretSideNavToggle]' +}) +export class EgretSideNavToggleDirective implements OnInit, OnDestroy { + isMobile; + screenSizeWatcher: Subscription; + constructor( + private media: MediaObserver, + @Host() @Self() @Optional() public sideNav: MatSidenav + ) { + } + + ngOnInit() { + this.initSideNav(); + } + + ngOnDestroy() { + if(this.screenSizeWatcher) { + this.screenSizeWatcher.unsubscribe() + } + } + + updateSidenav() { + var self = this; + setTimeout(() => { + self.sideNav.opened = !self.isMobile; + self.sideNav.mode = self.isMobile ? 'over' : 'side'; + }) + } + initSideNav() { + this.isMobile = this.media.isActive('xs') || this.media.isActive('sm'); + this.updateSidenav(); + this.screenSizeWatcher = this.media.media$.subscribe((change: MediaChange) => { + this.isMobile = (change.mqAlias == 'xs') || (change.mqAlias == 'sm'); + this.updateSidenav(); + }); + } + +} diff --git a/src/app/shared/directives/font-size.directive.ts b/src/app/shared/directives/font-size.directive.ts new file mode 100644 index 00000000..a792b469 --- /dev/null +++ b/src/app/shared/directives/font-size.directive.ts @@ -0,0 +1,13 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { Directive, ElementRef, Attribute, OnInit } from '@angular/core'; + +@Directive({ selector: '[fontSize]' }) +export class FontSizeDirective implements OnInit { + constructor( @Attribute('fontSize') public fontSize: string, private el: ElementRef) { } + ngOnInit() { + this.el.nativeElement.fontSize = this.fontSize; + } +} diff --git a/src/app/shared/directives/scroll-to.directive.ts b/src/app/shared/directives/scroll-to.directive.ts new file mode 100644 index 00000000..db462243 --- /dev/null +++ b/src/app/shared/directives/scroll-to.directive.ts @@ -0,0 +1,68 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { Directive, ElementRef, Attribute, OnInit, HostListener } from '@angular/core'; + +@Directive({ selector: '[scrollTo]' }) +export class ScrollToDirective implements OnInit { + constructor( @Attribute('scrollTo') public elmID: string, private el: ElementRef) { } + + ngOnInit() {} + + currentYPosition() { + // Firefox, Chrome, Opera, Safari + if (self.pageYOffset) return self.pageYOffset; + // Internet Explorer 6 - standards mode + if (document.documentElement && document.documentElement.scrollTop) + return document.documentElement.scrollTop; + // Internet Explorer 6, 7 and 8 + if (document.body.scrollTop) return document.body.scrollTop; + return 0; + }; + + elmYPosition(eID) { + var elm = document.getElementById(eID); + var y = elm.offsetTop; + var node: any = elm; + while (node.offsetParent && node.offsetParent != document.body) { + node = node.offsetParent; + y += node.offsetTop; + } + return y; + }; + + @HostListener('click', ['$event']) + smoothScroll() { + if(!this.elmID) + return; + var startY = this.currentYPosition(); + var stopY = this.elmYPosition(this.elmID); + var distance = stopY > startY ? stopY - startY : startY - stopY; + if (distance < 100) { + scrollTo(0, stopY); + return; + } + var speed = Math.round(distance / 50); + if (speed >= 20) speed = 20; + var step = Math.round(distance / 25); + var leapY = stopY > startY ? startY + step : startY - step; + var timer = 0; + if (stopY > startY) { + for (var i = startY; i < stopY; i += step) { + setTimeout("window.scrollTo(0, " + leapY + ")", timer * speed); + leapY += step; + if (leapY > stopY) leapY = stopY; + timer++; + } + return; + } + for (var i = startY; i > stopY; i -= step) { + setTimeout("window.scrollTo(0, " + leapY + ")", timer * speed); + leapY -= step; + if (leapY < stopY) leapY = stopY; + timer++; + } + return false; + }; +} \ No newline at end of file diff --git a/src/app/shared/fields/color-field/color-field.component.css b/src/app/shared/fields/color-field/color-field.component.css new file mode 100644 index 00000000..18ca2ff6 --- /dev/null +++ b/src/app/shared/fields/color-field/color-field.component.css @@ -0,0 +1,28 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +.tv-field-color-input { + position: relative; + display: inline-block; + box-sizing: border-box; + width: 44px; + height: 20px; + margin: 6px 0px 6px 6px; + vertical-align: top; + cursor: pointer; + transition: opacity 100ms; + background-color: #fff; + background-image: linear-gradient( 45deg, #000 25%, transparent 25%, transparent 75%, #000 75%, #000 100%), linear-gradient( 45deg, #000 25%, transparent 25%, transparent 75%, #000 75%, #000 100%); + background-size: 17.6px 17.6px; + background-position: 0 0, 8.8px 8.8px; +} + +.tv-field-color-input>div { + background: rgb(0, 0, 0); + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; +} \ No newline at end of file diff --git a/src/app/shared/fields/color-field/color-field.component.html b/src/app/shared/fields/color-field/color-field.component.html new file mode 100644 index 00000000..f97dcb8a --- /dev/null +++ b/src/app/shared/fields/color-field/color-field.component.html @@ -0,0 +1,20 @@ + + +
+ +
+

+
+
+ +
+ + + + + +
+ +
\ No newline at end of file diff --git a/src/app/shared/fields/color-field/color-field.component.ts b/src/app/shared/fields/color-field/color-field.component.ts new file mode 100644 index 00000000..2def39b7 --- /dev/null +++ b/src/app/shared/fields/color-field/color-field.component.ts @@ -0,0 +1,86 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { Component, OnInit, Input, HostListener, ElementRef, ViewChild } from '@angular/core'; +import { AbstractFieldComponent } from 'app/core/components/abstract-field.component'; +import { Color } from 'three'; + +@Component( { + selector: 'app-color-field', + templateUrl: './color-field.component.html', + styleUrls: [ './color-field.component.css' ] +} ) +export class ColorFieldComponent extends AbstractFieldComponent implements OnInit { + + /** + * This is the toogle button elemenbt, look at HTML and see its defination + */ + @ViewChild( 'toggleButton' ) toggleButton: ElementRef; + @ViewChild( 'colorPicker' ) colorPicker: ElementRef; + + @Input() isPickerOpen = false; + + @Input() value: Color; + + @Input() position = 'top'; + + get hex () { return '#' + this.value.getHexString(); } + + set hex ( value: string ) { + + this.value.setStyle( value ); + + this.valueChanged.emit( this.value ); + + this.changed.emit( this.value ); + + } + + ngOnInit (): void { + + this.value = new Color( this.value ).copy( this.value ); + + } + + onChange ( $event ) { + + // do nothing + + } + + onChangeCompleted ( $event ) { + + this.value.setStyle( $event.color.hex ); + + this.valueChanged.emit( this.value ); + + this.changed.emit( this.value ); + + } + + togglePicker () { + + if ( !this.isPickerOpen ) { + + this.isPickerOpen = true; + + } else { + + this.isPickerOpen = false; + + } + + } + + @HostListener( 'window:mousedown', [ '$event' ] ) + onGlobalClick ( e ): void { + + if ( !this.colorPicker.nativeElement.contains( event.target ) ) { + + // clicked outside + this.isPickerOpen = false; + } + + } +} diff --git a/src/app/shared/fields/double-field/double-field.component.css b/src/app/shared/fields/double-field/double-field.component.css new file mode 100644 index 00000000..707523f7 --- /dev/null +++ b/src/app/shared/fields/double-field/double-field.component.css @@ -0,0 +1,4 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + diff --git a/src/app/shared/fields/double-field/double-field.component.html b/src/app/shared/fields/double-field/double-field.component.html new file mode 100644 index 00000000..9d89c03d --- /dev/null +++ b/src/app/shared/fields/double-field/double-field.component.html @@ -0,0 +1,23 @@ + + + + + + + + + + +
+ + + +
+ + + +
+ +
\ No newline at end of file diff --git a/src/app/shared/fields/double-field/double-field.component.ts b/src/app/shared/fields/double-field/double-field.component.ts new file mode 100644 index 00000000..b6e98382 --- /dev/null +++ b/src/app/shared/fields/double-field/double-field.component.ts @@ -0,0 +1,113 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { Component, Input, OnInit } from '@angular/core'; +import { AbstractFieldComponent } from '../../../core/components/abstract-field.component'; +import { Maths } from 'app/utils/maths'; + +@Component( { + selector: 'app-double-field', + templateUrl: './double-field.component.html', + styleUrls: [ './double-field.component.css' ] +} ) +export class DoubleFieldComponent extends AbstractFieldComponent implements OnInit { + + @Input() value: any; + + @Input() label: string; + + @Input() min: any = -Infinity; + + @Input() max: any = Infinity; + + @Input() step: number = 0.1; + + inFocus = false; + + sendTimeout: any; + + constructor () { + + super(); + + } + + ngOnInit () { + + this.min = parseFloat( this.min ); + this.max = parseFloat( this.max ); + this.value = parseFloat( this.value ); + + } + + onBlur () { + + this.inFocus = false; + + } + + onFocus () { + + this.inFocus = true; + + } + + onWheel ( $event: WheelEvent ) { + + if ( this.disabled ) return; + + if ( !this.inFocus ) return; + + // presvent default action to stop scrolling + $event.preventDefault(); + $event.stopPropagation(); + + // console.log( $event.deltaX, $event.deltaY ); + + if ( $event.deltaY < 0 && this.value < this.max ) this.value += this.step; + else if ( $event.deltaY < 0 && this.value >= this.max ) this.value = this.max; + + if ( $event.deltaY > 0 && this.value > this.min ) this.value -= this.step; + else if ( $event.deltaY > 0 && this.value <= this.min ) this.value = this.min; + + this.value = +this.value.toFixed( 3 ); + + if ( this.value == NaN ) this.value = 0; + + this.value = Maths.clamp( this.value, this.min, this.max ); + + // this helps avoid sending update event in every scroll + + if ( this.sendTimeout ) { + + clearTimeout( this.sendTimeout ); + + } + + this.sendTimeout = setTimeout( () => { + + this.valueChanged.emit( this.value ); + + this.changed.emit( this.value ); + + }, 300 ); + } + + onModelChanged ( $event: any ) { + + if ( this.disabled ) return; + + this.value = parseFloat( $event ); + + if ( this.value == NaN ) this.value = 0; + + this.value = Maths.clamp( this.value, this.min, this.max ); + + this.valueChanged.emit( this.value ); + + this.changed.emit( this.value ); + + } + +} diff --git a/src/app/shared/fields/dropdown-field/dropdown-field.component.css b/src/app/shared/fields/dropdown-field/dropdown-field.component.css new file mode 100644 index 00000000..6073ea46 --- /dev/null +++ b/src/app/shared/fields/dropdown-field/dropdown-field.component.css @@ -0,0 +1,28 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +.tv-field-dropdown { + width: 100%; + background: transparent; + color: inherit; + border: none; + outline: none; + box-shadow: none; +} + +.tv-field-option { + background: #424243; + outline: none; + box-shadow: none; +} + +select, option { + outline: none; + box-shadow: none; +} + +select:focus, option:focus, .tv-field-dropddown:focus, .tv-field-option:focus { + outline: none; + box-shadow: none; +} \ No newline at end of file diff --git a/src/app/shared/fields/dropdown-field/dropdown-field.component.html b/src/app/shared/fields/dropdown-field/dropdown-field.component.html new file mode 100644 index 00000000..43fd6b4f --- /dev/null +++ b/src/app/shared/fields/dropdown-field/dropdown-field.component.html @@ -0,0 +1,17 @@ + + +
+ + + +
+ + + +
+ +
\ No newline at end of file diff --git a/src/app/shared/fields/dropdown-field/dropdown-field.component.ts b/src/app/shared/fields/dropdown-field/dropdown-field.component.ts new file mode 100644 index 00000000..742794d3 --- /dev/null +++ b/src/app/shared/fields/dropdown-field/dropdown-field.component.ts @@ -0,0 +1,32 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { Component, OnInit, Input } from '@angular/core'; +import { AbstractFieldComponent } from 'app/core/components/abstract-field.component'; + +@Component( { + selector: 'app-dropdown-field', + templateUrl: './dropdown-field.component.html', + styleUrls: [ './dropdown-field.component.css' ] +} ) +export class DropdownFieldComponent extends AbstractFieldComponent implements OnInit { + + @Input() value: any; + + @Input() label: string; + + @Input() options: [] = []; + + constructor () { + + super(); + + } + + ngOnInit () { + + + } + +} diff --git a/src/app/shared/fields/enum-field/enum-field.component.css b/src/app/shared/fields/enum-field/enum-field.component.css new file mode 100644 index 00000000..86bb1505 --- /dev/null +++ b/src/app/shared/fields/enum-field/enum-field.component.css @@ -0,0 +1,29 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +.tv-field-dropdown { + width: 100%; + background: transparent; + color: inherit; + border: none; + outline: none; + box-shadow: none; + font-size: 0.850em; +} + +.tv-field-option { + background: #424243; + outline: none; + box-shadow: none; +} + +select, option { + outline: none; + box-shadow: none; +} + +select:focus, option:focus, .tv-field-dropddown:focus, .tv-field-option:focus { + outline: none; + box-shadow: none; +} \ No newline at end of file diff --git a/src/app/shared/fields/enum-field/enum-field.component.html b/src/app/shared/fields/enum-field/enum-field.component.html new file mode 100644 index 00000000..5660cf4f --- /dev/null +++ b/src/app/shared/fields/enum-field/enum-field.component.html @@ -0,0 +1,23 @@ + + +
+ + + +
+ + + +
+ +
\ No newline at end of file diff --git a/src/app/shared/fields/enum-field/enum-field.component.ts b/src/app/shared/fields/enum-field/enum-field.component.ts new file mode 100644 index 00000000..1741999e --- /dev/null +++ b/src/app/shared/fields/enum-field/enum-field.component.ts @@ -0,0 +1,60 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { Component, OnInit, Input } from '@angular/core'; +import { AbstractFieldComponent } from 'app/core/components/abstract-field.component'; + +@Component( { + selector: 'app-enum-field', + templateUrl: './enum-field.component.html', + styleUrls: [ './enum-field.component.css' ] +} ) +export class EnumFieldComponent extends AbstractFieldComponent implements OnInit { + + @Input() value: any; + + @Input() label: string; + + @Input() enum: { key, value }; + + public options = []; + + constructor () { + + super(); + + } + + ngOnInit () { + + // KEY, VALUE + // UNKNWON = unkonwn + // RURAL = rurual + // + + // labels are the actual enums + // UNKNOWN, RURAL ETC + const labels = Object.keys( this.enum ); + + Object.values( this.enum ).forEach( ( value, index ) => { + + // label is the string to be shown to user + // this will only be a string + const label = labels[ index ]; + + this.options.push( { + value: value, + label: label, + } ) + + } ) + + } + + onChanged ( $event ) { + + this.changed.emit( $event.target.value ); + + } +} diff --git a/src/app/shared/fields/string-field/string-field.component.html b/src/app/shared/fields/string-field/string-field.component.html new file mode 100644 index 00000000..86ecfa07 --- /dev/null +++ b/src/app/shared/fields/string-field/string-field.component.html @@ -0,0 +1,14 @@ + + + + + + +
+ +
+ +
+
\ No newline at end of file diff --git a/src/app/shared/fields/string-field/string-field.component.ts b/src/app/shared/fields/string-field/string-field.component.ts new file mode 100644 index 00000000..1d1e2033 --- /dev/null +++ b/src/app/shared/fields/string-field/string-field.component.ts @@ -0,0 +1,22 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { Component, OnInit, Input } from '@angular/core'; +import { AbstractFieldComponent } from 'app/core/components/abstract-field.component'; + +@Component( { + selector: 'app-string-field', + templateUrl: './string-field.component.html', +} ) +export class StringFieldComponent extends AbstractFieldComponent { + + @Input() value: any; + + constructor () { + + super(); + + } + +} diff --git a/src/app/shared/helpers/url.helper.ts b/src/app/shared/helpers/url.helper.ts new file mode 100644 index 00000000..b4deca32 --- /dev/null +++ b/src/app/shared/helpers/url.helper.ts @@ -0,0 +1,14 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +export function getQueryParam(prop) { + var params = {}; + var search = decodeURIComponent(window.location.href.slice(window.location.href.indexOf('?') + 1)); + var definitions = search.split('&'); + definitions.forEach(function (val, key) { + var parts = val.split('=', 2); + params[parts[0]] = parts[1]; + }); + return (prop && prop in params) ? params[prop] : params; +} \ No newline at end of file diff --git a/src/app/shared/helpers/utils.ts b/src/app/shared/helpers/utils.ts new file mode 100644 index 00000000..f9faa0ab --- /dev/null +++ b/src/app/shared/helpers/utils.ts @@ -0,0 +1,12 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +export function getIndexBy(array: Array<{}>, { name, value }): number { + for (let i = 0; i < array.length; i++) { + if (array[i][name] === value) { + return i; + } + } + return -1; +} diff --git a/src/app/shared/helpers/window.helper.ts b/src/app/shared/helpers/window.helper.ts new file mode 100644 index 00000000..bb9caa90 --- /dev/null +++ b/src/app/shared/helpers/window.helper.ts @@ -0,0 +1,36 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { ClassProvider, FactoryProvider, InjectionToken } from "@angular/core"; + +export function _window(): any { + return window; +} +export const WINDOW = new InjectionToken("WindowToken"); +export abstract class WindowRef { + get nativeWindow(): Window { + throw new Error("Not implemented."); + } +} +export class BrowserWindowRef extends WindowRef { + constructor() { + super(); + } + get nativeWindow(): Window { + return _window(); + } +} +const browserWindowProvider: ClassProvider = { + provide: WindowRef, + useClass: BrowserWindowRef +}; +export const windowProvider: FactoryProvider = { + provide: WINDOW, + useFactory: _window, + deps: [] +}; +export const WINDOW_PROVIDERS = [ + browserWindowProvider, + windowProvider +]; diff --git a/src/app/shared/inmemory-db/calendarEvents.ts b/src/app/shared/inmemory-db/calendarEvents.ts new file mode 100644 index 00000000..bbcfdb92 --- /dev/null +++ b/src/app/shared/inmemory-db/calendarEvents.ts @@ -0,0 +1,62 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { + startOfDay, + endOfDay, + subDays, + addDays, + endOfMonth, + isSameDay, + isSameMonth, + addHours +} from 'date-fns'; +import { EgretCalendarEvent } from '../../shared/models/event.model'; + +export class CalendarEventDB { + private colors: any = { + red: { + primary: '#f44336', + secondary: '#FAE3E3' + }, + blue: { + primary: '#247ba0 ', + secondary: '#D1E8FF' + }, + yellow: { + primary: '#ffd97d', + secondary: '#FDF1BA' + } + }; + + public events: any[] = [{ + _id: '100', + start: subDays(startOfDay(new Date()), 1), + end: addDays(new Date(), 1), + title: 'A 3 day event', + color: this.colors.red + }, { + _id: '101', + start: startOfDay(new Date()), + title: 'An event with no end date', + color: this.colors.yellow + }, { + _id: '102', + start: subDays(endOfMonth(new Date()), 3), + end: addDays(endOfMonth(new Date()), 3), + title: 'A long event that spans 2 months', + color: this.colors.blue + }, { + _id: '103', + start: addHours(startOfDay(new Date()), 2), + end: new Date(), + title: 'A draggable and resizable event', + color: this.colors.yellow, + resizable: { + beforeStart: true, + afterEnd: true + }, + draggable: true + }]; +} \ No newline at end of file diff --git a/src/app/shared/inmemory-db/chat-db.ts b/src/app/shared/inmemory-db/chat-db.ts new file mode 100644 index 00000000..eef9016e --- /dev/null +++ b/src/app/shared/inmemory-db/chat-db.ts @@ -0,0 +1,181 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +export class ChatDB { + public static user = [ + { + id: "7863a6802ez0e277a0f98534", + name: "John Doe", + avatar: "assets/images/face-1.jpg", + status: "online", + chatInfo: [ + { + chatId: "89564a680b3249760ea21fe77", + contactId: "323sa680b3249760ea21rt47", + contactName: "Frank Powell", + unread: 4, + lastChatTime: "2017-06-12T02:10:18.931Z" + }, + { + chatId: "3289564a680b2134760ea21fe7753", + contactId: "14663a3406eb47ffa63d4fec9429cb71", + contactName: "Betty Diaz", + unread: 0, + lastChatTime: "2017-06-12T02:10:18.931Z" + } + ] + } + ]; + public static contacts = [ + { + id: "323sa680b3249760ea21rt47", + name: "Frank Powell", + avatar: "assets/images/faces/13.jpg", + status: "online", + mood: "" + }, + { + id: "14663a3406eb47ffa63d4fec9429cb71", + name: "Betty Diaz", + avatar: "assets/images/faces/12.jpg", + status: "online", + mood: "" + }, + { + id: "43bd9bc59d164b5aea498e3ae1c24c3c", + name: "Brian Stephens", + avatar: "assets/images/faces/3.jpg", + status: "online", + mood: "" + }, + { + id: "3fc8e01f3ce649d1caf884fbf4f698e4", + name: "Jacqueline Day", + avatar: "assets/images/faces/16.jpg", + status: "offline", + mood: "" + }, + { + id: "e929b1d790ab49968ed8e34648553df4", + name: "Arthur Mendoza", + avatar: "assets/images/faces/10.jpg", + status: "online", + mood: "" + }, + { + id: "d6caf04bba614632b5fecf91aebf4564", + name: "Jeremy Lee", + avatar: "assets/images/faces/9.jpg", + status: "offline", + mood: "" + }, + { + id: "be0fb188c8e242f097fafa24632107e4", + name: "Johnny Newman", + avatar: "assets/images/faces/5.jpg", + status: "offline", + mood: "" + }, + { + id: "dea902191b964a68ba5f2d93cff37e13", + name: "Jeffrey Little", + avatar: "assets/images/faces/15.jpg", + status: "online", + mood: "" + }, + { + id: "0bf58f5ccc4543a9f8747350b7bda3c7", + name: "Barbara Romero", + avatar: "assets/images/faces/4.jpg", + status: "offline", + mood: "" + }, + { + id: "c5d7498bbcb84d81fc72168871ac6a6e", + name: "Daniel James", + avatar: "assets/images/faces/2.jpg", + status: "offline", + mood: "" + }, + { + id: "97bfbdd9413e46efdaca2010400fe18c", + name: "Alice Sanders", + avatar: "assets/images/faces/17.jpg", + status: "offline", + mood: "" + } + ]; + public static chatCollection = [ + { + id: "89564a680b3249760ea21fe77", + chats: [ + { + contactId: "323sa680b3249760ea21rt47", + text: "Do you ever find yourself falling into the “discount trap?”", + time: "2018-02-32T08:45:28.291Z" + }, + { + contactId: "7863a6802ez0e277a0f98534", + text: "Giving away your knowledge or product just to gain clients?", + time: "2018-02-32T08:45:28.291Z" + }, + { + contactId: "323sa680b3249760ea21rt47", + text: "Yes", + time: "2018-02-32T08:45:28.291Z" + }, + { + contactId: "7863a6802ez0e277a0f98534", + text: "Don’t feel bad. It happens to a lot of us", + time: "2018-02-32T08:45:28.291Z" + }, + { + contactId: "323sa680b3249760ea21rt47", + text: "Do you ever find yourself falling into the “discount trap?”", + time: "2018-02-32T08:45:28.291Z" + }, + { + contactId: "7863a6802ez0e277a0f98534", + text: "Giving away your knowledge or product just to gain clients?", + time: "2018-02-32T08:45:28.291Z" + }, + { + contactId: "323sa680b3249760ea21rt47", + text: "Yes", + time: "2018-02-32T08:45:28.291Z" + }, + { + contactId: "7863a6802ez0e277a0f98534", + text: "Don’t feel bad. It happens to a lot of us", + time: "2018-02-32T08:45:28.291Z" + } + ] + }, + { + id: "3289564a680b2134760ea21fe7753", + chats: [ + { + contactId: "14663a3406eb47ffa63d4fec9429cb71", + text: "Do you ever find yourself falling into the “discount trap?”", + time: "2018-03-32T08:45:28.291Z" + }, + { + contactId: "7863a6802ez0e277a0f98534", + text: "Giving away your knowledge or product just to gain clients?", + time: "2018-03-32T08:45:28.291Z" + }, + { + contactId: "14663a3406eb47ffa63d4fec9429cb71", + text: "Yes", + time: "2018-03-32T08:45:28.291Z" + }, + { + contactId: "7863a6802ez0e277a0f98534", + text: "Don’t feel bad. It happens to a lot of us", + time: "2018-03-32T08:45:28.291Z" + } + ] + } + ]; +} diff --git a/src/app/shared/inmemory-db/countries.ts b/src/app/shared/inmemory-db/countries.ts new file mode 100644 index 00000000..1f5eceee --- /dev/null +++ b/src/app/shared/inmemory-db/countries.ts @@ -0,0 +1,251 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +export class CountryDB { + public countries = [ + { name: 'Afghanistan', code: 'AF' }, + { name: 'Åland Islands', code: 'AX' }, + { name: 'Albania', code: 'AL' }, + { name: 'Algeria', code: 'DZ' }, + { name: 'American Samoa', code: 'AS' }, + { name: 'AndorrA', code: 'AD' }, + { name: 'Angola', code: 'AO' }, + { name: 'Anguilla', code: 'AI' }, + { name: 'Antarctica', code: 'AQ' }, + { name: 'Antigua and Barbuda', code: 'AG' }, + { name: 'Argentina', code: 'AR' }, + { name: 'Armenia', code: 'AM' }, + { name: 'Aruba', code: 'AW' }, + { name: 'Australia', code: 'AU' }, + { name: 'Austria', code: 'AT' }, + { name: 'Azerbaijan', code: 'AZ' }, + { name: 'Bahamas', code: 'BS' }, + { name: 'Bahrain', code: 'BH' }, + { name: 'Bangladesh', code: 'BD' }, + { name: 'Barbados', code: 'BB' }, + { name: 'Belarus', code: 'BY' }, + { name: 'Belgium', code: 'BE' }, + { name: 'Belize', code: 'BZ' }, + { name: 'Benin', code: 'BJ' }, + { name: 'Bermuda', code: 'BM' }, + { name: 'Bhutan', code: 'BT' }, + { name: 'Bolivia', code: 'BO' }, + { name: 'Bosnia and Herzegovina', code: 'BA' }, + { name: 'Botswana', code: 'BW' }, + { name: 'Bouvet Island', code: 'BV' }, + { name: 'Brazil', code: 'BR' }, + { name: 'British Indian Ocean Territory', code: 'IO' }, + { name: 'Brunei Darussalam', code: 'BN' }, + { name: 'Bulgaria', code: 'BG' }, + { name: 'Burkina Faso', code: 'BF' }, + { name: 'Burundi', code: 'BI' }, + { name: 'Cambodia', code: 'KH' }, + { name: 'Cameroon', code: 'CM' }, + { name: 'Canada', code: 'CA' }, + { name: 'Cape Verde', code: 'CV' }, + { name: 'Cayman Islands', code: 'KY' }, + { name: 'Central African Republic', code: 'CF' }, + { name: 'Chad', code: 'TD' }, + { name: 'Chile', code: 'CL' }, + { name: 'China', code: 'CN' }, + { name: 'Christmas Island', code: 'CX' }, + { name: 'Cocos (Keeling) Islands', code: 'CC' }, + { name: 'Colombia', code: 'CO' }, + { name: 'Comoros', code: 'KM' }, + { name: 'Congo', code: 'CG' }, + { name: 'Congo, The Democratic Republic of the', code: 'CD' }, + { name: 'Cook Islands', code: 'CK' }, + { name: 'Costa Rica', code: 'CR' }, + { name: 'Cote D\'Ivoire', code: 'CI' }, + { name: 'Croatia', code: 'HR' }, + { name: 'Cuba', code: 'CU' }, + { name: 'Cyprus', code: 'CY' }, + { name: 'Czech Republic', code: 'CZ' }, + { name: 'Denmark', code: 'DK' }, + { name: 'Djibouti', code: 'DJ' }, + { name: 'Dominica', code: 'DM' }, + { name: 'Dominican Republic', code: 'DO' }, + { name: 'Ecuador', code: 'EC' }, + { name: 'Egypt', code: 'EG' }, + { name: 'El Salvador', code: 'SV' }, + { name: 'Equatorial Guinea', code: 'GQ' }, + { name: 'Eritrea', code: 'ER' }, + { name: 'Estonia', code: 'EE' }, + { name: 'Ethiopia', code: 'ET' }, + { name: 'Falkland Islands (Malvinas)', code: 'FK' }, + { name: 'Faroe Islands', code: 'FO' }, + { name: 'Fiji', code: 'FJ' }, + { name: 'Finland', code: 'FI' }, + { name: 'France', code: 'FR' }, + { name: 'French Guiana', code: 'GF' }, + { name: 'French Polynesia', code: 'PF' }, + { name: 'French Southern Territories', code: 'TF' }, + { name: 'Gabon', code: 'GA' }, + { name: 'Gambia', code: 'GM' }, + { name: 'Georgia', code: 'GE' }, + { name: 'Germany', code: 'DE' }, + { name: 'Ghana', code: 'GH' }, + { name: 'Gibraltar', code: 'GI' }, + { name: 'Greece', code: 'GR' }, + { name: 'Greenland', code: 'GL' }, + { name: 'Grenada', code: 'GD' }, + { name: 'Guadeloupe', code: 'GP' }, + { name: 'Guam', code: 'GU' }, + { name: 'Guatemala', code: 'GT' }, + { name: 'Guernsey', code: 'GG' }, + { name: 'Guinea', code: 'GN' }, + { name: 'Guinea-Bissau', code: 'GW' }, + { name: 'Guyana', code: 'GY' }, + { name: 'Haiti', code: 'HT' }, + { name: 'Heard Island and Mcdonald Islands', code: 'HM' }, + { name: 'Holy See (Vatican City State)', code: 'VA' }, + { name: 'Honduras', code: 'HN' }, + { name: 'Hong Kong', code: 'HK' }, + { name: 'Hungary', code: 'HU' }, + { name: 'Iceland', code: 'IS' }, + { name: 'India', code: 'IN' }, + { name: 'Indonesia', code: 'ID' }, + { name: 'Iran, Islamic Republic Of', code: 'IR' }, + { name: 'Iraq', code: 'IQ' }, + { name: 'Ireland', code: 'IE' }, + { name: 'Isle of Man', code: 'IM' }, + { name: 'Israel', code: 'IL' }, + { name: 'Italy', code: 'IT' }, + { name: 'Jamaica', code: 'JM' }, + { name: 'Japan', code: 'JP' }, + { name: 'Jersey', code: 'JE' }, + { name: 'Jordan', code: 'JO' }, + { name: 'Kazakhstan', code: 'KZ' }, + { name: 'Kenya', code: 'KE' }, + { name: 'Kiribati', code: 'KI' }, + { name: 'Korea, Democratic People\'S Republic of', code: 'KP' }, + { name: 'Korea, Republic of', code: 'KR' }, + { name: 'Kuwait', code: 'KW' }, + { name: 'Kyrgyzstan', code: 'KG' }, + { name: 'Lao People\'S Democratic Republic', code: 'LA' }, + { name: 'Latvia', code: 'LV' }, + { name: 'Lebanon', code: 'LB' }, + { name: 'Lesotho', code: 'LS' }, + { name: 'Liberia', code: 'LR' }, + { name: 'Libyan Arab Jamahiriya', code: 'LY' }, + { name: 'Liechtenstein', code: 'LI' }, + { name: 'Lithuania', code: 'LT' }, + { name: 'Luxembourg', code: 'LU' }, + { name: 'Macao', code: 'MO' }, + { name: 'Macedonia, The Former Yugoslav Republic of', code: 'MK' }, + { name: 'Madagascar', code: 'MG' }, + { name: 'Malawi', code: 'MW' }, + { name: 'Malaysia', code: 'MY' }, + { name: 'Maldives', code: 'MV' }, + { name: 'Mali', code: 'ML' }, + { name: 'Malta', code: 'MT' }, + { name: 'Marshall Islands', code: 'MH' }, + { name: 'Martinique', code: 'MQ' }, + { name: 'Mauritania', code: 'MR' }, + { name: 'Mauritius', code: 'MU' }, + { name: 'Mayotte', code: 'YT' }, + { name: 'Mexico', code: 'MX' }, + { name: 'Micronesia, Federated States of', code: 'FM' }, + { name: 'Moldova, Republic of', code: 'MD' }, + { name: 'Monaco', code: 'MC' }, + { name: 'Mongolia', code: 'MN' }, + { name: 'Montserrat', code: 'MS' }, + { name: 'Morocco', code: 'MA' }, + { name: 'Mozambique', code: 'MZ' }, + { name: 'Myanmar', code: 'MM' }, + { name: 'Namibia', code: 'NA' }, + { name: 'Nauru', code: 'NR' }, + { name: 'Nepal', code: 'NP' }, + { name: 'Netherlands', code: 'NL' }, + { name: 'Netherlands Antilles', code: 'AN' }, + { name: 'New Caledonia', code: 'NC' }, + { name: 'New Zealand', code: 'NZ' }, + { name: 'Nicaragua', code: 'NI' }, + { name: 'Niger', code: 'NE' }, + { name: 'Nigeria', code: 'NG' }, + { name: 'Niue', code: 'NU' }, + { name: 'Norfolk Island', code: 'NF' }, + { name: 'Northern Mariana Islands', code: 'MP' }, + { name: 'Norway', code: 'NO' }, + { name: 'Oman', code: 'OM' }, + { name: 'Pakistan', code: 'PK' }, + { name: 'Palau', code: 'PW' }, + { name: 'Palestinian Territory, Occupied', code: 'PS' }, + { name: 'Panama', code: 'PA' }, + { name: 'Papua New Guinea', code: 'PG' }, + { name: 'Paraguay', code: 'PY' }, + { name: 'Peru', code: 'PE' }, + { name: 'Philippines', code: 'PH' }, + { name: 'Pitcairn', code: 'PN' }, + { name: 'Poland', code: 'PL' }, + { name: 'Portugal', code: 'PT' }, + { name: 'Puerto Rico', code: 'PR' }, + { name: 'Qatar', code: 'QA' }, + { name: 'Reunion', code: 'RE' }, + { name: 'Romania', code: 'RO' }, + { name: 'Russian Federation', code: 'RU' }, + { name: 'RWANDA', code: 'RW' }, + { name: 'Saint Helena', code: 'SH' }, + { name: 'Saint Kitts and Nevis', code: 'KN' }, + { name: 'Saint Lucia', code: 'LC' }, + { name: 'Saint Pierre and Miquelon', code: 'PM' }, + { name: 'Saint Vincent and the Grenadines', code: 'VC' }, + { name: 'Samoa', code: 'WS' }, + { name: 'San Marino', code: 'SM' }, + { name: 'Sao Tome and Principe', code: 'ST' }, + { name: 'Saudi Arabia', code: 'SA' }, + { name: 'Senegal', code: 'SN' }, + { name: 'Serbia and Montenegro', code: 'CS' }, + { name: 'Seychelles', code: 'SC' }, + { name: 'Sierra Leone', code: 'SL' }, + { name: 'Singapore', code: 'SG' }, + { name: 'Slovakia', code: 'SK' }, + { name: 'Slovenia', code: 'SI' }, + { name: 'Solomon Islands', code: 'SB' }, + { name: 'Somalia', code: 'SO' }, + { name: 'South Africa', code: 'ZA' }, + { name: 'South Georgia and the South Sandwich Islands', code: 'GS' }, + { name: 'Spain', code: 'ES' }, + { name: 'Sri Lanka', code: 'LK' }, + { name: 'Sudan', code: 'SD' }, + { name: 'Suriname', code: 'SR' }, + { name: 'Svalbard and Jan Mayen', code: 'SJ' }, + { name: 'Swaziland', code: 'SZ' }, + { name: 'Sweden', code: 'SE' }, + { name: 'Switzerland', code: 'CH' }, + { name: 'Syrian Arab Republic', code: 'SY' }, + { name: 'Taiwan, Province of China', code: 'TW' }, + { name: 'Tajikistan', code: 'TJ' }, + { name: 'Tanzania, United Republic of', code: 'TZ' }, + { name: 'Thailand', code: 'TH' }, + { name: 'Timor-Leste', code: 'TL' }, + { name: 'Togo', code: 'TG' }, + { name: 'Tokelau', code: 'TK' }, + { name: 'Tonga', code: 'TO' }, + { name: 'Trinidad and Tobago', code: 'TT' }, + { name: 'Tunisia', code: 'TN' }, + { name: 'Turkey', code: 'TR' }, + { name: 'Turkmenistan', code: 'TM' }, + { name: 'Turks and Caicos Islands', code: 'TC' }, + { name: 'Tuvalu', code: 'TV' }, + { name: 'Uganda', code: 'UG' }, + { name: 'Ukraine', code: 'UA' }, + { name: 'United Arab Emirates', code: 'AE' }, + { name: 'United Kingdom', code: 'GB' }, + { name: 'United States', code: 'US' }, + { name: 'United States Minor Outlying Islands', code: 'UM' }, + { name: 'Uruguay', code: 'UY' }, + { name: 'Uzbekistan', code: 'UZ' }, + { name: 'Vanuatu', code: 'VU' }, + { name: 'Venezuela', code: 'VE' }, + { name: 'Viet Nam', code: 'VN' }, + { name: 'Virgin Islands, British', code: 'VG' }, + { name: 'Virgin Islands, U.S.', code: 'VI' }, + { name: 'Wallis and Futuna', code: 'WF' }, + { name: 'Western Sahara', code: 'EH' }, + { name: 'Yemen', code: 'YE' }, + { name: 'Zambia', code: 'ZM' }, + { name: 'Zimbabwe', code: 'ZW' } + ] +} \ No newline at end of file diff --git a/src/app/shared/inmemory-db/inbox.ts b/src/app/shared/inmemory-db/inbox.ts new file mode 100644 index 00000000..6c363f9e --- /dev/null +++ b/src/app/shared/inmemory-db/inbox.ts @@ -0,0 +1,325 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +export class InboxDB { + public messages = [ + { + sender: { + name: 'Henrik Gevorg', + photo: 'assets/images/face-1.jpg' + }, + date: new Date('1/25/2018'), + selected: false, + subject: 'Welcome to Angular Egret', + message: `

Natus consequuntur perspiciatis esse beatae illo quos eaque.

+

Earum, quisquam, fugit? Numquam dolor magni nisi? Suscipit odit, ipsam iusto enim culpa, + temporibus vero possimus error voluptates sequi. Iusto ipsam, nihil? Eveniet modi maxime animi excepturi a dignissimos doloribus, + inventore sed ratione, ducimus atque earum maiores tenetur officia commodi dicta tempora consequatur non nesciunt ipsam, + consequuntur quia fuga aspernatur impedit et? Natus, earum.

+
+ Earum, quisquam, fugit? Numquam dolor magni nisi? Suscipit odit, ipsam iusto enim culpa, + temporibus vero possimus error voluptates sequi. +
+

Earum, quisquam, fugit? Numquam dolor magni nisi? Suscipit odit, ipsam iusto enim culpa, + temporibus vero possimus error voluptates sequi. Iusto ipsam, nihil? Eveniet modi maxime animi excepturi a dignissimos doloribus, + inventore sed ratione, ducimus atque earum maiores tenetur officia commodi dicta tempora consequatur non nesciunt ipsam, + consequuntur quia fuga aspernatur impedit et? Natus, earum.


+ Thanks
+ Jhone` + }, + { + sender: { + name: 'Gevorg Spartak', + photo: 'assets/images/face-2.jpg' + }, + date: new Date('4/3/2017'), + selected: false, + subject: 'Confirm your email address', + message: `

Natus consequuntur perspiciatis esse beatae illo quos eaque.

+

Earum, quisquam, fugit? Numquam dolor magni nisi? Suscipit odit, ipsam iusto enim culpa, + temporibus vero possimus error voluptates sequi. Iusto ipsam, nihil? Eveniet modi maxime animi excepturi a dignissimos doloribus, + inventore sed ratione, ducimus atque earum maiores tenetur officia commodi dicta tempora consequatur non nesciunt ipsam, + consequuntur quia fuga aspernatur impedit et? Natus, earum.

+
+ Earum, quisquam, fugit? Numquam dolor magni nisi? Suscipit odit, ipsam iusto enim culpa, + temporibus vero possimus error voluptates sequi. +
+

Earum, quisquam, fugit? Numquam dolor magni nisi? Suscipit odit, ipsam iusto enim culpa, + temporibus vero possimus error voluptates sequi. Iusto ipsam, nihil? Eveniet modi maxime animi excepturi a dignissimos doloribus, + inventore sed ratione, ducimus atque earum maiores tenetur officia commodi dicta tempora consequatur non nesciunt ipsam, + consequuntur quia fuga aspernatur impedit et? Natus, earum.

+
+ Earum, quisquam, fugit? Numquam dolor magni nisi? Suscipit odit, ipsam iusto enim culpa, + temporibus vero possimus error voluptates sequi. +

+ Thanks
+ Mark` + + }, + { + sender: { + name: 'Petros Toros', + photo: 'assets/images/face-3.jpg' + }, + date: new Date('1/20/2017'), + selected: false, + subject: 'New order informations', + message: `

Natus consequuntur perspiciatis esse beatae illo quos eaque.

+

Earum, quisquam, fugit? Numquam dolor magni nisi? Suscipit odit, ipsam iusto enim culpa, + temporibus vero possimus error voluptates sequi. Iusto ipsam, nihil? Eveniet modi maxime animi excepturi a dignissimos doloribus, + inventore sed ratione, ducimus atque earum maiores tenetur officia commodi dicta tempora consequatur non nesciunt ipsam, + consequuntur quia fuga aspernatur impedit et? Natus, earum.

+
+ Earum, quisquam, fugit? Numquam dolor magni nisi? Suscipit odit, ipsam iusto enim culpa, + temporibus vero possimus error voluptates sequi. +
+

Earum, quisquam, fugit? Numquam dolor magni nisi? Suscipit odit, ipsam iusto enim culpa, + temporibus vero possimus error voluptates sequi. Iusto ipsam, nihil? Eveniet modi maxime animi excepturi a dignissimos doloribus, + inventore sed ratione, ducimus atque earum maiores tenetur officia commodi dicta tempora consequatur non nesciunt ipsam, + consequuntur quia fuga aspernatur impedit et? Natus, earum.


+ Thanks
+ Jhone` + + }, + { + sender: { + name: 'Henrik Gevorg', + photo: 'assets/images/face-1.jpg' + }, + date: new Date('1/8/2017'), + selected: false, + subject: 'Welcome to Angular Egret', + message: `

Natus consequuntur perspiciatis esse beatae illo quos eaque.

+

Earum, quisquam, fugit? Numquam dolor magni nisi? Suscipit odit, ipsam iusto enim culpa, + temporibus vero possimus error voluptates sequi. Iusto ipsam, nihil? Eveniet modi maxime animi excepturi a dignissimos doloribus, + inventore sed ratione, ducimus atque earum maiores tenetur officia commodi dicta tempora consequatur non nesciunt ipsam, + consequuntur quia fuga aspernatur impedit et? Natus, earum.

+
+ Earum, quisquam, fugit? Numquam dolor magni nisi? Suscipit odit, ipsam iusto enim culpa, + temporibus vero possimus error voluptates sequi. +
+

Earum, quisquam, fugit? Numquam dolor magni nisi? Suscipit odit, ipsam iusto enim culpa, + temporibus vero possimus error voluptates sequi. Iusto ipsam, nihil? Eveniet modi maxime animi excepturi a dignissimos doloribus, + inventore sed ratione, ducimus atque earum maiores tenetur officia commodi dicta tempora consequatur non nesciunt ipsam, + consequuntur quia fuga aspernatur impedit et? Natus, earum.


+ Thanks
+ Jhone` + }, + { + sender: { + name: 'Gevorg Spartak', + photo: 'assets/images/face-2.jpg' + }, + date: new Date('10/3/2016'), + selected: false, + subject: 'Confirm your email address', + message: `

Natus consequuntur perspiciatis esse beatae illo quos eaque.

+

Earum, quisquam, fugit? Numquam dolor magni nisi? Suscipit odit, ipsam iusto enim culpa, + temporibus vero possimus error voluptates sequi. Iusto ipsam, nihil? Eveniet modi maxime animi excepturi a dignissimos doloribus, + inventore sed ratione, ducimus atque earum maiores tenetur officia commodi dicta tempora consequatur non nesciunt ipsam, + consequuntur quia fuga aspernatur impedit et? Natus, earum.

+
+ Earum, quisquam, fugit? Numquam dolor magni nisi? Suscipit odit, ipsam iusto enim culpa, + temporibus vero possimus error voluptates sequi. +
+

Earum, quisquam, fugit? Numquam dolor magni nisi? Suscipit odit, ipsam iusto enim culpa, + temporibus vero possimus error voluptates sequi. Iusto ipsam, nihil? Eveniet modi maxime animi excepturi a dignissimos doloribus, + inventore sed ratione, ducimus atque earum maiores tenetur officia commodi dicta tempora consequatur non nesciunt ipsam, + consequuntur quia fuga aspernatur impedit et? Natus, earum.

+
+ Earum, quisquam, fugit? Numquam dolor magni nisi? Suscipit odit, ipsam iusto enim culpa, + temporibus vero possimus error voluptates sequi. +

+ Thanks
+ Mark` + + }, + { + sender: { + name: 'Petros Toros', + photo: 'assets/images/face-4.jpg' + }, + date: new Date('10/3/2015'), + selected: false, + subject: 'New order informations', + message: `

Natus consequuntur perspiciatis esse beatae illo quos eaque.

+

Earum, quisquam, fugit? Numquam dolor magni nisi? Suscipit odit, ipsam iusto enim culpa, + temporibus vero possimus error voluptates sequi. Iusto ipsam, nihil? Eveniet modi maxime animi excepturi a dignissimos doloribus, + inventore sed ratione, ducimus atque earum maiores tenetur officia commodi dicta tempora consequatur non nesciunt ipsam, + consequuntur quia fuga aspernatur impedit et? Natus, earum.

+
+ Earum, quisquam, fugit? Numquam dolor magni nisi? Suscipit odit, ipsam iusto enim culpa, + temporibus vero possimus error voluptates sequi. +
+

Earum, quisquam, fugit? Numquam dolor magni nisi? Suscipit odit, ipsam iusto enim culpa, + temporibus vero possimus error voluptates sequi. Iusto ipsam, nihil? Eveniet modi maxime animi excepturi a dignissimos doloribus, + inventore sed ratione, ducimus atque earum maiores tenetur officia commodi dicta tempora consequatur non nesciunt ipsam, + consequuntur quia fuga aspernatur impedit et? Natus, earum.


+ Thanks
+ Jhone` + + }, + { + sender: { + name: 'Henrik Gevorg', + photo: 'assets/images/face-1.jpg' + }, + date: new Date('10/3/2015'), + selected: false, + subject: 'Welcome to Angular Egret', + message: `

Natus consequuntur perspiciatis esse beatae illo quos eaque.

+

Earum, quisquam, fugit? Numquam dolor magni nisi? Suscipit odit, ipsam iusto enim culpa, + temporibus vero possimus error voluptates sequi. Iusto ipsam, nihil? Eveniet modi maxime animi excepturi a dignissimos doloribus, + inventore sed ratione, ducimus atque earum maiores tenetur officia commodi dicta tempora consequatur non nesciunt ipsam, + consequuntur quia fuga aspernatur impedit et? Natus, earum.

+
+ Earum, quisquam, fugit? Numquam dolor magni nisi? Suscipit odit, ipsam iusto enim culpa, + temporibus vero possimus error voluptates sequi. +
+

Earum, quisquam, fugit? Numquam dolor magni nisi? Suscipit odit, ipsam iusto enim culpa, + temporibus vero possimus error voluptates sequi. Iusto ipsam, nihil? Eveniet modi maxime animi excepturi a dignissimos doloribus, + inventore sed ratione, ducimus atque earum maiores tenetur officia commodi dicta tempora consequatur non nesciunt ipsam, + consequuntur quia fuga aspernatur impedit et? Natus, earum.


+ Thanks
+ Jhone` + }, + { + sender: { + name: 'Gevorg Spartak', + photo: 'assets/images/face-2.jpg' + }, + date: new Date('10/3/2015'), + selected: false, + subject: 'Confirm your email address', + message: `

Natus consequuntur perspiciatis esse beatae illo quos eaque.

+

Earum, quisquam, fugit? Numquam dolor magni nisi? Suscipit odit, ipsam iusto enim culpa, + temporibus vero possimus error voluptates sequi. Iusto ipsam, nihil? Eveniet modi maxime animi excepturi a dignissimos doloribus, + inventore sed ratione, ducimus atque earum maiores tenetur officia commodi dicta tempora consequatur non nesciunt ipsam, + consequuntur quia fuga aspernatur impedit et? Natus, earum.

+
+ Earum, quisquam, fugit? Numquam dolor magni nisi? Suscipit odit, ipsam iusto enim culpa, + temporibus vero possimus error voluptates sequi. +
+

Earum, quisquam, fugit? Numquam dolor magni nisi? Suscipit odit, ipsam iusto enim culpa, + temporibus vero possimus error voluptates sequi. Iusto ipsam, nihil? Eveniet modi maxime animi excepturi a dignissimos doloribus, + inventore sed ratione, ducimus atque earum maiores tenetur officia commodi dicta tempora consequatur non nesciunt ipsam, + consequuntur quia fuga aspernatur impedit et? Natus, earum.

+
+ Earum, quisquam, fugit? Numquam dolor magni nisi? Suscipit odit, ipsam iusto enim culpa, + temporibus vero possimus error voluptates sequi. +

+ Thanks
+ Mark` + + }, + { + sender: { + name: 'Petros Toros', + photo: 'assets/images/face-4.jpg' + }, + date: new Date('10/3/2015'), + selected: false, + subject: 'New order informations', + message: `

Natus consequuntur perspiciatis esse beatae illo quos eaque.

+

Earum, quisquam, fugit? Numquam dolor magni nisi? Suscipit odit, ipsam iusto enim culpa, + temporibus vero possimus error voluptates sequi. Iusto ipsam, nihil? Eveniet modi maxime animi excepturi a dignissimos doloribus, + inventore sed ratione, ducimus atque earum maiores tenetur officia commodi dicta tempora consequatur non nesciunt ipsam, + consequuntur quia fuga aspernatur impedit et? Natus, earum.

+
+ Earum, quisquam, fugit? Numquam dolor magni nisi? Suscipit odit, ipsam iusto enim culpa, + temporibus vero possimus error voluptates sequi. +
+

Earum, quisquam, fugit? Numquam dolor magni nisi? Suscipit odit, ipsam iusto enim culpa, + temporibus vero possimus error voluptates sequi. Iusto ipsam, nihil? Eveniet modi maxime animi excepturi a dignissimos doloribus, + inventore sed ratione, ducimus atque earum maiores tenetur officia commodi dicta tempora consequatur non nesciunt ipsam, + consequuntur quia fuga aspernatur impedit et? Natus, earum.


+ Thanks
+ Jhone` + + }, + { + sender: { + name: 'Gevorg Spartak', + photo: 'assets/images/face-2.jpg' + }, + date: new Date('10/3/2015'), + selected: false, + subject: 'Confirm your email address', + message: `

Natus consequuntur perspiciatis esse beatae illo quos eaque.

+

Earum, quisquam, fugit? Numquam dolor magni nisi? Suscipit odit, ipsam iusto enim culpa, + temporibus vero possimus error voluptates sequi. Iusto ipsam, nihil? Eveniet modi maxime animi excepturi a dignissimos doloribus, + inventore sed ratione, ducimus atque earum maiores tenetur officia commodi dicta tempora consequatur non nesciunt ipsam, + consequuntur quia fuga aspernatur impedit et? Natus, earum.

+
+ Earum, quisquam, fugit? Numquam dolor magni nisi? Suscipit odit, ipsam iusto enim culpa, + temporibus vero possimus error voluptates sequi. +
+

Earum, quisquam, fugit? Numquam dolor magni nisi? Suscipit odit, ipsam iusto enim culpa, + temporibus vero possimus error voluptates sequi. Iusto ipsam, nihil? Eveniet modi maxime animi excepturi a dignissimos doloribus, + inventore sed ratione, ducimus atque earum maiores tenetur officia commodi dicta tempora consequatur non nesciunt ipsam, + consequuntur quia fuga aspernatur impedit et? Natus, earum.

+
+ Earum, quisquam, fugit? Numquam dolor magni nisi? Suscipit odit, ipsam iusto enim culpa, + temporibus vero possimus error voluptates sequi. +

+ Thanks
+ Mark` + + }, + { + sender: { + name: 'Petros Toros', + photo: 'assets/images/face-4.jpg' + }, + date: new Date('10/3/2015'), + selected: false, + subject: 'New order informations', + message: `

Natus consequuntur perspiciatis esse beatae illo quos eaque.

+

Earum, quisquam, fugit? Numquam dolor magni nisi? Suscipit odit, ipsam iusto enim culpa, + temporibus vero possimus error voluptates sequi. Iusto ipsam, nihil? Eveniet modi maxime animi excepturi a dignissimos doloribus, + inventore sed ratione, ducimus atque earum maiores tenetur officia commodi dicta tempora consequatur non nesciunt ipsam, + consequuntur quia fuga aspernatur impedit et? Natus, earum.

+
+ Earum, quisquam, fugit? Numquam dolor magni nisi? Suscipit odit, ipsam iusto enim culpa, + temporibus vero possimus error voluptates sequi. +
+

Earum, quisquam, fugit? Numquam dolor magni nisi? Suscipit odit, ipsam iusto enim culpa, + temporibus vero possimus error voluptates sequi. Iusto ipsam, nihil? Eveniet modi maxime animi excepturi a dignissimos doloribus, + inventore sed ratione, ducimus atque earum maiores tenetur officia commodi dicta tempora consequatur non nesciunt ipsam, + consequuntur quia fuga aspernatur impedit et? Natus, earum.


+ Thanks
+ Jhone` + + }, + { + sender: { + name: 'Gevorg Spartak', + photo: 'assets/images/face-2.jpg' + }, + date: new Date('10/3/2012'), + selected: false, + subject: 'Confirm your email address', + message: `

Natus consequuntur perspiciatis esse beatae illo quos eaque.

+

Earum, quisquam, fugit? Numquam dolor magni nisi? Suscipit odit, ipsam iusto enim culpa, + temporibus vero possimus error voluptates sequi. Iusto ipsam, nihil? Eveniet modi maxime animi excepturi a dignissimos doloribus, + inventore sed ratione, ducimus atque earum maiores tenetur officia commodi dicta tempora consequatur non nesciunt ipsam, + consequuntur quia fuga aspernatur impedit et? Natus, earum.

+
+ Earum, quisquam, fugit? Numquam dolor magni nisi? Suscipit odit, ipsam iusto enim culpa, + temporibus vero possimus error voluptates sequi. +
+

Earum, quisquam, fugit? Numquam dolor magni nisi? Suscipit odit, ipsam iusto enim culpa, + temporibus vero possimus error voluptates sequi. Iusto ipsam, nihil? Eveniet modi maxime animi excepturi a dignissimos doloribus, + inventore sed ratione, ducimus atque earum maiores tenetur officia commodi dicta tempora consequatur non nesciunt ipsam, + consequuntur quia fuga aspernatur impedit et? Natus, earum.

+
+ Earum, quisquam, fugit? Numquam dolor magni nisi? Suscipit odit, ipsam iusto enim culpa, + temporibus vero possimus error voluptates sequi. +

+ Thanks
+ Mark` + + } + ] +} \ No newline at end of file diff --git a/src/app/shared/inmemory-db/products.ts b/src/app/shared/inmemory-db/products.ts new file mode 100644 index 00000000..5e2c5ab4 --- /dev/null +++ b/src/app/shared/inmemory-db/products.ts @@ -0,0 +1,702 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +export class ProductDB { + public products = [ + { + '_id': '5a9ae2106518248b68251fdf', + 'name': 'Wireless Bluetooth V4.0 Portable Speaker with HD Sound and Bass', + 'subtitle': 'Admodum assentior ad duo', + 'description': 'Lorem ipsum dolor sit amet, et nec putent quodsi, admodum assentior ad duo. Pri ad sapientem ocurreret incorrupte', + 'category': 'speaker', + 'tags': [ + 'sunt', + 'sunt', + 'culpa' + ], + 'price': { + 'sale': 32, + 'previous': 54 + }, + 'ratings': { + 'rating': 3.86, + 'ratingCount': 26 + }, + 'features': [ + 'aliquip aliquip', + 'nulla laboris', + 'pariatur consequat' + ], + 'photo': '../../../../assets/images/products/speaker-1.jpg', + 'gallery': [ + '../../../../assets/images/products/speaker-1.jpg', + '../../../../assets/images/products/speaker-2.jpg' + ], + 'badge': { + 'text': '20% off', + 'color': '#0D47A1' + } + }, + { + '_id': '5a9ae210b7b4d3ad2f048bbe', + 'name': 'Portable Speaker with HD Sound', + 'subtitle': 'Admodum assentior ad duo', + 'description': 'cillum eiusmod', + 'category': 'speaker', + 'tags': [ + 'Lorem', + 'nisi', + 'ad' + ], + 'price': { + 'sale': 25, + 'previous': 43 + }, + 'ratings': { + 'rating': 3.72, + 'ratingCount': 18 + }, + 'features': [ + 'magna est', + 'consectetur dolor', + 'est proident' + ], + 'photo': '../../../../assets/images/products/speaker-2.jpg', + 'gallery': [ + '../../../../assets/images/products/speaker-1.jpg', + '../../../../assets/images/products/speaker-2.jpg' + ], + 'badge': { + 'text': 'Sale', + 'color': '#DD2C00' + } + }, + { + '_id': '5a9ae210d9a8d6dda7256417', + 'name': 'Lightweight On-Ear Headphones - Black', + 'subtitle': 'On-ear fit to minimize noise so you can hear every beat', + 'description': 'sit laborum', + 'category': 'headphone', + 'tags': [ + 'eu', + 'irure', + 'proident' + ], + 'price': { + 'sale': 29, + 'previous': 55 + }, + 'ratings': { + 'rating': 3.79, + 'ratingCount': 77 + }, + 'features': [ + 'laboris id', + 'magna eu', + 'sint quis' + ], + 'photo': '../../../../assets/images/products/headphone-2.jpg', + 'gallery': [ + '../../../../assets/images/products/headphone-1.jpg', + '../../../../assets/images/products/headphone-2.jpg', + '../../../../assets/images/products/headphone-3.jpg', + '../../../../assets/images/products/headphone-4.jpg' + ], + 'badge': { + 'text': '-40%', + 'color': '#0288D1' + } + }, + { + '_id': '5a9ae210e8329237332e56d7', + 'name': 'Automatic-self-wind mens Watch 5102PR-001 (Certified Pre-owned)', + 'subtitle': 'Admodum assentior ad duo', + 'description': 'eiusmod elit', + 'category': 'watch', + 'tags': [ + 'laborum', + 'minim', + 'tempor' + ], + 'price': { + 'sale': 33, + 'previous': 58 + }, + 'ratings': { + 'rating': 4.74, + 'ratingCount': 64 + }, + 'features': [ + 'cillum ullamco', + 'ad minim', + 'duis exercitation' + ], + 'photo': '../../../../assets/images/products/watch-1.jpg', + 'gallery': [ + '../../../../assets/images/products/watch-1.jpg', + '../../../../assets/images/products/watch-2.jpg' + ], + 'badge': { + 'text': '', + 'color': 'red' + } + }, + { + '_id': '5a9ae210cb9937d28c6eca1a', + 'name': 'Automatic-self-wind mens Watch 5102PR-001', + 'subtitle': 'Admodum assentior ad duo', + 'description': 'dolore tempor', + 'category': 'watch', + 'tags': [ + 'Lorem', + 'dolor', + 'duis' + ], + 'price': { + 'sale': 38, + 'previous': 50 + }, + 'ratings': { + 'rating': 4.43, + 'ratingCount': 98 + }, + 'features': [ + 'aliquip consequat', + 'excepteur non', + 'aliquip eu' + ], + 'photo': '../../../../assets/images/products/watch-2.jpg', + 'gallery': [ + '../../../../assets/images/products/watch-1.jpg', + '../../../../assets/images/products/watch-2.jpg' + ], + 'badge': { + 'text': '', + 'color': 'red' + } + }, + { + '_id': '5a9ae2106f155194e5c95d67', + 'name': 'On-Ear Headphones - Black', + 'subtitle': 'Admodum assentior ad duo', + 'description': 'elit Lorem', + 'category': 'headphone', + 'tags': [ + 'magna', + 'veniam', + 'sunt' + ], + 'price': { + 'sale': 38, + 'previous': 54 + }, + 'ratings': { + 'rating': 4.84, + 'ratingCount': 52 + }, + 'features': [ + 'est mollit', + 'adipisicing exercitation', + 'esse incididunt' + ], + 'photo': '../../../../assets/images/products/headphone-3.jpg', + 'gallery': [ + '../../../../assets/images/products/headphone-1.jpg', + '../../../../assets/images/products/headphone-2.jpg', + '../../../../assets/images/products/headphone-3.jpg', + '../../../../assets/images/products/headphone-4.jpg' + ], + 'badge': { + 'text': '', + 'color': 'red' + } + }, + { + '_id': '5a9ae2101625a02fee92e27f', + 'name': 'In-Ear Headphone', + 'subtitle': 'Admodum assentior ad duo', + 'description': 'proident non', + 'category': 'headphone', + 'tags': [ + 'Lorem', + 'occaecat', + 'laborum' + ], + 'price': { + 'sale': 31, + 'previous': 58 + }, + 'ratings': { + 'rating': 3.18, + 'ratingCount': 90 + }, + 'features': [ + 'ullamco quis', + 'veniam laboris', + 'nulla sunt' + ], + 'photo': '../../../../assets/images/products/headphone-4.jpg', + 'gallery': [ + '../../../../assets/images/products/headphone-1.jpg', + '../../../../assets/images/products/headphone-2.jpg', + '../../../../assets/images/products/headphone-3.jpg', + '../../../../assets/images/products/headphone-4.jpg' + ], + 'badge': { + 'text': '', + 'color': 'red' + } + }, + { + '_id': '5a9ae2108970b01447ec34aa', + 'name': 'Duis exercitation nostrud anim', + 'subtitle': 'Admodum assentior ad duo', + 'description': 'dolore enim', + 'category': 'phone', + 'tags': [ + 'do', + 'aliqua', + 'irure' + ], + 'price': { + 'sale': 22, + 'previous': 44 + }, + 'ratings': { + 'rating': 3.53, + 'ratingCount': 47 + }, + 'features': [ + 'sunt laboris', + 'incididunt nulla', + 'ullamco qui' + ], + 'photo': '../../../../assets/images/products/iphone-2.jpg', + 'gallery': [ + '../../../../assets/images/products/iphone-1.jpg', + '../../../../assets/images/products/iphone-2.jpg' + ], + 'badge': { + 'text': '', + 'color': 'red' + } + }, + { + '_id': '5a9ae2103c04707145e21300', + 'name': 'Dolor eu nostrud excepteur', + 'description': 'enim fugiat', + 'category': 'phone', + 'tags': [ + 'laborum', + 'nulla', + 'sit' + ], + 'price': { + 'sale': 31, + 'previous': 40 + }, + 'ratings': { + 'rating': 3.42, + 'ratingCount': 35 + }, + 'features': [ + 'exercitation excepteur', + 'eiusmod mollit', + 'irure adipisicing' + ], + 'photo': '../../../../assets/images/products/iphone-1.jpg', + 'gallery': [ + '../../../../assets/images/products/iphone-1.jpg', + '../../../../assets/images/products/iphone-2.jpg' + ], + 'badge': { + 'text': '', + 'color': 'red' + } + }, + { + '_id': '5a9ae21021b2911c97ad6c5b', + 'name': 'Over-Ear Headphones, Stereo Lightweight Adjustable Wired Headset', + 'subtitle': 'Admodum assentior ad duo', + 'description': 'sit commodo', + 'category': 'headphone', + 'tags': [ + 'adipisicing', + 'labore', + 'voluptate' + ], + 'price': { + 'sale': 33, + 'previous': 57 + }, + 'ratings': { + 'rating': 3.51, + 'ratingCount': 60 + }, + 'features': [ + 'culpa id', + 'eu excepteur', + 'incididunt aute' + ], + 'photo': '../../../../assets/images/products/headphone-1.jpg', + 'gallery': [ + '../../../../assets/images/products/headphone-1.jpg', + '../../../../assets/images/products/headphone-2.jpg', + '../../../../assets/images/products/headphone-3.jpg', + '../../../../assets/images/products/headphone-4.jpg' + ], + 'badge': { + 'text': '', + 'color': 'red' + } + },{ + '_id': '5a9ae2106518248b68251fdf', + 'name': 'Wireless Bluetooth V4.0 Portable Speaker with HD Sound and Bass', + 'subtitle': 'Admodum assentior ad duo', + 'description': 'Lorem ipsum dolor sit amet, et nec putent quodsi, admodum assentior ad duo. Pri ad sapientem ocurreret incorrupte', + 'category': 'speaker', + 'tags': [ + 'sunt', + 'sunt', + 'culpa' + ], + 'price': { + 'sale': 32, + 'previous': 54 + }, + 'ratings': { + 'rating': 3.86, + 'ratingCount': 26 + }, + 'features': [ + 'aliquip aliquip', + 'nulla laboris', + 'pariatur consequat' + ], + 'photo': '../../../../assets/images/products/speaker-1.jpg', + 'gallery': [ + '../../../../assets/images/products/speaker-1.jpg', + '../../../../assets/images/products/speaker-2.jpg' + ], + 'badge': { + 'text': '20% off', + 'color': '#0D47A1' + } + }, + { + '_id': '5a9ae210b7b4d3ad2f048dsbbe', + 'name': 'Portable Speaker with HD Sound', + 'subtitle': 'Admodum assentior ad duo', + 'description': 'cillum eiusmod', + 'category': 'speaker', + 'tags': [ + 'Lorem', + 'nisi', + 'ad' + ], + 'price': { + 'sale': 25, + 'previous': 43 + }, + 'ratings': { + 'rating': 3.72, + 'ratingCount': 18 + }, + 'features': [ + 'magna est', + 'consectetur dolor', + 'est proident' + ], + 'photo': '../../../../assets/images/products/speaker-2.jpg', + 'gallery': [ + '../../../../assets/images/products/speaker-1.jpg', + '../../../../assets/images/products/speaker-2.jpg' + ], + 'badge': { + 'text': 'Sale', + 'color': '#DD2C00' + } + }, + { + '_id': '5a9ae2sd10d9a8d6dda7256417', + 'name': 'Lightweight On-Ear Headphones - Black', + 'subtitle': 'On-ear fit to minimize noise so you can hear every beat', + 'description': 'sit laborum', + 'category': 'headphone', + 'tags': [ + 'eu', + 'irure', + 'proident' + ], + 'price': { + 'sale': 29, + 'previous': 55 + }, + 'ratings': { + 'rating': 3.79, + 'ratingCount': 77 + }, + 'features': [ + 'laboris id', + 'magna eu', + 'sint quis' + ], + 'photo': '../../../../assets/images/products/headphone-2.jpg', + 'gallery': [ + '../../../../assets/images/products/headphone-1.jpg', + '../../../../assets/images/products/headphone-2.jpg', + '../../../../assets/images/products/headphone-3.jpg', + '../../../../assets/images/products/headphone-4.jpg' + ], + 'badge': { + 'text': '-40%', + 'color': '#0288D1' + } + }, + { + '_id': '5a9ae210e8329fs237332e56d7', + 'name': 'Automatic-self-wind mens Watch 5102PR-001 (Certified Pre-owned)', + 'subtitle': 'Admodum assentior ad duo', + 'description': 'eiusmod elit', + 'category': 'watch', + 'tags': [ + 'laborum', + 'minim', + 'tempor' + ], + 'price': { + 'sale': 33, + 'previous': 58 + }, + 'ratings': { + 'rating': 4.74, + 'ratingCount': 64 + }, + 'features': [ + 'cillum ullamco', + 'ad minim', + 'duis exercitation' + ], + 'photo': '../../../../assets/images/products/watch-1.jpg', + 'gallery': [ + '../../../../assets/images/products/watch-1.jpg', + '../../../../assets/images/products/watch-2.jpg' + ], + 'badge': { + 'text': '', + 'color': 'red' + } + }, + { + '_id': '5a9ae210cba9937d28c6eca1a', + 'name': 'Automatic-self-wind mens Watch 5102PR-001', + 'subtitle': 'Admodum assentior ad duo', + 'description': 'dolore tempor', + 'category': 'watch', + 'tags': [ + 'Lorem', + 'dolor', + 'duis' + ], + 'price': { + 'sale': 38, + 'previous': 50 + }, + 'ratings': { + 'rating': 4.43, + 'ratingCount': 98 + }, + 'features': [ + 'aliquip consequat', + 'excepteur non', + 'aliquip eu' + ], + 'photo': '../../../../assets/images/products/watch-2.jpg', + 'gallery': [ + '../../../../assets/images/products/watch-1.jpg', + '../../../../assets/images/products/watch-2.jpg' + ], + 'badge': { + 'text': '', + 'color': 'red' + } + }, + { + '_id': '5ad9ae2106f155194e5c95d67', + 'name': 'On-Ear Headphones - Black', + 'subtitle': 'Admodum assentior ad duo', + 'description': 'elit Lorem', + 'category': 'headphone', + 'tags': [ + 'magna', + 'veniam', + 'sunt' + ], + 'price': { + 'sale': 38, + 'previous': 54 + }, + 'ratings': { + 'rating': 4.84, + 'ratingCount': 52 + }, + 'features': [ + 'est mollit', + 'adipisicing exercitation', + 'esse incididunt' + ], + 'photo': '../../../../assets/images/products/headphone-3.jpg', + 'gallery': [ + '../../../../assets/images/products/headphone-1.jpg', + '../../../../assets/images/products/headphone-2.jpg', + '../../../../assets/images/products/headphone-3.jpg', + '../../../../assets/images/products/headphone-4.jpg' + ], + 'badge': { + 'text': '', + 'color': 'red' + } + }, + { + '_id': '5a9ae2101625a02fee92fe27f', + 'name': 'In-Ear Headphone', + 'subtitle': 'Admodum assentior ad duo', + 'description': 'proident non', + 'category': 'headphone', + 'tags': [ + 'Lorem', + 'occaecat', + 'laborum' + ], + 'price': { + 'sale': 31, + 'previous': 58 + }, + 'ratings': { + 'rating': 3.18, + 'ratingCount': 90 + }, + 'features': [ + 'ullamco quis', + 'veniam laboris', + 'nulla sunt' + ], + 'photo': '../../../../assets/images/products/headphone-4.jpg', + 'gallery': [ + '../../../../assets/images/products/headphone-1.jpg', + '../../../../assets/images/products/headphone-2.jpg', + '../../../../assets/images/products/headphone-3.jpg', + '../../../../assets/images/products/headphone-4.jpg' + ], + 'badge': { + 'text': '', + 'color': 'red' + } + }, + { + '_id': '5a9ae2108970bs01447ec34aa', + 'name': 'Duis exercitation nostrud anim', + 'subtitle': 'Admodum assentior ad duo', + 'description': 'dolore enim', + 'category': 'phone', + 'tags': [ + 'do', + 'aliqua', + 'irure' + ], + 'price': { + 'sale': 22, + 'previous': 44 + }, + 'ratings': { + 'rating': 3.53, + 'ratingCount': 47 + }, + 'features': [ + 'sunt laboris', + 'incididunt nulla', + 'ullamco qui' + ], + 'photo': '../../../../assets/images/products/iphone-2.jpg', + 'gallery': [ + '../../../../assets/images/products/iphone-1.jpg', + '../../../../assets/images/products/iphone-2.jpg' + ], + 'badge': { + 'text': '', + 'color': 'red' + } + }, + { + '_id': '5a9ae2103c0470f7145e21300', + 'name': 'Dolor eu nostrud excepteur', + 'description': 'enim fugiat', + 'category': 'phone', + 'tags': [ + 'laborum', + 'nulla', + 'sit' + ], + 'price': { + 'sale': 31, + 'previous': 40 + }, + 'ratings': { + 'rating': 3.42, + 'ratingCount': 35 + }, + 'features': [ + 'exercitation excepteur', + 'eiusmod mollit', + 'irure adipisicing' + ], + 'photo': '../../../../assets/images/products/iphone-1.jpg', + 'gallery': [ + '../../../../assets/images/products/iphone-1.jpg', + '../../../../assets/images/products/iphone-2.jpg' + ], + 'badge': { + 'text': '', + 'color': 'red' + } + }, + { + '_id': '5a9aef21021b2911c97ad6c5b', + 'name': 'Over-Ear Headphones, Stereo Lightweight Adjustable Wired Headset', + 'subtitle': 'Admodum assentior ad duo', + 'description': 'sit commodo', + 'category': 'headphone', + 'tags': [ + 'adipisicing', + 'labore', + 'voluptate' + ], + 'price': { + 'sale': 33, + 'previous': 57 + }, + 'ratings': { + 'rating': 3.51, + 'ratingCount': 60 + }, + 'features': [ + 'culpa id', + 'eu excepteur', + 'incididunt aute' + ], + 'photo': '../../../../assets/images/products/headphone-1.jpg', + 'gallery': [ + '../../../../assets/images/products/headphone-1.jpg', + '../../../../assets/images/products/headphone-2.jpg', + '../../../../assets/images/products/headphone-3.jpg', + '../../../../assets/images/products/headphone-4.jpg' + ], + 'badge': { + 'text': '', + 'color': 'red' + } + } + + ] +} \ No newline at end of file diff --git a/src/app/shared/inmemory-db/users.ts b/src/app/shared/inmemory-db/users.ts new file mode 100644 index 00000000..d3ff4d96 --- /dev/null +++ b/src/app/shared/inmemory-db/users.ts @@ -0,0 +1,1028 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +export class UserDB { + public users = [ + { + '_id': '5a7b73f76bed15c94d1e46d4', + 'index': 0, + 'guid': 'c01da2d1-07f8-4acc-a1e3-72dda7310af8', + 'isActive': false, + 'balance': 2838.08, + 'age': 30, + 'name': 'Stefanie Marsh', + 'gender': 'female', + 'company': 'ACIUM', + 'email': 'stefaniemarsh@acium.com', + 'phone': '+1 (857) 535-2066', + 'address': '163 Poplar Avenue, Cliffside, Virginia, 4592', + 'bd': '2015-02-08T04:28:44 -06:00' + }, + { + '_id': '5a7b73f7f79f4250b96a355a', + 'index': 1, + 'guid': '3f04aa40-62da-466d-ac14-2b8a5da3d1ce', + 'isActive': true, + 'balance': 3043.81, + 'age': 39, + 'name': 'Elena Bennett', + 'gender': 'female', + 'company': 'FIBRODYNE', + 'email': 'elenabennett@fibrodyne.com', + 'phone': '+1 (994) 570-2070', + 'address': '526 Grace Court, Cherokee, Oregon, 7017', + 'bd': '2017-11-15T09:04:57 -06:00' + }, + { + '_id': '5a7b73f78b64a02a67204d6e', + 'index': 2, + 'guid': 'e7d9d61e-b657-4fcf-b069-2eb9bfdc44fa', + 'isActive': true, + 'balance': 1796.92, + 'age': 23, + 'name': 'Joni Cabrera', + 'gender': 'female', + 'company': 'POWERNET', + 'email': 'jonicabrera@powernet.com', + 'phone': '+1 (848) 410-2368', + 'address': '554 Barlow Drive, Alamo, Michigan, 3686', + 'bd': '2017-10-15T12:55:51 -06:00' + }, + { + '_id': '5a7b73f7572e59b231149b94', + 'index': 3, + 'guid': '47673d82-ab31-48a1-8a16-2c6701573c67', + 'isActive': false, + 'balance': 2850.27, + 'age': 37, + 'name': 'Gallagher Shaw', + 'gender': 'male', + 'company': 'ZILLAR', + 'email': 'gallaghershaw@zillar.com', + 'phone': '+1 (896) 422-3786', + 'address': '111 Argyle Road, Graball, Idaho, 7272', + 'bd': '2017-11-19T03:38:30 -06:00' + }, + { + '_id': '5a7b73f70f9d074552e13090', + 'index': 4, + 'guid': 'bc9c7cd3-04e0-4095-a933-af28efaf3b3e', + 'isActive': false, + 'balance': 3743.48, + 'age': 26, + 'name': 'Blanchard Knapp', + 'gender': 'male', + 'company': 'ACRODANCE', + 'email': 'blanchardknapp@acrodance.com', + 'phone': '+1 (867) 542-2772', + 'address': '707 Malta Street, Yukon, Wyoming, 6861', + 'bd': '2014-05-28T01:33:58 -06:00' + }, + { + '_id': '5a7b73f78988bd6e92650473', + 'index': 5, + 'guid': '08cb947c-e49c-4736-9687-0fca0992ec38', + 'isActive': false, + 'balance': 3453.79, + 'age': 34, + 'name': 'Parker Rivas', + 'gender': 'male', + 'company': 'SLAMBDA', + 'email': 'parkerrivas@slambda.com', + 'phone': '+1 (997) 413-2418', + 'address': '543 Roosevelt Place, Tibbie, Minnesota, 6944', + 'bd': '2015-01-05T09:55:23 -06:00' + }, + { + '_id': '5a7b73f72488770f90649570', + 'index': 6, + 'guid': '771c85d5-7762-4bae-96fd-09892a9c4374', + 'isActive': false, + 'balance': 3334.73, + 'age': 20, + 'name': 'Alexandria Forbes', + 'gender': 'female', + 'company': 'EQUITOX', + 'email': 'alexandriaforbes@equitox.com', + 'phone': '+1 (869) 521-2533', + 'address': '663 Minna Street, Omar, Alabama, 5265', + 'bd': '2017-03-09T05:48:57 -06:00' + }, + { + '_id': '5a7b73f7c576e368b321a705', + 'index': 7, + 'guid': '2455a7ef-a537-46e1-a210-75e5e2187460', + 'isActive': false, + 'balance': 3488.64, + 'age': 37, + 'name': 'Lessie Wise', + 'gender': 'female', + 'company': 'AFFLUEX', + 'email': 'lessiewise@affluex.com', + 'phone': '+1 (820) 404-2967', + 'address': '752 Woodhull Street, Utting, Oklahoma, 2739', + 'bd': '2014-10-21T03:09:34 -06:00' + }, + { + '_id': '5a7b73f705f8a9c6e35c8ca2', + 'index': 8, + 'guid': 'a90d65a8-681d-462f-bf08-eceeef366375', + 'isActive': true, + 'balance': 3786.67, + 'age': 36, + 'name': 'Carrie Gates', + 'gender': 'female', + 'company': 'VIRVA', + 'email': 'carriegates@virva.com', + 'phone': '+1 (845) 463-3986', + 'address': '561 Boulevard Court, Rote, Louisiana, 8458', + 'bd': '2017-03-30T02:06:23 -06:00' + }, + { + '_id': '5a7b73f7a3e2be2dbb7b093e', + 'index': 9, + 'guid': 'fb3d0f97-91ae-4336-b0b4-19f4a00fe567', + 'isActive': false, + 'balance': 3335.5, + 'age': 33, + 'name': 'Dalton Spears', + 'gender': 'male', + 'company': 'MIRACLIS', + 'email': 'daltonspears@miraclis.com', + 'phone': '+1 (919) 541-3528', + 'address': '167 Lester Court, Glasgow, Arkansas, 6311', + 'bd': '2017-04-01T01:41:12 -06:00' + }, + { + '_id': '5a7b73f716de69a9217c1273', + 'index': 10, + 'guid': '129a92fd-848f-48eb-98a1-aebf6e92b079', + 'isActive': false, + 'balance': 3811.15, + 'age': 30, + 'name': 'Delia Merrill', + 'gender': 'female', + 'company': 'COMTEST', + 'email': 'deliamerrill@comtest.com', + 'phone': '+1 (879) 401-2304', + 'address': '761 Polhemus Place, Kidder, Puerto Rico, 5901', + 'bd': '2014-08-29T08:42:59 -06:00' + }, + { + '_id': '5a7b73f7ed19007bed2d29fb', + 'index': 11, + 'guid': 'd799b69a-192d-4ee3-9a69-9e8e5afc45b0', + 'isActive': false, + 'balance': 3935.82, + 'age': 28, + 'name': 'Vance Aguilar', + 'gender': 'male', + 'company': 'CYCLONICA', + 'email': 'vanceaguilar@cyclonica.com', + 'phone': '+1 (972) 549-2681', + 'address': '653 Billings Place, Gardners, Connecticut, 7805', + 'bd': '2015-02-21T03:06:14 -06:00' + }, + { + '_id': '5a7b73f78d0dc0858a70c44a', + 'index': 12, + 'guid': '8cbb37bb-7644-4993-b48b-df3a69deb339', + 'isActive': true, + 'balance': 3868.95, + 'age': 28, + 'name': 'Adams Harper', + 'gender': 'male', + 'company': 'NORSUP', + 'email': 'adamsharper@norsup.com', + 'phone': '+1 (824) 494-3395', + 'address': '571 Turner Place, Norris, Mississippi, 3829', + 'bd': '2014-01-30T02:05:53 -06:00' + }, + { + '_id': '5a7b73f7e929494a8568a885', + 'index': 13, + 'guid': '22ec32d7-0ba9-4366-b6d8-ca16389a2cd9', + 'isActive': false, + 'balance': 3954.41, + 'age': 34, + 'name': 'Bass Sexton', + 'gender': 'male', + 'company': 'CIRCUM', + 'email': 'basssexton@circum.com', + 'phone': '+1 (930) 476-3634', + 'address': '563 Victor Road, Richmond, Kansas, 7742', + 'bd': '2014-05-04T10:16:32 -06:00' + }, + { + '_id': '5a7b73f767e97ce3136444fd', + 'index': 14, + 'guid': '031d282f-0be9-49e1-a211-9aa59d449d91', + 'isActive': false, + 'balance': 3287.33, + 'age': 24, + 'name': 'Howard Velez', + 'gender': 'male', + 'company': 'ECOSYS', + 'email': 'howardvelez@ecosys.com', + 'phone': '+1 (920) 556-2885', + 'address': '378 Grimes Road, Websterville, Marshall Islands, 3506', + 'bd': '2015-12-19T08:17:58 -06:00' + }, + { + '_id': '5a7b73f7fba076653cc18925', + 'index': 15, + 'guid': 'd76ab6d6-d1db-4286-8516-ce6c9db3972a', + 'isActive': false, + 'balance': 3279.98, + 'age': 21, + 'name': 'Lola Morton', + 'gender': 'female', + 'company': 'PROVIDCO', + 'email': 'lolamorton@providco.com', + 'phone': '+1 (963) 458-2788', + 'address': '991 Ashland Place, Richville, New York, 3529', + 'bd': '2016-11-29T07:58:24 -06:00' + }, + { + '_id': '5a7b73f7c6d408bc853be87c', + 'index': 16, + 'guid': '30c2d1c7-770b-4adb-b6df-cc205d748323', + 'isActive': false, + 'balance': 3955.55, + 'age': 37, + 'name': 'Bishop Rutledge', + 'gender': 'male', + 'company': 'DAYCORE', + 'email': 'bishoprutledge@daycore.com', + 'phone': '+1 (886) 539-3156', + 'address': '870 Vanderveer Place, Bridgetown, California, 7593', + 'bd': '2014-11-10T04:47:00 -06:00' + }, + { + '_id': '5a7b73f7abe6c78719d2f494', + 'index': 17, + 'guid': '2d8e77a1-4a88-4642-b6a8-693de296661c', + 'isActive': true, + 'balance': 1832.83, + 'age': 23, + 'name': 'Lea Reese', + 'gender': 'female', + 'company': 'GLUID', + 'email': 'leareese@gluid.com', + 'phone': '+1 (866) 413-2199', + 'address': '811 Dunne Place, Vowinckel, Rhode Island, 8646', + 'bd': '2014-03-16T04:30:06 -06:00' + }, + { + '_id': '5a7b73f72d64af126b8080be', + 'index': 18, + 'guid': 'e1e8ee63-6d08-48fc-a077-2265cee34f23', + 'isActive': true, + 'balance': 2419.18, + 'age': 23, + 'name': 'Knox Moses', + 'gender': 'male', + 'company': 'BRAINCLIP', + 'email': 'knoxmoses@brainclip.com', + 'phone': '+1 (982) 519-2486', + 'address': '917 Turnbull Avenue, Shasta, Virgin Islands, 7016', + 'bd': '2015-11-09T10:11:15 -06:00' + }, + { + '_id': '5a7b73f789b4e9086d34b255', + 'index': 19, + 'guid': '13552b7d-928c-4b92-a2ae-5ccbee807594', + 'isActive': false, + 'balance': 1220.91, + 'age': 22, + 'name': 'Marsha Jacobs', + 'gender': 'female', + 'company': 'COMSTAR', + 'email': 'marshajacobs@comstar.com', + 'phone': '+1 (858) 511-2546', + 'address': '580 Hampton Avenue, Ilchester, New Hampshire, 2191', + 'bd': '2016-02-11T01:34:23 -06:00' + }, + { + '_id': '5a7b73f737eea8e94089b7b4', + 'index': 20, + 'guid': 'cf577c87-b40c-4c09-9fac-d04c9a824b86', + 'isActive': false, + 'balance': 2446.07, + 'age': 25, + 'name': 'Bell Emerson', + 'gender': 'male', + 'company': 'MULTIFLEX', + 'email': 'bellemerson@multiflex.com', + 'phone': '+1 (806) 496-2473', + 'address': '238 Oxford Walk, Monument, New Mexico, 1345', + 'bd': '2016-10-07T01:07:21 -06:00' + }, + { + '_id': '5a7b73f76bc821dc6ee56ee2', + 'index': 21, + 'guid': 'b6c685c2-a497-4261-9217-622723d5235f', + 'isActive': false, + 'balance': 3694.63, + 'age': 33, + 'name': 'Cecelia Graham', + 'gender': 'female', + 'company': 'ZOXY', + 'email': 'ceceliagraham@zoxy.com', + 'phone': '+1 (933) 429-3129', + 'address': '954 Lawton Street, Terlingua, New Jersey, 6723', + 'bd': '2017-12-01T04:36:13 -06:00' + }, + { + '_id': '5a7b73f794c27c4048290cbf', + 'index': 22, + 'guid': '7e887403-8ff5-41b4-9902-bb63ff714fee', + 'isActive': true, + 'balance': 2804.02, + 'age': 29, + 'name': 'Anthony Pennington', + 'gender': 'male', + 'company': 'NAMEGEN', + 'email': 'anthonypennington@namegen.com', + 'phone': '+1 (860) 458-3988', + 'address': '287 Auburn Place, Gardiner, Northern Mariana Islands, 7131', + 'bd': '2018-02-04T11:06:51 -06:00' + }, + { + '_id': '5a7b73f720a5781f7d19597a', + 'index': 23, + 'guid': '9e108687-e1ca-4385-bdd5-62ab006f8aa3', + 'isActive': true, + 'balance': 1984.1, + 'age': 36, + 'name': 'Mayo Justice', + 'gender': 'male', + 'company': 'SLOFAST', + 'email': 'mayojustice@slofast.com', + 'phone': '+1 (854) 428-2270', + 'address': '648 Melba Court, Dodge, Pennsylvania, 7596', + 'bd': '2016-12-29T07:28:10 -06:00' + }, + { + '_id': '5a7b73f7f0a4c5e6c9807fb2', + 'index': 24, + 'guid': '93b0b383-dd69-4453-be26-f13ae361ce67', + 'isActive': true, + 'balance': 1845.13, + 'age': 22, + 'name': 'Vaughn Salazar', + 'gender': 'male', + 'company': 'ZAGGLE', + 'email': 'vaughnsalazar@zaggle.com', + 'phone': '+1 (986) 415-3294', + 'address': '382 Dewitt Avenue, Goodville, Palau, 711', + 'bd': '2014-10-31T12:32:59 -06:00' + }, + { + '_id': '5a7b73f7e6c45298c709371c', + 'index': 25, + 'guid': '5a059bbb-3f6d-47bc-ba2b-c13eeaaa93b4', + 'isActive': false, + 'balance': 3684.79, + 'age': 31, + 'name': 'Calhoun Bradshaw', + 'gender': 'male', + 'company': 'OVERPLEX', + 'email': 'calhounbradshaw@overplex.com', + 'phone': '+1 (964) 594-2363', + 'address': '527 Seton Place, Wedgewood, Wisconsin, 8306', + 'bd': '2016-05-27T10:46:17 -06:00' + }, + { + '_id': '5a7b73f79468759d25ecdcf4', + 'index': 26, + 'guid': '68d7f78e-5001-480b-a67d-72b370a5c2de', + 'isActive': false, + 'balance': 1831.14, + 'age': 29, + 'name': 'Dianne Bauer', + 'gender': 'female', + 'company': 'XUMONK', + 'email': 'diannebauer@xumonk.com', + 'phone': '+1 (866) 510-2479', + 'address': '540 Moffat Street, Emison, South Carolina, 7329', + 'bd': '2014-09-02T04:57:23 -06:00' + }, + { + '_id': '5a7b73f7346b1bbab11524fa', + 'index': 27, + 'guid': '0729eef8-36c5-4aa2-8e31-f5e2ca19b94b', + 'isActive': false, + 'balance': 1719.77, + 'age': 22, + 'name': 'Hebert Bryan', + 'gender': 'male', + 'company': 'COMTRAIL', + 'email': 'hebertbryan@comtrail.com', + 'phone': '+1 (838) 579-3709', + 'address': '669 Hausman Street, Gerber, Kentucky, 7779', + 'bd': '2017-11-29T12:22:59 -06:00' + }, + { + '_id': '5a7b73f75116874002de08de', + 'index': 28, + 'guid': '63014b40-3f1e-40ff-b2f7-f55ef6a5a599', + 'isActive': true, + 'balance': 1973.27, + 'age': 20, + 'name': 'Cash Bean', + 'gender': 'male', + 'company': 'SUPREMIA', + 'email': 'cashbean@supremia.com', + 'phone': '+1 (846) 551-2291', + 'address': '152 Garnet Street, Boling, Nevada, 4867', + 'bd': '2014-01-06T10:18:37 -06:00' + }, + { + '_id': '5a7b73f739be4dc1f743993c', + 'index': 29, + 'guid': 'ae498760-b43b-4c9c-8575-820f419984f6', + 'isActive': true, + 'balance': 2118.14, + 'age': 36, + 'name': 'Candy Hopper', + 'gender': 'female', + 'company': 'ACCUFARM', + 'email': 'candyhopper@accufarm.com', + 'phone': '+1 (841) 425-2442', + 'address': '695 Nassau Avenue, Nutrioso, Maryland, 2026', + 'bd': '2016-01-03T02:15:56 -06:00' + }, + { + '_id': '5a7b73f70b86f2969d762be2', + 'index': 30, + 'guid': 'f19cb86e-ab4f-4d07-833a-4adb8a19d0af', + 'isActive': false, + 'balance': 3794.89, + 'age': 37, + 'name': 'Fisher Powell', + 'gender': 'male', + 'company': 'ENOMEN', + 'email': 'fisherpowell@enomen.com', + 'phone': '+1 (876) 562-2932', + 'address': '616 Tapscott Avenue, Crucible, Nebraska, 4900', + 'bd': '2018-01-31T05:15:13 -06:00' + }, + { + '_id': '5a7b73f7394648a68c2a6ae3', + 'index': 31, + 'guid': 'a88e5389-0b07-4d19-ac6c-718ce9e0de55', + 'isActive': false, + 'balance': 3343.45, + 'age': 38, + 'name': 'Rosemary Sloan', + 'gender': 'female', + 'company': 'PHORMULA', + 'email': 'rosemarysloan@phormula.com', + 'phone': '+1 (924) 517-3289', + 'address': '687 Navy Walk, Edmund, Delaware, 1419', + 'bd': '2018-01-23T11:32:25 -06:00' + }, + { + '_id': '5a7b73f77ad97f4e1c2fa65a', + 'index': 32, + 'guid': 'fb915568-2875-49b3-96d7-6b54b2b186a1', + 'isActive': true, + 'balance': 2680.62, + 'age': 30, + 'name': 'Elba Glover', + 'gender': 'female', + 'company': 'APPLICA', + 'email': 'elbaglover@applica.com', + 'phone': '+1 (857) 495-3565', + 'address': '279 Bridgewater Street, Edneyville, Utah, 9246', + 'bd': '2015-10-03T12:24:56 -06:00' + }, + { + '_id': '5a7b73f72598106a97fbf7d5', + 'index': 33, + 'guid': 'fac3cd4b-2d42-4b4f-9d6f-0bac689bd47b', + 'isActive': false, + 'balance': 3286.46, + 'age': 37, + 'name': 'Mildred Short', + 'gender': 'female', + 'company': 'NIXELT', + 'email': 'mildredshort@nixelt.com', + 'phone': '+1 (980) 530-3588', + 'address': '434 Elm Place, Coloma, West Virginia, 1990', + 'bd': '2016-03-22T10:13:26 -06:00' + }, + { + '_id': '5a7b73f7b88290b05f53faa1', + 'index': 34, + 'guid': 'b1c6a3a3-00bd-4bc6-87df-69eecd909ab5', + 'isActive': false, + 'balance': 1484.16, + 'age': 24, + 'name': 'Karin Schultz', + 'gender': 'female', + 'company': 'PLASMOS', + 'email': 'karinschultz@plasmos.com', + 'phone': '+1 (904) 544-2796', + 'address': '380 Rockaway Avenue, Faxon, American Samoa, 5776', + 'bd': '2016-03-27T09:30:36 -06:00' + }, + { + '_id': '5a7b73f7d2f7429d0caec5fe', + 'index': 35, + 'guid': '62c961ac-49b1-4a69-b4bf-13a396ec4fd9', + 'isActive': false, + 'balance': 3450.17, + 'age': 23, + 'name': 'Addie Rose', + 'gender': 'female', + 'company': 'XYQAG', + 'email': 'addierose@xyqag.com', + 'phone': '+1 (838) 549-3147', + 'address': '999 Coleridge Street, Golconda, Vermont, 9575', + 'bd': '2016-10-01T06:50:42 -06:00' + }, + { + '_id': '5a7b73f78a4c54ff8334e053', + 'index': 36, + 'guid': '4f2f7ae5-0bd1-4665-b97f-c556e5162349', + 'isActive': false, + 'balance': 1797.89, + 'age': 23, + 'name': 'Janie Ellison', + 'gender': 'female', + 'company': 'SPLINX', + 'email': 'janieellison@splinx.com', + 'phone': '+1 (947) 460-2254', + 'address': '114 Landis Court, Genoa, Indiana, 5198', + 'bd': '2017-07-28T12:45:44 -06:00' + }, + { + '_id': '5a7b73f7c87f7e86fcb00055', + 'index': 37, + 'guid': 'b7236378-8129-44b5-bcc6-0369290ffad6', + 'isActive': false, + 'balance': 3776.51, + 'age': 38, + 'name': 'Elisabeth Campbell', + 'gender': 'female', + 'company': 'GOKO', + 'email': 'elisabethcampbell@goko.com', + 'phone': '+1 (849) 430-3377', + 'address': '832 Kermit Place, Lutsen, Georgia, 9145', + 'bd': '2015-04-26T06:40:08 -06:00' + }, + { + '_id': '5a7b73f712f9208f145fa6ea', + 'index': 38, + 'guid': '5c955e3a-5f3a-4ead-96ee-80a5de6dc479', + 'isActive': true, + 'balance': 3794.93, + 'age': 27, + 'name': 'Noble Holland', + 'gender': 'male', + 'company': 'NUTRALAB', + 'email': 'nobleholland@nutralab.com', + 'phone': '+1 (888) 573-3730', + 'address': '408 Roosevelt Court, Hiwasse, North Dakota, 281', + 'bd': '2014-03-25T12:24:34 -06:00' + }, + { + '_id': '5a7b73f7aa1f371de59df90b', + 'index': 39, + 'guid': '94698a81-61a6-4e23-a952-76a50fba71ef', + 'isActive': true, + 'balance': 2205.55, + 'age': 35, + 'name': 'Laverne Brock', + 'gender': 'female', + 'company': 'ICOLOGY', + 'email': 'lavernebrock@icology.com', + 'phone': '+1 (821) 600-3174', + 'address': '391 Conover Street, Cassel, Tennessee, 6566', + 'bd': '2016-01-27T09:40:41 -06:00' + }, + { + '_id': '5a7b73f7c45c697931199945', + 'index': 40, + 'guid': 'a05a215f-be1c-49d1-89ca-c821b118f923', + 'isActive': true, + 'balance': 2397.12, + 'age': 29, + 'name': 'Irene Frost', + 'gender': 'female', + 'company': 'RODEMCO', + 'email': 'irenefrost@rodemco.com', + 'phone': '+1 (918) 539-2612', + 'address': '401 Moore Place, Groton, Arizona, 3415', + 'bd': '2017-09-14T09:46:55 -06:00' + }, + { + '_id': '5a7b73f7ef55416e92ebc818', + 'index': 41, + 'guid': '1ae8ceac-e8d0-4417-9f6f-04cd4e4738ad', + 'isActive': false, + 'balance': 3335.51, + 'age': 35, + 'name': 'Beard Hendricks', + 'gender': 'male', + 'company': 'QUONK', + 'email': 'beardhendricks@quonk.com', + 'phone': '+1 (847) 521-3952', + 'address': '576 Bayard Street, Chloride, Federated States Of Micronesia, 8070', + 'bd': '2016-11-01T12:47:26 -06:00' + }, + { + '_id': '5a7b73f7cbeecfe6febd672d', + 'index': 42, + 'guid': 'afdf3298-77bd-46b3-ae8d-232f815c5f01', + 'isActive': false, + 'balance': 2205.01, + 'age': 37, + 'name': 'Nelson Shields', + 'gender': 'male', + 'company': 'ARTWORLDS', + 'email': 'nelsonshields@artworlds.com', + 'phone': '+1 (956) 534-3050', + 'address': '581 Maple Street, Needmore, Colorado, 2062', + 'bd': '2014-07-21T08:22:01 -06:00' + }, + { + '_id': '5a7b73f71803de25c5f754ad', + 'index': 43, + 'guid': '5b872cad-4388-496b-8ede-5f86990dec00', + 'isActive': true, + 'balance': 1001.05, + 'age': 21, + 'name': 'Luella Duffy', + 'gender': 'female', + 'company': 'KROG', + 'email': 'luelladuffy@krog.com', + 'phone': '+1 (973) 451-2222', + 'address': '349 Bryant Street, Tioga, South Dakota, 6493', + 'bd': '2016-04-27T02:46:46 -06:00' + }, + { + '_id': '5a7b73f77f2a05eacb331c74', + 'index': 44, + 'guid': '7d6b7650-10d7-435d-87ca-33a1fe12cd57', + 'isActive': false, + 'balance': 1926.79, + 'age': 27, + 'name': 'Rosa Guthrie', + 'gender': 'female', + 'company': 'COMTOURS', + 'email': 'rosaguthrie@comtours.com', + 'phone': '+1 (814) 528-2701', + 'address': '719 Kathleen Court, Morriston, Guam, 4011', + 'bd': '2015-07-02T08:22:18 -06:00' + }, + { + '_id': '5a7b73f7727afbb0fc15653b', + 'index': 45, + 'guid': 'ebbc985b-227e-4954-a8a6-588b2a2bff22', + 'isActive': false, + 'balance': 2464.9, + 'age': 29, + 'name': 'Dillard Carlson', + 'gender': 'male', + 'company': 'COMCUR', + 'email': 'dillardcarlson@comcur.com', + 'phone': '+1 (847) 469-3741', + 'address': '918 Oceanic Avenue, Cochranville, Missouri, 1018', + 'bd': '2016-06-11T11:31:54 -06:00' + }, + { + '_id': '5a7b73f71dd7612e967e01ae', + 'index': 46, + 'guid': '63a2ee7f-2141-4ec5-b1e2-fcdcd62f28ed', + 'isActive': false, + 'balance': 3917.74, + 'age': 25, + 'name': 'Faye Walls', + 'gender': 'female', + 'company': 'EMERGENT', + 'email': 'fayewalls@emergent.com', + 'phone': '+1 (964) 527-3791', + 'address': '947 Judge Street, Nescatunga, Maine, 4928', + 'bd': '2014-06-23T12:46:21 -06:00' + }, + { + '_id': '5a7b73f7b33c73c425db7ee0', + 'index': 47, + 'guid': '61d40a89-af0c-40ca-8970-c54978134e6b', + 'isActive': true, + 'balance': 2213.18, + 'age': 32, + 'name': 'Norma Hooper', + 'gender': 'female', + 'company': 'PARCOE', + 'email': 'normahooper@parcoe.com', + 'phone': '+1 (827) 503-2742', + 'address': '470 Fenimore Street, Hatteras, Texas, 1582', + 'bd': '2015-01-15T12:22:00 -06:00' + }, + { + '_id': '5a7b73f7c30aa4064670cf21', + 'index': 48, + 'guid': '969d77af-b251-4924-82cf-7c787752161d', + 'isActive': false, + 'balance': 3673.94, + 'age': 23, + 'name': 'Lee Wiggins', + 'gender': 'female', + 'company': 'NITRACYR', + 'email': 'leewiggins@nitracyr.com', + 'phone': '+1 (941) 478-3536', + 'address': '958 Flatbush Avenue, Clara, North Carolina, 970', + 'bd': '2018-01-09T11:09:34 -06:00' + }, + { + '_id': '5a7b73f7ecd5a4859f2d94dc', + 'index': 49, + 'guid': 'cdf9b8de-a309-4cb7-80bb-f1b830b8b640', + 'isActive': true, + 'balance': 2166.21, + 'age': 27, + 'name': 'Alvarez Lynch', + 'gender': 'male', + 'company': 'KIGGLE', + 'email': 'alvarezlynch@kiggle.com', + 'phone': '+1 (929) 528-3805', + 'address': '901 Stratford Road, Derwood, Iowa, 1402', + 'bd': '2015-01-08T04:28:57 -06:00' + }, + { + '_id': '5a7b73f7216c8cabc849eea7', + 'index': 50, + 'guid': 'c4175d6a-1560-468e-b682-701c1549b6b1', + 'isActive': false, + 'balance': 3479.39, + 'age': 39, + 'name': 'Oneal Rosario', + 'gender': 'male', + 'company': 'UBERLUX', + 'email': 'onealrosario@uberlux.com', + 'phone': '+1 (951) 572-3027', + 'address': '267 Rockaway Parkway, Chapin, Montana, 7813', + 'bd': '2014-02-10T05:08:13 -06:00' + }, + { + '_id': '5a7b73f78841719bf955b2d9', + 'index': 51, + 'guid': '966c9ce6-9151-47cb-8c71-98c4cd0d2f40', + 'isActive': false, + 'balance': 1625.49, + 'age': 36, + 'name': 'Olsen Stevens', + 'gender': 'male', + 'company': 'EMPIRICA', + 'email': 'olsenstevens@empirica.com', + 'phone': '+1 (871) 403-3377', + 'address': '704 Lamont Court, Saranap, Massachusetts, 3171', + 'bd': '2014-09-17T05:13:13 -06:00' + }, + { + '_id': '5a7b73f7b7b8e578dff0f85c', + 'index': 52, + 'guid': '8269a34f-3a02-47d6-bcb1-8f076bb478f0', + 'isActive': true, + 'balance': 1143.73, + 'age': 27, + 'name': 'Marian Henson', + 'gender': 'female', + 'company': 'ENDIPINE', + 'email': 'marianhenson@endipine.com', + 'phone': '+1 (995) 406-2592', + 'address': '803 Ellery Street, Boykin, Alaska, 8624', + 'bd': '2016-08-28T01:22:51 -06:00' + }, + { + '_id': '5a7b73f737459ec79c91ca75', + 'index': 53, + 'guid': 'badb9342-10fd-4520-ae66-c246e47add8f', + 'isActive': false, + 'balance': 1458.01, + 'age': 23, + 'name': 'Dudley Dickson', + 'gender': 'male', + 'company': 'POLARIA', + 'email': 'dudleydickson@polaria.com', + 'phone': '+1 (860) 428-3250', + 'address': '833 Revere Place, Rockbridge, Illinois, 4628', + 'bd': '2017-01-19T12:36:59 -06:00' + }, + { + '_id': '5a7b73f70ddc6fc11ebf043a', + 'index': 54, + 'guid': '52b1be89-8186-4685-81b7-203c17ed9f89', + 'isActive': true, + 'balance': 2815.76, + 'age': 25, + 'name': 'Earnestine Oneil', + 'gender': 'female', + 'company': 'CYTREK', + 'email': 'earnestineoneil@cytrek.com', + 'phone': '+1 (879) 541-3490', + 'address': '442 Emerald Street, Graniteville, Hawaii, 1302', + 'bd': '2017-07-07T10:34:33 -06:00' + }, + { + '_id': '5a7b73f78b816185ccd2b4b3', + 'index': 55, + 'guid': 'e66850ea-546b-4eb5-ae76-d66b0e727f44', + 'isActive': true, + 'balance': 3645.09, + 'age': 21, + 'name': 'Nicholson Mason', + 'gender': 'male', + 'company': 'TELEQUIET', + 'email': 'nicholsonmason@telequiet.com', + 'phone': '+1 (861) 528-3215', + 'address': '261 Aitken Place, Cecilia, Ohio, 1381', + 'bd': '2016-03-20T08:31:34 -06:00' + }, + { + '_id': '5a7b73f780f8bf8fbe24d75c', + 'index': 56, + 'guid': '40b999cd-00bf-46e0-9107-b44906d832e0', + 'isActive': false, + 'balance': 2477.66, + 'age': 36, + 'name': 'Linda Shaffer', + 'gender': 'female', + 'company': 'ZORK', + 'email': 'lindashaffer@zork.com', + 'phone': '+1 (828) 524-3011', + 'address': '350 Plymouth Street, Waterford, Washington, 6715', + 'bd': '2017-07-09T05:51:11 -06:00' + }, + { + '_id': '5a7b73f741e22fc19ffa6952', + 'index': 57, + 'guid': 'cc2ac19d-7d67-4f60-973a-369160a9c377', + 'isActive': false, + 'balance': 2651.39, + 'age': 20, + 'name': 'Montoya Riggs', + 'gender': 'male', + 'company': 'MARKETOID', + 'email': 'montoyariggs@marketoid.com', + 'phone': '+1 (809) 562-3786', + 'address': '633 Monitor Street, Chicopee, District Of Columbia, 550', + 'bd': '2016-02-05T12:36:05 -06:00' + }, + { + '_id': '5a7b73f7de56ead40c26e69a', + 'index': 58, + 'guid': '6e0b06b8-1199-498c-8002-41f4972aa2d2', + 'isActive': false, + 'balance': 3463.92, + 'age': 28, + 'name': 'Walker Duran', + 'gender': 'male', + 'company': 'GEOFORM', + 'email': 'walkerduran@geoform.com', + 'phone': '+1 (868) 502-2553', + 'address': '550 Kensington Walk, Wyano, Virginia, 7703', + 'bd': '2017-08-18T12:39:37 -06:00' + }, + { + '_id': '5a7b73f70a04fe142269ea8d', + 'index': 59, + 'guid': 'c6733cd5-1e73-4317-b4bc-1a9e597581a4', + 'isActive': true, + 'balance': 3846.35, + 'age': 26, + 'name': 'Suzanne House', + 'gender': 'female', + 'company': 'SYBIXTEX', + 'email': 'suzannehouse@sybixtex.com', + 'phone': '+1 (892) 533-2739', + 'address': '367 Harwood Place, Twilight, Oregon, 9799', + 'bd': '2016-11-26T11:57:18 -06:00' + }, + { + '_id': '5a7b73f7339943d94af3b39d', + 'index': 60, + 'guid': '4ff2c2aa-0573-4be1-a1c8-f684af8a5fbf', + 'isActive': false, + 'balance': 2717.94, + 'age': 26, + 'name': 'Lewis Oconnor', + 'gender': 'male', + 'company': 'EXOZENT', + 'email': 'lewisoconnor@exozent.com', + 'phone': '+1 (954) 582-2660', + 'address': '717 Sutter Avenue, Bartley, Michigan, 1142', + 'bd': '2017-08-21T08:25:00 -06:00' + }, + { + '_id': '5a7b73f7d8e266ad1bc5daa8', + 'index': 61, + 'guid': '94667aad-86fc-4a2c-94fb-11b572307c75', + 'isActive': false, + 'balance': 2725.58, + 'age': 39, + 'name': 'Shelley Bonner', + 'gender': 'female', + 'company': 'INDEXIA', + 'email': 'shelleybonner@indexia.com', + 'phone': '+1 (965) 490-3768', + 'address': '896 Clinton Avenue, Canoochee, Idaho, 1154', + 'bd': '2016-04-11T06:08:29 -06:00' + }, + { + '_id': '5a7b73f7e74a5af674e4cbdd', + 'index': 62, + 'guid': 'ec68c47e-7cbd-485e-8d54-fab1bb6ea008', + 'isActive': true, + 'balance': 1343.87, + 'age': 29, + 'name': 'Mccall Morales', + 'gender': 'male', + 'company': 'QUILITY', + 'email': 'mccallmorales@quility.com', + 'phone': '+1 (939) 455-2610', + 'address': '325 Crystal Street, Harleigh, Wyoming, 5658', + 'bd': '2014-11-20T07:30:04 -06:00' + }, + { + '_id': '5a7b73f7efb231e53a0c94cd', + 'index': 63, + 'guid': '6a8b3f55-406c-4ae8-be59-94a0f8fbd180', + 'isActive': false, + 'balance': 1092.69, + 'age': 37, + 'name': 'Vera Mcpherson', + 'gender': 'female', + 'company': 'CIPROMOX', + 'email': 'veramcpherson@cipromox.com', + 'phone': '+1 (890) 500-3729', + 'address': '771 Beard Street, Rivera, Minnesota, 4726', + 'bd': '2017-07-13T02:47:50 -06:00' + }, + { + '_id': '5a7b73f7e345c5dfc5d636e4', + 'index': 64, + 'guid': '46879caf-76e6-46e0-9b8b-bc17667a81ea', + 'isActive': true, + 'balance': 2077.12, + 'age': 36, + 'name': 'Gregory Roth', + 'gender': 'male', + 'company': 'EARWAX', + 'email': 'gregoryroth@earwax.com', + 'phone': '+1 (806) 595-2477', + 'address': '349 Dunham Place, Sardis, Alabama, 3320', + 'bd': '2017-11-08T02:26:23 -06:00' + }, + { + '_id': '5a7b73f77f5f9d730fab11e0', + 'index': 65, + 'guid': '9cfb8f58-7acf-4a39-bf2b-c90269c33db0', + 'isActive': true, + 'balance': 3503.58, + 'age': 31, + 'name': 'Russell Carver', + 'gender': 'male', + 'company': 'PREMIANT', + 'email': 'russellcarver@premiant.com', + 'phone': '+1 (849) 521-2335', + 'address': '851 Noble Street, Holcombe, Oklahoma, 311', + 'bd': '2016-07-10T10:08:35 -06:00' + }, + { + '_id': '5a7b73f7cab10f461153989c', + 'index': 66, + 'guid': '2562a818-4451-4193-94cd-650d131ff097', + 'isActive': false, + 'balance': 1652.9, + 'age': 21, + 'name': 'Darlene Hurley', + 'gender': 'female', + 'company': 'STELAECOR', + 'email': 'darlenehurley@stelaecor.com', + 'phone': '+1 (868) 492-2270', + 'address': '627 Wilson Street, Loveland, Louisiana, 765', + 'bd': '2017-05-20T12:39:31 -06:00' + }, + { + '_id': '5a7b73f7ecccc997e4160a59', + 'index': 67, + 'guid': '0050170f-0283-481d-9633-dc9d134be121', + 'isActive': true, + 'balance': 3692.88, + 'age': 21, + 'name': 'Lela Bailey', + 'gender': 'female', + 'company': 'AQUOAVO', + 'email': 'lelabailey@aquoavo.com', + 'phone': '+1 (917) 449-2329', + 'address': '121 Adams Street, Malo, Arkansas, 7435', + 'bd': '2016-11-06T04:55:46 -06:00' + } + ] +} \ No newline at end of file diff --git a/src/app/shared/models/event.model.ts b/src/app/shared/models/event.model.ts new file mode 100644 index 00000000..c413f9b9 --- /dev/null +++ b/src/app/shared/models/event.model.ts @@ -0,0 +1,62 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { CalendarEventAction, CalendarEvent } from 'angular-calendar'; +import { + startOfDay, + endOfDay, + subDays, + addDays, + endOfMonth, + isSameDay, + isSameMonth, + addHours +} from 'date-fns'; + +export class EgretCalendarEvent implements CalendarEvent { + _id?: string; + start: Date; + end?: Date; + title: string; + color?: { + primary: string; + secondary: string; + }; + actions?: CalendarEventAction[]; + allDay?: boolean; + cssClass?: string; + resizable?: { + beforeStart?: boolean; + afterEnd?: boolean; + }; + draggable?: boolean; + meta?: { + location: string, + notes: string + }; + + constructor(data?) { + data = data || {}; + this.start = new Date(data.start) || startOfDay(new Date()); + this.end = data.end ? new Date(data.end) : null; + this._id = data._id || ''; + this.title = data.title || ''; + this.color = { + primary: data.color && data.color.primary || '#247ba0', + secondary: data.color && data.color.secondary || '#D1E8FF' + }; + this.draggable = data.draggable || true; + this.resizable = { + beforeStart: data.resizable && data.resizable.beforeStart || true, + afterEnd: data.resizable && data.resizable.afterEnd || true + }; + this.actions = data.actions || []; + this.allDay = data.allDay || false; + this.cssClass = data.cssClass || ''; + this.meta = { + location: data.meta && data.meta.location || '', + notes: data.meta && data.meta.notes || '' + }; + } +} diff --git a/src/app/shared/models/product.model.ts b/src/app/shared/models/product.model.ts new file mode 100644 index 00000000..a3a6baf0 --- /dev/null +++ b/src/app/shared/models/product.model.ts @@ -0,0 +1,23 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +export class Product { + public _id: string; + public name: string; + public description?: string; + public category?: string; + public tags?: string[]; + public price: { + sale: number, + previous?: number + }; + public ratings?: { + rating: number, + ratingCount: number + }; + public features?: string[]; + public photo?: string; + public gallery?: string[]; + public badge?: { text: string, color?: string }; +} diff --git a/src/app/shared/pipes/excerpt.pipe.ts b/src/app/shared/pipes/excerpt.pipe.ts new file mode 100644 index 00000000..dedfa78a --- /dev/null +++ b/src/app/shared/pipes/excerpt.pipe.ts @@ -0,0 +1,14 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { Pipe, PipeTransform } from "@angular/core"; + +@Pipe({ name: 'excerpt' }) +export class ExcerptPipe implements PipeTransform { + transform(text: string, limit: number = 5) { + if(text.length <= limit) + return text; + return text.substring(0, limit) + '...'; + } +} \ No newline at end of file diff --git a/src/app/shared/pipes/get-value-by-key.pipe.ts b/src/app/shared/pipes/get-value-by-key.pipe.ts new file mode 100644 index 00000000..d2eaed65 --- /dev/null +++ b/src/app/shared/pipes/get-value-by-key.pipe.ts @@ -0,0 +1,39 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { Pipe, PipeTransform } from "@angular/core"; + +@Pipe( { + name: "getValueByKey", + pure: false +} ) +export class GetValueByKeyPipe implements PipeTransform { + transform ( value: any[], id: number, property: string ): any { + const filteredObj = value.find( item => { + if ( item.id !== undefined ) { + return item.id === id; + } + + return false; + } ); + + if ( filteredObj ) { + return filteredObj[ property ]; + } + } +} + +@Pipe( { name: 'keys' } ) +export class KeysPipe implements PipeTransform { + transform ( value, args: string[] ): any { + let keys = []; + for ( var enumMember in value ) { + if ( !isNaN( parseInt( enumMember, 10 ) ) ) { + keys.push( { key: enumMember, value: value[ enumMember ] } ); + // Uncomment if you want log + } + } + return keys; + } +} \ No newline at end of file diff --git a/src/app/shared/pipes/relative-time.pipe.ts b/src/app/shared/pipes/relative-time.pipe.ts new file mode 100644 index 00000000..21e5b033 --- /dev/null +++ b/src/app/shared/pipes/relative-time.pipe.ts @@ -0,0 +1,37 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { Pipe, PipeTransform } from "@angular/core"; + +@Pipe({ name: 'relativeTime' }) +export class RelativeTimePipe implements PipeTransform { + transform(value: Date) { + if(!(value instanceof Date)) + value = new Date(value); + + let seconds: number = Math.floor(((new Date()).getTime() - value.getTime()) / 1000); + let interval: number = Math.floor(seconds / 31536000); + + if (interval > 1) { + return interval + " years ago"; + } + interval = Math.floor(seconds / 2592000); + if (interval > 1) { + return interval + " months ago"; + } + interval = Math.floor(seconds / 86400); + if (interval > 1) { + return interval + " days ago"; + } + interval = Math.floor(seconds / 3600); + if (interval > 1) { + return interval + " hours ago"; + } + interval = Math.floor(seconds / 60); + if (interval > 1) { + return interval + " minutes ago"; + } + return Math.floor(seconds) + " seconds ago"; + } +} \ No newline at end of file diff --git a/src/app/shared/pipes/translate.pipe.ts b/src/app/shared/pipes/translate.pipe.ts new file mode 100644 index 00000000..4d2c5880 --- /dev/null +++ b/src/app/shared/pipes/translate.pipe.ts @@ -0,0 +1,16 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { Pipe, PipeTransform } from '@angular/core'; + +@Pipe({ + name: 'translate' +}) +export class TranslatePipe implements PipeTransform { + + transform(value: any, args?: any): any { + return value; + } + +} diff --git a/src/app/shared/search/search-input-over/search-input-over.component.html b/src/app/shared/search/search-input-over/search-input-over.component.html new file mode 100644 index 00000000..db667052 --- /dev/null +++ b/src/app/shared/search/search-input-over/search-input-over.component.html @@ -0,0 +1,13 @@ + + +
+ +
+ +
+ close +
diff --git a/src/app/shared/search/search-input-over/search-input-over.component.scss b/src/app/shared/search/search-input-over/search-input-over.component.scss new file mode 100644 index 00000000..33971da7 --- /dev/null +++ b/src/app/shared/search/search-input-over/search-input-over.component.scss @@ -0,0 +1,44 @@ +/*! + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +.search-bar-wide { + &.open { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 64px; + z-index: 999; + .search-icon-btn { + display: none; + } + div { + display: flex; + } + .search-close { + display: block; + } + } + div { + display: none; + height: 100%; + width: 100%; + input { + height: 100%; + width: 100%; + border: 0; + outline: 0; + padding: 0; + font-weight: 700; + padding-left: 15px; + } + } + .search-close { + display: none; + position: absolute; + top: 20px; + right: 15px; + cursor: pointer; + } +} \ No newline at end of file diff --git a/src/app/shared/search/search-input-over/search-input-over.component.ts b/src/app/shared/search/search-input-over/search-input-over.component.ts new file mode 100644 index 00000000..d5dbaf19 --- /dev/null +++ b/src/app/shared/search/search-input-over/search-input-over.component.ts @@ -0,0 +1,65 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { + Component, + OnInit, + Output, + EventEmitter, + OnDestroy, + Input +} from "@angular/core"; +import { FormControl } from "@angular/forms"; +import { Subscription } from "rxjs"; +import { debounceTime } from "rxjs/operators"; +import { SearchService } from "../search.service"; +import { Router, ActivatedRoute } from "@angular/router"; + +@Component({ + selector: "egret-search-input-over", + templateUrl: "./search-input-over.component.html", + styleUrls: ["./search-input-over.component.scss"] +}) +export class SearchInputOverComponent implements OnInit, OnDestroy { + isOpen: boolean; + @Input('resultPage') resultPage: string; + @Input('placeholder') placeholder: string = "Search here"; + @Output("search") search = new EventEmitter(); + searchCtrl = new FormControl(); + searchCtrlSub: Subscription; + constructor( + private searchService: SearchService, + private router: Router, + private route: ActivatedRoute + ) {} + + ngOnInit() { + this.searchCtrl.valueChanges.pipe(debounceTime(200)) + .subscribe(value => { + this.search.emit(value); + this.searchService.searchTerm.next(value); + }); + } + + ngOnDestroy() { + if (this.searchCtrlSub) { + this.searchCtrlSub.unsubscribe(); + } + } + navigateToResult() { + if(this.resultPage) { + this.router.navigateByUrl(this.resultPage); + } + } + open() { + this.isOpen = true; + this.navigateToResult(); + } + close() { + this.isOpen = false; + } + toggle() { + this.isOpen = !this.isOpen; + } +} diff --git a/src/app/shared/search/search.module.ts b/src/app/shared/search/search.module.ts new file mode 100644 index 00000000..d621772a --- /dev/null +++ b/src/app/shared/search/search.module.ts @@ -0,0 +1,16 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { NgModule } from "@angular/core"; +import { CommonModule } from "@angular/common"; +import { SearchInputOverComponent } from "./search-input-over/search-input-over.component"; +import { ReactiveFormsModule } from "@angular/forms"; +import { MatIconModule, MatButtonModule } from "@angular/material"; + +@NgModule({ + declarations: [SearchInputOverComponent], + exports: [SearchInputOverComponent], + imports: [ReactiveFormsModule, MatIconModule, MatButtonModule, CommonModule] +}) +export class SearchModule {} diff --git a/src/app/shared/search/search.service.ts b/src/app/shared/search/search.service.ts new file mode 100644 index 00000000..60122f18 --- /dev/null +++ b/src/app/shared/search/search.service.ts @@ -0,0 +1,17 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { Injectable } from "@angular/core"; +import { BehaviorSubject } from "rxjs/BehaviorSubject"; +import { Observable } from "rxjs"; + +@Injectable({ + providedIn: "root" +}) +export class SearchService { + public searchTerm: BehaviorSubject = new BehaviorSubject(""); + public searchTerm$: Observable = this.searchTerm.asObservable(); + + constructor() {} +} diff --git a/src/app/shared/services/app-confirm/app-confirm.component.ts b/src/app/shared/services/app-confirm/app-confirm.component.ts new file mode 100644 index 00000000..cae3a31a --- /dev/null +++ b/src/app/shared/services/app-confirm/app-confirm.component.ts @@ -0,0 +1,32 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material'; +import { Component, Inject } from '@angular/core'; + +@Component({ + selector: 'app-confirm', + template: `

{{ data.title }}

+
{{ data.message }}
+
+ +   + + +
`, +}) +export class AppComfirmComponent { + constructor( + public dialogRef: MatDialogRef, + @Inject(MAT_DIALOG_DATA) public data:any + ) {} +} \ No newline at end of file diff --git a/src/app/shared/services/app-confirm/app-confirm.service.ts b/src/app/shared/services/app-confirm/app-confirm.service.ts new file mode 100644 index 00000000..e1a5289b --- /dev/null +++ b/src/app/shared/services/app-confirm/app-confirm.service.ts @@ -0,0 +1,32 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { Observable } from 'rxjs'; +import { MatDialogRef, MatDialog, MatDialogConfig } from '@angular/material'; +import { Injectable } from '@angular/core'; + +import { AppComfirmComponent } from './app-confirm.component'; + +interface confirmData { + title?: string, + message?: string +} + +@Injectable() +export class AppConfirmService { + + constructor(private dialog: MatDialog) { } + + public confirm(data:confirmData = {}): Observable { + data.title = data.title || 'Confirm'; + data.message = data.message || 'Are you sure?'; + let dialogRef: MatDialogRef; + dialogRef = this.dialog.open(AppComfirmComponent, { + width: '380px', + disableClose: true, + data: {title: data.title, message: data.message} + }); + return dialogRef.afterClosed(); + } +} \ No newline at end of file diff --git a/src/app/shared/services/app-loader/app-loader.component.css b/src/app/shared/services/app-loader/app-loader.component.css new file mode 100644 index 00000000..ff06ba20 --- /dev/null +++ b/src/app/shared/services/app-loader/app-loader.component.css @@ -0,0 +1,7 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +.mat-dialog-content { + min-height: 122px; +} \ No newline at end of file diff --git a/src/app/shared/services/app-loader/app-loader.component.html b/src/app/shared/services/app-loader/app-loader.component.html new file mode 100644 index 00000000..d5a3b2d5 --- /dev/null +++ b/src/app/shared/services/app-loader/app-loader.component.html @@ -0,0 +1,10 @@ + + +
+
{{ title }}
+
+ +
+
diff --git a/src/app/shared/services/app-loader/app-loader.component.ts b/src/app/shared/services/app-loader/app-loader.component.ts new file mode 100644 index 00000000..8854a24c --- /dev/null +++ b/src/app/shared/services/app-loader/app-loader.component.ts @@ -0,0 +1,21 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { Component, OnInit } from '@angular/core'; +import { MatDialogRef } from '@angular/material'; + +@Component({ + selector: 'app-app-loader', + templateUrl: './app-loader.component.html', + styleUrls: ['./app-loader.component.css'] +}) +export class AppLoaderComponent implements OnInit { + title; + message; + constructor(public dialogRef: MatDialogRef) {} + + ngOnInit() { + } + +} diff --git a/src/app/shared/services/app-loader/app-loader.service.ts b/src/app/shared/services/app-loader/app-loader.service.ts new file mode 100644 index 00000000..cdd3ef99 --- /dev/null +++ b/src/app/shared/services/app-loader/app-loader.service.ts @@ -0,0 +1,26 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { Injectable } from '@angular/core'; +import { MatDialog, MatDialogRef } from '@angular/material'; +import { Observable } from 'rxjs'; +import { AppLoaderComponent } from './app-loader.component'; + +@Injectable() +export class AppLoaderService { + dialogRef: MatDialogRef; + constructor(private dialog: MatDialog) { } + + public open(title: string = 'Please wait'): Observable { + this.dialogRef = this.dialog.open(AppLoaderComponent, { disableClose: true, backdropClass: 'light-backdrop'}); + this.dialogRef.updateSize('200px'); + this.dialogRef.componentInstance.title = title; + return this.dialogRef.afterClosed(); + } + + public close() { + if(this.dialogRef) + this.dialogRef.close(); + } +} diff --git a/src/app/shared/services/auth/auth.guard.ts b/src/app/shared/services/auth/auth.guard.ts new file mode 100644 index 00000000..5c2aa3fb --- /dev/null +++ b/src/app/shared/services/auth/auth.guard.ts @@ -0,0 +1,21 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { Injectable } from '@angular/core'; +import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, Router } from '@angular/router'; + +@Injectable() +export class AuthGuard implements CanActivate { + public authToken; + private isAuthenticated = true; // Set this value dynamically + + constructor ( private router: Router ) { } + canActivate ( route: ActivatedRouteSnapshot, state: RouterStateSnapshot ) { + if ( this.isAuthenticated ) { + return true + } + this.router.navigate( [ '/sessions/signin' ] ); + return false; + } +} \ No newline at end of file diff --git a/src/app/shared/services/customizer.service.ts b/src/app/shared/services/customizer.service.ts new file mode 100644 index 00000000..e693a931 --- /dev/null +++ b/src/app/shared/services/customizer.service.ts @@ -0,0 +1,159 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { Injectable } from "@angular/core"; +import { Router, NavigationStart } from "@angular/router"; +import { filter } from "rxjs/operators"; +import { LayoutService } from "./layout.service"; + +@Injectable({ + providedIn: "root" +}) +export class CustomizerService { + + + colors = [ + { + class: "black", + active: false + }, + { + class: "white", + active: false + }, + { + class: "slate-gray", + active: false + }, + { + class: "dark-gray", + active: false + }, + { + class: "gray", + active: false + }, + { + class: "dark-purple", + active: false + }, + { + class: "blue", + active: false + }, + { + class: "dark-blue", + active: false + }, + { + class: "indigo", + active: false + }, + { + class: "yellow", + active: false + }, + { + class: "green", + active: false + }, + { + class: "pink", + active: false + }, + { + class: "red", + active: false + } + ]; + selectedSidebarColor; + topbarColors: any[]; + sidebarColors: any[]; + + constructor( + private router: Router, + private layout: LayoutService, + ) { + this.topbarColors = this.getTopbarColors(); + this.sidebarColors = this.getSidebarColors(); + } + + getSidebarColors() { + let sidebarColors = ['black', 'white', 'dark-gray', 'dark-purple', 'dark-blue',]; + return this.colors.filter(color => { + return sidebarColors.includes(color.class); + }) + .map(c => { + c.active = c.class === this.layout.layoutConf.sidebarColor; + return {...c}; + });; + } + + getTopbarColors() { + let topbarColors = ['black', 'white', 'dark-gray', 'dark-purple', 'dark-blue', 'indigo', 'pink', 'red', 'yellow', 'green']; + return this.colors.filter(color => { + return topbarColors.includes(color.class); + }) + .map(c => { + c.active = c.class === this.layout.layoutConf.topbarColor; + return {...c}; + }); + } + + changeSidebarColor(color) { + this.layout.publishLayoutChange({ sidebarColor: color.class }); + this.sidebarColors = this.getSidebarColors(); + } + + changeTopbarColor(color) { + this.layout.publishLayoutChange({ topbarColor: color.class }); + this.topbarColors = this.getTopbarColors(); + } + + removeClass(el, className) { + if (!el || el.length === 0) return; + if (!el.length) { + el.classList.remove(className); + } else { + for (var i = 0; i < el.length; i++) { + el[i].classList.remove(className); + } + } + } + addClass(el, className) { + if (!el) return; + if (!el.length) { + el.classList.add(className); + } else { + for (var i = 0; i < el.length; i++) { + el[i].classList.add(className); + } + } + } + findClosest(el, className) { + if (!el) return; + while (el) { + var parent = el.parentElement; + if (parent && this.hasClass(parent, className)) { + return parent; + } + el = parent; + } + } + hasClass(el, className) { + if (!el) return; + return ( + ` ${el.className} `.replace(/[\n\t]/g, " ").indexOf(` ${className} `) > -1 + ); + } + toggleClass(el, className) { + if (!el) return; + if (this.hasClass(el, className)) { + this.removeClass(el, className); + } else { + this.addClass(el, className); + } + } + +} \ No newline at end of file diff --git a/src/app/shared/services/error-handler.service.ts b/src/app/shared/services/error-handler.service.ts new file mode 100644 index 00000000..d34af389 --- /dev/null +++ b/src/app/shared/services/error-handler.service.ts @@ -0,0 +1,49 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { ApplicationRef, ChangeDetectorRef, ErrorHandler, Injectable, Injector } from '@angular/core'; +import { SnackBar } from '../../services/snack-bar.service'; +import { environment } from '../../../environments/environment'; +import { AnalyticsService } from 'app/core/analytics/analytics.service'; + +@Injectable() +export class ErrorHandlerService extends ErrorHandler { + + errorCount = 0; + + constructor ( protected injector: Injector, private analytics: AnalyticsService ) { + super(); + } + + // https://github.com/angular/angular/issues/17010 + handleError ( error: Error ) { + + if ( !environment.production ) SnackBar.error( error.message ); + if ( !environment.production ) console.error( error ); + + if ( environment.production && this.analytics ) this.analytics.trackError( error ); + if ( environment.production ) SnackBar.error( `${ error.name } : Oops Something Went Wrong` ); + + let increment = 5; + let max = 50; + + // Prevents change detection + let debugCtx = error[ 'ngDebugContext' ]; + let changeDetectorRef = debugCtx && debugCtx.injector.get( ChangeDetectorRef ); + if ( changeDetectorRef ) changeDetectorRef.detach(); + + this.errorCount = this.errorCount + 1; + if ( this.errorCount % increment === 0 ) { + super.handleError( error ); + + if ( this.errorCount === max ) { + + let appRef = this.injector.get( ApplicationRef ); + appRef.tick(); + } + } else if ( this.errorCount === 1 ) { + super.handleError( error ); + } + } +} diff --git a/src/app/shared/services/landing-page.service.ts b/src/app/shared/services/landing-page.service.ts new file mode 100644 index 00000000..b6896342 --- /dev/null +++ b/src/app/shared/services/landing-page.service.ts @@ -0,0 +1,28 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +/* + Only Required if you want to use Angular Landing + (https://themeforest.net/item/angular-landing-material-design-angular-app-landing-page/21198258) +*/ +import { Injectable, Inject } from '@angular/core'; +import { DOCUMENT } from '@angular/platform-browser'; + +@Injectable() +export class LandingPageService { + + constructor( + @Inject(DOCUMENT) private document: Document + ) { } + + public addFix() { + this.document.documentElement.classList.add('landing'); + this.document.body.classList.add('landing'); + } + public removeFix() { + this.document.documentElement.classList.remove('landing'); + this.document.body.classList.remove('landing'); + } + +} diff --git a/src/app/shared/services/layout.service.ts b/src/app/shared/services/layout.service.ts new file mode 100644 index 00000000..92198966 --- /dev/null +++ b/src/app/shared/services/layout.service.ts @@ -0,0 +1,117 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { Injectable, HostListener, Renderer2 } from '@angular/core'; +import { BehaviorSubject } from 'rxjs'; +import { Router } from '@angular/router'; +import { getQueryParam } from '../helpers/url.helper'; +import { ThemeService } from './theme.service'; + +export interface ILayoutConf { + navigationPos?: string; // side, top + sidebarStyle?: string; // full, compact, closed + sidebarCompactToggle?: boolean; // sidebar expandable on hover + sidebarColor?: string; // Sidebar background color http://demos.ui-lib.com/egret-doc/#egret-colors + dir?: string; // ltr, rtl + isMobile?: boolean; // updated automatically + useBreadcrumb?: boolean; // Breadcrumb enabled/disabled + breadcrumb?: string; // simple, title + topbarFixed?: boolean; // Fixed header + topbarColor?: string; // Header background color http://demos.ui-lib.com/egret-doc/#egret-colors + matTheme?: string // material theme. egret-blue, egret-navy, egret-dark-purple, egret-dark-pink + perfectScrollbar?: boolean; +} +export interface ILayoutChangeOptions { + duration?: number, + transitionClass?: boolean +} +interface IAdjustScreenOptions { + browserEvent?: any, + route?: string +} + + +@Injectable({ + providedIn: 'root' +}) +export class LayoutService { + public layoutConf: ILayoutConf; + layoutConfSubject = new BehaviorSubject(this.layoutConf); + layoutConf$ = this.layoutConfSubject.asObservable(); + public isMobile: boolean; + public currentRoute: string; + public fullWidthRoutes = ['shop']; + + constructor( + private router: Router, + private themeService: ThemeService + ) { + this.setAppLayout(); + } + + setAppLayout() { + //******** SET YOUR LAYOUT OPTIONS HERE ********* + this.layoutConf = { + "navigationPos": "side", // side, top + "sidebarStyle": "closed", // full, compact, closed + "sidebarColor": "dark-gray", // http://demos.ui-lib.com/egret-doc/#egret-colors + "sidebarCompactToggle": false, // applied when "sidebarStyle" is "compact" + "dir": "ltr", // ltr, rtl + "useBreadcrumb": false, + "topbarFixed": false, + "topbarColor": "dark-gray", // http://demos.ui-lib.com/egret-doc/#egret-colors + "matTheme": "egret-dark-pink", // egret-blue, egret-navy, egret-dark-purple, egret-dark-pink + "breadcrumb": "simple", // simple, title + "perfectScrollbar": true + } + + //******* Only for demo purpose *** + this.setLayoutFromQuery(); + //********************** + } + + publishLayoutChange(lc: ILayoutConf, opt: ILayoutChangeOptions = {}) { + if(this.layoutConf.matTheme !== lc.matTheme && lc.matTheme) { + this.themeService.changeTheme(this.layoutConf.matTheme, lc.matTheme); + } + + this.layoutConf = Object.assign(this.layoutConf, lc); + this.layoutConfSubject.next(this.layoutConf); + } + + applyMatTheme(r: Renderer2) { + this.themeService.applyMatTheme(r, this.layoutConf.matTheme); + } + + setLayoutFromQuery() { + let layoutConfString = getQueryParam('layout'); + try { + this.layoutConf = JSON.parse(layoutConfString); + // this.publishLayoutChange(this.layoutConf); + } catch (e) { } + } + + adjustLayout(options: IAdjustScreenOptions = {}) { + let sidebarStyle: string; + this.isMobile = this.isSm(); + this.currentRoute = options.route || this.currentRoute; + sidebarStyle = this.isMobile ? 'closed' : this.layoutConf.sidebarStyle; + + if (this.currentRoute) { + this.fullWidthRoutes.forEach(route => { + if(this.currentRoute.indexOf(route) !== -1) { + sidebarStyle = 'closed'; + } + }); + } + + this.publishLayoutChange({ + isMobile: this.isMobile, + sidebarStyle: sidebarStyle + }); + } + isSm() { + return window.matchMedia(`(max-width: 959px)`).matches; + } +} \ No newline at end of file diff --git a/src/app/shared/services/navigation.service.ts b/src/app/shared/services/navigation.service.ts new file mode 100644 index 00000000..91c5376f --- /dev/null +++ b/src/app/shared/services/navigation.service.ts @@ -0,0 +1,78 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { Injectable } from '@angular/core'; +import { BehaviorSubject } from 'rxjs'; + +interface IMenuItem { + type: string, // Possible values: link/dropDown/icon/separator/extLink + name?: string, // Used as display text for item and title for separator type + state?: string, // Router state + icon?: string, // Material icon name + tooltip?: string, // Tooltip text + disabled?: boolean, // If true, item will not be appeared in sidenav. + sub?: IChildItem[], // Dropdown items + badges?: IBadge[] +} +interface IChildItem { + type?: string, + name: string, // Display text + state?: string, // Router state + icon?: string, + sub?: IChildItem[] +} + +interface IBadge { + color: string; // primary/accent/warn/hex color codes(#fff000) + value: string; // Display text +} + +@Injectable() +export class NavigationService { + constructor() { } + + defaultMenu: IMenuItem[] = [ + { + name: 'BLANK', + type: 'link', + icon: 'dashboard', + state: 'others/blank' + }, + { + name: 'DOC', + type: 'extLink', + tooltip: 'Documentation', + icon: 'library_books', + state: 'http://demos.ui-lib.com/egret-doc/' + } + ] + + + // Icon menu TITLE at the very top of navigation. + // This title will appear if any icon type item is present in menu. + iconTypeMenuTitle: string = 'Frequently Accessed'; + // sets iconMenu as default; + menuItems = new BehaviorSubject(this.defaultMenu); + // navigation component has subscribed to this Observable + menuItems$ = this.menuItems.asObservable(); + + // Customizer component uses this method to change menu. + // You can remove this method and customizer component. + // Or you can customize this method to supply different menu for + // different user type. + publishNavigationChange(menuType: string) { + // switch (menuType) { + // case 'separator-menu': + // this.menuItems.next(this.separatorMenu); + // break; + // case 'icon-menu': + // this.menuItems.next(this.iconMenu); + // break; + // default: + // this.menuItems.next(this.defaultMenu); + // } + + this.menuItems.next(this.defaultMenu); + } +} \ No newline at end of file diff --git a/src/app/shared/services/route-parts.service.ts b/src/app/shared/services/route-parts.service.ts new file mode 100644 index 00000000..c1aa14de --- /dev/null +++ b/src/app/shared/services/route-parts.service.ts @@ -0,0 +1,41 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { Injectable } from '@angular/core'; +import { Router, ActivatedRoute, NavigationEnd, ActivatedRouteSnapshot, Params, PRIMARY_OUTLET } from "@angular/router"; + +interface IRoutePart { + title: string, + breadcrumb: string, + params?: Params, + url: string, + urlSegments: any[] +} + +@Injectable() +export class RoutePartsService { + public routeParts: IRoutePart[]; + constructor(private router: Router) {} + + ngOnInit() { + } + generateRouteParts(snapshot: ActivatedRouteSnapshot): IRoutePart[] { + var routeParts = []; + if (snapshot) { + if (snapshot.firstChild) { + routeParts = routeParts.concat(this.generateRouteParts(snapshot.firstChild)); + } + if (snapshot.data['title'] && snapshot.url.length) { + routeParts.push({ + title: snapshot.data['title'], + breadcrumb: snapshot.data['breadcrumb'], + url: snapshot.url[0].path, + urlSegments: snapshot.url, + params: snapshot.params + }); + } + } + return routeParts; + } +} \ No newline at end of file diff --git a/src/app/shared/services/theme.service.ts b/src/app/shared/services/theme.service.ts new file mode 100644 index 00000000..25fa7fee --- /dev/null +++ b/src/app/shared/services/theme.service.ts @@ -0,0 +1,78 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { Injectable, Inject, Renderer2 } from '@angular/core'; +import { DOCUMENT } from '@angular/common'; +import { getQueryParam } from '../helpers/url.helper'; + +export interface ITheme { + name: string, + baseColor?: string, + isActive?: boolean +} + +@Injectable() +export class ThemeService { + public egretThemes :ITheme[] = [{ + "name": "egret-dark-purple", + "baseColor": "#9c27b0", + "isActive": false + }, { + "name": "egret-dark-pink", + "baseColor": "#e91e63", + "isActive": false + }, { + "name": "egret-blue", + "baseColor": "#03a9f4", + "isActive": true + }, { + "name": "egret-navy", + "baseColor": "#10174c", + "isActive": false + }]; + public activatedTheme: ITheme; + private renderer: Renderer2; + constructor( + @Inject(DOCUMENT) private document: Document + ) {} + + // Invoked in AppComponent and apply 'activatedTheme' on startup + applyMatTheme(r: Renderer2, themeName: string) { + this.renderer = r; + + this.activatedTheme = this.egretThemes[1]; + + // *********** ONLY FOR DEMO ********** + this.setThemeFromQuery(); + // ************************************ + + // this.changeTheme(themeName); + this.renderer.addClass(this.document.body, themeName); + + } + + changeTheme(prevTheme, themeName: string) { + this.renderer.removeClass(this.document.body, prevTheme); + this.renderer.addClass(this.document.body, themeName); + this.flipActiveFlag(themeName); + } + flipActiveFlag(themeName:string) { + this.egretThemes.forEach((t) => { + t.isActive = false; + if(t.name === themeName) { + t.isActive = true; + this.activatedTheme = t; + } + }); + } + + // *********** ONLY FOR DEMO ********** + setThemeFromQuery() { + let themeStr = getQueryParam('theme'); + try { + this.activatedTheme = JSON.parse(themeStr); + this.flipActiveFlag(this.activatedTheme.name); + } catch(e) {} + } +} diff --git a/src/app/shared/shared.module.ts b/src/app/shared/shared.module.ts new file mode 100644 index 00000000..e6ce1c10 --- /dev/null +++ b/src/app/shared/shared.module.ts @@ -0,0 +1,205 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { RouterModule } from '@angular/router'; +import { FlexLayoutModule } from '@angular/flex-layout'; +import { TranslateModule } from '@ngx-translate/core'; +import { PerfectScrollbarModule } from 'ngx-perfect-scrollbar'; + +import { + MatButtonModule, + MatCardModule, + MatCheckboxModule, + MatDialogModule, + MatFormFieldModule, + MatGridListModule, + MatIconModule, + MatInputModule, + MatListModule, + MatMenuModule, + MatOptionModule, + MatProgressSpinnerModule, + MatRadioModule, + // MatRippleModule, + MatSelectModule, + MatSidenavModule, + MatSliderModule, + MatSnackBarModule, + MatTableModule, + MatTabsModule, + MatToolbarModule, + MatTooltipModule, + MatTreeModule, +} from '@angular/material'; +// ONLY REQUIRED FOR **SIDE** NAVIGATION LAYOUT +import { HeaderSideComponent } from './components/header-side/header-side.component'; +import { SidebarSideComponent } from './components/sidebar-side/sidebar-side.component'; +// ONLY REQUIRED FOR **TOP** NAVIGATION LAYOUT +import { HeaderTopComponent } from './components/header-top/header-top.component'; +import { SidebarTopComponent } from './components/sidebar-top/sidebar-top.component'; +// ONLY FOR DEMO (Removable without changing any layout configuration) +import { CustomizerComponent } from './components/customizer/customizer.component'; +// ALL TIME REQUIRED +import { AdminLayoutComponent } from './components/layouts/admin-layout/admin-layout.component'; +import { AuthLayoutComponent } from './components/layouts/auth-layout/auth-layout.component'; +import { NotificationsComponent } from './components/notifications/notifications.component'; +import { SidenavComponent } from './components/sidenav/sidenav.component'; +import { BreadcrumbComponent } from './components/breadcrumb/breadcrumb.component'; +import { AppComfirmComponent } from './services/app-confirm/app-confirm.component'; +import { AppLoaderComponent } from './services/app-loader/app-loader.component'; +// DIRECTIVES +import { FontSizeDirective } from './directives/font-size.directive'; +import { ScrollToDirective } from './directives/scroll-to.directive'; +import { AppDropdownDirective } from './directives/dropdown.directive'; +import { DropdownAnchorDirective } from './directives/dropdown-anchor.directive'; +import { DropdownLinkDirective } from './directives/dropdown-link.directive'; +import { EgretSideNavToggleDirective } from './directives/egret-side-nav-toggle.directive'; +// PIPES +import { RelativeTimePipe } from './pipes/relative-time.pipe'; +import { ExcerptPipe } from './pipes/excerpt.pipe'; +import { GetValueByKeyPipe, KeysPipe } from './pipes/get-value-by-key.pipe'; +// SERVICES +import { ThemeService } from './services/theme.service'; +import { NavigationService } from './services/navigation.service'; +import { RoutePartsService } from './services/route-parts.service'; +import { AuthGuard } from './services/auth/auth.guard'; +import { AppConfirmService } from './services/app-confirm/app-confirm.service'; +import { AppLoaderService } from './services/app-loader/app-loader.service'; +import { ButtonLoadingComponent } from './components/button-loading/button-loading.component'; +import { SearchModule } from './search/search.module'; + +import { StatusBarComponent } from '../views/editor/status-bar/status-bar.component'; +import { ComponentContainerDirective } from './directives/component-container.directive'; +import { DoubleFieldComponent } from './fields/double-field/double-field.component'; +import { EnumFieldComponent } from './fields/enum-field/enum-field.component'; +import { ImportFileDialogComponent } from './dialogs/import-file-dialog/import-file-dialog.component'; +import { ObjectInspectorComponent } from './components/object-inspector/object-inspector.component'; +import { SearchPipe } from 'app/core/pipes/search.pipe'; +import { FooterBottomComponent } from './components/footer-bottom/footer-bottom.component'; +import { TranslatePipe } from './pipes/translate.pipe'; +import { PropBrowserComponent } from './components/prop-browser/prop-browser.component'; +import { TrackDirective } from 'app/core/analytics/track.directive'; +import { StringFieldComponent } from './fields/string-field/string-field.component'; +import { ColorFieldComponent } from './fields/color-field/color-field.component'; +import { ColorPickerModule } from 'ngx-color-picker'; +import { ColorSketchModule } from 'ngx-color/sketch'; +import { ColorChromeModule } from 'ngx-color/chrome'; +import { DropdownFieldComponent } from './fields/dropdown-field/dropdown-field.component'; +import { SplashComponent } from './components/splash/splash.component'; + +/* + Only Required if you want to use Angular Landing + (https://themeforest.net/item/angular-landing-material-design-angular-app-landing-page/21198258) +*/ +// import { LandingPageService } from '../shared/services/landing-page.service'; + +const classesToInclude = [ + HeaderTopComponent, + SidebarTopComponent, + SidenavComponent, + NotificationsComponent, + SidebarSideComponent, + HeaderSideComponent, + AdminLayoutComponent, + AuthLayoutComponent, + BreadcrumbComponent, + AppComfirmComponent, + AppLoaderComponent, + CustomizerComponent, + ButtonLoadingComponent, + FontSizeDirective, + ScrollToDirective, + AppDropdownDirective, + DropdownAnchorDirective, + DropdownLinkDirective, + ComponentContainerDirective, + EgretSideNavToggleDirective, + RelativeTimePipe, + ExcerptPipe, + GetValueByKeyPipe, + KeysPipe, + StatusBarComponent, + ImportFileDialogComponent, + ObjectInspectorComponent, + SearchPipe, + FooterBottomComponent, + SplashComponent, + TranslatePipe, + + // Fields + DoubleFieldComponent, + DropdownFieldComponent, + EnumFieldComponent, + StringFieldComponent, + ColorFieldComponent, + PropBrowserComponent, + + // Material + + // Directive + TrackDirective, + +]; + +@NgModule( { + imports: [ + CommonModule, + FormsModule, + ReactiveFormsModule, + RouterModule, + FlexLayoutModule, + TranslateModule, + MatSliderModule, + MatSidenavModule, + MatListModule, + MatTooltipModule, + MatOptionModule, + MatSelectModule, + MatMenuModule, + MatTabsModule, + MatSnackBarModule, + MatGridListModule, + MatToolbarModule, + MatIconModule, + MatButtonModule, + MatRadioModule, + MatCheckboxModule, + MatCardModule, + MatProgressSpinnerModule, + // MatRippleModule, + MatDialogModule, + SearchModule, + PerfectScrollbarModule, + MatTreeModule, + MatFormFieldModule, + MatInputModule, + MatTableModule, + MatListModule, + MatDialogModule, + ColorPickerModule, + ColorSketchModule, + ColorChromeModule, + ], + entryComponents: [ + AppComfirmComponent, + AppLoaderComponent, + ImportFileDialogComponent + ], + providers: [ + ThemeService, + NavigationService, + RoutePartsService, + AuthGuard, + AppConfirmService, + AppLoaderService + // LandingPageService + ], + declarations: classesToInclude, + exports: [ classesToInclude, MatTreeModule, MatSelectModule, MatListModule, MatDialogModule ] +} ) +export class SharedModule { +} diff --git a/src/app/shared/utils/colors.service.ts b/src/app/shared/utils/colors.service.ts new file mode 100644 index 00000000..8413725a --- /dev/null +++ b/src/app/shared/utils/colors.service.ts @@ -0,0 +1,47 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +export class COLOR { + + static RED = 0xFF0000; + static GREEN = 0x00FF00; + static DARKGREEN = 0x006400; + static YELLOW = 0xFFFF00; + static BLUE = 0x0000FF; + static SKYBLUE = 0x00FFFF; + static DARKBLUE = 0x00008B; + static ORANGE = 0xFF4500; + static GOLD = 0xFFD700; + + static DEFAULT_BOX_COLOR = 0xff0000; + static HIGHTLIGHT_BOX_COLOR = 0x00ff00; + static CROSSHAIR_COLOR = 0x00ff00; + static DASHED_LINE_COLOR = 0xff00ff; + static MAGENTA = 0xFF00FF; + static BLACK = 0x000000; + static LIGHTGRAY = 0x666666; + static GRAY = 0x333333; + static DARKGRAY = 0x555555; + static FORESTGREEN = 0x228B22; + static LIGHTGREEN = 0x008000; + static WHITE = 0xFFFFFF; + + private static rgbToHex ( rgb ) { + + var hex = Number( rgb ).toString( 16 ); + + if ( hex.length < 2 ) hex = "0" + hex; + + return hex; + } + + static fullColorHex ( r: number, g: number, b: number ): string { + + var red = this.rgbToHex( r ); + var green = this.rgbToHex( g ); + var blue = this.rgbToHex( b ); + + return red + green + blue; + } +} diff --git a/src/app/utils/maths.spec.ts b/src/app/utils/maths.spec.ts new file mode 100644 index 00000000..cabac2ab --- /dev/null +++ b/src/app/utils/maths.spec.ts @@ -0,0 +1,21 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { Maths } from './maths'; + +describe( 'Maths', () => { + + it( 'should give correct number for randomNumberBetweenExcept', () => { + + const numbers = [ 1, 2, 3, 4, 5 ]; + + const result = Maths.randomNumberBetweenExcept( 0, 10, numbers ); + + const includes = numbers.includes( result, 0 ); + + expect( includes ).toBe( false ); + + } ); + +} ); diff --git a/src/app/utils/maths.ts b/src/app/utils/maths.ts new file mode 100644 index 00000000..59a3a323 --- /dev/null +++ b/src/app/utils/maths.ts @@ -0,0 +1,562 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { TvLaneSide, TvSide } from 'app/modules/tv-map/models/tv-common'; +import { Vector2, Vector3 } from 'three'; + +export class Maths { + + /** + * + */ + public static readonly M_PI = 3.1415926535; + + /** + * + */ + public static readonly M_PI_2 = 1.5707963267948966; + + /** + * Degrees-to-radians conversion constant (Read Only). + */ + public static readonly Deg2Rad = 0.0174532924; + + /** + * Radians-to-degrees conversion constant (Read Only). + */ + public static readonly Rad2Deg = 57.29578; + + /** + * Speed to Kilometer per hour + */ + public static readonly Speed2KPH = 0.27777777777; + + /** + * Speed to Miles per hour + */ + public static readonly Speed2MPH = 0.44702726866; + + /** + * A very small positive value + */ + public static readonly Epsilon = 0.00000000001; + + public static sinHdgPlusPiO2 ( laneSide: TvLaneSide, hdg: number ): number { + + if ( laneSide == TvLaneSide.LEFT ) { + return Math.sin( hdg + Maths.M_PI_2 ); + } + + return -Math.sin( hdg + Maths.M_PI_2 ); + } + + public static cosHdgPlusPiO2 ( laneSide: TvLaneSide, hdg: number ): number { + + if ( laneSide == TvLaneSide.LEFT ) { + return Math.cos( hdg + Maths.M_PI_2 ); + } + + return -Math.cos( hdg + Maths.M_PI_2 ); + } + + public static areaOfTriangle ( A: Vector3, B: Vector3, C: Vector3 ): number { + + // https://www.mathopenref.com/coordtrianglearea.html + const area = 0.5 * ( ( A.x * ( B.y - C.y ) ) + ( B.x * ( C.y - A.y ) ) + ( C.x * ( A.y - B.y ) ) ); + + return Math.abs( area ); + } + + static heightOfTriangle ( A: Vector3, B: Vector3, C: Vector3 ): number { + + const area = this.areaOfTriangle( A, B, C ); + + const base = A.distanceTo( C ); + + const height = ( 2 * area ) / base; + + return height; + } + + static findSide ( point: Vector3, rayOrigin: Vector3, rayHdg: number ): TvSide { + + // find the end of the chord line + const x = rayOrigin.x + Math.cos( rayHdg ) * 100; + const y = rayOrigin.y + Math.sin( rayHdg ) * 100; + const z = 0; + + const rayEnd = new Vector3( x, y, z ); + + return this.direction( rayOrigin, rayEnd, point ); + } + + public static direction ( start: Vector3, end: Vector3, point: Vector3 ): TvSide { + + const a = start.clone(); + const b = end.clone(); + const c = point.clone(); + + // var dot = a.x * b.x + a.y * b.y; + // if ( dot > 0 ) + // console.log( '<90 degrees' ); + // else if ( dot < 0 ) + // console.log( '>90 degrees' ); + // else + // console.log( '90 degrees' ); + + // subtracting co-ordinates of point A from + // B and P, to make A as origin + b.x -= a.x; + b.y -= a.y; + c.x -= a.x; + c.y -= a.y; + + // Determining cross Product + let cross_product = b.x * c.y - b.y * c.x; + + // return RIGHT if cross product is positive + if ( cross_product > 0 ) + return TvSide.LEFT; + + // return LEFT if cross product is negative + else if ( cross_product < 0 ) + return TvSide.RIGHT; + + else + console.error( 'unknown side' ); + + // return ZERO if cross product is zero. + // return ZERO; + } + + static linearInterpolation ( x: number, y: number, t: number ) { + + return ( 1 - t ) * x + t * y; + + } + + static linearInterpolationVector3 ( p1: Vector3, p2: Vector3, t: number ) { + + const a = new Vector3(); + + a.x = this.linearInterpolation( p1.x, p2.x, t ); + a.y = this.linearInterpolation( p1.y, p2.y, t ); + a.z = this.linearInterpolation( p1.z, p2.z, t ); + + return a; + } + + public static cosineInterpolation ( x, y, t ) { + + const t2 = ( 1 - Math.cos( t * Math.PI ) ) * 0.5; + + return ( x * ( 1 - t2 ) + y * t2 ); + } + + public static sineInterpolation ( x, y, t ) { + + const t2 = ( 1 - Math.cos( t * Math.PI ) ) * 0.5; + + return ( x * ( 1 - t2 ) + y * t2 ); + } + + public static randomNumberBetween ( min: number, max: number ) { + + return Math.floor( Math.random() * ( max - min + 1 ) + min ); + + } + + public static randomFloatBetween ( min: number, max: number ) { + + return Math.random() * ( max - min ) + min; + + } + + public static randomNumberBetweenExcept ( min: number, max: number, numbersToAvoid: number[] ): number { + + let found = false; + + let tries = 0; + + while ( !found ) { + + tries++; + + if ( tries > 3 ) break; + + const randomNumber = this.randomNumberBetween( min, max ); + + const used = numbersToAvoid.includes( randomNumber, 0 ); + + if ( !used ) { + + found = true; + + return randomNumber; + + } + + } + + return null; + } + + public static randomFloat () { + + return Math.random(); + } + + static cubicInterpolation ( x: number, y: number, t: any ) { + + return y; + + } + + static moveTowards ( v1: Vector3, v2: Vector3, maxDistanceDelta: number ) { + + const current = v1.clone(); + const target = v2.clone(); + + const normalised = target.sub( current ).normalize(); + + return current.add( normalised.multiplyScalar( maxDistanceDelta ) ); + + } + + static angle ( p1: Vector3, p2: Vector3, p3: Vector3 ): number { + + const v1 = p1.clone(); + const v2 = p2.clone(); + const v3 = p3.clone(); + + const a = v1.clone().sub( v2 ); + const b = v1.clone().sub( v3 ); + + const dot = a.dot( b ); + + const mA = a.length(); + const mB = b.length(); + + return Math.acos( ( dot / ( mA * mB ) ) ); + + // return Math.atan2( p3.y - p1.y, p3.x - p1.x ) - Math.atan2( p2.y - p1.y, p2.x - p1.x ); + + } + + /** + * Check intersection between 2 line segments + * + * @param p1 + * @param q1 + * @param p2 + * @param q2 + * @returns boolean + */ + static doLineSegmentIntersect ( p1: Vector3, q1: Vector3, p2: Vector3, q2: Vector3 ): boolean { + + // Find the four orientations needed for general and + // special cases + let o1 = this.orientation( p1, q1, p2 ); + let o2 = this.orientation( p1, q1, q2 ); + let o3 = this.orientation( p2, q2, p1 ); + let o4 = this.orientation( p2, q2, q1 ); + + // General case + if ( o1 != o2 && o3 != o4 ) + return true; + + // Special Cases + // p1, q1 and p2 are colinear and p2 lies on segment p1q1 + if ( o1 == 0 && this.onSegment( p1, p2, q1 ) ) return true; + + // p1, q1 and q2 are colinear and q2 lies on segment p1q1 + if ( o2 == 0 && this.onSegment( p1, q2, q1 ) ) return true; + + // p2, q2 and p1 are colinear and p1 lies on segment p2q2 + if ( o3 == 0 && this.onSegment( p2, p1, q2 ) ) return true; + + // p2, q2 and q1 are colinear and q1 lies on segment p2q2 + if ( o4 == 0 && this.onSegment( p2, q1, q2 ) ) return true; + + return false; + } + + static onSegment ( p: Vector3, q: Vector3, r: Vector3 ) { + + if ( + q.x <= Math.max( p.x, r.x ) && + q.x >= Math.min( p.x, r.x ) && + q.y <= Math.max( p.y, r.y ) && + q.y >= Math.min( p.y, r.y ) + ) { + return true; + } + + return false; + } + + // To find orientation of ordered triplet (p, q, r). + // The function returns following values + // 0 --> p, q and r are colinear + // 1 --> Clockwise + // 2 --> Counterclockwise + static orientation ( p: Vector3, q: Vector3, r: Vector3 ) { + + // See https://www.geeksforgeeks.org/orientation-3-ordered-points/ + // for details of below formula. + let val = ( q.y - p.y ) * ( r.x - q.x ) - + ( q.x - p.x ) * ( r.y - q.y ); + + if ( val == 0 ) return 0; // colinear + + return ( val > 0 ) ? 1 : 2; // clock or counterclock wise + } + + /** + * Get point of intersection of two line segments AB with CD + * @param A + * @param B + * @param C + * @param D + */ + static lineLineIntersection ( A: Vector3, B: Vector3, C: Vector3, D: Vector3 ): Vector3 { + + // Line AB represented as a1x + b1y = c1 + const a1 = B.y - A.y; + const b1 = A.x - B.x; + const c1 = a1 * A.x + b1 * A.y; + + // Line CD represented as a2x + b2y = c2 + const a2 = D.y - C.y; + const b2 = C.x - D.x; + const c2 = a2 * ( C.x ) + b2 * ( C.y ); + + const determinant = a1 * b2 - a2 * b1; + + if ( determinant === 0 ) { + // The lines are parallel. This is simplified + // by returning a pair of FLT_MAX + return new Vector3( Number.MAX_VALUE, Number.MAX_VALUE, 0 ); + + } + + const x = ( b2 * c1 - b1 * c2 ) / determinant; + const y = ( a1 * c2 - a2 * c1 ) / determinant; + + return new Vector3( x, y, 0 ); + } + + /** + * Get points of intersection of 2 points with headings + * @param p1 + * @param p1Heading angle in radians + * @param p2 + * @param p2Heading angle in radians + */ + static lineLineIntersection_2 ( p1: Vector3, p1Heading: number, p2: Vector3, p2Heading: number ): Vector3 { + + let a = new Vector3( + p1.x + Math.cos( p1Heading ) * 100, + p1.y + Math.sin( p1Heading ) * 100, + 0 + ) + + let b = new Vector3( + p2.x + Math.cos( p2Heading ) * 100, + p2.y + Math.sin( p2Heading ) * 100, + 0 + ) + + return this.lineLineIntersection( p1, a, p2, b ); + } + + static findRadius ( p1: Vector3, p1Heading: number, p3: Vector3, p3Heading: number ) { + + const distance = 1; + + const p2 = new Vector3( + p1.x + Math.cos( p1Heading + Maths.M_PI_2 ) * distance, + p1.y + Math.sin( p1Heading + Maths.M_PI_2 ) * distance + ); + + const p4 = new Vector3( + p3.x + Math.cos( p3Heading + Maths.M_PI_2 ) * distance, + p3.y + Math.sin( p3Heading + Maths.M_PI_2 ) * distance + ); + + const center = Maths.lineLineIntersection( p1, p2, p3, p4 ); + + const radius = p1.distanceTo( center ); + + // or + // const radius = p3.distanceTo( center ); + + return { + radius, + center + }; + + } + + static clamp ( num: number, min: number, max: number ) { + + return num <= min ? min : num >= max ? max : num; + + } + + static approxEquals ( a: number, b: number, precision = 0.00001 ): boolean { + + return Math.abs( a - b ) <= precision; + + } + + static slope ( A: Vector2 | Vector3, B: Vector2 | Vector3 ) { + + return ( B.y - A.y ) / ( B.x - A.x ); + + } + + static isPointOnLine ( A: Vector3, B: Vector3, C: Vector3 ): boolean { + + // https://stackoverflow.com/a/17693146 + + let AC = A.distanceTo( C ) + B.distanceTo( C ); + let AB = A.distanceTo( B ); + + return Maths.approxEquals( AC, AB ); + + // + // OLD LOGIC BELOW NOT WORKING + + // const slope = this.slope( A, B ); + + // if ( slope == Infinity ) { + + // return C.x === B.x && + + // A.y <= C.y && + + // C.y <= B.y && + + // A.y <= B.y; + + // } else { + + // const on = C.y - A.y == slope * ( C.x - A.x ); + + // const between = + + // Math.min( A.x, B.x ) <= C.x && + + // C.x <= Math.max( A.x, B.x ) && + + // Math.min( A.y, B.y ) <= C.y && + + // C.y <= Math.max( A.y, B.y ); + + + // return on && between; + + // } + } + + /** + * + * @param A start of line + * @param B end of line + * @param C center of arc/circle + * @param R radius of arc/circle + */ + static getLineArcIntersections ( A: Vector3, B: Vector3, C: Vector3, R: number ): Vector3[] { + + // TODO: need fix, arc theta check is not being done + + // https://revisionmaths.com/advanced-level-maths-revision/pure-maths/geometry/equation-circle + // https://stackoverflow.com/a/1088058 + // equation of circle with center (a,b) with radius r is + // + // (x-a)^2 + (y-b)^2 = r^2 + // + + // line segment + // const A = new Vector3( 0, 0, 0 ); + // const B = new Vector3( 0, 150, 0 ); + // const slope = Maths.slope( A, B ); + + // center of circle + // const C = new Vector3( 0, 0, 0 ); + // const R = 100; + + const LAB = A.distanceTo( B ); + + // direction vector + const D = B.sub( A ).divideScalar( LAB ); + // const D = new Vector3( + // ( B.x - A.x ) / LAB, + // ( B.y - A.y ) / LAB, + // 0 + // ); + + // compute the distance between the points A and E, where + // E is the point of AB closest the circle center (Cx, Cy) + const t = D.x * ( C.x - A.x ) + D.y * ( C.y - A.y ) + + const E = new Vector3( + t * D.x + A.y, + t * D.y + A.y, + 0 + ); + + const LEC = E.distanceTo( C ); + + // line intersects + if ( LEC < R ) { + + // compute distance from t to circle intersection point + const dt = Math.sqrt( R * R - LEC * LEC ); + + // compute first intersection point + const F = new Vector3( + ( t - dt ) * D.x + A.x, + ( t - dt ) * D.y + A.y, + 0 + ); + + // compute second intersection point + const G = new Vector3( + ( t + dt ) * D.x + A.x, + ( t + dt ) * D.y + A.y, + 0 + ); + + const intersections = []; + + + const F_online = Maths.isPointOnLine( A, B, F ); + + if ( F_online ) intersections.push( F ); + + const G_online = Maths.isPointOnLine( A, B, G ); + + if ( G_online ) intersections.push( G ); + + return intersections; + + // const isXOnLine = Maths.isPointOnLine( A, B, new Vector3( 0, 10, 0 ) ); + + } else if ( Maths.approxEquals( LEC, R ) ) { + + // TODO: Return tanget also in future + // tangent point to circle is E + + return []; + + } else { + + // line does not intersect + return []; + + } + } +} diff --git a/src/app/utils/spiral-utils.ts b/src/app/utils/spiral-utils.ts new file mode 100644 index 00000000..ac4fcdf4 --- /dev/null +++ b/src/app/utils/spiral-utils.ts @@ -0,0 +1,213 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +/* ====== LOCAL VARIABLES ====== */ + +const M_PI = Math.PI; + +/* S(x) for small x */ +const sn = [ + -2.99181919401019853726e3, + 7.08840045257738576863e5, + -6.29741486205862506537e7, + 2.54890880573376359104e9, + -4.42979518059697779103e10, + 3.18016297876567817986e11, +]; +const sd = [ + /* 1.00000000000000000000E0,*/ + 2.81376268889994315696e2, + 4.55847810806532581675e4, + 5.1734388877009640073e6, + 4.19320245898111231129e8, + 2.2441179564534092094e10, + 6.07366389490084639049e11, +]; + +/* C(x) for small x */ +const cn = [ + -4.98843114573573548651e-8, + 9.50428062829859605134e-6, + -6.45191435683965050962e-4, + 1.88843319396703850064e-2, + -2.05525900955013891793e-1, + 9.99999999999999998822e-1, +]; +const cd = [ + 3.99982968972495980367e-12, + 9.15439215774657478799e-10, + 1.25001862479598821474e-7, + 1.22262789024179030997e-5, + 8.68029542941784300606e-4, + 4.12142090722199792936e-2, + 1.00000000000000000118, +]; + +/* Auxiliary function f(x) */ +const fn = [ + 4.21543555043677546506e-1, + 1.43407919780758885261e-1, + 1.15220955073585758835e-2, + 3.450179397825740279e-4, + 4.63613749287867322088e-6, + 3.05568983790257605827e-8, + 1.02304514164907233465e-10, + 1.72010743268161828879e-13, + 1.34283276233062758925e-16, + 3.76329711269987889006e-20, +]; +const fd = [ + /* 1.00000000000000000000E0,*/ + 7.51586398353378947175e-1, + 1.16888925859191382142e-1, + 6.44051526508858611005e-3, + 1.55934409164153020873e-4, + 1.8462756734893054587e-6, + 1.12699224763999035261e-8, + 3.60140029589371370404e-11, + 5.8875453362157841001e-14, + 4.52001434074129701496e-17, + 1.25443237090011264384e-20, +]; + +/* Auxiliary function g(x) */ +const gn = [ + 5.04442073643383265887e-1, + 1.97102833525523411709e-1, + 1.87648584092575249293e-2, + 6.84079380915393090172e-4, + 1.15138826111884280931e-5, + 9.82852443688422223854e-8, + 4.45344415861750144738e-10, + 1.08268041139020870318e-12, + 1.37555460633261799868e-15, + 8.36354435630677421531e-19, + 1.86958710162783235106e-22, +]; +const gd = [ + /* 1.00000000000000000000E0,*/ + 1.47495759925128324529, + 3.37748989120019970451e-1, + 2.53603741420338795122e-2, + 8.14679107184306179049e-4, + 1.27545075667729118702e-5, + 1.04314589657571990585e-7, + 4.60680728146520428211e-10, + 1.10273215066240270757e-12, + 1.38796531259578871258e-15, + 8.39158816283118707363e-19, + 1.86958710162783236342e-22, +]; + +function polevl ( x, coef, n ) { + + let ans; + let p = 0; + let i; + + ans = coef[ p++ ]; + i = n; + + do { + ans = ans * x + coef[ p++ ]; + } while ( --i ); + + return ans; +} + +function p1evl ( x, coef, n ) { + + let ans; + let p = 0; + let i; + + ans = x + coef[ p++ ]; + i = n - 1; + + do { + ans = ans * x + coef[ p++ ]; + } while ( --i ); + + return ans; +} + +function fresnel ( xxa, ssa, cca ) { + let f, g, cc, ss, c, s, t, u; + let x, x2; + + x = Math.abs( xxa ); + x2 = x * x; + + if ( x2 < 2.5625 ) { + t = x2 * x2; + ss = ( x * x2 * polevl( t, sn, 5 ) ) / p1evl( t, sd, 6 ); + cc = ( x * polevl( t, cn, 5 ) ) / polevl( t, cd, 6 ); + } else if ( x > 36974.0 ) { + cc = 0.5; + ss = 0.5; + } else { + x2 = x * x; + t = M_PI * x2; + u = 1.0 / ( t * t ); + t = 1.0 / t; + f = 1.0 - ( u * polevl( u, fn, 9 ) ) / p1evl( u, fd, 10 ); + g = ( t * polevl( u, gn, 10 ) ) / p1evl( u, gd, 11 ); + + t = M_PI * 0.5 * x2; + c = Math.cos( t ); + s = Math.sin( t ); + t = M_PI * x; + cc = 0.5 + ( f * s - g * c ) / t; + ss = 0.5 - ( f * c + g * s ) / t; + } + + if ( xxa < 0.0 ) { + cc = -cc; + ss = -ss; + } + + cca = cc; + ssa = ss; + + return { y: ssa, x: cca }; +} + +function odrSpiral ( s, cDot, x, y, t ) { + + let a; + + a = 1.0 / Math.sqrt( Math.abs( cDot ) ); + a *= Math.sqrt( M_PI ); + + let f = fresnel( s / a, y, x ); + + x = f.x; + y = f.y; + + x *= a; + y *= a; + + if ( cDot < 0.0 ) y *= -1.0; + + t = s * s * cDot * 0.5; + + return { x, y, t }; +} + +export class SpiralUtils { + + public static fresnel ( xxa, ssa, cca ) { + + return fresnel( xxa, ssa, cca ); + + } + + static odrSpiral ( s, cDot, x, y, t ) { + + return odrSpiral( s, cDot, x, y, t ); + + } + +} + diff --git a/src/app/views/editor/dialogs/export-glb-dialog/export-glb-dialog.component.html b/src/app/views/editor/dialogs/export-glb-dialog/export-glb-dialog.component.html new file mode 100644 index 00000000..ae75227f --- /dev/null +++ b/src/app/views/editor/dialogs/export-glb-dialog/export-glb-dialog.component.html @@ -0,0 +1,17 @@ + + +

Export GLB

+
+ + Filename + + +
+ +
+
+ + +
\ No newline at end of file diff --git a/src/app/views/editor/dialogs/export-glb-dialog/export-glb-dialog.component.ts b/src/app/views/editor/dialogs/export-glb-dialog/export-glb-dialog.component.ts new file mode 100644 index 00000000..099d0326 --- /dev/null +++ b/src/app/views/editor/dialogs/export-glb-dialog/export-glb-dialog.component.ts @@ -0,0 +1,43 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { Component, OnInit } from '@angular/core'; +import { MatDialogRef } from '@angular/material'; +import { ExporterService } from 'app/services/exporter.service'; + +@Component( { + selector: 'app-export-glb-dialog', + templateUrl: './export-glb-dialog.component.html' +} ) +export class ExportGlbDialog implements OnInit { + + // popup window implementation + // https://stackblitz.com/edit/angular-open-window-prxgi7?file=src%2Fapp%2Fapp.component.html + + filename: string = 'road.glb'; + + forcePowerOfTwoTextures: boolean = true; + + constructor ( + private dialogRef: MatDialogRef, + private exporter: ExporterService, + ) { + } + + ngOnInit () { + + // this.exporter.exportGLB(); + + } + + export () { + + this.exporter.exportGLB( this.filename ); + + this.dialogRef.close(); + + } + + +} diff --git a/src/app/views/editor/dialogs/export-opendrive-dialog/export-opendrive-dialog.component.html b/src/app/views/editor/dialogs/export-opendrive-dialog/export-opendrive-dialog.component.html new file mode 100644 index 00000000..ff6592b2 --- /dev/null +++ b/src/app/views/editor/dialogs/export-opendrive-dialog/export-opendrive-dialog.component.html @@ -0,0 +1,7 @@ + + +

+ export-opendrive-dialog works! +

diff --git a/src/app/views/editor/dialogs/export-opendrive-dialog/export-opendrive-dialog.component.ts b/src/app/views/editor/dialogs/export-opendrive-dialog/export-opendrive-dialog.component.ts new file mode 100644 index 00000000..03ca49b2 --- /dev/null +++ b/src/app/views/editor/dialogs/export-opendrive-dialog/export-opendrive-dialog.component.ts @@ -0,0 +1,18 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { Component, OnInit } from '@angular/core'; + +@Component( { + selector: 'app-export-opendrive-dialog', + templateUrl: './export-opendrive-dialog.component.html' +} ) +export class ExportOpenDriveDialog implements OnInit { + + constructor () { } + + ngOnInit () { + } + +} diff --git a/src/app/views/editor/editor.component.css b/src/app/views/editor/editor.component.css new file mode 100644 index 00000000..707523f7 --- /dev/null +++ b/src/app/views/editor/editor.component.css @@ -0,0 +1,4 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + diff --git a/src/app/views/editor/editor.component.html b/src/app/views/editor/editor.component.html new file mode 100644 index 00000000..5988ebc8 --- /dev/null +++ b/src/app/views/editor/editor.component.html @@ -0,0 +1,33 @@ + + +
+ +
+ + + + + + + + + + + + + + + +
+ + +
+ + + +
+ + +
\ No newline at end of file diff --git a/src/app/views/editor/editor.component.ts b/src/app/views/editor/editor.component.ts new file mode 100644 index 00000000..5e142a31 --- /dev/null +++ b/src/app/views/editor/editor.component.ts @@ -0,0 +1,77 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { AfterContentInit, Component, HostListener, OnInit } from '@angular/core'; +import { MatDialog } from '@angular/material'; +import { AnalyticsService } from '../../core/analytics/analytics.service'; +import { NewRoadDialogComponent } from '../../modules/tv-map/dialogs/new-road-dialog/new-road-dialog.component'; +import { KeyboardInput } from '../../core/input'; +import { CommandHistory } from '../../services/command-history'; +import { MainFileService } from 'app/services/main-file.service'; + +@Component( { + selector: 'app-editor', + templateUrl: './editor.component.html', + styleUrls: [ './editor.component.css' ], +} ) +export class EditorComponent implements OnInit, AfterContentInit { + + constructor ( + private dialog: MatDialog, + private analytics: AnalyticsService, + private mainFileService: MainFileService + ) { + + } + + + ngOnInit () { + + this.mainFileService.newFile(); + + } + + ngAfterContentInit (): void { + + // setTimeout( () => { + + // this.showNewScenarioDialog(); + + // }, 300 ); + + } + + showNewScenarioDialog () { + + this.dialog.open( NewRoadDialogComponent, { + width: '680px', + height: '680px', + data: null, + disableClose: true + } ); + + } + + @HostListener( 'document:keydown', [ '$event' ] ) + onKeyDown ( e: KeyboardEvent ) { + + KeyboardInput.OnKeyDown( e ); + + if ( e.keyCode === 90 && e.ctrlKey ) { + CommandHistory.undo(); + } + + if ( e.keyCode === 89 && e.ctrlKey ) { + CommandHistory.redo(); + } + } + + @HostListener( 'document:keyup', [ '$event' ] ) + onKeyUp ( e: KeyboardEvent ) { + + KeyboardInput.OnKeyUp( e ); + + } + +} diff --git a/src/app/views/editor/editor.module.ts b/src/app/views/editor/editor.module.ts new file mode 100644 index 00000000..551d738a --- /dev/null +++ b/src/app/views/editor/editor.module.ts @@ -0,0 +1,76 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { ToolBarComponent } from './tool-bar/tool-bar.component'; +import { MenuBarComponent } from './menu-bar/menu-bar.component'; +import { EditorLayoutComponent } from './layout/editor-layout.component'; +import { EditorComponent } from './editor.component'; +import { FlexModule, FlexLayoutModule } from '@angular/flex-layout'; +import { RouterModule } from '@angular/router'; +import { SharedModule } from '../../shared/shared.module'; +import { MatButtonModule, MatDividerModule, MatIconModule, MatMenuModule, MatToolbarModule, MatTooltipModule, MatTreeModule, MatGridListModule, MatCardModule, MatListModule, MatCheckboxModule, MatFormFieldModule, MatInputModule, MatTableModule, MatTabsModule } from '@angular/material'; +import { ThreeJsModule } from '../../modules/three-js/three-js.module'; +import { SatPopoverModule } from '@ncstate/sat-popover'; +import { TranslateModule } from '@ngx-translate/core'; +import { PlayerBarComponent } from './player-bar/player-bar.component'; +import { ProjectBrowserComponent } from './project-browser/project-browser.component'; +import { ProjectHierarchyComponent } from './project-browser/project-hierarchy/project-hierarchy.component'; +import { ProjectBreadcrumbsComponent } from './project-browser/project-breadcrumbs/project-breadcrumbs.component'; +import { FolderFilesComponent } from './project-browser/folder-files/folder-files.component'; +import { PerfectScrollbarModule } from 'ngx-perfect-scrollbar'; +import { ExportGlbDialog } from './dialogs/export-glb-dialog/export-glb-dialog.component'; +import { ExportOpenDriveDialog } from './dialogs/export-opendrive-dialog/export-opendrive-dialog.component'; +import { FileComponent } from './project-browser/file/file.component'; +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; + +@NgModule( { + declarations: [ + ToolBarComponent, + MenuBarComponent, + EditorLayoutComponent, + EditorComponent, + PlayerBarComponent, + ProjectBrowserComponent, + ProjectHierarchyComponent, + ProjectBreadcrumbsComponent, + FolderFilesComponent, + FileComponent, + ExportGlbDialog, + ExportOpenDriveDialog, + ], + imports: [ + CommonModule, + FormsModule, + ReactiveFormsModule, + FlexModule, + RouterModule, + SharedModule, + MatMenuModule, + MatToolbarModule, + MatDividerModule, + ThreeJsModule, + SatPopoverModule, + MatIconModule, + MatTooltipModule, + MatButtonModule, + TranslateModule, + MatTreeModule, + MatGridListModule, + MatCardModule, + FlexLayoutModule, + PerfectScrollbarModule, + MatListModule, + MatCheckboxModule, + MatFormFieldModule, + MatInputModule, + MatTabsModule, + ], + entryComponents: [ + ExportGlbDialog + ] +} ) +export class EditorModule { +} diff --git a/src/app/views/editor/layout/editor-layout.component.html b/src/app/views/editor/layout/editor-layout.component.html new file mode 100644 index 00000000..992fe424 --- /dev/null +++ b/src/app/views/editor/layout/editor-layout.component.html @@ -0,0 +1,20 @@ + + +
+ + + + + +
+ +
+ + +
+ +
+ +
\ No newline at end of file diff --git a/src/app/views/editor/layout/editor-layout.component.ts b/src/app/views/editor/layout/editor-layout.component.ts new file mode 100644 index 00000000..80867143 --- /dev/null +++ b/src/app/views/editor/layout/editor-layout.component.ts @@ -0,0 +1,19 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { Component, OnInit } from '@angular/core'; + +@Component( { + selector: 'app-layout', + templateUrl: './editor-layout.component.html', +} ) +export class EditorLayoutComponent implements OnInit { + + constructor () { + } + + ngOnInit () { + } + +} diff --git a/src/app/views/editor/menu-bar/menu-bar.component.html b/src/app/views/editor/menu-bar/menu-bar.component.html new file mode 100644 index 00000000..b27f425c --- /dev/null +++ b/src/app/views/editor/menu-bar/menu-bar.component.html @@ -0,0 +1,125 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/app/views/editor/menu-bar/menu-bar.component.ts b/src/app/views/editor/menu-bar/menu-bar.component.ts new file mode 100644 index 00000000..9aad2686 --- /dev/null +++ b/src/app/views/editor/menu-bar/menu-bar.component.ts @@ -0,0 +1,195 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { HttpClient } from '@angular/common/http'; +import { Component, OnInit } from '@angular/core'; +import { MatDialog } from '@angular/material'; +import { Router } from '@angular/router'; +import { ExporterService } from 'app/services/exporter.service'; +import { ElectronService } from 'ngx-electron'; +import { AppService } from '../../../core/services/app.service'; +import { NewRoadDialogComponent } from '../../../modules/tv-map/dialogs/new-road-dialog/new-road-dialog.component'; +import { TvMapService } from '../../../modules/tv-map/services/tv-map.service'; +import { AppLinks } from '../../../services/app-links'; +import { CommandHistory } from '../../../services/command-history'; +import { FileService } from '../../../services/file.service'; +import { Environment } from 'app/core/utils/environment'; +import { RecentFileService } from 'app/services/recent-file.service'; +import { IFile } from 'app/core/models/file'; +import { MainFileService } from 'app/services/main-file.service'; +import { ExportGlbDialog } from '../dialogs/export-glb-dialog/export-glb-dialog.component'; +import { RoadStyleService } from 'app/services/road-style.service'; +import { OdWriter } from 'app/modules/tv-map/services/open-drive-writer.service'; +import { RoadExporterService } from 'app/services/road-style-exporter.service'; + + +@Component( { + selector: 'app-menu-bar', + templateUrl: './menu-bar.component.html', +} ) +export class MenuBarComponent implements OnInit { + + get oscEnabled (): boolean { return Environment.oscEnabled; } + + constructor ( + private appService: AppService, + private odService: TvMapService, + private electron: ElectronService, + private dialog: MatDialog, + private http: HttpClient, + private exporter: ExporterService, + private router: Router, + private recentFileService: RecentFileService, + private mainFileService: MainFileService, + private odExporter: OdWriter, + private roadStyleExporter: RoadExporterService + ) { + } + + get recentFiles () { return this.recentFileService.recentFiles } + + get isElectronApp () { + + return this.electron.isElectronApp; + + } + + ngOnInit () { + + } + + onNewFile () { + + this.mainFileService.newFile(); + + } + + onOpenFile () { + + this.mainFileService.showOpenWindow( this.mainFileService.fileService.projectFolder ); + + } + + showNewRoadDialog () { + + this.dialog.open( NewRoadDialogComponent, { + width: '680px', + height: '680px', + data: null, + disableClose: true + } ); + + } + + onSave () { + + this.mainFileService.save(); + + } + + onSaveAs () { + + this.mainFileService.saveAs(); + + } + + + onExit () { + + this.appService.exit(); + + } + + onUndo () { + + CommandHistory.undo(); + + } + + onRedo () { + + CommandHistory.redo(); + + } + + openManual () { + + window.open( AppLinks.roadEditorManualLink, '_blank' ); + + } + + openContactUs () { + + window.open( AppLinks.contactUsLink, '_blank' ); + + } + + openUserGuide () { + + window.open( AppLinks.documentationLink, '_blank' ); + + } + + importRecentFile ( file: IFile ) { + + this.mainFileService.openFromPath( file.path, null ); + + } + + onImportOpenDRIVE () { + + this.odService.open(); + + } + + onExportOpenDRIVE () { + + this.exporter.exportOpenDrive(); + + } + + onExportGLTF () { + + this.exporter.exportGTLF(); + + } + + onExportGLB () { + + // this.exporter.exportGLB(); + + this.dialog.open( ExportGlbDialog, { + width: '25vw', + } ); + + } + + importOdExample ( filename: string ) { + + if ( filename == null ) throw new Error( 'Invalid filename' ); + + const filepath = `./assets/open-drive/${ filename }`; + + this.http.get( filepath, { responseType: 'text' } ).subscribe( response => { + + this.odService.importContent( response ); + + } ); + } + + onExportCARLA () { + + this.exporter.exportCARLA(); + + } + + logout () { + + this.appService.auth.logout(); + + this.router.navigateByUrl( AppService.loginUrl ); + + } + +} diff --git a/src/app/views/editor/player-bar/player-bar.component.css b/src/app/views/editor/player-bar/player-bar.component.css new file mode 100644 index 00000000..8b7be98f --- /dev/null +++ b/src/app/views/editor/player-bar/player-bar.component.css @@ -0,0 +1,13 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +.app-player-bar { + top: 12px; + right: 10px; + background: #424242; + height: 38px; + padding: 0px 4px !important; + position: absolute; + width: auto !important; +} \ No newline at end of file diff --git a/src/app/views/editor/player-bar/player-bar.component.html b/src/app/views/editor/player-bar/player-bar.component.html new file mode 100644 index 00000000..349aaa6f --- /dev/null +++ b/src/app/views/editor/player-bar/player-bar.component.html @@ -0,0 +1,23 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/app/views/editor/player-bar/player-bar.component.ts b/src/app/views/editor/player-bar/player-bar.component.ts new file mode 100644 index 00000000..7f416880 --- /dev/null +++ b/src/app/views/editor/player-bar/player-bar.component.ts @@ -0,0 +1,74 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { Component } from '@angular/core'; +import { PlayerService } from '../../../core/player.service'; + +@Component( { + selector: 'app-player-bar', + templateUrl: './player-bar.component.html', + styleUrls: [ './player-bar.component.css' ] +} ) +export class PlayerBarComponent { + + public isPlaying: boolean; + public hasStarted: boolean; + + // reference to handle + private handle: any; + + constructor ( private playerService: PlayerService ) { } + + playSimulation () { + + if ( this.isPlaying ) return; + + this.isPlaying = true; + + this.playerService.play(); + + this.hasStarted = true; + } + + pauseSimulation () { + + if ( !this.isPlaying ) return; + + this.isPlaying = false; + + this.playerService.pause(); + + } + + stopSimulation () { + + if ( !this.hasStarted ) return; + + this.isPlaying = false; + + this.playerService.stop(); + + this.hasStarted = false; + } + + playSingleSimulationStep () { + + this.playSimulation(); + + this.pauseSimulation(); + + } + + onMouseDown () { + + this.handle = setInterval( () => this.playSingleSimulationStep(), 20 ); + + } + + onMouseUp () { + + clearInterval( this.handle ); + + } +} diff --git a/src/app/views/editor/project-browser/file-node.model.ts b/src/app/views/editor/project-browser/file-node.model.ts new file mode 100644 index 00000000..3674c37d --- /dev/null +++ b/src/app/views/editor/project-browser/file-node.model.ts @@ -0,0 +1,45 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { FileService } from 'app/services/file.service'; + +/** Flat node with expandable and level information */ +export class FileNode { + + constructor ( public name: string, public level = 1, public expandable = false, public isLoading = false, public path: string = '', public type: string = '', public isSelected = false, public isDeleted = false ) { } + + sub_folders ( fileService: FileService ): FileNode[] { + + const files = fileService.readPathContentsSync( this.path ); + + const folders = []; + + files.forEach( file => { + + if ( file.type === 'directory' ) folders.push( new FileNode( file.name, 0, true, false, file.path, file.type ) ); + + } ); + + return folders; + } + + sub_files ( fileService: FileService ): FileNode[] { + + // console.log( 'get-sub-files', this.name ); + + const files = fileService.readPathContentsSync( this.path ); + + const items = []; + + files.forEach( file => { + + const extension = FileService.getExtension( file.name ); + + if ( extension !== 'meta' ) items.push( new FileNode( file.name, 0, true, false, file.path, file.type ) ); + + } ); + + return items; + } +} diff --git a/src/app/views/editor/project-browser/file/file.component.css b/src/app/views/editor/project-browser/file/file.component.css new file mode 100644 index 00000000..3b2d5d21 --- /dev/null +++ b/src/app/views/editor/project-browser/file/file.component.css @@ -0,0 +1,50 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +.file-card { + margin: 0px !important; + padding: 0px !important; + border-radius: 1px !important; + background: transparent !important; + border: 0px solid !important; + cursor: pointer; +} + +.file-card-image { + display: block; + margin-left: auto; + margin-right: auto; + width: 67%; + border-top-left-radius: inherit; + border-top-right-radius: inherit; +} + +.file-name { + margin: 4px 4px 4px 4px !important; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + font-size: 0.75em; + text-align: center; +} + +.file-name input { + background: transparent; + border: 0px solid transparent; + width: 100%; + color: #fff; + text-align: center; + font-size: inherit; +} + +.file-card img { + max-width: 100%; + width: 56px; + margin-left: auto; + margin-right: auto; + display: block; + margin-left: auto; + margin-right: auto; + width: 67%; +} \ No newline at end of file diff --git a/src/app/views/editor/project-browser/file/file.component.html b/src/app/views/editor/project-browser/file/file.component.html new file mode 100644 index 00000000..484cac79 --- /dev/null +++ b/src/app/views/editor/project-browser/file/file.component.html @@ -0,0 +1,72 @@ + + + + +

{{ file.name }}

+
+ + + +

{{ filename }}

+

+ +

+
+ + + +

{{ filename }}

+

+ +

+
+ + + +

{{ filename }}

+

+ +

+
+ + + +

{{ filename }}

+

+ +

+
+ + + +

{{ filename }}

+

+ +

+
+ + + +

{{ filename }}

+

+ +

+
+ + + +

{{ filename }}

+

+ +

+
+ + + +

{{ filename }}

+

+ +

+
\ No newline at end of file diff --git a/src/app/views/editor/project-browser/file/file.component.ts b/src/app/views/editor/project-browser/file/file.component.ts new file mode 100644 index 00000000..729fcdfa --- /dev/null +++ b/src/app/views/editor/project-browser/file/file.component.ts @@ -0,0 +1,535 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { Component, ElementRef, EventEmitter, HostListener, Input, OnInit, Output, ViewChild } from '@angular/core'; +import { FileNode } from '../file-node.model'; +import { ElectronService } from 'ngx-electron'; +import { ContextMenuType, MenuService } from 'app/services/menu.service'; +import { AssetLoaderService } from 'app/services/asset-loader.service'; +import { AppInspector } from 'app/core/inspector'; +import { InspectorFactoryService } from 'app/core/factories/inspector-factory.service'; +import { Material } from 'three'; +import { Metadata } from 'app/core/models/metadata.model'; +import { PreviewService } from 'app/views/inspectors/object-preview/object-preview.service'; +import { FileService } from 'app/services/file.service'; +import { AssetDatabase } from 'app/services/asset-database'; +import { TvRoadSign } from 'app/modules/tv-map/models/tv-road-sign.model'; +import { SnackBar } from 'app/services/snack-bar.service'; +import { FileUtils } from 'app/services/file-utils'; +import { MetadataFactory } from 'app/core/factories/metadata-factory.service'; +import { ProjectBrowserService } from '../project-browser.service'; +import { ImporterService } from 'app/services/importer.service'; +import { RoadStyle, RoadStyleService } from 'app/services/road-style.service'; +import { TvRoadMarking } from 'app/modules/tv-map/services/tv-marking.service'; + +@Component( { + selector: 'app-file', + templateUrl: './file.component.html', + styleUrls: [ './file.component.css' ] +} ) +export class FileComponent implements OnInit { + + @ViewChild( 'nameInput' ) nameInputRef: ElementRef; + + @Output() deleted = new EventEmitter(); + @Output() renamed = new EventEmitter(); + + @Input() file: FileNode; + + public extension: string; + + public metadata: Metadata; + + public showRenaming: boolean; + + // public previewImage; + public get previewImage () { return this.metadata && this.metadata.preview } + + public get isModel (): boolean { return this.metadata && this.metadata.importer == "ModelImporter" } + + public get isMaterial (): boolean { return this.metadata && this.metadata.importer == "MaterialImporter" } + + public get isTexture (): boolean { return this.metadata && this.metadata.importer == "TextureImporter" } + + public get isRoadStyle (): boolean { return this.metadata && this.metadata.importer == "RoadStyleImporter" } + + public get isRoadMarking (): boolean { return this.metadata && this.metadata.importer == "RoadMarkingImporter" } + + public get isScene (): boolean { return this.metadata && this.metadata.importer == "SceneImporter" } + + public get isSign (): boolean { return this.metadata && this.metadata.importer == "SignImporter" } + + public get isDirectory (): boolean { return this.metadata && this.file.type == 'directory' } + + public get isUnknown (): boolean { return !this.isDirectory && ( !this.metadata || !this.extension ); } + + constructor ( + private electron: ElectronService, + private menuService: MenuService, + private assetService: AssetLoaderService, + private previewService: PreviewService, + private fileService: FileService, + private projectBrowserService: ProjectBrowserService, + private importer: ImporterService, + ) { + + } + + get filename () { return this.file.name.split( '.' )[ 0 ]; } + + // set filename ( value ) { this.file.name = value; } + + // get extension () { return this.file.name.split( '.' )[ 1 ]; } + + get filePath () { + + if ( this.electron.isLinux ) return "file:///" + this.file.path; + + if ( this.electron.isWindows ) return "file:///" + this.file.path; + + } + + ngOnInit () { + + try { + + this.extension = this.file.name.split( '.' )[ 1 ]; + + if ( !this.assetService.hasMetaFile( this.file ) ) { + + MetadataFactory.createMetadata( this.file.name, this.extension, this.file.path ); + + } + + this.metadata = this.assetService.fetchMetaFile( this.file ); + + } catch ( error ) { + + console.error( error ); + + } + + if ( !this.metadata ) return; + + this.metadata = this.assetService.find( this.metadata.guid ); + + try { + + if ( !this.metadata.preview ) { + + if ( this.metadata.importer === "MaterialImporter" ) { + + const instance: Material = AssetDatabase.getInstance( this.metadata.guid ); + + this.metadata.preview = this.previewService.getMaterialPreview( instance ); + + } else if ( this.metadata.importer === "SignImporter" ) { + + const instance: TvRoadSign = AssetDatabase.getInstance( this.metadata.guid ); + + this.metadata.preview = this.previewService.getSignPreview( instance ); + + } else if ( this.metadata.importer === "ModelImporter" ) { + + // const instance: Object3D = AssetCache.getInstance( this.metadata.guid ); + + this.assetService.modelImporterService.load( this.metadata.path, ( obj ) => { + + this.metadata.preview = this.previewService.getModelPreview( obj ); + + AssetDatabase.setInstance( this.metadata.guid, obj ); + + }, this.metadata ); + + } else if ( this.metadata.importer === "RoadStyleImporter" ) { + + const instance: RoadStyle = AssetDatabase.getInstance( this.metadata.guid ); + + this.metadata.preview = this.previewService.getRoadStylePreview( instance ); + + } else if ( this.metadata.importer === "RoadMarkingImporter" ) { + + const instance: TvRoadMarking = AssetDatabase.getInstance( this.metadata.guid ); + + this.metadata.preview = this.previewService.getRoadMarkingPreview( instance ); + + } + + + } + + } catch ( error ) { + + } + } + + @HostListener( 'click', [ '$event' ] ) + onClick ( $event ) { + + $event.preventDefault(); + $event.stopPropagation(); + + if ( this.isDirectory ) return; + + if ( this.isScene ) return; + + try { + + const instance = AssetDatabase.getInstance( this.metadata.guid ); + const inspector = InspectorFactoryService.getInspectorByExtension( this.extension ); + + if ( this.metadata.importer === "MaterialImporter" ) { + + AppInspector.setInspector( inspector, { + material: instance, + guid: this.metadata.guid + } ); + + } else if ( this.metadata.importer === "TextureImporter" ) { + + AppInspector.setInspector( inspector, { + texture: instance, + guid: this.metadata.guid + } ); + + } else if ( this.metadata.importer === "RoadStyleImporter" ) { + + RoadStyleService.setCurrentStyle( instance as RoadStyle ); + + AppInspector.setInspector( inspector, { + roadStyle: instance, + guid: this.metadata.guid + } ); + + } else if ( this.metadata.importer === "ModelImporter" ) { + + AppInspector.setInspector( inspector, this.metadata ); + + + } else if ( this.metadata.importer === "RoadMarkingImporter" ) { + + AppInspector.setInspector( inspector, { + roadMarking: instance, + guid: this.metadata.guid + } ) + + } else { + + AppInspector.setInspector( inspector, instance ); + + } + + } catch ( error ) { + + SnackBar.error( error ); + + } + + } + + // getFileInstance ( extension: string, guid: string, path: string ): any { + + // if ( this.assetService.assetInstances.has( guid ) ) { + + // return this.assetService.assetInstances.get( guid ); + + // } + + // let instance = null; + + // switch ( extension ) { + + // case 'png': instance = new TextureLoader().load( path ); break; + + // case 'svg': instance = new TextureLoader().load( path ); break; + + // case 'jpg': instance = new TextureLoader().load( path ); break; + + // case 'jpeg': instance = new TextureLoader().load( path ); break; + + // case 'material': instance = new MaterialLoader().parse( path ); break; + + // default: break; + // } + + // if ( instance ) this.assetService.assetInstances.set( guid, instance ); + + // return instance; + // } + + @HostListener( 'dblclick', [ '$event' ] ) + onDoubleClick ( $event ) { + + if ( this.isDirectory ) { + + this.projectBrowserService.folderChanged.emit( this.file ); + + } else { + + switch ( this.extension ) { + + case 'scene': + this.importer.importScene( this.file.path ); + SnackBar.success( "Importing Scene " + this.file.name ); + break; + + } + + } + } + + // @HostListener( 'mousedown', [ '$event' ] ) + // onMouseDown ( $event ) { + + // $event.preventDefault(); + // $event.stopPropagation(); + + // } + + // @HostListener( 'mouseover', [ '$event' ] ) + // onMouseOver ( $event ) { + + // $event.preventDefault(); + // $event.stopPropagation(); + + // } + + // @HostListener( 'mouseleave', [ '$event' ] ) + // onMouseLeave ( $event ) { + + // $event.preventDefault(); + // $event.stopPropagation(); + + // } + + @HostListener( 'contextmenu', [ '$event' ] ) + onContextMenu ( $event ) { + + $event.preventDefault(); + $event.stopPropagation(); + + if ( !this.electron.isElectronApp ) return; + + this.menuService.registerContextMenu( ContextMenuType.HIERARCHY, [ + { + label: 'New', + enabled: false, + }, + { + label: 'Delete', + click: () => this.deleteNode(), + }, + { + label: 'Rename', + click: () => this.renameNode(), + enabled: !this.isDirectory, + }, + { + label: 'Duplicate', + click: () => { }, + enabled: false, + }, + { + label: 'Show In Explorer', + click: () => this.showInExplorer() + }, + { + label: 'Reimport', + click: () => this.reimport(), + enabled: false, + }, + { + label: 'Reimport All', + click: () => this.reimportAll(), + enabled: false, + }, + ] ); + + this.menuService.showContextMenu( ContextMenuType.HIERARCHY ); + } + + + deleteNode () { + + try { + + if ( this.isDirectory ) { + + // TODO: need to loop over each file in the folder to delete them + // from database as well + this.fileService.deleteFolderSync( this.file.path ); + this.fileService.deleteFileSync( this.file.path + '.meta' ); + + this.file.isDeleted = true; + + AssetDatabase.remove( this.metadata.guid ); + + SnackBar.success( "Folder deleted" ); + + } else { + + this.fileService.deleteFileSync( this.file.path ); + this.fileService.deleteFileSync( this.file.path + '.meta' ); + + this.file.isDeleted = true; + + AssetDatabase.remove( this.metadata.guid ); + + SnackBar.success( "File deleted" ); + } + + this.deleted.emit( this.file ); + + } catch ( error ) { + + console.error( error ); + + } + + } + + renameNode () { + + this.showRenaming = true; + + setTimeout( () => { + + if ( this.nameInputRef ) this.nameInputRef.nativeElement.focus(); + if ( this.nameInputRef ) this.nameInputRef.nativeElement.select(); + + }, 100 ); + + } + + showInExplorer () { + + try { + + this.electron.shell.showItemInFolder( this.file.path ); + + } catch ( error ) { + + SnackBar.error( error ); + + } + + } + + reimport () { + + SnackBar.error( "Not able to reimport" ); + + // console.error( "method not implemented" ); + // this.assetService.reimport( this.file, this.extension ); + + } + + reimportAll () { + + SnackBar.error( "Not able to reimport" ); + + } + + // @HostListener( 'dragover', [ '$event' ] ) + // onDragOver ( $event ) { + + // $event.preventDefault(); + // $event.stopPropagation(); + + // } + + @HostListener( 'dragstart', [ '$event' ] ) + onDragStart ( $event ) { + + // if ( this.extension == "png" || this.extension == "jpg" || this.extension == "svg" ) { + // return; + // } + + // $event.preventDefault(); + // $event.stopPropagation(); + + console.log( "dragstat", $event ); + + $event.dataTransfer.setData( "path", this.file.path ); + + if ( this.metadata ) { + + $event.dataTransfer.setData( "guid", this.metadata.guid ); + + } + } + + onBlur ( $event ) { + + this.showRenaming = false; + + } + + onFocus ( $event ) { + + this.showRenaming = true; + + } + + @HostListener( 'window:keydown', [ '$event' ] ) + onKeyDown ( $event: KeyboardEvent ) { + + if ( !this.showRenaming ) return; + + if ( $event.keyCode === 13 && this.nameInputRef ) { + + this.file.name = this.nameInputRef.nativeElement.value + "." + this.extension; + + const oldPath = this.file.path; + + const currentFolder = FileUtils.getDirectoryFromPath( this.file.path ); + + const newPath = this.fileService.join( currentFolder, this.file.name ); + + if ( !this.metadata ) { + + this.metadata = MetadataFactory.createMetadata( this.file.name, this.extension, this.file.path ); + + } + + this.metadata.path = newPath; + this.metadata.preview = null; + + try { + + MetadataFactory.saveMetadataFile( oldPath + ".meta", this.metadata ); + + this.fileService.fs.renameSync( oldPath, newPath ); + + this.fileService.fs.renameSync( oldPath + ".meta", newPath + ".meta" ); + + this.renamed.emit( this.file ); + + } catch ( error ) { + + console.error( error ); + + } + + this.showRenaming = false; + + } + + } + + // @HostListener( 'dragleave', [ '$event' ] ) + // onDragLeave ( $event ) { + + // $event.preventDefault(); + // $event.stopPropagation(); + + // } + + // @HostListener( 'drop', [ '$event' ] ) + // onDrop ( $event: DragEvent ) { + + // // + + // } +} diff --git a/src/app/views/editor/project-browser/folder-files/folder-files.component.css b/src/app/views/editor/project-browser/folder-files/folder-files.component.css new file mode 100644 index 00000000..ae740923 --- /dev/null +++ b/src/app/views/editor/project-browser/folder-files/folder-files.component.css @@ -0,0 +1,46 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +.folder-item { + color: black !important; +} + +.folder { + text-align: center; + width: 100%; + align-self: center; +} + +.folder-icon { + font-size: 64px !important; + width: 64px; + color: #55d2d8; + cursor: pointer; +} + +.folder-icon:hover, .folder-icon.selected { + color: #3aadb3; +} + +.folder-icon.selected { + background: red; +} + + +.folder-name { + margin: 4px 4px 4px 4px !important; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + font-size: 0.75em; +} + +.content { + padding: 16px 8px 0px 0px !important; +} + +.content > mat-card { + margin: 0px !important; +} + diff --git a/src/app/views/editor/project-browser/folder-files/folder-files.component.html b/src/app/views/editor/project-browser/folder-files/folder-files.component.html new file mode 100644 index 00000000..722dda66 --- /dev/null +++ b/src/app/views/editor/project-browser/folder-files/folder-files.component.html @@ -0,0 +1,17 @@ + + +
+ +
+ +
+ + + +
+ +
+ +
\ No newline at end of file diff --git a/src/app/views/editor/project-browser/folder-files/folder-files.component.ts b/src/app/views/editor/project-browser/folder-files/folder-files.component.ts new file mode 100644 index 00000000..4efbde67 --- /dev/null +++ b/src/app/views/editor/project-browser/folder-files/folder-files.component.ts @@ -0,0 +1,366 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { Component, EventEmitter, Input, OnInit, Output, ApplicationRef, HostListener, ViewChild, ElementRef, AfterViewInit } from '@angular/core'; +import { FileNode } from "../file-node.model"; +import { FileService } from 'app/services/file.service'; +import { ImporterService } from 'app/services/importer.service'; +import { SnackBar } from 'app/services/snack-bar.service'; +import { ElectronService } from 'ngx-electron'; +import { MenuService, ContextMenuType } from 'app/services/menu.service'; +import { ProjectBrowserService } from '../project-browser.service'; +import { AssetFactory } from 'app/core/factories/asset-factory.service'; + +@Component( { + selector: 'app-folder-files', + templateUrl: './folder-files.component.html', + styleUrls: [ './folder-files.component.css' ] +} ) +export class FolderFilesComponent implements OnInit, AfterViewInit { + + @ViewChild( 'content' ) contentRef: ElementRef; + + @Input() folder: FileNode; + + @Input() files: FileNode[] = []; + + @Output() folderChanged = new EventEmitter(); + + @Input() selectedNode: FileNode; + + widthInPercent: string; + + constructor ( + private importer: ImporterService, + private electron: ElectronService, + private menuService: MenuService, + private fileService: FileService, + private appRef: ApplicationRef, + private projectBrowserService: ProjectBrowserService + ) { } + + get sortedFiles () { + + let sorted = []; + + this.files.filter( f => f.type == 'directory' ).forEach( f => sorted.push( f ) ) + + this.files.filter( f => f.type != 'directory' ).forEach( f => sorted.push( f ) ) + + return sorted; + } + + ngOnInit () { + + // Debug.log( 'init-folder-files' ); + + } + + ngAfterViewInit () { + + this.updateThumbnailCount( this.contentRef.nativeElement.clientWidth ); + + } + + deleteNode ( node: FileNode ): void { + + + + } + + showInExplorer (): void { + + try { + + const selectedFile = this.files.find( file => file.isSelected === true ); + + if ( selectedFile ) { + + this.electron.shell.showItemInFolder( selectedFile.path ); + + } else { + + this.electron.shell.openItem( this.folder.path ); + + } + + } catch ( error ) { + + SnackBar.error( "Some error occurred" ); + + } + + } + + onContextMenu ( $event, selectedNode?: FileNode ) { + + $event.preventDefault(); + $event.stopPropagation(); + + if ( !this.electron.isElectronApp ) return; + + this.menuService.registerContextMenu( ContextMenuType.HIERARCHY, [ + { + label: 'New', + submenu: [ + { label: 'Scene', click: () => this.createNewScene() }, + { label: 'Folder', click: () => this.createNewFolder() }, + { label: 'Material', click: () => this.createNewMaterial() }, + { label: 'Road Marking', click: () => this.createNewRoadMarking() }, + // { label: 'Prop Set' }, + // { label: 'Extrusion Style' }, + // { label: 'Post Style' }, + // { label: 'Sign', click: () => this.createNewSign() }, + // { label: 'Crosswalk Marking' }, + // { label: 'Lane Marking' }, + // { label: 'Polygon Marking' }, + ] + }, + // { + // label: 'Delete', + // click: () => this.deleteNode( selectedNode ), + // enabled: selectedNode ? true : false + // }, + // { + // label: 'Rename', + // click: () => this.renameNode( selectedNode ), + // enabled: selectedNode ? true : false + // }, + // { + // label: 'Duplicate', + // click: () => { console.log( "add vehiclie" ) }, + // enabled: selectedNode ? true : false + // }, + { + label: 'Show In Explorer', + click: () => this.showInExplorer() + }, + // { + // label: 'Reimport', + // click: () => this.reimport( selectedNode ) + // }, + // { + // label: 'Reimport All', + // click: () => this.reimportAll() + // }, + ] ); + + this.menuService.showContextMenu( ContextMenuType.HIERARCHY ); + } + + reimport ( node: FileNode ) { + + console.error( "method not implemented" ); + + // if ( !node ) return; + + // this.assets.reimport( node ); + + } + + reimportAll (): void { + + console.error( "method not implemented" ); + + // this.assets.reimportProject(); + + } + + renameNode ( node: FileNode ): void { + + if ( !node ) return; + + if ( node.type === 'directory' ) { + + + + } else { + + + } + + } + + createNewScene () { + + try { + + AssetFactory.createNewScene( this.folder.path ); + + this.refershFolder(); + + } catch ( error ) { + + SnackBar.error( error ); + + } + + } + + createNewFolder () { + + try { + + AssetFactory.createNewFolder( this.folder.path ); + + this.refershFolder(); + + } catch ( error ) { + + SnackBar.error( error ); + + } + + } + + createNewMaterial () { + + try { + + AssetFactory.createNewMaterial( this.folder.path, "NewMaterial" ); + + this.refershFolder(); + + } catch ( error ) { + + SnackBar.error( error ); + + } + + } + + createNewSign () { + + try { + + AssetFactory.createNewSign( "NewSign", this.folder.path ); + + this.refershFolder(); + + } catch ( error ) { + + SnackBar.error( error ); + + } + + } + + createNewRoadMarking (): void { + + try { + + AssetFactory.createNewRoadMarking( this.folder.path, "NewRoadMarking" ); + + this.refershFolder(); + + } catch ( error ) { + + SnackBar.error( error ); + + } + + } + + doubleClickFolder ( node: FileNode ) { + + if ( node.type === 'directory' ) this.folderChanged.emit( node ); + + } + + selectFolder () { + + // Debug.log( 'select-folder', node ); + + // if ( node.type === 'directory' ) this.folderChanged.emit( node ); + + } + + onMouseDown ( node: FileNode ) { + + // console.log( 'mouse-down', node.name ); + + this.selectedNode = node; + + // // unselected all + // this.files.forEach( file => file.isSelected = false ); + + // // select this node + // node.isSelected = true; + + } + + onMouseOver ( node: FileNode ) { + + // console.log( 'mouse-over', node.name ); + + this.selectedNode = node; + + } + + onMouseOut () { + + // console.log( 'mouseout', node.name ); + + // if ( !this.selectedNode ) return; + + // this.selectedNode.isSelected = false; + + // this.selectedNode = null; + + } + + importFile ( file: FileNode ) { + + this.projectBrowserService.fileDoubleClicked.emit( file ); + + this.importer.importViaPath( file.path, file.name ); + + } + + onDragStart ( $event: DragEvent, node: FileNode ) { + + $event.dataTransfer.setData( "path", node.path ); + + } + + @HostListener( "window:resize" ) + onWindowResize () { + + this.updateThumbnailCount( this.contentRef.nativeElement.clientWidth ); + + } + + updateThumbnailCount ( width: number ) { + + // 125 is the minimum width for the item + const count = Math.floor( width / 100 ); + + this.widthInPercent = ( 100 / count ) + '%'; + + // console.log( "show ", count, "for", width ); + } + + onFileDeleted ( $node: FileNode ) { + + if ( !$node ) return; + + this.files = this.files.filter( file => !file.isDeleted ); + + this.refershFolder(); + } + + onFileRenamed ( $event ) { + + this.refershFolder(); + + } + + refershFolder () { + + this.files = this.folder.sub_files( this.fileService ); + + this.appRef.tick(); + + } +} diff --git a/src/app/views/editor/project-browser/project-breadcrumbs/project-breadcrumbs.component.css b/src/app/views/editor/project-browser/project-breadcrumbs/project-breadcrumbs.component.css new file mode 100644 index 00000000..707523f7 --- /dev/null +++ b/src/app/views/editor/project-browser/project-breadcrumbs/project-breadcrumbs.component.css @@ -0,0 +1,4 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + diff --git a/src/app/views/editor/project-browser/project-breadcrumbs/project-breadcrumbs.component.html b/src/app/views/editor/project-browser/project-breadcrumbs/project-breadcrumbs.component.html new file mode 100644 index 00000000..cc2dc330 --- /dev/null +++ b/src/app/views/editor/project-browser/project-breadcrumbs/project-breadcrumbs.component.html @@ -0,0 +1,7 @@ + + +

+ project-breadcrumbs works! +

diff --git a/src/app/views/editor/project-browser/project-breadcrumbs/project-breadcrumbs.component.spec.ts b/src/app/views/editor/project-browser/project-breadcrumbs/project-breadcrumbs.component.spec.ts new file mode 100644 index 00000000..12596fb9 --- /dev/null +++ b/src/app/views/editor/project-browser/project-breadcrumbs/project-breadcrumbs.component.spec.ts @@ -0,0 +1,29 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { ProjectBreadcrumbsComponent } from './project-breadcrumbs.component'; + +describe('ProjectBreadcrumbsComponent', () => { + let component: ProjectBreadcrumbsComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ ProjectBreadcrumbsComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(ProjectBreadcrumbsComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/views/editor/project-browser/project-breadcrumbs/project-breadcrumbs.component.ts b/src/app/views/editor/project-browser/project-breadcrumbs/project-breadcrumbs.component.ts new file mode 100644 index 00000000..6f7293a8 --- /dev/null +++ b/src/app/views/editor/project-browser/project-breadcrumbs/project-breadcrumbs.component.ts @@ -0,0 +1,19 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { Component, OnInit } from '@angular/core'; + +@Component({ + selector: 'app-project-breadcrumbs', + templateUrl: './project-breadcrumbs.component.html', + styleUrls: ['./project-breadcrumbs.component.css'] +}) +export class ProjectBreadcrumbsComponent implements OnInit { + + constructor() { } + + ngOnInit() { + } + +} diff --git a/src/app/views/editor/project-browser/project-browser.component.css b/src/app/views/editor/project-browser/project-browser.component.css new file mode 100644 index 00000000..66251bd3 --- /dev/null +++ b/src/app/views/editor/project-browser/project-browser.component.css @@ -0,0 +1,17 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +.folder-file-container { + height: 200px; + background: #424242; + border-left: 1px solid #171717; + border-bottom: 1px solid #171717; + border-top: 1px solid #171717; +} + +.project-heirarchy-container { + height: 200px; + border-bottom: 1px solid #171717; + border-top: 1px solid #171717; +} \ No newline at end of file diff --git a/src/app/views/editor/project-browser/project-browser.component.html b/src/app/views/editor/project-browser/project-browser.component.html new file mode 100644 index 00000000..5a76c020 --- /dev/null +++ b/src/app/views/editor/project-browser/project-browser.component.html @@ -0,0 +1,20 @@ + + +
+ +
+ + + + +
+ +
+ + + +
+ +
\ No newline at end of file diff --git a/src/app/views/editor/project-browser/project-browser.component.ts b/src/app/views/editor/project-browser/project-browser.component.ts new file mode 100644 index 00000000..ecdf3640 --- /dev/null +++ b/src/app/views/editor/project-browser/project-browser.component.ts @@ -0,0 +1,332 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { Component, Injectable, OnInit, HostListener, ApplicationRef } from '@angular/core'; +import { FlatTreeControl, NestedTreeControl } from '@angular/cdk/tree'; +import { MatTreeNestedDataSource } from '@angular/material/tree'; +import { FileService } from 'app/services/file.service'; +import { BehaviorSubject, merge, Observable } from 'rxjs'; +import { CollectionViewer, SelectionChange } from '@angular/cdk/collections'; +import { map } from 'rxjs/operators'; +import { AssetLoaderService } from 'app/services/asset-loader.service'; +import { FileNode } from './file-node.model'; +import { ProjectBrowserService } from './project-browser.service'; +import { ImporterService } from 'app/services/importer.service'; + +// const DOCUMENT_PATH = '/home/himanshu/Documents/Truevision/'; + +/** + * Database for dynamic data. When expanding a node in the tree, the data source will need to fetch + * the descendants data from the database. + */ +export class DynamicDatabase { + + rootLevelNodes: string[] = [ 'Fruits', 'Vegetables' ]; + + // private projectDir = '/home/himanshu/Documents/Truevision'; + private get projectDir () { return this.fileService.projectFolder; } + + private init: FileNode[]; + + constructor ( private fileService: FileService ) { + + this.init = this.getFolderInPath( this.projectDir, 0 ); + + } + + /** Initial data from database */ + initialData (): FileNode[] { + + return this.init; + + // return this.init = this.getFolderInPath( this.projectDir, 0 ); + + // return this.rootLevelNodes.map( name => new FileNode( name, 0, true ) ); + } + + getChildren ( node: FileNode ): FileNode[] | undefined { + + return this.getFolderInPath( node.path, node.level + 1 ); + + // return this.dataMap.get( node ); + + } + + getFolderInPath ( path: string, level: number ) { + + const tmp: FileNode[] = []; + + // this.fileService.readPathContents( path ).then( ( files: any[] ) => { + + // files.forEach( file => { + + // if ( file.type === 'directory' ) tmp.push( new FileNode( file.name, level, true, false, file.path ) ); + + // } ); + + // } ); + + return tmp; + + } + + isExpandable ( node: FileNode ): boolean { + + return this.getFolderInPath( node.name, node.level + 1 ).length > 0; + + // return this.dataMap.has( node ); + + } +} + +/** + * File database, it can build a tree structured Json object from string. + * Each node in Json object represents a file or a directory. For a file, it has filename and type. + * For a directory, it has filename and children (a list of files or directories). + * The input will be a json object string, and the output is a list of `FileNode` with nested + * structure. + */ +@Injectable() +export class DynamicDataSource { + + dataChange = new BehaviorSubject( [] ); + + constructor ( private treeControl: FlatTreeControl, private database: DynamicDatabase ) { + } + + set data ( value: FileNode[] ) { + this.treeControl.dataNodes = value; + this.dataChange.next( value ); + } + + get data (): FileNode[] { + return this.dataChange.value; + } + + connect ( collectionViewer: CollectionViewer ): Observable { + + this.treeControl.expansionModel.onChange.subscribe( change => { + if ( ( change as SelectionChange ).added || + ( change as SelectionChange ).removed ) { + this.handleTreeControl( change as SelectionChange ); + } + } ); + + return merge( collectionViewer.viewChange, this.dataChange ).pipe( map( () => this.data ) ); + } + + /** Handle expand/collapse behaviors */ + handleTreeControl ( change: SelectionChange ) { + if ( change.added ) { + change.added.forEach( node => this.toggleNode( node, true ) ); + } + if ( change.removed ) { + change.removed.slice().reverse().forEach( node => this.toggleNode( node, false ) ); + } + } + + /** + * Toggle the node, remove from display list + */ + toggleNode ( node: FileNode, expand: boolean ) { + const children = this.database.getChildren( node ); + const index = this.data.indexOf( node ); + if ( !children || index < 0 ) { // If no children, or cannot find the node, no op + return; + } + + node.isLoading = true; + + setTimeout( () => { + if ( expand ) { + const nodes = children.map( child => + new FileNode( child.name, node.level + 1, this.database.isExpandable( child ), false, child.path ) ); + this.data.splice( index + 1, 0, ...nodes ); + } else { + let count = 0; + for ( let i = index + 1; i < this.data.length && this.data[ i ].level > node.level; i++, count++ ) { + } + this.data.splice( index + 1, count ); + } + + // notify the change + this.dataChange.next( this.data ); + node.isLoading = false; + }, 100 ); + } +} + +@Component( { + selector: 'app-project-browser', + templateUrl: './project-browser.component.html', + styleUrls: [ './project-browser.component.css' ], +} ) +export class ProjectBrowserComponent implements OnInit { + + selectedFolder: FileNode; + + treeControl = new NestedTreeControl( ( node: FileNode ) => { + if ( node.type === 'directory' ) return node.sub_folders( this.fileService ); + else return []; + } ); + + dataSource = new MatTreeNestedDataSource(); + + files: FileNode[] = []; + + constructor ( + private fileService: FileService, + private assets: AssetLoaderService, + private projectBrowser: ProjectBrowserService, + private importer: ImporterService, + private appRef: ApplicationRef + ) { + + const db = new DynamicDatabase( fileService ); + + this.dataSource.data = []; + + } + + ngOnInit () { + + this.assets.init(); + + this.loadFilesInFolder(); + + this.projectBrowser.folderChanged.subscribe( node => this.onFolderChanged( node ) ); + + } + + // get files () { + + // if ( !this.selectedFolder ) return []; + + // return this.selectedFolder.sub_files( this.fileService ); + + // } + + onFolderChanged ( node: FileNode ) { + + // console.log( 'folder-changed', e ); + + this.selectedFolder = node; + + this.files = this.selectedFolder.sub_files( this.fileService ); + + } + + selectFolder ( e: FileNode ) { + + // console.log( 'select-folder', e ); + + } + + selectFile ( e: FileNode ) { + + // console.log( 'select-file', e ); + } + + onClick ( node: FileNode ) { + + // console.log( node ); + + this.selectedFolder = node; + + const result = node.sub_folders( this.fileService ); + + // console.log( result ); + + // result.subscribe( files => { + + // console.log( files ); + + // } ); + + // this.fileService.readPathContents( DOCUMENT_PATH ).then( ( files: any[] ) => { + + // const tmp = []; + + // files.forEach( file => { + + // if ( file.type === 'directory' ) { + + // tmp.push( new FileNode( file.name, 0, true, false, file.path, file.type ) ); + + // } + + // } ); + + // this.dataSource.data = tmp; + + // } ); + + // console.log( node ); + + } + + loadFilesInFolder () { + + const files = this.fileService.readPathContentsSync( this.fileService.projectFolder ); + + const tmp = []; + + files.forEach( file => { + + if ( file.type === 'directory' ) { + + tmp.push( new FileNode( file.name, 0, true, false, file.path, file.type ) ); + + } + + } ); + + this.dataSource.data = tmp; + } + + @HostListener( 'dragover', [ '$event' ] ) + onDragOver ( evt ) { + + evt.preventDefault(); + evt.stopPropagation(); + } + + @HostListener( 'dragleave', [ '$event' ] ) + onDragLeave ( evt ) { + + evt.preventDefault(); + evt.stopPropagation(); + + } + + @HostListener( 'drop', [ '$event' ] ) + onDrop ( $event: DragEvent ) { + + // console.log( $event ); + // console.log( $event.dataTransfer.files ); + + $event.preventDefault(); + $event.stopPropagation(); + + const folderPath = this.selectedFolder ? + this.selectedFolder.path : + this.fileService.projectFolder; + + for ( let i = 0; i < $event.dataTransfer.files.length; i++ ) { + + const file = $event.dataTransfer.files[ i ]; + + this.importer.onFileDropped( file, folderPath ); + + } + + if ( this.selectedFolder ) { + + this.files = this.selectedFolder.sub_files( this.fileService ); + + this.appRef.tick(); + + } + } +} diff --git a/src/app/views/editor/project-browser/project-browser.service.ts b/src/app/views/editor/project-browser/project-browser.service.ts new file mode 100644 index 00000000..45838795 --- /dev/null +++ b/src/app/views/editor/project-browser/project-browser.service.ts @@ -0,0 +1,98 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { Injectable, EventEmitter } from '@angular/core'; +import { FileNode } from './file-node.model'; +import { AssetLoaderService } from 'app/services/asset-loader.service'; +import { AppInspector } from 'app/core/inspector'; +import { Metadata } from 'app/core/models/metadata.model'; +import { InspectorFactoryService } from 'app/core/factories/inspector-factory.service'; + +@Injectable( { + providedIn: 'root' +} ) +export class ProjectBrowserService { + + public static lastFile: FileNode; + public static lastAsset: Metadata; + public static lastMetadata: Metadata; + + /** + * @deprecated not in use + */ + public fileClicked = new EventEmitter(); + public fileDoubleClicked = new EventEmitter(); + + public folderChanged = new EventEmitter(); + + constructor ( private assets: AssetLoaderService ) { + + // this.fileClicked.subscribe( file => this.onFileClicked( file ) ) + + } + + /** + * + * @param file + * @deprecated not in used + */ + onFileClicked ( file: FileNode ) { + + try { + + const meta = this.assets.fetchMetaFile( file ); + + // console.log( meta.importer ); + + const data = this.assets.find( meta.guid ); + + // let instance = null; + + // if ( this.assets.assetInstances.has( meta.guid ) ) { + // instance = this.assets.assetInstances.get( meta.guid ); + // } else{ + // instance = this.assets.assetInstances.set(meta.guid, ) + // } + + ProjectBrowserService.lastFile = file; + ProjectBrowserService.lastAsset = data; + ProjectBrowserService.lastMetadata = meta; + + switch ( meta.importer ) { + case 'SignImporter': + AppInspector.setInspector( + InspectorFactoryService.getInpectorByFilename( file.name ), + data + ); + break; + + default: + AppInspector.setInspector( + InspectorFactoryService.getInpectorByFilename( file.name ), + data + ); + break; + } + + } catch ( error ) { + + console.error( error ); + + } + } + + showFileByGuid ( guid: string ) { + + // const metdata = this.assets.find( guid ); + + // const directory = metdata.path.split( '/' ).slice( 0, -1 ).join( '/' ); + + // // this.fileSelected.emit( new FileNode( "", 0, false, false, metdata.path, "file", true, false ) ); + + // this.folderChanged.emit( new FileNode( "", 0, false, false, directory, "directory", false, false ) ); + + // // console.log( metdata.path, directory ); + } + +} diff --git a/src/app/views/editor/project-browser/project-hierarchy/project-hierarchy.component.css b/src/app/views/editor/project-browser/project-hierarchy/project-hierarchy.component.css new file mode 100644 index 00000000..16a01588 --- /dev/null +++ b/src/app/views/editor/project-browser/project-hierarchy/project-hierarchy.component.css @@ -0,0 +1,19 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +.example-tree-invisible { + display: none; +} + +.example-tree ul, +.example-tree li { + margin-top: 0; + margin-bottom: 0; + list-style-type: none; + padding-inline-start: 5px !important; +} + +.mat-icon-button { + margin-right: 15px !important; +} \ No newline at end of file diff --git a/src/app/views/editor/project-browser/project-hierarchy/project-hierarchy.component.html b/src/app/views/editor/project-browser/project-hierarchy/project-hierarchy.component.html new file mode 100644 index 00000000..1ea04583 --- /dev/null +++ b/src/app/views/editor/project-browser/project-hierarchy/project-hierarchy.component.html @@ -0,0 +1,34 @@ + + + + + + +
  • + + + {{node.name}} +
  • +
    + + +
  • + +
    + + {{node.name}} +
    +
      + +
    +
  • +
    + +
    \ No newline at end of file diff --git a/src/app/views/editor/project-browser/project-hierarchy/project-hierarchy.component.ts b/src/app/views/editor/project-browser/project-hierarchy/project-hierarchy.component.ts new file mode 100644 index 00000000..31437b3c --- /dev/null +++ b/src/app/views/editor/project-browser/project-hierarchy/project-hierarchy.component.ts @@ -0,0 +1,40 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core'; +import { FileNode } from "../file-node.model"; + +@Component( { + selector: 'app-project-hierarchy', + templateUrl: './project-hierarchy.component.html', + styleUrls: [ './project-hierarchy.component.css' ] +} ) +export class ProjectHierarchyComponent implements OnInit { + + @Output() folderChanged = new EventEmitter(); + + @Input() treeControl; + @Input() dataSource; + + selectedFolder; + + getLevel = ( node: FileNode ) => node.level; + isExpandable = ( node: FileNode ) => node.expandable; + hasChild = ( _: number, node: FileNode ) => true; + + constructor () { } + + ngOnInit () { + } + + onClick ( node: FileNode ) { + + // console.log( 'folder-selected-in-hierarchy', node.name ); + + this.selectedFolder = node; + + this.folderChanged.emit( node ); + + } +} diff --git a/src/app/views/editor/status-bar/status-bar.component.css b/src/app/views/editor/status-bar/status-bar.component.css new file mode 100644 index 00000000..707523f7 --- /dev/null +++ b/src/app/views/editor/status-bar/status-bar.component.css @@ -0,0 +1,4 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + diff --git a/src/app/views/editor/status-bar/status-bar.component.html b/src/app/views/editor/status-bar/status-bar.component.html new file mode 100644 index 00000000..c6855305 --- /dev/null +++ b/src/app/views/editor/status-bar/status-bar.component.html @@ -0,0 +1,12 @@ + + +
    + World x = {{ x | number }}, y = {{ y | number }}, z = {{ z | number }} | + RoadId = {{ roadId }} + + s = {{ s | number }} + t = {{ t | number }} + +
    \ No newline at end of file diff --git a/src/app/views/editor/status-bar/status-bar.component.ts b/src/app/views/editor/status-bar/status-bar.component.ts new file mode 100644 index 00000000..40c27d66 --- /dev/null +++ b/src/app/views/editor/status-bar/status-bar.component.ts @@ -0,0 +1,165 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { Component, OnInit } from '@angular/core'; +import { MonoBehaviour } from 'app/core/components/mono-behaviour'; +import { PointerEventData } from 'app/events/pointer-event-data'; +import * as THREE from 'three'; +import { Vector3 } from 'three'; +import { TvMapSourceFile } from '../../../modules/tv-map/services/tv-map-source-file'; +import { TvRoad } from '../../../modules/tv-map/models/tv-road.model'; +import { TvPosTheta } from '../../../modules/tv-map/models/tv-pos-theta'; +import { TvMapQueries } from '../../../modules/tv-map/queries/tv-map-queries'; +import { Time } from '../../../core/time'; + +@Component( { + selector: 'app-status-bar', + templateUrl: './status-bar.component.html', + styleUrls: [ './status-bar.component.css' ] +} ) +export class StatusBarComponent extends MonoBehaviour implements OnInit { + + private sphere: THREE.Mesh; + + private cursor: PointerEventData; + private road: TvRoad; + + private pos = new TvPosTheta; + + constructor () { + + super(); + + const geom = new THREE.SphereGeometry( 1 ); + // var geometry = new THREE.BoxGeometry( 1, 1, 1 ); + // var material = new THREE.MeshBasicMaterial( { color: 0x00ff00 } ); + // obj.gameObject = new THREE.Mesh( geometry, material ); + const material = new THREE.MeshBasicMaterial( { color: 0x00ff00 } ); + + this.sphere = new THREE.Mesh( geom, material ); + + } + + get seconds () { + return Time.seconds; + } + + get time () { + return Time.time * 0.001; + } + + get x () { + return this.cursor.point.x; + } + + get y () { + return this.cursor.point.y; + } + + get z () { + return this.cursor.point.z; + } + + get openDrive () { + return TvMapSourceFile.openDrive; + } + + get s () { + return this.pos.s; + } + + get t () { + return this.pos.t; + } + + get name () { + + if ( this.cursor && this.cursor.object ) { + + return this.cursor.object.name; + + } + + return null; + } + + get roadId () { + + if ( this.road ) return this.road.id; + + // if ( this.cursor && this.cursor.object && this.cursor.object.userData.data ) { + // + // // return this.cursor.object.userData.data.attr_id; + // return this.cursor.object.userData.data.roadId; + // + // } + + return null; + } + + get laneId () { + + if ( this.cursor && this.cursor.object && this.cursor.object.userData.data ) { + + return this.cursor.object.userData.data.attr_id; + + } + + return null; + } + + ngOnInit () { + + this.cursor = new PointerEventData(); + this.cursor.point = new Vector3(); + + + } + + fetchRoadCoordinates ( point: Vector3 ) { + + this.openDrive.roads.forEach( road => { + + // road.getGeometryCoords() + + road.geometries.forEach( geometry => { + + const nearest = geometry.getNearestPointFrom( point.x, point.y ); + + // Debug.log( nearest ); + + this.sphere.position.set( nearest.x, nearest.y, 0 ); + + } ); + + } ); + + } + + distanceFromRoad ( road, point ) { + + + } + + onPointerClicked ( data: PointerEventData ) { + + // AppService.engine.add( this.sphere ); + + const road = TvMapQueries.getRoadByCoords( data.point.x, data.point.y, this.pos ); + + } + + onPointerMoved ( data: PointerEventData ) { + + this.cursor = data; + + // this.sphere.position.copy( data.point ); + + this.road = TvMapQueries.getRoadByCoords( data.point.x, data.point.y, this.pos ); + + // this.fetchRoadCoordinates( data.point ); + + } + +} diff --git a/src/app/views/editor/tool-bar/tool-bar.component.html b/src/app/views/editor/tool-bar/tool-bar.component.html new file mode 100644 index 00000000..b586da80 --- /dev/null +++ b/src/app/views/editor/tool-bar/tool-bar.component.html @@ -0,0 +1,277 @@ + + + + + + + +
    +
    {{ 'ROAD-GEOMETRY-TITLE' | translate }}
    +
    +
    +
    + + + + +
    +
    {{ 'LANE-WIDTH-TOOL-TITLE' | translate }}
    +
    +
    +
    + + + + + + + + +
    +
    {{ 'ADD-LANE-TOOL-TITLE' | translate }}
    +
    +
    +
    + + + + +
    +
    {{ 'LANE-MARKING-TOOL-TITLE' | translate }}
    +
    +
    +
    + + + + +
    +
    {{ 'LANE-TOOL-TITLE' | translate }}
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    \ No newline at end of file diff --git a/src/app/views/editor/tool-bar/tool-bar.component.ts b/src/app/views/editor/tool-bar/tool-bar.component.ts new file mode 100644 index 00000000..07e3cea0 --- /dev/null +++ b/src/app/views/editor/tool-bar/tool-bar.component.ts @@ -0,0 +1,205 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { Component, OnInit } from '@angular/core'; +import { BaseTool } from '../../../core/tools/base-tool'; +import { LaneWidthTool } from '../../../core/tools/lane-width-tool'; +import { LaneTool } from '../../../core/tools/lane-tool'; +import { LaneAddTool } from '../../../core/tools/lane-add-tool'; +import { LaneMarkingTool } from '../../../core/tools/lane-marking-tool'; +import { ElectronService } from 'ngx-electron'; +import { TvMapService } from '../../../modules/tv-map/services/tv-map.service'; +import { TvSignService } from '../../../modules/tv-map/services/tv-sign.service'; +import { ThreeService } from '../../../modules/three-js/three.service'; +import { Environment } from '../../../core/utils/environment'; +import { ToolManager } from '../../../core/tools/tool-manager'; +import { MarkingPointTool } from '../../../core/tools/marking-point-tool'; +import { TvMarkingService } from '../../../modules/tv-map/services/tv-marking.service'; +import { MarkingLineTool } from 'app/core/tools/marking-line-tool'; +import { ParkingBoxTool } from 'app/core/tools/parking-box-tool'; +import { FileService } from '../../../services/file.service'; +import { PropPointTool } from 'app/core/tools/prop-point-tool'; +import { RoadTool } from 'app/core/tools/road-tool'; +import { ModelImporterService } from 'app/services/model-importer.service'; +import { PropCurveTool } from 'app/core/tools/prop-curve-tool'; +import { SurfaceTool } from 'app/core/tools/surface-tool'; +import { LaneOffsetTool } from 'app/core/tools/lane-offset-tool'; +import { CommandHistory } from 'app/services/command-history'; +import { SetToolCommand } from 'app/core/commands/set-tool-command'; +import { ManeuverTool } from 'app/core/tools/maneuver-tool'; +import { PropPolygonTool } from 'app/core/tools/prop-polygon-tool'; + +@Component( { + selector: 'app-tool-bar', + templateUrl: './tool-bar.component.html', +} ) +export class ToolBarComponent implements OnInit { + + currentTool: BaseTool; + currentToolName: string; + + get oscEnabled (): boolean { return Environment.oscEnabled; } + + constructor ( + private electronService: ElectronService, + private odService: TvMapService, + private signService: TvSignService, + private threeService: ThreeService, + private fileService: FileService, + private modelImporter: ModelImporterService + ) { + } + + get showImportButton () { + + return this.electronService.isElectronApp && !Environment.production; + + } + + get isPropToolSelected () { + + return this.currentTool instanceof PropPointTool || + this.currentTool instanceof PropCurveTool || + this.currentTool instanceof PropPolygonTool; + + } + + get isMarkingToolSelected () { + + return this.currentTool instanceof MarkingLineTool || + this.currentTool instanceof MarkingPointTool; + + } + + ngOnInit () { + + ToolManager.toolChanged.subscribe( ( tool: BaseTool ) => { + this.currentTool = tool; + this.currentToolName = tool ? tool.name : null; + } ); + + } + + showRoadTool () { + + this.setTool( new RoadTool() ); + + } + + showManeueverTool () { + + this.setTool( new ManeuverTool() ); + + } + + showLaneWidthTool () { + + this.setTool( new LaneWidthTool() ); + + } + + showLaneOffsetTool () { + + this.setTool( new LaneOffsetTool() ); + + } + + showRoadSignTool () { + + this.setTool( new PropPointTool() ); + + } + + showPropPointTool () { + + this.setTool( new PropPointTool() ); + + } + + showPropCurveTool () { + + this.setTool( new PropCurveTool() ); + + } + + showPropPolygonTool () { + + this.setTool( new PropPolygonTool() ); + + } + + showSurfaceTool () { + + this.setTool( new SurfaceTool() ); + + } + + showLaneMarkingTool () { + + this.setTool( new LaneMarkingTool() ); + + } + + // importOpenDrive () { + + // new OpenScenarioExporter( this.oscService, this.odService, this.fileService, this.simulation ).exportAndPlay(); + + // } + + showAddLaneTool () { + + this.setTool( new LaneAddTool() ); + + } + + showLaneTool () { + + this.setTool( new LaneTool() ); + + } + + changeCamera () { + + this.threeService.changeCamera(); + + } + + setMarkingPointTool () { + + this.setTool( new MarkingPointTool() ); + + } + + setMarkingLineTool () { + + this.setTool( new MarkingLineTool() ); + + } + + setParkingBoxTool () { + + this.setTool( new ParkingBoxTool() ); + + } + + setParkingPolygonTool () { + + this.setTool( new ParkingBoxTool() ); + + } + + setMiscShape () { + + // this.setTool( new MiscShapeTool() ); + + // AppInspector.setInspector( OscParamatersInspectorComponent, OscSourceFile.scenario.parameterDeclaration ); + + } + + private setTool ( tool: BaseTool ) { + + CommandHistory.execute( new SetToolCommand( tool ) ); + + } +} diff --git a/src/app/views/fields/material-field/material-field.component.css b/src/app/views/fields/material-field/material-field.component.css new file mode 100644 index 00000000..e0bcc04c --- /dev/null +++ b/src/app/views/fields/material-field/material-field.component.css @@ -0,0 +1,12 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +.material-card { + padding: 8px; +} + +.material-card-thumbnail { + float: left; + margin-right: 8px; +} \ No newline at end of file diff --git a/src/app/views/fields/material-field/material-field.component.html b/src/app/views/fields/material-field/material-field.component.html new file mode 100644 index 00000000..0d2c71c5 --- /dev/null +++ b/src/app/views/fields/material-field/material-field.component.html @@ -0,0 +1,11 @@ + + + +
    + +
    + {{ label }} + {{ filename }} +
    \ No newline at end of file diff --git a/src/app/views/fields/material-field/material-field.component.ts b/src/app/views/fields/material-field/material-field.component.ts new file mode 100644 index 00000000..682111da --- /dev/null +++ b/src/app/views/fields/material-field/material-field.component.ts @@ -0,0 +1,100 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { Component, EventEmitter, HostListener, Input, OnInit, Output } from '@angular/core'; +import { PreviewService } from '../../inspectors/object-preview/object-preview.service'; +import { AssetDatabase } from 'app/services/asset-database'; +import { TvMaterial } from 'app/modules/three-js/objects/tv-material.model'; + +@Component( { + selector: 'app-material-field', + templateUrl: './material-field.component.html', + styleUrls: [ './material-field.component.css' ] +} ) +export class MaterialFieldComponent implements OnInit { + + @Output() changed = new EventEmitter(); + + @Input() guid: string; + + @Input() label: string; + + public get preview () { return this.metadata ? this.metadata.preview : null; } + + public get metadata () { return AssetDatabase.getMetadata( this.guid ); } + + public get material () { return AssetDatabase.getInstance( this.guid ); } + + public get filename () { return AssetDatabase.getAssetNameByGuid( this.guid ); } + + constructor ( private previewService: PreviewService ) { } + + ngOnInit () { + + if ( this.metadata && !this.preview ) this.metadata.preview = this.previewService.getMaterialPreview( this.material ); + + } + + @HostListener( 'click', [ '$event' ] ) + onClick ( $event ) { + + $event.preventDefault(); + $event.stopPropagation(); + + } + + @HostListener( 'dblclick', [ '$event' ] ) + onDoubleClick ( $event ) { + + $event.preventDefault(); + $event.stopPropagation(); + + } + + @HostListener( 'dragover', [ '$event' ] ) + onDragOver ( $event ) { + + // console.log( "dragover", $event ) + + $event.preventDefault(); + $event.stopPropagation(); + + } + + @HostListener( 'dragleave', [ '$event' ] ) + onDragLeave ( $event ) { + + // console.log( "dragleave", $event ) + + $event.preventDefault(); + $event.stopPropagation(); + + } + + @HostListener( 'drop', [ '$event' ] ) + onDrop ( $event: DragEvent ) { + + // console.log( "drop", $event ) + // console.log( "guid", $event.dataTransfer.getData( "guid" ) ); + + $event.preventDefault(); + $event.stopPropagation(); + + const guid = $event.dataTransfer.getData( "guid" ); + + if ( guid ) { + + const metadata = AssetDatabase.getMetadata( guid ); + + if ( metadata && metadata.importer === "MaterialImporter" ) { + + this.changed.emit( guid ); + + // update preview + // metadata.preview = this.previewService.getMaterialPreview( AssetDatabase.getInstance( guid ) ); + } + } + } + +} diff --git a/src/app/views/fields/texture-field/texture-field.component.css b/src/app/views/fields/texture-field/texture-field.component.css new file mode 100644 index 00000000..3ab4ae6f --- /dev/null +++ b/src/app/views/fields/texture-field/texture-field.component.css @@ -0,0 +1,14 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +.texture-card { + padding: 8px; +} + +.texture-card-thumbnail { + float: left; + margin-right: 8px; + height: 24px !important; + width: 24px !important; +} \ No newline at end of file diff --git a/src/app/views/fields/texture-field/texture-field.component.html b/src/app/views/fields/texture-field/texture-field.component.html new file mode 100644 index 00000000..dd76c108 --- /dev/null +++ b/src/app/views/fields/texture-field/texture-field.component.html @@ -0,0 +1,11 @@ + + + +
    + +
    + {{ label }} + {{ filename }} +
    \ No newline at end of file diff --git a/src/app/views/fields/texture-field/texture-field.component.ts b/src/app/views/fields/texture-field/texture-field.component.ts new file mode 100644 index 00000000..fdc9fc3c --- /dev/null +++ b/src/app/views/fields/texture-field/texture-field.component.ts @@ -0,0 +1,92 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { Component, EventEmitter, HostListener, Input, OnInit, Output } from '@angular/core'; +import { Texture } from 'three'; +import { AssetDatabase } from 'app/services/asset-database'; + +@Component( { + selector: 'app-texture-field', + templateUrl: './texture-field.component.html', + styleUrls: [ './texture-field.component.css' ] +} ) +export class TextureFieldComponent implements OnInit { + + @Output() changed = new EventEmitter(); + + @Input() guid: string; + + @Input() label: string = 'Map'; + + public texture: Texture; + + constructor () { } + + get thumbnail () { return this.texture && this.texture.image ? this.texture.image.currentSrc : "" } + + get filename () { return AssetDatabase.getAssetNameByGuid( this.guid ); } + + get metadata () { return AssetDatabase.getMetadata( this.guid ); } + + ngOnInit () { + + if ( this.guid ) this.texture = AssetDatabase.getInstance( this.guid ); + + } + + @HostListener( 'click', [ '$event' ] ) + onClick ( $event ) { + + $event.preventDefault(); + $event.stopPropagation(); + + } + + @HostListener( 'dblclick', [ '$event' ] ) + onDoubleClick ( $event ) { + + $event.preventDefault(); + $event.stopPropagation(); + + } + + @HostListener( 'dragover', [ '$event' ] ) + onDragOver ( $event ) { + + $event.preventDefault(); + $event.stopPropagation(); + + } + + @HostListener( 'dragleave', [ '$event' ] ) + onDragLeave ( $event ) { + + $event.preventDefault(); + $event.stopPropagation(); + + } + + + @HostListener( 'drop', [ '$event' ] ) + onDrop ( $event: DragEvent ) { + + $event.preventDefault(); + $event.stopPropagation(); + + const guid = $event.dataTransfer.getData( "guid" ); + + if ( guid ) { + + const metadata = AssetDatabase.getMetadata( guid ); + + if ( metadata.importer === "TextureImporter" ) { + + this.texture = AssetDatabase.getInstance( guid ); + + this.changed.emit( guid ); + + } + } + } +} diff --git a/src/app/views/inspectors/junction-entry-inspector/junction-entry-inspector.component.css b/src/app/views/inspectors/junction-entry-inspector/junction-entry-inspector.component.css new file mode 100644 index 00000000..707523f7 --- /dev/null +++ b/src/app/views/inspectors/junction-entry-inspector/junction-entry-inspector.component.css @@ -0,0 +1,4 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + diff --git a/src/app/views/inspectors/junction-entry-inspector/junction-entry-inspector.component.html b/src/app/views/inspectors/junction-entry-inspector/junction-entry-inspector.component.html new file mode 100644 index 00000000..b72b094c --- /dev/null +++ b/src/app/views/inspectors/junction-entry-inspector/junction-entry-inspector.component.html @@ -0,0 +1,5 @@ + + +

    Junction Entry Object

    \ No newline at end of file diff --git a/src/app/views/inspectors/junction-entry-inspector/junction-entry-inspector.component.ts b/src/app/views/inspectors/junction-entry-inspector/junction-entry-inspector.component.ts new file mode 100644 index 00000000..23c1c6da --- /dev/null +++ b/src/app/views/inspectors/junction-entry-inspector/junction-entry-inspector.component.ts @@ -0,0 +1,36 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { Component, OnDestroy, OnInit } from '@angular/core'; +import { BaseInspector } from 'app/core/components/base-inspector.component'; +import { IComponent } from 'app/core/game-object'; +import { JunctionEntryObject } from 'app/modules/three-js/objects/junction-entry.object'; + +@Component( { + selector: 'app-junction-entry-inspector', + templateUrl: './junction-entry-inspector.component.html', + styleUrls: [ './junction-entry-inspector.component.css' ] +} ) +export class JunctionEntryInspector extends BaseInspector implements OnInit, OnDestroy, IComponent { + + data: JunctionEntryObject; + + constructor () { + + super(); + + } + + ngOnInit () { + + if ( this.data ) this.data.select(); + + } + + ngOnDestroy (): void { + + if ( this.data ) this.data.unselect(); + + } +} diff --git a/src/app/views/inspectors/lane-link-inspector/lane-link-inspector.component.css b/src/app/views/inspectors/lane-link-inspector/lane-link-inspector.component.css new file mode 100644 index 00000000..707523f7 --- /dev/null +++ b/src/app/views/inspectors/lane-link-inspector/lane-link-inspector.component.css @@ -0,0 +1,4 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + diff --git a/src/app/views/inspectors/lane-link-inspector/lane-link-inspector.component.html b/src/app/views/inspectors/lane-link-inspector/lane-link-inspector.component.html new file mode 100644 index 00000000..63f792e0 --- /dev/null +++ b/src/app/views/inspectors/lane-link-inspector/lane-link-inspector.component.html @@ -0,0 +1,15 @@ + + +
    + + + + + + + + + +
    \ No newline at end of file diff --git a/src/app/views/inspectors/lane-link-inspector/lane-link-inspector.component.ts b/src/app/views/inspectors/lane-link-inspector/lane-link-inspector.component.ts new file mode 100644 index 00000000..339c3e94 --- /dev/null +++ b/src/app/views/inspectors/lane-link-inspector/lane-link-inspector.component.ts @@ -0,0 +1,87 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { Component, OnDestroy, OnInit } from '@angular/core'; +import { IComponent } from 'app/core/game-object'; +import { LanePathObject } from '../../../modules/tv-map/models/tv-junction-lane-link'; +import { CommandHistory } from 'app/services/command-history'; +import { DeleteLinkCommand } from 'app/core/commands/delete-link-command'; +import { SetInspectorCommand } from 'app/core/commands/set-inspector-command'; +import { MultiCmdsCommand } from 'app/core/commands/multi-cmds-command'; +import { BaseInspector } from 'app/core/components/base-inspector.component'; + +@Component( { + selector: 'app-lane-link-inspector', + templateUrl: './lane-link-inspector.component.html', + styleUrls: [ './lane-link-inspector.component.css' ] +} ) +export class LaneLinkInspector extends BaseInspector implements OnInit, OnDestroy, IComponent { + + data: LanePathObject; + + constructor () { + + super(); + + } + + ngOnInit () { + + if ( this.data.link && this.data.link.lanePath ) { + + this.data.link.show(); + + } + + if ( this.data.connection ) { + + const road = this.openDrive.getRoadById( this.data.connection.connectingRoad ); + + if ( road && road.spline ) road.spline.show(); + + } + + if ( this.data.connectingRoad ) { + + this.data.connectingRoad.showNodes(); + + this.data.connectingRoad.spline.show(); + + } + } + + ngOnDestroy (): void { + + if ( this.data.link && this.data.link.lanePath ) this.data.link.lanePath.unselect(); + + if ( this.data.connection ) { + + const road = this.openDrive.getRoadById( this.data.connection.connectingRoad ); + + if ( road && road.spline ) road.spline.hide(); + + } + + if ( this.data.connectingRoad ) { + + this.data.connectingRoad.hideNodes(); + + this.data.connectingRoad.spline.hide(); + + } + } + + onDelete () { + + if ( !this.data.link ) return; + + const commands = []; + + commands.push( new DeleteLinkCommand( this.data.connection, this.data.link, this.data ) ); + + commands.push( new SetInspectorCommand( null, null ) ); + + CommandHistory.execute( new MultiCmdsCommand( commands ) ); + } +} diff --git a/src/app/views/inspectors/lane-offset-inspector/lane-offset-inspector.component.html b/src/app/views/inspectors/lane-offset-inspector/lane-offset-inspector.component.html new file mode 100644 index 00000000..ec117aab --- /dev/null +++ b/src/app/views/inspectors/lane-offset-inspector/lane-offset-inspector.component.html @@ -0,0 +1,11 @@ + + +
    + + + + + +
    \ No newline at end of file diff --git a/src/app/views/inspectors/lane-offset-inspector/lane-offset-inspector.component.ts b/src/app/views/inspectors/lane-offset-inspector/lane-offset-inspector.component.ts new file mode 100644 index 00000000..9bcfb490 --- /dev/null +++ b/src/app/views/inspectors/lane-offset-inspector/lane-offset-inspector.component.ts @@ -0,0 +1,109 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { Component, EventEmitter, OnDestroy, OnInit } from '@angular/core'; +import { IComponent } from 'app/core/game-object'; +import { LaneOffsetNode } from 'app/modules/three-js/objects/control-point'; +import { BaseInspector } from 'app/core/components/base-inspector.component'; +import { COLOR } from 'app/shared/utils/colors.service'; +import { NodeFactoryService } from 'app/core/factories/node-factory.service'; +import { SceneService } from 'app/core/services/scene.service'; +import { TvRoad } from 'app/modules/tv-map/models/tv-road.model'; +import { OdLaneReferenceLineBuilder, LineType } from 'app/modules/tv-map/builders/od-lane-reference-line-builder'; + +export class LaneOffsetInspectorData { + constructor ( public node: LaneOffsetNode, public road: TvRoad ) { } +} + +@Component( { + selector: 'app-lane-offset-inspector', + templateUrl: './lane-offset-inspector.component.html' +} ) +export class LaneOffsetInspector extends BaseInspector implements OnInit, IComponent, OnDestroy { + + public static valueChanged = new EventEmitter(); + + public static offsetChanged = new EventEmitter(); + public static distanceChanged = new EventEmitter(); + + public data: LaneOffsetInspectorData; + + public laneHelper = new OdLaneReferenceLineBuilder( null, LineType.SOLID, COLOR.MAGENTA ); + + get laneOffset () { return this.data.node.laneOffset; } + + constructor () { + + super(); + + } + + ngOnInit () { + + if ( this.data.node ) { + + this.data.node.point.select(); + + } + + if ( this.data.road ) this.showNodes( this.data.road ); + } + + ngOnDestroy () { + + if ( this.data.node ) { + + this.data.node.point.unselect(); + + } + + if ( this.data.road ) this.hideNodes( this.data.road ); + } + + onDistanceChanged ( $value: number ) { + + LaneOffsetInspector.distanceChanged.emit( $value ); + + } + + onOffsetChanged ( $value ) { + + LaneOffsetInspector.offsetChanged.emit( $value ); + + } + + private hideNodes ( road: TvRoad ): void { + + road.getLaneOffsets().forEach( laneOffset => { + + if ( laneOffset.mesh ) { + + laneOffset.mesh.visible = false; + + } + + } ); + + } + + private showNodes ( road: TvRoad ) { + + road.getLaneOffsets().forEach( laneOffset => { + + if ( laneOffset.mesh ) { + + laneOffset.mesh.visible = true; + + } else { + + laneOffset.mesh = NodeFactoryService.createLaneOffsetNode( road, laneOffset ); + + SceneService.add( laneOffset.mesh ); + + } + + } ); + + } +} \ No newline at end of file diff --git a/src/app/views/inspectors/lane-roadmark-inspector/lane-roadmark-inspector.component.css b/src/app/views/inspectors/lane-roadmark-inspector/lane-roadmark-inspector.component.css new file mode 100644 index 00000000..707523f7 --- /dev/null +++ b/src/app/views/inspectors/lane-roadmark-inspector/lane-roadmark-inspector.component.css @@ -0,0 +1,4 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + diff --git a/src/app/views/inspectors/lane-roadmark-inspector/lane-roadmark-inspector.component.html b/src/app/views/inspectors/lane-roadmark-inspector/lane-roadmark-inspector.component.html new file mode 100644 index 00000000..db8a2030 --- /dev/null +++ b/src/app/views/inspectors/lane-roadmark-inspector/lane-roadmark-inspector.component.html @@ -0,0 +1,43 @@ + + +
    + + + + + + + Type + + None + Solid + Broken + + + + + Weight + + Standard + Bold + + + + + Color + + Standard (White) + White + Yellow + + + + + + + +
    diff --git a/src/app/views/inspectors/lane-roadmark-inspector/lane-roadmark-inspector.component.ts b/src/app/views/inspectors/lane-roadmark-inspector/lane-roadmark-inspector.component.ts new file mode 100644 index 00000000..500f897e --- /dev/null +++ b/src/app/views/inspectors/lane-roadmark-inspector/lane-roadmark-inspector.component.ts @@ -0,0 +1,101 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { Component, OnDestroy, OnInit } from '@angular/core'; +import { IComponent } from '../../../core/game-object'; +import { TvLaneRoadMark } from '../../../modules/tv-map/models/tv-lane-road-mark'; +import { TvRoadMarkTypes } from '../../../modules/tv-map/models/tv-common'; +import { SetRoadmarkValueCommand } from '../../../core/commands/set-roadmark-value-command'; +import { CommandHistory } from '../../../services/command-history'; +import { MatSelectChange } from '@angular/material'; +import { RemoveRoadmarkCommand } from '../../../core/commands/remove-roadmark-command'; +import { BaseInspector } from '../../../core/components/base-inspector.component'; + +@Component( { + selector: 'app-lane-roadmark-inspector', + templateUrl: './lane-roadmark-inspector.component.html', + styleUrls: [ './lane-roadmark-inspector.component.css' ] +} ) +export class LaneRoadmarkInspectorComponent extends BaseInspector implements OnInit, IComponent, OnDestroy { + + data: TvLaneRoadMark; + + constructor () { + super(); + } + + get roadMark () { + return this.data; + } + + get lane () { + return this.data; + } + + get types () { + return TvRoadMarkTypes; + } + + // get roadmarks (): OdLaneRoadMark[] { + // return this.data.getLaneRoadMarkVector(); + // } + + ngOnInit () { + + if ( this.roadMark && this.roadMark.node && this.roadMark.node.point ) { + + this.roadMark.node.point.select(); + + } + + } + + ngOnDestroy () { + + if ( this.roadMark && this.roadMark.node && this.roadMark.node.point ) { + + this.roadMark.node.point.unselect(); + + } + + } + + onDelete () { + + CommandHistory.execute( new RemoveRoadmarkCommand( this.roadMark, this.roadMark.lane ) ); + + } + + onWidthChanged ( value: number, item: TvLaneRoadMark ) { + + if ( item.width == value ) return; + + CommandHistory.execute( ( new SetRoadmarkValueCommand( item, 'width', value ) ) ); + + } + + onTypeChanged ( $event: MatSelectChange, item: TvLaneRoadMark ) { + + if ( item.type == $event.value ) return; + + CommandHistory.execute( ( new SetRoadmarkValueCommand( item, 'type', $event.value ) ) ); + + } + + onWeightChanged ( $event: MatSelectChange, item: TvLaneRoadMark ) { + + if ( item.weight == $event.value ) return; + + CommandHistory.execute( ( new SetRoadmarkValueCommand( item, 'weight', $event.value ) ) ); + + } + + onColorChanged ( $event: MatSelectChange, item: TvLaneRoadMark ) { + + if ( item.color == $event.value ) return; + + CommandHistory.execute( ( new SetRoadmarkValueCommand( item, 'color', $event.value ) ) ); + + } +} diff --git a/src/app/views/inspectors/lane-type-inspector/lane-inspector.component.html b/src/app/views/inspectors/lane-type-inspector/lane-inspector.component.html new file mode 100644 index 00000000..3f70f3a5 --- /dev/null +++ b/src/app/views/inspectors/lane-type-inspector/lane-inspector.component.html @@ -0,0 +1,32 @@ + + +
    + + + + + + + + + + + Type + + Driving + Border + Shoulder + Sidewalk + Stop + + + + + + +
    + + + \ No newline at end of file diff --git a/src/app/views/inspectors/lane-type-inspector/lane-inspector.component.ts b/src/app/views/inspectors/lane-type-inspector/lane-inspector.component.ts new file mode 100644 index 00000000..37e2da21 --- /dev/null +++ b/src/app/views/inspectors/lane-type-inspector/lane-inspector.component.ts @@ -0,0 +1,91 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { Component } from '@angular/core'; +import { IComponent } from '../../../core/game-object'; +import { TvLane } from '../../../modules/tv-map/models/tv-lane'; +import { TvLaneType } from '../../../modules/tv-map/models/tv-common'; +import { MatSelectChange } from '@angular/material'; +import { FrontSide, MeshBasicMaterial } from 'three'; +import { OdTextures } from '../../../modules/tv-map/builders/od.textures'; +import { COLOR } from '../../../shared/utils/colors.service'; +import { BaseInspector } from '../../../core/components/base-inspector.component'; +import { RemoveLaneCommand } from '../../../core/commands/remove-lane-command'; +import { CommandHistory } from '../../../services/command-history'; +import { SetLanePropertyCommand } from '../../../core/commands/set-lane-property-command'; +import { ICommandCallback } from '../../../core/commands/i-command'; + +@Component( { + selector: 'app-lane-type-inspector', + templateUrl: './lane-inspector.component.html', +} ) +export class LaneInspectorComponent extends BaseInspector implements IComponent, ICommandCallback { + + data: TvLane; + + get types () { + return TvLaneType; + } + + get lane (): TvLane { + return this.data; + } + + onChange ( $event: MatSelectChange ) { + + this.rebuild(); + + } + + onDelete () { + + const road = this.openDrive.getRoadById( this.lane.roadId ); + + const laneSection = road.getLaneSectionById( this.lane.laneSectionId ); + + CommandHistory.execute( new RemoveLaneCommand( laneSection, this.lane ) ); + + } + + onTypeChanged ( $event: MatSelectChange ) { + + const cmd = new SetLanePropertyCommand( this.lane, 'type', $event.value ); + + cmd.callbacks = this; + + CommandHistory.execute( cmd ); + + } + + onExecute (): void { + + this.rebuild(); + + } + + onUndo (): void { + + this.rebuild(); + + } + + onRedo (): void { + + this.rebuild(); + + } + + rebuild () { + + const material = new MeshBasicMaterial( { + map: OdTextures.getLaneTexture( this.lane ), + color: COLOR.WHITE, + wireframe: false, + side: FrontSide + } ); + + this.lane.gameObject.material = null; + this.lane.gameObject.material = material; + } +} diff --git a/src/app/views/inspectors/lane-width-inspector/lane-width-inspector.component.html b/src/app/views/inspectors/lane-width-inspector/lane-width-inspector.component.html new file mode 100644 index 00000000..a4f78e8c --- /dev/null +++ b/src/app/views/inspectors/lane-width-inspector/lane-width-inspector.component.html @@ -0,0 +1,11 @@ + + +
    + + + + + +
    \ No newline at end of file diff --git a/src/app/views/inspectors/lane-width-inspector/lane-width-inspector.component.ts b/src/app/views/inspectors/lane-width-inspector/lane-width-inspector.component.ts new file mode 100644 index 00000000..e1932db8 --- /dev/null +++ b/src/app/views/inspectors/lane-width-inspector/lane-width-inspector.component.ts @@ -0,0 +1,125 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { Component, EventEmitter, OnDestroy, OnInit } from '@angular/core'; +import { IComponent } from 'app/core/game-object'; +import { LaneWidthNode } from 'app/modules/three-js/objects/control-point'; +import { TvLaneWidth } from '../../../modules/tv-map/models/tv-lane-width'; +import { TvLane } from '../../../modules/tv-map/models/tv-lane'; +import { BaseInspector } from 'app/core/components/base-inspector.component'; +import { LaneWidthTool } from 'app/core/tools/lane-width-tool'; +import { SnackBar } from 'app/services/snack-bar.service'; +import { LineType, OdLaneReferenceLineBuilder } from '../../../modules/tv-map/builders/od-lane-reference-line-builder'; +import { CommandHistory } from 'app/services/command-history'; +import { RemoveWidthNodeCommand } from 'app/core/commands/remove-width-node-command'; + +export interface LaneWidthInspectorData { + node: LaneWidthNode; +} + +@Component( { + selector: 'app-lane-width-inspector', + templateUrl: './lane-width-inspector.component.html' +} ) +export class LaneWidthInspector extends BaseInspector implements OnInit, IComponent, OnDestroy { + + public static widthChanged = new EventEmitter(); + public static distanceChanged = new EventEmitter(); + + data: { + node: LaneWidthNode, + lane: TvLane, + }; + + private laneHelper = new OdLaneReferenceLineBuilder( null, LineType.DASHED ); + + constructor () { + + super(); + + } + + get node () { return this.data.node } + + set node ( value ) { this.data.node = value; } + + get width (): TvLaneWidth { + return this.data.node.laneWidth; + } + + get roadId () { + + if ( this.data.lane ) return this.data.lane.roadId; + + if ( this.data.node ) return this.data.node.roadId; + + SnackBar.error( "Road not found" ); + } + + get road () { + + return this.openDrive.getRoadById( this.roadId ); + + } + + ngOnInit () { + + if ( this.data.node ) { + + this.data.node.point.select(); + + } + + if ( this.road ) { + + LaneWidthTool.showNodes( this.road ); + + this.laneHelper.drawRoad( this.road, LineType.DASHED ); + + } + } + + ngOnDestroy () { + + if ( this.data.node ) { + + this.data.node.point.unselect(); + + } + + if ( this.road ) { + + LaneWidthTool.hideNodes( this.road ); + + } + + this.laneHelper.clear(); + } + + onWidthChanged ( $value: number ) { + + // this.width.a = $value; + + LaneWidthInspector.widthChanged.emit( $value ); + + } + + onDistanceChanged ( $value: number ) { + + // this.node.s = this.width.s = $value; + + LaneWidthInspector.distanceChanged.emit( $value ); + + } + + onDelete () { + + if ( this.node ) { + + CommandHistory.execute( new RemoveWidthNodeCommand( this.node ) ); + + } + + } +} diff --git a/src/app/views/inspectors/material-inspector/material-inspector.component.css b/src/app/views/inspectors/material-inspector/material-inspector.component.css new file mode 100644 index 00000000..802f0e61 --- /dev/null +++ b/src/app/views/inspectors/material-inspector/material-inspector.component.css @@ -0,0 +1,22 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +.material-card { + margin: 0px !important; + padding: 0px 0px 16px !important; + border: none !important; +} + +.material-card-thumbnail { + float: left; + margin-right: 8px; +} + +.material-preview { + text-align: center; + border: 0px solid !important; + margin-left: -20px !important; + margin-top: -20px !important; + margin-bottom: -20px !important; +} \ No newline at end of file diff --git a/src/app/views/inspectors/material-inspector/material-inspector.component.html b/src/app/views/inspectors/material-inspector/material-inspector.component.html new file mode 100644 index 00000000..c3415c6d --- /dev/null +++ b/src/app/views/inspectors/material-inspector/material-inspector.component.html @@ -0,0 +1,33 @@ + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    \ No newline at end of file diff --git a/src/app/views/inspectors/material-inspector/material-inspector.component.ts b/src/app/views/inspectors/material-inspector/material-inspector.component.ts new file mode 100644 index 00000000..0fa867fd --- /dev/null +++ b/src/app/views/inspectors/material-inspector/material-inspector.component.ts @@ -0,0 +1,110 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { Component, OnDestroy, OnInit } from '@angular/core'; +import { IComponent } from 'app/core/game-object'; +import { Metadata } from 'app/core/models/metadata.model'; +import { PreviewService } from '../object-preview/object-preview.service'; +import { TvMaterial } from 'app/modules/three-js/objects/tv-material.model'; +import { AssetFactory } from 'app/core/factories/asset-factory.service'; +import { AssetDatabase } from 'app/services/asset-database'; +import { Color } from 'three'; + +@Component( { + selector: 'app-material-inspector', + templateUrl: './material-inspector.component.html', + styleUrls: [ './material-inspector.component.css' ] +} ) +export class MaterialInspector implements OnInit, IComponent, OnDestroy { + + public data: { + material: TvMaterial, + guid: string + }; + + public metadata: Metadata; + + get thumbnail () { return this.metadata.preview; } + + get material () { return this.data.material; } + + get color (): any { return '#' + this.material.color.getHexString(); } + + set color ( value: any ) { this.material.color.setStyle( value ); this.updatePreviewCache(); } + + get emissive () { return '#' + this.material.emissive.getHexString(); } + + set emissive ( value ) { this.material.emissive.setStyle( value ); this.updatePreviewCache(); } + + constructor ( + private previewService: PreviewService, + ) { + } + + ngOnInit () { + + this.metadata = AssetDatabase.getMetadata( this.data.guid ); + + } + + onNameChanged ( $name ) { + + this.material.name = $name; + + } + + onColorChanged ( $value: Color ) { + + this.material.color = $value; + + this.updatePreviewCache(); + } + + // not being used + onEmissiveColorChanged ( $value: Color ) { + + this.material.emissive = $value; + + this.updatePreviewCache(); + } + + onRoughnessChanged ( $value ) { + + this.material.roughness = $value; + + this.updatePreviewCache(); + } + + onMetalnessChanged ( $value ) { + + this.material.metalness = $value; + + this.updatePreviewCache(); + } + + onMapChanged ( $guid: string, map: string ) { + + this.material[ `${ map }Guid` ] = $guid; + this.material[ map ] = AssetDatabase.getInstance( $guid ); + this.material[ map ].needsUpdate = true; + + this.material.needsUpdate = true; + + this.updatePreviewCache(); + } + + + ngOnDestroy () { + + AssetFactory.updateMaterial( this.metadata.path, this.material ); + + this.updatePreviewCache(); + } + + updatePreviewCache () { + + this.metadata.preview = this.previewService.getMaterialPreview( this.material ); + + } +} diff --git a/src/app/views/inspectors/object-preview/object-preview.component.css b/src/app/views/inspectors/object-preview/object-preview.component.css new file mode 100644 index 00000000..707523f7 --- /dev/null +++ b/src/app/views/inspectors/object-preview/object-preview.component.css @@ -0,0 +1,4 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + diff --git a/src/app/views/inspectors/object-preview/object-preview.component.html b/src/app/views/inspectors/object-preview/object-preview.component.html new file mode 100644 index 00000000..c0f45c0b --- /dev/null +++ b/src/app/views/inspectors/object-preview/object-preview.component.html @@ -0,0 +1,7 @@ + + +
    + +
    \ No newline at end of file diff --git a/src/app/views/inspectors/object-preview/object-preview.component.ts b/src/app/views/inspectors/object-preview/object-preview.component.ts new file mode 100644 index 00000000..0602ecbd --- /dev/null +++ b/src/app/views/inspectors/object-preview/object-preview.component.ts @@ -0,0 +1,206 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { AfterViewInit, Component, ElementRef, Input, OnDestroy, OnInit, ViewChild } from '@angular/core'; +import { BoxGeometry, Color, Mesh, MeshBasicMaterial, PerspectiveCamera, Scene, WebGLRenderer, Object3D, DirectionalLight, AmbientLight, Box3, Vector3 } from 'three'; +import { COLOR } from 'app/shared/utils/colors.service'; +import { IViewportController } from 'app/modules/three-js/objects/i-viewport-controller'; +import { OrbitControls } from 'app/modules/three-js/objects/orbit-controls'; +import { PreviewService } from './object-preview.service'; +import { AssetDatabase } from 'app/services/asset-database'; +import { RoadStyle } from 'app/services/road-style.service'; + +@Component( { + selector: 'app-object-preview', + templateUrl: './object-preview.component.html', + styleUrls: [ './object-preview.component.css' ] +} ) +export class ObjectPreviewComponent implements OnInit, AfterViewInit, OnDestroy { + + @Input() path: string + + @Input() guid: string; + + @Input() object: Object3D; + + @Input() objectType: 'default' | 'model' | 'material' | 'roadstyle' = 'default'; + + @ViewChild( 'viewport' ) viewportRef: ElementRef; + + public renderer: WebGLRenderer; + public frameId: number; + + public scene: Scene = new Scene; + public camera: PerspectiveCamera; + public controls: OrbitControls; + + cube: Mesh; + + get canvas (): HTMLCanvasElement { + return this.viewportRef.nativeElement; + } + + get width (): number { + return this.canvas.width; + } + + get height () { + return this.canvas.height; + } + + constructor ( private previewService: PreviewService ) { + + this.render = this.render.bind( this ); + + } + + ngOnInit () { + + + } + + ngAfterViewInit (): void { + + this.renderer = new WebGLRenderer( { alpha: false, antialias: true, precision: 'highp', stencil: false } ); + this.renderer.setPixelRatio( window.devicePixelRatio ); + this.renderer.setClearColor( 0xffffff, 1 ); + this.renderer.autoClear = true; + + // setting this after loading + // this.renderer.setSize( this.width, 100 ); + + this.camera = new PerspectiveCamera( 75, 200 / 100, 0.1, 1000 ); + + this.controls = OrbitControls.getNew( this.camera, this.canvas ); + + this.addDirectionLight(); + + this.canvas.appendChild( this.renderer.domElement ); + + switch ( this.objectType ) { + + case 'model': this.modelPreviewSetup(); break; + + case 'material': this.materialPreviewSetup(); break; + + case 'roadstyle': this.roadStylePreviewSetup(); break; + + default: this.defaultObjectSetup(); break; + } + + setTimeout( () => { + + this.setCanvasSize(); + + }, 300 ); + + this.render(); + } + + ngOnDestroy (): void { + + if ( this.frameId ) cancelAnimationFrame( this.frameId ); + + if ( this.renderer ) this.renderer.dispose(); + + } + + modelPreviewSetup () { + + this.previewService.modelPreviewSetup( this.scene, this.camera, this.object ); + + this.scene.add( this.previewService.ground ); + + this.controls.setRotateEnabled( true ); + } + + materialPreviewSetup () { + + } + + roadStylePreviewSetup () { + + if ( !this.guid ) return; + + const roadStyle = AssetDatabase.getInstance( this.guid ); + + // this.camera.position.z = roadStyle. + + } + + defaultObjectSetup () { + + this.scene.background = new Color( COLOR.BLACK ) + + const geometry = new BoxGeometry( 1, 1, 1 ); + const material = new MeshBasicMaterial( { color: 0x00ff00 } ); + const cube = this.cube = new Mesh( geometry, material ); + + if ( this.object ) { + + this.scene.add( this.object ); + + } else { + + this.scene.add( cube ); + + } + + this.camera = new PerspectiveCamera( 75, 200 / 100, 0.1, 1000 ); + this.camera.position.z = 5; + + // this.controls = new EditorControls( this.camera, this.renderer.domElement ); + this.controls = OrbitControls.getNew( this.camera, this.canvas ); + + // console.log( this.width, this.height, this.canvas.clientWidth, this.canvas.clientHeight ); + } + + + render () { + + // this seems a faster want to call render function + this.frameId = requestAnimationFrame( this.render ); + + // this.frameId = requestAnimationFrame( () => { + // this.render(); + // } ); + + this.renderer.render( this.scene, this.camera ); + + this.controls.update(); + } + + setCanvasSize () { + + const container = this.renderer.domElement.parentElement.parentElement; + + const box = container.getBoundingClientRect(); + + const width = container.clientWidth || 300; + const height = 300; // container.clientHeight; + + this.renderer.setViewport( -box.left, -box.top, width, height ); + this.renderer.setSize( width, height ); + + this.camera.aspect = width / height; + this.camera.updateProjectionMatrix(); + + } + + addDirectionLight () { + + const directionaLight = new DirectionalLight( '0xffffff', 1 ); + + directionaLight.position.set( 5, 10, 7.5 ); + + this.scene.add( directionaLight ); + + this.scene.add( directionaLight.target ); + + const ambientLight = new AmbientLight( 0x404040, 1 ); + + this.scene.add( ambientLight ); + + } +} diff --git a/src/app/views/inspectors/object-preview/object-preview.service.ts b/src/app/views/inspectors/object-preview/object-preview.service.ts new file mode 100644 index 00000000..abae7d53 --- /dev/null +++ b/src/app/views/inspectors/object-preview/object-preview.service.ts @@ -0,0 +1,306 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { Injectable } from '@angular/core'; +import { + AmbientLight, + BoxGeometry, + DirectionalLight, + Material, + Mesh, + MeshBasicMaterial, + Object3D, + PerspectiveCamera, + Scene, + SphereGeometry, + WebGLRenderer, + Box3, + Color, + Vector3, + Texture, + MeshLambertMaterial, + PlaneBufferGeometry, + TextureLoader +} from 'three'; +import { IViewportController } from 'app/modules/three-js/objects/i-viewport-controller'; +import { TvRoadSign } from '../../../modules/tv-map/models/tv-road-sign.model'; +import { RoadStyle } from 'app/services/road-style.service'; +import { TvRoad } from 'app/modules/tv-map/models/tv-road.model'; +import { TvMapBuilder } from 'app/modules/tv-map/builders/od-builder.service'; +import { GameObject } from 'app/core/game-object'; +import { COLOR } from 'app/shared/utils/colors.service'; +import * as THREE from 'three'; +import { TvRoadMarking } from 'app/modules/tv-map/services/tv-marking.service'; +import { AssetDatabase } from 'app/services/asset-database'; + +const WIDTH = 200; +const HEIGHT = 200; + +@Injectable( { + providedIn: 'root' +} ) +export class PreviewService { + + public instance: PreviewService; + + public renderer: WebGLRenderer; + public frameId: number; + + public scene: Scene = new Scene; + public camera: PerspectiveCamera; + public controls: IViewportController; + public ground: Mesh; + + private cube: Mesh; + private sphere: Mesh; + + private groundTexture = new TextureLoader().load( 'assets/grass.jpg' ); + + constructor () { + + this.ngOnInit(); + this.ngAfterViewInit(); + + } + + ngOnInit () { + + const geometry = new BoxGeometry( 1, 1, 1 ); + const material = new MeshBasicMaterial( { color: 0x00ff00 } ); + const cube = this.cube = new Mesh( geometry, material ); + + this.scene.add( cube ); + + this.sphere = new Mesh( new SphereGeometry( 1, 32, 32 ), new MeshBasicMaterial( { color: 0x00ff00 } ) ); + + this.scene.add( this.sphere ); + + this.resetCamera(); + + const directionaLight = new DirectionalLight( '0xffffff', 1 ); + directionaLight.position.set( 45, 45, 45 ); + + this.scene.add( directionaLight ); + + this.scene.add( new AmbientLight( 0x404040, 1 ) ); + + this.addGreenGround( this.scene ); + + this.ground.visible = false; + } + + ngAfterViewInit (): void { + + this.renderer = new WebGLRenderer( { alpha: true, antialias: true, precision: 'highp' } ); + + this.renderer.setSize( WIDTH, HEIGHT ); + + } + + ngOnDestroy (): void { + + if ( this.frameId != null ) { + + cancelAnimationFrame( this.frameId ); + + } + + } + + resetCamera () { + + this.camera = new PerspectiveCamera( 50, WIDTH / HEIGHT, 0.1, 1000 ); + this.camera.position.z = 3; + + } + + // private render () { + + // // this seems a faster want to call render function + // // requestAnimationFrame( this.render ); + + // // this.frameId = requestAnimationFrame( () => { + // // this.render(); + // // } ); + + // this.cube.rotation.x += 0.01; + // this.cube.rotation.y += 0.01; + + // this.renderer.render( this.scene, this.camera ); + + // // console.log( this.scene.children.length ); + + // // this.controls.update(); + + // } + + // setCanvasSize () { + + // const container = this.renderer.domElement.parentElement.parentElement; + + // const box = container.getBoundingClientRect(); + + // const width = container.clientWidth || 300; + // const height = 300; // container.clientHeight; + + // this.renderer.setViewport( -box.left, -box.top, width, height ); + // this.renderer.setSize( width, height ); + + // this.camera.aspect = width / height; + // this.camera.updateProjectionMatrix(); + + // } + + getMaterialPreview ( material: Material ): string { + + if ( !material ) return; + + this.cube.visible = false; + this.sphere.visible = true; + + this.cube.material = this.sphere.material = material; + + this.renderer.setSize( WIDTH, HEIGHT ); + + this.renderer.render( this.scene, this.camera ); + + const image = this.renderer.domElement.toDataURL(); + + this.cube.visible = this.sphere.visible = false; + + return image; + } + + getSignPreview ( sign: TvRoadSign ): string { + + return ''; + + } + + getModelPreview ( model: Object3D ): string { + + if ( !model ) return; + + this.cube.visible = this.sphere.visible = false; + + this.modelPreviewSetup( this.scene, this.camera, model ); + + this.renderer.setSize( WIDTH, HEIGHT ); + + this.renderer.render( this.scene, this.camera ); + + const image = this.renderer.domElement.toDataURL(); + + this.scene.remove( model ); + + this.ground.visible = false; + + return image; + } + + getRoadStylePreview ( roadStyle: RoadStyle ): string { + + this.camera.position.z = 20; + + this.cube.visible = this.sphere.visible = false; + + const gameObject = new GameObject(); + + const road = new TvRoad( '', 0, 1, -1 ); + + road.laneSections.push( roadStyle.laneSection ); + + road.addGeometryLine( 0, -50, 0, 0, 100 ); + + TvMapBuilder.buildRoad( gameObject, road ); + + this.scene.add( gameObject ); + + this.camera.position.z = road.getLeftSideWidth( 0 ) + road.getRightsideWidth( 0 ); + + this.renderer.setSize( WIDTH, HEIGHT ); + + this.renderer.render( this.scene, this.camera ); + + const image = this.renderer.domElement.toDataURL(); + + this.scene.remove( gameObject ); + + return image; + } + + getRoadMarkingPreview ( marking: TvRoadMarking ): string { + + this.camera.position.z = 20; + + this.sphere.visible = false; + + this.cube.visible = true; + + const texture = AssetDatabase.getInstance( marking.textureGuid ) as Texture; + + if ( !texture ) return; + + ( this.cube.material as MeshBasicMaterial ).map = texture; + + ( this.cube.material as MeshBasicMaterial ).map.needsUpdate = true; + + ( this.cube.material as MeshBasicMaterial ).needsUpdate = true; + + this.renderer.setSize( WIDTH, HEIGHT ); + + this.renderer.render( this.scene, this.camera ); + + const image = this.renderer.domElement.toDataURL(); + + this.cube.visible = this.sphere.visible = false; + + return image; + } + + + modelPreviewSetup ( scene: Scene, camera: PerspectiveCamera, object: Object3D ) { + + scene.background = new Color( 0xcce0ff ); + + scene.fog = new THREE.Fog( 0xcce0ff, 500, 10000 ); + + object.position.set( 0, 0, 0 ); + + scene.add( object ); + + this.ground.visible = true; + + const box = new Box3().setFromObject( object ); + + const size = box.getSize( new Vector3() ).length(); + + const center = box.getCenter( new Vector3() ); + + camera.position.set( 0, 1, size * 1 ); + + camera.lookAt( object.position ); + + camera.updateProjectionMatrix(); + } + + private addGreenGround ( scene ) { + + // ground + this.groundTexture.wrapS = this.groundTexture.wrapT = THREE.RepeatWrapping; + this.groundTexture.repeat.set( 1000, 1000 ); + this.groundTexture.anisotropy = 16; + + const groundMaterial = new MeshLambertMaterial( { map: this.groundTexture } ); + + this.ground = new Mesh( new PlaneBufferGeometry( 20000, 20000 ), groundMaterial ); + + this.ground.position.y = 0; + this.ground.rotation.x = - Math.PI / 2; + this.ground.receiveShadow = true; + + scene.add( this.ground ); + } +} + diff --git a/src/app/views/inspectors/prop-curve-inspector/prop-curve-inspector.component.html b/src/app/views/inspectors/prop-curve-inspector/prop-curve-inspector.component.html new file mode 100644 index 00000000..d6904c6b --- /dev/null +++ b/src/app/views/inspectors/prop-curve-inspector/prop-curve-inspector.component.html @@ -0,0 +1,19 @@ + + +
    + + + + + + + +
    + +
    +
    + Control Point
    + +
    \ No newline at end of file diff --git a/src/app/views/inspectors/prop-curve-inspector/prop-curve-inspector.component.ts b/src/app/views/inspectors/prop-curve-inspector/prop-curve-inspector.component.ts new file mode 100644 index 00000000..64b1f240 --- /dev/null +++ b/src/app/views/inspectors/prop-curve-inspector/prop-curve-inspector.component.ts @@ -0,0 +1,57 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { Component, OnDestroy, OnInit, Output, EventEmitter } from '@angular/core'; +import { IComponent } from 'app/core/game-object'; +import { AnyControlPoint } from 'app/modules/three-js/objects/control-point'; +import { PropCurve } from "../../../modules/tv-map/models/prop-curve"; +import { PropService } from 'app/services/prop-service'; + +export class PropCurveInspectorData { + + constructor ( + public controlPoint: AnyControlPoint, + public propCurve: PropCurve + ) { + } +} + +@Component( { + selector: 'app-prop-curve-inspector', + templateUrl: './prop-curve-inspector.component.html' +} ) +export class PropCurveInspectorComponent implements OnInit, IComponent, OnDestroy { + + data: PropCurveInspectorData; + + constructor () { } + + ngOnInit (): void { } + + ngOnDestroy (): void { } + + onSpacingChanged ( $event: string ) { + + this.data.propCurve.spacing = parseFloat( $event ); + + PropService.updateCurveProps( this.data.propCurve ); + + } + + onRotationChanged ( $event: string ) { + + this.data.propCurve.rotation = parseFloat( $event ); + + PropService.updateCurveProps( this.data.propCurve ); + + } + + onPositionVarianceChanged ( $event: string ) { + + this.data.propCurve.positionVariance = parseFloat( $event ); + + PropService.updateCurveProps( this.data.propCurve ); + + } +} diff --git a/src/app/views/inspectors/prop-instance-inspector/prop-instance-inspector.component.html b/src/app/views/inspectors/prop-instance-inspector/prop-instance-inspector.component.html new file mode 100644 index 00000000..712ad94b --- /dev/null +++ b/src/app/views/inspectors/prop-instance-inspector/prop-instance-inspector.component.html @@ -0,0 +1,21 @@ + + +
    + + + +
    + +
    +
    +
    + + + + + +
    + + \ No newline at end of file diff --git a/src/app/views/inspectors/prop-instance-inspector/prop-instance-inspector.component.ts b/src/app/views/inspectors/prop-instance-inspector/prop-instance-inspector.component.ts new file mode 100644 index 00000000..72b15400 --- /dev/null +++ b/src/app/views/inspectors/prop-instance-inspector/prop-instance-inspector.component.ts @@ -0,0 +1,62 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { Component, OnDestroy, OnInit } from '@angular/core'; +import { IComponent } from 'app/core/game-object'; +import { PropInstance } from 'app/core/models/prop-instance.model'; +import { AssetDatabase } from 'app/services/asset-database'; + +// import { ProjectBrowserService } from 'app/views/editor/project-browser/project-browser.service'; + +@Component( { + selector: 'app-prop-instance-inspector', + templateUrl: './prop-instance-inspector.component.html', + styles: [ + `.example-card { + max-width: 400px; + } + .example-header-image { + background-size: cover; + cursor: pointer; + } + .example-header-image:hover { + border: 1px solid blue; + } + + ` + ] + +} ) +export class PropInstanceInspectorComponent implements OnInit, IComponent, OnDestroy { + + data: PropInstance; + + // TODO: Fix Editing, Position, Rotation, Scale + + previewImage: string; + + constructor () { } + + get metadata () { return AssetDatabase.getMetadata( this.data.guid ); } + + ngOnInit (): void { + + if ( this.data ) { + + this.previewImage = this.metadata.preview; + + } + + } + + ngOnDestroy (): void { } + + onPropModelClicked () { + + console.error( "method not implemented" ); + // this.projectBrowser.showFileByGuid( this.data.guid ); + + } + +} diff --git a/src/app/views/inspectors/prop-model-inspector/prop-model-inspector.component.html b/src/app/views/inspectors/prop-model-inspector/prop-model-inspector.component.html new file mode 100644 index 00000000..951747c8 --- /dev/null +++ b/src/app/views/inspectors/prop-model-inspector/prop-model-inspector.component.html @@ -0,0 +1,11 @@ + + +
    + + + + + +
    \ No newline at end of file diff --git a/src/app/views/inspectors/prop-model-inspector/prop-model-inspector.component.ts b/src/app/views/inspectors/prop-model-inspector/prop-model-inspector.component.ts new file mode 100644 index 00000000..942efac7 --- /dev/null +++ b/src/app/views/inspectors/prop-model-inspector/prop-model-inspector.component.ts @@ -0,0 +1,71 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { Component, OnDestroy, OnInit } from '@angular/core'; +import { IComponent } from 'app/core/game-object'; +import { Vector3, Object3D } from 'three'; +import { AssetLoaderService } from 'app/services/asset-loader.service'; +import { DynamicMeta } from 'app/core/models/metadata.model'; +import { CommandHistory } from 'app/services/command-history'; +import { SetValueCommand } from 'app/modules/three-js/commands/set-value-command'; +import { PropModel } from 'app/core/models/prop-model.model'; +import { AssetFactory } from 'app/core/factories/asset-factory.service'; +import { AssetDatabase } from 'app/services/asset-database'; +import { PropService } from 'app/services/prop-service'; + +@Component( { + selector: 'app-prop-model-inspector', + templateUrl: './prop-model-inspector.component.html' +} ) +export class PropModelInspectorComponent implements OnInit, IComponent, OnDestroy { + + public data: DynamicMeta; + + public rotationVariance: Vector3; + + public scaleVariance: Vector3; + + public object: Object3D; + + get prop () { return this.data.data as PropModel; } + + constructor ( private assetService: AssetLoaderService ) { } + + ngOnInit () { + + // this.rotationVariance = new Vector3( this.prop.rotationVariance.x, this.prop.rotationVariance.y, this.prop.rotationVariance.z ); + + // this.scaleVariance = new Vector3( this.prop.scaleVariance.x, this.prop.scaleVariance.y, this.prop.scaleVariance.z ); + + this.object = AssetDatabase.getInstance( this.data.guid ) as Object3D; + + PropService.setProp( this.data ); + } + + ngOnDestroy () { + + this.updateAssetFile(); + + } + + updateAssetFile () { + + // AssetFactory.updatePropModelByGuid( this.data.guid, this.prop ); + + } + + rotationChanged () { + + this.updateAssetFile(); + + CommandHistory.execute( new SetValueCommand( this.prop, 'rotationVariance', this.rotationVariance ) ); + } + + scaleChanged () { + + this.updateAssetFile(); + + CommandHistory.execute( new SetValueCommand( this.prop, 'scaleVariance', this.scaleVariance ) ); + } +} diff --git a/src/app/views/inspectors/prop-polygon-inspector/prop-polygon-inspector.component.css b/src/app/views/inspectors/prop-polygon-inspector/prop-polygon-inspector.component.css new file mode 100644 index 00000000..707523f7 --- /dev/null +++ b/src/app/views/inspectors/prop-polygon-inspector/prop-polygon-inspector.component.css @@ -0,0 +1,4 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + diff --git a/src/app/views/inspectors/prop-polygon-inspector/prop-polygon-inspector.component.html b/src/app/views/inspectors/prop-polygon-inspector/prop-polygon-inspector.component.html new file mode 100644 index 00000000..2cb6a94c --- /dev/null +++ b/src/app/views/inspectors/prop-polygon-inspector/prop-polygon-inspector.component.html @@ -0,0 +1,16 @@ + + +
    + + + + +
    + +
    +
    + Control Point
    + +
    \ No newline at end of file diff --git a/src/app/views/inspectors/prop-polygon-inspector/prop-polygon-inspector.component.ts b/src/app/views/inspectors/prop-polygon-inspector/prop-polygon-inspector.component.ts new file mode 100644 index 00000000..cfea43e2 --- /dev/null +++ b/src/app/views/inspectors/prop-polygon-inspector/prop-polygon-inspector.component.ts @@ -0,0 +1,41 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { Component, OnDestroy, OnInit, Output, EventEmitter } from '@angular/core'; +import { IComponent } from 'app/core/game-object'; +import { AnyControlPoint } from 'app/modules/three-js/objects/control-point'; +import { PropService } from 'app/services/prop-service'; +import { PropPolygon } from 'app/modules/tv-map/models/prop-polygons'; + +export class PropPolygonInspectorData { + + constructor ( + public controlPoint: AnyControlPoint, + public polygon: PropPolygon + ) { + } +} + +@Component( { + selector: 'app-prop-polygon-inspector', + templateUrl: './prop-polygon-inspector.component.html' +} ) +export class PropPolygonInspectorComponent implements OnInit, IComponent, OnDestroy { + + data: PropPolygonInspectorData; + + constructor () { } + + ngOnInit (): void { } + + ngOnDestroy (): void { } + + onDensityChanged ( $event: string ) { + + this.data.polygon.density = parseFloat( $event ); + + PropService.updateCurvePolygonProps( this.data.polygon ); + + } +} diff --git a/src/app/views/inspectors/road-control-point-inspector/road-control-point-inspector.component.css b/src/app/views/inspectors/road-control-point-inspector/road-control-point-inspector.component.css new file mode 100644 index 00000000..707523f7 --- /dev/null +++ b/src/app/views/inspectors/road-control-point-inspector/road-control-point-inspector.component.css @@ -0,0 +1,4 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + diff --git a/src/app/views/inspectors/road-control-point-inspector/road-control-point-inspector.component.html b/src/app/views/inspectors/road-control-point-inspector/road-control-point-inspector.component.html new file mode 100644 index 00000000..6840c73f --- /dev/null +++ b/src/app/views/inspectors/road-control-point-inspector/road-control-point-inspector.component.html @@ -0,0 +1,23 @@ + + +
    + + Road Control Point

    + + + + + + + + + + + + + + + +
    \ No newline at end of file diff --git a/src/app/views/inspectors/road-control-point-inspector/road-control-point-inspector.component.ts b/src/app/views/inspectors/road-control-point-inspector/road-control-point-inspector.component.ts new file mode 100644 index 00000000..63ceb136 --- /dev/null +++ b/src/app/views/inspectors/road-control-point-inspector/road-control-point-inspector.component.ts @@ -0,0 +1,42 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { Component, Input, OnDestroy, OnInit } from '@angular/core'; +import { IComponent } from 'app/core/game-object'; +import { RoadControlPoint } from 'app/modules/three-js/objects/road-control-point'; +import { BaseInspector } from 'app/core/components/base-inspector.component'; +import { TvRoad } from '../../../modules/tv-map/models/tv-road.model'; + +@Component( { + selector: 'app-road-control-point-inspector', + templateUrl: './road-control-point-inspector.component.html', + styleUrls: [ './road-control-point-inspector.component.css' ] +} ) +export class RoadControlPointInspector extends BaseInspector implements OnInit, OnDestroy, IComponent { + + @Input() data: RoadControlPoint; + + constructor () { super() } + + get controlPoint (): RoadControlPoint { return this.data; } + + get road (): TvRoad { return this.data.road } + + ngOnInit (): void { + + if ( this.data ) this.data.select(); + + if ( this.road && this.road.spline ) this.road.spline.show(); + + } + + ngOnDestroy (): void { + + if ( this.data ) this.data.unselect(); + + if ( this.road && this.road.spline ) this.road.spline.hide(); + + } + +} diff --git a/src/app/views/inspectors/road-inspector/road-inspector.component.html b/src/app/views/inspectors/road-inspector/road-inspector.component.html new file mode 100644 index 00000000..758a22c5 --- /dev/null +++ b/src/app/views/inspectors/road-inspector/road-inspector.component.html @@ -0,0 +1,38 @@ + + +
    + + + + + + + + + + + + + + + + + + + + + + + +
    + Road Control Point
    + + + + + + +
    +
    \ No newline at end of file diff --git a/src/app/views/inspectors/road-inspector/road-inspector.component.ts b/src/app/views/inspectors/road-inspector/road-inspector.component.ts new file mode 100644 index 00000000..d7716d9e --- /dev/null +++ b/src/app/views/inspectors/road-inspector/road-inspector.component.ts @@ -0,0 +1,146 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { Component, OnDestroy, OnInit } from '@angular/core'; +import { IComponent } from '../../../core/game-object'; +import { TvRoad } from '../../../modules/tv-map/models/tv-road.model'; +import { TvRoadType } from '../../../modules/tv-map/models/tv-common'; +import { TvMapBuilder } from '../../../modules/tv-map/builders/od-builder.service'; +import { RoadNode } from 'app/modules/three-js/objects/road-node'; +import { CommandHistory } from 'app/services/command-history'; +import { SetValueCommand } from 'app/modules/three-js/commands/set-value-command'; +import { RoadControlPoint } from 'app/modules/three-js/objects/road-control-point'; + +@Component( { + selector: 'app-road-inspector', + templateUrl: './road-inspector.component.html', + styles: [ + ` + .example-card { + max-width: 400px; + } + + .example-header-image { + background-image: url('https://material.angular.io/assets/img/examples/shiba1.jpg'); + background-size: cover; + } + ` + ] +} ) +export class RoadInspector implements OnInit, OnDestroy, IComponent { + + data: { + road: TvRoad, + controlPoint: RoadControlPoint, + node: RoadNode, + }; + + constructor () { } + + get road (): TvRoad { return this.data.road; } + + get controlPoint (): RoadControlPoint { return this.data.controlPoint; } + + get node (): RoadNode { return this.data.node; } + + get roadSpeed () { return this.roadType ? this.roadType.speed.max : null } + + get roadTypesEnum () { return TvRoadType; } + + get type () { return this.roadType ? this.roadType.type : null } + + get roadType () { return this.road ? this.road.getRoadTypeAt( 0 ) : null; } + + ngOnInit () { + + if ( this.road ) this.road.spline.show(); + + if ( this.controlPoint ) this.controlPoint.select(); + + if ( this.data.node ) this.node.selected(); + } + + ngOnDestroy () { + + if ( this.road ) this.road.spline.hide(); + + if ( this.controlPoint ) this.controlPoint.unselect(); + + if ( this.data.node ) this.data.node.unselected(); + + } + + onRoadSpeedChanged ( $value: number ) { + + CommandHistory.execute( new SetValueCommand( this.roadType.speed, 'max', $value ) ); + + } + + onRoadTypeChanged ( $value: any ) { + + CommandHistory.execute( new SetValueCommand( this.roadType, 'type', $value ) ); + + } + + onDrivingMaterialChanged ( $guid: string ) { + + this.road.drivingMaterialGuid = $guid; + + this.road.laneSections.forEach( section => { + + section.lanes.forEach( lane => { + + lane.gameObject.material = TvMapBuilder.getLaneMaterial( this.road, lane ); + + } ) + + } ) + + } + + onSidewalkMaterialChanged ( $guid: string ) { + + this.road.sidewalkMaterialGuid = $guid; + + this.road.laneSections.forEach( section => { + + section.lanes.forEach( lane => { + + lane.gameObject.material = TvMapBuilder.getLaneMaterial( this.road, lane ); + + } ) + + } ) + } + + onBorderMaterialChanged ( $guid: string ) { + + this.road.borderMaterialGuid = $guid; + + this.road.laneSections.forEach( section => { + + section.lanes.forEach( lane => { + + lane.gameObject.material = TvMapBuilder.getLaneMaterial( this.road, lane ); + + } ) + + } ) + } + + onShoulderMaterialChanged ( $guid: string ) { + + this.road.shoulderMaterialGuid = $guid; + + this.road.laneSections.forEach( section => { + + section.lanes.forEach( lane => { + + lane.gameObject.material = TvMapBuilder.getLaneMaterial( this.road, lane ); + + } ) + + } ) + } +} diff --git a/src/app/views/inspectors/road-marking-inspector/road-marking-inspector.component.css b/src/app/views/inspectors/road-marking-inspector/road-marking-inspector.component.css new file mode 100644 index 00000000..707523f7 --- /dev/null +++ b/src/app/views/inspectors/road-marking-inspector/road-marking-inspector.component.css @@ -0,0 +1,4 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + diff --git a/src/app/views/inspectors/road-marking-inspector/road-marking-inspector.component.html b/src/app/views/inspectors/road-marking-inspector/road-marking-inspector.component.html new file mode 100644 index 00000000..6ff8eb26 --- /dev/null +++ b/src/app/views/inspectors/road-marking-inspector/road-marking-inspector.component.html @@ -0,0 +1,13 @@ + + +
    + + + + + + + +
    \ No newline at end of file diff --git a/src/app/views/inspectors/road-marking-inspector/road-marking-inspector.component.ts b/src/app/views/inspectors/road-marking-inspector/road-marking-inspector.component.ts new file mode 100644 index 00000000..dfef6695 --- /dev/null +++ b/src/app/views/inspectors/road-marking-inspector/road-marking-inspector.component.ts @@ -0,0 +1,67 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { Component, OnInit, OnDestroy } from '@angular/core'; +import { IComponent } from 'app/core/game-object'; +import { TvRoadMarking, TvMarkingService } from 'app/modules/tv-map/services/tv-marking.service'; +import { Metadata } from 'app/core/models/metadata.model'; +import { AssetDatabase } from 'app/services/asset-database'; +import { Texture } from 'three'; +import { CommandHistory } from 'app/services/command-history'; +import { SetValueCommand } from 'app/modules/three-js/commands/set-value-command'; +import { AssetFactory } from 'app/core/factories/asset-factory.service'; + +@Component( { + selector: 'app-road-marking-inspector', + templateUrl: './road-marking-inspector.component.html', + styleUrls: [ './road-marking-inspector.component.css' ] +} ) +export class RoadMarkingInspector implements OnInit, IComponent, OnDestroy { + + data: { + roadMarking: TvRoadMarking, + guid: string + }; + + metadata: Metadata; + + texture: Texture; + + constructor () { } + + // get thumbnail () { return this.metadata.preview; } + + get thumbnail () { return this.texture && this.texture.image ? this.texture.image.currentSrc : "" } + + ngOnInit () { + + TvMarkingService.currentMarking = this.data.roadMarking; + + this.metadata = AssetDatabase.getMetadata( this.data.guid ); + + this.texture = AssetDatabase.getInstance( this.data.roadMarking.textureGuid ); + + } + + ngOnDestroy (): void { + + this.updateAssetFile(); + + } + + updateAssetFile () { + + if ( !this.metadata ) return; + + AssetFactory.updateRoadMarking( this.metadata.path, this.data.roadMarking ); + + } + + + onTextureChanged ( $guid: string ) { + + CommandHistory.execute( new SetValueCommand( this.data.roadMarking, 'textureGuid', $guid ) ); + + } +} diff --git a/src/app/views/inspectors/road-object-inspector/road-object-inspector.component.html b/src/app/views/inspectors/road-object-inspector/road-object-inspector.component.html new file mode 100644 index 00000000..2ed3b31a --- /dev/null +++ b/src/app/views/inspectors/road-object-inspector/road-object-inspector.component.html @@ -0,0 +1,25 @@ + + + + + + +
    + + + + + + + + + + + + + + + +
    \ No newline at end of file diff --git a/src/app/views/inspectors/road-object-inspector/road-object-inspector.component.ts b/src/app/views/inspectors/road-object-inspector/road-object-inspector.component.ts new file mode 100644 index 00000000..380f5bcb --- /dev/null +++ b/src/app/views/inspectors/road-object-inspector/road-object-inspector.component.ts @@ -0,0 +1,44 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { Component, OnInit } from '@angular/core'; +import { IComponent } from 'app/core/game-object'; +import { TvRoadObject } from '../../../modules/tv-map/models/tv-road-object'; + +@Component( { + selector: 'app-road-object-inspector', + templateUrl: './road-object-inspector.component.html', +} ) +export class RoadObjectInspectorComponent implements OnInit, IComponent { + + data: TvRoadObject; + + constructor () { + } + + // TODO: Get this properly + get signMaterial () { + return this.data.mesh.material; + } + + ngOnInit () { + } + + updatePosition ( $event: number ) { + + // const road = OdSourceFile.openDrive.getRoadById( this.data.road_id ); + + // const pose = road.getPositionAt( this.data.attr_s, this.data.attr_t ); + + // this.data.GameObject.position.set( pose.x, pose.y, 0 ); + + } + + onDelete () { + + // CommandHistory.execute( new RemoveSignalCommand( this.data ) ); + + } + +} diff --git a/src/app/views/inspectors/road-sign-inspector/road-sign-inspector.component.css b/src/app/views/inspectors/road-sign-inspector/road-sign-inspector.component.css new file mode 100644 index 00000000..707523f7 --- /dev/null +++ b/src/app/views/inspectors/road-sign-inspector/road-sign-inspector.component.css @@ -0,0 +1,4 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + diff --git a/src/app/views/inspectors/road-sign-inspector/road-sign-inspector.component.html b/src/app/views/inspectors/road-sign-inspector/road-sign-inspector.component.html new file mode 100644 index 00000000..df0b9d90 --- /dev/null +++ b/src/app/views/inspectors/road-sign-inspector/road-sign-inspector.component.html @@ -0,0 +1,9 @@ + + +

    + road-sign-inspector works! + + {{ data | json }} +

    diff --git a/src/app/views/inspectors/road-sign-inspector/road-sign-inspector.component.ts b/src/app/views/inspectors/road-sign-inspector/road-sign-inspector.component.ts new file mode 100644 index 00000000..2ce3a71d --- /dev/null +++ b/src/app/views/inspectors/road-sign-inspector/road-sign-inspector.component.ts @@ -0,0 +1,22 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { Component, OnInit } from '@angular/core'; +import { IComponent } from 'app/core/game-object'; +import { TvRoadSign } from '../../../modules/tv-map/models/tv-road-sign.model'; + +@Component( { + selector: 'app-road-sign-inspector', + templateUrl: './road-sign-inspector.component.html', + styleUrls: [ './road-sign-inspector.component.css' ] +} ) +export class RoadSignInspector implements OnInit, IComponent { + + public data: TvRoadSign; + + constructor () { } + + ngOnInit () { } + +} diff --git a/src/app/views/inspectors/road-style-inspector/road-style-inspector.component.css b/src/app/views/inspectors/road-style-inspector/road-style-inspector.component.css new file mode 100644 index 00000000..707523f7 --- /dev/null +++ b/src/app/views/inspectors/road-style-inspector/road-style-inspector.component.css @@ -0,0 +1,4 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + diff --git a/src/app/views/inspectors/road-style-inspector/road-style-inspector.component.html b/src/app/views/inspectors/road-style-inspector/road-style-inspector.component.html new file mode 100644 index 00000000..f2dcbd6a --- /dev/null +++ b/src/app/views/inspectors/road-style-inspector/road-style-inspector.component.html @@ -0,0 +1,5 @@ + + + diff --git a/src/app/views/inspectors/road-style-inspector/road-style-inspector.component.ts b/src/app/views/inspectors/road-style-inspector/road-style-inspector.component.ts new file mode 100644 index 00000000..a4d20dc9 --- /dev/null +++ b/src/app/views/inspectors/road-style-inspector/road-style-inspector.component.ts @@ -0,0 +1,50 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { Component, OnInit, OnDestroy } from '@angular/core'; +import { IComponent, GameObject } from 'app/core/game-object'; +import { RoadStyle } from 'app/services/road-style.service'; +import { TvRoad } from 'app/modules/tv-map/models/tv-road.model'; +import { TvMapBuilder } from 'app/modules/tv-map/builders/od-builder.service'; +import { Object3D } from 'three'; + +@Component( { + selector: 'app-road-style-inspector', + templateUrl: './road-style-inspector.component.html', + styleUrls: [ './road-style-inspector.component.css' ] +} ) +export class RoadStyleInspector implements OnInit, IComponent, OnDestroy { + + object: Object3D; + + constructor () { } + + data: { + roadStyle: RoadStyle, + guid: string + }; + + ngOnInit () { + + console.log( this.data ); + + const gameObject = new GameObject(); + + const road = new TvRoad( "", 0, 1, -1 ); + + road.addGeometryLine( 0, -50, 0, 0, 100 ); + + road.laneSections.push( this.data.roadStyle.laneSection ); + + TvMapBuilder.buildRoad( gameObject, road ); + + this.object = road.gameObject; + } + + ngOnDestroy (): void { + + + } + +} diff --git a/src/app/views/inspectors/shape-inspector/shape-inspector.component.css b/src/app/views/inspectors/shape-inspector/shape-inspector.component.css new file mode 100644 index 00000000..707523f7 --- /dev/null +++ b/src/app/views/inspectors/shape-inspector/shape-inspector.component.css @@ -0,0 +1,4 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + diff --git a/src/app/views/inspectors/shape-inspector/shape-inspector.component.html b/src/app/views/inspectors/shape-inspector/shape-inspector.component.html new file mode 100644 index 00000000..12034360 --- /dev/null +++ b/src/app/views/inspectors/shape-inspector/shape-inspector.component.html @@ -0,0 +1,26 @@ + + +
    + +
    + + + + + + + + + + + + + + + +
    + + +
    \ No newline at end of file diff --git a/src/app/views/inspectors/shape-inspector/shape-inspector.component.ts b/src/app/views/inspectors/shape-inspector/shape-inspector.component.ts new file mode 100644 index 00000000..a40c69aa --- /dev/null +++ b/src/app/views/inspectors/shape-inspector/shape-inspector.component.ts @@ -0,0 +1,90 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { Component, OnInit } from '@angular/core'; +import { IComponent } from '../../../core/game-object'; +import { SceneService } from '../../../core/services/scene.service'; +import * as THREE from 'three'; +import { CurvePath, Mesh, Shape, Vector3 } from 'three'; + +@Component( { + selector: 'app-shape-inspector', + templateUrl: './shape-inspector.component.html', + styleUrls: [ './shape-inspector.component.css' ] +} ) +export class ShapeInspectorComponent implements OnInit, IComponent { + + data: Shape; + + mesh: Mesh; + + _spline: any; + + constructor () { + } + + get shape () { + return this.data; + } + + get randomSpline () { + if ( this._spline ) return this._spline; + var randomPoints = []; + for ( var i = 0; i < 10; i++ ) { + randomPoints.push( new THREE.Vector3( ( i - 4.5 ) * 50, THREE.Math.randFloat( -50, 50 ), THREE.Math.randFloat( -50, 50 ) ) ); + } + var randomSpline = this._spline = new THREE.CatmullRomCurve3( randomPoints ); + return randomSpline; + } + + get geometry () { + + var curePath = new CurvePath(); + + curePath.add( this.randomSpline ); + + var extrudeSettings = { + steps: 200, + bevelEnabled: false, + extrudePath: curePath + }; + + var geometry = new THREE.ExtrudeBufferGeometry( this.shape, extrudeSettings ); + + return geometry; + } + + ngOnInit () { + + var length = 12, width = 8; + + var shape = new THREE.Shape(); + shape.moveTo( 0, 0 ); + shape.lineTo( 0, width ); + shape.lineTo( length, width ); + shape.lineTo( length, 0 ); + shape.lineTo( 0, 0 ); + + this.data = shape; + + var material = new THREE.MeshBasicMaterial( { color: 0x00ff00, wireframe: true } ); + + this.mesh = new THREE.Mesh( this.geometry, material ); + + SceneService.add( this.mesh ); + } + + updateGroupGeometry () { + + this.mesh.geometry.dispose(); + + this.mesh.geometry = this.geometry; + } + + onChange () { + + this.updateGroupGeometry(); + + } +} diff --git a/src/app/views/inspectors/signal-inspector/signal-inspector.component.html b/src/app/views/inspectors/signal-inspector/signal-inspector.component.html new file mode 100644 index 00000000..5e4182da --- /dev/null +++ b/src/app/views/inspectors/signal-inspector/signal-inspector.component.html @@ -0,0 +1,31 @@ + + +
    + + {{model}} + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    diff --git a/src/app/views/inspectors/signal-inspector/signal-inspector.component.ts b/src/app/views/inspectors/signal-inspector/signal-inspector.component.ts new file mode 100644 index 00000000..5636a934 --- /dev/null +++ b/src/app/views/inspectors/signal-inspector/signal-inspector.component.ts @@ -0,0 +1,51 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { Component, OnInit } from '@angular/core'; +import { IComponent } from 'app/core/game-object'; +import { TvRoadSignal } from '../../../modules/tv-map/models/tv-road-signal.model'; +import { RemoveSignalCommand } from '../../../core/commands/remove-signal-command'; +import { TvMapSourceFile } from '../../../modules/tv-map/services/tv-map-source-file'; +import { CommandHistory } from '../../../services/command-history'; + +@Component( { + selector: 'app-signal-inspector', + templateUrl: './signal-inspector.component.html', +} ) +export class OdSignalInspectorComponent implements OnInit, IComponent { + + data: TvRoadSignal; + + constructor () { + } + + // TODO: Get this properly + get signMaterial () { + return this.data.gameObject.children[ 0 ].children[ 0 ][ 'material' ]; + } + + get model () { + return this.data.assetName.attr_value; + } + + ngOnInit () { + } + + updatePosition ( $event: number ) { + + const road = TvMapSourceFile.openDrive.getRoadById( this.data.roadId ); + + const pose = road.getPositionAt( this.data.s, this.data.t ); + + this.data.gameObject.position.set( pose.x, pose.y, 0 ); + + } + + onDelete () { + + CommandHistory.execute( new RemoveSignalCommand( this.data ) ); + + } + +} diff --git a/src/app/views/inspectors/texture-inspector/texture-inspector.component.css b/src/app/views/inspectors/texture-inspector/texture-inspector.component.css new file mode 100644 index 00000000..707523f7 --- /dev/null +++ b/src/app/views/inspectors/texture-inspector/texture-inspector.component.css @@ -0,0 +1,4 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + diff --git a/src/app/views/inspectors/texture-inspector/texture-inspector.component.html b/src/app/views/inspectors/texture-inspector/texture-inspector.component.html new file mode 100644 index 00000000..79c0a575 --- /dev/null +++ b/src/app/views/inspectors/texture-inspector/texture-inspector.component.html @@ -0,0 +1,37 @@ + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + +
    \ No newline at end of file diff --git a/src/app/views/inspectors/texture-inspector/texture-inspector.component.ts b/src/app/views/inspectors/texture-inspector/texture-inspector.component.ts new file mode 100644 index 00000000..1bd2c165 --- /dev/null +++ b/src/app/views/inspectors/texture-inspector/texture-inspector.component.ts @@ -0,0 +1,59 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { Component, OnDestroy, OnInit } from '@angular/core'; +import { Texture } from 'three'; +import { IComponent } from 'app/core/game-object'; +import { Metadata } from 'app/core/models/metadata.model'; +import { AssetDatabase } from 'app/services/asset-database'; +import { MetadataFactory } from 'app/core/factories/metadata-factory.service'; + +@Component( { + selector: 'app-texture-inspector', + templateUrl: './texture-inspector.component.html', + styleUrls: [ './texture-inspector.component.css' ] +} ) +export class TextureInspector implements OnInit, IComponent, OnDestroy { + + // @Input() texture: Texture; + + public data: { + texture: Texture, + guid: string + }; + + public metadata: Metadata; + + get texture (): Texture { return this.data.texture; } + + constructor () { } + + ngOnInit () { + + this.metadata = AssetDatabase.getMetadata( this.data.guid ); + + console.log( this.data ); + // console.log( this.texture ); + // console.log( this.texture.image ); + + } + + ngOnDestroy () { + + if ( this.texture ) { + + // TODO: fix file saving + MetadataFactory.createTextureMetadata( this.metadata.guid, this.metadata.path, this.texture ); + + } + } + + onChange ( $event ) { + + this.texture.needsUpdate = true; + + } + + +} diff --git a/src/app/views/inspectors/transform-inspector/transform-inspector.component.html b/src/app/views/inspectors/transform-inspector/transform-inspector.component.html new file mode 100644 index 00000000..4a23aea4 --- /dev/null +++ b/src/app/views/inspectors/transform-inspector/transform-inspector.component.html @@ -0,0 +1,45 @@ + + +
    + +
    + Position
    + + + + + + + + + +
    + +
    + Rotation
    + + + + + + + + + +
    + +
    + Scale
    + + + + + + + + + +
    +
    \ No newline at end of file diff --git a/src/app/views/inspectors/transform-inspector/transform-inspector.component.ts b/src/app/views/inspectors/transform-inspector/transform-inspector.component.ts new file mode 100644 index 00000000..738052f4 --- /dev/null +++ b/src/app/views/inspectors/transform-inspector/transform-inspector.component.ts @@ -0,0 +1,68 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { Component, Input, OnInit } from '@angular/core'; +import { IComponent } from '../../../core/game-object'; +import { Euler, Object3D, Vector3 } from 'three'; +import { CommandHistory } from '../../../services/command-history'; +import { SetPositionCommand } from '../../../modules/three-js/commands/set-position-command'; +import { SetRotationCommand } from '../../../modules/three-js/commands/set-rotation-command'; +import { SetScaleCommand } from '../../../modules/three-js/commands/set-scale-command'; +import { Maths } from '../../../utils/maths'; + +@Component( { + selector: 'app-transform-inspector', + templateUrl: './transform-inspector.component.html', +} ) +export class TransformInspectorComponent implements OnInit, IComponent { + + @Input() data: Object3D; + + @Input() showPosition = true; + @Input() showRotation = true; + @Input() showScale = true; + + public position: Vector3; + public rotation: Euler; + public scale: Vector3; + + constructor () { + } + + + ngOnInit (): void { + + this.position = this.data.position.clone(); + this.rotation = this.data.rotation.clone(); + this.scale = this.data.scale.clone(); + + this.rotation.x *= Maths.Rad2Deg; + this.rotation.y *= Maths.Rad2Deg; + this.rotation.z *= Maths.Rad2Deg; + } + + positionChanged () { + + CommandHistory.execute( new SetPositionCommand( this.data, this.position ) ); + + } + + rotationChanged () { + + const rotation = this.rotation.clone(); + + rotation.x *= Maths.Deg2Rad; + rotation.y *= Maths.Deg2Rad; + rotation.z *= Maths.Deg2Rad; + + CommandHistory.execute( new SetRotationCommand( this.data, rotation ) ); + + } + + scaleChanged () { + + CommandHistory.execute( new SetScaleCommand( this.data, this.scale ) ); + + } +} diff --git a/src/app/views/sessions/error/error.component.css b/src/app/views/sessions/error/error.component.css new file mode 100755 index 00000000..707523f7 --- /dev/null +++ b/src/app/views/sessions/error/error.component.css @@ -0,0 +1,4 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + diff --git a/src/app/views/sessions/error/error.component.html b/src/app/views/sessions/error/error.component.html new file mode 100755 index 00000000..d780a4eb --- /dev/null +++ b/src/app/views/sessions/error/error.component.html @@ -0,0 +1,26 @@ + + +
    +
    +
    + warning +
    +

    500

    +
    Server Error!
    +
    +
    + +
    + + +
    +
    +
    diff --git a/src/app/views/sessions/error/error.component.ts b/src/app/views/sessions/error/error.component.ts new file mode 100755 index 00000000..9a008c76 --- /dev/null +++ b/src/app/views/sessions/error/error.component.ts @@ -0,0 +1,19 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { Component, OnInit } from '@angular/core'; + +@Component({ + selector: 'app-error', + templateUrl: './error.component.html', + styleUrls: ['./error.component.css'] +}) +export class ErrorComponent implements OnInit { + + constructor() { } + + ngOnInit() { + } + +} diff --git a/src/app/views/sessions/forgot-password/forgot-password.component.css b/src/app/views/sessions/forgot-password/forgot-password.component.css new file mode 100755 index 00000000..707523f7 --- /dev/null +++ b/src/app/views/sessions/forgot-password/forgot-password.component.css @@ -0,0 +1,4 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + diff --git a/src/app/views/sessions/forgot-password/forgot-password.component.html b/src/app/views/sessions/forgot-password/forgot-password.component.html new file mode 100755 index 00000000..bbf55f10 --- /dev/null +++ b/src/app/views/sessions/forgot-password/forgot-password.component.html @@ -0,0 +1,33 @@ + + +
    +
    + + + +
    +

    New password will be sent to your email address

    +
    +
    + +
    + + + + Email is required +
    + + + +
    +
    +
    +
    +
    \ No newline at end of file diff --git a/src/app/views/sessions/forgot-password/forgot-password.component.ts b/src/app/views/sessions/forgot-password/forgot-password.component.ts new file mode 100755 index 00000000..7963f9a7 --- /dev/null +++ b/src/app/views/sessions/forgot-password/forgot-password.component.ts @@ -0,0 +1,51 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { Component, OnInit, ViewChild } from '@angular/core'; +import { MatProgressBar, MatButton } from '@angular/material'; +import { AuthService } from 'app/core/services/auth.service'; +import { SnackBar } from 'app/services/snack-bar.service'; + +@Component( { + selector: 'app-forgot-password', + templateUrl: './forgot-password.component.html', + styleUrls: [ './forgot-password.component.css' ] +} ) +export class ForgotPasswordComponent implements OnInit { + + userEmail; + + @ViewChild( MatProgressBar ) progressBar: MatProgressBar; + + @ViewChild( MatButton ) submitButton: MatButton; + + constructor ( private authService: AuthService ) { } + + ngOnInit () { } + + submitEmail () { + + this.submitButton.disabled = true; + + this.progressBar.mode = 'indeterminate'; + + this.authService.forgotPassword( this.userEmail ).subscribe( response => { + + this.submitButton.disabled = false; + + this.progressBar.mode = 'determinate'; + + SnackBar.show( "Please check your email inbox to reset the password" ); + + }, error => { + + this.submitButton.disabled = false; + + this.progressBar.mode = 'determinate'; + + SnackBar.error( "Error occured in resseting password" ); + + } ); + } +} diff --git a/src/app/views/sessions/lockscreen/lockscreen.component.css b/src/app/views/sessions/lockscreen/lockscreen.component.css new file mode 100755 index 00000000..707523f7 --- /dev/null +++ b/src/app/views/sessions/lockscreen/lockscreen.component.css @@ -0,0 +1,4 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + diff --git a/src/app/views/sessions/lockscreen/lockscreen.component.html b/src/app/views/sessions/lockscreen/lockscreen.component.html new file mode 100755 index 00000000..f17ed46b --- /dev/null +++ b/src/app/views/sessions/lockscreen/lockscreen.component.html @@ -0,0 +1,41 @@ + + +
    +
    + + + +
    +
    + +
    John Doe
    + Last seen 1 hour ago +
    +
    +
    + + + + Password is required +
    + + + +
    +
    +
    +
    +
    +
    + diff --git a/src/app/views/sessions/lockscreen/lockscreen.component.ts b/src/app/views/sessions/lockscreen/lockscreen.component.ts new file mode 100755 index 00000000..118ff1ef --- /dev/null +++ b/src/app/views/sessions/lockscreen/lockscreen.component.ts @@ -0,0 +1,31 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { Component, OnInit, ViewChild } from '@angular/core'; +import { MatProgressBar, MatButton } from '@angular/material'; + +@Component({ + selector: 'app-lockscreen', + templateUrl: './lockscreen.component.html', + styleUrls: ['./lockscreen.component.css'] +}) +export class LockscreenComponent implements OnInit { + @ViewChild(MatProgressBar) progressBar: MatProgressBar; + @ViewChild(MatButton) submitButton: MatButton; + + lockscreenData = { + password: '' + } + + constructor() { } + + ngOnInit() { + } + + unlock() { + + this.submitButton.disabled = true; + this.progressBar.mode = 'indeterminate'; + } +} diff --git a/src/app/views/sessions/not-found/not-found.component.css b/src/app/views/sessions/not-found/not-found.component.css new file mode 100755 index 00000000..707523f7 --- /dev/null +++ b/src/app/views/sessions/not-found/not-found.component.css @@ -0,0 +1,4 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + diff --git a/src/app/views/sessions/not-found/not-found.component.html b/src/app/views/sessions/not-found/not-found.component.html new file mode 100755 index 00000000..78eec6ab --- /dev/null +++ b/src/app/views/sessions/not-found/not-found.component.html @@ -0,0 +1,21 @@ + + +
    +
    +
    + error +
    +

    404

    +
    Page Not Found!
    +
    +
    + +
    + + + +
    +
    +
    \ No newline at end of file diff --git a/src/app/views/sessions/not-found/not-found.component.ts b/src/app/views/sessions/not-found/not-found.component.ts new file mode 100755 index 00000000..1edc962c --- /dev/null +++ b/src/app/views/sessions/not-found/not-found.component.ts @@ -0,0 +1,19 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { Component, OnInit } from '@angular/core'; + +@Component( { + selector: 'app-not-found', + templateUrl: './not-found.component.html', + styleUrls: [ './not-found.component.css' ] +} ) +export class NotFoundComponent implements OnInit { + + constructor () { } + + ngOnInit () { + } + +} diff --git a/src/app/views/sessions/sessions.module.ts b/src/app/views/sessions/sessions.module.ts new file mode 100755 index 00000000..ab9541c1 --- /dev/null +++ b/src/app/views/sessions/sessions.module.ts @@ -0,0 +1,50 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { RouterModule } from '@angular/router'; +import { + MatProgressBarModule, + MatButtonModule, + MatInputModule, + MatCardModule, + MatCheckboxModule, + MatIconModule +} from '@angular/material'; +import { FlexLayoutModule } from '@angular/flex-layout'; + +import { ForgotPasswordComponent } from './forgot-password/forgot-password.component'; +import { LockscreenComponent } from './lockscreen/lockscreen.component'; +import { SigninComponent } from './signin/signin.component'; +import { SignupComponent } from './signup/signup.component'; +import { SessionsRoutes } from './sessions.routing'; +import { NotFoundComponent } from './not-found/not-found.component'; +import { ErrorComponent } from './error/error.component'; + +@NgModule( { + imports: [ + CommonModule, + FormsModule, + ReactiveFormsModule, + MatProgressBarModule, + MatButtonModule, + MatInputModule, + MatCardModule, + MatCheckboxModule, + MatIconModule, + FlexLayoutModule, + RouterModule.forChild( SessionsRoutes ) + ], + declarations: [ + ForgotPasswordComponent, + LockscreenComponent, + SigninComponent, + SignupComponent, + NotFoundComponent, + ErrorComponent + ] +} ) +export class SessionsModule { } diff --git a/src/app/views/sessions/sessions.routing.ts b/src/app/views/sessions/sessions.routing.ts new file mode 100755 index 00000000..1be04b43 --- /dev/null +++ b/src/app/views/sessions/sessions.routing.ts @@ -0,0 +1,43 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { Routes } from '@angular/router'; + +import { ForgotPasswordComponent } from './forgot-password/forgot-password.component'; +import { LockscreenComponent } from './lockscreen/lockscreen.component'; +import { SigninComponent } from './signin/signin.component'; +import { SignupComponent } from './signup/signup.component'; +import { NotFoundComponent } from './not-found/not-found.component'; +import { ErrorComponent } from './error/error.component'; + +export const SessionsRoutes: Routes = [ + { + path: 'sessions', + children: [ { + path: 'signup', + component: SignupComponent, + data: { title: 'Signup' } + }, { + path: 'signin', + component: SigninComponent, + data: { title: 'Signin' } + }, { + path: 'forgot-password', + component: ForgotPasswordComponent, + data: { title: 'Forgot password' } + }, { + path: 'lockscreen', + component: LockscreenComponent, + data: { title: 'Lockscreen' } + }, { + path: '404', + component: NotFoundComponent, + data: { title: 'Not Found' } + }, { + path: 'error', + component: ErrorComponent, + data: { title: 'Error' } + } ] + } +]; diff --git a/src/app/views/sessions/signin/signin.component.css b/src/app/views/sessions/signin/signin.component.css new file mode 100755 index 00000000..970306a8 --- /dev/null +++ b/src/app/views/sessions/signin/signin.component.css @@ -0,0 +1,7 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +a.link { + color: #00a8ab; +} \ No newline at end of file diff --git a/src/app/views/sessions/signin/signin.component.html b/src/app/views/sessions/signin/signin.component.html new file mode 100755 index 00000000..a0b3919d --- /dev/null +++ b/src/app/views/sessions/signin/signin.component.html @@ -0,0 +1,39 @@ + + +
    +
    + + + +
    +
    +
    + + + + Email is required + Incorrect Email +
    + +
    + + + + Password is required +
    + + + + +
    +
    +
    + +
    +
    \ No newline at end of file diff --git a/src/app/views/sessions/signin/signin.component.ts b/src/app/views/sessions/signin/signin.component.ts new file mode 100755 index 00000000..548770db --- /dev/null +++ b/src/app/views/sessions/signin/signin.component.ts @@ -0,0 +1,89 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { Component, OnInit, ViewChild } from '@angular/core'; +import { MatProgressBar, MatButton } from '@angular/material'; +import { Validators, FormGroup, FormControl } from '@angular/forms'; +import { AuthService } from 'app/core/services/auth.service'; +import { SnackBar } from 'app/services/snack-bar.service'; +import { Router } from '@angular/router'; +import { AppService } from 'app/core/services/app.service'; +import { AppLinks } from 'app/services/app-links'; +import { ElectronService } from 'ngx-electron'; + +@Component( { + selector: 'app-signin', + templateUrl: './signin.component.html', + styleUrls: [ './signin.component.css' ] +} ) +export class SigninComponent implements OnInit { + + @ViewChild( MatProgressBar ) progressBar: MatProgressBar; + @ViewChild( MatButton ) submitButton: MatButton; + + signinForm: FormGroup; + + constructor ( private authService: AuthService, private router: Router, private electron: ElectronService ) { } + + ngOnInit () { + + this.authService.logout(); + + this.signinForm = new FormGroup( { + email: new FormControl( '', [Validators.required, Validators.email] ), + password: new FormControl( '', Validators.required ), + rememberMe: new FormControl( false ) + } ); + + } + + signin () { + + const formData = this.signinForm.value; + + this.submitButton.disabled = true; + + this.progressBar.mode = 'indeterminate'; + + this.authService + .login( formData.email, formData.password ) + .subscribe( res => this.onSuccess( res ), err => this.onError( err ) ); + } + + onError ( error: any ) { + + this.submitButton.disabled = false; + this.progressBar.mode = 'determinate'; + + let message = 'some error occured'; + + if ( error != null && error.message != null ) message = error.message; + + SnackBar.error( message ); + + } + + onSuccess ( response: any ) { + + this.submitButton.disabled = false; + this.progressBar.mode = 'determinate'; + + SnackBar.show( 'Successfully signed in' ); + + this.router.navigateByUrl( AppService.homeUrl ); + + } + + onCreateAccount () { + + this.electron.shell.openExternal( AppLinks.createAccountLink ); + + } + + onForgotPassword () { + + this.electron.shell.openExternal( AppLinks.forgotPasswordLink ); + + } +} diff --git a/src/app/views/sessions/signup/signup.component.css b/src/app/views/sessions/signup/signup.component.css new file mode 100755 index 00000000..707523f7 --- /dev/null +++ b/src/app/views/sessions/signup/signup.component.css @@ -0,0 +1,4 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + diff --git a/src/app/views/sessions/signup/signup.component.html b/src/app/views/sessions/signup/signup.component.html new file mode 100755 index 00000000..4d0953ea --- /dev/null +++ b/src/app/views/sessions/signup/signup.component.html @@ -0,0 +1,81 @@ + + +
    +
    + + + +
    + +

    Sign up to use our service

    +
    +
    +
    + + + + Name is required +
    + +
    + + + + Email is required + + Invaild email address +
    + +
    + + + + Password is required +
    + +
    + + + + Confirm password is required. + Passwords do not math. +
    + +
    + I have + read and agree to the terms of service. + + You must agree to the terms and conditions +
    + + + +
    +
    +
    +
    +
    \ No newline at end of file diff --git a/src/app/views/sessions/signup/signup.component.ts b/src/app/views/sessions/signup/signup.component.ts new file mode 100755 index 00000000..7c416d31 --- /dev/null +++ b/src/app/views/sessions/signup/signup.component.ts @@ -0,0 +1,101 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import { Component, OnInit, ViewChild } from '@angular/core'; +import { MatProgressBar, MatButton } from '@angular/material'; +import { Validators, FormGroup, FormControl } from '@angular/forms'; +import { CustomValidators } from 'ng2-validation'; +import { AuthService } from 'app/core/services/auth.service'; +import { Router } from '@angular/router'; +import { ElectronService } from 'ngx-electron'; +import { SnackBar } from 'app/services/snack-bar.service'; +import { AppService } from 'app/core/services/app.service'; + +@Component( { + selector: 'app-signup', + templateUrl: './signup.component.html', + styleUrls: [ './signup.component.css' ] +} ) +export class SignupComponent implements OnInit { + + @ViewChild( MatProgressBar ) progressBar: MatProgressBar; + @ViewChild( MatButton ) submitButton: MatButton; + + signupForm: FormGroup; + + constructor ( + private authService: AuthService, + private router: Router, + private electron: ElectronService + ) { } + + ngOnInit () { + + this.authService.logout(); + + const password = new FormControl( '', Validators.required ); + const confirmPassword = new FormControl( '', CustomValidators.equalTo( password ) ); + + this.signupForm = new FormGroup( { + name: new FormControl( '', [ Validators.required ] ), + email: new FormControl( '', [ Validators.required, Validators.email ] ), + password, + confirmPassword, + agreed: new FormControl( '', ( control: FormControl ) => { + const agreed = control.value; + if ( !agreed ) { + return { agreed: true }; + } + return null; + } ) + } ); + } + + signup () { + + const form = this.signupForm.value; + + this.submitButton.disabled = true; + + this.progressBar.mode = 'indeterminate'; + + console.log( form ); + + this.authService.register( + form.name, + form.email, + form.password, + form.confirmPassword, + form.agreed + ).subscribe( + response => this.onSuccess( response ), + error => this.onError( error ) + ); + } + + onSuccess ( response: any ): void { + + this.submitButton.disabled = false; + this.progressBar.mode = 'determinate'; + + SnackBar.show( 'Successfully signed in' ); + + this.router.navigateByUrl( AppService.homeUrl ); + } + + + onError ( error: any ): void { + + this.submitButton.disabled = false; + this.progressBar.mode = 'determinate'; + + let message = 'some error occured'; + + if ( error != null && error.message != null ) message = error.message; + + SnackBar.error( message ); + + } + +} diff --git a/src/assets/.gitkeep b/src/assets/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/src/assets/arrow.svg b/src/assets/arrow.svg new file mode 100644 index 00000000..d9cb2755 --- /dev/null +++ b/src/assets/arrow.svg @@ -0,0 +1,12 @@ + + 144-1442847_exit-to-app-button-vector-entry-exit-i + + + + + + + \ No newline at end of file diff --git a/src/assets/arrow_icon.psd b/src/assets/arrow_icon.psd new file mode 100644 index 00000000..a8ce648c Binary files /dev/null and b/src/assets/arrow_icon.psd differ diff --git a/src/assets/asphalt.jpg b/src/assets/asphalt.jpg new file mode 100644 index 00000000..0711bd7a Binary files /dev/null and b/src/assets/asphalt.jpg differ diff --git a/src/assets/asphalt2.png b/src/assets/asphalt2.png new file mode 100644 index 00000000..be8b0c35 Binary files /dev/null and b/src/assets/asphalt2.png differ diff --git a/src/assets/car-icon-circle.png b/src/assets/car-icon-circle.png new file mode 100644 index 00000000..a3d1bcc3 Binary files /dev/null and b/src/assets/car-icon-circle.png differ diff --git a/src/assets/car-icon-square.png b/src/assets/car-icon-square.png new file mode 100644 index 00000000..2d8aa773 Binary files /dev/null and b/src/assets/car-icon-square.png differ diff --git a/src/assets/car.png b/src/assets/car.png new file mode 100644 index 00000000..4c367786 Binary files /dev/null and b/src/assets/car.png differ diff --git a/src/assets/decals/manhole-1.png b/src/assets/decals/manhole-1.png new file mode 100644 index 00000000..0ee7df25 Binary files /dev/null and b/src/assets/decals/manhole-1.png differ diff --git a/src/assets/disc.png b/src/assets/disc.png new file mode 100644 index 00000000..92572c62 Binary files /dev/null and b/src/assets/disc.png differ diff --git a/src/assets/flat-asphalt.png b/src/assets/flat-asphalt.png new file mode 100644 index 00000000..a9124bd0 Binary files /dev/null and b/src/assets/flat-asphalt.png differ diff --git a/src/assets/flat-roadmarks.png b/src/assets/flat-roadmarks.png new file mode 100644 index 00000000..15065035 Binary files /dev/null and b/src/assets/flat-roadmarks.png differ diff --git a/src/assets/folder-icon.png b/src/assets/folder-icon.png new file mode 100644 index 00000000..c8631c0c Binary files /dev/null and b/src/assets/folder-icon.png differ diff --git a/src/assets/fonts/helvetiker_regular.typeface.json b/src/assets/fonts/helvetiker_regular.typeface.json new file mode 100644 index 00000000..d19293b4 --- /dev/null +++ b/src/assets/fonts/helvetiker_regular.typeface.json @@ -0,0 +1 @@ +{"glyphs":{"ο":{"x_min":0,"x_max":712,"ha":815,"o":"m 356 -25 q 96 88 192 -25 q 0 368 0 201 q 92 642 0 533 q 356 761 192 761 q 617 644 517 761 q 712 368 712 533 q 619 91 712 201 q 356 -25 520 -25 m 356 85 q 527 175 465 85 q 583 369 583 255 q 528 562 583 484 q 356 651 466 651 q 189 560 250 651 q 135 369 135 481 q 187 177 135 257 q 356 85 250 85 "},"S":{"x_min":0,"x_max":788,"ha":890,"o":"m 788 291 q 662 54 788 144 q 397 -26 550 -26 q 116 68 226 -26 q 0 337 0 168 l 131 337 q 200 152 131 220 q 384 85 269 85 q 557 129 479 85 q 650 270 650 183 q 490 429 650 379 q 194 513 341 470 q 33 739 33 584 q 142 964 33 881 q 388 1041 242 1041 q 644 957 543 1041 q 756 716 756 867 l 625 716 q 561 874 625 816 q 395 933 497 933 q 243 891 309 933 q 164 759 164 841 q 325 609 164 656 q 625 526 475 568 q 788 291 788 454 "},"¦":{"x_min":343,"x_max":449,"ha":792,"o":"m 449 462 l 343 462 l 343 986 l 449 986 l 449 462 m 449 -242 l 343 -242 l 343 280 l 449 280 l 449 -242 "},"/":{"x_min":183.25,"x_max":608.328125,"ha":792,"o":"m 608 1041 l 266 -129 l 183 -129 l 520 1041 l 608 1041 "},"Τ":{"x_min":-0.4375,"x_max":777.453125,"ha":839,"o":"m 777 893 l 458 893 l 458 0 l 319 0 l 319 892 l 0 892 l 0 1013 l 777 1013 l 777 893 "},"y":{"x_min":0,"x_max":684.78125,"ha":771,"o":"m 684 738 l 388 -83 q 311 -216 356 -167 q 173 -279 252 -279 q 97 -266 133 -279 l 97 -149 q 132 -155 109 -151 q 168 -160 155 -160 q 240 -114 213 -160 q 274 -26 248 -98 l 0 738 l 137 737 l 341 139 l 548 737 l 684 738 "},"Π":{"x_min":0,"x_max":803,"ha":917,"o":"m 803 0 l 667 0 l 667 886 l 140 886 l 140 0 l 0 0 l 0 1012 l 803 1012 l 803 0 "},"ΐ":{"x_min":-111,"x_max":339,"ha":361,"o":"m 339 800 l 229 800 l 229 925 l 339 925 l 339 800 m -1 800 l -111 800 l -111 925 l -1 925 l -1 800 m 284 3 q 233 -10 258 -5 q 182 -15 207 -15 q 85 26 119 -15 q 42 200 42 79 l 42 737 l 167 737 l 168 215 q 172 141 168 157 q 226 101 183 101 q 248 103 239 101 q 284 112 257 104 l 284 3 m 302 1040 l 113 819 l 30 819 l 165 1040 l 302 1040 "},"g":{"x_min":0,"x_max":686,"ha":838,"o":"m 686 34 q 586 -213 686 -121 q 331 -306 487 -306 q 131 -252 216 -306 q 31 -84 31 -190 l 155 -84 q 228 -174 166 -138 q 345 -207 284 -207 q 514 -109 454 -207 q 564 89 564 -27 q 461 6 521 36 q 335 -23 401 -23 q 88 100 184 -23 q 0 370 0 215 q 87 634 0 522 q 330 758 183 758 q 457 728 398 758 q 564 644 515 699 l 564 737 l 686 737 l 686 34 m 582 367 q 529 560 582 481 q 358 652 468 652 q 189 561 250 652 q 135 369 135 482 q 189 176 135 255 q 361 85 251 85 q 529 176 468 85 q 582 367 582 255 "},"²":{"x_min":0,"x_max":442,"ha":539,"o":"m 442 383 l 0 383 q 91 566 0 492 q 260 668 176 617 q 354 798 354 727 q 315 875 354 845 q 227 905 277 905 q 136 869 173 905 q 99 761 99 833 l 14 761 q 82 922 14 864 q 232 974 141 974 q 379 926 316 974 q 442 797 442 878 q 351 635 442 704 q 183 539 321 611 q 92 455 92 491 l 442 455 l 442 383 "},"–":{"x_min":0,"x_max":705.5625,"ha":803,"o":"m 705 334 l 0 334 l 0 410 l 705 410 l 705 334 "},"Κ":{"x_min":0,"x_max":819.5625,"ha":893,"o":"m 819 0 l 650 0 l 294 509 l 139 356 l 139 0 l 0 0 l 0 1013 l 139 1013 l 139 526 l 626 1013 l 809 1013 l 395 600 l 819 0 "},"ƒ":{"x_min":-46.265625,"x_max":392,"ha":513,"o":"m 392 651 l 259 651 l 79 -279 l -46 -278 l 134 651 l 14 651 l 14 751 l 135 751 q 151 948 135 900 q 304 1041 185 1041 q 334 1040 319 1041 q 392 1034 348 1039 l 392 922 q 337 931 360 931 q 271 883 287 931 q 260 793 260 853 l 260 751 l 392 751 l 392 651 "},"e":{"x_min":0,"x_max":714,"ha":813,"o":"m 714 326 l 140 326 q 200 157 140 227 q 359 87 260 87 q 488 130 431 87 q 561 245 545 174 l 697 245 q 577 48 670 123 q 358 -26 484 -26 q 97 85 195 -26 q 0 363 0 197 q 94 642 0 529 q 358 765 195 765 q 626 627 529 765 q 714 326 714 503 m 576 429 q 507 583 564 522 q 355 650 445 650 q 206 583 266 650 q 140 429 152 522 l 576 429 "},"ό":{"x_min":0,"x_max":712,"ha":815,"o":"m 356 -25 q 94 91 194 -25 q 0 368 0 202 q 92 642 0 533 q 356 761 192 761 q 617 644 517 761 q 712 368 712 533 q 619 91 712 201 q 356 -25 520 -25 m 356 85 q 527 175 465 85 q 583 369 583 255 q 528 562 583 484 q 356 651 466 651 q 189 560 250 651 q 135 369 135 481 q 187 177 135 257 q 356 85 250 85 m 576 1040 l 387 819 l 303 819 l 438 1040 l 576 1040 "},"J":{"x_min":0,"x_max":588,"ha":699,"o":"m 588 279 q 287 -26 588 -26 q 58 73 126 -26 q 0 327 0 158 l 133 327 q 160 172 133 227 q 288 96 198 96 q 426 171 391 96 q 449 336 449 219 l 449 1013 l 588 1013 l 588 279 "},"»":{"x_min":-1,"x_max":503,"ha":601,"o":"m 503 302 l 280 136 l 281 256 l 429 373 l 281 486 l 280 608 l 503 440 l 503 302 m 221 302 l 0 136 l 0 255 l 145 372 l 0 486 l -1 608 l 221 440 l 221 302 "},"©":{"x_min":-3,"x_max":1008,"ha":1106,"o":"m 502 -7 q 123 151 263 -7 q -3 501 -3 294 q 123 851 -3 706 q 502 1011 263 1011 q 881 851 739 1011 q 1008 501 1008 708 q 883 151 1008 292 q 502 -7 744 -7 m 502 60 q 830 197 709 60 q 940 501 940 322 q 831 805 940 681 q 502 944 709 944 q 174 805 296 944 q 65 501 65 680 q 173 197 65 320 q 502 60 294 60 m 741 394 q 661 246 731 302 q 496 190 591 190 q 294 285 369 190 q 228 497 228 370 q 295 714 228 625 q 499 813 370 813 q 656 762 588 813 q 733 625 724 711 l 634 625 q 589 704 629 673 q 498 735 550 735 q 377 666 421 735 q 334 504 334 597 q 374 340 334 408 q 490 272 415 272 q 589 304 549 272 q 638 394 628 337 l 741 394 "},"ώ":{"x_min":0,"x_max":922,"ha":1030,"o":"m 687 1040 l 498 819 l 415 819 l 549 1040 l 687 1040 m 922 339 q 856 97 922 203 q 650 -26 780 -26 q 538 9 587 -26 q 461 103 489 44 q 387 12 436 46 q 277 -22 339 -22 q 69 97 147 -22 q 0 338 0 202 q 45 551 0 444 q 161 737 84 643 l 302 737 q 175 552 219 647 q 124 336 124 446 q 155 179 124 248 q 275 88 197 88 q 375 163 341 88 q 400 294 400 219 l 400 572 l 524 572 l 524 294 q 561 135 524 192 q 643 88 591 88 q 762 182 719 88 q 797 341 797 257 q 745 555 797 450 q 619 737 705 637 l 760 737 q 874 551 835 640 q 922 339 922 444 "},"^":{"x_min":193.0625,"x_max":598.609375,"ha":792,"o":"m 598 772 l 515 772 l 395 931 l 277 772 l 193 772 l 326 1013 l 462 1013 l 598 772 "},"«":{"x_min":0,"x_max":507.203125,"ha":604,"o":"m 506 136 l 284 302 l 284 440 l 506 608 l 507 485 l 360 371 l 506 255 l 506 136 m 222 136 l 0 302 l 0 440 l 222 608 l 221 486 l 73 373 l 222 256 l 222 136 "},"D":{"x_min":0,"x_max":828,"ha":935,"o":"m 389 1013 q 714 867 593 1013 q 828 521 828 729 q 712 161 828 309 q 382 0 587 0 l 0 0 l 0 1013 l 389 1013 m 376 124 q 607 247 523 124 q 681 510 681 355 q 607 771 681 662 q 376 896 522 896 l 139 896 l 139 124 l 376 124 "},"∙":{"x_min":0,"x_max":142,"ha":239,"o":"m 142 585 l 0 585 l 0 738 l 142 738 l 142 585 "},"ÿ":{"x_min":0,"x_max":47,"ha":125,"o":"m 47 3 q 37 -7 47 -7 q 28 0 30 -7 q 39 -4 32 -4 q 45 3 45 -1 l 37 0 q 28 9 28 0 q 39 19 28 19 l 47 16 l 47 19 l 47 3 m 37 1 q 44 8 44 1 q 37 16 44 16 q 30 8 30 16 q 37 1 30 1 m 26 1 l 23 22 l 14 0 l 3 22 l 3 3 l 0 25 l 13 1 l 22 25 l 26 1 "},"w":{"x_min":0,"x_max":1009.71875,"ha":1100,"o":"m 1009 738 l 783 0 l 658 0 l 501 567 l 345 0 l 222 0 l 0 738 l 130 738 l 284 174 l 432 737 l 576 738 l 721 173 l 881 737 l 1009 738 "},"$":{"x_min":0,"x_max":700,"ha":793,"o":"m 664 717 l 542 717 q 490 825 531 785 q 381 872 450 865 l 381 551 q 620 446 540 522 q 700 241 700 370 q 618 45 700 116 q 381 -25 536 -25 l 381 -152 l 307 -152 l 307 -25 q 81 62 162 -25 q 0 297 0 149 l 124 297 q 169 146 124 204 q 307 81 215 89 l 307 441 q 80 536 148 469 q 13 725 13 603 q 96 910 13 839 q 307 982 180 982 l 307 1077 l 381 1077 l 381 982 q 574 917 494 982 q 664 717 664 845 m 307 565 l 307 872 q 187 831 233 872 q 142 724 142 791 q 180 618 142 656 q 307 565 218 580 m 381 76 q 562 237 562 96 q 517 361 562 313 q 381 423 472 409 l 381 76 "},"\\":{"x_min":-0.015625,"x_max":425.0625,"ha":522,"o":"m 425 -129 l 337 -129 l 0 1041 l 83 1041 l 425 -129 "},"µ":{"x_min":0,"x_max":697.21875,"ha":747,"o":"m 697 -4 q 629 -14 658 -14 q 498 97 513 -14 q 422 9 470 41 q 313 -23 374 -23 q 207 4 258 -23 q 119 81 156 32 l 119 -278 l 0 -278 l 0 738 l 124 738 l 124 343 q 165 173 124 246 q 308 83 216 83 q 452 178 402 83 q 493 359 493 255 l 493 738 l 617 738 l 617 214 q 623 136 617 160 q 673 92 637 92 q 697 96 684 92 l 697 -4 "},"Ι":{"x_min":42,"x_max":181,"ha":297,"o":"m 181 0 l 42 0 l 42 1013 l 181 1013 l 181 0 "},"Ύ":{"x_min":0,"x_max":1144.5,"ha":1214,"o":"m 1144 1012 l 807 416 l 807 0 l 667 0 l 667 416 l 325 1012 l 465 1012 l 736 533 l 1004 1012 l 1144 1012 m 277 1040 l 83 799 l 0 799 l 140 1040 l 277 1040 "},"’":{"x_min":0,"x_max":139,"ha":236,"o":"m 139 851 q 102 737 139 784 q 0 669 65 690 l 0 734 q 59 787 42 741 q 72 873 72 821 l 0 873 l 0 1013 l 139 1013 l 139 851 "},"Ν":{"x_min":0,"x_max":801,"ha":915,"o":"m 801 0 l 651 0 l 131 822 l 131 0 l 0 0 l 0 1013 l 151 1013 l 670 191 l 670 1013 l 801 1013 l 801 0 "},"-":{"x_min":8.71875,"x_max":350.390625,"ha":478,"o":"m 350 317 l 8 317 l 8 428 l 350 428 l 350 317 "},"Q":{"x_min":0,"x_max":968,"ha":1072,"o":"m 954 5 l 887 -79 l 744 35 q 622 -11 687 2 q 483 -26 556 -26 q 127 130 262 -26 q 0 504 0 279 q 127 880 0 728 q 484 1041 262 1041 q 841 884 708 1041 q 968 507 968 735 q 933 293 968 398 q 832 104 899 188 l 954 5 m 723 191 q 802 330 777 248 q 828 499 828 412 q 744 790 828 673 q 483 922 650 922 q 228 791 322 922 q 142 505 142 673 q 227 221 142 337 q 487 91 323 91 q 632 123 566 91 l 520 215 l 587 301 l 723 191 "},"ς":{"x_min":1,"x_max":676.28125,"ha":740,"o":"m 676 460 l 551 460 q 498 595 542 546 q 365 651 448 651 q 199 578 263 651 q 136 401 136 505 q 266 178 136 241 q 508 106 387 142 q 640 -50 640 62 q 625 -158 640 -105 q 583 -278 611 -211 l 465 -278 q 498 -182 490 -211 q 515 -80 515 -126 q 381 12 515 -15 q 134 91 197 51 q 1 388 1 179 q 100 651 1 542 q 354 761 199 761 q 587 680 498 761 q 676 460 676 599 "},"M":{"x_min":0,"x_max":954,"ha":1067,"o":"m 954 0 l 819 0 l 819 869 l 537 0 l 405 0 l 128 866 l 128 0 l 0 0 l 0 1013 l 200 1013 l 472 160 l 757 1013 l 954 1013 l 954 0 "},"Ψ":{"x_min":0,"x_max":1006,"ha":1094,"o":"m 1006 678 q 914 319 1006 429 q 571 200 814 200 l 571 0 l 433 0 l 433 200 q 92 319 194 200 q 0 678 0 429 l 0 1013 l 139 1013 l 139 679 q 191 417 139 492 q 433 326 255 326 l 433 1013 l 571 1013 l 571 326 l 580 326 q 813 423 747 326 q 868 679 868 502 l 868 1013 l 1006 1013 l 1006 678 "},"C":{"x_min":0,"x_max":886,"ha":944,"o":"m 886 379 q 760 87 886 201 q 455 -26 634 -26 q 112 136 236 -26 q 0 509 0 283 q 118 882 0 737 q 469 1041 245 1041 q 748 955 630 1041 q 879 708 879 859 l 745 708 q 649 862 724 805 q 473 920 573 920 q 219 791 312 920 q 136 509 136 675 q 217 229 136 344 q 470 99 311 99 q 672 179 591 99 q 753 379 753 259 l 886 379 "},"!":{"x_min":0,"x_max":138,"ha":236,"o":"m 138 684 q 116 409 138 629 q 105 244 105 299 l 33 244 q 16 465 33 313 q 0 684 0 616 l 0 1013 l 138 1013 l 138 684 m 138 0 l 0 0 l 0 151 l 138 151 l 138 0 "},"{":{"x_min":0,"x_max":480.5625,"ha":578,"o":"m 480 -286 q 237 -213 303 -286 q 187 -45 187 -159 q 194 48 187 -15 q 201 141 201 112 q 164 264 201 225 q 0 314 118 314 l 0 417 q 164 471 119 417 q 201 605 201 514 q 199 665 201 644 q 193 772 193 769 q 241 941 193 887 q 480 1015 308 1015 l 480 915 q 336 866 375 915 q 306 742 306 828 q 310 662 306 717 q 314 577 314 606 q 288 452 314 500 q 176 365 256 391 q 289 275 257 337 q 314 143 314 226 q 313 84 314 107 q 310 -11 310 -5 q 339 -131 310 -94 q 480 -182 377 -182 l 480 -286 "},"X":{"x_min":-0.015625,"x_max":854.15625,"ha":940,"o":"m 854 0 l 683 0 l 423 409 l 166 0 l 0 0 l 347 519 l 18 1013 l 186 1013 l 428 637 l 675 1013 l 836 1013 l 504 520 l 854 0 "},"#":{"x_min":0,"x_max":963.890625,"ha":1061,"o":"m 963 690 l 927 590 l 719 590 l 655 410 l 876 410 l 840 310 l 618 310 l 508 -3 l 393 -2 l 506 309 l 329 310 l 215 -2 l 102 -3 l 212 310 l 0 310 l 36 410 l 248 409 l 312 590 l 86 590 l 120 690 l 347 690 l 459 1006 l 573 1006 l 462 690 l 640 690 l 751 1006 l 865 1006 l 754 690 l 963 690 m 606 590 l 425 590 l 362 410 l 543 410 l 606 590 "},"ι":{"x_min":42,"x_max":284,"ha":361,"o":"m 284 3 q 233 -10 258 -5 q 182 -15 207 -15 q 85 26 119 -15 q 42 200 42 79 l 42 738 l 167 738 l 168 215 q 172 141 168 157 q 226 101 183 101 q 248 103 239 101 q 284 112 257 104 l 284 3 "},"Ά":{"x_min":0,"x_max":906.953125,"ha":982,"o":"m 283 1040 l 88 799 l 5 799 l 145 1040 l 283 1040 m 906 0 l 756 0 l 650 303 l 251 303 l 143 0 l 0 0 l 376 1012 l 529 1012 l 906 0 m 609 421 l 452 866 l 293 421 l 609 421 "},")":{"x_min":0,"x_max":318,"ha":415,"o":"m 318 365 q 257 25 318 191 q 87 -290 197 -141 l 0 -290 q 140 21 93 -128 q 193 360 193 189 q 141 704 193 537 q 0 1024 97 850 l 87 1024 q 257 706 197 871 q 318 365 318 542 "},"ε":{"x_min":0,"x_max":634.71875,"ha":714,"o":"m 634 234 q 527 38 634 110 q 300 -25 433 -25 q 98 29 183 -25 q 0 204 0 93 q 37 314 0 265 q 128 390 67 353 q 56 460 82 419 q 26 555 26 505 q 114 712 26 654 q 295 763 191 763 q 499 700 416 763 q 589 515 589 631 l 478 515 q 419 618 464 580 q 307 657 374 657 q 207 630 253 657 q 151 547 151 598 q 238 445 151 469 q 389 434 280 434 l 389 331 l 349 331 q 206 315 255 331 q 125 210 125 287 q 183 107 125 145 q 302 76 233 76 q 436 117 379 76 q 509 234 493 159 l 634 234 "},"Δ":{"x_min":0,"x_max":952.78125,"ha":1028,"o":"m 952 0 l 0 0 l 400 1013 l 551 1013 l 952 0 m 762 124 l 476 867 l 187 124 l 762 124 "},"}":{"x_min":0,"x_max":481,"ha":578,"o":"m 481 314 q 318 262 364 314 q 282 136 282 222 q 284 65 282 97 q 293 -58 293 -48 q 241 -217 293 -166 q 0 -286 174 -286 l 0 -182 q 143 -130 105 -182 q 171 -2 171 -93 q 168 81 171 22 q 165 144 165 140 q 188 275 165 229 q 306 365 220 339 q 191 455 224 391 q 165 588 165 505 q 168 681 165 624 q 171 742 171 737 q 141 865 171 827 q 0 915 102 915 l 0 1015 q 243 942 176 1015 q 293 773 293 888 q 287 675 293 741 q 282 590 282 608 q 318 466 282 505 q 481 417 364 417 l 481 314 "},"‰":{"x_min":-3,"x_max":1672,"ha":1821,"o":"m 846 0 q 664 76 732 0 q 603 244 603 145 q 662 412 603 344 q 846 489 729 489 q 1027 412 959 489 q 1089 244 1089 343 q 1029 76 1089 144 q 846 0 962 0 m 845 103 q 945 143 910 103 q 981 243 981 184 q 947 340 981 301 q 845 385 910 385 q 745 342 782 385 q 709 243 709 300 q 742 147 709 186 q 845 103 781 103 m 888 986 l 284 -25 l 199 -25 l 803 986 l 888 986 m 241 468 q 58 545 126 468 q -3 715 -3 615 q 56 881 -3 813 q 238 958 124 958 q 421 881 353 958 q 483 712 483 813 q 423 544 483 612 q 241 468 356 468 m 241 855 q 137 811 175 855 q 100 710 100 768 q 136 612 100 653 q 240 572 172 572 q 344 614 306 572 q 382 713 382 656 q 347 810 382 771 q 241 855 308 855 m 1428 0 q 1246 76 1314 0 q 1185 244 1185 145 q 1244 412 1185 344 q 1428 489 1311 489 q 1610 412 1542 489 q 1672 244 1672 343 q 1612 76 1672 144 q 1428 0 1545 0 m 1427 103 q 1528 143 1492 103 q 1564 243 1564 184 q 1530 340 1564 301 q 1427 385 1492 385 q 1327 342 1364 385 q 1291 243 1291 300 q 1324 147 1291 186 q 1427 103 1363 103 "},"a":{"x_min":0,"x_max":698.609375,"ha":794,"o":"m 698 0 q 661 -12 679 -7 q 615 -17 643 -17 q 536 12 564 -17 q 500 96 508 41 q 384 6 456 37 q 236 -25 312 -25 q 65 31 130 -25 q 0 194 0 88 q 118 390 0 334 q 328 435 180 420 q 488 483 476 451 q 495 523 495 504 q 442 619 495 584 q 325 654 389 654 q 209 617 257 654 q 152 513 161 580 l 33 513 q 123 705 33 633 q 332 772 207 772 q 528 712 448 772 q 617 531 617 645 l 617 163 q 624 108 617 126 q 664 90 632 90 l 698 94 l 698 0 m 491 262 l 491 372 q 272 329 350 347 q 128 201 128 294 q 166 113 128 144 q 264 83 205 83 q 414 130 346 83 q 491 262 491 183 "},"—":{"x_min":0,"x_max":941.671875,"ha":1039,"o":"m 941 334 l 0 334 l 0 410 l 941 410 l 941 334 "},"=":{"x_min":8.71875,"x_max":780.953125,"ha":792,"o":"m 780 510 l 8 510 l 8 606 l 780 606 l 780 510 m 780 235 l 8 235 l 8 332 l 780 332 l 780 235 "},"N":{"x_min":0,"x_max":801,"ha":914,"o":"m 801 0 l 651 0 l 131 823 l 131 0 l 0 0 l 0 1013 l 151 1013 l 670 193 l 670 1013 l 801 1013 l 801 0 "},"ρ":{"x_min":0,"x_max":712,"ha":797,"o":"m 712 369 q 620 94 712 207 q 362 -26 521 -26 q 230 2 292 -26 q 119 83 167 30 l 119 -278 l 0 -278 l 0 362 q 91 643 0 531 q 355 764 190 764 q 617 647 517 764 q 712 369 712 536 m 583 366 q 530 559 583 480 q 359 651 469 651 q 190 562 252 651 q 135 370 135 483 q 189 176 135 257 q 359 85 250 85 q 528 175 466 85 q 583 366 583 254 "},"2":{"x_min":59,"x_max":731,"ha":792,"o":"m 731 0 l 59 0 q 197 314 59 188 q 457 487 199 315 q 598 691 598 580 q 543 819 598 772 q 411 867 488 867 q 272 811 328 867 q 209 630 209 747 l 81 630 q 182 901 81 805 q 408 986 271 986 q 629 909 536 986 q 731 694 731 826 q 613 449 731 541 q 378 316 495 383 q 201 122 235 234 l 731 122 l 731 0 "},"¯":{"x_min":0,"x_max":941.671875,"ha":938,"o":"m 941 1033 l 0 1033 l 0 1109 l 941 1109 l 941 1033 "},"Z":{"x_min":0,"x_max":779,"ha":849,"o":"m 779 0 l 0 0 l 0 113 l 621 896 l 40 896 l 40 1013 l 779 1013 l 778 887 l 171 124 l 779 124 l 779 0 "},"u":{"x_min":0,"x_max":617,"ha":729,"o":"m 617 0 l 499 0 l 499 110 q 391 10 460 45 q 246 -25 322 -25 q 61 58 127 -25 q 0 258 0 136 l 0 738 l 125 738 l 125 284 q 156 148 125 202 q 273 82 197 82 q 433 165 369 82 q 493 340 493 243 l 493 738 l 617 738 l 617 0 "},"k":{"x_min":0,"x_max":612.484375,"ha":697,"o":"m 612 738 l 338 465 l 608 0 l 469 0 l 251 382 l 121 251 l 121 0 l 0 0 l 0 1013 l 121 1013 l 121 402 l 456 738 l 612 738 "},"Η":{"x_min":0,"x_max":803,"ha":917,"o":"m 803 0 l 667 0 l 667 475 l 140 475 l 140 0 l 0 0 l 0 1013 l 140 1013 l 140 599 l 667 599 l 667 1013 l 803 1013 l 803 0 "},"Α":{"x_min":0,"x_max":906.953125,"ha":985,"o":"m 906 0 l 756 0 l 650 303 l 251 303 l 143 0 l 0 0 l 376 1013 l 529 1013 l 906 0 m 609 421 l 452 866 l 293 421 l 609 421 "},"s":{"x_min":0,"x_max":604,"ha":697,"o":"m 604 217 q 501 36 604 104 q 292 -23 411 -23 q 86 43 166 -23 q 0 238 0 114 l 121 237 q 175 122 121 164 q 300 85 223 85 q 415 112 363 85 q 479 207 479 147 q 361 309 479 276 q 140 372 141 370 q 21 544 21 426 q 111 708 21 647 q 298 761 190 761 q 492 705 413 761 q 583 531 583 643 l 462 531 q 412 625 462 594 q 298 657 363 657 q 199 636 242 657 q 143 558 143 608 q 262 454 143 486 q 484 394 479 397 q 604 217 604 341 "},"B":{"x_min":0,"x_max":778,"ha":876,"o":"m 580 546 q 724 469 670 535 q 778 311 778 403 q 673 83 778 171 q 432 0 575 0 l 0 0 l 0 1013 l 411 1013 q 629 957 541 1013 q 732 768 732 892 q 691 633 732 693 q 580 546 650 572 m 393 899 l 139 899 l 139 588 l 379 588 q 521 624 462 588 q 592 744 592 667 q 531 859 592 819 q 393 899 471 899 m 419 124 q 566 169 504 124 q 635 303 635 219 q 559 436 635 389 q 402 477 494 477 l 139 477 l 139 124 l 419 124 "},"…":{"x_min":0,"x_max":614,"ha":708,"o":"m 142 0 l 0 0 l 0 151 l 142 151 l 142 0 m 378 0 l 236 0 l 236 151 l 378 151 l 378 0 m 614 0 l 472 0 l 472 151 l 614 151 l 614 0 "},"?":{"x_min":0,"x_max":607,"ha":704,"o":"m 607 777 q 543 599 607 674 q 422 474 482 537 q 357 272 357 391 l 236 272 q 297 487 236 395 q 411 619 298 490 q 474 762 474 691 q 422 885 474 838 q 301 933 371 933 q 179 880 228 933 q 124 706 124 819 l 0 706 q 94 963 0 872 q 302 1044 177 1044 q 511 973 423 1044 q 607 777 607 895 m 370 0 l 230 0 l 230 151 l 370 151 l 370 0 "},"H":{"x_min":0,"x_max":803,"ha":915,"o":"m 803 0 l 667 0 l 667 475 l 140 475 l 140 0 l 0 0 l 0 1013 l 140 1013 l 140 599 l 667 599 l 667 1013 l 803 1013 l 803 0 "},"ν":{"x_min":0,"x_max":675,"ha":761,"o":"m 675 738 l 404 0 l 272 0 l 0 738 l 133 738 l 340 147 l 541 738 l 675 738 "},"c":{"x_min":1,"x_max":701.390625,"ha":775,"o":"m 701 264 q 584 53 681 133 q 353 -26 487 -26 q 91 91 188 -26 q 1 370 1 201 q 92 645 1 537 q 353 761 190 761 q 572 688 479 761 q 690 493 666 615 l 556 493 q 487 606 545 562 q 356 650 428 650 q 186 563 246 650 q 134 372 134 487 q 188 179 134 258 q 359 88 250 88 q 492 136 437 88 q 566 264 548 185 l 701 264 "},"¶":{"x_min":0,"x_max":566.671875,"ha":678,"o":"m 21 892 l 52 892 l 98 761 l 145 892 l 176 892 l 178 741 l 157 741 l 157 867 l 108 741 l 88 741 l 40 871 l 40 741 l 21 741 l 21 892 m 308 854 l 308 731 q 252 691 308 691 q 227 691 240 691 q 207 696 213 695 l 207 712 l 253 706 q 288 733 288 706 l 288 763 q 244 741 279 741 q 193 797 193 741 q 261 860 193 860 q 287 860 273 860 q 308 854 302 855 m 288 842 l 263 843 q 213 796 213 843 q 248 756 213 756 q 288 796 288 756 l 288 842 m 566 988 l 502 988 l 502 -1 l 439 -1 l 439 988 l 317 988 l 317 -1 l 252 -1 l 252 602 q 81 653 155 602 q 0 805 0 711 q 101 989 0 918 q 309 1053 194 1053 l 566 1053 l 566 988 "},"β":{"x_min":0,"x_max":660,"ha":745,"o":"m 471 550 q 610 450 561 522 q 660 280 660 378 q 578 64 660 151 q 367 -22 497 -22 q 239 5 299 -22 q 126 82 178 32 l 126 -278 l 0 -278 l 0 593 q 54 903 0 801 q 318 1042 127 1042 q 519 964 436 1042 q 603 771 603 887 q 567 644 603 701 q 471 550 532 586 m 337 79 q 476 138 418 79 q 535 279 535 198 q 427 437 535 386 q 226 477 344 477 l 226 583 q 398 620 329 583 q 486 762 486 668 q 435 884 486 833 q 312 935 384 935 q 169 861 219 935 q 126 698 126 797 l 126 362 q 170 169 126 242 q 337 79 224 79 "},"Μ":{"x_min":0,"x_max":954,"ha":1068,"o":"m 954 0 l 819 0 l 819 868 l 537 0 l 405 0 l 128 865 l 128 0 l 0 0 l 0 1013 l 199 1013 l 472 158 l 758 1013 l 954 1013 l 954 0 "},"Ό":{"x_min":0.109375,"x_max":1120,"ha":1217,"o":"m 1120 505 q 994 132 1120 282 q 642 -29 861 -29 q 290 130 422 -29 q 167 505 167 280 q 294 883 167 730 q 650 1046 430 1046 q 999 882 868 1046 q 1120 505 1120 730 m 977 504 q 896 784 977 669 q 644 915 804 915 q 391 785 484 915 q 307 504 307 669 q 391 224 307 339 q 644 95 486 95 q 894 224 803 95 q 977 504 977 339 m 277 1040 l 83 799 l 0 799 l 140 1040 l 277 1040 "},"Ή":{"x_min":0,"x_max":1158,"ha":1275,"o":"m 1158 0 l 1022 0 l 1022 475 l 496 475 l 496 0 l 356 0 l 356 1012 l 496 1012 l 496 599 l 1022 599 l 1022 1012 l 1158 1012 l 1158 0 m 277 1040 l 83 799 l 0 799 l 140 1040 l 277 1040 "},"•":{"x_min":0,"x_max":663.890625,"ha":775,"o":"m 663 529 q 566 293 663 391 q 331 196 469 196 q 97 294 194 196 q 0 529 0 393 q 96 763 0 665 q 331 861 193 861 q 566 763 469 861 q 663 529 663 665 "},"¥":{"x_min":0.1875,"x_max":819.546875,"ha":886,"o":"m 563 561 l 697 561 l 696 487 l 520 487 l 482 416 l 482 380 l 697 380 l 695 308 l 482 308 l 482 0 l 342 0 l 342 308 l 125 308 l 125 380 l 342 380 l 342 417 l 303 487 l 125 487 l 125 561 l 258 561 l 0 1013 l 140 1013 l 411 533 l 679 1013 l 819 1013 l 563 561 "},"(":{"x_min":0,"x_max":318.0625,"ha":415,"o":"m 318 -290 l 230 -290 q 61 23 122 -142 q 0 365 0 190 q 62 712 0 540 q 230 1024 119 869 l 318 1024 q 175 705 219 853 q 125 360 125 542 q 176 22 125 187 q 318 -290 223 -127 "},"U":{"x_min":0,"x_max":796,"ha":904,"o":"m 796 393 q 681 93 796 212 q 386 -25 566 -25 q 101 95 208 -25 q 0 393 0 211 l 0 1013 l 138 1013 l 138 391 q 204 191 138 270 q 394 107 276 107 q 586 191 512 107 q 656 391 656 270 l 656 1013 l 796 1013 l 796 393 "},"γ":{"x_min":0.5,"x_max":744.953125,"ha":822,"o":"m 744 737 l 463 54 l 463 -278 l 338 -278 l 338 54 l 154 495 q 104 597 124 569 q 13 651 67 651 l 0 651 l 0 751 l 39 753 q 168 711 121 753 q 242 594 207 676 l 403 208 l 617 737 l 744 737 "},"α":{"x_min":0,"x_max":765.5625,"ha":809,"o":"m 765 -4 q 698 -14 726 -14 q 564 97 586 -14 q 466 7 525 40 q 337 -26 407 -26 q 88 98 186 -26 q 0 369 0 212 q 88 637 0 525 q 337 760 184 760 q 465 728 407 760 q 563 637 524 696 l 563 739 l 685 739 l 685 222 q 693 141 685 168 q 748 94 708 94 q 765 96 760 94 l 765 -4 m 584 371 q 531 562 584 485 q 360 653 470 653 q 192 566 254 653 q 135 379 135 489 q 186 181 135 261 q 358 84 247 84 q 528 176 465 84 q 584 371 584 260 "},"F":{"x_min":0,"x_max":683.328125,"ha":717,"o":"m 683 888 l 140 888 l 140 583 l 613 583 l 613 458 l 140 458 l 140 0 l 0 0 l 0 1013 l 683 1013 l 683 888 "},"­":{"x_min":0,"x_max":705.5625,"ha":803,"o":"m 705 334 l 0 334 l 0 410 l 705 410 l 705 334 "},":":{"x_min":0,"x_max":142,"ha":239,"o":"m 142 585 l 0 585 l 0 738 l 142 738 l 142 585 m 142 0 l 0 0 l 0 151 l 142 151 l 142 0 "},"Χ":{"x_min":0,"x_max":854.171875,"ha":935,"o":"m 854 0 l 683 0 l 423 409 l 166 0 l 0 0 l 347 519 l 18 1013 l 186 1013 l 427 637 l 675 1013 l 836 1013 l 504 521 l 854 0 "},"*":{"x_min":116,"x_max":674,"ha":792,"o":"m 674 768 l 475 713 l 610 544 l 517 477 l 394 652 l 272 478 l 178 544 l 314 713 l 116 766 l 153 876 l 341 812 l 342 1013 l 446 1013 l 446 811 l 635 874 l 674 768 "},"†":{"x_min":0,"x_max":777,"ha":835,"o":"m 458 804 l 777 804 l 777 683 l 458 683 l 458 0 l 319 0 l 319 681 l 0 683 l 0 804 l 319 804 l 319 1015 l 458 1013 l 458 804 "},"°":{"x_min":0,"x_max":347,"ha":444,"o":"m 173 802 q 43 856 91 802 q 0 977 0 905 q 45 1101 0 1049 q 173 1153 90 1153 q 303 1098 255 1153 q 347 977 347 1049 q 303 856 347 905 q 173 802 256 802 m 173 884 q 238 910 214 884 q 262 973 262 937 q 239 1038 262 1012 q 173 1064 217 1064 q 108 1037 132 1064 q 85 973 85 1010 q 108 910 85 937 q 173 884 132 884 "},"V":{"x_min":0,"x_max":862.71875,"ha":940,"o":"m 862 1013 l 505 0 l 361 0 l 0 1013 l 143 1013 l 434 165 l 718 1012 l 862 1013 "},"Ξ":{"x_min":0,"x_max":734.71875,"ha":763,"o":"m 723 889 l 9 889 l 9 1013 l 723 1013 l 723 889 m 673 463 l 61 463 l 61 589 l 673 589 l 673 463 m 734 0 l 0 0 l 0 124 l 734 124 l 734 0 "}," ":{"x_min":0,"x_max":0,"ha":853},"Ϋ":{"x_min":0.328125,"x_max":819.515625,"ha":889,"o":"m 588 1046 l 460 1046 l 460 1189 l 588 1189 l 588 1046 m 360 1046 l 232 1046 l 232 1189 l 360 1189 l 360 1046 m 819 1012 l 482 416 l 482 0 l 342 0 l 342 416 l 0 1012 l 140 1012 l 411 533 l 679 1012 l 819 1012 "},"0":{"x_min":73,"x_max":715,"ha":792,"o":"m 394 -29 q 153 129 242 -29 q 73 479 73 272 q 152 829 73 687 q 394 989 241 989 q 634 829 545 989 q 715 479 715 684 q 635 129 715 270 q 394 -29 546 -29 m 394 89 q 546 211 489 89 q 598 479 598 322 q 548 748 598 640 q 394 871 491 871 q 241 748 298 871 q 190 479 190 637 q 239 211 190 319 q 394 89 296 89 "},"”":{"x_min":0,"x_max":347,"ha":454,"o":"m 139 851 q 102 737 139 784 q 0 669 65 690 l 0 734 q 59 787 42 741 q 72 873 72 821 l 0 873 l 0 1013 l 139 1013 l 139 851 m 347 851 q 310 737 347 784 q 208 669 273 690 l 208 734 q 267 787 250 741 q 280 873 280 821 l 208 873 l 208 1013 l 347 1013 l 347 851 "},"@":{"x_min":0,"x_max":1260,"ha":1357,"o":"m 1098 -45 q 877 -160 1001 -117 q 633 -203 752 -203 q 155 -29 327 -203 q 0 360 0 127 q 176 802 0 616 q 687 1008 372 1008 q 1123 854 969 1008 q 1260 517 1260 718 q 1155 216 1260 341 q 868 82 1044 82 q 772 106 801 82 q 737 202 737 135 q 647 113 700 144 q 527 82 594 82 q 367 147 420 82 q 314 312 314 212 q 401 565 314 452 q 639 690 498 690 q 810 588 760 690 l 849 668 l 938 668 q 877 441 900 532 q 833 226 833 268 q 853 182 833 198 q 902 167 873 167 q 1088 272 1012 167 q 1159 512 1159 372 q 1051 793 1159 681 q 687 925 925 925 q 248 747 415 925 q 97 361 97 586 q 226 26 97 159 q 627 -122 370 -122 q 856 -87 737 -122 q 1061 8 976 -53 l 1098 -45 m 786 488 q 738 580 777 545 q 643 615 700 615 q 483 517 548 615 q 425 322 425 430 q 457 203 425 250 q 552 156 490 156 q 722 273 665 156 q 786 488 738 309 "},"Ί":{"x_min":0,"x_max":499,"ha":613,"o":"m 277 1040 l 83 799 l 0 799 l 140 1040 l 277 1040 m 499 0 l 360 0 l 360 1012 l 499 1012 l 499 0 "},"i":{"x_min":14,"x_max":136,"ha":275,"o":"m 136 873 l 14 873 l 14 1013 l 136 1013 l 136 873 m 136 0 l 14 0 l 14 737 l 136 737 l 136 0 "},"Β":{"x_min":0,"x_max":778,"ha":877,"o":"m 580 545 q 724 468 671 534 q 778 310 778 402 q 673 83 778 170 q 432 0 575 0 l 0 0 l 0 1013 l 411 1013 q 629 957 541 1013 q 732 768 732 891 q 691 632 732 692 q 580 545 650 571 m 393 899 l 139 899 l 139 587 l 379 587 q 521 623 462 587 q 592 744 592 666 q 531 859 592 819 q 393 899 471 899 m 419 124 q 566 169 504 124 q 635 302 635 219 q 559 435 635 388 q 402 476 494 476 l 139 476 l 139 124 l 419 124 "},"υ":{"x_min":0,"x_max":617,"ha":725,"o":"m 617 352 q 540 94 617 199 q 308 -24 455 -24 q 76 94 161 -24 q 0 352 0 199 l 0 739 l 126 739 l 126 355 q 169 185 126 257 q 312 98 220 98 q 451 185 402 98 q 492 355 492 257 l 492 739 l 617 739 l 617 352 "},"]":{"x_min":0,"x_max":275,"ha":372,"o":"m 275 -281 l 0 -281 l 0 -187 l 151 -187 l 151 920 l 0 920 l 0 1013 l 275 1013 l 275 -281 "},"m":{"x_min":0,"x_max":1019,"ha":1128,"o":"m 1019 0 l 897 0 l 897 454 q 860 591 897 536 q 739 660 816 660 q 613 586 659 660 q 573 436 573 522 l 573 0 l 447 0 l 447 455 q 412 591 447 535 q 294 657 372 657 q 165 586 213 657 q 122 437 122 521 l 122 0 l 0 0 l 0 738 l 117 738 l 117 640 q 202 730 150 697 q 316 763 254 763 q 437 730 381 763 q 525 642 494 697 q 621 731 559 700 q 753 763 682 763 q 943 694 867 763 q 1019 512 1019 625 l 1019 0 "},"χ":{"x_min":8.328125,"x_max":780.5625,"ha":815,"o":"m 780 -278 q 715 -294 747 -294 q 616 -257 663 -294 q 548 -175 576 -227 l 379 133 l 143 -277 l 9 -277 l 313 254 l 163 522 q 127 586 131 580 q 36 640 91 640 q 8 637 27 640 l 8 752 l 52 757 q 162 719 113 757 q 236 627 200 690 l 383 372 l 594 737 l 726 737 l 448 250 l 625 -69 q 670 -153 647 -110 q 743 -188 695 -188 q 780 -184 759 -188 l 780 -278 "},"8":{"x_min":55,"x_max":736,"ha":792,"o":"m 571 527 q 694 424 652 491 q 736 280 736 358 q 648 71 736 158 q 395 -26 551 -26 q 142 69 238 -26 q 55 279 55 157 q 96 425 55 359 q 220 527 138 491 q 120 615 153 562 q 88 726 88 668 q 171 904 88 827 q 395 986 261 986 q 618 905 529 986 q 702 727 702 830 q 670 616 702 667 q 571 527 638 565 m 394 565 q 519 610 475 565 q 563 717 563 655 q 521 823 563 781 q 392 872 474 872 q 265 824 312 872 q 224 720 224 783 q 265 613 224 656 q 394 565 312 565 m 395 91 q 545 150 488 91 q 597 280 597 204 q 546 408 597 355 q 395 465 492 465 q 244 408 299 465 q 194 280 194 356 q 244 150 194 203 q 395 91 299 91 "},"ί":{"x_min":42,"x_max":326.71875,"ha":361,"o":"m 284 3 q 233 -10 258 -5 q 182 -15 207 -15 q 85 26 119 -15 q 42 200 42 79 l 42 737 l 167 737 l 168 215 q 172 141 168 157 q 226 101 183 101 q 248 102 239 101 q 284 112 257 104 l 284 3 m 326 1040 l 137 819 l 54 819 l 189 1040 l 326 1040 "},"Ζ":{"x_min":0,"x_max":779.171875,"ha":850,"o":"m 779 0 l 0 0 l 0 113 l 620 896 l 40 896 l 40 1013 l 779 1013 l 779 887 l 170 124 l 779 124 l 779 0 "},"R":{"x_min":0,"x_max":781.953125,"ha":907,"o":"m 781 0 l 623 0 q 587 242 590 52 q 407 433 585 433 l 138 433 l 138 0 l 0 0 l 0 1013 l 396 1013 q 636 946 539 1013 q 749 731 749 868 q 711 597 749 659 q 608 502 674 534 q 718 370 696 474 q 729 207 722 352 q 781 26 736 62 l 781 0 m 373 551 q 533 594 465 551 q 614 731 614 645 q 532 859 614 815 q 373 896 465 896 l 138 896 l 138 551 l 373 551 "},"o":{"x_min":0,"x_max":713,"ha":821,"o":"m 357 -25 q 94 91 194 -25 q 0 368 0 202 q 93 642 0 533 q 357 761 193 761 q 618 644 518 761 q 713 368 713 533 q 619 91 713 201 q 357 -25 521 -25 m 357 85 q 528 175 465 85 q 584 369 584 255 q 529 562 584 484 q 357 651 467 651 q 189 560 250 651 q 135 369 135 481 q 187 177 135 257 q 357 85 250 85 "},"5":{"x_min":54.171875,"x_max":738,"ha":792,"o":"m 738 314 q 626 60 738 153 q 382 -23 526 -23 q 155 47 248 -23 q 54 256 54 125 l 183 256 q 259 132 204 174 q 382 91 314 91 q 533 149 471 91 q 602 314 602 213 q 538 469 602 411 q 386 528 475 528 q 284 506 332 528 q 197 439 237 484 l 81 439 l 159 958 l 684 958 l 684 840 l 254 840 l 214 579 q 306 627 258 612 q 407 643 354 643 q 636 552 540 643 q 738 314 738 457 "},"7":{"x_min":58.71875,"x_max":730.953125,"ha":792,"o":"m 730 839 q 469 448 560 641 q 335 0 378 255 l 192 0 q 328 441 235 252 q 593 830 421 630 l 58 830 l 58 958 l 730 958 l 730 839 "},"K":{"x_min":0,"x_max":819.46875,"ha":906,"o":"m 819 0 l 649 0 l 294 509 l 139 355 l 139 0 l 0 0 l 0 1013 l 139 1013 l 139 526 l 626 1013 l 809 1013 l 395 600 l 819 0 "},",":{"x_min":0,"x_max":142,"ha":239,"o":"m 142 -12 q 105 -132 142 -82 q 0 -205 68 -182 l 0 -138 q 57 -82 40 -124 q 70 0 70 -51 l 0 0 l 0 151 l 142 151 l 142 -12 "},"d":{"x_min":0,"x_max":683,"ha":796,"o":"m 683 0 l 564 0 l 564 93 q 456 6 516 38 q 327 -25 395 -25 q 87 100 181 -25 q 0 365 0 215 q 90 639 0 525 q 343 763 187 763 q 564 647 486 763 l 564 1013 l 683 1013 l 683 0 m 582 373 q 529 562 582 484 q 361 653 468 653 q 190 561 253 653 q 135 365 135 479 q 189 175 135 254 q 358 85 251 85 q 529 178 468 85 q 582 373 582 258 "},"¨":{"x_min":-109,"x_max":247,"ha":232,"o":"m 247 1046 l 119 1046 l 119 1189 l 247 1189 l 247 1046 m 19 1046 l -109 1046 l -109 1189 l 19 1189 l 19 1046 "},"E":{"x_min":0,"x_max":736.109375,"ha":789,"o":"m 736 0 l 0 0 l 0 1013 l 725 1013 l 725 889 l 139 889 l 139 585 l 677 585 l 677 467 l 139 467 l 139 125 l 736 125 l 736 0 "},"Y":{"x_min":0,"x_max":820,"ha":886,"o":"m 820 1013 l 482 416 l 482 0 l 342 0 l 342 416 l 0 1013 l 140 1013 l 411 534 l 679 1012 l 820 1013 "},"\"":{"x_min":0,"x_max":299,"ha":396,"o":"m 299 606 l 203 606 l 203 988 l 299 988 l 299 606 m 96 606 l 0 606 l 0 988 l 96 988 l 96 606 "},"‹":{"x_min":17.984375,"x_max":773.609375,"ha":792,"o":"m 773 40 l 18 376 l 17 465 l 773 799 l 773 692 l 159 420 l 773 149 l 773 40 "},"„":{"x_min":0,"x_max":364,"ha":467,"o":"m 141 -12 q 104 -132 141 -82 q 0 -205 67 -182 l 0 -138 q 56 -82 40 -124 q 69 0 69 -51 l 0 0 l 0 151 l 141 151 l 141 -12 m 364 -12 q 327 -132 364 -82 q 222 -205 290 -182 l 222 -138 q 279 -82 262 -124 q 292 0 292 -51 l 222 0 l 222 151 l 364 151 l 364 -12 "},"δ":{"x_min":1,"x_max":710,"ha":810,"o":"m 710 360 q 616 87 710 196 q 356 -28 518 -28 q 99 82 197 -28 q 1 356 1 192 q 100 606 1 509 q 355 703 199 703 q 180 829 288 754 q 70 903 124 866 l 70 1012 l 643 1012 l 643 901 l 258 901 q 462 763 422 794 q 636 592 577 677 q 710 360 710 485 m 584 365 q 552 501 584 447 q 451 602 521 555 q 372 611 411 611 q 197 541 258 611 q 136 355 136 472 q 190 171 136 245 q 358 85 252 85 q 528 173 465 85 q 584 365 584 252 "},"έ":{"x_min":0,"x_max":634.71875,"ha":714,"o":"m 634 234 q 527 38 634 110 q 300 -25 433 -25 q 98 29 183 -25 q 0 204 0 93 q 37 313 0 265 q 128 390 67 352 q 56 459 82 419 q 26 555 26 505 q 114 712 26 654 q 295 763 191 763 q 499 700 416 763 q 589 515 589 631 l 478 515 q 419 618 464 580 q 307 657 374 657 q 207 630 253 657 q 151 547 151 598 q 238 445 151 469 q 389 434 280 434 l 389 331 l 349 331 q 206 315 255 331 q 125 210 125 287 q 183 107 125 145 q 302 76 233 76 q 436 117 379 76 q 509 234 493 159 l 634 234 m 520 1040 l 331 819 l 248 819 l 383 1040 l 520 1040 "},"ω":{"x_min":0,"x_max":922,"ha":1031,"o":"m 922 339 q 856 97 922 203 q 650 -26 780 -26 q 538 9 587 -26 q 461 103 489 44 q 387 12 436 46 q 277 -22 339 -22 q 69 97 147 -22 q 0 339 0 203 q 45 551 0 444 q 161 738 84 643 l 302 738 q 175 553 219 647 q 124 336 124 446 q 155 179 124 249 q 275 88 197 88 q 375 163 341 88 q 400 294 400 219 l 400 572 l 524 572 l 524 294 q 561 135 524 192 q 643 88 591 88 q 762 182 719 88 q 797 342 797 257 q 745 556 797 450 q 619 738 705 638 l 760 738 q 874 551 835 640 q 922 339 922 444 "},"´":{"x_min":0,"x_max":96,"ha":251,"o":"m 96 606 l 0 606 l 0 988 l 96 988 l 96 606 "},"±":{"x_min":11,"x_max":781,"ha":792,"o":"m 781 490 l 446 490 l 446 255 l 349 255 l 349 490 l 11 490 l 11 586 l 349 586 l 349 819 l 446 819 l 446 586 l 781 586 l 781 490 m 781 21 l 11 21 l 11 115 l 781 115 l 781 21 "},"|":{"x_min":343,"x_max":449,"ha":792,"o":"m 449 462 l 343 462 l 343 986 l 449 986 l 449 462 m 449 -242 l 343 -242 l 343 280 l 449 280 l 449 -242 "},"ϋ":{"x_min":0,"x_max":617,"ha":725,"o":"m 482 800 l 372 800 l 372 925 l 482 925 l 482 800 m 239 800 l 129 800 l 129 925 l 239 925 l 239 800 m 617 352 q 540 93 617 199 q 308 -24 455 -24 q 76 93 161 -24 q 0 352 0 199 l 0 738 l 126 738 l 126 354 q 169 185 126 257 q 312 98 220 98 q 451 185 402 98 q 492 354 492 257 l 492 738 l 617 738 l 617 352 "},"§":{"x_min":0,"x_max":593,"ha":690,"o":"m 593 425 q 554 312 593 369 q 467 233 516 254 q 537 83 537 172 q 459 -74 537 -12 q 288 -133 387 -133 q 115 -69 184 -133 q 47 96 47 -6 l 166 96 q 199 7 166 40 q 288 -26 232 -26 q 371 -5 332 -26 q 420 60 420 21 q 311 201 420 139 q 108 309 210 255 q 0 490 0 383 q 33 602 0 551 q 124 687 66 654 q 75 743 93 712 q 58 812 58 773 q 133 984 58 920 q 300 1043 201 1043 q 458 987 394 1043 q 529 814 529 925 l 411 814 q 370 908 404 877 q 289 939 336 939 q 213 911 246 939 q 180 841 180 883 q 286 720 180 779 q 484 612 480 615 q 593 425 593 534 m 467 409 q 355 544 467 473 q 196 630 228 612 q 146 587 162 609 q 124 525 124 558 q 239 387 124 462 q 398 298 369 315 q 448 345 429 316 q 467 409 467 375 "},"b":{"x_min":0,"x_max":685,"ha":783,"o":"m 685 372 q 597 99 685 213 q 347 -25 501 -25 q 219 5 277 -25 q 121 93 161 36 l 121 0 l 0 0 l 0 1013 l 121 1013 l 121 634 q 214 723 157 692 q 341 754 272 754 q 591 637 493 754 q 685 372 685 526 m 554 356 q 499 550 554 470 q 328 644 437 644 q 162 556 223 644 q 108 369 108 478 q 160 176 108 256 q 330 83 221 83 q 498 169 435 83 q 554 356 554 245 "},"q":{"x_min":0,"x_max":683,"ha":876,"o":"m 683 -278 l 564 -278 l 564 97 q 474 8 533 39 q 345 -23 415 -23 q 91 93 188 -23 q 0 364 0 203 q 87 635 0 522 q 337 760 184 760 q 466 727 408 760 q 564 637 523 695 l 564 737 l 683 737 l 683 -278 m 582 375 q 527 564 582 488 q 358 652 466 652 q 190 565 253 652 q 135 377 135 488 q 189 179 135 261 q 361 84 251 84 q 530 179 469 84 q 582 375 582 260 "},"Ω":{"x_min":-0.171875,"x_max":969.5625,"ha":1068,"o":"m 969 0 l 555 0 l 555 123 q 744 308 675 194 q 814 558 814 423 q 726 812 814 709 q 484 922 633 922 q 244 820 334 922 q 154 567 154 719 q 223 316 154 433 q 412 123 292 199 l 412 0 l 0 0 l 0 124 l 217 124 q 68 327 122 210 q 15 572 15 444 q 144 911 15 781 q 484 1041 274 1041 q 822 909 691 1041 q 953 569 953 777 q 899 326 953 443 q 750 124 846 210 l 969 124 l 969 0 "},"ύ":{"x_min":0,"x_max":617,"ha":725,"o":"m 617 352 q 540 93 617 199 q 308 -24 455 -24 q 76 93 161 -24 q 0 352 0 199 l 0 738 l 126 738 l 126 354 q 169 185 126 257 q 312 98 220 98 q 451 185 402 98 q 492 354 492 257 l 492 738 l 617 738 l 617 352 m 535 1040 l 346 819 l 262 819 l 397 1040 l 535 1040 "},"z":{"x_min":-0.015625,"x_max":613.890625,"ha":697,"o":"m 613 0 l 0 0 l 0 100 l 433 630 l 20 630 l 20 738 l 594 738 l 593 636 l 163 110 l 613 110 l 613 0 "},"™":{"x_min":0,"x_max":894,"ha":1000,"o":"m 389 951 l 229 951 l 229 503 l 160 503 l 160 951 l 0 951 l 0 1011 l 389 1011 l 389 951 m 894 503 l 827 503 l 827 939 l 685 503 l 620 503 l 481 937 l 481 503 l 417 503 l 417 1011 l 517 1011 l 653 580 l 796 1010 l 894 1011 l 894 503 "},"ή":{"x_min":0.78125,"x_max":697,"ha":810,"o":"m 697 -278 l 572 -278 l 572 454 q 540 587 572 536 q 425 650 501 650 q 271 579 337 650 q 206 420 206 509 l 206 0 l 81 0 l 81 489 q 73 588 81 562 q 0 644 56 644 l 0 741 q 68 755 38 755 q 158 721 124 755 q 200 630 193 687 q 297 726 234 692 q 434 761 359 761 q 620 692 544 761 q 697 516 697 624 l 697 -278 m 479 1040 l 290 819 l 207 819 l 341 1040 l 479 1040 "},"Θ":{"x_min":0,"x_max":960,"ha":1056,"o":"m 960 507 q 833 129 960 280 q 476 -32 698 -32 q 123 129 255 -32 q 0 507 0 280 q 123 883 0 732 q 476 1045 255 1045 q 832 883 696 1045 q 960 507 960 732 m 817 500 q 733 789 817 669 q 476 924 639 924 q 223 792 317 924 q 142 507 142 675 q 222 222 142 339 q 476 89 315 89 q 730 218 636 89 q 817 500 817 334 m 716 449 l 243 449 l 243 571 l 716 571 l 716 449 "},"®":{"x_min":-3,"x_max":1008,"ha":1106,"o":"m 503 532 q 614 562 566 532 q 672 658 672 598 q 614 747 672 716 q 503 772 569 772 l 338 772 l 338 532 l 503 532 m 502 -7 q 123 151 263 -7 q -3 501 -3 294 q 123 851 -3 706 q 502 1011 263 1011 q 881 851 739 1011 q 1008 501 1008 708 q 883 151 1008 292 q 502 -7 744 -7 m 502 60 q 830 197 709 60 q 940 501 940 322 q 831 805 940 681 q 502 944 709 944 q 174 805 296 944 q 65 501 65 680 q 173 197 65 320 q 502 60 294 60 m 788 146 l 678 146 q 653 316 655 183 q 527 449 652 449 l 338 449 l 338 146 l 241 146 l 241 854 l 518 854 q 688 808 621 854 q 766 658 766 755 q 739 563 766 607 q 668 497 713 519 q 751 331 747 472 q 788 164 756 190 l 788 146 "},"~":{"x_min":0,"x_max":833,"ha":931,"o":"m 833 958 q 778 753 833 831 q 594 665 716 665 q 402 761 502 665 q 240 857 302 857 q 131 795 166 857 q 104 665 104 745 l 0 665 q 54 867 0 789 q 237 958 116 958 q 429 861 331 958 q 594 765 527 765 q 704 827 670 765 q 729 958 729 874 l 833 958 "},"Ε":{"x_min":0,"x_max":736.21875,"ha":778,"o":"m 736 0 l 0 0 l 0 1013 l 725 1013 l 725 889 l 139 889 l 139 585 l 677 585 l 677 467 l 139 467 l 139 125 l 736 125 l 736 0 "},"³":{"x_min":0,"x_max":450,"ha":547,"o":"m 450 552 q 379 413 450 464 q 220 366 313 366 q 69 414 130 366 q 0 567 0 470 l 85 567 q 126 470 85 504 q 225 437 168 437 q 320 467 280 437 q 360 552 360 498 q 318 632 360 608 q 213 657 276 657 q 195 657 203 657 q 176 657 181 657 l 176 722 q 279 733 249 722 q 334 815 334 752 q 300 881 334 856 q 220 907 267 907 q 133 875 169 907 q 97 781 97 844 l 15 781 q 78 926 15 875 q 220 972 135 972 q 364 930 303 972 q 426 817 426 888 q 344 697 426 733 q 421 642 392 681 q 450 552 450 603 "},"[":{"x_min":0,"x_max":273.609375,"ha":371,"o":"m 273 -281 l 0 -281 l 0 1013 l 273 1013 l 273 920 l 124 920 l 124 -187 l 273 -187 l 273 -281 "},"L":{"x_min":0,"x_max":645.828125,"ha":696,"o":"m 645 0 l 0 0 l 0 1013 l 140 1013 l 140 126 l 645 126 l 645 0 "},"σ":{"x_min":0,"x_max":803.390625,"ha":894,"o":"m 803 628 l 633 628 q 713 368 713 512 q 618 93 713 204 q 357 -25 518 -25 q 94 91 194 -25 q 0 368 0 201 q 94 644 0 533 q 356 761 194 761 q 481 750 398 761 q 608 739 564 739 l 803 739 l 803 628 m 360 85 q 529 180 467 85 q 584 374 584 262 q 527 566 584 490 q 352 651 463 651 q 187 559 247 651 q 135 368 135 478 q 189 175 135 254 q 360 85 251 85 "},"ζ":{"x_min":0,"x_max":573,"ha":642,"o":"m 573 -40 q 553 -162 573 -97 q 510 -278 543 -193 l 400 -278 q 441 -187 428 -219 q 462 -90 462 -132 q 378 -14 462 -14 q 108 45 197 -14 q 0 290 0 117 q 108 631 0 462 q 353 901 194 767 l 55 901 l 55 1012 l 561 1012 l 561 924 q 261 669 382 831 q 128 301 128 489 q 243 117 128 149 q 458 98 350 108 q 573 -40 573 80 "},"θ":{"x_min":0,"x_max":674,"ha":778,"o":"m 674 496 q 601 160 674 304 q 336 -26 508 -26 q 73 153 165 -26 q 0 485 0 296 q 72 840 0 683 q 343 1045 166 1045 q 605 844 516 1045 q 674 496 674 692 m 546 579 q 498 798 546 691 q 336 935 437 935 q 178 798 237 935 q 126 579 137 701 l 546 579 m 546 475 l 126 475 q 170 233 126 348 q 338 80 230 80 q 504 233 447 80 q 546 475 546 346 "},"Ο":{"x_min":0,"x_max":958,"ha":1054,"o":"m 485 1042 q 834 883 703 1042 q 958 511 958 735 q 834 136 958 287 q 481 -26 701 -26 q 126 130 261 -26 q 0 504 0 279 q 127 880 0 729 q 485 1042 263 1042 m 480 98 q 731 225 638 98 q 815 504 815 340 q 733 783 815 670 q 480 913 640 913 q 226 785 321 913 q 142 504 142 671 q 226 224 142 339 q 480 98 319 98 "},"Γ":{"x_min":0,"x_max":705.28125,"ha":749,"o":"m 705 886 l 140 886 l 140 0 l 0 0 l 0 1012 l 705 1012 l 705 886 "}," ":{"x_min":0,"x_max":0,"ha":375},"%":{"x_min":-3,"x_max":1089,"ha":1186,"o":"m 845 0 q 663 76 731 0 q 602 244 602 145 q 661 412 602 344 q 845 489 728 489 q 1027 412 959 489 q 1089 244 1089 343 q 1029 76 1089 144 q 845 0 962 0 m 844 103 q 945 143 909 103 q 981 243 981 184 q 947 340 981 301 q 844 385 909 385 q 744 342 781 385 q 708 243 708 300 q 741 147 708 186 q 844 103 780 103 m 888 986 l 284 -25 l 199 -25 l 803 986 l 888 986 m 241 468 q 58 545 126 468 q -3 715 -3 615 q 56 881 -3 813 q 238 958 124 958 q 421 881 353 958 q 483 712 483 813 q 423 544 483 612 q 241 468 356 468 m 241 855 q 137 811 175 855 q 100 710 100 768 q 136 612 100 653 q 240 572 172 572 q 344 614 306 572 q 382 713 382 656 q 347 810 382 771 q 241 855 308 855 "},"P":{"x_min":0,"x_max":726,"ha":806,"o":"m 424 1013 q 640 931 555 1013 q 726 719 726 850 q 637 506 726 587 q 413 426 548 426 l 140 426 l 140 0 l 0 0 l 0 1013 l 424 1013 m 379 889 l 140 889 l 140 548 l 372 548 q 522 589 459 548 q 593 720 593 637 q 528 845 593 801 q 379 889 463 889 "},"Έ":{"x_min":0,"x_max":1078.21875,"ha":1118,"o":"m 1078 0 l 342 0 l 342 1013 l 1067 1013 l 1067 889 l 481 889 l 481 585 l 1019 585 l 1019 467 l 481 467 l 481 125 l 1078 125 l 1078 0 m 277 1040 l 83 799 l 0 799 l 140 1040 l 277 1040 "},"Ώ":{"x_min":0.125,"x_max":1136.546875,"ha":1235,"o":"m 1136 0 l 722 0 l 722 123 q 911 309 842 194 q 981 558 981 423 q 893 813 981 710 q 651 923 800 923 q 411 821 501 923 q 321 568 321 720 q 390 316 321 433 q 579 123 459 200 l 579 0 l 166 0 l 166 124 l 384 124 q 235 327 289 210 q 182 572 182 444 q 311 912 182 782 q 651 1042 441 1042 q 989 910 858 1042 q 1120 569 1120 778 q 1066 326 1120 443 q 917 124 1013 210 l 1136 124 l 1136 0 m 277 1040 l 83 800 l 0 800 l 140 1041 l 277 1040 "},"_":{"x_min":0,"x_max":705.5625,"ha":803,"o":"m 705 -334 l 0 -334 l 0 -234 l 705 -234 l 705 -334 "},"Ϊ":{"x_min":-110,"x_max":246,"ha":275,"o":"m 246 1046 l 118 1046 l 118 1189 l 246 1189 l 246 1046 m 18 1046 l -110 1046 l -110 1189 l 18 1189 l 18 1046 m 136 0 l 0 0 l 0 1012 l 136 1012 l 136 0 "},"+":{"x_min":23,"x_max":768,"ha":792,"o":"m 768 372 l 444 372 l 444 0 l 347 0 l 347 372 l 23 372 l 23 468 l 347 468 l 347 840 l 444 840 l 444 468 l 768 468 l 768 372 "},"½":{"x_min":0,"x_max":1050,"ha":1149,"o":"m 1050 0 l 625 0 q 712 178 625 108 q 878 277 722 187 q 967 385 967 328 q 932 456 967 429 q 850 484 897 484 q 759 450 798 484 q 721 352 721 416 l 640 352 q 706 502 640 448 q 851 551 766 551 q 987 509 931 551 q 1050 385 1050 462 q 976 251 1050 301 q 829 179 902 215 q 717 68 740 133 l 1050 68 l 1050 0 m 834 985 l 215 -28 l 130 -28 l 750 984 l 834 985 m 224 422 l 142 422 l 142 811 l 0 811 l 0 867 q 104 889 62 867 q 164 973 157 916 l 224 973 l 224 422 "},"Ρ":{"x_min":0,"x_max":720,"ha":783,"o":"m 424 1013 q 637 933 554 1013 q 720 723 720 853 q 633 508 720 591 q 413 426 546 426 l 140 426 l 140 0 l 0 0 l 0 1013 l 424 1013 m 378 889 l 140 889 l 140 548 l 371 548 q 521 589 458 548 q 592 720 592 637 q 527 845 592 801 q 378 889 463 889 "},"'":{"x_min":0,"x_max":139,"ha":236,"o":"m 139 851 q 102 737 139 784 q 0 669 65 690 l 0 734 q 59 787 42 741 q 72 873 72 821 l 0 873 l 0 1013 l 139 1013 l 139 851 "},"ª":{"x_min":0,"x_max":350,"ha":397,"o":"m 350 625 q 307 616 328 616 q 266 631 281 616 q 247 673 251 645 q 190 628 225 644 q 116 613 156 613 q 32 641 64 613 q 0 722 0 669 q 72 826 0 800 q 247 866 159 846 l 247 887 q 220 934 247 916 q 162 953 194 953 q 104 934 129 953 q 76 882 80 915 l 16 882 q 60 976 16 941 q 166 1011 104 1011 q 266 979 224 1011 q 308 891 308 948 l 308 706 q 311 679 308 688 q 331 670 315 670 l 350 672 l 350 625 m 247 757 l 247 811 q 136 790 175 798 q 64 726 64 773 q 83 682 64 697 q 132 667 103 667 q 207 690 174 667 q 247 757 247 718 "},"΅":{"x_min":0,"x_max":450,"ha":553,"o":"m 450 800 l 340 800 l 340 925 l 450 925 l 450 800 m 406 1040 l 212 800 l 129 800 l 269 1040 l 406 1040 m 110 800 l 0 800 l 0 925 l 110 925 l 110 800 "},"T":{"x_min":0,"x_max":777,"ha":835,"o":"m 777 894 l 458 894 l 458 0 l 319 0 l 319 894 l 0 894 l 0 1013 l 777 1013 l 777 894 "},"Φ":{"x_min":0,"x_max":915,"ha":997,"o":"m 527 0 l 389 0 l 389 122 q 110 231 220 122 q 0 509 0 340 q 110 785 0 677 q 389 893 220 893 l 389 1013 l 527 1013 l 527 893 q 804 786 693 893 q 915 509 915 679 q 805 231 915 341 q 527 122 696 122 l 527 0 m 527 226 q 712 310 641 226 q 779 507 779 389 q 712 705 779 627 q 527 787 641 787 l 527 226 m 389 226 l 389 787 q 205 698 275 775 q 136 505 136 620 q 206 308 136 391 q 389 226 276 226 "},"⁋":{"x_min":0,"x_max":0,"ha":694},"j":{"x_min":-77.78125,"x_max":167,"ha":349,"o":"m 167 871 l 42 871 l 42 1013 l 167 1013 l 167 871 m 167 -80 q 121 -231 167 -184 q -26 -278 76 -278 l -77 -278 l -77 -164 l -41 -164 q 26 -143 11 -164 q 42 -65 42 -122 l 42 737 l 167 737 l 167 -80 "},"Σ":{"x_min":0,"x_max":756.953125,"ha":819,"o":"m 756 0 l 0 0 l 0 107 l 395 523 l 22 904 l 22 1013 l 745 1013 l 745 889 l 209 889 l 566 523 l 187 125 l 756 125 l 756 0 "},"1":{"x_min":215.671875,"x_max":574,"ha":792,"o":"m 574 0 l 442 0 l 442 697 l 215 697 l 215 796 q 386 833 330 796 q 475 986 447 875 l 574 986 l 574 0 "},"›":{"x_min":18.0625,"x_max":774,"ha":792,"o":"m 774 376 l 18 40 l 18 149 l 631 421 l 18 692 l 18 799 l 774 465 l 774 376 "},"<":{"x_min":17.984375,"x_max":773.609375,"ha":792,"o":"m 773 40 l 18 376 l 17 465 l 773 799 l 773 692 l 159 420 l 773 149 l 773 40 "},"£":{"x_min":0,"x_max":704.484375,"ha":801,"o":"m 704 41 q 623 -10 664 5 q 543 -26 583 -26 q 359 15 501 -26 q 243 36 288 36 q 158 23 197 36 q 73 -21 119 10 l 6 76 q 125 195 90 150 q 175 331 175 262 q 147 443 175 383 l 0 443 l 0 512 l 108 512 q 43 734 43 623 q 120 929 43 854 q 358 1010 204 1010 q 579 936 487 1010 q 678 729 678 857 l 678 684 l 552 684 q 504 838 552 780 q 362 896 457 896 q 216 852 263 896 q 176 747 176 815 q 199 627 176 697 q 248 512 217 574 l 468 512 l 468 443 l 279 443 q 297 356 297 398 q 230 194 297 279 q 153 107 211 170 q 227 133 190 125 q 293 142 264 142 q 410 119 339 142 q 516 96 482 96 q 579 105 550 96 q 648 142 608 115 l 704 41 "},"t":{"x_min":0,"x_max":367,"ha":458,"o":"m 367 0 q 312 -5 339 -2 q 262 -8 284 -8 q 145 28 183 -8 q 108 143 108 64 l 108 638 l 0 638 l 0 738 l 108 738 l 108 944 l 232 944 l 232 738 l 367 738 l 367 638 l 232 638 l 232 185 q 248 121 232 140 q 307 102 264 102 q 345 104 330 102 q 367 107 360 107 l 367 0 "},"¬":{"x_min":0,"x_max":706,"ha":803,"o":"m 706 411 l 706 158 l 630 158 l 630 335 l 0 335 l 0 411 l 706 411 "},"λ":{"x_min":0,"x_max":750,"ha":803,"o":"m 750 -7 q 679 -15 716 -15 q 538 59 591 -15 q 466 214 512 97 l 336 551 l 126 0 l 0 0 l 270 705 q 223 837 247 770 q 116 899 190 899 q 90 898 100 899 l 90 1004 q 152 1011 125 1011 q 298 938 244 1011 q 373 783 326 901 l 605 192 q 649 115 629 136 q 716 95 669 95 l 736 95 q 750 97 745 97 l 750 -7 "},"W":{"x_min":0,"x_max":1263.890625,"ha":1351,"o":"m 1263 1013 l 995 0 l 859 0 l 627 837 l 405 0 l 265 0 l 0 1013 l 136 1013 l 342 202 l 556 1013 l 701 1013 l 921 207 l 1133 1012 l 1263 1013 "},">":{"x_min":18.0625,"x_max":774,"ha":792,"o":"m 774 376 l 18 40 l 18 149 l 631 421 l 18 692 l 18 799 l 774 465 l 774 376 "},"v":{"x_min":0,"x_max":675.15625,"ha":761,"o":"m 675 738 l 404 0 l 272 0 l 0 738 l 133 737 l 340 147 l 541 737 l 675 738 "},"τ":{"x_min":0.28125,"x_max":644.5,"ha":703,"o":"m 644 628 l 382 628 l 382 179 q 388 120 382 137 q 436 91 401 91 q 474 94 447 91 q 504 97 501 97 l 504 0 q 454 -9 482 -5 q 401 -14 426 -14 q 278 67 308 -14 q 260 233 260 118 l 260 628 l 0 628 l 0 739 l 644 739 l 644 628 "},"ξ":{"x_min":0,"x_max":624.9375,"ha":699,"o":"m 624 -37 q 608 -153 624 -96 q 563 -278 593 -211 l 454 -278 q 491 -183 486 -200 q 511 -83 511 -126 q 484 -23 511 -44 q 370 1 452 1 q 323 0 354 1 q 283 -1 293 -1 q 84 76 169 -1 q 0 266 0 154 q 56 431 0 358 q 197 538 108 498 q 94 613 134 562 q 54 730 54 665 q 77 823 54 780 q 143 901 101 867 l 27 901 l 27 1012 l 576 1012 l 576 901 l 380 901 q 244 863 303 901 q 178 745 178 820 q 312 600 178 636 q 532 582 380 582 l 532 479 q 276 455 361 479 q 118 281 118 410 q 165 173 118 217 q 274 120 208 133 q 494 101 384 110 q 624 -37 624 76 "},"&":{"x_min":-3,"x_max":894.25,"ha":992,"o":"m 894 0 l 725 0 l 624 123 q 471 0 553 40 q 306 -41 390 -41 q 168 -7 231 -41 q 62 92 105 26 q 14 187 31 139 q -3 276 -3 235 q 55 433 -3 358 q 248 581 114 508 q 170 689 196 640 q 137 817 137 751 q 214 985 137 922 q 384 1041 284 1041 q 548 988 483 1041 q 622 824 622 928 q 563 666 622 739 q 431 556 516 608 l 621 326 q 649 407 639 361 q 663 493 653 426 l 781 493 q 703 229 781 352 l 894 0 m 504 818 q 468 908 504 877 q 384 940 433 940 q 293 907 331 940 q 255 818 255 875 q 289 714 255 767 q 363 628 313 678 q 477 729 446 682 q 504 818 504 771 m 556 209 l 314 499 q 179 395 223 449 q 135 283 135 341 q 146 222 135 253 q 183 158 158 192 q 333 80 241 80 q 556 209 448 80 "},"Λ":{"x_min":0,"x_max":862.5,"ha":942,"o":"m 862 0 l 719 0 l 426 847 l 143 0 l 0 0 l 356 1013 l 501 1013 l 862 0 "},"I":{"x_min":41,"x_max":180,"ha":293,"o":"m 180 0 l 41 0 l 41 1013 l 180 1013 l 180 0 "},"G":{"x_min":0,"x_max":921,"ha":1011,"o":"m 921 0 l 832 0 l 801 136 q 655 15 741 58 q 470 -28 568 -28 q 126 133 259 -28 q 0 499 0 284 q 125 881 0 731 q 486 1043 259 1043 q 763 957 647 1043 q 905 709 890 864 l 772 709 q 668 866 747 807 q 486 926 589 926 q 228 795 322 926 q 142 507 142 677 q 228 224 142 342 q 483 94 323 94 q 712 195 625 94 q 796 435 796 291 l 477 435 l 477 549 l 921 549 l 921 0 "},"ΰ":{"x_min":0,"x_max":617,"ha":725,"o":"m 524 800 l 414 800 l 414 925 l 524 925 l 524 800 m 183 800 l 73 800 l 73 925 l 183 925 l 183 800 m 617 352 q 540 93 617 199 q 308 -24 455 -24 q 76 93 161 -24 q 0 352 0 199 l 0 738 l 126 738 l 126 354 q 169 185 126 257 q 312 98 220 98 q 451 185 402 98 q 492 354 492 257 l 492 738 l 617 738 l 617 352 m 489 1040 l 300 819 l 216 819 l 351 1040 l 489 1040 "},"`":{"x_min":0,"x_max":138.890625,"ha":236,"o":"m 138 699 l 0 699 l 0 861 q 36 974 0 929 q 138 1041 72 1020 l 138 977 q 82 931 95 969 q 69 839 69 893 l 138 839 l 138 699 "},"·":{"x_min":0,"x_max":142,"ha":239,"o":"m 142 585 l 0 585 l 0 738 l 142 738 l 142 585 "},"Υ":{"x_min":0.328125,"x_max":819.515625,"ha":889,"o":"m 819 1013 l 482 416 l 482 0 l 342 0 l 342 416 l 0 1013 l 140 1013 l 411 533 l 679 1013 l 819 1013 "},"r":{"x_min":0,"x_max":355.5625,"ha":432,"o":"m 355 621 l 343 621 q 179 569 236 621 q 122 411 122 518 l 122 0 l 0 0 l 0 737 l 117 737 l 117 604 q 204 719 146 686 q 355 753 262 753 l 355 621 "},"x":{"x_min":0,"x_max":675,"ha":764,"o":"m 675 0 l 525 0 l 331 286 l 144 0 l 0 0 l 256 379 l 12 738 l 157 737 l 336 473 l 516 738 l 661 738 l 412 380 l 675 0 "},"μ":{"x_min":0,"x_max":696.609375,"ha":747,"o":"m 696 -4 q 628 -14 657 -14 q 498 97 513 -14 q 422 8 470 41 q 313 -24 374 -24 q 207 3 258 -24 q 120 80 157 31 l 120 -278 l 0 -278 l 0 738 l 124 738 l 124 343 q 165 172 124 246 q 308 82 216 82 q 451 177 402 82 q 492 358 492 254 l 492 738 l 616 738 l 616 214 q 623 136 616 160 q 673 92 636 92 q 696 95 684 92 l 696 -4 "},"h":{"x_min":0,"x_max":615,"ha":724,"o":"m 615 472 l 615 0 l 490 0 l 490 454 q 456 590 490 535 q 338 654 416 654 q 186 588 251 654 q 122 436 122 522 l 122 0 l 0 0 l 0 1013 l 122 1013 l 122 633 q 218 727 149 694 q 362 760 287 760 q 552 676 484 760 q 615 472 615 600 "},".":{"x_min":0,"x_max":142,"ha":239,"o":"m 142 0 l 0 0 l 0 151 l 142 151 l 142 0 "},"φ":{"x_min":-2,"x_max":878,"ha":974,"o":"m 496 -279 l 378 -279 l 378 -17 q 101 88 204 -17 q -2 367 -2 194 q 68 626 -2 510 q 283 758 151 758 l 283 646 q 167 537 209 626 q 133 373 133 462 q 192 177 133 254 q 378 93 259 93 l 378 758 q 445 764 426 763 q 476 765 464 765 q 765 659 653 765 q 878 377 878 553 q 771 96 878 209 q 496 -17 665 -17 l 496 -279 m 496 93 l 514 93 q 687 183 623 93 q 746 380 746 265 q 691 569 746 491 q 522 658 629 658 l 496 656 l 496 93 "},";":{"x_min":0,"x_max":142,"ha":239,"o":"m 142 585 l 0 585 l 0 738 l 142 738 l 142 585 m 142 -12 q 105 -132 142 -82 q 0 -206 68 -182 l 0 -138 q 58 -82 43 -123 q 68 0 68 -56 l 0 0 l 0 151 l 142 151 l 142 -12 "},"f":{"x_min":0,"x_max":378,"ha":472,"o":"m 378 638 l 246 638 l 246 0 l 121 0 l 121 638 l 0 638 l 0 738 l 121 738 q 137 935 121 887 q 290 1028 171 1028 q 320 1027 305 1028 q 378 1021 334 1026 l 378 908 q 323 918 346 918 q 257 870 273 918 q 246 780 246 840 l 246 738 l 378 738 l 378 638 "},"“":{"x_min":1,"x_max":348.21875,"ha":454,"o":"m 140 670 l 1 670 l 1 830 q 37 943 1 897 q 140 1011 74 990 l 140 947 q 82 900 97 940 q 68 810 68 861 l 140 810 l 140 670 m 348 670 l 209 670 l 209 830 q 245 943 209 897 q 348 1011 282 990 l 348 947 q 290 900 305 940 q 276 810 276 861 l 348 810 l 348 670 "},"A":{"x_min":0.03125,"x_max":906.953125,"ha":1008,"o":"m 906 0 l 756 0 l 648 303 l 251 303 l 142 0 l 0 0 l 376 1013 l 529 1013 l 906 0 m 610 421 l 452 867 l 293 421 l 610 421 "},"6":{"x_min":53,"x_max":739,"ha":792,"o":"m 739 312 q 633 62 739 162 q 400 -31 534 -31 q 162 78 257 -31 q 53 439 53 206 q 178 859 53 712 q 441 986 284 986 q 643 912 559 986 q 732 713 732 833 l 601 713 q 544 830 594 786 q 426 875 494 875 q 268 793 331 875 q 193 517 193 697 q 301 597 240 570 q 427 624 362 624 q 643 540 552 624 q 739 312 739 451 m 603 298 q 540 461 603 400 q 404 516 484 516 q 268 461 323 516 q 207 300 207 401 q 269 137 207 198 q 405 83 325 83 q 541 137 486 83 q 603 298 603 197 "},"‘":{"x_min":1,"x_max":139.890625,"ha":236,"o":"m 139 670 l 1 670 l 1 830 q 37 943 1 897 q 139 1011 74 990 l 139 947 q 82 900 97 940 q 68 810 68 861 l 139 810 l 139 670 "},"ϊ":{"x_min":-70,"x_max":283,"ha":361,"o":"m 283 800 l 173 800 l 173 925 l 283 925 l 283 800 m 40 800 l -70 800 l -70 925 l 40 925 l 40 800 m 283 3 q 232 -10 257 -5 q 181 -15 206 -15 q 84 26 118 -15 q 41 200 41 79 l 41 737 l 166 737 l 167 215 q 171 141 167 157 q 225 101 182 101 q 247 103 238 101 q 283 112 256 104 l 283 3 "},"π":{"x_min":-0.21875,"x_max":773.21875,"ha":857,"o":"m 773 -7 l 707 -11 q 575 40 607 -11 q 552 174 552 77 l 552 226 l 552 626 l 222 626 l 222 0 l 97 0 l 97 626 l 0 626 l 0 737 l 773 737 l 773 626 l 676 626 l 676 171 q 695 103 676 117 q 773 90 714 90 l 773 -7 "},"ά":{"x_min":0,"x_max":765.5625,"ha":809,"o":"m 765 -4 q 698 -14 726 -14 q 564 97 586 -14 q 466 7 525 40 q 337 -26 407 -26 q 88 98 186 -26 q 0 369 0 212 q 88 637 0 525 q 337 760 184 760 q 465 727 407 760 q 563 637 524 695 l 563 738 l 685 738 l 685 222 q 693 141 685 168 q 748 94 708 94 q 765 95 760 94 l 765 -4 m 584 371 q 531 562 584 485 q 360 653 470 653 q 192 566 254 653 q 135 379 135 489 q 186 181 135 261 q 358 84 247 84 q 528 176 465 84 q 584 371 584 260 m 604 1040 l 415 819 l 332 819 l 466 1040 l 604 1040 "},"O":{"x_min":0,"x_max":958,"ha":1057,"o":"m 485 1041 q 834 882 702 1041 q 958 512 958 734 q 834 136 958 287 q 481 -26 702 -26 q 126 130 261 -26 q 0 504 0 279 q 127 880 0 728 q 485 1041 263 1041 m 480 98 q 731 225 638 98 q 815 504 815 340 q 733 783 815 669 q 480 912 640 912 q 226 784 321 912 q 142 504 142 670 q 226 224 142 339 q 480 98 319 98 "},"n":{"x_min":0,"x_max":615,"ha":724,"o":"m 615 463 l 615 0 l 490 0 l 490 454 q 453 592 490 537 q 331 656 410 656 q 178 585 240 656 q 117 421 117 514 l 117 0 l 0 0 l 0 738 l 117 738 l 117 630 q 218 728 150 693 q 359 764 286 764 q 552 675 484 764 q 615 463 615 593 "},"3":{"x_min":54,"x_max":737,"ha":792,"o":"m 737 284 q 635 55 737 141 q 399 -25 541 -25 q 156 52 248 -25 q 54 308 54 140 l 185 308 q 245 147 185 202 q 395 96 302 96 q 539 140 484 96 q 602 280 602 190 q 510 429 602 390 q 324 454 451 454 l 324 565 q 487 584 441 565 q 565 719 565 617 q 515 835 565 791 q 395 879 466 879 q 255 824 307 879 q 203 661 203 769 l 78 661 q 166 909 78 822 q 387 992 250 992 q 603 921 513 992 q 701 723 701 844 q 669 607 701 656 q 578 524 637 558 q 696 434 655 499 q 737 284 737 369 "},"9":{"x_min":53,"x_max":739,"ha":792,"o":"m 739 524 q 619 94 739 241 q 362 -32 516 -32 q 150 47 242 -32 q 59 244 59 126 l 191 244 q 246 129 191 176 q 373 82 301 82 q 526 161 466 82 q 597 440 597 255 q 363 334 501 334 q 130 432 216 334 q 53 650 53 521 q 134 880 53 786 q 383 986 226 986 q 659 841 566 986 q 739 524 739 719 m 388 449 q 535 514 480 449 q 585 658 585 573 q 535 805 585 744 q 388 873 480 873 q 242 809 294 873 q 191 658 191 745 q 239 514 191 572 q 388 449 292 449 "},"l":{"x_min":41,"x_max":166,"ha":279,"o":"m 166 0 l 41 0 l 41 1013 l 166 1013 l 166 0 "},"¤":{"x_min":40.09375,"x_max":728.796875,"ha":825,"o":"m 728 304 l 649 224 l 512 363 q 383 331 458 331 q 256 363 310 331 l 119 224 l 40 304 l 177 441 q 150 553 150 493 q 184 673 150 621 l 40 818 l 119 898 l 267 749 q 321 766 291 759 q 384 773 351 773 q 447 766 417 773 q 501 749 477 759 l 649 898 l 728 818 l 585 675 q 612 618 604 648 q 621 553 621 587 q 591 441 621 491 l 728 304 m 384 682 q 280 643 318 682 q 243 551 243 604 q 279 461 243 499 q 383 423 316 423 q 487 461 449 423 q 525 553 525 500 q 490 641 525 605 q 384 682 451 682 "},"κ":{"x_min":0,"x_max":632.328125,"ha":679,"o":"m 632 0 l 482 0 l 225 384 l 124 288 l 124 0 l 0 0 l 0 738 l 124 738 l 124 446 l 433 738 l 596 738 l 312 466 l 632 0 "},"4":{"x_min":48,"x_max":742.453125,"ha":792,"o":"m 742 243 l 602 243 l 602 0 l 476 0 l 476 243 l 48 243 l 48 368 l 476 958 l 602 958 l 602 354 l 742 354 l 742 243 m 476 354 l 476 792 l 162 354 l 476 354 "},"p":{"x_min":0,"x_max":685,"ha":786,"o":"m 685 364 q 598 96 685 205 q 350 -23 504 -23 q 121 89 205 -23 l 121 -278 l 0 -278 l 0 738 l 121 738 l 121 633 q 220 726 159 691 q 351 761 280 761 q 598 636 504 761 q 685 364 685 522 m 557 371 q 501 560 557 481 q 330 651 437 651 q 162 559 223 651 q 108 366 108 479 q 162 177 108 254 q 333 87 224 87 q 502 178 441 87 q 557 371 557 258 "},"‡":{"x_min":0,"x_max":777,"ha":835,"o":"m 458 238 l 458 0 l 319 0 l 319 238 l 0 238 l 0 360 l 319 360 l 319 681 l 0 683 l 0 804 l 319 804 l 319 1015 l 458 1013 l 458 804 l 777 804 l 777 683 l 458 683 l 458 360 l 777 360 l 777 238 l 458 238 "},"ψ":{"x_min":0,"x_max":808,"ha":907,"o":"m 465 -278 l 341 -278 l 341 -15 q 87 102 180 -15 q 0 378 0 210 l 0 739 l 133 739 l 133 379 q 182 195 133 275 q 341 98 242 98 l 341 922 l 465 922 l 465 98 q 623 195 563 98 q 675 382 675 278 l 675 742 l 808 742 l 808 381 q 720 104 808 213 q 466 -13 627 -13 l 465 -278 "},"η":{"x_min":0.78125,"x_max":697,"ha":810,"o":"m 697 -278 l 572 -278 l 572 454 q 540 587 572 536 q 425 650 501 650 q 271 579 337 650 q 206 420 206 509 l 206 0 l 81 0 l 81 489 q 73 588 81 562 q 0 644 56 644 l 0 741 q 68 755 38 755 q 158 720 124 755 q 200 630 193 686 q 297 726 234 692 q 434 761 359 761 q 620 692 544 761 q 697 516 697 624 l 697 -278 "}},"cssFontWeight":"normal","ascender":1189,"underlinePosition":-100,"cssFontStyle":"normal","boundingBox":{"yMin":-334,"xMin":-111,"yMax":1189,"xMax":1672},"resolution":1000,"original_font_information":{"postscript_name":"Helvetiker-Regular","version_string":"Version 1.00 2004 initial release","vendor_url":"http://www.magenta.gr/","full_font_name":"Helvetiker","font_family_name":"Helvetiker","copyright":"Copyright (c) Μagenta ltd, 2004","description":"","trademark":"","designer":"","designer_url":"","unique_font_identifier":"Μagenta ltd:Helvetiker:22-10-104","license_url":"http://www.ellak.gr/fonts/MgOpen/license.html","license_description":"Copyright (c) 2004 by MAGENTA Ltd. All Rights Reserved.\r\n\r\nPermission is hereby granted, free of charge, to any person obtaining a copy of the fonts accompanying this license (\"Fonts\") and associated documentation files (the \"Font Software\"), to reproduce and distribute the Font Software, including without limitation the rights to use, copy, merge, publish, distribute, and/or sell copies of the Font Software, and to permit persons to whom the Font Software is furnished to do so, subject to the following conditions: \r\n\r\nThe above copyright and this permission notice shall be included in all copies of one or more of the Font Software typefaces.\r\n\r\nThe Font Software may be modified, altered, or added to, and in particular the designs of glyphs or characters in the Fonts may be modified and additional glyphs or characters may be added to the Fonts, only if the fonts are renamed to names not containing the word \"MgOpen\", or if the modifications are accepted for inclusion in the Font Software itself by the each appointed Administrator.\r\n\r\nThis License becomes null and void to the extent applicable to Fonts or Font Software that has been modified and is distributed under the \"MgOpen\" name.\r\n\r\nThe Font Software may be sold as part of a larger software package but no copy of one or more of the Font Software typefaces may be sold by itself. \r\n\r\nTHE FONT SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL MAGENTA OR PERSONS OR BODIES IN CHARGE OF ADMINISTRATION AND MAINTENANCE OF THE FONT SOFTWARE BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE.","manufacturer_name":"Μagenta ltd","font_sub_family_name":"Regular"},"descender":-334,"familyName":"Helvetiker","lineHeight":1522,"underlineThickness":50} \ No newline at end of file diff --git a/src/assets/grass.jpg b/src/assets/grass.jpg new file mode 100644 index 00000000..ed6c7f85 Binary files /dev/null and b/src/assets/grass.jpg differ diff --git a/src/assets/i18n/en.json b/src/assets/i18n/en.json new file mode 100644 index 00000000..11f188ad --- /dev/null +++ b/src/assets/i18n/en.json @@ -0,0 +1,112 @@ +{ + "DASHBOARD": "Dashboard", + "APPS": "Apps", + "INBOX": "Inbox", + "CHAT": "Chat", + "CALENDAR": "Calendar", + "DIALOGS": "Dialogs", + "CONFIRM": "Confirm", + "LOADER": "Loader", + + "MATERIAL": "Material", + "BUTTONS": "Buttons", + "CARDS": "Cards", + "SELECT": "Select", + "AUTOCOMPLETE": "Autocomplete", + "INPUT": "Input", + "CHECKBOX": "Checkbox", + "DATEPICKER": "Datepicker", + "RADIO": "Radio", + "TOOLBAR": "Toolbar", + "LISTS": "Lists", + "GRIDS": "Grids", + "PROGRESS": "Progress", + "TABS": "Tabs", + "SWITCH": "Switch", + "TOOLTIP": "Tooltip", + "MENU": "Menu", + "MEMBER": "Member", + "SAVE": "Save", + "SLIDER": "Slider", + "SNACKBAR": "Snackbar", + + "FORMS": "Forms", + "EDITOR": "Editor", + "BASIC": "Basic", + "UPLOAD": "Upload", + "WIZARD": "Wizard", + + "TABLES": "Tables", + "TOUR": "Tour", + "FULLSCREEN": "Fullscreen", + "PAGING": "Paging", + "FILTER": "Filter", + + "MAP": "Map", + "CHARTS": "Charts", + "DND": "Drag and Drop", + + "PAGES": "Pages", + "SESSIONS": "Sessions", + "SIGNIN": "Signin", + "SIGNUP": "Signup", + "FORGOT": "Forgot", + "LOCKSCREEN": "Lockscreen", + + "OTHERS": "Others", + "GALLERY": "Gallery", + "PRICINGS": "Pricings", + "USERS": "Users", + "PROFILE": "Profile", + "BLANK": "Blank", + "MATICONS": "Material Icons", + "ADD": "Add", + "ITEM": "Item", + "SUBITEM": "Subitem", + "DOC": "Documentation", + "NOTFOUND": "Not Found", + "ERROR": "Error", + "OVERVIEW": "Overview", + "SETTINGS": "Settings", + + "ECOMMERCE": "Ecommerce", + "SHOP": "Shop", + "PRODUCTS": "Products", + "PRODUCT DETAILS": "Product Details", + "CART": "Cart", + "CHECKOUT": "Checkout", + + "ROAD-GEOMETRY-TITLE": "Road Tool", + "ROAD-GEOMETRY-DESCRIPTION": "Tool to create road geometries.
    Use SHIFT + LEFT CLICK to create control points.
    LEFT CLICK control points to drag and reshape road.
    LEFT CLICK to select node (Red Highlights at start/end of road).
    Roads can be linked/joined by selecting 2 nodes
    ", + + "LANE-WIDTH-TOOL-TITLE": "Lane Width Tool", + "LANE-WIDTH-TOOL-DESCRIPTION": "Tool to edit width of lane.
    Use SHIFT + LEFT CLICK to insert a new node.
    Use LEFT CLICK to select a node and edit.
    Drag selected node to change distance value
    Change node properties from the inspector.
    ", + + "ADD-LANE-TOOL-TITLE": "Add Lane Tool", + "ADD-LANE-TOOL-DESCRIPTION": "Add new duplicate lane.
    Use SHIFT + LEFT CLICK to select the lane you want to duplicate.
    New lane will be added next to the selected lane.
    ", + + "LANE-TYPE-TOOL-TITLE": "Lane Type Tool", + "LANE-TYPE-TOOL-DESCRIPTION": "Change the type of lane.
    Use SHIFT + LEFT CLICK to select the lane.
    Change the lane type from the inspector.
    ", + "LANE-TOOL-TITLE": "Lane Tool", + "LANE-TOOL-DESCRIPTION": "Change the type, material & other properties of lane.
    Use SHIFT + LEFT CLICK to select the lane.
    Change the lane type from the inspector.
    ", + + "LANE-MARKING-TOOL-TITLE": "Lane Marking Tool", + "LANE-MARKING-TOOL-DESCRIPTION": "Tool to edit lane markings.
    Use LEFT CLICK to select road, lane, marking via node or lines.
    Use SHIFT + LEFT CLICK to insert new node.
    Use LEFT CLICK to select node & drag it to change distance value.
    Change node and marking properties from inspector
    ", + + "LANE-DIRECTION-TOOL-TITLE": "Lane Direction Tool", + "LANE-DIRECTION-TOOL-DESCRIPTION": "Adjust Lane Direction for traffic.
    Use SHIFT + LEFT CLICK to select the lane
    Change values from the inspector.
    ", + + "ROAD-SIGNAL-TOOL-TITLE": "Road Signal Tool", + "ROAD-SIGNAL-TOOL-DESCRIPTION": "Add Traffic Signals.
    Use SHIFT + LEFT CLICK to add a signal in the environment.
    Drag the control point to move the signal.
    Change values from the inspector.", + + "ROAD-OBJECT-TOOL-TITLE": "Road Object Tool", + "ROAD-OBJECT-TOOL-DESCRIPTION": "Add objects & props in the environment.
    Use SHIFT + LEFT CLICK to add an object in the environment.
    Drag the control point to move the object.
    Change values from the inspector.", + + "ROAD-SIGN-TOOL-TITLE": "Road Sign Tool", + "ROAD-SIGN-TOOL-DESCRIPTION": "Add Road Sign.
    Use SHIFT + LEFT CLICK to add a sign in the environment.
    Drag the control point to move the sign.
    Change values from the inspector.", + "VEHICLE-TOOL-TITLE": "Vehicle Tool", + "VEHICLE-TOOL-DESCRIPTION": "Add Vehicle actors.
    Use SHIFT + LEFT CLICK to add drop an actor in scene.
    Drag the control point to move the actor.
    Change values from the inspector.", + + "EXTRA": "EXTRA" + +} \ No newline at end of file diff --git a/src/assets/i18n/es.json b/src/assets/i18n/es.json new file mode 100644 index 00000000..fdf00998 --- /dev/null +++ b/src/assets/i18n/es.json @@ -0,0 +1,78 @@ +{ + "DASHBOARD": "Tablero", + "APPS": "Apps", + "INBOX": "Mensaje", + "CHAT": "Charla", + "CALENDAR": "Calendario", + "DIALOGS": "Diálogo", + "CONFIRM": "Confirmar", + "LOADER": "Loader", + + "MEMBER": "Miembro", + "MATERIAL": "Material", + "BUTTONS": "Botones", + "CARDS": "Tarjeta", + "SELECT": "Seleccionar", + "AUTOCOMPLETE": "Autocomplete", + "INPUT": "Entrada", + "CHECKBOX": "Checkbox", + "DATEPICKER": "Datepicker", + "RADIO": "Radio", + "TOOLBAR": "Toolbar", + "LISTS": "lista", + "GRIDS": "Grids", + "PROGRESS": "Progreso", + "TABS": "Tabs", + "SWITCH": "Cambio", + "TOOLTIP": "Tooltip", + "MENU": "Menú", + "SAVE": "Salvar", + "SLIDER": "Slider", + "SNACKBAR": "Snackbar", + + "FORMS": "Formularios", + "EDITOR": "Editor", + "BASIC": "Sencillo", + "UPLOAD": "Upload", + "WIZARD": "paso a paso", + + "TABLES": "Mesas", + "FULLSCREEN": "Pantalla Completa", + "PAGING": "Paging", + "FILTER": "Filtrar", + + "MAP": "Mapa", + "CHARTS": "Gráfico", + "DND": "Arrastrar y soltar", + + "PAGES": "Página", + "TOUR": "Gira", + "SESSIONS": "Sesiones", + "SIGNIN": "Registrarse", + "SIGNUP": "Regístrate", + "FORGOT": "Forgot", + "LOCKSCREEN": "Lockscreen", + + "OTHERS": "Otros", + "GALLERY": "Gallery", + "PRICINGS": "Pricings", + "USERS": "Users", + "PROFILE": "Perfil", + "BLANK": "Blank", + "MATICONS": "Material Iconos", + "ADD": "Añadir", + "ITEM": "ít.", + "SUBITEM": "Subitem", + "DOC": "Documentación", + "NOTFOUND": "Perdió", + "ERROR": "Error", + "OVERVIEW": "Resumen", + "SETTINGS": "Ajustes", + + "ECOMMERCE": "Ecommerce", + "SHOP": "Shop", + "PRODUCTS": "Products", + "PRODUCT DETAILS": "Product Details", + "CART": "Cart", + "CHECKOUT": "Checkout" +} \ No newline at end of file diff --git a/src/assets/icon.png b/src/assets/icon.png new file mode 100644 index 00000000..1da2ab11 Binary files /dev/null and b/src/assets/icon.png differ diff --git a/src/assets/icon_delete.png b/src/assets/icon_delete.png new file mode 100644 index 00000000..eabad1cc Binary files /dev/null and b/src/assets/icon_delete.png differ diff --git a/src/assets/icons/Icon-1024.png b/src/assets/icons/Icon-1024.png new file mode 100644 index 00000000..a35ead06 Binary files /dev/null and b/src/assets/icons/Icon-1024.png differ diff --git a/src/assets/icons/Icon-256.png b/src/assets/icons/Icon-256.png new file mode 100644 index 00000000..bddbe38b Binary files /dev/null and b/src/assets/icons/Icon-256.png differ diff --git a/src/assets/icons/Icon-512.png b/src/assets/icons/Icon-512.png new file mode 100644 index 00000000..3a39c146 Binary files /dev/null and b/src/assets/icons/Icon-512.png differ diff --git a/src/assets/markings/arrow-straight.png b/src/assets/markings/arrow-straight.png new file mode 100644 index 00000000..64abf948 Binary files /dev/null and b/src/assets/markings/arrow-straight.png differ diff --git a/src/assets/markings/crosswalk-marking.png b/src/assets/markings/crosswalk-marking.png new file mode 100644 index 00000000..6a94702d Binary files /dev/null and b/src/assets/markings/crosswalk-marking.png differ diff --git a/src/assets/markings/manhole-1.png b/src/assets/markings/manhole-1.png new file mode 100644 index 00000000..0ee7df25 Binary files /dev/null and b/src/assets/markings/manhole-1.png differ diff --git a/src/assets/open-drive/RQ31_130Limit.xodr b/src/assets/open-drive/RQ31_130Limit.xodr new file mode 100644 index 00000000..9922932b --- /dev/null +++ b/src/assets/open-drive/RQ31_130Limit.xodr @@ -0,0 +1,116 @@ + + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + + + + + + + + + + + +
    +
    diff --git a/src/assets/open-drive/SampleDatabase_01.xodr b/src/assets/open-drive/SampleDatabase_01.xodr new file mode 100644 index 00000000..88ccf45c --- /dev/null +++ b/src/assets/open-drive/SampleDatabase_01.xodr @@ -0,0 +1,10156 @@ + + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + + + + + + + + + + + + +
    + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + + + + + + + + + + + + +
    + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + + + + + + + + + + + + +
    + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + +
    + + + + + + + + + + + + + + + + + + + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + +
    + + + + + + + + + + + + + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + +
    + + + + + + + + + + + + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + + + + + + + + + + + + +
    + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + + + + + + + + + + + + +
    + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + + + + + + + + + + + + +
    + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + + + + + + + + + + + + +
    + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + + + + + + + + + + + +
    + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + + + + + + + + + + + + +
    + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + + + + + + + + + + + + +
    + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + +
    + + + + + + + + + + + + + + + + + + + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + +
    + + + + + + + + + + + + + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + +
    + + + + + + + + + + + + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + + + + + + + + + + + + +
    + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + + + + + + + + + + + + +
    + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + + + + + + + + + + + + +
    + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + + + + + + + + + + + + +
    + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + + + + + + + + + + + + +
    + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + + + + + + + + + + + + +
    + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + + + + + + + + + + + + +
    + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + + + + + + + + + + + + +
    + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + + + + + + + + + + + + + +
    + + + + +
    + + + + + + + + + + + + + + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + + + + + + + + + + + + + +
    + + + + +
    + + + + + + + + + + + + + + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + + + + + + + + + + + + +
    + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + + + + + + + + + + + + +
    + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + + + + + + + + + + + + +
    + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + + + + + + + + + + + + +
    + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + + + + + + + + + + + + +
    + + + + + + +
    + + + + + + + + + + + + + + + + + + + + +
    +
    + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + + + + + + + + + + + + +
    + + + + + + +
    + + + + + + + + + + + + + + + + + + + + +
    +
    + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + + + + + + + + + + + + +
    + + + + + + +
    + + + + + + + + + + + + + + + + + + + + +
    +
    + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + + + + + + + + + + + + +
    + + + + + + +
    + + + + + + + + + + + + + + + + + + + + +
    +
    + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + + +
    + + + + + + + + + + + + + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + + + + + + +
    + + + + + + +
    + + + + + + + + + + + + + + + + + + + +
    + + + + + + + + + + + + + +
    + + + + + + +
    + + + + + + + + + + + + + + + + + + + + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + +
    + + + + + + + + + + + + + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + + + + +
    + + + + + + +
    + + + + + + + + + + + + + + + + + + + +
    + + + + + + + + + + + + + +
    + + + + + + +
    + + + + + + + + + + + + + + + + + + + + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    diff --git a/src/assets/open-drive/crossing-8-complex.xodr b/src/assets/open-drive/crossing-8-complex.xodr new file mode 100644 index 00000000..89778301 --- /dev/null +++ b/src/assets/open-drive/crossing-8-complex.xodr @@ -0,0 +1,1841 @@ + + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + + + + + + + + + +
    + + + + + + + + + + + + + + + + + + + +
    + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + + + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + + + + + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + + + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + + + + + + + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    diff --git a/src/assets/open-drive/crossing-8-course.xodr b/src/assets/open-drive/crossing-8-course.xodr new file mode 100644 index 00000000..f75da8c9 --- /dev/null +++ b/src/assets/open-drive/crossing-8-course.xodr @@ -0,0 +1,1453 @@ + + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + + + +
    + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + + + +
    + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + + + +
    + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + + + +
    + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + +
    +
    + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + +
    +
    + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + +
    +
    + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + +
    +
    + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    diff --git a/src/assets/open-drive/roundabout-8-course.xodr b/src/assets/open-drive/roundabout-8-course.xodr new file mode 100644 index 00000000..57f64367 --- /dev/null +++ b/src/assets/open-drive/roundabout-8-course.xodr @@ -0,0 +1,2358 @@ + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + +
    + + + + + + + + + + + + + + + + + + + + + +
    + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + + + + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + +
    + + + + + + + + + + + + + + + + + + + + + +
    + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + + + + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + + + + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + +
    + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + + + + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + +
    + + + + + + + + + + + + + + + + + + + + + +
    + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/assets/open-drive/straight-road.xml b/src/assets/open-drive/straight-road.xml new file mode 100644 index 00000000..e0390106 --- /dev/null +++ b/src/assets/open-drive/straight-road.xml @@ -0,0 +1,80 @@ + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + +
    + diff --git a/src/assets/open-drive/test.xml b/src/assets/open-drive/test.xml new file mode 100644 index 00000000..60cf6189 --- /dev/null +++ b/src/assets/open-drive/test.xml @@ -0,0 +1,81 @@ + + + +
    + + + + + + + + + + + + + + +
    + + + +
    + + + + + + + + + + + + +
    + +
    + + + +
    + + + + + + + + + + + + +
    + +
    + + + +
    + + + + + + + + + + + + +
    +
    + + +
    + diff --git a/src/assets/open-scenario/follow-trajectory.xml b/src/assets/open-scenario/follow-trajectory.xml new file mode 100644 index 00000000..8dceee7b --- /dev/null +++ b/src/assets/open-scenario/follow-trajectory.xml @@ -0,0 +1,138 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/assets/open-scenario/overtaker.xml b/src/assets/open-scenario/overtaker.xml new file mode 100644 index 00000000..43d1aafe --- /dev/null +++ b/src/assets/open-scenario/overtaker.xml @@ -0,0 +1,197 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/assets/open-scenario/traffic-jam.xml b/src/assets/open-scenario/traffic-jam.xml new file mode 100644 index 00000000..39c32cad --- /dev/null +++ b/src/assets/open-scenario/traffic-jam.xml @@ -0,0 +1,245 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/assets/pedestrian.png b/src/assets/pedestrian.png new file mode 100644 index 00000000..ea534968 Binary files /dev/null and b/src/assets/pedestrian.png differ diff --git a/src/assets/point.png b/src/assets/point.png new file mode 100644 index 00000000..0a560790 Binary files /dev/null and b/src/assets/point.png differ diff --git a/src/assets/roadmarks.png b/src/assets/roadmarks.png new file mode 100644 index 00000000..dccdad95 Binary files /dev/null and b/src/assets/roadmarks.png differ diff --git a/src/assets/scene-icon.png b/src/assets/scene-icon.png new file mode 100644 index 00000000..acf3e43a Binary files /dev/null and b/src/assets/scene-icon.png differ diff --git a/src/assets/sidewalk.jpg b/src/assets/sidewalk.jpg new file mode 100644 index 00000000..d8938e66 Binary files /dev/null and b/src/assets/sidewalk.jpg differ diff --git a/src/assets/signs/back_circle.png b/src/assets/signs/back_circle.png new file mode 100644 index 00000000..4d9a3e63 Binary files /dev/null and b/src/assets/signs/back_circle.png differ diff --git a/src/assets/signs/back_diamond.png b/src/assets/signs/back_diamond.png new file mode 100644 index 00000000..e70c2c47 Binary files /dev/null and b/src/assets/signs/back_diamond.png differ diff --git a/src/assets/signs/back_square.png b/src/assets/signs/back_square.png new file mode 100644 index 00000000..bd8a557e Binary files /dev/null and b/src/assets/signs/back_square.png differ diff --git a/src/assets/signs/chevron_left.png b/src/assets/signs/chevron_left.png new file mode 100644 index 00000000..61515063 Binary files /dev/null and b/src/assets/signs/chevron_left.png differ diff --git a/src/assets/signs/chevron_right.png b/src/assets/signs/chevron_right.png new file mode 100644 index 00000000..713b95ad Binary files /dev/null and b/src/assets/signs/chevron_right.png differ diff --git a/src/assets/signs/construction_zone_ahead.png b/src/assets/signs/construction_zone_ahead.png new file mode 100755 index 00000000..fae2b895 Binary files /dev/null and b/src/assets/signs/construction_zone_ahead.png differ diff --git a/src/assets/signs/curve_ahead.png b/src/assets/signs/curve_ahead.png new file mode 100755 index 00000000..c242afdc Binary files /dev/null and b/src/assets/signs/curve_ahead.png differ diff --git a/src/assets/signs/metal.png b/src/assets/signs/metal.png new file mode 100644 index 00000000..a9124bd0 Binary files /dev/null and b/src/assets/signs/metal.png differ diff --git a/src/assets/signs/no_entry.png b/src/assets/signs/no_entry.png new file mode 100755 index 00000000..6f06bb5f Binary files /dev/null and b/src/assets/signs/no_entry.png differ diff --git a/src/assets/signs/road_closed.png b/src/assets/signs/road_closed.png new file mode 100755 index 00000000..cb3f524d Binary files /dev/null and b/src/assets/signs/road_closed.png differ diff --git a/src/assets/signs/sharp_left_curve_ahead.png b/src/assets/signs/sharp_left_curve_ahead.png new file mode 100755 index 00000000..428239d9 Binary files /dev/null and b/src/assets/signs/sharp_left_curve_ahead.png differ diff --git a/src/assets/signs/sharp_right_curve_ahead.png b/src/assets/signs/sharp_right_curve_ahead.png new file mode 100755 index 00000000..47cba823 Binary files /dev/null and b/src/assets/signs/sharp_right_curve_ahead.png differ diff --git a/src/assets/signs/slow.png b/src/assets/signs/slow.png new file mode 100755 index 00000000..d600cbd3 Binary files /dev/null and b/src/assets/signs/slow.png differ diff --git a/src/assets/signs/stop.png b/src/assets/signs/stop.png new file mode 100755 index 00000000..1f1e2bf7 Binary files /dev/null and b/src/assets/signs/stop.png differ diff --git a/src/assets/signs/two_way_traffic.png b/src/assets/signs/two_way_traffic.png new file mode 100755 index 00000000..a6f2a6cc Binary files /dev/null and b/src/assets/signs/two_way_traffic.png differ diff --git a/src/assets/splash_1.jpeg b/src/assets/splash_1.jpeg new file mode 100644 index 00000000..f08ebc5c Binary files /dev/null and b/src/assets/splash_1.jpeg differ diff --git a/src/assets/styles/app.scss b/src/assets/styles/app.scss new file mode 100644 index 00000000..d9f43062 --- /dev/null +++ b/src/assets/styles/app.scss @@ -0,0 +1,37 @@ + +// Third pirty style files +// Sidebar Scroll +@import '~perfect-scrollbar/css/perfect-scrollbar.css'; + +// Flag icon +// @import '~flag-icon-css/css/flag-icon.min.css'; + +// Rich Text Editor +@import '~quill/dist/quill.core.css'; +@import '~quill/dist/quill.snow.css'; + +// Drag and Drop +@import '~dragula/dist/dragula.css'; + +// Calendar +@import '~angular-calendar/css/angular-calendar.css'; + +// User tour +@import '~hopscotch/dist/css/hopscotch.min.css'; + +// Page loader Topbar +@import '../../vendor/pace/pace-theme-min.css'; + +// Main Variables File +@import "scss/variables"; + +// Open _themes.scss and remove unnecessary theme files +@import "scss/themes"; + +// Open _main.scss and keep files which are required for your current layout. +@import "scss/main"; + +// Open _views.scss and remove unnecessary view related files +@import "scss/views"; + + diff --git a/src/assets/styles/scss/_animations.scss b/src/assets/styles/scss/_animations.scss new file mode 100644 index 00000000..65d8d603 --- /dev/null +++ b/src/assets/styles/scss/_animations.scss @@ -0,0 +1,12 @@ +/*! + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +@keyframes spin { + 0% {transform: rotate(0)} + 100% {transform: rotate(360deg)} +} + +.spin { + animation: spin 2s infinite linear; +} \ No newline at end of file diff --git a/src/assets/styles/scss/_colors.scss b/src/assets/styles/scss/_colors.scss new file mode 100644 index 00000000..105a791a --- /dev/null +++ b/src/assets/styles/scss/_colors.scss @@ -0,0 +1,101 @@ +/*! + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +$solid-colors: ( + white: ( + color: #fff, + alt: #eee, + foreground: rgba(0, 0, 0, 0.76) + ), + black: ( + color: #1e2129, + alt: #2d323d, + foreground: #ffffff + ), + gray: ( + color: #9E9E9E, + alt: #9E9E9E, + foreground: #ffffff + ), + dark-gray: ( + color: #424242, + alt: #303030, + foreground: #ffffff + ), + slate-gray: ( + color: #607D8B, + alt: #37474F, + foreground: #ffffff + ), + dark-purple: ( + color: #322740, + alt: #322740, + foreground: #ffffff + ), + + dark-blue: ( + color: #10174c, + alt: #151c4e, + foreground: #ffffff + ), + blue: ( + color: #03A9F4, + alt: #039BE5, + foreground: #ffffff + ), + indigo: ( + color: #3F51B5, + alt: #5C6BC0, + foreground: #ffffff + ), + yellow: ( + color: #fcc02e, + alt: #f9c23b, + foreground: rgba(0, 0, 0, 0.87) + ), + green: ( + color: #4CAF50, + alt: #66BB6A, + foreground: rgba(0, 0, 0, 0.87) + ), + red: ( + color: #F44336, + alt: #FF5252, + foreground: #ffffff + ), + orange: ( + color: #FF5722, + alt: #FF6E40, + foreground: #ffffff + ), + pink: ( + color: #e91f63, + alt: #e82c6c, + foreground: #ffffff + ), +); + +// Gradient colors +$gradient-colors: ( + gradient-purple-indigo: ( + "start-color": #663399, + "end-color": #33214b, + "foreground": #ffffff + ), + gradient-black-blue: ( + "start-color": #004e92, + "end-color": #000428, + "foreground": #ffffff + ), + gradient-black-gray: ( + "start-color": #404040, + "end-color": #000000, + "foreground": #ffffff + ), + gradient-steel-gray: ( + "start-color": #616d86, + "end-color": #1f1c2c, + "foreground": #ffffff + ) +); diff --git a/src/assets/styles/scss/_components.scss b/src/assets/styles/scss/_components.scss new file mode 100644 index 00000000..e754cd9d --- /dev/null +++ b/src/assets/styles/scss/_components.scss @@ -0,0 +1,8 @@ +/*! + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +@import "components/button"; +@import "components/mat-table"; +@import "components/card"; +@import "components/others"; \ No newline at end of file diff --git a/src/assets/styles/scss/_main.scss b/src/assets/styles/scss/_main.scss new file mode 100644 index 00000000..1d0ae787 --- /dev/null +++ b/src/assets/styles/scss/_main.scss @@ -0,0 +1,34 @@ +/*! + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +/* +* REQUIRED STYLES +*/ +@import "mixins"; +@import "colors"; +@import "palette"; +@import "utilities"; + +@import "animations"; +@import "main/scaffolding"; +@import "main/typography"; +@import "main/layout"; +@import "main/header"; +@import "main/sidenav"; +@import "components"; + + +/* +* ALTERNATIVE STYLES +*/ +@import "main/breadcrumb"; // REQUIRED ONLY FOR BREADCRUMBS +@import "main/notifications"; // REQUIRED ONLY FOR NOTIFICATIONS + +@import "main/sidebar"; // REQUIRED ONLY FOR SIDE NAVIGATION LAYOUT +@import "main/header-side"; // REQUIRED ONLY FOR SIDE NAVIGATION LAYOUT + +@import "main/topnav"; // REQUIRED ONLY FOR TOP NAVIGATION LAYOUT +@import "main/header-top"; // REQUIRED ONLY FOR TOP NAVIGATION LAYOUT + + diff --git a/src/assets/styles/scss/_mixins.scss b/src/assets/styles/scss/_mixins.scss new file mode 100644 index 00000000..42110e78 --- /dev/null +++ b/src/assets/styles/scss/_mixins.scss @@ -0,0 +1,5 @@ +/*! + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +@import 'mixins/gradients'; diff --git a/src/assets/styles/scss/_palette.scss b/src/assets/styles/scss/_palette.scss new file mode 100644 index 00000000..fcd72b9d --- /dev/null +++ b/src/assets/styles/scss/_palette.scss @@ -0,0 +1,81 @@ +/*! + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + + @each $color_name, $scolor in $solid-colors { + // $alt: map-get($scolor, alt); + $color: map-get($scolor, color); + $foreground: map-get($scolor, foreground); + + .sidebar-#{$color_name} { + .navigation-hold { + background: $color !important; + + a, + .sidenav-hold .sidenav li.open a .mat-icon:not(.menu-caret), + .sidenav li.open > div > a > span:not(.menuitem-badge), + .sidenav li.open > a > span, + .icon-menu .mat-raised-button, + .app-user .app-user-name, + .branding .app-logo-text { + color: $foreground !important; + } + .app-user .app-user-photo, + .sidenav-hold .sidenav li.open a .mat-icon:not(.menu-caret) { + border-color: $foreground !important; + } + .icon-menu .mat-raised-button, .branding { + background: $color !important; + } + .app-user { + background: lighten($color, 5) !important; + } + li.open { + background: lighten($color, 2) !important; + > a { + background: lighten($color, 4) !important; + } + } + .text-muted { + color: desaturate($foreground, 40) !important; + } + } + } + .topbar-#{$color_name} { + .topbar.mat-toolbar { + background: $color !important; + color: $foreground !important; + } + } + + .ngx-datatable.header-#{$color_name} { + .datatable-header { + background: $color; + .datatable-header-cell { + color: $foreground; + } + } + } + + .#{$color_name} { + background: $color !important; + color: $foreground !important; + .mat-table { + background: transparent !important; + } + .ngx-datatable.material *, + .mat-table, + .mat-cell, + .mat-header-cell { + color: $foreground !important; + } + .ngx-datatable.material:not(.cell-selection) .datatable-body-row:hover, + .ngx-datatable.material:not(.cell-selection) .datatable-body-row:hover .datatable-row-group { + background: lighten($color, 5) + } + + } + .text-#{$color_name} { + color: $color; + } + } \ No newline at end of file diff --git a/src/assets/styles/scss/_themes.scss b/src/assets/styles/scss/_themes.scss new file mode 100644 index 00000000..840132fb --- /dev/null +++ b/src/assets/styles/scss/_themes.scss @@ -0,0 +1,15 @@ +/*! + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +@import '~@angular/material/theming'; +@include mat-core(); + +// Keep atleast one file +//@import "themes/egret-navy"; +//@import "themes/egret-blue"; +//@import "themes/egret-dark-purple"; +@import "themes/egret-dark-pink"; + +$background: map-get($egret-theme, background); +$foreground: map-get($egret-theme, foreground); diff --git a/src/assets/styles/scss/_utilities.scss b/src/assets/styles/scss/_utilities.scss new file mode 100644 index 00000000..682c98fa --- /dev/null +++ b/src/assets/styles/scss/_utilities.scss @@ -0,0 +1,7 @@ +/*! + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +@import "utilities/avatar"; +@import "utilities/border"; +@import "utilities/others"; \ No newline at end of file diff --git a/src/assets/styles/scss/_variables.scss b/src/assets/styles/scss/_variables.scss new file mode 100644 index 00000000..556d4330 --- /dev/null +++ b/src/assets/styles/scss/_variables.scss @@ -0,0 +1,34 @@ +/*! + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +// General +$transitionDuration: 300ms !default; +$gutter: 1rem !default; +$transitionTiming: cubic-bezier(.35,0,.25,1); +$sidebarTrnTime: .3s; + +// Widths +$sidebar-width: 15rem !default; +$compact-sidebar-width: 48px !default; +$compact-big-sidebar-width: 150px !default; + +// Typography +$font-family-base: Roboto, "Helvetica Neue", sans-serif !default; +$font-size-base: .875rem !default; +$font-weight-base: 400 !default; +$font-weight-medium: 500 !default; +$font-weight-bold: 600 !default; + +$font-size-h1: 2.5rem !default; +$font-size-h2: 2rem !default; +$font-size-h3: 1.75rem !default; +$font-size-h4: 1.5rem !default; +$font-size-h5: 1.25rem !default; +$font-size-h6: 1rem !default; + +$headings-margin-bottom: (1rem / 2) !default; +$headings-font-family: inherit !default; +$headings-font-weight: 400 !default; +$headings-line-height: 1.1 !default; +$headings-color: inherit !default; \ No newline at end of file diff --git a/src/assets/styles/scss/_views.scss b/src/assets/styles/scss/_views.scss new file mode 100644 index 00000000..518b16ca --- /dev/null +++ b/src/assets/styles/scss/_views.scss @@ -0,0 +1,23 @@ +/*! + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +// Remove imports which are not needed for your project +@import "views/sessions"; +@import "views/inbox"; +@import "views/calendar"; +@import "views/chats"; +@import "views/gallery"; +@import "views/pricings"; +@import "views/users"; +@import "views/profile"; +@import "views/shop"; +@import "views/home"; + +/* + Only Required if you want to use Angular Landing + (https://themeforest.net/item/angular-landing-material-design-angular-app-landing-page/21198258) +*/ +// @import "views/landing"; + +@import "views/popover"; \ No newline at end of file diff --git a/src/assets/styles/scss/components/_button.scss b/src/assets/styles/scss/components/_button.scss new file mode 100644 index 00000000..894f15a6 --- /dev/null +++ b/src/assets/styles/scss/components/_button.scss @@ -0,0 +1,26 @@ +/*! + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +/*-------------- Loading buttons --------------*/ +.button-loading { + .mat-button-wrapper { + display: flex; + align-items: center; + } +} +.btn-spinner { + width: 1em; + height: 1em; + background: transparent; + border-radius: 50%; + margin: 0 16px 0 0; + border: 2px solid transparent; + animation: btn-glow 1s ease infinite;; +} + +@keyframes btn-glow { + 0% { box-shadow: 0 0 0 .4em darken(#777676, 10%), 0 0 0 .1em darken(#777676, 10%); transform:rotate(360deg); } + 50% { border-top-color: #777676;} + 100% { box-shadow: 0 0 0 .4em darken(#777676, 10%), 0 0 0 3.6em transparent; } +} diff --git a/src/assets/styles/scss/components/_card.scss b/src/assets/styles/scss/components/_card.scss new file mode 100644 index 00000000..be67f05e --- /dev/null +++ b/src/assets/styles/scss/components/_card.scss @@ -0,0 +1,74 @@ +/*! + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +.mat-card { + margin: .5rem; + overflow: hidden; + + &.rounded-circle { + border-radius: 200px !important; + } +} +.mat-card.default { + padding: 0; +} +.mat-card.default .mat-card-title { + padding-top: 1rem; + padding-left: 1rem; + padding-right: 1rem; + line-height: 1; + font-weight: 400; +} +.mat-card.default>:first-child { + border-radius: 2px 2px 0 0; +} +.mat-card.default .mat-card-subtitle:first-child { + padding-top: 1rem; +} +.mat-card.default .mat-card-subtitle { + padding-left: 1rem; + padding-right: 1rem; + line-height: 1; +} +.mat-card.default .mat-card-content { + padding: 1rem; + margin-bottom: 0; + position: relative; +} +.mat-card.default .mat-card-actions, +.mat-card.default .mat-card-actions:last-child { + padding: .5rem; + margin: 0; +} +.mat-card.default>:last-child { + border-radius: 0 0 2px 2px; +} + +.mat-card .mat-card-title .mat-divider, .mat-divider.full-width { + margin-left: -24px; + margin-right: -24px; +} + +.mat-card.p-0 .mat-card-title .card-title-text { + padding: 1rem 1.5rem; +} +.mat-card.p-0 .mat-card-title .card-title-text .card-control { + height: 24px; + width: 24px; + line-height: 24px; +} +.mat-card.p-0 .mat-card-title .card-title-text .mat-card-subtitle { + margin: 0; +} +.mat-card.p-0 .mat-card-title .mat-divider { + margin-left: 0; + margin-right: 0; + border-top-color: rgba(0, 0, 0, 0.04); +} +.mat-card.p-0 .mat-card-image { + margin: 0 0 1rem !important; +} +.mat-card.p-0 .mat-card-content { + padding: 0 1.5rem 1.5rem; +} \ No newline at end of file diff --git a/src/assets/styles/scss/components/_mat-table.scss b/src/assets/styles/scss/components/_mat-table.scss new file mode 100644 index 00000000..7df2b1c0 --- /dev/null +++ b/src/assets/styles/scss/components/_mat-table.scss @@ -0,0 +1,67 @@ +/*! + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +@media screen and (max-width: 960px) { + .mat-table { + border: 0; + vertical-align: middle; + padding: 0 24px; + } + + .mat-table caption { + font-size: 1em; + } + + .mat-table .mat-header-cell { + + border: 10px solid; + clip: rect(0 0 0 0); + height: 1px; + margin: -1px; + padding: 0; + position: absolute; + width: 1px; + } + + .mat-table .mat-row { + border-bottom: 5px solid #ddd; + display: block; + } + /* + .mat-table .mat-row:nth-child(even) {background: #CCC} + .mat-table .mat-row:nth-child(odd) {background: #FFF} + */ + mat-cell:first-of-type, mat-cell:last-of-type, + mat-footer-cell:last-of-type, mat-header-cell:last-of-type { + padding: 0; + } + + .mat-table .mat-cell { + border-bottom: 1px solid #ddd; + display: block; + font-size: 1em; + text-align: right; + font-weight: bold; + height:30px; + margin-bottom: 4%; + } + .mat-table .mat-cell:before { + /* + * aria-label has no advantage, it won't be read inside a table + content: attr(aria-label); + */ + content: attr(data-label); + float: left; + text-transform: uppercase; + font-weight: normal; + + font-size: .85em; + } + .mat-table .mat-cell:last-child { + border-bottom: 0; + } + .mat-table .mat-cell:first-child { + margin-top: 4%; + } +} \ No newline at end of file diff --git a/src/assets/styles/scss/components/_others.scss b/src/assets/styles/scss/components/_others.scss new file mode 100644 index 00000000..8188a2fc --- /dev/null +++ b/src/assets/styles/scss/components/_others.scss @@ -0,0 +1,7 @@ +/*! + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +.mat-drawer-container { + // background: transparent !important; +} \ No newline at end of file diff --git a/src/assets/styles/scss/main/_breadcrumb.scss b/src/assets/styles/scss/main/_breadcrumb.scss new file mode 100644 index 00000000..938585f5 --- /dev/null +++ b/src/assets/styles/scss/main/_breadcrumb.scss @@ -0,0 +1,79 @@ +/*! + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +.breadcrumb { + display: block; + margin: 0; + padding: 0; + li { + list-style: none; + float: left; + &:not(:first-child):before { + content: "/\A0"; + padding: 0 8px; + } + } +} + +.breadcrumb-bar { + position: relative; + width: 100%; + .breadcrumb { + padding: 0 .333rem; + overflow: hidden; + li { + line-height: 40px; + &:not(:first-child):before { + padding: 0 8px; + } + a { + font-weight: 400; + font-size: 1rem; + } + } + } +} +.breadcrumb-title { + display: flex; + align-items: center; + padding: 0.5rem .333rem; + line-height: 40px; + .bc-title { + font-size: $font-size-h5; + margin: 0; + line-height: 40px; + } + .breadcrumb { + position: relative; + margin-left: 24px; + &::before { + position: absolute; + content: ""; + height: 24px; + width: 2px; + background: #6a6a6a; + left: -12px; + top: 8px; + } + } +} + +[dir=rtl] { + .breadcrumb-bar, + .breadcrumb-title { + .breadcrumb li { + float: right; + } + } + .breadcrumb-title { + .breadcrumb { + margin-left: 0; + margin-right: 24px; + &::before { + left: auto; + right: -12px; + } + } + } +} \ No newline at end of file diff --git a/src/assets/styles/scss/main/_header-side.scss b/src/assets/styles/scss/main/_header-side.scss new file mode 100644 index 00000000..6411b73f --- /dev/null +++ b/src/assets/styles/scss/main/_header-side.scss @@ -0,0 +1,58 @@ +/*! + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +.topbar { + &.mat-toolbar { + position: relative; + box-shadow: 0 3px 1px -2px rgba(0,0,0,.2), 0 2px 2px 0 rgba(0,0,0,.14), 0 1px 5px 0 rgba(0,0,0,.12); + z-index: 999; + } + .mat-select-value { + font-weight: 400; + color: #ffffff; + } +} + +.toggle-collapsed { + transform: rotate(0deg); + -webkit-transition: transform .3s cubic-bezier(.35,0,.25,1); + transition: transform .3s cubic-bezier(.35,0,.25,1); +} +.sidebar-compact .toggle-collapsed { + transform: rotate(180deg); +} + +.search-bar { + .top-search-form { + position: relative; + background: #fff; + border-radius: 40px; + margin-right: 1rem; + display: block; + max-width: 220px; + margin-left: 20px; + box-shadow: inset 0 0 2px 2px rgba(136, 136, 136, 0.2); + + .material-icons { + position: absolute; + top: 50%; + left: 10px; + margin-top: -12px; + color: rgba(0,0,0,.87); + } + input { + font-size: 1rem; + padding: .6rem .75rem; + z-index: 2; + cursor: text; + text-indent: 30px; + border: none; + background: transparent; + width: 100%; + outline: 0; + } + } + +} + diff --git a/src/assets/styles/scss/main/_header-top.scss b/src/assets/styles/scss/main/_header-top.scss new file mode 100644 index 00000000..ac38e0ab --- /dev/null +++ b/src/assets/styles/scss/main/_header-top.scss @@ -0,0 +1,53 @@ +/*! + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +.header-topnav { + margin:0; + padding: 0; + background-color: #ffffff; + // box-shadow: 0 0 4px rgba(0,0,0,.14), 0 4px 8px rgba(0,0,0,.28); + position: relative; + z-index: 999; + .container { + padding: 0; + } + .topbar-branding { + float: left; + height: 48px; + padding: 8px; + margin: 0 8px; + img { + height: 100%; + width: auto; + } + } + .topnav { + // margin-left: -19px; + display: flex; + align-items: center; + } + .header-topnav-right { + float: right; + height: 48px; + display: flex; + align-items: center; + padding-right: .67rem; + } +} + +@media (max-width: 959px) { + .header-topnav-right { + position: absolute; + right: 6px; + top: 0; + } +} + +[dir=rtl] { + .header-topnav { + .topnav { + flex-direction: row-reverse; + } + } +} \ No newline at end of file diff --git a/src/assets/styles/scss/main/_header.scss b/src/assets/styles/scss/main/_header.scss new file mode 100644 index 00000000..24458124 --- /dev/null +++ b/src/assets/styles/scss/main/_header.scss @@ -0,0 +1,54 @@ +/*! + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +.notification-number { + position: absolute; + top: 0; + left: 50%; + width: 20px; + height: 20px; + font-size: 12px; + font-weight: 700; + line-height: 20px; + color: #fff; + text-align: center; + border-radius: 50%; +} + +#langToggle .mat-select-trigger { + width: 80px; + min-width: 80px; +} +.theme-list { + .mat-menu-item { + width: 48px; + height: 48px; + padding: 5px; + .egret-swatch { + height: 100%; + width: 100%; + border-radius: 50%; + } + .active-icon { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + margin: auto !important; + color: #ffffff; + } + } +} + +.topbar-button-right { + margin: 0 .5rem !important; +} + +[dir=rtl] { +// .topbar-button-right { +// margin-right: 1rem; +// margin-left: 0 !important; +// } +} \ No newline at end of file diff --git a/src/assets/styles/scss/main/_layout.scss b/src/assets/styles/scss/main/_layout.scss new file mode 100644 index 00000000..e9aa693a --- /dev/null +++ b/src/assets/styles/scss/main/_layout.scss @@ -0,0 +1,205 @@ +/*! + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +.app-admin-container { + max-width: 100%; + height: 100vh; + + .rightside-content-hold { + padding: 0.333333rem; + overflow-x: hidden; + position: relative; + min-height: 450px; + } +} + +.sidebar-panel { + position: fixed; + top: 0; + left: 0; + min-height: 100vh; + z-index: 10001; + width: $sidebar-width; + box-shadow: 0 3px 1px -2px rgba(0, 0, 0, 0.2), 0 2px 2px 0 rgba(0, 0, 0, 0.14), + 0 1px 5px 0 rgba(0, 0, 0, 0.12); + -webkit-transition: all $sidebarTrnTime $transitionTiming; + transition: all $sidebarTrnTime $transitionTiming; + overflow: hidden; + .sidebar-hold { + width: $sidebar-width; + } + .navigation-hold { + position: absolute; + height: 100%; + width: 100%; + margin-top: 64px; + background: rgba(255, 255, 255, 0.95); + left: 0; + } + ul { + margin: 0; + padding: 0; + list-style: none; + } +} + +.main-content-wrap { + position: relative; + float: right; + height: 100vh; + transition: width $sidebarTrnTime $transitionTiming; + &.ps > .ps__rail-y { + z-index: 9999; + } +} + +.sidebar-backdrop { + top: 0; + left: 0; + right: 0; + bottom: 0; + position: fixed; + display: block; + z-index: 10000; + visibility: hidden; + opacity: 0; + background-color: rgba(0, 0, 0, 0.6); + transition: all $sidebarTrnTime $transitionTiming; + &.visible { + visibility: visible; + opacity: 1; + } +} + +// -------- +.app-admin-container.sidebar-full:not(.navigation-top, .compact-toggle-active) { + .main-content-wrap { + width: calc(100% - #{$sidebar-width}); + } + .sidebar-panel { + width: $sidebar-width; + overflow: hidden; + } +} +.app-admin-container.compact-toggle-active { + .main-content-wrap { + width: calc(100% - #{$compact-sidebar-width}); + } +} + +.app-admin-container.sidebar-compact { + .sidebar-panel, + .sidebar-hold { + width: $compact-sidebar-width; + } +} + +.app-admin-container.sidebar-compact-big { + .main-content-wrap { + width: calc(100% - #{$compact-big-sidebar-width}); + } + .sidebar-panel { + width: $compact-big-sidebar-width; + .branding { + width: $compact-big-sidebar-width; + .app-logo { + margin: auto; + } + } + .navigation-hold { + overflow: visible !important; + } + } +} + +.app-admin-container.sidebar-closed { + .sidebar-panel { + overflow: hidden; + left: -$sidebar-width; + .branding { + left: -$sidebar-width; + .app-logo-text { + // display: none; + } + } + } + .main-content-wrap { + width: 100%; + } +} + +.fixed-topbar { + .rightside-content-hold { + overflow: auto; + position: absolute; + bottom: 0; + left: 0; + right: 0; + top: 64px; + } +} + +.layout-intransition { + .branding { + display: none !important; + } + + .app-user-controls, + .app-user-name { + opacity: 0 !important; + } +} + +[dir="rtl"] { + &.app-admin-container.sidebar-closed { + .sidebar-panel { + right: -$sidebar-width; + } + .branding { + left: auto !important; + right: -$sidebar-width; + } + } + .main-content-wrap { + float: left; + } + .sidebar-panel { + right: 0; + left: auto !important; + } +} + +.app-admin-container.sidebar-full.navigation-top { + .sidebar-panel { + left: 0; + } +} +.app-admin-container.navigation-top { + .main-content-wrap { + float: none; + height: calc(100vh - 48px); + width: 100%; + } + .rightside-content-hold { + max-width: 1400px; + margin: 0 auto; + padding-left: 15px; + padding-right: 15px; + } + .sidebar-panel { + overflow: hidden; + left: -$sidebar-width; + .navigation-hold { + margin-top: 48px; + } + } +} + +@media (max-width: 959px) { + .app-admin-container.sidebar-full { + .main-content-wrap { + width: 100% !important; + } + } +} diff --git a/src/assets/styles/scss/main/_notifications.scss b/src/assets/styles/scss/main/_notifications.scss new file mode 100644 index 00000000..365e1c0e --- /dev/null +++ b/src/assets/styles/scss/main/_notifications.scss @@ -0,0 +1,19 @@ +/*! + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +.notification-list { + min-width: 240px; +} +.notification-list .notific-item a { + outline: 0; + width: 100%; + display: flex; + flex-direction: column; +} +.notification-list .notific-item .message { + font-size: .875rem !important; +} +.notification-list .notific-item .time { + font-size: .75rem !important; +} \ No newline at end of file diff --git a/src/assets/styles/scss/main/_scaffolding.scss b/src/assets/styles/scss/main/_scaffolding.scss new file mode 100644 index 00000000..dab77c9b --- /dev/null +++ b/src/assets/styles/scss/main/_scaffolding.scss @@ -0,0 +1,810 @@ +/*! + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +html { + font-size: 16px; +} + +html:not(.landing), +body:not(.landing) { + width: 100%; + height: 100%; + position: relative; + overflow: hidden; + -webkit-tap-highlight-color: rgba(0,0,0,0); + -webkit-touch-callout: none; + min-height: 100%; + -webkit-text-size-adjust: 100%; + -ms-text-size-adjust: 100%; + text-size-adjust: 100%; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +body { + margin: 0; + padding: 0; + font-weight: $font-weight-base; + font-size: $font-size-base; + color: rgba(0, 0, 0, 0.87); + line-height: 1.5; + font-family: $font-family-base; +} + +[tabindex='-1']:focus { + outline: none; +} + +select, +button, +textarea, +input { + vertical-align: baseline; +} +div { + box-sizing: border-box; +} +html, body { + &[dir=rtl], &[dir=ltr] { + unicode-bidi: embed + } +} +bdo[dir=rtl] { + direction: rtl; + unicode-bidi: bidi-override; +} +bdo[dir=ltr] { + direction: ltr; + unicode-bidi: bidi-override; +} +.mat-card { + font-size: .875rem; +} +img:not(.mat-card-image) { + max-width: 100%; +} +a, a:focus, a:hover { + text-decoration: none; +} +a { + color: inherit; +} +p { + margin: 0 0 16px; +} +.h1, +.h2, +.h3, +.h4, +.h5, +.h6, +h1, +h2, +h3, +h4, +h5, +h6 { + margin-bottom: .5rem; + font-weight: 400; + line-height: 1.1; + color: inherit; +} +.h1, +h1 { + font-size: 2rem; +} +.h2, +h2 { + font-size: 1.75rem; +} +.h3, +h3 { + font-size: 1.5rem; +} +.h4, +h4 { + font-size: 1.25rem; +} +.h5, +h5 { + font-size: 1rem; +} +.h6, +h6 { + font-size: .875rem; +} +code { + padding: 8px; + background: rgba(0, 0, 0, .08); +} +/*---- Common -----*/ +.container { + max-width: 1400px; + margin: 0 auto; + padding: 0 15px; +} +.bg-none { + background: transparent !important; +} +.bg-white { + background: #ffffff !important; +} +.m-0 { + margin: 0 !important; +} +.m-1 { + margin: 1rem !important; +} +.mt-1 { + margin-top: 1rem !important; +} +.mt-05 { + margin-top: .5rem !important; +} +.mt-0 { + margin-top: 0 !important; +} +.mr-1 { + margin-right: 1rem !important; +} +.mr-05 { + margin-right: .5rem !important; +} +.mr-0 { + margin-right: 0 !important; +} +.mb-1 { + margin-bottom: 1rem !important; +} +.mb-0 { + margin-bottom: 0 !important; +} +.ml-1 { + margin-left: 1rem !important; +} +.ml-05 { + margin-left: .5rem !important; +} +.ml-0 { + margin-left: 0 !important; +} +.mb-05 { + margin-bottom: .5rem !important; +} +.m-333 { + margin: .333333rem !important; +} +.margin-333 { + margin: .333333rem !important; +} +.pt-1 { + padding-top: 1rem !important; +} +.pt-0 { + padding-top: 0 !important; +} +.pr-1 { + padding-right: 1rem !important; +} +.pr-0 { + padding-right: 0 !important; +} +.pb-1 { + padding-bottom: 1rem !important; +} +.pb-0 { + padding-bottom: 0 !important; +} +.pl-1 { + padding-left: 1rem !important; +} +.pl-0 { + padding-left: 0 !important; +} +.p-0 { + padding: 0 !important; +} +.p-1 { + padding: 1rem !important; +} +.p-05 { + padding: .5rem !important; +} + +.height-100 { + min-height: 100vh; +} +.fix { + position: relative; + overflow: hidden; +} +.fix-elm::after { + display: table; + width: 100%; + content: ""; +} +.mat-box-shadow { + box-shadow: 0 3px 1px -2px rgba(0,0,0,.2), 0 2px 2px 0 rgba(0,0,0,.14), 0 1px 5px 0 rgba(0,0,0,.12); +} + +.light-gray { + background: rgba(0, 0, 0, .024); +} +.light-mat-gray { + background: rgba(0, 0, 0, .08); +} +.mat-blue { + background: #247ba0; + color: #FEFEFE; +} +.mat-red { + background-color: #f44336!important; + color: #fff!important; +} +.mat-indigo { + background-color: #3f51b5 !important; + color: #fff!important; +} +.mat-brown { + background-color: #785548 !important; + color: #FEFEFE; +} +.mat-teal { + background-color: #009688!important; + color: #fff!important; +} +.mat-purple { + background-color: #9c27b0!important; + color: hsla(0,0%,100%,.87)!important; +} +.fz-1 { + font-size: 1rem !important; +} +.fz-2 { + font-size: 2rem !important; +} +.text-center { + text-align: center; +} +.text-right { + text-align: right; +} +.font-light { + font-weight: 300 !important; +} +.font-normal { + font-weight: normal !important; +} +.fw-300 { + font-weight: 300 !important; +} +.fw-400 { + font-weight: 400 !important; +} +.fw-500 { + font-weight: 500 !important; +} +.fw-600 { + font-weight: 600 !important; +} +.fw-700 { + font-weight: 700 !important; +} +.fw-800 { + font-weight: 800 !important; +} +.fw-900 { + font-weight: 900 !important; +} + +.text-muted { + color: rgba(0, 0, 0, .54) !important; +} +.text-muted-white { + color: rgba(255, 255, 255, .54) !important; +} +.text-gray { + color: rgba(0, 0, 0, .7) !important; +} +.text-sm { + font-size: .813rem; +} +.list-item-active { + border-left: 3px solid; +} +.material-icons.icon-sm { + font-size: 18px !important; + line-height: 18px !important; + height: 18px; + width: 18px; +} +.material-icons.icon-xs { + font-size: 13px !important; + line-height: 13px; + height: 13px; + width: 13px; +} + +.mat-button, +.mat-raised-button { + font-weight: 400 !important; +} +[mat-lg-button] { + padding: 0 32px !important; + font-size: 18px; + line-height: 56px !important; +} +.mat-icon-button[mat-sm-button] { + height: 24px; + width: 24px; + line-height: 24px; +} + +.mat-icon-button[mat-xs-button] { + height: 20px; + width: 20px; + line-height: 20px; +} +.mat-icon-button[mat-xs-button] .mat-icon { + font-size: 16px; + line-height: 20px !important; + height: 20px; + width: 20px; +} +.mat-chip[mat-sm-chip] { + padding: 4px 6px 4px 6px !important; + border-radius: 4px !important; + font-size: 12px !important; + line-height: 12px !important; + max-height: 20px; + box-sizing: border-box; +} +.mat-icon-button.img-button img { + width: 32px; + height: 32px; + border-radius: 50%; +} +.compact-list .mat-list-item { + line-height: 1.1; + transition: all .3s cubic-bezier(0.075, 0.82, 0.165, 1); +} +.compact-list .mat-list-item:hover { + background: rgba(0, 0, 0, .08); + cursor: pointer; +} +.list-tasktype .tasktype-item { + padding: 12px; +} +.list-tasktype .tasktype-item:hover { + background: rgba(0, 0, 0, .08); +} +.list-tasktype .tasktype-item .tasktype-action { + visibility: hidden; +} +.list-tasktype .tasktype-item:hover .tasktype-action { + visibility: visible; +} +.doughnut-grid { + border-radius: 2px; + overflow: hidden; +} +.doughnut-grid .doughnut-grid-item { + padding: 1rem; +} +.doughnut-grid .doughnut-grid-item p { + margin: 0; +} +.doughnut-grid .doughnut-grid-item .chart { + margin: 0 0 8px; +} + +.logo-group { + display: flex; + align-items: center; + justify-content: center; + text-align: center; +} +.toolbar-avatar { + display: inline-block; + position: relative; + width: 40px; + height: 40px; + line-height: 24px; +} +.toolbar-avatar > img { + width: 40px !important; + border-radius: 50%; +} +.toolbar-avatar.md, +.toolbar-avatar.md > img { + width: 48px !important; + height: 48px; +} +.toolbar-avatar > .status-dot { + position: absolute; + width: 10px; + height: 10px; + border-radius: 50%; + top: 25px; + right: -2px; + border: 2px solid #ffffff; + background: #ccc; +} +.full-width { + width: 100% !important; +} +.dnd-item { + cursor: move; + cursor: grab; + cursor: -webkit-grab; +} +.icon-circle { + color: rgb(255, 255, 255); + font-size: 1.2rem; + text-align: center; + line-height: 1.6rem; + border-radius: 50%; +} +.mat-sidenav .mat-list-item:hover { + background: rgba(0, 0, 0, .035) !important; +} +.mat-chip { + position: relative; + overflow: hidden; +} +.text-small { + font-size: .813rem; +} +.text-small .mat-icon { + font-size: 1rem; + vertical-align: sub; + margin: 0 2px; +} +.mat-card-title { + font-size: 1rem !important; +} +.icon-chip { + font-size: 11px !important; + padding: 4px 8px !important; + display: flex !important; + flex-direction: row; + align-items: center; + justify-content: center; +} +.icon-chip .mat-icon { + font-size: 16px; + height: 16px; + width: 16px; + margin-right: 4px; +} +.mat-chip:not(.mat-basic-chip) { + display: inline-block; + padding: 8px 12px 8px 12px; + border-radius: 24px; + font-size: 13px; + line-height: 16px; +} +.ql-container .ql-editor { + min-height: 200px; +} +.chart { + display: block; + width: 100%; +} +.form-error-msg { + color: #f44336; + display: block; + padding: 5px 0; +} +.accordion-handle { + cursor: pointer; +} +.app-accordion { + max-height: 200px; + transition: max-height .3s ease; +} +.app-accordion.open { + max-height: 1000px; +} +.app-accordion .accordion-content { + max-height: 0; + overflow: hidden; + transition: max-height .3s ease; +} +.app-accordion.open .accordion-content { + max-height: 800px; +} +.app-accordion.open .hidden-on-open { + display: none !important; +} +.app-accordion:not(.open) .show-on-open { + display: none !important; +} +mat-list a[mat-list-item] .mat-list-item-content, +mat-list mat-list-item .mat-list-item-content, +mat-nav-list a[mat-list-item] .mat-list-item-content, +mat-nav-list mat-list-item .mat-list-item-content, +[mat-menu-item], +.mat-option, +body .mat-select-trigger { + font-size: .875rem !important; +} +.mat-ripple { + position: relative; +} +.fileupload-drop-zone { + text-align: center; + border: 1px dashed rgba(0, 0, 0, .15); + padding: 48px; + height: 120px; +} +.default-table { + text-align: left; +} +.default-table > thead tr th { + font-weight: 400; + padding: .9rem 1.2rem; + border-bottom: 1px solid rgba(0, 0, 0, .12); +} +.default-table tbody tr td { + padding: .9rem 1.2rem; +} +.app-error { + text-align: center; + width: 320px; + max-width: 320px; + margin: 0 auto; +} +.app-error .error-icon { + height: 120px; + width: 120px; + font-size: 120px; + float: left; +} +.app-error .error-text { + float: right; + width: 200px; + max-width: 200px; +} +.app-error .error-title { + font-size: 5rem; + font-weight: 900; + margin: 0; +} +.app-error .error-subtitle { + font-size: 1.5rem; + font-weight: 300; +} +.app-error .error-actions { + width: 100%; + overflow: hidden; + min-height: 54px; + margin-top: 100px; +} + + +/*---- Loader ----*/ +.app-loader, +.view-loader { + height: 100%; + width: 100%; + position: absolute; + top: 0; + left: 0; + display: flex; + align-items: center; +} +.spinner { + width: 40px; + height: 40px; + position: relative; + margin: auto; +} +.double-bounce1, .double-bounce2 { + width: 100%; + height: 100%; + border-radius: 50%; + opacity: 0.6; + position: absolute; + top: 0; + left: 0; + -webkit-animation: sk-bounce 2.0s infinite ease-in-out; + animation: sk-bounce 2.0s infinite ease-in-out; +} +.double-bounce2 { + -webkit-animation-delay: -1.0s; + animation-delay: -1.0s; +} +.view-loader { + display: block; + padding-top: 160px; + background: rgba(255, 255, 255, .3); + z-index: 9999; +} +.view-loader * { + margin: auto; +} +@-webkit-keyframes sk-bounce { + 0%, 100% { -webkit-transform: scale(0.0) } + 50% { -webkit-transform: scale(1.0) } +} +@keyframes sk-bounce { + 0%, 100% { + transform: scale(0.0); + -webkit-transform: scale(0.0); + } + 50% { + transform: scale(1.0); + -webkit-transform: scale(1.0); + } +} + +/*---- Third pirty adjust -----*/ + +/*------- quill rich text editor ----------*/ +.p-0.mat-card-content .ql-container { + border: 0 !important; +} +.p-0.mat-card-content .ql-toolbar.ql-snow { + border: 0; + border-bottom: 1px solid rgba(0, 0, 0, .12); +} +/*--- Scroll Bar ---*/ +.ps__scrollbar-y-rail { + z-index: 999; +} +.collapsed-menu .ps__scrollbar-y-rail { + z-index: auto; +} + +/*--- Data table ---*/ +.ngx-datatable.material { + box-shadow: none !important; +} +// .datatable-body, .datatable-body-row { +// min-width: 100% !important; +// } +// datatable-scroller { +// width: 100% !important; +// } +/*------ Map ------*/ +.agm-info-window-content { + color: rgba(0, 0, 0, .87); +} +/*-------- Chart js ---------*/ +.chart { + margin-left: -10px; +} + +/*-------- Hopscotch Tour ---------*/ +div.hopscotch-bubble, +div.hopscotch-bubble .hopscotch-title, +div.hopscotch-bubble .hopscotch-content, +div.hopscotch-bubble .hopscotch-nav-button { + font-family: "Roboto",Helvetica Neue,sans-serif !important; +} +div.hopscotch-bubble { + border: 1px solid; + border-radius: 2px; +} +div.hopscotch-bubble .hopscotch-bubble-number { + border-radius: 50%; + box-sizing: border-box; + padding: 0; + font-weight: 500; + height: 32px; + width: 32px; + line-height: 32px; + background: transparent; + color: rgba(0, 0, 0, 0.87); +} +div.hopscotch-bubble .hopscotch-title { + font-size: 17px; + font-weight: 400; + color: #ffffff; + letter-spacing: .1px; +} +div.hopscotch-bubble .hopscotch-content { + margin: -5px 0 16px; +} +div.hopscotch-bubble .hopscotch-bubble-close { + background-size: 8px; + background-position: 8px 8px; + background-repeat: no-repeat; + cursor: pointer; +} +div.hopscotch-bubble .hopscotch-bubble-close:active { + outline: 0; +} +/* up arrow */ +div.hopscotch-bubble .hopscotch-bubble-arrow-container.up { + top: -17px; +} +/* right arrow */ +div.hopscotch-bubble .hopscotch-bubble-arrow-container.right { + right: -34px; +} +/* bottom arrow */ +div.hopscotch-bubble .hopscotch-bubble-arrow-container.down { + bottom: -34px; +} +/* Left Arrow */ +div.hopscotch-bubble .hopscotch-bubble-arrow-container.left { + left: -17px; +} + +div.hopscotch-bubble .hopscotch-nav-button { + border: 0; + border-radius: 2px; + font-weight: normal; + text-shadow: none !important; + padding: 0 18px; + height: 30px; + line-height: 30px; + font-size: 13px; + background-image: none !important; + box-shadow: 0 3px 1px -2px rgba(0,0,0,.2), 0 2px 2px 0 rgba(0,0,0,.14), 0 1px 5px 0 rgba(0,0,0,.12); + transform: translate3d(0,0,0); + transition: background .4s cubic-bezier(.25,.8,.25,1),box-shadow 280ms cubic-bezier(.4,0,.2,1); +} + +div.hopscotch-bubble .hopscotch-nav-button:hover, +div.hopscotch-bubble .hopscotch-nav-button:active, +div.hopscotch-bubble .hopscotch-nav-button:focus { + box-shadow: 0 5px 5px -3px rgba(0,0,0,.2), 0 8px 10px 1px rgba(0,0,0,.14), 0 3px 14px 2px rgba(0,0,0,.12);; + outline: none; +} +div.hopscotch-bubble .hopscotch-nav-button.prev, +div.hopscotch-bubble .hopscotch-nav-button.prev:hover { + background-color: #ffffff; + color: rgba(0, 0, 0, .87); +} + +.ps__rail-y { + right: 0 !important; + left: auto !important; +} + +[dir=rtl] { + .pr-1 { + padding-left: 1rem; + padding-right: 0 !important; + } + .mr-1 { + margin-right: 0 !important; + margin-left: 1rem; + } + .ps__rail-y { + right: auto !important; + left: 0 !important; + } + .ps__thumb-y { + right: auto !important; + left: 1px !important; + } + .ngx-datatable .datatable-footer .datatable-pager .pager { + float: left; + } + .user-card .user-details .mat-icon { + margin-right: 0; + margin-left: .5rem; + } +} + + + +@media (max-width: 767px) { + .mat-card-title .mat-divider { + margin-left: 0; + margin-right: 0; + } + .accordion-handle { + flex-direction: column !important; + } + .app-error .error-icon { + height: 100px; + width: 100px; + font-size: 100px; + } +} \ No newline at end of file diff --git a/src/assets/styles/scss/main/_sidebar.scss b/src/assets/styles/scss/main/_sidebar.scss new file mode 100644 index 00000000..3768b5ab --- /dev/null +++ b/src/assets/styles/scss/main/_sidebar.scss @@ -0,0 +1,137 @@ +/*! + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +.sidebar-compact-switch { + position: fixed; + top: 26px; + left: 12rem; + width: 24px; + height: 12px; + border-radius: 10px; + border: 1px solid #666; + cursor: pointer; + z-index: 999999; + opacity: 1; + transition: left 0.3s, opacity 0.1s; + span { + position: absolute; + left: 1px; + top: 1px; + display: inline-block; + width: 8px; + height: 8px; + border-radius: 50%; + background: #666; + transition: all $sidebarTrnTime $transitionTiming; + } + &.active { + background: #444; + span { + left: 13px; + background: #ffffff; + } + } +} + +.branding { + display: flex; + align-items: center; + position: fixed; + padding: 0 16px; + width: $sidebar-width; + height: 64px; + line-height: 64px; + top: 0; + left: 0; + z-index: 999; + color: #444; + background: #ffffff; + transition: all $sidebarTrnTime $transitionTiming; + overflow: hidden; + .app-logo { + width: auto; + height: 22px; + margin-right: 8px; + margin-bottom: 2px; + } + .app-logo-text { + width: auto; + font-size: 20px; + font-weight: 400; + } +} + +.app-user { + text-align: center; + width: 100%; + padding: 1rem 0; + .app-user-photo { + width: 72px; + margin: 0 auto 8px; + height: 72px; + box-sizing: border-box; + padding: 4px; + border-radius: 50%; + } + .app-user-controls { + .mat-icon-button { + margin: 0 2px; + } + } + img { + width: 100%; + height: 100%; + border-radius: 50%; + } + .app-user-name { + display: block; + font-size: 0.875rem; + font-weight: 300; + color: rgba(0, 0, 0, 0.96); + margin-left: -2px; + } +} + +.app-admin-container.sidebar-compact { + .sidebar-compact-switch { + opacity: 0; + left: -2rem; + } + .branding { + width: $compact-sidebar-width; + padding: 0; + .app-logo { + margin: auto; + } + } + .app-user { + padding: .5rem 0; + .app-user-photo { + width: 40px; + height: 40px; + margin-bottom: 0; + } + } + .app-user-name, + .app-user-controls, + .app-logo-text { + display: none !important; + } +} + +[dir="rtl"] { + .sidebar-compact-switch { + left: auto; + right: 12rem; + } + .branding { + top: 0; + left: auto !important; + right: 0; + .app-logo { + margin-right: 0; + margin-left: 8px; + } + } +} diff --git a/src/assets/styles/scss/main/_sidenav.scss b/src/assets/styles/scss/main/_sidenav.scss new file mode 100644 index 00000000..b5688f9a --- /dev/null +++ b/src/assets/styles/scss/main/_sidenav.scss @@ -0,0 +1,299 @@ +/*! + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +.sidenav-hold { + .menuitem-badge { + padding: 3px 10px; + line-height: 12px; + color: #ffffff !important; + font-weight: 400; + border-radius: 4px; + font-size: 12px; + margin-right: 8px; + } + .icon-menu { + padding: 0 24px; + opacity: 1; + transition: all $sidebarTrnTime ease-in; + } + .icon-menu .icon-menu-item { + display: inline-block; + } + .icon-menu .icon-menu-item button { + min-width: auto; + margin: 4px; + } + .icon-menu > .mat-divider { + margin: 1rem 0; + } + .nav-item-sep { + padding: 0; + margin: 0 0 1rem; + .icon-menu-title { + padding-left: 0px; + margin-left: -8px; + } + span { + padding: 16px 0 0 16px; + display: block; + font-size: 12px; + } + } + .sidenav { + li { + cursor: pointer; + ul.submenu { + max-height: 0; + overflow: hidden; + // transition: all $sidebarTrnTime $transitionTiming; + opacity: 1; + &.lvl2 a, + &.lvl3 a { + height: 44px; + } + &.lvl2 a { + padding: 0 16px 0 55px; + } + &.lvl3 a { + padding: 0 16px 0 64px; + } + } + } + a { + position: relative; + width: 100%; + padding: 0 16px 0 0; + display: flex; + box-sizing: border-box; + align-items: center; + -ms-flex-direction: row; + flex-direction: row; + -webkit-box-orient: horizontal; + -webkit-box-direction: normal; + text-decoration: none; + .mat-icon:not(.menu-caret) { + text-align: center; + margin-right: 3px; + height: 48px; + width: 48px; + line-height: 48px; + border-left: 3px solid; + border-color: transparent; + } + .menu-caret { + font-size: 1rem; + line-height: 1; + height: 16px; + width: 16px; + transition: transform .3s cubic-bezier(.35,0,.25,1); + } + } + } +} + + +.sidebar-full .sidenav-hold { + + li { + ul { + position: relative; + max-height: 0; + overflow: hidden; + transition: max-height .4s cubic-bezier(.35,0,.25,1); + &::after, + &::before { + content: ""; + left: 0; + position: absolute; + width: 100%; + height: 2px; + z-index: 3; + } + &::after { + background: linear-gradient(-180deg,rgba(0, 0 ,0 , .06), transparent); + bottom: 0; + } + &::before { + background: linear-gradient(180deg,rgba(0, 0, 0, .1),transparent); + top: 0; + } + } + + &.open, + &.open li.open { + background: rgba(0,0,0,.02); + } + &.open > div > ul, + &.open > ul { + max-height: 1000px; + background: rgba(0,0,0,.005); + } + &.open > div > a .menu-caret, + &.open > a .menu-caret { + transform: rotate(90deg) + } + } + a { + height: 48px; + // padding: 0 16px 0 0; + } +} + +.sidebar-compact .sidenav-hold, +.sidebar-compact-big .sidenav-hold { + .icon-menu { + padding: 8px 0 0; + .nav-item-sep { + display: none; + } + } + .nav-item-sep { + margin: 1rem 0; + span { + display: none; + } + } + a { + padding: 0 !important; + } + li { + position: relative; + z-index: 10001; + .lvl1 > a > .mat-icon.menu-caret, + .menuitem-badge { + display: none; + } + &:hover > div > ul.submenu, + &:hover > ul.submenu { + max-height: 1000px; + visibility: visible; + opacity: 1; + transform: translateY(0); + transition: all 0.4s cubic-bezier(.35,0,.25,1); + } + ul.submenu { + overflow: visible; + position: absolute; + left: 100%; + top: 0; + width: 200px; + z-index: 9999; + visibility: hidden; + opacity: 0; + transform: translateY(10px); + box-shadow: 0 2px 1px -1px rgba(0,0,0,.2), 0 1px 1px 0 rgba(0,0,0,.14), 0 1px 3px 0 rgba(0,0,0,.12); + // a { + + // } + &.lvl2 > li > a, + &.lvl3 > li > a { + height: 36px; + line-height: 36px; + padding: 0 16px !important; + } + } + } +} + +.sidebar-compact { + .icon-menu { + width: 48px; + .icon-menu-item { + button { + margin: 0 0 4px 0; + padding: 0 12px; + } + } + } + + .sidenav-hold { + .mat-icon:not(.menu-caret) { + margin-right: 0; + } + .item-name.lvl1 { + display: none; + } + } +} + +.sidebar-compact-big { + .sidenav { + > li > div > a { + text-align: center; + display: block !important; + padding: 16px 0; + .item-name.lvl1 { + // font-size: 1rem; + } + } + .mat-icon:not(.menu-caret) { + height: 36px; + width: 150px; + font-size: 24px; + line-height: 36px; + margin: 0; + } + } +} + +.layout-intransition { + // display: none; + .sidenav ul.submenu { + opacity: 0 !important; + } +} + +[dir=rtl] { + &.sidebar-full { + .sidenav-hold { + a { + padding: 0 0 0 16px; + } + } + } + &.sidebar-compact .sidenav-hold, + &.sidebar-compact-big .sidenav-hold { + a { + padding: 0 !important; + } + li ul.submenu { + left: auto !important; + right: 100%; + &.lvl2 > li > a, + &.lvl3 > li > a { + padding: 0 16px !important; + } + } + } + .sidenav-hold { + .sidenav { + a { + padding: 0 0 0 16px; + .mat-icon:not(.menu-caret) { + border-left: none; + border-right: 3px solid; + margin-right: 1px; + border-color: transparent; + } + } + li ul.submenu { + &.lvl2 > li > a { + padding: 0 55px 0 16px; + } + &.lvl3 > li > a { + padding: 0 64px 0 16px; + } + } + } + .nav-item-sep { + .icon-menu-title { + padding-right: 0 !important; + margin: 0 -6px 0 0 !important; + } + span { + padding: 16px 20px 0 0; + } + } + } +} diff --git a/src/assets/styles/scss/main/_topnav.scss b/src/assets/styles/scss/main/_topnav.scss new file mode 100644 index 00000000..ca34900b --- /dev/null +++ b/src/assets/styles/scss/main/_topnav.scss @@ -0,0 +1,236 @@ +/*! + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +.topnav { + &:after { + content:""; + display:table; + clear:both; + } + label.menu-toggle { + height: 48px; + width: 48px; + box-sizing: border-box; + padding: 12px; + border-radius: 50%; + .mat-icon { + font-size: 24px; + } + } + .toggle, + [id^=drop] { + display: none; + } + ul { + padding:0; + margin:0; + list-style: none; + position: relative; + } + ul:not(.menu) { + box-shadow: 0 0 4px rgba(0,0,0,0), 0 4px 8px rgba(0,0,0,.28); + } + ul.menu { + float: left; + height: 48px; + padding-right: 45px; + > li { + float: left; + > div { + > a, + > div { + border-bottom: 2px solid; + height: 48px; + box-sizing: border-box; + border-color: transparent; + margin: 0 6px; + } + } + } + } + ul li { + margin: 0px; + display: inline-block; + } + + a, label { + display: flex; + flex-direction: row; + align-items: center; + padding: 13px 20px; + height: 44px; + font-size: .875rem; + text-decoration: none; + box-sizing: border-box; + .mat-icon { + font-size: 16px; + height: 20px; + line-height: 20px; + width: 20px; + margin-right: 2px; + } + } + ul li ul li:hover, + ul li ul li.open { + background: #eeeeee; + } + + ul ul { + opacity: 0; + visibility: hidden; + position: absolute; + background: #ffffff; + color: rgba(0,0,0,.87); + /* has to be the same number as the "line-height" of "nav a" */ + top: 48px; + transform: translateY(-100px); + transition: all 0.3s ease-in-out; + z-index: -1; + } + + ul li:hover > div > div > ul, + ul li:hover > div > ul { + opacity: 1; + visibility: visible; + transform: translateY(0); + } + + ul ul li { + width:170px; + float:none; + display:list-item; + position: relative; + } + ul ul ul { + top: 0; + left:170px; + } + ul ul ul li { + position: relative; + top: 0; + } + + li > a:after { content: ' +'; } + li > a:only-child:after { content: ''; } +} + + +/* Media Queries +--------------------------------------------- */ + +@media all and (max-width : 768px) { + .topnav { + margin: 0; + .toggle + a { + display: none; + } + .menu { + opacity: 0; + visibility: hidden; + height: auto !important; + width: 100%; + li { + a { + border: none !important; + } + } + } + + .toggle { + display: flex; + text-decoration:none; + border:none; + } + + .toggle:hover { + // background-color: #eeeeee; + } + ul { + overflow: hidden; + max-height: 0px; + transform: translateY(0px) !important; + transition: max-height 0.3s ease-in-out; + } + + [id^=drop]:checked + ul { + opacity: 1; + visibility: visible; + max-height: 2000px; + } + [id^=drop]:checked + ul.menu { + max-height: 300px; + overflow-y: scroll; + } + + ul li { + position: relative; + opacity: 1; + visibility: visible; + width: 100%; + z-index: 1; + } + + ul ul .toggle, + ul ul a { + padding: 0 40px; + } + + ul ul ul a { + padding: 0 80px; + } + + ul li ul li .toggle, + ul ul a, + ul ul ul a{ + padding:14px 20px; + } + + /* Hide Dropdowns by Default */ + ul ul { + float: none; + position: relative; + top: 0; + left: 0; + box-shadow: none !important; + z-index: 1; + } + + /* Hide menus on hover */ + ul li:hover > div > div > ul, + ul li:hover > div > ul { + opacity: 0; + visibility: hidden; + } + + ul ul li { + opacity: 1; + visibility: visible; + width: 100%; + } + ul ul ul { + left: 0; + } + ul ul ul li { + position: static; + } + } +} + +@media all and (max-width : 330px) { + .topnav ul li { + display:block; + width: 94%; + } +} + +[dir=rtl] { + .topnav { + a, label { + .mat-icon { + margin-right: 0; + margin-left: 2px; + } + } + } +} \ No newline at end of file diff --git a/src/assets/styles/scss/main/_typography.scss b/src/assets/styles/scss/main/_typography.scss new file mode 100644 index 00000000..bfe2652f --- /dev/null +++ b/src/assets/styles/scss/main/_typography.scss @@ -0,0 +1,104 @@ +/*! + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +h1, +h2, +h3, +h4, +h5, +h6, +.h1, +.h2, +.h3, +.h4, +.h5, +.h6 { + margin-bottom: $headings-margin-bottom; + font-weight: $headings-font-weight; + line-height: $headings-line-height; + color: $headings-color; +} + +h1, +.h1 { + font-size: $font-size-h1; +} +h2, +.h2 { + font-size: $font-size-h2; +} +h3, +.h3 { + font-size: $font-size-h3; +} +h4, +.h4 { + font-size: $font-size-h4; +} +h5, +.h5 { + font-size: $font-size-h5; +} +h6, +.h6 { + font-size: $font-size-h6; +} + +a, +a:focus, +a:hover { + cursor: pointer; + text-decoration: none; + color: inherit; + outline: 0; +} + +button { + outline: 0; +} + +figure { + margin: 0; +} + +strong, +b { + font-weight: 700; +} + +.strikethrough { + text-decoration: line-through; +} +.text-uppercase { + text-transform: uppercase; +} +.text-capitalize { + text-transform: capitalize; +} + + +@for $i from 10 through 78 { + .text-#{$i} { + font-size: #{$i}px; + } +} +@for $i from 10 through 24 { + .mat-icon-#{$i} { + font-size: #{$i}px; + width: #{$i}px; + height: #{$i}px; + } +} +.font-weight-light { + font-weight: 300; +} +.font-weight-normal { + font-weight: 400; +} +.font-weight-bold { + font-weight: bold; +} +.line-height-1 { + line-height: 1 !important; +} \ No newline at end of file diff --git a/src/assets/styles/scss/mixins/_gradients.scss b/src/assets/styles/scss/mixins/_gradients.scss new file mode 100644 index 00000000..1e22ed96 --- /dev/null +++ b/src/assets/styles/scss/mixins/_gradients.scss @@ -0,0 +1,28 @@ +/*! + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +@mixin directional-gradient($dir, $from, $to, $stop: 100%) { + background-color: $from !important; + background-image: -moz-linear-gradient($dir, $from 0%, $to $stop) !important; + + background-image: -webkit-gradient( + linear, + $dir, + right top, + color-stop(0%, $from), + color-stop($stop, $to) + ) !important; + + background-image: -webkit-linear-gradient($dir, $from 0%, $to $stop) !important; + + background-image: -o-linear-gradient($dir, $from 0%, $to $stop) !important; + + background: -ms-linear-gradient($dir, $from 0%, $to $stop) !important; + + /* Standard */ + background: linear-gradient($dir, $from 0%, $to $stop) !important; + + /* IE6-9 */ + filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='$from', endColorstr='$to',GradientType=1 ); + } \ No newline at end of file diff --git a/src/assets/styles/scss/themes/_egret-blue.scss b/src/assets/styles/scss/themes/_egret-blue.scss new file mode 100644 index 00000000..3c791367 --- /dev/null +++ b/src/assets/styles/scss/themes/_egret-blue.scss @@ -0,0 +1,177 @@ +$black-87-opacity: rgba(0,0,0, .87); + +// You can define colors here (using http://mcg.mbitson.com) +$mat-primary: ( + 50 : #ffffff, + 100 : #b6e8fe, + 200 : #7ed6fd, + 300 : #38bffd, + 400 : #19b6fc, + 500 : #03a9f4, + 600 : #0394d6, + 700 : #027fb8, + 800 : #026a99, + 900 : #02557b, + A100 : #f7fdff, + A200 : #91ddff, + A400 : #2bbdff, + A700 : #11b5ff, + contrast: ( + 50 : #000000, + 100 : #000000, + 200 : #000000, + 300 : #000000, + 400 : #000000, + 500 : #ffffff, + 600 : #ffffff, + 700 : #ffffff, + 800 : #ffffff, + 900 : #ffffff, + A100 : #000000, + A200 : #000000, + A400 : #000000, + A700 : #000000, + ) +); + +$mat-accent: ( + 50 : #fffaef, + 100 : #fff4d8, + 200 : #ffecbe, + 300 : #ffe4a4, + 400 : #ffdf91, + 500 : #ffd97d, + 600 : #ffd575, + 700 : #ffcf6a, + 800 : #ffca60, + 900 : #ffc04d, + A100 : #ffffff, + A200 : #ffffff, + A400 : #fffbf3, + A700 : #fff1da, + contrast: ( + 50 : #000000, + 100 : #000000, + 200 : #000000, + 300 : #000000, + 400 : #000000, + 500 : #000000, + 600 : #000000, + 700 : #000000, + 800 : #000000, + 900 : #000000, + A100 : #000000, + A200 : #000000, + A400 : #000000, + A700 : #000000, + ) +); + + + +// mandatory stuff for theming +$egret-primary: mat-palette($mat-primary); +$egret-accent: mat-palette($mat-accent); + +// include the custom theme components into a theme object +$egret-theme: mat-light-theme($egret-primary, $egret-accent); + +$primary-dark: darken( map-get($mat-primary, 500), 8% ); + +// include the custom theme object into the angular material theme +.egret-blue { + @include angular-material-theme($egret-theme); + + .mat-bg-primary, + .topbar, + .chats-wrap .conversations-hold .single-conversation.me .conversation-msg, + .ngx-datatable .datatable-footer .datatable-pager .pager .pages.active a, + .fileupload-drop-zone.dz-file-over, + .toolbar-avatar.online > .status-dot, + .cal-open-day-events, + div.hopscotch-bubble { + background: map-get($mat-primary, 500) !important; + color: #ffffff !important; + } + .mat-color-primary, + .list-item-active, + .sidenav-hold .sidenav li.open a .mat-icon:not(.menu-caret), + .sidenav-hold .sidenav li.open > div > a > span:not(.menuitem-badge), + .sidenav-hold .sidenav li.open > a > span { + color: map-get($mat-primary, 500) !important; + } + .topnav ul.menu > li > div.open > a, + .topnav ul.menu > li > div.open > div, + .sidebar-panel .sidebar-list-item.open > .mat-list-item-content > .sub-menu, + .sidenav-hold .sidenav li.open a .mat-icon:not(.menu-caret), + .list-item-active { + border-color: map-get($mat-primary, 500) !important; + } + .sidebar-panel { + background: url('../../assets/images/sidebar-bg.jpg') no-repeat; + } + .sidebar-compact ul.submenu, + .default-bg { + background: #ffffff !important; + } + .default-light-bg { + background: #fafafa !important; + } + /* Hopscotch Tour */ + div.hopscotch-bubble { + border-color: $primary-dark; + } + /* up arrow */ + div.hopscotch-bubble .hopscotch-bubble-arrow-container.up .hopscotch-bubble-arrow-border { + border-bottom: 17px solid $primary-dark; + } + div.hopscotch-bubble .hopscotch-bubble-arrow-container.up .hopscotch-bubble-arrow { + border-bottom: 17px solid map-get($mat-primary, 500); + top: -16px; + } + /* right arrow */ + div.hopscotch-bubble .hopscotch-bubble-arrow-container.right .hopscotch-bubble-arrow-border { + border-left: 17px solid $primary-dark; + } + div.hopscotch-bubble .hopscotch-bubble-arrow-container.right .hopscotch-bubble-arrow { + border-left: 17px solid map-get($mat-primary, 500); + left: -1px; + } + /* bottom arrow */ + div.hopscotch-bubble .hopscotch-bubble-arrow-container.down .hopscotch-bubble-arrow-border { + border-top: 17px solid $primary-dark; + } + div.hopscotch-bubble .hopscotch-bubble-arrow-container.down .hopscotch-bubble-arrow { + border-top: 17px solid map-get($mat-primary, 500); + top: -18px; + } + /* Left Arrow */ + div.hopscotch-bubble .hopscotch-bubble-arrow-container.left .hopscotch-bubble-arrow-border { + border-right: 17px solid $primary-dark; + } + div.hopscotch-bubble .hopscotch-bubble-arrow-container.left .hopscotch-bubble-arrow { + left: 1px; + border-right: 17px solid map-get($mat-primary, 500); + } + + .mat-bg-accent, + div.hopscotch-bubble .hopscotch-bubble-number, + div.hopscotch-bubble .hopscotch-nav-button.next:hover, + div.hopscotch-bubble .hopscotch-nav-button.next { + background-color: map-get($mat-accent, 500); + color: black; + } + .mat-bg-warn { + background-color: #f44336; + color: white; + } + .mat-color-accent { + color: map-get($mat-accent, 500); + } + .mat-color-warn { + color: #f44336; + } + .mat-color-default { + color: rgba(0, 0, 0, 0.87); + } +} \ No newline at end of file diff --git a/src/assets/styles/scss/themes/_egret-dark-pink.scss b/src/assets/styles/scss/themes/_egret-dark-pink.scss new file mode 100644 index 00000000..18ce70d7 --- /dev/null +++ b/src/assets/styles/scss/themes/_egret-dark-pink.scss @@ -0,0 +1,433 @@ +$mat-primary: ( + 50 : #fce4ec, + 100 : #f8bcd0, + 200 : #f48fb1, + 300 : #f06292, + 400 : #ec407a, + 500 : #2debff, + 600 : #e61a5b, + 700 : #e31651, + 800 : #df1247, + 900 : #d90a35, + A100 : #ffffff, + A200 : #ffd0d8, + A400 : #ff9dae, + A700 : #ff8498, + contrast: ( + 50 : #000000, + 100 : #000000, + 200 : #000000, + 300 : #000000, + 400 : #ffffff, + 500 : #ffffff, + 600 : #ffffff, + 700 : #ffffff, + 800 : #ffffff, + 900 : #ffffff, + A100 : #000000, + A200 : #000000, + A400 : #000000, + A700 : #000000, + ) +); + +$mat-accent: ( + 50 : #eceff1, + 100 : #cfd8dc, + 200 : #b0bec5, + 300 : #90a4ae, + 400 : #78919c, + 500 : #607d8b, + 600 : #587583, + 700 : #4e6a78, + 800 : #44606e, + 900 : #334d5b, + A100 : #abe1ff, + A200 : #78ceff, + A400 : #45bcff, + A700 : #2bb3ff, + contrast: ( + 50 : #000000, + 100 : #000000, + 200 : #000000, + 300 : #000000, + 400 : #000000, + 500 : #ffffff, + 600 : #ffffff, + 700 : #ffffff, + 800 : #ffffff, + 900 : #ffffff, + A100 : #000000, + A200 : #000000, + A400 : #000000, + A700 : #000000, + ) +); + +// mandatory stuff for theming +$egret-primary: mat-palette($mat-primary); +$egret-accent: mat-palette($mat-accent); + +// include the custom theme components into a theme object +$egret-theme: mat-dark-theme($egret-primary, $egret-accent); + +$primary-dark: darken(map-get($mat-primary, 500), 8%); + +$app-background: #424242; +$panel-border-color: #171717; + +// include the custom theme object into the angular material theme +.egret-dark-pink { + @include angular-material-theme($egret-theme); + + + .example-form { + min-width: 60px; + max-width: 500px; + // width: 100%; + } + + .mat-flat-button, .mat-stroked-button { + border-radius: 1px !important; + } + + .example-full-width { + width: 100%; + } + + .example-button-row button, + .example-button-row a { + margin-right: 8px; + } + + .mat-menu-panel { + border-radius: 0px !important; + min-width: 300px; + } + + .mat-icon-button { + height: 36px !important; + } + + .mat-menu-item { + line-height: 36px; + height: 36px; + padding: 0 24px; + } + + .mat-divider { + margin: 10px 0px !important; + } + + .material-icons { + font-size: 18px !important; + line-height: 18px !important; + } + + .mat-card { + margin: .01rem !important; + // overflow: scroll !important; + border-radius: 0px !important; + width: 100% !important; + border: 1px solid #464646 !important; + } + + .mat-card:not([class*='mat-elevation-z']) { + box-shadow: none !important; + } + + .mat-drawer-container { + background-color: $app-background !important; + } + + .mat-tab-label { + height: 36px !important; + background: #3c3c3c !important; + opacity: 1; + min-width: 120px !important; + } + + .mat-tab-label-active { + background: #424242 !important; + } + + .mat-card.p-0 .mat-card-content { + padding: 0 0rem 0rem !important; + } + + .mat-grid-tile .mat-figure { + align-items: normal !important; + justify-content: left !important; + } + + .mat-tab-header { + background: $app-background; + } + + .mat-tree-node { + min-height: 36px; + } + + .mat-tab-nav-bar, .mat-tab-header { + // border-bottom: 0px !important; + } + + .inspector-form-container { + margin: 18px 12px; + } + + .inspector-form-container > * { + width: 100%; + } + + .mat-tab-group { + height: 100%; + } + + .mat-tab-body-wrapper { + // overflow: scroll; + } + + .mat-tree-node:hover { + background: #545454; + cursor: pointer; + } + + .mat-tree-node:active { + background: #0090ec; + cursor: pointer; + } + + .mat-tree-node:focus { + background: #0090ec; + cursor: pointer; + } + + .mat-toolbar { + background: $app-background; + border-bottom: 1px solid $panel-border-color; + min-height: 36px; + } + + .app-toolbar.mat-toolbar{ + height: 48px; + padding: 0px 8px; + } + + .app-menubar.mat-toolbar { + padding: 0px 0px !important; + position: relative; + z-index: 999; + height: 44px; + } + + .mat-grid-tile.selected { + background: #2d2d2d; + } + + .mat-card.active { + background: #292929 !important; + } + + .mat-bg-primary, + .topbar, + .chats-wrap .conversations-hold .single-conversation.me .conversation-msg, + .ngx-datatable .datatable-footer .datatable-pager .pager .pages.active a, + .fileupload-drop-zone.dz-file-over, + .toolbar-avatar.online > .status-dot, + .cal-open-day-events, + div.hopscotch-bubble { + background: map-get($mat-primary, 500) !important; + color: #ffffff !important; + } + + .mat-color-primary, + .list-item-active, + .sidenav-hold .sidenav li.open a .mat-icon:not(.menu-caret), + .sidenav-hold .sidenav li.open > div > a > span:not(.menuitem-badge), + .sidenav-hold .sidenav li.open > a > span { + color: map-get($mat-primary, 500) !important; + } + + .sidebar-panel .sidebar-list-item.open > .mat-list-item-content > a > span, + .sidebar-panel .sidebar-list-item.open .sub-menu .mat-list-item.selected a { + font-weight: 500; + } + + .topnav ul.menu > li > div.open > a, + .sidebar-panel .sidebar-list-item.open > .mat-list-item-content > .sub-menu, + .sidenav-hold .sidenav li.open a .mat-icon:not(.menu-caret), + .list-item-active { + border-color: map-get($mat-primary, 500) !important; + } + + .ngx-datatable.material, + .messages-wrap .inbox-hold { + background: transparent !important; + } + + // .sidebar-panel { + // background: url('../../assets/images/sidebar-bg-dark.jpg') no-repeat; + // } + .header-topnav, + .topnav ul ul, + .app-user .app-user-name, + .sidebar-panel .sidebar-list-item mat-icon:not(.menu-caret), + .ngx-datatable.material *, + .ql-snow .ql-picker, + .ngx-pagination a, .ngx-pagination button { + color: white !important; + } + + .text-muted { + color: rgba(255, 255, 255, .54) !important; + } + + .ql-snow .ql-stroke { + stroke: #ffffff; + } + + .ql-snow .ql-fill { + fill: #ffffff; + } + + .ql-toolbar.ql-snow, + .ql-toolbar.ql-snow + .ql-container.ql-snow { + border-color: rgba(255, 255, 255, 0.12) !important; + } + + // .sidebar-panel .navigation-hold, + .message-item.open .message-head, + .ngx-datatable.material .datatable-header, + .bg-white, + .cal-month-view .cal-cell-row:hover, + .chats-wrap .conversations-hold .single-conversation.sender .conversation-msg, + .cal-day-view .cal-hour:nth-child(odd), + &.collapsed-menu .sidebar-panel .sidebar-list-item.open .sub-menu, + .ngx-pagination a:hover, .ngx-pagination button:hover, + .product-container .products-pagination .ngx-pagination .current { + background: rgba(66, 66, 66, 0.92) !important; + } + + .sidebar-compact ul.submenu, + .topnav ul li ul li:hover, + .topnav ul li ul li.open, + .default-bg, + .default-light-bg { + background: #424242; + color: #ffffff; + } + + .header-topnav, + .topnav ul ul, + .ngx-datatable.material:not(.cell-selection) .datatable-body-row:hover, + .ngx-datatable.material:not(.cell-selection) .datatable-body-row:hover .datatable-row-group, + .cal-month-view .cal-cell-row .cal-cell:hover, + .cal-month-view .cal-cell.cal-has-events.cal-open, + .cal-week-view .cal-day-headers .cal-header:hover, + .cal-week-view .cal-day-headers .cal-drag-over, + .cal-day-view .cal-hour-segment:hover, + .cal-day-view .cal-drag-over .cal-hour-segment { + background: #333 !important; + } + + .cal-month-view .cal-day-cell:not(:last-child), + .cal-month-view .cal-days, + .cal-week-view .cal-day-headers .cal-header:not(:last-child), + .cal-week-view .cal-day-headers, + .cal-month-view .cal-days .cal-cell-row, + .cal-day-view .cal-hour:not(:last-child) .cal-hour-segment, + .cal-day-view .cal-hour:last-child :not(:last-child) .cal-hour-segment { + border-color: #000 !important; + } + + /* Hopscotch Tour */ + div.hopscotch-bubble { + border-color: $primary-dark; + } + + /* up arrow */ + div.hopscotch-bubble .hopscotch-bubble-arrow-container.up .hopscotch-bubble-arrow-border { + border-bottom: 17px solid $primary-dark; + } + + div.hopscotch-bubble .hopscotch-bubble-arrow-container.up .hopscotch-bubble-arrow { + border-bottom: 17px solid map-get($mat-primary, 500); + top: -16px; + } + + /* right arrow */ + div.hopscotch-bubble .hopscotch-bubble-arrow-container.right .hopscotch-bubble-arrow-border { + border-left: 17px solid $primary-dark; + } + + div.hopscotch-bubble .hopscotch-bubble-arrow-container.right .hopscotch-bubble-arrow { + border-left: 17px solid map-get($mat-primary, 500); + left: -1px; + } + + /* bottom arrow */ + div.hopscotch-bubble .hopscotch-bubble-arrow-container.down .hopscotch-bubble-arrow-border { + border-top: 17px solid $primary-dark; + } + + div.hopscotch-bubble .hopscotch-bubble-arrow-container.down .hopscotch-bubble-arrow { + border-top: 17px solid map-get($mat-primary, 500); + top: -18px; + } + + /* Left Arrow */ + div.hopscotch-bubble .hopscotch-bubble-arrow-container.left .hopscotch-bubble-arrow-border { + border-right: 17px solid $primary-dark; + } + + div.hopscotch-bubble .hopscotch-bubble-arrow-container.left .hopscotch-bubble-arrow { + left: 1px; + border-right: 17px solid map-get($mat-primary, 500); + } + + .mat-bg-accent, + div.hopscotch-bubble .hopscotch-bubble-number, + div.hopscotch-bubble .hopscotch-nav-button.next:hover, + div.hopscotch-bubble .hopscotch-nav-button.next { + background-color: map-get($mat-accent, 500); + color: black; + } + + .mat-bg-warn { + background-color: #f44336; + color: white; + } + + .mat-color-accent { + color: map-get($mat-accent, 500); + } + + .mat-color-warn { + color: #f44336; + } + + .mat-color-default { + color: #ffffff; + } + + /* OVER-RIDES */ + .mat-primary .mat-option.mat-selected:not(.mat-option-disabled) { + color: map-get($mat-accent, 50); + } + + .mat-button, .mat-flat-button, .mat-icon-button, .mat-stroked-button { + border-radius: 0 !important; + } + + .app-menubar .mat-button { + line-height: 43px !important; + } + + .app-toolbar .mat-button.mat-accent, + .app-toolbar .mat-icon-button.mat-accent, + .app-toolbar .mat-stroked-button.mat-accent { + color: #f1f1f1; + background: $panel-border-color; + } +} diff --git a/src/assets/styles/scss/themes/_egret-dark-purple.scss b/src/assets/styles/scss/themes/_egret-dark-purple.scss new file mode 100644 index 00000000..8e9056ec --- /dev/null +++ b/src/assets/styles/scss/themes/_egret-dark-purple.scss @@ -0,0 +1,240 @@ +$mat-primary: ( + 50 : #f3e5f6, + 100 : #e1bee7, + 200 : #ce93d8, + 300 : #ba68c8, + 400 : #ab47bc, + 500 : #9c27b0, + 600 : #9423a9, + 700 : #8a1da0, + 800 : #801797, + 900 : #6e0e87, + A100 : #efb8ff, + A200 : #e485ff, + A400 : #d852ff, + A700 : #d238ff, + contrast: ( + 50 : #000000, + 100 : #000000, + 200 : #000000, + 300 : #000000, + 400 : #ffffff, + 500 : #ffffff, + 600 : #ffffff, + 700 : #ffffff, + 800 : #ffffff, + 900 : #ffffff, + A100 : #000000, + A200 : #000000, + A400 : #000000, + A700 : #ffffff, + ) +); + +$mat-accent: ( + 50 : #eaf5ea, + 100 : #c9e7cb, + 200 : #a6d7a8, + 300 : #82c785, + 400 : #67bb6a, + 500 : #4caf50, + 600 : #45a849, + 700 : #3c9f40, + 800 : #339637, + 900 : #248627, + A100 : #c5ffc7, + A200 : #92ff95, + A400 : #5fff64, + A700 : #46ff4b, + contrast: ( + 50 : #000000, + 100 : #000000, + 200 : #000000, + 300 : #000000, + 400 : #000000, + 500 : #000000, + 600 : #ffffff, + 700 : #ffffff, + 800 : #ffffff, + 900 : #ffffff, + A100 : #000000, + A200 : #000000, + A400 : #000000, + A700 : #000000, + ) +); + +// mandatory stuff for theming +$egret-primary: mat-palette($mat-primary); +$egret-accent: mat-palette($mat-accent); + +// include the custom theme components into a theme object +$egret-theme: mat-dark-theme($egret-primary, $egret-accent); + +$primary-dark: darken( map-get($mat-primary, 500), 8% ); + +// include the custom theme object into the angular material theme +.egret-dark-purple { + @include angular-material-theme($egret-theme); + + .mat-bg-primary, + .topbar, + .chats-wrap .conversations-hold .single-conversation.me .conversation-msg, + .ngx-datatable .datatable-footer .datatable-pager .pager .pages.active a, + .fileupload-drop-zone.dz-file-over, + .toolbar-avatar.online > .status-dot, + .cal-open-day-events, + div.hopscotch-bubble { + background: map-get($mat-primary, 500) !important; + color: #ffffff !important; + } + .mat-color-primary, + .list-item-active, + .sidenav-hold .sidenav li.open a .mat-icon:not(.menu-caret), + .sidenav-hold .sidenav li.open > div > a > span:not(.menuitem-badge), + .sidenav-hold .sidenav li.open > a > span { + color: map-get($mat-primary, 500) !important; + } + .sidebar-panel .sidebar-list-item.open > .mat-list-item-content > a > span, + .sidebar-panel .sidebar-list-item.open .sub-menu .mat-list-item.selected a { + font-weight: 500; + } + .topnav ul.menu > li > div.open > a, + .sidebar-panel .sidebar-list-item.open > .mat-list-item-content > .sub-menu, + .sidenav-hold .sidenav li.open a .mat-icon:not(.menu-caret), + .list-item-active { + border-color: map-get($mat-primary, 500) !important; + } + .ngx-datatable.material, + .messages-wrap .inbox-hold { + background: transparent !important; + } + // .sidebar-panel { + // background: url('../../assets/images/sidebar-bg-dark.jpg') no-repeat; + // } + .header-topnav, + .topnav ul ul, + .app-user .app-user-name, + .sidebar-panel .sidebar-list-item mat-icon:not(.menu-caret), + .ngx-datatable.material *, + .ql-snow .ql-picker, + .mat-option, + .mat-dialog-container, + .ngx-pagination a, .ngx-pagination button { + color: white !important; + } + .text-muted { + color: rgba(255, 255, 255, .54) !important; + } + + .ql-snow .ql-stroke { + stroke: #ffffff; + } + .ql-snow .ql-fill { + fill: #ffffff; + } + .ql-toolbar.ql-snow, + .ql-toolbar.ql-snow + .ql-container.ql-snow { + border-color: rgba(255, 255, 255, 0.12) !important; + } + + // .sidebar-panel .navigation-hold, + .message-item.open .message-head, + .ngx-datatable.material .datatable-header, + .bg-white, + .cal-month-view .cal-cell-row:hover, + .chats-wrap .conversations-hold .single-conversation.sender .conversation-msg, + .cal-day-view .cal-hour:nth-child(odd), + &.collapsed-menu .sidebar-panel .sidebar-list-item.open .sub-menu, + .ngx-pagination a:hover, .ngx-pagination button:hover, + .product-container .products-pagination .ngx-pagination .current { + background: rgba(66, 66, 66, 0.92) !important; + } + .sidebar-compact ul.submenu, + .topnav ul li ul li:hover, + .topnav ul li ul li.open, + .default-bg, + .default-light-bg { + background: #424242; + color: #ffffff; + } + .header-topnav, + .topnav ul ul, + .ngx-datatable.material:not(.cell-selection) .datatable-body-row:hover, + .ngx-datatable.material:not(.cell-selection) .datatable-body-row:hover .datatable-row-group, + .cal-month-view .cal-cell-row .cal-cell:hover, + .cal-month-view .cal-cell.cal-has-events.cal-open, + .cal-week-view .cal-day-headers .cal-header:hover, + .cal-week-view .cal-day-headers .cal-drag-over, + .cal-day-view .cal-hour-segment:hover, + .cal-day-view .cal-drag-over .cal-hour-segment { + background: #333 !important; + } + .cal-month-view .cal-day-cell:not(:last-child), + .cal-month-view .cal-days, + .cal-week-view .cal-day-headers .cal-header:not(:last-child), + .cal-week-view .cal-day-headers, + .cal-month-view .cal-days .cal-cell-row, + .cal-day-view .cal-hour:not(:last-child) .cal-hour-segment, + .cal-day-view .cal-hour:last-child :not(:last-child) .cal-hour-segment { + border-color: #000 !important; + } + + /* Hopscotch Tour */ + div.hopscotch-bubble { + border-color: $primary-dark; + } + /* up arrow */ + div.hopscotch-bubble .hopscotch-bubble-arrow-container.up .hopscotch-bubble-arrow-border { + border-bottom: 17px solid $primary-dark; + } + div.hopscotch-bubble .hopscotch-bubble-arrow-container.up .hopscotch-bubble-arrow { + border-bottom: 17px solid map-get($mat-primary, 500); + top: -16px; + } + /* right arrow */ + div.hopscotch-bubble .hopscotch-bubble-arrow-container.right .hopscotch-bubble-arrow-border { + border-left: 17px solid $primary-dark; + } + div.hopscotch-bubble .hopscotch-bubble-arrow-container.right .hopscotch-bubble-arrow { + border-left: 17px solid map-get($mat-primary, 500); + left: -1px; + } + /* bottom arrow */ + div.hopscotch-bubble .hopscotch-bubble-arrow-container.down .hopscotch-bubble-arrow-border { + border-top: 17px solid $primary-dark; + } + div.hopscotch-bubble .hopscotch-bubble-arrow-container.down .hopscotch-bubble-arrow { + border-top: 17px solid map-get($mat-primary, 500); + top: -18px; + } + /* Left Arrow */ + div.hopscotch-bubble .hopscotch-bubble-arrow-container.left .hopscotch-bubble-arrow-border { + border-right: 17px solid $primary-dark; + } + div.hopscotch-bubble .hopscotch-bubble-arrow-container.left .hopscotch-bubble-arrow { + left: 1px; + border-right: 17px solid map-get($mat-primary, 500); + } + + .mat-bg-accent, + div.hopscotch-bubble .hopscotch-bubble-number, + div.hopscotch-bubble .hopscotch-nav-button.next:hover, + div.hopscotch-bubble .hopscotch-nav-button.next { + background-color: map-get($mat-accent, 500); + color: black; + } + .mat-bg-warn { + background-color: #f44336; + color: white; + } + .mat-color-accent { + color: map-get($mat-accent, 500); + } + .mat-color-warn { + color: #f44336; + } + .mat-color-default { + color: #ffffff; + } +} \ No newline at end of file diff --git a/src/assets/styles/scss/themes/_egret-navy.scss b/src/assets/styles/scss/themes/_egret-navy.scss new file mode 100644 index 00000000..6f315b90 --- /dev/null +++ b/src/assets/styles/scss/themes/_egret-navy.scss @@ -0,0 +1,187 @@ +// You can define colors here (using http://mcg.mbitson.com) +$mat-primary: ( + 50 : #e2e3ea, + 100 : #b7b9c9, + 200 : #888ba6, + 300 : #585d82, + 400 : #343a67, + 500 : #10174c, + 600 : #0e1445, + 700 : #0c113c, + 800 : #090d33, + 900 : #050724, + A100 : #6068ff, + A200 : #2d38ff, + A400 : #000df9, + A700 : #000be0, + contrast: ( + 50 : #000000, + 100 : #000000, + 200 : #000000, + 300 : #ffffff, + 400 : #ffffff, + 500 : #ffffff, + 600 : #ffffff, + 700 : #ffffff, + 800 : #ffffff, + 900 : #ffffff, + A100 : #ffffff, + A200 : #ffffff, + A400 : #ffffff, + A700 : #ffffff, + ) +); + +$mat-accent: ( + 50 : #fff8e1, + 100 : #ffecb5, + 200 : #ffe083, + 300 : #ffd451, + 400 : #ffca2c, + 500 : #ffc107, + 600 : #ffbb06, + 700 : #ffb305, + 800 : #ffab04, + 900 : #ff9e02, + A100 : #ffffff, + A200 : #fffaf2, + A400 : #ffe4bf, + A700 : #ffd9a6, + contrast: ( + 50 : #000000, + 100 : #000000, + 200 : #000000, + 300 : #000000, + 400 : #000000, + 500 : #000000, + 600 : #000000, + 700 : #000000, + 800 : #000000, + 900 : #000000, + A100 : #000000, + A200 : #000000, + A400 : #000000, + A700 : #000000, + ) +); + +// mandatory stuff for theming +$egret-primary: mat-palette($mat-primary); +$egret-accent: mat-palette($mat-accent); + +// include the custom theme components into a theme object +$egret-theme: mat-light-theme($egret-primary, $egret-accent); + +$primary-dark: darken( map-get($mat-primary, 500), 8% ); + +// include the custom theme object into the angular material theme +.egret-navy { + @include angular-material-theme($egret-theme); + + .mat-bg-primary, + .topbar:not(.topbar-white), + .chats-wrap .conversations-hold .single-conversation.me .conversation-msg, + .ngx-datatable .datatable-footer .datatable-pager .pager .pages.active a, + .fileupload-drop-zone.dz-file-over, + .toolbar-avatar.online > .status-dot, + .cal-open-day-events, + div.hopscotch-bubble { + background: map-get($mat-primary, 500) !important; + color: #ffffff !important; + } + .mat-color-primary, + .list-item-active, + .sidenav-hold .sidenav li.open a .mat-icon:not(.menu-caret), + .sidenav-hold .sidenav li.open > div > a > span:not(.menuitem-badge), + .sidenav-hold .sidenav li.open > a > span { + color: map-get($mat-primary, 500) !important; + } + .sidebar-panel .mat-nav-list .mat-list-item { + color: rgba(0, 0, 0, 0.94) + } + .topnav ul.menu > li > div.open > a, + .topnav ul.menu > li > div.open > div, + .sidebar-panel .sidebar-list-item.open > .mat-list-item-content > .sub-menu, + .sidenav-hold .sidenav li.open a .mat-icon:not(.menu-caret), + .list-item-active { + border-color: map-get($mat-primary, 500) !important; + } + .sidebar-panel { + background: url('../../assets/images/sidebar-bg.jpg') no-repeat; + } + .sidebar-compact ul.submenu, + .default-bg { + background: #ffffff !important; + } + .default-light-bg { + background: #fafafa !important; + } + /* White Background */ + .topbar-white { + background: #ffffff; + // .mat-icon { + // color: rgba(0, 0, 0, 0.87); + // } + .search-bar .top-search-form { + box-shadow: inset 0 0 2px 2px rgba(136, 136, 136, 0.2); + } + } + + /* Hopscotch Tour */ + div.hopscotch-bubble { + border-color: $primary-dark; + } + /* up arrow */ + div.hopscotch-bubble .hopscotch-bubble-arrow-container.up .hopscotch-bubble-arrow-border { + border-bottom: 17px solid $primary-dark; + } + div.hopscotch-bubble .hopscotch-bubble-arrow-container.up .hopscotch-bubble-arrow { + border-bottom: 17px solid map-get($mat-primary, 500); + top: -16px; + } + /* right arrow */ + div.hopscotch-bubble .hopscotch-bubble-arrow-container.right .hopscotch-bubble-arrow-border { + border-left: 17px solid $primary-dark; + } + div.hopscotch-bubble .hopscotch-bubble-arrow-container.right .hopscotch-bubble-arrow { + border-left: 17px solid map-get($mat-primary, 500); + left: -1px; + } + /* bottom arrow */ + div.hopscotch-bubble .hopscotch-bubble-arrow-container.down .hopscotch-bubble-arrow-border { + border-top: 17px solid $primary-dark; + } + div.hopscotch-bubble .hopscotch-bubble-arrow-container.down .hopscotch-bubble-arrow { + border-top: 17px solid map-get($mat-primary, 500); + top: -18px; + } + /* Left Arrow */ + div.hopscotch-bubble .hopscotch-bubble-arrow-container.left .hopscotch-bubble-arrow-border { + border-right: 17px solid $primary-dark; + } + div.hopscotch-bubble .hopscotch-bubble-arrow-container.left .hopscotch-bubble-arrow { + left: 1px; + border-right: 17px solid map-get($mat-primary, 500); + } + + .mat-bg-accent, + div.hopscotch-bubble .hopscotch-bubble-number, + div.hopscotch-bubble .hopscotch-nav-button.next:hover, + div.hopscotch-bubble .hopscotch-nav-button.next { + background-color: map-get($mat-accent, 500); + color: black; + } + .mat-bg-warn { + background-color: #f44336; + color: white; + } + .mat-color-accent { + color: map-get($mat-accent, 500); + } + .mat-color-warn { + color: #f44336; + } + .mat-color-default { + color: rgba(0, 0, 0, 0.87); + } +} \ No newline at end of file diff --git a/src/assets/styles/scss/utilities/_avatar.scss b/src/assets/styles/scss/utilities/_avatar.scss new file mode 100644 index 00000000..ad5eb6f9 --- /dev/null +++ b/src/assets/styles/scss/utilities/_avatar.scss @@ -0,0 +1,21 @@ +/*! + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +.avatar-xs { + width: 24px; + height: 24px; +} +.avatar-sm { + width: 36px; + height: 36px; +} +.avatar-md { + width: 54px; + height: 54px; +} + +.avatar-lg { + width: 80px; + height: 80px; +} \ No newline at end of file diff --git a/src/assets/styles/scss/utilities/_border.scss b/src/assets/styles/scss/utilities/_border.scss new file mode 100644 index 00000000..4c5d871e --- /dev/null +++ b/src/assets/styles/scss/utilities/_border.scss @@ -0,0 +1,50 @@ +/*! + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +.border-0 { + border: 0 !important; +} +.border-top-0 { + border-top: 0 !important; +} +.border-right-0 { + border-right: 0 !important; +} +.border-bottom-0 { + border-bottom: 0 !important; +} +.border-left-0 { + border-left: 0 !important; +} +.border { + border: 1px solid rgba(0, 0, 0, 0.1); +} +.border-top { + border-top: 1px solid rgba(0, 0, 0, 0.1) !important; +} +.border-right { + border-right: 1px solid rgba(0, 0, 0, 0.1) !important; +} +.border-bottom { + border-bottom: 1px solid rgba(0, 0, 0, 0.1) !important; +} +.border-left { + border-left: 1px solid rgba(0, 0, 0, 0.1) !important; +} + +.border-light { + border: 1px solid rgba(255, 255, 255, 0.1); +} +.border-top-light { + border-top: 1px solid rgba(255, 255, 255, 0.1) !important; +} +.border-right-light { + border-right: 1px solid rgba(255, 255, 255, 0.1) !important; +} +.border-bottom-light { + border-bottom: 1px solid rgba(255, 255, 255, 0.1) !important; +} +.border-left-light { + border-left: 1px solid rgba(255, 255, 255, 0.1) !important; +} diff --git a/src/assets/styles/scss/utilities/_others.scss b/src/assets/styles/scss/utilities/_others.scss new file mode 100644 index 00000000..35893aaf --- /dev/null +++ b/src/assets/styles/scss/utilities/_others.scss @@ -0,0 +1,14 @@ +/*! + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +.rounded { + border-radius: 8px; + &-circle { + border-radius: 50%; + } + &.mat-progress-bar, .mat-progress-bar-fill { + border-radius: 10px; + overflow: hidden; + } +} \ No newline at end of file diff --git a/src/assets/styles/scss/views/_calendar.scss b/src/assets/styles/scss/views/_calendar.scss new file mode 100644 index 00000000..68474e4a --- /dev/null +++ b/src/assets/styles/scss/views/_calendar.scss @@ -0,0 +1,34 @@ +/*! + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +/*---- Calendar -----*/ +.card-title-text.calendar-title { + padding: .5rem 1.5rem !important; +} +.cal-top-col { + width: 33.3333%; + float: left; +} +.cal-event-action .material-icons { + vertical-align: sub; + margin: 6px; +} +.cal-open-day-events { + box-shadow: none !important; +} + +.calendar-form-dialog { + .mat-dialog-container { + padding: 0; + } +} +.color-picker-input.mat-input-element { + padding: 4px 0 !important; +} + +@media (max-width: 767px) { + .cal-top-col { + width: 100%; + } +} \ No newline at end of file diff --git a/src/assets/styles/scss/views/_chats.scss b/src/assets/styles/scss/views/_chats.scss new file mode 100644 index 00000000..f3888819 --- /dev/null +++ b/src/assets/styles/scss/views/_chats.scss @@ -0,0 +1,73 @@ +/*! + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +/*---- Chats -----*/ +.chat-sidenav { + width: 15rem; + border-right: 1px solid rgba(0, 0, 0, .12); + overflow: hidden !important; +} +.chat-sidebar-toolbar, +.chat-toolbar, +.chat-sidebar-toolbar .mat-toolbar-row, +.chat-toolbar .mat-toolbar-row { + min-height: 56px !important; + height: 56px !important; +} +.chat-sidebar-scroll { + position: relative; + height: calc(100% - 56px); + overflow-y: scroll; +} +.chat-intro { + height: calc(100vh - 240px); +} +.chats-wrap .conversations-hold { + padding-top: 1rem; + position: relative; + height: calc(100vh - 330px); + overflow-y: scroll; +} +.chats-wrap .conversations-hold .single-conversation { + overflow: hidden; + padding: 0 2rem; + margin-bottom: 2rem; +} +.chats-wrap .conversations-hold .single-conversation .toolbar-avatar { + vertical-align: bottom; + margin-right: 1rem; +} +.chats-wrap .conversations-hold .single-conversation .chat-username { + margin: 0 0 .625rem; + font-size: .875rem; +} +.chats-wrap .conversations-hold .single-conversation .conversation-msg { + padding: .48rem .94rem; + display: inline-block; + border-radius: 4px; +} +.chats-wrap .conversations-hold .single-conversation.sender .conversation-msg { + background: #ffffff; +} + +.chats-wrap .conversations-hold .single-conversation .chat-date { + font-size: 11px; + padding: 2px; + margin: 0; + clear: both; +} +.chats-wrap .chat-input-actions { + padding: .6rem; +} + +[dir=rtl] { + .chat-sidenav { + border-right: none; + border-left: 1px solid rgba(0, 0, 0, 0.12); + } + .chats-wrap .conversations-hold .single-conversation .toolbar-avatar { + margin-right: 0; + margin-left: 1rem; + } +} \ No newline at end of file diff --git a/src/assets/styles/scss/views/_gallery.scss b/src/assets/styles/scss/views/_gallery.scss new file mode 100644 index 00000000..47360ce9 --- /dev/null +++ b/src/assets/styles/scss/views/_gallery.scss @@ -0,0 +1,39 @@ +/*! + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +/*---- Gallery -----*/ +.app-gallery .mat-figure .gallery-control-wrap { + position: absolute; + width: 100%; + height: 40px; + top: 0; + left: 0; + background: rgba(0, 0, 0, .35); + opacity: 0; + transition: opacity .3s ease-in; + -webkit-transition: opacity .3s ease-in; +} +.app-gallery img { + max-width: 100%; +} +.app-gallery .mat-figure:hover .gallery-control-wrap { + opacity: 1; +} +.app-gallery .mat-figure .gallery-control-wrap .gallery-control { + padding: 0 0 0 1rem; +} +.app-gallery .mat-figure .gallery-control-wrap .gallery-control .photo-detail { + margin: 0; + color: #FEFEFE; + line-height: 40px; +} +.app-gallery .mat-figure .gallery-control-wrap .gallery-control .mat-icon { + color: #fff; +} + +[dir=rtl] { + .app-gallery .mat-figure .gallery-control-wrap .gallery-control .photo-detail { + margin-right: 1rem; + } +} \ No newline at end of file diff --git a/src/assets/styles/scss/views/_home.scss b/src/assets/styles/scss/views/_home.scss new file mode 100644 index 00000000..42870551 --- /dev/null +++ b/src/assets/styles/scss/views/_home.scss @@ -0,0 +1,90 @@ +/*! + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +.home-section { + padding: 80px 0; +} +.scrollable { + position: relative; + width: 100%; + height: 100vh; +} +.section-intro { + padding: 100px 0 120px; + color: #ffffff; + .egret { + font-size: 36px; + font-weight: 900; + text-transform: uppercase; + letter-spacing: 1px; + text-shadow: 0px 1px 3px rgba(0, 0, 0, 0.48); + } + h1 { + font-size: 32px; + margin: 2rem 0 1rem; + font-weight: 300; + } + p { + font-size: 16px; + max-width: 450px; + margin: 0 auto 1.4rem; + } + img { + display: block; + margin: 0 auto; + } +} +.section-demos { + position: relative; + background: #ffffff; + .demo-box-wrap { + padding: 2rem; + } + .demo-box { + position: relative; + border-radius: 8px; + transition: .2s all ease-in-out; + box-shadow: 0 3px 1px -2px rgba(0,0,0,.2), 0 2px 2px 0 rgba(0,0,0,.14), 0 1px 5px 0 rgba(0,0,0,.12); + &:hover { + box-shadow: 0 10px 16px rgba(0,0,0,.2); + .caption { + opacity: 1; + transform: translateY(-10px) + } + } + .caption { + opacity: 0; + background: #ffffff; + padding: 1rem; + position: absolute; + z-index: 99; + top: -20px; + left: 0; + right: 0; + margin: auto; + border-radius: 8px; + width: 220px; + transition: .2s all ease-in-out; + box-shadow: 0 3px 1px -2px rgba(0,0,0,.2), 0 2px 2px 0 rgba(0,0,0,.14), 0 1px 5px 0 rgba(0,0,0,.12); + } + h3 { + margin: 0 0 1rem; + font-size: 22px; + font-weight: 300; + } + img { + width: 100%; + height: auto; + float: left; + } + .screenshot { + overflow: hidden; + border-radius: 8px; + margin-bottom: 2rem; + // min-height: 180px; + cursor: pointer; + } + } +} + diff --git a/src/assets/styles/scss/views/_inbox.scss b/src/assets/styles/scss/views/_inbox.scss new file mode 100644 index 00000000..86e8c32d --- /dev/null +++ b/src/assets/styles/scss/views/_inbox.scss @@ -0,0 +1,93 @@ +/*! + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +/*---- Inbox -----*/ +.inbox-sidenav { + width: 15rem; + padding: .333rem; + background: transparent !important; +} +.inbox-sidenav .inbox-nav-list .mat-list-item-content > a > span { + font-size: .875rem; +} + +.inbox-sidenav .mat-list-item-content .mat-icon { + margin-right: 8px; +} + + +.inbox-toolbar { + height: 56px !important; +} +.inbox-toolbar .mat-toolbar-row { + height: 56px !important; +} + + +.show-on-open { + display: none !important; +} + +.mat-expanded { + .show-on-open { + display: inherit !important; + } + .hide-on-open { + display: none !important; + } + .mat-expansion-panel-header { + margin-bottom: 1rem; + } +} + +.messages-wrap { + padding: .333rem; + min-height: 800px; + .mat-expansion-panel-header-title { + align-items: center; + } + .mat-expansion-panel-header-description { + align-items: center; + } + + .mail-checkbox.mat-checkbox { + position: relative; + width: 20px; + height: 22px; + overflow: hidden; + margin-right: 8px; + .mat-checkbox-layout { + position: absolute; + top: 0; + left: 0; + } + } + .inbox-face { + height: 36px; + width: 36px; + border-radius: 50%; + } +} + +[dir=rtl] { + .inbox-sidenav .mat-list-item-content .mat-icon { + margin-right: 0; + margin-left: 8px; + } + .messages-wrap { + .mail-checkbox { + margin-right: 0; + margin-left: .5rem; + .mat-checkbox-inner-container { + margin-left: 0; + } + } + } +} + +@media (max-width: 959px) { + .inbox-sidenav { + background: inherit !important; + } +} \ No newline at end of file diff --git a/src/assets/styles/scss/views/_landing.scss b/src/assets/styles/scss/views/_landing.scss new file mode 100644 index 00000000..59223bfa --- /dev/null +++ b/src/assets/styles/scss/views/_landing.scss @@ -0,0 +1,144 @@ +/*! + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +/* + Only Required if you want to use Angular Landing + (https://themeforest.net/item/angular-landing-material-design-angular-app-landing-page/21198258) +*/ + +body.landing { + .h1, + .h2, + .h3, + .h4, + .h5, + .h6, + h1, + h2, + h3, + h4, + h5, + h6 { + margin-bottom: .5rem; + font-weight: 400; + line-height: 1.1; + color: inherit; + } + .h1, + h1 { + font-size: 2rem; + } + .h2, + h2 { + font-size: 1.75rem; + } + .h3, + h3 { + font-size: 1.5rem; + } + .h4, + h4 { + font-size: 1.25rem; + } + .h5, + h5 { + font-size: 1rem; + } + .h6, + h6 { + font-size: .875rem; + } + .container { + max-width: 1170px; + margin: 0 auto; + padding: 0 15px; + } + .section-header h2 { + font-size: 32px; + font-weight: 700; + margin: 0 0 16px; + } +} +@-webkit-keyframes slideDown { + 0% { top: -60px } + 100% { top: 0px } +} +@keyframes slideDown { + 0% { top: -60px } + 100% { top: 0px } +} +.section-padding { + padding: 80px 0; +} + +.home-section { + padding: 80px 0; +} +.home-section mat-card:hover { + box-shadow: 0 10px 16px rgba(0, 0, 0, 0.2); +} + +.section-header { + margin: 0 0 2rem; +} + +.section-header h2 { + font-size: 32px; + font-weight: 700; + margin: 0 0 16px; +} +.section-header p { + font-size: 16px; + max-width: 36rem; + margin: 0; +} + +.home-section-action { + padding: 2.5rem 0 0; +} + +[mat-card-icon] { + font-size: 64px !important; + height: 64px !important; + width: 64px !important; + transition: all .3s ease; +} +.home-fancy-card { + border-top: 2px solid #212121; + transition: all .3s ease; +} + +.home-fancy-card:hover { + border-top: 2px solid #3f51b5; +} +.home-fancy-card:hover [mat-card-icon] { + color: #3f51b5; +} +.home-fancy-card .description { + font-size: 15px; + color: #616161; +} + +/*------ Carousel -------*/ +.ngucarousel { + position: relative; + max-width: 1100px; + margin: 0 auto; +} +.ngucarousel-inner { + padding: 8px 0 18px; +} +.ngucarousel .carousel-left, +.ngucarousel .carousel-right { + position: absolute; + top: calc(50% - 28px); + background: #ffffff !important; + color: rgba(0, 0, 0, .87); +} +.ngucarousel .carousel-left { + left: -20px; +} +.ngucarousel .carousel-right { + right: -20px; +} diff --git a/src/assets/styles/scss/views/_popover.scss b/src/assets/styles/scss/views/_popover.scss new file mode 100644 index 00000000..6d1603c0 --- /dev/null +++ b/src/assets/styles/scss/views/_popover.scss @@ -0,0 +1,18 @@ +/*! + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +.simple-popover { + padding: 16px; + background: white; + max-width: 400px; + line-height: 22px; + font-size: 13px; +} + +.simple-popover .title { + font-weight: bold; + font-size: 16px; + margin-bottom: 8px; + color: $headings-color; +} \ No newline at end of file diff --git a/src/assets/styles/scss/views/_pricings.scss b/src/assets/styles/scss/views/_pricings.scss new file mode 100644 index 00000000..3d04f415 --- /dev/null +++ b/src/assets/styles/scss/views/_pricings.scss @@ -0,0 +1,10 @@ +/*! + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +/*---- Plans & Pricings -----*/ +.plan-pricing .mat-list-item .mat-list-item-content { + display: inline-flex !important; + text-align: center; + font-size: .875rem !important; +} \ No newline at end of file diff --git a/src/assets/styles/scss/views/_profile.scss b/src/assets/styles/scss/views/_profile.scss new file mode 100644 index 00000000..68bc044e --- /dev/null +++ b/src/assets/styles/scss/views/_profile.scss @@ -0,0 +1,85 @@ +/*! + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +/*------- Profile ------*/ +.profile-sidebar { + box-sizing: border-box; +} +.profile-sidebar .propic { + width: 100%; + margin-bottom: 6px; +} +.profile-sidebar .propic img { + width: 50%; + height: auto; + border-radius: 50%; +} +.profile-sidebar .profile-title .main-title { + font-size: 1.5rem; +} +.profile-sidebar .profile-nav { + margin-left: -24px; + margin-right: -24px; +} +.profile-sidebar .profile-nav .mat-list-item .mat-icon { + margin-right: 8px; +} + +.timeline { + position: relative; + overflow: hidden; +} +.timeline::before { + content: ""; + position: absolute; + top: 40px; + bottom: 0; + left: 24px; + width: 1px; + background-color: rgba(0, 0, 0, .12); +} +.timeline .timeline-item { + width: 100%; + overflow: hidden; + margin-bottom: 24px; +} +.timeline .timeline-badge { + float: left; + position: relative; + margin-right: 30px; + height: 48px; + width: 48px; + border-radius: 50%; + overflow: hidden; +} +.timeline .timeline-badge img { + max-width: 100%; +} +.timeline .timeline-badge .icon-badge { + text-align: center; + width: 48px; + height: 48px; + line-height: 48px; +} +.timeline .timeline-body-top { + margin-bottom: 8px; +} +.timeline .timeline-body { + float: right; + width: calc(100% - 78px); +} +.timeline .timeline-body-content img { + border-radius: 4px; + max-width: 100%; +} +.timeline .timeline-body-content > :last-child { + margin-bottom: 8px; +} + +[dir=rtl] { + .profile-sidebar .profile-nav .mat-list-item .mat-icon { + margin-right: 0; + margin-left: 8px; + } +} \ No newline at end of file diff --git a/src/assets/styles/scss/views/_sessions.scss b/src/assets/styles/scss/views/_sessions.scss new file mode 100644 index 00000000..286c3582 --- /dev/null +++ b/src/assets/styles/scss/views/_sessions.scss @@ -0,0 +1,41 @@ +/*! + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +/*---- Session (Sign in, sign up, forgot, lockscreen) -----*/ +.page-wrap { + display: flex; + align-items: center; + padding: 40px 1rem; + height: 100%; +} +.session-progress { + position: relative; + bottom: -4px; + z-index: 9999; + border-top-left-radius: 4px; + border-top-right-radius: 4px; +} +.session-form-hold { + width: 100%; + max-width: 400px; + margin: 0 auto; +} +.session-form-hold > .mat-card { + margin: 0; +} +.session-lockscreen { + max-width: 320px; +} +.lockscreen-user { + text-align: center; + margin-bottom: 1rem; + display: flow-root; + overflow: hidden; +} +.lockscreen-face { + max-width: 72px; + max-height: 72px; + border-radius: 50%; + margin-bottom: .5rem; +} \ No newline at end of file diff --git a/src/assets/styles/scss/views/_shop.scss b/src/assets/styles/scss/views/_shop.scss new file mode 100644 index 00000000..7c90d547 --- /dev/null +++ b/src/assets/styles/scss/views/_shop.scss @@ -0,0 +1,396 @@ +/*! + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +.product-rating { + display: flex; + align-items: center; + margin-left: -5px; +} +h1.title { + font-size: 1.8rem; + margin: 0 0 1rem; +} +.p-line { + font-size: .813rem; + margin-bottom: 4px; +} +.option-label { + margin-bottom: 6px; + display: block; +} +.product-search, +.quantity-input { + border: 0; + height: 32px; + border-radius: 2px; + padding-left: 8px; + font-size: 14px; + width: 260px; + box-shadow: 0px 2px 1px -1px rgba(0, 0, 0, 0.12), 0px 1px 1px 0px rgba(0, 0, 0, 0.08), 0px 1px 3px 0px rgba(0, 0, 0, 0); + transition: all $transitionDuration $transitionTiming; + &:focus { + outline: none; + box-shadow: 0px 2px 1px -1px rgba(0, 0, 0, 0.2), 0px 1px 1px 0px rgba(0, 0, 0, 0.14), 0px 1px 3px 0px rgba(0, 0, 0, 0.12) + } +} +.quantity-input { + width: 80px; +} + +.shop-wrap { + margin: 1rem .33rem .33rem; + overflow: hidden; + min-height: 750px; +} +.shop-sidebar { + width: 15rem; +} +.shop-filters-wrap { + margin-top: 7px; + padding: 0 .5rem 0 .333rem; + .product-categories { + list-style: none; + margin: 0; + padding: 0; + li { + cursor: pointer; + padding: 5px; + text-transform: capitalize; + } + } +} + + +.shop-top-toolbar { + margin-bottom: .5rem; + padding: 0 8px; +} +.product-search-wrap { + .product-search { + width: 260px; + margin: 6px 0 0; + } +} + + + +.product-container { + display: flex; + flex-wrap: wrap; + margin: 0; + min-height: 750px; + .products-pagination { + width: 100%; + text-align: center; + margin: 1.5rem 0; + align-self: flex-end; + justify-content: flex-end; + ul { + padding: 0; + margin: 0; + } + .ngx-pagination .current { + background: #e6e6e6; + color: rgba(0,0,0,.87) + } + } + + .product-wrap { + width: 33.333%; + float: left; + .product-inner { + display: flex; + flex-direction: column; + position: relative; + border-radius: 4px; + box-shadow: 0 3px 1px -2px rgba(0,0,0,.2), 0 2px 2px 0 rgba(0,0,0,.14), 0 1px 5px 0 rgba(0,0,0,.12); + overflow: hidden; + margin: 8px; + transition: all .3s $transitionTiming; + &:hover { + box-shadow: 0px 3px 5px -1px rgba(0, 0, 0, 0.2), 0px 6px 10px 0px rgba(0, 0, 0, 0.14), 0px 1px 18px 0px rgba(0, 0, 0, 0.12); + } + .product-badge { + color: #fff; + text-align: center; + position: absolute; + top: 20px; + left: 20px; + display: flex; + z-index: 100; + height: 64px; + width: 64px; + border-radius: 50%; + justify-content: center; + align-items: center; + padding: 8px; + transform: rotate(-30deg); + span { + font-weight: 500; + line-height: 1.1; + } + } + .featured-img { + display: flex; + flex-direction: column; + cursor: pointer; + min-height: 180px; + } + .info-wrap { + display: flex; + flex-direction: column; + .main-info { + width: 100%; + margin-bottom: 1rem; + min-height: 150px; + .title { + margin: 0; + font-size: 1.2rem; + font-weight: 500; + margin-bottom: 4px; + cursor: pointer; + &:active { + outline: none; + } + } + } + .actions { + width: 100%; + display: flex; + flex-direction: row; + div { + flex: 50%; + display: flex; + } + .price-rating { + flex-direction: column; + justify-content: flex-start; + .price { + align-items: center; + span { font-size: 1.2rem; margin-right: 4px; } + } + } + .add-to-cart { + align-items: center; + } + } + } + + + ul { + margin: 0; + padding: 0 0 0 8px; + list-style: none; + li { + margin-bottom: 4px; + display: flex; + .mat-icon { + margin-right: 8px; + } + } + } + .more-info-wrap { + display: none; + } + } + } +} + +.product-details-wrap { + .gallery-photo { + min-height: 400px; + width: calc(100% - 80px); + float: left; + padding-right: 1rem; + margin-bottom: 1rem; + img { + width: 100%; + height: auto; + border-radius: 4px; + } + } + .gallery-thumbnails { + width: 80px; + float: left; + padding-right: .667rem; + img { + width: 100%; + border-radius: 2px; + display: inline-block; + opacity: 1; + cursor: pointer; + transition: all .3s ease-in; + &.active { + opacity: .6; + } + } + } +} + + +.cart-table{ + width: 100%; + border-collapse: separate; + border-radius: 2px; + margin-bottom: 1.5rem; + border-spacing: 0; + .cart-thumbnail { + height: 44px; + width: auto; + border-radius: 2px; + } + thead { + tr { + border-collapse: separate; + background: rgba(0, 0, 0, 0.03); + th { + border-collapse: separate; + text-align: left; + padding: 1rem; + font-weight: 500; + letter-spacing: .1px; + } + } + } + tbody { + tr { + td { + text-align: left; + padding: 16px; + } + } + } +} + +@media (max-width: 1100px) { + .product-container { + .product-wrap { + width: 50%; + } + } +} +@media (max-width: 960px) { + .shop-filters-wrap { + margin: 0; + padding: 0; + } + .shop-sidebar { + background: #fff; + } +} +@media (max-width: 680px) { + .product-container { + .product-wrap { + width: 100%; + } + } + + .product-details-wrap { + .gallery-photo { + min-height: auto; + } + } + + + /* Force table to not be like tables anymore */ + table, thead, tbody, th, td, tr { + display: block; + } + + /* Hide table headers (but not display: none;, for accessibility) */ + thead tr { + position: absolute; + top: -9999px; + left: -9999px; + } + + tr { border-bottom: 1px solid rgba(0, 0, 0, 0.1); } + tr:last-child { border-bottom: none; } + + td { + /* Behave like a "row" */ + border: none; + border-bottom: 1px solid rgba(0, 0, 0, 0.03); + position: relative; + padding-left: 50%; + } + + td:before { + /* Now like a table header */ + position: absolute; + /* Top/left values mimic padding */ + top: 6px; + left: 6px; + width: 45%; + padding-right: 10px; + white-space: nowrap; + } +} + +@media (min-width: 1100px) { + .product-container.list-view { + .product-wrap { + width: 100%; + .product-inner { + flex-direction: row; + } + padding-right: 0; + .featured-img { + flex: 3 0; + img { + width: 100%; + max-width: 100%; + min-height: 100%; + } + } + .info-wrap { + flex-direction: column; + flex: 2 0; + .actions { + align-self: flex-end; + justify-content: flex-end; + margin-top: auto; + } + } + .more-info-wrap { + display: flex; + align-items: stretch; + flex: 2 0; + position: relative; + .more-info { + width: 100%; + padding-left: 1rem; + border-left: 1px solid #e6e6e6; + } + } + } + } +} + +[dir=rtl] { + .product-search, + .quantity-input { + padding-right: 8px; + } + .product-container.list-view .product-wrap .more-info-wrap .more-info { + border: 0; + padding-left: 0; + } + .product-container .product-wrap .product-inner .product-badge { + left: auto; + right: 20px; + } + .product-container .product-wrap .product-inner ul li .mat-icon { + margin-right: 0; + margin-left: 8px; + } + .product-details-wrap { + .gallery-photo { + padding: 0; + } + .product-content-wrap { + padding: 0 1rem; + } + } +} \ No newline at end of file diff --git a/src/assets/styles/scss/views/_users.scss b/src/assets/styles/scss/views/_users.scss new file mode 100644 index 00000000..6a7451bf --- /dev/null +++ b/src/assets/styles/scss/views/_users.scss @@ -0,0 +1,16 @@ +/*! + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +/*---- Users -----*/ +.user-card .card-title-text { + padding: .5rem 1.5rem !important; + align-items: center; +} +.user-card .user-details > p:last-child { + margin-bottom: 0; +} +.user-card .user-details .mat-icon { + vertical-align: top; + margin-right: .5rem; +} \ No newline at end of file diff --git a/src/assets/textures/blank.png b/src/assets/textures/blank.png new file mode 100644 index 00000000..889a2158 Binary files /dev/null and b/src/assets/textures/blank.png differ diff --git a/src/assets/textures/invisible.png b/src/assets/textures/invisible.png new file mode 100644 index 00000000..889a2158 Binary files /dev/null and b/src/assets/textures/invisible.png differ diff --git a/src/assets/textures/terrain/grasslight-big.jpg b/src/assets/textures/terrain/grasslight-big.jpg new file mode 100644 index 00000000..ed6c7f85 Binary files /dev/null and b/src/assets/textures/terrain/grasslight-big.jpg differ diff --git a/src/assets/top-down-tree.png b/src/assets/top-down-tree.png new file mode 100644 index 00000000..cf3b9c9f Binary files /dev/null and b/src/assets/top-down-tree.png differ diff --git a/src/assets/traffic-icons/traffic-lights.png b/src/assets/traffic-icons/traffic-lights.png new file mode 100644 index 00000000..0ea3ee3b Binary files /dev/null and b/src/assets/traffic-icons/traffic-lights.png differ diff --git a/src/assets/unknown-file-icon.png b/src/assets/unknown-file-icon.png new file mode 100644 index 00000000..1b6ef73a Binary files /dev/null and b/src/assets/unknown-file-icon.png differ diff --git a/src/assets/uv_grid_opengl.jpg b/src/assets/uv_grid_opengl.jpg new file mode 100644 index 00000000..88500050 Binary files /dev/null and b/src/assets/uv_grid_opengl.jpg differ diff --git a/src/assets/vehicle-placeholder.png b/src/assets/vehicle-placeholder.png new file mode 100644 index 00000000..39537b12 Binary files /dev/null and b/src/assets/vehicle-placeholder.png differ diff --git a/src/assets/vehicles/AudiA3_blue_147kW.png b/src/assets/vehicles/AudiA3_blue_147kW.png new file mode 100644 index 00000000..f8516271 Binary files /dev/null and b/src/assets/vehicles/AudiA3_blue_147kW.png differ diff --git a/src/assets/vehicles/bmw_525i_1992_white.png b/src/assets/vehicles/bmw_525i_1992_white.png new file mode 100644 index 00000000..f8516271 Binary files /dev/null and b/src/assets/vehicles/bmw_525i_1992_white.png differ diff --git a/src/assets/vehicles/default.png b/src/assets/vehicles/default.png new file mode 100644 index 00000000..4c367786 Binary files /dev/null and b/src/assets/vehicles/default.png differ diff --git a/src/assets/vehicles/vehicle_1.png b/src/assets/vehicles/vehicle_1.png new file mode 100644 index 00000000..4c367786 Binary files /dev/null and b/src/assets/vehicles/vehicle_1.png differ diff --git a/src/assets/vehicles/vehicle_2.png b/src/assets/vehicles/vehicle_2.png new file mode 100644 index 00000000..f8516271 Binary files /dev/null and b/src/assets/vehicles/vehicle_2.png differ diff --git a/src/browserslist b/src/browserslist new file mode 100644 index 00000000..37371cb0 --- /dev/null +++ b/src/browserslist @@ -0,0 +1,11 @@ +# This file is currently used by autoprefixer to adjust CSS to support the below specified browsers +# For additional information regarding the format and rule options, please see: +# https://github.com/browserslist/browserslist#queries +# +# For IE 9-11 support, please remove 'not' from the last line of the file and adjust as needed + +> 0.5% +last 2 versions +Firefox ESR +not dead +not IE 9-11 \ No newline at end of file diff --git a/src/environments/.gitignore b/src/environments/.gitignore new file mode 100644 index 00000000..d6a75b93 --- /dev/null +++ b/src/environments/.gitignore @@ -0,0 +1 @@ +environment.* \ No newline at end of file diff --git a/src/favicon.ico b/src/favicon.ico new file mode 100644 index 00000000..e74af5b0 Binary files /dev/null and b/src/favicon.ico differ diff --git a/src/index.html b/src/index.html new file mode 100644 index 00000000..a95be82a --- /dev/null +++ b/src/index.html @@ -0,0 +1,35 @@ + + + + + + + + Truevision + + + + + + + + + + + +
    +
    +
    +
    +
    +
    +
    + + + diff --git a/src/karma.conf.js b/src/karma.conf.js new file mode 100644 index 00000000..4a317a14 --- /dev/null +++ b/src/karma.conf.js @@ -0,0 +1,42 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +// Karma configuration file, see link for more information +// https://karma-runner.github.io/1.0/config/configuration-file.html + +module.exports = function (config) { + config.set({ + basePath: '', + frameworks: ['jasmine', '@angular-devkit/build-angular'], + plugins: [ + require('karma-jasmine'), + require('karma-chrome-launcher'), + require('karma-jasmine-html-reporter'), + require('karma-coverage-istanbul-reporter'), + require('@angular-devkit/build-angular/plugins/karma') + ], + client: { + clearContext: false // leave Jasmine Spec Runner output visible in browser + }, + coverageIstanbulReporter: { + dir: require('path').join(__dirname, '../coverage/true-label'), + reports: ['html', 'lcovonly', 'text-summary'], + fixWebpackSourcePaths: true + }, + reporters: ['progress', 'kjhtml'], + port: 9876, + colors: true, + logLevel: config.LOG_INFO, + autoWatch: true, + browsers: ['ChromeDebugging'], + singleRun: false, + customLaunchers: { + ChromeDebugging: { + base: 'Chrome', + flags: ['--remote-debugging-port=9333'] + } + }, + restartOnFileChange: true + }); +}; diff --git a/src/main.ts b/src/main.ts new file mode 100644 index 00000000..064a6f1d --- /dev/null +++ b/src/main.ts @@ -0,0 +1,17 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +import 'hammerjs'; +import { enableProdMode } from '@angular/core'; +import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; + +import { AppModule } from './app/app.module'; +import { environment } from './environments/environment'; + +if (environment.production) { + enableProdMode(); +} + +platformBrowserDynamic().bootstrapModule(AppModule) + .catch(err => console.error(err)); diff --git a/src/polyfills.ts b/src/polyfills.ts new file mode 100644 index 00000000..cf75be43 --- /dev/null +++ b/src/polyfills.ts @@ -0,0 +1,67 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +/** + * This file includes polyfills needed by Angular and is loaded before the app. + * You can add your own extra polyfills to this file. + * + * This file is divided into 2 sections: + * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers. + * 2. Application imports. Files imported after ZoneJS that should be loaded before your main + * file. + * + * The current setup is for so-called "evergreen" browsers; the last versions of browsers that + * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera), + * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile. + * + * Learn more in https://angular.io/guide/browser-support + */ + +/*************************************************************************************************** + * BROWSER POLYFILLS + */ + +/** IE10 and IE11 requires the following for NgClass support on SVG elements */ +// import 'classlist.js'; // Run `npm install --save classlist.js`. + +/** + * Web Animations `@angular/platform-browser/animations` + * Only required if AnimationBuilder is used within the application and using IE/Edge or Safari. + * Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0). + */ +// import 'web-animations-js'; // Run `npm install --save web-animations-js`. + +/** + * By default, zone.js will patch all possible macroTask and DomEvents + * user can disable parts of macroTask/DomEvents patch by setting following flags + * because those flags need to be set before `zone.js` being loaded, and webpack + * will put import in the top of bundle, so user need to create a separate file + * in this directory (for example: zone-flags.ts), and put the following flags + * into that file, and then add the following code before importing zone.js. + * import './zone-flags.ts'; + * + * The flags allowed in zone-flags.ts are listed here. + * + * The following flags will work for all browsers. + * + * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame + * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick + * (window as any).__zone_symbol__BLACK_LISTED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames + * + * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js + * with the following flag, it will bypass `zone.js` patch for IE/Edge + * + * (window as any).__Zone_enable_cross_context_check = true; + * + */ + +/*************************************************************************************************** + * Zone JS is required by default for Angular itself. + */ +import 'zone.js/dist/zone'; // Included with Angular CLI. + + +/*************************************************************************************************** + * APPLICATION IMPORTS + */ diff --git a/src/splash.html b/src/splash.html new file mode 100644 index 00000000..167aed96 --- /dev/null +++ b/src/splash.html @@ -0,0 +1,14 @@ + + + + + +
    + + +

    2022.3 Beta

    +
    + + \ No newline at end of file diff --git a/src/styles.css b/src/styles.css new file mode 100644 index 00000000..48df0b67 --- /dev/null +++ b/src/styles.css @@ -0,0 +1,147 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +/* You can add global styles to this file, and also import other style files */ + +html, +body { + height: 100%; +} + +body { + margin: 0; + font-family: Roboto, "Helvetica Neue", sans-serif; +} + +.mat-grid-tile .mat-figure { + align-items: normal !important; + justify-content: normal !important; +} + +mat-list-item.mat-list-item.mat-3-line:hover { + background: rgb(214, 214, 214); +} + +.mat-grid-tile .mat-figure .right-side-bar-container { + align-items: normal !important; + justify-content: normal !important; + padding: 10px 30px !important; +} + +.mat-expansion-panel-body { + padding: 16px 24px 16px 24px !important; +} + +.disable-select { + user-select: none; + /* supported by Chrome and Opera */ + -webkit-user-select: none; + /* Safari */ + -khtml-user-select: none; + /* Konqueror HTML */ + -moz-user-select: none; + /* Firefox */ + -ms-user-select: none; + /* Internet Explorer/Edge */ +} + +.green-snackbar { + background: green !important; + color: white !important; +} + +.red-snackbar { + background: red !important; + color: white !important; +} + +.mat-tv-dialog-actions { + padding: 16px 16px !important; + margin: 0px !important; +} + +.mat-tv-dialog-content { + margin: 0px !important; + padding: 16px 16px !important; +} + +.mat-tv-dialog-title { + margin: 0 !important; + padding: 4px 16px !important; + background: white; + color: #545454; + font-size: 1em; +} + +.mat-dialog-container { + padding: 0px 0px 0px 0px !important; + border-radius: 1px !important; +} + +.mat-tv-dialog-actions button { + margin-left: 10px; +} + +.tv-field { + margin-bottom: 8px; + display: flex; + margin: 4px 0px; + min-height: 24px; + align-items: center; + flex-direction: row; + flex-wrap: nowrap; + position: relative; + min-width: 0; + border: 0 solid #232e30; +} + +.tv-field-label { + color: #b1b8ba; + width: 30%; + margin: 0; + display: inline-block; + box-sizing: border-box; + vertical-align: middle; + transition: opacity 100ms; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + user-select: none; + border: 0 solid #232e30; + font-size: 0.850em; +} + +.tv-field-text-input { + color: #b1b8ba; + max-width: 70%; + flex: 1; + margin: 0 0 0 6px; + display: inline-block; + border: 1px solid #293538; + border-radius: 2px; + box-sizing: border-box; + min-height: 24px; + height: 24px; + background-color: #333333; + vertical-align: top; + transition: color 100ms, background-color 100ms, box-shadow 100ms; + position: relative; +} + +.tv-field-text-input>input { + width: 100%; + padding: 0 6px; + line-height: 1; + color: inherit; + background: transparent; + border: none; + outline: none; + box-shadow: none; + font-size: 0.850em; +} + +.mat-tab-body-content { + height: 100% !important; + overflow: hidden !important; +} \ No newline at end of file diff --git a/src/test.ts b/src/test.ts new file mode 100644 index 00000000..3590ce97 --- /dev/null +++ b/src/test.ts @@ -0,0 +1,24 @@ +/* + * Copyright Truesense AI Solutions Pvt Ltd, All Rights Reserved. + */ + +// This file is required by karma.conf.js and loads recursively all the .spec and framework files + +import 'zone.js/dist/zone-testing'; +import { getTestBed } from '@angular/core/testing'; +import { + BrowserDynamicTestingModule, + platformBrowserDynamicTesting +} from '@angular/platform-browser-dynamic/testing'; + +declare const require: any; + +// First, initialize the Angular testing environment. +getTestBed().initTestEnvironment( + BrowserDynamicTestingModule, + platformBrowserDynamicTesting() +); +// Then we find all the tests. +const context = require.context('./', true, /\.spec\.ts$/); +// And load the modules. +context.keys().map(context); diff --git a/src/tsconfig.app.json b/src/tsconfig.app.json new file mode 100644 index 00000000..4f3430a0 --- /dev/null +++ b/src/tsconfig.app.json @@ -0,0 +1,13 @@ +{ + "extends": "../tsconfig.json", + "compilerOptions": { + "outDir": "../out-tsc/app", + "module": "esnext", + "baseUrl": "", + "types": [] + }, + "exclude": [ + "test.ts", + "**/*.spec.ts" + ] +} diff --git a/src/tsconfig.spec.json b/src/tsconfig.spec.json new file mode 100644 index 00000000..de773363 --- /dev/null +++ b/src/tsconfig.spec.json @@ -0,0 +1,18 @@ +{ + "extends": "../tsconfig.json", + "compilerOptions": { + "outDir": "../out-tsc/spec", + "types": [ + "jasmine", + "node" + ] + }, + "files": [ + "test.ts", + "polyfills.ts" + ], + "include": [ + "**/*.spec.ts", + "**/*.d.ts" + ] +} diff --git a/src/tslint.json b/src/tslint.json new file mode 100644 index 00000000..52e2c1a5 --- /dev/null +++ b/src/tslint.json @@ -0,0 +1,17 @@ +{ + "extends": "../tslint.json", + "rules": { + "directive-selector": [ + true, + "attribute", + "app", + "camelCase" + ], + "component-selector": [ + true, + "element", + "app", + "kebab-case" + ] + } +} diff --git a/src/vendor/Chart.min.js b/src/vendor/Chart.min.js new file mode 100755 index 00000000..0bdd5e53 --- /dev/null +++ b/src/vendor/Chart.min.js @@ -0,0 +1,14 @@ +/*! + * Chart.js + * http://chartjs.org/ + * Version: 2.1.6 + * + * Copyright 2016 Nick Downie + * Released under the MIT license + * https://github.com/chartjs/Chart.js/blob/master/LICENSE.md + */ +!function(t){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=t();else if("function"==typeof define&&define.amd)define([],t);else{var e;e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:this,e.Chart=t()}}(function(){return function t(e,a,i){function n(r,l){if(!a[r]){if(!e[r]){var s="function"==typeof require&&require;if(!l&&s)return s(r,!0);if(o)return o(r,!0);var d=new Error("Cannot find module '"+r+"'");throw d.code="MODULE_NOT_FOUND",d}var u=a[r]={exports:{}};e[r][0].call(u.exports,function(t){var a=e[r][1][t];return n(a?a:t)},u,u.exports,t,e,a,i)}return a[r].exports}for(var o="function"==typeof require&&require,r=0;re||t[3]&&t[3]<1?c(t,e):"rgb("+t[0]+", "+t[1]+", "+t[2]+")"}function c(t,e){return void 0===e&&(e=void 0!==t[3]?t[3]:1),"rgba("+t[0]+", "+t[1]+", "+t[2]+", "+e+")"}function h(t,e){if(1>e||t[3]&&t[3]<1)return f(t,e);var a=Math.round(t[0]/255*100),i=Math.round(t[1]/255*100),n=Math.round(t[2]/255*100);return"rgb("+a+"%, "+i+"%, "+n+"%)"}function f(t,e){var a=Math.round(t[0]/255*100),i=Math.round(t[1]/255*100),n=Math.round(t[2]/255*100);return"rgba("+a+"%, "+i+"%, "+n+"%, "+(e||t[3]||1)+")"}function g(t,e){return 1>e||t[3]&&t[3]<1?p(t,e):"hsl("+t[0]+", "+t[1]+"%, "+t[2]+"%)"}function p(t,e){return void 0===e&&(e=void 0!==t[3]?t[3]:1),"hsla("+t[0]+", "+t[1]+"%, "+t[2]+"%, "+e+")"}function m(t,e){return void 0===e&&(e=void 0!==t[3]?t[3]:1),"hwb("+t[0]+", "+t[1]+"%, "+t[2]+"%"+(void 0!==e&&1!==e?", "+e:"")+")"}function b(t){return k[t.slice(0,3)]}function v(t,e,a){return Math.min(Math.max(e,t),a)}function x(t){var e=t.toString(16).toUpperCase();return e.length<2?"0"+e:e}var y=t(6);e.exports={getRgba:i,getHsla:n,getRgb:r,getHsl:l,getHwb:o,getAlpha:s,hexString:d,rgbString:u,rgbaString:c,percentString:h,percentaString:f,hslString:g,hslaString:p,hwbString:m,keyword:b};var k={};for(var S in y)k[y[S]]=S},{6:6}],3:[function(t,e,a){var i=t(5),n=t(2),o=function(t){if(t instanceof o)return t;if(!(this instanceof o))return new o(t);this.values={rgb:[0,0,0],hsl:[0,0,0],hsv:[0,0,0],hwb:[0,0,0],cmyk:[0,0,0,0],alpha:1};var e;if("string"==typeof t)if(e=n.getRgba(t))this.setValues("rgb",e);else if(e=n.getHsla(t))this.setValues("hsl",e);else{if(!(e=n.getHwb(t)))throw new Error('Unable to parse color from string "'+t+'"');this.setValues("hwb",e)}else if("object"==typeof t)if(e=t,void 0!==e.r||void 0!==e.red)this.setValues("rgb",e);else if(void 0!==e.l||void 0!==e.lightness)this.setValues("hsl",e);else if(void 0!==e.v||void 0!==e.value)this.setValues("hsv",e);else if(void 0!==e.w||void 0!==e.whiteness)this.setValues("hwb",e);else{if(void 0===e.c&&void 0===e.cyan)throw new Error("Unable to parse color from object "+JSON.stringify(t));this.setValues("cmyk",e)}};o.prototype={rgb:function(){return this.setSpace("rgb",arguments)},hsl:function(){return this.setSpace("hsl",arguments)},hsv:function(){return this.setSpace("hsv",arguments)},hwb:function(){return this.setSpace("hwb",arguments)},cmyk:function(){return this.setSpace("cmyk",arguments)},rgbArray:function(){return this.values.rgb},hslArray:function(){return this.values.hsl},hsvArray:function(){return this.values.hsv},hwbArray:function(){var t=this.values;return 1!==t.alpha?t.hwb.concat([t.alpha]):t.hwb},cmykArray:function(){return this.values.cmyk},rgbaArray:function(){var t=this.values;return t.rgb.concat([t.alpha])},hslaArray:function(){var t=this.values;return t.hsl.concat([t.alpha])},alpha:function(t){return void 0===t?this.values.alpha:(this.setValues("alpha",t),this)},red:function(t){return this.setChannel("rgb",0,t)},green:function(t){return this.setChannel("rgb",1,t)},blue:function(t){return this.setChannel("rgb",2,t)},hue:function(t){return t&&(t%=360,t=0>t?360+t:t),this.setChannel("hsl",0,t)},saturation:function(t){return this.setChannel("hsl",1,t)},lightness:function(t){return this.setChannel("hsl",2,t)},saturationv:function(t){return this.setChannel("hsv",1,t)},whiteness:function(t){return this.setChannel("hwb",1,t)},blackness:function(t){return this.setChannel("hwb",2,t)},value:function(t){return this.setChannel("hsv",2,t)},cyan:function(t){return this.setChannel("cmyk",0,t)},magenta:function(t){return this.setChannel("cmyk",1,t)},yellow:function(t){return this.setChannel("cmyk",2,t)},black:function(t){return this.setChannel("cmyk",3,t)},hexString:function(){return n.hexString(this.values.rgb)},rgbString:function(){return n.rgbString(this.values.rgb,this.values.alpha)},rgbaString:function(){return n.rgbaString(this.values.rgb,this.values.alpha)},percentString:function(){return n.percentString(this.values.rgb,this.values.alpha)},hslString:function(){return n.hslString(this.values.hsl,this.values.alpha)},hslaString:function(){return n.hslaString(this.values.hsl,this.values.alpha)},hwbString:function(){return n.hwbString(this.values.hwb,this.values.alpha)},keyword:function(){return n.keyword(this.values.rgb,this.values.alpha)},rgbNumber:function(){var t=this.values.rgb;return t[0]<<16|t[1]<<8|t[2]},luminosity:function(){for(var t=this.values.rgb,e=[],a=0;a=i?i/12.92:Math.pow((i+.055)/1.055,2.4)}return.2126*e[0]+.7152*e[1]+.0722*e[2]},contrast:function(t){var e=this.luminosity(),a=t.luminosity();return e>a?(e+.05)/(a+.05):(a+.05)/(e+.05)},level:function(t){var e=this.contrast(t);return e>=7.1?"AAA":e>=4.5?"AA":""},dark:function(){var t=this.values.rgb,e=(299*t[0]+587*t[1]+114*t[2])/1e3;return 128>e},light:function(){return!this.dark()},negate:function(){for(var t=[],e=0;3>e;e++)t[e]=255-this.values.rgb[e];return this.setValues("rgb",t),this},lighten:function(t){var e=this.values.hsl;return e[2]+=e[2]*t,this.setValues("hsl",e),this},darken:function(t){var e=this.values.hsl;return e[2]-=e[2]*t,this.setValues("hsl",e),this},saturate:function(t){var e=this.values.hsl;return e[1]+=e[1]*t,this.setValues("hsl",e),this},desaturate:function(t){var e=this.values.hsl;return e[1]-=e[1]*t,this.setValues("hsl",e),this},whiten:function(t){var e=this.values.hwb;return e[1]+=e[1]*t,this.setValues("hwb",e),this},blacken:function(t){var e=this.values.hwb;return e[2]+=e[2]*t,this.setValues("hwb",e),this},greyscale:function(){var t=this.values.rgb,e=.3*t[0]+.59*t[1]+.11*t[2];return this.setValues("rgb",[e,e,e]),this},clearer:function(t){var e=this.values.alpha;return this.setValues("alpha",e-e*t),this},opaquer:function(t){var e=this.values.alpha;return this.setValues("alpha",e+e*t),this},rotate:function(t){var e=this.values.hsl,a=(e[0]+t)%360;return e[0]=0>a?360+a:a,this.setValues("hsl",e),this},mix:function(t,e){var a=this,i=t,n=void 0===e?.5:e,o=2*n-1,r=a.alpha()-i.alpha(),l=((o*r===-1?o:(o+r)/(1+o*r))+1)/2,s=1-l;return this.rgb(l*a.red()+s*i.red(),l*a.green()+s*i.green(),l*a.blue()+s*i.blue()).alpha(a.alpha()*n+i.alpha()*(1-n))},toJSON:function(){return this.rgb()},clone:function(){var t,e,a=new o,i=this.values,n=a.values;for(var r in i)i.hasOwnProperty(r)&&(t=i[r],e={}.toString.call(t),"[object Array]"===e?n[r]=t.slice(0):"[object Number]"===e?n[r]=t:console.error("unexpected color value:",t));return a}},o.prototype.spaces={rgb:["red","green","blue"],hsl:["hue","saturation","lightness"],hsv:["hue","saturation","value"],hwb:["hue","whiteness","blackness"],cmyk:["cyan","magenta","yellow","black"]},o.prototype.maxes={rgb:[255,255,255],hsl:[360,100,100],hsv:[360,100,100],hwb:[360,100,100],cmyk:[100,100,100,100]},o.prototype.getValues=function(t){for(var e=this.values,a={},i=0;ie&&(e+=360),i=(l+s)/2,a=s==l?0:.5>=i?d/(s+l):d/(2-s-l),[e,100*a,100*i]}function n(t){var e,a,i,n=t[0],o=t[1],r=t[2],l=Math.min(n,o,r),s=Math.max(n,o,r),d=s-l;return a=0==s?0:d/s*1e3/10,s==l?e=0:n==s?e=(o-r)/d:o==s?e=2+(r-n)/d:r==s&&(e=4+(n-o)/d),e=Math.min(60*e,360),0>e&&(e+=360),i=s/255*1e3/10,[e,a,i]}function o(t){var e=t[0],a=t[1],n=t[2],o=i(t)[0],r=1/255*Math.min(e,Math.min(a,n)),n=1-1/255*Math.max(e,Math.max(a,n));return[o,100*r,100*n]}function l(t){var e,a,i,n,o=t[0]/255,r=t[1]/255,l=t[2]/255;return n=Math.min(1-o,1-r,1-l),e=(1-o-n)/(1-n)||0,a=(1-r-n)/(1-n)||0,i=(1-l-n)/(1-n)||0,[100*e,100*a,100*i,100*n]}function s(t){return Q[JSON.stringify(t)]}function d(t){var e=t[0]/255,a=t[1]/255,i=t[2]/255;e=e>.04045?Math.pow((e+.055)/1.055,2.4):e/12.92,a=a>.04045?Math.pow((a+.055)/1.055,2.4):a/12.92,i=i>.04045?Math.pow((i+.055)/1.055,2.4):i/12.92;var n=.4124*e+.3576*a+.1805*i,o=.2126*e+.7152*a+.0722*i,r=.0193*e+.1192*a+.9505*i;return[100*n,100*o,100*r]}function u(t){var e,a,i,n=d(t),o=n[0],r=n[1],l=n[2];return o/=95.047,r/=100,l/=108.883,o=o>.008856?Math.pow(o,1/3):7.787*o+16/116,r=r>.008856?Math.pow(r,1/3):7.787*r+16/116,l=l>.008856?Math.pow(l,1/3):7.787*l+16/116,e=116*r-16,a=500*(o-r),i=200*(r-l),[e,a,i]}function c(t){return W(u(t))}function h(t){var e,a,i,n,o,r=t[0]/360,l=t[1]/100,s=t[2]/100;if(0==l)return o=255*s,[o,o,o];a=.5>s?s*(1+l):s+l-s*l,e=2*s-a,n=[0,0,0];for(var d=0;3>d;d++)i=r+1/3*-(d-1),0>i&&i++,i>1&&i--,o=1>6*i?e+6*(a-e)*i:1>2*i?a:2>3*i?e+(a-e)*(2/3-i)*6:e,n[d]=255*o;return n}function f(t){var e,a,i=t[0],n=t[1]/100,o=t[2]/100;return 0===o?[0,0,0]:(o*=2,n*=1>=o?o:2-o,a=(o+n)/2,e=2*n/(o+n),[i,100*e,100*a])}function p(t){return o(h(t))}function m(t){return l(h(t))}function v(t){return s(h(t))}function x(t){var e=t[0]/60,a=t[1]/100,i=t[2]/100,n=Math.floor(e)%6,o=e-Math.floor(e),r=255*i*(1-a),l=255*i*(1-a*o),s=255*i*(1-a*(1-o)),i=255*i;switch(n){case 0:return[i,s,r];case 1:return[l,i,r];case 2:return[r,i,s];case 3:return[r,l,i];case 4:return[s,r,i];case 5:return[i,r,l]}}function y(t){var e,a,i=t[0],n=t[1]/100,o=t[2]/100;return a=(2-n)*o,e=n*o,e/=1>=a?a:2-a,e=e||0,a/=2,[i,100*e,100*a]}function k(t){return o(x(t))}function S(t){return l(x(t))}function w(t){return s(x(t))}function C(t){var e,a,i,n,o=t[0]/360,l=t[1]/100,s=t[2]/100,d=l+s;switch(d>1&&(l/=d,s/=d),e=Math.floor(6*o),a=1-s,i=6*o-e,0!=(1&e)&&(i=1-i),n=l+i*(a-l),e){default:case 6:case 0:r=a,g=n,b=l;break;case 1:r=n,g=a,b=l;break;case 2:r=l,g=a,b=n;break;case 3:r=l,g=n,b=a;break;case 4:r=n,g=l,b=a;break;case 5:r=a,g=l,b=n}return[255*r,255*g,255*b]}function M(t){return i(C(t))}function D(t){return n(C(t))}function A(t){return l(C(t))}function I(t){return s(C(t))}function F(t){var e,a,i,n=t[0]/100,o=t[1]/100,r=t[2]/100,l=t[3]/100;return e=1-Math.min(1,n*(1-l)+l),a=1-Math.min(1,o*(1-l)+l),i=1-Math.min(1,r*(1-l)+l),[255*e,255*a,255*i]}function T(t){return i(F(t))}function P(t){return n(F(t))}function _(t){return o(F(t))}function R(t){return s(F(t))}function V(t){var e,a,i,n=t[0]/100,o=t[1]/100,r=t[2]/100;return e=3.2406*n+-1.5372*o+r*-.4986,a=n*-.9689+1.8758*o+.0415*r,i=.0557*n+o*-.204+1.057*r,e=e>.0031308?1.055*Math.pow(e,1/2.4)-.055:e=12.92*e,a=a>.0031308?1.055*Math.pow(a,1/2.4)-.055:a=12.92*a,i=i>.0031308?1.055*Math.pow(i,1/2.4)-.055:i=12.92*i,e=Math.min(Math.max(0,e),1),a=Math.min(Math.max(0,a),1),i=Math.min(Math.max(0,i),1),[255*e,255*a,255*i]}function O(t){var e,a,i,n=t[0],o=t[1],r=t[2];return n/=95.047,o/=100,r/=108.883,n=n>.008856?Math.pow(n,1/3):7.787*n+16/116,o=o>.008856?Math.pow(o,1/3):7.787*o+16/116,r=r>.008856?Math.pow(r,1/3):7.787*r+16/116,e=116*o-16,a=500*(n-o),i=200*(o-r),[e,a,i]}function L(t){return W(O(t))}function B(t){var e,a,i,n,o=t[0],r=t[1],l=t[2];return 8>=o?(a=100*o/903.3,n=7.787*(a/100)+16/116):(a=100*Math.pow((o+16)/116,3),n=Math.pow(a/100,1/3)),e=.008856>=e/95.047?e=95.047*(r/500+n-16/116)/7.787:95.047*Math.pow(r/500+n,3),i=.008859>=i/108.883?i=108.883*(n-l/200-16/116)/7.787:108.883*Math.pow(n-l/200,3),[e,a,i]}function W(t){var e,a,i,n=t[0],o=t[1],r=t[2];return e=Math.atan2(r,o),a=360*e/2/Math.PI,0>a&&(a+=360),i=Math.sqrt(o*o+r*r),[n,i,a]}function z(t){return V(B(t))}function H(t){var e,a,i,n=t[0],o=t[1],r=t[2];return i=r/360*2*Math.PI,e=o*Math.cos(i),a=o*Math.sin(i),[n,e,a]}function N(t){return B(H(t))}function E(t){return z(H(t))}function U(t){return G[t]}function q(t){return i(U(t))}function j(t){return n(U(t))}function Y(t){return o(U(t))}function J(t){return l(U(t))}function X(t){return u(U(t))}function Z(t){return d(U(t))}e.exports={rgb2hsl:i,rgb2hsv:n,rgb2hwb:o,rgb2cmyk:l,rgb2keyword:s,rgb2xyz:d,rgb2lab:u,rgb2lch:c,hsl2rgb:h,hsl2hsv:f,hsl2hwb:p,hsl2cmyk:m,hsl2keyword:v,hsv2rgb:x,hsv2hsl:y,hsv2hwb:k,hsv2cmyk:S,hsv2keyword:w,hwb2rgb:C,hwb2hsl:M,hwb2hsv:D,hwb2cmyk:A,hwb2keyword:I,cmyk2rgb:F,cmyk2hsl:T,cmyk2hsv:P,cmyk2hwb:_,cmyk2keyword:R,keyword2rgb:U,keyword2hsl:q,keyword2hsv:j,keyword2hwb:Y,keyword2cmyk:J,keyword2lab:X,keyword2xyz:Z,xyz2rgb:V,xyz2lab:O,xyz2lch:L,lab2xyz:B,lab2rgb:z,lab2lch:W,lch2lab:H,lch2xyz:N,lch2rgb:E};var G={aliceblue:[240,248,255],antiquewhite:[250,235,215],aqua:[0,255,255],aquamarine:[127,255,212],azure:[240,255,255],beige:[245,245,220],bisque:[255,228,196],black:[0,0,0],blanchedalmond:[255,235,205],blue:[0,0,255],blueviolet:[138,43,226],brown:[165,42,42],burlywood:[222,184,135],cadetblue:[95,158,160],chartreuse:[127,255,0],chocolate:[210,105,30],coral:[255,127,80],cornflowerblue:[100,149,237],cornsilk:[255,248,220],crimson:[220,20,60],cyan:[0,255,255],darkblue:[0,0,139],darkcyan:[0,139,139],darkgoldenrod:[184,134,11],darkgray:[169,169,169],darkgreen:[0,100,0],darkgrey:[169,169,169],darkkhaki:[189,183,107],darkmagenta:[139,0,139],darkolivegreen:[85,107,47],darkorange:[255,140,0],darkorchid:[153,50,204],darkred:[139,0,0],darksalmon:[233,150,122],darkseagreen:[143,188,143],darkslateblue:[72,61,139],darkslategray:[47,79,79],darkslategrey:[47,79,79],darkturquoise:[0,206,209],darkviolet:[148,0,211],deeppink:[255,20,147],deepskyblue:[0,191,255],dimgray:[105,105,105],dimgrey:[105,105,105],dodgerblue:[30,144,255],firebrick:[178,34,34],floralwhite:[255,250,240],forestgreen:[34,139,34],fuchsia:[255,0,255],gainsboro:[220,220,220],ghostwhite:[248,248,255],gold:[255,215,0],goldenrod:[218,165,32],gray:[128,128,128],green:[0,128,0],greenyellow:[173,255,47],grey:[128,128,128],honeydew:[240,255,240],hotpink:[255,105,180],indianred:[205,92,92],indigo:[75,0,130],ivory:[255,255,240],khaki:[240,230,140],lavender:[230,230,250],lavenderblush:[255,240,245],lawngreen:[124,252,0],lemonchiffon:[255,250,205],lightblue:[173,216,230],lightcoral:[240,128,128],lightcyan:[224,255,255],lightgoldenrodyellow:[250,250,210],lightgray:[211,211,211],lightgreen:[144,238,144],lightgrey:[211,211,211],lightpink:[255,182,193],lightsalmon:[255,160,122],lightseagreen:[32,178,170],lightskyblue:[135,206,250],lightslategray:[119,136,153],lightslategrey:[119,136,153],lightsteelblue:[176,196,222],lightyellow:[255,255,224],lime:[0,255,0],limegreen:[50,205,50],linen:[250,240,230],magenta:[255,0,255],maroon:[128,0,0],mediumaquamarine:[102,205,170],mediumblue:[0,0,205],mediumorchid:[186,85,211],mediumpurple:[147,112,219],mediumseagreen:[60,179,113],mediumslateblue:[123,104,238],mediumspringgreen:[0,250,154],mediumturquoise:[72,209,204],mediumvioletred:[199,21,133],midnightblue:[25,25,112],mintcream:[245,255,250],mistyrose:[255,228,225],moccasin:[255,228,181],navajowhite:[255,222,173],navy:[0,0,128],oldlace:[253,245,230],olive:[128,128,0],olivedrab:[107,142,35],orange:[255,165,0],orangered:[255,69,0],orchid:[218,112,214],palegoldenrod:[238,232,170],palegreen:[152,251,152],paleturquoise:[175,238,238],palevioletred:[219,112,147],papayawhip:[255,239,213],peachpuff:[255,218,185],peru:[205,133,63],pink:[255,192,203],plum:[221,160,221],powderblue:[176,224,230],purple:[128,0,128],rebeccapurple:[102,51,153],red:[255,0,0],rosybrown:[188,143,143],royalblue:[65,105,225],saddlebrown:[139,69,19],salmon:[250,128,114],sandybrown:[244,164,96],seagreen:[46,139,87],seashell:[255,245,238],sienna:[160,82,45],silver:[192,192,192],skyblue:[135,206,235],slateblue:[106,90,205],slategray:[112,128,144],slategrey:[112,128,144],snow:[255,250,250],springgreen:[0,255,127],steelblue:[70,130,180],tan:[210,180,140],teal:[0,128,128],thistle:[216,191,216],tomato:[255,99,71],turquoise:[64,224,208],violet:[238,130,238],wheat:[245,222,179],white:[255,255,255],whitesmoke:[245,245,245],yellow:[255,255,0],yellowgreen:[154,205,50]},Q={};for(var $ in G)Q[JSON.stringify(G[$])]=$},{}],5:[function(t,e,a){var i=t(4),n=function(){return new d};for(var o in i){n[o+"Raw"]=function(t){return function(e){return"number"==typeof e&&(e=Array.prototype.slice.call(arguments)),i[t](e)}}(o);var r=/(\w+)2(\w+)/.exec(o),l=r[1],s=r[2];n[l]=n[l]||{},n[l][s]=n[o]=function(t){return function(e){"number"==typeof e&&(e=Array.prototype.slice.call(arguments));var a=i[t](e);if("string"==typeof a||void 0===a)return a;for(var n=0;ns)for(var d=0;t>d;d++){var u=l[d],c=r.getDatasetMeta(d);c.bar&&c.yAxisID===n.id&&r.isDatasetVisible(d)&&(o+=u.data[e]<0?u.data[e]:0)}else for(var h=0;t>h;h++){var f=l[h],g=r.getDatasetMeta(h);g.bar&&g.yAxisID===n.id&&r.isDatasetVisible(h)&&(o+=f.data[e]>0?f.data[e]:0)}return n.getPixelForValue(o)}return n.getBasePixel()},getRuler:function(t){var e,a=this,i=a.getMeta(),n=a.getScaleForId(i.xAxisID),o=a.getBarCount();e="category"===n.options.type?n.getPixelForTick(t+1)-n.getPixelForTick(t):n.width/n.ticks.length;var r=e*n.options.categoryPercentage,l=(e-e*n.options.categoryPercentage)/2,s=r/o;if(n.ticks.length!==a.chart.data.labels.length){var d=n.ticks.length/a.chart.data.labels.length;s*=d}var u=s*n.options.barPercentage,c=s-s*n.options.barPercentage;return{datasetCount:o,tickWidth:e,categoryWidth:r,categorySpacing:l,fullBarWidth:s,barWidth:u,barSpacing:c}},calculateBarWidth:function(t){var e=this.getScaleForId(this.getMeta().xAxisID),a=this.getRuler(t);return e.options.stacked?a.categoryWidth:a.barWidth},getBarIndex:function(t){var e,a,i=0;for(a=0;t>a;++a)e=this.chart.getDatasetMeta(a),e.bar&&this.chart.isDatasetVisible(a)&&++i;return i},calculateBarX:function(t,e){var a=this,i=a.getMeta(),n=a.getScaleForId(i.xAxisID),o=a.getBarIndex(e),r=a.getRuler(t),l=n.getPixelForValue(null,t,e,a.chart.isCombo);return l-=a.chart.isCombo?r.tickWidth/2:0,n.options.stacked?l+r.categoryWidth/2+r.categorySpacing:l+r.barWidth/2+r.categorySpacing+r.barWidth*o+r.barSpacing/2+r.barSpacing*o},calculateBarY:function(t,e){var a=this,i=a.getMeta(),n=a.getScaleForId(i.yAxisID),o=a.getDataset().data[t];if(n.options.stacked){for(var r=0,l=0,s=0;e>s;s++){var d=a.chart.data.datasets[s],u=a.chart.getDatasetMeta(s);u.bar&&u.yAxisID===n.id&&a.chart.isDatasetVisible(s)&&(d.data[t]<0?l+=d.data[t]||0:r+=d.data[t]||0)}return 0>o?n.getPixelForValue(l+o):n.getPixelForValue(r+o)}return n.getPixelForValue(o)},draw:function(t){var a=this,i=t||1;e.each(a.getMeta().data,function(t,e){var n=a.getDataset().data[e];null===n||void 0===n||isNaN(n)||t.transition(i).draw()},a)},setHoverStyle:function(t){var a=this.chart.data.datasets[t._datasetIndex],i=t._index,n=t.custom||{},o=t._model;o.backgroundColor=n.hoverBackgroundColor?n.hoverBackgroundColor:e.getValueAtIndexOrDefault(a.hoverBackgroundColor,i,e.getHoverColor(o.backgroundColor)),o.borderColor=n.hoverBorderColor?n.hoverBorderColor:e.getValueAtIndexOrDefault(a.hoverBorderColor,i,e.getHoverColor(o.borderColor)),o.borderWidth=n.hoverBorderWidth?n.hoverBorderWidth:e.getValueAtIndexOrDefault(a.hoverBorderWidth,i,o.borderWidth)},removeHoverStyle:function(t){var a=this.chart.data.datasets[t._datasetIndex],i=t._index,n=t.custom||{},o=t._model,r=this.chart.options.elements.rectangle;o.backgroundColor=n.backgroundColor?n.backgroundColor:e.getValueAtIndexOrDefault(a.backgroundColor,i,r.backgroundColor),o.borderColor=n.borderColor?n.borderColor:e.getValueAtIndexOrDefault(a.borderColor,i,r.borderColor),o.borderWidth=n.borderWidth?n.borderWidth:e.getValueAtIndexOrDefault(a.borderWidth,i,r.borderWidth)}}),t.defaults.horizontalBar={hover:{mode:"label"},scales:{xAxes:[{type:"linear",position:"bottom"}],yAxes:[{position:"left",type:"category",categoryPercentage:.8,barPercentage:.9,gridLines:{offsetGridLines:!0}}]},elements:{rectangle:{borderSkipped:"left"}},tooltips:{callbacks:{title:function(t,e){var a="";return t.length>0&&(t[0].yLabel?a=t[0].yLabel:e.labels.length>0&&t[0].indexc;c++)e.lineTo.apply(e,t(c));e.fill(),a.borderWidth&&e.stroke()},inRange:function(t,e){var a=this._view,i=!1;return a&&(i=a.x=a.y-a.height/2&&e<=a.y+a.height/2&&t>=a.x&&t<=a.base:e>=a.y-a.height/2&&e<=a.y+a.height/2&&t>=a.base&&t<=a.x),i}}),t.pivot()},calculateBarBase:function(t,e){ +var a=this,i=a.getMeta(),n=a.getScaleForId(i.xAxisID),o=0;if(n.options.stacked){var r=a.chart.data.datasets[t].data[e];if(0>r)for(var l=0;t>l;l++){var s=a.chart.data.datasets[l],d=a.chart.getDatasetMeta(l);d.bar&&d.xAxisID===n.id&&a.chart.isDatasetVisible(l)&&(o+=s.data[e]<0?s.data[e]:0)}else for(var u=0;t>u;u++){var c=a.chart.data.datasets[u],h=a.chart.getDatasetMeta(u);h.bar&&h.xAxisID===n.id&&a.chart.isDatasetVisible(u)&&(o+=c.data[e]>0?c.data[e]:0)}return n.getPixelForValue(o)}return n.getBasePixel()},getRuler:function(t){var e,a=this,i=a.getMeta(),n=a.getScaleForId(i.yAxisID),o=a.getBarCount();e="category"===n.options.type?n.getPixelForTick(t+1)-n.getPixelForTick(t):n.width/n.ticks.length;var r=e*n.options.categoryPercentage,l=(e-e*n.options.categoryPercentage)/2,s=r/o;if(n.ticks.length!==a.chart.data.labels.length){var d=n.ticks.length/a.chart.data.labels.length;s*=d}var u=s*n.options.barPercentage,c=s-s*n.options.barPercentage;return{datasetCount:o,tickHeight:e,categoryHeight:r,categorySpacing:l,fullBarHeight:s,barHeight:u,barSpacing:c}},calculateBarHeight:function(t){var e=this,a=e.getScaleForId(e.getMeta().yAxisID),i=e.getRuler(t);return a.options.stacked?i.categoryHeight:i.barHeight},calculateBarX:function(t,e){var a=this,i=a.getMeta(),n=a.getScaleForId(i.xAxisID),o=a.getDataset().data[t];if(n.options.stacked){for(var r=0,l=0,s=0;e>s;s++){var d=a.chart.data.datasets[s],u=a.chart.getDatasetMeta(s);u.bar&&u.xAxisID===n.id&&a.chart.isDatasetVisible(s)&&(d.data[t]<0?l+=d.data[t]||0:r+=d.data[t]||0)}return 0>o?n.getPixelForValue(l+o):n.getPixelForValue(r+o)}return n.getPixelForValue(o)},calculateBarY:function(t,e){var a=this,i=a.getMeta(),n=a.getScaleForId(i.yAxisID),o=a.getBarIndex(e),r=a.getRuler(t),l=n.getPixelForValue(null,t,e,a.chart.isCombo);return l-=a.chart.isCombo?r.tickHeight/2:0,n.options.stacked?l+r.categoryHeight/2+r.categorySpacing:l+r.barHeight/2+r.categorySpacing+r.barHeight*o+r.barSpacing/2+r.barSpacing*o}})}},{}],16:[function(t,e,a){"use strict";e.exports=function(t){var e=t.helpers;t.defaults.bubble={hover:{mode:"single"},scales:{xAxes:[{type:"linear",position:"bottom",id:"x-axis-0"}],yAxes:[{type:"linear",position:"left",id:"y-axis-0"}]},tooltips:{callbacks:{title:function(t,e){return""},label:function(t,e){var a=e.datasets[t.datasetIndex].label||"",i=e.datasets[t.datasetIndex].data[t.index];return a+": ("+i.x+", "+i.y+", "+i.r+")"}}}},t.controllers.bubble=t.DatasetController.extend({dataElementType:t.elements.Point,update:function(t){var a=this,i=a.getMeta(),n=i.data;e.each(n,function(e,i){a.updateElement(e,i,t)})},updateElement:function(a,i,n){var o=this,r=o.getMeta(),l=o.getScaleForId(r.xAxisID),s=o.getScaleForId(r.yAxisID),d=a.custom||{},u=o.getDataset(),c=u.data[i],h=o.chart.options.elements.point,f=o.index;e.extend(a,{_xScale:l,_yScale:s,_datasetIndex:f,_index:i,_model:{x:n?l.getPixelForDecimal(.5):l.getPixelForValue(c,i,f,o.chart.isCombo),y:n?s.getBasePixel():s.getPixelForValue(c,i,f),radius:n?0:d.radius?d.radius:o.getRadius(c),hitRadius:d.hitRadius?d.hitRadius:e.getValueAtIndexOrDefault(u.hitRadius,i,h.hitRadius)}}),t.DatasetController.prototype.removeHoverStyle.call(o,a,h);var g=a._model;g.skip=d.skip?d.skip:isNaN(g.x)||isNaN(g.y),a.pivot()},getRadius:function(t){return t.r||this.chart.options.elements.point.radius},setHoverStyle:function(a){var i=this;t.DatasetController.prototype.setHoverStyle.call(i,a);var n=i.chart.data.datasets[a._datasetIndex],o=a._index,r=a.custom||{},l=a._model;l.radius=r.hoverRadius?r.hoverRadius:e.getValueAtIndexOrDefault(n.hoverRadius,o,i.chart.options.elements.point.hoverRadius)+i.getRadius(n.data[o])},removeHoverStyle:function(e){var a=this;t.DatasetController.prototype.removeHoverStyle.call(a,e,a.chart.options.elements.point);var i=a.chart.data.datasets[e._datasetIndex].data[e._index],n=e.custom||{},o=e._model;o.radius=n.radius?n.radius:a.getRadius(i)}})}},{}],17:[function(t,e,a){"use strict";e.exports=function(t){var e=t.helpers,a=t.defaults;a.doughnut={animation:{animateRotate:!0,animateScale:!1},aspectRatio:1,hover:{mode:"single"},legendCallback:function(t){var e=[];e.push('
      ');var a=t.data,i=a.datasets,n=a.labels;if(i.length)for(var o=0;o'),n[o]&&e.push(n[o]),e.push("");return e.push("
    "),e.join("")},legend:{labels:{generateLabels:function(t){var a=t.data;return a.labels.length&&a.datasets.length?a.labels.map(function(i,n){var o=t.getDatasetMeta(0),r=a.datasets[0],l=o.data[n],s=l.custom||{},d=e.getValueAtIndexOrDefault,u=t.options.elements.arc,c=s.backgroundColor?s.backgroundColor:d(r.backgroundColor,n,u.backgroundColor),h=s.borderColor?s.borderColor:d(r.borderColor,n,u.borderColor),f=s.borderWidth?s.borderWidth:d(r.borderWidth,n,u.borderWidth);return{text:i,fillStyle:c,strokeStyle:h,lineWidth:f,hidden:isNaN(r.data[n])||o.data[n].hidden,index:n}}):[]}},onClick:function(t,e){var a,i,n,o=e.index,r=this.chart;for(a=0,i=(r.data.datasets||[]).length;i>a;++a)n=r.getDatasetMeta(a),n.data[o].hidden=!n.data[o].hidden;r.update()}},cutoutPercentage:50,rotation:Math.PI*-.5,circumference:2*Math.PI,tooltips:{callbacks:{title:function(){return""},label:function(t,e){return e.labels[t.index]+": "+e.datasets[t.datasetIndex].data[t.index]}}}},a.pie=e.clone(a.doughnut),e.extend(a.pie,{cutoutPercentage:0}),t.controllers.doughnut=t.controllers.pie=t.DatasetController.extend({dataElementType:t.elements.Arc,linkScales:e.noop,getRingIndex:function(t){for(var e=0,a=0;t>a;++a)this.chart.isDatasetVisible(a)&&++e;return e},update:function(t){var a=this,i=a.chart,n=i.chartArea,o=i.options,r=o.elements.arc,l=n.right-n.left-r.borderWidth,s=n.bottom-n.top-r.borderWidth,d=Math.min(l,s),u={x:0,y:0},c=a.getMeta(),h=o.cutoutPercentage,f=o.circumference;if(f<2*Math.PI){var g=o.rotation%(2*Math.PI);g+=2*Math.PI*(g>=Math.PI?-1:g<-Math.PI?1:0);var p=g+f,m={x:Math.cos(g),y:Math.sin(g)},b={x:Math.cos(p),y:Math.sin(p)},v=0>=g&&p>=0||g<=2*Math.PI&&2*Math.PI<=p,x=g<=.5*Math.PI&&.5*Math.PI<=p||g<=2.5*Math.PI&&2.5*Math.PI<=p,y=g<=-Math.PI&&-Math.PI<=p||g<=Math.PI&&Math.PI<=p,k=g<=.5*-Math.PI&&.5*-Math.PI<=p||g<=1.5*Math.PI&&1.5*Math.PI<=p,S=h/100,w={x:y?-1:Math.min(m.x*(m.x<0?1:S),b.x*(b.x<0?1:S)),y:k?-1:Math.min(m.y*(m.y<0?1:S),b.y*(b.y<0?1:S))},C={x:v?1:Math.max(m.x*(m.x>0?1:S),b.x*(b.x>0?1:S)),y:x?1:Math.max(m.y*(m.y>0?1:S),b.y*(b.y>0?1:S))},M={width:.5*(C.x-w.x),height:.5*(C.y-w.y)};d=Math.min(l/M.width,s/M.height),u={x:(C.x+w.x)*-.5,y:(C.y+w.y)*-.5}}i.outerRadius=Math.max(d/2,0),i.innerRadius=Math.max(h?i.outerRadius/100*h:1,0),i.radiusLength=(i.outerRadius-i.innerRadius)/i.getVisibleDatasetCount(),i.offsetX=u.x*i.outerRadius,i.offsetY=u.y*i.outerRadius,c.total=a.calculateTotal(),a.outerRadius=i.outerRadius-i.radiusLength*a.getRingIndex(a.index),a.innerRadius=a.outerRadius-i.radiusLength,e.each(c.data,function(e,i){a.updateElement(e,i,t)})},updateElement:function(t,a,i){var n=this,o=n.chart,r=o.chartArea,l=o.options,s=l.animation,d=(l.elements.arc,(r.left+r.right)/2),u=(r.top+r.bottom)/2,c=l.rotation,h=l.rotation,f=n.getDataset(),g=i&&s.animateRotate?0:t.hidden?0:n.calculateCircumference(f.data[a])*(l.circumference/(2*Math.PI)),p=i&&s.animateScale?0:n.innerRadius,m=i&&s.animateScale?0:n.outerRadius,b=(t.custom||{},e.getValueAtIndexOrDefault);e.extend(t,{_datasetIndex:n.index,_index:a,_model:{x:d+o.offsetX,y:u+o.offsetY,startAngle:c,endAngle:h,circumference:g,outerRadius:m,innerRadius:p,label:b(f.label,a,o.data.labels[a])}});var v=t._model;this.removeHoverStyle(t),i&&s.animateRotate||(0===a?v.startAngle=l.rotation:v.startAngle=n.getMeta().data[a-1]._model.endAngle,v.endAngle=v.startAngle+v.circumference),t.pivot()},removeHoverStyle:function(e){t.DatasetController.prototype.removeHoverStyle.call(this,e,this.chart.options.elements.arc)},calculateTotal:function(){var t,a=this.getDataset(),i=this.getMeta(),n=0;return e.each(i.data,function(e,i){t=a.data[i],isNaN(t)||e.hidden||(n+=Math.abs(t))}),n},calculateCircumference:function(t){var e=this.getMeta().total;return e>0&&!isNaN(t)?2*Math.PI*(t/e):0}})}},{}],18:[function(t,e,a){"use strict";e.exports=function(t){function e(t,e){return a.getValueOrDefault(t.showLine,e.showLines)}var a=t.helpers;t.defaults.line={showLines:!0,hover:{mode:"label"},scales:{xAxes:[{type:"category",id:"x-axis-0"}],yAxes:[{type:"linear",id:"y-axis-0"}]}},t.controllers.line=t.DatasetController.extend({datasetElementType:t.elements.Line,dataElementType:t.elements.Point,addElementAndReset:function(a){var i=this,n=i.chart.options,o=i.getMeta();t.DatasetController.prototype.addElementAndReset.call(i,a),e(i.getDataset(),n)&&0!==o.dataset._model.tension&&i.updateBezierControlPoints()},update:function(t){var i,n,o,r=this,l=r.getMeta(),s=l.dataset,d=l.data||[],u=r.chart.options,c=u.elements.line,h=r.getScaleForId(l.yAxisID),f=r.getDataset(),g=e(f,u);for(g&&(o=s.custom||{},void 0!==f.tension&&void 0===f.lineTension&&(f.lineTension=f.tension),s._scale=h,s._datasetIndex=r.index,s._children=d,s._model={spanGaps:f.spanGaps?f.spanGaps:!1,tension:o.tension?o.tension:a.getValueOrDefault(f.lineTension,c.tension),backgroundColor:o.backgroundColor?o.backgroundColor:f.backgroundColor||c.backgroundColor,borderWidth:o.borderWidth?o.borderWidth:f.borderWidth||c.borderWidth,borderColor:o.borderColor?o.borderColor:f.borderColor||c.borderColor,borderCapStyle:o.borderCapStyle?o.borderCapStyle:f.borderCapStyle||c.borderCapStyle,borderDash:o.borderDash?o.borderDash:f.borderDash||c.borderDash,borderDashOffset:o.borderDashOffset?o.borderDashOffset:f.borderDashOffset||c.borderDashOffset,borderJoinStyle:o.borderJoinStyle?o.borderJoinStyle:f.borderJoinStyle||c.borderJoinStyle,fill:o.fill?o.fill:void 0!==f.fill?f.fill:c.fill,scaleTop:h.top,scaleBottom:h.bottom,scaleZero:h.getBasePixel()},s.pivot()),i=0,n=d.length;n>i;++i)r.updateElement(d[i],i,t);for(g&&0!==s._model.tension&&r.updateBezierControlPoints(),i=0,n=d.length;n>i;++i)d[i].pivot()},getPointBackgroundColor:function(t,e){var i=this.chart.options.elements.point.backgroundColor,n=this.getDataset(),o=t.custom||{};return o.backgroundColor?i=o.backgroundColor:n.pointBackgroundColor?i=a.getValueAtIndexOrDefault(n.pointBackgroundColor,e,i):n.backgroundColor&&(i=n.backgroundColor),i},getPointBorderColor:function(t,e){var i=this.chart.options.elements.point.borderColor,n=this.getDataset(),o=t.custom||{};return o.borderColor?i=o.borderColor:n.pointBorderColor?i=a.getValueAtIndexOrDefault(n.pointBorderColor,e,i):n.borderColor&&(i=n.borderColor),i},getPointBorderWidth:function(t,e){var i=this.chart.options.elements.point.borderWidth,n=this.getDataset(),o=t.custom||{};return o.borderWidth?i=o.borderWidth:n.pointBorderWidth?i=a.getValueAtIndexOrDefault(n.pointBorderWidth,e,i):n.borderWidth&&(i=n.borderWidth),i},updateElement:function(t,e,i){var n,o,r=this,l=r.getMeta(),s=t.custom||{},d=r.getDataset(),u=r.index,c=d.data[e],h=r.getScaleForId(l.yAxisID),f=r.getScaleForId(l.xAxisID),g=r.chart.options.elements.point;void 0!==d.radius&&void 0===d.pointRadius&&(d.pointRadius=d.radius),void 0!==d.hitRadius&&void 0===d.pointHitRadius&&(d.pointHitRadius=d.hitRadius),n=f.getPixelForValue(c,e,u,r.chart.isCombo),o=i?h.getBasePixel():r.calculatePointY(c,e,u,r.chart.isCombo),t._xScale=f,t._yScale=h,t._datasetIndex=u,t._index=e,t._model={x:n,y:o,skip:s.skip||isNaN(n)||isNaN(o),radius:s.radius||a.getValueAtIndexOrDefault(d.pointRadius,e,g.radius),pointStyle:s.pointStyle||a.getValueAtIndexOrDefault(d.pointStyle,e,g.pointStyle),backgroundColor:r.getPointBackgroundColor(t,e),borderColor:r.getPointBorderColor(t,e),borderWidth:r.getPointBorderWidth(t,e),tension:l.dataset._model?l.dataset._model.tension:0,hitRadius:s.hitRadius||a.getValueAtIndexOrDefault(d.pointHitRadius,e,g.hitRadius)}},calculatePointY:function(t,e,a,i){var n,o,r,l=this,s=l.chart,d=l.getMeta(),u=l.getScaleForId(d.yAxisID),c=0,h=0;if(u.options.stacked){for(n=0;a>n;n++)o=s.data.datasets[n],r=s.getDatasetMeta(n),"line"===r.type&&s.isDatasetVisible(n)&&(o.data[e]<0?h+=o.data[e]||0:c+=o.data[e]||0);return 0>t?u.getPixelForValue(h+t):u.getPixelForValue(c+t)}return u.getPixelForValue(t)},updateBezierControlPoints:function(){var t,e,i,n,o,r=this.getMeta(),l=(this.chart.chartArea,r.data||[]);for(t=0,e=l.length;e>t;++t)i=l[t],n=i._model,o=a.splineCurve(a.previousItem(l,t)._model,n,a.nextItem(l,t)._model,r.dataset._model.tension),n.controlPointPreviousX=o.previous.x,n.controlPointPreviousY=o.previous.y,n.controlPointNextX=o.next.x,n.controlPointNextY=o.next.y},draw:function(t){var a,i,n=this,o=n.getMeta(),r=o.data||[],l=t||1;for(a=0,i=r.length;i>a;++a)r[a].transition(l);for(e(n.getDataset(),n.chart.options)&&o.dataset.transition(l).draw(),a=0,i=r.length;i>a;++a)r[a].draw()},setHoverStyle:function(t){var e=this.chart.data.datasets[t._datasetIndex],i=t._index,n=t.custom||{},o=t._model;o.radius=n.hoverRadius||a.getValueAtIndexOrDefault(e.pointHoverRadius,i,this.chart.options.elements.point.hoverRadius),o.backgroundColor=n.hoverBackgroundColor||a.getValueAtIndexOrDefault(e.pointHoverBackgroundColor,i,a.getHoverColor(o.backgroundColor)),o.borderColor=n.hoverBorderColor||a.getValueAtIndexOrDefault(e.pointHoverBorderColor,i,a.getHoverColor(o.borderColor)),o.borderWidth=n.hoverBorderWidth||a.getValueAtIndexOrDefault(e.pointHoverBorderWidth,i,o.borderWidth)},removeHoverStyle:function(t){var e=this,i=e.chart.data.datasets[t._datasetIndex],n=t._index,o=t.custom||{},r=t._model;void 0!==i.radius&&void 0===i.pointRadius&&(i.pointRadius=i.radius),r.radius=o.radius||a.getValueAtIndexOrDefault(i.pointRadius,n,e.chart.options.elements.point.radius),r.backgroundColor=e.getPointBackgroundColor(t,n),r.borderColor=e.getPointBorderColor(t,n),r.borderWidth=e.getPointBorderWidth(t,n)}})}},{}],19:[function(t,e,a){"use strict";e.exports=function(t){var e=t.helpers;t.defaults.polarArea={scale:{type:"radialLinear",lineArc:!0},animation:{animateRotate:!0,animateScale:!0},aspectRatio:1,legendCallback:function(t){var e=[];e.push('
      ');var a=t.data,i=a.datasets,n=a.labels;if(i.length)for(var o=0;o'),n[o]&&e.push(n[o]),e.push("");return e.push("
    "),e.join("")},legend:{labels:{generateLabels:function(t){var a=t.data;return a.labels.length&&a.datasets.length?a.labels.map(function(i,n){var o=t.getDatasetMeta(0),r=a.datasets[0],l=o.data[n],s=l.custom||{},d=e.getValueAtIndexOrDefault,u=t.options.elements.arc,c=s.backgroundColor?s.backgroundColor:d(r.backgroundColor,n,u.backgroundColor),h=s.borderColor?s.borderColor:d(r.borderColor,n,u.borderColor),f=s.borderWidth?s.borderWidth:d(r.borderWidth,n,u.borderWidth);return{text:i,fillStyle:c,strokeStyle:h,lineWidth:f,hidden:isNaN(r.data[n])||o.data[n].hidden,index:n}}):[]}},onClick:function(t,e){var a,i,n,o=e.index,r=this.chart;for(a=0,i=(r.data.datasets||[]).length;i>a;++a)n=r.getDatasetMeta(a),n.data[o].hidden=!n.data[o].hidden;r.update()}},tooltips:{callbacks:{title:function(){return""},label:function(t,e){return e.labels[t.index]+": "+t.yLabel}}}},t.controllers.polarArea=t.DatasetController.extend({dataElementType:t.elements.Arc,linkScales:e.noop,update:function(t){var a=this,i=a.chart,n=i.chartArea,o=a.getMeta(),r=i.options,l=r.elements.arc,s=Math.min(n.right-n.left,n.bottom-n.top);i.outerRadius=Math.max((s-l.borderWidth/2)/2,0),i.innerRadius=Math.max(r.cutoutPercentage?i.outerRadius/100*r.cutoutPercentage:1,0),i.radiusLength=(i.outerRadius-i.innerRadius)/i.getVisibleDatasetCount(),a.outerRadius=i.outerRadius-i.radiusLength*a.index,a.innerRadius=a.outerRadius-i.radiusLength,o.count=a.countVisibleElements(),e.each(o.data,function(e,i){a.updateElement(e,i,t)})},updateElement:function(t,a,i){for(var n=this,o=n.chart,r=o.chartArea,l=n.getDataset(),s=o.options,d=s.animation,u=(s.elements.arc,t.custom||{},o.scale),c=e.getValueAtIndexOrDefault,h=o.data.labels,f=n.calculateCircumference(l.data[a]),g=(r.left+r.right)/2,p=(r.top+r.bottom)/2,m=0,b=n.getMeta(),v=0;a>v;++v)isNaN(l.data[v])||b.data[v].hidden||++m;var x=-.5*Math.PI,y=t.hidden?0:u.getDistanceFromCenterForValue(l.data[a]),k=x+f*m,S=k+(t.hidden?0:f),w=d.animateScale?0:u.getDistanceFromCenterForValue(l.data[a]);e.extend(t,{_datasetIndex:n.index,_index:a,_scale:u,_model:{x:g,y:p,innerRadius:0,outerRadius:i?w:y,startAngle:i&&d.animateRotate?x:k,endAngle:i&&d.animateRotate?x:S,label:c(h,a,h[a])}}),n.removeHoverStyle(t),t.pivot()},removeHoverStyle:function(e){t.DatasetController.prototype.removeHoverStyle.call(this,e,this.chart.options.elements.arc)},countVisibleElements:function(){var t=this.getDataset(),a=this.getMeta(),i=0;return e.each(a.data,function(e,a){isNaN(t.data[a])||e.hidden||i++}),i},calculateCircumference:function(t){var e=this.getMeta().count;return e>0&&!isNaN(t)?2*Math.PI/e:0}})}},{}],20:[function(t,e,a){"use strict";e.exports=function(t){var e=t.helpers;t.defaults.radar={scale:{type:"radialLinear"},elements:{line:{tension:0}}},t.controllers.radar=t.DatasetController.extend({datasetElementType:t.elements.Line,dataElementType:t.elements.Point,linkScales:e.noop,addElementAndReset:function(e){t.DatasetController.prototype.addElementAndReset.call(this,e),this.updateBezierControlPoints()},update:function(t){var a=this,i=a.getMeta(),n=i.dataset,o=i.data,r=n.custom||{},l=a.getDataset(),s=a.chart.options.elements.line,d=a.chart.scale;void 0!==l.tension&&void 0===l.lineTension&&(l.lineTension=l.tension),e.extend(i.dataset,{_datasetIndex:a.index,_children:o,_loop:!0,_model:{tension:r.tension?r.tension:e.getValueOrDefault(l.lineTension,s.tension),backgroundColor:r.backgroundColor?r.backgroundColor:l.backgroundColor||s.backgroundColor,borderWidth:r.borderWidth?r.borderWidth:l.borderWidth||s.borderWidth,borderColor:r.borderColor?r.borderColor:l.borderColor||s.borderColor,fill:r.fill?r.fill:void 0!==l.fill?l.fill:s.fill,borderCapStyle:r.borderCapStyle?r.borderCapStyle:l.borderCapStyle||s.borderCapStyle,borderDash:r.borderDash?r.borderDash:l.borderDash||s.borderDash,borderDashOffset:r.borderDashOffset?r.borderDashOffset:l.borderDashOffset||s.borderDashOffset,borderJoinStyle:r.borderJoinStyle?r.borderJoinStyle:l.borderJoinStyle||s.borderJoinStyle,scaleTop:d.top,scaleBottom:d.bottom,scaleZero:d.getBasePosition()}}),i.dataset.pivot(),e.each(o,function(e,i){a.updateElement(e,i,t)},a),a.updateBezierControlPoints()},updateElement:function(t,a,i){var n=this,o=t.custom||{},r=n.getDataset(),l=n.chart.scale,s=n.chart.options.elements.point,d=l.getPointPositionForValue(a,r.data[a]);e.extend(t,{_datasetIndex:n.index,_index:a,_scale:l,_model:{x:i?l.xCenter:d.x,y:i?l.yCenter:d.y,tension:o.tension?o.tension:e.getValueOrDefault(r.tension,n.chart.options.elements.line.tension),radius:o.radius?o.radius:e.getValueAtIndexOrDefault(r.pointRadius,a,s.radius),backgroundColor:o.backgroundColor?o.backgroundColor:e.getValueAtIndexOrDefault(r.pointBackgroundColor,a,s.backgroundColor),borderColor:o.borderColor?o.borderColor:e.getValueAtIndexOrDefault(r.pointBorderColor,a,s.borderColor),borderWidth:o.borderWidth?o.borderWidth:e.getValueAtIndexOrDefault(r.pointBorderWidth,a,s.borderWidth),pointStyle:o.pointStyle?o.pointStyle:e.getValueAtIndexOrDefault(r.pointStyle,a,s.pointStyle),hitRadius:o.hitRadius?o.hitRadius:e.getValueAtIndexOrDefault(r.hitRadius,a,s.hitRadius)}}),t._model.skip=o.skip?o.skip:isNaN(t._model.x)||isNaN(t._model.y)},updateBezierControlPoints:function(){var t=this.chart.chartArea,a=this.getMeta();e.each(a.data,function(i,n){var o=i._model,r=e.splineCurve(e.previousItem(a.data,n,!0)._model,o,e.nextItem(a.data,n,!0)._model,o.tension);o.controlPointPreviousX=Math.max(Math.min(r.previous.x,t.right),t.left),o.controlPointPreviousY=Math.max(Math.min(r.previous.y,t.bottom),t.top),o.controlPointNextX=Math.max(Math.min(r.next.x,t.right),t.left),o.controlPointNextY=Math.max(Math.min(r.next.y,t.bottom),t.top),i.pivot()})},draw:function(t){var a=this.getMeta(),i=t||1;e.each(a.data,function(t,e){t.transition(i)}),a.dataset.transition(i).draw(),e.each(a.data,function(t){t.draw()})},setHoverStyle:function(t){var a=this.chart.data.datasets[t._datasetIndex],i=t.custom||{},n=t._index,o=t._model;o.radius=i.hoverRadius?i.hoverRadius:e.getValueAtIndexOrDefault(a.pointHoverRadius,n,this.chart.options.elements.point.hoverRadius),o.backgroundColor=i.hoverBackgroundColor?i.hoverBackgroundColor:e.getValueAtIndexOrDefault(a.pointHoverBackgroundColor,n,e.getHoverColor(o.backgroundColor)),o.borderColor=i.hoverBorderColor?i.hoverBorderColor:e.getValueAtIndexOrDefault(a.pointHoverBorderColor,n,e.getHoverColor(o.borderColor)),o.borderWidth=i.hoverBorderWidth?i.hoverBorderWidth:e.getValueAtIndexOrDefault(a.pointHoverBorderWidth,n,o.borderWidth)},removeHoverStyle:function(t){var a=this.chart.data.datasets[t._datasetIndex],i=t.custom||{},n=t._index,o=t._model,r=this.chart.options.elements.point;o.radius=i.radius?i.radius:e.getValueAtIndexOrDefault(a.radius,n,r.radius),o.backgroundColor=i.backgroundColor?i.backgroundColor:e.getValueAtIndexOrDefault(a.pointBackgroundColor,n,r.backgroundColor),o.borderColor=i.borderColor?i.borderColor:e.getValueAtIndexOrDefault(a.pointBorderColor,n,r.borderColor),o.borderWidth=i.borderWidth?i.borderWidth:e.getValueAtIndexOrDefault(a.pointBorderWidth,n,r.borderWidth)}})}},{}],21:[function(t,e,a){"use strict";e.exports=function(t){var e=t.helpers;t.defaults.global.animation={duration:1e3,easing:"easeOutQuart",onProgress:e.noop,onComplete:e.noop},t.Animation=t.Element.extend({currentStep:null,numSteps:60,easing:"",render:null,onAnimationProgress:null,onAnimationComplete:null}),t.animationService={frameDuration:17,animations:[],dropFrames:0,request:null,addAnimation:function(t,e,a,i){var n=this;i||(t.animating=!0);for(var o=0;o1&&(a=Math.floor(t.dropFrames),t.dropFrames=t.dropFrames%1);for(var i=0;it.animations[i].animationObject.numSteps&&(t.animations[i].animationObject.currentStep=t.animations[i].animationObject.numSteps),t.animations[i].animationObject.render(t.animations[i].chartInstance,t.animations[i].animationObject),t.animations[i].animationObject.onAnimationProgress&&t.animations[i].animationObject.onAnimationProgress.call&&t.animations[i].animationObject.onAnimationProgress.call(t.animations[i].chartInstance,t.animations[i]),t.animations[i].animationObject.currentStep===t.animations[i].animationObject.numSteps?(t.animations[i].animationObject.onAnimationComplete&&t.animations[i].animationObject.onAnimationComplete.call&&t.animations[i].animationObject.onAnimationComplete.call(t.animations[i].chartInstance,t.animations[i]),t.animations[i].chartInstance.animating=!1,t.animations.splice(i,1)):++i;var n=Date.now(),o=(n-e)/t.frameDuration;t.dropFrames+=o,t.animations.length>0&&t.requestAnimationFrame()}}}},{}],22:[function(t,e,a){"use strict";e.exports=function(t){var e=t.helpers;t.types={},t.instances={},t.controllers={},t.Controller=function(a){return this.chart=a,this.config=a.config,this.options=this.config.options=e.configMerge(t.defaults.global,t.defaults[this.config.type],this.config.options||{}),this.id=e.uid(),Object.defineProperty(this,"data",{get:function(){return this.config.data}}),t.instances[this.id]=this,this.options.responsive&&this.resize(!0),this.initialize(),this},e.extend(t.Controller.prototype,{initialize:function(){var e=this;return t.plugins.notify("beforeInit",[e]),e.bindEvents(),e.ensureScalesHaveIDs(),e.buildOrUpdateControllers(),e.buildScales(),e.updateLayout(),e.resetElements(),e.initToolTip(),e.update(),t.plugins.notify("afterInit",[e]),e},clear:function(){return e.clear(this.chart),this},stop:function(){return t.animationService.cancelAnimation(this),this},resize:function(a){var i=this,n=i.chart,o=n.canvas,r=e.getMaximumWidth(o),l=n.aspectRatio,s=i.options.maintainAspectRatio&&isNaN(l)===!1&&isFinite(l)&&0!==l?r/l:e.getMaximumHeight(o),d=n.width!==r||n.height!==s;if(!d)return i;o.width=n.width=r,o.height=n.height=s,e.retinaScale(n);var u={width:r,height:s};return t.plugins.notify("resize",[i,u]),i.options.onResize&&i.options.onResize(i,u),a||(i.stop(),i.update(i.options.responsiveAnimationDuration)),i},ensureScalesHaveIDs:function(){var t=this.options,a=t.scales||{},i=t.scale;e.each(a.xAxes,function(t,e){t.id=t.id||"x-axis-"+e}),e.each(a.yAxes,function(t,e){t.id=t.id||"y-axis-"+e}),i&&(i.id=i.id||"scale")},buildScales:function(){var a=this,i=a.options,n=a.scales={},o=[];i.scales&&(o=o.concat((i.scales.xAxes||[]).map(function(t){return{options:t,dtype:"category"}}),(i.scales.yAxes||[]).map(function(t){return{options:t,dtype:"linear"}}))),i.scale&&o.push({options:i.scale,dtype:"radialLinear",isDefault:!0}),e.each(o,function(i,o){var r=i.options,l=e.getValueOrDefault(r.type,i.dtype),s=t.scaleService.getScaleConstructor(l);if(s){var d=new s({id:r.id,options:r,ctx:a.chart.ctx,chart:a});n[d.id]=d,i.isDefault&&(a.scale=d)}}),t.scaleService.addScalesToLayout(this)},updateLayout:function(){t.layoutService.update(this,this.chart.width,this.chart.height)},buildOrUpdateControllers:function(){var a=this,i=[],n=[];if(e.each(a.data.datasets,function(e,o){var r=a.getDatasetMeta(o);r.type||(r.type=e.type||a.config.type),i.push(r.type),r.controller?r.controller.updateIndex(o):(r.controller=new t.controllers[r.type](a,o),n.push(r.controller))},a),i.length>1)for(var o=1;oe;++e)i.getDatasetMeta(e).controller.update();t.plugins.notify("afterDatasetsUpdate",[i])}},render:function(a,i){var n=this;t.plugins.notify("beforeRender",[n]);var o=n.options.animation;if(o&&("undefined"!=typeof a&&0!==a||"undefined"==typeof a&&0!==o.duration)){var r=new t.Animation;r.numSteps=(a||o.duration)/16.66,r.easing=o.easing,r.render=function(t,a){var i=e.easingEffects[a.easing],n=a.currentStep/a.numSteps,o=i(n);t.draw(o,n,a.currentStep)},r.onAnimationProgress=o.onProgress,r.onAnimationComplete=o.onComplete,t.animationService.addAnimation(n,r,a,i)}else n.draw(),o&&o.onComplete&&o.onComplete.call&&o.onComplete.call(n);return n},draw:function(a){var i=this,n=a||1;i.clear(),t.plugins.notify("beforeDraw",[i,n]),e.each(i.boxes,function(t){t.draw(i.chartArea)},i),i.scale&&i.scale.draw(),t.plugins.notify("beforeDatasetsDraw",[i,n]),e.each(i.data.datasets,function(t,e){i.isDatasetVisible(e)&&i.getDatasetMeta(e).controller.draw(a)},i,!0),t.plugins.notify("afterDatasetsDraw",[i,n]),i.tooltip.transition(n).draw(),t.plugins.notify("afterDraw",[i,n])},getElementAtEvent:function(t){var a=this,i=e.getRelativePosition(t,a.chart),n=[];return e.each(a.data.datasets,function(t,o){if(a.isDatasetVisible(o)){var r=a.getDatasetMeta(o);e.each(r.data,function(t,e){return t.inRange(i.x,i.y)?(n.push(t),n):void 0})}}),n},getElementsAtEvent:function(t){var a=this,i=e.getRelativePosition(t,a.chart),n=[],o=function(){if(a.data.datasets)for(var t=0;t0&&(e=this.getDatasetMeta(e[0]._datasetIndex).data),e},getDatasetMeta:function(t){var e=this,a=e.data.datasets[t];a._meta||(a._meta={});var i=a._meta[e.id];return i||(i=a._meta[e.id]={type:null,data:[],dataset:null,controller:null,hidden:null,xAxisID:null,yAxisID:null}),i},getVisibleDatasetCount:function(){for(var t=0,e=0,a=this.data.datasets.length;a>e;++e)this.isDatasetVisible(e)&&t++;return t},isDatasetVisible:function(t){var e=this.getDatasetMeta(t);return"boolean"==typeof e.hidden?!e.hidden:!this.data.datasets[t].hidden},generateLegend:function(){return this.options.legendCallback(this)},destroy:function(){var a=this;a.stop(),a.clear(),e.unbindEvents(a,a.events),e.removeResizeListener(a.chart.canvas.parentNode);var i=a.chart.canvas;i.width=a.chart.width,i.height=a.chart.height,void 0!==a.chart.originalDevicePixelRatio&&a.chart.ctx.scale(1/a.chart.originalDevicePixelRatio,1/a.chart.originalDevicePixelRatio),i.style.width=a.chart.originalCanvasStyleWidth,i.style.height=a.chart.originalCanvasStyleHeight,t.plugins.notify("destroy",[a]),delete t.instances[a.id]},toBase64Image:function(){return this.chart.canvas.toDataURL.apply(this.chart.canvas,arguments)},initToolTip:function(){var e=this;e.tooltip=new t.Tooltip({_chart:e.chart,_chartInstance:e,_data:e.data,_options:e.options.tooltips},e)},bindEvents:function(){var t=this;e.bindEvents(t,t.options.events,function(e){t.eventHandler(e)})},updateHoverStyle:function(t,e,a){var i,n,o,r=a?"setHoverStyle":"removeHoverStyle";switch(e){case"single":t=[t[0]];break;case"label":case"dataset":break;default:return}for(n=0,o=t.length;o>n;++n)i=t[n],i&&this.getDatasetMeta(i._datasetIndex).controller[r](i)},eventHandler:function(t){var a=this,i=a.tooltip,n=a.options||{},o=n.hover,r=n.tooltips;return a.lastActive=a.lastActive||[],a.lastTooltipActive=a.lastTooltipActive||[],"mouseout"===t.type?(a.active=[],a.tooltipActive=[]):(a.active=a.getElementsAtEventForMode(t,o.mode),a.tooltipActive=a.getElementsAtEventForMode(t,r.mode)),o.onHover&&o.onHover.call(a,a.active),("mouseup"===t.type||"click"===t.type)&&(n.onClick&&n.onClick.call(a,t,a.active),a.legend&&a.legend.handleEvent&&a.legend.handleEvent(t)),a.lastActive.length&&a.updateHoverStyle(a.lastActive,o.mode,!1),a.active.length&&o.mode&&a.updateHoverStyle(a.active,o.mode,!0),(r.enabled||r.custom)&&(i.initialize(),i._active=a.tooltipActive,i.update(!0)),i.pivot(),a.animating||e.arrayEquals(a.active,a.lastActive)&&e.arrayEquals(a.tooltipActive,a.lastTooltipActive)||(a.stop(),(r.enabled||r.custom)&&i.update(!0),a.render(o.animationDuration,!0)),a.lastActive=a.active,a.lastTooltipActive=a.tooltipActive,a}})}},{}],23:[function(t,e,a){"use strict";e.exports=function(t){var e=t.helpers,a=e.noop;t.DatasetController=function(t,e){this.initialize.call(this,t,e)},e.extend(t.DatasetController.prototype,{datasetElementType:null,dataElementType:null,initialize:function(t,e){var a=this;a.chart=t,a.index=e,a.linkScales(),a.addElements()},updateIndex:function(t){this.index=t},linkScales:function(){var t=this,e=t.getMeta(),a=t.getDataset();null===e.xAxisID&&(e.xAxisID=a.xAxisID||t.chart.options.scales.xAxes[0].id),null===e.yAxisID&&(e.yAxisID=a.yAxisID||t.chart.options.scales.yAxes[0].id); +},getDataset:function(){return this.chart.data.datasets[this.index]},getMeta:function(){return this.chart.getDatasetMeta(this.index)},getScaleForId:function(t){return this.chart.scales[t]},reset:function(){this.update(!0)},createMetaDataset:function(){var t=this,e=t.datasetElementType;return e&&new e({_chart:t.chart.chart,_datasetIndex:t.index})},createMetaData:function(t){var e=this,a=e.dataElementType;return a&&new a({_chart:e.chart.chart,_datasetIndex:e.index,_index:t})},addElements:function(){var t,e,a=this,i=a.getMeta(),n=a.getDataset().data||[],o=i.data;for(t=0,e=n.length;e>t;++t)o[t]=o[t]||a.createMetaData(i,t);i.dataset=i.dataset||a.createMetaDataset()},addElementAndReset:function(t){var e=this,a=e.createMetaData(t);e.getMeta().data.splice(t,0,a),e.updateElement(a,t,!0)},buildOrUpdateElements:function(){var t=this.getMeta(),e=t.data,a=this.getDataset().data.length,i=e.length;if(i>a)e.splice(a,i-a);else if(a>i)for(var n=i;a>n;++n)this.addElementAndReset(n)},update:a,draw:function(t){var a=t||1;e.each(this.getMeta().data,function(t,e){t.transition(a).draw()})},removeHoverStyle:function(t,a){var i=this.chart.data.datasets[t._datasetIndex],n=t._index,o=t.custom||{},r=e.getValueAtIndexOrDefault,l=(e.color,t._model);l.backgroundColor=o.backgroundColor?o.backgroundColor:r(i.backgroundColor,n,a.backgroundColor),l.borderColor=o.borderColor?o.borderColor:r(i.borderColor,n,a.borderColor),l.borderWidth=o.borderWidth?o.borderWidth:r(i.borderWidth,n,a.borderWidth)},setHoverStyle:function(t){var a=this.chart.data.datasets[t._datasetIndex],i=t._index,n=t.custom||{},o=e.getValueAtIndexOrDefault,r=(e.color,e.getHoverColor),l=t._model;l.backgroundColor=n.hoverBackgroundColor?n.hoverBackgroundColor:o(a.hoverBackgroundColor,i,r(l.backgroundColor)),l.borderColor=n.hoverBorderColor?n.hoverBorderColor:o(a.hoverBorderColor,i,r(l.borderColor)),l.borderWidth=n.hoverBorderWidth?n.hoverBorderWidth:o(a.hoverBorderWidth,i,l.borderWidth)}}),t.DatasetController.extend=e.inherits}},{}],24:[function(t,e,a){"use strict";e.exports=function(t){var e=t.helpers;t.elements={},t.Element=function(t){e.extend(this,t),this.initialize.apply(this,arguments)},e.extend(t.Element.prototype,{initialize:function(){this.hidden=!1},pivot:function(){var t=this;return t._view||(t._view=e.clone(t._model)),t._start=e.clone(t._view),t},transition:function(t){var a=this;return a._view||(a._view=e.clone(a._model)),1===t?(a._view=a._model,a._start=null,a):(a._start||a.pivot(),e.each(a._model,function(i,n){if("_"===n[0]);else if(a._view.hasOwnProperty(n))if(i===a._view[n]);else if("string"==typeof i)try{var o=e.color(a._model[n]).mix(e.color(a._start[n]),t);a._view[n]=o.rgbString()}catch(r){a._view[n]=i}else if("number"==typeof i){var l=void 0!==a._start[n]&&isNaN(a._start[n])===!1?a._start[n]:0;a._view[n]=(a._model[n]-l)*t+l}else a._view[n]=i;else"number"!=typeof i||isNaN(a._view[n])?a._view[n]=i:a._view[n]=i*t},a),a)},tooltipPosition:function(){return{x:this._model.x,y:this._model.y}},hasValue:function(){return e.isNumber(this._model.x)&&e.isNumber(this._model.y)}}),t.Element.extend=e.inherits}},{}],25:[function(t,e,a){"use strict";var i=t(3);e.exports=function(t){function e(t,e,a){var i;return"string"==typeof t?(i=parseInt(t,10),-1!=t.indexOf("%")&&(i=i/100*e.parentNode[a])):i=t,i}function a(t){return void 0!==t&&null!==t&&"none"!==t}function n(t,i,n){var o=document.defaultView,r=t.parentNode,l=o.getComputedStyle(t)[i],s=o.getComputedStyle(r)[i],d=a(l),u=a(s),c=Number.POSITIVE_INFINITY;return d||u?Math.min(d?e(l,t,n):c,u?e(s,r,n):c):"none"}var o=t.helpers={};o.each=function(t,e,a,i){var n,r;if(o.isArray(t))if(r=t.length,i)for(n=r-1;n>=0;n--)e.call(a,t[n],n);else for(n=0;r>n;n++)e.call(a,t[n],n);else if("object"==typeof t){var l=Object.keys(t);for(r=l.length,n=0;r>n;n++)e.call(a,t[l[n]],l[n])}},o.clone=function(t){var e={};return o.each(t,function(t,a){o.isArray(t)?e[a]=t.slice(0):"object"==typeof t&&null!==t?e[a]=o.clone(t):e[a]=t}),e},o.extend=function(t){for(var e=function(e,a){t[a]=e},a=1,i=arguments.length;i>a;a++)o.each(arguments[a],e);return t},o.configMerge=function(e){var a=o.clone(e);return o.each(Array.prototype.slice.call(arguments,1),function(e){o.each(e,function(e,i){if("scales"===i)a[i]=o.scaleMerge(a.hasOwnProperty(i)?a[i]:{},e);else if("scale"===i)a[i]=o.configMerge(a.hasOwnProperty(i)?a[i]:{},t.scaleService.getScaleDefaults(e.type),e);else if(a.hasOwnProperty(i)&&o.isArray(a[i])&&o.isArray(e)){var n=a[i];o.each(e,function(t,e){e=i[a].length||!i[a][n].type?i[a].push(o.configMerge(l,e)):e.type&&e.type!==i[a][n].type?i[a][n]=o.configMerge(i[a][n],l,e):i[a][n]=o.configMerge(i[a][n],e)}):(i[a]=[],o.each(e,function(e){var n=o.getValueOrDefault(e.type,"xAxes"===a?"category":"linear");i[a].push(o.configMerge(t.scaleService.getScaleDefaults(n),e))})):i.hasOwnProperty(a)&&"object"==typeof i[a]&&null!==i[a]&&"object"==typeof e?i[a]=o.configMerge(i[a],e):i[a]=e}),i},o.getValueAtIndexOrDefault=function(t,e,a){return void 0===t||null===t?a:o.isArray(t)?ea;++a)if(t[a]===e)return a;return-1},o.where=function(t,e){if(o.isArray(t)&&Array.prototype.filter)return t.filter(e);var a=[];return o.each(t,function(t){e(t)&&a.push(t)}),a},o.findIndex=Array.prototype.findIndex?function(t,e,a){return t.findIndex(e,a)}:function(t,e,a){a=void 0===a?t:a;for(var i=0,n=t.length;n>i;++i)if(e.call(a,t[i],i,t))return i;return-1},o.findNextWhere=function(t,e,a){(void 0===a||null===a)&&(a=-1);for(var i=a+1;i=0;i--){var n=t[i];if(e(n))return n}},o.inherits=function(t){var e=this,a=t&&t.hasOwnProperty("constructor")?t.constructor:function(){return e.apply(this,arguments)},i=function(){this.constructor=a};return i.prototype=e.prototype,a.prototype=new i,a.extend=o.inherits,t&&o.extend(a.prototype,t),a.__super__=e.prototype,a},o.noop=function(){},o.uid=function(){var t=0;return function(){return t++}}(),o.isNumber=function(t){return!isNaN(parseFloat(t))&&isFinite(t)},o.almostEquals=function(t,e,a){return Math.abs(t-e)0?1:-1},o.log10=Math.log10?function(t){return Math.log10(t)}:function(t){return Math.log(t)/Math.LN10},o.toRadians=function(t){return t*(Math.PI/180)},o.toDegrees=function(t){return t*(180/Math.PI)},o.getAngleFromPoint=function(t,e){var a=e.x-t.x,i=e.y-t.y,n=Math.sqrt(a*a+i*i),o=Math.atan2(i,a);return o<-.5*Math.PI&&(o+=2*Math.PI),{angle:o,distance:n}},o.aliasPixel=function(t){return t%2===0?0:.5},o.splineCurve=function(t,e,a,i){var n=t.skip?e:t,o=e,r=a.skip?e:a,l=Math.sqrt(Math.pow(o.x-n.x,2)+Math.pow(o.y-n.y,2)),s=Math.sqrt(Math.pow(r.x-o.x,2)+Math.pow(r.y-o.y,2)),d=l/(l+s),u=s/(l+s);d=isNaN(d)?0:d,u=isNaN(u)?0:u;var c=i*d,h=i*u;return{previous:{x:o.x-c*(r.x-n.x),y:o.y-c*(r.y-n.y)},next:{x:o.x+h*(r.x-n.x),y:o.y+h*(r.y-n.y)}}},o.nextItem=function(t,e,a){return a?e>=t.length-1?t[0]:t[e+1]:e>=t.length-1?t[t.length-1]:t[e+1]},o.previousItem=function(t,e,a){return a?0>=e?t[t.length-1]:t[e-1]:0>=e?t[0]:t[e-1]},o.niceNum=function(t,e){var a,i=Math.floor(o.log10(t)),n=t/Math.pow(10,i);return a=e?1.5>n?1:3>n?2:7>n?5:10:1>=n?1:2>=n?2:5>=n?5:10,a*Math.pow(10,i)};var r=o.easingEffects={linear:function(t){return t},easeInQuad:function(t){return t*t},easeOutQuad:function(t){return-1*t*(t-2)},easeInOutQuad:function(t){return(t/=.5)<1?.5*t*t:-0.5*(--t*(t-2)-1)},easeInCubic:function(t){return t*t*t},easeOutCubic:function(t){return 1*((t=t/1-1)*t*t+1)},easeInOutCubic:function(t){return(t/=.5)<1?.5*t*t*t:.5*((t-=2)*t*t+2)},easeInQuart:function(t){return t*t*t*t},easeOutQuart:function(t){return-1*((t=t/1-1)*t*t*t-1)},easeInOutQuart:function(t){return(t/=.5)<1?.5*t*t*t*t:-0.5*((t-=2)*t*t*t-2)},easeInQuint:function(t){return 1*(t/=1)*t*t*t*t},easeOutQuint:function(t){return 1*((t=t/1-1)*t*t*t*t+1)},easeInOutQuint:function(t){return(t/=.5)<1?.5*t*t*t*t*t:.5*((t-=2)*t*t*t*t+2)},easeInSine:function(t){return-1*Math.cos(t/1*(Math.PI/2))+1},easeOutSine:function(t){return 1*Math.sin(t/1*(Math.PI/2))},easeInOutSine:function(t){return-0.5*(Math.cos(Math.PI*t/1)-1)},easeInExpo:function(t){return 0===t?1:1*Math.pow(2,10*(t/1-1))},easeOutExpo:function(t){return 1===t?1:1*(-Math.pow(2,-10*t/1)+1)},easeInOutExpo:function(t){return 0===t?0:1===t?1:(t/=.5)<1?.5*Math.pow(2,10*(t-1)):.5*(-Math.pow(2,-10*--t)+2)},easeInCirc:function(t){return t>=1?t:-1*(Math.sqrt(1-(t/=1)*t)-1)},easeOutCirc:function(t){return 1*Math.sqrt(1-(t=t/1-1)*t)},easeInOutCirc:function(t){return(t/=.5)<1?-0.5*(Math.sqrt(1-t*t)-1):.5*(Math.sqrt(1-(t-=2)*t)+1)},easeInElastic:function(t){var e=1.70158,a=0,i=1;return 0===t?0:1===(t/=1)?1:(a||(a=.3),it?-.5*(i*Math.pow(2,10*(t-=1))*Math.sin((1*t-e)*(2*Math.PI)/a)):i*Math.pow(2,-10*(t-=1))*Math.sin((1*t-e)*(2*Math.PI)/a)*.5+1)},easeInBack:function(t){var e=1.70158;return 1*(t/=1)*t*((e+1)*t-e)},easeOutBack:function(t){var e=1.70158;return 1*((t=t/1-1)*t*((e+1)*t+e)+1)},easeInOutBack:function(t){var e=1.70158;return(t/=.5)<1?.5*(t*t*(((e*=1.525)+1)*t-e)):.5*((t-=2)*t*(((e*=1.525)+1)*t+e)+2)},easeInBounce:function(t){return 1-r.easeOutBounce(1-t)},easeOutBounce:function(t){return(t/=1)<1/2.75?1*(7.5625*t*t):2/2.75>t?1*(7.5625*(t-=1.5/2.75)*t+.75):2.5/2.75>t?1*(7.5625*(t-=2.25/2.75)*t+.9375):1*(7.5625*(t-=2.625/2.75)*t+.984375)},easeInOutBounce:function(t){return.5>t?.5*r.easeInBounce(2*t):.5*r.easeOutBounce(2*t-1)+.5}};o.requestAnimFrame=function(){return window.requestAnimationFrame||window.webkitRequestAnimationFrame||window.mozRequestAnimationFrame||window.oRequestAnimationFrame||window.msRequestAnimationFrame||function(t){return window.setTimeout(t,1e3/60)}}(),o.cancelAnimFrame=function(){return window.cancelAnimationFrame||window.webkitCancelAnimationFrame||window.mozCancelAnimationFrame||window.oCancelAnimationFrame||window.msCancelAnimationFrame||function(t){return window.clearTimeout(t,1e3/60)}}(),o.getRelativePosition=function(t,e){var a,i,n=t.originalEvent||t,r=t.currentTarget||t.srcElement,l=r.getBoundingClientRect(),s=n.touches;s&&s.length>0?(a=s[0].clientX,i=s[0].clientY):(a=n.clientX,i=n.clientY);var d=parseFloat(o.getStyle(r,"padding-left")),u=parseFloat(o.getStyle(r,"padding-top")),c=parseFloat(o.getStyle(r,"padding-right")),h=parseFloat(o.getStyle(r,"padding-bottom")),f=l.right-l.left-d-c,g=l.bottom-l.top-u-h;return a=Math.round((a-l.left-d)/f*r.width/e.currentDevicePixelRatio),i=Math.round((i-l.top-u)/g*r.height/e.currentDevicePixelRatio),{x:a,y:i}},o.addEvent=function(t,e,a){t.addEventListener?t.addEventListener(e,a):t.attachEvent?t.attachEvent("on"+e,a):t["on"+e]=a},o.removeEvent=function(t,e,a){t.removeEventListener?t.removeEventListener(e,a,!1):t.detachEvent?t.detachEvent("on"+e,a):t["on"+e]=o.noop},o.bindEvents=function(t,e,a){var i=t.events=t.events||{};o.each(e,function(e){i[e]=function(){a.apply(t,arguments)},o.addEvent(t.chart.canvas,e,i[e])})},o.unbindEvents=function(t,e){var a=t.chart.canvas;o.each(e,function(t,e){o.removeEvent(a,e,t)})},o.getConstraintWidth=function(t){return n(t,"max-width","clientWidth")},o.getConstraintHeight=function(t){return n(t,"max-height","clientHeight")},o.getMaximumWidth=function(t){var e=t.parentNode,a=parseInt(o.getStyle(e,"padding-left"))+parseInt(o.getStyle(e,"padding-right")),i=e.clientWidth-a,n=o.getConstraintWidth(t);return isNaN(n)?i:Math.min(i,n)},o.getMaximumHeight=function(t){var e=t.parentNode,a=parseInt(o.getStyle(e,"padding-top"))+parseInt(o.getStyle(e,"padding-bottom")),i=e.clientHeight-a,n=o.getConstraintHeight(t);return isNaN(n)?i:Math.min(i,n)},o.getStyle=function(t,e){return t.currentStyle?t.currentStyle[e]:document.defaultView.getComputedStyle(t,null).getPropertyValue(e)},o.retinaScale=function(t){var e=t.ctx,a=t.canvas,i=a.width,n=a.height,o=t.currentDevicePixelRatio=window.devicePixelRatio||1;1!==o&&(a.height=n*o,a.width=i*o,e.scale(o,o),t.originalDevicePixelRatio=t.originalDevicePixelRatio||o),a.style.width=i+"px",a.style.height=n+"px"},o.clear=function(t){t.ctx.clearRect(0,0,t.width,t.height)},o.fontString=function(t,e,a){return e+" "+t+"px "+a},o.longestText=function(t,e,a,i){i=i||{};var n=i.data=i.data||{},r=i.garbageCollect=i.garbageCollect||[];i.font!==e&&(n=i.data={},r=i.garbageCollect=[],i.font=e),t.font=e;var l=0;o.each(a,function(e){void 0!==e&&null!==e&&o.isArray(e)!==!0?l=o.measureText(t,n,r,l,e):o.isArray(e)&&o.each(e,function(e){void 0===e||null===e||o.isArray(e)||(l=o.measureText(t,n,r,l,e))})});var s=r.length/2;if(s>a.length){for(var d=0;s>d;d++)delete n[r[d]];r.splice(0,s)}return l},o.measureText=function(t,e,a,i,n){var o=e[n];return o||(o=e[n]=t.measureText(n).width,a.push(n)),o>i&&(i=o),i},o.numberOfLabelLines=function(t){var e=1;return o.each(t,function(t){o.isArray(t)&&t.length>e&&(e=t.length)}),e},o.drawRoundedRectangle=function(t,e,a,i,n,o){t.beginPath(),t.moveTo(e+o,a),t.lineTo(e+i-o,a),t.quadraticCurveTo(e+i,a,e+i,a+o),t.lineTo(e+i,a+n-o),t.quadraticCurveTo(e+i,a+n,e+i-o,a+n),t.lineTo(e+o,a+n),t.quadraticCurveTo(e,a+n,e,a+n-o),t.lineTo(e,a+o),t.quadraticCurveTo(e,a,e+o,a),t.closePath()},o.color=function(e){return i?i(e instanceof CanvasGradient?t.defaults.global.defaultColor:e):(console.log("Color.js not found!"),e)},o.addResizeListener=function(t,e){var a=document.createElement("iframe"),i="chartjs-hidden-iframe";a.classlist?a.classlist.add(i):a.setAttribute("class",i);var n=a.style;n.width="100%",n.display="block",n.border=0,n.height=0,n.margin=0,n.position="absolute",n.left=0,n.right=0,n.top=0,n.bottom=0,t.insertBefore(a,t.firstChild),(a.contentWindow||a).onresize=function(){e&&e()}},o.removeResizeListener=function(t){var e=t.querySelector(".chartjs-hidden-iframe");e&&e.parentNode.removeChild(e)},o.isArray=Array.isArray?function(t){return Array.isArray(t)}:function(t){return"[object Array]"===Object.prototype.toString.call(t)},o.arrayEquals=function(t,e){var a,i,n,r;if(!t||!e||t.length!=e.length)return!1;for(a=0,i=t.length;i>a;++a)if(n=t[a],r=e[a],n instanceof Array&&r instanceof Array){if(!o.arrayEquals(n,r))return!1}else if(n!=r)return!1;return!0},o.callCallback=function(t,e,a){t&&"function"==typeof t.call&&t.apply(a,e)},o.getHoverColor=function(t){return t instanceof CanvasPattern?t:o.color(t).saturate(.5).darken(.1).rgbString()}}},{3:3}],26:[function(t,e,a){"use strict";e.exports=function(){var t=function(e,a){var i=this,n=t.helpers;return i.config=a,e.length&&e[0].getContext&&(e=e[0]),e.getContext&&(e=e.getContext("2d")),i.ctx=e,i.canvas=e.canvas,e.canvas.style.display=e.canvas.style.display||"block",i.width=e.canvas.width||parseInt(n.getStyle(e.canvas,"width"),10)||n.getMaximumWidth(e.canvas),i.height=e.canvas.height||parseInt(n.getStyle(e.canvas,"height"),10)||n.getMaximumHeight(e.canvas),i.aspectRatio=i.width/i.height,(isNaN(i.aspectRatio)||isFinite(i.aspectRatio)===!1)&&(i.aspectRatio=void 0!==a.aspectRatio?a.aspectRatio:2),i.originalCanvasStyleWidth=e.canvas.style.width,i.originalCanvasStyleHeight=e.canvas.style.height,n.retinaScale(i),a&&(i.controller=new t.Controller(i)),n.addResizeListener(e.canvas.parentNode,function(){i.controller&&i.controller.config.options.responsive&&i.controller.resize()}),i.controller?i.controller:i};return t.defaults={global:{responsive:!0,responsiveAnimationDuration:0,maintainAspectRatio:!0,events:["mousemove","mouseout","click","touchstart","touchmove"],hover:{onHover:null,mode:"single",animationDuration:400},onClick:null,defaultColor:"rgba(0,0,0,0.1)",defaultFontColor:"#666",defaultFontFamily:"'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",defaultFontSize:12,defaultFontStyle:"normal",showLines:!0,elements:{},legendCallback:function(t){var e=[];e.push('
      ');for(var a=0;a'),t.data.datasets[a].label&&e.push(t.data.datasets[a].label),e.push("");return e.push("
    "),e.join("")}}},t.Chart=t,t}},{}],27:[function(t,e,a){"use strict";e.exports=function(t){var e=t.helpers;t.layoutService={defaults:{},addBox:function(t,e){t.boxes||(t.boxes=[]),t.boxes.push(e)},removeBox:function(t,e){t.boxes&&t.boxes.splice(t.boxes.indexOf(e),1)},update:function(t,a,i){function n(t){var e,a=t.isHorizontal();a?(e=t.update(t.options.fullWidth?p:k,y),S-=e.height):(e=t.update(x,v),k-=e.width),w.push({horizontal:a,minSize:e,box:t})}function o(t){var a=e.findNextWhere(w,function(e){return e.box===t});if(a)if(t.isHorizontal()){var i={left:C,right:M,top:0,bottom:0};t.update(t.options.fullWidth?p:k,m/2,i)}else t.update(a.minSize.width,S)}function r(t){var a=e.findNextWhere(w,function(e){return e.box===t}),i={left:0,right:0,top:D,bottom:A};a&&t.update(a.minSize.width,S,i)}function l(t){t.isHorizontal()?(t.left=t.options.fullWidth?s:C,t.right=t.options.fullWidth?a-s:C+k,t.top=P,t.bottom=P+t.height,P=t.bottom):(t.left=T,t.right=T+t.width,t.top=D,t.bottom=D+S,T=t.right)}if(t){var s=0,d=0,u=e.where(t.boxes,function(t){return"left"===t.options.position}),c=e.where(t.boxes,function(t){return"right"===t.options.position}),h=e.where(t.boxes,function(t){return"top"===t.options.position}),f=e.where(t.boxes,function(t){return"bottom"===t.options.position}),g=e.where(t.boxes,function(t){return"chartArea"===t.options.position});h.sort(function(t,e){return(e.options.fullWidth?1:0)-(t.options.fullWidth?1:0)}),f.sort(function(t,e){return(t.options.fullWidth?1:0)-(e.options.fullWidth?1:0)});var p=a-2*s,m=i-2*d,b=p/2,v=m/2,x=(a-b)/(u.length+c.length),y=(i-v)/(h.length+f.length),k=p,S=m,w=[];e.each(u.concat(c,h,f),n);var C=s,M=s,D=d,A=d;e.each(u.concat(c),o),e.each(u,function(t){C+=t.width}),e.each(c,function(t){M+=t.width}),e.each(h.concat(f),o),e.each(h,function(t){D+=t.height}),e.each(f,function(t){A+=t.height}),e.each(u.concat(c),r),C=s,M=s,D=d,A=d,e.each(u,function(t){C+=t.width}),e.each(c,function(t){M+=t.width}),e.each(h,function(t){D+=t.height}),e.each(f,function(t){A+=t.height});var I=i-D-A,F=a-C-M;(F!==k||I!==S)&&(e.each(u,function(t){t.height=I}),e.each(c,function(t){t.height=I}),e.each(h,function(t){t.options.fullWidth||(t.width=F)}),e.each(f,function(t){t.options.fullWidth||(t.width=F)}),S=I,k=F);var T=s,P=d;e.each(u.concat(h),l),T+=k,P+=S,e.each(c,l),e.each(f,l),t.chartArea={left:C,top:D,right:C+k,bottom:D+S},e.each(g,function(e){e.left=t.chartArea.left,e.top=t.chartArea.top,e.right=t.chartArea.right,e.bottom=t.chartArea.bottom,e.update(k,S)})}}}}},{}],28:[function(t,e,a){"use strict";e.exports=function(t){var e=t.helpers,a=e.noop;t.defaults.global.legend={display:!0,position:"top",fullWidth:!0,reverse:!1,onClick:function(t,e){var a=e.datasetIndex,i=this.chart,n=i.getDatasetMeta(a);n.hidden=null===n.hidden?!i.data.datasets[a].hidden:null,i.update()},labels:{boxWidth:40,padding:10,generateLabels:function(t){var a=t.data;return e.isArray(a.datasets)?a.datasets.map(function(a,i){return{text:a.label,fillStyle:e.isArray(a.backgroundColor)?a.backgroundColor[0]:a.backgroundColor,hidden:!t.isDatasetVisible(i),lineCap:a.borderCapStyle,lineDash:a.borderDash,lineDashOffset:a.borderDashOffset,lineJoin:a.borderJoinStyle,lineWidth:a.borderWidth,strokeStyle:a.borderColor,datasetIndex:i}},this):[]}}},t.Legend=t.Element.extend({initialize:function(t){e.extend(this,t),this.legendHitBoxes=[],this.doughnutMode=!1},beforeUpdate:a,update:function(t,e,a){var i=this;return i.beforeUpdate(),i.maxWidth=t,i.maxHeight=e,i.margins=a,i.beforeSetDimensions(),i.setDimensions(),i.afterSetDimensions(),i.beforeBuildLabels(),i.buildLabels(),i.afterBuildLabels(),i.beforeFit(),i.fit(),i.afterFit(),i.afterUpdate(),i.minSize},afterUpdate:a,beforeSetDimensions:a,setDimensions:function(){var t=this;t.isHorizontal()?(t.width=t.maxWidth,t.left=0,t.right=t.width):(t.height=t.maxHeight,t.top=0,t.bottom=t.height),t.paddingLeft=0,t.paddingTop=0,t.paddingRight=0,t.paddingBottom=0,t.minSize={width:0,height:0}},afterSetDimensions:a,beforeBuildLabels:a,buildLabels:function(){var t=this;t.legendItems=t.options.labels.generateLabels.call(t,t.chart),t.options.reverse&&t.legendItems.reverse()},afterBuildLabels:a,beforeFit:a,fit:function(){var a=this,i=a.options,n=i.labels,o=i.display,r=a.ctx,l=t.defaults.global,s=e.getValueOrDefault,d=s(n.fontSize,l.defaultFontSize),u=s(n.fontStyle,l.defaultFontStyle),c=s(n.fontFamily,l.defaultFontFamily),h=e.fontString(d,u,c),f=a.legendHitBoxes=[],g=a.minSize,p=a.isHorizontal();if(p?(g.width=a.maxWidth,g.height=o?10:0):(g.width=o?10:0,g.height=a.maxHeight),o)if(r.font=h,p){var m=a.lineWidths=[0],b=a.legendItems.length?d+n.padding:0;r.textAlign="left",r.textBaseline="top",e.each(a.legendItems,function(t,e){var i=n.boxWidth+d/2+r.measureText(t.text).width;m[m.length-1]+i+n.padding>=a.width&&(b+=d+n.padding,m[m.length]=a.left),f[e]={left:0,top:0,width:i,height:d},m[m.length-1]+=i+n.padding}),g.height+=b}else{var v=n.padding,x=a.columnWidths=[],y=n.padding,k=0,S=0,w=d+v;e.each(a.legendItems,function(t,e){var a=n.boxWidth+d/2+r.measureText(t.text).width;S+w>g.height&&(y+=k+n.padding,x.push(k),k=0,S=0),k=Math.max(k,a),S+=w,f[e]={left:0,top:0,width:a,height:d}}),y+=k,x.push(k),g.width+=y}a.width=g.width,a.height=g.height},afterFit:a,isHorizontal:function(){return"top"===this.options.position||"bottom"===this.options.position},draw:function(){var a=this,i=a.options,n=i.labels,o=t.defaults.global,r=o.elements.line,l=a.width,s=(a.height,a.lineWidths);if(i.display){var d,u=a.ctx,c=e.getValueOrDefault,h=c(n.fontColor,o.defaultFontColor),f=c(n.fontSize,o.defaultFontSize),g=c(n.fontStyle,o.defaultFontStyle),p=c(n.fontFamily,o.defaultFontFamily),m=e.fontString(f,g,p);u.textAlign="left",u.textBaseline="top",u.lineWidth=.5,u.strokeStyle=h,u.fillStyle=h,u.font=m;var b=n.boxWidth,v=a.legendHitBoxes,x=function(t,e,a){u.save(),u.fillStyle=c(a.fillStyle,o.defaultColor),u.lineCap=c(a.lineCap,r.borderCapStyle),u.lineDashOffset=c(a.lineDashOffset,r.borderDashOffset),u.lineJoin=c(a.lineJoin,r.borderJoinStyle),u.lineWidth=c(a.lineWidth,r.borderWidth),u.strokeStyle=c(a.strokeStyle,o.defaultColor),u.setLineDash&&u.setLineDash(c(a.lineDash,r.borderDash)),u.strokeRect(t,e,b,f),u.fillRect(t,e,b,f),u.restore()},y=function(t,e,a,i){u.fillText(a.text,b+f/2+t,e),a.hidden&&(u.beginPath(),u.lineWidth=2,u.moveTo(b+f/2+t,e+f/2),u.lineTo(b+f/2+t+i,e+f/2),u.stroke())},k=a.isHorizontal();d=k?{x:a.left+(l-s[0])/2,y:a.top+n.padding,line:0}:{x:a.left+n.padding,y:a.top,line:0};var S=f+n.padding;e.each(a.legendItems,function(t,e){var i=u.measureText(t.text).width,o=b+f/2+i,r=d.x,c=d.y;k?r+o>=l&&(c=d.y+=f+n.padding,d.line++,r=d.x=a.left+(l-s[d.line])/2):c+S>a.bottom&&(r=d.x=r+a.columnWidths[d.line]+n.padding,c=d.y=a.top,d.line++),x(r,c,t),v[e].left=r,v[e].top=c,y(r,c,t,i),k?d.x+=o+n.padding:d.y+=S})}},handleEvent:function(t){var a=this,i=e.getRelativePosition(t,a.chart.chart),n=i.x,o=i.y,r=a.options;if(n>=a.left&&n<=a.right&&o>=a.top&&o<=a.bottom)for(var l=a.legendHitBoxes,s=0;s=d.left&&n<=d.left+d.width&&o>=d.top&&o<=d.top+d.height){r.onClick&&r.onClick.call(a,t,a.legendItems[s]);break}}}}),t.plugins.register({beforeInit:function(e){var a=e.options,i=a.legend;i&&(e.legend=new t.Legend({ctx:e.chart.ctx,options:i,chart:e}),t.layoutService.addBox(e,e.legend))}})}},{}],29:[function(t,e,a){"use strict";e.exports=function(t){var e=t.helpers.noop;t.plugins={_plugins:[],register:function(t){var e=this._plugins;[].concat(t).forEach(function(t){-1===e.indexOf(t)&&e.push(t)})},unregister:function(t){var e=this._plugins;[].concat(t).forEach(function(t){var a=e.indexOf(t);-1!==a&&e.splice(a,1)})},clear:function(){this._plugins=[]},count:function(){return this._plugins.length},getAll:function(){return this._plugins},notify:function(t,e){var a,i,n=this._plugins,o=n.length;for(a=0;o>a;++a)if(i=n[a],"function"==typeof i[t]&&i[t].apply(i,e||[])===!1)return!1;return!0}},t.PluginBase=t.Element.extend({beforeInit:e,afterInit:e,beforeUpdate:e,afterUpdate:e,beforeDraw:e,afterDraw:e,destroy:e}),t.pluginService=t.plugins}},{}],30:[function(t,e,a){"use strict";e.exports=function(t){var e=t.helpers;t.defaults.scale={display:!0,position:"left",gridLines:{display:!0,color:"rgba(0, 0, 0, 0.1)",lineWidth:1,drawBorder:!0,drawOnChartArea:!0,drawTicks:!0,tickMarkLength:10,zeroLineWidth:1,zeroLineColor:"rgba(0,0,0,0.25)",offsetGridLines:!1},scaleLabel:{labelString:"",display:!1},ticks:{beginAtZero:!1,minRotation:0,maxRotation:50,mirror:!1,padding:10,reverse:!1,display:!0,autoSkip:!0,autoSkipPadding:0,labelOffset:0,callback:function(t){return e.isArray(t)?t:""+t}}},t.Scale=t.Element.extend({beforeUpdate:function(){e.callCallback(this.options.beforeUpdate,[this])},update:function(t,a,i){var n=this;return n.beforeUpdate(),n.maxWidth=t,n.maxHeight=a,n.margins=e.extend({left:0,right:0,top:0,bottom:0},i),n.beforeSetDimensions(),n.setDimensions(),n.afterSetDimensions(),n.beforeDataLimits(),n.determineDataLimits(),n.afterDataLimits(),n.beforeBuildTicks(),n.buildTicks(),n.afterBuildTicks(),n.beforeTickToLabelConversion(),n.convertTicksToLabels(),n.afterTickToLabelConversion(),n.beforeCalculateTickRotation(),n.calculateTickRotation(),n.afterCalculateTickRotation(),n.beforeFit(),n.fit(),n.afterFit(),n.afterUpdate(),n.minSize},afterUpdate:function(){e.callCallback(this.options.afterUpdate,[this])},beforeSetDimensions:function(){e.callCallback(this.options.beforeSetDimensions,[this])},setDimensions:function(){var t=this;t.isHorizontal()?(t.width=t.maxWidth,t.left=0,t.right=t.width):(t.height=t.maxHeight,t.top=0,t.bottom=t.height),t.paddingLeft=0,t.paddingTop=0,t.paddingRight=0,t.paddingBottom=0},afterSetDimensions:function(){e.callCallback(this.options.afterSetDimensions,[this])},beforeDataLimits:function(){e.callCallback(this.options.beforeDataLimits,[this])},determineDataLimits:e.noop,afterDataLimits:function(){e.callCallback(this.options.afterDataLimits,[this])},beforeBuildTicks:function(){e.callCallback(this.options.beforeBuildTicks,[this])},buildTicks:e.noop,afterBuildTicks:function(){e.callCallback(this.options.afterBuildTicks,[this])},beforeTickToLabelConversion:function(){e.callCallback(this.options.beforeTickToLabelConversion,[this])},convertTicksToLabels:function(){var t=this;t.ticks=t.ticks.map(function(e,a,i){return t.options.ticks.userCallback?t.options.ticks.userCallback(e,a,i):t.options.ticks.callback(e,a,i)},t)},afterTickToLabelConversion:function(){e.callCallback(this.options.afterTickToLabelConversion,[this])},beforeCalculateTickRotation:function(){e.callCallback(this.options.beforeCalculateTickRotation,[this])},calculateTickRotation:function(){var a=this,i=a.ctx,n=t.defaults.global,o=a.options.ticks,r=e.getValueOrDefault(o.fontSize,n.defaultFontSize),l=e.getValueOrDefault(o.fontStyle,n.defaultFontStyle),s=e.getValueOrDefault(o.fontFamily,n.defaultFontFamily),d=e.fontString(r,l,s);i.font=d;var u,c=i.measureText(a.ticks[0]).width,h=i.measureText(a.ticks[a.ticks.length-1]).width;if(a.labelRotation=o.minRotation||0,a.paddingRight=0,a.paddingLeft=0,a.options.display&&a.isHorizontal()){a.paddingRight=h/2+3,a.paddingLeft=c/2+3,a.longestTextCache||(a.longestTextCache={});for(var f,g,p=e.longestText(i,d,a.ticks,a.longestTextCache),m=p,b=a.getPixelForTick(1)-a.getPixelForTick(0)-6;m>b&&a.labelRotationa.yLabelWidth&&(a.paddingLeft=u+r/2),a.paddingRight=r/2,g*p>a.maxHeight){a.labelRotation--;break}a.labelRotation++,m=f*p}}a.margins&&(a.paddingLeft=Math.max(a.paddingLeft-a.margins.left,0),a.paddingRight=Math.max(a.paddingRight-a.margins.right,0))},afterCalculateTickRotation:function(){e.callCallback(this.options.afterCalculateTickRotation,[this])},beforeFit:function(){e.callCallback(this.options.beforeFit,[this])},fit:function(){var a=this,i=a.minSize={width:0,height:0},n=a.options,o=t.defaults.global,r=n.ticks,l=n.scaleLabel,s=n.display,d=a.isHorizontal(),u=e.getValueOrDefault(r.fontSize,o.defaultFontSize),c=e.getValueOrDefault(r.fontStyle,o.defaultFontStyle),h=e.getValueOrDefault(r.fontFamily,o.defaultFontFamily),f=e.fontString(u,c,h),g=e.getValueOrDefault(l.fontSize,o.defaultFontSize),p=e.getValueOrDefault(l.fontStyle,o.defaultFontStyle),m=e.getValueOrDefault(l.fontFamily,o.defaultFontFamily),b=(e.fontString(g,p,m),n.gridLines.tickMarkLength);if(d?i.width=a.isFullWidth()?a.maxWidth-a.margins.left-a.margins.right:a.maxWidth:i.width=s?b:0,d?i.height=s?b:0:i.height=a.maxHeight,l.display&&s&&(d?i.height+=1.5*g:i.width+=1.5*g),r.display&&s){a.longestTextCache||(a.longestTextCache={});var v=e.longestText(a.ctx,f,a.ticks,a.longestTextCache),x=e.numberOfLabelLines(a.ticks),y=.5*u;if(d){a.longestLabelWidth=v;var k=Math.sin(e.toRadians(a.labelRotation))*a.longestLabelWidth+u*x+y*x;i.height=Math.min(a.maxHeight,i.height+k),a.ctx.font=f;var S=a.ctx.measureText(a.ticks[0]).width,w=a.ctx.measureText(a.ticks[a.ticks.length-1]).width,C=Math.cos(e.toRadians(a.labelRotation)),M=Math.sin(e.toRadians(a.labelRotation));a.paddingLeft=0!==a.labelRotation?C*S+3:S/2+3,a.paddingRight=0!==a.labelRotation?M*(u/2)+3:w/2+3}else{var D=a.maxWidth-i.width,A=r.mirror;A?v=0:v+=a.options.ticks.padding,D>v?i.width+=v:i.width=a.maxWidth,a.paddingTop=u/2,a.paddingBottom=u/2}}a.margins&&(a.paddingLeft=Math.max(a.paddingLeft-a.margins.left,0),a.paddingTop=Math.max(a.paddingTop-a.margins.top,0),a.paddingRight=Math.max(a.paddingRight-a.margins.right,0),a.paddingBottom=Math.max(a.paddingBottom-a.margins.bottom,0)),a.width=i.width,a.height=i.height},afterFit:function(){e.callCallback(this.options.afterFit,[this])},isHorizontal:function(){return"top"===this.options.position||"bottom"===this.options.position},isFullWidth:function(){return this.options.fullWidth},getRightValue:function a(t){return null===t||"undefined"==typeof t?NaN:"number"==typeof t&&isNaN(t)?NaN:"object"==typeof t?t instanceof Date||t.isValid?t:a(this.isHorizontal()?t.x:t.y):t},getLabelForIndex:e.noop,getPixelForValue:e.noop,getValueForPixel:e.noop,getPixelForTick:function(t,e){var a=this;if(a.isHorizontal()){var i=a.width-(a.paddingLeft+a.paddingRight),n=i/Math.max(a.ticks.length-(a.options.gridLines.offsetGridLines?0:1),1),o=n*t+a.paddingLeft;e&&(o+=n/2);var r=a.left+Math.round(o);return r+=a.isFullWidth()?a.margins.left:0}var l=a.height-(a.paddingTop+a.paddingBottom);return a.top+t*(l/(a.ticks.length-1))},getPixelForDecimal:function(t){var e=this;if(e.isHorizontal()){var a=e.width-(e.paddingLeft+e.paddingRight),i=a*t+e.paddingLeft,n=e.left+Math.round(i);return n+=e.isFullWidth()?e.margins.left:0}return e.top+t*e.height},getBasePixel:function(){var t=this,e=t.min,a=t.max;return t.getPixelForValue(t.beginAtZero?0:0>e&&0>a?a:e>0&&a>0?e:0)},draw:function(a){var i=this,n=i.options;if(n.display){ +var o,r,l=i.ctx,s=t.defaults.global,d=n.ticks,u=n.gridLines,c=n.scaleLabel,h=0!==i.labelRotation,f=d.autoSkip,g=i.isHorizontal();d.maxTicksLimit&&(r=d.maxTicksLimit);var p=e.getValueOrDefault(d.fontColor,s.defaultFontColor),m=e.getValueOrDefault(d.fontSize,s.defaultFontSize),b=e.getValueOrDefault(d.fontStyle,s.defaultFontStyle),v=e.getValueOrDefault(d.fontFamily,s.defaultFontFamily),x=e.fontString(m,b,v),y=u.tickMarkLength,k=e.getValueOrDefault(c.fontColor,s.defaultFontColor),S=e.getValueOrDefault(c.fontSize,s.defaultFontSize),w=e.getValueOrDefault(c.fontStyle,s.defaultFontStyle),C=e.getValueOrDefault(c.fontFamily,s.defaultFontFamily),M=e.fontString(S,w,C),D=e.toRadians(i.labelRotation),A=Math.cos(D),I=(Math.sin(D),i.longestLabelWidth*A);l.fillStyle=p;var F=[];if(g){if(o=!1,h&&(I/=2),(I+d.autoSkipPadding)*i.ticks.length>i.width-(i.paddingLeft+i.paddingRight)&&(o=1+Math.floor((I+d.autoSkipPadding)*i.ticks.length/(i.width-(i.paddingLeft+i.paddingRight)))),r&&i.ticks.length>r)for(;!o||i.ticks.length/(o||1)>r;)o||(o=1),o+=1;f||(o=!1)}var T="right"===n.position?i.left:i.right-y,P="right"===n.position?i.left+y:i.right,_="bottom"===n.position?i.top:i.bottom-y,R="bottom"===n.position?i.top+y:i.bottom;if(e.each(i.ticks,function(t,r){if(void 0!==t&&null!==t){var l=i.ticks.length===r+1,s=o>1&&r%o>0||r%o===0&&r+o>=i.ticks.length;if((!s||l)&&void 0!==t&&null!==t){var c,f;r===("undefined"!=typeof i.zeroLineIndex?i.zeroLineIndex:0)?(c=u.zeroLineWidth,f=u.zeroLineColor):(c=e.getValueAtIndexOrDefault(u.lineWidth,r),f=e.getValueAtIndexOrDefault(u.color,r));var p,m,b,v,x,k,S,w,C,M,A,I="middle";if(g){h||(I="top"===n.position?"bottom":"top"),A=h?"right":"center";var V=i.getPixelForTick(r)+e.aliasPixel(c);C=i.getPixelForTick(r,u.offsetGridLines)+d.labelOffset,M=h?i.top+12:"top"===n.position?i.bottom-y:i.top+y,p=b=x=S=V,m=_,v=R,k=a.top,w=a.bottom}else{"left"===n.position?d.mirror?(C=i.right+d.padding,A="left"):(C=i.right-d.padding,A="right"):d.mirror?(C=i.left-d.padding,A="right"):(C=i.left+d.padding,A="left");var O=i.getPixelForTick(r);O+=e.aliasPixel(c),M=i.getPixelForTick(r,u.offsetGridLines),p=T,b=P,x=a.left,S=a.right,m=v=k=w=O}F.push({tx1:p,ty1:m,tx2:b,ty2:v,x1:x,y1:k,x2:S,y2:w,labelX:C,labelY:M,glWidth:c,glColor:f,rotation:-1*D,label:t,textBaseline:I,textAlign:A})}}}),e.each(F,function(t){if(u.display&&(l.lineWidth=t.glWidth,l.strokeStyle=t.glColor,l.beginPath(),u.drawTicks&&(l.moveTo(t.tx1,t.ty1),l.lineTo(t.tx2,t.ty2)),u.drawOnChartArea&&(l.moveTo(t.x1,t.y1),l.lineTo(t.x2,t.y2)),l.stroke()),d.display){l.save(),l.translate(t.labelX,t.labelY),l.rotate(t.rotation),l.font=x,l.textBaseline=t.textBaseline,l.textAlign=t.textAlign;var a=t.label;if(e.isArray(a))for(var i=0,n=0;ie;++e){var o=t[e];if(o&&o.hasValue()){var r=o.tooltipPosition();i.push(r.x),n.push(r.y)}}var l=0,s=0;for(e=0,a-i.length;a>e;++e)l+=i[e],s+=n[e];return{x:Math.round(l/i.length),y:Math.round(s/i.length)}}function i(t){var e=t._xScale,a=t._yScale||t._scale,i=t._index,n=t._datasetIndex;return{xLabel:e?e.getLabelForIndex(i,n):"",yLabel:a?a.getLabelForIndex(i,n):"",index:i,datasetIndex:n}}var n=t.helpers;t.defaults.global.tooltips={enabled:!0,custom:null,mode:"single",backgroundColor:"rgba(0,0,0,0.8)",titleFontStyle:"bold",titleSpacing:2,titleMarginBottom:6,titleFontColor:"#fff",titleAlign:"left",bodySpacing:2,bodyFontColor:"#fff",bodyAlign:"left",footerFontStyle:"bold",footerSpacing:2,footerMarginTop:6,footerFontColor:"#fff",footerAlign:"left",yPadding:6,xPadding:6,yAlign:"center",xAlign:"center",caretSize:5,cornerRadius:6,multiKeyBackground:"#fff",callbacks:{beforeTitle:n.noop,title:function(t,e){var a="",i=e.labels,n=i?i.length:0;if(t.length>0){var o=t[0];o.xLabel?a=o.xLabel:n>0&&o.indexe;++e)g.push(i(d[e]));l.itemSort&&(g=g.sort(l.itemSort)),d.length>1&&n.each(g,function(t){h.push(l.callbacks.labelColor.call(r,t,c))}),n.extend(s,{title:r.getTitle(g,u),beforeBody:r.getBeforeBody(g,u),body:r.getBody(g,u),afterBody:r.getAfterBody(g,u),footer:r.getFooter(g,u),x:Math.round(f.x),y:Math.round(f.y),caretPadding:n.getValueOrDefault(f.padding,2),labelColors:h});var p=r.getTooltipSize(s);r.determineAlignment(p),n.extend(s,r.getBackgroundPoint(s,p))}else r._model.opacity=0;return t&&l.custom&&l.custom.call(r,s),r},getTooltipSize:function(t){var e=this._chart.ctx,a={height:2*t.yPadding,width:0},i=t.body,o=i.reduce(function(t,e){return t+e.before.length+e.lines.length+e.after.length},0);o+=t.beforeBody.length+t.afterBody.length;var r=t.title.length,l=t.footer.length,s=t.titleFontSize,d=t.bodyFontSize,u=t.footerFontSize;a.height+=r*s,a.height+=(r-1)*t.titleSpacing,a.height+=r?t.titleMarginBottom:0,a.height+=o*d,a.height+=o?(o-1)*t.bodySpacing:0,a.height+=l?t.footerMarginTop:0,a.height+=l*u,a.height+=l?(l-1)*t.footerSpacing:0;var c=0,h=function(t){a.width=Math.max(a.width,e.measureText(t).width+c)};return e.font=n.fontString(s,t._titleFontStyle,t._titleFontFamily),n.each(t.title,h),e.font=n.fontString(d,t._bodyFontStyle,t._bodyFontFamily),n.each(t.beforeBody.concat(t.afterBody),h),c=i.length>1?d+2:0,n.each(i,function(t){n.each(t.before,h),n.each(t.lines,h),n.each(t.after,h)}),c=0,e.font=n.fontString(u,t._footerFontStyle,t._footerFontFamily),n.each(t.footer,h),a.width+=2*t.xPadding,a},determineAlignment:function(t){var e=this,a=e._model,i=e._chart,n=e._chartInstance.chartArea;a.yi.height-t.height&&(a.yAlign="bottom");var o,r,l,s,d,u=(n.left+n.right)/2,c=(n.top+n.bottom)/2;"center"===a.yAlign?(o=function(t){return u>=t},r=function(t){return t>u}):(o=function(e){return e<=t.width/2},r=function(e){return e>=i.width-t.width/2}),l=function(e){return e+t.width>i.width},s=function(e){return e-t.width<0},d=function(t){return c>=t?"top":"bottom"},o(a.x)?(a.xAlign="left",l(a.x)&&(a.xAlign="center",a.yAlign=d(a.y))):r(a.x)&&(a.xAlign="right",s(a.x)&&(a.xAlign="center",a.yAlign=d(a.y)))},getBackgroundPoint:function(t,e){var a={x:t.x,y:t.y},i=t.caretSize,n=t.caretPadding,o=t.cornerRadius,r=t.xAlign,l=t.yAlign,s=i+n,d=o+n;return"right"===r?a.x-=e.width:"center"===r&&(a.x-=e.width/2),"top"===l?a.y+=s:"bottom"===l?a.y-=e.height+s:a.y-=e.height/2,"center"===l?"left"===r?a.x+=s:"right"===r&&(a.x-=s):"left"===r?a.x-=d:"right"===r&&(a.x+=d),a},drawCaret:function(t,e,a,i){var o,r,l,s,d,u,c=this._view,h=this._chart.ctx,f=c.caretSize,g=c.cornerRadius,p=c.xAlign,m=c.yAlign,b=t.x,v=t.y,x=e.width,y=e.height;"center"===m?("left"===p?(o=b,r=o-f,l=o):(o=b+x,r=o+f,l=o),d=v+y/2,s=d-f,u=d+f):("left"===p?(o=b+g,r=o+f,l=r+f):"right"===p?(o=b+x-g,r=o-f,l=r-f):(r=b+x/2,o=r-f,l=r+f),"top"===m?(s=v,d=s-f,u=s):(s=v+y,d=s+f,u=s));var k=n.color(c.backgroundColor);h.fillStyle=k.alpha(a*k.alpha()).rgbString(),h.beginPath(),h.moveTo(o,s),h.lineTo(r,d),h.lineTo(l,u),h.closePath(),h.fill()},drawTitle:function(t,e,a,i){var o=e.title;if(o.length){a.textAlign=e._titleAlign,a.textBaseline="top";var r=e.titleFontSize,l=e.titleSpacing,s=n.color(e.titleFontColor);a.fillStyle=s.alpha(i*s.alpha()).rgbString(),a.font=n.fontString(r,e._titleFontStyle,e._titleFontFamily);var d,u;for(d=0,u=o.length;u>d;++d)a.fillText(o[d],t.x,t.y),t.y+=r+l,d+1===o.length&&(t.y+=e.titleMarginBottom-l)}},drawBody:function(t,e,a,i){var o=e.bodyFontSize,r=e.bodySpacing,l=e.body;a.textAlign=e._bodyAlign,a.textBaseline="top";var s=n.color(e.bodyFontColor),d=s.alpha(i*s.alpha()).rgbString();a.fillStyle=d,a.font=n.fontString(o,e._bodyFontStyle,e._bodyFontFamily);var u=0,c=function(e){a.fillText(e,t.x+u,t.y),t.y+=o+r};n.each(e.beforeBody,c);var h=l.length>1;u=h?o+2:0,n.each(l,function(r,l){n.each(r.before,c),n.each(r.lines,function(r){h&&(a.fillStyle=n.color(e.legendColorBackground).alpha(i).rgbaString(),a.fillRect(t.x,t.y,o,o),a.strokeStyle=n.color(e.labelColors[l].borderColor).alpha(i).rgbaString(),a.strokeRect(t.x,t.y,o,o),a.fillStyle=n.color(e.labelColors[l].backgroundColor).alpha(i).rgbaString(),a.fillRect(t.x+1,t.y+1,o-2,o-2),a.fillStyle=d),c(r)}),n.each(r.after,c)}),u=0,n.each(e.afterBody,c),t.y-=r},drawFooter:function(t,e,a,i){var o=e.footer;if(o.length){t.y+=e.footerMarginTop,a.textAlign=e._footerAlign,a.textBaseline="top";var r=n.color(e.footerFontColor);a.fillStyle=r.alpha(i*r.alpha()).rgbString(),a.font=n.fontString(e.footerFontSize,e._footerFontStyle,e._footerFontFamily),n.each(o,function(i){a.fillText(i,t.x,t.y),t.y+=e.footerFontSize+e.footerSpacing})}},draw:function(){var t=this._chart.ctx,e=this._view;if(0!==e.opacity){var a=this.getTooltipSize(e),i={x:e.x,y:e.y},o=Math.abs(e.opacity<.001)?0:e.opacity;if(this._options.enabled){var r=n.color(e.backgroundColor);t.fillStyle=r.alpha(o*r.alpha()).rgbString(),n.drawRoundedRectangle(t,i.x,i.y,a.width,a.height,e.cornerRadius),t.fill(),this.drawCaret(i,a,o,e.caretPadding),i.x+=e.xPadding,i.y+=e.yPadding,this.drawTitle(i,e,t,o),this.drawBody(i,e,t,o),this.drawFooter(i,e,t,o)}}}})}},{}],34:[function(t,e,a){"use strict";e.exports=function(t,e){var a=t.helpers,i=t.defaults.global;i.elements.arc={backgroundColor:i.defaultColor,borderColor:"#fff",borderWidth:2},t.elements.Arc=t.Element.extend({inLabelRange:function(t){var e=this._view;return e?Math.pow(t-e.x,2)s;)s+=2*Math.PI;for(;o>s;)o-=2*Math.PI;for(;l>o;)o+=2*Math.PI;var d=o>=l&&s>=o,u=r>=i.innerRadius&&r<=i.outerRadius;return d&&u}return!1},tooltipPosition:function(){var t=this._view,e=t.startAngle+(t.endAngle-t.startAngle)/2,a=(t.outerRadius-t.innerRadius)/2+t.innerRadius;return{x:t.x+Math.cos(e)*a,y:t.y+Math.sin(e)*a}},draw:function(){var t=this._chart.ctx,e=this._view,a=e.startAngle,i=e.endAngle;t.beginPath(),t.arc(e.x,e.y,e.outerRadius,a,i),t.arc(e.x,e.y,e.innerRadius,i,a,!0),t.closePath(),t.strokeStyle=e.borderColor,t.lineWidth=e.borderWidth,t.fillStyle=e.backgroundColor,t.fill(),t.lineJoin="bevel",e.borderWidth&&t.stroke()}})}},{}],35:[function(t,e,a){"use strict";e.exports=function(t){var e=t.helpers,a=t.defaults.global;t.defaults.global.elements.line={tension:.4,backgroundColor:a.defaultColor,borderWidth:3,borderColor:a.defaultColor,borderCapStyle:"butt",borderDash:[],borderDashOffset:0,borderJoinStyle:"miter",fill:!0},t.elements.Line=t.Element.extend({lineToNextPoint:function(t,e,a,i,n){var o=this,r=o._chart.ctx,l=o._view?o._view.spanGaps:!1;e._view.skip&&!l?i.call(o,t,e,a):t._view.skip&&!l?n.call(o,t,e,a):0===e._view.tension?r.lineTo(e._view.x,e._view.y):r.bezierCurveTo(t._view.controlPointNextX,t._view.controlPointNextY,e._view.controlPointPreviousX,e._view.controlPointPreviousY,e._view.x,e._view.y)},draw:function(){function t(t){r._view.skip||l._view.skip?t&&o.lineTo(i._view.scaleZero.x,i._view.scaleZero.y):o.bezierCurveTo(l._view.controlPointNextX,l._view.controlPointNextY,r._view.controlPointPreviousX,r._view.controlPointPreviousY,r._view.x,r._view.y)}var i=this,n=i._view,o=i._chart.ctx,r=i._children[0],l=i._children[i._children.length-1];o.save(),i._children.length>0&&n.fill&&(o.beginPath(),e.each(i._children,function(t,a){var r=e.previousItem(i._children,a),l=e.nextItem(i._children,a);0===a?(i._loop?o.moveTo(n.scaleZero.x,n.scaleZero.y):o.moveTo(t._view.x,n.scaleZero),t._view.skip?i._loop||o.moveTo(l._view.x,i._view.scaleZero):o.lineTo(t._view.x,t._view.y)):i.lineToNextPoint(r,t,l,function(t,e,a){i._loop?o.lineTo(i._view.scaleZero.x,i._view.scaleZero.y):(o.lineTo(t._view.x,i._view.scaleZero),o.moveTo(a._view.x,i._view.scaleZero))},function(t,e){o.lineTo(e._view.x,e._view.y)})},i),i._loop?t(!0):(o.lineTo(i._children[i._children.length-1]._view.x,n.scaleZero),o.lineTo(i._children[0]._view.x,n.scaleZero)),o.fillStyle=n.backgroundColor||a.defaultColor,o.closePath(),o.fill());var s=a.elements.line;o.lineCap=n.borderCapStyle||s.borderCapStyle,o.setLineDash&&o.setLineDash(n.borderDash||s.borderDash),o.lineDashOffset=n.borderDashOffset||s.borderDashOffset,o.lineJoin=n.borderJoinStyle||s.borderJoinStyle,o.lineWidth=n.borderWidth||s.borderWidth,o.strokeStyle=n.borderColor||a.defaultColor,o.beginPath(),e.each(i._children,function(t,a){var n=e.previousItem(i._children,a),r=e.nextItem(i._children,a);0===a?o.moveTo(t._view.x,t._view.y):i.lineToNextPoint(n,t,r,function(t,e,a){o.moveTo(a._view.x,a._view.y)},function(t,e){o.moveTo(e._view.x,e._view.y)})},i),i._loop&&i._children.length>0&&t(),o.stroke(),o.restore()}})}},{}],36:[function(t,e,a){"use strict";e.exports=function(t){var e=t.helpers,a=t.defaults.global,i=a.defaultColor;a.elements.point={radius:3,pointStyle:"circle",backgroundColor:i,borderWidth:1,borderColor:i,hitRadius:1,hoverRadius:4,hoverBorderWidth:1},t.elements.Point=t.Element.extend({inRange:function(t,e){var a=this._view;return a?Math.pow(t-a.x,2)+Math.pow(e-a.y,2)=h)){switch(u.strokeStyle=d.borderColor||i,u.lineWidth=e.getValueOrDefault(d.borderWidth,a.elements.point.borderWidth),u.fillStyle=d.backgroundColor||i,c){default:u.beginPath(),u.arc(f,g,h,0,2*Math.PI),u.closePath(),u.fill();break;case"triangle":u.beginPath(),n=3*h/Math.sqrt(3),l=n*Math.sqrt(3)/2,u.moveTo(f-n/2,g+l/3),u.lineTo(f+n/2,g+l/3),u.lineTo(f,g-2*l/3),u.closePath(),u.fill();break;case"rect":s=1/Math.SQRT2*h,u.fillRect(f-s,g-s,2*s,2*s),u.strokeRect(f-s,g-s,2*s,2*s);break;case"rectRot":s=1/Math.SQRT2*h,u.beginPath(),u.moveTo(f-s,g),u.lineTo(f,g+s),u.lineTo(f+s,g),u.lineTo(f,g-s),u.closePath(),u.fill();break;case"cross":u.beginPath(),u.moveTo(f,g+h),u.lineTo(f,g-h),u.moveTo(f-h,g),u.lineTo(f+h,g),u.closePath();break;case"crossRot":u.beginPath(),o=Math.cos(Math.PI/4)*h,r=Math.sin(Math.PI/4)*h,u.moveTo(f-o,g-r),u.lineTo(f+o,g+r),u.moveTo(f-o,g+r),u.lineTo(f+o,g-r),u.closePath();break;case"star":u.beginPath(),u.moveTo(f,g+h),u.lineTo(f,g-h),u.moveTo(f-h,g),u.lineTo(f+h,g),o=Math.cos(Math.PI/4)*h,r=Math.sin(Math.PI/4)*h,u.moveTo(f-o,g-r),u.lineTo(f+o,g+r),u.moveTo(f-o,g+r),u.lineTo(f+o,g-r),u.closePath();break;case"line":u.beginPath(),u.moveTo(f-h,g),u.lineTo(f+h,g),u.closePath();break;case"dash":u.beginPath(),u.moveTo(f,g),u.lineTo(f+h,g),u.closePath()}u.stroke()}}}})}},{}],37:[function(t,e,a){"use strict";e.exports=function(t){var e=(t.helpers,t.defaults.global);e.elements.rectangle={backgroundColor:e.defaultColor,borderWidth:0,borderColor:e.defaultColor,borderSkipped:"bottom"},t.elements.Rectangle=t.Element.extend({draw:function(){function t(t){return s[(u+t)%4]}var e=this._chart.ctx,a=this._view,i=a.width/2,n=a.x-i,o=a.x+i,r=a.base-(a.base-a.y),l=a.borderWidth/2;a.borderWidth&&(n+=l,o-=l,r+=l),e.beginPath(),e.fillStyle=a.backgroundColor,e.strokeStyle=a.borderColor,e.lineWidth=a.borderWidth;var s=[[n,a.base],[n,r],[o,r],[o,a.base]],d=["bottom","left","top","right"],u=d.indexOf(a.borderSkipped,0);-1===u&&(u=0),e.moveTo.apply(e,t(0));for(var c=1;4>c;c++)e.lineTo.apply(e,t(c));e.fill(),a.borderWidth&&e.stroke()},height:function(){var t=this._view;return t.base-t.y},inRange:function(t,e){var a=this._view;return a?a.y=a.x-a.width/2&&t<=a.x+a.width/2&&e>=a.y&&e<=a.base:t>=a.x-a.width/2&&t<=a.x+a.width/2&&e>=a.base&&e<=a.y:!1},inLabelRange:function(t){var e=this._view;return e?t>=e.x-e.width/2&&t<=e.x+e.width/2:!1},tooltipPosition:function(){var t=this._view;return{x:t.x,y:t.y}}})}},{}],38:[function(t,e,a){"use strict";e.exports=function(t){var e=t.helpers,a={position:"bottom"},i=t.Scale.extend({determineDataLimits:function(){var t=this;t.minIndex=0,t.maxIndex=t.chart.data.labels.length-1;var a;void 0!==t.options.ticks.min&&(a=e.indexOf(t.chart.data.labels,t.options.ticks.min),t.minIndex=-1!==a?a:t.minIndex),void 0!==t.options.ticks.max&&(a=e.indexOf(t.chart.data.labels,t.options.ticks.max),t.maxIndex=-1!==a?a:t.maxIndex),t.min=t.chart.data.labels[t.minIndex],t.max=t.chart.data.labels[t.maxIndex]},buildTicks:function(t){var e=this;e.ticks=0===e.minIndex&&e.maxIndex===e.chart.data.labels.length-1?e.chart.data.labels:e.chart.data.labels.slice(e.minIndex,e.maxIndex+1)},getLabelForIndex:function(t,e){return this.ticks[t]},getPixelForValue:function(t,e,a,i){var n=this,o=Math.max(n.maxIndex+1-n.minIndex-(n.options.gridLines.offsetGridLines?0:1),1);if(n.isHorizontal()){var r=n.width-(n.paddingLeft+n.paddingRight),l=r/o,s=l*(e-n.minIndex)+n.paddingLeft;return n.options.gridLines.offsetGridLines&&i&&(s+=l/2),n.left+Math.round(s)}var d=n.height-(n.paddingTop+n.paddingBottom),u=d/o,c=u*(e-n.minIndex)+n.paddingTop;return n.options.gridLines.offsetGridLines&&i&&(c+=u/2),n.top+Math.round(c)},getPixelForTick:function(t,e){return this.getPixelForValue(this.ticks[t],t+this.minIndex,null,e)},getValueForPixel:function(t){var e,a=this,i=Math.max(a.ticks.length-(a.options.gridLines.offsetGridLines?0:1),1),n=a.isHorizontal(),o=n?a.width-(a.paddingLeft+a.paddingRight):a.height-(a.paddingTop+a.paddingBottom),r=o/i;return a.options.gridLines.offsetGridLines&&(t-=r/2),t-=n?a.paddingLeft:a.paddingTop,e=0>=t?0:Math.round(t/r)}});t.scaleService.registerScaleType("category",i,a)}},{}],39:[function(t,e,a){"use strict";e.exports=function(t){var e=t.helpers,a={position:"left",ticks:{callback:function(t,a,i){var n=i.length>3?i[2]-i[1]:i[1]-i[0];Math.abs(n)>1&&t!==Math.floor(t)&&(n=t-Math.floor(t));var o=e.log10(Math.abs(n)),r="";if(0!==t){var l=-1*Math.floor(o);l=Math.max(Math.min(l,20),0),r=t.toFixed(l)}else r="0";return r}}},i=t.LinearScaleBase.extend({determineDataLimits:function(){function t(t){return l?t.xAxisID===a.id:t.yAxisID===a.id}var a=this,i=a.options,n=(i.ticks,a.chart),o=n.data,r=o.datasets,l=a.isHorizontal();if(a.min=null,a.max=null,i.stacked){var s={},d=!1,u=!1;e.each(r,function(o,r){var l=n.getDatasetMeta(r);void 0===s[l.type]&&(s[l.type]={positiveValues:[],negativeValues:[]});var c=s[l.type].positiveValues,h=s[l.type].negativeValues;n.isDatasetVisible(r)&&t(l)&&e.each(o.data,function(t,e){var n=+a.getRightValue(t);isNaN(n)||l.data[e].hidden||(c[e]=c[e]||0,h[e]=h[e]||0,i.relativePoints?c[e]=100:0>n?(u=!0,h[e]+=n):(d=!0,c[e]+=n))})}),e.each(s,function(t){var i=t.positiveValues.concat(t.negativeValues),n=e.min(i),o=e.max(i);a.min=null===a.min?n:Math.min(a.min,n),a.max=null===a.max?o:Math.max(a.max,o)})}else e.each(r,function(i,o){var r=n.getDatasetMeta(o);n.isDatasetVisible(o)&&t(r)&&e.each(i.data,function(t,e){var i=+a.getRightValue(t);isNaN(i)||r.data[e].hidden||(null===a.min?a.min=i:ia.max&&(a.max=i))})});this.handleTickRangeOptions()},getTickLimit:function(){var a,i=this,n=i.options.ticks;if(i.isHorizontal())a=Math.min(n.maxTicksLimit?n.maxTicksLimit:11,Math.ceil(i.width/50));else{var o=e.getValueOrDefault(n.fontSize,t.defaults.global.defaultFontSize);a=Math.min(n.maxTicksLimit?n.maxTicksLimit:11,Math.ceil(i.height/(2*o)))}return a},handleDirectionalChanges:function(){this.isHorizontal()||this.ticks.reverse()},getLabelForIndex:function(t,e){return+this.getRightValue(this.chart.data.datasets[e].data[t])},getPixelForValue:function(t,e,a,i){var n,o,r=this,l=r.paddingLeft,s=r.paddingBottom,d=r.start,u=+r.getRightValue(t),c=r.end-d;return r.isHorizontal()?(o=r.width-(l+r.paddingRight),n=r.left+o/c*(u-d),Math.round(n+l)):(o=r.height-(r.paddingTop+s),n=r.bottom-s-o/c*(u-d),Math.round(n))},getValueForPixel:function(t){var e=this,a=e.isHorizontal(),i=e.paddingLeft,n=e.paddingBottom,o=a?e.width-(i+e.paddingRight):e.height-(e.paddingTop+n),r=(a?t-e.left-i:e.bottom-n-t)/o;return e.start+(e.end-e.start)*r},getPixelForTick:function(t,e){return this.getPixelForValue(this.ticksAsNumbers[t],null,null,e)}});t.scaleService.registerScaleType("linear",i,a)}},{}],40:[function(t,e,a){"use strict";e.exports=function(t){var e=t.helpers,a=e.noop;t.LinearScaleBase=t.Scale.extend({handleTickRangeOptions:function(){var t=this,a=t.options,i=a.ticks;if(i.beginAtZero){var n=e.sign(t.min),o=e.sign(t.max);0>n&&0>o?t.max=0:n>0&&o>0&&(t.min=0)}void 0!==i.min?t.min=i.min:void 0!==i.suggestedMin&&(t.min=Math.min(t.min,i.suggestedMin)),void 0!==i.max?t.max=i.max:void 0!==i.suggestedMax&&(t.max=Math.max(t.max,i.suggestedMax)),t.min===t.max&&(t.max++,i.beginAtZero||t.min--)},getTickLimit:a,handleDirectionalChanges:a,buildTicks:function(){var t=this,a=t.options,i=a.ticks,n=e.getValueOrDefault,o=(t.isHorizontal(),t.ticks=[]),r=t.getTickLimit();r=Math.max(2,r);var l,s=i.fixedStepSize&&i.fixedStepSize>0||i.stepSize&&i.stepSize>0;if(s)l=n(i.fixedStepSize,i.stepSize);else{var d=e.niceNum(t.max-t.min,!1);l=e.niceNum(d/(r-1),!0)}var u=Math.floor(t.min/l)*l,c=Math.ceil(t.max/l)*l,h=(c-u)/l;h=e.almostEquals(h,Math.round(h),l/1e3)?Math.round(h):Math.ceil(h),o.push(void 0!==i.min?i.min:u);for(var f=1;h>f;++f)o.push(u+f*l);o.push(void 0!==i.max?i.max:c),t.handleDirectionalChanges(),t.max=e.max(o),t.min=e.min(o),i.reverse?(o.reverse(),t.start=t.max,t.end=t.min):(t.start=t.min,t.end=t.max)},convertTicksToLabels:function(){var e=this;e.ticksAsNumbers=e.ticks.slice(),e.zeroLineIndex=e.ticks.indexOf(0),t.Scale.prototype.convertTicksToLabels.call(e)}})}},{}],41:[function(t,e,a){"use strict";e.exports=function(t){var e=t.helpers,a={position:"left",ticks:{callback:function(t,a,i){var n=t/Math.pow(10,Math.floor(e.log10(t)));return 1===n||2===n||5===n||0===a||a===i.length-1?t.toExponential():""}}},i=t.Scale.extend({determineDataLimits:function(){function t(t){return d?t.xAxisID===a.id:t.yAxisID===a.id}var a=this,i=a.options,n=i.ticks,o=a.chart,r=o.data,l=r.datasets,s=e.getValueOrDefault,d=a.isHorizontal();if(a.min=null,a.max=null,i.stacked){var u={};e.each(l,function(n,r){var l=o.getDatasetMeta(r);o.isDatasetVisible(r)&&t(l)&&(void 0===u[l.type]&&(u[l.type]=[]),e.each(n.data,function(t,e){var n=u[l.type],o=+a.getRightValue(t);isNaN(o)||l.data[e].hidden||(n[e]=n[e]||0,i.relativePoints?n[e]=100:n[e]+=o)}))}),e.each(u,function(t){var i=e.min(t),n=e.max(t);a.min=null===a.min?i:Math.min(a.min,i),a.max=null===a.max?n:Math.max(a.max,n)})}else e.each(l,function(i,n){var r=o.getDatasetMeta(n);o.isDatasetVisible(n)&&t(r)&&e.each(i.data,function(t,e){var i=+a.getRightValue(t);isNaN(i)||r.data[e].hidden||(null===a.min?a.min=i:ia.max&&(a.max=i))})});a.min=s(n.min,a.min),a.max=s(n.max,a.max),a.min===a.max&&(0!==a.min&&null!==a.min?(a.min=Math.pow(10,Math.floor(e.log10(a.min))-1),a.max=Math.pow(10,Math.floor(e.log10(a.max))+1)):(a.min=1,a.max=10))},buildTicks:function(){for(var t=this,a=t.options,i=a.ticks,n=e.getValueOrDefault,o=t.ticks=[],r=n(i.min,Math.pow(10,Math.floor(e.log10(t.min))));rt.max&&(t.max=i))})}}),t.handleTickRangeOptions()},getTickLimit:function(){var t=this.options.ticks,i=e.getValueOrDefault(t.fontSize,a.defaultFontSize);return Math.min(t.maxTicksLimit?t.maxTicksLimit:11,Math.ceil(this.drawingArea/(1.5*i)))},convertTicksToLabels:function(){ +var e=this;t.LinearScaleBase.prototype.convertTicksToLabels.call(e),e.pointLabels=e.chart.data.labels.map(e.options.pointLabels.callback,e)},getLabelForIndex:function(t,e){return+this.getRightValue(this.chart.data.datasets[e].data[t])},fit:function(){var t,i,n,o,r,l,s,d,u,c,h,f,g=this.options.pointLabels,p=e.getValueOrDefault(g.fontSize,a.defaultFontSize),m=e.getValueOrDefault(g.fontStyle,a.defaultFontStyle),b=e.getValueOrDefault(g.fontFamily,a.defaultFontFamily),v=e.fontString(p,m,b),x=e.min([this.height/2-p-5,this.width/2]),y=this.width,k=0;for(this.ctx.font=v,i=0;iy&&(y=t.x+o,r=i),t.x-oy&&(y=t.x+n,r=i):i>this.getValueCount()/2&&t.x-ne&&0>a?a:e>0&&a>0?e:0)},draw:function(){var t=this,i=t.options,n=i.gridLines,o=i.ticks,r=i.angleLines,l=i.pointLabels,s=e.getValueOrDefault;if(i.display){var d=t.ctx,u=s(o.fontSize,a.defaultFontSize),c=s(o.fontStyle,a.defaultFontStyle),h=s(o.fontFamily,a.defaultFontFamily),f=e.fontString(u,c,h);if(e.each(t.ticks,function(r,l){if(l>0||i.reverse){var c=t.getDistanceFromCenterForValue(t.ticksAsNumbers[l]),h=t.yCenter-c;if(n.display&&0!==l)if(d.strokeStyle=e.getValueAtIndexOrDefault(n.color,l-1),d.lineWidth=e.getValueAtIndexOrDefault(n.lineWidth,l-1),i.lineArc)d.beginPath(),d.arc(t.xCenter,t.yCenter,c,0,2*Math.PI),d.closePath(),d.stroke();else{d.beginPath();for(var g=0;g=0;x--){if(r.display){var y=t.getPointPosition(x,g);d.beginPath(),d.moveTo(t.xCenter,t.yCenter),d.lineTo(y.x,y.y),d.stroke(),d.closePath()}var k=t.getPointPosition(x,g+5),S=s(l.fontColor,a.defaultFontColor);d.font=v,d.fillStyle=S;var w=t.pointLabels,C=w.length,M=w.length/2,D=M/2,A=D>x||x>C-D,I=x===D||x===C-D;0===x?d.textAlign="center":x===M?d.textAlign="center":M>x?d.textAlign="left":d.textAlign="right",I?d.textBaseline="middle":A?d.textBaseline="bottom":d.textBaseline="top",d.fillText(w[x]?w[x]:"",k.x,k.y)}}}}});t.scaleService.registerScaleType("radialLinear",n,i)}},{}],43:[function(t,e,a){"use strict";var i=t(1);i="function"==typeof i?i:window.moment,e.exports=function(t){var e=t.helpers,a={units:[{name:"millisecond",steps:[1,2,5,10,20,50,100,250,500]},{name:"second",steps:[1,2,5,10,30]},{name:"minute",steps:[1,2,5,10,30]},{name:"hour",steps:[1,2,3,6,12]},{name:"day",steps:[1,2,5]},{name:"week",maxStep:4},{name:"month",maxStep:3},{name:"quarter",maxStep:4},{name:"year",maxStep:!1}]},n={position:"bottom",time:{parser:!1,format:!1,unit:!1,round:!1,displayFormat:!1,isoWeekday:!1,displayFormats:{millisecond:"h:mm:ss.SSS a",second:"h:mm:ss a",minute:"h:mm:ss a",hour:"MMM D, hA",day:"ll",week:"ll",month:"MMM YYYY",quarter:"[Q]Q - YYYY",year:"YYYY"}},ticks:{autoSkip:!1}},o=t.Scale.extend({initialize:function(){if(!i)throw new Error("Chart.js - Moment.js could not be found! You must include it before Chart.js to use the time scale. Download at https://momentjs.com");t.Scale.prototype.initialize.call(this)},getLabelMoment:function(t,e){return this.labelMoments[t][e]},getMomentStartOf:function(t){var e=this;return"week"===e.options.time.unit&&e.options.time.isoWeekday!==!1?t.clone().startOf("isoWeek").isoWeekday(e.options.time.isoWeekday):t.clone().startOf(e.tickUnit)},determineDataLimits:function(){var t=this;t.labelMoments=[];var a=[];t.chart.data.labels&&t.chart.data.labels.length>0?(e.each(t.chart.data.labels,function(e,i){var n=t.parseTime(e);n.isValid()&&(t.options.time.round&&n.startOf(t.options.time.round),a.push(n))},t),t.firstTick=i.min.call(t,a),t.lastTick=i.max.call(t,a)):(t.firstTick=null,t.lastTick=null),e.each(t.chart.data.datasets,function(n,o){var r=[],l=t.chart.isDatasetVisible(o);"object"==typeof n.data[0]&&null!==n.data[0]?e.each(n.data,function(e,a){var n=t.parseTime(t.getRightValue(e));n.isValid()&&(t.options.time.round&&n.startOf(t.options.time.round),r.push(n),l&&(t.firstTick=null!==t.firstTick?i.min(t.firstTick,n):n,t.lastTick=null!==t.lastTick?i.max(t.lastTick,n):n))},t):r=a,t.labelMoments.push(r)},t),t.options.time.min&&(t.firstTick=t.parseTime(t.options.time.min)),t.options.time.max&&(t.lastTick=t.parseTime(t.options.time.max)),t.firstTick=(t.firstTick||i()).clone(),t.lastTick=(t.lastTick||i()).clone()},buildTicks:function(i){var n=this;n.ctx.save();var o=e.getValueOrDefault(n.options.ticks.fontSize,t.defaults.global.defaultFontSize),r=e.getValueOrDefault(n.options.ticks.fontStyle,t.defaults.global.defaultFontStyle),l=e.getValueOrDefault(n.options.ticks.fontFamily,t.defaults.global.defaultFontFamily),s=e.fontString(o,r,l);if(n.ctx.font=s,n.ticks=[],n.unitScale=1,n.scaleSizeInUnits=0,n.options.time.unit)n.tickUnit=n.options.time.unit||"day",n.displayFormat=n.options.time.displayFormats[n.tickUnit],n.scaleSizeInUnits=n.lastTick.diff(n.firstTick,n.tickUnit,!0),n.unitScale=e.getValueOrDefault(n.options.time.unitStepSize,1);else{var d=n.isHorizontal()?n.width-(n.paddingLeft+n.paddingRight):n.height-(n.paddingTop+n.paddingBottom),u=n.tickFormatFunction(n.firstTick,0,[]),c=n.ctx.measureText(u).width,h=Math.cos(e.toRadians(n.options.ticks.maxRotation)),f=Math.sin(e.toRadians(n.options.ticks.maxRotation));c=c*h+o*f;var g=d/c;n.tickUnit="millisecond",n.scaleSizeInUnits=n.lastTick.diff(n.firstTick,n.tickUnit,!0),n.displayFormat=n.options.time.displayFormats[n.tickUnit];for(var p=0,m=a.units[p];p=Math.ceil(n.scaleSizeInUnits/g)){n.unitScale=e.getValueOrDefault(n.options.time.unitStepSize,m.steps[b]);break}break}if(m.maxStep===!1||Math.ceil(n.scaleSizeInUnits/g)=0)break;S%n.unitScale===0&&n.ticks.push(w)}var C=n.ticks[n.ticks.length-1].diff(n.lastTick,n.tickUnit);(0!==C||0===n.scaleSizeInUnits)&&(n.options.time.max?(n.ticks.push(n.lastTick.clone()),n.scaleSizeInUnits=n.lastTick.diff(n.ticks[0],n.tickUnit,!0)):(n.ticks.push(n.lastTick.clone()),n.scaleSizeInUnits=n.lastTick.diff(n.firstTick,n.tickUnit,!0))),n.ctx.restore()},getLabelForIndex:function(t,e){var a=this,i=a.chart.data.labels&&tb;b++)if(b in this&&this[b]===a)return b;return-1};for(t={catchupTime:500,initialRate:.03,minTime:500,ghostTime:500,maxProgressPerFrame:10,easeFactor:1.25,startOnPageLoad:!0,restartOnPushState:!0,restartOnRequestAfter:500,target:"body",elements:{checkInterval:100,selectors:["body"]},eventLag:{minSamples:10,sampleCount:3,lagThreshold:3},ajax:{trackMethods:["GET"],trackWebSockets:!0,ignoreURLs:[]}},B=function(){var a;return null!=(a="undefined"!=typeof performance&&null!==performance&&"function"==typeof performance.now?performance.now():void 0)?a:+new Date},D=window.requestAnimationFrame||window.mozRequestAnimationFrame||window.webkitRequestAnimationFrame||window.msRequestAnimationFrame,s=window.cancelAnimationFrame||window.mozCancelAnimationFrame,null==D&&(D=function(a){return setTimeout(a,50)},s=function(a){return clearTimeout(a)}),F=function(a){var b,c;return b=B(),(c=function(){var d;return d=B()-b,d>=33?(b=B(),a(d,function(){return D(c)})):setTimeout(c,33-d)})()},E=function(){var a,b,c;return c=arguments[0],b=arguments[1],a=3<=arguments.length?W.call(arguments,2):[],"function"==typeof c[b]?c[b].apply(c,a):c[b]},u=function(){var a,b,c,d,e,f,g;for(b=arguments[0],d=2<=arguments.length?W.call(arguments,1):[],f=0,g=d.length;g>f;f++)if(c=d[f])for(a in c)X.call(c,a)&&(e=c[a],null!=b[a]&&"object"==typeof b[a]&&null!=e&&"object"==typeof e?u(b[a],e):b[a]=e);return b},p=function(a){var b,c,d,e,f;for(c=b=0,e=0,f=a.length;f>e;e++)d=a[e],c+=Math.abs(d),b++;return c/b},w=function(a,b){var c,d,e;if(null==a&&(a="options"),null==b&&(b=!0),e=document.querySelector("[data-pace-"+a+"]")){if(c=e.getAttribute("data-pace-"+a),!b)return c;try{return JSON.parse(c)}catch(f){return d=f,"undefined"!=typeof console&&null!==console?console.error("Error parsing inline pace options",d):void 0}}},g=function(){function a(){}return a.prototype.on=function(a,b,c,d){var e;return null==d&&(d=!1),null==this.bindings&&(this.bindings={}),null==(e=this.bindings)[a]&&(e[a]=[]),this.bindings[a].push({handler:b,ctx:c,once:d})},a.prototype.once=function(a,b,c){return this.on(a,b,c,!0)},a.prototype.off=function(a,b){var c,d,e;if(null!=(null!=(d=this.bindings)?d[a]:void 0)){if(null==b)return delete this.bindings[a];for(c=0,e=[];cP;P++)J=T[P],C[J]===!0&&(C[J]=t[J]);i=function(a){function b(){return U=b.__super__.constructor.apply(this,arguments)}return Y(b,a),b}(Error),b=function(){function a(){this.progress=0}return a.prototype.getElement=function(){var a;if(null==this.el){if(a=document.querySelector(C.target),!a)throw new i;this.el=document.createElement("div"),this.el.className="pace pace-active",document.body.className=document.body.className.replace(/pace-done/g,""),document.body.className+=" pace-running",this.el.innerHTML='
    \n
    \n
    \n
    ',null!=a.firstChild?a.insertBefore(this.el,a.firstChild):a.appendChild(this.el)}return this.el},a.prototype.finish=function(){var a;return a=this.getElement(),a.className=a.className.replace("pace-active",""),a.className+=" pace-inactive",document.body.className=document.body.className.replace("pace-running",""),document.body.className+=" pace-done"},a.prototype.update=function(a){return this.progress=a,this.render()},a.prototype.destroy=function(){try{this.getElement().parentNode.removeChild(this.getElement())}catch(a){i=a}return this.el=void 0},a.prototype.render=function(){var a,b;return null==document.querySelector(C.target)?!1:(a=this.getElement(),a.children[0].style.width=""+this.progress+"%",(!this.lastRenderedProgress||this.lastRenderedProgress|0!==this.progress|0)&&(a.children[0].setAttribute("data-progress-text",""+(0|this.progress)+"%"),this.progress>=100?b="99":(b=this.progress<10?"0":"",b+=0|this.progress),a.children[0].setAttribute("data-progress",""+b)),this.lastRenderedProgress=this.progress)},a.prototype.done=function(){return this.progress>=100},a}(),h=function(){function a(){this.bindings={}}return a.prototype.trigger=function(a,b){var c,d,e,f,g;if(null!=this.bindings[a]){for(f=this.bindings[a],g=[],d=0,e=f.length;e>d;d++)c=f[d],g.push(c.call(this,b));return g}},a.prototype.on=function(a,b){var c;return null==(c=this.bindings)[a]&&(c[a]=[]),this.bindings[a].push(b)},a}(),O=window.XMLHttpRequest,N=window.XDomainRequest,M=window.WebSocket,v=function(a,b){var c,d,e,f;f=[];for(d in b.prototype)try{e=b.prototype[d],f.push(null==a[d]&&"function"!=typeof e?a[d]=e:void 0)}catch(g){c=g}return f},z=[],Pace.ignore=function(){var a,b,c;return b=arguments[0],a=2<=arguments.length?W.call(arguments,1):[],z.unshift("ignore"),c=b.apply(null,a),z.shift(),c},Pace.track=function(){var a,b,c;return b=arguments[0],a=2<=arguments.length?W.call(arguments,1):[],z.unshift("track"),c=b.apply(null,a),z.shift(),c},I=function(a){var b;if(null==a&&(a="GET"),"track"===z[0])return"force";if(!z.length&&C.ajax){if("socket"===a&&C.ajax.trackWebSockets)return!0;if(b=a.toUpperCase(),Z.call(C.ajax.trackMethods,b)>=0)return!0}return!1},j=function(a){function b(){var a,c=this;b.__super__.constructor.apply(this,arguments),a=function(a){var b;return b=a.open,a.open=function(d,e){return I(d)&&c.trigger("request",{type:d,url:e,request:a}),b.apply(a,arguments)}},window.XMLHttpRequest=function(b){var c;return c=new O(b),a(c),c},v(window.XMLHttpRequest,O),null!=N&&(window.XDomainRequest=function(){var b;return b=new N,a(b),b},v(window.XDomainRequest,N)),null!=M&&C.ajax.trackWebSockets&&(window.WebSocket=function(a,b){var d;return d=null!=b?new M(a,b):new M(a),I("socket")&&c.trigger("request",{type:"socket",url:a,protocols:b,request:d}),d},v(window.WebSocket,M))}return Y(b,a),b}(h),Q=null,x=function(){return null==Q&&(Q=new j),Q},H=function(a){var b,c,d,e;for(e=C.ajax.ignoreURLs,c=0,d=e.length;d>c;c++)if(b=e[c],"string"==typeof b){if(-1!==a.indexOf(b))return!0}else if(b.test(a))return!0;return!1},x().on("request",function(b){var c,d,e,f,g;return f=b.type,e=b.request,g=b.url,H(g)?void 0:Pace.running||C.restartOnRequestAfter===!1&&"force"!==I(f)?void 0:(d=arguments,c=C.restartOnRequestAfter||0,"boolean"==typeof c&&(c=0),setTimeout(function(){var b,c,g,h,i,j;if(b="socket"===f?e.readyState<2:0<(h=e.readyState)&&4>h){for(Pace.restart(),i=Pace.sources,j=[],c=0,g=i.length;g>c;c++){if(J=i[c],J instanceof a){J.watch.apply(J,d);break}j.push(void 0)}return j}},c))}),a=function(){function a(){var a=this;this.elements=[],x().on("request",function(){return a.watch.apply(a,arguments)})}return a.prototype.watch=function(a){var b,c,d,e;return d=a.type,b=a.request,e=a.url,H(e)?void 0:(c="socket"===d?new m(b):new n(b),this.elements.push(c))},a}(),n=function(){function a(a){var b,c,d,e,f,g,h=this;if(this.progress=0,null!=window.ProgressEvent)for(c=null,a.addEventListener("progress",function(a){return h.progress=a.lengthComputable?100*a.loaded/a.total:h.progress+(100-h.progress)/2}),g=["load","abort","timeout","error"],d=0,e=g.length;e>d;d++)b=g[d],a.addEventListener(b,function(){return h.progress=100});else f=a.onreadystatechange,a.onreadystatechange=function(){var b;return 0===(b=a.readyState)||4===b?h.progress=100:3===a.readyState&&(h.progress=50),"function"==typeof f?f.apply(null,arguments):void 0}}return a}(),m=function(){function a(a){var b,c,d,e,f=this;for(this.progress=0,e=["error","open"],c=0,d=e.length;d>c;c++)b=e[c],a.addEventListener(b,function(){return f.progress=100})}return a}(),d=function(){function a(a){var b,c,d,f;for(null==a&&(a={}),this.elements=[],null==a.selectors&&(a.selectors=[]),f=a.selectors,c=0,d=f.length;d>c;c++)b=f[c],this.elements.push(new e(b))}return a}(),e=function(){function a(a){this.selector=a,this.progress=0,this.check()}return a.prototype.check=function(){var a=this;return document.querySelector(this.selector)?this.done():setTimeout(function(){return a.check()},C.elements.checkInterval)},a.prototype.done=function(){return this.progress=100},a}(),c=function(){function a(){var a,b,c=this;this.progress=null!=(b=this.states[document.readyState])?b:100,a=document.onreadystatechange,document.onreadystatechange=function(){return null!=c.states[document.readyState]&&(c.progress=c.states[document.readyState]),"function"==typeof a?a.apply(null,arguments):void 0}}return a.prototype.states={loading:0,interactive:50,complete:100},a}(),f=function(){function a(){var a,b,c,d,e,f=this;this.progress=0,a=0,e=[],d=0,c=B(),b=setInterval(function(){var g;return g=B()-c-50,c=B(),e.push(g),e.length>C.eventLag.sampleCount&&e.shift(),a=p(e),++d>=C.eventLag.minSamples&&a=100&&(this.done=!0),b===this.last?this.sinceLastUpdate+=a:(this.sinceLastUpdate&&(this.rate=(b-this.last)/this.sinceLastUpdate),this.catchup=(b-this.progress)/C.catchupTime,this.sinceLastUpdate=0,this.last=b),b>this.progress&&(this.progress+=this.catchup*a),c=1-Math.pow(this.progress/100,C.easeFactor),this.progress+=c*this.rate*a,this.progress=Math.min(this.lastProgress+C.maxProgressPerFrame,this.progress),this.progress=Math.max(0,this.progress),this.progress=Math.min(100,this.progress),this.lastProgress=this.progress,this.progress},a}(),K=null,G=null,q=null,L=null,o=null,r=null,Pace.running=!1,y=function(){return C.restartOnPushState?Pace.restart():void 0},null!=window.history.pushState&&(S=window.history.pushState,window.history.pushState=function(){return y(),S.apply(window.history,arguments)}),null!=window.history.replaceState&&(V=window.history.replaceState,window.history.replaceState=function(){return y(),V.apply(window.history,arguments)}),k={ajax:a,elements:d,document:c,eventLag:f},(A=function(){var a,c,d,e,f,g,h,i;for(Pace.sources=K=[],g=["ajax","elements","document","eventLag"],c=0,e=g.length;e>c;c++)a=g[c],C[a]!==!1&&K.push(new k[a](C[a]));for(i=null!=(h=C.extraSources)?h:[],d=0,f=i.length;f>d;d++)J=i[d],K.push(new J(C));return Pace.bar=q=new b,G=[],L=new l})(),Pace.stop=function(){return Pace.trigger("stop"),Pace.running=!1,q.destroy(),r=!0,null!=o&&("function"==typeof s&&s(o),o=null),A()},Pace.restart=function(){return Pace.trigger("restart"),Pace.stop(),Pace.start()},Pace.go=function(){var a;return Pace.running=!0,q.render(),a=B(),r=!1,o=F(function(b,c){var d,e,f,g,h,i,j,k,m,n,o,p,s,t,u,v;for(k=100-q.progress,e=o=0,f=!0,i=p=0,t=K.length;t>p;i=++p)for(J=K[i],n=null!=G[i]?G[i]:G[i]=[],h=null!=(v=J.elements)?v:[J],j=s=0,u=h.length;u>s;j=++s)g=h[j],m=null!=n[j]?n[j]:n[j]=new l(g),f&=m.done,m.done||(e++,o+=m.tick(b));return d=o/e,q.update(L.tick(b,d)),q.done()||f||r?(q.update(100),Pace.trigger("done"),setTimeout(function(){return q.finish(),Pace.running=!1,Pace.trigger("hide")},Math.max(C.ghostTime,Math.max(C.minTime-(B()-a),0)))):c()})},Pace.start=function(a){u(C,a),Pace.running=!0;try{q.render()}catch(b){i=b}return document.querySelector(".pace")?(Pace.trigger("start"),Pace.go()):setTimeout(Pace.start,50)},"function"==typeof define&&define.amd?define(function(){return Pace}):"object"==typeof exports?module.exports=Pace:C.startOnPageLoad&&Pace.start()}).call(this); \ No newline at end of file diff --git a/third-party/Grpc.Tools.1.8.0/Grpc.Tools.1.8.0.nupkg b/third-party/Grpc.Tools.1.8.0/Grpc.Tools.1.8.0.nupkg new file mode 100644 index 00000000..6fa09e6e Binary files /dev/null and b/third-party/Grpc.Tools.1.8.0/Grpc.Tools.1.8.0.nupkg differ diff --git a/third-party/Grpc.Tools.1.8.0/tools/linux_x64/grpc_csharp_plugin b/third-party/Grpc.Tools.1.8.0/tools/linux_x64/grpc_csharp_plugin new file mode 100755 index 00000000..90568bb9 Binary files /dev/null and b/third-party/Grpc.Tools.1.8.0/tools/linux_x64/grpc_csharp_plugin differ diff --git a/third-party/Grpc.Tools.1.8.0/tools/linux_x64/protoc b/third-party/Grpc.Tools.1.8.0/tools/linux_x64/protoc new file mode 100755 index 00000000..a84d92aa Binary files /dev/null and b/third-party/Grpc.Tools.1.8.0/tools/linux_x64/protoc differ diff --git a/third-party/Grpc.Tools.1.8.0/tools/linux_x86/grpc_csharp_plugin b/third-party/Grpc.Tools.1.8.0/tools/linux_x86/grpc_csharp_plugin new file mode 100755 index 00000000..4dd4eeff Binary files /dev/null and b/third-party/Grpc.Tools.1.8.0/tools/linux_x86/grpc_csharp_plugin differ diff --git a/third-party/Grpc.Tools.1.8.0/tools/linux_x86/protoc b/third-party/Grpc.Tools.1.8.0/tools/linux_x86/protoc new file mode 100755 index 00000000..46718cc0 Binary files /dev/null and b/third-party/Grpc.Tools.1.8.0/tools/linux_x86/protoc differ diff --git a/third-party/Grpc.Tools.1.8.0/tools/macosx_x64/grpc_csharp_plugin b/third-party/Grpc.Tools.1.8.0/tools/macosx_x64/grpc_csharp_plugin new file mode 100755 index 00000000..1378f0cb Binary files /dev/null and b/third-party/Grpc.Tools.1.8.0/tools/macosx_x64/grpc_csharp_plugin differ diff --git a/third-party/Grpc.Tools.1.8.0/tools/macosx_x64/protoc b/third-party/Grpc.Tools.1.8.0/tools/macosx_x64/protoc new file mode 100755 index 00000000..9d2a4823 Binary files /dev/null and b/third-party/Grpc.Tools.1.8.0/tools/macosx_x64/protoc differ diff --git a/third-party/Grpc.Tools.1.8.0/tools/macosx_x86/grpc_csharp_plugin b/third-party/Grpc.Tools.1.8.0/tools/macosx_x86/grpc_csharp_plugin new file mode 100755 index 00000000..18ab6bf5 Binary files /dev/null and b/third-party/Grpc.Tools.1.8.0/tools/macosx_x86/grpc_csharp_plugin differ diff --git a/third-party/Grpc.Tools.1.8.0/tools/macosx_x86/protoc b/third-party/Grpc.Tools.1.8.0/tools/macosx_x86/protoc new file mode 100755 index 00000000..b9daf1ee Binary files /dev/null and b/third-party/Grpc.Tools.1.8.0/tools/macosx_x86/protoc differ diff --git a/third-party/Grpc.Tools.1.8.0/tools/windows_x64/grpc_csharp_plugin.exe b/third-party/Grpc.Tools.1.8.0/tools/windows_x64/grpc_csharp_plugin.exe new file mode 100755 index 00000000..95bc37dd --- /dev/null +++ b/third-party/Grpc.Tools.1.8.0/tools/windows_x64/grpc_csharp_plugin.exe @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fb375ce2da429874761ccccbe0afaa6577b2e60dac97aa27fbfc28e8250daf71 +size 1276928 diff --git a/third-party/Grpc.Tools.1.8.0/tools/windows_x64/protoc.exe b/third-party/Grpc.Tools.1.8.0/tools/windows_x64/protoc.exe new file mode 100755 index 00000000..c716a3a2 --- /dev/null +++ b/third-party/Grpc.Tools.1.8.0/tools/windows_x64/protoc.exe @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f0465ae212aacf175da9b8fb08098f9fe324cd55fb4e53b8056301cb0d10bd22 +size 3111936 diff --git a/third-party/Grpc.Tools.1.8.0/tools/windows_x86/grpc_csharp_plugin.exe b/third-party/Grpc.Tools.1.8.0/tools/windows_x86/grpc_csharp_plugin.exe new file mode 100755 index 00000000..775ca672 --- /dev/null +++ b/third-party/Grpc.Tools.1.8.0/tools/windows_x86/grpc_csharp_plugin.exe @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e2df115bd8208a715fafb3663c7a2c739dfd209515bd4bf4f38d6fbb7de6dc45 +size 978944 diff --git a/third-party/Grpc.Tools.1.8.0/tools/windows_x86/protoc.exe b/third-party/Grpc.Tools.1.8.0/tools/windows_x86/protoc.exe new file mode 100755 index 00000000..08ce22f6 --- /dev/null +++ b/third-party/Grpc.Tools.1.8.0/tools/windows_x86/protoc.exe @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b4bbabd0ecd34e1d5e28856fe9271e10801b0372c17de41ef4b574f9404f685b +size 2391040 diff --git a/third-party/OpenDRIVE_1.4/OpenDRIVEFormatSpecRev1.4H.pdf b/third-party/OpenDRIVE_1.4/OpenDRIVEFormatSpecRev1.4H.pdf new file mode 100644 index 00000000..c80436ec Binary files /dev/null and b/third-party/OpenDRIVE_1.4/OpenDRIVEFormatSpecRev1.4H.pdf differ diff --git a/third-party/OpenDRIVE_1.4/OpenDRIVEStyleGuideRevC.pdf b/third-party/OpenDRIVE_1.4/OpenDRIVEStyleGuideRevC.pdf new file mode 100644 index 00000000..d59f3a51 Binary files /dev/null and b/third-party/OpenDRIVE_1.4/OpenDRIVEStyleGuideRevC.pdf differ diff --git a/third-party/OpenDRIVE_1.4/OpenDRIVE_1.4H_Schema_Files.xsd b/third-party/OpenDRIVE_1.4/OpenDRIVE_1.4H_Schema_Files.xsd new file mode 100644 index 00000000..85f7d288 --- /dev/null +++ b/third-party/OpenDRIVE_1.4/OpenDRIVE_1.4H_Schema_Files.xsd @@ -0,0 +1,1285 @@ + + + + + + + XML Schema Definition for OpenDRIVE XML files - Rev. 1.4H, excluding SET records, (c)2015 by VIRES Simulationstechnologie GmbH, Germany + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/third-party/protoc-3.0.2-osx-x86_64/readme.txt b/third-party/protoc-3.0.2-osx-x86_64/readme.txt new file mode 100644 index 00000000..bb8d0aca --- /dev/null +++ b/third-party/protoc-3.0.2-osx-x86_64/readme.txt @@ -0,0 +1,13 @@ +Version: protoc-3.0.2-osx-x86_64 + +Protocol Buffers - Google's data interchange format +Copyright 2008 Google Inc. +https://developers.google.com/protocol-buffers/ + +This package contains a precompiled binary version of the protocol buffer +compiler (protoc). This binary is intended for users who want to use Protocol +Buffers in languages other than C++ but do not want to compile protoc +themselves. To install, simply place this binary somewhere in your PATH. + +Please refer to our official github site for more installation instructions: + https://github.com/google/protobuf diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 00000000..3b1969cf --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,24 @@ +{ + "compileOnSave": false, + "compilerOptions": { + "baseUrl": "src", + "outDir": "./dist/out-tsc", + "inlineSources": true, + "sourceMap": true, + "declaration": false, + "module": "esnext", + "moduleResolution": "node", + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "downlevelIteration": true, + "importHelpers": true, + "target": "es5", + "typeRoots": [ + "node_modules/@types" + ], + "lib": [ + "es2018", + "dom" + ] + } +} diff --git a/tslint.json b/tslint.json new file mode 100644 index 00000000..3644e34b --- /dev/null +++ b/tslint.json @@ -0,0 +1,65 @@ +{ + "extends": "tslint:recommended", + "rulesDirectory": ["codelyzer"], + "rules": { + "no-unused-vars": "warn", + "array-type": false, + "arrow-parens": false, + "prefer-for-of": false, + "curly": false, + "deprecation": { + "severity": "warn" + }, + "import-blacklist": [true, "rxjs/Rx"], + "interface-name": false, + "max-classes-per-file": false, + "max-line-length": [true, 140], + "member-access": false, + "member-ordering": [ + true, + { + "order": [ + "static-field", + "public-instance-field", + "protected-instance-field", + "private-instance-field", + "constructor", + "static-method", + "public-instance-method", + "protected-instance-method", + "private-instance-method" + ] + } + ], + "no-consecutive-blank-lines": false, + "no-console": true, + "no-empty": false, + "no-inferrable-types": [false, "ignore-params"], + "no-non-null-assertion": true, + "no-redundant-jsdoc": true, + "no-switch-case-fall-through": true, + "no-use-before-declare": true, + "no-var-requires": false, + "object-literal-key-quotes": [true, "as-needed"], + "object-literal-sort-keys": false, + "ordered-imports": false, + "quotemark": [false, "single"], + "trailing-comma": false, + "no-output-on-prefix": true, + "use-input-property-decorator": true, + "use-output-property-decorator": true, + "use-host-property-decorator": true, + "no-input-rename": true, + "no-output-rename": true, + "use-life-cycle-interface": true, + "use-pipe-transform-interface": true, + "component-class-suffix": true, + "directive-class-suffix": true, + "space-before-function-paren": true, + "new-parens": false, + "newline-before-return": true, + "one-variable-per-declaration": false, + "one-line": false, + "semicolon": false + } +}