├── .editorconfig ├── .eslintrc.json ├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── Bug_report.md │ └── Feature_request.md └── workflows │ ├── CI.yml │ └── publish-on-npm.yml ├── .gitignore ├── .storybook ├── main.ts ├── preview.ts ├── tsconfig.json └── typings.d.ts ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── LICENSE ├── README.md ├── angular.json ├── karma.conf.js ├── package-lock.json ├── package.json ├── projects └── atft │ ├── .storybook │ ├── main.ts │ ├── preview.ts │ ├── tsconfig.json │ └── typings.d.ts │ ├── CHANGELOG.md │ ├── README.md │ ├── karma.conf.js │ ├── ng-package.json │ ├── package.json │ ├── src │ ├── index.spec.ts │ ├── lib │ │ ├── actor │ │ │ ├── data-center │ │ │ │ ├── atft-data-center-actor.module.ts │ │ │ │ ├── index.ts │ │ │ │ ├── layer │ │ │ │ │ ├── grid-actor.component.spec.ts │ │ │ │ │ ├── grid-actor.component.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── layer-actor.component.spec.ts │ │ │ │ │ └── layer-actor.component.ts │ │ │ │ ├── layout │ │ │ │ │ ├── dagre-composition.component.spec.ts │ │ │ │ │ ├── dagre-composition.component.ts │ │ │ │ │ ├── dagre-edge.component.spec.ts │ │ │ │ │ ├── dagre-edge.component.ts │ │ │ │ │ ├── dagre-layout.component.spec.ts │ │ │ │ │ ├── dagre-layout.component.ts │ │ │ │ │ ├── dagre-model.ts │ │ │ │ │ ├── dagre-node.component.ts │ │ │ │ │ ├── dagre-utils.spec.ts │ │ │ │ │ ├── dagre-utils.ts │ │ │ │ │ ├── dagre-yaml-parser.component.spec.ts │ │ │ │ │ ├── dagre-yaml-parser.component.ts │ │ │ │ │ └── index.ts │ │ │ │ ├── server │ │ │ │ │ ├── abstract-server-actor.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── model-actor.component.ts │ │ │ │ │ ├── server-barrel-actor.component.ts │ │ │ │ │ ├── server-compact-actor.component.ts │ │ │ │ │ ├── server-icon-actor.component.ts │ │ │ │ │ ├── server-stand-actor.component.spec.ts │ │ │ │ │ ├── server-stand-actor.component.ts │ │ │ │ │ └── workstation-actor.component.ts │ │ │ │ └── service │ │ │ │ │ ├── actor-repository.service.ts │ │ │ │ │ └── index.ts │ │ │ └── ux │ │ │ │ ├── index.ts │ │ │ │ ├── loader │ │ │ │ ├── index.ts │ │ │ │ ├── loader-actor.component.spec.ts │ │ │ │ └── loader-actor.component.ts │ │ │ │ ├── text │ │ │ │ ├── index.ts │ │ │ │ ├── text-actor.component.spec.ts │ │ │ │ └── text-actor.component.ts │ │ │ │ └── ux-actor.module.ts │ │ ├── animation │ │ │ ├── animation.service.spec.ts │ │ │ ├── animation.service.ts │ │ │ ├── atft-animation.module.ts │ │ │ ├── index.ts │ │ │ └── timeline-emitter │ │ │ │ ├── emit-step.component.ts │ │ │ │ ├── index.ts │ │ │ │ └── timeline-emitter.component.ts │ │ ├── atft.module.ts │ │ ├── camera │ │ │ ├── abstract-camera.ts │ │ │ ├── atft-camera.module.ts │ │ │ ├── index.ts │ │ │ ├── orthographic-camera.component.ts │ │ │ └── perspective-camera.component.ts │ │ ├── control │ │ │ ├── abstract-orbit-controls.ts │ │ │ ├── atft-control.module.ts │ │ │ ├── controls.component.scss │ │ │ ├── index.ts │ │ │ ├── map-controls.component.spec.ts │ │ │ ├── map-controls.component.ts │ │ │ ├── orbit-controls.component.spec.ts │ │ │ └── orbit-controls.component.ts │ │ ├── effect │ │ │ ├── atft-effect.module.ts │ │ │ ├── compose │ │ │ │ ├── abstract-compose-effect.ts │ │ │ │ ├── blur.component.ts │ │ │ │ ├── dof.component.ts │ │ │ │ ├── dot-screen.component.ts │ │ │ │ ├── effect-composer.component.spec.ts │ │ │ │ └── effect-composer.component.ts │ │ │ ├── dashed-draw.directive.spec.ts │ │ │ ├── dashed-draw.directive.ts │ │ │ ├── fog.component.ts │ │ │ └── index.ts │ │ ├── object │ │ │ ├── abstract-empty-directive.ts │ │ │ ├── abstract-lazy-object-3d.ts │ │ │ ├── abstract-object-3d.ts │ │ │ ├── atft-object.module.ts │ │ │ ├── connector │ │ │ │ ├── abstract-connector.ts │ │ │ │ ├── atft-connector.module.ts │ │ │ │ ├── index.ts │ │ │ │ └── line-connector.component.ts │ │ │ ├── content-projection.component.ts │ │ │ ├── helper │ │ │ │ ├── atft-helper.module.ts │ │ │ │ ├── axes-helper.component.spec.ts │ │ │ │ ├── axes-helper.component.ts │ │ │ │ ├── empty.component.ts │ │ │ │ ├── grid-helper.component.spec.ts │ │ │ │ ├── grid-helper.component.ts │ │ │ │ └── index.ts │ │ │ ├── index.ts │ │ │ ├── light │ │ │ │ ├── ambient-light.component.ts │ │ │ │ ├── atft-light.module.ts │ │ │ │ ├── directional-light.component.spec.ts │ │ │ │ ├── directional-light.component.ts │ │ │ │ ├── hemisphere-light.component.spec.ts │ │ │ │ ├── hemisphere-light.component.ts │ │ │ │ ├── index.ts │ │ │ │ ├── point-light.component.spec.ts │ │ │ │ └── point-light.component.ts │ │ │ ├── loader │ │ │ │ ├── abstract-model-loader.ts │ │ │ │ ├── atft-loader.module.ts │ │ │ │ ├── audio-loader.component.ts │ │ │ │ ├── index.ts │ │ │ │ ├── obj-loader.component.spec.ts │ │ │ │ ├── obj-loader.component.ts │ │ │ │ ├── object-loader.component.spec.ts │ │ │ │ ├── object-loader.component.ts │ │ │ │ ├── services │ │ │ │ │ ├── abstract-asset.service.ts │ │ │ │ │ ├── abstract-cache.service.ts │ │ │ │ │ ├── font.service.ts │ │ │ │ │ ├── icon.service.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── model.service.ts │ │ │ │ │ ├── obj-loader.service.ts │ │ │ │ │ └── svg-loader.service.ts │ │ │ │ ├── stl-loader.component.spec.ts │ │ │ │ ├── stl-loader.component.ts │ │ │ │ ├── svg-loader.component.spec.ts │ │ │ │ └── svg-loader.component.ts │ │ │ ├── mesh │ │ │ │ ├── abstract-mesh-3d.ts │ │ │ │ ├── abstract-mesh.spec.ts │ │ │ │ ├── atft-mesh.module.ts │ │ │ │ ├── box-mesh.component.spec.ts │ │ │ │ ├── box-mesh.component.ts │ │ │ │ ├── cylinder-mesh.component.spec.ts │ │ │ │ ├── cylinder-mesh.component.ts │ │ │ │ ├── frame-mesh.component.spec.ts │ │ │ │ ├── frame-mesh.component.ts │ │ │ │ ├── grid-mesh.component.spec.ts │ │ │ │ ├── grid-mesh.component.ts │ │ │ │ ├── index.ts │ │ │ │ ├── plane-mesh.component.spec.ts │ │ │ │ ├── plane-mesh.component.ts │ │ │ │ ├── sphere-mesh.component.spec.ts │ │ │ │ ├── sphere-mesh.component.ts │ │ │ │ ├── torus-mesh.component.spec.ts │ │ │ │ ├── torus-mesh.component.ts │ │ │ │ └── video-mesh.component.ts │ │ │ ├── scene.component.ts │ │ │ └── text │ │ │ │ ├── atft-text.module.ts │ │ │ │ ├── index.ts │ │ │ │ ├── text-mesh.component.spec.ts │ │ │ │ └── text-mesh.component.ts │ │ ├── pipe │ │ │ ├── atft-pipe.module.ts │ │ │ ├── deg2rad.pipe.ts │ │ │ ├── index.ts │ │ │ └── rad2deg.pipe.ts │ │ ├── raycaster │ │ │ ├── atft-raycaster.module.ts │ │ │ ├── index.ts │ │ │ ├── raycaster-camera.directive.spec.ts │ │ │ ├── raycaster-camera.directive.ts │ │ │ ├── raycaster-enable.directive.spec.ts │ │ │ ├── raycaster-enable.directive.ts │ │ │ ├── raycaster-event.ts │ │ │ ├── raycaster-group.directive.spec.ts │ │ │ ├── raycaster-group.directive.ts │ │ │ ├── raycaster.service.spec.ts │ │ │ └── raycaster.service.ts │ │ ├── renderer │ │ │ ├── atft-renderer.module.ts │ │ │ ├── bloom.service.ts │ │ │ ├── index.ts │ │ │ ├── renderer-canvas.component.html │ │ │ ├── renderer-canvas.component.scss │ │ │ ├── renderer-canvas.component.ts │ │ │ └── renderer.service.ts │ │ ├── stats │ │ │ ├── index.ts │ │ │ ├── stats-auto-show.directive.ts │ │ │ ├── stats.module.ts │ │ │ ├── stats.service.spec.ts │ │ │ └── stats.service.ts │ │ ├── threejs-fork │ │ │ ├── SVGLoader.ts │ │ │ └── index.ts │ │ └── util │ │ │ ├── applied-material.spec.ts │ │ │ ├── applied-material.ts │ │ │ ├── calculate-size.spec.ts │ │ │ ├── calculate-size.ts │ │ │ ├── fix-center.spec.ts │ │ │ ├── fix-center.ts │ │ │ ├── index.ts │ │ │ ├── provide-parent.ts │ │ │ ├── removeFromArray.ts │ │ │ ├── scale-to-fit.spec.ts │ │ │ └── scale-to-fit.ts │ ├── public_api.ts │ └── test.ts │ ├── tsconfig.lib.json │ ├── tsconfig.lib.prod.json │ └── tsconfig.spec.json ├── src ├── app │ ├── app-routing.module.ts │ ├── app.component.html │ ├── app.component.scss │ ├── app.component.spec.ts │ ├── app.component.ts │ └── app.module.ts ├── assets │ ├── .gitkeep │ ├── audio │ │ ├── sample1.mp3 │ │ └── sample2.mp3 │ ├── font │ │ └── helvetiker_regular.typeface.json │ ├── model │ │ ├── Menger_sponge_sample.stl │ │ ├── Server.json │ │ └── smiley │ │ │ ├── Smiley_Sphere001_5.jpg │ │ │ ├── smiley.mtl │ │ │ └── smiley.obj │ └── svg │ │ ├── App-Services.svg │ │ ├── border-none-solid.svg │ │ ├── expeditedssl-brands.svg │ │ ├── external-link-alt-solid.svg │ │ ├── galactic-republic-brands.svg │ │ ├── grid-world.svg │ │ ├── idea.svg │ │ ├── server-solid.svg │ │ ├── sitemap-solid.svg │ │ ├── upload.svg │ │ ├── users-solid.svg │ │ └── worldwide.svg ├── environments │ ├── environment.prod.ts │ └── environment.ts ├── favicon.ico ├── index.html ├── main.ts ├── stories │ ├── all-in-one │ │ ├── infrastructure.stories.ts │ │ └── virtual-city.stories.ts │ ├── animation │ │ ├── animate-loop.stories.ts │ │ ├── animate-mixer.stories.ts │ │ ├── animate-reactive-grid.stories.ts │ │ └── connector │ │ │ ├── connector-line-animated.stories.ts │ │ │ ├── connector-line-bloom.stories.ts │ │ │ └── connector-line.stories.ts │ ├── assets │ │ ├── accessibility.png │ │ ├── accessibility.svg │ │ ├── addon-library.png │ │ ├── assets.png │ │ ├── context.png │ │ ├── discord.svg │ │ ├── docs.png │ │ ├── figma-plugin.png │ │ ├── github.svg │ │ ├── share.png │ │ ├── styling.png │ │ ├── testing.png │ │ ├── theming.png │ │ ├── tutorials.svg │ │ └── youtube.svg │ ├── basic │ │ ├── dynamic │ │ │ ├── editor.stories.ts │ │ │ ├── for-loop.stories.ts │ │ │ ├── ng-content.stories.ts │ │ │ └── routing.stories.ts │ │ ├── loader │ │ │ ├── loader-audio.stories.ts │ │ │ ├── loader-obj.stories.ts │ │ │ ├── loader-object.stories.ts │ │ │ ├── loader-stl.stories.ts │ │ │ └── loader-svg.stories.ts │ │ ├── mesh │ │ │ ├── mesh-box.stories.ts │ │ │ ├── mesh-server.stories.ts │ │ │ ├── mesh-text.stories.ts │ │ │ └── mesh-video.stories.ts │ │ ├── object │ │ │ ├── object-modification.stories.ts │ │ │ └── object-raycaster.stories.ts │ │ └── timeline │ │ │ └── timeline-emitter.stories.ts │ ├── dagre-layout │ │ ├── dagre-composition.stories.ts │ │ ├── dagre-dynamic.stories.ts │ │ ├── dagre-icons.stories.ts │ │ ├── dagre-loop.stories.ts │ │ ├── dagre-route.stories.ts │ │ └── dagre-simple-layout.stories.ts │ ├── effect │ │ ├── blur.stories.ts │ │ ├── dashed-draw.stories.ts │ │ ├── depth-of-field.stories.ts │ │ ├── dot-screen.stories.ts │ │ └── fog.stories.ts │ ├── scene-wrapper │ │ ├── axes-scene-wrapper.ts │ │ ├── effects-scene-wrapper.ts │ │ ├── performance-scene-wrapper.ts │ │ ├── ux-scene-wrapper.ts │ │ └── world-scene-wrapper.ts │ └── ux │ │ ├── intro.stories.ts │ │ ├── loader.stories.ts │ │ └── text.stories.ts └── styles.scss ├── tsconfig.app.json ├── tsconfig.json └── tsconfig.spec.json /.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see https://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.ts] 12 | quote_type = single 13 | 14 | [*.md] 15 | max_line_length = off 16 | trim_trailing_whitespace = false 17 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "overrides": [ 4 | { 5 | "files": [ 6 | "*.ts" 7 | ], 8 | "parserOptions": { 9 | "project": [ 10 | "tsconfig.json", 11 | "e2e/tsconfig.json" 12 | ], 13 | "createDefaultProgram": true 14 | }, 15 | "extends": [ 16 | "plugin:@angular-eslint/recommended", 17 | "eslint:recommended", 18 | "plugin:@typescript-eslint/recommended", 19 | "plugin:@typescript-eslint/recommended-requiring-type-checking", 20 | "plugin:@angular-eslint/template/process-inline-templates" 21 | ], 22 | "rules": { 23 | "@angular-eslint/component-selector": 0, 24 | "@angular-eslint/directive-selector": 0, 25 | "@typescript-eslint/no-unsafe-call": 0, 26 | "@typescript-eslint/no-unsafe-member-access": 0, 27 | "@typescript-eslint/no-unsafe-assignment": 0, 28 | "@typescript-eslint/no-unsafe-return": 0, 29 | "@typescript-eslint/no-unsafe-argument": 0, 30 | "@typescript-eslint/no-explicit-any": 0, 31 | "@typescript-eslint/explicit-module-boundary-types": 0, 32 | "@typescript-eslint/unbound-method": 0, 33 | "@angular-eslint/no-output-native": 0, 34 | "@typescript-eslint/no-unused-vars": 2, 35 | "@typescript-eslint/ban-ts-comment": 0, 36 | "storybook/context-in-play-function": 0 37 | } 38 | }, 39 | { 40 | "files": [ 41 | "*.spec.ts" 42 | ], 43 | "rules": { 44 | "@typescript-eslint/no-floating-promises": 0 45 | } 46 | }, 47 | { 48 | "files": [ 49 | "*.html" 50 | ], 51 | "extends": [ 52 | "plugin:@angular-eslint/template/recommended" 53 | ], 54 | "rules": {} 55 | } 56 | ], 57 | "extends": [ 58 | "plugin:storybook/recommended" 59 | ] 60 | } 61 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: makimenko 4 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/Bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | 5 | --- 6 | 7 | ## Steps 8 | 1. 9 | 10 | ## Actual Result 11 | 12 | ## Expected Result 13 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/Feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | 5 | --- 6 | 7 | **Is your feature request related to a problem? Please describe.** 8 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 9 | 10 | **Describe the solution you'd like** 11 | A clear and concise description of what you want to happen. 12 | 13 | **Describe alternatives you've considered** 14 | A clear and concise description of any alternative solutions or features you've considered. 15 | 16 | **Additional context** 17 | Add any other context or screenshots about the feature request here. 18 | -------------------------------------------------------------------------------- /.github/workflows/publish-on-npm.yml: -------------------------------------------------------------------------------- 1 | name: Publish on NPM 2 | 3 | on: 4 | release: 5 | types: [published] 6 | 7 | jobs: 8 | build: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v1 12 | - uses: actions/setup-node@v1 13 | with: 14 | node-version: 12 15 | registry-url: https://registry.npmjs.org/ 16 | - name: Install project dependencies 17 | run: npm i 18 | - name: Build atft library 19 | run: npx ng build atft 20 | - run: npm publish --access public 21 | working-directory: ./dist/atft 22 | env: 23 | NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # Compiled output 4 | /dist 5 | /tmp 6 | /out-tsc 7 | /bazel-out 8 | 9 | # Node 10 | /node_modules 11 | npm-debug.log 12 | yarn-error.log 13 | 14 | # IDEs and editors 15 | .idea/ 16 | .project 17 | .classpath 18 | .c9/ 19 | *.launch 20 | .settings/ 21 | *.sublime-workspace 22 | 23 | # Visual Studio Code 24 | .vscode/* 25 | !.vscode/settings.json 26 | !.vscode/tasks.json 27 | !.vscode/launch.json 28 | !.vscode/extensions.json 29 | .history/* 30 | 31 | # Miscellaneous 32 | /.angular/cache 33 | .sass-cache/ 34 | /connect.lock 35 | /coverage 36 | /libpeerconnection.log 37 | testem.log 38 | /typings 39 | 40 | # System files 41 | .DS_Store 42 | Thumbs.db 43 | /.angular/ 44 | 45 | *.sh 46 | -------------------------------------------------------------------------------- /.storybook/main.ts: -------------------------------------------------------------------------------- 1 | import type {StorybookConfig} from "@storybook/angular"; 2 | 3 | const config: StorybookConfig = { 4 | stories: ["../src/**/*.mdx", "../src/**/*.stories.@(js|jsx|mjs|ts|tsx)"], 5 | addons: [ 6 | "@storybook/addon-links", 7 | "@storybook/addon-essentials", 8 | "@storybook/addon-interactions", 9 | ], 10 | framework: { 11 | name: "@storybook/angular", 12 | options: {}, 13 | }, 14 | docs: { 15 | autodocs: "tag", 16 | }, 17 | staticDirs: [ 18 | {from: '../src/assets', to: '/assets' } 19 | ], previewHead: (head) => ` 20 | 36 | ${head} 37 | `, 38 | }; 39 | export default config; 40 | -------------------------------------------------------------------------------- /.storybook/preview.ts: -------------------------------------------------------------------------------- 1 | import type { Preview } from "@storybook/angular"; 2 | 3 | const preview: Preview = { 4 | parameters: { 5 | layout: 'fullscreen', 6 | actions: { argTypesRegex: "^on[A-Z].*" }, 7 | controls: { 8 | matchers: { 9 | color: /(background|color)$/i, 10 | date: /Date$/i, 11 | }, 12 | }, 13 | options: { 14 | storySort: {method: 'alphabetical'} 15 | }, 16 | }, 17 | }; 18 | 19 | export default preview; 20 | -------------------------------------------------------------------------------- /.storybook/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.app.json", 3 | "compilerOptions": { 4 | "types": ["node"], 5 | "allowSyntheticDefaultImports": true, 6 | "resolveJsonModule": true 7 | }, 8 | "exclude": ["../src/test.ts", "../src/**/*.spec.ts"], 9 | "include": ["../src/**/*", "./preview.ts"], 10 | "files": ["./typings.d.ts"] 11 | } 12 | -------------------------------------------------------------------------------- /.storybook/typings.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.md' { 2 | const content: string; 3 | export default content; 4 | } 5 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. 4 | 5 | ### [0.0.1](https://github.com/makimenko/angular-template-for-threejs/compare/v1.5.4...v0.0.1) (2023-12-04) 6 | 7 | 8 | ### Features 9 | 10 | * Upgrade to Angular 17 ([c28180a](https://github.com/makimenko/angular-template-for-threejs/commit/c28180a7230ebf974295cf3cbf8c8c61a64aab15)) 11 | 12 | 13 | ### Bug Fixes 14 | 15 | * light intensity ([d8a9455](https://github.com/makimenko/angular-template-for-threejs/commit/d8a94558b0429c76220e2fb415b46d4c7075f52e)) 16 | * upgrade three from 0.158.0 to 0.159.0 ([#442](https://github.com/makimenko/angular-template-for-threejs/issues/442)) ([71c3760](https://github.com/makimenko/angular-template-for-threejs/commit/71c37609e406c2bc89b812dd7fd4eb0d14da206d)) 17 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Mihail Akimenko 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration file, see link for more information 2 | // https://karma-runner.github.io/1.0/config/configuration-file.html 3 | 4 | module.exports = function (config) { 5 | config.set({ 6 | basePath: 'src', 7 | frameworks: ['jasmine', '@angular-devkit/build-angular'], 8 | plugins: [ 9 | require('karma-jasmine'), 10 | require('karma-chrome-launcher'), 11 | require('karma-jasmine-html-reporter'), 12 | require('karma-coverage'), 13 | require('@angular-devkit/build-angular/plugins/karma') 14 | ], 15 | client: { 16 | jasmine: { 17 | // you can add configuration options for Jasmine here 18 | // the possible options are listed at https://jasmine.github.io/api/edge/Configuration.html 19 | // for example, you can disable the random execution with `random: false` 20 | // or set a specific seed with `seed: 4321` 21 | }, 22 | clearContext: false // leave Jasmine Spec Runner output visible in browser 23 | }, 24 | customLaunchers: { 25 | ChromeHeadlessCustom: { 26 | base: 'ChromeHeadless', 27 | flags: ['--no-sandbox', '--disable-gpu'] 28 | } 29 | }, 30 | jasmineHtmlReporter: { 31 | suppressAll: true // removes the duplicated traces 32 | }, 33 | coverageReporter: { 34 | dir: require('path').join(__dirname, './coverage/app'), 35 | subdir: '.', 36 | reporters: [ 37 | { type: 'html' }, 38 | { type: 'text-summary' }, 39 | { type: 'lcov', subdir: 'report-lcov' }, 40 | ] 41 | }, 42 | preprocessors: { 43 | 'src/**/*.ts': 'coverage' 44 | }, 45 | reporters: ['progress', 'kjhtml', 'coverage'], 46 | port: 9876, 47 | colors: true, 48 | logLevel: config.LOG_INFO, 49 | autoWatch: true, 50 | browsers: ['Chrome'], 51 | singleRun: false, 52 | restartOnFileChange: true 53 | }); 54 | }; 55 | -------------------------------------------------------------------------------- /projects/atft/.storybook/main.ts: -------------------------------------------------------------------------------- 1 | import type { StorybookConfig } from "@storybook/angular"; 2 | 3 | const config: StorybookConfig = { 4 | stories: ["../src/**/*.mdx", "../src/**/*.stories.@(js|jsx|mjs|ts|tsx)"], 5 | addons: [ 6 | "@storybook/addon-links", 7 | "@storybook/addon-essentials", 8 | "@storybook/addon-interactions", 9 | ], 10 | framework: { 11 | name: "@storybook/angular", 12 | options: {}, 13 | }, 14 | docs: { 15 | autodocs: "tag", 16 | }, 17 | }; 18 | export default config; 19 | -------------------------------------------------------------------------------- /projects/atft/.storybook/preview.ts: -------------------------------------------------------------------------------- 1 | import type { Preview } from "@storybook/angular"; 2 | 3 | const preview: Preview = { 4 | parameters: { 5 | actions: { argTypesRegex: "^on[A-Z].*" }, 6 | controls: { 7 | matchers: { 8 | color: /(background|color)$/i, 9 | date: /Date$/i, 10 | }, 11 | }, 12 | }, 13 | }; 14 | 15 | export default preview; 16 | -------------------------------------------------------------------------------- /projects/atft/.storybook/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.lib.json", 3 | "compilerOptions": { 4 | "types": ["node"], 5 | "allowSyntheticDefaultImports": true, 6 | "resolveJsonModule": true 7 | }, 8 | "exclude": ["../src/test.ts", "../src/**/*.spec.ts"], 9 | "include": ["../src/**/*", "./preview.ts"], 10 | "files": ["./typings.d.ts"] 11 | } 12 | -------------------------------------------------------------------------------- /projects/atft/.storybook/typings.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.md' { 2 | const content: string; 3 | export default content; 4 | } 5 | -------------------------------------------------------------------------------- /projects/atft/karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration file, see link for more information 2 | // https://karma-runner.github.io/1.0/config/configuration-file.html 3 | 4 | module.exports = function (config) { 5 | config.set({ 6 | basePath: '', 7 | frameworks: ['jasmine', '@angular-devkit/build-angular'], 8 | plugins: [ 9 | require('karma-jasmine'), 10 | require('karma-chrome-launcher'), 11 | require('karma-jasmine-html-reporter'), 12 | require('karma-coverage'), 13 | require('@angular-devkit/build-angular/plugins/karma') 14 | ], 15 | client: { 16 | jasmine: { 17 | // you can add configuration options for Jasmine here 18 | // the possible options are listed at https://jasmine.github.io/api/edge/Configuration.html 19 | // for example, you can disable the random execution with `random: false` 20 | // or set a specific seed with `seed: 4321` 21 | }, 22 | clearContext: false // leave Jasmine Spec Runner output visible in browser 23 | }, 24 | customLaunchers: { 25 | ChromeHeadlessCustom: { 26 | base: 'ChromeHeadless', 27 | flags: ['--no-sandbox', '--disable-gpu'] 28 | } 29 | }, 30 | jasmineHtmlReporter: { 31 | suppressAll: true // removes the duplicated traces 32 | }, 33 | coverageReporter: { 34 | dir: require('path').join(__dirname, '../../coverage'), 35 | subdir: '.', 36 | reporters: [ 37 | { type: 'html' }, 38 | { type: 'text-summary' }, 39 | { type: 'lcov', subdir: 'report-lcov' }, 40 | ] 41 | }, 42 | preprocessors: { 43 | 'src/**/*.ts': 'coverage' 44 | }, 45 | reporters: ['progress', 'kjhtml', 'coverage'], 46 | port: 9876, 47 | colors: true, 48 | logLevel: config.LOG_INFO, 49 | autoWatch: true, 50 | browsers: ['Chrome'], 51 | singleRun: false, 52 | restartOnFileChange: true 53 | }); 54 | }; 55 | -------------------------------------------------------------------------------- /projects/atft/ng-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../../node_modules/ng-packagr/ng-package.schema.json", 3 | "dest": "../../dist/atft", 4 | "lib": { 5 | "entryFile": "src/public_api.ts" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /projects/atft/src/index.spec.ts: -------------------------------------------------------------------------------- 1 | const orgConsoleError = window.console.error; 2 | 3 | export function patchConsoleToFailOnError() { 4 | window.console.error = function (...args: any[]) { 5 | orgConsoleError.apply(this, args); 6 | if (args && args.toString() !== '[object ProgressEvent]') { 7 | try { 8 | throw new Error('console.error'); 9 | } catch (err) { 10 | /* eslint-disable no-console */ 11 | console.info('console.error', args, err); 12 | } 13 | fail('console.error was called, this is not allowed in a unit test run'); 14 | } 15 | }; 16 | } 17 | 18 | 19 | beforeAll(() => patchConsoleToFailOnError()); 20 | -------------------------------------------------------------------------------- /projects/atft/src/lib/actor/data-center/index.ts: -------------------------------------------------------------------------------- 1 | export * from './atft-data-center-actor.module'; 2 | export * from './layer'; 3 | export * from './server'; 4 | export * from './layout'; 5 | -------------------------------------------------------------------------------- /projects/atft/src/lib/actor/data-center/layer/grid-actor.component.spec.ts: -------------------------------------------------------------------------------- 1 | import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing'; 2 | import {AtftDataCenterActorModule} from '../atft-data-center-actor.module'; 3 | import {RendererService} from '../../../renderer'; 4 | import {AnimationService} from '../../../animation'; 5 | import {StatsService} from '../../../stats'; 6 | import {GridActorComponent} from './grid-actor.component'; 7 | 8 | describe('actor', () => { 9 | describe('GridActorComponent', () => { 10 | let component: GridActorComponent; 11 | let fixture: ComponentFixture; 12 | 13 | beforeEach(waitForAsync(() => { 14 | TestBed.configureTestingModule({ 15 | imports: [ 16 | AtftDataCenterActorModule 17 | ], 18 | providers: [ 19 | StatsService, 20 | RendererService, 21 | AnimationService 22 | ] 23 | }); 24 | fixture = TestBed.createComponent(GridActorComponent); 25 | component = fixture.componentInstance; 26 | return TestBed.compileComponents(); 27 | })); 28 | 29 | 30 | it('init', waitForAsync(() => { 31 | fixture.detectChanges(); 32 | expect(component).toBeTruthy(); 33 | })); 34 | 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /projects/atft/src/lib/actor/data-center/layer/index.ts: -------------------------------------------------------------------------------- 1 | export * from './layer-actor.component'; 2 | export * from './grid-actor.component'; 3 | -------------------------------------------------------------------------------- /projects/atft/src/lib/actor/data-center/layer/layer-actor.component.spec.ts: -------------------------------------------------------------------------------- 1 | import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing'; 2 | import {AtftDataCenterActorModule} from '../atft-data-center-actor.module'; 3 | import {RendererService} from '../../../renderer'; 4 | import {AnimationService} from '../../../animation'; 5 | import {LayerActorComponent} from './layer-actor.component'; 6 | import {StatsService} from '../../../stats'; 7 | 8 | describe('actor', () => { 9 | describe('LayerActorComponent', () => { 10 | let component: LayerActorComponent; 11 | let fixture: ComponentFixture; 12 | 13 | beforeEach(waitForAsync(() => { 14 | TestBed.configureTestingModule({ 15 | imports: [ 16 | AtftDataCenterActorModule 17 | ], 18 | providers: [ 19 | StatsService, 20 | RendererService, 21 | AnimationService 22 | ] 23 | }); 24 | fixture = TestBed.createComponent(LayerActorComponent); 25 | component = fixture.componentInstance; 26 | return TestBed.compileComponents(); 27 | })); 28 | 29 | 30 | it('init', waitForAsync(() => { 31 | fixture.detectChanges(); 32 | expect(component).toBeTruthy(); 33 | 34 | component.onSelected(); 35 | component.onDeselected(); 36 | component.onClick(); 37 | 38 | })); 39 | 40 | 41 | }); 42 | }); 43 | -------------------------------------------------------------------------------- /projects/atft/src/lib/actor/data-center/layer/layer-actor.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, EventEmitter, Input, Output } from '@angular/core'; 2 | import { AbstractEmptyDirective } from '../../../object'; 3 | import { provideParent } from '../../../util'; 4 | 5 | 6 | @Component({ 7 | selector: 'atft-layer-actor', 8 | providers: [provideParent(LayerActorComponent)], 9 | template: ` 10 | 12 | 14 | 15 | 16 | ` 17 | }) 18 | export class LayerActorComponent extends AbstractEmptyDirective { 19 | @Input() 20 | label!: string; 21 | 22 | @Input() 23 | set width(width: number) { 24 | this._width = width; 25 | this.translateLabelX = this._width / 2 - 10; 26 | } 27 | 28 | get width(): number { 29 | return this._width; 30 | } 31 | 32 | protected _width!: number; 33 | 34 | @Input() 35 | height!: number; 36 | 37 | @Output() render = new EventEmitter(); 38 | @Output() selected = new EventEmitter(); 39 | @Output() deselected = new EventEmitter(); 40 | 41 | color: string | number = '#A0A0A0'; 42 | 43 | translateLabelX!: number; 44 | 45 | public onSelected() { 46 | this.color = '#A4A4A4'; 47 | } 48 | 49 | public onDeselected() { 50 | this.color = '#A0A0A0'; 51 | } 52 | 53 | public onClick() { 54 | this.color = '#A0A0A0'; 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /projects/atft/src/lib/actor/data-center/layout/dagre-edge.component.spec.ts: -------------------------------------------------------------------------------- 1 | import {ComponentFixture, TestBed} from '@angular/core/testing'; 2 | import {StatsService} from '../../../stats'; 3 | import {RendererService} from '../../../renderer'; 4 | import {AnimationService} from '../../../animation'; 5 | import {FontService, IconService, ModelService, ObjLoaderService, SvgLoaderService} from '../../../object'; 6 | 7 | import {ActorRepositoryService} from '../service'; 8 | import {RaycasterService} from '../../../raycaster'; 9 | import {DagreEdgeComponent, EdgeType} from './dagre-edge.component'; 10 | import {DagreLayoutComponent} from './dagre-layout.component'; 11 | 12 | describe('dagre-layout', () => { 13 | 14 | describe('DagreEdgeComponent', () => { 15 | let component: DagreEdgeComponent; 16 | let fixture: ComponentFixture; 17 | 18 | beforeEach(() => { 19 | TestBed.configureTestingModule({ 20 | declarations: [ 21 | DagreEdgeComponent 22 | ], 23 | providers: [ 24 | StatsService, 25 | RendererService, 26 | AnimationService, 27 | FontService, 28 | ActorRepositoryService, 29 | ModelService, 30 | DagreLayoutComponent, 31 | RaycasterService, 32 | ObjLoaderService, 33 | SvgLoaderService, 34 | IconService 35 | ] 36 | }); 37 | fixture = TestBed.createComponent(DagreEdgeComponent); 38 | TestBed.compileComponents(); 39 | }); 40 | 41 | it('init', () => { 42 | component = fixture.componentInstance; 43 | component.type = EdgeType.association; 44 | fixture.detectChanges(); 45 | expect(component).toBeTruthy(); 46 | 47 | component.ngOnChanges({}); 48 | component.ngOnDestroy(); 49 | 50 | }); 51 | 52 | }); 53 | 54 | }); 55 | -------------------------------------------------------------------------------- /projects/atft/src/lib/actor/data-center/layout/dagre-layout.component.spec.ts: -------------------------------------------------------------------------------- 1 | import {ComponentFixture, TestBed} from '@angular/core/testing'; 2 | import {StatsService} from '../../../stats'; 3 | import {RendererService} from '../../../renderer'; 4 | import {AnimationService} from '../../../animation'; 5 | import {FontService, IconService, ModelService, ObjLoaderService, SvgLoaderService} from '../../../object'; 6 | 7 | import {ActorRepositoryService} from '../service'; 8 | import {DagreLayoutComponent} from './dagre-layout.component'; 9 | import {RaycasterService} from '../../../raycaster'; 10 | 11 | describe('dagre-layout', () => { 12 | 13 | describe('DagreLayoutComponent', () => { 14 | let component: DagreLayoutComponent; 15 | let fixture: ComponentFixture; 16 | 17 | beforeEach(() => { 18 | TestBed.configureTestingModule({ 19 | declarations: [ 20 | DagreLayoutComponent 21 | ], 22 | providers: [ 23 | StatsService, 24 | RendererService, 25 | AnimationService, 26 | FontService, 27 | ActorRepositoryService, 28 | ModelService, 29 | RaycasterService, 30 | DagreLayoutComponent, 31 | ObjLoaderService, 32 | SvgLoaderService, 33 | IconService 34 | ] 35 | 36 | }); 37 | fixture = TestBed.createComponent(DagreLayoutComponent); 38 | TestBed.compileComponents(); 39 | }); 40 | 41 | it('init', () => { 42 | component = fixture.componentInstance; 43 | fixture.detectChanges(); 44 | expect(component).toBeTruthy(); 45 | 46 | component.ngOnChanges({}); 47 | component.ngOnDestroy(); 48 | 49 | }); 50 | 51 | }); 52 | 53 | }); 54 | -------------------------------------------------------------------------------- /projects/atft/src/lib/actor/data-center/layout/dagre-model.ts: -------------------------------------------------------------------------------- 1 | import * as dagre from 'dagre'; 2 | 3 | export interface BaseInfo { 4 | name: string; 5 | label?: string; 6 | composition?: string; 7 | } 8 | 9 | export interface Node extends BaseInfo { 10 | type?: string; 11 | icon?: string; 12 | model?: string; 13 | } 14 | 15 | export interface Composition extends BaseInfo { 16 | border?: string; 17 | } 18 | 19 | export interface Edge extends BaseInfo { 20 | from: string; 21 | to: string; 22 | type?: string; 23 | color?: number; 24 | } 25 | 26 | export interface GraphModel { 27 | layout?: dagre.GraphLabel; 28 | compositions?: Array; 29 | nodes?: Array; 30 | edges?: Array; 31 | } 32 | -------------------------------------------------------------------------------- /projects/atft/src/lib/actor/data-center/layout/dagre-utils.spec.ts: -------------------------------------------------------------------------------- 1 | import {DagreUtils} from './dagre-utils'; 2 | import {GraphModel} from './dagre-model'; 3 | import * as dagre from 'dagre'; 4 | 5 | describe('dagre-layout', () => { 6 | describe('dagre-utils', () => { 7 | 8 | it('single node', () => { 9 | const model: GraphModel = { 10 | layout: { 11 | width: 100, 12 | height: 100 13 | }, 14 | nodes: [{ 15 | name: 'user', 16 | model: 'user', 17 | }] 18 | }; 19 | const graph: dagre.graphlib.Graph = DagreUtils.modelToGraph(model); 20 | expect(graph.nodes().length).toBe(1); 21 | expect(graph.nodes()[0]).toBe('user'); 22 | }); 23 | 24 | it('basic graph', () => { 25 | const model: GraphModel = { 26 | layout: { 27 | width: 100, 28 | height: 100 29 | }, 30 | nodes: [{ 31 | name: 'user', 32 | model: 'user', 33 | }, { 34 | name: 'spa', 35 | type: 'compact', 36 | icon: 'connected_tv' 37 | }, { 38 | name: 'api', 39 | composition: 'backend' 40 | }], 41 | edges: [{ 42 | name: 'e1', 43 | from: 'user', 44 | to: 'spa' 45 | }, { 46 | name: 'e2', 47 | from: 'spa', 48 | to: 'api' 49 | }], 50 | compositions: [{ 51 | name: 'backend' 52 | }] 53 | }; 54 | const graph: dagre.graphlib.Graph = DagreUtils.modelToGraph(model); 55 | expect(graph.nodes().length).toBe(3 /* nodes */ + 1 /* composition */); 56 | expect(graph.edges().length).toBe(2); 57 | }); 58 | 59 | 60 | }); 61 | }); 62 | -------------------------------------------------------------------------------- /projects/atft/src/lib/actor/data-center/layout/dagre-utils.ts: -------------------------------------------------------------------------------- 1 | import * as dagre from 'dagre'; 2 | import {BaseInfo, Edge, GraphModel, Node} from './dagre-model'; 3 | 4 | 5 | /** 6 | * WIKI: https://github.com/dagrejs/dagre/wiki 7 | */ 8 | export class DagreUtils { 9 | 10 | public static modelToGraph(model: GraphModel): dagre.graphlib.Graph { 11 | const g = new dagre.graphlib.Graph({ 12 | compound: true, 13 | multigraph: true 14 | }); 15 | 16 | g.setGraph(this.getLayout(model)) 17 | 18 | g.setDefaultEdgeLabel(function () { 19 | return {}; 20 | }); 21 | 22 | this.updateGraph(g, model); 23 | 24 | // console.log('DagreUtils.layout model', model); 25 | // console.log('DagreUtils.layout g', g); 26 | dagre.layout(g); 27 | // console.log('DagreUtils.layout result (g.nodes())', g.nodes()); 28 | return g; 29 | } 30 | 31 | public static updateBaseInfo(g: dagre.graphlib.Graph, baseInfo: Array | undefined ) { 32 | if (baseInfo) { 33 | baseInfo.forEach((node: Node) => { 34 | g.setNode(node.name, {label: node.label, width: 18, height: 18}); 35 | if (node.composition) { 36 | g.setParent(node.name, node.composition); 37 | } 38 | }); 39 | } 40 | } 41 | 42 | public static updateEdges(g: dagre.graphlib.Graph, model: GraphModel) { 43 | if (model.edges) { 44 | model.edges.forEach((edge: Edge) => { 45 | g.setEdge(edge.from, edge.to, {name: edge.name}); 46 | }); 47 | } 48 | } 49 | 50 | public static updateGraph(g: dagre.graphlib.Graph, model: GraphModel) { 51 | this.updateBaseInfo(g, model.compositions); 52 | this.updateBaseInfo(g, model.nodes); 53 | this.updateEdges(g, model); 54 | } 55 | 56 | public static getLayout(model: GraphModel): any { 57 | const layout: dagre.GraphLabel | undefined = model.layout; 58 | return layout; 59 | } 60 | 61 | } 62 | -------------------------------------------------------------------------------- /projects/atft/src/lib/actor/data-center/layout/index.ts: -------------------------------------------------------------------------------- 1 | export * from './dagre-layout.component'; 2 | export * from './dagre-node.component'; 3 | export * from './dagre-edge.component'; 4 | export * from './dagre-composition.component'; 5 | export * from './dagre-yaml-parser.component'; 6 | export * from './dagre-model'; 7 | -------------------------------------------------------------------------------- /projects/atft/src/lib/actor/data-center/server/abstract-server-actor.ts: -------------------------------------------------------------------------------- 1 | import {Directive, EventEmitter, Input, Output} from '@angular/core'; 2 | import {AbstractEmptyDirective} from '../../../object'; 3 | 4 | @Directive() 5 | export abstract class AbstractServerActor extends AbstractEmptyDirective { 6 | 7 | @Input() 8 | label!: string; 9 | 10 | @Output() 11 | render = new EventEmitter(); 12 | 13 | @Output() 14 | selected = new EventEmitter(); 15 | 16 | @Output() 17 | deselected = new EventEmitter(); 18 | 19 | @Output() 20 | actorClick = new EventEmitter(); 21 | 22 | @Input() 23 | icon?: string; 24 | 25 | color: string | number = '#ffffff'; 26 | 27 | @Input() 28 | showFrame = true; 29 | 30 | public onSelected() { 31 | // console.log('ServerActorComponent.onSelected'); 32 | this.color = '#fff0f0'; 33 | } 34 | 35 | public onDeselected() { 36 | // console.log('ServerActorComponent.onDeselected'); 37 | this.color = '#ffffff'; 38 | } 39 | 40 | public onClick() { 41 | console.log('ServerActorComponent.onClick'); 42 | this.color = '#ffa0a0'; 43 | this.actorClick.emit(); 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /projects/atft/src/lib/actor/data-center/server/index.ts: -------------------------------------------------------------------------------- 1 | export * from './abstract-server-actor'; 2 | export * from './server-barrel-actor.component'; 3 | export * from './server-stand-actor.component'; 4 | export * from './server-compact-actor.component'; 5 | export * from './server-icon-actor.component'; 6 | export * from './workstation-actor.component'; 7 | export * from './model-actor.component'; 8 | -------------------------------------------------------------------------------- /projects/atft/src/lib/actor/data-center/server/model-actor.component.ts: -------------------------------------------------------------------------------- 1 | import {Component, Input, Optional, SkipSelf} from '@angular/core'; 2 | import {provideParent} from '../../../util'; 3 | import {AbstractServerActor} from './abstract-server-actor'; 4 | import {AbstractObject3D, ModelService} from '../../../object'; 5 | import {RendererService} from '../../../renderer'; 6 | 7 | @Component({ 8 | selector: 'atft-model-actor', 9 | providers: [provideParent(ModelActorComponent)], 10 | template: ` 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 20 | 21 | 23 | 24 | 25 | 26 | ` 27 | }) 28 | export class ModelActorComponent extends AbstractServerActor { 29 | 30 | @Input() 31 | set model(model: string) { 32 | const iconProvider = this.modelService.getSource(model); 33 | this.modelPath = iconProvider.url; 34 | } 35 | 36 | get model(): string { 37 | return this.model; 38 | } 39 | 40 | public modelPath!: string; 41 | 42 | constructor( 43 | protected override rendererService: RendererService, 44 | @SkipSelf() @Optional() protected override parent: AbstractObject3D, 45 | protected modelService: ModelService 46 | ) { 47 | super(rendererService, parent); 48 | } 49 | 50 | 51 | } 52 | -------------------------------------------------------------------------------- /projects/atft/src/lib/actor/data-center/server/server-barrel-actor.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { provideParent } from '../../../util'; 3 | import { AbstractServerActor } from './abstract-server-actor'; 4 | 5 | @Component({ 6 | selector: 'atft-server-barrel-actor', 7 | providers: [provideParent(ServerBarrelActorComponent)], 8 | template: ` 9 | 10 | 11 | 12 | 13 | 15 | 16 | 17 | 20 | 21 | 22 | 23 | 25 | 26 | 28 | 29 | 30 | ` 31 | }) 32 | export class ServerBarrelActorComponent extends AbstractServerActor { 33 | 34 | textColor = '#DADADA'; 35 | 36 | } 37 | -------------------------------------------------------------------------------- /projects/atft/src/lib/actor/data-center/server/server-compact-actor.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { provideParent } from '../../../util'; 3 | import { AbstractServerActor } from './abstract-server-actor'; 4 | 5 | @Component({ 6 | selector: 'atft-server-compact-actor', 7 | providers: [provideParent(ServerCompactActorComponent)], 8 | template: ` 9 | 10 | 11 | 12 | 13 | 15 | 18 | 19 | 20 | 21 | 22 | 24 | 25 | 27 | 28 | 29 | ` 30 | }) 31 | export class ServerCompactActorComponent extends AbstractServerActor { 32 | 33 | } 34 | -------------------------------------------------------------------------------- /projects/atft/src/lib/actor/data-center/server/server-icon-actor.component.ts: -------------------------------------------------------------------------------- 1 | import {Component} from '@angular/core'; 2 | import {provideParent} from '../../../util'; 3 | import {AbstractServerActor} from './abstract-server-actor'; 4 | 5 | @Component({ 6 | selector: 'atft-server-icon-actor', 7 | providers: [provideParent(ServerIconActorComponent)], 8 | template: ` 9 | 10 | 11 | 12 | 15 | 16 | 17 | 18 | 20 | 21 | 23 | 24 | 25 | 26 | ` 27 | }) 28 | export class ServerIconActorComponent extends AbstractServerActor { 29 | 30 | } 31 | -------------------------------------------------------------------------------- /projects/atft/src/lib/actor/data-center/server/server-stand-actor.component.spec.ts: -------------------------------------------------------------------------------- 1 | import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing'; 2 | import {AtftDataCenterActorModule} from '../atft-data-center-actor.module'; 3 | import {RendererService} from '../../../renderer'; 4 | import {AnimationService} from '../../../animation'; 5 | import {ServerStandActorComponent} from './server-stand-actor.component'; 6 | import {StatsService} from '../../../stats'; 7 | 8 | describe('actor', () => { 9 | describe('ServerStandActorComponent', () => { 10 | let component: ServerStandActorComponent; 11 | let fixture: ComponentFixture; 12 | 13 | beforeEach(waitForAsync(() => { 14 | TestBed.configureTestingModule({ 15 | imports: [ 16 | AtftDataCenterActorModule 17 | ], 18 | providers: [ 19 | StatsService, 20 | RendererService, 21 | AnimationService 22 | ] 23 | }); 24 | fixture = TestBed.createComponent(ServerStandActorComponent); 25 | component = fixture.componentInstance; 26 | return TestBed.compileComponents(); 27 | })); 28 | 29 | 30 | it('init', waitForAsync(() => { 31 | fixture.detectChanges(); 32 | expect(component).toBeTruthy(); 33 | 34 | component.onSelected(); 35 | component.onDeselected(); 36 | component.onClick(); 37 | 38 | })); 39 | 40 | 41 | }); 42 | }); 43 | -------------------------------------------------------------------------------- /projects/atft/src/lib/actor/data-center/server/server-stand-actor.component.ts: -------------------------------------------------------------------------------- 1 | import {Component} from '@angular/core'; 2 | import { provideParent } from '../../../util'; 3 | import { AbstractServerActor } from './abstract-server-actor'; 4 | 5 | @Component({ 6 | selector: 'atft-server-stand-actor', 7 | providers: [provideParent(ServerStandActorComponent)], 8 | template: ` 9 | 10 | 11 | 12 | 13 | 15 | 18 | 19 | 20 | 21 | 22 | 25 | 26 | 28 | 29 | 30 | ` 31 | }) 32 | export class ServerStandActorComponent extends AbstractServerActor { 33 | 34 | 35 | } 36 | -------------------------------------------------------------------------------- /projects/atft/src/lib/actor/data-center/service/index.ts: -------------------------------------------------------------------------------- 1 | export * from './actor-repository.service'; 2 | -------------------------------------------------------------------------------- /projects/atft/src/lib/actor/ux/index.ts: -------------------------------------------------------------------------------- 1 | export * from './ux-actor.module'; 2 | export * from './text'; 3 | export * from './loader'; 4 | -------------------------------------------------------------------------------- /projects/atft/src/lib/actor/ux/loader/index.ts: -------------------------------------------------------------------------------- 1 | export * from './loader-actor.component'; 2 | -------------------------------------------------------------------------------- /projects/atft/src/lib/actor/ux/loader/loader-actor.component.spec.ts: -------------------------------------------------------------------------------- 1 | import {ComponentFixture, TestBed} from '@angular/core/testing'; 2 | import {StatsService} from '../../../stats'; 3 | import {RendererService} from '../../../renderer'; 4 | import {AnimationService} from '../../../animation'; 5 | import {TextMeshComponent} from '../../../object'; 6 | import {LoaderActorComponent} from './loader-actor.component'; 7 | 8 | 9 | describe('ux', () => { 10 | 11 | describe('LoaderActorComponent', () => { 12 | let component: LoaderActorComponent; 13 | let fixture: ComponentFixture; 14 | 15 | beforeEach(() => { 16 | TestBed.configureTestingModule({ 17 | declarations: [ 18 | LoaderActorComponent, 19 | TextMeshComponent 20 | ], 21 | providers: [ 22 | StatsService, 23 | RendererService, 24 | AnimationService 25 | ] 26 | }); 27 | fixture = TestBed.createComponent(LoaderActorComponent); 28 | TestBed.compileComponents(); 29 | }); 30 | 31 | it('init', () => { 32 | component = fixture.componentInstance; 33 | component.animate = true; 34 | component.materialColor = '#ff0000'; 35 | 36 | fixture.autoDetectChanges(true); 37 | fixture.detectChanges(); 38 | 39 | expect(component).toBeTruthy(); 40 | }); 41 | 42 | }); 43 | 44 | }); 45 | -------------------------------------------------------------------------------- /projects/atft/src/lib/actor/ux/text/index.ts: -------------------------------------------------------------------------------- 1 | export * from './text-actor.component'; 2 | -------------------------------------------------------------------------------- /projects/atft/src/lib/actor/ux/text/text-actor.component.spec.ts: -------------------------------------------------------------------------------- 1 | import {TextActorComponent} from './text-actor.component'; 2 | import {ComponentFixture, TestBed} from '@angular/core/testing'; 3 | import {StatsService} from '../../../stats'; 4 | import {RendererService} from '../../../renderer'; 5 | import {AnimationService} from '../../../animation'; 6 | import {FontService, TextMeshComponent} from '../../../object'; 7 | 8 | 9 | describe('ux', () => { 10 | 11 | describe('TextActorComponent', () => { 12 | let component: TextActorComponent; 13 | let fixture: ComponentFixture; 14 | 15 | beforeEach(() => { 16 | TestBed.configureTestingModule({ 17 | declarations: [ 18 | TextActorComponent, 19 | TextMeshComponent 20 | ], 21 | providers: [ 22 | StatsService, 23 | RendererService, 24 | AnimationService, 25 | FontService 26 | ] 27 | }); 28 | fixture = TestBed.createComponent(TextActorComponent); 29 | TestBed.compileComponents(); 30 | }); 31 | 32 | it('init', () => { 33 | component = fixture.componentInstance; 34 | component.text = 'Sample Text'; 35 | component.currentText = ''; 36 | component.animate = true; 37 | component.materialColor = '#ff0000'; 38 | 39 | fixture.detectChanges(); 40 | 41 | expect(component).toBeTruthy(); 42 | expect(component.currentText).toBe(''); 43 | }); 44 | 45 | }); 46 | 47 | }); 48 | -------------------------------------------------------------------------------- /projects/atft/src/lib/actor/ux/ux-actor.module.ts: -------------------------------------------------------------------------------- 1 | import {NgModule} from '@angular/core'; 2 | import {CommonModule} from '@angular/common'; 3 | import {AtftModule} from '../../atft.module'; 4 | import {TextActorComponent} from './text/text-actor.component'; 5 | import {LoaderActorComponent} from './loader'; 6 | 7 | @NgModule({ 8 | imports: [ 9 | CommonModule, 10 | AtftModule 11 | ], 12 | declarations: [ 13 | TextActorComponent, 14 | LoaderActorComponent 15 | ], 16 | exports: [ 17 | TextActorComponent, 18 | LoaderActorComponent 19 | ] 20 | }) 21 | export class UxActorModule { 22 | } 23 | -------------------------------------------------------------------------------- /projects/atft/src/lib/animation/animation.service.spec.ts: -------------------------------------------------------------------------------- 1 | import {AnimationService} from './animation.service'; 2 | import {RendererService} from '../renderer'; 3 | import {fakeAsync, tick} from '@angular/core/testing'; 4 | import {StatsService} from '../stats'; 5 | 6 | describe('animation', () => { 7 | describe('AnimationService', () => { 8 | 9 | 10 | it('validate', fakeAsync(() => { 11 | const stats = new StatsService(); 12 | const renderer = new RendererService(stats); 13 | const animation = new AnimationService(renderer); 14 | 15 | let called = false; 16 | animation.animate.subscribe(() => { 17 | called = true; 18 | }); 19 | spyOn(window, 'requestAnimationFrame').and.callThrough(); 20 | animation.start(); 21 | 22 | tick(); 23 | animation.animationStep(); 24 | tick(); 25 | animation.stop(); 26 | 27 | expect(window.requestAnimationFrame).toHaveBeenCalled(); 28 | expect(called).toBeTruthy(); 29 | })); 30 | 31 | 32 | }); 33 | 34 | }); 35 | -------------------------------------------------------------------------------- /projects/atft/src/lib/animation/atft-animation.module.ts: -------------------------------------------------------------------------------- 1 | import {NgModule} from '@angular/core'; 2 | import {AnimationService} from './animation.service'; 3 | import {EmitStepComponent, TimelineEmitterComponent} from "./timeline-emitter"; 4 | 5 | 6 | @NgModule({ 7 | declarations: [ 8 | TimelineEmitterComponent, 9 | EmitStepComponent, 10 | ], 11 | providers: [ 12 | AnimationService, 13 | ], 14 | exports: [ 15 | TimelineEmitterComponent, 16 | EmitStepComponent, 17 | ], 18 | }) 19 | export class AtftAnimationModule { 20 | } 21 | -------------------------------------------------------------------------------- /projects/atft/src/lib/animation/index.ts: -------------------------------------------------------------------------------- 1 | export * from './atft-animation.module'; 2 | export * from './animation.service'; 3 | export * from './timeline-emitter'; 4 | -------------------------------------------------------------------------------- /projects/atft/src/lib/animation/timeline-emitter/emit-step.component.ts: -------------------------------------------------------------------------------- 1 | import { 2 | AfterViewInit, 3 | Component, 4 | EventEmitter, 5 | Input, 6 | OnChanges, 7 | OnDestroy, 8 | Output, 9 | SimpleChanges 10 | } from '@angular/core'; 11 | import {provideParent} from "../../util"; 12 | 13 | @Component({ 14 | selector: 'atft-emit-step', 15 | providers: [provideParent(EmitStepComponent)], 16 | template: '' 17 | }) 18 | export class EmitStepComponent implements AfterViewInit, OnDestroy, OnChanges { 19 | 20 | @Input() ms!: number; 21 | @Output() action = new EventEmitter(); 22 | 23 | 24 | constructor() { 25 | console.log('EmitStepComponent.constructor'); 26 | } 27 | 28 | public ngAfterViewInit() { 29 | console.log('EmitStepComponent.ngAfterViewInit'); 30 | } 31 | 32 | ngOnDestroy(): void { 33 | console.log('EmitStepComponent.ngOnDestroy'); 34 | } 35 | 36 | public ngOnChanges(changes: SimpleChanges) { 37 | console.log('EmitStepComponent.ngOnChanges', changes); 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /projects/atft/src/lib/animation/timeline-emitter/index.ts: -------------------------------------------------------------------------------- 1 | export * from './timeline-emitter.component' 2 | export * from './emit-step.component' 3 | -------------------------------------------------------------------------------- /projects/atft/src/lib/animation/timeline-emitter/timeline-emitter.component.ts: -------------------------------------------------------------------------------- 1 | import { 2 | AfterViewInit, 3 | Component, 4 | ContentChildren, 5 | Input, 6 | OnChanges, 7 | OnDestroy, 8 | OnInit, 9 | QueryList, 10 | SimpleChanges 11 | } from '@angular/core'; 12 | import {EmitStepComponent} from "./emit-step.component"; 13 | 14 | @Component({ 15 | selector: 'atft-timeline-emitter', 16 | template: '' 17 | }) 18 | export class TimelineEmitterComponent implements AfterViewInit, OnDestroy, OnChanges, OnInit { 19 | 20 | @ContentChildren(EmitStepComponent, {descendants: true}) emitSteps!: QueryList; 21 | @Input() autostart = false; 22 | 23 | constructor() { 24 | console.log('TimelineEmitterComponent.constructor'); 25 | } 26 | 27 | public ngAfterViewInit() { 28 | console.log('TimelineEmitterComponent.ngAfterViewInit', this.emitSteps); 29 | if (this.autostart) { 30 | this.start() 31 | } 32 | } 33 | 34 | ngOnDestroy(): void { 35 | console.log('TimelineEmitterComponent.ngOnDestroy'); 36 | } 37 | 38 | public ngOnChanges(changes: SimpleChanges) { 39 | console.log('TimelineEmitterComponent.ngOnChanges', changes); 40 | } 41 | 42 | ngOnInit(): void { 43 | console.log('TimelineEmitterComponent.ngOnInit', this.emitSteps); 44 | } 45 | 46 | public start() { 47 | console.log('TimelineEmitterComponent.start'); 48 | this.emitSteps?.forEach(i => { 49 | console.log('TimelineEmitterComponent.start setTimeout on', i.ms); 50 | setTimeout(() => { 51 | i.action.emit() 52 | }, i.ms) 53 | }) 54 | } 55 | 56 | } 57 | -------------------------------------------------------------------------------- /projects/atft/src/lib/atft.module.ts: -------------------------------------------------------------------------------- 1 | import {NgModule} from '@angular/core'; 2 | import {AtftCameraModule} from './camera/atft-camera.module'; 3 | import {AtftObjectModule} from './object/atft-object.module'; 4 | import {AtftControlModule} from './control/atft-control.module'; 5 | import {AtftPipeModule} from './pipe/atft-pipe.module'; 6 | import {AtftRendererModule} from './renderer/atft-renderer.module'; 7 | import {AtftAnimationModule} from './animation/atft-animation.module'; 8 | import {AtftRaycasterModule} from './raycaster/atft-raycaster.module'; 9 | import {AtftStatsModule} from './stats/stats.module'; 10 | import {AtftEffectModule} from './effect/atft-effect.module'; 11 | 12 | // NOTE: In case of "ERROR in Unexpected value 'undefined' exported by the module 'AtftModule" fix imports (do not import index.ts) 13 | 14 | @NgModule({ 15 | imports: [ 16 | AtftCameraModule, 17 | AtftObjectModule, 18 | AtftControlModule, 19 | AtftPipeModule, 20 | AtftRendererModule, 21 | AtftAnimationModule, 22 | AtftRaycasterModule, 23 | AtftStatsModule, 24 | AtftEffectModule 25 | ], 26 | exports: [ 27 | AtftCameraModule, 28 | AtftObjectModule, 29 | AtftControlModule, 30 | AtftPipeModule, 31 | AtftRendererModule, 32 | AtftAnimationModule, 33 | AtftRaycasterModule, 34 | AtftStatsModule, 35 | AtftEffectModule 36 | ] 37 | }) 38 | export class AtftModule { 39 | } 40 | 41 | -------------------------------------------------------------------------------- /projects/atft/src/lib/camera/atft-camera.module.ts: -------------------------------------------------------------------------------- 1 | import {NgModule} from '@angular/core'; 2 | import {CommonModule} from '@angular/common'; 3 | import {PerspectiveCameraComponent} from './perspective-camera.component'; 4 | import {OrthographicCameraComponent} from './orthographic-camera.component'; 5 | 6 | @NgModule({ 7 | imports: [ 8 | CommonModule 9 | ], 10 | declarations: [ 11 | PerspectiveCameraComponent, 12 | OrthographicCameraComponent 13 | ], 14 | exports: [ 15 | PerspectiveCameraComponent, 16 | OrthographicCameraComponent 17 | ] 18 | }) 19 | export class AtftCameraModule { 20 | } 21 | -------------------------------------------------------------------------------- /projects/atft/src/lib/camera/index.ts: -------------------------------------------------------------------------------- 1 | export * from './atft-camera.module'; 2 | export * from './abstract-camera'; 3 | export * from './perspective-camera.component'; 4 | export * from './orthographic-camera.component'; 5 | -------------------------------------------------------------------------------- /projects/atft/src/lib/camera/perspective-camera.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input } from '@angular/core'; 2 | import * as THREE from 'three'; 3 | import { RendererService } from '../renderer/renderer.service'; 4 | import { provideParent } from '../util'; 5 | import { AbstractCamera } from './abstract-camera'; 6 | 7 | @Component({ 8 | selector: 'atft-perspective-camera', 9 | providers: [provideParent(PerspectiveCameraComponent, AbstractCamera)], 10 | template: '' 11 | }) 12 | export class PerspectiveCameraComponent extends AbstractCamera { 13 | 14 | @Input() fov!: number; 15 | @Input() near!: number; 16 | @Input() far!: number; 17 | 18 | constructor( 19 | protected override rendererService: RendererService 20 | ) { 21 | super(rendererService); 22 | } 23 | 24 | protected createCamera(): void { 25 | // console.log('PerspectiveCameraComponent.createCamera'); 26 | // let aspectRatio = undefined; // Updated later 27 | this.camera = new THREE.PerspectiveCamera( 28 | this.fov, 29 | undefined, 30 | this.near, 31 | this.far 32 | ); 33 | } 34 | 35 | public updateAspectRatio(aspect: number) { 36 | // console.log('PerspectiveCameraComponent.updateAspectRatio: ' + aspect); 37 | this.camera.aspect = aspect; 38 | this.camera.updateProjectionMatrix(); 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /projects/atft/src/lib/control/atft-control.module.ts: -------------------------------------------------------------------------------- 1 | import {NgModule} from '@angular/core'; 2 | import {CommonModule} from '@angular/common'; 3 | import {OrbitControlsComponent} from './orbit-controls.component'; 4 | import {MapControlsComponent} from './map-controls.component'; 5 | 6 | 7 | @NgModule({ 8 | declarations: [ 9 | OrbitControlsComponent, 10 | MapControlsComponent 11 | ], 12 | imports: [ 13 | CommonModule 14 | ], 15 | exports: [ 16 | OrbitControlsComponent, 17 | MapControlsComponent 18 | ] 19 | }) 20 | export class AtftControlModule { 21 | } 22 | -------------------------------------------------------------------------------- /projects/atft/src/lib/control/controls.component.scss: -------------------------------------------------------------------------------- 1 | :host { 2 | display: flex; 3 | flex: 1; 4 | height: 100%; 5 | } 6 | -------------------------------------------------------------------------------- /projects/atft/src/lib/control/index.ts: -------------------------------------------------------------------------------- 1 | export * from './atft-control.module'; 2 | export * from './orbit-controls.component'; 3 | export * from './map-controls.component'; 4 | -------------------------------------------------------------------------------- /projects/atft/src/lib/control/orbit-controls.component.ts: -------------------------------------------------------------------------------- 1 | import {Component, Input, OnChanges, SimpleChanges} from '@angular/core'; 2 | import {OrbitControls} from 'three/examples/jsm/controls/OrbitControls'; 3 | import {RendererService} from '../renderer/renderer.service'; 4 | import {RaycasterService} from '../raycaster/raycaster.service'; 5 | import {AbstractOrbitControls} from './abstract-orbit-controls'; 6 | 7 | @Component({ 8 | selector: 'atft-orbit-controls', 9 | template: ` 10 | `, 11 | styleUrls: ['controls.component.scss'] 12 | }) 13 | export class OrbitControlsComponent extends AbstractOrbitControls implements OnChanges { 14 | 15 | @Input() rotateSpeed = 1.0; 16 | @Input() zoomSpeed = 1.2; 17 | 18 | constructor( 19 | protected override rendererService: RendererService, 20 | protected override raycasterService: RaycasterService 21 | ) { 22 | super(rendererService, raycasterService); 23 | } 24 | 25 | protected setUpControls() { 26 | this.controls = new OrbitControls( 27 | this.childCameras.first.camera, 28 | this.listeningControlElement && this.listeningControlElement.nativeElement 29 | ); 30 | this.controls.rotateSpeed = this.rotateSpeed; 31 | this.controls.zoomSpeed = this.zoomSpeed; 32 | } 33 | 34 | override ngOnChanges(changes: SimpleChanges) { 35 | if (!this.controls) { 36 | return; 37 | } 38 | super.ngOnChanges(changes); 39 | 40 | if (changes['rotateSpeed']) { 41 | this.controls.rotateSpeed = this.rotateSpeed; 42 | } 43 | if (changes['zoomSpeed']) { 44 | this.controls.zoomSpeed = this.zoomSpeed; 45 | } 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /projects/atft/src/lib/effect/atft-effect.module.ts: -------------------------------------------------------------------------------- 1 | import {NgModule} from '@angular/core'; 2 | import {CommonModule} from '@angular/common'; 3 | import {DashedDrawDirective} from './dashed-draw.directive'; 4 | import {FogComponent} from './fog.component'; 5 | import {DofComponent} from './compose/dof.component'; 6 | import {DotScreenComponent} from './compose/dot-screen.component'; 7 | import {EffectComposerComponent} from './compose/effect-composer.component'; 8 | import {BlurComponent} from './compose/blur.component'; 9 | 10 | 11 | @NgModule({ 12 | imports: [ 13 | CommonModule 14 | ], 15 | declarations: [ 16 | DashedDrawDirective, 17 | FogComponent, 18 | DofComponent, 19 | DotScreenComponent, 20 | EffectComposerComponent, 21 | BlurComponent 22 | ], 23 | exports: [ 24 | DashedDrawDirective, 25 | FogComponent, 26 | DofComponent, 27 | DotScreenComponent, 28 | EffectComposerComponent, 29 | BlurComponent 30 | ] 31 | }) 32 | export class AtftEffectModule { 33 | } 34 | -------------------------------------------------------------------------------- /projects/atft/src/lib/effect/compose/abstract-compose-effect.ts: -------------------------------------------------------------------------------- 1 | import {AfterViewInit, Directive, OnChanges, OnDestroy, Optional, SimpleChanges, SkipSelf} from '@angular/core'; 2 | import {RendererService} from '../../renderer/renderer.service'; 3 | import {EffectComposerComponent} from './effect-composer.component'; 4 | import {Pass} from 'three/examples/jsm/postprocessing/Pass'; 5 | 6 | @Directive() 7 | export abstract class AbstractComposeEffect implements AfterViewInit, OnDestroy, OnChanges { 8 | 9 | protected pass: T[] = []; 10 | 11 | constructor( 12 | protected rendererService: RendererService, 13 | @SkipSelf() @Optional() protected composer: EffectComposerComponent 14 | ) { 15 | // console.log('AbstractComposeEffect.constructor', parentScene); 16 | } 17 | 18 | protected enable() { 19 | if (this.composer) { 20 | // console.log('AbstractComposeEffect.enable', this.rendererService); 21 | 22 | this.initPasses(); 23 | 24 | this.pass.forEach(pass => this.composer.addPass(pass)); 25 | this.rendererService.render(); 26 | } 27 | } 28 | 29 | protected disable() { 30 | if (this.composer) { 31 | // console.log('AbstractComposeEffect.disable'); 32 | this.pass.forEach(pass => this.composer.removePass(pass)); 33 | this.rendererService.render(); 34 | } 35 | } 36 | 37 | public ngAfterViewInit() { 38 | this.enable(); 39 | } 40 | 41 | ngOnDestroy(): void { 42 | this.disable(); 43 | } 44 | 45 | public ngOnChanges(changes: SimpleChanges) { 46 | if (!this.pass) { 47 | return; 48 | } 49 | if (this.applyChanges(changes)) { 50 | this.rendererService.render(); 51 | } 52 | } 53 | 54 | abstract initPasses() : void; 55 | 56 | abstract applyChanges(changes: SimpleChanges): boolean; 57 | 58 | 59 | } 60 | -------------------------------------------------------------------------------- /projects/atft/src/lib/effect/compose/blur.component.ts: -------------------------------------------------------------------------------- 1 | import {Component, Optional, SimpleChanges, SkipSelf} from '@angular/core'; 2 | import {RendererService} from '../../renderer/renderer.service'; 3 | import {ShaderPass} from 'three/examples/jsm/postprocessing/ShaderPass'; 4 | import {VerticalBlurShader} from 'three/examples/jsm/shaders/VerticalBlurShader'; 5 | import {HorizontalBlurShader} from 'three/examples/jsm/shaders/HorizontalBlurShader'; 6 | import {EffectComposerComponent} from './effect-composer.component'; 7 | import {AbstractComposeEffect} from './abstract-compose-effect'; 8 | 9 | @Component({ 10 | selector: 'atft-blur', 11 | template: '' 12 | }) 13 | export class BlurComponent extends AbstractComposeEffect { 14 | 15 | constructor( 16 | protected override rendererService: RendererService, 17 | @SkipSelf() @Optional() protected override composer: EffectComposerComponent 18 | ) { 19 | super(rendererService, composer); 20 | } 21 | 22 | initPasses() { 23 | this.pass.push(new ShaderPass(VerticalBlurShader)); 24 | this.pass.push(new ShaderPass(HorizontalBlurShader)); 25 | this.pass.push(new ShaderPass(VerticalBlurShader)); 26 | this.pass.push(new ShaderPass(HorizontalBlurShader)); 27 | } 28 | 29 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 30 | applyChanges(changes: SimpleChanges) { 31 | // TODO: implement changes 32 | return false; 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /projects/atft/src/lib/effect/compose/dot-screen.component.ts: -------------------------------------------------------------------------------- 1 | import {Component, Input, Optional, SimpleChanges, SkipSelf} from '@angular/core'; 2 | import {RendererService} from '../../renderer/renderer.service'; 3 | import {EffectComposerComponent} from './effect-composer.component'; 4 | import {ShaderPass} from 'three/examples/jsm/postprocessing/ShaderPass'; 5 | import {DotScreenShader} from 'three/examples/jsm/shaders/DotScreenShader'; 6 | import {AbstractComposeEffect} from './abstract-compose-effect'; 7 | 8 | @Component({ 9 | selector: 'atft-dot-screen', 10 | template: '' 11 | }) 12 | export class DotScreenComponent extends AbstractComposeEffect { 13 | 14 | @Input() scale = 4; 15 | 16 | constructor( 17 | protected override rendererService: RendererService, 18 | @SkipSelf() @Optional() protected override composer: EffectComposerComponent 19 | ) { 20 | super(rendererService, composer); 21 | } 22 | 23 | initPasses() { 24 | this.pass.push(new ShaderPass(DotScreenShader)); 25 | } 26 | 27 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 28 | applyChanges(changes: SimpleChanges): boolean { 29 | // TODO: Implement 30 | return false; 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /projects/atft/src/lib/effect/compose/effect-composer.component.spec.ts: -------------------------------------------------------------------------------- 1 | import {Component} from '@angular/core'; 2 | import {ComponentFixture, TestBed} from '@angular/core/testing'; 3 | import {StatsService} from '../../stats'; 4 | import {RendererService} from '../../renderer'; 5 | import {AnimationService} from '../../animation'; 6 | import {EffectComposerComponent} from './effect-composer.component'; 7 | import {BlurComponent} from './blur.component'; 8 | 9 | 10 | @Component({ 11 | template: ` 12 | 13 | 14 | ` 15 | }) 16 | export class MockComponent { } 17 | 18 | describe('effect', () => { 19 | 20 | describe('EffectComposerComponent', () => { 21 | let component: MockComponent; 22 | let fixture: ComponentFixture; 23 | 24 | beforeEach(() => { 25 | TestBed.configureTestingModule({ 26 | declarations: [ 27 | MockComponent, 28 | EffectComposerComponent, 29 | BlurComponent 30 | ], 31 | providers: [ 32 | StatsService, 33 | RendererService, 34 | AnimationService, 35 | ] 36 | }); 37 | fixture = TestBed.createComponent(MockComponent); 38 | TestBed.compileComponents(); 39 | }); 40 | 41 | it('init', () => { 42 | component = fixture.componentInstance; 43 | 44 | fixture.detectChanges(); 45 | expect(component).toBeTruthy(); 46 | }); 47 | 48 | }); 49 | 50 | }); 51 | -------------------------------------------------------------------------------- /projects/atft/src/lib/effect/dashed-draw.directive.spec.ts: -------------------------------------------------------------------------------- 1 | import {ComponentFixture, TestBed} from '@angular/core/testing'; 2 | import {DashedDrawDirective} from './dashed-draw.directive'; 3 | import {StatsService} from '../stats'; 4 | import {RendererService} from '../renderer'; 5 | import {AnimationService} from '../animation'; 6 | import {Component} from '@angular/core'; 7 | import {BoxMeshComponent} from '../object'; 8 | 9 | 10 | @Component({ 11 | template: ` 12 | 13 | ` 14 | }) 15 | export class MockComponent { } 16 | 17 | describe('effect', () => { 18 | 19 | describe('DashedDrawDirective', () => { 20 | let component: MockComponent; 21 | let fixture: ComponentFixture; 22 | 23 | beforeEach(() => { 24 | TestBed.configureTestingModule({ 25 | declarations: [ 26 | DashedDrawDirective, 27 | MockComponent, 28 | BoxMeshComponent 29 | ], 30 | providers: [ 31 | StatsService, 32 | RendererService, 33 | AnimationService, 34 | ] 35 | }); 36 | fixture = TestBed.createComponent(MockComponent); 37 | TestBed.compileComponents(); 38 | }); 39 | 40 | it('init', () => { 41 | component = fixture.componentInstance; 42 | 43 | fixture.detectChanges(); 44 | expect(component).toBeTruthy(); 45 | }); 46 | 47 | }); 48 | 49 | }); 50 | -------------------------------------------------------------------------------- /projects/atft/src/lib/effect/fog.component.ts: -------------------------------------------------------------------------------- 1 | import {AfterViewInit, Component, Input, OnChanges, OnDestroy, Optional, SimpleChanges, SkipSelf} from '@angular/core'; 2 | import {RendererService} from '../renderer/renderer.service'; 3 | import {SceneComponent} from '../object'; 4 | import * as THREE from 'three'; 5 | 6 | @Component({ 7 | selector: 'atft-fog', 8 | template: '' 9 | }) 10 | export class FogComponent implements AfterViewInit, OnDestroy, OnChanges { 11 | 12 | @Input() color: string | number = '#0000AA'; 13 | @Input() near = 10; 14 | @Input() far = 100; 15 | 16 | protected scene!: THREE.Scene; 17 | 18 | constructor( 19 | protected rendererService: RendererService, 20 | @SkipSelf() @Optional() protected parent: SceneComponent 21 | ) { 22 | // console.log('FogComponent.constructor', parentScene); 23 | 24 | } 25 | 26 | 27 | public ngAfterViewInit() { 28 | // console.log('ngAfterViewInit', this.parentScene); 29 | if (this.parent && this.parent.getObject() && this.parent.getObject().isScene) { 30 | // console.log('FogComponent detected parentScene scene', this.parentScene); 31 | this.scene = this.parent.getObject(); 32 | this.enableFog(); 33 | } 34 | } 35 | 36 | protected enableFog() { 37 | // console.log('enableFog'); 38 | this.scene.fog = new THREE.Fog(this.color, this.near, this.far); 39 | this.rendererService.render(); 40 | } 41 | 42 | protected disableFog() { 43 | // console.log('disableFog'); 44 | this.scene.fog = null; 45 | this.rendererService.render(); 46 | } 47 | 48 | ngOnDestroy(): void { 49 | this.disableFog(); 50 | } 51 | 52 | 53 | public ngOnChanges(changes: SimpleChanges) { 54 | if (!this.scene) { 55 | return; 56 | } 57 | 58 | if (['color', 'near', 'far'].some(propName => propName in changes)) { 59 | this.enableFog(); 60 | } 61 | 62 | } 63 | 64 | } 65 | -------------------------------------------------------------------------------- /projects/atft/src/lib/effect/index.ts: -------------------------------------------------------------------------------- 1 | export * from './atft-effect.module'; 2 | export * from './dashed-draw.directive'; 3 | export * from './fog.component'; 4 | export * from './compose/dof.component'; 5 | export * from './compose/dot-screen.component'; 6 | export * from './compose/effect-composer.component'; 7 | export * from './compose/blur.component'; 8 | -------------------------------------------------------------------------------- /projects/atft/src/lib/object/abstract-empty-directive.ts: -------------------------------------------------------------------------------- 1 | import { Directive } from '@angular/core'; 2 | import * as THREE from 'three'; 3 | import { AbstractObject3D } from './abstract-object-3d'; 4 | 5 | @Directive() 6 | export abstract class AbstractEmptyDirective extends AbstractObject3D { 7 | 8 | protected newObject3DInstance(): THREE.Object3D { 9 | return new THREE.Object3D(); 10 | } 11 | 12 | } 13 | -------------------------------------------------------------------------------- /projects/atft/src/lib/object/atft-object.module.ts: -------------------------------------------------------------------------------- 1 | import {NgModule} from '@angular/core'; 2 | import {AtftConnectorModule} from './connector/atft-connector.module'; 3 | import {AtftHelperModule} from './helper/atft-helper.module'; 4 | import {AtftLightModule} from './light/atft-light.module'; 5 | import {AtftLoaderModule} from './loader/atft-loader.module'; 6 | import {AtftMeshModule} from './mesh/atft-mesh.module'; 7 | import {AtftTextModule} from './text/atft-text.module'; 8 | import {CommonModule} from '@angular/common'; 9 | import {SceneComponent} from './scene.component'; 10 | import {ContentProjectionComponent} from './content-projection.component'; 11 | import {FontService, IconService, ModelService, ObjLoaderService, SvgLoaderService} from './loader'; 12 | 13 | 14 | @NgModule({ 15 | imports: [ 16 | CommonModule, 17 | AtftConnectorModule, 18 | AtftHelperModule, 19 | AtftLightModule, 20 | AtftLoaderModule, 21 | AtftMeshModule, 22 | AtftTextModule 23 | ], 24 | declarations: [ 25 | SceneComponent, 26 | ContentProjectionComponent 27 | ], 28 | exports: [ 29 | SceneComponent, 30 | AtftConnectorModule, 31 | AtftHelperModule, 32 | AtftLightModule, 33 | AtftLoaderModule, 34 | AtftMeshModule, 35 | AtftTextModule 36 | ], providers: [ 37 | FontService, 38 | SvgLoaderService, 39 | IconService, 40 | ObjLoaderService, 41 | ModelService 42 | ] 43 | }) 44 | export class AtftObjectModule { 45 | } 46 | -------------------------------------------------------------------------------- /projects/atft/src/lib/object/connector/abstract-connector.ts: -------------------------------------------------------------------------------- 1 | import {Directive, Input, OnDestroy} from '@angular/core'; 2 | import * as THREE from 'three'; 3 | import {AbstractObject3D} from '../abstract-object-3d'; 4 | import {Subscription} from 'rxjs'; 5 | 6 | @Directive() 7 | export abstract class AbstractConnector extends AbstractObject3D implements OnDestroy { 8 | 9 | @Input() 10 | source!: AbstractObject3D; 11 | 12 | @Input() 13 | target!: AbstractObject3D; 14 | 15 | protected sourceSub!: Subscription; 16 | protected targetSub!: Subscription; 17 | 18 | protected newObject3DInstance(): THREE.Object3D { 19 | const line = this.createLineMesh(); 20 | if (this.source && this.target) { 21 | this.watchObjects(); 22 | } 23 | return line; 24 | } 25 | 26 | private watchObjects() { 27 | this.sourceSub = this.source.changed.subscribe(() => { 28 | this.updateLineGeometry(); 29 | }); 30 | 31 | this.targetSub = this.target.changed.subscribe(() => { 32 | this.updateLineGeometry(); 33 | }); 34 | } 35 | 36 | public override ngOnDestroy() { 37 | super.ngOnDestroy(); 38 | 39 | this.sourceSub?.unsubscribe(); 40 | this.targetSub?.unsubscribe(); 41 | } 42 | 43 | /** 44 | * Create line mesh 45 | */ 46 | abstract createLineMesh(): T; 47 | 48 | /** 49 | * If at least one line end (source or target object) changed, then line geoetry should be updated as well 50 | * // TODO: Calculate only when source/target positions were changed 51 | */ 52 | abstract updateLineGeometry(): void; 53 | 54 | } 55 | -------------------------------------------------------------------------------- /projects/atft/src/lib/object/connector/atft-connector.module.ts: -------------------------------------------------------------------------------- 1 | import {NgModule} from '@angular/core'; 2 | import {CommonModule} from '@angular/common'; 3 | import {LineConnectorComponent} from './line-connector.component'; 4 | 5 | 6 | @NgModule({ 7 | declarations: [ 8 | LineConnectorComponent 9 | ], 10 | imports: [ 11 | CommonModule 12 | ], 13 | exports: [ 14 | LineConnectorComponent 15 | ] 16 | }) 17 | export class AtftConnectorModule { 18 | } 19 | -------------------------------------------------------------------------------- /projects/atft/src/lib/object/connector/index.ts: -------------------------------------------------------------------------------- 1 | export * from './atft-connector.module'; 2 | export * from './line-connector.component'; 3 | -------------------------------------------------------------------------------- /projects/atft/src/lib/object/content-projection.component.ts: -------------------------------------------------------------------------------- 1 | import {Component, Optional, SkipSelf, ViewChild} from '@angular/core'; 2 | import {EmptyComponent} from './helper/empty.component'; 3 | import {AbstractObject3D} from './abstract-object-3d'; 4 | import {RendererService} from '../renderer/renderer.service'; 5 | import {provideParent} from '../util'; 6 | 7 | @Component({ 8 | selector: 'atft-content-projection', 9 | providers: [provideParent(ContentProjectionComponent)], 10 | template: '' 11 | }) 12 | export class ContentProjectionComponent extends EmptyComponent { 13 | 14 | @ViewChild('contentProjection') contentProjection!: AbstractObject3D; 15 | 16 | constructor( 17 | protected override rendererService: RendererService, 18 | @SkipSelf() @Optional() protected override parent: AbstractObject3D 19 | ) { 20 | super(rendererService, parent); 21 | } 22 | 23 | override addChild(object: AbstractObject3D) { 24 | if (this.contentProjection) { 25 | this.contentProjection.addChild(object); 26 | } else { 27 | console.error('ContentProjectionComponent error: #contentProjection name not found! Embedded child object in "ng-content" can not be attached to parentScene object'); 28 | } 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /projects/atft/src/lib/object/helper/atft-helper.module.ts: -------------------------------------------------------------------------------- 1 | import {NgModule} from '@angular/core'; 2 | import {CommonModule} from '@angular/common'; 3 | import {AxesHelperComponent} from './axes-helper.component'; 4 | import {EmptyComponent} from './empty.component'; 5 | import {GridHelperComponent} from './grid-helper.component'; 6 | 7 | 8 | @NgModule({ 9 | declarations: [ 10 | AxesHelperComponent, 11 | EmptyComponent, 12 | GridHelperComponent 13 | ], 14 | imports: [ 15 | CommonModule 16 | ], 17 | exports: [ 18 | AxesHelperComponent, 19 | EmptyComponent, 20 | GridHelperComponent 21 | ] 22 | }) 23 | export class AtftHelperModule { 24 | } 25 | -------------------------------------------------------------------------------- /projects/atft/src/lib/object/helper/axes-helper.component.spec.ts: -------------------------------------------------------------------------------- 1 | import {RendererService} from '../../renderer/renderer.service'; 2 | import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing'; 3 | import {StatsService} from '../../stats'; 4 | import {AxesHelperComponent} from './axes-helper.component'; 5 | 6 | describe('helper', () => { 7 | describe('AxesHelperComponent', () => { 8 | let component: AxesHelperComponent; 9 | let fixture: ComponentFixture; 10 | 11 | beforeEach(waitForAsync(() => { 12 | TestBed.configureTestingModule({ 13 | declarations: [ 14 | AxesHelperComponent 15 | ], 16 | providers: [ 17 | StatsService, 18 | RendererService 19 | ] 20 | }); 21 | fixture = TestBed.createComponent(AxesHelperComponent); 22 | component = fixture.componentInstance; 23 | return TestBed.compileComponents(); 24 | })); 25 | 26 | it('init', () => { 27 | fixture.detectChanges(); 28 | expect(component).toBeTruthy(); 29 | }); 30 | 31 | }); 32 | }); 33 | -------------------------------------------------------------------------------- /projects/atft/src/lib/object/helper/axes-helper.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input, Optional, SkipSelf } from '@angular/core'; 2 | import * as THREE from 'three'; 3 | import { RendererService } from '../../renderer/renderer.service'; 4 | import { provideParent } from '../../util'; 5 | import { AbstractObject3D } from '../abstract-object-3d'; 6 | 7 | @Component({ 8 | selector: 'atft-axes-helper', 9 | providers: [provideParent(AxesHelperComponent)], 10 | template: '' 11 | }) 12 | export class AxesHelperComponent extends AbstractObject3D { 13 | 14 | @Input() size = 50; 15 | 16 | constructor( 17 | protected override rendererService: RendererService, 18 | @SkipSelf() @Optional() protected override parent: AbstractObject3D 19 | ) { 20 | super(rendererService, parent); 21 | } 22 | 23 | protected newObject3DInstance(): THREE.AxesHelper { 24 | // console.log('AxesHelperComponent.newObject3DInstance'); 25 | return new THREE.AxesHelper(this.size); 26 | } 27 | 28 | 29 | } 30 | -------------------------------------------------------------------------------- /projects/atft/src/lib/object/helper/empty.component.ts: -------------------------------------------------------------------------------- 1 | import { AfterViewInit, Component, Optional, SkipSelf } from '@angular/core'; 2 | import * as THREE from 'three'; 3 | import { RendererService } from '../../renderer/renderer.service'; 4 | import { provideParent } from '../../util'; 5 | import { AbstractObject3D } from '../abstract-object-3d'; 6 | 7 | @Component({ 8 | selector: 'atft-empty', 9 | providers: [provideParent(EmptyComponent)], 10 | template: '' 11 | }) 12 | export class EmptyComponent extends AbstractObject3D implements AfterViewInit { 13 | 14 | constructor( 15 | protected override rendererService: RendererService, 16 | @SkipSelf() @Optional() protected override parent: AbstractObject3D 17 | ) { 18 | super(rendererService, parent); 19 | } 20 | 21 | protected newObject3DInstance(): THREE.Object3D { 22 | return new THREE.Object3D(); 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /projects/atft/src/lib/object/helper/grid-helper.component.spec.ts: -------------------------------------------------------------------------------- 1 | import {RendererService} from '../../renderer/renderer.service'; 2 | import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing'; 3 | import {StatsService} from '../../stats'; 4 | import {GridHelperComponent} from './grid-helper.component'; 5 | 6 | describe('helper', () => { 7 | describe('GridHelperComponent', () => { 8 | let component: GridHelperComponent; 9 | let fixture: ComponentFixture; 10 | 11 | beforeEach(waitForAsync(() => { 12 | TestBed.configureTestingModule({ 13 | declarations: [ 14 | GridHelperComponent 15 | ], 16 | providers: [ 17 | StatsService, 18 | RendererService 19 | ] 20 | }); 21 | fixture = TestBed.createComponent(GridHelperComponent); 22 | component = fixture.componentInstance; 23 | return TestBed.compileComponents(); 24 | })); 25 | 26 | it('init', () => { 27 | fixture.detectChanges(); 28 | expect(component).toBeTruthy(); 29 | }); 30 | 31 | }); 32 | }); 33 | -------------------------------------------------------------------------------- /projects/atft/src/lib/object/helper/grid-helper.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input, Optional, SkipSelf } from '@angular/core'; 2 | import * as THREE from 'three'; 3 | import { RendererService } from '../../renderer/renderer.service'; 4 | import { provideParent } from '../../util'; 5 | import { AbstractObject3D } from '../abstract-object-3d'; 6 | 7 | @Component({ 8 | selector: 'atft-grid-helper', 9 | providers: [provideParent(GridHelperComponent)], 10 | template: '' 11 | }) 12 | export class GridHelperComponent extends AbstractObject3D { 13 | 14 | @Input() size!: number; 15 | @Input() divisions!: number; 16 | 17 | constructor( 18 | protected override rendererService: RendererService, 19 | @SkipSelf() @Optional() protected override parent: AbstractObject3D 20 | ) { 21 | super(rendererService, parent); 22 | } 23 | 24 | protected newObject3DInstance(): THREE.GridHelper { 25 | // console.log('GridHelperComponent.newObject3DInstance'); 26 | return new THREE.GridHelper(this.size, this.divisions); 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /projects/atft/src/lib/object/helper/index.ts: -------------------------------------------------------------------------------- 1 | export * from './atft-helper.module'; 2 | export * from './grid-helper.component'; 3 | export * from './axes-helper.component'; 4 | export * from './empty.component'; 5 | -------------------------------------------------------------------------------- /projects/atft/src/lib/object/index.ts: -------------------------------------------------------------------------------- 1 | export * from './atft-object.module'; 2 | 3 | export * from './abstract-object-3d'; 4 | export * from './abstract-empty-directive'; 5 | export * from './abstract-lazy-object-3d'; 6 | export * from './content-projection.component'; 7 | export * from './scene.component'; 8 | 9 | export * from './helper'; 10 | export * from './light'; 11 | export * from './loader'; 12 | export * from './mesh'; 13 | export * from './connector'; 14 | export * from './text'; 15 | -------------------------------------------------------------------------------- /projects/atft/src/lib/object/light/ambient-light.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input, Optional, SkipSelf } from '@angular/core'; 2 | import * as THREE from 'three'; 3 | import { RendererService } from '../../renderer/renderer.service'; 4 | import { provideParent } from '../../util'; 5 | import { AbstractObject3D } from '../abstract-object-3d'; 6 | 7 | @Component({ 8 | selector: 'atft-ambient-light', 9 | providers: [provideParent(AmbientLightComponent)], 10 | template: '' 11 | }) 12 | export class AmbientLightComponent extends AbstractObject3D { 13 | 14 | @Input() color: string | number = '#FFFFFF'; 15 | @Input() intensity = 0.8; 16 | 17 | constructor( 18 | protected override rendererService: RendererService, 19 | @SkipSelf() @Optional() protected override parent: AbstractObject3D 20 | ) { 21 | super(rendererService, parent); 22 | } 23 | 24 | protected newObject3DInstance() { 25 | const light = new THREE.AmbientLight(this.color); 26 | light.intensity = this.intensity; 27 | return light; 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /projects/atft/src/lib/object/light/atft-light.module.ts: -------------------------------------------------------------------------------- 1 | import {NgModule} from '@angular/core'; 2 | import {CommonModule} from '@angular/common'; 3 | import {PointLightComponent} from './point-light.component'; 4 | import {HemisphereLightComponent} from './hemisphere-light.component'; 5 | import {DirectionalLightComponent} from './directional-light.component'; 6 | import {AmbientLightComponent} from './ambient-light.component'; 7 | 8 | 9 | @NgModule({ 10 | declarations: [ 11 | DirectionalLightComponent, 12 | HemisphereLightComponent, 13 | PointLightComponent, 14 | AmbientLightComponent 15 | ], 16 | imports: [ 17 | CommonModule 18 | ], 19 | exports: [ 20 | DirectionalLightComponent, 21 | HemisphereLightComponent, 22 | PointLightComponent, 23 | AmbientLightComponent 24 | ] 25 | }) 26 | export class AtftLightModule { 27 | } 28 | -------------------------------------------------------------------------------- /projects/atft/src/lib/object/light/directional-light.component.spec.ts: -------------------------------------------------------------------------------- 1 | import {RendererService} from '../../renderer/renderer.service'; 2 | import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing'; 3 | import {DirectionalLightComponent} from './directional-light.component'; 4 | import {StatsService} from '../../stats'; 5 | 6 | describe('light', () => { 7 | describe('DirectionalLightComponent', () => { 8 | let component: DirectionalLightComponent; 9 | let fixture: ComponentFixture; 10 | 11 | beforeEach(waitForAsync(() => { 12 | TestBed.configureTestingModule({ 13 | declarations: [ 14 | DirectionalLightComponent 15 | ], 16 | providers: [ 17 | StatsService, 18 | RendererService 19 | ] 20 | }); 21 | fixture = TestBed.createComponent(DirectionalLightComponent); 22 | component = fixture.componentInstance; 23 | component.castShadow = true; 24 | return TestBed.compileComponents(); 25 | })); 26 | 27 | it('init', () => { 28 | fixture.detectChanges(); 29 | expect(component).toBeTruthy(); 30 | }); 31 | 32 | }); 33 | }); 34 | -------------------------------------------------------------------------------- /projects/atft/src/lib/object/light/directional-light.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input, Optional, SkipSelf } from '@angular/core'; 2 | import * as THREE from 'three'; 3 | import { RendererService } from '../../renderer/renderer.service'; 4 | import { provideParent } from '../../util'; 5 | import { AbstractObject3D } from '../abstract-object-3d'; 6 | 7 | @Component({ 8 | selector: 'atft-directional-light', 9 | providers: [provideParent(DirectionalLightComponent)], 10 | template: '' 11 | }) 12 | export class DirectionalLightComponent extends AbstractObject3D { 13 | 14 | @Input() color: string | number = '#FFFFFF'; 15 | @Input() intensity = 1; 16 | // by default, target is 0,0,0 17 | @Input() target = new THREE.Object3D(); 18 | @Input() castShadow = true; 19 | 20 | constructor( 21 | protected override rendererService: RendererService, 22 | @SkipSelf() @Optional() protected override parent: AbstractObject3D 23 | ) { 24 | super(rendererService, parent); 25 | } 26 | 27 | protected newObject3DInstance() { 28 | 29 | const light = new THREE.DirectionalLight( 30 | this.color, 31 | this.intensity 32 | ); 33 | 34 | light.target = this.target; 35 | 36 | if (this.castShadow === true) { 37 | light.castShadow = this.castShadow; 38 | // TODO: props 39 | light.shadow.camera.top = 100; 40 | light.shadow.camera.bottom = -100; 41 | light.shadow.camera.left = -100; 42 | light.shadow.camera.right = 100; 43 | light.shadow.camera.near = 0.1; 44 | light.shadow.camera.far = 500; 45 | light.shadow.mapSize.set(1024, 1024); 46 | light.shadow.bias = -0.001; 47 | 48 | } 49 | return light; 50 | 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /projects/atft/src/lib/object/light/hemisphere-light.component.spec.ts: -------------------------------------------------------------------------------- 1 | import {RendererService} from '../../renderer/renderer.service'; 2 | import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing'; 3 | import {HemisphereLightComponent} from './hemisphere-light.component'; 4 | import {StatsService} from '../../stats'; 5 | 6 | describe('light', () => { 7 | describe('HemisphereLightComponent', () => { 8 | let component: HemisphereLightComponent; 9 | let fixture: ComponentFixture; 10 | 11 | beforeEach(waitForAsync(() => { 12 | TestBed.configureTestingModule({ 13 | declarations: [ 14 | HemisphereLightComponent 15 | ], 16 | providers: [ 17 | StatsService, 18 | RendererService 19 | ] 20 | }); 21 | fixture = TestBed.createComponent(HemisphereLightComponent); 22 | component = fixture.componentInstance; 23 | return TestBed.compileComponents(); 24 | })); 25 | 26 | it('init', () => { 27 | fixture.detectChanges(); 28 | expect(component).toBeTruthy(); 29 | }); 30 | 31 | }); 32 | }); 33 | -------------------------------------------------------------------------------- /projects/atft/src/lib/object/light/hemisphere-light.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input, Optional, SkipSelf } from '@angular/core'; 2 | import * as THREE from 'three'; 3 | import { RendererService } from '../../renderer/renderer.service'; 4 | import { provideParent } from '../../util'; 5 | import { AbstractObject3D } from '../abstract-object-3d'; 6 | 7 | @Component({ 8 | selector: 'atft-hemisphere-light', 9 | providers: [provideParent(HemisphereLightComponent)], 10 | template: '' 11 | }) 12 | export class HemisphereLightComponent extends AbstractObject3D { 13 | 14 | @Input() skyColor: string | number = '#ffffff'; 15 | @Input() groundColor: string | number = '#444444'; 16 | @Input() intensity = 1; 17 | 18 | constructor( 19 | protected override rendererService: RendererService, 20 | @SkipSelf() @Optional() protected override parent: AbstractObject3D 21 | ) { 22 | super(rendererService, parent); 23 | } 24 | 25 | protected newObject3DInstance() { 26 | 27 | const light = new THREE.HemisphereLight( 28 | this.skyColor, 29 | this.groundColor, 30 | this.intensity 31 | ); 32 | 33 | return light; 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /projects/atft/src/lib/object/light/index.ts: -------------------------------------------------------------------------------- 1 | export * from './atft-light.module'; 2 | export * from './point-light.component'; 3 | export * from './directional-light.component'; 4 | export * from './hemisphere-light.component'; 5 | export * from './ambient-light.component'; 6 | -------------------------------------------------------------------------------- /projects/atft/src/lib/object/light/point-light.component.spec.ts: -------------------------------------------------------------------------------- 1 | import {RendererService} from '../../renderer/renderer.service'; 2 | import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing'; 3 | import {PointLightComponent} from './point-light.component'; 4 | import {StatsService} from '../../stats'; 5 | 6 | describe('light', () => { 7 | describe('PointLightComponent', () => { 8 | let component: PointLightComponent; 9 | let fixture: ComponentFixture; 10 | 11 | beforeEach(waitForAsync(() => { 12 | TestBed.configureTestingModule({ 13 | declarations: [ 14 | PointLightComponent 15 | ], 16 | providers: [ 17 | StatsService, 18 | RendererService 19 | ] 20 | }); 21 | fixture = TestBed.createComponent(PointLightComponent); 22 | component = fixture.componentInstance; 23 | component.castShadow = true; 24 | return TestBed.compileComponents(); 25 | })); 26 | 27 | it('init', () => { 28 | fixture.detectChanges(); 29 | expect(component).toBeTruthy(); 30 | }); 31 | 32 | }); 33 | }); 34 | -------------------------------------------------------------------------------- /projects/atft/src/lib/object/loader/abstract-model-loader.ts: -------------------------------------------------------------------------------- 1 | import {Directive, Input} from '@angular/core'; 2 | import {AbstractLazyObject3D} from '../abstract-lazy-object-3d'; 3 | 4 | /** 5 | * Helper parentScene class for model loader. 6 | * 7 | * @see ObjLoaderComponent 8 | */ 9 | @Directive() 10 | export abstract class AbstractModelLoader extends AbstractLazyObject3D { 11 | 12 | protected _model!: string; 13 | 14 | /** 15 | * The model data source (usually a URI). 16 | * Settings this property only hides the previous model upon successful 17 | * loading of the new one. This especially means that if the new data source 18 | * is invalid, the old model will *not* be removed from the scene. 19 | */ 20 | @Input() 21 | public set model(newModelUrl: string) { 22 | // console.log('AbstractModelLoader.model', newModelUrl); 23 | this._model = newModelUrl; 24 | if (this.object) { 25 | super.startLoading(); 26 | } 27 | } 28 | 29 | /** 30 | * The current model data source (usually a URI). 31 | */ 32 | public get model() { 33 | return this._model; 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /projects/atft/src/lib/object/loader/atft-loader.module.ts: -------------------------------------------------------------------------------- 1 | import {NgModule} from '@angular/core'; 2 | import {CommonModule} from '@angular/common'; 3 | import {ObjectLoaderComponent} from './object-loader.component'; 4 | import {ObjLoaderComponent} from './obj-loader.component'; 5 | import {SVGLoaderComponent} from './svg-loader.component'; 6 | import {StlLoaderComponent} from './stl-loader.component'; 7 | import {AudioLoaderComponent} from "./audio-loader.component"; 8 | 9 | 10 | @NgModule({ 11 | declarations: [ 12 | ObjLoaderComponent, 13 | ObjectLoaderComponent, 14 | SVGLoaderComponent, 15 | StlLoaderComponent, 16 | AudioLoaderComponent 17 | ], 18 | imports: [ 19 | CommonModule 20 | ], 21 | exports: [ 22 | ObjLoaderComponent, 23 | ObjectLoaderComponent, 24 | SVGLoaderComponent, 25 | StlLoaderComponent, 26 | AudioLoaderComponent 27 | ] 28 | }) 29 | export class AtftLoaderModule { 30 | } 31 | -------------------------------------------------------------------------------- /projects/atft/src/lib/object/loader/index.ts: -------------------------------------------------------------------------------- 1 | export * from './atft-loader.module'; 2 | export * from './obj-loader.component'; 3 | export * from './object-loader.component'; 4 | export * from './svg-loader.component'; 5 | export * from './stl-loader.component'; 6 | export * from './abstract-model-loader'; 7 | export * from './audio-loader.component'; 8 | export * from './services'; 9 | -------------------------------------------------------------------------------- /projects/atft/src/lib/object/loader/obj-loader.component.spec.ts: -------------------------------------------------------------------------------- 1 | import {RendererService} from '../../renderer/renderer.service'; 2 | import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing'; 3 | import {ObjLoaderComponent} from './obj-loader.component'; 4 | import {AtftLoaderModule} from './atft-loader.module'; 5 | import {StatsService} from '../../stats'; 6 | import {ObjLoaderService} from './services'; 7 | 8 | describe('loader', () => { 9 | describe('ObjLoaderComponent', () => { 10 | let component: ObjLoaderComponent; 11 | let fixture: ComponentFixture; 12 | 13 | beforeEach(waitForAsync(() => { 14 | TestBed.configureTestingModule({ 15 | imports: [ 16 | AtftLoaderModule 17 | ], 18 | providers: [ 19 | StatsService, 20 | RendererService, 21 | ObjLoaderService 22 | ] 23 | }); 24 | fixture = TestBed.createComponent(ObjLoaderComponent); 25 | return TestBed.compileComponents(); 26 | })); 27 | 28 | it('load', waitForAsync(() => { 29 | component = fixture.componentInstance; 30 | component.model = '/assets/model/smiley/smiley.obj'; 31 | component.material = '/assets/model/smiley/smiley.mtl'; 32 | fixture.detectChanges(); 33 | expect(component).toBeTruthy(); 34 | fixture.detectChanges(); 35 | })); 36 | 37 | }); 38 | }); 39 | -------------------------------------------------------------------------------- /projects/atft/src/lib/object/loader/object-loader.component.spec.ts: -------------------------------------------------------------------------------- 1 | import {RendererService} from '../../renderer/renderer.service'; 2 | import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing'; 3 | import {AtftLoaderModule} from './atft-loader.module'; 4 | import {ObjectLoaderComponent} from './object-loader.component'; 5 | import {StatsService} from '../../stats'; 6 | 7 | describe('loader', () => { 8 | describe('ObjectLoaderComponent', () => { 9 | let component: ObjectLoaderComponent; 10 | let fixture: ComponentFixture; 11 | 12 | beforeEach(waitForAsync(() => { 13 | TestBed.configureTestingModule({ 14 | imports: [ 15 | AtftLoaderModule 16 | ], 17 | providers: [ 18 | StatsService, 19 | RendererService 20 | ] 21 | }); 22 | fixture = TestBed.createComponent(ObjectLoaderComponent); 23 | return TestBed.compileComponents(); 24 | })); 25 | 26 | it('load', waitForAsync(() => { 27 | component = fixture.componentInstance; 28 | component.model = '/assets/model/Server.json'; 29 | fixture.detectChanges(); 30 | 31 | expect(component).toBeTruthy(); 32 | fixture.detectChanges(); 33 | })); 34 | 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /projects/atft/src/lib/object/loader/object-loader.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Optional, SkipSelf } from '@angular/core'; 2 | 3 | import * as THREE from 'three'; 4 | import { RendererService } from '../../renderer/renderer.service'; 5 | import { provideParent } from '../../util'; 6 | import { AbstractObject3D } from '../abstract-object-3d'; 7 | import { AbstractModelLoader } from './abstract-model-loader'; 8 | 9 | @Component({ 10 | selector: 'atft-object-loader', 11 | providers: [provideParent(ObjectLoaderComponent)], 12 | template: '' 13 | }) 14 | export class ObjectLoaderComponent extends AbstractModelLoader { 15 | private loader = new THREE.ObjectLoader(); 16 | 17 | constructor( 18 | protected override rendererService: RendererService, 19 | @SkipSelf() @Optional() protected override parent: AbstractObject3D 20 | ) { 21 | super(rendererService, parent); 22 | } 23 | 24 | protected async loadLazyObject() { 25 | // console.log('ObjectLoaderComponent.loadLazyObject'); 26 | return new Promise((resolve, reject) => { 27 | this.loader.load(this.model, model => { 28 | // BUG #95: it seems that some textures loaded after last render (and model has black texture instead) 29 | resolve(model); 30 | }, 31 | undefined, 32 | reject 33 | ); 34 | }); 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /projects/atft/src/lib/object/loader/services/abstract-asset.service.ts: -------------------------------------------------------------------------------- 1 | import {Injectable} from '@angular/core'; 2 | 3 | const SEPARATOR = ':'; 4 | const REPLACE_SYMBOL = '?'; 5 | 6 | export interface BaseAssetSource { 7 | url: string; 8 | } 9 | 10 | @Injectable() 11 | export abstract class AbstractAssetService { 12 | 13 | protected providers: Map = new Map(); 14 | abstract defaultProvider: string; 15 | 16 | constructor() { 17 | this.init(); 18 | } 19 | 20 | protected abstract init(): void; 21 | 22 | public registerProvider(key: string, source: T) { 23 | this.providers.set(key, source); 24 | } 25 | 26 | public setDefaultProvider(key: string) { 27 | this.defaultProvider = key; 28 | } 29 | 30 | public getSource(icon: string): T { 31 | if (icon) { 32 | if (icon.indexOf(SEPARATOR) > 0) { 33 | const args = icon.split(SEPARATOR); 34 | return this.getSourceByNamespace(args[0], args[1]); 35 | } else { 36 | return this.getSourceByNamespace(this.defaultProvider, icon); 37 | } 38 | } else { 39 | return this.defaultIfNotFound(icon); 40 | } 41 | } 42 | 43 | public getSourceByNamespace(namespace: string, icon: string): T { 44 | // console.log('AbstractAssetService.getUrlByNamespace', namespace + ', ' + icon); 45 | const provider = this.providers.get(namespace); 46 | if (!provider) { 47 | console.warn('Icon provider not found', provider); 48 | return this.defaultIfNotFound(icon); 49 | } 50 | const finalUrl = provider.url.replace(REPLACE_SYMBOL, icon); 51 | // console.log('AbstractAssetService.getUrlByNamespace url', svgUrl); 52 | return this.getFinalResult(finalUrl, provider); 53 | } 54 | 55 | public abstract getFinalResult(finalUrl: string, provider: T): T; 56 | 57 | public abstract defaultIfNotFound(icon: string): T; 58 | 59 | } 60 | -------------------------------------------------------------------------------- /projects/atft/src/lib/object/loader/services/abstract-cache.service.ts: -------------------------------------------------------------------------------- 1 | export abstract class AbstractCacheService { 2 | 3 | protected cache = new Map>(); 4 | 5 | public async load(key: string): Promise { 6 | const cacheHit = this.cache.get(key); 7 | if (await cacheHit) { 8 | // console.log('AbstractCacheService.cacheHit', key); 9 | if (cacheHit) { 10 | return cacheHit; 11 | } else { 12 | return Promise.reject("Failed to get value from cache") 13 | } 14 | } else { 15 | // console.log('AbstractCacheService.cacheMiss', key); 16 | const cacheMiss = this.getValue(key); 17 | this.cache.set(key, cacheMiss); 18 | return cacheMiss; 19 | } 20 | } 21 | 22 | protected abstract getValue(key: string): Promise; 23 | 24 | } 25 | -------------------------------------------------------------------------------- /projects/atft/src/lib/object/loader/services/font.service.ts: -------------------------------------------------------------------------------- 1 | import {Injectable} from '@angular/core'; 2 | import {AbstractCacheService} from './abstract-cache.service'; 3 | import {Font, FontLoader} from 'three/examples/jsm/loaders/FontLoader.js'; 4 | 5 | @Injectable() 6 | export class FontService extends AbstractCacheService { 7 | 8 | protected getValue(key: string): Promise { 9 | // console.log('FontService.getValue'); 10 | return new Promise(resolve => { 11 | const loader = new FontLoader(); 12 | loader.load(key, resolve); 13 | }); 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /projects/atft/src/lib/object/loader/services/index.ts: -------------------------------------------------------------------------------- 1 | export * from './font.service'; 2 | export * from './svg-loader.service'; 3 | export * from './icon.service'; 4 | export * from './obj-loader.service'; 5 | export * from './model.service'; 6 | -------------------------------------------------------------------------------- /projects/atft/src/lib/object/loader/services/model.service.ts: -------------------------------------------------------------------------------- 1 | import {Injectable} from '@angular/core'; 2 | import {AbstractAssetService, BaseAssetSource} from './abstract-asset.service'; 3 | 4 | 5 | @Injectable() 6 | export class ModelService extends AbstractAssetService { 7 | 8 | defaultProvider = '3d'; 9 | 10 | protected init() { 11 | this.registerProvider('3d', { 12 | url: 'https://raw.githubusercontent.com/makimenko/files/master/actor-models/?.obj' 13 | }); 14 | } 15 | 16 | defaultIfNotFound(icon: string): BaseAssetSource { 17 | return { 18 | url: icon 19 | }; 20 | } 21 | 22 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 23 | getFinalResult(finalUrl: string, provider: BaseAssetSource): BaseAssetSource { 24 | return { 25 | url: finalUrl 26 | }; 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /projects/atft/src/lib/object/loader/services/obj-loader.service.ts: -------------------------------------------------------------------------------- 1 | import {Injectable} from '@angular/core'; 2 | import {AbstractCacheService} from './abstract-cache.service'; 3 | import {OBJLoader} from 'three/examples/jsm/loaders/OBJLoader'; 4 | import * as THREE from 'three'; 5 | import {MTLLoader} from 'three/examples/jsm/loaders/MTLLoader'; 6 | 7 | 8 | @Injectable() 9 | export class ObjLoaderService extends AbstractCacheService { 10 | 11 | private loader = new OBJLoader(); 12 | 13 | protected getValue(key: string): Promise { 14 | // console.log('ObjLoaderService.getValue'); 15 | return new Promise((resolve, reject) => { 16 | this.loader.load(key, model => { 17 | resolve(model); 18 | }, undefined, reject); 19 | }); 20 | } 21 | 22 | public setMaterials(materialCreator: MTLLoader.MaterialCreator): void { 23 | this.loader.setMaterials(materialCreator); 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /projects/atft/src/lib/object/loader/services/svg-loader.service.ts: -------------------------------------------------------------------------------- 1 | import {Injectable} from '@angular/core'; 2 | import {ShapePath} from 'three'; 3 | import {AbstractCacheService} from './abstract-cache.service'; 4 | import {SVGLoader} from '../../../threejs-fork/SVGLoader'; 5 | 6 | 7 | @Injectable() 8 | export class SvgLoaderService extends AbstractCacheService { 9 | 10 | protected getValue(key: string): Promise { 11 | // console.log('SvgLoaderService.getValue'); 12 | return new Promise((resolve, reject) => { 13 | const loader = new SVGLoader(); 14 | // @ts-ignore 15 | loader.load(key, data => { 16 | resolve(data.paths); 17 | }, 18 | undefined, 19 | reject 20 | ); 21 | }); 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /projects/atft/src/lib/object/loader/stl-loader.component.spec.ts: -------------------------------------------------------------------------------- 1 | import {RendererService} from '../../renderer/renderer.service'; 2 | import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing'; 3 | import {IconService, SvgLoaderService} from './services'; 4 | import {SVGLoaderComponent} from './svg-loader.component'; 5 | import {StatsService} from '../../stats'; 6 | import {StlLoaderComponent} from './stl-loader.component'; 7 | 8 | describe('loader', () => { 9 | 10 | describe('StlLoaderComponent', () => { 11 | let component: StlLoaderComponent; 12 | let fixture: ComponentFixture; 13 | 14 | beforeEach(waitForAsync(() => { 15 | TestBed.configureTestingModule({ 16 | declarations: [ 17 | SVGLoaderComponent 18 | ], 19 | providers: [ 20 | StatsService, 21 | RendererService, 22 | SvgLoaderService, 23 | IconService 24 | ] 25 | }); 26 | fixture = TestBed.createComponent(StlLoaderComponent); 27 | return TestBed.compileComponents(); 28 | })); 29 | 30 | it('load', (() => { 31 | component = fixture.componentInstance; 32 | component.model = '/assets/model/Menger_sponge_sample.stl'; 33 | component.materialColor = '#ff0000'; 34 | fixture.detectChanges(); 35 | component.ngOnInit(); 36 | 37 | expect(component).toBeTruthy(); 38 | })); 39 | 40 | }); 41 | 42 | }); 43 | -------------------------------------------------------------------------------- /projects/atft/src/lib/object/loader/stl-loader.component.ts: -------------------------------------------------------------------------------- 1 | import {Component, Input, Optional, SkipSelf} from '@angular/core'; 2 | 3 | import * as THREE from 'three'; 4 | import {RendererService} from '../../renderer/renderer.service'; 5 | import {appliedMaterial, provideParent} from '../../util'; 6 | import {AbstractObject3D} from '../abstract-object-3d'; 7 | import {AbstractModelLoader} from './abstract-model-loader'; 8 | import {STLLoader} from 'three/examples/jsm/loaders/STLLoader'; 9 | 10 | @Component({ 11 | selector: 'atft-stl-loader', 12 | providers: [provideParent(StlLoaderComponent)], 13 | template: '' 14 | }) 15 | export class StlLoaderComponent extends AbstractModelLoader { 16 | private loader = new STLLoader(); 17 | 18 | @Input() 19 | material!: string; 20 | 21 | @Input() 22 | materialColor: string | number = '#FFFFFF'; 23 | 24 | @Input() 25 | depthWrite = true; 26 | 27 | constructor( 28 | protected override rendererService: RendererService, 29 | @SkipSelf() @Optional() protected override parent: AbstractObject3D 30 | ) { 31 | super(rendererService, parent); 32 | } 33 | 34 | protected async loadLazyObject() { 35 | // console.log('StlLoaderComponent.loadLazyObject'); 36 | return new Promise((resolve, reject) => { 37 | this.loader.load(this.model, geometry => { 38 | const material = appliedMaterial(this.materialColor, this.material, this.depthWrite); 39 | const mesh = new THREE.Mesh(geometry, material); 40 | resolve(mesh); 41 | }, 42 | undefined, 43 | reject 44 | ); 45 | }); 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /projects/atft/src/lib/object/mesh/abstract-mesh-3d.ts: -------------------------------------------------------------------------------- 1 | import { Input, OnChanges, SimpleChanges, Directive } from '@angular/core'; 2 | import * as THREE from 'three'; 3 | import {AbstractObject3D} from '../abstract-object-3d'; 4 | import {appliedMaterial} from '../../util'; 5 | 6 | @Directive() 7 | export abstract class AbstractMesh extends AbstractObject3D implements OnChanges { 8 | 9 | @Input() 10 | material!: string; 11 | 12 | @Input() 13 | materialColor: string | number = '#5DADE2'; 14 | 15 | @Input() 16 | castShadow = true; 17 | 18 | @Input() 19 | receiveShadow = true; 20 | 21 | @Input() 22 | depthWrite = true; 23 | 24 | protected getMaterial(): THREE.Material { 25 | return appliedMaterial(this.materialColor, this.material, this.depthWrite); 26 | } 27 | 28 | protected applyShadowProps(mesh: THREE.Mesh) { 29 | mesh.castShadow = this.castShadow; 30 | mesh.receiveShadow = this.receiveShadow; 31 | } 32 | 33 | public override ngOnChanges(changes: SimpleChanges) { 34 | super.ngOnChanges(changes); 35 | if (!this.getObject()) { 36 | return; 37 | } 38 | 39 | let mustRerender = false; 40 | if (['material', 'materialColor', 'depthWrite'].some(propName => propName in changes)) { 41 | this.applyMaterial(); 42 | mustRerender = true; 43 | } 44 | 45 | if (mustRerender) { 46 | this.rendererService.render(); 47 | } 48 | } 49 | 50 | public applyMaterial() { 51 | this.getObject().material = this.getMaterial(); 52 | } 53 | 54 | 55 | } 56 | -------------------------------------------------------------------------------- /projects/atft/src/lib/object/mesh/atft-mesh.module.ts: -------------------------------------------------------------------------------- 1 | import {NgModule} from '@angular/core'; 2 | import {CommonModule} from '@angular/common'; 3 | import {BoxMeshComponent} from './box-mesh.component'; 4 | import {CylinderMeshComponent} from './cylinder-mesh.component'; 5 | import {FrameMeshComponent} from './frame-mesh.component'; 6 | import {PlaneMeshComponent} from './plane-mesh.component'; 7 | import {SphereMeshComponent} from './sphere-mesh.component'; 8 | import {TorusMeshComponent} from './torus-mesh.component'; 9 | import {GridMeshComponent} from './grid-mesh.component'; 10 | import {VideoMeshComponent} from './video-mesh.component'; 11 | 12 | 13 | @NgModule({ 14 | declarations: [ 15 | BoxMeshComponent, 16 | CylinderMeshComponent, 17 | FrameMeshComponent, 18 | PlaneMeshComponent, 19 | SphereMeshComponent, 20 | TorusMeshComponent, 21 | GridMeshComponent, 22 | VideoMeshComponent 23 | ], 24 | imports: [ 25 | CommonModule 26 | ], 27 | exports: [ 28 | BoxMeshComponent, 29 | CylinderMeshComponent, 30 | FrameMeshComponent, 31 | PlaneMeshComponent, 32 | SphereMeshComponent, 33 | TorusMeshComponent, 34 | GridMeshComponent, 35 | VideoMeshComponent 36 | ] 37 | }) 38 | export class AtftMeshModule { 39 | } 40 | -------------------------------------------------------------------------------- /projects/atft/src/lib/object/mesh/box-mesh.component.spec.ts: -------------------------------------------------------------------------------- 1 | import {RendererService} from '../../renderer/renderer.service'; 2 | import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing'; 3 | import {BoxMeshComponent} from './box-mesh.component'; 4 | import {StatsService} from '../../stats'; 5 | 6 | describe('mesh', () => { 7 | describe('BoxMeshComponent', () => { 8 | let component: BoxMeshComponent; 9 | let fixture: ComponentFixture; 10 | 11 | beforeEach(waitForAsync(() => { 12 | TestBed.configureTestingModule({ 13 | declarations: [ 14 | BoxMeshComponent 15 | ], 16 | providers: [ 17 | StatsService, 18 | RendererService 19 | ] 20 | }); 21 | fixture = TestBed.createComponent(BoxMeshComponent); 22 | component = fixture.componentInstance; 23 | return TestBed.compileComponents(); 24 | })); 25 | 26 | it('should create an instance', () => { 27 | fixture.detectChanges(); 28 | expect(component).toBeTruthy(); 29 | }); 30 | 31 | }); 32 | }); 33 | -------------------------------------------------------------------------------- /projects/atft/src/lib/object/mesh/cylinder-mesh.component.spec.ts: -------------------------------------------------------------------------------- 1 | import {CylinderMeshComponent} from './cylinder-mesh.component'; 2 | import {RendererService} from '../../renderer/renderer.service'; 3 | import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing'; 4 | import {StatsService} from '../../stats'; 5 | 6 | describe('mesh', () => { 7 | describe('CylinderMeshComponent', () => { 8 | let component: CylinderMeshComponent; 9 | let fixture: ComponentFixture; 10 | 11 | beforeEach(waitForAsync(() => { 12 | TestBed.configureTestingModule({ 13 | declarations: [ 14 | CylinderMeshComponent 15 | ], 16 | providers: [ 17 | StatsService, 18 | RendererService 19 | ] 20 | }); 21 | fixture = TestBed.createComponent(CylinderMeshComponent); 22 | component = fixture.componentInstance; 23 | return TestBed.compileComponents(); 24 | })); 25 | 26 | it('should create an instance', () => { 27 | fixture.detectChanges(); 28 | expect(component).toBeTruthy(); 29 | }); 30 | 31 | }); 32 | }); 33 | -------------------------------------------------------------------------------- /projects/atft/src/lib/object/mesh/cylinder-mesh.component.ts: -------------------------------------------------------------------------------- 1 | import {Component, Input, Optional, SkipSelf} from '@angular/core'; 2 | import * as THREE from 'three'; 3 | import {provideParent} from '../../util'; 4 | import {AbstractMesh} from './abstract-mesh-3d'; 5 | import {AbstractObject3D} from '../abstract-object-3d'; 6 | import {RendererService} from '../../renderer/renderer.service'; 7 | 8 | @Component({ 9 | selector: 'atft-cylinder-mesh', 10 | providers: [provideParent(CylinderMeshComponent)], 11 | template: '' 12 | }) 13 | export class CylinderMeshComponent extends AbstractMesh { 14 | 15 | @Input() 16 | radiusTop = 1.0; 17 | @Input() 18 | radiusBottom = 1.0; 19 | @Input() 20 | height = 1.0; 21 | @Input() 22 | radialSegments = 8; 23 | @Input() 24 | heightSegments = 1; 25 | @Input() 26 | openEnded = false; 27 | @Input() 28 | thetaStart = 0.0; 29 | @Input() 30 | thetaLength = 2 * Math.PI; 31 | 32 | constructor( 33 | protected override rendererService: RendererService, 34 | @SkipSelf() @Optional() protected override parent: AbstractObject3D 35 | ) { 36 | super(rendererService, parent); 37 | // console.log('CylinderMeshComponent.constructor', parentScene); 38 | } 39 | 40 | protected newObject3DInstance(): THREE.Mesh { 41 | // console.log('CylinderMeshComponent.newObject3DInstance'); 42 | const geometry = new THREE.CylinderGeometry(this.radiusTop, this.radiusBottom, this.height, this.radialSegments, this.heightSegments, 43 | this.openEnded, this.thetaStart, this.thetaLength); 44 | const material = this.getMaterial(); 45 | const mesh = new THREE.Mesh(geometry, material); 46 | this.applyShadowProps(mesh); 47 | return mesh; 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /projects/atft/src/lib/object/mesh/frame-mesh.component.spec.ts: -------------------------------------------------------------------------------- 1 | import {RendererService} from '../../renderer/renderer.service'; 2 | import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing'; 3 | import {FrameMeshComponent} from './frame-mesh.component'; 4 | import {StatsService} from '../../stats'; 5 | 6 | describe('mesh', () => { 7 | describe('FrameMeshComponent', () => { 8 | let component: FrameMeshComponent; 9 | let fixture: ComponentFixture; 10 | 11 | beforeEach(waitForAsync(() => { 12 | TestBed.configureTestingModule({ 13 | declarations: [ 14 | FrameMeshComponent 15 | ], 16 | providers: [ 17 | StatsService, 18 | RendererService 19 | ] 20 | }); 21 | fixture = TestBed.createComponent(FrameMeshComponent); 22 | component = fixture.componentInstance; 23 | return TestBed.compileComponents(); 24 | })); 25 | 26 | it('should create an instance', () => { 27 | fixture.detectChanges(); 28 | expect(component).toBeTruthy(); 29 | }); 30 | 31 | }); 32 | }); 33 | -------------------------------------------------------------------------------- /projects/atft/src/lib/object/mesh/grid-mesh.component.spec.ts: -------------------------------------------------------------------------------- 1 | import {RendererService} from '../../renderer/renderer.service'; 2 | import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing'; 3 | import {StatsService} from '../../stats/stats.service'; 4 | import {GridMeshComponent} from './grid-mesh.component'; 5 | 6 | describe('mesh', () => { 7 | describe('GridMeshComponent', () => { 8 | let component: GridMeshComponent; 9 | let fixture: ComponentFixture; 10 | 11 | beforeEach(waitForAsync(() => { 12 | TestBed.configureTestingModule({ 13 | declarations: [ 14 | GridMeshComponent 15 | ], 16 | providers: [ 17 | StatsService, 18 | RendererService 19 | ] 20 | }); 21 | fixture = TestBed.createComponent(GridMeshComponent); 22 | component = fixture.componentInstance; 23 | return TestBed.compileComponents(); 24 | })); 25 | 26 | it('should create an instance', () => { 27 | fixture.detectChanges(); 28 | expect(component).toBeTruthy(); 29 | }); 30 | 31 | }); 32 | }); 33 | -------------------------------------------------------------------------------- /projects/atft/src/lib/object/mesh/index.ts: -------------------------------------------------------------------------------- 1 | export * from './atft-mesh.module'; 2 | export * from './abstract-mesh-3d'; 3 | export * from './cylinder-mesh.component'; 4 | export * from './sphere-mesh.component'; 5 | export * from './torus-mesh.component'; 6 | export * from './box-mesh.component'; 7 | export * from './plane-mesh.component'; 8 | export * from './frame-mesh.component'; 9 | export * from './grid-mesh.component'; 10 | export * from './video-mesh.component'; 11 | -------------------------------------------------------------------------------- /projects/atft/src/lib/object/mesh/plane-mesh.component.spec.ts: -------------------------------------------------------------------------------- 1 | import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing'; 2 | import {RendererService} from '../../renderer/renderer.service'; 3 | import {PlaneMeshComponent} from './plane-mesh.component'; 4 | import {StatsService} from '../../stats'; 5 | 6 | describe('mesh', () => { 7 | describe('PlaneMeshComponent', () => { 8 | let component: PlaneMeshComponent; 9 | let fixture: ComponentFixture; 10 | 11 | beforeEach(waitForAsync(() => { 12 | TestBed.configureTestingModule({ 13 | declarations: [ 14 | PlaneMeshComponent 15 | ], 16 | providers: [ 17 | StatsService, 18 | RendererService 19 | ] 20 | }); 21 | fixture = TestBed.createComponent(PlaneMeshComponent); 22 | component = fixture.componentInstance; 23 | return TestBed.compileComponents(); 24 | })); 25 | 26 | it('should create an instance', () => { 27 | fixture.detectChanges(); 28 | expect(component).toBeTruthy(); 29 | }); 30 | 31 | }); 32 | }); 33 | -------------------------------------------------------------------------------- /projects/atft/src/lib/object/mesh/sphere-mesh.component.spec.ts: -------------------------------------------------------------------------------- 1 | import {SphereMeshComponent} from './sphere-mesh.component'; 2 | import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing'; 3 | import {RendererService} from '../../renderer/renderer.service'; 4 | import {StatsService} from '../../stats'; 5 | 6 | describe('mesh', () => { 7 | describe('SphereMeshComponent', () => { 8 | let component: SphereMeshComponent; 9 | let fixture: ComponentFixture; 10 | 11 | beforeEach(waitForAsync(() => { 12 | TestBed.configureTestingModule({ 13 | declarations: [ 14 | SphereMeshComponent 15 | ], 16 | providers: [ 17 | StatsService, 18 | RendererService 19 | ] 20 | }); 21 | fixture = TestBed.createComponent(SphereMeshComponent); 22 | component = fixture.componentInstance; 23 | return TestBed.compileComponents(); 24 | })); 25 | 26 | it('should create an instance', () => { 27 | fixture.detectChanges(); 28 | expect(component).toBeTruthy(); 29 | }); 30 | 31 | }); 32 | }); 33 | -------------------------------------------------------------------------------- /projects/atft/src/lib/object/mesh/sphere-mesh.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input, Optional, SkipSelf } from '@angular/core'; 2 | import * as THREE from 'three'; 3 | import { RendererService } from '../../renderer/renderer.service'; 4 | import { provideParent } from '../../util'; 5 | import { AbstractObject3D } from '../abstract-object-3d'; 6 | import { AbstractMesh } from './abstract-mesh-3d'; 7 | 8 | @Component({ 9 | selector: 'atft-sphere-mesh', 10 | providers: [provideParent(SphereMeshComponent)], 11 | template: '' 12 | }) 13 | export class SphereMeshComponent extends AbstractMesh { 14 | 15 | @Input() radius!: number; 16 | @Input() widthSegments!: number; 17 | @Input() heightSegments!: number; 18 | 19 | constructor( 20 | protected override rendererService: RendererService, 21 | @SkipSelf() @Optional() protected override parent: AbstractObject3D 22 | ) { 23 | super(rendererService, parent); 24 | } 25 | 26 | protected newObject3DInstance(): THREE.Mesh { 27 | // console.log('SphereMeshComponent.newObject3DInstance'); 28 | const geometry = new THREE.SphereGeometry(this.radius, this.widthSegments, this.heightSegments); 29 | const material = this.getMaterial(); 30 | const mesh = new THREE.Mesh(geometry, material); 31 | this.applyShadowProps(mesh); 32 | return mesh; 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /projects/atft/src/lib/object/mesh/torus-mesh.component.spec.ts: -------------------------------------------------------------------------------- 1 | import {TorusMeshComponent} from './torus-mesh.component'; 2 | import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing'; 3 | import {RendererService} from '../../renderer/renderer.service'; 4 | import {StatsService} from '../../stats'; 5 | 6 | describe('mesh', () => { 7 | describe('TorusMeshComponent', () => { 8 | let component: TorusMeshComponent; 9 | let fixture: ComponentFixture; 10 | 11 | beforeEach(waitForAsync(() => { 12 | TestBed.configureTestingModule({ 13 | declarations: [ 14 | TorusMeshComponent 15 | ], 16 | providers: [ 17 | StatsService, 18 | RendererService 19 | ] 20 | }); 21 | fixture = TestBed.createComponent(TorusMeshComponent); 22 | component = fixture.componentInstance; 23 | return TestBed.compileComponents(); 24 | })); 25 | 26 | 27 | it('should create an instance', () => { 28 | fixture.detectChanges(); 29 | expect(component).toBeTruthy(); 30 | }); 31 | 32 | }); 33 | }); 34 | -------------------------------------------------------------------------------- /projects/atft/src/lib/object/mesh/torus-mesh.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input, Optional, SkipSelf } from '@angular/core'; 2 | import * as THREE from 'three'; 3 | import { RendererService } from '../../renderer/renderer.service'; 4 | import { provideParent } from '../../util'; 5 | import { AbstractObject3D } from '../abstract-object-3d'; 6 | import { AbstractMesh } from './abstract-mesh-3d'; 7 | 8 | @Component({ 9 | selector: 'atft-torus-mesh', 10 | providers: [provideParent(TorusMeshComponent)], 11 | template: '' 12 | }) 13 | export class TorusMeshComponent extends AbstractMesh { 14 | 15 | /** 16 | * Radius of the torus, from the center of the torus to the center of the tube 17 | */ 18 | @Input() radius = 0.4; 19 | 20 | /** 21 | * Radius of the tube. 22 | */ 23 | @Input() 24 | tube!: number; 25 | 26 | @Input() 27 | radialSegments = 8; 28 | 29 | @Input() 30 | tubularSegments = 6; 31 | 32 | @Input() 33 | arc: number = Math.PI * 2; 34 | 35 | constructor( 36 | protected override rendererService: RendererService, 37 | @SkipSelf() @Optional() protected override parent: AbstractObject3D 38 | ) { 39 | super(rendererService, parent); 40 | } 41 | 42 | protected newObject3DInstance(): THREE.Mesh { 43 | this.radius *= 1; 44 | this.tube *= 1; 45 | this.radialSegments *= 1; 46 | this.tubularSegments *= 1; 47 | 48 | const geometry = new THREE.TorusGeometry(this.radius, this.tube, 49 | this.radialSegments, this.tubularSegments); 50 | const material = this.getMaterial(); 51 | const mesh = new THREE.Mesh(geometry, material); 52 | this.applyShadowProps(mesh); 53 | return mesh; 54 | } 55 | 56 | } 57 | -------------------------------------------------------------------------------- /projects/atft/src/lib/object/scene.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input, OnChanges, Optional, SimpleChanges, SkipSelf } from '@angular/core'; 2 | import * as THREE from 'three'; 3 | import { RendererService } from '../renderer/renderer.service'; 4 | import { provideParent } from '../util'; 5 | import { AbstractObject3D } from './abstract-object-3d'; 6 | 7 | @Component({ 8 | selector: 'atft-scene', 9 | providers: [provideParent(SceneComponent)], 10 | template: '' 11 | }) 12 | export class SceneComponent extends AbstractObject3D implements OnChanges { 13 | 14 | @Input() background: number | string = '#ffffff'; 15 | 16 | constructor( 17 | protected override rendererService: RendererService, 18 | @SkipSelf() @Optional() protected override parent: AbstractObject3D 19 | ) { 20 | super(rendererService, parent); 21 | // TODO: directive? 22 | rendererService.setScene(this); 23 | } 24 | 25 | protected newObject3DInstance(): THREE.Scene { 26 | const scene = new THREE.Scene(); 27 | scene.background = new THREE.Color(this.background); 28 | return scene; 29 | } 30 | 31 | public override updateParent() { 32 | // No Parent for scene. Skip: super.updateParent(); 33 | } 34 | 35 | public override ngOnChanges(changes: SimpleChanges) { 36 | super.ngOnChanges(changes); 37 | if (!this.object) { 38 | return; 39 | } 40 | 41 | let modified = false; 42 | 43 | if (['background'].some(propName => propName in changes)) { 44 | this.getObject().background = new THREE.Color(this.background); 45 | modified = true; 46 | } 47 | 48 | if (modified) { 49 | this.rendererService.render(); 50 | } 51 | 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /projects/atft/src/lib/object/text/atft-text.module.ts: -------------------------------------------------------------------------------- 1 | import {NgModule} from '@angular/core'; 2 | import {CommonModule} from '@angular/common'; 3 | import {TextMeshComponent} from './text-mesh.component'; 4 | 5 | 6 | @NgModule({ 7 | declarations: [ 8 | TextMeshComponent 9 | ], 10 | imports: [ 11 | CommonModule 12 | ], 13 | exports: [ 14 | TextMeshComponent 15 | ] 16 | }) 17 | export class AtftTextModule { 18 | } 19 | -------------------------------------------------------------------------------- /projects/atft/src/lib/object/text/index.ts: -------------------------------------------------------------------------------- 1 | export * from './atft-text.module'; 2 | export * from './text-mesh.component'; 3 | -------------------------------------------------------------------------------- /projects/atft/src/lib/object/text/text-mesh.component.spec.ts: -------------------------------------------------------------------------------- 1 | import {RendererService} from '../../renderer/renderer.service'; 2 | import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing'; 3 | import { FontService } from '../loader/services'; 4 | import {TextMeshComponent} from './text-mesh.component'; 5 | import {StatsService} from '../../stats'; 6 | 7 | describe('text', () => { 8 | describe('TextMeshComponent', () => { 9 | let component: TextMeshComponent; 10 | let fixture: ComponentFixture; 11 | 12 | beforeEach(waitForAsync(() => { 13 | TestBed.configureTestingModule({ 14 | declarations: [ 15 | TextMeshComponent 16 | ], 17 | providers: [ 18 | StatsService, 19 | RendererService, 20 | FontService 21 | ] 22 | }); 23 | fixture = TestBed.createComponent(TextMeshComponent); 24 | component = fixture.componentInstance; 25 | component.castShadow = true; 26 | return TestBed.compileComponents(); 27 | })); 28 | 29 | it('init', () => { 30 | fixture.detectChanges(); 31 | expect(component).toBeTruthy(); 32 | }); 33 | 34 | it('text', () => { 35 | fixture.detectChanges(); 36 | expect(component.text).toBe('Text'); 37 | }); 38 | 39 | it('color', () => { 40 | fixture.detectChanges(); 41 | expect(component.materialColor).toBe('#DADADA'); 42 | }); 43 | 44 | }); 45 | }); 46 | -------------------------------------------------------------------------------- /projects/atft/src/lib/pipe/atft-pipe.module.ts: -------------------------------------------------------------------------------- 1 | import {NgModule} from '@angular/core'; 2 | import {Deg2RadPipe} from './deg2rad.pipe'; 3 | import {Rad2DegPipe} from './rad2deg.pipe'; 4 | import {CommonModule} from '@angular/common'; 5 | 6 | 7 | @NgModule({ 8 | imports: [ 9 | CommonModule 10 | ], 11 | declarations: [ 12 | Deg2RadPipe, 13 | Rad2DegPipe 14 | ], 15 | exports: [ 16 | Deg2RadPipe, 17 | Rad2DegPipe 18 | ] 19 | }) 20 | export class AtftPipeModule { 21 | } 22 | -------------------------------------------------------------------------------- /projects/atft/src/lib/pipe/deg2rad.pipe.ts: -------------------------------------------------------------------------------- 1 | import {Pipe, PipeTransform} from '@angular/core'; 2 | 3 | @Pipe({ 4 | name: 'deg2rad' 5 | }) 6 | export class Deg2RadPipe implements PipeTransform { 7 | 8 | /** 9 | * Converts degrees to radians 10 | * @param degree Degrees 11 | */ 12 | transform(degrees: number): number { 13 | return (degrees / 180) * Math.PI; 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /projects/atft/src/lib/pipe/index.ts: -------------------------------------------------------------------------------- 1 | export * from './atft-pipe.module'; 2 | 3 | export * from './deg2rad.pipe'; 4 | export * from './rad2deg.pipe'; 5 | -------------------------------------------------------------------------------- /projects/atft/src/lib/pipe/rad2deg.pipe.ts: -------------------------------------------------------------------------------- 1 | import {Pipe, PipeTransform} from '@angular/core'; 2 | 3 | @Pipe({ 4 | name: 'rad2deg' 5 | }) 6 | export class Rad2DegPipe implements PipeTransform { 7 | 8 | /** 9 | * Converts radians to degrees 10 | * @param radians Radians 11 | */ 12 | transform(radians: number): number { 13 | return radians * (180 / Math.PI); 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /projects/atft/src/lib/raycaster/atft-raycaster.module.ts: -------------------------------------------------------------------------------- 1 | import {NgModule} from '@angular/core'; 2 | import {CommonModule} from '@angular/common'; 3 | import {RaycasterGroupDirective} from './raycaster-group.directive'; 4 | import {RaycasterService} from './raycaster.service'; 5 | import {RaycasterCameraDirective} from './raycaster-camera.directive'; 6 | import {RaycasterEnableDirective} from './raycaster-enable.directive'; 7 | 8 | 9 | @NgModule({ 10 | imports: [ 11 | CommonModule 12 | ], 13 | declarations: [ 14 | RaycasterGroupDirective, 15 | RaycasterCameraDirective, 16 | RaycasterEnableDirective 17 | ], 18 | providers: [ 19 | RaycasterService 20 | ], 21 | exports: [ 22 | RaycasterGroupDirective, 23 | RaycasterCameraDirective, 24 | RaycasterEnableDirective 25 | ] 26 | }) 27 | export class AtftRaycasterModule { 28 | } 29 | -------------------------------------------------------------------------------- /projects/atft/src/lib/raycaster/index.ts: -------------------------------------------------------------------------------- 1 | export * from './atft-raycaster.module'; 2 | export * from './raycaster-group.directive'; 3 | export * from './raycaster-enable.directive'; 4 | export * from './raycaster-camera.directive'; 5 | export * from './raycaster.service'; 6 | export * from './raycaster-event'; 7 | -------------------------------------------------------------------------------- /projects/atft/src/lib/raycaster/raycaster-camera.directive.spec.ts: -------------------------------------------------------------------------------- 1 | import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing'; 2 | import {RendererService} from '../renderer/renderer.service'; 3 | import {Component} from '@angular/core'; 4 | import {AnimationService} from '../animation'; 5 | import {RaycasterCameraDirective} from '../raycaster/raycaster-camera.directive'; 6 | import {StatsService} from '../stats/stats.service'; 7 | import {RaycasterService} from './raycaster.service'; 8 | import {PerspectiveCameraComponent} from '../camera'; 9 | 10 | @Component({ 11 | selector: 'atft-mock', 12 | template: ` 13 | 14 | ` 15 | }) 16 | class MockComponent { 17 | 18 | } 19 | 20 | describe('raycaster', () => { 21 | 22 | describe('RaycasterCameraDirective', () => { 23 | let component: MockComponent; 24 | let fixture: ComponentFixture; 25 | 26 | beforeEach(waitForAsync(() => { 27 | TestBed.configureTestingModule({ 28 | declarations: [ 29 | PerspectiveCameraComponent, 30 | MockComponent, 31 | RaycasterCameraDirective 32 | ], 33 | providers: [ 34 | StatsService, 35 | RendererService, 36 | AnimationService, 37 | RaycasterService 38 | ] 39 | }); 40 | fixture = TestBed.createComponent(MockComponent); 41 | component = fixture.componentInstance; 42 | return TestBed.compileComponents(); 43 | })); 44 | 45 | it('init', () => { 46 | fixture.detectChanges(); 47 | expect(component).toBeTruthy(); 48 | }); 49 | }); 50 | 51 | }); 52 | -------------------------------------------------------------------------------- /projects/atft/src/lib/raycaster/raycaster-camera.directive.ts: -------------------------------------------------------------------------------- 1 | import {AfterViewInit, Directive} from '@angular/core'; 2 | import {AbstractCamera} from '../camera/abstract-camera'; 3 | import {RaycasterService} from './raycaster.service'; 4 | 5 | @Directive({selector: '[atft-raycaster-camera]'}) 6 | export class RaycasterCameraDirective implements AfterViewInit { 7 | 8 | constructor( 9 | private host: AbstractCamera, 10 | private raycasterService: RaycasterService 11 | ) { 12 | 13 | } 14 | 15 | ngAfterViewInit(): void { 16 | this.raycasterService.setCamera(this.host); 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /projects/atft/src/lib/raycaster/raycaster-enable.directive.spec.ts: -------------------------------------------------------------------------------- 1 | import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing'; 2 | import {RendererService} from '../renderer/renderer.service'; 3 | import {Component} from '@angular/core'; 4 | import {AnimationService} from '../animation'; 5 | import {StatsService} from '../stats/stats.service'; 6 | import {RaycasterService} from './raycaster.service'; 7 | import {PerspectiveCameraComponent} from '../camera'; 8 | import {RaycasterEnableDirective} from './raycaster-enable.directive'; 9 | 10 | @Component({ 11 | selector: 'atft-mock', 12 | template: ` 13 | 14 | ` 15 | }) 16 | class MockComponent { 17 | 18 | } 19 | 20 | describe('raycaster', () => { 21 | 22 | describe('RaycasterEnableDirective', () => { 23 | let component: MockComponent; 24 | let fixture: ComponentFixture; 25 | 26 | beforeEach(waitForAsync(() => { 27 | TestBed.configureTestingModule({ 28 | declarations: [ 29 | PerspectiveCameraComponent, 30 | MockComponent, 31 | RaycasterEnableDirective 32 | ], 33 | providers: [ 34 | StatsService, 35 | RendererService, 36 | AnimationService, 37 | RaycasterService 38 | ] 39 | }); 40 | fixture = TestBed.createComponent(MockComponent); 41 | component = fixture.componentInstance; 42 | return TestBed.compileComponents(); 43 | })); 44 | 45 | it('init', () => { 46 | fixture.detectChanges(); 47 | expect(component).toBeTruthy(); 48 | }); 49 | }); 50 | 51 | }); 52 | -------------------------------------------------------------------------------- /projects/atft/src/lib/raycaster/raycaster-enable.directive.ts: -------------------------------------------------------------------------------- 1 | import {AfterViewInit, Directive} from '@angular/core'; 2 | import {RaycasterService} from './raycaster.service'; 3 | 4 | @Directive({selector: '[atft-raycaster-enable]'}) 5 | export class RaycasterEnableDirective implements AfterViewInit { 6 | 7 | constructor( 8 | private raycasterService: RaycasterService 9 | ) { 10 | 11 | } 12 | 13 | ngAfterViewInit(): void { 14 | this.raycasterService.enable(); 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /projects/atft/src/lib/raycaster/raycaster-event.ts: -------------------------------------------------------------------------------- 1 | export enum RaycasterEvent { 2 | mouseEnter = 'mouseEnter', 3 | mouseExit = 'mouseExit', 4 | click = 'click' 5 | } 6 | -------------------------------------------------------------------------------- /projects/atft/src/lib/raycaster/raycaster-group.directive.spec.ts: -------------------------------------------------------------------------------- 1 | import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing'; 2 | import {RendererService} from '../renderer/renderer.service'; 3 | import {Component} from '@angular/core'; 4 | import {AnimationService} from '../animation'; 5 | import {StatsService} from '../stats/stats.service'; 6 | import {RaycasterService} from './raycaster.service'; 7 | import {EmptyComponent} from '../object/helper'; 8 | import {RaycasterGroupDirective} from './raycaster-group.directive'; 9 | 10 | @Component({ 11 | selector: 'atft-mock', 12 | template: ` 13 | 14 | ` 15 | }) 16 | class MockComponent { 17 | 18 | } 19 | 20 | describe('raycaster', () => { 21 | 22 | describe('RaycasterEnableDirective', () => { 23 | let component: MockComponent; 24 | let fixture: ComponentFixture; 25 | 26 | beforeEach(waitForAsync(() => { 27 | TestBed.configureTestingModule({ 28 | declarations: [ 29 | EmptyComponent, 30 | MockComponent, 31 | RaycasterGroupDirective 32 | ], 33 | providers: [ 34 | StatsService, 35 | RendererService, 36 | AnimationService, 37 | RaycasterService 38 | ] 39 | }); 40 | fixture = TestBed.createComponent(MockComponent); 41 | component = fixture.componentInstance; 42 | return TestBed.compileComponents(); 43 | })); 44 | 45 | it('init', () => { 46 | fixture.detectChanges(); 47 | expect(component).toBeTruthy(); 48 | }); 49 | }); 50 | 51 | }); 52 | -------------------------------------------------------------------------------- /projects/atft/src/lib/renderer/atft-renderer.module.ts: -------------------------------------------------------------------------------- 1 | import {NgModule} from '@angular/core'; 2 | import {RendererCanvasComponent} from './renderer-canvas.component'; 3 | import {CommonModule} from '@angular/common'; 4 | import {RendererService} from './renderer.service'; 5 | import {BloomService} from './bloom.service'; 6 | 7 | 8 | @NgModule({ 9 | imports: [ 10 | CommonModule 11 | ], 12 | declarations: [ 13 | RendererCanvasComponent 14 | ], 15 | providers: [ 16 | RendererService, 17 | BloomService 18 | ], 19 | exports: [ 20 | RendererCanvasComponent 21 | ] 22 | }) 23 | export class AtftRendererModule { 24 | } 25 | -------------------------------------------------------------------------------- /projects/atft/src/lib/renderer/index.ts: -------------------------------------------------------------------------------- 1 | export * from './atft-renderer.module'; 2 | export * from './renderer-canvas.component'; 3 | export * from './renderer.service'; 4 | -------------------------------------------------------------------------------- /projects/atft/src/lib/renderer/renderer-canvas.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /projects/atft/src/lib/renderer/renderer-canvas.component.scss: -------------------------------------------------------------------------------- 1 | :host { 2 | display: flex; 3 | flex: 1; 4 | height: 100%; 5 | } 6 | 7 | canvas { 8 | flex: 1; 9 | outline: none; 10 | } 11 | -------------------------------------------------------------------------------- /projects/atft/src/lib/stats/index.ts: -------------------------------------------------------------------------------- 1 | export * from './stats.module'; 2 | export * from './stats.service'; 3 | export * from './stats-auto-show.directive'; 4 | -------------------------------------------------------------------------------- /projects/atft/src/lib/stats/stats-auto-show.directive.ts: -------------------------------------------------------------------------------- 1 | import {AfterViewInit, Directive} from '@angular/core'; 2 | import {StatsService} from './stats.service'; 3 | 4 | @Directive({selector: '[atft-stats-auto-show]'}) 5 | export class StatsAutoShowDirective implements AfterViewInit { 6 | 7 | constructor( 8 | private statsService: StatsService 9 | ) { 10 | 11 | } 12 | 13 | ngAfterViewInit(): void { 14 | this.statsService.create(); 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /projects/atft/src/lib/stats/stats.module.ts: -------------------------------------------------------------------------------- 1 | import {NgModule} from '@angular/core'; 2 | import {CommonModule} from '@angular/common'; 3 | import {StatsService} from './stats.service'; 4 | import {StatsAutoShowDirective} from './stats-auto-show.directive'; 5 | 6 | 7 | @NgModule({ 8 | imports: [ 9 | CommonModule 10 | ], 11 | providers: [ 12 | StatsService 13 | ], 14 | declarations: [ 15 | StatsAutoShowDirective 16 | ], 17 | exports: [ 18 | StatsAutoShowDirective 19 | ] 20 | }) 21 | export class AtftStatsModule { 22 | } 23 | -------------------------------------------------------------------------------- /projects/atft/src/lib/stats/stats.service.spec.ts: -------------------------------------------------------------------------------- 1 | import {StatsService} from '../stats'; 2 | 3 | describe('stats', () => { 4 | describe('StatsService', () => { 5 | 6 | 7 | it('create and remove', () => { 8 | const stats = new StatsService(); 9 | 10 | const beforeCount = document.body.childElementCount; 11 | stats.create(); 12 | expect(beforeCount + 1 === document.body.childElementCount).toBeTruthy(); 13 | 14 | stats.update(); 15 | 16 | stats.ngOnDestroy(); 17 | expect(beforeCount === document.body.childElementCount).toBeTruthy(); 18 | 19 | }); 20 | 21 | it('toggle', () => { 22 | const stats = new StatsService(); 23 | 24 | const beforeCount = document.body.childElementCount; 25 | stats.toggle(); 26 | expect(beforeCount + 1 === document.body.childElementCount).toBeTruthy(); 27 | 28 | stats.toggle(); 29 | expect(beforeCount === document.body.childElementCount).toBeTruthy(); 30 | }); 31 | 32 | it('event', () => { 33 | const stats = new StatsService(); 34 | stats.toggle(); 35 | 36 | const beforeCount = document.body.childElementCount; 37 | 38 | const eventAltS = new KeyboardEvent('keydown', {altKey: true, key: 's'}); 39 | document.body.dispatchEvent(eventAltS); 40 | expect(document.body.childElementCount).toBeGreaterThan(beforeCount); 41 | 42 | document.body.dispatchEvent(eventAltS); 43 | expect(beforeCount === document.body.childElementCount).toBeTruthy(); 44 | }); 45 | 46 | }); 47 | 48 | }); 49 | -------------------------------------------------------------------------------- /projects/atft/src/lib/stats/stats.service.ts: -------------------------------------------------------------------------------- 1 | import {Injectable, OnDestroy} from '@angular/core'; 2 | import Stats from 'three/examples/jsm/libs/stats.module.js'; 3 | 4 | 5 | @Injectable() 6 | export class StatsService implements OnDestroy { 7 | 8 | private stats?: Stats; 9 | 10 | constructor() { 11 | document.body.addEventListener('keydown', event => { 12 | if (event.altKey && event.key === 's') { 13 | this.toggle(); 14 | } 15 | }); 16 | } 17 | 18 | public update() { 19 | if (this.stats) { 20 | this.stats.update(); 21 | } 22 | } 23 | 24 | 25 | public toggle() { 26 | (this.stats ? this.remove() : this.create()); 27 | } 28 | 29 | public create() { 30 | if (!this.stats) { 31 | this.stats = new Stats(); 32 | document.body.appendChild(this.stats.dom); 33 | } 34 | } 35 | 36 | public remove() { 37 | if (this.stats) { 38 | this.stats.dom.remove(); 39 | // @ts-ignore 40 | this.stats.dom = undefined; 41 | this.stats = undefined; 42 | } 43 | } 44 | 45 | ngOnDestroy(): void { 46 | this.remove(); 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /projects/atft/src/lib/threejs-fork/index.ts: -------------------------------------------------------------------------------- 1 | export * from './SVGLoader'; 2 | -------------------------------------------------------------------------------- /projects/atft/src/lib/util/applied-material.spec.ts: -------------------------------------------------------------------------------- 1 | import {appliedMaterial} from './applied-material'; 2 | import * as THREE from 'three'; 3 | 4 | describe('utils', () => { 5 | describe('appliedMaterial', () => { 6 | 7 | it('default', () => { 8 | const material = appliedMaterial(0xff0000); 9 | expect(material.constructor).toBe(THREE.MeshPhongMaterial); 10 | expect(material.depthWrite).toBeTruthy(); 11 | }); 12 | 13 | it('basic', () => { 14 | const material = appliedMaterial(0xff0000, 'basic'); 15 | expect(material.constructor).toBe(THREE.MeshBasicMaterial); 16 | }); 17 | 18 | it('lambert', () => { 19 | const material = appliedMaterial(0xff0000, 'lamb', true); 20 | expect(material.constructor).toBe(THREE.MeshLambertMaterial); 21 | expect(material.depthWrite).toBeTruthy(); 22 | }); 23 | 24 | it('lambert depth', () => { 25 | const material = appliedMaterial(0xff0000, 'lamb', false); 26 | expect(material.constructor).toBe(THREE.MeshLambertMaterial); 27 | expect(material.depthWrite).toBeFalsy(); 28 | }); 29 | 30 | it('Color object', () => { 31 | const material = appliedMaterial(new THREE.Color(1, 0, 0)); 32 | expect(material.constructor).toBe(THREE.MeshPhongMaterial); 33 | }); 34 | 35 | it('Color hex string', () => { 36 | const material = appliedMaterial('#ff0000'); 37 | expect(material.constructor).toBe(THREE.MeshPhongMaterial); 38 | expect(material.depthWrite).toBeTruthy(); 39 | }); 40 | 41 | }); 42 | 43 | }); 44 | -------------------------------------------------------------------------------- /projects/atft/src/lib/util/applied-material.ts: -------------------------------------------------------------------------------- 1 | import * as THREE from 'three'; 2 | 3 | /** 4 | * Creates material 5 | * 6 | * @param materialColor color 7 | * @param materialName material label as string 8 | * @param depthWrite enable depth write 9 | */ 10 | export function appliedMaterial(materialColor: string | number | THREE.Color, materialName?: string, depthWrite?: boolean): THREE.Material { 11 | 12 | const props = { 13 | color: (materialColor instanceof THREE.Color ? materialColor : new THREE.Color(materialColor)), 14 | side: THREE.DoubleSide, 15 | depthWrite: (depthWrite !== undefined ? depthWrite : true) 16 | }; 17 | 18 | if (materialName === 'lamb') { 19 | return new THREE.MeshLambertMaterial(props); 20 | } else if (materialName === 'basic') { 21 | return new THREE.MeshBasicMaterial(props); 22 | } else { 23 | return new THREE.MeshPhongMaterial(props); 24 | } 25 | 26 | } 27 | 28 | 29 | -------------------------------------------------------------------------------- /projects/atft/src/lib/util/calculate-size.spec.ts: -------------------------------------------------------------------------------- 1 | import {calculateSize} from './calculate-size'; 2 | import * as THREE from 'three'; 3 | 4 | describe('utils', () => { 5 | describe('calculateSize', () => { 6 | 7 | it('scaled box', () => { 8 | const geometry = new THREE.BoxGeometry(2, 3, 4); 9 | const obj = new THREE.Mesh(geometry); 10 | obj.scale.x = 2; 11 | obj.scale.y = 3; 12 | obj.scale.z = 4; 13 | 14 | const vector = calculateSize(obj); 15 | expect(vector.x).toEqual(2 * 2); 16 | expect(vector.y).toEqual(3 * 3); 17 | expect(vector.z).toEqual(4 * 4); 18 | }); 19 | 20 | 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /projects/atft/src/lib/util/calculate-size.ts: -------------------------------------------------------------------------------- 1 | import * as THREE from 'three'; 2 | 3 | export function calculateSize(group: THREE.Object3D): THREE.Vector3 { 4 | const box = new THREE.Box3().setFromObject(group); 5 | return new THREE.Vector3( 6 | box.max.x - box.min.x, 7 | box.max.y - box.min.y, 8 | box.max.z - box.min.z 9 | ); 10 | } 11 | -------------------------------------------------------------------------------- /projects/atft/src/lib/util/fix-center.spec.ts: -------------------------------------------------------------------------------- 1 | import * as THREE from 'three'; 2 | import {fixCenter} from './fix-center'; 3 | 4 | describe('utils', () => { 5 | describe('fixCenter', () => { 6 | 7 | it('simple', () => { 8 | const geometry = new THREE.BoxGeometry(4, 1, 2); 9 | const obj = new THREE.Mesh(geometry); 10 | fixCenter(obj); 11 | 12 | expect(obj.position.x).toEqual(0); 13 | expect(obj.position.y).toEqual(0); 14 | expect(obj.position.z).toEqual(0); 15 | }); 16 | 17 | it('group', () => { 18 | const geometry = new THREE.BoxGeometry(1, 1, 1); 19 | const group = new THREE.Group(); 20 | const obj = new THREE.Mesh(geometry); 21 | obj.position.x = 2; 22 | obj.position.y = -2; 23 | obj.position.z = 0; 24 | group.add(obj); 25 | fixCenter(group); 26 | 27 | expect(obj.position.x).toEqual(2); 28 | expect(obj.position.y).toEqual(-2); 29 | expect(obj.position.z).toEqual(0); 30 | }); 31 | 32 | 33 | }); 34 | }); 35 | -------------------------------------------------------------------------------- /projects/atft/src/lib/util/fix-center.ts: -------------------------------------------------------------------------------- 1 | import * as THREE from 'three'; 2 | 3 | export function fixCenter(group: THREE.Object3D) { 4 | const box = new THREE.Box3().setFromObject(group); 5 | // console.log('box', box); 6 | group.translateX(-((box.max.x - box.min.x) / 2) - box.min.x); 7 | group.translateY(-((box.max.y - box.min.y) / 2) - box.min.y); 8 | group.translateZ(-((box.max.z - box.min.z) / 2) - box.min.z); 9 | } 10 | -------------------------------------------------------------------------------- /projects/atft/src/lib/util/index.ts: -------------------------------------------------------------------------------- 1 | export * from './applied-material'; 2 | export * from './calculate-size'; 3 | export * from './fix-center'; 4 | export * from './scale-to-fit'; 5 | export * from './provide-parent'; 6 | -------------------------------------------------------------------------------- /projects/atft/src/lib/util/provide-parent.ts: -------------------------------------------------------------------------------- 1 | // https://angular.io/guide/dependency-injection-navtree 2 | // Helper method to provide the current component instance in the name of a `parentType`. 3 | // The `parentType` defaults to `Parent` when omitting the second parameter. 4 | import { forwardRef } from '@angular/core'; 5 | import { AbstractObject3D } from '../object/abstract-object-3d'; 6 | 7 | export function provideParent(component: any, parentType?: any) { 8 | return { provide: parentType || AbstractObject3D, useExisting: forwardRef(() => component) }; 9 | } 10 | -------------------------------------------------------------------------------- /projects/atft/src/lib/util/removeFromArray.ts: -------------------------------------------------------------------------------- 1 | export function removeFromArray(array: Array, element: T): void { 2 | const index = array.indexOf(element, 0); 3 | if (index > -1) { 4 | array.splice(index, 1); 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /projects/atft/src/lib/util/scale-to-fit.spec.ts: -------------------------------------------------------------------------------- 1 | import * as THREE from 'three'; 2 | import {scaleToFit} from './scale-to-fit'; 3 | 4 | describe('utils', () => { 5 | describe('scaleToFit', () => { 6 | 7 | it('simple', () => { 8 | const geometry = new THREE.BoxGeometry(10, 20, 30); 9 | const obj = new THREE.Mesh(geometry); 10 | 11 | const max = new THREE.Vector3(20, 10, 30); 12 | scaleToFit(obj, max); 13 | 14 | expect(obj.scale.x).toEqual(1); 15 | expect(obj.scale.y).toEqual(0.5); 16 | expect(obj.scale.z).toEqual(1); 17 | }); 18 | 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /projects/atft/src/lib/util/scale-to-fit.ts: -------------------------------------------------------------------------------- 1 | import * as THREE from 'three'; 2 | import {calculateSize} from './calculate-size'; 3 | 4 | export function scaleToFit(group: THREE.Object3D, max: THREE.Vector3): void { 5 | const box = calculateSize(group); 6 | 7 | const scaleX = max.x / box.x; 8 | const scaleY = max.y / box.y; 9 | const scaleZ = max.z / box.z; 10 | 11 | group.scale.set( 12 | (scaleX < 1 ? scaleX : 1), 13 | (scaleY < 1 ? scaleY : 1), 14 | (scaleZ < 1 ? scaleZ : 1) 15 | ); 16 | 17 | } 18 | -------------------------------------------------------------------------------- /projects/atft/src/public_api.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Public API Surface of atft 3 | */ 4 | 5 | export * from './lib/atft.module'; 6 | export * from './lib/camera'; 7 | export * from './lib/control'; 8 | export * from './lib/object'; 9 | export * from './lib/pipe'; 10 | export * from './lib/renderer'; 11 | export * from './lib/util'; 12 | export * from './lib/animation'; 13 | export * from './lib/raycaster'; 14 | export * from './lib/stats'; 15 | export * from './lib/effect'; 16 | export * from './lib/threejs-fork'; 17 | 18 | export * from './lib/actor/data-center/'; 19 | export * from './lib/actor/ux/'; 20 | -------------------------------------------------------------------------------- /projects/atft/src/test.ts: -------------------------------------------------------------------------------- 1 | // This file is required by karma.conf.js and loads recursively all the .spec and framework files 2 | 3 | import 'core-js/es7/reflect'; 4 | import 'zone.js/dist/zone'; 5 | import 'zone.js/dist/zone-testing'; 6 | import {getTestBed} from '@angular/core/testing'; 7 | import {BrowserDynamicTestingModule, platformBrowserDynamicTesting} from '@angular/platform-browser-dynamic/testing'; 8 | 9 | declare const require: any; 10 | 11 | // First, initialize the Angular testing environment. 12 | getTestBed().initTestEnvironment( 13 | BrowserDynamicTestingModule, 14 | platformBrowserDynamicTesting() 15 | ); 16 | // Then we find all the tests. 17 | const context = require.context('./', true, /\.ts$/); 18 | // And load the modules. 19 | context.keys().map(context); 20 | -------------------------------------------------------------------------------- /projects/atft/tsconfig.lib.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "extends": "../../tsconfig.json", 4 | "compilerOptions": { 5 | "outDir": "../../out-tsc/lib", 6 | "declaration": true, 7 | "declarationMap": true, 8 | "inlineSources": true, 9 | "types": [] 10 | }, 11 | "exclude": [ 12 | "**/*.spec.ts" 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /projects/atft/tsconfig.lib.prod.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "extends": "./tsconfig.lib.json", 4 | "compilerOptions": { 5 | "declarationMap": false 6 | }, 7 | "angularCompilerOptions": { 8 | "compilationMode": "partial" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /projects/atft/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "extends": "../../tsconfig.json", 4 | "compilerOptions": { 5 | "outDir": "../../out-tsc/spec", 6 | "types": [ 7 | "jasmine" 8 | ] 9 | }, 10 | "include": [ 11 | "**/*.spec.ts", 12 | "**/*.ts" 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /src/app/app-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { RouterModule, Routes } from '@angular/router'; 3 | 4 | const routes: Routes = []; 5 | 6 | @NgModule({ 7 | imports: [RouterModule.forRoot(routes)], 8 | exports: [RouterModule] 9 | }) 10 | export class AppRoutingModule { } 11 | -------------------------------------------------------------------------------- /src/app/app.component.scss: -------------------------------------------------------------------------------- 1 | :host { 2 | width: 100%; 3 | height: 100%; 4 | max-height: 100%; 5 | display: flex; 6 | } 7 | -------------------------------------------------------------------------------- /src/app/app.component.spec.ts: -------------------------------------------------------------------------------- 1 | import {TestBed, waitForAsync} from '@angular/core/testing'; 2 | import {AppComponent} from './app.component'; 3 | 4 | import {FormsModule} from '@angular/forms'; 5 | import {AtftModule} from 'atft'; 6 | 7 | describe('AppComponent', () => { 8 | beforeEach(waitForAsync(() => { 9 | TestBed.configureTestingModule({ 10 | declarations: [ 11 | AppComponent 12 | ], 13 | imports: [ 14 | FormsModule, 15 | AtftModule 16 | ] 17 | }).compileComponents(); 18 | })); 19 | it('should create the app', waitForAsync(() => { 20 | const fixture = TestBed.createComponent(AppComponent); 21 | const app = fixture.debugElement.componentInstance; 22 | expect(app).toBeTruthy(); 23 | })); 24 | it(`should have as title 'app'`, waitForAsync(() => { 25 | const fixture = TestBed.createComponent(AppComponent); 26 | const app = fixture.debugElement.componentInstance; 27 | expect(app.title).toEqual('app'); 28 | })); 29 | it('should create element', waitForAsync(() => { 30 | const fixture = TestBed.createComponent(AppComponent); 31 | fixture.detectChanges(); 32 | const compiled = fixture.debugElement.nativeElement; 33 | expect(compiled.querySelector('canvas')).not.toBeNull('No rendered'); 34 | })); 35 | }); 36 | -------------------------------------------------------------------------------- /src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-root', 5 | templateUrl: './app.component.html', 6 | styleUrls: ['./app.component.scss'] 7 | }) 8 | export class AppComponent { 9 | title = 'app'; 10 | 11 | public rotationX = 0.0; 12 | public rotationY = 0.0; 13 | public rotationZ = 0.0; 14 | 15 | public translationY = 0.0; 16 | 17 | public cameraPositionX = 20.0; 18 | 19 | public someColor = 0x0000ff; 20 | 21 | public mouseEnter() { 22 | console.log('mouseEnter'); 23 | } 24 | 25 | public click() { 26 | console.log('click'); 27 | } 28 | 29 | public mouseExit() { 30 | console.log('mouseExit'); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/app/app.module.ts: -------------------------------------------------------------------------------- 1 | import {BrowserModule} from '@angular/platform-browser'; 2 | import {FormsModule} from '@angular/forms'; 3 | import {NgModule} from '@angular/core'; 4 | 5 | import {AppComponent} from './app.component'; 6 | 7 | // import {AtftModule} from 'atft'; 8 | 9 | // For development (code watch): 10 | import { AtftModule } from 'projects/atft/src/lib/atft.module'; 11 | 12 | @NgModule({ 13 | declarations: [ 14 | AppComponent 15 | ], 16 | imports: [ 17 | BrowserModule, 18 | FormsModule, 19 | AtftModule 20 | ], 21 | providers: [], 22 | bootstrap: [AppComponent] 23 | }) 24 | export class AppModule { } 25 | -------------------------------------------------------------------------------- /src/assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/makimenko/angular-template-for-threejs/184a6682f08b81a0125dfe13769fea056dfa5d8c/src/assets/.gitkeep -------------------------------------------------------------------------------- /src/assets/audio/sample1.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/makimenko/angular-template-for-threejs/184a6682f08b81a0125dfe13769fea056dfa5d8c/src/assets/audio/sample1.mp3 -------------------------------------------------------------------------------- /src/assets/audio/sample2.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/makimenko/angular-template-for-threejs/184a6682f08b81a0125dfe13769fea056dfa5d8c/src/assets/audio/sample2.mp3 -------------------------------------------------------------------------------- /src/assets/model/Menger_sponge_sample.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/makimenko/angular-template-for-threejs/184a6682f08b81a0125dfe13769fea056dfa5d8c/src/assets/model/Menger_sponge_sample.stl -------------------------------------------------------------------------------- /src/assets/model/smiley/Smiley_Sphere001_5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/makimenko/angular-template-for-threejs/184a6682f08b81a0125dfe13769fea056dfa5d8c/src/assets/model/smiley/Smiley_Sphere001_5.jpg -------------------------------------------------------------------------------- /src/assets/model/smiley/smiley.mtl: -------------------------------------------------------------------------------- 1 | # smiley.mtl 3D model 2 | # 3 | # Created by LucaPresidente 4 | # 5 | # This file is distributed under Free Art License, see http://artlibre.org/licence/lal/en for more informations 6 | # and under CC-BY Creative Commons Attribution 3.0 Unported license, see http://creativecommons.org/licenses/by/3.0/ 7 | # at your choice 8 | # 9 | 10 | newmtl Arc001_1 11 | illum 2 12 | Ka 1.0 0.87 0.0 13 | Kd 1.0 0.87 0.0 14 | Ks 0.9 0.9 0.9 15 | Ns 30.0 16 | Ni 1.5 17 | 18 | newmtl Arc005_2 19 | illum 2 20 | Ka 0.19 1.0 0.0 21 | Kd 0.19 1.0 0.0 22 | Ks 0.9 0.9 0.9 23 | Ns 30.0 24 | Ni 1.5 25 | 26 | newmtl Arc006_3 27 | illum 2 28 | Ka 1.0 0.0 0.0 29 | Kd 1.0 0.0 0.0 30 | Ks 0.9 0.9 0.9 31 | Ns 30.0 32 | Ni 1.5 33 | 34 | newmtl Arc007_4 35 | illum 2 36 | Ka 1.0 0.4 0.0 37 | Kd 1.0 0.4 0.0 38 | Ks 0.9 0.9 0.9 39 | Ns 30.0 40 | Ni 1.5 41 | 42 | newmtl Sphere001_5 43 | illum 2 44 | Ka 0.2 0.2 0.2 45 | Kd 1.0 1.0 1.0 46 | Ks 0.9 0.9 0.9 47 | Ns 30.0 48 | Ni 1.5 49 | map_Kd Smiley_Sphere001_5.jpg 50 | -------------------------------------------------------------------------------- /src/assets/svg/expeditedssl-brands.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/svg/external-link-alt-solid.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/svg/galactic-republic-brands.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/svg/server-solid.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/svg/sitemap-solid.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/svg/upload.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 15 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /src/assets/svg/users-solid.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true 3 | }; 4 | -------------------------------------------------------------------------------- /src/environments/environment.ts: -------------------------------------------------------------------------------- 1 | // This file can be replaced during build by using the `fileReplacements` array. 2 | // `ng build ---prod` replaces `environment.ts` with `environment.prod.ts`. 3 | // The list of file replacements can be found in `angular.json`. 4 | 5 | export const environment = { 6 | production: false 7 | }; 8 | 9 | /* 10 | * In development mode, to ignore zone related error stack frames such as 11 | * `zone.run`, `zoneDelegate.invokeTask` for easier debugging, you can 12 | * import the following file, but please comment it out in production mode 13 | * because it will have performance impact when throw error 14 | */ 15 | // import 'zone.js/dist/zone-error'; // Included with Angular CLI. 16 | -------------------------------------------------------------------------------- /src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/makimenko/angular-template-for-threejs/184a6682f08b81a0125dfe13769fea056dfa5d8c/src/favicon.ico -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | AngularTemplateForThreejs 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import {enableProdMode} from '@angular/core'; 2 | import {platformBrowserDynamic} from '@angular/platform-browser-dynamic'; 3 | 4 | import {AppModule} from './app/app.module'; 5 | import {environment} from './environments/environment'; 6 | 7 | if (environment.production) { 8 | enableProdMode(); 9 | } 10 | 11 | platformBrowserDynamic().bootstrapModule(AppModule) 12 | .catch(err => console.log(err)); 13 | -------------------------------------------------------------------------------- /src/stories/animation/connector/connector-line.stories.ts: -------------------------------------------------------------------------------- 1 | import {Component, Input} from '@angular/core'; 2 | import {Meta, moduleMetadata, StoryObj} from '@storybook/angular'; 3 | // NOTE: Do direct import instead of library (allows to watch component and easy to develop) 4 | import {AtftModule} from '../../../../projects/atft/src/lib/atft.module'; 5 | import {axesSceneWrapper} from '../../scene-wrapper/axes-scene-wrapper'; 6 | 7 | 8 | @Component({ 9 | selector: 'app-storybook', 10 | template: axesSceneWrapper(` 11 | 13 | 14 | 16 | 17 | 18 | 19 | `) 20 | }) 21 | class StorybookLineComponent { 22 | 23 | @Input() lineColor: any; 24 | @Input() translateZ = 5; 25 | 26 | } 27 | 28 | 29 | 30 | const meta: Meta = { 31 | title: 'Animate/Connector/Line', 32 | component: StorybookLineComponent, 33 | decorators: [ 34 | moduleMetadata({ 35 | imports: [ 36 | AtftModule 37 | ] 38 | }) 39 | ], 40 | argTypes: { 41 | lineColor: {control: {type: 'color'}}, 42 | translateZ: {control: {type: 'range', min: -100, max: 100, step: 1}}, 43 | } 44 | }; 45 | 46 | 47 | export default meta; 48 | type Story = StoryObj; 49 | 50 | export const Sample: Story = { 51 | args: { 52 | lineColor: '#ff0000', 53 | translateZ: 5, 54 | }, 55 | }; 56 | -------------------------------------------------------------------------------- /src/stories/assets/accessibility.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/makimenko/angular-template-for-threejs/184a6682f08b81a0125dfe13769fea056dfa5d8c/src/stories/assets/accessibility.png -------------------------------------------------------------------------------- /src/stories/assets/accessibility.svg: -------------------------------------------------------------------------------- 1 | 2 | Accessibility 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/stories/assets/addon-library.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/makimenko/angular-template-for-threejs/184a6682f08b81a0125dfe13769fea056dfa5d8c/src/stories/assets/addon-library.png -------------------------------------------------------------------------------- /src/stories/assets/assets.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/makimenko/angular-template-for-threejs/184a6682f08b81a0125dfe13769fea056dfa5d8c/src/stories/assets/assets.png -------------------------------------------------------------------------------- /src/stories/assets/context.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/makimenko/angular-template-for-threejs/184a6682f08b81a0125dfe13769fea056dfa5d8c/src/stories/assets/context.png -------------------------------------------------------------------------------- /src/stories/assets/docs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/makimenko/angular-template-for-threejs/184a6682f08b81a0125dfe13769fea056dfa5d8c/src/stories/assets/docs.png -------------------------------------------------------------------------------- /src/stories/assets/figma-plugin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/makimenko/angular-template-for-threejs/184a6682f08b81a0125dfe13769fea056dfa5d8c/src/stories/assets/figma-plugin.png -------------------------------------------------------------------------------- /src/stories/assets/share.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/makimenko/angular-template-for-threejs/184a6682f08b81a0125dfe13769fea056dfa5d8c/src/stories/assets/share.png -------------------------------------------------------------------------------- /src/stories/assets/styling.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/makimenko/angular-template-for-threejs/184a6682f08b81a0125dfe13769fea056dfa5d8c/src/stories/assets/styling.png -------------------------------------------------------------------------------- /src/stories/assets/testing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/makimenko/angular-template-for-threejs/184a6682f08b81a0125dfe13769fea056dfa5d8c/src/stories/assets/testing.png -------------------------------------------------------------------------------- /src/stories/assets/theming.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/makimenko/angular-template-for-threejs/184a6682f08b81a0125dfe13769fea056dfa5d8c/src/stories/assets/theming.png -------------------------------------------------------------------------------- /src/stories/assets/tutorials.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/stories/assets/youtube.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/stories/basic/dynamic/for-loop.stories.ts: -------------------------------------------------------------------------------- 1 | import {Component} from '@angular/core'; 2 | import {Meta, moduleMetadata, StoryObj} from '@storybook/angular'; 3 | // NOTE: Do direct import instead of library (allows to watch component and easy to develop) 4 | import {AtftModule} from '../../../../projects/atft/src/lib/atft.module'; 5 | import {axesSceneWrapper} from '../../scene-wrapper/axes-scene-wrapper'; 6 | import {AtftDataCenterActorModule} from "../../../../projects/atft/src/lib/actor/data-center"; 7 | 8 | @Component({ 9 | selector: 'app-storybook', 10 | template: axesSceneWrapper(` 11 | 12 |
13 | 15 | 16 |
17 |
18 | `) 19 | }) 20 | class StorybookDynamicComponent { 21 | 22 | numObjects!: number; 23 | 24 | fakeArray(length: number): Array { 25 | if (length >= 0) { 26 | const arr = new Array(length); 27 | for (let i = 0; i < arr.length; i++) { 28 | arr[i] = i * 10; 29 | } 30 | return arr; 31 | } else { 32 | return [] 33 | } 34 | } 35 | 36 | } 37 | 38 | 39 | 40 | const meta: Meta = { 41 | title: 'Basic/Dynamic/For Loop', 42 | component: StorybookDynamicComponent, 43 | decorators: [ 44 | moduleMetadata({ 45 | imports: [ 46 | AtftModule, 47 | AtftDataCenterActorModule 48 | ] 49 | }) 50 | ], 51 | argTypes: { 52 | numObjects: { control: { type: 'range', min: 0, max: 5, step: 1 } } 53 | } 54 | }; 55 | 56 | 57 | export default meta; 58 | type Story = StoryObj; 59 | 60 | export const ForLoop: Story = { 61 | args: { 62 | numObjects: 2 63 | }, 64 | }; 65 | 66 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /src/stories/basic/loader/loader-audio.stories.ts: -------------------------------------------------------------------------------- 1 | import {Component, Input} from '@angular/core'; 2 | import {Meta, moduleMetadata, StoryObj} from '@storybook/angular'; 3 | // NOTE: Do direct import instead of library (allows to watch component and easy to develop) 4 | import {AtftModule} from '../../../../projects/atft/src/lib/atft.module'; 5 | import {worldSceneWrapper} from "../../scene-wrapper/world-scene-wrapper"; 6 | 7 | 8 | @Component({ 9 | selector: 'app-storybook', 10 | template: worldSceneWrapper(` 11 | 12 | `) 13 | }) 14 | class StorybookAudioComponent { 15 | @Input() url = "assets/audio/sample1.mp3"; 16 | @Input() volume = 1; 17 | @Input() loop = false; 18 | } 19 | 20 | 21 | const meta: Meta = { 22 | title: 'Basic/Loader/Audio', 23 | component: StorybookAudioComponent, 24 | decorators: [ 25 | moduleMetadata({ 26 | imports: [ 27 | AtftModule 28 | ] 29 | }) 30 | ], 31 | argTypes: { 32 | loop: { 33 | description: 'Keep playing forever?', 34 | control: { 35 | type: 'boolean' 36 | } 37 | }, 38 | url: { 39 | options: [ 40 | 'assets/audio/sample1.mp3', 41 | 'assets/audio/sample2.mp3' 42 | ], 43 | control: { 44 | type: 'select' 45 | } 46 | }, 47 | } 48 | }; 49 | 50 | 51 | export default meta; 52 | type Story = StoryObj; 53 | 54 | export const Sample1: Story = { 55 | args: { 56 | loop: true, 57 | url: 'assets/audio/sample1.mp3', 58 | volume: 0.7 59 | }, 60 | }; 61 | 62 | export const Sample2: Story = { 63 | args: { 64 | loop: false, 65 | url: 'assets/audio/sample2.mp3', 66 | volume: 1 67 | }, 68 | }; 69 | -------------------------------------------------------------------------------- /src/stories/basic/loader/loader-obj.stories.ts: -------------------------------------------------------------------------------- 1 | import {Component} from '@angular/core'; 2 | import {Meta, moduleMetadata, StoryObj} from '@storybook/angular'; 3 | // NOTE: Do direct import instead of library (allows to watch component and easy to develop) 4 | import {AtftModule} from '../../../../projects/atft/src/lib/atft.module'; 5 | import {worldSceneWrapper} from "../../scene-wrapper/world-scene-wrapper"; 6 | 7 | 8 | const modelPath = 'https://raw.githubusercontent.com/makimenko/files/master/angular-template-for-threejs/model/SampleArchitecture'; 9 | 10 | @Component({ 11 | selector: 'app-storybook', 12 | template: worldSceneWrapper(` 13 | 18 | > 19 | 20 | `) 21 | }) 22 | class StorybookObjLoaderComponent { 23 | 24 | } 25 | 26 | 27 | const meta: Meta = { 28 | title: 'Basic/Loader/Obj (*.obj)', 29 | component: StorybookObjLoaderComponent, 30 | decorators: [ 31 | moduleMetadata({ 32 | imports: [ 33 | AtftModule 34 | ] 35 | }) 36 | ], 37 | argTypes: { 38 | } 39 | }; 40 | 41 | 42 | export default meta; 43 | type Story = StoryObj; 44 | 45 | export const SampleDatacenter: Story = { 46 | args: { 47 | }, 48 | }; 49 | -------------------------------------------------------------------------------- /src/stories/basic/loader/loader-object.stories.ts: -------------------------------------------------------------------------------- 1 | import {Component} from '@angular/core'; 2 | import {Meta, moduleMetadata, StoryObj} from '@storybook/angular'; 3 | // NOTE: Do direct import instead of library (allows to watch component and easy to develop) 4 | import {AtftModule} from '../../../../projects/atft/src/lib/atft.module'; 5 | import {axesSceneWrapper} from '../../scene-wrapper/axes-scene-wrapper'; 6 | 7 | 8 | @Component({ 9 | selector: 'app-storybook', 10 | template: axesSceneWrapper(` 11 | 12 | 13 | `) 14 | }) 15 | class StorybookObjectLoaderComponent { 16 | 17 | } 18 | 19 | 20 | const meta: Meta = { 21 | title: 'Basic/Loader/Object (*.json)', 22 | component: StorybookObjectLoaderComponent, 23 | decorators: [ 24 | moduleMetadata({ 25 | imports: [ 26 | AtftModule 27 | ] 28 | }) 29 | ], 30 | argTypes: { 31 | } 32 | }; 33 | 34 | 35 | export default meta; 36 | type Story = StoryObj; 37 | 38 | export const SampleJSON: Story = { 39 | args: { 40 | }, 41 | }; 42 | -------------------------------------------------------------------------------- /src/stories/basic/loader/loader-stl.stories.ts: -------------------------------------------------------------------------------- 1 | import {Component} from '@angular/core'; 2 | import {Meta, moduleMetadata, StoryObj} from '@storybook/angular'; 3 | // NOTE: Do direct import instead of library (allows to watch component and easy to develop) 4 | import {AtftModule} from '../../../../projects/atft/src/lib/atft.module'; 5 | import {axesSceneWrapper} from '../../scene-wrapper/axes-scene-wrapper'; 6 | 7 | 8 | @Component({ 9 | selector: 'app-storybook', 10 | template: axesSceneWrapper(` 11 | 15 | 16 | `) 17 | }) 18 | class StorybookStlLoaderComponent { 19 | } 20 | 21 | const meta: Meta = { 22 | title: 'Basic/Loader/Stl (*.stl)', 23 | component: StorybookStlLoaderComponent, 24 | decorators: [ 25 | moduleMetadata({ 26 | imports: [ 27 | AtftModule 28 | ] 29 | }) 30 | ], 31 | argTypes: {} 32 | }; 33 | 34 | 35 | export default meta; 36 | type Story = StoryObj; 37 | 38 | export const Sample: Story = { 39 | args: {}, 40 | }; 41 | -------------------------------------------------------------------------------- /src/stories/basic/mesh/mesh-box.stories.ts: -------------------------------------------------------------------------------- 1 | import {Component, Input} from '@angular/core'; 2 | import {Meta, moduleMetadata, StoryObj} from '@storybook/angular'; 3 | // NOTE: Do direct import instead of library (allows to watch component and easy to develop) 4 | import {AtftModule} from '../../../../projects/atft/src/lib/atft.module'; 5 | import {axesSceneWrapper} from '../../scene-wrapper/axes-scene-wrapper'; 6 | 7 | 8 | @Component({ 9 | selector: 'app-storybook', 10 | template: axesSceneWrapper(` 11 | 12 | `) 13 | }) 14 | class StorybookBoxMeshComponent { 15 | @Input() height = 10; 16 | @Input() width = 10; 17 | @Input() depth = 10; 18 | @Input() materialColor!:any; 19 | 20 | } 21 | 22 | 23 | 24 | const meta: Meta = { 25 | title: 'Basic/Mesh/Box', 26 | component: StorybookBoxMeshComponent, 27 | decorators: [ 28 | moduleMetadata({ 29 | imports: [ 30 | AtftModule 31 | ] 32 | }) 33 | ], 34 | argTypes: { 35 | height: {control: {type: 'range', min: 1, max: 50, step: 1}}, 36 | width : {control: {type: 'range', min: 1, max: 50, step: 1}}, 37 | depth: {control: {type: 'range', min: 1, max: 50, step: 1}}, 38 | materialColor: {control: {type: 'color'}} 39 | } 40 | }; 41 | 42 | 43 | export default meta; 44 | type Story = StoryObj; 45 | 46 | export const Red: Story = { 47 | args: { 48 | materialColor: '#ff0000', 49 | height: 10, 50 | width: 10, 51 | depth: 10 52 | }, 53 | }; 54 | 55 | export const Depth: Story = { 56 | args: { 57 | materialColor: '#00ff00', 58 | height: 10, 59 | width: 10, 60 | depth: 20 61 | }, 62 | }; 63 | -------------------------------------------------------------------------------- /src/stories/basic/mesh/mesh-text.stories.ts: -------------------------------------------------------------------------------- 1 | import {Component, Input} from '@angular/core'; 2 | import {Meta, moduleMetadata, StoryObj} from '@storybook/angular'; 3 | // NOTE: Do direct import instead of library (allows to watch component and easy to develop) 4 | import {AtftModule} from '../../../../projects/atft/src/lib/atft.module'; 5 | import {axesSceneWrapper} from '../../scene-wrapper/axes-scene-wrapper'; 6 | 7 | 8 | @Component({ 9 | selector: 'app-storybook', 10 | template: axesSceneWrapper(` 11 | 13 | `) 14 | }) 15 | class StorybookTextMeshComponent { 16 | @Input() text!: string; 17 | @Input() materialColor!: any; 18 | } 19 | 20 | 21 | 22 | const meta: Meta = { 23 | title: 'Basic/Mesh/Text', 24 | component: StorybookTextMeshComponent, 25 | decorators: [ 26 | moduleMetadata({ 27 | imports: [ 28 | AtftModule 29 | ] 30 | }) 31 | ], 32 | argTypes: { 33 | materialColor: {control: {type: 'color'}} 34 | } 35 | }; 36 | 37 | 38 | export default meta; 39 | type Story = StoryObj; 40 | 41 | export const Hello: Story = { 42 | args: { 43 | materialColor: '#ff0000', 44 | text: 'Hello World! :)' 45 | }, 46 | }; 47 | -------------------------------------------------------------------------------- /src/stories/basic/mesh/mesh-video.stories.ts: -------------------------------------------------------------------------------- 1 | import {Component, Input} from '@angular/core'; 2 | import {Meta, moduleMetadata, StoryObj} from '@storybook/angular'; 3 | // NOTE: Do direct import instead of library (allows to watch component and easy to develop) 4 | import {AtftModule} from '../../../../projects/atft/src/lib/atft.module'; 5 | import {axesSceneWrapper} from '../../scene-wrapper/axes-scene-wrapper'; 6 | 7 | 8 | @Component({ 9 | selector: 'app-storybook', 10 | template: axesSceneWrapper(` 11 | 14 | 16 | 17 | 18 | `) 19 | }) 20 | class StorybookVideoComponent { 21 | @Input() videoSrc!: string; 22 | 23 | } 24 | 25 | 26 | const meta: Meta = { 27 | title: 'Basic/Mesh/Video', 28 | component: StorybookVideoComponent, 29 | decorators: [ 30 | moduleMetadata({ 31 | imports: [ 32 | AtftModule 33 | ] 34 | }) 35 | ], 36 | argTypes: {} 37 | }; 38 | 39 | 40 | export default meta; 41 | type Story = StoryObj; 42 | 43 | export const FuturisticUI720p: Story = { 44 | args: { 45 | videoSrc : 'https://raw.githubusercontent.com/makimenko/files/master/angular-template-for-threejs/videos/ui/retro_futuristic_ui_720p.mp4' 46 | }, 47 | }; 48 | 49 | export const FuturisticUI360p: Story = { 50 | args: { 51 | videoSrc : 'https://raw.githubusercontent.com/makimenko/files/master/angular-template-for-threejs/videos/ui/retro_futuristic_ui_360p.mp4' 52 | }, 53 | }; 54 | -------------------------------------------------------------------------------- /src/stories/basic/object/object-raycaster.stories.ts: -------------------------------------------------------------------------------- 1 | import {Meta, moduleMetadata, StoryObj} from '@storybook/angular'; 2 | import {Component} from "@angular/core"; 3 | import {AtftModule} from '../../../../projects/atft/src/lib/atft.module'; 4 | import {worldSceneWrapper} from "../../scene-wrapper/world-scene-wrapper"; 5 | 6 | @Component({ 7 | selector: 'app-storybook', 8 | template: worldSceneWrapper(` 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | `) 18 | }) 19 | class StorybookRaycasterGroupComponent { 20 | 21 | color: string | number = '#00ff00'; 22 | 23 | mouseEnter() { 24 | console.log('mouseEnter'); 25 | this.color = '#0000ff'; 26 | } 27 | 28 | mouseExit() { 29 | console.log('mouseExit'); 30 | this.color = '#00ff00'; 31 | } 32 | 33 | click() { 34 | console.log('click'); 35 | this.color = '#FF0000'; 36 | } 37 | 38 | } 39 | 40 | const meta: Meta = { 41 | title: 'Basic/Object/Raycaster', 42 | component: StorybookRaycasterGroupComponent, 43 | decorators: [ 44 | moduleMetadata({ 45 | imports: [ 46 | AtftModule 47 | ] 48 | }) 49 | ], 50 | 51 | }; 52 | 53 | 54 | export default meta; 55 | type Story = StoryObj; 56 | 57 | export const MouseEvents: Story = { 58 | }; 59 | -------------------------------------------------------------------------------- /src/stories/basic/timeline/timeline-emitter.stories.ts: -------------------------------------------------------------------------------- 1 | import {Component, Input} from '@angular/core'; 2 | import {Meta, moduleMetadata, StoryObj} from '@storybook/angular'; 3 | // NOTE: Do direct import instead of library (allows to watch component and easy to develop) 4 | import {AtftModule} from '../../../../projects/atft/src/lib/atft.module'; 5 | import {axesSceneWrapper} from "../../scene-wrapper/axes-scene-wrapper"; 6 | 7 | 8 | @Component({ 9 | selector: 'app-storybook', 10 | template: axesSceneWrapper(` 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | `) 19 | }) 20 | class TimelineEmitterStoryComponent { 21 | @Input() visibleOne = false; 22 | @Input() visibleTwo = false; 23 | } 24 | 25 | 26 | const meta: Meta = { 27 | title: 'Basic/Timeline/Emitter', 28 | component: TimelineEmitterStoryComponent, 29 | decorators: [ 30 | moduleMetadata({ 31 | imports: [ 32 | AtftModule 33 | ] 34 | }) 35 | ], 36 | argTypes: {} 37 | }; 38 | 39 | 40 | export default meta; 41 | type Story = StoryObj; 42 | 43 | export const Emitter: Story = { 44 | args: {}, 45 | }; 46 | 47 | -------------------------------------------------------------------------------- /src/stories/dagre-layout/dagre-loop.stories.ts: -------------------------------------------------------------------------------- 1 | import {Component} from '@angular/core'; 2 | import {Meta, moduleMetadata, StoryObj} from '@storybook/angular'; 3 | import {AtftDataCenterActorModule} from '../../../projects/atft/src/lib/actor/data-center'; 4 | // NOTE: Do direct import instead of library (allows to watch component and easy to develop) 5 | import {AtftModule} from '../../../projects/atft/src/lib/atft.module'; 6 | import {worldSceneWrapper} from '../scene-wrapper/world-scene-wrapper'; 7 | import {AnimationService} from '../../../projects/atft/src/lib/animation'; 8 | 9 | 10 | @Component({ 11 | selector: 'app-storybook', 12 | template: worldSceneWrapper(` 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | `) 21 | }) 22 | class StorybookLoopComponent { 23 | 24 | constructor(private animationService: AnimationService) { 25 | this.animationService.start(); 26 | } 27 | 28 | numDatabases = 1; 29 | 30 | fakeArray(length: number): Array { 31 | if (length >= 0) { 32 | return new Array(length); 33 | } else { 34 | return [] 35 | } 36 | } 37 | 38 | } 39 | 40 | 41 | const meta: Meta = { 42 | title: 'Dagre Layout/Loop', 43 | component: StorybookLoopComponent, 44 | decorators: [ 45 | moduleMetadata({ 46 | imports: [ 47 | AtftModule, 48 | AtftDataCenterActorModule 49 | ] 50 | }) 51 | ] 52 | }; 53 | 54 | 55 | export default meta; 56 | type Story = StoryObj; 57 | 58 | export const Loop: Story = { 59 | args: { 60 | numDatabases: 0 61 | } 62 | }; 63 | 64 | 65 | -------------------------------------------------------------------------------- /src/stories/effect/blur.stories.ts: -------------------------------------------------------------------------------- 1 | import {Meta, moduleMetadata, StoryObj} from '@storybook/angular'; 2 | // NOTE: Do direct import instead of library (allows to watch component and easy to develop) 3 | import {AtftModule} from '../../../projects/atft/src/lib/atft.module'; 4 | import {Component, Input} from '@angular/core'; 5 | import {effectsSceneWrapper} from '../scene-wrapper/effects-scene-wrapper'; 6 | import {AtftDataCenterActorModule} from "../../../projects/atft/src/lib/actor/data-center"; 7 | 8 | 9 | @Component({ 10 | selector: 'app-storybook', 11 | template: effectsSceneWrapper(` 12 | 13 | 14 | 15 | `) 16 | }) 17 | class StorybookBlurComponent { 18 | @Input() 19 | enable = true; 20 | 21 | @Input() 22 | background : any; 23 | } 24 | 25 | 26 | 27 | const meta: Meta = { 28 | title: 'Effects/Blur', 29 | component: StorybookBlurComponent, 30 | decorators: [ 31 | moduleMetadata({ 32 | imports: [ 33 | AtftModule, 34 | AtftDataCenterActorModule 35 | ] 36 | }) 37 | ], 38 | argTypes: { 39 | enable: {control: {type: 'boolean'}}, 40 | background: { 41 | options: [ 42 | '#FFFFFF', 43 | '#DDDDDD', 44 | '#AA0000', 45 | '#00AA00', 46 | '#0000AA' 47 | ], 48 | control: { 49 | type: 'color' 50 | } 51 | }, 52 | } 53 | }; 54 | 55 | 56 | export default meta; 57 | type Story = StoryObj; 58 | 59 | export const Blur: Story = { 60 | args: { 61 | enable : true, 62 | background : '#FFFFFF' 63 | }, 64 | }; 65 | 66 | -------------------------------------------------------------------------------- /src/stories/effect/dashed-draw.stories.ts: -------------------------------------------------------------------------------- 1 | import {Meta, moduleMetadata, StoryObj} from '@storybook/angular'; 2 | // NOTE: Do direct import instead of library (allows to watch component and easy to develop) 3 | import {AtftModule} from '../../../projects/atft/src/lib/atft.module'; 4 | import {Component} from '@angular/core'; 5 | import {performanceSceneWrapper} from '../scene-wrapper/performance-scene-wrapper'; 6 | 7 | @Component({ 8 | selector: 'app-storybook', 9 | template: performanceSceneWrapper(` 10 | 15 | 16 | 22 | 23 | 30 | `) 31 | }) 32 | class StorybookDashedDrawComponent { 33 | 34 | } 35 | 36 | 37 | const meta: Meta = { 38 | title: 'Effects/Dashed Draw', 39 | component: StorybookDashedDrawComponent, 40 | decorators: [ 41 | moduleMetadata({ 42 | imports: [ 43 | AtftModule 44 | ] 45 | }) 46 | ], 47 | }; 48 | 49 | 50 | export default meta; 51 | type Story = StoryObj; 52 | 53 | export const DashedDraw: Story = { 54 | }; 55 | 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /src/stories/effect/dot-screen.stories.ts: -------------------------------------------------------------------------------- 1 | import {Meta, moduleMetadata, StoryObj} from '@storybook/angular'; 2 | // NOTE: Do direct import instead of library (allows to watch component and easy to develop) 3 | import {AtftModule} from '../../../projects/atft/src/lib/atft.module'; 4 | import {Component, Input} from '@angular/core'; 5 | import {effectsSceneWrapper} from '../scene-wrapper/effects-scene-wrapper'; 6 | import {AtftDataCenterActorModule} from "../../../projects/atft/src/lib/actor/data-center"; 7 | 8 | 9 | @Component({ 10 | selector: 'app-storybook', 11 | template: effectsSceneWrapper(` 12 | 13 | 14 | 15 | `) 16 | }) 17 | class StorybookDotScreenComponent { 18 | 19 | @Input() 20 | enable = true; 21 | 22 | @Input() 23 | background : any; 24 | 25 | } 26 | 27 | 28 | 29 | const meta: Meta = { 30 | title: 'Effects/Dot Screen', 31 | component: StorybookDotScreenComponent, 32 | decorators: [ 33 | moduleMetadata({ 34 | imports: [ 35 | AtftModule, 36 | AtftDataCenterActorModule 37 | ] 38 | }) 39 | ], 40 | argTypes: { 41 | enable: {control: {type: 'boolean'}}, 42 | background: { 43 | options: [ 44 | '#FFFFFF', 45 | '#DDDDDD', 46 | '#AA0000', 47 | '#00AA00', 48 | '#0000AA' 49 | ], 50 | control: { 51 | type: 'color' 52 | } 53 | }, 54 | } 55 | }; 56 | 57 | 58 | export default meta; 59 | type Story = StoryObj; 60 | 61 | export const DotScreen: Story = { 62 | args: { 63 | enable: true, 64 | background: '#FFFFFF' 65 | }, 66 | }; 67 | 68 | 69 | -------------------------------------------------------------------------------- /src/stories/scene-wrapper/axes-scene-wrapper.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Default Storybook 3D scene wrapper includes light, camera, orbit control, Z axus up, grid and axis helper. 3 | * Such template allows to focus on storybook inputs/knobs and actions testing. 4 | * 5 | * @param content content template 6 | */ 7 | export const axesSceneWrapper = (content: string) => ` 8 | 9 | 10 | 11 | 12 | 13 | 14 | 16 | 18 | ${content} 19 | 20 | 21 | 22 | `; 23 | 24 | 25 | -------------------------------------------------------------------------------- /src/stories/scene-wrapper/effects-scene-wrapper.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Test effects 3 | * 4 | * @param content content template 5 | */ 6 | export const effectsSceneWrapper = (content: string) => ` 7 | 8 | 9 | 11 | 12 | 13 | ${content} 14 | 15 | 17 | 19 | 20 | 23 | 24 | 25 | 26 | 27 | 28 | `; 29 | 30 | 31 | -------------------------------------------------------------------------------- /src/stories/scene-wrapper/performance-scene-wrapper.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Default Storybook 3D scene wrapper includes light, camera, orbit control, Z axus up, grid and axis helper. 3 | * Such template allows to focus on storybook inputs/knobs and actions testing. 4 | * 5 | * @param content content template 6 | */ 7 | export const performanceSceneWrapper = (content: string) => ` 8 | 9 | 10 | 11 | 12 | 14 | 16 | ${content} 17 | 18 | 19 | 20 | `; 21 | 22 | 23 | -------------------------------------------------------------------------------- /src/stories/scene-wrapper/ux-scene-wrapper.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Minimalistic Storybook 3D scene wrapper includes light, camera and orbit control. 3 | * Such template allows to focus on storybook models testing. 4 | * 5 | * @param content content template 6 | */ 7 | export const uxSceneWrapper = (content: string) => ` 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 17 | 19 | 20 | ${content} 21 | 22 | 23 | 24 | `; 25 | 26 | 27 | -------------------------------------------------------------------------------- /src/stories/scene-wrapper/world-scene-wrapper.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Minimalistic Storybook 3D scene wrapper includes light, camera and orbit control. 3 | * Such template allows to focus on storybook models testing. 4 | * 5 | * @param content content template 6 | */ 7 | export const worldSceneWrapper = (content: string) => ` 8 | 9 | 10 | 12 | 13 | 14 | 15 | 17 | 18 | 19 | 21 | 22 | 24 | 25 | ${content} 26 | 27 | 28 | 29 | `; 30 | 31 | 32 | -------------------------------------------------------------------------------- /src/stories/ux/loader.stories.ts: -------------------------------------------------------------------------------- 1 | import {Component} from '@angular/core'; 2 | import {Meta, moduleMetadata, StoryObj} from '@storybook/angular'; 3 | // NOTE: Do direct import instead of library (allows to watch component and easy to develop) 4 | import {AtftModule} from '../../../projects/atft/src/lib/atft.module'; 5 | import {UxActorModule} from '../../../projects/atft/src/lib/actor/ux'; 6 | import {uxSceneWrapper} from '../scene-wrapper/ux-scene-wrapper'; 7 | 8 | 9 | @Component({ 10 | selector: 'app-storybook', 11 | template: uxSceneWrapper(` 12 | 13 | `) 14 | }) 15 | class StorybookTextComponent { 16 | 17 | } 18 | 19 | const meta: Meta = { 20 | title: 'UX/Loader', 21 | component: StorybookTextComponent, 22 | decorators: [ 23 | moduleMetadata({ 24 | imports: [ 25 | AtftModule, 26 | UxActorModule 27 | ] 28 | }) 29 | ], 30 | }; 31 | 32 | 33 | export default meta; 34 | type Story = StoryObj; 35 | 36 | export const Loader: Story = { 37 | }; 38 | 39 | 40 | -------------------------------------------------------------------------------- /src/stories/ux/text.stories.ts: -------------------------------------------------------------------------------- 1 | import {Component} from '@angular/core'; 2 | import {Meta, moduleMetadata, StoryObj} from '@storybook/angular'; 3 | // NOTE: Do direct import instead of library (allows to watch component and easy to develop) 4 | import {AtftModule} from '../../../projects/atft/src/lib/atft.module'; 5 | import {uxSceneWrapper} from '../scene-wrapper/ux-scene-wrapper'; 6 | import {UxActorModule} from '../../../projects/atft/src/lib/actor/ux'; 7 | 8 | 9 | @Component({ 10 | selector: 'app-storybook', 11 | template: uxSceneWrapper(` 12 | 13 | 14 | `) 15 | }) 16 | class StorybookTextComponent { 17 | 18 | label!: string; 19 | animate!: boolean; 20 | minDelay!: number; 21 | maxDelay!: number; 22 | 23 | } 24 | 25 | 26 | 27 | 28 | const meta: Meta = { 29 | title: 'UX/Text', 30 | component: StorybookTextComponent, 31 | decorators: [ 32 | moduleMetadata({ 33 | imports: [ 34 | AtftModule, 35 | UxActorModule 36 | ] 37 | }) 38 | ], 39 | argTypes: { 40 | animate: { 41 | description: 'Enable animated text?', 42 | control: { 43 | type: 'boolean' 44 | } 45 | }, 46 | label: { 47 | description: 'Please choose title of workstation', 48 | control: {type: 'text'} 49 | }, 50 | minDelay: {control: {type: 'range', min: 0, max: 300, step: 1}}, 51 | maxDelay: {control: {type: 'range', min: 0, max: 1000, step: 1}}, 52 | } 53 | }; 54 | 55 | 56 | export default meta; 57 | type Story = StoryObj; 58 | 59 | export const Text: Story = { 60 | args: { 61 | animate: true, 62 | label: 'Hello, welcome to the world of animationService!', 63 | minDelay: 5, 64 | maxDelay: 20 65 | }, 66 | }; 67 | 68 | 69 | -------------------------------------------------------------------------------- /src/styles.scss: -------------------------------------------------------------------------------- 1 | /* You can add global styles to this file, and also import object style files */ 2 | html, body { 3 | width: 100%; 4 | height: 100%; 5 | margin: 0; 6 | } 7 | 8 | -------------------------------------------------------------------------------- /tsconfig.app.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "extends": "./tsconfig.json", 4 | "compilerOptions": { 5 | "outDir": "./out-tsc/app", 6 | "types": [] 7 | }, 8 | "files": [ 9 | "src/main.ts" 10 | ], 11 | "include": [ 12 | "src/**/*.d.ts" 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "compileOnSave": false, 4 | "compilerOptions": { 5 | "paths": { 6 | "atft": [ 7 | "dist/atft" 8 | ] 9 | }, 10 | "baseUrl": "./", 11 | "outDir": "./dist/out-tsc", 12 | "forceConsistentCasingInFileNames": true, 13 | "strict": true, 14 | "noImplicitOverride": true, 15 | "noPropertyAccessFromIndexSignature": true, 16 | "noImplicitReturns": true, 17 | "noFallthroughCasesInSwitch": true, 18 | "sourceMap": true, 19 | "declaration": false, 20 | "downlevelIteration": true, 21 | "experimentalDecorators": true, 22 | "moduleResolution": "node", 23 | "importHelpers": true, 24 | "target": "ES2022", 25 | "module": "ES2022", 26 | "useDefineForClassFields": false, 27 | "typeRoots": [ 28 | "node_modules/@types", 29 | "./src/typings.d.ts" 30 | ], 31 | "lib": [ 32 | "ES2022", 33 | "dom" 34 | ] 35 | }, 36 | "angularCompilerOptions": { 37 | "enableI18nLegacyMessageIdFormat": false, 38 | "fullTemplateTypeCheck": true, 39 | "preserveWhitespaces": true, 40 | "strictInjectionParameters": true, 41 | "strictInputAccessModifiers": true, 42 | "strictTemplates": true 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "extends": "./tsconfig.json", 4 | "compilerOptions": { 5 | "outDir": "./out-tsc/spec", 6 | "types": [ 7 | "jasmine" 8 | ] 9 | }, 10 | "include": [ 11 | "src/**/*.spec.ts", 12 | "src/**/*.d.ts" 13 | ] 14 | } 15 | --------------------------------------------------------------------------------