├── .github
├── FUNDING.yml
└── workflows
│ └── ci.yml
├── .gitignore
├── 3dof
├── .yarnrc.yml
├── .gitignore
├── rollup.config.js
├── LICENSE
├── package.json
├── README.md
├── src
│ └── index.js
└── example
│ └── index.html
├── fix-fog
├── .yarnrc.yml
├── .gitignore
├── rollup.config.js
├── src
│ └── index.js
├── LICENSE
├── package.json
├── README.md
└── example
│ └── index.html
├── hud
├── .yarnrc.yml
├── .gitignore
├── rollup.config.js
├── LICENSE
├── package.json
├── README.md
├── example
│ └── index.html
└── src
│ └── index.js
├── mirror
├── .yarnrc.yml
├── .gitignore
├── rollup.config.js
├── LICENSE
├── package.json
├── README.md
├── example
│ └── index.html
└── src
│ └── index.js
├── effekseer
├── .yarnrc.yml
├── .gitignore
├── src
│ ├── main.ts
│ ├── effekseer.component.ts
│ └── effekseer.system.ts
├── rollup.config.dev.js
├── tsconfig.json
├── rollup.config.prod.js
├── package.json
├── vendor
│ ├── README.md
│ └── effekseer.d.ts
├── README.md
└── example
│ └── index.html
├── extra-stats
├── .yarnrc.yml
├── .gitignore
├── src
│ ├── rStats.three.js
│ ├── index.js
│ └── rStats.three-alloc.js
├── rollup.config.js
├── LICENSE
├── package.json
├── example
│ └── index.html
└── README.md
├── highlight
├── .yarnrc.yml
├── .gitignore
├── rollup.config.js
├── LICENSE
├── package.json
├── example
│ └── index.html
├── README.md
└── src
│ └── index.js
├── screen-fade
├── .yarnrc.yml
├── .gitignore
├── rollup.config.js
├── tsconfig.json
├── types
│ └── screen-fade.d.ts
├── LICENSE
├── example
│ └── index.html
├── src
│ └── index.js
├── package.json
└── README.md
├── motion-controller
├── .yarnrc.yml
├── .gitignore
├── src
│ ├── main.ts
│ ├── hand-joint-names.ts
│ ├── motion-controller-space.component.ts
│ ├── utils.ts
│ ├── motion-controller.system.ts
│ └── motion-controller-model.component.ts
├── rollup.config.dev.js
├── tsconfig.json
├── README.md
├── rollup.config.prod.js
├── package.json
└── example
│ └── index.html
├── sky-background
├── .yarnrc.yml
├── .gitignore
├── rollup.config.js
├── tsconfig.json
├── LICENSE
├── example
│ └── index.html
├── package.json
├── types
│ └── sky-background.d.ts
├── README.md
└── src
│ └── index.js
├── .yarnrc.yml
├── package.json
├── scripts
├── README.md
├── build-gh-pages.sh
└── chunks
│ ├── github-corner-right.html
│ └── github-corner-left.html
└── README.md
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | ko_fi: fernsolutions
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | .yarn/
3 | dist/
--------------------------------------------------------------------------------
/3dof/.yarnrc.yml:
--------------------------------------------------------------------------------
1 | nodeLinker: node-modules
2 |
--------------------------------------------------------------------------------
/fix-fog/.yarnrc.yml:
--------------------------------------------------------------------------------
1 | nodeLinker: node-modules
2 |
--------------------------------------------------------------------------------
/hud/.yarnrc.yml:
--------------------------------------------------------------------------------
1 | nodeLinker: node-modules
2 |
--------------------------------------------------------------------------------
/mirror/.yarnrc.yml:
--------------------------------------------------------------------------------
1 | nodeLinker: node-modules
2 |
--------------------------------------------------------------------------------
/effekseer/.yarnrc.yml:
--------------------------------------------------------------------------------
1 | nodeLinker: node-modules
2 |
--------------------------------------------------------------------------------
/extra-stats/.yarnrc.yml:
--------------------------------------------------------------------------------
1 | nodeLinker: node-modules
2 |
--------------------------------------------------------------------------------
/highlight/.yarnrc.yml:
--------------------------------------------------------------------------------
1 | nodeLinker: node-modules
2 |
--------------------------------------------------------------------------------
/screen-fade/.yarnrc.yml:
--------------------------------------------------------------------------------
1 | nodeLinker: node-modules
2 |
--------------------------------------------------------------------------------
/highlight/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | .yarn/
3 | dist/
--------------------------------------------------------------------------------
/hud/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | .yarn/
3 | dist/
4 | site/
--------------------------------------------------------------------------------
/motion-controller/.yarnrc.yml:
--------------------------------------------------------------------------------
1 | nodeLinker: node-modules
2 |
--------------------------------------------------------------------------------
/sky-background/.yarnrc.yml:
--------------------------------------------------------------------------------
1 | nodeLinker: node-modules
2 |
--------------------------------------------------------------------------------
/3dof/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | .yarn/
3 | dist/
4 | site/
--------------------------------------------------------------------------------
/fix-fog/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | .yarn/
3 | dist/
4 | site/
--------------------------------------------------------------------------------
/mirror/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | .yarn/
3 | dist/
4 | site/
--------------------------------------------------------------------------------
/effekseer/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | .yarn/
3 | dist/
4 | temp/
--------------------------------------------------------------------------------
/extra-stats/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | .yarn/
3 | dist/
4 | site/
--------------------------------------------------------------------------------
/screen-fade/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | .yarn/
3 | dist/
4 | site/
--------------------------------------------------------------------------------
/motion-controller/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | .yarn/
3 | dist/
4 | temp/
--------------------------------------------------------------------------------
/sky-background/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | .yarn/
3 | dist/
4 | site/
--------------------------------------------------------------------------------
/.yarnrc.yml:
--------------------------------------------------------------------------------
1 | compressionLevel: mixed
2 |
3 | enableGlobalCache: false
4 |
5 | nodeLinker: node-modules
6 |
--------------------------------------------------------------------------------
/effekseer/src/main.ts:
--------------------------------------------------------------------------------
1 | import 'aframe';
2 | import './effekseer.component';
3 | import './effekseer.system';
4 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "fern-aframe-components",
3 | "workspaces": [
4 | "*"
5 | ]
6 | }
7 |
--------------------------------------------------------------------------------
/motion-controller/src/main.ts:
--------------------------------------------------------------------------------
1 | import 'aframe';
2 | import './motion-controller-model.component';
3 | import './motion-controller-space.component';
4 | import './motion-controller.system';
--------------------------------------------------------------------------------
/scripts/README.md:
--------------------------------------------------------------------------------
1 | Some simple scripts to populate the `dist/` folder with built versions of all the components and their corresponding examples, ready to be deployed to GitHub Pages. See the `.github/workflows/ci.yml` file for the entire process.
2 |
--------------------------------------------------------------------------------
/extra-stats/src/rStats.three.js:
--------------------------------------------------------------------------------
1 | export function updatedThreeStats(renderer) {
2 | const rawThreeStats = window.threeStats(renderer);
3 |
4 | // Sane limits for mobile (Quest 2, Pico Neo 3 Link, etc..) HMDs
5 | rawThreeStats.values['renderer.info.render.calls'].over = 150;
6 | rawThreeStats.values['renderer.info.render.triangles'].over = 1_200_000;
7 |
8 | return rawThreeStats;
9 | }
--------------------------------------------------------------------------------
/3dof/rollup.config.js:
--------------------------------------------------------------------------------
1 | import { terser } from "rollup-plugin-terser";
2 | import pkg from './package.json';
3 |
4 | export default {
5 | input: 'src/index.js',
6 | plugins: [
7 | terser(),
8 | ],
9 | output: [
10 | {
11 | file: pkg.browser,
12 | format: 'umd',
13 | },
14 | {
15 | file: pkg.module,
16 | format: 'es'
17 | },
18 | ],
19 | };
--------------------------------------------------------------------------------
/hud/rollup.config.js:
--------------------------------------------------------------------------------
1 | import { terser } from "rollup-plugin-terser";
2 | import pkg from './package.json';
3 |
4 | export default {
5 | input: 'src/index.js',
6 | plugins: [
7 | terser(),
8 | ],
9 | output: [
10 | {
11 | file: pkg.browser,
12 | format: 'umd',
13 | },
14 | {
15 | file: pkg.module,
16 | format: 'es'
17 | },
18 | ],
19 | };
--------------------------------------------------------------------------------
/mirror/rollup.config.js:
--------------------------------------------------------------------------------
1 | import { terser } from "rollup-plugin-terser";
2 | import pkg from './package.json';
3 |
4 | export default {
5 | input: 'src/index.js',
6 | plugins: [
7 | terser(),
8 | ],
9 | output: [
10 | {
11 | file: pkg.browser,
12 | format: 'umd',
13 | },
14 | {
15 | file: pkg.module,
16 | format: 'es'
17 | },
18 | ],
19 | };
--------------------------------------------------------------------------------
/extra-stats/rollup.config.js:
--------------------------------------------------------------------------------
1 | import { terser } from "rollup-plugin-terser";
2 | import pkg from './package.json';
3 |
4 | export default {
5 | input: 'src/index.js',
6 | plugins: [
7 | terser(),
8 | ],
9 | output: [
10 | {
11 | file: pkg.browser,
12 | format: 'umd',
13 | },
14 | {
15 | file: pkg.module,
16 | format: 'es'
17 | },
18 | ],
19 | };
--------------------------------------------------------------------------------
/fix-fog/rollup.config.js:
--------------------------------------------------------------------------------
1 | import { terser } from "rollup-plugin-terser";
2 | import pkg from './package.json';
3 |
4 | export default {
5 | input: 'src/index.js',
6 | plugins: [
7 | terser(),
8 | ],
9 | output: [
10 | {
11 | file: pkg.browser,
12 | format: 'umd',
13 | },
14 | {
15 | file: pkg.module,
16 | format: 'es'
17 | },
18 | ],
19 | };
--------------------------------------------------------------------------------
/highlight/rollup.config.js:
--------------------------------------------------------------------------------
1 | import { terser } from "rollup-plugin-terser";
2 | import pkg from './package.json';
3 |
4 | export default {
5 | input: 'src/index.js',
6 | plugins: [
7 | terser(),
8 | ],
9 | output: [
10 | {
11 | file: pkg.browser,
12 | format: 'umd',
13 | },
14 | {
15 | file: pkg.module,
16 | format: 'es'
17 | },
18 | ],
19 | };
--------------------------------------------------------------------------------
/screen-fade/rollup.config.js:
--------------------------------------------------------------------------------
1 | import { terser } from "rollup-plugin-terser";
2 | import pkg from './package.json';
3 |
4 | export default {
5 | input: 'src/index.js',
6 | plugins: [
7 | terser(),
8 | ],
9 | output: [
10 | {
11 | file: pkg.browser,
12 | format: 'umd',
13 | },
14 | {
15 | file: pkg.module,
16 | format: 'es'
17 | },
18 | ],
19 | };
--------------------------------------------------------------------------------
/sky-background/rollup.config.js:
--------------------------------------------------------------------------------
1 | import { terser } from "rollup-plugin-terser";
2 | import pkg from './package.json';
3 |
4 | export default {
5 | input: 'src/index.js',
6 | plugins: [
7 | terser(),
8 | ],
9 | output: [
10 | {
11 | file: pkg.browser,
12 | format: 'umd',
13 | },
14 | {
15 | file: pkg.module,
16 | format: 'es'
17 | },
18 | ],
19 | };
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: ci
2 | on:
3 | push:
4 | branches:
5 | - main
6 | permissions:
7 | contents: write
8 | jobs:
9 | deploy:
10 | runs-on: ubuntu-latest
11 | steps:
12 | - uses: actions/checkout@v3
13 | - run: yarn set version stable
14 | - uses: actions/setup-python@v4
15 | with:
16 | python-version: 3.x
17 | - run: pip install ghp-import
18 | - name: install, build, and test
19 | run: |
20 | ./scripts/build-gh-pages.sh
21 | - name: deploy
22 | run: ghp-import ./dist -c aframe-components.fern.solutions -p -f
23 |
--------------------------------------------------------------------------------
/screen-fade/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "esnext",
4 | "useDefineForClassFields": true,
5 | "module": "esnext",
6 | "lib": ["esnext", "dom"],
7 | "moduleResolution": "node",
8 | "strict": true,
9 | "resolveJsonModule": true,
10 | "isolatedModules": true,
11 | "esModuleInterop": true,
12 | "noEmit": true,
13 | "noUnusedLocals": true,
14 | "noUnusedParameters": true,
15 | "noImplicitReturns": true,
16 | "noImplicitAny": true,
17 | "skipLibCheck": true,
18 | "declaration": false,
19 | "paths": {
20 | "aframe": ["./node_modules/aframe-types"]
21 | }
22 | },
23 | "include": ["types"]
24 | }
25 |
--------------------------------------------------------------------------------
/sky-background/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "esnext",
4 | "useDefineForClassFields": true,
5 | "module": "esnext",
6 | "lib": ["esnext", "dom"],
7 | "moduleResolution": "node",
8 | "strict": true,
9 | "resolveJsonModule": true,
10 | "isolatedModules": true,
11 | "esModuleInterop": true,
12 | "noEmit": true,
13 | "noUnusedLocals": true,
14 | "noUnusedParameters": true,
15 | "noImplicitReturns": true,
16 | "noImplicitAny": true,
17 | "skipLibCheck": true,
18 | "declaration": false,
19 | "paths": {
20 | "aframe": ["./node_modules/aframe-types"]
21 | }
22 | },
23 | "include": ["types"]
24 | }
25 |
--------------------------------------------------------------------------------
/screen-fade/types/screen-fade.d.ts:
--------------------------------------------------------------------------------
1 | import "aframe";
2 |
3 | declare module "aframe" {
4 | import { Component } from "aframe";
5 |
6 | export interface Components {
7 | /**
8 | * Component allowing the screen to be faded to and from a solid color.
9 | * The effect works in both desktop and VR mode. This can be used for situations like
10 | * loading a scene, handling transitions or snap turning.
11 | */
12 | "screen-fade": Component<{
13 | /** The solid color the screen fades to */
14 | 'color': { type: "color", default: "#000000" },
15 | /** The intensity of the fade between 0.0 and 1.0 */
16 | 'intensity': { type: "number", default: 0.0, max: 1.0, min: 0.0 }
17 | }>
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/scripts/build-gh-pages.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -xe
3 |
4 | yarn workspaces foreach -A install --immutable
5 | yarn workspaces foreach -A run build
6 |
7 | rm -rf ./dist
8 | mkdir -p ./dist/js/
9 | cp README.md ./dist/
10 | for dir in ./*/
11 | do
12 | dir=$(basename $dir)
13 | if [ -e "./$dir/package.json" ]; then
14 | mkdir -p "./dist/$dir/"
15 | cp -R "./$dir/dist/"*".umd.min.js" ./dist/js/
16 | cp -R "./$dir/example/"* "./dist/$dir/"
17 | fi
18 | done
19 |
20 | # Insert GitHub corner into example files
21 | find "./dist/" -name "*.html" -exec sed -i '
22 | //{
23 | r ./scripts/chunks/github-corner-left.html
24 | D
25 | }
26 | //{
27 | r ./scripts/chunks/github-corner-right.html
28 | D
29 | }' {} \;
30 |
--------------------------------------------------------------------------------
/motion-controller/rollup.config.dev.js:
--------------------------------------------------------------------------------
1 | import esbuild from 'rollup-plugin-esbuild';
2 | import { nodeResolve } from '@rollup/plugin-node-resolve';
3 | import pkg from './package.json';
4 |
5 | export default [
6 | {
7 | input: 'src/main.ts',
8 | plugins: [
9 | nodeResolve({ resolveOnly: ['@webxr-input-profiles/motion-controllers'] }),
10 | esbuild(),
11 | ],
12 | external: ['aframe'],
13 | output: [
14 | {
15 | name: 'aframe-motion-controller',
16 | file: pkg.browser,
17 | sourcemap: true,
18 | format: 'umd',
19 | globals: {
20 | aframe: 'AFRAME',
21 | three: 'THREE'
22 | }
23 | }
24 | ],
25 | }
26 | ]
--------------------------------------------------------------------------------
/effekseer/rollup.config.dev.js:
--------------------------------------------------------------------------------
1 | import typescript from '@rollup/plugin-typescript';
2 | import { nodeResolve } from '@rollup/plugin-node-resolve';
3 | import pkg from './package.json';
4 |
5 | export default [
6 | {
7 | input: 'src/main.ts',
8 | plugins: [
9 | nodeResolve(),
10 | typescript({ sourceMap: true }),
11 | ],
12 | external: ['aframe', 'effekseer'],
13 | output: [
14 | {
15 | name: 'aframe-effekseer',
16 | file: pkg.browser,
17 | sourcemap: true,
18 | format: 'umd',
19 | globals: {
20 | aframe: 'AFRAME',
21 | three: 'THREE',
22 | "@zip.js/zip.js": "zip"
23 | }
24 | }
25 | ],
26 | }
27 | ]
--------------------------------------------------------------------------------
/motion-controller/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "esnext",
4 | "useDefineForClassFields": true,
5 | "module": "esnext",
6 | "baseUrl": "./",
7 | "lib": ["esnext", "dom"],
8 | "moduleResolution": "node",
9 | "strict": true,
10 | "resolveJsonModule": true,
11 | "isolatedModules": true,
12 | "esModuleInterop": true,
13 | "noEmit": true,
14 | "noUnusedLocals": true,
15 | "noUnusedParameters": true,
16 | "noImplicitReturns": true,
17 | "noImplicitAny": true,
18 | "skipLibCheck": true,
19 | "declaration": false,
20 | "rootDir": "src",
21 | "paths": {
22 | "aframe": ["../node_modules/aframe-types"]
23 | }
24 | },
25 | "include": ["src"],
26 | "exclude": ["node_modules", "**/dist/*"]
27 | }
28 |
--------------------------------------------------------------------------------
/effekseer/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "esnext",
4 | "useDefineForClassFields": true,
5 | "module": "esnext",
6 | "baseUrl": "./",
7 | "lib": ["esnext", "dom"],
8 | "moduleResolution": "node",
9 | "strict": true,
10 | "resolveJsonModule": true,
11 | "isolatedModules": true,
12 | "esModuleInterop": true,
13 | "noEmit": true,
14 | "noUnusedLocals": true,
15 | "noUnusedParameters": true,
16 | "noImplicitReturns": true,
17 | "noImplicitAny": true,
18 | "skipLibCheck": true,
19 | "declaration": false,
20 | "rootDir": "src",
21 | "paths": {
22 | "aframe": ["../node_modules/aframe-types"],
23 | "effekseer": ["vendor/effekseer.d.ts"]
24 | }
25 | },
26 | "include": ["src"],
27 | "exclude": ["node_modules", "**/dist/*"]
28 | }
29 |
--------------------------------------------------------------------------------
/fix-fog/src/index.js:
--------------------------------------------------------------------------------
1 | THREE.ShaderChunk.fog_pars_vertex = /*glsl*/`
2 | #ifdef USE_FOG
3 | varying vec3 vFogPosition;
4 | #endif`;
5 |
6 | THREE.ShaderChunk.fog_vertex = /*glsl*/`
7 | #ifdef USE_FOG
8 | vFogPosition = worldPosition.xyz;
9 | #endif`;
10 |
11 | THREE.ShaderChunk.fog_pars_fragment = /*glsl*/`
12 | #ifdef USE_FOG
13 | uniform vec3 fogColor;
14 | varying vec3 vFogPosition;
15 | #ifdef FOG_EXP2
16 | uniform float fogDensity;
17 | #else
18 | uniform float fogNear;
19 | uniform float fogFar;
20 | #endif
21 | #endif`;
22 |
23 | THREE.ShaderChunk.fog_fragment = /*glsl*/`
24 | #ifdef USE_FOG
25 | float fogDepth = distance( cameraPosition, vFogPosition );
26 | #ifdef FOG_EXP2
27 | float fogFactor = 1.0 - exp( - fogDensity * fogDensity * fogDepth * fogDepth );
28 | #else
29 | float fogFactor = smoothstep( fogNear, fogFar, fogDepth );
30 | #endif
31 | gl_FragColor.rgb = mix( gl_FragColor.rgb, fogColor, fogFactor );
32 | #endif`;
--------------------------------------------------------------------------------
/motion-controller/src/hand-joint-names.ts:
--------------------------------------------------------------------------------
1 | export const HAND_JOINT_NAMES = [
2 | "wrist",
3 |
4 | "thumb-metacarpal",
5 | "thumb-phalanx-proximal",
6 | "thumb-phalanx-distal",
7 | "thumb-tip",
8 |
9 | "index-finger-metacarpal",
10 | "index-finger-phalanx-proximal",
11 | "index-finger-phalanx-intermediate",
12 | "index-finger-phalanx-distal",
13 | "index-finger-tip",
14 |
15 | "middle-finger-metacarpal",
16 | "middle-finger-phalanx-proximal",
17 | "middle-finger-phalanx-intermediate",
18 | "middle-finger-phalanx-distal",
19 | "middle-finger-tip",
20 |
21 | "ring-finger-metacarpal",
22 | "ring-finger-phalanx-proximal",
23 | "ring-finger-phalanx-intermediate",
24 | "ring-finger-phalanx-distal",
25 | "ring-finger-tip",
26 |
27 | "pinky-finger-metacarpal",
28 | "pinky-finger-phalanx-proximal",
29 | "pinky-finger-phalanx-intermediate",
30 | "pinky-finger-phalanx-distal",
31 | "pinky-finger-tip"
32 | ];
33 |
--------------------------------------------------------------------------------
/hud/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 Noeri Huisman
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 |
--------------------------------------------------------------------------------
/3dof/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023-2024 Noeri Huisman
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 |
--------------------------------------------------------------------------------
/fix-fog/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 Noeri Huisman
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 |
--------------------------------------------------------------------------------
/highlight/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 Noeri Huisman
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 |
--------------------------------------------------------------------------------
/mirror/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 Noeri Huisman
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 |
--------------------------------------------------------------------------------
/extra-stats/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 Noeri Huisman
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 |
--------------------------------------------------------------------------------
/screen-fade/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 Noeri Huisman
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 |
--------------------------------------------------------------------------------
/sky-background/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 Noeri Huisman
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 |
--------------------------------------------------------------------------------
/3dof/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@fern-solutions/aframe-3dof",
3 | "version": "1.1.1",
4 | "description": "A-Frame component for rendering in 3DoF when in VR",
5 | "module": "dist/3dof.esm.min.js",
6 | "browser": "dist/3dof.umd.min.js",
7 | "main": "dist/3dof.esm.min.js",
8 | "author": "Noeri Huisman",
9 | "license": "MIT",
10 | "scripts": {
11 | "dev": "concurrently \"rollup -c -w\" \"live-server --port=4000 --no-browser ./example --mount=/js:./dist\"",
12 | "build": "rollup -c",
13 | "watch": "rollup -c -w"
14 | },
15 | "repository": {
16 | "type": "git",
17 | "url": "git+https://github.com/mrxz/fern-aframe-components"
18 | },
19 | "bugs": {
20 | "url": "https://github.com/mrxz/fern-aframe-components/issues"
21 | },
22 | "homepage": "https://github.com/mrxz/fern-aframe-components/tree/main/3dof#readme",
23 | "keywords": [
24 | "aframe",
25 | "vr",
26 | "xr",
27 | "webxr"
28 | ],
29 | "files": [
30 | "dist"
31 | ],
32 | "devDependencies": {
33 | "@compodoc/live-server": "1.2.3",
34 | "concurrently": "^7.1.0",
35 | "rollup": "^2.71.1",
36 | "rollup-plugin-terser": "^7.0.2"
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/mirror/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@fern-solutions/aframe-mirror",
3 | "version": "1.1.1",
4 | "description": "A-Frame component for high-quality mirrors",
5 | "module": "dist/mirror.esm.min.js",
6 | "browser": "dist/mirror.umd.min.js",
7 | "main": "dist/mirror.esm.min.js",
8 | "author": "Noeri Huisman",
9 | "license": "MIT",
10 | "scripts": {
11 | "dev": "concurrently \"rollup -c -w\" \"live-server --port=4000 --no-browser ./example --mount=/js:./dist\"",
12 | "build": "rollup -c",
13 | "watch": "rollup -c -w"
14 | },
15 | "repository": {
16 | "type": "git",
17 | "url": "git+https://github.com/mrxz/fern-aframe-components"
18 | },
19 | "bugs": {
20 | "url": "https://github.com/mrxz/fern-aframe-components/issues"
21 | },
22 | "homepage": "https://github.com/mrxz/fern-aframe-components/tree/main/mirror/#readme",
23 | "keywords": [
24 | "aframe",
25 | "vr",
26 | "xr",
27 | "webxr"
28 | ],
29 | "files": [
30 | "dist"
31 | ],
32 | "devDependencies": {
33 | "@compodoc/live-server": "1.2.3",
34 | "concurrently": "^7.1.0",
35 | "rollup": "^2.71.1",
36 | "rollup-plugin-terser": "^7.0.2"
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/fix-fog/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@fern-solutions/aframe-fix-fog",
3 | "version": "1.0.0",
4 | "description": "Improves fog in VR by computing depth in world space",
5 | "module": "dist/fix-fog.esm.min.js",
6 | "browser": "dist/fix-fog.umd.min.js",
7 | "main": "dist/fix-fog.esm.min.js",
8 | "author": "Noeri Huisman",
9 | "license": "MIT",
10 | "scripts": {
11 | "dev": "concurrently \"rollup -c -w\" \"live-server --port=4000 --no-browser ./example --mount=/js:./dist\"",
12 | "build": "rollup -c",
13 | "watch": "rollup -c -w"
14 | },
15 | "repository": {
16 | "type": "git",
17 | "url": "git+https://github.com/mrxz/fern-aframe-components"
18 | },
19 | "bugs": {
20 | "url": "https://github.com/mrxz/fern-aframe-components/issues"
21 | },
22 | "homepage": "https://github.com/mrxz/fern-aframe-components/tree/main/fix-fog/#readme",
23 | "keywords": [
24 | "aframe",
25 | "vr",
26 | "xr",
27 | "webxr"
28 | ],
29 | "files": [
30 | "dist"
31 | ],
32 | "devDependencies": {
33 | "@compodoc/live-server": "1.2.3",
34 | "concurrently": "^7.1.0",
35 | "rollup": "^2.71.1",
36 | "rollup-plugin-terser": "^7.0.2"
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/hud/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@fern-solutions/aframe-hud",
3 | "version": "0.1.0",
4 | "description": "A-Frame component for adding HUD elements on top of the rest in both desktop and VR",
5 | "module": "dist/hud.esm.min.js",
6 | "browser": "dist/hud.umd.min.js",
7 | "main": "dist/hud.esm.min.js",
8 | "author": "Noeri Huisman",
9 | "license": "MIT",
10 | "scripts": {
11 | "dev": "concurrently \"rollup -c -w\" \"live-server --port=4000 --no-browser ./example --mount=/js:./dist\"",
12 | "build": "rollup -c",
13 | "watch": "rollup -c -w"
14 | },
15 | "repository": {
16 | "type": "git",
17 | "url": "git+https://github.com/mrxz/fern-aframe-components"
18 | },
19 | "bugs": {
20 | "url": "https://github.com/mrxz/fern-aframe-components/issues"
21 | },
22 | "homepage": "https://github.com/mrxz/fern-aframe-components/tree/main/hud#readme",
23 | "keywords": [
24 | "aframe",
25 | "vr",
26 | "xr",
27 | "webxr"
28 | ],
29 | "files": [
30 | "dist"
31 | ],
32 | "devDependencies": {
33 | "@compodoc/live-server": "1.2.3",
34 | "concurrently": "^7.1.0",
35 | "rollup": "^2.71.1",
36 | "rollup-plugin-terser": "^7.0.2"
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/highlight/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@fern-solutions/aframe-highlight",
3 | "version": "0.2.0",
4 | "description": "A-Frame component for giving objects a highlight when occluded",
5 | "module": "dist/highlight.esm.min.js",
6 | "browser": "dist/highlight.umd.min.js",
7 | "main": "dist/highlight.esm.min.js",
8 | "author": "Noeri Huisman",
9 | "license": "MIT",
10 | "scripts": {
11 | "dev": "concurrently \"rollup -c -w\" \"live-server --port=4000 --no-browser ./example --mount=/js:./dist\"",
12 | "build": "rollup -c",
13 | "watch": "rollup -c -w"
14 | },
15 | "repository": {
16 | "type": "git",
17 | "url": "git+https://github.com/mrxz/fern-aframe-components"
18 | },
19 | "bugs": {
20 | "url": "https://github.com/mrxz/fern-aframe-components/issues"
21 | },
22 | "homepage": "https://github.com/mrxz/fern-aframe-components/tree/main/highlight/#readme",
23 | "keywords": [
24 | "aframe",
25 | "vr",
26 | "xr",
27 | "webxr"
28 | ],
29 | "files": [
30 | "dist"
31 | ],
32 | "devDependencies": {
33 | "@compodoc/live-server": "1.2.3",
34 | "concurrently": "^7.1.0",
35 | "rollup": "^2.71.1",
36 | "rollup-plugin-terser": "^7.0.2"
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/sky-background/example/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Fern A-Frame Components | Screen Fade
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/extra-stats/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@fern-solutions/aframe-extra-stats",
3 | "version": "1.0.2",
4 | "description": "A-Frame component that adds additional stats to the built-in stats component",
5 | "module": "dist/extra-stats.esm.min.js",
6 | "browser": "dist/extra-stats.umd.min.js",
7 | "main": "dist/extra-stats.esm.min.js",
8 | "author": "Noeri Huisman",
9 | "license": "MIT",
10 | "scripts": {
11 | "dev": "concurrently \"rollup -c -w\" \"live-server --port=4000 --no-browser ./example --mount=/js:./dist\"",
12 | "build": "rollup -c",
13 | "watch": "rollup -c -w"
14 | },
15 | "repository": {
16 | "type": "git",
17 | "url": "git+https://github.com/mrxz/fern-aframe-components"
18 | },
19 | "bugs": {
20 | "url": "https://github.com/mrxz/fern-aframe-components/issues"
21 | },
22 | "homepage": "https://github.com/mrxz/fern-aframe-components/tree/main/extra-stats/#readme",
23 | "keywords": [
24 | "aframe",
25 | "vr",
26 | "xr",
27 | "webxr"
28 | ],
29 | "files": [
30 | "dist"
31 | ],
32 | "devDependencies": {
33 | "@compodoc/live-server": "1.2.3",
34 | "concurrently": "^7.1.0",
35 | "rollup": "^2.71.1",
36 | "rollup-plugin-terser": "^7.0.2"
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/motion-controller/README.md:
--------------------------------------------------------------------------------
1 | # Motion Controller component
2 | [](https://www.npmjs.com/package/@fern-solutions/aframe-motion-controller)
3 | [](https://www.npmjs.com/package/@fern-solutions/aframe-motion-controller)
4 | [](https://github.com/mrxz/fern-aframe-components/)
5 | [](https://twitter.com/noerihuisman)
6 | [](https://arvr.social/@noerihuisman)
7 | [](https://ko-fi.com/fernsolutions)
8 |
9 | https://github.com/immersive-web/webxr-input-profiles
10 |
11 | Checkout the example: [Online Demo](https://aframe-components.fern.solutions/motion-controller) | [Source](https://github.com/mrxz/fern-aframe-components/blob/main/motion-controller/example/index.html)
12 |
13 | ## Usage
14 |
15 |
16 | ## Properties
17 |
--------------------------------------------------------------------------------
/motion-controller/rollup.config.prod.js:
--------------------------------------------------------------------------------
1 | import terser from '@rollup/plugin-terser';
2 | import typescript from '@rollup/plugin-typescript';
3 | import dts from 'rollup-plugin-dts';
4 | import { nodeResolve } from '@rollup/plugin-node-resolve';
5 | import pkg from './package.json';
6 |
7 | export default [
8 | {
9 | input: 'src/main.ts',
10 | plugins: [
11 | nodeResolve({ resolveOnly: ['aframe-typescript'] }),
12 | typescript({ compilerOptions: { declaration: true, declarationDir: 'typings' } }),
13 | terser(),
14 | ],
15 | external: ['aframe'],
16 | output: [
17 | {
18 | name: 'aframe-motion-controller',
19 | file: pkg.browser,
20 | format: 'umd',
21 | globals: {
22 | aframe: 'AFRAME',
23 | three: 'THREE'
24 | }
25 | },
26 | {
27 | file: pkg.module,
28 | format: 'es'
29 | },
30 | ],
31 | },
32 | {
33 | input: './dist/typings/main.d.ts',
34 | output: [{ file: 'dist/aframe-motion-controller.d.ts', format: "es" }],
35 | plugins: [dts()],
36 | external: ['aframe'],
37 | }
38 | ]
--------------------------------------------------------------------------------
/effekseer/rollup.config.prod.js:
--------------------------------------------------------------------------------
1 | import terser from '@rollup/plugin-terser';
2 | import typescript from '@rollup/plugin-typescript';
3 | import dts from 'rollup-plugin-dts';
4 | import { nodeResolve } from '@rollup/plugin-node-resolve';
5 | import pkg from './package.json';
6 |
7 | export default [
8 | {
9 | input: 'src/main.ts',
10 | plugins: [
11 | nodeResolve(),
12 | typescript({ compilerOptions: { declaration: true, declarationDir: 'typings' } }),
13 | terser(),
14 | ],
15 | external: ['aframe', 'effekseer'],
16 | output: [
17 | {
18 | name: 'aframe-effekseer',
19 | file: pkg.browser,
20 | format: 'umd',
21 | globals: {
22 | aframe: 'AFRAME',
23 | three: 'THREE',
24 | "@zip.js/zip.js": "zip"
25 | }
26 | },
27 | {
28 | file: pkg.module,
29 | format: 'es'
30 | },
31 | ],
32 | },
33 | {
34 | input: './dist/typings/main.d.ts',
35 | output: [{ file: 'dist/aframe-effekseer.d.ts', format: "es" }],
36 | plugins: [dts()],
37 | external: ['aframe', 'effekseer'],
38 | }
39 | ]
--------------------------------------------------------------------------------
/screen-fade/example/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Fern A-Frame Components | Screen Fade
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/effekseer/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@fern-solutions/aframe-effekseer",
3 | "version": "1.0.0",
4 | "description": "A-Frame component for rendering Effekseer effects",
5 | "module": "dist/aframe-effekseer.esm.min.js",
6 | "browser": "dist/aframe-effekseer.umd.min.js",
7 | "main": "dist/aframe-effekseer.esm.min.js",
8 | "types": "dist/aframe-effekseer.d.ts",
9 | "author": "Noeri Huisman",
10 | "license": "MIT",
11 | "scripts": {
12 | "dev": "concurrently \"rollup -c rollup.config.dev.js -w\" \"live-server --port=4000 --no-browser ./example --mount=/js:./dist --mount=/src:./src\"",
13 | "build": "rollup -c rollup.config.prod.js"
14 | },
15 | "keywords": [
16 | "aframe",
17 | "typescript",
18 | "webxr"
19 | ],
20 | "files": [
21 | "dist",
22 | "!dist/typings"
23 | ],
24 | "devDependencies": {
25 | "@compodoc/live-server": "1.2.3",
26 | "@rollup/plugin-node-resolve": "^15.0.2",
27 | "@rollup/plugin-terser": "^0.4.3",
28 | "@rollup/plugin-typescript": "^11.1.1",
29 | "@types/animejs": "3.1.0",
30 | "@types/three": "0.147.1",
31 | "aframe-types": "0.9.1",
32 | "concurrently": "^7.1.0",
33 | "rimraf": "^5.0.1",
34 | "rollup": "^2.71.1",
35 | "rollup-plugin-dts": "^5.3.0",
36 | "typedoc": "^0.24.7",
37 | "typescript": "^5.0.4"
38 | },
39 | "dependencies": {
40 | "@zip.js/zip.js": "^2.7.20"
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/screen-fade/src/index.js:
--------------------------------------------------------------------------------
1 | const VERTEX_SHADER = /*glsl*/`
2 | void main() {
3 | vec3 newPosition = position * 2.0;
4 | gl_Position = vec4(newPosition, 1.0);
5 | }`;
6 |
7 | const FRAGMENT_SHADER = /*glsl*/`
8 | uniform vec3 color;
9 | uniform float intensity;
10 | void main() {
11 | gl_FragColor = vec4(color, intensity);
12 | }`;
13 |
14 | AFRAME.registerComponent('screen-fade', {
15 | schema: {
16 | color: { type: "color", default: "#000000" },
17 | intensity: { type: "number", default: 0.0, max: 1.0, min: 0.0 }
18 | },
19 | init: function() {
20 | const geometry = new THREE.PlaneGeometry(1, 1);
21 | this.material = new THREE.ShaderMaterial({
22 | vertexShader: VERTEX_SHADER,
23 | fragmentShader: FRAGMENT_SHADER,
24 | transparent: true,
25 | depthTest: false,
26 | uniforms: {
27 | color: { value: new THREE.Color(this.data.color) },
28 | intensity: { value: this.data.intensity }
29 | }
30 | });
31 | this.fullscreenQuad = new THREE.Mesh(geometry, this.material)
32 |
33 | this.el.setObject3D('fullscreenQuad', this.fullscreenQuad);
34 | },
35 | update: function() {
36 | this.material.uniforms.color.value.set(this.data.color);
37 | this.material.uniforms.intensity.value = this.data.intensity;
38 | }
39 | });
40 |
--------------------------------------------------------------------------------
/screen-fade/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@fern-solutions/aframe-screen-fade",
3 | "version": "1.0.2",
4 | "description": "A-Frame component for fading the screen, for both desktop and VR",
5 | "module": "dist/screen-fade.esm.min.js",
6 | "browser": "dist/screen-fade.umd.min.js",
7 | "main": "dist/screen-fade.esm.min.js",
8 | "types": "types/screen-fade.d.ts",
9 | "author": "Noeri Huisman",
10 | "license": "MIT",
11 | "scripts": {
12 | "dev": "concurrently \"rollup -c -w\" \"live-server --port=4000 --no-browser ./example --mount=/js:./dist\"",
13 | "build": "rollup -c",
14 | "watch": "rollup -c -w"
15 | },
16 | "repository": {
17 | "type": "git",
18 | "url": "git+https://github.com/mrxz/fern-aframe-components"
19 | },
20 | "bugs": {
21 | "url": "https://github.com/mrxz/fern-aframe-components/issues"
22 | },
23 | "homepage": "https://github.com/mrxz/fern-aframe-components/tree/main/screen-fade#readme",
24 | "keywords": [
25 | "aframe",
26 | "vr",
27 | "xr",
28 | "webxr"
29 | ],
30 | "files": [
31 | "dist",
32 | "types"
33 | ],
34 | "devDependencies": {
35 | "@compodoc/live-server": "1.2.3",
36 | "@types/animejs": "3.1.0",
37 | "@types/three": "0.147.1",
38 | "aframe-types": "0.9.1",
39 | "concurrently": "^7.1.0",
40 | "rollup": "^2.71.1",
41 | "rollup-plugin-terser": "^7.0.2",
42 | "typescript": "^5.0.4"
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/effekseer/vendor/README.md:
--------------------------------------------------------------------------------
1 | This typings file comes from EffekseerForWebGL release. Since Effekseer doesn't publish npm packages, the typings file is included in this repository. See the original repository for full source and details: https://github.com/effekseer/EffekseerForWebGL
2 |
3 | ## License
4 | The MIT License (MIT)
5 |
6 | Copyright (c) 2011 Effekseer Project
7 |
8 | Permission is hereby granted, free of charge, to any person obtaining a copy of
9 | this software and associated documentation files (the "Software"), to deal in
10 | the Software without restriction, including without limitation the rights to
11 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
12 | the Software, and to permit persons to whom the Software is furnished to do so,
13 | subject to the following conditions:
14 |
15 | The above copyright notice and this permission notice shall be included in all
16 | copies or substantial portions of the Software.
17 |
18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
20 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
21 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
22 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
23 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--------------------------------------------------------------------------------
/sky-background/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@fern-solutions/aframe-sky-background",
3 | "version": "1.0.4",
4 | "description": "A-Frame primitive for efficiently rendering a sky in the background",
5 | "module": "dist/sky-background.esm.min.js",
6 | "browser": "dist/sky-background.umd.min.js",
7 | "main": "dist/sky-background.esm.min.js",
8 | "types": "types/sky-background.d.ts",
9 | "author": "Noeri Huisman",
10 | "license": "MIT",
11 | "scripts": {
12 | "dev": "concurrently \"rollup -c -w\" \"live-server --port=4000 --no-browser ./example --mount=/js:./dist\"",
13 | "build": "rollup -c",
14 | "watch": "rollup -c -w"
15 | },
16 | "repository": {
17 | "type": "git",
18 | "url": "git+https://github.com/mrxz/fern-aframe-components"
19 | },
20 | "bugs": {
21 | "url": "https://github.com/mrxz/fern-aframe-components/issues"
22 | },
23 | "homepage": "https://github.com/mrxz/fern-aframe-components/tree/main/sky-background#readme",
24 | "keywords": [
25 | "aframe",
26 | "vr",
27 | "xr",
28 | "webxr"
29 | ],
30 | "files": [
31 | "dist",
32 | "types"
33 | ],
34 | "devDependencies": {
35 | "@compodoc/live-server": "1.2.3",
36 | "@types/animejs": "3.1.0",
37 | "@types/three": "0.147.1",
38 | "aframe-types": "0.9.1",
39 | "concurrently": "^7.1.0",
40 | "rollup": "^2.71.1",
41 | "rollup-plugin-terser": "^7.0.2",
42 | "typescript": "^5.0.4"
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/scripts/chunks/github-corner-right.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/motion-controller/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@fern-solutions/aframe-motion-controller",
3 | "version": "0.1.0",
4 | "description": "A-Frame component for loading WebXR input profiles and models",
5 | "module": "dist/aframe-motion-controller.esm.min.js",
6 | "browser": "dist/aframe-motion-controller.umd.min.js",
7 | "main": "dist/aframe-motion-controller.esm.min.js",
8 | "types": "dist/aframe-motion-controller.d.ts",
9 | "author": "Noeri Huisman",
10 | "license": "MIT",
11 | "scripts": {
12 | "dev": "concurrently \"rollup -c rollup.config.dev.js -w\" \"live-server --port=4000 --no-browser ./example --mount=/js:./dist --mount=/src:./src\"",
13 | "build": "rollup -c rollup.config.prod.js"
14 | },
15 | "keywords": [
16 | "aframe",
17 | "typescript",
18 | "webxr"
19 | ],
20 | "files": [
21 | "dist",
22 | "!dist/typings"
23 | ],
24 | "devDependencies": {
25 | "@compodoc/live-server": "1.2.3",
26 | "@rollup/plugin-node-resolve": "^15.0.2",
27 | "@rollup/plugin-terser": "^0.4.3",
28 | "@rollup/plugin-typescript": "^11.1.1",
29 | "@types/animejs": "3.1.0",
30 | "@types/three": "0.147.1",
31 | "aframe-types": "0.9.1",
32 | "concurrently": "^7.1.0",
33 | "esbuild": "^0.18.17",
34 | "rimraf": "^5.0.1",
35 | "rollup": "^2.71.1",
36 | "rollup-plugin-dts": "^5.3.0",
37 | "rollup-plugin-esbuild": "^5.0.0",
38 | "typedoc": "^0.24.7",
39 | "typescript": "^5.0.4"
40 | },
41 | "dependencies": {
42 | "@webxr-input-profiles/motion-controllers": "^1.0.0"
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/scripts/chunks/github-corner-left.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/3dof/README.md:
--------------------------------------------------------------------------------
1 | # 3DoF component
2 | [](https://www.npmjs.com/package/@fern-solutions/aframe-3dof)
3 | [](https://www.npmjs.com/package/@fern-solutions/aframe-3dof)
4 | [](https://github.com/mrxz/fern-aframe-components/)
5 | [](https://twitter.com/noerihuisman)
6 | [](https://arvr.social/@noerihuisman)
7 | [](https://ko-fi.com/fernsolutions)
8 |
9 | This component can be used to render a scene in either monoscopic or stereoscopic 3DoF. Only the orientation of the head is used. The position of the camera can be controlled with the position property.
10 |
11 | Checkout the example: [Online Demo](https://aframe-components.fern.solutions/3dof) | [Source](https://github.com/mrxz/fern-aframe-components/blob/main/3dof/example/index.html)
12 |
13 | ## Usage
14 | Load the script from [npm](https://www.npmjs.com/package/@fern-solutions/aframe-3dof) or add the following script tag:
15 | ```HTML
16 |
17 | ```
18 |
19 | The `3dof` component can be added to any `a-scene` element as follows:
20 | ```HTML
21 |
22 | ```
--------------------------------------------------------------------------------
/motion-controller/example/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Fern A-Frame Components | Motion Controller
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/fix-fog/README.md:
--------------------------------------------------------------------------------
1 | # Fix Fog
2 | [](https://www.npmjs.com/package/@fern-solutions/aframe-fix-fog)
3 | [](https://www.npmjs.com/package/@fern-solutions/aframe-fix-fog)
4 | [](https://github.com/mrxz/fern-aframe-components/)
5 | [](https://twitter.com/noerihuisman)
6 | [](https://arvr.social/@noerihuisman)
7 | [](https://ko-fi.com/fernsolutions)
8 |
9 | This changes the fog depth computation from camera space to world space. This improves the fog effect in VR, most noticeably in case of dense fog. It does this by changing some of the shader chunks of Three.js. For more details, read the dev log: [A-Frame Adventures 02 - Fixing Fog](https://fern.solutions/dev-logs/aframe-adventures-02/)
10 |
11 | Checkout the example: [Online Demo](https://aframe-components.fern.solutions/fix-fog) | [Source](https://github.com/mrxz/fern-aframe-components/blob/main/fix-fog/example/index.html)
12 |
13 | ## Usage
14 | Load the script from [npm](https://www.npmjs.com/package/@fern-solutions/aframe-fix-fog) or add the following script tag:
15 | ```HTML
16 |
17 | ```
18 |
19 | That's all, it will automatically update the shader chunks :-)
--------------------------------------------------------------------------------
/extra-stats/src/index.js:
--------------------------------------------------------------------------------
1 | import { threeAllocStats } from './rStats.three-alloc';
2 | import { updatedThreeStats } from './rStats.three';
3 |
4 | AFRAME.registerComponent('extra-stats', {
5 | schema: {
6 | three: { type: "boolean", default: true },
7 | aframe: { type: "boolean", default: true },
8 | threeAlloc: { type: "boolean", default: true },
9 | },
10 | init: function() {
11 | const scene = this.el;
12 | if(scene.hasAttribute('stats')) {
13 | console.warn("Both 'stats' and 'extra-stats' are added, only one should be used at a time!");
14 | }
15 |
16 | const plugins = [
17 | this.data.three ? updatedThreeStats(scene.renderer) : null,
18 | this.data.aframe ? window.aframeStats(scene) : null,
19 | this.data.threeAlloc ? threeAllocStats() : null,
20 | ].filter(x => x !== null);
21 | this.stats = new window.rStats({
22 | css: [], // Rely on A-Frame stylesheet
23 | values: {
24 | fps: { caption: 'fps', below: 30 },
25 | },
26 | groups: [
27 | { caption: 'Framerate', values: ['fps', 'raf'] }
28 | ],
29 | plugins: plugins
30 | });
31 | this.statsEl = document.querySelector('.rs-base');
32 |
33 | scene.addEventListener('enter-vr', () => this.statsEl.classList.add('a-hidden'));
34 | scene.addEventListener('exit-vr', () => this.statsEl.classList.remove('a-hidden'));
35 | },
36 | tick: function () {
37 | if(!this.stats) {
38 | return;
39 | }
40 |
41 | this.stats('rAF').tick();
42 | this.stats('FPS').frame();
43 | this.stats().update();
44 | },
45 | });
46 |
--------------------------------------------------------------------------------
/sky-background/types/sky-background.d.ts:
--------------------------------------------------------------------------------
1 | import "aframe";
2 |
3 | declare module "aframe" {
4 | export interface Components {
5 | "sky-background": Component<{}>;
6 | }
7 |
8 | export interface Shaders {
9 | "sky-background": Shader<{
10 | /** The solid color of the sky at the top */
11 | topColor: { type: 'color', is: 'uniform', default: '#0077ff' },
12 | /** The solid color of the sky at the bottom */
13 | bottomColor: { type: 'color', is: 'uniform', default: '#ffffff' },
14 | /** Offset in meters to 'angle' the gradient a bit */
15 | offset: { type: 'float', is: 'uniform', default: 120.0 },
16 | /** Exponent used to blend between the top and bottom color as a function of height */
17 | exponent: { type: 'float', is: 'uniform', default: 0.9 },
18 | /** The equirectangular texture to use, disables the gradient sky */
19 | src: {type: 'map'},
20 | }>;
21 | }
22 |
23 | export interface Primitives {
24 | /**
25 | * This primitives allows a sky to be added that's either a gradient or an equirectangular skybox.
26 | * In contrast to the built-in `` this doesn't use a sphere geometry. It renders a fullscreen
27 | * triangle covering the far plane, ensuring it's always in the background and more performant.
28 | */
29 | "a-sky-background": PrimitiveConstructor<'geometry' | 'material' | 'sky-background', {
30 | 'top-color': 'material.topColor',
31 | 'bottom-color': 'material.bottomColor',
32 | 'offset': 'material.offset',
33 | 'exponent': 'material.exponent',
34 | 'src': 'material.src'
35 | }>;
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/hud/README.md:
--------------------------------------------------------------------------------
1 | # HUD component
2 | [](https://www.npmjs.com/package/@fern-solutions/aframe-hud)
3 | [](https://www.npmjs.com/package/@fern-solutions/aframe-hud)
4 | [](https://github.com/mrxz/fern-aframe-components/)
5 | [](https://twitter.com/noerihuisman)
6 | [](https://arvr.social/@noerihuisman)
7 | [](https://ko-fi.com/fernsolutions)
8 |
9 | This component allows hud elements to be rendered. The elements render in both desktop and VR mode. On desktop the elements appear on the screen (flat), whereas in VR they are projected on a sphere around the user's head.
10 |
11 | The intended usage is mostly for debugging, but can be used for simple overlays as well.
12 |
13 | Checkout the example: [Online Demo](https://aframe-components.fern.solutions/hud) | [Source](https://github.com/mrxz/fern-aframe-components/blob/main/hud/example/index.html)
14 |
15 | ## Usage
16 | Load the script from [npm](https://www.npmjs.com/package/@fern-solutions/aframe-hud) or add the following script tag:
17 | ```HTML
18 |
19 | ```
20 |
21 | The `a-hud` and `a-hud-element` primitives can be used as children of `` as follows:
22 | ```HTML
23 |
24 |
25 |
26 |
27 |
28 | ```
--------------------------------------------------------------------------------
/motion-controller/src/motion-controller-space.component.ts:
--------------------------------------------------------------------------------
1 | import * as AFRAME from 'aframe';
2 |
3 | const MotionControllerSpaceComponent = AFRAME.registerComponent('motion-controller-space', {
4 | schema: {
5 | hand: { type: 'string', oneOf: ['left', 'right'], default: 'left' },
6 | space: { type: 'string', oneOf: ['gripSpace', 'targetRaySpace'], default: 'targetRaySpace' },
7 | },
8 | __fields: {} as {
9 | motionControllerSystem: AFRAME.Systems['motion-controller'],
10 | inputSource: XRInputSource|undefined,
11 | },
12 | init: function() {
13 | this.motionControllerSystem = this.el.sceneEl.systems['motion-controller'];
14 | this.el.sceneEl.addEventListener('motion-controller-change', _event => {
15 | const inputSourceRecord = this.motionControllerSystem[this.data.hand];
16 | this.inputSource = inputSourceRecord?.xrInputSource;
17 | });
18 | },
19 | tick: function() {
20 | const xrFrame = this.el.sceneEl.frame;
21 | const xrReferenceSpace = this.el.sceneEl.renderer.xr.getReferenceSpace?.();
22 | if(!this.inputSource || !xrFrame || !xrReferenceSpace) {
23 | return;
24 | }
25 |
26 | const xrSpace = this.inputSource[this.data.space];
27 | if(!xrSpace) {
28 | return;
29 | }
30 |
31 | // FIXME: Consider getting the pose in the system and caching for subsequent retrievals
32 | const xrPose = xrFrame.getPose(xrSpace, xrReferenceSpace);
33 | if(xrPose) {
34 | this.el.object3D.matrix.fromArray(xrPose.transform.matrix);
35 | this.el.object3D.matrix.decompose(this.el.object3D.position, this.el.object3D.quaternion, this.el.object3D.scale);
36 | }
37 | }
38 | });
39 |
40 | declare module "aframe" {
41 | export interface Components {
42 | "motion-controller-space": InstanceType
43 | }
44 | }
--------------------------------------------------------------------------------
/highlight/example/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Fern A-Frame Components | Highlight
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/screen-fade/README.md:
--------------------------------------------------------------------------------
1 | # Screen Fade component
2 | [](https://www.npmjs.com/package/@fern-solutions/aframe-screen-fade)
3 | [](https://www.npmjs.com/package/@fern-solutions/aframe-screen-fade)
4 | [](https://github.com/mrxz/fern-aframe-components/)
5 | [](https://twitter.com/noerihuisman)
6 | [](https://arvr.social/@noerihuisman)
7 | [](https://ko-fi.com/fernsolutions)
8 |
9 | This component allows the screen to be faded to and from a solid color. The effect works in both desktop and VR mode. This can be used for situations like loading a scene, handling transitions or snap turning.
10 |
11 | Checkout the example: [Online Demo](https://aframe-components.fern.solutions/screen-fade) | [Source](https://github.com/mrxz/fern-aframe-components/blob/main/screen-fade/example/index.html)
12 |
13 | ## Usage
14 | Load the script from [npm](https://www.npmjs.com/package/@fern-solutions/aframe-screen-fade) or add the following script tag:
15 | ```HTML
16 |
17 | ```
18 |
19 | The `screen-fade` component can be attached to the `` as follows:
20 | ```HTML
21 |
22 | ```
23 |
24 | The fade itself can then be controlled using the `intensity` property.
25 |
26 | ## Properties
27 | | Name | Type | Default |Description |
28 | | ---- | ---- | ------- |----------- |
29 | | `color` | color | #000000 | The solid color the screen fades to |
30 | | `intensity` | float | 0.0 | The intensity of the fade between 0.0 and 1.0 |
31 |
--------------------------------------------------------------------------------
/extra-stats/example/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Fern A-Frame Components | Extra Stats
5 |
6 |
7 |
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 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://github.com/mrxz/fern-aframe-components/)
2 | [](https://github.com/mrxz/fern-aframe-components/)
3 | [](https://twitter.com/noerihuisman)
4 | [](https://arvr.social/@noerihuisman)
5 | [](https://ko-fi.com/fernsolutions)
6 |
7 | This is a collection of A-Frame components. Check the individual components for their usages and corresponding examples.
8 |
9 | * [Mirror ``](./mirror): Mirror component and primitive for high-quality mirrors
10 | * [Highlight `highlight`](./highlight): Render a highlight for objects when they are occluded
11 | * [Screen Fade `screen-fade`](./screen-fade): Allows fade transitions in both desktop and VR mode
12 | * [Sky Background ``](./sky-background): Primitive for efficiently rendering a sky in the background
13 | * [Effekseer `effekseer`](./effekseer/): Component for integrating [Effekseer](https://effekseer.github.io/en/) effects into A-Frame projects
14 | * [Extra Stats `extra-stats`](./extra-stats): Replacement for the `stats` component that shows additional stats
15 | * [Fix Fog](./fix-fog): Changes fog depth from camera space to world space, [fixing fog in VR](https://fern.solutions/dev-logs/aframe-adventures-02/)
16 | * [HUD `` and ``](./hud): HUD components for debugging purposes
17 | * [3DoF `3dof`](./3dof): Monoscopic or stereoscopic 3DoF rendering
18 |
19 | ### A-Frame Locomotion
20 | If you're looking for components for locomotion, be sure to check out [aframe-locomotion](https://github.com/mrxz/aframe-locomotion).
21 |
22 | I hope you find these components useful. If you have any suggestions, questions or ideas, feel free to reach out to me.
23 |
24 |
25 |
--------------------------------------------------------------------------------
/3dof/src/index.js:
--------------------------------------------------------------------------------
1 | AFRAME.registerComponent('3dof', {
2 | schema: {
3 | position: { type: 'vec3' },
4 | stereo: { default: true }
5 | },
6 | init: function() {
7 | const renderer = this.el.sceneEl.renderer;
8 | renderer.xr.cameraAutoUpdate = false;
9 | },
10 | tick: (function() {
11 | const targetPosition = new THREE.Vector3();
12 | const eyeCenterPosition = new THREE.Vector3();
13 | const eyePosition = new THREE.Vector3();
14 |
15 | return function() {
16 | const renderer = this.el.sceneEl.renderer;
17 | if(!renderer.xr.isPresenting) {
18 | return;
19 | }
20 | targetPosition.copy(this.data.position);
21 |
22 | const xrCamera = renderer.xr.getCamera();
23 | // Update camera to let THREE compute the center position between the eyes
24 | renderer.xr.updateCamera(xrCamera);
25 | eyeCenterPosition.setFromMatrixPosition(xrCamera.matrixWorld);
26 |
27 | // Place xrCamera at the designated position
28 | xrCamera.matrix.setPosition(targetPosition);
29 | xrCamera.matrixWorld.copy(xrCamera.matrix);
30 | xrCamera.matrixWorldInverse.copy(xrCamera.matrixWorld).invert();
31 |
32 | // Update camera object (e.g. a-camera) to match new position
33 | // This ensures child object inherit the proper parent transform
34 | const cameraObject = this.el.sceneEl.camera.el.object3D;
35 | cameraObject.matrix.copy(xrCamera.matrix);
36 | cameraObject.matrix.decompose(cameraObject.position, cameraObject.quaternion, cameraObject.scale);
37 |
38 | // Update individual eye cameras
39 | if(this.data.stereo) {
40 | for(const camera of xrCamera.cameras) {
41 | eyePosition.setFromMatrixPosition(camera.matrix).sub(eyeCenterPosition).add(targetPosition);
42 | camera.matrix.setPosition(eyePosition);
43 | camera.matrixWorld.copy(camera.matrix);
44 | camera.matrixWorldInverse.copy(camera.matrixWorld).invert();
45 | }
46 | } else {
47 | for(const camera of xrCamera.cameras) {
48 | camera.matrix.setPosition(targetPosition);
49 | camera.matrixWorld.copy(camera.matrix);
50 | camera.matrixWorldInverse.copy(camera.matrixWorld).invert();
51 | }
52 | }
53 | }
54 | })(),
55 | });
--------------------------------------------------------------------------------
/extra-stats/README.md:
--------------------------------------------------------------------------------
1 | # Extra Stats component
2 | [](https://www.npmjs.com/package/@fern-solutions/aframe-extra-stats)
3 | [](https://www.npmjs.com/package/@fern-solutions/aframe-extra-stats)
4 | [](https://github.com/mrxz/fern-aframe-components/)
5 | [](https://twitter.com/noerihuisman)
6 | [](https://arvr.social/@noerihuisman)
7 | [](https://ko-fi.com/fernsolutions)
8 |
9 | This component expands on the built-in `stats` component with additional stats. It's intended for debugging and development purposes only.
10 |
11 | Checkout the example: [Online Demo](https://aframe-components.fern.solutions/extra-stats) | [Source](https://github.com/mrxz/fern-aframe-components/blob/main/extra-stats/example/index.html)
12 |
13 | ## Usage
14 | Load the script from [npm](https://www.npmjs.com/package/@fern-solutions/aframe-extra-stats) or add the following script tag:
15 | ```HTML
16 |
17 | ```
18 |
19 | The `extra-stats` component should be added to an `` and is intended to **_replace_** the `stats` component. Make sure to only add one or the other to the ``. Example:
20 | ```HTML
21 |
22 |
23 |
24 | ```
25 |
26 | Properties can be used to enable or disable groups. For example, the following results in a stats panel similar to the built-in one:
27 | ```HTML
28 |
29 |
30 |
31 | ```
32 |
33 | **Note:** The properties can't be changed after initialization, as they determin with which plugins rStats is initialized.
34 |
35 | ## Properties
36 | | Name | Type | Default |Description |
37 | | ---- | ---- | ------- |----------- |
38 | | `three` | boolean | true | Show the Three.js related stats |
39 | | `aframe` | boolean | true | Show the A-Frame related stats ("Load Time" and "Entities") |
40 | | `three-alloc` | boolean | true | Show the Three.js allocations of various types (Vectors, Matrices, Quaternions and Colors) |
41 |
--------------------------------------------------------------------------------
/sky-background/README.md:
--------------------------------------------------------------------------------
1 | # Sky Background component
2 | [](https://www.npmjs.com/package/@fern-solutions/aframe-sky-background)
3 | [](https://www.npmjs.com/package/@fern-solutions/aframe-sky-background)
4 | [](https://github.com/mrxz/fern-aframe-components/)
5 | [](https://twitter.com/noerihuisman)
6 | [](https://arvr.social/@noerihuisman)
7 | [](https://ko-fi.com/fernsolutions)
8 |
9 | This primitives allows a sky to be added that's either a gradient or an equirectangular skybox. In contrast to the built-in `` this doesn't use a sphere geometry. It renders a fullscreen triangle covering the far plane, ensuring it's always in the background and more performant.
10 |
11 | Checkout the example: [Online Demo](https://aframe-components.fern.solutions/sky-background) | [Source](https://github.com/mrxz/fern-aframe-components/blob/main/sky-background/example/index.html)
12 |
13 | ## Usage
14 | Load the script from [npm](https://www.npmjs.com/package/@fern-solutions/aframe-sky-background) or add the following script tag:
15 | ```HTML
16 |
17 | ```
18 |
19 | The `` primitive can be used as follows:
20 | ```HTML
21 |
22 | ```
23 | Instead of a gradient sky, an equirectangular skybox texture can be used as well:
24 | ```HTML
25 |
26 | ```
27 |
28 | ## Attributes
29 | | Name | Type | Default |Description |
30 | | ---- | ---- | ------- |----------- |
31 | | `top-color` | color | #0077ff | The solid color of the sky at the top |
32 | | `bottom-color` | color | #ffffff | The solid color of the sky at the bottom |
33 | | `offset` | number | 120 | Offset in meters to 'angle' the gradient a bit |
34 | | `exponent` | number | 0.9 | Exponent used to blend between the top and bottom color as a function of height |
35 | | `src` | map | null | The equirectangular texture to use, disables the gradient sky |
36 |
37 |
--------------------------------------------------------------------------------
/mirror/README.md:
--------------------------------------------------------------------------------
1 | # Mirror component
2 | [](https://www.npmjs.com/package/@fern-solutions/aframe-mirror)
3 | [](https://www.npmjs.com/package/@fern-solutions/aframe-mirror)
4 | [](https://github.com/mrxz/fern-aframe-components/)
5 | [](https://twitter.com/noerihuisman)
6 | [](https://arvr.social/@noerihuisman)
7 | [](https://ko-fi.com/fernsolutions)
8 |
9 | The `mirror` component and corresponding `` primitive allows a high-quality mirror to be added to your A-Frame scenes with ease. Instead of rendering to a texture, it uses a stencil buffer and renders directly into the framebuffer resulting in a high-quality mirror. It works in both desktop and VR mode.
10 |
11 | Inspiration for this came from a snippet from Carmack's Unscripted talk during the Meta Connect 2022 (emphasis mine):
12 | > Almost all the mirrors that you see in VR games are done by rendering a separate view and they're usually **blurrier, aliased, generally not particularly high quality**. While in Home we're doing a neat little trick which is actually what I was doing all the way back in the Doom 3 game, where you sort of flip the world around, render through that cut-out and you can get as high a quality in the mirror as you get looking at things directly.
13 | > Source: https://www.youtube.com/watch?v=ouq5yyzSiAw&t=892s
14 |
15 | Checkout the example: [Online Demo](https://aframe-components.fern.solutions/mirror) | [Source](https://github.com/mrxz/fern-aframe-components/blob/main/mirror/example/index.html)
16 |
17 | ## Usage
18 | Load the script from [npm](https://www.npmjs.com/package/@fern-solutions/aframe-mirror) or add the following script tag:
19 | ```HTML
20 |
21 | ```
22 |
23 | The `a-mirror` primitive can be added to your scene as follows:
24 | ```HTML
25 |
26 | ```
27 |
28 | Additionally layers can be specified to show or hide objects in the mirror view. For example, you might want to render the avatar of the user only in the mirror and not the main camera view.
29 |
30 | ## Properties
31 | | Name | Type | Default |Description |
32 | | ---- | ---- | ------- |----------- |
33 | | `layers` | array | [0] | List of layers that should be enabled when rendering the mirror view |
34 |
35 | ## Limitations
36 | * Mirrors are not rendered recursively, so any mirror seen from another mirror will just render as an opaque plane
37 | * Avoid mixing transparency with mirrors (e.g. looking at a mirror through transparent objects). Depending on the render-order this either results in the overlap being an opaque mirror or the transparent object not being visible.
38 |
--------------------------------------------------------------------------------
/extra-stats/src/rStats.three-alloc.js:
--------------------------------------------------------------------------------
1 | export function threeAllocStats() {
2 | let _rS = null;
3 |
4 | const _values = {
5 | v2: {
6 | caption: 'Vector2'
7 | },
8 | v3: {
9 | caption: 'Vector3'
10 | },
11 | v4: {
12 | caption: 'Vector4'
13 | },
14 | quat: {
15 | caption: 'Quaternion'
16 | },
17 | mat3: {
18 | caption: 'Matrix3'
19 | },
20 | mat4: {
21 | caption: 'Matrix4'
22 | },
23 | color: {
24 | caption: 'Color'
25 | },
26 | }
27 |
28 | const keys = ['v2', 'v3', 'v4', 'quat', 'mat3', 'mat4', 'color'];
29 | const _groups = [{
30 | caption: 'Three.js allocs',
31 | values: keys
32 | }];
33 |
34 | const counters = {};
35 | const resetCounters = () => keys.forEach(key => counters[key] = 0);
36 | resetCounters();
37 | const increment = (key) => counters[key]++;
38 |
39 | function _update() {
40 | keys.forEach(key => _rS(key).set(counters[key]));
41 | resetCounters();
42 | }
43 |
44 | function _start() { }
45 |
46 | function _end() { }
47 |
48 | class InstrumentedVector2 extends THREE.Vector2 {
49 | constructor() {
50 | super(...arguments);
51 | increment('v2');
52 | }
53 | }
54 | class InstrumentedVector3 extends THREE.Vector3 {
55 | constructor() {
56 | super(...arguments);
57 | increment('v3');
58 | }
59 | }
60 | class InstrumentedVector4 extends THREE.Vector4 {
61 | constructor() {
62 | super(...arguments);
63 | increment('v4');
64 | }
65 | }
66 | class InstrumentedQuaternion extends THREE.Quaternion {
67 | constructor() {
68 | super(...arguments);
69 | increment('quat');
70 | }
71 | }
72 | class InstrumentedMatrix3 extends THREE.Matrix3 {
73 | constructor() {
74 | super(...arguments);
75 | increment('mat3');
76 | }
77 | }
78 | class InstrumentedMatrix4 extends THREE.Matrix4 {
79 | constructor() {
80 | super(...arguments);
81 | increment('mat4');
82 | }
83 | }
84 | class InstrumentedColor extends THREE.Color {
85 | constructor() {
86 | super(...arguments);
87 | increment('color');
88 | }
89 | }
90 | function _attach(r) {
91 | _rS = r;
92 | THREE.Vector2 = InstrumentedVector2;
93 | THREE.Vector3 = InstrumentedVector3;
94 | THREE.Vector4 = InstrumentedVector4;
95 | THREE.Quaternion = InstrumentedQuaternion;
96 | THREE.Matrix3 = InstrumentedMatrix3;
97 | THREE.Matrix4 = InstrumentedMatrix4;
98 | THREE.Color = InstrumentedColor;
99 | }
100 |
101 | return {
102 | update: _update,
103 | start: _start,
104 | end: _end,
105 | attach: _attach,
106 | values: _values,
107 | groups: _groups,
108 | fractions: []
109 | };
110 | }
--------------------------------------------------------------------------------
/3dof/example/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Fern A-Frame Components | 3DoF
5 |
6 |
7 |
8 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
--------------------------------------------------------------------------------
/mirror/example/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Fern A-Frame Components | Mirror
5 |
6 |
7 |
8 |
9 |
10 |
11 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
--------------------------------------------------------------------------------
/highlight/README.md:
--------------------------------------------------------------------------------
1 | # Highlight component
2 | [](https://www.npmjs.com/package/@fern-solutions/aframe-highlight)
3 | [](https://www.npmjs.com/package/@fern-solutions/aframe-highlight)
4 | [](https://github.com/mrxz/fern-aframe-components/)
5 | [](https://twitter.com/noerihuisman)
6 | [](https://arvr.social/@noerihuisman)
7 | [](https://ko-fi.com/fernsolutions)
8 |
9 | This component adds a highlight to objects. The highlight can be drawn on either the occluded parts (default) or the visible parts (`mode: visible`). This allows the user to easily and quickly spot them. The highlight consists of accentuating the rim of the object.
10 |
11 | Checkout the example: [Online Demo](https://aframe-components.fern.solutions/highlight) | [Source](https://github.com/mrxz/fern-aframe-components/blob/main/highlight/example/index.html)
12 |
13 | ## Usage
14 | Load the script from [npm](https://www.npmjs.com/package/@fern-solutions/aframe-highlight) or add the following script tag:
15 | ```HTML
16 |
17 | ```
18 |
19 | The `highlight` component can be attached to any object:
20 | ```HTML
21 |
22 | ```
23 |
24 | To ensure certain objects or entities are rendered on top of any highlight (e.g. hands), you can use the `above-highlight` component:
25 | ```HTML
26 |
27 | ```
28 |
29 | ## Properties
30 | | Name | Type | Default |Description |
31 | | ---- | ---- | ------- |----------- |
32 | | `rimColor` | color | #FF0000 | The color at the rim of the object |
33 | | `rimOpacity` | float | 1.0 | The opacity of the rim between 0.0 and 1.0 |
34 | | `coreColor` | color | #000000 | The color at the core of the object |
35 | | `coreOpacity` | float | 0.0 | The opacity of the core between 0.0 and 1.0 |
36 | | `mode` | 'occlusion' or 'visible | occlusion | Whether to show the highlight on occluded or visible parts of the object|
37 |
38 | ## Caveats
39 | This component tries to be a relatively lightweight and does not introduce any post-processing. Instead it renders the highlighted objects one (or two) more times to achieve the effect. There are however a couple of caveats associated with this:
40 | 1. If the object is expensive to draw, the highlight rendering can be expensive as well
41 | 2. The component uses an `Object3D` directly attached to the scene with `renderOrder` set to 1000. If you make use of `renderOrder` make sure there is no conflict.
42 | 3. When rendering the highlight on occluded parts, the object itself is rendered without proper depth testing, meaning for concave objects the highlight won't always match the outer surface.
43 | 4. Multiple objects with highlights when occluded can render in arbitrary order. It's recommended to limit the amount of entities this effect is used on and try to make sure these entities don't overlap each other.
--------------------------------------------------------------------------------
/effekseer/README.md:
--------------------------------------------------------------------------------
1 | # Effekseer component
2 | [](https://www.npmjs.com/package/@fern-solutions/aframe-effekseer)
3 | [](https://www.npmjs.com/package/@fern-solutions/aframe-effekseer)
4 | [](https://github.com/mrxz/fern-aframe-components/)
5 | [](https://twitter.com/noerihuisman)
6 | [](https://arvr.social/@noerihuisman)
7 | [](https://ko-fi.com/fernsolutions)
8 |
9 | This component allows integrating [Effekseer](https://effekseer.github.io/en/) effects into A-Frame projects. The effects also work in VR, though not in AR ([EffekseerForWebGL#74](https://github.com/effekseer/EffekseerForWebGL/issues/74)).
10 |
11 | Checkout the example: [Online Demo](https://aframe-components.fern.solutions/effekseer) | [Source](https://github.com/mrxz/fern-aframe-components/blob/main/effekseer/example/index.html)
12 |
13 | ## Usage
14 | The setup requires a couple of libraries to be loaded before the component is loaded. For testing the library the below snippet can be copied and used, but for production use it's recommended to bundle your own versions of the relevant dependencies:
15 | ```HTML
16 |
17 |
18 |
19 |
20 |
21 |
22 | ```
23 |
24 | The `effekseer` system needs to be configured on the `` to load the effekseer wasm:
25 | ```HTML
26 |
27 |
28 | ```
29 |
30 | Next you can add effects to your scene as follows
31 | ```HTML
32 |
33 |
34 |
35 | (...)
36 |
37 | ```
38 |
39 | ## Properties
40 | The `effekseer` component supports the following properties:
41 | | Name | Type | Default |Description |
42 | | ---- | ---- | ------- |----------- |
43 | | `src` | asset | | URL or path to the `.efk` or `.efkpkg` (requires zip.js) |
44 | | `autoplay` | boolean | true | Automatically start playing the effect once loaded |
45 | | `loop` | boolean | false | Restart the effect as soon as it ends |
46 | | `dynamic` | boolean | false | Update the world transform of the effect every tick. Allows the effect to move, rotate and scale along with the entity. Only enabled if you need this behaviour, otherwise leave it disabled for performance reasons |
47 |
48 | ## Methods
49 | The component exposes a couple of methods for interacting with the effect: `playEffect()`, `pauseEffect()`, `resumeEffect()`, `stopEffect()` and `setTargetLocation(targetLocation: THREE.Vector3)`
50 | (See example for details on how to use these)
--------------------------------------------------------------------------------
/hud/example/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Fern A-Frame Components | HUD
5 |
6 |
7 |
8 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
--------------------------------------------------------------------------------
/sky-background/src/index.js:
--------------------------------------------------------------------------------
1 | AFRAME.registerShader('sky-background', {
2 | schema: {
3 | topColor: { type: 'color', is: 'uniform', default: '#0077ff' },
4 | bottomColor: { type: 'color', is: 'uniform', default: '#ffffff' },
5 | offset: { type: 'float', is: 'uniform', default: 120.0 },
6 | exponent: { type: 'float', is: 'uniform', default: 0.9 },
7 | src: {type: 'map'},
8 | },
9 | vertexShader: /* glsl */`
10 | varying vec2 vUv;
11 |
12 | void main() {
13 | vUv = uv*2.0;
14 | gl_Position = vec4(position.xy, 1.0, 1.0);
15 | }
16 | `,
17 | fragmentShader: /* glsl */`
18 | uniform vec3 topColor;
19 | uniform vec3 bottomColor;
20 | uniform float offset;
21 | uniform float exponent;
22 | uniform sampler2D map;
23 |
24 | uniform mat4 cameraWorldMatrix;
25 | uniform mat4 invProjectionMatrix;
26 |
27 | varying vec2 vUv;
28 |
29 | #include
30 | #include
31 |
32 | void main() {
33 | vec2 ndc = 2.0 * vUv - vec2(1.0);
34 | // Convert ndc to ray origin
35 | vec4 rayOrigin4 = cameraWorldMatrix * invProjectionMatrix * vec4( ndc, - 1.0, 1.0 );
36 | vec3 rayOrigin = rayOrigin4.xyz / rayOrigin4.w;
37 | // Compute ray direction
38 | vec3 rayDirection = normalize( mat3(cameraWorldMatrix) * ( invProjectionMatrix * vec4( ndc, 0.0, 1.0 ) ).xyz );
39 |
40 | #ifdef USE_MAP
41 | gl_FragColor = vec4(texture(map, equirectUv(rayDirection)).rgb, 1.0);
42 | #else
43 | float h = normalize((rayOrigin + rayDirection * 500.0) + offset).y;
44 | gl_FragColor = vec4(mix(bottomColor, topColor, max(pow(max(h, 0.0 ), exponent), 0.0)), 1.0);
45 | #endif
46 |
47 | #include
48 | #include
49 | #include
50 | }`,
51 | init: function(data) {
52 | // Handle compatibility with older Three.js versions (A-Frame <1.5.0)
53 | if(+AFRAME.THREE.REVISION < 158) {
54 | this.fragmentShader = this.fragmentShader.replace(/colorspace_fragment/, 'encodings_fragment');
55 | }
56 |
57 | this.__proto__.__proto__.init.call(this, data);
58 | this.material.uniforms.map = { value: null };
59 | this.el.addEventListener('materialtextureloaded', e => {
60 | // Mipmaps on equirectangular images causes a seam
61 | e.detail.texture.generateMipmaps = false;
62 | // Unlike built-in materials, having 'material.map' doesn't result in Three.js
63 | // setting the uniform as well, so do it manually.
64 | this.material.uniforms.map.value = e.detail.texture;
65 | });
66 | },
67 | update: function(data) {
68 | this.__proto__.__proto__.update.call(this, data);
69 | AFRAME.utils.material.updateMap(this, data);
70 | }
71 | });
72 |
73 | AFRAME.registerComponent('sky-background', {
74 | init: function() {
75 | const mesh = this.el.getObject3D('mesh');
76 | mesh.frustumCulled = false;
77 | mesh.material.uniforms.cameraWorldMatrix = { value: new THREE.Matrix4() };
78 | mesh.material.uniforms.invProjectionMatrix = { value: new THREE.Matrix4() };
79 | mesh.onBeforeRender = (renderer, scene, camera, geometry, material, group) => {
80 | material.uniforms.cameraWorldMatrix.value.copy(camera.matrixWorld);
81 | material.uniforms.cameraWorldMatrix.needsUpdate = true;
82 | material.uniforms.invProjectionMatrix.value.copy(camera.projectionMatrix).invert();
83 | material.uniforms.invProjectionMatrix.needsUpdate = true;
84 | }
85 | }
86 | });
87 |
88 | AFRAME.registerPrimitive('a-sky-background', {
89 | defaultComponents: {
90 | geometry: {
91 | primitive: 'triangle',
92 | vertexA: { x: -1, y: -1, z: 0 },
93 | vertexB: { x: 3, y: -1, z: 0 },
94 | vertexC: { x: -1, y: 3, z: 0 },
95 | },
96 | material: {
97 | shader: 'sky-background',
98 |
99 | },
100 | 'sky-background': {},
101 | },
102 | mappings: {
103 | 'top-color': 'material.topColor',
104 | 'bottom-color': 'material.bottomColor',
105 | 'offset': 'material.offset',
106 | 'exponent': 'material.exponent',
107 | 'src': 'material.src'
108 | }
109 | });
110 |
--------------------------------------------------------------------------------
/effekseer/example/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Fern A-Frame Components | Effekseer
7 |
8 |
9 |
10 |
11 |
12 |
13 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
--------------------------------------------------------------------------------
/motion-controller/src/utils.ts:
--------------------------------------------------------------------------------
1 | import * as THREE from "three";
2 |
3 | export function phongMaterialFromStandardMaterial(sourceMaterial: THREE.MeshStandardMaterial) {
4 | return new THREE.MeshPhongMaterial({
5 | color: sourceMaterial.color.clone(),
6 | map: sourceMaterial.map,
7 | side: sourceMaterial.side,
8 | });
9 | }
10 |
11 | export function occluderMaterialFromStandardMaterial(sourceMaterial: THREE.MeshStandardMaterial) {
12 | return new THREE.MeshBasicMaterial({
13 | colorWrite: false,
14 | side: sourceMaterial.side,
15 | });
16 | }
17 |
18 | interface MaterialOnBeforeRender {
19 | onBeforeRender: (renderer: THREE.WebGLRenderer, scene: THREE.Scene, camera: THREE.Camera, geometry: THREE.BufferGeometry, object: THREE.Object3D, group: any) => void;
20 | }
21 | export function hologramMaterialFromStandardMaterial(sourceMaterial: THREE.MeshStandardMaterial) {
22 | const hologramMaterial = new THREE.ShaderMaterial({
23 | side: sourceMaterial.side,
24 | opacity: 0.4,
25 | transparent: true,
26 | vertexShader: /* glsl */`
27 | #include
28 | #include
29 | #include
30 |
31 | uniform float outline;
32 |
33 | varying vec3 vObjectPosition;
34 | varying vec3 vWorldPosition;
35 |
36 | void main() {
37 | #include
38 | #include
39 | #include
40 | #include
41 | #include
42 |
43 | #include
44 | if(outline > 0.0) {
45 | transformed += normal*0.001;
46 | }
47 | #include
48 | #include
49 |
50 | vObjectPosition = position;
51 |
52 | vWorldPosition = mvPosition.xyz;
53 | }
54 | `,
55 | // Note: this shader is hard-coded and optimized for the generic-hand asset
56 | fragmentShader: /* glsl */`
57 | #include
58 | #include
59 | #include
60 |
61 | uniform float outline;
62 |
63 | varying vec3 vObjectPosition;
64 | varying vec3 vWorldPosition;
65 |
66 | void main() {
67 |
68 | if(outline > 0.0) {
69 | float alpha = 0.5 * min((-vObjectPosition.y + 0.09)/0.15, 1.0);
70 | gl_FragColor = vec4(1.0, 1.0, 1.0, alpha);
71 | } else {
72 | vec3 toCamera = normalize(cameraPosition - vWorldPosition);
73 | float factor = pow(1.0 - dot(toCamera, vNormal), 10.0);
74 |
75 | float alpha = 0.5 * min((-vObjectPosition.y + 0.06)/0.15, 1.0);
76 | vec3 color = mix(vec3(0.0), vec3(0.8, 0.8, 1.0), factor);
77 |
78 | gl_FragColor = vec4(color, alpha);
79 | }
80 |
81 | #include
82 | #include
83 | }
84 | `,
85 | uniforms: {
86 | outline: { value: 1.0 }
87 | }
88 | });
89 |
90 | // Note: onBeforeRender on material lacks types (internal for Three.js), but is slightly more convenient
91 | // in our case, so use it anyway.
92 | (hologramMaterial as unknown as MaterialOnBeforeRender).onBeforeRender = (renderer, scene, camera, geometry, object, group) => {
93 | // Block depth
94 | hologramMaterial.colorWrite = false;
95 | renderer.renderBufferDirect(camera, scene, geometry, hologramMaterial, object, group);
96 | hologramMaterial.colorWrite = true;
97 | // Outline
98 | hologramMaterial.side = THREE.BackSide;
99 | hologramMaterial.uniforms.outline.value = 1.0;
100 | (hologramMaterial.uniforms.outline as any).needsUpdate = true;
101 | hologramMaterial.needsUpdate = true;
102 | renderer.renderBufferDirect(camera, scene, geometry, hologramMaterial, object, group);
103 | // Restore for normal render
104 | hologramMaterial.side = THREE.FrontSide;
105 | hologramMaterial.uniforms.outline.value = 0.0;
106 | (hologramMaterial.uniforms.outline as any).needsUpdate = true;
107 | hologramMaterial.needsUpdate = true;
108 |
109 | }
110 |
111 | return hologramMaterial;
112 | }
--------------------------------------------------------------------------------
/effekseer/src/effekseer.component.ts:
--------------------------------------------------------------------------------
1 | import * as AFRAME from 'aframe';
2 | import * as THREE from 'three';
3 | import 'effekseer';
4 |
5 | /**
6 | * Component for rendering an Effekseer effect.
7 | */
8 | export const EffekseerComponent = AFRAME.registerComponent('effekseer', {
9 | schema: {
10 | /** The .efk or .efkpkg file to use */
11 | src: { type: 'asset' },
12 |
13 | /** Whether or not to play the effect as soon as it's loaded */
14 | autoPlay: { type: 'boolean', default: true },
15 | /** Whether to loop the effect or not */
16 | loop: { type: 'boolean', default: false },
17 | /** Whether or not to update the effects scale, position and rotation each tick */
18 | dynamic: { type: 'boolean', default: false },
19 | },
20 | __fields: {} as {
21 | effect: effekseer.EffekseerEffect|null,
22 | handle: effekseer.EffekseerHandle|null,
23 |
24 | tempMatrixArray: Float32Array,
25 | targetLocation: THREE.Vector3,
26 | },
27 | init: function() {
28 | this.tempMatrixArray = new Float32Array(16);
29 | this.targetLocation = new THREE.Vector3();
30 | },
31 | update: function(oldData) {
32 | if(oldData.src !== this.data.src) {
33 | // Ask system for the effect
34 | this.effect = null;
35 | if(this.handle) {
36 | this.handle.stop();
37 | this.handle = null;
38 | }
39 | if(this.data.src) {
40 | const loadingSrc = this.data.src;
41 | this.system.getOrLoadEffect(this.data.src).then(effect => {
42 | // Make sure the loaded effect is still the intended effect
43 | if(this.data.src !== loadingSrc) {
44 | return;
45 | }
46 | this.effect = effect;
47 | // Request a handle
48 | if(this.data.autoPlay) {
49 | // Ensure the objects matrix world is set
50 | this.el.object3D.updateMatrixWorld();
51 | this.playEffect();
52 | }
53 | }).catch(reason => {
54 | console.error(`Failed to load effect ${this.data.src}: ${reason}`);
55 | if(this.data.src === loadingSrc) {
56 | this.effect = null;
57 | this.handle = null;
58 | }
59 | });
60 | }
61 | }
62 | },
63 | updateTransform: function() {
64 | this.el.object3D.matrixWorld.toArray(this.tempMatrixArray);
65 | this.handle!.setMatrix(this.tempMatrixArray);
66 | },
67 | /**
68 | * Starts a new playback of the effect. This doesn't stop any already playing
69 | * effects associated with this component. Call {@link stopEffect()} for that instead
70 | */
71 | playEffect: function() {
72 | this.handle = this.system.playEffect(this.effect!);
73 | this.updateTransform();
74 | this.setTargetLocation(this.targetLocation);
75 | },
76 | /**
77 | * Pauses the playback of the effect
78 | */
79 | pauseEffect: function() {
80 | this.handle?.setPaused(true);
81 | },
82 | /**
83 | * Resumes the effect in case it has been paused
84 | */
85 | resumeEffect: function() {
86 | this.handle?.setPaused(false);
87 | },
88 | /**
89 | * Stops the effect
90 | */
91 | stopEffect: function() {
92 | this.handle?.stop();
93 | this.handle = null;
94 | },
95 | /**
96 | * Sets the target location for effects that make use of this.
97 | * This is NOT the location of the effect, but the location the effect _targets_ on.
98 | * Not all effects make use of this location.
99 | * @param target The location the effect should target on
100 | */
101 | setTargetLocation: function(target: THREE.Vector3) {
102 | this.targetLocation.copy(target);
103 | this.handle?.setTargetLocation(target.x, target.y, target.z);
104 | },
105 | tick: function() {
106 | if(!this.handle) {
107 | return;
108 | }
109 |
110 | // FIXME: It seems not all effects have a natural end
111 | // preventing them from looping at all.
112 | if(this.data.loop && !this.handle.exists) {
113 | this.playEffect();
114 | }
115 |
116 | if(this.data.dynamic) {
117 | this.updateTransform();
118 | }
119 | },
120 | remove: function() {
121 | this.handle?.stop();
122 | }
123 | });
124 |
125 | declare module "aframe" {
126 | interface Components {
127 | "effekseer": InstanceType
128 | }
129 | }
130 |
--------------------------------------------------------------------------------
/effekseer/src/effekseer.system.ts:
--------------------------------------------------------------------------------
1 | import * as AFRAME from 'aframe';
2 | import * as THREE from 'three';
3 | import 'effekseer';
4 | import * as zip from '@zip.js/zip.js';
5 |
6 | const createUnzip = async function(buffer: Uint8Array) {
7 | const cachedFiles: {[key: string]: Uint8Array} = {};
8 | const reader = new zip.ZipReader(new zip.Uint8ArrayReader(buffer));
9 | const entries = await reader.getEntries({});
10 | await Promise.allSettled(entries.map(async e => {
11 | const writer = new zip.Uint8ArrayWriter();
12 | cachedFiles[e.filename] = await e.getData!(writer);
13 | }));
14 |
15 | return function(_buffer: Uint8Array) {
16 | return {
17 | decompress(file: string) {
18 | return cachedFiles[file];
19 | }
20 | }
21 | };
22 | }
23 |
24 | /**
25 | * System for managing the Effekseer context and handling rendering of the effects
26 | */
27 | export const EffekseerSystem = AFRAME.registerSystem('effekseer', {
28 | schema: {
29 | /** URL to the effekseer.wasm file */
30 | wasmPath: { type: "string" },
31 | /** Frame-rate at which the effects are played back */
32 | frameRate: { type: "number", default: 60.0 }
33 | },
34 |
35 | __fields: {} as {
36 | getContext: Promise,
37 | context: effekseer.EffekseerContext,
38 | effects: Map,
39 |
40 | fileLoader: THREE.FileLoader,
41 | sentinel: THREE.Mesh,
42 | },
43 |
44 | init: function() {
45 | this.effects = new Map();
46 | this.fileLoader = new THREE.FileLoader().setResponseType('arraybuffer');
47 |
48 | const renderer = this.el.sceneEl.renderer;
49 | this.getContext = new Promise((resolve, reject) => {
50 | effekseer.initRuntime(this.data.wasmPath, () => {
51 | this.context = effekseer.createContext();
52 | this.context.init(renderer.getContext(), {
53 | instanceMaxCount: 2000,
54 | squareMaxCount: 8000,
55 | });
56 | this.context.setRestorationOfStatesFlag(false);
57 | resolve(this.context);
58 | }, () => {
59 | reject('Failed to load effekseer wasm')
60 | });
61 | })
62 |
63 | // Create a sentinel
64 | const sentinel = new THREE.Mesh();
65 | sentinel.frustumCulled = false;
66 | (sentinel.material as THREE.MeshBasicMaterial).transparent = true;
67 | sentinel.renderOrder = Number.MAX_VALUE;
68 | this.sentinel = sentinel;
69 | this.el.sceneEl.object3D.add(this.sentinel);
70 |
71 | sentinel.onAfterRender = (renderer, _scene, camera) => {
72 | if(!this.context) {
73 | return;
74 | }
75 | const renderTarget = renderer.getRenderTarget();
76 |
77 | this.context.setProjectionMatrix(Float32Array.from(camera.projectionMatrix.elements));
78 | this.context.setCameraMatrix(Float32Array.from(camera.matrixWorldInverse.elements));
79 | this.context.draw();
80 |
81 | renderer.resetState();
82 | renderer.setRenderTarget(renderTarget);
83 | }
84 | },
85 |
86 | getOrLoadEffect(src: string): Promise {
87 | if(this.effects.has(src)) {
88 | return Promise.resolve(this.effects.get(src)!);
89 | }
90 |
91 | return this.fileLoader.loadAsync(src).then(buffer => new Promise((resolve, reject) => {
92 | this.getContext.then(_ => {
93 | const basePath = src.substring(0, src.lastIndexOf('/') + 1);
94 | let effect: effekseer.EffekseerEffect;
95 | const onload = () => {
96 | // The onload callback doesn't provide the effect as an argument,
97 | // so use a timeout to ensure the return of loadEffect has taken place.
98 | setTimeout(() => resolve(effect!), 0)
99 | };
100 | if(src.endsWith(".efkpkg")) {
101 | // Note: While the library 'handles' unzipping it does so synchronously
102 | // Since zip.js is async, handle the unzipping upfront.
103 | createUnzip(new Uint8Array(buffer as ArrayBuffer)).then(Unzip => {
104 | effect = this.context.loadEffectPackage(buffer, Unzip, 1.0,
105 | onload,
106 | reject);
107 | this.effects.set(src, effect);
108 | })
109 | } else {
110 | effect = this.context.loadEffect(buffer, 1.0,
111 | onload,
112 | reject,
113 | (path) => {
114 | // Paths are relative to src
115 | return basePath + path;
116 | });
117 | this.effects.set(src, effect);
118 | }
119 | });
120 | }));
121 | },
122 |
123 | playEffect(effect: effekseer.EffekseerEffect): effekseer.EffekseerHandle {
124 | // Note: entire transform matrix is set briefly after, so simply pass origin here
125 | return this.context.play(effect, 0, 0, 0);
126 | },
127 |
128 | tick: function(_t, dt) {
129 | if(!this.context) {
130 | return;
131 | }
132 | this.context.update(dt/1000.0 * this.data.frameRate);
133 | }
134 | });
135 |
136 | declare module "aframe" {
137 | interface Systems {
138 | "effekseer": InstanceType,
139 | }
140 | }
141 |
--------------------------------------------------------------------------------
/fix-fog/example/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Fern A-Frame Components | Fix Fog
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
--------------------------------------------------------------------------------
/hud/src/index.js:
--------------------------------------------------------------------------------
1 | AFRAME.registerPrimitive('a-hud', {
2 | defaultComponents: {
3 | hud: { },
4 | },
5 | mappings: {
6 | radius: 'hud.radius',
7 | 'horizontal-fov': 'hud.horizontalFov',
8 | 'vertical-fov': 'hud.verticalFov',
9 | 'scale-factor': 'hud.scaleFactor',
10 | }
11 | });
12 |
13 | AFRAME.registerPrimitive('a-hud-element', {
14 | defaultComponents: {
15 | 'hud-element': { },
16 | },
17 | mappings: {
18 | align: 'hud-element.align',
19 | anchor: 'hud-element.anchor',
20 | 'content-size': 'hud-element.contentSize',
21 | 'hud-size': 'hud-element.hudSize',
22 | }
23 | });
24 |
25 | AFRAME.registerComponent('hud', {
26 | schema: {
27 | radius: { type: 'number', default: 1 },
28 | horizontalFov: { type: 'number', default: 80 },
29 | verticalFov: { type: 'number', default: 60 },
30 | scaleFactor: { type: 'number', default: 1.0 },
31 | },
32 | init: function() {
33 | this.flat = true;
34 | this.el.sceneEl.addEventListener('rendererresize', () => {
35 | // Relayout is only needed on resize if the layout is flat (= screen space)
36 | if(this.flat) {
37 | this.relayout()
38 | }
39 | });
40 | this.el.sceneEl.addEventListener('enter-vr', () => {
41 | this.flat = false;
42 | this.relayout();
43 | });
44 | this.el.sceneEl.addEventListener('exit-vr', () => {
45 | this.flat = true;
46 | this.relayout();
47 | });
48 | },
49 | relayout: function() {
50 | for(const child of this.el.children) {
51 | if(child.components['hud-element']) {
52 | child.components['hud-element'].layout(this)
53 | }
54 | };
55 | },
56 | convertCoordinates: function(coordinates, outV3) {
57 | if(this.flat) {
58 | const camera = this.el.sceneEl.camera;
59 | const yScale = Math.tan(THREE.MathUtils.DEG2RAD * 0.5 * camera.fov) / camera.zoom;
60 | const xScale = yScale * camera.aspect;
61 |
62 | outV3.set(coordinates.x * xScale, coordinates.y * yScale, -1);
63 | } else {
64 | // Compute spherical coordinates
65 | const theta = - this.data.horizontalFov/2.0 * coordinates.x;
66 | const phi = -90 + this.data.verticalFov/2.0 * coordinates.y;
67 |
68 | outV3.set(
69 | Math.sin(THREE.MathUtils.DEG2RAD*phi) * Math.sin(THREE.MathUtils.DEG2RAD*theta),
70 | Math.cos(THREE.MathUtils.DEG2RAD*phi),
71 | Math.sin(THREE.MathUtils.DEG2RAD*phi) * Math.cos(THREE.MathUtils.DEG2RAD*theta));
72 | outV3.multiplyScalar(this.data.radius);
73 | }
74 | },
75 | convertWidth: function(width) {
76 | return this.flat ? width * this.data.scaleFactor : width;
77 | },
78 | convertHeight: function(height) {
79 | // Height is given in "width percentage", so needs to be adjusted based on aspect ratio.
80 | const adjustedHeight = height * this.aspectRatio();
81 | return this.flat ? adjustedHeight * this.data.scaleFactor : adjustedHeight;
82 | },
83 | aspectRatio: function() {
84 | if(this.flat) {
85 | return this.el.sceneEl.camera.aspect;
86 | }
87 | return this.data.horizontalFov / this.data.verticalFov;
88 | },
89 | scale: function() {
90 | if(this.flat) {
91 | const camera = this.el.sceneEl.camera;
92 | const yScale = Math.tan(THREE.MathUtils.DEG2RAD * 0.5 * camera.fov) / camera.zoom;
93 | return 2.0 * yScale * camera.aspect * this.data.scaleFactor;
94 | }
95 | return this.data.horizontalFov/360 * this.data.radius*Math.PI*2.0;
96 | },
97 | orientate: (function() {
98 | const tempMat4 = new THREE.Matrix4();
99 | const up = new THREE.Vector3(0, 1, 0);
100 | const origin = new THREE.Vector3(0, 0, 0);
101 | return function(position, outQuaternion) {
102 | if(this.flat) {
103 | outQuaternion.identity();
104 | return;
105 | }
106 |
107 | tempMat4.lookAt(origin, position, up);
108 | outQuaternion.setFromRotationMatrix(tempMat4);
109 | };
110 | })()
111 | });
112 |
113 | /* Normalized coordinates lookup for anchor/align points */
114 | const COORDINATES = {
115 | 'top-left': new THREE.Vector2(-1.0, 1.0),
116 | 'top': new THREE.Vector2(0.0, 1.0),
117 | 'top-right': new THREE.Vector2(1.0, 1.0),
118 | 'right': new THREE.Vector2(1.0, 0.0),
119 | 'bottom-right': new THREE.Vector2(1.0, -1.0),
120 | 'bottom': new THREE.Vector2(0.0, -1.0),
121 | 'bottom-left': new THREE.Vector2(-1.0, -1.0),
122 | 'left': new THREE.Vector2(-1.0, 0.0),
123 | 'center': new THREE.Vector2(0.0, 0.0),
124 | };
125 |
126 | AFRAME.registerComponent('hud-element', {
127 | schema: {
128 | align: { type: 'string', default: 'center' },
129 | anchor: { type: 'string', default: 'center' },
130 | contentSize: { type: 'vec2', default: new THREE.Vector2(1.0, 1.0) },
131 | hudSize: { type: 'number', default: 1.0 },
132 | },
133 | init: function() {
134 | this.coordinates = new THREE.Vector2();
135 | },
136 | update: function() {
137 | const hud = this.el.parentElement.components['hud'];
138 | if(hud) {
139 | this.layout(hud);
140 | }
141 | },
142 | layout: function(hud) {
143 | const aspect = this.data.contentSize.y / this.data.contentSize.x;
144 |
145 | const coordinates = this.coordinates.copy(COORDINATES[this.data.align]);
146 | const anchor = COORDINATES[this.data.anchor];
147 | coordinates.x -= anchor.x * hud.convertWidth(this.data.hudSize);
148 | coordinates.y -= anchor.y * hud.convertHeight(this.data.hudSize * aspect);
149 | hud.convertCoordinates(coordinates, this.el.object3D.position);
150 |
151 | const scale = hud.scale() * this.data.hudSize / this.data.contentSize.x;
152 | this.el.object3D.scale.set(scale, scale, scale);
153 |
154 | hud.orientate(this.el.object3D.position, this.el.object3D.quaternion);
155 | }
156 | });
--------------------------------------------------------------------------------
/highlight/src/index.js:
--------------------------------------------------------------------------------
1 | const VERTEX_SHADER = /*glsl*/`
2 | #include
3 | #include
4 | #include
5 |
6 | out vec3 vNormal;
7 |
8 | void main() {
9 | vNormal = normalMatrix * normal;
10 |
11 | #include
12 | #include
13 | #include
14 | #include
15 | }`;
16 |
17 | let FRAGMENT_SHADER = /*glsl*/`
18 | #include
19 | #include
20 | #include
21 |
22 | uniform vec3 coreColor;
23 | uniform float coreOpacity;
24 | uniform vec3 rimColor;
25 | uniform float rimOpacity;
26 |
27 | in vec3 vNormal;
28 |
29 | void main() {
30 | float factor = 1.0 - max(0.0, dot(normalize(vNormal), vec3(0.0, 0.0, 1.0)));
31 | vec3 color = mix(coreColor, rimColor, factor);
32 | gl_FragColor = vec4(color, (factor * rimOpacity) + ((1.0 - factor) * coreOpacity));
33 |
34 | #include
35 | #include
36 | #include
37 | #include
38 | }`;
39 |
40 | // Handle compatibility with older Three.js versions (A-Frame 1.4.2)
41 | if(+AFRAME.THREE.REVISION < 158) {
42 | FRAGMENT_SHADER = FRAGMENT_SHADER.replace(/colorspace_fragment/, 'encodings_fragment');
43 | }
44 |
45 | AFRAME.registerSystem('highlight', {
46 | callbacks: [],
47 | afterCallbacks: [],
48 | init: function() {
49 | // Create a sentinel entity
50 | this.sentinel = new THREE.Mesh();
51 | this.sentinel.frustumCulled = false;
52 | this.sentinel.material.transparent = true;
53 | // FIXME: What if the application already uses render order for a different purpose?
54 | this.sentinel.renderOrder = 1000;
55 | this.el.object3D.add(this.sentinel)
56 |
57 | this.sentinel.onAfterRender = (renderer, scene, camera) => {
58 | this.callbacks.forEach(cb => cb(renderer, scene, camera));
59 | this.afterCallbacks.forEach(cb => cb(renderer, scene, camera))
60 | }
61 | },
62 | registerCallback: function(callback) {
63 | this.callbacks.push(callback);
64 | },
65 | unregisterCallback: function(callback) {
66 | const index = this.callbacks.indexOf(callback);
67 | if(index !== -1) {
68 | this.callbacks.splice(index, 1);
69 | }
70 | },
71 | registerAfterCallback: function(callback) {
72 | this.afterCallbacks.push(callback);
73 | },
74 | unregisterAfterCallback: function(callback) {
75 | const index = this.afterCallbacks.indexOf(callback);
76 | if(index !== -1) {
77 | this.afterCallbacks.splice(index, 1);
78 | }
79 | }
80 | });
81 |
82 | const HIGHLIGHT_ON_BEFORE_RENDER_HOOK = "_HIGHLIGHT_ON_BEFORE_RENDER_HOOK_";
83 | AFRAME.registerComponent('highlight', {
84 | schema: {
85 | coreColor: { type: "color", default: "#000000" },
86 | coreOpacity: { type: "number", default: 0.0, min: 0.0, max: 1.0 },
87 | rimColor: { type: "color", default: "#FF0000" },
88 | rimOpacity: { type: "number", default: 1.0, min: 0.0, max: 1.0 },
89 | mode: { type: "string", default: "occlusion" }, // occlusion, visible
90 | },
91 | modes: {
92 | "occlusion": { depthFunc: THREE.GreaterDepth, secondRender: true },
93 | "visible": { depthFunc: THREE.EqualDepth, secondRender: false },
94 | },
95 | init: function() {
96 | this.renderCalls = [];
97 |
98 | this.outlineMaterial = new THREE.ShaderMaterial({
99 | vertexShader: VERTEX_SHADER,
100 | fragmentShader: FRAGMENT_SHADER,
101 | transparent: true,
102 | depthTest: true,
103 | depthFunc: this.modes[this.data.mode].depthFunc,
104 | depthWrite: false,
105 | uniforms: {
106 | coreColor: { value: new THREE.Color(this.data.coreColor) },
107 | coreOpacity: { value: this.data.coreOpacity },
108 | rimColor: { value: new THREE.Color(this.data.rimColor) },
109 | rimOpacity: { value: this.data.rimOpacity },
110 | }
111 | });
112 |
113 | const setupRenderHooks = (object3D) => {
114 | object3D.traverse(c => {
115 | if(c.isMesh) {
116 | if(c.onBeforeRender !== THREE.Object3D.prototype.onBeforeRender && !c.onBeforeRender[HIGHLIGHT_ON_BEFORE_RENDER_HOOK]) {
117 | console.warn("Object already has an onBeforeRender hook! Highlight effect will likely not work for this entity!");
118 | return;
119 | }
120 |
121 | const captureRenderCallHook = (renderer, scene, camera, geometry, material, group) => {
122 | this.renderCalls.push({
123 | renderer, scene, camera, geometry, material, group, object: c
124 | });
125 | };
126 | captureRenderCallHook[HIGHLIGHT_ON_BEFORE_RENDER_HOOK] = true;
127 |
128 | c.onBeforeRender = captureRenderCallHook;
129 | }
130 | });
131 | }
132 | // Add hooks to existing meshes.
133 | setupRenderHooks(this.el.object3D);
134 | this.el.addEventListener('object3dset', e => {
135 | setupRenderHooks(e.detail.object);
136 | });
137 |
138 | this.callback = (renderer, scene, camera) => {
139 | this.renderCalls.forEach(renderCall => {
140 | const { renderer, scene, camera, geometry, material, group, object } = renderCall;
141 | renderer.renderBufferDirect(camera, scene, geometry, this.outlineMaterial, object, group);
142 |
143 | if(this.modes[this.data.mode].secondRender) {
144 | renderer.renderBufferDirect(camera, scene, geometry, material, object, group);
145 | }
146 | });
147 | this.renderCalls.splice(0, this.renderCalls.length);
148 | }
149 | this.system.registerCallback(this.callback);
150 | },
151 | update: function() {
152 | this.outlineMaterial.uniforms.coreColor.value.set(this.data.coreColor);
153 | this.outlineMaterial.uniforms.coreOpacity.value = this.data.coreOpacity;
154 | this.outlineMaterial.uniforms.rimColor.value.set(this.data.rimColor);
155 | this.outlineMaterial.uniforms.rimOpacity.value = this.data.rimOpacity;
156 | },
157 | remove: function() {
158 | this.system.unregisterCallback(this.callback);
159 | }
160 | });
161 |
162 | AFRAME.registerComponent('above-highlight', {
163 | init: function() {
164 | this.renderCalls = [];
165 |
166 | const setupRenderHooks = (object3D) => {
167 | object3D.traverse(c => {
168 | if(c.isMesh) {
169 | if(c.onBeforeRender !== THREE.Object3D.prototype.onBeforeRender && !c.onBeforeRender[HIGHLIGHT_ON_BEFORE_RENDER_HOOK]) {
170 | console.warn("Object already has an onBeforeRender hook! Highlight effect will likely not work for this entity!");
171 | return;
172 | }
173 |
174 | const captureRenderCallHook = (renderer, scene, camera, geometry, material, group) => {
175 | this.renderCalls.push({
176 | renderer, scene, camera, geometry, material, group, object: c
177 | });
178 | };
179 | captureRenderCallHook[HIGHLIGHT_ON_BEFORE_RENDER_HOOK] = true;
180 |
181 | c.onBeforeRender = captureRenderCallHook;
182 | }
183 | });
184 | }
185 | // Add hooks to existing meshes.
186 | setupRenderHooks(this.el.object3D);
187 | this.el.addEventListener('object3dset', e => {
188 | setupRenderHooks(e.detail.object);
189 | });
190 |
191 | this.callback = (renderer, scene, camera) => {
192 | this.renderCalls.forEach(renderCall => {
193 | const { renderer, scene, camera, geometry, material, group, object } = renderCall;
194 | renderer.renderBufferDirect(camera, scene, geometry, material, object, group);
195 | });
196 | this.renderCalls.splice(0, this.renderCalls.length);
197 | }
198 | this.el.sceneEl.systems.highlight.registerAfterCallback(this.callback);
199 | },
200 | remove: function() {
201 | this.el.sceneEl.systems.highlight.unregisterAfterCallback(this.callback);
202 | }
203 | });
--------------------------------------------------------------------------------
/motion-controller/src/motion-controller.system.ts:
--------------------------------------------------------------------------------
1 | import * as AFRAME from 'aframe';
2 | import { SceneEvent } from 'aframe';
3 | import { Component, fetchProfile, MotionController } from '@webxr-input-profiles/motion-controllers';
4 |
5 | const DEFAULT_INPUT_PROFILE_ASSETS_URI = 'https://cdn.jsdelivr.net/npm/@webxr-input-profiles/assets/dist/profiles';
6 | const HANDS_PROFILE_ID = 'generic-hand';
7 |
8 | export interface InputSourceRecord {
9 | xrInputSource: XRInputSource,
10 | motionController?: MotionController,
11 | componentState: {[key: string]: Component['values'] },
12 | jointState?: {poses: Float32Array, radii: Float32Array},
13 | };
14 |
15 | const MotionControllerSystem = AFRAME.registerSystem('motion-controller', {
16 | schema: {
17 | /** Base URI for fetching profiles and controller models */
18 | profilesUri: { type: 'string', default: DEFAULT_INPUT_PROFILE_ASSETS_URI },
19 | /** Enable or disable hand tracking (= pose for hand controllers) */
20 | enableHandTracking: { type: 'boolean', default: true },
21 | /** Whether or not input sources representing hands should be reported or not */
22 | enableHands: { type: 'boolean', default: true },
23 | },
24 | __fields: {} as {
25 | /* Currently active XR session */
26 | xrSession: XRSession|null;
27 | /* List of active input sources */
28 | inputSources: Array
29 |
30 | /* Dedicated slots for left/right hand for convenience */
31 | left: InputSourceRecord|null,
32 | right: InputSourceRecord|null,
33 | },
34 | init: function() {
35 | this.inputSources = [];
36 | this.left = null;
37 | this.right = null;
38 |
39 | if(this.data.enableHands && this.data.enableHandTracking) {
40 | this.sceneEl.setAttribute('webxr', {optionalFeatures: ['hand-tracking']});
41 | }
42 |
43 | const onInputSourcesChange = (event: XRInputSourceChangeEvent) => {
44 | event.removed.forEach(xrInputSource => {
45 | const index = this.inputSources.findIndex(inputSourceRecord => inputSourceRecord.xrInputSource === xrInputSource);
46 | if(index !== -1) {
47 | // TODO: Notify any component that holds it exclusively
48 | const [removed] = this.inputSources.splice(index, 1);
49 | if(this.left === removed) {
50 | this.left = null;
51 | }
52 | if(this.right === removed) {
53 | this.right = null;
54 | }
55 | }
56 | });
57 | event.added.forEach(async xrInputSource => {
58 | // Ensure the xrInputSource is relevant (only allow hands if hands are enabled)
59 | if(!this.data.enableHands && xrInputSource.profiles.includes(HANDS_PROFILE_ID)) {
60 | return;
61 | }
62 |
63 | const record: InputSourceRecord = { xrInputSource, componentState: {} };
64 | this.inputSources.push(record);
65 | // FIXME: Detect and report when there are multiple input sources with the same handedness
66 | if(xrInputSource.handedness === 'left') {
67 | this.left = record;
68 | } else if(xrInputSource.handedness === 'right') {
69 | this.right = record;
70 | }
71 | const { profile, assetPath } = await fetchProfile(xrInputSource, this.data.profilesUri);
72 | record.motionController = new MotionController(xrInputSource, profile, assetPath!);
73 | for(const componentKey in record.motionController!.components) {
74 | const component = record.motionController.components[componentKey];
75 | record.componentState[componentKey] = {...component.values};
76 | }
77 |
78 | // Special treatment for hand-tracking
79 | if(record.motionController!.id === HANDS_PROFILE_ID) {
80 | record.jointState = {
81 | poses: new Float32Array(16 * 25),
82 | radii: new Float32Array(25),
83 | };
84 | }
85 |
86 | // Notify anyone interested in this change
87 | this.sceneEl.emit('motion-controller-change' as keyof AFRAME.EntityEvents);
88 | });
89 |
90 | // Notify anyone interested in this change
91 | this.sceneEl.emit('motion-controller-change' as keyof AFRAME.EntityEvents);
92 | }
93 |
94 | this.el.sceneEl.addEventListener('enter-vr', _ => {
95 | this.xrSession = this.el.sceneEl.xrSession!;
96 | if(this.xrSession) {
97 | this.xrSession.addEventListener('inputsourceschange', onInputSourcesChange);
98 | }
99 | });
100 | this.el.sceneEl.addEventListener('exit-vr', _ => {
101 | if(this.xrSession) {
102 | this.xrSession.removeEventListener('inputsourceschange', onInputSourcesChange);
103 | this.xrSession = null;
104 | // Remove any input sources, as the session has ended
105 | this.inputSources.splice(0, this.inputSources.length);
106 | this.left = null;
107 | this.right = null;
108 | this.sceneEl.emit('motion-controller-change' as keyof AFRAME.EntityEvents);
109 | }
110 | });
111 | },
112 | tick: function() {
113 | // Update all motion controllers. This ensures that any code
114 | // polling the state gets up to date information, even when not visualized
115 | // FIXME: System tick happens after component ticks, meaning update is always 1 frame late :-/
116 | this.inputSources.forEach(inputSourceRecord => {
117 | if(!inputSourceRecord.motionController) {
118 | return;
119 | }
120 |
121 | // Let the motion controller library update the state
122 | inputSourceRecord.motionController.updateFromGamepad()
123 | if(inputSourceRecord.motionController.id === HANDS_PROFILE_ID) {
124 | this.updateHandJoints(inputSourceRecord);
125 | }
126 | const hand = this.left === inputSourceRecord ? 'left' : this.right === inputSourceRecord ? 'right' : undefined;
127 |
128 | // Compare the state with the last recorded state, and emit events for any changes
129 | for(const componentKey in inputSourceRecord.motionController.components) {
130 | const newState = inputSourceRecord.motionController?.components[componentKey]!;
131 | const oldState = inputSourceRecord.componentState[componentKey];
132 | const eventDetails: ButtonEventDetails = {
133 | inputSource: inputSourceRecord.xrInputSource,
134 | motionController: inputSourceRecord.motionController,
135 | hand,
136 | button: componentKey,
137 | buttonState: newState.values,
138 | };
139 | // Update state already so event handlers will see the new state when polling
140 | const oldButtonState = oldState.state;
141 | oldState.state = newState.values.state;
142 | const oldXAxis = oldState.xAxis;
143 | oldState.xAxis = newState.values.xAxis;
144 | const oldYAxis = oldState.yAxis;
145 | oldState.yAxis = newState.values.yAxis;
146 |
147 | if(newState.values.state !== oldButtonState) {
148 | if(oldButtonState === 'touched') {
149 | // No longer touched -> touchend
150 | this.el.emit('touchend', eventDetails);
151 | } else if(oldButtonState === 'pressed') {
152 | // No longer pressed -> buttonup
153 | this.el.emit('buttonup', eventDetails);
154 | }
155 |
156 | if(newState.values.state === 'touched') {
157 | // Now touched -> touchstart
158 | this.el.emit('touchstart', eventDetails);
159 | } else if(newState.values.state === 'pressed') {
160 | // Now pressed -> buttondown
161 | this.el.emit('buttondown', eventDetails);
162 | }
163 | }
164 |
165 | if(newState.type === 'thumbstick' || newState.type === 'touchpad') {
166 | if(oldXAxis !== newState.values.xAxis || oldYAxis !== newState.values.yAxis) {
167 | // Value along axis changed
168 | this.el.emit('axismove', eventDetails);
169 | }
170 | }
171 | }
172 | });
173 | },
174 | updateHandJoints: function(inputSourceRecord: InputSourceRecord) {
175 | const xrFrame = this.el.sceneEl.frame;
176 | if(!xrFrame) {
177 | return;
178 | }
179 |
180 | // Make sure the hand is present
181 | const hand = inputSourceRecord.xrInputSource.hand;
182 | if(!hand) {
183 | return;
184 | }
185 |
186 | // Note: @types/webxr misses quite a few of the hand tracking types
187 | (xrFrame as any).fillPoses(hand.values(), inputSourceRecord.xrInputSource.gripSpace, inputSourceRecord.jointState!.poses);
188 | // FIXME: Perhaps only fetch radii once or upon request(?)
189 | (xrFrame as any).fillJointRadii(hand.values(), inputSourceRecord.jointState!.radii);
190 | }
191 | });
192 |
193 | export interface ButtonEventDetails {
194 | inputSource: XRInputSource;
195 | motionController: MotionController
196 | hand?: 'left'|'right';
197 | button: string;
198 | buttonState: Component['values']
199 | }
200 |
201 | declare module "aframe" {
202 | export interface Systems {
203 | "motion-controller": InstanceType
204 | }
205 | export interface SceneEvents {
206 | "motion-controller-change": SceneEvent<{}>
207 | "touchstart": SceneEvent
208 | "touchend": SceneEvent
209 | "buttondown": SceneEvent
210 | "buttonup": SceneEvent
211 | "buttonchange": SceneEvent
212 | "axismove": SceneEvent
213 | }
214 | }
215 |
--------------------------------------------------------------------------------
/motion-controller/src/motion-controller-model.component.ts:
--------------------------------------------------------------------------------
1 | import * as AFRAME from 'aframe';
2 | import * as THREE from 'three';
3 | import { MotionController, VisualResponse } from '@webxr-input-profiles/motion-controllers';
4 | import { hologramMaterialFromStandardMaterial, occluderMaterialFromStandardMaterial, phongMaterialFromStandardMaterial } from './utils';
5 | import { HAND_JOINT_NAMES } from './hand-joint-names';
6 | import { InputSourceRecord } from './motion-controller.system';
7 |
8 | /* The below code is based on Three.js: https://github.com/mrdoob/three.js/blob/dev/examples/jsm/webxr/XRControllerModelFactory.js
9 | * MIT LICENSE
10 | * Copyright © 2010-2023 three.js authors
11 | */
12 |
13 | type EnhancedVisualResponse = VisualResponse & {
14 | valueNode?: THREE.Object3D,
15 | minNode?: THREE.Object3D,
16 | maxNode?: THREE.Object3D
17 | };
18 |
19 | const MotionControllerModelComponent = AFRAME.registerComponent('motion-controller-model', {
20 | schema: {
21 | hand: { type: 'string', oneOf: ['left', 'right'], default: 'left' },
22 | overrideMaterial: { type: 'string', oneOf: ['none', 'phong', 'occluder'], default: 'phong'},
23 | overrideHandMaterial: { type: 'string', oneOf: ['none', 'phong', 'occluder', 'hologram'], default: 'hologram'},
24 | buttonTouchColor: { type: 'color', default: '#8AB' },
25 | buttonPressColor: { type: 'color', default: '#2DF' }
26 | },
27 | __fields: {} as {
28 | motionControllerSystem: AFRAME.Systems['motion-controller'],
29 | inputSourceRecord: InputSourceRecord|null,
30 | motionController: MotionController|null,
31 | componentMeshes: Map>,
32 | // Only relevant for hand tracking models
33 | handJoints: Array
34 | },
35 | init: function() {
36 | this.motionControllerSystem = this.el.sceneEl.systems['motion-controller'];
37 | this.componentMeshes = new Map();
38 | this.handJoints = new Array(25);
39 | const gltfLoader = new AFRAME.THREE.GLTFLoader();
40 | this.el.sceneEl.addEventListener('motion-controller-change', _event => {
41 | const inputSourceRecord = this.motionControllerSystem[this.data.hand];
42 | this.inputSourceRecord = inputSourceRecord;
43 | if(inputSourceRecord && inputSourceRecord.motionController) {
44 | gltfLoader.load(inputSourceRecord.motionController.assetUrl, (gltf) => {
45 | // Make sure the motionController is still the one the model was loaded for
46 | if(this.motionController !== inputSourceRecord.motionController) {
47 | return;
48 | }
49 | this.el.setObject3D('mesh', gltf.scene);
50 | const isHandModel = this.motionController?.id === 'generic-hand';
51 |
52 | // Traverse the mesh to change materials and extract references to hand joints
53 | gltf.scene.traverse(child => {
54 | if(!(child as any).isMesh) {
55 | return;
56 | }
57 |
58 | // Extract bones from skinned mesh (as these are likely hand joints)
59 | if(isHandModel && child.type === 'SkinnedMesh') {
60 | const skinnedMesh = child as THREE.SkinnedMesh;
61 | const bones = skinnedMesh.skeleton.bones;
62 | for(const bone of bones) {
63 | const index = HAND_JOINT_NAMES.indexOf(bone.name);
64 | if(index !== -1) {
65 | this.handJoints[index] = bone;
66 | }
67 | }
68 |
69 | // Exclude them from frustum culling
70 | skinnedMesh.frustumCulled = false;
71 | }
72 |
73 | // The default materials might be physical based ones requiring an environment map
74 | // for proper rendering. Since this isn't always desirable, convert to phong material instead.
75 | const mesh = child as THREE.Mesh;
76 | switch(isHandModel ? this.data.overrideHandMaterial : this.data.overrideMaterial) {
77 | case 'phong':
78 | mesh.material = phongMaterialFromStandardMaterial(mesh.material as THREE.MeshStandardMaterial);
79 | break;
80 | case 'occluder':
81 | mesh.material = occluderMaterialFromStandardMaterial(mesh.material as THREE.MeshStandardMaterial);
82 | break;
83 | case 'hologram':
84 | mesh.material = hologramMaterialFromStandardMaterial(mesh.material as THREE.MeshStandardMaterial);
85 | break;
86 | }
87 | });
88 |
89 | this.componentMeshes.clear();
90 | Object.values(this.motionController!.components).forEach((component) => {
91 | // Can't traverse the rootNodes of the components, as these are hardly ever correct.
92 | // See: https://github.com/immersive-web/webxr-input-profiles/issues/249
93 | const componentMeshes: Array<{mesh: THREE.Mesh, originalColor: THREE.Color}> = [];
94 | this.componentMeshes.set(component.id, componentMeshes);
95 |
96 | // Enhance the visual responses with references to the actual Three.js objects from the loaded glTF
97 | Object.values(component.visualResponses).forEach((visualResponse) => {
98 | const valueNode = gltf.scene.getObjectByName(visualResponse.valueNodeName);
99 | const minNode = visualResponse.minNodeName ? gltf.scene.getObjectByName(visualResponse.minNodeName) : undefined;
100 | const maxNode = visualResponse.maxNodeName ? gltf.scene.getObjectByName(visualResponse.maxNodeName) : undefined;
101 |
102 | if(!valueNode) {
103 | console.error('Missing value node in model for visualResponse: ', visualResponse.componentProperty);
104 | return;
105 | }
106 |
107 | // Extract meshes from valueNodes
108 | valueNode.traverse(c => {
109 | if(c.type === 'Mesh') {
110 | const mesh = c as THREE.Mesh;
111 | const originalColor = (mesh.material as THREE.MeshPhongMaterial).color.clone();
112 | componentMeshes.push({mesh, originalColor});
113 | }
114 | });
115 |
116 | // Enhance VisualResponse with references to the actual nodes
117 | (visualResponse as EnhancedVisualResponse).valueNode = valueNode;
118 | if(visualResponse.valueNodeProperty === 'transform') {
119 | if(!minNode || !maxNode) {
120 | console.error('Missing value node in model for visualResponse: ', visualResponse.componentProperty);
121 | (visualResponse as EnhancedVisualResponse).valueNode = undefined;
122 | return;
123 | }
124 | (visualResponse as EnhancedVisualResponse).minNode = minNode;
125 | (visualResponse as EnhancedVisualResponse).maxNode = maxNode;
126 | }
127 | });
128 | });
129 | });
130 | this.motionController = inputSourceRecord.motionController;
131 | } else {
132 | // TODO: Remove mesh
133 | if(this.el.getObject3D('mesh')) {
134 | this.el.removeObject3D('mesh');
135 | }
136 | for(let i = 0; i < 25; i++) {
137 | this.handJoints[i] = undefined;
138 | }
139 | this.motionController = null;
140 | }
141 | });
142 | },
143 | remove: function() {
144 | // TODO: Clean-up any event handlers
145 | // TODO: Remove controller mesh from scene
146 | // TODO: Remove enhanced properties from motion controller instances(?)
147 | },
148 | tick: function() {
149 | if(!this.motionController || !this.el.getObject3D('mesh')) { // FIXME: Improve check for mesh
150 | return;
151 | }
152 |
153 | // Hand joints
154 | if(this.inputSourceRecord?.jointState) {
155 | for(let i = 0; i < 25; i++) {
156 | const joint = this.handJoints[i]!;
157 | joint.matrix.fromArray(this.inputSourceRecord!.jointState!.poses, i * 16);
158 | joint.matrix.decompose(joint.position, joint.quaternion, joint.scale);
159 | }
160 | }
161 |
162 | // Components and visual responses
163 | for(const componentId in this.motionController.components) {
164 | const component = this.motionController.components[componentId];
165 |
166 | // Update node data based on the visual responses' current states
167 | Object.values(component.visualResponses).forEach((visualResponse) => {
168 | const { valueNode, minNode, maxNode, value, valueNodeProperty } = visualResponse as EnhancedVisualResponse;
169 |
170 | // Skip if the visual response node is not found. No error is needed,
171 | // because it will have been reported at load time.
172 | if(!valueNode) return;
173 |
174 | // Calculate the new properties based on the weight supplied
175 | if(valueNodeProperty === 'visibility') {
176 | valueNode.visible = value as boolean;
177 | } else if(valueNodeProperty === 'transform') {
178 | valueNode.quaternion.slerpQuaternions(
179 | minNode!.quaternion,
180 | maxNode!.quaternion,
181 | value as number
182 | );
183 | valueNode.position.lerpVectors(
184 | minNode!.position,
185 | maxNode!.position,
186 | value as number
187 | );
188 | }
189 | });
190 |
191 | // Update color based on state
192 | // FIXME: Parse colors once instead of using the string representations
193 | let color: string|null = null;
194 | if(component.values.state === 'touched') {
195 | color = this.data.buttonTouchColor;
196 | } else if(component.values.state === 'pressed') {
197 | color = this.data.buttonPressColor;
198 | }
199 | this.componentMeshes.get(componentId)?.forEach(mesh => {
200 | // FIXME: This depends on the color of the controller model whether this is visible or not
201 | // Find a better way to colorize it, while maintaining texture
202 | (mesh.mesh.material as THREE.MeshPhongMaterial).color.set(color || mesh.originalColor);
203 | });
204 | }
205 | }
206 | });
207 |
208 | declare module "aframe" {
209 | export interface Components {
210 | "motion-controller-model": InstanceType
211 | }
212 | }
--------------------------------------------------------------------------------
/mirror/src/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * System for keeping track of any portal (incl. mirrors).
3 | * Keeps track of the (active) portals and render them at the
4 | * end of normal rendering.
5 | */
6 | AFRAME.registerSystem('portal', {
7 | schema: {},
8 | portals: [],
9 | init: function() {
10 | // Prevent auto clearing for each render
11 | const renderer = this.sceneEl.renderer;
12 | renderer.autoClear = false;
13 | renderer.info.autoReset = false;
14 |
15 | // No-op onAfterRender
16 | const nopAfterRender = function() {};
17 |
18 | // Create a sentinel
19 | const sentinel = new THREE.Mesh();
20 | sentinel.frustumCulled = false;
21 | sentinel.material.transparent = true;
22 | sentinel.renderOrder = Number.MAX_VALUE;
23 | this.sentinel = sentinel;
24 | this.sceneEl.object3D.add(this.sentinel);
25 |
26 | sentinel.onAfterRender = (renderer, scene, camera) => {
27 | // In case of XR, only call the render hooks for the last camera (e.g. right eye)
28 | if(renderer.xr.isPresenting) {
29 | const cameras = renderer.xr.getCamera().cameras;
30 | if(camera != cameras[cameras.length - 1]) {
31 | return;
32 | }
33 | }
34 | sentinel.visible = false;
35 | this.portals.forEach(portal => portal.setInactive());
36 |
37 | // Supress A-Frame's scene.onAfterRender callback during portal/mirror rendering
38 | const oldOnAfterRender = scene.onAfterRender;
39 | scene.onAfterRender = nopAfterRender;
40 |
41 | // Let portals and mirrors render themselves
42 | this.portals.forEach(portal => portal.render(renderer, scene, camera));
43 |
44 | this.portals.forEach(portal => portal.setActive());
45 | sentinel.visible = true;
46 | scene.onAfterRender = oldOnAfterRender;
47 | }
48 | },
49 | tick: function() {
50 | // Note: by default A-frame doesn't sort objects for rendering
51 | // so manually ensure the sentinel is at the tail end
52 | const sceneObject = this.sceneEl.object3D;
53 | if(sceneObject.children[sceneObject.children.length - 1] !== this.sentinel) {
54 | sceneObject.add(this.sentinel);
55 | }
56 |
57 | this.sceneEl.renderer.info.reset();
58 | },
59 | registerPortal: function(portal) {
60 | this.portals.push(portal);
61 | portal.setPortalId(this.portals.length);
62 | },
63 | unregisterPortal: function(portal) {
64 | const index = this.portals.indexOf(portal);
65 | if(index !== -1) {
66 | this.portals.splice(index, 1);
67 | this.portals.forEach((portal, index) => portal.setPortalId(index + 1));
68 | }
69 | }
70 | });
71 |
72 | /**
73 | * Base logic for portals
74 | */
75 | const baseComponent = {
76 | schema: {
77 | layers: { type: 'array', default: [0] }
78 | },
79 | init: function() {
80 | const mesh = this.el.getObject3D('mesh');
81 |
82 | // Setup the material of the portal (write to stencil, adhere to depth)
83 | this.surfaceMaterial = mesh.material;
84 | const material = this.surfaceMaterial;
85 | material.transparent = true;
86 | material.colorWrite = false;
87 | material.depthWrite = true;
88 | material.stencilWrite = true;
89 | material.depthFunc = THREE.LessEqualDepth;
90 | material.stencilFunc = THREE.AlwaysStencilFunc;
91 | material.stencilZPass = THREE.ReplaceStencilOp;
92 | material.stencilZFail = THREE.KeepStencilOp;
93 |
94 | // Register mirror (which gives it its id)
95 | this.el.sceneEl.systems['portal'].registerPortal(this);
96 | material.stencilRef = this.portalId;
97 |
98 | // Use onBeforeRender to determine if the mirror is inside the frustum
99 | this.insideFrustum = false;
100 | mesh.onBeforeRender = () => {
101 | this.insideFrustum = true;
102 | };
103 |
104 | // Layers for visibility
105 | this.layers = new THREE.Layers();
106 | this.layers.disableAll();
107 |
108 | // Temporary camera objects to hold the state before reflecting
109 | this.tempCamera = new THREE.PerspectiveCamera();
110 | this.tempCameras = [new THREE.PerspectiveCamera(), new THREE.PerspectiveCamera()];
111 |
112 | // Setup clipping plane
113 | this.clippingPlane = new THREE.Plane();
114 |
115 | // Utility for copying camera properties
116 | this.copyCamera = function(source, target) {
117 | target.matrixWorld.copy(source.matrixWorld);
118 | target.matrixWorldInverse.copy(source.matrixWorldInverse);
119 | target.projectionMatrix.copy(source.projectionMatrix);
120 | target.layers.mask = source.layers.mask;
121 | }
122 |
123 | // Monkey patch setMaterial on WebGLState
124 | const oldWebGLStateSetMaterialFn = this.el.sceneEl.renderer.state.setMaterial;
125 | const webGLStateSetMaterialFn = function(material, frontFaceCW) {
126 | oldWebGLStateSetMaterialFn(material, !frontFaceCW);
127 | };
128 | this.unpatchWebGLState = function(state) {
129 | state.setMaterial = oldWebGLStateSetMaterialFn;
130 | }
131 | this.patchWebGLState = function(state) {
132 | state.setMaterial = webGLStateSetMaterialFn;
133 | }
134 |
135 | // Temp variables
136 | this._mirrorPos = new THREE.Vector3();
137 | this._mirrorQuat = new THREE.Quaternion();
138 | this._cameraPos = new THREE.Vector3();
139 | this._cameraLPos = new THREE.Vector3();
140 | this._cameraRPos = new THREE.Vector3();
141 | this._normal = new THREE.Vector3();
142 | this._adjustMatrix = new THREE.Matrix4();
143 | },
144 | setPortalId: function(id) {
145 | this.portalId = id;
146 | this.surfaceMaterial.stencilRef = id;
147 | },
148 | setInactive: function() {
149 | this.surfaceMaterial.stencilWrite = false;
150 | },
151 | setActive: function() {
152 | this.surfaceMaterial.stencilWrite = true;
153 | },
154 | update: function() {
155 | this.layers.disableAll();
156 | this.data.layers.map(x => this.layers.enable(+x));
157 | },
158 | preRender: function() {},
159 | postRender: function() {},
160 | render: function(renderer, scene, camera) {
161 | // Only render if the portal surface is inside the frustum
162 | if(!this.insideFrustum) {
163 | return;
164 | }
165 | this.insideFrustum = false;
166 |
167 | // Temporarily move the camera
168 | const sceneCamera = renderer.xr.isPresenting ? renderer.xr.getCamera() : this.tempCamera;
169 |
170 | // Make sure the portal surface can be seen
171 | let visible;
172 | const mirrorPos = this.el.object3D.getWorldPosition(this._mirrorPos);
173 | const n = this._normal.set(0, 0, 1);
174 | n.applyQuaternion(this.el.object3D.getWorldQuaternion(this._mirrorQuat));
175 | if(renderer.xr.isPresenting) {
176 | const cameras = sceneCamera.cameras;
177 | this._cameraLPos.setFromMatrixPosition(cameras[0].matrixWorld);
178 | this._cameraRPos.setFromMatrixPosition(cameras[1].matrixWorld);
179 | visible =
180 | this._cameraLPos.subVectors(mirrorPos, this._cameraLPos).dot(n) <= 0.0 ||
181 | this._cameraRPos.subVectors(mirrorPos, this._cameraRPos).dot(n) <= 0.0;
182 |
183 | } else {
184 | const view = camera.getWorldPosition(this._cameraPos).subVectors(mirrorPos, this._cameraPos);
185 | visible = view.dot(n) <= 0.0;
186 | }
187 | if(!visible) {
188 | return;
189 | }
190 |
191 | // The portal surface is visible, so compute the clipping plane
192 | this.createClippingPlane(this.clippingPlane);
193 |
194 | // Callback to allow adjustments before rendering the portal contents
195 | if(this.onBeforeRender) {
196 | this.onBeforeRender(renderer, scene, camera, this);
197 | }
198 |
199 | // Construct a matrix for rendering the other side of the portal
200 | const adjustMatrix = this.createAdjustMatrix(this._adjustMatrix);
201 |
202 | // Update camera(s) for rendering the portal contents
203 | if(renderer.xr.isPresenting) {
204 | // Use temp-cameras to store camera matrices
205 | const cameras = sceneCamera.cameras;
206 | this.copyCamera(sceneCamera, this.tempCamera);
207 | for(let i = 0; i < cameras.length; i++) {
208 | this.copyCamera(cameras[i], this.tempCameras[i]);
209 |
210 | cameras[i].matrixWorld.premultiply(adjustMatrix);
211 | cameras[i].matrixWorldInverse.copy(cameras[i].matrixWorld).invert();
212 | cameras[i].layers.mask = this.layers.mask;
213 | }
214 |
215 | // Set projection matrix for frustum culling
216 | setProjectionFromUnion(sceneCamera, cameras[0], cameras[1]);
217 |
218 | // Apply clipping plane in projection matrix
219 | adjustProjectionMatrix(cameras[0], this.clippingPlane);
220 | adjustProjectionMatrix(cameras[1], this.clippingPlane);
221 | } else {
222 | sceneCamera.near = camera.near;
223 | sceneCamera.far = camera.far;
224 | sceneCamera.projectionMatrix.copy(camera.projectionMatrix);
225 |
226 | sceneCamera.matrix.copy(camera.matrixWorld).premultiply(adjustMatrix);
227 | sceneCamera.matrix.decompose(
228 | sceneCamera.position,
229 | sceneCamera.quaternion,
230 | sceneCamera.scale);
231 | sceneCamera.matrixWorld.copy(sceneCamera.matrix);
232 | sceneCamera.matrixWorldInverse.copy(sceneCamera.matrix).invert();
233 | adjustProjectionMatrix(sceneCamera, this.clippingPlane);
234 | }
235 |
236 | // Hide portal surface
237 | const mesh = this.el.getObject3D('mesh');
238 | mesh.visible = false;
239 |
240 | // Render portal contents
241 | renderer.xr.cameraAutoUpdate = false;
242 | this.preRender(renderer);
243 | renderer.state.buffers.stencil.setTest(true);
244 | renderer.state.buffers.stencil.setFunc(THREE.EqualStencilFunc, this.portalId, 0xFF);
245 | renderer.state.buffers.stencil.setOp(THREE.KeepStencilOp, THREE.KeepStencilOp, THREE.KeepStencilOp);
246 | renderer.state.buffers.stencil.setLocked(true);
247 |
248 | renderer.clearDepth();
249 | const oldLayersMask = sceneCamera.layers.mask;
250 | sceneCamera.layers.mask = this.layers.mask;
251 | const oldMatrixWorldAutoUpdate = scene.matrixWorldAutoUpdate;
252 | scene.matrixWorldAutoUpdate = false;
253 | renderer.render(scene, this.tempCamera);
254 | scene.matrixWorldAutoUpdate = oldMatrixWorldAutoUpdate;
255 | sceneCamera.layers.mask = oldLayersMask;
256 |
257 | renderer.state.buffers.stencil.setLocked(false);
258 | this.postRender(renderer);
259 | renderer.xr.cameraAutoUpdate = true;
260 |
261 | // Restore portal surface
262 | mesh.visible = true;
263 |
264 | // Restore cameras (in case of XR)
265 | if(renderer.xr.isPresenting) {
266 | const cameras = sceneCamera.cameras;
267 | this.copyCamera(this.tempCamera, sceneCamera);
268 | for(let i = 0; i < cameras.length; i++) {
269 | this.copyCamera(this.tempCameras[i], cameras[i]);
270 | }
271 | }
272 |
273 | // Callback to allow adjustments after rendering the portal contents
274 | if(this.onAfterRender) {
275 | this.onAfterRender(renderer, scene, camera, this);
276 | }
277 | },
278 | remove: function() {
279 | this.el.sceneEl.systems['portal'].unregisterPortal(this);
280 | }
281 | };
282 |
283 | AFRAME.registerComponent('mirror', {
284 | ...baseComponent,
285 | createClippingPlane: function(plane) {
286 | const mirrorPos = this.el.object3D.getWorldPosition(this._mirrorPos);
287 | const n = this._normal.set(0, 0, 1);
288 | n.applyQuaternion(this.el.object3D.getWorldQuaternion(this._mirrorQuat));
289 | const d = -mirrorPos.dot(n);
290 | return plane.set(n, d);
291 | },
292 | createAdjustMatrix: function(matrix) {
293 | const n = this.clippingPlane.normal;
294 | const d = this.clippingPlane.constant;
295 | return matrix.set(
296 | 1 -2*n.x*n.x, -2*n.x*n.y, -2*n.x*n.z, -2*n.x*d,
297 | -2*n.x*n.y, 1-2*n.y*n.y, -2*n.y*n.z, -2*n.y*d,
298 | -2*n.x*n.z, -2*n.y*n.z, 1-2*n.z*n.z, -2*n.z*d,
299 | 0, 0, 0, 1
300 | );
301 | },
302 | preRender: function(renderer) {
303 | this.patchWebGLState(renderer.state);
304 | },
305 | postRender: function(renderer) {
306 | this.unpatchWebGLState(renderer.state);
307 | }
308 | });
309 |
310 | AFRAME.registerComponent('portal', {
311 | ...baseComponent,
312 | schema: {
313 | ...baseComponent.schema,
314 | destination: { type: 'selector' }
315 | },
316 | createClippingPlane: function(plane) {
317 | // Clipping plane depends on the destination
318 | const destinationPos = this.data.destination.object3D.getWorldPosition(this._mirrorPos);
319 | const n = this._normal.set(0, 0, 1);
320 | n.applyQuaternion(this.data.destination.object3D.getWorldQuaternion(this._mirrorQuat));
321 | const d = -destinationPos.dot(n);
322 | return plane.set(n, d);
323 | },
324 | rotate180Matrix: new THREE.Matrix4().makeRotationY(Math.PI),
325 | createAdjustMatrix: function(matrix) {
326 | matrix.copy(this.el.object3D.matrixWorld);
327 | return matrix.invert()
328 | .premultiply(this.rotate180Matrix)
329 | .premultiply(this.data.destination.object3D.matrixWorld);
330 | }
331 | });
332 |
333 | /* Primitives */
334 | AFRAME.registerPrimitive('a-mirror', {
335 | defaultComponents: {
336 | geometry: { primitive: 'plane' },
337 | mirror: {}
338 | },
339 | mappings: {
340 | layers: 'mirror.layers',
341 | }
342 | });
343 |
344 | AFRAME.registerPrimitive('a-portal', {
345 | defaultComponents: {
346 | geometry: { primitive: 'plane' },
347 | portal: {}
348 | },
349 | mappings: {
350 | layers: 'portal.layers',
351 | destination: 'portal.destination',
352 | }
353 | });
354 |
355 | /* Utils */
356 | const adjustProjectionMatrix = (function() {
357 | const _tempV4 = new THREE.Vector4();
358 | const _tempPlane = new THREE.Plane();
359 | const _q = new THREE.Vector4();
360 | return function(sceneCamera, clippingPlane) {
361 | _tempPlane.copy(clippingPlane).applyMatrix4(sceneCamera.matrixWorldInverse);
362 | const clipPlane = _tempV4.set(_tempPlane.normal.x, _tempPlane.normal.y, _tempPlane.normal.z, _tempPlane.constant);
363 | const projectionMatrix = sceneCamera.projectionMatrix;
364 |
365 | _q.x = (Math.sign(clipPlane.x) + projectionMatrix.elements[8]) / projectionMatrix.elements[0];
366 | _q.y = (Math.sign(clipPlane.y) + projectionMatrix.elements[9]) / projectionMatrix.elements[5];
367 | _q.z = -1.0;
368 | _q.w = (1.0 + projectionMatrix.elements[10]) / projectionMatrix.elements[14];
369 |
370 | // Calculate the scaled plane vector
371 | clipPlane.multiplyScalar(2.0 / clipPlane.dot(_q));
372 |
373 | projectionMatrix.elements[2] = clipPlane.x;
374 | projectionMatrix.elements[6] = clipPlane.y;
375 | projectionMatrix.elements[10] = clipPlane.z + 1.0 + 0.0;
376 | projectionMatrix.elements[14] = clipPlane.w;
377 | };
378 | })();
379 |
380 | const setProjectionFromUnion = (function() {
381 | const _cameraLPos = new THREE.Vector3();
382 | const _cameraRPos = new THREE.Vector3();
383 |
384 | // Note: this method is straight from THREE.js WebXRManager.js
385 | // See: https://github.com/mrdoob/three.js/blob/8fd3b2acbd08952deee1e40c18b00907c5cd4c4d/src/renderers/webxr/WebXRManager.js#L429
386 | // Its replicated here since we do need its behaviour, but can't use the rest
387 | // of the XR camera auto updating logic.
388 | // Falls under The MIT License:
389 | // Copyright © 2010-2023 three.js authors
390 | return function(camera, cameraL, cameraR) {
391 | _cameraLPos.setFromMatrixPosition(cameraL.matrixWorld);
392 | _cameraRPos.setFromMatrixPosition(cameraR.matrixWorld);
393 |
394 | const ipd = _cameraLPos.distanceTo(_cameraRPos);
395 |
396 | const projL = cameraL.projectionMatrix.elements;
397 | const projR = cameraR.projectionMatrix.elements;
398 |
399 | // VR systems will have identical far and near planes, and
400 | // most likely identical top and bottom frustum extents.
401 | // Use the left camera for these values.
402 | const near = projL[ 14 ] / ( projL[ 10 ] - 1 );
403 | const far = projL[ 14 ] / ( projL[ 10 ] + 1 );
404 | const topFov = ( projL[ 9 ] + 1 ) / projL[ 5 ];
405 | const bottomFov = ( projL[ 9 ] - 1 ) / projL[ 5 ];
406 |
407 | const leftFov = ( projL[ 8 ] - 1 ) / projL[ 0 ];
408 | const rightFov = ( projR[ 8 ] + 1 ) / projR[ 0 ];
409 | const left = near * leftFov;
410 | const right = near * rightFov;
411 |
412 | // Calculate the new camera's position offset from the
413 | // left camera. xOffset should be roughly half `ipd`.
414 | const zOffset = ipd / ( - leftFov + rightFov );
415 | const xOffset = zOffset * - leftFov;
416 |
417 | // TODO: Better way to apply this offset?
418 | cameraL.matrixWorld.decompose( camera.position, camera.quaternion, camera.scale );
419 | camera.translateX( xOffset );
420 | camera.translateZ( zOffset );
421 | camera.matrixWorld.compose( camera.position, camera.quaternion, camera.scale );
422 | camera.matrixWorldInverse.copy( camera.matrixWorld ).invert();
423 |
424 | // Find the union of the frustum values of the cameras and scale
425 | // the values so that the near plane's position does not change in world space,
426 | // although must now be relative to the new union camera.
427 | const near2 = near + zOffset;
428 | const far2 = far + zOffset;
429 | const left2 = left - xOffset;
430 | const right2 = right + ( ipd - xOffset );
431 | const top2 = topFov * far / far2 * near2;
432 | const bottom2 = bottomFov * far / far2 * near2;
433 |
434 | camera.projectionMatrix.makePerspective( left2, right2, top2, bottom2, near2, far2 );
435 | }
436 | })();
437 |
438 | // Stencil buffer isn't enabled by default since Three.js r163
439 | if(parseInt(AFRAME.THREE.REVISION) >= 163) {
440 | document.addEventListener('render-target-loaded', e => {
441 | let rendererAttrString = e.target.getAttribute('renderer') ?? '';
442 | if(!/stencil\s*:\s*true/g.test(rendererAttrString)) {
443 | console.warn('[aframe-mirror] Mirror component requires a stencil buffer, enabling it. Add `renderer="stencil: true"` to your to get rid of this warning.')
444 | rendererAttrString += ';stencil:true';
445 | }
446 | e.target.setAttribute('renderer', rendererAttrString);
447 | })
448 | }
--------------------------------------------------------------------------------
/effekseer/vendor/effekseer.d.ts:
--------------------------------------------------------------------------------
1 |
2 | declare namespace effekseer {
3 |
4 | /**
5 | * Initialize Effekseer.js.
6 | * This function must be called at first if use WebAssembly
7 | * @param {string} path A file of webassembply
8 | * @param {function=} onload A function that is called at loading complete
9 | * @param {function=} onerror A function that is called at loading error.
10 | */
11 | export function initRuntime(path: string, onload: () => void, onerror: () => void): void;
12 |
13 | /**
14 | * Create a context to render in multiple scenes
15 | * @returns {EffekseerContext} context
16 | */
17 | export function createContext(): EffekseerContext;
18 |
19 | /**
20 | * Release specified context. After that, don't touch a context
21 | * @param {EffekseerContext} context context
22 | */
23 | export function releaseContext(context: EffekseerContext): void;
24 |
25 | /**
26 | * Set the flag whether Effekseer show logs
27 | * @param {boolean} flag
28 | */
29 | export function setSetLogEnabled(flag: boolean): void;
30 |
31 | /**
32 | * Set the string of cross origin for images
33 | * @param {string} crossOrigin
34 | */
35 | export function setImageCrossOrigin(crossOrigin: string): void;
36 |
37 | /**
38 | * Initialize graphics system.
39 | * @param {WebGLRenderingContext} webglContext WebGL Context
40 | * @param {object} settings Some settings with Effekseer initialization
41 | */
42 | export function init(webglContext: WebGLRenderingContext, settings?: object): void;
43 |
44 | /**
45 | * Advance frames.
46 | * @param {number=} deltaFrames number of advance frames
47 | */
48 | export function update(deltaFrames?: number): void;
49 |
50 | /**
51 | * Main rendering.
52 | */
53 | export function draw(): void;
54 |
55 | /**
56 | * Set camera projection from matrix.
57 | * @param matrixArray An array that is requred 16 elements
58 | */
59 | export function setProjectionMatrix(matrixArray: Float32Array): void;
60 |
61 | /**
62 | * Set camera projection from perspective parameters.
63 | * @param {number} fov Field of view in degree
64 | * @param {number} aspect Aspect ratio
65 | * @param {number} near Distance of near plane
66 | * @param {number} aspect Distance of far plane
67 | */
68 | export function setProjectionPerspective(fov: number, aspect: number, near: number, far: number): void;
69 |
70 | /**
71 | * Set camera projection from orthographic parameters.
72 | * @param {number} width Width coordinate of the view plane
73 | * @param {number} height Height coordinate of the view plane
74 | * @param {number} near Distance of near plane
75 | * @param {number} aspect Distance of far plane
76 | */
77 | export function setProjectionOrthographic(width: number, height: number, near: number, far: number): void;
78 |
79 | /**
80 | * Set camera view from matrix.
81 | * @param matrixArray An array that is requred 16 elements
82 | */
83 | export function setCameraMatrix(matrixArray: Float32Array): void;
84 |
85 | /**
86 | * Set camera view from lookat parameters.
87 | * @param {number} positionX X value of camera position
88 | * @param {number} positionY Y value of camera position
89 | * @param {number} positionZ Z value of camera position
90 | * @param {number} targetX X value of target position
91 | * @param {number} targetY Y value of target position
92 | * @param {number} targetZ Z value of target position
93 | * @param {number} upvecX X value of upper vector
94 | * @param {number} upvecY Y value of upper vector
95 | * @param {number} upvecZ Z value of upper vector
96 | */
97 | export function setCameraLookAt(
98 | positionX: number,
99 | positionY: number,
100 | positionZ: number,
101 | targetX: number,
102 | targetY: number,
103 | targetZ: number,
104 | upvecX: number,
105 | upvecY: number,
106 | upvecZ: number
107 | ): void;
108 |
109 | /**
110 | * Set camera view from lookat vector parameters.
111 | * @param {object} position camera position
112 | * @param {object} target target position
113 | * @param {object=} upvec upper vector
114 | */
115 | export function setCameraLookAtFromVector(position: object, target: object, upvec?: object): void;
116 |
117 | /**
118 | * Load the effect data file (and resources).
119 | * @param {string|ArrayBuffer} data A URL/ArrayBuffer of effect file (*.efk)
120 | * @param {number} scale A magnification rate for the effect. The effect is loaded magnificating with this specified number.
121 | * @param {function=} onload A function that is called at loading complete
122 | * @param {function=} onerror A function that is called at loading error. First argument is a message. Second argument is an url.
123 | * @returns {EffekseerEffect} The effect data
124 | */
125 | export function loadEffect(data: string|ArrayBuffer, scale?: number, onload?: () => void, onerror?: (reason: string, path: string) => void): EffekseerEffect;
126 | /**
127 | * Load the effect package file (resources included in the package).
128 | * @param {string|ArrayBuffer} data A URL/ArrayBuffer of effect package file (*.efkpkg)
129 | * @param {Object} Unzip An Unzip object.
130 | * @param {number} scale A magnification rate for the effect. The effect is loaded magnificating with this specified number.
131 | * @param {function=} onload A function that is called at loading complete
132 | * @param {function=} onerror A function that is called at loading error. First argument is a message. Second argument is an url.
133 | * @returns {EffekseerEffect} The effect data
134 | */
135 | export function loadEffectPackage(data: string|ArrayBuffer, Unzip: Object, scale?: number, onload?: () => void, onerror?: (reason: string, path: string) => void): EffekseerEffect;
136 | /**
137 | * Release the specified effect. Don't touch the instance of effect after released.
138 | * @param {EffekseerEffect} effect The loaded effect
139 | */
140 | export function releaseEffect(effect: EffekseerEffect): void;
141 |
142 | /**
143 | * Play the specified effect.
144 | * @param {EffekseerEffect} effect The loaded effect
145 | * @param {number} x X value of location that is emited
146 | * @param {number} y Y value of location that is emited
147 | * @param {number} z Z value of location that is emited
148 | * @returns {EffekseerHandle} The effect handle
149 | */
150 | export function play(effect: EffekseerEffect, x: number, y: number, z: number): EffekseerHandle;
151 |
152 | /**
153 | * Stop the all effects.
154 | */
155 | export function stopAll(): void;
156 |
157 | /**
158 | * Set the resource loader function.
159 | * @param {function} loader
160 | */
161 | export function setResourceLoader(loader: (path: string, onload?: () => void, onerror?: (reason: string, path: string) => void) => void): void;
162 |
163 | /**
164 | * Get whether VAO is supported
165 | */
166 | export function isVertexArrayObjectSupported(): boolean;
167 |
168 | export class EffekseerContext {
169 | /**
170 | * Initialize graphics system.
171 | * @param {WebGLRenderingContext} webglContext WebGL Context
172 | * @param {object} settings Some settings with Effekseer initialization
173 | */
174 | init(webglContext: WebGLRenderingContext, settings?: object): void;
175 |
176 | /**
177 | * Advance frames.
178 | * @param {number=} deltaFrames number of advance frames
179 | */
180 | update(deltaFrames?: number): void;
181 |
182 | /**
183 | * Main rendering.
184 | */
185 | draw(): void;
186 |
187 | /**
188 | * Set camera projection from matrix.
189 | * @param matrixArray An array that is requred 16 elements
190 | */
191 | setProjectionMatrix(matrixArray: Float32Array): void;
192 |
193 | /**
194 | * Set camera projection from perspective parameters.
195 | * @param {number} fov Field of view in degree
196 | * @param {number} aspect Aspect ratio
197 | * @param {number} near Distance of near plane
198 | * @param {number} aspect Distance of far plane
199 | */
200 | setProjectionPerspective(fov: number, aspect: number, near: number, far: number): void;
201 |
202 | /**
203 | * Set camera projection from orthographic parameters.
204 | * @param {number} width Width coordinate of the view plane
205 | * @param {number} height Height coordinate of the view plane
206 | * @param {number} near Distance of near plane
207 | * @param {number} aspect Distance of far plane
208 | */
209 | setProjectionOrthographic(width: number, height: number, near: number, far: number): void;
210 |
211 | /**
212 | * Set camera view from matrix.
213 | * @param matrixArray An array that is requred 16 elements
214 | */
215 | setCameraMatrix(matrixArray: Float32Array): void;
216 |
217 | /**
218 | * Set camera view from lookat parameters.
219 | * @param {number} positionX X value of camera position
220 | * @param {number} positionY Y value of camera position
221 | * @param {number} positionZ Z value of camera position
222 | * @param {number} targetX X value of target position
223 | * @param {number} targetY Y value of target position
224 | * @param {number} targetZ Z value of target position
225 | * @param {number} upvecX X value of upper vector
226 | * @param {number} upvecY Y value of upper vector
227 | * @param {number} upvecZ Z value of upper vector
228 | */
229 | setCameraLookAt(
230 | positionX: number,
231 | positionY: number,
232 | positionZ: number,
233 | targetX: number,
234 | targetY: number,
235 | targetZ: number,
236 | upvecX: number,
237 | upvecY: number,
238 | upvecZ: number
239 | ): void;
240 |
241 | /**
242 | * Set camera view from lookat vector parameters.
243 | * @param {object} position camera position
244 | * @param {object} target target position
245 | * @param {object=} upvec upper vector
246 | */
247 | setCameraLookAtFromVector(position: object, target: object, upvec?: object): void;
248 |
249 | /**
250 | * Load the effect data file (and resources).
251 | * @param {string|ArrayBuffer} data A URL/ArrayBuffer of effect file (*.efk)
252 | * @param {number} scale A magnification rate for the effect. The effect is loaded magnificating with this specified number.
253 | * @param {function=} onload A function that is called at loading complete
254 | * @param {function=} onerror A function that is called at loading error. First argument is a message. Second argument is an url.
255 | * @param {function=} redirect A function to redirect a path. First argument is an url and return redirected url.
256 | * @returns {EffekseerEffect} The effect data
257 | */
258 | loadEffect(data: string|ArrayBuffer, scale?: number, onload?: () => void, onerror?: (reason: string, path: string) => void, redirect?: (path: string) => string): EffekseerEffect;
259 |
260 | /**
261 | * Load the effect package file (resources included in the package).
262 | * @param {string|ArrayBuffer} data A URL/ArrayBuffer of effect file (*.efk)
263 | * @param {Object} Unzip An Unzip object.
264 | * @param {number} scale A magnification rate for the effect. The effect is loaded magnificating with this specified number.
265 | * @param {function=} onload A function that is called at loading complete
266 | * @param {function=} onerror A function that is called at loading error. First argument is a message. Second argument is an url.
267 | * @returns {EffekseerEffect} The effect data
268 | */
269 | loadEffectPackage(data: string|ArrayBuffer, Unzip: Object, scale?: number, onload?: () => void, onerror?: (reason: string, path: string) => void): EffekseerEffect;
270 |
271 | /**
272 | * Release the specified effect. Don't touch the instance of effect after released.
273 | * @param {EffekseerEffect} effect The loaded effect
274 | */
275 | releaseEffect(effect: EffekseerEffect): void;
276 |
277 | /**
278 | * Play the specified effect.
279 | * @param {EffekseerEffect} effect The loaded effect
280 | * @param {number} x X value of location that is emited
281 | * @param {number} y Y value of location that is emited
282 | * @param {number} z Z value of location that is emited
283 | * @returns {EffekseerHandle} The effect handle
284 | */
285 | play(effect: EffekseerEffect, x: number, y: number, z: number): EffekseerHandle;
286 |
287 | /**
288 | * Stop the all effects.
289 | */
290 | stopAll(): void;
291 |
292 | /**
293 | * Set the resource loader function.
294 | * @param {function} loader
295 | */
296 | setResourceLoader(loader: (path: string, onload?: () => void, onerror?: (reason: string, path: string) => void) => void): void;
297 |
298 | /**
299 | * Get whether VAO is supported
300 | */
301 | isVertexArrayObjectSupported(): boolean;
302 |
303 | /**
304 | * Gets the number of remaining allocated instances.
305 | */
306 | getRestInstancesCount(): Number;
307 |
308 | /**
309 | * Gets a time when updating
310 | */
311 | getUpdateTime(): Number;
312 |
313 | /**
314 | * Gets a time when drawing
315 | */
316 | getDrawTime(): Number;
317 |
318 | /**
319 | * Set the flag whether the library restores OpenGL states
320 | * if specified true, it makes slow.
321 | * if specified false, You need to restore OpenGL states by yourself
322 | * it must be called after init
323 | * @param {boolean} flag
324 | */
325 | setRestorationOfStatesFlag(flag: boolean): void;
326 |
327 | /**
328 | * Capture current frame buffer and set the image as a background
329 | * @param {number} x captured image's x offset
330 | * @param {number} y captured image's y offset
331 | * @param {number} width captured image's width
332 | * @param {number} height captured image's height
333 | */
334 | captureBackground(x: number, y: number, width: number, height: number): void;
335 |
336 | /**
337 | * Reset background
338 | */
339 | resetBackground(): void;
340 | }
341 |
342 | export class EffekseerEffect {
343 | constructor();
344 | }
345 |
346 | export class EffekseerHandle {
347 | constructor(native: any);
348 |
349 | /**
350 | * Stop this effect instance.
351 | */
352 | stop(): void;
353 |
354 | /**
355 | * Stop the root node of this effect instance.
356 | */
357 | stopRoot(): void;
358 |
359 | /**
360 | * if returned false, this effect is end of playing.
361 | */
362 | readonly exists: boolean;
363 |
364 | /**
365 | * Set frame of this effect instance.
366 | * @param {number} frame Frame of this effect instance.
367 | */
368 | setFrame(frame: number): void;
369 |
370 | /**
371 | * Set the location of this effect instance.
372 | * @param {number} x X value of location
373 | * @param {number} y Y value of location
374 | * @param {number} z Z value of location
375 | */
376 | setLocation(x: number, y: number, z: number): void;
377 | /**
378 | * Set the rotation of this effect instance.
379 | * @param {number} x X value of euler angle
380 | * @param {number} y Y value of euler angle
381 | * @param {number} z Z value of euler angle
382 | */
383 | setRotation(x: number, y: number, z: number): void;
384 |
385 | /**
386 | * Set the scale of this effect instance.
387 | * @param {number} x X value of scale factor
388 | * @param {number} y Y value of scale factor
389 | * @param {number} z Z value of scale factor
390 | */
391 | setScale(x: number, y: number, z: number): void;
392 |
393 | /**
394 | * Set the model matrix of this effect instance.
395 | * @param {array} matrixArray An array that is requred 16 elements
396 | */
397 | setMatrix(matrixArray: Float32Array): void;
398 |
399 | /**
400 | * Set the color of this effect instance.
401 | * @param {number} r R channel value of color
402 | * @param {number} g G channel value of color
403 | * @param {number} b B channel value of color
404 | * @param {number} a A channel value of color
405 | */
406 | setAllColor(r: number, g: number, b: number, a: number): void;
407 |
408 | /**
409 | * Set the target location of this effect instance.
410 | * @param {number} x X value of target location
411 | * @param {number} y Y value of target location
412 | * @param {number} z Z value of target location
413 | */
414 | setTargetLocation(x: number, y: number, z: number): void;
415 |
416 | /**
417 | * get a dynamic parameter, which changes effect parameters dynamically while playing
418 | * @param {number} index slot index
419 | * @returns {number} value
420 | */
421 | getDynamicInput(index: number): number;
422 |
423 | /**
424 | * specfiy a dynamic parameter, which changes effect parameters dynamically while playing
425 | * @param {number} index slot index
426 | * @param {number} value value
427 | */
428 | setDynamicInput(index: number, value: number): void;
429 |
430 | /**
431 | * Sends the specified trigger to the currently playing effect
432 | * @param {number} index trigger index
433 | */
434 | sendTrigger(index: number): void;
435 |
436 | /**
437 | * Set the paused flag of this effect instance.
438 | * if specified true, this effect playing will not advance.
439 | * @param {boolean} paused Paused flag
440 | */
441 | setPaused(paused: boolean): void;
442 |
443 | /**
444 | * Set the shown flag of this effect instance.
445 | * if specified false, this effect will be invisible.
446 | * @param {boolean} shown Shown flag
447 | */
448 | setShown(shown: boolean): void;
449 | /**
450 | * Set playing speed of this effect.
451 | * @param {number} speed Speed ratio
452 | */
453 | setSpeed(speed: number): void;
454 | /**
455 | * Set random seed of this effect.
456 | * @param {number} seed random seed
457 | */
458 | setRandomSeed(seed: number): void;
459 | }
460 | }
461 |
462 | declare module "effekseer" {
463 | export = effekseer;
464 | }
--------------------------------------------------------------------------------