├── .DS_Store ├── .babelrc ├── .env ├── .eslintignore ├── .eslintrc.js ├── .gitattributes ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── PULL_REQUEST_TEMPLATE.md └── workflows │ ├── codeql-analysis.yml │ ├── pr.yml │ └── release.yml ├── .gitignore ├── .prettierrc ├── .vscode ├── launch.json └── settings.json ├── CHANGELOG.md ├── LICENSE ├── README.md ├── __mocks__ └── emptyMock.js ├── core ├── edera_0.1.js └── edera_0.1.wasm ├── examples ├── .DS_Store └── orbital │ ├── 01_duomo_entrata.jpg │ ├── AlarmClock.glb │ └── index.html ├── jest.config.js ├── package-lock.json ├── package.json ├── src ├── hedera.ts └── model │ ├── ihedera.ts │ ├── ihederacoreentity.ts │ ├── ihederaentity.ts │ ├── iscenedescriptor.ts │ └── iworld.ts ├── test ├── index.spec.d.ts └── index.spec.ts ├── tsconfig.json └── webpack.config.js /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kedos-srl/3dera-js/157596a1c26b1e5e14a9377e6989eff6db19c687/.DS_Store -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["@babel/preset-env", "@babel/preset-typescript"], 3 | "plugins": [ 4 | "@babel/proposal-class-properties", 5 | "@babel/proposal-object-rest-spread" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | # Local env vars for debugging 2 | TS_NODE_IGNORE="false" 3 | TS_NODE_FILES="true" -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | src/types/global.d.ts -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | parser: '@typescript-eslint/parser', 4 | plugins: ['@typescript-eslint', 'node', 'prettier'], 5 | parserOptions: { 6 | tsconfigRootDir: __dirname, 7 | project: ['./tsconfig.json'], 8 | }, 9 | extends: [ 10 | 'eslint:recommended', 11 | 'plugin:node/recommended', 12 | 'plugin:@typescript-eslint/eslint-recommended', 13 | 'plugin:@typescript-eslint/recommended', 14 | 'plugin:@typescript-eslint/recommended-requiring-type-checking', 15 | 'plugin:prettier/recommended', 16 | ], 17 | rules: { 18 | 'prettier/prettier': 'warn', 19 | 'node/no-missing-import': 'off', 20 | 'node/no-empty-function': 'off', 21 | 'node/no-unsupported-features/es-syntax': 'off', 22 | 'node/no-missing-require': 'off', 23 | 'node/shebang': 'off', 24 | '@typescript-eslint/no-use-before-define': 'off', 25 | quotes: ['warn', 'single', { avoidEscape: true }], 26 | 'node/no-unpublished-import': 'off', 27 | '@typescript-eslint/no-unsafe-assignment': 'off', 28 | '@typescript-eslint/no-var-requires': 'off', 29 | '@typescript-eslint/ban-ts-comment': 'off', 30 | '@typescript-eslint/no-explicit-any': 'off', 31 | }, 32 | }; 33 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Set the repository to show as TypeScript rather than JS in GitHub 2 | *.js linguist-detectable=false -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "🐛 Bug Report" 3 | about: Report a reproducible bug or regression. 4 | title: '' 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | ## Current Behavior 11 | 12 | 13 | 14 | ## Expected Behavior 15 | 16 | 17 | 18 | ## Steps to Reproduce the Problem 19 | 20 | 1. 21 | 1. 22 | 1. 23 | 24 | ## Environment 25 | 26 | - Version: 27 | - Platform: 28 | - Node.js Version: 29 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 🌈 Feature request 3 | about: Suggest an amazing new idea for this project 4 | title: '' 5 | labels: enhancement 6 | assignees: '' 7 | 8 | --- 9 | 10 | ## Feature Request 11 | 12 | **Is your feature request related to a problem? Please describe.** 13 | 14 | 15 | **Describe the solution you'd like** 16 | 17 | 18 | **Describe alternatives you've considered** 19 | 20 | 21 | ## Are you willing to resolve this issue by submitting a Pull Request? 22 | 23 | 26 | 27 | - [ ] Yes, I have the time, and I know how to start. 28 | - [ ] Yes, I have the time, but I don't know how to start. I would need guidance. 29 | - [ ] No, I don't have the time, although I believe I could do it if I had the time... 30 | - [ ] No, I don't have the time and I wouldn't even know how to start. 31 | 32 | 35 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 7 | 8 | ### Description of change 9 | 10 | 23 | 24 | ### Pull-Request Checklist 25 | 26 | 31 | 32 | - [ ] Code is up-to-date with the `main` branch 33 | - [ ] `npm run lint` passes with this change 34 | - [ ] `npm run test` passes with this change 35 | - [ ] This pull request links relevant issues as `Fixes #0000` 36 | - [ ] There are new or updated unit tests validating the change 37 | - [ ] Documentation has been updated to reflect this change 38 | - [ ] The new commits follow conventions outlined in the [conventional commit spec](https://www.conventionalcommits.org/en/v1.0.0/) 39 | 40 | 43 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [ main ] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: [ main ] 20 | schedule: 21 | - cron: '36 7 * * 6' 22 | 23 | jobs: 24 | analyze: 25 | name: Analyze 26 | runs-on: ubuntu-latest 27 | permissions: 28 | actions: read 29 | contents: read 30 | security-events: write 31 | 32 | strategy: 33 | fail-fast: false 34 | matrix: 35 | language: [ 'javascript' ] 36 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] 37 | # Learn more: 38 | # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed 39 | 40 | steps: 41 | - name: Checkout repository 42 | uses: actions/checkout@v2 43 | 44 | # Initializes the CodeQL tools for scanning. 45 | - name: Initialize CodeQL 46 | uses: github/codeql-action/init@v1 47 | with: 48 | languages: ${{ matrix.language }} 49 | # If you wish to specify custom queries, you can do so here or in a config file. 50 | # By default, queries listed here will override any specified in a config file. 51 | # Prefix the list here with "+" to use these queries and those in the config file. 52 | # queries: ./path/to/local/query, your-org/your-repo/queries@main 53 | 54 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 55 | # If this step fails, then you should remove it and run the build manually (see below) 56 | - name: Autobuild 57 | uses: github/codeql-action/autobuild@v1 58 | 59 | # ℹ️ Command-line programs to run using the OS shell. 60 | # 📚 https://git.io/JvXDl 61 | 62 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 63 | # and modify them (or add more) to build your code if your project 64 | # uses a compiled language 65 | 66 | #- run: | 67 | # make bootstrap 68 | # make release 69 | 70 | - name: Perform CodeQL Analysis 71 | uses: github/codeql-action/analyze@v1 72 | -------------------------------------------------------------------------------- /.github/workflows/pr.yml: -------------------------------------------------------------------------------- 1 | name: Pull Request 2 | 3 | on: [pull_request] 4 | 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | 9 | strategy: 10 | matrix: 11 | node-version: [18.12.x] 12 | 13 | steps: 14 | - uses: actions/checkout@v2 15 | - name: Use Node.js ${{ matrix.node-version }} 16 | uses: actions/setup-node@v2 17 | with: 18 | node-version: ${{ matrix.node-version }} 19 | - run: npm ci 20 | - run: npm run build --if-present 21 | - run: npm test 22 | - run: npm run coverage 23 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | on: 3 | push: 4 | branches: 5 | - main 6 | jobs: 7 | release: 8 | name: Release 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Checkout 12 | uses: actions/checkout@v2 13 | with: 14 | fetch-depth: 0 15 | - name: Setup Node.js 16 | uses: actions/setup-node@v2 17 | with: 18 | node-version: lts/* 19 | - name: Install dependencies 20 | run: npm ci 21 | - name: Test 22 | run: npm test 23 | - name: Build 24 | run: npm run build 25 | - name: Release 26 | env: 27 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 28 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 29 | run: npx semantic-release 30 | - name: Code Coverage 31 | run: npm run coverage 32 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | *.lcov 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # Bower dependency directory (https://bower.io/) 32 | bower_components 33 | 34 | # node-waf configuration 35 | .lock-wscript 36 | 37 | # Compiled binary addons (https://nodejs.org/api/addons.html) 38 | build/Release 39 | 40 | # Dependency directories 41 | node_modules/ 42 | jspm_packages/ 43 | 44 | # Snowpack dependency directory (https://snowpack.dev/) 45 | web_modules/ 46 | 47 | # TypeScript cache 48 | *.tsbuildinfo 49 | 50 | # Optional npm cache directory 51 | .npm 52 | 53 | # Optional eslint cache 54 | .eslintcache 55 | 56 | # Microbundle cache 57 | .rpt2_cache/ 58 | .rts2_cache_cjs/ 59 | .rts2_cache_es/ 60 | .rts2_cache_umd/ 61 | 62 | # Optional REPL history 63 | .node_repl_history 64 | 65 | # Output of 'npm pack' 66 | *.tgz 67 | 68 | # Yarn Integrity file 69 | .yarn-integrity 70 | 71 | # dotenv environment variables file 72 | .env.test 73 | .env.local 74 | 75 | # parcel-bundler cache (https://parceljs.org/) 76 | .cache 77 | .parcel-cache 78 | 79 | # Next.js build output 80 | .next 81 | out 82 | 83 | # Nuxt.js build / generate output 84 | .nuxt 85 | dist 86 | 87 | # Gatsby files 88 | .cache/ 89 | # Comment in the public line in if your project uses Gatsby and not Next.js 90 | # https://nextjs.org/blog/next-9-1#public-directory-support 91 | # public 92 | 93 | # vuepress build output 94 | .vuepress/dist 95 | 96 | # Serverless directories 97 | .serverless/ 98 | 99 | # FuseBox cache 100 | .fusebox/ 101 | 102 | # DynamoDB Local files 103 | .dynamodb/ 104 | 105 | # TernJS port file 106 | .tern-port 107 | 108 | # Stores VSCode versions used for testing VSCode extensions 109 | .vscode-test 110 | 111 | # yarn v2 112 | .yarn/cache 113 | .yarn/unplugged 114 | .yarn/build-state.yml 115 | .yarn/install-state.gz 116 | .pnp.* 117 | 118 | # Compiled code 119 | lib/ 120 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "es5", 4 | "arrowParens": "avoid", 5 | "printWidth": 80 6 | } 7 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "Current TS File", 9 | "type": "node", 10 | "request": "launch", 11 | "runtimeExecutable": "node", 12 | "args": ["${relativeFile}"], 13 | "runtimeArgs": ["--nolazy", "-r", "ts-node/register/transpile-only"], 14 | "envFile": "${workspaceFolder}/.env", 15 | "cwd": "${workspaceRoot}", 16 | "internalConsoleOptions": "openOnSessionStart", 17 | "skipFiles": ["/**", "node_modules/**"] 18 | }, 19 | { 20 | "name": "Debug Jest Tests", 21 | "type": "node", 22 | "request": "launch", 23 | "runtimeArgs": [ 24 | "--inspect-brk", 25 | "${workspaceRoot}/node_modules/.bin/jest", 26 | "--runInBand" 27 | ], 28 | "envFile": "${workspaceFolder}/.env", 29 | "console": "integratedTerminal", 30 | "internalConsoleOptions": "neverOpen", 31 | "port": 9229, 32 | "disableOptimisticBPs": true, 33 | "windows": { 34 | "program": "${workspaceFolder}/node_modules/jest/bin/jest" 35 | } 36 | }, 37 | { 38 | "name": "Debug Jest Current File", 39 | "type": "node", 40 | "request": "launch", 41 | "program": "${workspaceFolder}/node_modules/.bin/jest", 42 | "args": ["${relativeFile}", "--config", "jest.config.js"], 43 | "console": "integratedTerminal", 44 | "internalConsoleOptions": "neverOpen", 45 | "port": 9229, 46 | "disableOptimisticBPs": true, 47 | "windows": { 48 | "program": "${workspaceFolder}/node_modules/jest/bin/jest" 49 | } 50 | } 51 | ] 52 | } 53 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.formatOnSave": true, 3 | "[typescript]": { 4 | "editor.defaultFormatter": "esbenp.prettier-vscode" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## [1.0.1] - 2024-12-10 4 | 5 | ### Changed 6 | 7 | - WASM is bundled in the main file 8 | - New APIs for entity management 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Kedos srl 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 3Dera-JS 2 | 3 | [![npm package][npm-img]][npm-url] 4 | [![Build Status][build-img]][build-url] 5 | [![Downloads][downloads-img]][downloads-url] 6 | [![Issues][issues-img]][issues-url] 7 | [![Code Coverage][codecov-img]][codecov-url] 8 | [![Commitizen Friendly][commitizen-img]][commitizen-url] 9 | [![Semantic Release][semantic-release-img]][semantic-release-url] 10 | 11 | > 3DeraJS is the JavaScript library to access 3Dera core functionality 12 | 13 | ## Install 14 | 15 | ```bash 16 | npm install @kedos-srl/3dera-js 17 | ``` 18 | 19 | ## Documentation 20 | 21 | To see all available API options, take a look at the [documentation](https://kedos-srl.github.io/3dera-doc/) 22 | 23 | ## Usage 24 | 25 | ```ts 26 | import HederaJS from '3dera-js'; 27 | 28 | const canvas = document.getElementById('3dera-canvas') as HTMLCanvasElement; 29 | await HederaJS.init(canvas); 30 | await HederaJS.start(); 31 | ``` 32 | 33 | [npm-img]: https://img.shields.io/npm/v/@kedos-srl/3dera-js 34 | [build-img]: https://github.com/Kedos-srl/3dera-js/actions/workflows/release.yml/badge.svg 35 | [build-url]: https://github.com/Kedos-srl/3dera-js/actions/workflows/release.yml 36 | [npm-url]: https://www.npmjs.com/package/@kedos-srl/3dera-js 37 | [issues-img]: https://img.shields.io/github/issues/Kedos-srl/3dera-js 38 | [issues-url]: https://github.com/Kedos-srl/3dera-js/issues 39 | [codecov-img]: https://codecov.io/gh/Kedos-srl/3dera-js/branch/main/graph/badge.svg 40 | [codecov-url]: https://codecov.io/gh/Kedos-srl/3dera-js 41 | [semantic-release-img]: https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg 42 | [semantic-release-url]: https://github.com/semantic-release/semantic-release 43 | [commitizen-img]: https://img.shields.io/badge/commitizen-friendly-brightgreen.svg 44 | [commitizen-url]: http://commitizen.github.io/cz-cli/ 45 | [downloads-img]: https://img.shields.io/npm/dt/@kedos-srl/3dera-js 46 | [downloads-url]: https://www.npmtrends.com/@kedos-srl/3dera-js 47 | -------------------------------------------------------------------------------- /__mocks__/emptyMock.js: -------------------------------------------------------------------------------- 1 | module.exports = {}; 2 | -------------------------------------------------------------------------------- /core/edera_0.1.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kedos-srl/3dera-js/157596a1c26b1e5e14a9377e6989eff6db19c687/core/edera_0.1.wasm -------------------------------------------------------------------------------- /examples/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kedos-srl/3dera-js/157596a1c26b1e5e14a9377e6989eff6db19c687/examples/.DS_Store -------------------------------------------------------------------------------- /examples/orbital/01_duomo_entrata.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kedos-srl/3dera-js/157596a1c26b1e5e14a9377e6989eff6db19c687/examples/orbital/01_duomo_entrata.jpg -------------------------------------------------------------------------------- /examples/orbital/AlarmClock.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kedos-srl/3dera-js/157596a1c26b1e5e14a9377e6989eff6db19c687/examples/orbital/AlarmClock.glb -------------------------------------------------------------------------------- /examples/orbital/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Orbital Clock 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | preset: 'ts-jest', 3 | testEnvironment: 'node', 4 | testMatch: ['**/test/**/*.spec.ts'], 5 | collectCoverageFrom: [ 6 | '/src/**/*.ts', 7 | '!/core/*.wasm', 8 | '!/src/types/**/*.ts', 9 | ], 10 | globals: { 11 | 'ts-jest': { 12 | diagnostics: false, 13 | isolatedModules: true, 14 | }, 15 | }, 16 | moduleNameMapper: { 17 | '\\.wasm$': '/__mocks__/emptyMock.js', 18 | }, 19 | }; 20 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@kedos-srl/3dera-js", 3 | "version": "1.0.2", 4 | "description": "A blazing fast powerful tool for 3D visualization with WASM engine", 5 | "type": "commonjs", 6 | "module": "hedera.js", 7 | "main": "hedera.js", 8 | "types": "hedera.d.ts", 9 | "files": [ 10 | "dist/**/*" 11 | ], 12 | "scripts": { 13 | "build": "webpack --config webpack.config.js && npx tsc --declaration --emitDeclarationOnly --outDir dist", 14 | "clean": "rm -rf ./lib/", 15 | "cm": "cz", 16 | "coverage": "codecov", 17 | "lint": "eslint ./src/ --fix", 18 | "semantic-release": "semantic-release", 19 | "test:watch": "jest --watch", 20 | "test": "jest --coverage", 21 | "typecheck": "tsc --noEmit", 22 | "postbuild": "" 23 | }, 24 | "repository": { 25 | "type": "git", 26 | "url": "git+https://github.com/Kedos-srl/3dera-js.git" 27 | }, 28 | "license": "MIT", 29 | "publishConfig": { 30 | "access": "public" 31 | }, 32 | "author": "Kedos srl (https://github.com/Kedos-srl)", 33 | "engines": { 34 | "node": ">=12.0" 35 | }, 36 | "keywords": [ 37 | "3D", 38 | "Hedera", 39 | "3Dera", 40 | "3D visualization", 41 | "engine", 42 | "rendering", 43 | "graphics", 44 | "web", 45 | "webgl", 46 | "wasm", 47 | "webassembly" 48 | ], 49 | "bugs": { 50 | "url": "https://github.com/Kedos-srl/3dera-js/issues" 51 | }, 52 | "homepage": "https://github.com/Kedos-srl/3dera-js#readme", 53 | "peerDependencies": { 54 | "hammerjs": "~2.0.8", 55 | "jszip": "~3.10.1" 56 | }, 57 | "devDependencies": { 58 | "@babel/core": "^7.26.0", 59 | "@babel/plugin-proposal-class-properties": "^7.18.6", 60 | "@babel/plugin-proposal-object-rest-spread": "^7.20.7", 61 | "@babel/preset-env": "^7.26.0", 62 | "@babel/preset-typescript": "^7.26.0", 63 | "@types/hammerjs": "^2.0.46", 64 | "@types/jest": "^27.0.24", 65 | "@types/node": "^12.20.11", 66 | "@typescript-eslint/eslint-plugin": "^4.22.0", 67 | "@typescript-eslint/parser": "^4.22.0", 68 | "babel-loader": "^9.2.1", 69 | "codecov": "^3.8.1", 70 | "commitizen": "^4.2.4", 71 | "copy-webpack-plugin": "^12.0.2", 72 | "cz-conventional-changelog": "^3.3.0", 73 | "eslint": "^7.25.0", 74 | "eslint-config-prettier": "^8.3.0", 75 | "eslint-plugin-node": "^11.1.0", 76 | "eslint-plugin-prettier": "^3.4.0", 77 | "jest": "^27.2.0", 78 | "lint-staged": "^10.5.4", 79 | "npm-prepare-dist": "^0.5.0", 80 | "prettier": "^2.2.1", 81 | "semantic-release": "^19.0.2", 82 | "ts-jest": "^27.0.24", 83 | "ts-loader": "^9.5.1", 84 | "ts-node": "^10.2.1", 85 | "typescript": "^4.2.4", 86 | "url-loader": "^4.1.1", 87 | "webpack": "^5.97.1", 88 | "webpack-cli": "^5.1.4" 89 | }, 90 | "config": { 91 | "commitizen": { 92 | "path": "./node_modules/cz-conventional-changelog" 93 | } 94 | }, 95 | "lint-staged": { 96 | "*.ts": "eslint --cache --cache-location .eslintcache --fix" 97 | }, 98 | "release": { 99 | "branches": [ 100 | "main" 101 | ] 102 | }, 103 | "directories": { 104 | "test": "test" 105 | } 106 | } -------------------------------------------------------------------------------- /src/hedera.ts: -------------------------------------------------------------------------------- 1 | import JSZip from 'jszip'; 2 | import Hammer from 'hammerjs'; 3 | // @ts-ignore 4 | import '../core/edera_0.1.js'; 5 | // @ts-ignore 6 | // @jes 7 | import wasmBase64 from '../core/edera_0.1.wasm'; 8 | import { IHedera } from './model/ihedera'; 9 | import { IHederaCoreEntity } from './model/ihederacoreentity'; 10 | import { IHederaEntity } from './model/ihederaentity'; 11 | import { ISceneDescriptor } from './model/iscenedescriptor'; 12 | import { IWorld } from './model/iworld'; 13 | 14 | export default class HederaJS { 15 | private static canvas: HTMLCanvasElement; 16 | private static rect: DOMRect; 17 | private static mapEntities: Map = new Map(); 18 | private static cameraType: 'orbit' | 'fpv' = 'orbit'; 19 | 20 | private static mousedownHandlers: (( 21 | e: MouseEvent, 22 | canvasX: number, 23 | canvasY: number 24 | ) => void)[] = []; 25 | private static mouseupHandlers: (( 26 | e: MouseEvent, 27 | canvasX: number, 28 | canvasY: number 29 | ) => void)[] = []; 30 | private static mousemoveHandlers: (( 31 | e: MouseEvent, 32 | canvasX: number, 33 | canvasY: number 34 | ) => void)[] = []; 35 | private static mousewheelHandlers: (( 36 | e: WheelEvent | any, 37 | canvasX: number, 38 | canvasY: number 39 | ) => void)[] = []; 40 | 41 | private static hexToRgb(hex: string) { 42 | const matches = hex 43 | .replace( 44 | /^#?([a-f\d])([a-f\d])([a-f\d])$/i, 45 | (m, r: number, g: number, b: number) => `#${r}${r}${g}${g}${b}${b}` 46 | ) 47 | .substring(1) 48 | .match(/.{2}/g); 49 | if (!matches) { 50 | return [0, 0, 0]; 51 | } 52 | return matches.map(x => parseInt(x, 16) / 255); 53 | } 54 | 55 | private static base64ToBlob(base64: string, type = 'application/wasm') { 56 | const binaryString = atob(base64); 57 | const len = binaryString.length; 58 | const bytes = new Uint8Array(len); 59 | for (let i = 0; i < len; i++) { 60 | bytes[i] = binaryString.charCodeAt(i); 61 | } 62 | return new Blob([bytes], { type }); 63 | } 64 | 65 | private static enableCameraMovement() { 66 | let currentMouseDragX = 0; 67 | let currentMouseDragY = 0; 68 | let lastMouseDragX = 0; 69 | let lastMouseDragY = 0; 70 | let cameraZoom = 0.2; //Max: 3 71 | let zoomIn: boolean; 72 | let draggingEntity: IHederaEntity | undefined; 73 | 74 | // Intercetta i click 75 | HederaJS.onMouseDown((e: MouseEvent, canvasX: number, canvasY: number) => { 76 | if (e.buttons === 1) { 77 | lastMouseDragX = canvasX; 78 | lastMouseDragY = canvasY; 79 | const entitiesDragEnabled = Array.from( 80 | HederaJS.mapEntities.values() 81 | ).filter(entity => entity.dragEnabled); 82 | for (const entity of entitiesDragEnabled) { 83 | const coreEntity = HederaJS.getCoreEntity(entity.id); 84 | if (!coreEntity) { 85 | continue; 86 | } 87 | const entityTrovato = HederaJS.world.selectOnScreen( 88 | coreEntity, 89 | canvasX, 90 | canvasY 91 | ); 92 | if (entityTrovato) { 93 | console.log('Entity trovato: ', entity); 94 | draggingEntity = entity; 95 | } 96 | } 97 | } 98 | }); 99 | 100 | // Intercetta il rilascio del click 101 | HederaJS.onMouseUp(() => { 102 | draggingEntity = undefined; 103 | }); 104 | 105 | // Intercetta il movimento del mouse 106 | HederaJS.onMouseMove((e: MouseEvent, canvasX: number, canvasY: number) => { 107 | // Questo setTimeout serve per evitare che l'evento onMouseMove venga chiamato prima del onMouseDown 108 | // Serve ad evitare il glitch grafico che si verifica quando si clicca e si muove il mouse velocemente 109 | setTimeout(() => { 110 | // Sto muovendo il mouse e ho il tasto sinistro cliccato 111 | currentMouseDragX = canvasX - lastMouseDragX; 112 | currentMouseDragY = canvasY - lastMouseDragY; 113 | lastMouseDragX = canvasX; 114 | lastMouseDragY = canvasY; 115 | 116 | let x = currentMouseDragX * HederaJS.deltaTime; 117 | let y = currentMouseDragY * HederaJS.deltaTime; 118 | 119 | if (e.buttons === 1) { 120 | if (draggingEntity) { 121 | x *= 0.07; 122 | y *= 0.07; 123 | HederaJS.translateEntity(draggingEntity.id, -x, -y, 0); 124 | } else { 125 | if (HederaJS.cameraType === 'orbit') { 126 | HederaJS.world.translateCameraOrbitView(x, y, 14); 127 | HederaJS.world.updateCameraOrbitView(); 128 | } else { 129 | x *= 0.21; 130 | y *= 0.21; 131 | HederaJS.world.rotateCameraFirstPersonView(-x, -y, 0, 30); 132 | HederaJS.world.updateCameraFirstPersonView(1); 133 | } 134 | } 135 | } 136 | }, 10); 137 | }); 138 | 139 | HederaJS.onMouseWheel((e: WheelEvent) => { 140 | zoomIn = e.deltaY < 0; 141 | 142 | if (zoomIn && cameraZoom < 3) { 143 | cameraZoom += 0.2; 144 | } else if (!zoomIn && cameraZoom > 0.2) { 145 | cameraZoom -= 0.2; 146 | } 147 | 148 | if (HederaJS.cameraType === 'orbit') { 149 | HederaJS.world.setTargetCameraOrbitView(0, 0, 0, cameraZoom + 20); 150 | HederaJS.world.updateCameraOrbitView(); 151 | } else { 152 | /* 153 | Calcolo del fov partendo dal cameraZoom: 154 | CameraZoom 0.2 -> fov 48 155 | CameraZoom 3 -> fov 90 156 | */ 157 | const fov = 90 - (cameraZoom - 0.2) * 15; 158 | HederaJS.world.setCameraProjectionPerspective(fov, 0.1, 300); 159 | HederaJS.world.updateCameraFirstPersonView(0); 160 | } 161 | }); 162 | 163 | const hammerCanvas = new Hammer(HederaJS.canvas, {}); 164 | hammerCanvas.get('pinch').set({ enable: true }); 165 | hammerCanvas.on('pinch', (event: HammerInput) => { 166 | if (event.scale > 1 && cameraZoom < 3) { 167 | cameraZoom += (event.scale - 1) * 0.2; 168 | } else if (event.scale < 1 && cameraZoom > 0.2) { 169 | cameraZoom -= (1 - event.scale) * 0.2; 170 | } 171 | 172 | if (HederaJS.cameraType === 'orbit') { 173 | HederaJS.world.setTargetCameraOrbitView(0, 0, 0, cameraZoom + 20); 174 | HederaJS.world.updateCameraOrbitView(); 175 | } else { 176 | /* 177 | Calcolo del fov partendo dal cameraZoom: 178 | CameraZoom 0.2 -> fov 48 179 | CameraZoom 3 -> fov 90 180 | */ 181 | const fov = 90 - (cameraZoom - 0.2) * 15; 182 | HederaJS.world.setCameraProjectionPerspective(fov, 0.1, 300); 183 | HederaJS.world.updateCameraFirstPersonView(0); 184 | } 185 | }); 186 | } 187 | 188 | private static loadAssets(params: { 189 | arrayBuffer: ArrayBuffer; 190 | type: string; 191 | }) { 192 | HederaJS.world.mainLoopPause(); 193 | 194 | if (params.type === 'HPK') { 195 | HederaJS.world.loadHAssetsHpk(params.arrayBuffer); 196 | } else if (params.type === 'ZIP') { 197 | HederaJS.world.loadHAassetsArchive(params.arrayBuffer); 198 | } else { 199 | console.error('Invalid asset type:' + params.type); 200 | } 201 | HederaJS.world.enableOnScreenSelection(); 202 | HederaJS.world.mainLoopResume(); 203 | } 204 | 205 | private static loadEntities(jsonData: ISceneDescriptor) { 206 | this.mapEntities.clear(); 207 | for (const room of jsonData.collectors.rooms) { 208 | const coreEntity = HederaJS.getCoreEntity(room.id); 209 | if (coreEntity) { 210 | this.mapEntities.set(coreEntity.id, { 211 | id: room.id, 212 | position: { x: 0, y: 0 }, 213 | rotation: { 214 | x: room.rotation[0], 215 | y: room.rotation[1], 216 | z: room.rotation[2], 217 | }, 218 | scale: { x: room.size, y: room.size, z: room.size }, 219 | distance: undefined, 220 | alpha: 1, 221 | visible: room.renderable, 222 | dragEnabled: false, 223 | }); 224 | } 225 | } 226 | for (const widget of jsonData.collectors.widgets) { 227 | const coreEntity = HederaJS.getCoreEntity(widget.id); 228 | if (coreEntity) { 229 | this.mapEntities.set(coreEntity.id, { 230 | id: widget.id, 231 | position: { x: widget.position[0], y: widget.position[1] }, 232 | rotation: { x: 0, y: 0, z: 0 }, 233 | scale: { x: widget.size[0], y: widget.size[1], z: widget.size[2] }, 234 | distance: widget.distance, 235 | alpha: 1, 236 | visible: widget.renderable, 237 | dragEnabled: false, 238 | }); 239 | } 240 | } 241 | for (const spritesheet of jsonData.collectors.spritesheets) { 242 | const coreEntity = HederaJS.getCoreEntity(spritesheet.id); 243 | if (coreEntity) { 244 | this.mapEntities.set(coreEntity.id, { 245 | id: spritesheet.id, 246 | position: { x: spritesheet.sprite[0], y: spritesheet.sprite[1] }, 247 | rotation: { x: 0, y: 0, z: 0 }, 248 | scale: { x: spritesheet.size[0], y: spritesheet.size[1], z: 1 }, 249 | distance: undefined, 250 | alpha: 1, 251 | visible: spritesheet.renderable, 252 | dragEnabled: false, 253 | }); 254 | } 255 | } 256 | } 257 | 258 | /** 259 | * @param id - Entity ID 260 | * @returns {id: number} - Entity Core of HederaJS, contains only the internal id of Hedera 261 | */ 262 | private static getCoreEntity(id: string): IHederaCoreEntity | undefined { 263 | const coreEntity = HederaJS.world.getEntity(id); 264 | if (!coreEntity || coreEntity.id === undefined || coreEntity.id == null) { 265 | console.error('Core Entity not found for id: ' + id); 266 | } 267 | return coreEntity; 268 | } 269 | 270 | /************************************************ PUBLIC *********************************************/ 271 | 272 | public static Hedera: IHedera = (window as { [key: string]: any })['Hedera']; 273 | public static world: IWorld; 274 | 275 | /** 276 | * Returns the time elapsed since the last frame 277 | */ 278 | public static get deltaTime(): number { 279 | return HederaJS.Hedera.delta_time; 280 | } 281 | 282 | /** 283 | * Associate a function to be performed with each frame of Hedera rendering 284 | */ 285 | public static onUpdate(updateHandler: () => void): void { 286 | HederaJS.Hedera.update = updateHandler; 287 | } 288 | 289 | /** 290 | * Associates a function to be performed on mouse button release or touchend from mobile 291 | */ 292 | public static onMouseUp( 293 | mouseupHandler: (e: MouseEvent, canvasX: number, canvasY: number) => void 294 | ): void { 295 | HederaJS.mouseupHandlers.push(mouseupHandler); 296 | } 297 | 298 | /** 299 | * Associates a function to be performed with mouse button click or touchstart from mobile 300 | */ 301 | public static onMouseDown( 302 | mousedownHandler: (e: MouseEvent, canvasX: number, canvasY: number) => void 303 | ): void { 304 | HederaJS.mousedownHandlers.push(mousedownHandler); 305 | } 306 | 307 | /** 308 | * Associates a function to be performed with mouse movement or touchmove from mobile 309 | */ 310 | public static onMouseMove( 311 | mousemoveHandler: (e: MouseEvent, canvasX: number, canvasY: number) => void 312 | ): void { 313 | HederaJS.mousemoveHandlers.push(mousemoveHandler); 314 | } 315 | 316 | /** 317 | * Associates a function to be performed with the rotation of the mouse wheel or pinch from mobile 318 | */ 319 | public static onMouseWheel( 320 | mousewheelHandler: ( 321 | e: WheelEvent | any, 322 | canvasX: number, 323 | canvasY: number 324 | ) => void 325 | ): void { 326 | HederaJS.mousewheelHandlers.push(mousewheelHandler); 327 | } 328 | 329 | /** 330 | * HederaJS initialization, to be called only once at application startup 331 | */ 332 | public static async init(canvas: HTMLCanvasElement): Promise { 333 | return new Promise(function (resolve) { 334 | canvas.getContext('webgl', { premultipliedAlpha: true }); 335 | canvas.addEventListener('contextmenu', e => e.preventDefault()); 336 | HederaJS.canvas = canvas; 337 | 338 | const base64Clean = (wasmBase64 as string).replace( 339 | /^data:[a-zA-Z]*\/[a-zA-Z]*;base64,/, 340 | '' 341 | ); 342 | const wasmBlob = HederaJS.base64ToBlob(base64Clean); 343 | const wasmUrl = URL.createObjectURL(wasmBlob); 344 | 345 | HederaJS.Hedera = (window as { [key: string]: any })['Hedera']; 346 | HederaJS.Hedera.start(canvas, wasmUrl); 347 | window.addEventListener('on_hedera_startup', () => { 348 | HederaJS.Hedera = (window as { [key: string]: any })['Hedera']; 349 | HederaJS.Hedera.update = () => undefined; 350 | resolve(HederaJS.Hedera); 351 | HederaJS.rect = canvas.getBoundingClientRect(); 352 | }); 353 | 354 | window.addEventListener( 355 | 'resize', 356 | () => { 357 | HederaJS.canvas.width = 358 | HederaJS.canvas.clientWidth * window.devicePixelRatio; 359 | HederaJS.canvas.height = 360 | HederaJS.canvas.clientHeight * window.devicePixelRatio; 361 | HederaJS.rect = canvas.getBoundingClientRect(); 362 | }, 363 | true 364 | ); 365 | }); 366 | } 367 | 368 | /** 369 | * Starting HederaJS, make sure you have called the HederaJS.init method first. 370 | * @param cameraType Type of camera to be used, 'orbit' for orbital camera, 'fpv' for first-person camera 371 | */ 372 | public static start(cameraType: 'orbit' | 'fpv'): void { 373 | HederaJS.canvas.width = 374 | HederaJS.canvas.clientWidth * window.devicePixelRatio; 375 | HederaJS.canvas.height = 376 | HederaJS.canvas.clientHeight * window.devicePixelRatio; 377 | /*const gl = HederaJS.canvas.getContext('webgl', { 378 | premultipliedAlpha: false, 379 | });*/ 380 | 381 | const scaleX = HederaJS.canvas.width / HederaJS.rect.width; 382 | const scaleY = HederaJS.canvas.height / HederaJS.rect.height; 383 | 384 | HederaJS.canvas.addEventListener('mousedown', (e: MouseEvent) => { 385 | const canvasX = (e.x - HederaJS.rect.left) * scaleX; 386 | const canvasY = (e.y - HederaJS.rect.top) * scaleY; 387 | HederaJS.mousedownHandlers.forEach(mouseDownHandler => { 388 | mouseDownHandler(e, canvasX, canvasY); 389 | }); 390 | }); 391 | 392 | HederaJS.canvas.addEventListener('touchstart', (e: TouchEvent) => { 393 | HederaJS.mousedownHandlers.forEach(mouseDownHandler => { 394 | const canvasX = (e.touches[0].clientX - HederaJS.rect.left) * scaleX; 395 | const canvasY = (e.touches[0].clientY - HederaJS.rect.top) * scaleY; 396 | const mouseEvent = new MouseEvent('mousedown', { 397 | ...e, 398 | buttons: 1, 399 | clientX: e.touches[0].clientX, 400 | clientY: e.touches[0].clientY, 401 | }); 402 | mouseDownHandler(mouseEvent, canvasX, canvasY); 403 | }); 404 | }); 405 | 406 | HederaJS.canvas.addEventListener('mouseup', (e: MouseEvent) => { 407 | const canvasX = (e.x - HederaJS.rect.left) * scaleX; 408 | const canvasY = (e.y - HederaJS.rect.top) * scaleY; 409 | HederaJS.mouseupHandlers.forEach(mouseUpHandler => { 410 | mouseUpHandler(e, canvasX, canvasY); 411 | }); 412 | }); 413 | 414 | HederaJS.canvas.addEventListener('touchend', (e: TouchEvent) => { 415 | HederaJS.mouseupHandlers.forEach(mouseUpHandler => { 416 | const canvasX = 417 | (e.changedTouches[0].clientX - HederaJS.rect.left) * scaleX; 418 | const canvasY = 419 | (e.changedTouches[0].clientY - HederaJS.rect.top) * scaleY; 420 | const mouseEvent = new MouseEvent('mouseup', { 421 | ...e, 422 | buttons: 1, 423 | clientX: e.changedTouches[0].clientX, 424 | clientY: e.changedTouches[0].clientY, 425 | }); 426 | mouseUpHandler(mouseEvent, canvasX, canvasY); 427 | }); 428 | }); 429 | 430 | HederaJS.canvas.addEventListener('mousemove', (e: MouseEvent) => { 431 | const canvasX = (e.x - HederaJS.rect.left) * scaleX; 432 | const canvasY = (e.y - HederaJS.rect.top) * scaleY; 433 | HederaJS.mousemoveHandlers.forEach(mouseMoveHandler => { 434 | mouseMoveHandler(e, canvasX, canvasY); 435 | }); 436 | }); 437 | 438 | HederaJS.canvas.addEventListener('touchmove', (e: TouchEvent) => { 439 | const canvasX = (e.touches[0].clientX - HederaJS.rect.left) * scaleX; 440 | const canvasY = (e.touches[0].clientY - HederaJS.rect.top) * scaleY; 441 | HederaJS.mousemoveHandlers.forEach(mouseMoveHandler => { 442 | const mouseEvent = new MouseEvent('mousemove', { 443 | ...e, 444 | buttons: 1, 445 | clientX: e.touches[0].clientX, 446 | clientY: e.touches[0].clientY, 447 | }); 448 | if (e.touches.length === 1) { 449 | mouseMoveHandler(mouseEvent, canvasX, canvasY); 450 | } 451 | }); 452 | }); 453 | 454 | HederaJS.canvas.addEventListener('wheel', (e: WheelEvent) => { 455 | const canvasX = (e.x - HederaJS.rect.left) * scaleX; 456 | const canvasY = (e.y - HederaJS.rect.top) * scaleY; 457 | HederaJS.mousewheelHandlers.forEach(mousewheelHandler => { 458 | mousewheelHandler(e, canvasX, canvasY); 459 | }); 460 | }); 461 | 462 | const hammerCanvas = new Hammer(HederaJS.canvas, {}); 463 | hammerCanvas.on('pinch', (event: HammerInput) => { 464 | const canvasX = (event.center.x - HederaJS.rect.left) * scaleX; 465 | const canvasY = (event.center.y - HederaJS.rect.top) * scaleY; 466 | HederaJS.mousewheelHandlers.forEach(mousewheelHandler => { 467 | mousewheelHandler(event, canvasX, canvasY); 468 | }); 469 | }); 470 | 471 | HederaJS.Hedera.update(); 472 | // eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access 473 | HederaJS.world = new (HederaJS.Hedera as any).World(); 474 | HederaJS.cameraType = cameraType; 475 | 476 | HederaJS.enableCameraMovement(); 477 | if (cameraType === 'orbit') { 478 | HederaJS.world.setTargetCameraOrbitView(0, 0, 0, 20); 479 | HederaJS.world.setCameraProjectionPerspective(45, 0.1, 300); 480 | HederaJS.world.updateCameraOrbitView(); 481 | } else { 482 | HederaJS.world.setRotationCameraFirstPersonView(0, 0, 0, 0); 483 | HederaJS.world.setCameraProjectionPerspective(45, 0.1, 300); 484 | HederaJS.world.updateCameraFirstPersonView(1); 485 | } 486 | } 487 | 488 | /** 489 | * Load a scene into HederaJS. 490 | * @param jsonData JSON file containing the description of the scene ( images, 3D models, rooms, widgets, spritesheets ) 491 | * @param assetsUrls Array of strings containing the URLs of the assets to be loaded. 492 | * @returns {Promise} - True if the scene has been loaded correctly, false otherwise 493 | */ 494 | public static async loadScene( 495 | jsonData: ISceneDescriptor, 496 | assetsUrls: string[] 497 | ): Promise { 498 | const zip = new JSZip(); 499 | zip.file('data.json', JSON.stringify(jsonData)); 500 | for (const url of assetsUrls) { 501 | const filename = url.split('/').pop(); 502 | const file = await fetch(url); 503 | if (filename && file.ok) { 504 | zip.file(filename, file.blob()); 505 | } else { 506 | console.error('Error loading file: ' + url); 507 | return false; 508 | } 509 | } 510 | const zipHedera = await zip.generateAsync({ type: 'arraybuffer' }); 511 | this.loadAssets({ arrayBuffer: zipHedera, type: 'ZIP' }); 512 | this.loadEntities(jsonData); 513 | return true; 514 | } 515 | 516 | /** 517 | * Load a scene into HederaJS from a ZIP file. 518 | * @param zipFileArrayBuffer ArrayBuffer containing the ZIP file. 519 | * 520 | * zip file must contain: 521 | * - data.json: JSON file containing the description of the scene ( images, 3D models, rooms, widgets, spritesheets ) 522 | * - assets in the root of the zip file 523 | * 524 | * @returns {Promise} - True if the scene has been loaded correctly, false otherwise 525 | */ 526 | public static async loadSceneArchive( 527 | zipFileArrayBuffer: ArrayBuffer 528 | ): Promise { 529 | const zip = new JSZip(); 530 | const zipFile = await zip.loadAsync(zipFileArrayBuffer); 531 | const jsonDataFile = zipFile.file('data.json'); 532 | if (jsonDataFile) { 533 | const jsonData = JSON.parse(await jsonDataFile.async('text')); 534 | this.loadAssets({ arrayBuffer: zipFileArrayBuffer, type: 'ZIP' }); 535 | this.loadEntities(jsonData); 536 | return true; 537 | } 538 | console.error('data.json not found in ZIP file'); 539 | return false; 540 | } 541 | 542 | /** 543 | * Associate a function to execute to the hover on a widget. 544 | * @param widgetId Id of the widget on which to intercept the hover. 545 | * @param callback Function to execute when event occurs, parameter passed: widgetTried: {id} of widget found or null and, mouseEvent 546 | */ 547 | public static handleEntityHover( 548 | callback: ( 549 | idEntityTrovata: IHederaEntity | undefined, 550 | mouseEvent: MouseEvent 551 | ) => void 552 | ): void { 553 | HederaJS.onMouseMove((mouseEvent: MouseEvent) => { 554 | const scaleX = HederaJS.canvas.width / HederaJS.rect.width; 555 | const scaleY = HederaJS.canvas.height / HederaJS.rect.height; 556 | const canvasX = (mouseEvent.x - HederaJS.rect.left) * scaleX; 557 | const canvasY = (mouseEvent.y - HederaJS.rect.top) * scaleY; 558 | const entity = HederaJS.world.searchOnScreen(canvasX, canvasY); 559 | if (entity) { 560 | callback(this.mapEntities.get(entity.id), mouseEvent); 561 | } else { 562 | callback(undefined, mouseEvent); 563 | } 564 | }); 565 | } 566 | 567 | /** 568 | * Associate a function to be executed when a widget is clicked on. 569 | * @param widgetId Id of the widget to intercept the click on. 570 | * @param callback Function to execute when event occurs, parameter passed: widgetTried: 0|1 if widget was found at mouse position, mouseEvent 571 | */ 572 | public static handleWidgetMouseDown( 573 | widgetId: string, 574 | callback: (widgetTrovato: number, mouseEvent: MouseEvent) => void 575 | ): void { 576 | const coreEntity = HederaJS.getCoreEntity(widgetId); 577 | if (coreEntity) { 578 | HederaJS.onMouseDown( 579 | (e: MouseEvent, canvasX: number, canvasY: number) => { 580 | const widgetTrovato = HederaJS.world.selectOnScreen( 581 | coreEntity, 582 | canvasX, 583 | canvasY 584 | ); 585 | callback(widgetTrovato, e); 586 | } 587 | ); 588 | } 589 | } 590 | 591 | /** 592 | * Associate a function to execute on click release on a widget. 593 | * @param widgetId Id of widget on which to intercept click release. 594 | * @param callback Function to execute when event occurs, parameter passed: widgetTried: 0|1 if widget was found at mouse position, mouseEvent 595 | */ 596 | public static handleWidgetMouseUp( 597 | widgetId: string, 598 | callback: (widgetTrovato: number, mouseEvent: MouseEvent) => void 599 | ): void { 600 | const coreEntity = HederaJS.getCoreEntity(widgetId); 601 | if (coreEntity) { 602 | HederaJS.onMouseUp((e: MouseEvent, canvasX: number, canvasY: number) => { 603 | const widgetTrovato = HederaJS.world.selectOnScreen( 604 | coreEntity, 605 | canvasX, 606 | canvasY 607 | ); 608 | callback(widgetTrovato, e); 609 | }); 610 | } 611 | } 612 | 613 | /** 614 | * Sets the camera view. 615 | * @param x position for orbital camera, yaw (yaw) for first-person camera (0-360) 616 | * @param y position y for orbital camera, pitch (pitch) for first-person camera (0-360) 617 | * @param z position z for orbital chamber, roll for first-person chamber (0-360) 618 | */ 619 | public static setCameraPosition(x: number, y: number, z = 0): void { 620 | if (HederaJS.cameraType === 'orbit') { 621 | HederaJS.world.setTargetCameraOrbitView(x, -y, z, 1); 622 | HederaJS.world.updateCameraOrbitView(); 623 | } else { 624 | HederaJS.world.setRotationCameraFirstPersonView(x, -y, z, 1); 625 | HederaJS.world.updateCameraFirstPersonView(1); 626 | } 627 | } 628 | 629 | /** 630 | * Sets the field of view of the camera (adjusts the zoom of the view). 631 | * @param fov Value of the field of view, between 0 and 360 632 | */ 633 | public static setCameraFov(fov: number): void { 634 | HederaJS.world.setCameraProjectionPerspective(fov, 0.1, 300); 635 | if (HederaJS.cameraType === 'orbit') { 636 | HederaJS.world.updateCameraOrbitView(); 637 | } else { 638 | HederaJS.world.updateCameraFirstPersonView(1); 639 | } 640 | } 641 | 642 | /** 643 | * Returns an entity from its id. 644 | * @param entityId Entity ID. 645 | * @returns {IHederaEntity} Entity 646 | */ 647 | public static getEntity(entityId: string): IHederaEntity | undefined { 648 | const coreEntity = HederaJS.getCoreEntity(entityId); 649 | if (coreEntity) { 650 | return this.mapEntities.get(coreEntity.id); 651 | } 652 | return undefined; 653 | } 654 | 655 | /** 656 | * Adds an entity to the rendering queue, making it visible. 657 | * @param widgetId Entity ID. 658 | */ 659 | public static showEntity(entityId: string): void { 660 | const coreEntity = HederaJS.getCoreEntity(entityId); 661 | if (coreEntity) { 662 | const entity = this.mapEntities.get(coreEntity.id); 663 | if (entity) { 664 | entity.visible = true; 665 | } 666 | HederaJS.world.attachComponentRenderer(coreEntity); 667 | } 668 | } 669 | 670 | /** 671 | * Removes an entity from the rendering queue, making it invisible. 672 | * @param widgetId Entity ID. 673 | */ 674 | public static hideEntity(entityId: string): void { 675 | const coreEntity = HederaJS.getCoreEntity(entityId); 676 | if (coreEntity) { 677 | const entity = this.mapEntities.get(coreEntity.id); 678 | if (entity) { 679 | entity.visible = false; 680 | } 681 | HederaJS.world.removeComponentRenderer(coreEntity); 682 | } 683 | } 684 | 685 | /** 686 | * Set the color of an entity 687 | * @param entityId Entity ID. 688 | * @param color Color in hexadecimal format (#RRGGBB). 689 | * @param alpha Color transparency (0-1) 690 | */ 691 | public static setEntityColor( 692 | entityId: string, 693 | color: string, 694 | alpha = 1 695 | ): void { 696 | const coreEntity = HederaJS.getCoreEntity(entityId); 697 | if (coreEntity) { 698 | const [r, g, b] = this.hexToRgb(color); 699 | HederaJS.world.changeComponentColor(coreEntity, 0, r, g, b, alpha); 700 | } 701 | } 702 | 703 | /** 704 | * Set transparency of an entity 705 | * @param entityId Entity ID 706 | * @param alpha Transparency value (0-1) 707 | */ 708 | public static setEntityAlpha(entityId: string, alpha: number): void { 709 | const coreEntity = HederaJS.getCoreEntity(entityId); 710 | if (coreEntity) { 711 | const entity = this.mapEntities.get(coreEntity.id); 712 | if (entity) { 713 | entity.alpha = alpha; 714 | } 715 | HederaJS.world.changeComponentAlpha(coreEntity, 0, alpha); 716 | } 717 | } 718 | 719 | /** 720 | * Sets the size of an entity. 721 | * @param entityId Entity ID. 722 | * @param scale Scale value (0-X): 1 = original size, 2 = double size, 0.5 = half size 723 | */ 724 | public static setEntitySize(entityId: string, scale: number): void { 725 | const coreEntity = HederaJS.getCoreEntity(entityId); 726 | if (coreEntity) { 727 | const entity = this.mapEntities.get(coreEntity.id); 728 | if (entity) { 729 | entity.scale = { x: scale, y: scale, z: scale }; 730 | } 731 | HederaJS.world.setScale(coreEntity, scale, scale, scale); 732 | } else { 733 | console.error('Entity not found for id: ' + entityId); 734 | } 735 | } 736 | 737 | /** 738 | * Sets the position of an entity, in the case of orbital chamber, sets the spherical position, otherwise the spatial position 739 | * @param entityId Entity ID. 740 | * @param x Position x - if not specified, keeps current position 741 | * @param y Position y - if not specified, keeps current position 742 | * @param distance Distance - if not specified, keeps current distance 743 | */ 744 | public static setEntityPosition( 745 | entityId: string, 746 | x?: number, 747 | y?: number, 748 | distance?: number 749 | ): void { 750 | const coreEntity = HederaJS.getCoreEntity(entityId); 751 | if (coreEntity) { 752 | const entity = this.mapEntities.get(coreEntity.id); 753 | if (entity) { 754 | entity.position.x = x ?? entity.position.x; 755 | entity.position.y = y ?? entity.position.y; 756 | entity.distance = distance ?? entity.distance; 757 | HederaJS.world.setSphericalPosition( 758 | coreEntity, 759 | entity.position.x, 760 | entity.position.y, 761 | distance || 0 762 | ); 763 | } 764 | } 765 | } 766 | 767 | /** 768 | * Moves an entity by a specified quantity. 769 | * @param entityId Entity ID 770 | * @param x Move x 771 | * @param y Move y 772 | * @param distance Increment 773 | */ 774 | public static translateEntity( 775 | entityId: string, 776 | x = 0, 777 | y = 0, 778 | distance = 0 779 | ): void { 780 | const coreEntity = HederaJS.getCoreEntity(entityId); 781 | if (coreEntity) { 782 | const entity = this.mapEntities.get(coreEntity.id); 783 | if (entity) { 784 | entity.position.x += x; 785 | entity.position.y += y; 786 | if (entity.distance) { 787 | entity.distance += distance; 788 | } 789 | HederaJS.world.setSphericalPosition( 790 | coreEntity, 791 | entity.position.x, 792 | entity.position.y, 793 | entity.distance || 0 794 | ); 795 | } 796 | } 797 | } 798 | 799 | /** 800 | * Sets the position of an entity, in the case of orbital chamber, sets the spherical position, otherwise the spatial position 801 | * @param entityId Entity ID. 802 | * @param x Rotation x yaw (yaw) (0-360) - if not specified, keeps current rotation 803 | * @param y Rotation y pitch (pitch) (0-360) - if not specified, retains current rotation 804 | * @param z Rotation z roll (roll) (0-360) - if not specified, keeps current rotation 805 | */ 806 | public static setEntityRotation( 807 | entityId: string, 808 | x?: number, 809 | y?: number, 810 | z?: number 811 | ): void { 812 | const coreEntity = HederaJS.getCoreEntity(entityId); 813 | if (coreEntity) { 814 | const entity = this.mapEntities.get(coreEntity.id); 815 | if (entity) { 816 | entity.rotation.x = x ?? entity.rotation.x; 817 | entity.rotation.y = y ?? entity.rotation.y; 818 | entity.rotation.z = z ?? entity.rotation.z; 819 | HederaJS.world.setRotation( 820 | coreEntity, 821 | entity.rotation.x, 822 | entity.rotation.y, 823 | entity.rotation.z 824 | ); 825 | } 826 | } 827 | } 828 | 829 | /** 830 | * Rotates an entity by a specified quantity. 831 | * @param entityId Entity ID 832 | * @param x Rotation x yaw (yaw). 833 | * @param y Rotation y pitch (pitch). 834 | * @param z Rotation z roll (roll). 835 | */ 836 | public static rotateEntity(entityId: string, x = 0, y = 0, z = 0): void { 837 | const coreEntity = HederaJS.getCoreEntity(entityId); 838 | if (coreEntity) { 839 | const entity = this.mapEntities.get(coreEntity.id); 840 | if (entity) { 841 | HederaJS.world.rotate( 842 | coreEntity, 843 | entity.rotation.x, 844 | entity.rotation.y, 845 | entity.rotation.z 846 | ); 847 | entity.rotation.x += x; 848 | entity.rotation.y += y; 849 | entity.rotation.z += z; 850 | } 851 | } 852 | } 853 | 854 | /** 855 | * Set an entity as draggable by holding down the left mouse button. 856 | * @param entityId Entity ID. 857 | * @param dragEnabled Enables or disables entity drag. 858 | */ 859 | public static setEntityDraggable( 860 | entityId: string, 861 | dragEnabled: boolean 862 | ): void { 863 | const coreEntity = HederaJS.getCoreEntity(entityId); 864 | if (!coreEntity) { 865 | return; 866 | } 867 | const entity = this.mapEntities.get(coreEntity.id); 868 | if (entity) { 869 | entity.dragEnabled = dragEnabled; 870 | } 871 | } 872 | } 873 | -------------------------------------------------------------------------------- /src/model/ihedera.ts: -------------------------------------------------------------------------------- 1 | import { IWorld } from './iworld'; 2 | 3 | export interface IHedera { 4 | World: IWorld; 5 | Entity: number; 6 | EventObject: any; 7 | delta_time: number; 8 | canvas: HTMLCanvasElement; 9 | setCanvas(canvas: HTMLCanvasElement): void; 10 | update(): void; 11 | start(canvas: HTMLCanvasElement, wasmLocation?: string): void; 12 | } 13 | -------------------------------------------------------------------------------- /src/model/ihederacoreentity.ts: -------------------------------------------------------------------------------- 1 | export interface IHederaCoreEntity { 2 | id: number; 3 | } 4 | -------------------------------------------------------------------------------- /src/model/ihederaentity.ts: -------------------------------------------------------------------------------- 1 | export interface IHederaEntity { 2 | id: string; 3 | position: { x: number; y: number }; 4 | rotation: { x: number; y: number; z: number }; 5 | scale: { x: number; y: number; z: number }; 6 | distance: number | undefined; 7 | alpha: number; 8 | visible: boolean; 9 | dragEnabled: boolean; 10 | } 11 | -------------------------------------------------------------------------------- /src/model/iscenedescriptor.ts: -------------------------------------------------------------------------------- 1 | export interface ISceneDescriptor { 2 | assets: { 3 | images: { 4 | id: string; 5 | type: 'img' | 'panorama'; 6 | ibl: 'low' | 'mid' | 'high'; 7 | alpha: 'opaque' | 'blend' | 'mask'; 8 | mask: number; 9 | url: number; 10 | }[]; 11 | glb: { 12 | url: number; 13 | }[]; 14 | url: string[]; 15 | }; 16 | collectors: { 17 | rooms: { 18 | id: string; 19 | image_id: string; 20 | size: number; 21 | renderable: boolean; 22 | rotation: number[]; 23 | }[]; 24 | widgets: { 25 | id: string; 26 | image_id: string; 27 | size: number[]; 28 | distance: number; 29 | position: number[]; 30 | off_scene: number; 31 | mask: number; 32 | renderable: boolean; 33 | }[]; 34 | spritesheets: { 35 | id: string; 36 | image_id: string; 37 | frames: number; 38 | sprite: number[]; 39 | size: number[]; 40 | renderable: boolean; 41 | }[]; 42 | }; 43 | } 44 | -------------------------------------------------------------------------------- /src/model/iworld.ts: -------------------------------------------------------------------------------- 1 | import { IHederaCoreEntity } from './ihederacoreentity'; 2 | 3 | export interface IWorld { 4 | mainLoopPause(): void; 5 | mainLoopResume(): void; 6 | loadHAssetsJson(json: string): void; 7 | loadHAssetsHpk(arraybuffer: ArrayBuffer): void; 8 | loadHAassetsArchive(arraybuffer: ArrayBuffer): void; 9 | attachComponentRenderer(entity: IHederaCoreEntity): void; 10 | removeComponentRenderer(entity: IHederaCoreEntity): void; 11 | setCameraProjectionPerspective( 12 | fildOfView: number, 13 | nearPlane: number, 14 | farPlane: number 15 | ): void; 16 | setRotationCameraFirstPersonView( 17 | yaw: number, 18 | pitch: number, 19 | roll: number, 20 | speed: number 21 | ): void; 22 | rotateCameraFirstPersonView( 23 | yaw: number, 24 | pitch: number, 25 | roll: number, 26 | speed: number 27 | ): void; 28 | updateCameraFirstPersonView(smooth: number): void; 29 | setTargetCameraOrbitView( 30 | x: number, 31 | y: number, 32 | z: number, 33 | distance: number 34 | ): void; 35 | translateCameraOrbitView(x: number, y: number, speed: number): void; 36 | updateCameraOrbitView(): void; 37 | getCameraYaw(): number; 38 | getCameraPitch(): number; 39 | getCameraRoll(): number; 40 | getEntity(name: string): IHederaCoreEntity | undefined; 41 | entityExists(name: string): boolean; 42 | translate(entity: IHederaCoreEntity, x: number, y: number, z: number): void; 43 | setPosition(entity: IHederaCoreEntity, x: number, y: number, z: number): void; 44 | setSphericalPosition( 45 | entity: IHederaCoreEntity, 46 | x: number, 47 | y: number, 48 | distance: number 49 | ): void; 50 | setSphericalPositionTarget( 51 | entity: IHederaCoreEntity, 52 | x: number, 53 | y: number, 54 | target: IHederaCoreEntity, 55 | distance: number 56 | ): void; 57 | rotate(entity: IHederaCoreEntity, x: number, y: number, z: number): void; 58 | setRotation(entity: IHederaCoreEntity, x: number, y: number, z: number): void; 59 | followView(entity: IHederaCoreEntity): void; 60 | firstPersonViewLift(t: number): void; 61 | firstPersonViewStrafe(t: number): void; 62 | firstPersonViewWalk(t: number): void; 63 | scale(entity: IHederaCoreEntity, x: number, y: number, z: number): void; 64 | setScale(entity: IHederaCoreEntity, x: number, y: number, z: number): void; 65 | spritesheetAnimate( 66 | entity: IHederaCoreEntity, 67 | rowSize: number, 68 | speed: number 69 | ): void; 70 | spritesheetAnimateRepeat( 71 | entity: IHederaCoreEntity, 72 | rowSize: number, 73 | speed: number 74 | ): void; 75 | spritesheetAnimateRow( 76 | entity: IHederaCoreEntity, 77 | rowSize: number, 78 | speed: number 79 | ): void; 80 | spritesheetAnimateRowRepeat( 81 | entity: IHederaCoreEntity, 82 | rowSize: number, 83 | speed: number 84 | ): void; 85 | spritesheetReset( 86 | entity: IHederaCoreEntity, 87 | rowSize: number, 88 | speed: number 89 | ): void; 90 | enableOnScreenSelection(): void; 91 | selectOnScreen(entity: IHederaCoreEntity, x: number, y: number): number; 92 | searchOnScreen(x: number, y: number): IHederaCoreEntity; 93 | changeComponentColor( 94 | entity: IHederaCoreEntity, 95 | primitiveIndex: number, 96 | r: number, 97 | g: number, 98 | b: number, 99 | a: number 100 | ): void; 101 | changeComponentAlpha( 102 | entity: IHederaCoreEntity, 103 | primitiveIndex: number, 104 | value: number 105 | ): void; 106 | getEntiyCameraAngleRad(entity: IHederaCoreEntity): number; 107 | getEntiyCameraAngleDeg(entity: IHederaCoreEntity): number; 108 | getEntiyEntityAngleRad( 109 | entity: IHederaCoreEntity, 110 | entity2: IHederaCoreEntity 111 | ): number; 112 | getEntiyEntityAngleDeg( 113 | entity: IHederaCoreEntity, 114 | entity2: IHederaCoreEntity 115 | ): number; 116 | } 117 | -------------------------------------------------------------------------------- /test/index.spec.d.ts: -------------------------------------------------------------------------------- 1 | export {}; 2 | -------------------------------------------------------------------------------- /test/index.spec.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @jest-environment jsdom 3 | */ 4 | /* eslint-disable @typescript-eslint/unbound-method */ 5 | import HederaJS from '../src/hedera'; 6 | 7 | describe('HederaJS', () => { 8 | describe('init', () => { 9 | it('HederaJS should be declared', () => { 10 | expect(HederaJS).toBeDefined(); 11 | }); 12 | it('HederaJS.init should be declared', () => { 13 | expect(HederaJS.init).toBeDefined(); 14 | }); 15 | it('HederaJS.start should be declared', () => { 16 | expect(HederaJS.start).toBeDefined(); 17 | }); 18 | it('HederaJS.onUpdate should be declared', () => { 19 | expect(HederaJS.onUpdate).toBeDefined(); 20 | }); 21 | it('HederaJS.onMouseDown should be declared', () => { 22 | expect(HederaJS.onMouseDown).toBeDefined(); 23 | }); 24 | it('HederaJS.onMouseMove should be declared', () => { 25 | expect(HederaJS.onMouseMove).toBeDefined(); 26 | }); 27 | it('HederaJS.loadScene should be declared', () => { 28 | expect(HederaJS.loadScene).toBeDefined(); 29 | }); 30 | }); 31 | }); 32 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Visit https://aka.ms/tsconfig.json to read more about this file */ 4 | 5 | /* Basic Options */ 6 | // "incremental": true, /* Enable incremental compilation */ 7 | "target": "ESNext" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */, 8 | "module": "commonjs" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */, 9 | "moduleResolution": "node" /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */, 10 | "allowSyntheticDefaultImports": true, 11 | // "lib": [], /* Specify library files to be included in the compilation. */ 12 | // "allowJs": true, /* Allow javascript files to be compiled. */ 13 | // "checkJs": true, /* Report errors in .js files. */ 14 | // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', 'react', 'react-jsx' or 'react-jsxdev'. */ 15 | "declaration": true /* Generates corresponding '.d.ts' file. */, 16 | // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ 17 | // "sourceMap": true, /* Generates corresponding '.map' file. */ 18 | // "outFile": "./", /* Concatenate and emit output to single file. */ 19 | "outDir": "./dist" /* Redirect output structure to the directory. */, 20 | "rootDir": "./src" /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */, 21 | // "composite": true, /* Enable project compilation */ 22 | // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ 23 | // "removeComments": true, /* Do not emit comments to output. */ 24 | // "noEmit": true, /* Do not emit outputs. */ 25 | // "importHelpers": true, /* Import emit helpers from 'tslib'. */ 26 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ 27 | // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ 28 | 29 | /* Strict Type-Checking Options */ 30 | "strict": true /* Enable all strict type-checking options. */, 31 | // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ 32 | // "strictNullChecks": true, /* Enable strict null checks. */ 33 | // "strictFunctionTypes": true, /* Enable strict checking of function types. */ 34 | // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ 35 | // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ 36 | // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ 37 | // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ 38 | 39 | /* Additional Checks */ 40 | // "noUnusedLocals": true, /* Report errors on unused locals. */ 41 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 42 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 43 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 44 | // "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */ 45 | // "noPropertyAccessFromIndexSignature": true, /* Require undeclared properties from index signatures to use element accesses. */ 46 | 47 | /* Module Resolution Options */ 48 | // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ 49 | // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ 50 | // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ 51 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ 52 | // "typeRoots": [], /* List of folders to include type definitions from. */ 53 | // "types": [], /* Type declaration files to be included in compilation. */ 54 | // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ 55 | "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */, 56 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ 57 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 58 | 59 | /* Source Map Options */ 60 | "sourceMap": true, 61 | // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ 62 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 63 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ 64 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ 65 | 66 | /* Experimental Options */ 67 | // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ 68 | // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ 69 | 70 | /* Advanced Options */ 71 | "skipLibCheck": true /* Skip type checking of declaration files. */, 72 | "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */ 73 | }, 74 | "include": [ 75 | "src/**/*.ts", 76 | "src/*.ts", 77 | "../package.json", 78 | "../README.md", 79 | "../LICENSE", 80 | "jest.config.js" 81 | ], 82 | "exclude": ["**/*.wasm", "__mocks__/*.js"] 83 | } 84 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const CopyPlugin = require('copy-webpack-plugin'); 3 | 4 | module.exports = { 5 | entry: './src/hedera.ts', 6 | output: { 7 | filename: 'hedera.js', 8 | path: path.resolve(__dirname, 'dist'), 9 | library: { 10 | name: 'HederaJS', 11 | type: 'umd', // Permette di esportare come UMD, CommonJS o ES module 12 | export: 'default', 13 | }, 14 | }, 15 | module: { 16 | rules: [ 17 | { 18 | test: /\.(js|jsx|tsx|ts)$/, 19 | exclude: /node_modules/, 20 | loader: 'babel-loader', 21 | }, 22 | { 23 | test: /\.wasm$/, 24 | use: { 25 | loader: 'url-loader', 26 | options: { 27 | limit: Infinity, // Converte tutto in Base64 28 | }, 29 | }, 30 | }, 31 | ], 32 | }, 33 | resolve: { 34 | extensions: ['*', '.js', '.jsx', '.tsx', '.ts'], 35 | fallback: { 36 | crypto: false, 37 | fs: false, 38 | path: false, 39 | }, 40 | }, 41 | devtool: 'source-map', 42 | mode: 'development', // Usa 'production' per la versione finale ottimizzata 43 | plugins: [ 44 | new CopyPlugin({ 45 | patterns: ['LICENSE', 'package.json', 'README.md'], 46 | }), 47 | ], 48 | }; 49 | --------------------------------------------------------------------------------