mirror of
https://gitee.com/ice-gl/icegl-three-vue-tres.git
synced 2025-04-05 06:22:43 +08:00
因为库 @alienkitty/space.js,进场翻墙下载不了,故放在了lib里,不在外部依赖
This commit is contained in:
parent
3f9cdd3da2
commit
374927f3ed
3
.gitignore
vendored
3
.gitignore
vendored
@ -15,4 +15,5 @@ dist
|
||||
#不排除 排除alienkitty 和 oimophysics 基于:https://github.com/alienkitty/alien.js 库
|
||||
#因为这两个库从npm包很难下载,且有的下架了
|
||||
#!/node_modules/@alienkitty/
|
||||
#!/node_modules/oimophysics/
|
||||
#!/node_modules/oimophysics/
|
||||
yarn.lock
|
||||
|
@ -22,7 +22,6 @@
|
||||
"tweakpane": "^4.0.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@alienkitty/space.js": "^1.1.0",
|
||||
"@fesjs/builder-vite": "^3.0.3",
|
||||
"@fesjs/fes": "^3.1.5",
|
||||
"@fesjs/fes-design": "^0.8.10",
|
||||
@ -53,4 +52,4 @@
|
||||
"vue": "^3.3.8"
|
||||
},
|
||||
"private": true
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
45
src/plugins/floor/lib/@alienkitty/space.js/.eslintrc.json
Normal file
45
src/plugins/floor/lib/@alienkitty/space.js/.eslintrc.json
Normal file
@ -0,0 +1,45 @@
|
||||
{
|
||||
"env": {
|
||||
"browser": true,
|
||||
"es2022": true
|
||||
},
|
||||
"extends": "eslint:recommended",
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 13,
|
||||
"sourceType": "module"
|
||||
},
|
||||
"plugins": [
|
||||
"html"
|
||||
],
|
||||
"rules": {
|
||||
"arrow-parens": ["error", "as-needed"],
|
||||
"arrow-spacing": ["error", { "before": true, "after": true }],
|
||||
"comma-dangle": ["warn", "never"],
|
||||
"comma-spacing": ["error", { "before": false, "after": true }],
|
||||
"curly": ["error", "multi-line"],
|
||||
"eqeqeq": ["error", "always"],
|
||||
"indent": ["error", 4, { "SwitchCase": 1 }],
|
||||
"key-spacing": ["error", { "beforeColon": false, "afterColon": true }],
|
||||
"keyword-spacing": ["error", { "before": true, "after": true }],
|
||||
"linebreak-style": ["error", "unix"],
|
||||
"lines-between-class-members": ["error", "always", { "exceptAfterSingleLine": true }],
|
||||
"new-parens": "error",
|
||||
"no-inner-declarations": "off",
|
||||
"no-return-await": "error",
|
||||
"object-curly-spacing": ["error", "always"],
|
||||
"object-shorthand": ["error", "always"],
|
||||
"one-var": ["error", { "initialized": "never" }],
|
||||
"padded-blocks": ["error", "never"],
|
||||
"prefer-arrow-callback": "error",
|
||||
"prefer-const": ["error", { "destructuring": "any" }],
|
||||
"quotes": ["error", "single"],
|
||||
"semi-spacing": ["error", { "before": false, "after": true }],
|
||||
"semi": ["error", "always"],
|
||||
"sort-imports": ["warn", { "ignoreDeclarationSort": true }],
|
||||
"space-before-blocks": ["error", "always"],
|
||||
"space-before-function-paren": ["error", { "anonymous": "always", "named": "never", "asyncArrow": "always" }],
|
||||
"space-in-parens": ["error", "never"],
|
||||
"space-infix-ops": "error",
|
||||
"space-unary-ops": ["error", { "words": true, "nonwords": false }]
|
||||
}
|
||||
}
|
4
src/plugins/floor/lib/@alienkitty/space.js/.gitignore
vendored
Executable file
4
src/plugins/floor/lib/@alienkitty/space.js/.gitignore
vendored
Executable file
@ -0,0 +1,4 @@
|
||||
.DS_Store
|
||||
node_modules
|
||||
package-lock.json
|
||||
**/public/assets/js/*.js
|
3
src/plugins/floor/lib/@alienkitty/space.js/.npmignore
Normal file
3
src/plugins/floor/lib/@alienkitty/space.js/.npmignore
Normal file
@ -0,0 +1,3 @@
|
||||
.eslintrc.json
|
||||
examples
|
||||
space.js.png
|
21
src/plugins/floor/lib/@alienkitty/space.js/LICENSE
Executable file
21
src/plugins/floor/lib/@alienkitty/space.js/LICENSE
Executable file
@ -0,0 +1,21 @@
|
||||
The MIT License
|
||||
|
||||
Copyright © 2023 Patrick Schroen
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
212
src/plugins/floor/lib/@alienkitty/space.js/README.md
Executable file
212
src/plugins/floor/lib/@alienkitty/space.js/README.md
Executable file
@ -0,0 +1,212 @@
|
||||
# Space.js
|
||||
|
||||
[![NPM Package][npm]][npm-url]
|
||||
[![NPM Downloads][npm-downloads]][npmtrends-url]
|
||||
[![DeepScan][deepscan]][deepscan-url]
|
||||
[![Discord][discord]][discord-url]
|
||||
|
||||
This library is part of two sibling libraries, [Space.js](https://github.com/alienkitty/space.js) for UI, Panel components, Tween, Web Audio, loaders, utilities, and [Alien.js](https://github.com/alienkitty/alien.js) for 3D utilities, materials, shaders and physics.
|
||||
|
||||
<p>
|
||||
<img src="https://github.com/alienkitty/space.js/raw/main/space.js.png" alt="Space.js">
|
||||
</p>
|
||||
|
||||
### Usage
|
||||
|
||||
Space.js is divided into two entry points depending on your use case.
|
||||
|
||||
The main entry point without any dependencies is for the UI components, loaders and utilities.
|
||||
|
||||
```sh
|
||||
npm i @alienkitty/space.js
|
||||
```
|
||||
|
||||
```js
|
||||
import { ... } from '@alienkitty/space.js';
|
||||
```
|
||||
|
||||
For example the UI and Panel components:
|
||||
|
||||
```js
|
||||
import { Panel, PanelItem, UI } from '@alienkitty/space.js';
|
||||
```
|
||||
|
||||
[Tween](https://github.com/alienkitty/alien.js/wiki/Tween) animation engine:
|
||||
|
||||
```js
|
||||
import { ticker, tween } from '@alienkitty/space.js';
|
||||
|
||||
ticker.start();
|
||||
|
||||
const data = {
|
||||
radius: 0
|
||||
};
|
||||
|
||||
tween(data, { radius: 24, spring: 1.2, damping: 0.4 }, 1000, 'easeOutElastic', null, () => {
|
||||
console.log(data.radius);
|
||||
});
|
||||
```
|
||||
|
||||
Web Audio engine:
|
||||
|
||||
```js
|
||||
import { BufferLoader, WebAudio } from '@alienkitty/space.js';
|
||||
|
||||
const bufferLoader = new BufferLoader();
|
||||
await bufferLoader.loadAllAsync(['assets/sounds/gong.mp3']);
|
||||
WebAudio.init({ sampleRate: 48000 });
|
||||
WebAudio.load(bufferLoader.files);
|
||||
|
||||
const gong = WebAudio.get('gong');
|
||||
gong.gain.set(0.5);
|
||||
|
||||
document.addEventListener('pointerdown', () => {
|
||||
gong.play();
|
||||
});
|
||||
```
|
||||
|
||||
Audio stream support:
|
||||
|
||||
```js
|
||||
import { WebAudio } from '@alienkitty/space.js';
|
||||
|
||||
WebAudio.init({ sampleRate: 48000 });
|
||||
|
||||
// Shoutcast streams append a semicolon (;) to the URL
|
||||
WebAudio.load({ protonradio: 'https://shoutcast.protonradio.com/;' });
|
||||
|
||||
const protonradio = WebAudio.get('protonradio');
|
||||
protonradio.gain.set(1);
|
||||
|
||||
document.addEventListener('pointerdown', () => {
|
||||
protonradio.play();
|
||||
});
|
||||
```
|
||||
|
||||
And the `@alienkitty/space.js/three` entry point for [three.js](https://github.com/mrdoob/three.js) UI components, loaders and utilities.
|
||||
|
||||
```sh
|
||||
npm i three @alienkitty/space.js
|
||||
```
|
||||
|
||||
```js
|
||||
import { EnvironmentTextureLoader } from '@alienkitty/space.js/three';
|
||||
|
||||
// ...
|
||||
const environmentLoader = new EnvironmentTextureLoader(renderer);
|
||||
environmentLoader.load('assets/textures/env/jewelry_black_contrast.jpg', texture => {
|
||||
scene.environment = texture;
|
||||
});
|
||||
```
|
||||
|
||||
### Examples
|
||||
|
||||
#### ui
|
||||
|
||||
[logo](https://space.js.org/examples/logo.html) (interface)
|
||||
[progress](https://space.js.org/examples/progress_canvas.html) (canvas)
|
||||
[progress](https://space.js.org/examples/progress.html) (svg)
|
||||
[progress indeterminate](https://space.js.org/examples/progress_indeterminate.html) (svg)
|
||||
[close](https://space.js.org/examples/close.html) (svg)
|
||||
[tween](https://space.js.org/examples/tween.html) (svg)
|
||||
[magnetic](https://space.js.org/examples/magnetic.html) (component, svg)
|
||||
[styles](https://space.js.org/examples/styles.html)
|
||||
[fps](https://space.js.org/examples/fps.html)
|
||||
[fps panel](https://space.js.org/examples/fps_panel.html)
|
||||
[panel](https://space.js.org/examples/panel.html) (standalone)
|
||||
[ufo](https://ufo.ai/) (2d scene, smooth scroll with skew effect)
|
||||
|
||||
#### 3d
|
||||
|
||||
[materials](https://space.js.org/examples/three/3d_materials.html)
|
||||
[materials instancing](https://space.js.org/examples/three/3d_materials_instancing.html) ([debug](https://space.js.org/examples/three/3d_materials_instancing.html?3&debug))
|
||||
[materials instancing](https://space.js.org/examples/three/3d_materials_instancing_modified.html) (custom, [debug](https://space.js.org/examples/three/3d_materials_instancing_modified.html?3&debug))
|
||||
[lights](https://space.js.org/examples/three/3d_lights.html)
|
||||
|
||||
#### audio
|
||||
|
||||
[gong](https://space.js.org/examples/audio_gong.html)
|
||||
[stream](https://space.js.org/examples/audio_stream.html)
|
||||
[rhythm](https://space.js.org/examples/audio_rhythm.html)
|
||||
|
||||
#### thread
|
||||
|
||||
[canvas](https://space.js.org/examples/thread_canvas.html) (noise)
|
||||
|
||||
### Getting started
|
||||
|
||||
Clone this repository and run the examples:
|
||||
|
||||
```sh
|
||||
git clone https://github.com/alienkitty/space.js
|
||||
cd space.js
|
||||
npx servez
|
||||
```
|
||||
|
||||
### ESLint
|
||||
|
||||
```sh
|
||||
npm i -D eslint eslint-plugin-html
|
||||
npx eslint src
|
||||
npx eslint examples/about/src
|
||||
npx eslint examples/three/*.html
|
||||
npx eslint examples/*.html
|
||||
```
|
||||
|
||||
### Roadmap
|
||||
|
||||
#### v1.0
|
||||
|
||||
* [x] Initial release based on the UI components from [Multiuser Blocks](https://multiuser-blocks.glitch.me/)
|
||||
|
||||
#### v1.1
|
||||
|
||||
* [x] Three.js material UI
|
||||
* [x] Three.js light UI
|
||||
* [x] Three.js UI keyboard support
|
||||
|
||||
#### v1.2
|
||||
|
||||
* [x] Three.js UI multiple select
|
||||
* [ ] Material texture drag and drop
|
||||
* [ ] Material texture thumbnails
|
||||
|
||||
#### v1.3
|
||||
|
||||
* [ ] GLTF drag and drop
|
||||
* [ ] Load/save scene
|
||||
|
||||
#### v1.4
|
||||
|
||||
* [ ] Move objects
|
||||
* [ ] Change camera perspective
|
||||
|
||||
#### v1.5
|
||||
|
||||
* [ ] OGL version
|
||||
* [ ] Documentation
|
||||
|
||||
#### v1.6
|
||||
|
||||
* [ ] WebXR version
|
||||
|
||||
### Resources
|
||||
|
||||
* [Tween](https://github.com/alienkitty/alien.js/wiki/Tween)
|
||||
* [Changelog](https://github.com/alienkitty/space.js/releases)
|
||||
|
||||
### See also
|
||||
|
||||
* [Alien.js](https://github.com/alienkitty/alien.js)
|
||||
* [Three.js](https://github.com/mrdoob/three.js)
|
||||
* [OGL](https://github.com/oframe/ogl)
|
||||
|
||||
|
||||
[npm]: https://img.shields.io/npm/v/@alienkitty/space.js
|
||||
[npm-url]: https://www.npmjs.com/package/@alienkitty/space.js
|
||||
[npm-downloads]: https://img.shields.io/npm/dw/@alienkitty/space.js
|
||||
[npmtrends-url]: https://www.npmtrends.com/@alienkitty/space.js
|
||||
[deepscan]: https://deepscan.io/api/teams/20020/projects/23997/branches/734568/badge/grade.svg
|
||||
[deepscan-url]: https://deepscan.io/dashboard#view=project&tid=20020&pid=23997&bid=734568
|
||||
[discord]: https://img.shields.io/discord/773739853913260032
|
||||
[discord-url]: https://discord.gg/9rSkAzB7PM
|
22
src/plugins/floor/lib/@alienkitty/space.js/examples/about/package.json
Executable file
22
src/plugins/floor/lib/@alienkitty/space.js/examples/about/package.json
Executable file
@ -0,0 +1,22 @@
|
||||
{
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"build": "rollup -c",
|
||||
"dev": "concurrently --names \"ROLLUP,HTTP\" -c \"bgBlue.bold,bgGreen.bold\" \"rollup -c -w -m inline\" \"servez public\"",
|
||||
"start": "servez public"
|
||||
},
|
||||
"dependencies": {
|
||||
"@alienkitty/alien.js": "alienkitty/alien.js#dev",
|
||||
"@alienkitty/space.js": "alienkitty/space.js#dev",
|
||||
"oimophysics": "saharan/OimoPhysics#v1.2.3",
|
||||
"three": "latest"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@rollup/plugin-node-resolve": "latest",
|
||||
"concurrently": "latest",
|
||||
"rollup": "latest",
|
||||
"rollup-plugin-bundleutils": "latest",
|
||||
"servez": "latest"
|
||||
}
|
||||
}
|
@ -0,0 +1,104 @@
|
||||
@font-face {
|
||||
font-family: 'D-DIN';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
src: url(../fonts/D-DIN.woff2) format('woff2');
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Gothic A1';
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
src: url(../fonts/GothicA1-Medium.woff2) format('woff2');
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Gothic A1';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
src: url(../fonts/GothicA1-Bold.woff2) format('woff2');
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Roboto Mono';
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
src: url(../fonts/RobotoMono-Medium.woff2) format('woff2');
|
||||
}
|
||||
|
||||
:root {
|
||||
--bg-color: #000;
|
||||
--ui-font-family: 'Roboto Mono', monospace;
|
||||
--ui-font-weight: 400;
|
||||
--ui-font-size: 11px;
|
||||
--ui-line-height: 15px;
|
||||
--ui-letter-spacing: 0.02em;
|
||||
--ui-number-letter-spacing: 0.5px;
|
||||
--ui-secondary-font-size: 10px;
|
||||
--ui-secondary-letter-spacing: 0.5px;
|
||||
--ui-color: rgba(255, 255, 255, 0.94);
|
||||
--ui-color-triplet: 255, 255, 255;
|
||||
--ui-color-line: rgba(255, 255, 255, 0.5);
|
||||
--ui-invert-light-color: #000;
|
||||
--ui-invert-light-color-triplet: 0, 0, 0;
|
||||
--ui-invert-light-color-line: rgba(0, 0, 0, 0.5);
|
||||
--ui-invert-dark-color: rgba(255, 255, 255, 0.94);
|
||||
--ui-invert-dark-color-triplet: 255, 255, 255;
|
||||
--ui-invert-dark-color-line: rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
|
||||
*, :after, :before {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
touch-action: none;
|
||||
-webkit-touch-callout: none;
|
||||
-webkit-user-drag: none;
|
||||
-webkit-text-size-adjust: none;
|
||||
text-size-adjust: none;
|
||||
}
|
||||
|
||||
*:focus {
|
||||
outline: 0;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
html, body {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
position: fixed;
|
||||
font-family: 'Gothic A1', sans-serif;
|
||||
font-weight: 500;
|
||||
font-size: 13px;
|
||||
line-height: 1.5;
|
||||
background-color: var(--bg-color);
|
||||
color: var(--ui-color);
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
}
|
||||
|
||||
a {
|
||||
color: var(--ui-color);
|
||||
text-decoration: none;
|
||||
text-underline-offset: 3px;
|
||||
}
|
||||
|
||||
a:hover, a:focus {
|
||||
color: var(--ui-color);
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
::selection {
|
||||
background-color: var(--ui-color);
|
||||
color: var(--bg-color);
|
||||
}
|
||||
|
||||
.ui .info, .ui .target {
|
||||
-moz-osx-font-smoothing: auto;
|
||||
-webkit-font-smoothing: auto;
|
||||
}
|
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="90" height="86" viewBox="0 0 88 88"><defs><linearGradient id="lightA" gradientUnits="userSpaceOnUse" x1="59.167" y1="42.175" x2="45.501" y2="36.342"><stop offset="0" stop-color="#000"/><stop offset=".22" stop-color="#fff"/><stop offset="1" stop-color="#fff"/></linearGradient><linearGradient id="lightB" gradientUnits="userSpaceOnUse" x1="65.39" y1="42.27" x2="76.057" y2="34.103"><stop offset="0" stop-color="#000"/><stop offset=".22" stop-color="#fff"/><stop offset="1" stop-color="#fff"/></linearGradient><linearGradient id="darkA" gradientUnits="userSpaceOnUse" x1="59.167" y1="42.175" x2="45.501" y2="36.342"><stop offset="0" stop-color="#fff"/><stop offset=".22" stop-color="#000"/><stop offset="1" stop-color="#000"/></linearGradient><linearGradient id="darkB" gradientUnits="userSpaceOnUse" x1="65.39" y1="42.27" x2="76.057" y2="34.103"><stop offset="0" stop-color="#fff"/><stop offset=".22" stop-color="#000"/><stop offset="1" stop-color="#000"/></linearGradient><style>@media (prefers-color-scheme: dark){.cls-1{fill:url(#darkA);}.cls-2{fill:url(#darkB);}.cls-3{fill:#fff;}}</style></defs><path d="M48.891 34.6c.529.634 1.342 1.367 2.439 2.2l2.666 1.95 2.666 1.65 2.326 1.9c-2.912.833-6.277 1-10.098.5-3.518-.5-6.715-1.467-9.589-2.9-.983-.5-1.929-1.283-2.837-2.35-.907-1.133-1.456-2.15-1.646-3.05-.377-1.7-.151-3.133.681-4.3.87-1.267 2.25-1.9 4.142-1.9 1.967-.033 3.782.667 5.448 2.1l1.984 2.05 1.818 2.15" fill="url(#lightA)" class="cls-1"/><path d="M76.918 28.95c1.436.467 2.383 1.55 2.836 3.25.492 1.867.246 3.467-.736 4.8-.719.966-1.703 1.933-2.951 2.9-1.324 1.033-2.553 1.7-3.688 2-2.988.767-5.221.9-6.695.4l3.461-3.5c.832-1.1 1.57-2.483 2.213-4.15.529-1.434 1.172-2.667 1.93-3.7.982-1.466 1.93-2.183 2.836-2.15l.794.15" fill="url(#lightB)" class="cls-2"/><path d="M83.102 11.1c-1.814 2.467-3.084 4.55-3.801 6.25-1.098 2.567-1.191 4.784-.283 6.65l1.588 2.65 2.043 3.3c1.854 3.5 2.23 7.333 1.135 11.5-1.061 3.8-3.084 7.117-6.072 9.95-2.533 2.301-5.314 3.867-8.34 4.7-1.398.366-1.984 1.534-1.758 3.5.037.634.207 1.45.51 2.45l.625 2.2c.453 1.833.699 4.884.736 9.149-.037 1.5.02 2.351.172 2.551.227.333 1.059.533 2.496.6 1.777.033 3.311.666 4.596 1.9 1.361 1.333 1.605 2.766.738 4.3-.871 1.466-2.725 2.333-5.561 2.6l-3.633.101-39.657-.101c-1.249-.1-2.307-.566-3.178-1.399-1.059-1-1.853-1.584-2.382-1.75-3.178-.934-6.374-2.684-9.588-5.25A37.243 37.243 0 0 1 5.261 68.1C2.84 64.567 1.327 61.117.722 57.75c-.681-3.8-.17-7.116 1.532-9.95 1.286-2.267 3.196-3.883 5.73-4.85 2.799-1.1 5.257-.816 7.375.85 1.476 1.2 2.307 2.7 2.497 4.5.114 1.733-.378 3.367-1.476 4.9l-1.588 2-1.475 2.149c-.681 1.467-.946 3.184-.794 5.15.114 2.033.644 3.683 1.588 4.95 2.875 3.699 5.031 5.434 6.468 5.199.87-.166 1.362-.866 1.475-2.1l.057-3.1c.605-3.801 1.532-7.033 2.779-9.7.606-1.267 1.57-2.733 2.894-4.4l3.291-4.2c.794-1.133.983-2.1.567-2.899L29.6 43.5a20.754 20.754 0 0 1-1.815-3.55c-.341-.833-.567-1.833-.681-3l-.34-2.4c-.189-.767-.606-1.35-1.249-1.75-.719-.5-1.815-.867-3.291-1.1l1.021-.65c.492-.233.719-.467.681-.7-.038-.2-.511-.533-1.418-1l-1.646-.8c1.097-.5 1.305-1.2.624-2.1l-2.212-2.2-3.518-4.7-3.971-4.95c-1.172-1.167-2.062-2.366-2.667-3.6.87-.133 2.042.033 3.518.5l3.348 1.1 12.368 3.05 4.255 1.15c2.647.833 5.522 1.45 8.624 1.85l8.795.2c.68-.1 1.625-.366 2.836-.8l2.838-.7 4.65-.3c1.363-.167 2.838-.617 4.426-1.35 3.404-1.667 7.604-4.117 12.596-7.35L89.571.55c-.455.267-.891.967-1.305 2.1l-.965 2.4-4.199 6.05M47.074 32.45 45.09 30.4c-1.666-1.434-3.48-2.133-5.448-2.1-1.891 0-3.272.633-4.142 1.9-.832 1.167-1.059 2.6-.681 4.3.189.9.738 1.917 1.646 3.05.908 1.067 1.854 1.85 2.837 2.35 2.874 1.433 6.071 2.4 9.589 2.9 3.82.5 7.186.333 10.098-.5l-2.326-1.9-2.666-1.65-2.667-1.95c-1.098-.833-1.91-1.566-2.439-2.2l-1.817-2.15m29.049-3.65c-.906-.033-1.854.684-2.836 2.15-.758 1.034-1.4 2.267-1.93 3.7-.643 1.667-1.381 3.05-2.213 4.15l-3.461 3.5c1.475.5 3.707.367 6.695-.4 1.135-.3 2.363-.967 3.688-2 1.248-.967 2.232-1.934 2.951-2.9.982-1.333 1.229-2.934.736-4.8-.453-1.7-1.4-2.783-2.836-3.25l-.794-.15" fill="#000" class="cls-3"/></svg>
|
After Width: | Height: | Size: 4.0 KiB |
@ -0,0 +1,2 @@
|
||||
Free Jewelry HDRI Environment Map Black Contrast
|
||||
https://3djewels.pro/materials/hdri/jewelry-hdri-environment-map-black-contrast/
|
@ -0,0 +1,2 @@
|
||||
UV Texture Grids
|
||||
http://www.pixelcg.com/blog/?p=146
|
38
src/plugins/floor/lib/@alienkitty/space.js/examples/about/public/index.html
Executable file
38
src/plugins/floor/lib/@alienkitty/space.js/examples/about/public/index.html
Executable file
@ -0,0 +1,38 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
|
||||
<meta name="description" content="Future web UI.">
|
||||
<meta name="keywords" content="space, spacejs, javascript, 3d, animation, interaction, gui, ui, panel, tween, web audio, loaders, utilities, svg, canvas, threejs, webgl, webgl2, web workers, creative coding">
|
||||
|
||||
<title>Space.js</title>
|
||||
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:url" content="https://space.js.org/">
|
||||
<meta property="og:site_name" content="Space.js">
|
||||
<meta property="og:title" content="Space.js">
|
||||
<meta property="og:description" content="Future web UI.">
|
||||
<meta property="og:image" content="https://space.js.org/assets/meta/share.png">
|
||||
|
||||
<meta name="twitter:card" content="summary_large_image">
|
||||
<meta name="twitter:site" content="@pschroen">
|
||||
<meta name="twitter:creator" content="@pschroen">
|
||||
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Roboto+Mono">
|
||||
<link rel="stylesheet" href="assets/css/style.css">
|
||||
<link rel="icon" type="image/svg+xml" href="assets/meta/favicon.svg">
|
||||
|
||||
<script type="module">
|
||||
import { Preloader } from './assets/js/loader.js';
|
||||
|
||||
Preloader.init();
|
||||
</script>
|
||||
<script nomodule>
|
||||
location.href = 'unsupported.html';
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
@ -0,0 +1,70 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
|
||||
<meta name="robots" content="noindex, nofollow, noarchive">
|
||||
|
||||
<title>Sorry, your browser is out of date</title>
|
||||
|
||||
<style>
|
||||
@font-face {
|
||||
font-family: 'Roboto Mono';
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
src: url(assets/fonts/RobotoMono-Medium.woff2) format('woff2');
|
||||
}
|
||||
|
||||
*, :after, :before {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
touch-action: none;
|
||||
-webkit-touch-callout: none;
|
||||
-webkit-user-drag: none;
|
||||
-webkit-text-size-adjust: none;
|
||||
text-size-adjust: none;
|
||||
}
|
||||
|
||||
*:focus {
|
||||
outline: 0;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
html, body {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
position: fixed;
|
||||
font-family: 'Roboto Mono', monospace;
|
||||
font-weight: 400;
|
||||
font-size: 11px;
|
||||
line-height: 1.7;
|
||||
letter-spacing: 0.03em;
|
||||
background-color: #000;
|
||||
color: rgba(255, 255, 255, 0.94);
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
}
|
||||
|
||||
a, a:hover, a:focus {
|
||||
color: rgba(255, 255, 255, 0.94);
|
||||
}
|
||||
|
||||
::selection {
|
||||
background-color: rgba(255, 255, 255, 0.94);
|
||||
color: #000;
|
||||
}
|
||||
</style>
|
||||
|
||||
<link rel="icon" type="image/svg+xml" href="assets/meta/favicon.svg">
|
||||
</head>
|
||||
<body>
|
||||
<div style="position: absolute; width: 90px; height: 86px; background-size: 90px 86px; left: 50%; top: 50%; margin-left: -45px; margin-top: -108px; background-image: url(assets/images/fallback.png);"></div>
|
||||
<div style="position: absolute; width: 300px; left: 50%; top: 50%; margin-left: -150px; text-align: center; white-space: nowrap;"><span style="opacity: 0.75;">Sorry, your browser is out of date</span><br><a href="https://www.google.com/chrome/">Upgrade your browser</a></div>
|
||||
</body>
|
||||
</html>
|
28
src/plugins/floor/lib/@alienkitty/space.js/examples/about/rollup.config.js
Executable file
28
src/plugins/floor/lib/@alienkitty/space.js/examples/about/rollup.config.js
Executable file
@ -0,0 +1,28 @@
|
||||
import resolve from '@rollup/plugin-node-resolve';
|
||||
import { terser, timestamp } from 'rollup-plugin-bundleutils';
|
||||
|
||||
const production = !process.env.ROLLUP_WATCH;
|
||||
|
||||
export default {
|
||||
input: 'src/main.js',
|
||||
preserveEntrySignatures: 'allow-extension',
|
||||
output: {
|
||||
dir: 'public/assets/js',
|
||||
entryFileNames: 'loader.js',
|
||||
chunkFileNames: ({ name }) => `${name.toLowerCase()}.js`,
|
||||
format: 'es',
|
||||
minifyInternalExports: false
|
||||
},
|
||||
plugins: [
|
||||
resolve({
|
||||
browser: true
|
||||
}),
|
||||
production && terser({
|
||||
output: {
|
||||
preamble: `// ${timestamp()}`
|
||||
},
|
||||
keep_classnames: true,
|
||||
keep_fnames: true
|
||||
})
|
||||
]
|
||||
};
|
@ -0,0 +1,5 @@
|
||||
export class Config {
|
||||
static BREAKPOINT = 1000;
|
||||
|
||||
static DEBUG = /[?&]debug/.test(location.search);
|
||||
}
|
@ -0,0 +1,4 @@
|
||||
export class Layer {
|
||||
static DEFAULT = 0;
|
||||
static PICKING = 1;
|
||||
}
|
115
src/plugins/floor/lib/@alienkitty/space.js/examples/about/src/controllers/App.js
Executable file
115
src/plugins/floor/lib/@alienkitty/space.js/examples/about/src/controllers/App.js
Executable file
@ -0,0 +1,115 @@
|
||||
import { ImageBitmapLoaderThread, Stage, Thread, ticker, wait } from '@alienkitty/space.js/three';
|
||||
|
||||
import { WorldController } from './world/WorldController.js';
|
||||
import { CameraController } from './world/CameraController.js';
|
||||
import { SceneController } from './world/SceneController.js';
|
||||
import { PhysicsController } from './world/PhysicsController.js';
|
||||
import { InputManager } from './world/InputManager.js';
|
||||
import { RenderManager } from './world/RenderManager.js';
|
||||
import { PanelController } from './panels/PanelController.js';
|
||||
import { SceneView } from '../views/SceneView.js';
|
||||
import { UI } from '../views/UI.js';
|
||||
|
||||
export class App {
|
||||
static async init() {
|
||||
if (!/firefox/i.test(navigator.userAgent)) {
|
||||
this.initThread();
|
||||
}
|
||||
|
||||
this.initWorld();
|
||||
this.initViews();
|
||||
this.initControllers();
|
||||
|
||||
this.addListeners();
|
||||
this.onResize();
|
||||
|
||||
await Promise.all([
|
||||
document.fonts.ready,
|
||||
SceneController.ready(),
|
||||
WorldController.textureLoader.ready(),
|
||||
WorldController.environmentLoader.ready()
|
||||
]);
|
||||
|
||||
this.initPanel();
|
||||
}
|
||||
|
||||
static initThread() {
|
||||
ImageBitmapLoaderThread.init();
|
||||
|
||||
Thread.shared();
|
||||
}
|
||||
|
||||
static initWorld() {
|
||||
WorldController.init();
|
||||
Stage.add(WorldController.element);
|
||||
}
|
||||
|
||||
static initViews() {
|
||||
this.view = new SceneView();
|
||||
WorldController.scene.add(this.view);
|
||||
|
||||
this.ui = new UI();
|
||||
Stage.add(this.ui);
|
||||
}
|
||||
|
||||
static initControllers() {
|
||||
const { renderer, scene, camera, controls, physics } = WorldController;
|
||||
|
||||
CameraController.init(camera, controls);
|
||||
SceneController.init(this.view);
|
||||
PhysicsController.init(physics);
|
||||
InputManager.init(scene, camera, controls);
|
||||
RenderManager.init(renderer, scene, camera, this.ui);
|
||||
}
|
||||
|
||||
static initPanel() {
|
||||
const { renderer, scene, camera, physics } = WorldController;
|
||||
|
||||
PanelController.init(renderer, scene, camera, physics, this.view, this.ui);
|
||||
}
|
||||
|
||||
static addListeners() {
|
||||
Stage.events.on('invert', this.onInvert);
|
||||
window.addEventListener('resize', this.onResize);
|
||||
ticker.add(this.onUpdate);
|
||||
}
|
||||
|
||||
// Event handlers
|
||||
|
||||
static onInvert = ({ invert }) => {
|
||||
this.view.invert(invert);
|
||||
RenderManager.invert(invert);
|
||||
};
|
||||
|
||||
static onResize = () => {
|
||||
const width = document.documentElement.clientWidth;
|
||||
const height = document.documentElement.clientHeight;
|
||||
const dpr = window.devicePixelRatio;
|
||||
|
||||
WorldController.resize(width, height, dpr);
|
||||
CameraController.resize(width, height);
|
||||
RenderManager.resize(width, height, dpr);
|
||||
};
|
||||
|
||||
static onUpdate = (time, delta, frame) => {
|
||||
WorldController.update(time, delta, frame);
|
||||
CameraController.update();
|
||||
SceneController.update(time);
|
||||
PhysicsController.update();
|
||||
InputManager.update(time);
|
||||
RenderManager.update(time, delta, frame);
|
||||
PanelController.update(time);
|
||||
};
|
||||
|
||||
// Public methods
|
||||
|
||||
static start = async () => {
|
||||
WorldController.animateIn();
|
||||
SceneController.animateIn();
|
||||
|
||||
await wait(1000);
|
||||
|
||||
this.ui.animateIn();
|
||||
PanelController.animateIn();
|
||||
};
|
||||
}
|
@ -0,0 +1,60 @@
|
||||
import { MultiLoader, Stage } from '@alienkitty/space.js/three';
|
||||
|
||||
import { PreloaderView } from '../views/PreloaderView.js';
|
||||
|
||||
export class Preloader {
|
||||
static init() {
|
||||
this.initStage();
|
||||
this.initView();
|
||||
this.initLoader();
|
||||
|
||||
this.addListeners();
|
||||
}
|
||||
|
||||
static initStage() {
|
||||
Stage.init();
|
||||
}
|
||||
|
||||
static initView() {
|
||||
this.view = new PreloaderView();
|
||||
Stage.add(this.view);
|
||||
}
|
||||
|
||||
static async initLoader() {
|
||||
this.view.animateIn();
|
||||
|
||||
this.loader = new MultiLoader();
|
||||
this.loader.add(2);
|
||||
|
||||
const { App } = await import('./App.js');
|
||||
this.loader.trigger(1);
|
||||
|
||||
this.app = App;
|
||||
|
||||
await this.app.init();
|
||||
this.loader.trigger(1);
|
||||
}
|
||||
|
||||
static addListeners() {
|
||||
this.loader.events.on('progress', this.view.onProgress);
|
||||
this.view.events.on('complete', this.onComplete);
|
||||
}
|
||||
|
||||
static removeListeners() {
|
||||
this.loader.events.off('progress', this.view.onProgress);
|
||||
this.view.events.off('complete', this.onComplete);
|
||||
}
|
||||
|
||||
// Event handlers
|
||||
|
||||
static onComplete = async () => {
|
||||
this.removeListeners();
|
||||
|
||||
this.loader = this.loader.destroy();
|
||||
|
||||
await this.view.animateOut();
|
||||
this.view = this.view.destroy();
|
||||
|
||||
this.app.start();
|
||||
};
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
import { Panel, PanelItem } from '@alienkitty/space.js/three';
|
||||
|
||||
export class EnvPanel extends Panel {
|
||||
constructor(scene) {
|
||||
super();
|
||||
|
||||
this.scene = scene;
|
||||
|
||||
this.initPanel();
|
||||
}
|
||||
|
||||
initPanel() {
|
||||
const items = [
|
||||
{
|
||||
type: 'divider'
|
||||
}
|
||||
// TODO: Texture thumbnails
|
||||
];
|
||||
|
||||
items.forEach(data => {
|
||||
this.add(new PanelItem(data));
|
||||
});
|
||||
}
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
import { Panel, PanelItem, getKeyByValue } from '@alienkitty/space.js/three';
|
||||
|
||||
export class GridPanel extends Panel {
|
||||
constructor(helper) {
|
||||
super();
|
||||
|
||||
this.helper = helper;
|
||||
|
||||
this.initPanel();
|
||||
}
|
||||
|
||||
initPanel() {
|
||||
const helper = this.helper;
|
||||
|
||||
const gridOptions = {
|
||||
Off: false,
|
||||
On: true
|
||||
};
|
||||
|
||||
const items = [
|
||||
{
|
||||
type: 'divider'
|
||||
},
|
||||
{
|
||||
type: 'list',
|
||||
list: gridOptions,
|
||||
value: getKeyByValue(gridOptions, helper.visible),
|
||||
callback: value => {
|
||||
helper.visible = gridOptions[value];
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
items.forEach(data => {
|
||||
this.add(new PanelItem(data));
|
||||
});
|
||||
}
|
||||
}
|
@ -0,0 +1,203 @@
|
||||
import { Vector3 } from 'three';
|
||||
|
||||
import { LightOptions, LightPanelController, PanelItem, Point3D, Stage, brightness, getKeyByLight, getKeyByValue } from '@alienkitty/space.js/three';
|
||||
|
||||
import { WorldController } from '../world/WorldController.js';
|
||||
import { PhysicsController } from '../world/PhysicsController.js';
|
||||
import { ScenePanelController } from './ScenePanelController.js';
|
||||
import { PostPanel } from './PostPanel.js';
|
||||
import { EnvPanel } from './EnvPanel.js';
|
||||
import { GridPanel } from './GridPanel.js';
|
||||
|
||||
export class PanelController {
|
||||
static init(renderer, scene, camera, physics, view, ui) {
|
||||
this.renderer = renderer;
|
||||
this.scene = scene;
|
||||
this.camera = camera;
|
||||
this.physics = physics;
|
||||
this.view = view;
|
||||
this.ui = ui;
|
||||
|
||||
this.lastInvert = null;
|
||||
this.lights = [];
|
||||
|
||||
this.initControllers();
|
||||
this.initPanel();
|
||||
this.setInvert(this.scene.background);
|
||||
}
|
||||
|
||||
static initControllers() {
|
||||
const { textureLoader } = WorldController;
|
||||
|
||||
Point3D.init(this.scene, this.camera, {
|
||||
root: Stage,
|
||||
container: this.ui,
|
||||
physics: this.physics,
|
||||
loader: textureLoader,
|
||||
uvHelper: true
|
||||
});
|
||||
Point3D.enabled = false;
|
||||
|
||||
ScenePanelController.init(this.view);
|
||||
LightPanelController.init(this.scene);
|
||||
}
|
||||
|
||||
static initPanel() {
|
||||
const scene = this.scene;
|
||||
const physics = this.physics;
|
||||
|
||||
const physicsOptions = {
|
||||
Off: false,
|
||||
Physics: true
|
||||
};
|
||||
|
||||
const vector3 = new Vector3();
|
||||
const gravity = physics.world.getGravity();
|
||||
|
||||
const sceneOptions = {
|
||||
Post: PostPanel,
|
||||
Env: EnvPanel
|
||||
};
|
||||
|
||||
scene.traverse(object => {
|
||||
if (object.isLight) {
|
||||
const key = getKeyByLight(LightOptions, object);
|
||||
|
||||
sceneOptions[key] = [object, LightOptions[key][1]];
|
||||
|
||||
this.lights.push(object);
|
||||
}
|
||||
});
|
||||
|
||||
sceneOptions.Grid = GridPanel;
|
||||
|
||||
const items = [
|
||||
{
|
||||
label: 'FPS'
|
||||
},
|
||||
{
|
||||
type: 'divider'
|
||||
},
|
||||
{
|
||||
type: 'list',
|
||||
list: physicsOptions,
|
||||
value: getKeyByValue(physicsOptions, PhysicsController.enabled),
|
||||
callback: value => {
|
||||
PhysicsController.enabled = physicsOptions[value];
|
||||
|
||||
// Reset
|
||||
vector3.set(0, 0, 0);
|
||||
|
||||
physics.objects.forEach(object => {
|
||||
const { position, quaternion } = object;
|
||||
|
||||
physics.setPosition(object, position);
|
||||
physics.setOrientation(object, quaternion);
|
||||
physics.setLinearVelocity(object, vector3);
|
||||
physics.setAngularVelocity(object, vector3);
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'slider',
|
||||
label: 'Gravity',
|
||||
min: -10,
|
||||
max: 10,
|
||||
step: 0.1,
|
||||
value: -gravity.y,
|
||||
callback: value => {
|
||||
gravity.y = -value;
|
||||
physics.world.setGravity(gravity);
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'divider'
|
||||
},
|
||||
{
|
||||
type: 'color',
|
||||
value: scene.background,
|
||||
callback: value => {
|
||||
scene.background.copy(value);
|
||||
|
||||
this.setInvert(value);
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'divider'
|
||||
},
|
||||
{
|
||||
type: 'list',
|
||||
list: sceneOptions,
|
||||
value: 'Post',
|
||||
callback: (value, panel) => {
|
||||
switch (value) {
|
||||
case 'Post':
|
||||
case 'Env': {
|
||||
const ScenePanel = sceneOptions[value];
|
||||
|
||||
const scenePanel = new ScenePanel(scene);
|
||||
scenePanel.animateIn(true);
|
||||
|
||||
panel.setContent(scenePanel);
|
||||
break;
|
||||
}
|
||||
case 'Grid': {
|
||||
const ScenePanel = sceneOptions[value];
|
||||
|
||||
const scenePanel = new ScenePanel(this.view.floor.gridHelper);
|
||||
scenePanel.animateIn(true);
|
||||
|
||||
panel.setContent(scenePanel);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
const [light, LightPanel] = sceneOptions[value];
|
||||
|
||||
const lightPanel = new LightPanel(LightPanelController, light);
|
||||
lightPanel.animateIn(true);
|
||||
|
||||
panel.setContent(lightPanel);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
items.forEach(data => {
|
||||
this.ui.addPanel(new PanelItem(data));
|
||||
});
|
||||
}
|
||||
|
||||
// Public methods
|
||||
|
||||
static setInvert = value => {
|
||||
const invert = brightness(value) > 0.6; // Light colour is inverted
|
||||
|
||||
if (invert !== this.lastInvert) {
|
||||
this.lastInvert = invert;
|
||||
|
||||
this.ui.invert(invert);
|
||||
}
|
||||
};
|
||||
|
||||
static update = time => {
|
||||
if (!this.ui) {
|
||||
return;
|
||||
}
|
||||
|
||||
Point3D.update(time);
|
||||
|
||||
this.lights.forEach(light => {
|
||||
if (light.helper) {
|
||||
light.helper.update();
|
||||
}
|
||||
});
|
||||
|
||||
this.ui.update();
|
||||
};
|
||||
|
||||
static animateIn = () => {
|
||||
Point3D.enabled = true;
|
||||
};
|
||||
}
|
@ -0,0 +1,163 @@
|
||||
import { Panel, PanelItem, getKeyByValue } from '@alienkitty/space.js/three';
|
||||
|
||||
import { RenderManager } from '../world/RenderManager.js';
|
||||
|
||||
export class PostPanel extends Panel {
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.initPanel();
|
||||
}
|
||||
|
||||
initPanel() {
|
||||
const { luminosityMaterial, bloomCompositeMaterial, compositeMaterial } = RenderManager;
|
||||
|
||||
const postOptions = {
|
||||
Off: false,
|
||||
On: true
|
||||
};
|
||||
|
||||
const toneMappingOptions = {
|
||||
Off: false,
|
||||
Tone: true
|
||||
};
|
||||
|
||||
const gammaOptions = {
|
||||
Off: false,
|
||||
Gamma: true
|
||||
};
|
||||
|
||||
const postItems = [
|
||||
{
|
||||
type: 'divider'
|
||||
},
|
||||
{
|
||||
type: 'slider',
|
||||
label: 'Thresh',
|
||||
min: 0,
|
||||
max: 1,
|
||||
step: 0.01,
|
||||
value: luminosityMaterial.uniforms.uThreshold.value,
|
||||
callback: value => {
|
||||
luminosityMaterial.uniforms.uThreshold.value = value;
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'slider',
|
||||
label: 'Smooth',
|
||||
min: 0,
|
||||
max: 1,
|
||||
step: 0.01,
|
||||
value: luminosityMaterial.uniforms.uSmoothing.value,
|
||||
callback: value => {
|
||||
luminosityMaterial.uniforms.uSmoothing.value = value;
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'slider',
|
||||
label: 'Strength',
|
||||
min: 0,
|
||||
max: 2,
|
||||
step: 0.01,
|
||||
value: RenderManager.bloomStrength,
|
||||
callback: value => {
|
||||
RenderManager.bloomStrength = value;
|
||||
bloomCompositeMaterial.uniforms.uBloomFactors.value = RenderManager.bloomFactors();
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'slider',
|
||||
label: 'Radius',
|
||||
min: 0,
|
||||
max: 1,
|
||||
step: 0.01,
|
||||
value: RenderManager.bloomRadius,
|
||||
callback: value => {
|
||||
RenderManager.bloomRadius = value;
|
||||
bloomCompositeMaterial.uniforms.uBloomFactors.value = RenderManager.bloomFactors();
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'slider',
|
||||
label: 'Chroma',
|
||||
min: 0,
|
||||
max: 2,
|
||||
step: 0.01,
|
||||
value: compositeMaterial.uniforms.uBloomDistortion.value,
|
||||
callback: value => {
|
||||
compositeMaterial.uniforms.uBloomDistortion.value = value;
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'divider'
|
||||
},
|
||||
{
|
||||
type: 'list',
|
||||
label: 'Tone',
|
||||
list: toneMappingOptions,
|
||||
value: getKeyByValue(toneMappingOptions, compositeMaterial.uniforms.uToneMapping.value),
|
||||
callback: value => {
|
||||
compositeMaterial.uniforms.uToneMapping.value = toneMappingOptions[value];
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'slider',
|
||||
label: 'Exp',
|
||||
min: 0,
|
||||
max: 2,
|
||||
step: 0.01,
|
||||
value: compositeMaterial.uniforms.uExposure.value,
|
||||
callback: value => {
|
||||
compositeMaterial.uniforms.uExposure.value = value;
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'divider'
|
||||
},
|
||||
{
|
||||
type: 'list',
|
||||
label: 'Gamma',
|
||||
list: gammaOptions,
|
||||
value: getKeyByValue(gammaOptions, compositeMaterial.uniforms.uGamma.value),
|
||||
callback: value => {
|
||||
compositeMaterial.uniforms.uGamma.value = gammaOptions[value];
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
const items = [
|
||||
{
|
||||
type: 'divider'
|
||||
},
|
||||
{
|
||||
type: 'list',
|
||||
list: postOptions,
|
||||
value: getKeyByValue(postOptions, RenderManager.enabled),
|
||||
callback: (value, panel) => {
|
||||
if (!panel.group) {
|
||||
const postPanel = new Panel();
|
||||
postPanel.animateIn(true);
|
||||
|
||||
postItems.forEach(data => {
|
||||
postPanel.add(new PanelItem(data));
|
||||
});
|
||||
|
||||
panel.setContent(postPanel);
|
||||
}
|
||||
|
||||
RenderManager.enabled = postOptions[value];
|
||||
|
||||
if (RenderManager.enabled) {
|
||||
panel.group.show();
|
||||
} else {
|
||||
panel.group.hide();
|
||||
}
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
items.forEach(data => {
|
||||
this.add(new PanelItem(data));
|
||||
});
|
||||
}
|
||||
}
|
@ -0,0 +1,49 @@
|
||||
import { Vector3 } from 'three';
|
||||
|
||||
import { MaterialPanelController, Point3D } from '@alienkitty/space.js/three';
|
||||
|
||||
import { CameraController } from '../world/CameraController.js';
|
||||
|
||||
export class ScenePanelController {
|
||||
static init(view) {
|
||||
this.view = view;
|
||||
|
||||
this.initPanel();
|
||||
|
||||
this.addListeners();
|
||||
}
|
||||
|
||||
static initPanel() {
|
||||
const { darkPlanet, floatingCrystal, abstractCube } = this.view;
|
||||
|
||||
const objects = [darkPlanet, floatingCrystal, abstractCube];
|
||||
|
||||
objects.forEach(object => {
|
||||
const { geometry, material } = object.mesh;
|
||||
|
||||
object.point = new Point3D(object.mesh, {
|
||||
name: material.name,
|
||||
type: geometry.type
|
||||
});
|
||||
object.add(object.point);
|
||||
|
||||
MaterialPanelController.init(object.mesh, object.point);
|
||||
});
|
||||
|
||||
// Shrink tracker meshes a little bit
|
||||
floatingCrystal.point.mesh.scale.multiply(new Vector3(1, 0.9, 1));
|
||||
abstractCube.point.mesh.scale.multiplyScalar(0.9);
|
||||
}
|
||||
|
||||
static addListeners() {
|
||||
Point3D.events.on('click', this.onClick);
|
||||
}
|
||||
|
||||
// Event handlers
|
||||
|
||||
static onClick = () => {
|
||||
if (CameraController.isAnimatingOut) {
|
||||
CameraController.isAnimatingOut = false;
|
||||
}
|
||||
};
|
||||
}
|
@ -0,0 +1,67 @@
|
||||
import { Point3D, clearTween, delayedCall } from '@alienkitty/space.js/three';
|
||||
|
||||
export class CameraController {
|
||||
static init(camera, controls) {
|
||||
this.camera = camera;
|
||||
this.controls = controls;
|
||||
|
||||
this.isDown = false;
|
||||
this.isTransforming = false;
|
||||
this.isAnimatingOut = false;
|
||||
|
||||
this.addListeners();
|
||||
}
|
||||
|
||||
static addListeners() {
|
||||
this.controls.addEventListener('change', this.onChange);
|
||||
this.controls.addEventListener('start', this.onInteraction);
|
||||
this.controls.addEventListener('end', this.onInteraction);
|
||||
}
|
||||
|
||||
// Event handlers
|
||||
|
||||
static onChange = () => {
|
||||
if (this.isDown) {
|
||||
if (this.isTransforming) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.isTransforming = true;
|
||||
Point3D.enabled = false;
|
||||
|
||||
clearTween(this.timeout);
|
||||
|
||||
this.timeout = delayedCall(300, () => {
|
||||
if (!this.isAnimatingOut) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.isAnimatingOut = false;
|
||||
Point3D.animateOut();
|
||||
});
|
||||
|
||||
this.isAnimatingOut = true;
|
||||
}
|
||||
};
|
||||
|
||||
static onInteraction = ({ type }) => {
|
||||
if (type === 'start') {
|
||||
this.isDown = true;
|
||||
} else {
|
||||
this.isDown = false;
|
||||
this.isTransforming = false;
|
||||
Point3D.enabled = true;
|
||||
}
|
||||
};
|
||||
|
||||
// Public methods
|
||||
|
||||
static resize = (width, height) => {
|
||||
this.camera.aspect = width / height;
|
||||
this.camera.updateProjectionMatrix();
|
||||
};
|
||||
|
||||
static update = () => {
|
||||
this.controls.update();
|
||||
};
|
||||
}
|
@ -0,0 +1,246 @@
|
||||
import { Mesh, MeshBasicMaterial, Raycaster, Vector2 } from 'three';
|
||||
|
||||
import { Stage } from '@alienkitty/space.js/three';
|
||||
import { RigidBodyConfig, RigidBodyType, SphericalJointConfig } from '@alienkitty/alien.js/three/oimophysics';
|
||||
|
||||
import { Config } from '../../config/Config.js';
|
||||
import { Layer } from '../../config/Layer.js';
|
||||
import { WorldController } from './WorldController.js';
|
||||
import { CameraController } from './CameraController.js';
|
||||
import { PhysicsController } from './PhysicsController.js';
|
||||
|
||||
export class InputManager {
|
||||
static init(scene, camera, controls) {
|
||||
this.scene = scene;
|
||||
this.camera = camera;
|
||||
this.controls = controls;
|
||||
|
||||
this.raycaster = new Raycaster();
|
||||
this.raycaster.layers.enable(Layer.PICKING);
|
||||
|
||||
this.objects = [];
|
||||
this.mouse = new Vector2(-1, -1);
|
||||
this.delta = new Vector2();
|
||||
this.coords = new Vector2();
|
||||
this.hover = null;
|
||||
this.selected = null;
|
||||
this.click = null;
|
||||
this.lastTime = null;
|
||||
this.lastMouse = new Vector2();
|
||||
this.raycastInterval = 1 / 10; // 10 frames per second
|
||||
this.lastRaycast = 0;
|
||||
this.body = null;
|
||||
this.joint = null;
|
||||
this.enabled = true;
|
||||
|
||||
this.initMesh();
|
||||
|
||||
this.addListeners();
|
||||
}
|
||||
|
||||
static initMesh() {
|
||||
const { quad } = WorldController;
|
||||
|
||||
let material;
|
||||
|
||||
if (Config.DEBUG) {
|
||||
material = new MeshBasicMaterial({
|
||||
color: 0xff0000,
|
||||
wireframe: true
|
||||
});
|
||||
} else {
|
||||
material = new MeshBasicMaterial({ visible: false });
|
||||
}
|
||||
|
||||
this.dragPlane = new Mesh(quad, material);
|
||||
this.dragPlane.scale.multiplyScalar(200);
|
||||
this.dragPlane.layers.enable(Layer.PICKING);
|
||||
}
|
||||
|
||||
static addListeners() {
|
||||
window.addEventListener('pointerdown', this.onPointerDown);
|
||||
window.addEventListener('pointermove', this.onPointerMove);
|
||||
window.addEventListener('pointerup', this.onPointerUp);
|
||||
}
|
||||
|
||||
static removeListeners() {
|
||||
window.removeEventListener('pointerdown', this.onPointerDown);
|
||||
window.removeEventListener('pointermove', this.onPointerMove);
|
||||
window.removeEventListener('pointerup', this.onPointerUp);
|
||||
}
|
||||
|
||||
// Event handlers
|
||||
|
||||
static onPointerDown = e => {
|
||||
if (!this.enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.lastTime = performance.now();
|
||||
this.lastMouse.set(e.clientX, e.clientY);
|
||||
|
||||
this.onPointerMove(e);
|
||||
|
||||
if (this.hover) {
|
||||
this.click = this.hover;
|
||||
}
|
||||
};
|
||||
|
||||
static onPointerMove = e => {
|
||||
if (!this.enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (e) {
|
||||
this.mouse.x = e.clientX;
|
||||
this.mouse.y = e.clientY;
|
||||
this.coords.x = (this.mouse.x / document.documentElement.clientWidth) * 2 - 1;
|
||||
this.coords.y = 1 - (this.mouse.y / document.documentElement.clientHeight) * 2;
|
||||
}
|
||||
|
||||
if (this.selected) {
|
||||
this.raycaster.setFromCamera(this.coords, this.camera);
|
||||
|
||||
const intersection = this.raycaster.intersectObject(this.dragPlane);
|
||||
|
||||
if (intersection.length) {
|
||||
const point = intersection[0].point;
|
||||
|
||||
PhysicsController.physics.setPosition(this.body, point);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (document.elementFromPoint(this.mouse.x, this.mouse.y) instanceof HTMLCanvasElement) {
|
||||
this.raycaster.setFromCamera(this.coords, this.camera);
|
||||
|
||||
const intersection = this.raycaster.intersectObjects(this.objects);
|
||||
|
||||
if (intersection.length) {
|
||||
let object = intersection[0].object;
|
||||
|
||||
if (object.parent.isGroup) {
|
||||
object = object.parent;
|
||||
}
|
||||
|
||||
if (
|
||||
PhysicsController.enabled &&
|
||||
CameraController.isDown &&
|
||||
!CameraController.isTransforming &&
|
||||
this.selected !== object
|
||||
) {
|
||||
const point = intersection[0].point;
|
||||
|
||||
const body = new RigidBodyConfig();
|
||||
body.type = RigidBodyType.STATIC;
|
||||
body.position.copyFrom(point);
|
||||
PhysicsController.physics.add(body);
|
||||
|
||||
const joint = new SphericalJointConfig();
|
||||
joint.rigidBody1 = PhysicsController.physics.get(object);
|
||||
joint.rigidBody2 = PhysicsController.physics.get(body);
|
||||
joint.rigidBody1.getLocalPointTo(point, joint.localAnchor1);
|
||||
joint.rigidBody2.getLocalPointTo(point, joint.localAnchor2);
|
||||
joint.springDamper.setSpring(4, 1); // frequency, dampingRatio
|
||||
PhysicsController.physics.add(joint);
|
||||
|
||||
this.selected = object;
|
||||
this.body = body;
|
||||
this.joint = joint;
|
||||
|
||||
this.dragPlane.position.copy(point);
|
||||
this.dragPlane.quaternion.copy(this.camera.quaternion);
|
||||
|
||||
this.scene.add(this.dragPlane);
|
||||
|
||||
this.controls.enabled = false;
|
||||
|
||||
Stage.css({ cursor: 'move' });
|
||||
} else if (!this.hover) {
|
||||
this.hover = object;
|
||||
this.hover.onHover({ type: 'over' });
|
||||
Stage.css({ cursor: 'pointer' });
|
||||
} else if (this.hover !== object) {
|
||||
this.hover.onHover({ type: 'out' });
|
||||
this.hover = object;
|
||||
this.hover.onHover({ type: 'over' });
|
||||
Stage.css({ cursor: 'pointer' });
|
||||
}
|
||||
} else if (this.hover) {
|
||||
this.hover.onHover({ type: 'out' });
|
||||
this.hover = null;
|
||||
Stage.css({ cursor: '' });
|
||||
}
|
||||
} else if (this.hover) {
|
||||
this.hover.onHover({ type: 'out' });
|
||||
this.hover = null;
|
||||
Stage.css({ cursor: '' });
|
||||
}
|
||||
|
||||
this.delta.subVectors(this.mouse, this.lastMouse);
|
||||
};
|
||||
|
||||
static onPointerUp = e => {
|
||||
if (!this.enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.selected) {
|
||||
this.scene.remove(this.dragPlane);
|
||||
|
||||
PhysicsController.physics.remove(this.joint);
|
||||
PhysicsController.physics.remove(this.body);
|
||||
|
||||
this.selected = null;
|
||||
|
||||
this.controls.enabled = true;
|
||||
}
|
||||
|
||||
this.onPointerMove(e);
|
||||
|
||||
if (performance.now() - this.lastTime > 250 || this.delta.length() > 50) {
|
||||
this.click = null;
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.click && this.click === this.hover) {
|
||||
this.click.onClick();
|
||||
}
|
||||
|
||||
this.click = null;
|
||||
};
|
||||
|
||||
// Public methods
|
||||
|
||||
static update = time => {
|
||||
if (!navigator.maxTouchPoints && time - this.lastRaycast > this.raycastInterval) {
|
||||
this.onPointerMove();
|
||||
this.lastRaycast = time;
|
||||
}
|
||||
};
|
||||
|
||||
static add = (...objects) => {
|
||||
this.objects.push(...objects);
|
||||
};
|
||||
|
||||
static remove = (...objects) => {
|
||||
objects.forEach(object => {
|
||||
const index = this.objects.indexOf(object);
|
||||
|
||||
if (~index) {
|
||||
this.objects.splice(index, 1);
|
||||
}
|
||||
|
||||
if (object.parent.isGroup) {
|
||||
object = object.parent;
|
||||
}
|
||||
|
||||
if (object === this.hover) {
|
||||
this.hover.onHover({ type: 'out' });
|
||||
this.hover = null;
|
||||
Stage.css({ cursor: '' });
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
export class PhysicsController {
|
||||
static init(physics) {
|
||||
this.physics = physics;
|
||||
|
||||
this.enabled = false;
|
||||
}
|
||||
|
||||
// Public methods
|
||||
|
||||
static update = () => {
|
||||
if (!this.enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.physics.step();
|
||||
};
|
||||
}
|
@ -0,0 +1,190 @@
|
||||
import { MathUtils, Mesh, OrthographicCamera, Vector2, WebGLRenderTarget } from 'three';
|
||||
|
||||
import { BloomCompositeMaterial, LuminosityMaterial, UnrealBloomBlurMaterial } from '@alienkitty/alien.js/three';
|
||||
|
||||
import { WorldController } from './WorldController.js';
|
||||
import { CompositeMaterial } from '../../materials/CompositeMaterial.js';
|
||||
|
||||
const BlurDirectionX = new Vector2(1, 0);
|
||||
const BlurDirectionY = new Vector2(0, 1);
|
||||
|
||||
export class RenderManager {
|
||||
static init(renderer, scene, camera, ui) {
|
||||
this.renderer = renderer;
|
||||
this.scene = scene;
|
||||
this.camera = camera;
|
||||
this.ui = ui;
|
||||
|
||||
this.luminosityThreshold = 0.1;
|
||||
this.luminositySmoothing = 1;
|
||||
this.bloomStrength = 0.3;
|
||||
this.bloomRadius = 0.2;
|
||||
this.bloomDistortion = 1.45;
|
||||
this.enabled = true;
|
||||
|
||||
this.initRenderer();
|
||||
}
|
||||
|
||||
static initRenderer() {
|
||||
const { screenTriangle } = WorldController;
|
||||
|
||||
// Fullscreen triangle
|
||||
this.screenCamera = new OrthographicCamera(-1, 1, 1, -1, 0, 1);
|
||||
this.screen = new Mesh(screenTriangle);
|
||||
this.screen.frustumCulled = false;
|
||||
|
||||
// Render targets
|
||||
this.renderTarget = new WebGLRenderTarget(1, 1, {
|
||||
depthBuffer: false
|
||||
});
|
||||
|
||||
this.renderTargetsHorizontal = [];
|
||||
this.renderTargetsVertical = [];
|
||||
this.nMips = 5;
|
||||
|
||||
this.renderTargetBright = this.renderTarget.clone();
|
||||
|
||||
for (let i = 0, l = this.nMips; i < l; i++) {
|
||||
this.renderTargetsHorizontal.push(this.renderTarget.clone());
|
||||
this.renderTargetsVertical.push(this.renderTarget.clone());
|
||||
}
|
||||
|
||||
this.renderTarget.depthBuffer = true;
|
||||
|
||||
// Luminosity high pass material
|
||||
this.luminosityMaterial = new LuminosityMaterial();
|
||||
this.luminosityMaterial.uniforms.uThreshold.value = this.luminosityThreshold;
|
||||
this.luminosityMaterial.uniforms.uSmoothing.value = this.luminositySmoothing;
|
||||
|
||||
// Separable Gaussian blur materials
|
||||
this.blurMaterials = [];
|
||||
|
||||
const kernelSizeArray = [3, 5, 7, 9, 11];
|
||||
|
||||
for (let i = 0, l = this.nMips; i < l; i++) {
|
||||
this.blurMaterials.push(new UnrealBloomBlurMaterial(kernelSizeArray[i]));
|
||||
}
|
||||
|
||||
// Bloom composite material
|
||||
this.bloomCompositeMaterial = new BloomCompositeMaterial();
|
||||
this.bloomCompositeMaterial.uniforms.tBlur1.value = this.renderTargetsVertical[0].texture;
|
||||
this.bloomCompositeMaterial.uniforms.tBlur2.value = this.renderTargetsVertical[1].texture;
|
||||
this.bloomCompositeMaterial.uniforms.tBlur3.value = this.renderTargetsVertical[2].texture;
|
||||
this.bloomCompositeMaterial.uniforms.tBlur4.value = this.renderTargetsVertical[3].texture;
|
||||
this.bloomCompositeMaterial.uniforms.tBlur5.value = this.renderTargetsVertical[4].texture;
|
||||
this.bloomCompositeMaterial.uniforms.uBloomFactors.value = this.bloomFactors();
|
||||
|
||||
// Composite material
|
||||
this.compositeMaterial = new CompositeMaterial();
|
||||
this.compositeMaterial.uniforms.uBloomDistortion.value = this.bloomDistortion;
|
||||
}
|
||||
|
||||
static bloomFactors() {
|
||||
const bloomFactors = [1, 0.8, 0.6, 0.4, 0.2];
|
||||
|
||||
for (let i = 0, l = this.nMips; i < l; i++) {
|
||||
const factor = bloomFactors[i];
|
||||
bloomFactors[i] = this.bloomStrength * MathUtils.lerp(factor, 1.2 - factor, this.bloomRadius);
|
||||
}
|
||||
|
||||
return bloomFactors;
|
||||
}
|
||||
|
||||
// Public methods
|
||||
|
||||
static invert = isInverted => {
|
||||
if (isInverted) { // Light colour is inverted
|
||||
this.luminosityMaterial.uniforms.uThreshold.value = 0.75;
|
||||
this.compositeMaterial.uniforms.uGamma.value = true;
|
||||
} else {
|
||||
this.luminosityMaterial.uniforms.uThreshold.value = this.luminosityThreshold;
|
||||
this.compositeMaterial.uniforms.uGamma.value = false;
|
||||
}
|
||||
|
||||
this.ui.setPanelValue('Thresh', this.luminosityMaterial.uniforms.uThreshold.value);
|
||||
this.ui.setPanelValue('Gamma', this.compositeMaterial.uniforms.uGamma.value);
|
||||
};
|
||||
|
||||
static resize = (width, height, dpr) => {
|
||||
this.renderer.setPixelRatio(dpr);
|
||||
this.renderer.setSize(width, height);
|
||||
|
||||
width = Math.round(width * dpr);
|
||||
height = Math.round(height * dpr);
|
||||
|
||||
this.renderTarget.setSize(width, height);
|
||||
|
||||
width = MathUtils.floorPowerOfTwo(width) / 2;
|
||||
height = MathUtils.floorPowerOfTwo(height) / 2;
|
||||
|
||||
this.renderTargetBright.setSize(width, height);
|
||||
|
||||
for (let i = 0, l = this.nMips; i < l; i++) {
|
||||
this.renderTargetsHorizontal[i].setSize(width, height);
|
||||
this.renderTargetsVertical[i].setSize(width, height);
|
||||
|
||||
this.blurMaterials[i].uniforms.uResolution.value.set(width, height);
|
||||
|
||||
width /= 2;
|
||||
height /= 2;
|
||||
}
|
||||
};
|
||||
|
||||
static update = () => {
|
||||
const renderer = this.renderer;
|
||||
const scene = this.scene;
|
||||
const camera = this.camera;
|
||||
|
||||
if (!this.enabled) {
|
||||
renderer.setRenderTarget(null);
|
||||
renderer.render(scene, camera);
|
||||
return;
|
||||
}
|
||||
|
||||
const renderTarget = this.renderTarget;
|
||||
const renderTargetBright = this.renderTargetBright;
|
||||
const renderTargetsHorizontal = this.renderTargetsHorizontal;
|
||||
const renderTargetsVertical = this.renderTargetsVertical;
|
||||
|
||||
// Scene pass
|
||||
renderer.setRenderTarget(renderTarget);
|
||||
renderer.render(scene, camera);
|
||||
|
||||
// Extract bright areas
|
||||
this.luminosityMaterial.uniforms.tMap.value = renderTarget.texture;
|
||||
this.screen.material = this.luminosityMaterial;
|
||||
renderer.setRenderTarget(renderTargetBright);
|
||||
renderer.render(this.screen, this.screenCamera);
|
||||
|
||||
// Blur all the mips progressively
|
||||
let inputRenderTarget = renderTargetBright;
|
||||
|
||||
for (let i = 0, l = this.nMips; i < l; i++) {
|
||||
this.screen.material = this.blurMaterials[i];
|
||||
|
||||
this.blurMaterials[i].uniforms.tMap.value = inputRenderTarget.texture;
|
||||
this.blurMaterials[i].uniforms.uDirection.value = BlurDirectionX;
|
||||
renderer.setRenderTarget(renderTargetsHorizontal[i]);
|
||||
renderer.render(this.screen, this.screenCamera);
|
||||
|
||||
this.blurMaterials[i].uniforms.tMap.value = this.renderTargetsHorizontal[i].texture;
|
||||
this.blurMaterials[i].uniforms.uDirection.value = BlurDirectionY;
|
||||
renderer.setRenderTarget(renderTargetsVertical[i]);
|
||||
renderer.render(this.screen, this.screenCamera);
|
||||
|
||||
inputRenderTarget = renderTargetsVertical[i];
|
||||
}
|
||||
|
||||
// Composite all the mips
|
||||
this.screen.material = this.bloomCompositeMaterial;
|
||||
renderer.setRenderTarget(renderTargetsHorizontal[0]);
|
||||
renderer.render(this.screen, this.screenCamera);
|
||||
|
||||
// Composite pass (render to screen)
|
||||
this.compositeMaterial.uniforms.tScene.value = renderTarget.texture;
|
||||
this.compositeMaterial.uniforms.tBloom.value = renderTargetsHorizontal[0].texture;
|
||||
this.screen.material = this.compositeMaterial;
|
||||
renderer.setRenderTarget(null);
|
||||
renderer.render(this.screen, this.screenCamera);
|
||||
};
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
import { wait } from '@alienkitty/space.js/three';
|
||||
|
||||
import { RenderManager } from './RenderManager.js';
|
||||
|
||||
export class SceneController {
|
||||
static init(view) {
|
||||
this.view = view;
|
||||
}
|
||||
|
||||
// Public methods
|
||||
|
||||
static update = time => {
|
||||
if (!this.view.visible) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.view.update(time);
|
||||
};
|
||||
|
||||
static animateIn = () => {
|
||||
this.view.animateIn();
|
||||
|
||||
this.view.visible = true;
|
||||
};
|
||||
|
||||
static ready = async () => {
|
||||
await this.view.ready();
|
||||
|
||||
// Centre objects for prerender
|
||||
const currentPositions = this.view.children.map(object => object.position.clone());
|
||||
|
||||
this.view.children.forEach(object => object.position.set(0, 0, 0));
|
||||
this.view.visible = true;
|
||||
|
||||
RenderManager.update();
|
||||
|
||||
await wait(500);
|
||||
|
||||
// Restore positions
|
||||
this.view.visible = false;
|
||||
this.view.children.forEach((object, i) => object.position.copy(currentPositions[i]));
|
||||
};
|
||||
}
|
@ -0,0 +1,141 @@
|
||||
import { /* BasicShadowMap, */Color, ColorManagement, DirectionalLight, HemisphereLight, LinearSRGBColorSpace, PerspectiveCamera, PlaneGeometry, Scene, Vector2, WebGLRenderer } from 'three';
|
||||
|
||||
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
|
||||
|
||||
ColorManagement.enabled = false; // Disable color management
|
||||
|
||||
import { BufferGeometryLoader, EnvironmentTextureLoader, Interface, Stage, TextureLoader, getFrustum, getFullscreenTriangle } from '@alienkitty/space.js/three';
|
||||
import { OimoPhysics } from '@alienkitty/alien.js/three/oimophysics';
|
||||
|
||||
export class WorldController {
|
||||
static init() {
|
||||
this.initWorld();
|
||||
this.initLights();
|
||||
this.initLoaders();
|
||||
this.initEnvironment();
|
||||
this.initControls();
|
||||
this.initPhysics();
|
||||
|
||||
this.addListeners();
|
||||
}
|
||||
|
||||
static initWorld() {
|
||||
this.renderer = new WebGLRenderer({
|
||||
powerPreference: 'high-performance',
|
||||
stencil: false,
|
||||
antialias: true,
|
||||
// alpha: true
|
||||
});
|
||||
this.renderer.outputColorSpace = LinearSRGBColorSpace;
|
||||
|
||||
// this.element = this.renderer.domElement;
|
||||
this.element = new Interface(this.renderer.domElement);
|
||||
this.element.css({ opacity: 0 });
|
||||
|
||||
// Shadows
|
||||
// this.renderer.shadowMap.enabled = true;
|
||||
// this.renderer.shadowMap.type = BasicShadowMap;
|
||||
|
||||
// 3D scene
|
||||
this.scene = new Scene();
|
||||
this.scene.background = new Color(Stage.rootStyle.getPropertyValue('--bg-color').trim());
|
||||
this.camera = new PerspectiveCamera(30);
|
||||
this.camera.near = 0.5;
|
||||
this.camera.far = 40;
|
||||
this.camera.position.set(0, 6, 8);
|
||||
this.camera.lookAt(this.scene.position);
|
||||
|
||||
// Global geometries
|
||||
this.quad = new PlaneGeometry(1, 1);
|
||||
this.screenTriangle = getFullscreenTriangle();
|
||||
|
||||
// Global uniforms
|
||||
this.resolution = { value: new Vector2() };
|
||||
this.texelSize = { value: new Vector2() };
|
||||
this.aspect = { value: 1 };
|
||||
this.time = { value: 0 };
|
||||
this.frame = { value: 0 };
|
||||
|
||||
// Global settings
|
||||
this.anisotropy = this.renderer.capabilities.getMaxAnisotropy();
|
||||
}
|
||||
|
||||
static initLights() {
|
||||
this.scene.add(new HemisphereLight(0xffffff, 0x888888, 3));
|
||||
|
||||
const light = new DirectionalLight(0xffffff, 2);
|
||||
light.position.set(5, 5, 5);
|
||||
// light.castShadow = true;
|
||||
// light.shadow.mapSize.width = 2048;
|
||||
// light.shadow.mapSize.height = 2048;
|
||||
this.scene.add(light);
|
||||
}
|
||||
|
||||
static initLoaders() {
|
||||
this.textureLoader = new TextureLoader();
|
||||
/* this.textureLoader.setOptions({
|
||||
preserveData: true
|
||||
});
|
||||
this.textureLoader.cache = true; */
|
||||
|
||||
this.environmentLoader = new EnvironmentTextureLoader(this.renderer);
|
||||
this.bufferGeometryLoader = new BufferGeometryLoader();
|
||||
}
|
||||
|
||||
static async initEnvironment() {
|
||||
this.scene.environment = await this.loadEnvironmentTexture('assets/textures/env/jewelry_black_contrast.jpg');
|
||||
}
|
||||
|
||||
static initControls() {
|
||||
this.controls = new OrbitControls(this.camera, this.renderer.domElement);
|
||||
this.controls.enableDamping = true;
|
||||
}
|
||||
|
||||
static initPhysics() {
|
||||
this.physics = new OimoPhysics();
|
||||
}
|
||||
|
||||
static addListeners() {
|
||||
this.renderer.domElement.addEventListener('touchstart', this.onTouchStart);
|
||||
}
|
||||
|
||||
// Event handlers
|
||||
|
||||
static onTouchStart = e => {
|
||||
e.preventDefault();
|
||||
};
|
||||
|
||||
// Public methods
|
||||
|
||||
static resize = (width, height, dpr) => {
|
||||
width = Math.round(width * dpr);
|
||||
height = Math.round(height * dpr);
|
||||
|
||||
this.resolution.value.set(width, height);
|
||||
this.texelSize.value.set(1 / width, 1 / height);
|
||||
this.aspect.value = width / height;
|
||||
};
|
||||
|
||||
static update = (time, delta, frame) => {
|
||||
this.time.value = time;
|
||||
this.frame.value = frame;
|
||||
};
|
||||
|
||||
static animateIn = () => {
|
||||
this.element.tween({ opacity: 1 }, 1000, 'linear', () => {
|
||||
this.element.css({ opacity: '' });
|
||||
});
|
||||
};
|
||||
|
||||
static getTexture = (path, callback) => this.textureLoader.load(path, callback);
|
||||
|
||||
static loadTexture = path => this.textureLoader.loadAsync(path);
|
||||
|
||||
static loadEnvironmentTexture = path => this.environmentLoader.loadAsync(path);
|
||||
|
||||
static getBufferGeometry = (path, callback) => this.bufferGeometryLoader.load(path, callback);
|
||||
|
||||
static loadBufferGeometry = path => this.bufferGeometryLoader.loadAsync(path);
|
||||
|
||||
static getFrustum = offsetZ => getFrustum(this.camera, offsetZ);
|
||||
}
|
1
src/plugins/floor/lib/@alienkitty/space.js/examples/about/src/main.js
Executable file
1
src/plugins/floor/lib/@alienkitty/space.js/examples/about/src/main.js
Executable file
@ -0,0 +1 @@
|
||||
export { Preloader } from './controllers/Preloader.js';
|
@ -0,0 +1,24 @@
|
||||
import { GLSL3, NoBlending, RawShaderMaterial } from 'three';
|
||||
|
||||
import { vertexShader, fragmentShader } from '../shaders/CompositeShader.js';
|
||||
|
||||
export class CompositeMaterial extends RawShaderMaterial {
|
||||
constructor() {
|
||||
super({
|
||||
glslVersion: GLSL3,
|
||||
uniforms: {
|
||||
tScene: { value: null },
|
||||
tBloom: { value: null },
|
||||
uBloomDistortion: { value: 1.45 },
|
||||
uToneMapping: { value: false },
|
||||
uExposure: { value: 1 },
|
||||
uGamma: { value: false }
|
||||
},
|
||||
vertexShader,
|
||||
fragmentShader,
|
||||
blending: NoBlending,
|
||||
depthTest: false,
|
||||
depthWrite: false
|
||||
});
|
||||
}
|
||||
}
|
@ -0,0 +1,55 @@
|
||||
// Based on https://github.com/mrdoob/three.js/blob/dev/examples/jsm/shaders/ACESFilmicToneMappingShader.js by WestLangley
|
||||
// Based on https://github.com/mrdoob/three.js/blob/dev/examples/jsm/shaders/GammaCorrectionShader.js by WestLangley
|
||||
|
||||
import rgbshift from '@alienkitty/alien.js/src/shaders/modules/rgbshift/rgbshift.glsl.js';
|
||||
import encodings from '@alienkitty/alien.js/src/shaders/modules/encodings/encodings.glsl.js';
|
||||
|
||||
export const vertexShader = /* glsl */ `
|
||||
in vec3 position;
|
||||
in vec2 uv;
|
||||
|
||||
out vec2 vUv;
|
||||
|
||||
void main() {
|
||||
vUv = uv;
|
||||
|
||||
gl_Position = vec4(position, 1.0);
|
||||
}
|
||||
`;
|
||||
|
||||
export const fragmentShader = /* glsl */ `
|
||||
precision highp float;
|
||||
|
||||
uniform sampler2D tScene;
|
||||
uniform sampler2D tBloom;
|
||||
uniform float uBloomDistortion;
|
||||
uniform bool uToneMapping;
|
||||
uniform float uExposure;
|
||||
uniform bool uGamma;
|
||||
|
||||
in vec2 vUv;
|
||||
|
||||
out vec4 FragColor;
|
||||
|
||||
${rgbshift}
|
||||
${encodings}
|
||||
|
||||
void main() {
|
||||
FragColor = texture(tScene, vUv);
|
||||
|
||||
float angle = length(vUv - 0.5);
|
||||
float amount = 0.001 * uBloomDistortion;
|
||||
|
||||
FragColor.rgb += getRGB(tBloom, vUv, angle, amount).rgb;
|
||||
|
||||
if (uToneMapping) {
|
||||
FragColor.rgb *= uExposure;
|
||||
|
||||
FragColor = vec4(ACESFilmicToneMapping(FragColor.rgb), FragColor.a);
|
||||
}
|
||||
|
||||
if (uGamma) {
|
||||
FragColor = LinearToSRGB(FragColor);
|
||||
}
|
||||
}
|
||||
`;
|
@ -0,0 +1,74 @@
|
||||
import { Interface } from '@alienkitty/space.js/three';
|
||||
|
||||
import { ProgressCanvas } from './ui/ProgressCanvas.js';
|
||||
|
||||
export class PreloaderView extends Interface {
|
||||
constructor() {
|
||||
super('.preloader');
|
||||
|
||||
this.initHTML();
|
||||
this.initView();
|
||||
|
||||
this.addListeners();
|
||||
}
|
||||
|
||||
initHTML() {
|
||||
this.css({
|
||||
position: 'absolute',
|
||||
left: 0,
|
||||
top: 0,
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
backgroundColor: 'var(--bg-color)',
|
||||
zIndex: 100,
|
||||
pointerEvents: 'none'
|
||||
});
|
||||
}
|
||||
|
||||
initView() {
|
||||
this.view = new ProgressCanvas();
|
||||
this.view.css({
|
||||
position: 'absolute',
|
||||
left: '50%',
|
||||
top: '50%',
|
||||
marginLeft: -this.view.width / 2,
|
||||
marginTop: -this.view.height / 2
|
||||
});
|
||||
this.add(this.view);
|
||||
}
|
||||
|
||||
addListeners() {
|
||||
this.view.events.on('complete', this.onComplete);
|
||||
}
|
||||
|
||||
removeListeners() {
|
||||
this.view.events.off('complete', this.onComplete);
|
||||
}
|
||||
|
||||
// Event handlers
|
||||
|
||||
onProgress = e => {
|
||||
this.view.onProgress(e);
|
||||
};
|
||||
|
||||
onComplete = () => {
|
||||
this.events.emit('complete');
|
||||
};
|
||||
|
||||
// Public methods
|
||||
|
||||
animateIn = () => {
|
||||
this.view.animateIn();
|
||||
};
|
||||
|
||||
animateOut = () => {
|
||||
this.view.animateOut();
|
||||
return this.tween({ opacity: 0 }, 250, 'easeOutSine', 500);
|
||||
};
|
||||
|
||||
destroy = () => {
|
||||
this.removeListeners();
|
||||
|
||||
return super.destroy();
|
||||
};
|
||||
}
|
@ -0,0 +1,61 @@
|
||||
import { Group } from 'three';
|
||||
|
||||
import { InputManager } from '../controllers/world/InputManager.js';
|
||||
import { Floor } from './scene/Floor.js';
|
||||
import { DarkPlanet } from './scene/DarkPlanet.js';
|
||||
import { FloatingCrystal } from './scene/FloatingCrystal.js';
|
||||
import { AbstractCube } from './scene/AbstractCube.js';
|
||||
|
||||
export class SceneView extends Group {
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.visible = false;
|
||||
|
||||
this.initViews();
|
||||
}
|
||||
|
||||
initViews() {
|
||||
this.floor = new Floor();
|
||||
this.add(this.floor);
|
||||
|
||||
this.darkPlanet = new DarkPlanet();
|
||||
this.add(this.darkPlanet);
|
||||
|
||||
this.floatingCrystal = new FloatingCrystal();
|
||||
this.add(this.floatingCrystal);
|
||||
|
||||
this.abstractCube = new AbstractCube();
|
||||
this.add(this.abstractCube);
|
||||
}
|
||||
|
||||
addListeners() {
|
||||
InputManager.add(this.darkPlanet, this.floatingCrystal, this.abstractCube);
|
||||
}
|
||||
|
||||
removeListeners() {
|
||||
InputManager.remove(this.darkPlanet, this.floatingCrystal, this.abstractCube);
|
||||
}
|
||||
|
||||
// Public methods
|
||||
|
||||
invert = isInverted => {
|
||||
this.floor.invert(isInverted);
|
||||
};
|
||||
|
||||
update = time => {
|
||||
this.darkPlanet.update(time);
|
||||
this.floatingCrystal.update(time);
|
||||
this.abstractCube.update(time);
|
||||
};
|
||||
|
||||
animateIn = () => {
|
||||
this.addListeners();
|
||||
};
|
||||
|
||||
ready = () => Promise.all([
|
||||
this.darkPlanet.initMesh(),
|
||||
this.floatingCrystal.initMesh(),
|
||||
this.abstractCube.initMesh()
|
||||
]);
|
||||
}
|
71
src/plugins/floor/lib/@alienkitty/space.js/examples/about/src/views/UI.js
Executable file
71
src/plugins/floor/lib/@alienkitty/space.js/examples/about/src/views/UI.js
Executable file
@ -0,0 +1,71 @@
|
||||
import { Interface, Stage } from '@alienkitty/space.js/three';
|
||||
|
||||
import { Header } from './ui/Header.js';
|
||||
|
||||
export class UI extends Interface {
|
||||
constructor() {
|
||||
super('.ui');
|
||||
|
||||
this.invertColors = {
|
||||
light: Stage.rootStyle.getPropertyValue('--ui-invert-light-color').trim(),
|
||||
lightTriplet: Stage.rootStyle.getPropertyValue('--ui-invert-light-color-triplet').trim(),
|
||||
lightLine: Stage.rootStyle.getPropertyValue('--ui-invert-light-color-line').trim(),
|
||||
dark: Stage.rootStyle.getPropertyValue('--ui-invert-dark-color').trim(),
|
||||
darkTriplet: Stage.rootStyle.getPropertyValue('--ui-invert-dark-color-triplet').trim(),
|
||||
darkLine: Stage.rootStyle.getPropertyValue('--ui-invert-dark-color-line').trim()
|
||||
};
|
||||
|
||||
this.initHTML();
|
||||
this.initViews();
|
||||
}
|
||||
|
||||
initHTML() {
|
||||
this.css({
|
||||
position: 'fixed',
|
||||
left: 0,
|
||||
top: 0,
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
pointerEvents: 'none'
|
||||
});
|
||||
}
|
||||
|
||||
initViews() {
|
||||
this.header = new Header();
|
||||
this.add(this.header);
|
||||
}
|
||||
|
||||
// Public methods
|
||||
|
||||
addPanel = item => {
|
||||
this.header.info.panel.add(item);
|
||||
};
|
||||
|
||||
setPanelValue = (label, value) => {
|
||||
this.header.info.panel.setPanelValue(label, value);
|
||||
};
|
||||
|
||||
setPanelIndex = (label, index) => {
|
||||
this.header.info.panel.setPanelIndex(label, index);
|
||||
};
|
||||
|
||||
invert = isInverted => {
|
||||
Stage.root.style.setProperty('--ui-color', isInverted ? this.invertColors.light : this.invertColors.dark);
|
||||
Stage.root.style.setProperty('--ui-color-triplet', isInverted ? this.invertColors.lightTriplet : this.invertColors.darkTriplet);
|
||||
Stage.root.style.setProperty('--ui-color-line', isInverted ? this.invertColors.lightLine : this.invertColors.darkLine);
|
||||
|
||||
Stage.events.emit('invert', { invert: isInverted });
|
||||
};
|
||||
|
||||
update = () => {
|
||||
this.header.info.update();
|
||||
};
|
||||
|
||||
animateIn = () => {
|
||||
this.header.animateIn();
|
||||
};
|
||||
|
||||
animateOut = () => {
|
||||
this.header.animateOut();
|
||||
};
|
||||
}
|
@ -0,0 +1,66 @@
|
||||
import { BoxGeometry, Color, Group, MathUtils, Mesh, MeshStandardMaterial, Vector3 } from 'three';
|
||||
|
||||
import { WorldController } from '../../controllers/world/WorldController.js';
|
||||
import { PhysicsController } from '../../controllers/world/PhysicsController.js';
|
||||
|
||||
export class AbstractCube extends Group {
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.position.x = 2.5;
|
||||
this.rotation.x = MathUtils.degToRad(-45);
|
||||
this.rotation.z = MathUtils.degToRad(-45);
|
||||
|
||||
this.force = new Vector3();
|
||||
this.contact = false;
|
||||
}
|
||||
|
||||
async initMesh() {
|
||||
const { physics } = WorldController;
|
||||
|
||||
const geometry = new BoxGeometry();
|
||||
geometry.computeTangents();
|
||||
|
||||
const material = new MeshStandardMaterial({
|
||||
name: 'Abstract Cube',
|
||||
color: new Color().offsetHSL(0, 0, -0.65),
|
||||
metalness: 0.7,
|
||||
roughness: 0.7,
|
||||
envMapIntensity: 1.2,
|
||||
flatShading: true
|
||||
});
|
||||
|
||||
const mesh = new Mesh(geometry, material);
|
||||
// mesh.castShadow = true;
|
||||
// mesh.receiveShadow = true;
|
||||
this.add(mesh);
|
||||
|
||||
physics.add(mesh, { density: 2, autoSleep: false });
|
||||
|
||||
this.mesh = mesh;
|
||||
}
|
||||
|
||||
// Event handlers
|
||||
|
||||
onHover = ({ type }) => {
|
||||
console.log('AbstractCube', type);
|
||||
// if (type === 'over') {
|
||||
// } else {
|
||||
// }
|
||||
};
|
||||
|
||||
onClick = () => {
|
||||
console.log('AbstractCube', 'click');
|
||||
// open('https://alien.js.org/');
|
||||
};
|
||||
|
||||
// Public methods
|
||||
|
||||
update = () => {
|
||||
if (PhysicsController.enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.rotation.y -= 0.005;
|
||||
};
|
||||
}
|
@ -0,0 +1,70 @@
|
||||
import { Color, Group, MathUtils, Mesh, MeshStandardMaterial } from 'three';
|
||||
|
||||
import { getSphericalCube } from '@alienkitty/space.js/three';
|
||||
|
||||
import { WorldController } from '../../controllers/world/WorldController.js';
|
||||
import { PhysicsController } from '../../controllers/world/PhysicsController.js';
|
||||
|
||||
export class DarkPlanet extends Group {
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.position.x = -2.5;
|
||||
|
||||
// 25 degree tilt like Mars
|
||||
this.rotation.z = MathUtils.degToRad(25);
|
||||
}
|
||||
|
||||
async initMesh() {
|
||||
const { physics } = WorldController;
|
||||
|
||||
const geometry = getSphericalCube(0.6, 20);
|
||||
geometry.computeTangents();
|
||||
|
||||
// For sphere geometry physics
|
||||
geometry.type = 'SphereGeometry';
|
||||
geometry.parameters.radius = geometry.parameters.width;
|
||||
|
||||
const material = new MeshStandardMaterial({
|
||||
name: 'Dark Planet',
|
||||
color: new Color().offsetHSL(0, 0, -0.65),
|
||||
metalness: 0.7,
|
||||
roughness: 1,
|
||||
envMapIntensity: 1.2
|
||||
});
|
||||
|
||||
const mesh = new Mesh(geometry, material);
|
||||
// mesh.castShadow = true;
|
||||
// mesh.receiveShadow = true;
|
||||
this.add(mesh);
|
||||
|
||||
physics.add(mesh, { density: 2, autoSleep: false });
|
||||
|
||||
this.mesh = mesh;
|
||||
}
|
||||
|
||||
// Event handlers
|
||||
|
||||
onHover = ({ type }) => {
|
||||
console.log('DarkPlanet', type);
|
||||
// if (type === 'over') {
|
||||
// } else {
|
||||
// }
|
||||
};
|
||||
|
||||
onClick = () => {
|
||||
console.log('DarkPlanet', 'click');
|
||||
// open('https://alien.js.org/');
|
||||
};
|
||||
|
||||
// Public methods
|
||||
|
||||
update = () => {
|
||||
if (PhysicsController.enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Counter clockwise rotation
|
||||
this.mesh.rotation.y += 0.005;
|
||||
};
|
||||
}
|
@ -0,0 +1,71 @@
|
||||
import { Color, Group, Mesh, MeshStandardMaterial, OctahedronGeometry } from 'three';
|
||||
|
||||
import { mergeVertices } from 'three/addons/utils/BufferGeometryUtils.js';
|
||||
|
||||
import { WorldController } from '../../controllers/world/WorldController.js';
|
||||
import { PhysicsController } from '../../controllers/world/PhysicsController.js';
|
||||
|
||||
export class FloatingCrystal extends Group {
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.position.y = 0.7;
|
||||
|
||||
// Resize to rhombus shape
|
||||
this.scale.set(0.5, 1, 0.5);
|
||||
}
|
||||
|
||||
async initMesh() {
|
||||
const { physics } = WorldController;
|
||||
|
||||
let geometry = new OctahedronGeometry();
|
||||
|
||||
// Convert to indexed geometry
|
||||
geometry = mergeVertices(geometry);
|
||||
|
||||
geometry.computeTangents();
|
||||
|
||||
const material = new MeshStandardMaterial({
|
||||
name: 'Floating Crystal',
|
||||
color: new Color().offsetHSL(0, 0, -0.65),
|
||||
metalness: 0.7,
|
||||
roughness: 0.7,
|
||||
envMapIntensity: 1.2,
|
||||
flatShading: true
|
||||
});
|
||||
|
||||
const mesh = new Mesh(geometry, material);
|
||||
// mesh.castShadow = true;
|
||||
// mesh.receiveShadow = true;
|
||||
this.add(mesh);
|
||||
|
||||
physics.add(mesh, { density: 2, autoSleep: false });
|
||||
|
||||
this.mesh = mesh;
|
||||
}
|
||||
|
||||
// Event handlers
|
||||
|
||||
onHover = ({ type }) => {
|
||||
console.log('FloatingCrystal', type);
|
||||
// if (type === 'over') {
|
||||
// } else {
|
||||
// }
|
||||
};
|
||||
|
||||
onClick = () => {
|
||||
console.log('FloatingCrystal', 'click');
|
||||
// open('https://alien.js.org/');
|
||||
};
|
||||
|
||||
// Public methods
|
||||
|
||||
update = time => {
|
||||
if (PhysicsController.enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.position.y = 0.7 + Math.sin(time) * 0.1;
|
||||
this.rotation.y += 0.01;
|
||||
};
|
||||
}
|
@ -0,0 +1,50 @@
|
||||
import { BoxGeometry, Color, Group, Mesh } from 'three';
|
||||
|
||||
import { WorldController } from '../../controllers/world/WorldController.js';
|
||||
import { GridHelper } from './GridHelper.js';
|
||||
|
||||
export class Floor extends Group {
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.position.y = -1.36; // -0.86 - 1 / 2
|
||||
|
||||
this.initMesh();
|
||||
}
|
||||
|
||||
initMesh() {
|
||||
const { physics } = WorldController;
|
||||
|
||||
this.gridHelper = new GridHelper();
|
||||
this.gridHelper.position.y = 0.494; // 1 / 2 - 0.006
|
||||
this.add(this.gridHelper);
|
||||
|
||||
// Physics mesh
|
||||
const floor = new Mesh(new BoxGeometry(11, 1, 11));
|
||||
floor.geometry.setDrawRange(0, 0); // Avoid rendering geometry
|
||||
this.add(floor);
|
||||
|
||||
physics.add(floor, { density: 0, autoSleep: false });
|
||||
}
|
||||
|
||||
// Public methods
|
||||
|
||||
invert = isInverted => {
|
||||
const colorStyle = `rgb(${getComputedStyle(document.querySelector(':root')).getPropertyValue('--ui-color-triplet').trim()})`;
|
||||
const color = new Color(colorStyle);
|
||||
|
||||
if (!isInverted) { // Dark colour is muted
|
||||
color.offsetHSL(0, 0, -0.65);
|
||||
}
|
||||
|
||||
const array = color.toArray();
|
||||
|
||||
const colors = this.gridHelper.geometry.getAttribute('color');
|
||||
|
||||
for (let i = 0; i < colors.count; i++) {
|
||||
colors.setXYZ(i, ...array);
|
||||
}
|
||||
|
||||
colors.needsUpdate = true;
|
||||
};
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
import { BufferGeometry, Color, Float32BufferAttribute, LineBasicMaterial, LineSegments } from 'three';
|
||||
|
||||
export class GridHelper extends LineSegments {
|
||||
constructor(size = 10, divisions = 10, color = 0x888888) {
|
||||
color = new Color(color);
|
||||
|
||||
const step = size / divisions;
|
||||
const halfSize = size / 2;
|
||||
|
||||
const vertices = [];
|
||||
const colors = [];
|
||||
|
||||
for (let i = 0, j = 0, k = -halfSize; i <= divisions + 1; i++, k += step) {
|
||||
for (let l = -halfSize; l <= divisions + 1 - halfSize; l++) {
|
||||
vertices.push(-0.5625 + l, 0, k - 0.5, -0.4375 + l, 0, k - 0.5);
|
||||
vertices.push(-0.5 + l, 0, k - 0.5625, -0.5 + l, 0, k - 0.4375);
|
||||
|
||||
color.toArray(colors, j); j += 3;
|
||||
color.toArray(colors, j); j += 3;
|
||||
color.toArray(colors, j); j += 3;
|
||||
color.toArray(colors, j); j += 3;
|
||||
}
|
||||
}
|
||||
|
||||
const geometry = new BufferGeometry();
|
||||
geometry.setAttribute('position', new Float32BufferAttribute(vertices, 3));
|
||||
geometry.setAttribute('color', new Float32BufferAttribute(colors, 3));
|
||||
|
||||
const material = new LineBasicMaterial({ vertexColors: true, toneMapped: false });
|
||||
|
||||
super(geometry, material);
|
||||
}
|
||||
}
|
@ -0,0 +1,80 @@
|
||||
import { HeaderInfo, Interface } from '@alienkitty/space.js/three';
|
||||
|
||||
import { Config } from '../../config/Config.js';
|
||||
import { NavLink } from './NavLink.js';
|
||||
|
||||
export class Header extends Interface {
|
||||
constructor() {
|
||||
super('.header');
|
||||
|
||||
this.initHTML();
|
||||
this.initViews();
|
||||
|
||||
this.addListeners();
|
||||
this.onResize();
|
||||
}
|
||||
|
||||
initHTML() {
|
||||
this.css({
|
||||
position: 'absolute',
|
||||
left: 20,
|
||||
top: 20,
|
||||
right: 20
|
||||
});
|
||||
}
|
||||
|
||||
initViews() {
|
||||
this.about = new NavLink('Space.js', 'https://github.com/alienkitty/space.js');
|
||||
this.about.css({
|
||||
x: -10,
|
||||
opacity: 0
|
||||
});
|
||||
this.add(this.about);
|
||||
|
||||
this.info = new HeaderInfo();
|
||||
this.info.css({
|
||||
x: -10,
|
||||
opacity: 0
|
||||
});
|
||||
this.add(this.info);
|
||||
}
|
||||
|
||||
addListeners() {
|
||||
window.addEventListener('resize', this.onResize);
|
||||
}
|
||||
|
||||
removeListeners() {
|
||||
window.removeEventListener('resize', this.onResize);
|
||||
}
|
||||
|
||||
// Event handlers
|
||||
|
||||
onResize = () => {
|
||||
if (document.documentElement.clientWidth < Config.BREAKPOINT) {
|
||||
this.css({
|
||||
left: 10,
|
||||
top: 10,
|
||||
right: 10
|
||||
});
|
||||
} else {
|
||||
this.css({
|
||||
left: 20,
|
||||
top: 20,
|
||||
right: 20
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// Public methods
|
||||
|
||||
animateIn = () => {
|
||||
this.about.tween({ x: 0, opacity: 1 }, 1000, 'easeOutQuart');
|
||||
this.info.tween({ x: 0, opacity: 1 }, 1000, 'easeOutQuart', 200);
|
||||
};
|
||||
|
||||
destroy = () => {
|
||||
this.removeListeners();
|
||||
|
||||
return super.destroy();
|
||||
};
|
||||
}
|
@ -0,0 +1,86 @@
|
||||
import { Interface } from '@alienkitty/space.js/three';
|
||||
|
||||
export class NavLink extends Interface {
|
||||
constructor(title, link) {
|
||||
super('.link', 'a');
|
||||
|
||||
this.title = title;
|
||||
this.link = link;
|
||||
this.letters = [];
|
||||
|
||||
this.initHTML();
|
||||
this.initText();
|
||||
|
||||
this.addListeners();
|
||||
}
|
||||
|
||||
initHTML() {
|
||||
this.css({
|
||||
cssFloat: 'left',
|
||||
padding: 10,
|
||||
fontWeight: '700',
|
||||
fontSize: 11,
|
||||
lineHeight: 18,
|
||||
letterSpacing: '0.03em',
|
||||
textTransform: 'uppercase',
|
||||
textDecoration: 'none',
|
||||
whiteSpace: 'nowrap',
|
||||
pointerEvents: 'auto',
|
||||
webkitUserSelect: 'none',
|
||||
userSelect: 'none'
|
||||
});
|
||||
this.attr({ href: this.link });
|
||||
}
|
||||
|
||||
initText() {
|
||||
const split = this.title.split('');
|
||||
split.forEach(str => {
|
||||
if (str === ' ') {
|
||||
str = ' ';
|
||||
}
|
||||
|
||||
const letter = new Interface(null, 'span');
|
||||
letter.css({ display: 'inline-block' });
|
||||
letter.html(str);
|
||||
this.add(letter);
|
||||
|
||||
this.letters.push(letter);
|
||||
});
|
||||
}
|
||||
|
||||
addListeners() {
|
||||
this.element.addEventListener('mouseenter', this.onHover);
|
||||
this.element.addEventListener('mouseleave', this.onHover);
|
||||
this.element.addEventListener('click', this.onClick);
|
||||
}
|
||||
|
||||
removeListeners() {
|
||||
this.element.removeEventListener('mouseenter', this.onHover);
|
||||
this.element.removeEventListener('mouseleave', this.onHover);
|
||||
this.element.removeEventListener('click', this.onClick);
|
||||
}
|
||||
|
||||
// Event handlers
|
||||
|
||||
onHover = ({ type }) => {
|
||||
if (type === 'mouseenter') {
|
||||
this.letters.forEach((letter, i) => {
|
||||
letter.clearTween().tween({ y: -5, opacity: 0 }, 125, 'easeOutCubic', i * 15, () => {
|
||||
letter.css({ y: 5 }).tween({ y: 0, opacity: 1 }, 300, 'easeOutCubic');
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
onClick = () => {
|
||||
this.events.emit('click');
|
||||
};
|
||||
|
||||
// Public methods
|
||||
|
||||
destroy = () => {
|
||||
this.removeListeners();
|
||||
|
||||
return super.destroy();
|
||||
};
|
||||
}
|
@ -0,0 +1,101 @@
|
||||
import { Interface, Stage, clearTween, degToRad, ticker, tween } from '@alienkitty/space.js/three';
|
||||
|
||||
export class ProgressCanvas extends Interface {
|
||||
constructor() {
|
||||
super(null, 'canvas');
|
||||
|
||||
const size = 32;
|
||||
|
||||
this.width = size;
|
||||
this.height = size;
|
||||
this.x = size / 2;
|
||||
this.y = size / 2;
|
||||
this.radius = size * 0.4;
|
||||
this.startAngle = degToRad(-90);
|
||||
this.progress = 0;
|
||||
this.needsUpdate = false;
|
||||
|
||||
this.initCanvas();
|
||||
}
|
||||
|
||||
initCanvas() {
|
||||
this.context = this.element.getContext('2d');
|
||||
}
|
||||
|
||||
addListeners() {
|
||||
ticker.add(this.onUpdate);
|
||||
}
|
||||
|
||||
removeListeners() {
|
||||
ticker.remove(this.onUpdate);
|
||||
}
|
||||
|
||||
// Event handlers
|
||||
|
||||
onUpdate = () => {
|
||||
if (this.needsUpdate) {
|
||||
this.update();
|
||||
}
|
||||
};
|
||||
|
||||
onProgress = ({ progress }) => {
|
||||
clearTween(this);
|
||||
|
||||
this.needsUpdate = true;
|
||||
|
||||
tween(this, { progress }, 500, 'easeOutCubic', () => {
|
||||
this.needsUpdate = false;
|
||||
|
||||
if (this.progress >= 1) {
|
||||
this.onComplete();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
onComplete = () => {
|
||||
this.removeListeners();
|
||||
|
||||
this.events.emit('complete');
|
||||
};
|
||||
|
||||
// Public methods
|
||||
|
||||
resize = () => {
|
||||
const dpr = 2;
|
||||
|
||||
this.element.width = Math.round(this.width * dpr);
|
||||
this.element.height = Math.round(this.height * dpr);
|
||||
this.element.style.width = this.width + 'px';
|
||||
this.element.style.height = this.height + 'px';
|
||||
this.context.scale(dpr, dpr);
|
||||
|
||||
this.context.lineWidth = 1.5;
|
||||
this.context.strokeStyle = Stage.rootStyle.getPropertyValue('--ui-color').trim();
|
||||
|
||||
this.update();
|
||||
};
|
||||
|
||||
update = () => {
|
||||
this.context.clearRect(0, 0, this.element.width, this.element.height);
|
||||
this.context.beginPath();
|
||||
this.context.arc(this.x, this.y, this.radius, this.startAngle, this.startAngle + degToRad(360 * this.progress));
|
||||
this.context.stroke();
|
||||
};
|
||||
|
||||
animateIn = () => {
|
||||
this.addListeners();
|
||||
this.resize();
|
||||
};
|
||||
|
||||
animateOut = () => {
|
||||
this.tween({ scale: 1.1, opacity: 0 }, 400, 'easeInCubic');
|
||||
};
|
||||
|
||||
destroy = () => {
|
||||
this.removeListeners();
|
||||
|
||||
clearTween(this);
|
||||
|
||||
return super.destroy();
|
||||
};
|
||||
}
|
@ -0,0 +1,70 @@
|
||||
:root {
|
||||
--bg-color: #0e0e0e;
|
||||
--ui-font-family: 'Roboto Mono', monospace;
|
||||
--ui-font-weight: 400;
|
||||
--ui-font-size: 11px;
|
||||
--ui-line-height: 15px;
|
||||
--ui-letter-spacing: 0.02em;
|
||||
--ui-number-letter-spacing: 0.5px;
|
||||
--ui-secondary-font-size: 10px;
|
||||
--ui-secondary-letter-spacing: 0.5px;
|
||||
--ui-color: rgba(255, 255, 255, 0.94);
|
||||
--ui-color-triplet: 255, 255, 255;
|
||||
--ui-color-line: rgba(255, 255, 255, 0.5);
|
||||
--ui-invert-light-color: #000;
|
||||
--ui-invert-light-color-triplet: 0, 0, 0;
|
||||
--ui-invert-light-color-line: #000;
|
||||
--ui-invert-dark-color: rgba(255, 255, 255, 0.94);
|
||||
--ui-invert-dark-color-triplet: 255, 255, 255;
|
||||
--ui-invert-dark-color-line: rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
|
||||
*, :after, :before {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
touch-action: none;
|
||||
-webkit-touch-callout: none;
|
||||
-webkit-user-drag: none;
|
||||
-webkit-text-size-adjust: none;
|
||||
text-size-adjust: none;
|
||||
}
|
||||
|
||||
*:focus {
|
||||
outline: 0;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
html, body {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
position: fixed;
|
||||
font-family: var(--ui-font-family);
|
||||
font-weight: var(--ui-font-weight);
|
||||
font-size: var(--ui-font-size);
|
||||
line-height: var(--ui-line-height);
|
||||
letter-spacing: var(--ui-letter-spacing);
|
||||
background-color: var(--bg-color);
|
||||
color: var(--ui-color);
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
}
|
||||
|
||||
a {
|
||||
color: var(--ui-color);
|
||||
text-decoration: none;
|
||||
text-underline-offset: 3px;
|
||||
}
|
||||
|
||||
a:hover, a:focus {
|
||||
color: var(--ui-color);
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
::selection {
|
||||
background-color: var(--ui-color);
|
||||
color: var(--bg-color);
|
||||
}
|
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="90" height="86"><linearGradient id="a" gradientUnits="userSpaceOnUse" x1="59.167" y1="42.175" x2="45.501" y2="36.342"><stop offset="0" stop-color="#fff"/><stop offset=".22" stop-color="#424242"/><stop offset="1"/></linearGradient><path fill="url(#a)" d="M48.891 34.6c.529.634 1.342 1.367 2.439 2.2l2.666 1.95 2.666 1.65 2.326 1.9c-2.912.833-6.277 1-10.098.5-3.518-.5-6.715-1.467-9.589-2.9-.983-.5-1.929-1.283-2.837-2.35-.907-1.133-1.456-2.15-1.646-3.05-.377-1.7-.151-3.133.681-4.3.87-1.267 2.25-1.9 4.142-1.9 1.967-.033 3.782.667 5.448 2.1l1.984 2.05 1.818 2.15"/><linearGradient id="b" gradientUnits="userSpaceOnUse" x1="65.39" y1="42.27" x2="76.057" y2="34.103"><stop offset="0" stop-color="#fff"/><stop offset=".22" stop-color="#424242"/><stop offset="1"/></linearGradient><path fill="url(#b)" d="M76.918 28.95c1.436.467 2.383 1.55 2.836 3.25.492 1.867.246 3.467-.736 4.8-.719.966-1.703 1.933-2.951 2.9-1.324 1.033-2.553 1.7-3.688 2-2.988.767-5.221.9-6.695.4l3.461-3.5c.832-1.1 1.57-2.483 2.213-4.15.529-1.434 1.172-2.667 1.93-3.7.982-1.466 1.93-2.183 2.836-2.15l.794.15"/><path fill="#FFF" d="M83.102 11.1c-1.814 2.467-3.084 4.55-3.801 6.25-1.098 2.567-1.191 4.784-.283 6.65l1.588 2.65 2.043 3.3c1.854 3.5 2.23 7.333 1.135 11.5-1.061 3.8-3.084 7.117-6.072 9.95-2.533 2.301-5.314 3.867-8.34 4.7-1.398.366-1.984 1.534-1.758 3.5.037.634.207 1.45.51 2.45l.625 2.2c.453 1.833.699 4.884.736 9.149-.037 1.5.02 2.351.172 2.551.227.333 1.059.533 2.496.6 1.777.033 3.311.666 4.596 1.9 1.361 1.333 1.605 2.766.738 4.3-.871 1.466-2.725 2.333-5.561 2.6l-3.633.101-39.657-.101c-1.249-.1-2.307-.566-3.178-1.399-1.059-1-1.853-1.584-2.382-1.75-3.178-.934-6.374-2.684-9.588-5.25A37.243 37.243 0 015.261 68.1C2.84 64.567 1.327 61.117.722 57.75c-.681-3.8-.17-7.116 1.532-9.95 1.286-2.267 3.196-3.883 5.73-4.85 2.799-1.1 5.257-.816 7.375.85 1.476 1.2 2.307 2.7 2.497 4.5.114 1.733-.378 3.367-1.476 4.9l-1.588 2-1.475 2.149c-.681 1.467-.946 3.184-.794 5.15.114 2.033.644 3.683 1.588 4.95 2.875 3.699 5.031 5.434 6.468 5.199.87-.166 1.362-.866 1.475-2.1l.057-3.1c.605-3.801 1.532-7.033 2.779-9.7.606-1.267 1.57-2.733 2.894-4.4l3.291-4.2c.794-1.133.983-2.1.567-2.899L29.6 43.5a20.754 20.754 0 01-1.815-3.55c-.341-.833-.567-1.833-.681-3l-.34-2.4c-.189-.767-.606-1.35-1.249-1.75-.719-.5-1.815-.867-3.291-1.1l1.021-.65c.492-.233.719-.467.681-.7-.038-.2-.511-.533-1.418-1l-1.646-.8c1.097-.5 1.305-1.2.624-2.1l-2.212-2.2-3.518-4.7-3.971-4.95c-1.172-1.167-2.062-2.366-2.667-3.6.87-.133 2.042.033 3.518.5l3.348 1.1 12.368 3.05 4.255 1.15c2.647.833 5.522 1.45 8.624 1.85l8.795.2c.68-.1 1.625-.366 2.836-.8l2.838-.7 4.65-.3c1.363-.167 2.838-.617 4.426-1.35 3.404-1.667 7.604-4.117 12.596-7.35L89.571.55c-.455.267-.891.967-1.305 2.1l-.965 2.4-4.199 6.05M47.074 32.45L45.09 30.4c-1.666-1.434-3.48-2.133-5.448-2.1-1.891 0-3.272.633-4.142 1.9-.832 1.167-1.059 2.6-.681 4.3.189.9.738 1.917 1.646 3.05.908 1.067 1.854 1.85 2.837 2.35 2.874 1.433 6.071 2.4 9.589 2.9 3.82.5 7.186.333 10.098-.5l-2.326-1.9-2.666-1.65-2.667-1.95c-1.098-.833-1.91-1.566-2.439-2.2l-1.817-2.15m29.049-3.65c-.906-.033-1.854.684-2.836 2.15-.758 1.034-1.4 2.267-1.93 3.7-.643 1.667-1.381 3.05-2.213 4.15l-3.461 3.5c1.475.5 3.707.367 6.695-.4 1.135-.3 2.363-.967 3.688-2 1.248-.967 2.232-1.934 2.951-2.9.982-1.333 1.229-2.934.736-4.8-.453-1.7-1.4-2.783-2.836-3.25l-.794-.15"/></svg>
|
After Width: | Height: | Size: 3.3 KiB |
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="14"><path fill="#FFF" d="M23.772 7.024c0 1.8-1.173 3.333-3.518 4.601-2.307 1.266-5.068 1.9-8.283 1.9-3.216 0-5.995-.635-8.34-1.9C1.361 10.357.227 8.824.227 7.024c0-1.833 1.135-3.383 3.404-4.649 2.345-1.267 5.124-1.9 8.34-1.9 3.215 0 5.977.634 8.283 1.9 2.346 1.267 3.518 2.816 3.518 4.649"/></svg>
|
After Width: | Height: | Size: 356 B |
@ -0,0 +1,2 @@
|
||||
Composition Bed Dark Drum Rhythm Ambient Glitches Production Element Imaging Element Accent Transition
|
||||
https://www.audiomicro.com/composition-bed-dark-drum-rhythm-ambient-glitches-production-element-imaging-accent-transition-sound-effects-70803
|
@ -0,0 +1,2 @@
|
||||
Composition Bed Dark Drum Rhythm Ambient Glitches Production Element Imaging Element Accent Transition
|
||||
https://www.audiomicro.com/composition-bed-dark-drum-rhythm-ambient-glitches-production-element-imaging-accent-transition-sound-effects-70803
|
@ -0,0 +1,2 @@
|
||||
Glitch Hit Multimedia Notification Interactive 19 - AM -3 Semitones
|
||||
https://www.audiomicro.com/multimedia-technology-computer-glitch-hit-notification-interactive-19-sound-effects-1438571
|
@ -0,0 +1,2 @@
|
||||
Ethereal bells - AM
|
||||
https://www.audiomicro.com/ethereal-bells-sound-effects-1592135
|
@ -0,0 +1,2 @@
|
||||
Musical Chinese Gong 14 Inch Low Ascend Descend
|
||||
https://www.audiomicro.com/musical-chinese-gong-14-inch-low-ascend-descend-sound-effects-57155
|
@ -0,0 +1,2 @@
|
||||
Hi-hat
|
||||
https://www.html5rocks.com/en/tutorials/webaudio/intro/
|
@ -0,0 +1,2 @@
|
||||
Glitch Hit Multimedia Notification Interactive 19 - AM
|
||||
https://www.audiomicro.com/multimedia-technology-computer-glitch-hit-notification-interactive-19-sound-effects-1438571
|
@ -0,0 +1,2 @@
|
||||
Bass (Kick) Drum
|
||||
https://www.html5rocks.com/en/tutorials/webaudio/intro/
|
@ -0,0 +1,2 @@
|
||||
Metal Monk Chamber EVL071001
|
||||
https://www.audiomicro.com/sci-fi-drones-tones-drones-metal-monk-chamber-evl071001-sound-effects-123070
|
@ -0,0 +1,2 @@
|
||||
Snare Drum
|
||||
https://www.html5rocks.com/en/tutorials/webaudio/intro/
|
@ -0,0 +1,183 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
|
||||
|
||||
<title>Gong — Space.js</title>
|
||||
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Roboto+Mono&family=Roboto:wght@300&family=Gothic+A1:wght@400;700">
|
||||
<link rel="stylesheet" href="assets/css/style.css">
|
||||
|
||||
<script type="module">
|
||||
import { BufferLoader, Interface, WebAudio, clamp, delayedCall, guid, ticker } from '../src/index.js';
|
||||
|
||||
class Instructions extends Interface {
|
||||
constructor() {
|
||||
super('.instructions');
|
||||
|
||||
this.initHTML();
|
||||
}
|
||||
|
||||
initHTML() {
|
||||
this.invisible();
|
||||
this.css({
|
||||
position: 'absolute',
|
||||
left: '50%',
|
||||
bottom: 55,
|
||||
width: 300,
|
||||
marginLeft: -300 / 2,
|
||||
opacity: 0
|
||||
});
|
||||
|
||||
this.container = new Interface('.container');
|
||||
this.container.css({
|
||||
position: 'absolute',
|
||||
bottom: 0,
|
||||
width: '100%'
|
||||
});
|
||||
this.add(this.container);
|
||||
|
||||
this.text = new Interface('.text');
|
||||
this.text.css({
|
||||
fontFamily: 'Gothic A1, sans-serif',
|
||||
fontWeight: '700',
|
||||
fontSize: 10,
|
||||
lineHeight: 20,
|
||||
letterSpacing: 0.8,
|
||||
textAlign: 'center',
|
||||
textTransform: 'uppercase',
|
||||
opacity: 0.7
|
||||
});
|
||||
this.text.text(`${navigator.maxTouchPoints ? 'Tap' : 'Click'} for sound`);
|
||||
this.container.add(this.text);
|
||||
}
|
||||
|
||||
// Public methods
|
||||
|
||||
toggle = (show, delay = 0) => {
|
||||
if (show) {
|
||||
this.visible();
|
||||
this.tween({ opacity: 1 }, 800, 'easeInOutSine', delay);
|
||||
this.text.css({ y: 10 }).tween({ y: 0 }, 1200, 'easeOutCubic', delay);
|
||||
} else {
|
||||
this.tween({ opacity: 0 }, 300, 'easeOutSine', () => {
|
||||
this.invisible();
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
class UI extends Interface {
|
||||
constructor() {
|
||||
super('.ui');
|
||||
|
||||
this.initHTML();
|
||||
this.initViews();
|
||||
}
|
||||
|
||||
initHTML() {
|
||||
this.css({
|
||||
minHeight: '100%',
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
flexWrap: 'wrap',
|
||||
padding: '55px 0 125px',
|
||||
pointerEvents: 'none',
|
||||
webkitUserSelect: 'none',
|
||||
userSelect: 'none'
|
||||
});
|
||||
}
|
||||
|
||||
initViews() {
|
||||
this.instructions = new Instructions();
|
||||
this.add(this.instructions);
|
||||
}
|
||||
}
|
||||
|
||||
class AudioController {
|
||||
static init(instructions) {
|
||||
this.instructions = instructions;
|
||||
|
||||
this.addListeners();
|
||||
}
|
||||
|
||||
static addListeners() {
|
||||
document.addEventListener('visibilitychange', this.onVisibility);
|
||||
document.addEventListener('pointerdown', this.onPointerDown);
|
||||
|
||||
this.instructions.toggle(true);
|
||||
}
|
||||
|
||||
// Event handlers
|
||||
|
||||
static onVisibility = () => {
|
||||
if (document.hidden) {
|
||||
WebAudio.mute();
|
||||
} else {
|
||||
WebAudio.unmute();
|
||||
}
|
||||
};
|
||||
|
||||
static onPointerDown = ({ clientX, clientY }) => {
|
||||
// this.instructions.toggle(false);
|
||||
|
||||
const normalX = clientX / document.documentElement.clientWidth;
|
||||
const normalY = clientY / document.documentElement.clientHeight;
|
||||
const pan = clamp(((normalX * 2) - 1) * 0.8, -1, 1);
|
||||
const rate = clamp(0.8 + (1 - normalY) * 0.4, 0.8, 1.2);
|
||||
|
||||
const gong = WebAudio.clone('gong', guid());
|
||||
gong.gain.set(0.5);
|
||||
gong.stereoPan.set(pan);
|
||||
gong.playbackRate.set(rate);
|
||||
gong.play();
|
||||
|
||||
delayedCall(6000, () => {
|
||||
gong.destroy();
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
class App {
|
||||
static async init() {
|
||||
this.initLoader();
|
||||
this.initViews();
|
||||
|
||||
this.addListeners();
|
||||
|
||||
await this.bufferLoader.ready();
|
||||
|
||||
this.initAudio();
|
||||
}
|
||||
|
||||
static initLoader() {
|
||||
this.bufferLoader = new BufferLoader();
|
||||
this.bufferLoader.loadAll(['assets/sounds/gong.mp3']);
|
||||
}
|
||||
|
||||
static initViews() {
|
||||
this.ui = new UI();
|
||||
document.body.appendChild(this.ui.element);
|
||||
}
|
||||
|
||||
static initAudio() {
|
||||
WebAudio.init({ sampleRate: 48000 });
|
||||
WebAudio.load(this.bufferLoader.files);
|
||||
|
||||
AudioController.init(this.ui.instructions);
|
||||
}
|
||||
|
||||
static addListeners() {
|
||||
ticker.start();
|
||||
}
|
||||
}
|
||||
|
||||
App.init();
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
@ -0,0 +1,627 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
|
||||
|
||||
<title>Rhythm — Space.js</title>
|
||||
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Roboto+Mono&family=Roboto:wght@300&family=Gothic+A1:wght@400;700">
|
||||
<link rel="stylesheet" href="assets/css/style.css">
|
||||
|
||||
<style>
|
||||
*, :after, :before {
|
||||
touch-action: unset;
|
||||
}
|
||||
|
||||
body {
|
||||
position: unset;
|
||||
overscroll-behavior: none;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script type="module">
|
||||
import { BufferLoader, Interface, Panel, PanelItem, WebAudio, headsTails, ticker } from '../src/index.js';
|
||||
|
||||
class Instructions extends Interface {
|
||||
constructor() {
|
||||
super('.instructions');
|
||||
|
||||
this.initHTML();
|
||||
}
|
||||
|
||||
initHTML() {
|
||||
this.invisible();
|
||||
this.css({
|
||||
position: 'absolute',
|
||||
left: '50%',
|
||||
bottom: 55,
|
||||
width: 300,
|
||||
marginLeft: -300 / 2,
|
||||
opacity: 0
|
||||
});
|
||||
|
||||
this.container = new Interface('.container');
|
||||
this.container.css({
|
||||
position: 'absolute',
|
||||
bottom: 0,
|
||||
width: '100%'
|
||||
});
|
||||
this.add(this.container);
|
||||
|
||||
this.text = new Interface('.text');
|
||||
this.text.css({
|
||||
fontFamily: 'Gothic A1, sans-serif',
|
||||
fontWeight: '700',
|
||||
fontSize: 10,
|
||||
lineHeight: 20,
|
||||
letterSpacing: 0.8,
|
||||
textAlign: 'center',
|
||||
textTransform: 'uppercase',
|
||||
opacity: 0.7
|
||||
});
|
||||
this.text.text(`${navigator.maxTouchPoints ? 'Tap' : 'Click'} for sound`);
|
||||
this.container.add(this.text);
|
||||
}
|
||||
|
||||
// Public methods
|
||||
|
||||
toggle = (show, delay = 0) => {
|
||||
if (show) {
|
||||
this.visible();
|
||||
this.tween({ opacity: 1 }, 800, 'easeInOutSine', delay);
|
||||
this.text.css({ y: 10 }).tween({ y: 0 }, 1200, 'easeOutCubic', delay);
|
||||
} else {
|
||||
this.tween({ opacity: 0 }, 300, 'easeOutSine', () => {
|
||||
this.invisible();
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
class UI extends Interface {
|
||||
constructor() {
|
||||
super('.ui');
|
||||
|
||||
this.initHTML();
|
||||
this.initViews();
|
||||
}
|
||||
|
||||
initHTML() {
|
||||
this.css({
|
||||
minHeight: '100%',
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
flexWrap: 'wrap',
|
||||
gap: 20,
|
||||
padding: '55px 0 125px',
|
||||
pointerEvents: 'none',
|
||||
webkitUserSelect: 'none',
|
||||
userSelect: 'none'
|
||||
});
|
||||
}
|
||||
|
||||
initViews() {
|
||||
this.instructions = new Instructions();
|
||||
this.add(this.instructions);
|
||||
}
|
||||
}
|
||||
|
||||
class AudioController {
|
||||
static init(instructions) {
|
||||
this.instructions = instructions;
|
||||
|
||||
this.context = WebAudio.context;
|
||||
this.lastTime = null;
|
||||
|
||||
this.initSounds();
|
||||
|
||||
this.addListeners();
|
||||
}
|
||||
|
||||
static initSounds() {
|
||||
this.ambient = WebAudio.get('metal_monk_loop');
|
||||
this.ambient.gain.set(0.2);
|
||||
this.ambient.loop = true;
|
||||
this.ambient.play();
|
||||
|
||||
this.bells = WebAudio.get('ethereal_bells');
|
||||
this.bells.gain.set(0.5);
|
||||
|
||||
this.accent1 = WebAudio.get('accent_transition_1');
|
||||
this.accent1.gain.set(0.1);
|
||||
|
||||
this.accent2 = WebAudio.get('accent_transition_2');
|
||||
this.accent2.gain.set(0.05);
|
||||
|
||||
this.kick = WebAudio.get('kick');
|
||||
this.kick.gain.set(1);
|
||||
|
||||
this.snare = WebAudio.get('snare');
|
||||
this.snare.gain.set(1);
|
||||
|
||||
this.hihat = WebAudio.get('hihat');
|
||||
this.hihat.gain.set(1);
|
||||
}
|
||||
|
||||
static addListeners() {
|
||||
document.addEventListener('visibilitychange', this.onVisibility);
|
||||
document.addEventListener('pointerdown', this.onPointerDown);
|
||||
|
||||
this.instructions.toggle(true);
|
||||
}
|
||||
|
||||
// Event handlers
|
||||
|
||||
static onVisibility = () => {
|
||||
if (document.hidden) {
|
||||
WebAudio.mute();
|
||||
} else {
|
||||
WebAudio.unmute();
|
||||
}
|
||||
};
|
||||
|
||||
static onPointerDown = () => {
|
||||
// this.instructions.toggle(false);
|
||||
|
||||
// Based on https://www.html5rocks.com/en/tutorials/webaudio/intro/ by smus
|
||||
|
||||
const bells = this.bells;
|
||||
const accent1 = this.accent1;
|
||||
const accent2 = this.accent2;
|
||||
const kick = this.kick;
|
||||
const snare = this.snare;
|
||||
const hihat = this.hihat;
|
||||
|
||||
const tempo = 70; // BPM (beats per minute)
|
||||
const eighthNoteTime = (60 / tempo) / 2;
|
||||
const barLength = 8 * eighthNoteTime;
|
||||
|
||||
// Snap to bar length
|
||||
let startTime = Math.ceil(this.context.currentTime / barLength) * barLength;
|
||||
|
||||
// Next 4 bars
|
||||
const lastLength = this.lastTime + 4 * barLength;
|
||||
|
||||
if (this.lastTime !== null && startTime < lastLength) {
|
||||
startTime = lastLength;
|
||||
}
|
||||
|
||||
this.lastTime = startTime;
|
||||
|
||||
// Play the bells on the first eighth note
|
||||
bells.play(startTime + eighthNoteTime);
|
||||
|
||||
// Play the accents on bar 2, beat 4
|
||||
if (headsTails()) {
|
||||
accent1.play(startTime + barLength + 6 * eighthNoteTime);
|
||||
} else {
|
||||
accent2.play(startTime + barLength + 6 * eighthNoteTime);
|
||||
}
|
||||
|
||||
// Play 4 bars
|
||||
for (let bar = 0; bar < 4; bar++) {
|
||||
// We'll start playing the rhythm one eighth note from "now"
|
||||
const time = startTime + bar * barLength + eighthNoteTime;
|
||||
|
||||
// Play the bass (kick) drum on beats 1, 3
|
||||
kick.play(time);
|
||||
kick.play(time + 4 * eighthNoteTime);
|
||||
|
||||
// Play the snare drum on beats 2, 4
|
||||
snare.play(time + 2 * eighthNoteTime);
|
||||
snare.play(time + 6 * eighthNoteTime);
|
||||
|
||||
// Play the hi-hat every eighth note
|
||||
for (let i = 0; i < 8; i++) {
|
||||
hihat.play(time + i * eighthNoteTime);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
class PanelController {
|
||||
static init(ui) {
|
||||
this.ui = ui;
|
||||
|
||||
this.initPanel();
|
||||
}
|
||||
|
||||
static initPanel() {
|
||||
const { ambient, bells, accent1, accent2, kick, snare, hihat } = AudioController;
|
||||
|
||||
const track1 = new Panel();
|
||||
track1.animateIn();
|
||||
this.ui.add(track1);
|
||||
|
||||
[
|
||||
{
|
||||
label: 'Ambient'
|
||||
},
|
||||
{
|
||||
type: 'divider'
|
||||
},
|
||||
{
|
||||
type: 'slider',
|
||||
label: 'Volume',
|
||||
min: 0,
|
||||
max: 1,
|
||||
step: 0.01,
|
||||
value: ambient.gain.value,
|
||||
callback: value => {
|
||||
ambient.gain.value = value;
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'slider',
|
||||
label: 'Pan',
|
||||
min: -1,
|
||||
max: 1,
|
||||
step: 0.01,
|
||||
value: ambient.stereoPan.value,
|
||||
callback: value => {
|
||||
ambient.stereoPan.value = value;
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'slider',
|
||||
label: 'Rate',
|
||||
min: 0,
|
||||
max: 2,
|
||||
step: 0.01,
|
||||
value: ambient.playbackRate.value,
|
||||
callback: value => {
|
||||
ambient.playbackRate.value = value;
|
||||
}
|
||||
}
|
||||
].forEach(data => {
|
||||
track1.add(new PanelItem(data));
|
||||
});
|
||||
|
||||
const track2 = new Panel();
|
||||
track2.animateIn();
|
||||
this.ui.add(track2);
|
||||
|
||||
[
|
||||
{
|
||||
label: 'Bells'
|
||||
},
|
||||
{
|
||||
type: 'divider'
|
||||
},
|
||||
{
|
||||
type: 'slider',
|
||||
label: 'Volume',
|
||||
min: 0,
|
||||
max: 1,
|
||||
step: 0.01,
|
||||
value: bells.gain.value,
|
||||
callback: value => {
|
||||
bells.gain.value = value;
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'slider',
|
||||
label: 'Pan',
|
||||
min: -1,
|
||||
max: 1,
|
||||
step: 0.01,
|
||||
value: bells.stereoPan.value,
|
||||
callback: value => {
|
||||
bells.stereoPan.value = value;
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'slider',
|
||||
label: 'Rate',
|
||||
min: 0,
|
||||
max: 2,
|
||||
step: 0.01,
|
||||
value: bells.playbackRate.value,
|
||||
callback: value => {
|
||||
bells.playbackRate.value = value;
|
||||
}
|
||||
}
|
||||
].forEach(data => {
|
||||
track2.add(new PanelItem(data));
|
||||
});
|
||||
|
||||
const track3 = new Panel();
|
||||
track3.animateIn();
|
||||
this.ui.add(track3);
|
||||
|
||||
[
|
||||
{
|
||||
label: 'Accent1'
|
||||
},
|
||||
{
|
||||
type: 'divider'
|
||||
},
|
||||
{
|
||||
type: 'slider',
|
||||
label: 'Volume',
|
||||
min: 0,
|
||||
max: 1,
|
||||
step: 0.01,
|
||||
value: accent1.gain.value,
|
||||
callback: value => {
|
||||
accent1.gain.value = value;
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'slider',
|
||||
label: 'Pan',
|
||||
min: -1,
|
||||
max: 1,
|
||||
step: 0.01,
|
||||
value: accent1.stereoPan.value,
|
||||
callback: value => {
|
||||
accent1.stereoPan.value = value;
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'slider',
|
||||
label: 'Rate',
|
||||
min: 0,
|
||||
max: 2,
|
||||
step: 0.01,
|
||||
value: accent1.playbackRate.value,
|
||||
callback: value => {
|
||||
accent1.playbackRate.value = value;
|
||||
}
|
||||
}
|
||||
].forEach(data => {
|
||||
track3.add(new PanelItem(data));
|
||||
});
|
||||
|
||||
const track4 = new Panel();
|
||||
track4.animateIn();
|
||||
this.ui.add(track4);
|
||||
|
||||
[
|
||||
{
|
||||
label: 'Accent2'
|
||||
},
|
||||
{
|
||||
type: 'divider'
|
||||
},
|
||||
{
|
||||
type: 'slider',
|
||||
label: 'Volume',
|
||||
min: 0,
|
||||
max: 1,
|
||||
step: 0.01,
|
||||
value: accent2.gain.value,
|
||||
callback: value => {
|
||||
accent2.gain.value = value;
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'slider',
|
||||
label: 'Pan',
|
||||
min: -1,
|
||||
max: 1,
|
||||
step: 0.01,
|
||||
value: accent2.stereoPan.value,
|
||||
callback: value => {
|
||||
accent2.stereoPan.value = value;
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'slider',
|
||||
label: 'Rate',
|
||||
min: 0,
|
||||
max: 2,
|
||||
step: 0.01,
|
||||
value: accent2.playbackRate.value,
|
||||
callback: value => {
|
||||
accent2.playbackRate.value = value;
|
||||
}
|
||||
}
|
||||
].forEach(data => {
|
||||
track4.add(new PanelItem(data));
|
||||
});
|
||||
|
||||
const track5 = new Panel();
|
||||
track5.animateIn();
|
||||
this.ui.add(track5);
|
||||
|
||||
[
|
||||
{
|
||||
label: 'Kick'
|
||||
},
|
||||
{
|
||||
type: 'divider'
|
||||
},
|
||||
{
|
||||
type: 'slider',
|
||||
label: 'Volume',
|
||||
min: 0,
|
||||
max: 1,
|
||||
step: 0.01,
|
||||
value: kick.gain.value,
|
||||
callback: value => {
|
||||
kick.gain.value = value;
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'slider',
|
||||
label: 'Pan',
|
||||
min: -1,
|
||||
max: 1,
|
||||
step: 0.01,
|
||||
value: kick.stereoPan.value,
|
||||
callback: value => {
|
||||
kick.stereoPan.value = value;
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'slider',
|
||||
label: 'Rate',
|
||||
min: 0,
|
||||
max: 2,
|
||||
step: 0.01,
|
||||
value: kick.playbackRate.value,
|
||||
callback: value => {
|
||||
kick.playbackRate.value = value;
|
||||
}
|
||||
}
|
||||
].forEach(data => {
|
||||
track5.add(new PanelItem(data));
|
||||
});
|
||||
|
||||
const track6 = new Panel();
|
||||
track6.animateIn();
|
||||
this.ui.add(track6);
|
||||
|
||||
[
|
||||
{
|
||||
label: 'Snare'
|
||||
},
|
||||
{
|
||||
type: 'divider'
|
||||
},
|
||||
{
|
||||
type: 'slider',
|
||||
label: 'Volume',
|
||||
min: 0,
|
||||
max: 1,
|
||||
step: 0.01,
|
||||
value: snare.gain.value,
|
||||
callback: value => {
|
||||
snare.gain.value = value;
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'slider',
|
||||
label: 'Pan',
|
||||
min: -1,
|
||||
max: 1,
|
||||
step: 0.01,
|
||||
value: snare.stereoPan.value,
|
||||
callback: value => {
|
||||
snare.stereoPan.value = value;
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'slider',
|
||||
label: 'Rate',
|
||||
min: 0,
|
||||
max: 2,
|
||||
step: 0.01,
|
||||
value: snare.playbackRate.value,
|
||||
callback: value => {
|
||||
snare.playbackRate.value = value;
|
||||
}
|
||||
}
|
||||
].forEach(data => {
|
||||
track6.add(new PanelItem(data));
|
||||
});
|
||||
|
||||
const track7 = new Panel();
|
||||
track7.animateIn();
|
||||
this.ui.add(track7);
|
||||
|
||||
[
|
||||
{
|
||||
label: 'Hihat'
|
||||
},
|
||||
{
|
||||
type: 'divider'
|
||||
},
|
||||
{
|
||||
type: 'slider',
|
||||
label: 'Volume',
|
||||
min: 0,
|
||||
max: 1,
|
||||
step: 0.01,
|
||||
value: hihat.gain.value,
|
||||
callback: value => {
|
||||
hihat.gain.value = value;
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'slider',
|
||||
label: 'Pan',
|
||||
min: -1,
|
||||
max: 1,
|
||||
step: 0.01,
|
||||
value: hihat.stereoPan.value,
|
||||
callback: value => {
|
||||
hihat.stereoPan.value = value;
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'slider',
|
||||
label: 'Rate',
|
||||
min: 0,
|
||||
max: 2,
|
||||
step: 0.01,
|
||||
value: hihat.playbackRate.value,
|
||||
callback: value => {
|
||||
hihat.playbackRate.value = value;
|
||||
}
|
||||
}
|
||||
].forEach(data => {
|
||||
track7.add(new PanelItem(data));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class App {
|
||||
static async init() {
|
||||
this.initLoader();
|
||||
this.initViews();
|
||||
|
||||
this.addListeners();
|
||||
|
||||
await this.bufferLoader.ready();
|
||||
|
||||
this.initAudio();
|
||||
this.initPanel();
|
||||
}
|
||||
|
||||
static initLoader() {
|
||||
this.bufferLoader = new BufferLoader();
|
||||
this.bufferLoader.loadAll([
|
||||
'assets/sounds/metal_monk_loop.mp3',
|
||||
'assets/sounds/ethereal_bells.mp3',
|
||||
'assets/sounds/accent_transition_1.mp3',
|
||||
'assets/sounds/accent_transition_2.mp3',
|
||||
'assets/sounds/hover.mp3',
|
||||
'assets/sounds/click.mp3',
|
||||
'assets/sounds/kick.mp3',
|
||||
'assets/sounds/snare.mp3',
|
||||
'assets/sounds/hihat.mp3'
|
||||
]);
|
||||
}
|
||||
|
||||
static initViews() {
|
||||
this.ui = new UI();
|
||||
document.body.appendChild(this.ui.element);
|
||||
}
|
||||
|
||||
static initAudio() {
|
||||
WebAudio.init({ sampleRate: 48000 });
|
||||
WebAudio.load(this.bufferLoader.files);
|
||||
|
||||
AudioController.init(this.ui.instructions);
|
||||
}
|
||||
|
||||
static initPanel() {
|
||||
PanelController.init(this.ui);
|
||||
}
|
||||
|
||||
static addListeners() {
|
||||
ticker.start();
|
||||
}
|
||||
}
|
||||
|
||||
App.init();
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
@ -0,0 +1,227 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
|
||||
|
||||
<title>Stream — Space.js</title>
|
||||
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Roboto+Mono&family=Roboto:wght@300&family=Gothic+A1:wght@400;700">
|
||||
<link rel="stylesheet" href="assets/css/style.css">
|
||||
|
||||
<script type="module">
|
||||
import { Interface, Panel, PanelItem, WebAudio, ticker } from '../src/index.js';
|
||||
|
||||
class Instructions extends Interface {
|
||||
constructor() {
|
||||
super('.instructions');
|
||||
|
||||
this.initHTML();
|
||||
}
|
||||
|
||||
initHTML() {
|
||||
this.invisible();
|
||||
this.css({
|
||||
position: 'absolute',
|
||||
left: '50%',
|
||||
bottom: 55,
|
||||
width: 300,
|
||||
marginLeft: -300 / 2,
|
||||
opacity: 0
|
||||
});
|
||||
|
||||
this.container = new Interface('.container');
|
||||
this.container.css({
|
||||
position: 'absolute',
|
||||
bottom: 0,
|
||||
width: '100%'
|
||||
});
|
||||
this.add(this.container);
|
||||
|
||||
this.text = new Interface('.text');
|
||||
this.text.css({
|
||||
fontFamily: 'Gothic A1, sans-serif',
|
||||
fontWeight: '700',
|
||||
fontSize: 10,
|
||||
lineHeight: 20,
|
||||
letterSpacing: 0.8,
|
||||
textAlign: 'center',
|
||||
textTransform: 'uppercase',
|
||||
opacity: 0.7
|
||||
});
|
||||
this.text.text(`${navigator.maxTouchPoints ? 'Tap' : 'Click'} to play`);
|
||||
this.container.add(this.text);
|
||||
}
|
||||
|
||||
// Public methods
|
||||
|
||||
toggle = (show, delay = 0) => {
|
||||
if (show) {
|
||||
this.visible();
|
||||
this.tween({ opacity: 1 }, 800, 'easeInOutSine', delay);
|
||||
this.text.css({ y: 10 }).tween({ y: 0 }, 1200, 'easeOutCubic', delay);
|
||||
} else {
|
||||
this.tween({ opacity: 0 }, 300, 'easeOutSine', () => {
|
||||
this.invisible();
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
class UI extends Interface {
|
||||
constructor() {
|
||||
super('.ui');
|
||||
|
||||
this.initHTML();
|
||||
this.initViews();
|
||||
}
|
||||
|
||||
initHTML() {
|
||||
this.css({
|
||||
minHeight: '100%',
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
flexWrap: 'wrap',
|
||||
padding: '55px 0 125px',
|
||||
pointerEvents: 'none',
|
||||
webkitUserSelect: 'none',
|
||||
userSelect: 'none'
|
||||
});
|
||||
}
|
||||
|
||||
initViews() {
|
||||
this.instructions = new Instructions();
|
||||
this.add(this.instructions);
|
||||
}
|
||||
}
|
||||
|
||||
class AudioController {
|
||||
static init(instructions) {
|
||||
this.instructions = instructions;
|
||||
|
||||
this.initSounds();
|
||||
|
||||
this.addListeners();
|
||||
}
|
||||
|
||||
static initSounds() {
|
||||
this.protonradio = WebAudio.get('protonradio');
|
||||
this.protonradio.gain.set(1);
|
||||
}
|
||||
|
||||
static addListeners() {
|
||||
document.addEventListener('visibilitychange', this.onVisibility);
|
||||
document.addEventListener('pointerdown', this.onPointerDown);
|
||||
|
||||
this.instructions.toggle(true);
|
||||
}
|
||||
|
||||
// Event handlers
|
||||
|
||||
static onVisibility = () => {
|
||||
if (document.hidden) {
|
||||
WebAudio.mute();
|
||||
} else {
|
||||
WebAudio.unmute();
|
||||
}
|
||||
};
|
||||
|
||||
static onPointerDown = () => {
|
||||
this.instructions.toggle(false);
|
||||
|
||||
this.protonradio.play();
|
||||
};
|
||||
}
|
||||
|
||||
class PanelController {
|
||||
static init(ui) {
|
||||
this.ui = ui;
|
||||
|
||||
this.initPanel();
|
||||
}
|
||||
|
||||
static initPanel() {
|
||||
const { protonradio } = AudioController;
|
||||
|
||||
const panel = new Panel();
|
||||
panel.animateIn();
|
||||
this.ui.add(panel);
|
||||
|
||||
const items = [
|
||||
{
|
||||
label: 'Proton Radio'
|
||||
},
|
||||
{
|
||||
type: 'divider'
|
||||
},
|
||||
{
|
||||
type: 'slider',
|
||||
label: 'Volume',
|
||||
min: 0,
|
||||
max: 1,
|
||||
step: 0.01,
|
||||
value: protonradio.gain.value,
|
||||
callback: value => {
|
||||
protonradio.gain.value = value;
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'slider',
|
||||
label: 'Pan',
|
||||
min: -1,
|
||||
max: 1,
|
||||
step: 0.01,
|
||||
value: protonradio.stereoPan.value,
|
||||
callback: value => {
|
||||
protonradio.stereoPan.value = value;
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
items.forEach(data => {
|
||||
panel.add(new PanelItem(data));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class App {
|
||||
static async init() {
|
||||
this.initViews();
|
||||
|
||||
this.addListeners();
|
||||
|
||||
this.initAudio();
|
||||
this.initPanel();
|
||||
}
|
||||
|
||||
static initViews() {
|
||||
this.ui = new UI();
|
||||
document.body.appendChild(this.ui.element);
|
||||
}
|
||||
|
||||
static initAudio() {
|
||||
WebAudio.init({ sampleRate: 48000 });
|
||||
|
||||
// Shoutcast streams append a semicolon (;) to the URL
|
||||
WebAudio.load({ protonradio: 'https://shoutcast.protonradio.com/;' });
|
||||
|
||||
AudioController.init(this.ui.instructions);
|
||||
}
|
||||
|
||||
static initPanel() {
|
||||
PanelController.init(this.ui);
|
||||
}
|
||||
|
||||
static addListeners() {
|
||||
ticker.start();
|
||||
}
|
||||
}
|
||||
|
||||
App.init();
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
210
src/plugins/floor/lib/@alienkitty/space.js/examples/close.html
Normal file
210
src/plugins/floor/lib/@alienkitty/space.js/examples/close.html
Normal file
@ -0,0 +1,210 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
|
||||
|
||||
<title>Close — Space.js</title>
|
||||
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Roboto+Mono">
|
||||
<link rel="stylesheet" href="assets/css/style.css">
|
||||
|
||||
<script type="module">
|
||||
import { Interface, clearTween, ticker, tween } from '../src/index.js';
|
||||
|
||||
class Close extends Interface {
|
||||
constructor() {
|
||||
super(null, 'svg');
|
||||
|
||||
const size = 90;
|
||||
|
||||
this.width = size;
|
||||
this.height = size;
|
||||
this.x = size / 2;
|
||||
this.y = size / 2;
|
||||
this.radius = size * 0.4;
|
||||
this.animatedIn = false;
|
||||
this.needsUpdate = false;
|
||||
|
||||
this.initSVG();
|
||||
}
|
||||
|
||||
initSVG() {
|
||||
this.attr({
|
||||
width: this.width,
|
||||
height: this.height
|
||||
});
|
||||
|
||||
this.circle = new Interface(null, 'svg', 'circle');
|
||||
this.circle.attr({
|
||||
cx: this.x,
|
||||
cy: this.y,
|
||||
r: this.radius
|
||||
});
|
||||
this.circle.css({
|
||||
fill: 'none',
|
||||
stroke: 'var(--ui-color)',
|
||||
strokeWidth: 1.5
|
||||
});
|
||||
this.circle.start = 0;
|
||||
this.circle.offset = -0.25;
|
||||
this.circle.progress = 0;
|
||||
this.add(this.circle);
|
||||
|
||||
this.icon = new Interface(null, 'svg', 'g');
|
||||
this.icon.attr({
|
||||
transform: `translate(${(this.width - 22) / 2}, ${(this.height - 22) / 2})`
|
||||
});
|
||||
this.icon.css({
|
||||
fill: 'none',
|
||||
stroke: 'var(--ui-color)',
|
||||
strokeWidth: 1.5
|
||||
});
|
||||
this.add(this.icon);
|
||||
|
||||
this.line1 = new Interface(null, 'svg', 'line');
|
||||
this.line1.attr({
|
||||
x1: 0,
|
||||
y1: 0,
|
||||
x2: 22,
|
||||
y2: 22
|
||||
});
|
||||
this.line1.start = 0;
|
||||
this.line1.offset = 0;
|
||||
this.line1.progress = 0;
|
||||
this.icon.add(this.line1);
|
||||
|
||||
this.line2 = new Interface(null, 'svg', 'line');
|
||||
this.line2.attr({
|
||||
x1: 22,
|
||||
y1: 0,
|
||||
x2: 0,
|
||||
y2: 22
|
||||
});
|
||||
this.line2.start = 0;
|
||||
this.line2.offset = 0;
|
||||
this.line2.progress = 0;
|
||||
this.icon.add(this.line2);
|
||||
}
|
||||
|
||||
addListeners() {
|
||||
ticker.add(this.onUpdate);
|
||||
}
|
||||
|
||||
removeListeners() {
|
||||
ticker.remove(this.onUpdate);
|
||||
}
|
||||
|
||||
// Event handlers
|
||||
|
||||
onUpdate = () => {
|
||||
if (this.needsUpdate) {
|
||||
this.update();
|
||||
}
|
||||
};
|
||||
|
||||
// Public methods
|
||||
|
||||
update = () => {
|
||||
this.circle.line();
|
||||
this.line1.line();
|
||||
this.line2.line();
|
||||
};
|
||||
|
||||
animateIn = () => {
|
||||
if (this.needsUpdate) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.animatedIn = true;
|
||||
this.needsUpdate = true;
|
||||
|
||||
this.addListeners();
|
||||
|
||||
tween(this.circle, { progress: 1 }, 1000, 'easeOutCubic', () => {
|
||||
tween(this.line1, { progress: 1 }, 400, 'easeOutCubic', () => {
|
||||
tween(this.line2, { progress: 1 }, 400, 'easeOutCubic', () => {
|
||||
this.removeListeners();
|
||||
this.needsUpdate = false;
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
animateOut = () => {
|
||||
if (this.needsUpdate) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.animatedIn = false;
|
||||
this.needsUpdate = true;
|
||||
|
||||
this.addListeners();
|
||||
|
||||
tween(this.circle, { start: 1 }, 1000, 'easeInOutCubic', () => {
|
||||
this.circle.start = 0;
|
||||
this.removeListeners();
|
||||
this.needsUpdate = false;
|
||||
}, () => {
|
||||
this.circle.progress = 1 - this.circle.start;
|
||||
this.line1.progress = this.circle.progress;
|
||||
this.line2.progress = this.circle.progress;
|
||||
});
|
||||
};
|
||||
|
||||
destroy = () => {
|
||||
this.removeListeners();
|
||||
|
||||
clearTween(this.circle);
|
||||
clearTween(this.line1);
|
||||
clearTween(this.line2);
|
||||
|
||||
return super.destroy();
|
||||
};
|
||||
}
|
||||
|
||||
class App {
|
||||
static init() {
|
||||
this.initView();
|
||||
|
||||
this.addListeners();
|
||||
}
|
||||
|
||||
static initView() {
|
||||
this.view = new Close();
|
||||
this.view.css({
|
||||
position: 'absolute',
|
||||
left: '50%',
|
||||
top: '50%',
|
||||
marginLeft: -this.view.width / 2,
|
||||
marginTop: -this.view.height / 2,
|
||||
cursor: 'pointer'
|
||||
});
|
||||
document.body.appendChild(this.view.element);
|
||||
|
||||
this.view.animateIn();
|
||||
}
|
||||
|
||||
static addListeners() {
|
||||
this.view.element.addEventListener('click', this.onClick);
|
||||
ticker.start();
|
||||
}
|
||||
|
||||
// Event handlers
|
||||
|
||||
static onClick = () => {
|
||||
if (this.view.animatedIn) {
|
||||
this.view.animateOut();
|
||||
} else {
|
||||
this.view.animateIn();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
App.init();
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
31
src/plugins/floor/lib/@alienkitty/space.js/examples/fps.html
Normal file
31
src/plugins/floor/lib/@alienkitty/space.js/examples/fps.html
Normal file
@ -0,0 +1,31 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
|
||||
|
||||
<title>FPS — Space.js</title>
|
||||
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Roboto+Mono">
|
||||
<link rel="stylesheet" href="assets/css/style.css">
|
||||
|
||||
<script type="module">
|
||||
import { UI } from '../src/index.js';
|
||||
|
||||
const ui = new UI({ fps: true });
|
||||
ui.animateIn();
|
||||
document.body.appendChild(ui.element);
|
||||
|
||||
function animate() {
|
||||
requestAnimationFrame(animate);
|
||||
|
||||
ui.update();
|
||||
}
|
||||
|
||||
requestAnimationFrame(animate);
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
@ -0,0 +1,53 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
|
||||
|
||||
<title>FPS Panel — Space.js</title>
|
||||
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Roboto+Mono">
|
||||
<link rel="stylesheet" href="assets/css/style.css">
|
||||
|
||||
<script type="module">
|
||||
import { PanelItem, UI, brightness } from '../src/index.js';
|
||||
|
||||
const ui = new UI({ fps: true });
|
||||
ui.animateIn();
|
||||
document.body.appendChild(ui.element);
|
||||
|
||||
const items = [
|
||||
{
|
||||
label: 'FPS'
|
||||
},
|
||||
{
|
||||
type: 'divider'
|
||||
},
|
||||
{
|
||||
type: 'color',
|
||||
value: getComputedStyle(document.querySelector(':root')).getPropertyValue('--bg-color').trim(),
|
||||
callback: value => {
|
||||
document.body.style.backgroundColor = `#${value.getHexString()}`;
|
||||
|
||||
ui.invert(brightness(value) > 0.6); // Light colour is inverted
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
items.forEach(data => {
|
||||
ui.addPanel(new PanelItem(data));
|
||||
});
|
||||
|
||||
function animate() {
|
||||
requestAnimationFrame(animate);
|
||||
|
||||
ui.update();
|
||||
}
|
||||
|
||||
requestAnimationFrame(animate);
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
141
src/plugins/floor/lib/@alienkitty/space.js/examples/logo.html
Executable file
141
src/plugins/floor/lib/@alienkitty/space.js/examples/logo.html
Executable file
@ -0,0 +1,141 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
|
||||
|
||||
<title>Logo — Space.js</title>
|
||||
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Roboto+Mono">
|
||||
<link rel="stylesheet" href="assets/css/style.css">
|
||||
|
||||
<script type="module">
|
||||
import { Interface, ticker } from '../src/index.js';
|
||||
|
||||
class Logo extends Interface {
|
||||
constructor() {
|
||||
super('.logo');
|
||||
|
||||
this.initHTML();
|
||||
|
||||
this.addListeners();
|
||||
this.onResize();
|
||||
}
|
||||
|
||||
initHTML() {
|
||||
this.css({
|
||||
position: 'absolute',
|
||||
left: 50,
|
||||
top: 50,
|
||||
width: 64,
|
||||
height: 64,
|
||||
cursor: 'pointer',
|
||||
webkitUserSelect: 'none',
|
||||
userSelect: 'none',
|
||||
opacity: 0
|
||||
});
|
||||
|
||||
this.image = new Interface(null, 'img');
|
||||
this.image.attr({ src: 'assets/images/alienkitty.svg' });
|
||||
this.image.css({
|
||||
width: '100%',
|
||||
height: 'auto'
|
||||
});
|
||||
this.add(this.image);
|
||||
}
|
||||
|
||||
addListeners() {
|
||||
window.addEventListener('resize', this.onResize);
|
||||
this.element.addEventListener('mouseenter', this.onHover);
|
||||
this.element.addEventListener('mouseleave', this.onHover);
|
||||
this.element.addEventListener('click', this.onClick);
|
||||
}
|
||||
|
||||
removeListeners() {
|
||||
window.removeEventListener('resize', this.onResize);
|
||||
this.element.removeEventListener('mouseenter', this.onHover);
|
||||
this.element.removeEventListener('mouseleave', this.onHover);
|
||||
this.element.removeEventListener('click', this.onClick);
|
||||
}
|
||||
|
||||
// Event handlers
|
||||
|
||||
onResize = () => {
|
||||
const width = document.documentElement.clientWidth;
|
||||
const height = document.documentElement.clientHeight;
|
||||
|
||||
if (width < height) {
|
||||
this.css({
|
||||
left: 30,
|
||||
top: 30,
|
||||
width: 40,
|
||||
height: 40
|
||||
});
|
||||
} else {
|
||||
this.css({
|
||||
left: 50,
|
||||
top: 50,
|
||||
width: 64,
|
||||
height: 64
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
onHover = ({ type }) => {
|
||||
this.clearTween();
|
||||
|
||||
if (type === 'mouseenter') {
|
||||
this.tween({ opacity: 0.6 }, 300, 'easeOutCubic');
|
||||
} else {
|
||||
this.tween({ opacity: 1 }, 300, 'easeOutCubic');
|
||||
}
|
||||
};
|
||||
|
||||
onClick = () => {
|
||||
open('https://alien.js.org/');
|
||||
};
|
||||
|
||||
// Public methods
|
||||
|
||||
animateIn = () => {
|
||||
this.tween({ opacity: 1 }, 600, 'easeInOutSine');
|
||||
};
|
||||
|
||||
destroy = () => {
|
||||
this.removeListeners();
|
||||
|
||||
return super.destroy();
|
||||
};
|
||||
}
|
||||
|
||||
class App {
|
||||
static async init() {
|
||||
this.initViews();
|
||||
|
||||
this.addListeners();
|
||||
}
|
||||
|
||||
static initViews() {
|
||||
this.logo = new Logo();
|
||||
document.body.appendChild(this.logo.element);
|
||||
}
|
||||
|
||||
static addListeners() {
|
||||
window.addEventListener('load', this.onLoad);
|
||||
ticker.start();
|
||||
}
|
||||
|
||||
// Event handlers
|
||||
|
||||
static onLoad = () => {
|
||||
this.logo.animateIn();
|
||||
};
|
||||
}
|
||||
|
||||
App.init();
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
@ -0,0 +1,160 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
|
||||
|
||||
<title>Magnetic — Space.js</title>
|
||||
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Roboto+Mono">
|
||||
<link rel="stylesheet" href="assets/css/style.css">
|
||||
|
||||
<script type="module">
|
||||
import { Interface, Magnetic, clearTween, ticker, tween } from '../src/index.js';
|
||||
|
||||
class Progress extends Interface {
|
||||
constructor() {
|
||||
super(null, 'svg');
|
||||
|
||||
const size = 90;
|
||||
|
||||
this.width = size;
|
||||
this.height = size;
|
||||
this.x = size / 2;
|
||||
this.y = size / 2;
|
||||
this.radius = size * 0.4;
|
||||
this.progress = 0;
|
||||
this.needsUpdate = false;
|
||||
|
||||
this.initSVG();
|
||||
|
||||
this.addListeners();
|
||||
}
|
||||
|
||||
initSVG() {
|
||||
this.attr({
|
||||
width: this.width,
|
||||
height: this.height
|
||||
});
|
||||
|
||||
this.circle = new Interface(null, 'svg', 'circle');
|
||||
this.circle.attr({
|
||||
cx: this.x,
|
||||
cy: this.y,
|
||||
r: this.radius
|
||||
});
|
||||
this.circle.css({
|
||||
fill: 'none',
|
||||
stroke: 'var(--ui-color)',
|
||||
strokeWidth: 1.5
|
||||
});
|
||||
this.circle.start = 0;
|
||||
this.circle.offset = -0.25;
|
||||
this.add(this.circle);
|
||||
}
|
||||
|
||||
addListeners() {
|
||||
ticker.add(this.onUpdate);
|
||||
}
|
||||
|
||||
removeListeners() {
|
||||
ticker.remove(this.onUpdate);
|
||||
}
|
||||
|
||||
// Event handlers
|
||||
|
||||
onUpdate = () => {
|
||||
if (this.needsUpdate) {
|
||||
this.update();
|
||||
}
|
||||
};
|
||||
|
||||
onProgress = ({ progress }) => {
|
||||
clearTween(this);
|
||||
|
||||
this.needsUpdate = true;
|
||||
|
||||
tween(this, { progress }, 500, 'easeOutCubic', () => {
|
||||
this.needsUpdate = false;
|
||||
|
||||
if (this.progress >= 1) {
|
||||
this.onComplete();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
onComplete = () => {
|
||||
this.removeListeners();
|
||||
|
||||
this.events.emit('complete');
|
||||
};
|
||||
|
||||
// Public methods
|
||||
|
||||
update = () => {
|
||||
this.circle.line(this.progress);
|
||||
};
|
||||
|
||||
animateOut = callback => {
|
||||
this.tween({ scale: 0.9, opacity: 0 }, 400, 'easeInCubic', callback);
|
||||
};
|
||||
|
||||
destroy = () => {
|
||||
this.removeListeners();
|
||||
|
||||
clearTween(this);
|
||||
|
||||
return super.destroy();
|
||||
};
|
||||
}
|
||||
|
||||
class App {
|
||||
static init() {
|
||||
this.initView();
|
||||
|
||||
this.addListeners();
|
||||
}
|
||||
|
||||
static initView() {
|
||||
this.view = new Progress();
|
||||
this.view.css({
|
||||
position: 'absolute',
|
||||
left: '50%',
|
||||
top: '50%',
|
||||
marginLeft: -this.view.width / 2,
|
||||
marginTop: -this.view.height / 2,
|
||||
cursor: 'pointer'
|
||||
});
|
||||
document.body.appendChild(this.view.element);
|
||||
|
||||
this.magnet = new Magnetic(this.view);
|
||||
this.view.add(this.magnet);
|
||||
|
||||
this.view.onProgress({ progress: 1 });
|
||||
}
|
||||
|
||||
static addListeners() {
|
||||
this.view.element.addEventListener('click', this.onClick);
|
||||
ticker.start();
|
||||
}
|
||||
|
||||
// Event handlers
|
||||
|
||||
static onClick = () => {
|
||||
this.view.element.removeEventListener('click', this.onClick);
|
||||
|
||||
this.magnet.disable();
|
||||
|
||||
this.view.animateOut(() => {
|
||||
this.view = this.view.destroy();
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
App.init();
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
@ -0,0 +1,58 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
|
||||
|
||||
<title>Standalone Panel — Space.js</title>
|
||||
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Roboto+Mono">
|
||||
<link rel="stylesheet" href="assets/css/style.css">
|
||||
|
||||
<style>
|
||||
.ui {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script type="module">
|
||||
import { Panel, PanelItem, UI, brightness } from '../src/index.js';
|
||||
|
||||
const ui = new UI({ fps: true });
|
||||
document.body.appendChild(ui.element);
|
||||
|
||||
const panel = new Panel();
|
||||
panel.animateIn();
|
||||
ui.add(panel);
|
||||
|
||||
const items = [
|
||||
{
|
||||
type: 'color',
|
||||
value: getComputedStyle(document.querySelector(':root')).getPropertyValue('--bg-color').trim(),
|
||||
callback: value => {
|
||||
document.body.style.backgroundColor = `#${value.getHexString()}`;
|
||||
|
||||
ui.invert(brightness(value) > 0.6); // Light colour is inverted
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
items.forEach(data => {
|
||||
panel.add(new PanelItem(data));
|
||||
});
|
||||
|
||||
function animate() {
|
||||
requestAnimationFrame(animate);
|
||||
|
||||
ui.update();
|
||||
}
|
||||
|
||||
requestAnimationFrame(animate);
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
@ -0,0 +1,155 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
|
||||
|
||||
<title>Progress — Space.js</title>
|
||||
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Roboto+Mono">
|
||||
<link rel="stylesheet" href="assets/css/style.css">
|
||||
|
||||
<script type="module">
|
||||
import { Interface, clearTween, ticker, tween } from '../src/index.js';
|
||||
|
||||
class Progress extends Interface {
|
||||
constructor() {
|
||||
super(null, 'svg');
|
||||
|
||||
const size = 90;
|
||||
|
||||
this.width = size;
|
||||
this.height = size;
|
||||
this.x = size / 2;
|
||||
this.y = size / 2;
|
||||
this.radius = size * 0.4;
|
||||
this.progress = 0;
|
||||
this.needsUpdate = false;
|
||||
|
||||
this.initSVG();
|
||||
|
||||
this.addListeners();
|
||||
}
|
||||
|
||||
initSVG() {
|
||||
this.attr({
|
||||
width: this.width,
|
||||
height: this.height
|
||||
});
|
||||
|
||||
this.circle = new Interface(null, 'svg', 'circle');
|
||||
this.circle.attr({
|
||||
cx: this.x,
|
||||
cy: this.y,
|
||||
r: this.radius
|
||||
});
|
||||
this.circle.css({
|
||||
fill: 'none',
|
||||
stroke: 'var(--ui-color)',
|
||||
strokeWidth: 1.5
|
||||
});
|
||||
this.circle.start = 0;
|
||||
this.circle.offset = -0.25;
|
||||
this.add(this.circle);
|
||||
}
|
||||
|
||||
addListeners() {
|
||||
ticker.add(this.onUpdate);
|
||||
}
|
||||
|
||||
removeListeners() {
|
||||
ticker.remove(this.onUpdate);
|
||||
}
|
||||
|
||||
// Event handlers
|
||||
|
||||
onUpdate = () => {
|
||||
if (this.needsUpdate) {
|
||||
this.update();
|
||||
}
|
||||
};
|
||||
|
||||
onProgress = ({ progress }) => {
|
||||
clearTween(this);
|
||||
|
||||
this.needsUpdate = true;
|
||||
|
||||
tween(this, { progress }, 500, 'easeOutCubic', () => {
|
||||
this.needsUpdate = false;
|
||||
|
||||
if (this.progress >= 1) {
|
||||
this.onComplete();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
onComplete = () => {
|
||||
this.removeListeners();
|
||||
|
||||
this.events.emit('complete');
|
||||
};
|
||||
|
||||
// Public methods
|
||||
|
||||
update = () => {
|
||||
this.circle.line(this.progress);
|
||||
};
|
||||
|
||||
animateOut = callback => {
|
||||
this.tween({ scale: 0.9, opacity: 0 }, 400, 'easeInCubic', callback);
|
||||
};
|
||||
|
||||
destroy = () => {
|
||||
this.removeListeners();
|
||||
|
||||
clearTween(this);
|
||||
|
||||
return super.destroy();
|
||||
};
|
||||
}
|
||||
|
||||
class App {
|
||||
static init() {
|
||||
this.initView();
|
||||
|
||||
this.addListeners();
|
||||
}
|
||||
|
||||
static initView() {
|
||||
this.view = new Progress();
|
||||
this.view.css({
|
||||
position: 'absolute',
|
||||
left: '50%',
|
||||
top: '50%',
|
||||
marginLeft: -this.view.width / 2,
|
||||
marginTop: -this.view.height / 2,
|
||||
cursor: 'pointer'
|
||||
});
|
||||
document.body.appendChild(this.view.element);
|
||||
|
||||
this.view.onProgress({ progress: 1 });
|
||||
}
|
||||
|
||||
static addListeners() {
|
||||
this.view.element.addEventListener('click', this.onClick);
|
||||
ticker.start();
|
||||
}
|
||||
|
||||
// Event handlers
|
||||
|
||||
static onClick = () => {
|
||||
this.view.element.removeEventListener('click', this.onClick);
|
||||
|
||||
this.view.animateOut(() => {
|
||||
this.view = this.view.destroy();
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
App.init();
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
@ -0,0 +1,157 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
|
||||
|
||||
<title>Progress — Space.js</title>
|
||||
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Roboto+Mono">
|
||||
<link rel="stylesheet" href="assets/css/style.css">
|
||||
|
||||
<script type="module">
|
||||
import { Interface, clearTween, degToRad, ticker, tween } from '../src/index.js';
|
||||
|
||||
class ProgressCanvas extends Interface {
|
||||
constructor() {
|
||||
super(null, 'canvas');
|
||||
|
||||
const size = 90;
|
||||
|
||||
this.width = size;
|
||||
this.height = size;
|
||||
this.x = size / 2;
|
||||
this.y = size / 2;
|
||||
this.radius = size * 0.4;
|
||||
this.startAngle = degToRad(-90);
|
||||
this.progress = 0;
|
||||
this.needsUpdate = false;
|
||||
|
||||
this.initCanvas();
|
||||
|
||||
this.addListeners();
|
||||
this.resize();
|
||||
}
|
||||
|
||||
initCanvas() {
|
||||
this.context = this.element.getContext('2d');
|
||||
}
|
||||
|
||||
addListeners() {
|
||||
ticker.add(this.onUpdate);
|
||||
}
|
||||
|
||||
removeListeners() {
|
||||
ticker.remove(this.onUpdate);
|
||||
}
|
||||
|
||||
// Event handlers
|
||||
|
||||
onUpdate = () => {
|
||||
if (this.needsUpdate) {
|
||||
this.update();
|
||||
}
|
||||
};
|
||||
|
||||
onProgress = ({ progress }) => {
|
||||
clearTween(this);
|
||||
|
||||
this.needsUpdate = true;
|
||||
|
||||
tween(this, { progress }, 500, 'easeOutCubic', () => {
|
||||
this.needsUpdate = false;
|
||||
|
||||
if (this.progress >= 1) {
|
||||
this.onComplete();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
onComplete = () => {
|
||||
this.removeListeners();
|
||||
|
||||
this.events.emit('complete');
|
||||
};
|
||||
|
||||
// Public methods
|
||||
|
||||
resize = () => {
|
||||
const dpr = 2;
|
||||
|
||||
this.element.width = Math.round(this.width * dpr);
|
||||
this.element.height = Math.round(this.height * dpr);
|
||||
this.element.style.width = this.width + 'px';
|
||||
this.element.style.height = this.height + 'px';
|
||||
this.context.scale(dpr, dpr);
|
||||
|
||||
this.context.lineWidth = 1.5;
|
||||
this.context.strokeStyle = 'rgba(255, 255, 255, 0.94)';
|
||||
|
||||
this.update();
|
||||
};
|
||||
|
||||
update = () => {
|
||||
this.context.clearRect(0, 0, this.element.width, this.element.height);
|
||||
this.context.beginPath();
|
||||
this.context.arc(this.x, this.y, this.radius, this.startAngle, this.startAngle + degToRad(360 * this.progress));
|
||||
this.context.stroke();
|
||||
};
|
||||
|
||||
animateOut = callback => {
|
||||
this.tween({ scale: 0.9, opacity: 0 }, 400, 'easeInCubic', callback);
|
||||
};
|
||||
|
||||
destroy = () => {
|
||||
this.removeListeners();
|
||||
|
||||
clearTween(this);
|
||||
|
||||
return super.destroy();
|
||||
};
|
||||
}
|
||||
|
||||
class App {
|
||||
static init() {
|
||||
this.initView();
|
||||
|
||||
this.addListeners();
|
||||
}
|
||||
|
||||
static initView() {
|
||||
this.view = new ProgressCanvas();
|
||||
this.view.css({
|
||||
position: 'absolute',
|
||||
left: '50%',
|
||||
top: '50%',
|
||||
marginLeft: -this.view.width / 2,
|
||||
marginTop: -this.view.height / 2,
|
||||
cursor: 'pointer'
|
||||
});
|
||||
document.body.appendChild(this.view.element);
|
||||
|
||||
this.view.onProgress({ progress: 1 });
|
||||
}
|
||||
|
||||
static addListeners() {
|
||||
this.view.element.addEventListener('click', this.onClick);
|
||||
ticker.start();
|
||||
}
|
||||
|
||||
// Event handlers
|
||||
|
||||
static onClick = () => {
|
||||
this.view.element.removeEventListener('click', this.onClick);
|
||||
|
||||
this.view.animateOut(() => {
|
||||
this.view = this.view.destroy();
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
App.init();
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
@ -0,0 +1,161 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
|
||||
|
||||
<title>Indeterminate Progress — Space.js</title>
|
||||
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Roboto+Mono">
|
||||
<link rel="stylesheet" href="assets/css/style.css">
|
||||
|
||||
<script type="module">
|
||||
import { Interface, clearTween, ticker, tween } from '../src/index.js';
|
||||
|
||||
class ProgressIndeterminate extends Interface {
|
||||
constructor() {
|
||||
super(null, 'svg');
|
||||
|
||||
const size = 90;
|
||||
|
||||
this.width = size;
|
||||
this.height = size;
|
||||
this.x = size / 2;
|
||||
this.y = size / 2;
|
||||
this.radius = size * 0.4;
|
||||
this.animatedIn = false;
|
||||
this.needsUpdate = false;
|
||||
|
||||
this.initSVG();
|
||||
}
|
||||
|
||||
initSVG() {
|
||||
this.attr({
|
||||
width: this.width,
|
||||
height: this.height
|
||||
});
|
||||
|
||||
this.circle = new Interface(null, 'svg', 'circle');
|
||||
this.circle.attr({
|
||||
cx: this.x,
|
||||
cy: this.y,
|
||||
r: this.radius
|
||||
});
|
||||
this.circle.css({
|
||||
fill: 'none',
|
||||
stroke: 'var(--ui-color)',
|
||||
strokeWidth: 1.5
|
||||
});
|
||||
this.circle.start = 0;
|
||||
this.circle.offset = -0.25;
|
||||
this.circle.progress = 0;
|
||||
this.add(this.circle);
|
||||
}
|
||||
|
||||
addListeners() {
|
||||
ticker.add(this.onUpdate);
|
||||
}
|
||||
|
||||
removeListeners() {
|
||||
ticker.remove(this.onUpdate);
|
||||
}
|
||||
|
||||
// Event handlers
|
||||
|
||||
onUpdate = () => {
|
||||
if (this.needsUpdate) {
|
||||
this.update();
|
||||
}
|
||||
};
|
||||
|
||||
// Public methods
|
||||
|
||||
update = () => {
|
||||
this.circle.line();
|
||||
};
|
||||
|
||||
animateIn = () => {
|
||||
this.animatedIn = true;
|
||||
this.needsUpdate = true;
|
||||
|
||||
this.addListeners();
|
||||
|
||||
const start = () => {
|
||||
tween(this.circle, { progress: 1 }, 1000, 'easeOutCubic', () => {
|
||||
tween(this.circle, { start: 1 }, 1000, 'easeInOutCubic', () => {
|
||||
this.circle.start = 0;
|
||||
this.delayedCall(500, () => {
|
||||
if (this.animatedIn) {
|
||||
start();
|
||||
} else {
|
||||
this.removeListeners();
|
||||
this.needsUpdate = false;
|
||||
}
|
||||
});
|
||||
}, () => {
|
||||
this.circle.progress = 1 - this.circle.start;
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
start();
|
||||
};
|
||||
|
||||
animateOut = () => {
|
||||
this.animatedIn = false;
|
||||
};
|
||||
|
||||
destroy = () => {
|
||||
this.removeListeners();
|
||||
|
||||
clearTween(this.circle);
|
||||
|
||||
return super.destroy();
|
||||
};
|
||||
}
|
||||
|
||||
class App {
|
||||
static init() {
|
||||
this.initView();
|
||||
|
||||
this.addListeners();
|
||||
}
|
||||
|
||||
static initView() {
|
||||
this.view = new ProgressIndeterminate();
|
||||
this.view.css({
|
||||
position: 'absolute',
|
||||
left: '50%',
|
||||
top: '50%',
|
||||
marginLeft: -this.view.width / 2,
|
||||
marginTop: -this.view.height / 2,
|
||||
cursor: 'pointer'
|
||||
});
|
||||
document.body.appendChild(this.view.element);
|
||||
|
||||
this.view.animateIn();
|
||||
}
|
||||
|
||||
static addListeners() {
|
||||
this.view.element.addEventListener('click', this.onClick);
|
||||
ticker.start();
|
||||
}
|
||||
|
||||
// Event handlers
|
||||
|
||||
static onClick = () => {
|
||||
if (this.view.needsUpdate) {
|
||||
this.view.animateOut();
|
||||
} else {
|
||||
this.view.animateIn();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
App.init();
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
331
src/plugins/floor/lib/@alienkitty/space.js/examples/styles.html
Normal file
331
src/plugins/floor/lib/@alienkitty/space.js/examples/styles.html
Normal file
@ -0,0 +1,331 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
|
||||
|
||||
<title>Styles — Space.js</title>
|
||||
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Roboto+Mono&family=Roboto:wght@300&family=Gothic+A1:wght@400;700">
|
||||
<link rel="stylesheet" href="assets/css/style.css">
|
||||
|
||||
<script type="module">
|
||||
import { Interface, shuffle, ticker } from '../src/index.js';
|
||||
|
||||
class Config {
|
||||
static BREAKPOINT = 1000;
|
||||
}
|
||||
|
||||
class Styles {
|
||||
static body = {
|
||||
fontFamily: 'Gothic A1, sans-serif',
|
||||
fontWeight: '400',
|
||||
fontSize: 13,
|
||||
lineHeight: '1.5',
|
||||
letterSpacing: 'normal'
|
||||
};
|
||||
|
||||
static h1 = {
|
||||
width: 'fit-content',
|
||||
margin: '0 0 6px -1px',
|
||||
fontFamily: 'Roboto, sans-serif',
|
||||
fontWeight: '300',
|
||||
fontSize: 23,
|
||||
lineHeight: '1.3',
|
||||
letterSpacing: 'normal',
|
||||
textTransform: 'uppercase'
|
||||
};
|
||||
|
||||
static content = {
|
||||
width: 'fit-content',
|
||||
margin: '6px 0',
|
||||
...this.body
|
||||
};
|
||||
}
|
||||
|
||||
class DetailsLink extends Interface {
|
||||
constructor(title, link) {
|
||||
super('.link', 'a');
|
||||
|
||||
this.title = title;
|
||||
this.link = link;
|
||||
|
||||
this.initHTML();
|
||||
|
||||
this.addListeners();
|
||||
}
|
||||
|
||||
initHTML() {
|
||||
this.css({
|
||||
...Styles.body,
|
||||
lineHeight: 22
|
||||
});
|
||||
this.attr({ href: this.link });
|
||||
|
||||
this.text = new Interface('.text');
|
||||
this.text.css({
|
||||
display: 'inline-block'
|
||||
});
|
||||
this.text.text(this.title);
|
||||
this.add(this.text);
|
||||
|
||||
this.line = new Interface('.line');
|
||||
this.line.css({
|
||||
display: 'inline-block',
|
||||
fontWeight: '700',
|
||||
verticalAlign: 'middle'
|
||||
});
|
||||
this.line.html(' ―');
|
||||
this.add(this.line);
|
||||
}
|
||||
|
||||
addListeners() {
|
||||
this.element.addEventListener('mouseenter', this.onHover);
|
||||
this.element.addEventListener('mouseleave', this.onHover);
|
||||
}
|
||||
|
||||
// Event handlers
|
||||
|
||||
onHover = ({ type }) => {
|
||||
this.line.tween({ x: type === 'mouseenter' ? 10 : 0 }, 200, 'easeOutCubic');
|
||||
};
|
||||
}
|
||||
|
||||
class DetailsTitle extends Interface {
|
||||
constructor(title) {
|
||||
super('.title', 'h1');
|
||||
|
||||
this.title = title;
|
||||
this.letters = [];
|
||||
|
||||
this.initHTML();
|
||||
this.initText();
|
||||
}
|
||||
|
||||
initHTML() {
|
||||
this.css({
|
||||
...Styles.h1
|
||||
});
|
||||
}
|
||||
|
||||
initText() {
|
||||
const split = this.title.split('');
|
||||
|
||||
split.forEach(str => {
|
||||
if (str === ' ') {
|
||||
str = ' ';
|
||||
}
|
||||
|
||||
const letter = new Interface(null, 'span');
|
||||
letter.css({ display: 'inline-block' });
|
||||
letter.html(str);
|
||||
this.add(letter);
|
||||
|
||||
this.letters.push(letter);
|
||||
});
|
||||
}
|
||||
|
||||
// Public methods
|
||||
|
||||
animateIn = () => {
|
||||
shuffle(this.letters);
|
||||
|
||||
const underscores = this.letters.filter(letter => letter === '_');
|
||||
|
||||
underscores.forEach((letter, i) => {
|
||||
letter.css({ opacity: 0 }).tween({ opacity: 1 }, 2000, 'easeOutCubic', i * 15);
|
||||
});
|
||||
|
||||
const letters = this.letters.filter(letter => letter !== '_').slice(0, 2);
|
||||
|
||||
letters.forEach((letter, i) => {
|
||||
letter.css({ opacity: 0 }).tween({ opacity: 1 }, 2000, 'easeOutCubic', 100 + i * 15);
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
class Details extends Interface {
|
||||
constructor() {
|
||||
super('.details');
|
||||
|
||||
this.texts = [];
|
||||
|
||||
this.initHTML();
|
||||
this.initViews();
|
||||
|
||||
this.addListeners();
|
||||
this.onResize();
|
||||
}
|
||||
|
||||
initHTML() {
|
||||
this.invisible();
|
||||
this.css({
|
||||
position: 'absolute',
|
||||
left: 0,
|
||||
top: 0,
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
pointerEvents: 'none',
|
||||
opacity: 0
|
||||
});
|
||||
|
||||
this.container = new Interface('.container');
|
||||
this.container.css({
|
||||
width: 400,
|
||||
margin: '10% 10% 13%'
|
||||
});
|
||||
this.add(this.container);
|
||||
}
|
||||
|
||||
initViews() {
|
||||
this.title = new DetailsTitle('Lorem ipsum'.replace(/[\s.]+/g, '_'));
|
||||
this.title.css({
|
||||
width: 'fit-content'
|
||||
});
|
||||
this.container.add(this.title);
|
||||
this.texts.push(this.title);
|
||||
|
||||
this.text = new Interface('.text', 'p');
|
||||
this.text.css({
|
||||
width: 'fit-content',
|
||||
...Styles.content
|
||||
});
|
||||
this.text.html('Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.');
|
||||
this.container.add(this.text);
|
||||
this.texts.push(this.text);
|
||||
|
||||
const items = [
|
||||
{
|
||||
title: 'Lorem ipsum',
|
||||
link: 'https://en.wikipedia.org/wiki/Lorem_ipsum'
|
||||
}
|
||||
];
|
||||
|
||||
items.forEach(data => {
|
||||
const link = new DetailsLink(data.title, data.link);
|
||||
link.css({
|
||||
display: 'block',
|
||||
width: 'fit-content'
|
||||
});
|
||||
this.container.add(link);
|
||||
this.texts.push(link);
|
||||
});
|
||||
}
|
||||
|
||||
addListeners() {
|
||||
window.addEventListener('resize', this.onResize);
|
||||
}
|
||||
|
||||
// Event handlers
|
||||
|
||||
onResize = () => {
|
||||
if (document.documentElement.clientWidth < Config.BREAKPOINT) {
|
||||
this.css({ display: '' });
|
||||
|
||||
this.container.css({
|
||||
width: '',
|
||||
margin: '24px 20px 0'
|
||||
});
|
||||
} else {
|
||||
this.css({ display: 'flex' });
|
||||
|
||||
this.container.css({
|
||||
width: 400,
|
||||
margin: '10% 10% 13%'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// Public methods
|
||||
|
||||
animateIn = () => {
|
||||
this.visible();
|
||||
this.css({
|
||||
pointerEvents: 'auto',
|
||||
opacity: 1
|
||||
});
|
||||
|
||||
const duration = 2000;
|
||||
const stagger = 175;
|
||||
|
||||
this.texts.forEach((text, i) => {
|
||||
const delay = i === 0 ? 0 : duration;
|
||||
|
||||
text.css({ opacity: 0 }).tween({ opacity: 1 }, duration, 'easeOutCubic', delay + i * stagger);
|
||||
});
|
||||
|
||||
this.title.animateIn();
|
||||
};
|
||||
}
|
||||
|
||||
class UI extends Interface {
|
||||
constructor() {
|
||||
super('.ui');
|
||||
|
||||
this.initHTML();
|
||||
this.initViews();
|
||||
|
||||
this.addListeners();
|
||||
}
|
||||
|
||||
initHTML() {
|
||||
this.css({
|
||||
position: 'fixed',
|
||||
left: 0,
|
||||
top: 0,
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
pointerEvents: 'none'
|
||||
});
|
||||
}
|
||||
|
||||
initViews() {
|
||||
this.details = new Details();
|
||||
this.add(this.details);
|
||||
}
|
||||
|
||||
addListeners() {
|
||||
}
|
||||
|
||||
// Event handlers
|
||||
|
||||
// Public methods
|
||||
|
||||
animateIn = () => {
|
||||
this.details.animateIn();
|
||||
};
|
||||
}
|
||||
|
||||
class App {
|
||||
static async init() {
|
||||
this.initViews();
|
||||
|
||||
this.addListeners();
|
||||
}
|
||||
|
||||
static initViews() {
|
||||
this.ui = new UI();
|
||||
document.body.appendChild(this.ui.element);
|
||||
}
|
||||
|
||||
static addListeners() {
|
||||
window.addEventListener('load', this.onLoad);
|
||||
ticker.start();
|
||||
}
|
||||
|
||||
// Event handlers
|
||||
|
||||
static onLoad = () => {
|
||||
this.ui.animateIn();
|
||||
};
|
||||
}
|
||||
|
||||
App.init();
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
@ -0,0 +1,31 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
|
||||
|
||||
<title>Sound — Space.js</title>
|
||||
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Roboto+Mono">
|
||||
<link rel="stylesheet" href="assets/css/style.css">
|
||||
|
||||
<script type="module">
|
||||
import { BufferLoader, WebAudio } from '../src/index.js';
|
||||
|
||||
const bufferLoader = new BufferLoader();
|
||||
await bufferLoader.loadAllAsync(['assets/sounds/gong.mp3']);
|
||||
WebAudio.init({ sampleRate: 48000 });
|
||||
WebAudio.load(bufferLoader.files);
|
||||
|
||||
const gong = WebAudio.get('gong');
|
||||
gong.gain.set(0.5);
|
||||
|
||||
document.addEventListener('pointerdown', () => {
|
||||
gong.play();
|
||||
});
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
@ -0,0 +1,31 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
|
||||
|
||||
<title>Stream — Space.js</title>
|
||||
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Roboto+Mono">
|
||||
<link rel="stylesheet" href="assets/css/style.css">
|
||||
|
||||
<script type="module">
|
||||
import { WebAudio } from '../src/index.js';
|
||||
|
||||
WebAudio.init({ sampleRate: 48000 });
|
||||
|
||||
// Shoutcast streams append a semicolon (;) to the URL
|
||||
WebAudio.load({ protonradio: 'https://shoutcast.protonradio.com/;' });
|
||||
|
||||
const protonradio = WebAudio.get('protonradio');
|
||||
protonradio.gain.set(1);
|
||||
|
||||
document.addEventListener('pointerdown', () => {
|
||||
protonradio.play();
|
||||
});
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
@ -0,0 +1,29 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
|
||||
|
||||
<title>Tween — Space.js</title>
|
||||
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Roboto+Mono">
|
||||
<link rel="stylesheet" href="assets/css/style.css">
|
||||
|
||||
<script type="module">
|
||||
import { ticker, tween } from '../src/index.js';
|
||||
|
||||
ticker.start();
|
||||
|
||||
const data = {
|
||||
radius: 0
|
||||
};
|
||||
|
||||
tween(data, { radius: 24, spring: 1.2, damping: 0.4 }, 1000, 'easeOutElastic', null, () => {
|
||||
console.log(data.radius);
|
||||
});
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
@ -0,0 +1,233 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
|
||||
|
||||
<title>Canvas Thread — Space.js</title>
|
||||
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Roboto+Mono">
|
||||
<link rel="stylesheet" href="assets/css/style.css">
|
||||
|
||||
<script type="module">
|
||||
import { Thread, ticker } from '../src/index.js';
|
||||
|
||||
// Based on https://codepen.io/zepha/pen/VpXvBJ
|
||||
|
||||
class CanvasNoise {
|
||||
constructor(params) {
|
||||
this.params = params;
|
||||
|
||||
this.initParameters();
|
||||
this.initCanvas();
|
||||
}
|
||||
|
||||
initParameters() {
|
||||
const defaults = {
|
||||
width: 1,
|
||||
height: 1,
|
||||
tileSize: 250,
|
||||
monochrome: true
|
||||
};
|
||||
|
||||
this.params = Object.assign(defaults, this.params);
|
||||
}
|
||||
|
||||
initCanvas() {
|
||||
this.canvas = this.params.canvas;
|
||||
this.canvas.width = this.params.width;
|
||||
this.canvas.height = this.params.height;
|
||||
this.context = this.canvas.getContext('2d');
|
||||
|
||||
this.tile = typeof window === 'undefined' ? new OffscreenCanvas(this.params.tileSize, this.params.tileSize) : document.createElement('canvas');
|
||||
this.tile.width = this.params.tileSize;
|
||||
this.tile.height = this.params.tileSize;
|
||||
this.tileContext = this.tile.getContext('2d');
|
||||
}
|
||||
|
||||
// Public methods
|
||||
|
||||
resize = (width, height, dpr) => {
|
||||
this.canvas.width = Math.round(width * dpr);
|
||||
this.canvas.height = Math.round(height * dpr);
|
||||
|
||||
this.tile.width = Math.round(this.params.tileSize * dpr);
|
||||
this.tile.height = Math.round(this.params.tileSize * dpr);
|
||||
|
||||
this.width = this.canvas.width / this.tile.width + 1; // One extra tile for row offset
|
||||
this.height = this.canvas.height / this.tile.height;
|
||||
|
||||
this.update();
|
||||
};
|
||||
|
||||
update = () => {
|
||||
const pixels = new ImageData(this.tile.width, this.tile.height);
|
||||
|
||||
for (let i = 0, l = pixels.data.length; i < l; i += 4) {
|
||||
const rand = 255 * Math.random();
|
||||
|
||||
pixels.data[i] = this.params.monochrome ? rand : 255 * Math.random();
|
||||
pixels.data[i + 1] = this.params.monochrome ? rand : 255 * Math.random();
|
||||
pixels.data[i + 2] = this.params.monochrome ? rand : 255 * Math.random();
|
||||
pixels.data[i + 3] = 255;
|
||||
}
|
||||
|
||||
this.tileContext.putImageData(pixels, 0, 0);
|
||||
|
||||
for (let x = 0, xl = this.width; x < xl; x++) {
|
||||
for (let y = 0, yl = this.height; y < yl; y++) {
|
||||
this.context.drawImage(this.tile, x * this.tile.width - (y % 2 === 0 ? this.tile.width / 2 : 0), y * this.tile.height, this.tile.width, this.tile.height);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
class CanvasNoiseThread {
|
||||
constructor() {
|
||||
this.addListeners();
|
||||
}
|
||||
|
||||
addListeners() {
|
||||
addEventListener('message', this.onMessage);
|
||||
|
||||
ticker.start();
|
||||
}
|
||||
|
||||
// Event handlers
|
||||
|
||||
onMessage = ({ data }) => {
|
||||
this[data.message.fn].call(this, data.message);
|
||||
};
|
||||
|
||||
onUpdate = () => {
|
||||
this.noise.update();
|
||||
};
|
||||
|
||||
// Public methods
|
||||
|
||||
init = ({ params }) => {
|
||||
this.noise = new CanvasNoise(params);
|
||||
};
|
||||
|
||||
resize = ({ width, height, dpr }) => {
|
||||
this.noise.resize(width, height, dpr);
|
||||
};
|
||||
|
||||
start = ({ fps }) => {
|
||||
ticker.add(this.onUpdate, fps);
|
||||
};
|
||||
|
||||
stop = () => {
|
||||
ticker.remove(this.onUpdate);
|
||||
};
|
||||
}
|
||||
|
||||
class CanvasNoiseController {
|
||||
static init(params) {
|
||||
this.params = params;
|
||||
|
||||
this.initThread();
|
||||
}
|
||||
|
||||
static initThread() {
|
||||
if ('transferControlToOffscreen' in this.params.canvas && !/firefox/i.test(navigator.userAgent)) {
|
||||
this.thread = new Thread({
|
||||
imports: [
|
||||
['../src/index.js', 'ticker']
|
||||
],
|
||||
classes: [CanvasNoise],
|
||||
controller: [CanvasNoiseThread, 'init', 'resize', 'start', 'stop']
|
||||
});
|
||||
|
||||
this.element = this.params.canvas;
|
||||
this.params.canvas = this.element.transferControlToOffscreen();
|
||||
|
||||
this.thread.init({ params: this.params, buffer: [this.params.canvas] });
|
||||
} else {
|
||||
ticker.start();
|
||||
|
||||
this.noise = new CanvasNoise(this.params);
|
||||
}
|
||||
}
|
||||
|
||||
// Event handlers
|
||||
|
||||
static onUpdate = () => {
|
||||
this.noise.update();
|
||||
};
|
||||
|
||||
// Public methods
|
||||
|
||||
static resize = (width, height, dpr) => {
|
||||
if (this.thread) {
|
||||
this.thread.resize({ width, height, dpr });
|
||||
} else {
|
||||
this.noise.resize(width, height, dpr);
|
||||
}
|
||||
};
|
||||
|
||||
static start = () => {
|
||||
if (this.thread) {
|
||||
this.thread.start({ fps: 20 });
|
||||
} else {
|
||||
ticker.add(this.onUpdate, 20);
|
||||
}
|
||||
};
|
||||
|
||||
static stop = () => {
|
||||
if (this.thread) {
|
||||
this.thread.stop();
|
||||
} else {
|
||||
ticker.remove(this.onUpdate);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
class App {
|
||||
static async init() {
|
||||
this.initCanvas();
|
||||
this.initControllers();
|
||||
|
||||
this.addListeners();
|
||||
this.onResize();
|
||||
}
|
||||
|
||||
static initCanvas() {
|
||||
this.canvas = document.createElement('canvas');
|
||||
document.body.appendChild(this.canvas);
|
||||
}
|
||||
|
||||
static initControllers() {
|
||||
CanvasNoiseController.init({ canvas: this.canvas });
|
||||
}
|
||||
|
||||
static addListeners() {
|
||||
window.addEventListener('resize', this.onResize);
|
||||
window.addEventListener('load', this.onLoad);
|
||||
}
|
||||
|
||||
// Event handlers
|
||||
|
||||
static onResize = () => {
|
||||
const width = document.documentElement.clientWidth;
|
||||
const height = document.documentElement.clientHeight;
|
||||
const dpr = window.devicePixelRatio;
|
||||
|
||||
this.canvas.style.width = width + 'px';
|
||||
this.canvas.style.height = height + 'px';
|
||||
|
||||
CanvasNoiseController.resize(width, height, dpr);
|
||||
};
|
||||
|
||||
static onLoad = () => {
|
||||
CanvasNoiseController.start();
|
||||
};
|
||||
}
|
||||
|
||||
App.init();
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
@ -0,0 +1,146 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
|
||||
|
||||
<title>Lights — Space.js</title>
|
||||
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Roboto+Mono">
|
||||
<link rel="stylesheet" href="../assets/css/style.css">
|
||||
|
||||
<script type="importmap">
|
||||
{
|
||||
"imports": {
|
||||
"three": "https://unpkg.com/three/build/three.module.js",
|
||||
"three/addons/": "https://unpkg.com/three/examples/jsm/"
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<script type="module">
|
||||
import { AmbientLight, BoxGeometry, Color, DirectionalLight, HemisphereLight, Mesh, MeshStandardMaterial, PerspectiveCamera, PointLight, RectAreaLight, Scene, SpotLight, WebGLRenderer } from 'three';
|
||||
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
|
||||
import { RectAreaLightUniformsLib } from 'three/addons/lights/RectAreaLightUniformsLib.js';
|
||||
|
||||
// init
|
||||
|
||||
const renderer = new WebGLRenderer({ antialias: true });
|
||||
renderer.setPixelRatio(window.devicePixelRatio);
|
||||
renderer.setSize(window.innerWidth, window.innerHeight);
|
||||
document.body.appendChild(renderer.domElement);
|
||||
|
||||
const scene = new Scene();
|
||||
scene.background = new Color(0x0e0e0e);
|
||||
|
||||
const camera = new PerspectiveCamera(35, window.innerWidth / window.innerHeight, 1, 2000);
|
||||
camera.position.z = 10;
|
||||
|
||||
const controls = new OrbitControls(camera, renderer.domElement);
|
||||
controls.enableDamping = true;
|
||||
|
||||
// lights
|
||||
|
||||
scene.add(new AmbientLight(0xffffff, 3));
|
||||
|
||||
scene.add(new HemisphereLight(0xffffff, 0x888888, 3));
|
||||
|
||||
const directionalLight = new DirectionalLight(0xffffff, 2);
|
||||
directionalLight.position.set(5, 5, 5);
|
||||
scene.add(directionalLight);
|
||||
|
||||
const pointLight = new PointLight();
|
||||
scene.add(pointLight);
|
||||
|
||||
const spotLight = new SpotLight();
|
||||
spotLight.angle = 0.3;
|
||||
spotLight.penumbra = 0.2;
|
||||
spotLight.decay = 2;
|
||||
spotLight.position.set(-5, 5, 5);
|
||||
scene.add(spotLight);
|
||||
|
||||
RectAreaLightUniformsLib.init();
|
||||
|
||||
const rectLight1 = new RectAreaLight(0xff0000, 5, 4, 10);
|
||||
rectLight1.position.set(-5, 5, -5);
|
||||
rectLight1.lookAt(0, 0, 0);
|
||||
scene.add(rectLight1);
|
||||
|
||||
const rectLight2 = new RectAreaLight(0x00ff00, 5, 4, 10);
|
||||
rectLight2.position.set(0, 5, -5);
|
||||
rectLight2.lookAt(0, 0, 0);
|
||||
scene.add(rectLight2);
|
||||
|
||||
const rectLight3 = new RectAreaLight(0x0000ff, 5, 4, 10);
|
||||
rectLight3.position.set(5, 5, -5);
|
||||
rectLight3.lookAt(0, 0, 0);
|
||||
scene.add(rectLight3);
|
||||
|
||||
// mesh
|
||||
|
||||
const geometry = new BoxGeometry();
|
||||
const material = new MeshStandardMaterial({ color: 0x595959, metalness: 0.7, roughness: 0.7 });
|
||||
|
||||
const mesh = new Mesh(geometry, material);
|
||||
scene.add(mesh);
|
||||
|
||||
// panel
|
||||
|
||||
import { LightPanelController, PanelItem, UI } from '../../src/three.js';
|
||||
|
||||
const ui = new UI({ fps: true });
|
||||
ui.animateIn();
|
||||
document.body.appendChild(ui.element);
|
||||
|
||||
const items = [
|
||||
{
|
||||
label: 'FPS'
|
||||
}
|
||||
];
|
||||
|
||||
items.forEach(data => {
|
||||
ui.addPanel(new PanelItem(data));
|
||||
});
|
||||
|
||||
LightPanelController.init(scene, ui);
|
||||
|
||||
// animation
|
||||
|
||||
function animate(time) {
|
||||
requestAnimationFrame(animate);
|
||||
|
||||
time = time * 0.001; // seconds
|
||||
|
||||
mesh.rotation.x = time / 2;
|
||||
mesh.rotation.y = time;
|
||||
|
||||
pointLight.position.x = Math.sin(time * 1.7) * 2;
|
||||
pointLight.position.y = Math.cos(time * 1.5) * 2;
|
||||
pointLight.position.z = Math.cos(time * 1.3) * 2;
|
||||
|
||||
controls.update();
|
||||
|
||||
renderer.render(scene, camera);
|
||||
|
||||
LightPanelController.update();
|
||||
ui.update();
|
||||
}
|
||||
|
||||
requestAnimationFrame(animate);
|
||||
|
||||
// resize
|
||||
|
||||
window.addEventListener('resize', onWindowResize);
|
||||
|
||||
function onWindowResize() {
|
||||
camera.aspect = window.innerWidth / window.innerHeight;
|
||||
camera.updateProjectionMatrix();
|
||||
|
||||
renderer.setSize(window.innerWidth, window.innerHeight);
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
@ -0,0 +1,105 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
|
||||
|
||||
<title>Materials — Space.js</title>
|
||||
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Roboto+Mono">
|
||||
<link rel="stylesheet" href="../assets/css/style.css">
|
||||
|
||||
<script type="importmap">
|
||||
{
|
||||
"imports": {
|
||||
"three": "https://unpkg.com/three/build/three.module.js",
|
||||
"three/addons/": "https://unpkg.com/three/examples/jsm/"
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<script type="module">
|
||||
import { BoxGeometry, Color, HemisphereLight, Mesh, MeshNormalMaterial, PerspectiveCamera, Scene, WebGLRenderer } from 'three';
|
||||
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
|
||||
|
||||
// init
|
||||
|
||||
const renderer = new WebGLRenderer({ antialias: true });
|
||||
renderer.setPixelRatio(window.devicePixelRatio);
|
||||
renderer.setSize(window.innerWidth, window.innerHeight);
|
||||
document.body.appendChild(renderer.domElement);
|
||||
|
||||
const scene = new Scene();
|
||||
scene.background = new Color(0x0e0e0e);
|
||||
|
||||
const camera = new PerspectiveCamera(35, window.innerWidth / window.innerHeight, 1, 2000);
|
||||
camera.position.z = 10;
|
||||
|
||||
const controls = new OrbitControls(camera, renderer.domElement);
|
||||
controls.enableDamping = true;
|
||||
|
||||
// lights
|
||||
|
||||
scene.add(new HemisphereLight(0xffffff, 0x888888, 3));
|
||||
|
||||
// mesh
|
||||
|
||||
const geometry = new BoxGeometry();
|
||||
geometry.computeTangents();
|
||||
|
||||
const material = new MeshNormalMaterial();
|
||||
|
||||
const mesh = new Mesh(geometry, material);
|
||||
scene.add(mesh);
|
||||
|
||||
// panel
|
||||
|
||||
import { MaterialPanelController, Point3D, UI } from '../../src/three.js';
|
||||
|
||||
const ui = new UI({ fps: true });
|
||||
ui.animateIn();
|
||||
document.body.appendChild(ui.element);
|
||||
|
||||
Point3D.init(scene, camera);
|
||||
|
||||
const point = new Point3D(mesh);
|
||||
scene.add(point);
|
||||
|
||||
MaterialPanelController.init(mesh, point);
|
||||
|
||||
// animation
|
||||
|
||||
function animate(time) {
|
||||
requestAnimationFrame(animate);
|
||||
|
||||
time = time * 0.001; // seconds
|
||||
|
||||
mesh.rotation.x = time / 2;
|
||||
mesh.rotation.y = time;
|
||||
|
||||
controls.update();
|
||||
|
||||
renderer.render(scene, camera);
|
||||
|
||||
Point3D.update(time);
|
||||
ui.update();
|
||||
}
|
||||
|
||||
requestAnimationFrame(animate);
|
||||
|
||||
// resize
|
||||
|
||||
window.addEventListener('resize', onWindowResize);
|
||||
|
||||
function onWindowResize() {
|
||||
camera.aspect = window.innerWidth / window.innerHeight;
|
||||
camera.updateProjectionMatrix();
|
||||
|
||||
renderer.setSize(window.innerWidth, window.innerHeight);
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
@ -0,0 +1,138 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
|
||||
|
||||
<title>Materials Instancing — Space.js</title>
|
||||
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Roboto+Mono">
|
||||
<link rel="stylesheet" href="../assets/css/style.css">
|
||||
|
||||
<script type="importmap">
|
||||
{
|
||||
"imports": {
|
||||
"three": "https://unpkg.com/three/build/three.module.js",
|
||||
"three/addons/": "https://unpkg.com/three/examples/jsm/"
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<script type="module">
|
||||
import { Color, HemisphereLight, IcosahedronGeometry, InstancedMesh, Matrix4, MeshPhongMaterial, PerspectiveCamera, Scene, WebGLRenderer } from 'three';
|
||||
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
|
||||
import { mergeVertices } from 'three/addons/utils/BufferGeometryUtils.js';
|
||||
|
||||
const DEBUG = /[?&]debug/.test(location.search);
|
||||
|
||||
const amount = parseInt(location.search.slice(1), 10) || 3;
|
||||
const count = Math.pow(amount, 3);
|
||||
|
||||
const color = new Color();
|
||||
|
||||
// init
|
||||
|
||||
const renderer = new WebGLRenderer({ antialias: true });
|
||||
renderer.setPixelRatio(window.devicePixelRatio);
|
||||
renderer.setSize(window.innerWidth, window.innerHeight);
|
||||
document.body.appendChild(renderer.domElement);
|
||||
|
||||
const scene = new Scene();
|
||||
scene.background = new Color(0x0e0e0e);
|
||||
|
||||
const camera = new PerspectiveCamera(60, window.innerWidth / window.innerHeight, 1, 2000);
|
||||
camera.position.set(amount, amount, amount);
|
||||
camera.lookAt(scene.position);
|
||||
|
||||
const controls = new OrbitControls(camera, renderer.domElement);
|
||||
controls.enableDamping = true;
|
||||
controls.enableZoom = false;
|
||||
controls.enablePan = false;
|
||||
|
||||
// lights
|
||||
|
||||
scene.add(new HemisphereLight(0xffffff, 0x888888, 3));
|
||||
|
||||
// mesh
|
||||
|
||||
let geometry = new IcosahedronGeometry(0.5, 12);
|
||||
|
||||
// Convert to indexed geometry
|
||||
geometry = mergeVertices(geometry);
|
||||
|
||||
geometry.computeTangents();
|
||||
|
||||
const material = new MeshPhongMaterial();
|
||||
|
||||
const mesh = new InstancedMesh(geometry, material, count);
|
||||
|
||||
let i = 0;
|
||||
const offset = (amount - 1) / 2;
|
||||
|
||||
const matrix = new Matrix4();
|
||||
|
||||
for (let x = 0; x < amount; x++) {
|
||||
for (let y = 0; y < amount; y++) {
|
||||
for (let z = 0; z < amount; z++) {
|
||||
matrix.setPosition(offset - x, offset - y, offset - z);
|
||||
|
||||
mesh.setMatrixAt(i, matrix);
|
||||
mesh.setColorAt(i, color);
|
||||
|
||||
i++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
scene.add(mesh);
|
||||
|
||||
// panel
|
||||
|
||||
import { MaterialPanelController, Point3D, UI } from '../../src/three.js';
|
||||
|
||||
const ui = new UI({ fps: true });
|
||||
ui.animateIn();
|
||||
document.body.appendChild(ui.element);
|
||||
|
||||
Point3D.init(scene, camera, {
|
||||
debug: DEBUG
|
||||
});
|
||||
|
||||
const point = new Point3D(mesh);
|
||||
scene.add(point);
|
||||
|
||||
MaterialPanelController.init(mesh, point);
|
||||
|
||||
// animation
|
||||
|
||||
function animate(time) {
|
||||
requestAnimationFrame(animate);
|
||||
|
||||
time = time * 0.001; // seconds
|
||||
|
||||
controls.update();
|
||||
|
||||
renderer.render(scene, camera);
|
||||
|
||||
Point3D.update(time);
|
||||
ui.update();
|
||||
}
|
||||
|
||||
requestAnimationFrame(animate);
|
||||
|
||||
// resize
|
||||
|
||||
window.addEventListener('resize', onWindowResize);
|
||||
|
||||
function onWindowResize() {
|
||||
camera.aspect = window.innerWidth / window.innerHeight;
|
||||
camera.updateProjectionMatrix();
|
||||
|
||||
renderer.setSize(window.innerWidth, window.innerHeight);
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user