mirror of
https://github.com/troisjs/trois.git
synced 2024-11-24 04:12:02 +08:00
commit
fda1ab4912
32
.eslintrc.js
32
.eslintrc.js
@ -1,22 +1,28 @@
|
|||||||
/* eslint-disable quote-props */
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
root: true,
|
||||||
env: {
|
env: {
|
||||||
browser: true,
|
browser: true,
|
||||||
es2020: true,
|
es2020: true,
|
||||||
|
node: true,
|
||||||
},
|
},
|
||||||
extends: [
|
extends: [
|
||||||
'plugin:vue/essential',
|
'plugin:vue/essential',
|
||||||
|
'@vue/standard',
|
||||||
|
'@vue/typescript/recommended',
|
||||||
'standard',
|
'standard',
|
||||||
],
|
],
|
||||||
|
parser: 'vue-eslint-parser',
|
||||||
parserOptions: {
|
parserOptions: {
|
||||||
ecmaVersion: 12,
|
parser: '@typescript-eslint/parser',
|
||||||
sourceType: 'module',
|
|
||||||
},
|
},
|
||||||
plugins: [
|
// parserOptions: {
|
||||||
'vue',
|
// ecmaVersion: 2020,
|
||||||
],
|
// },
|
||||||
rules: {
|
rules: {
|
||||||
'semi': [2, 'always'],
|
'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
|
||||||
|
'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
|
||||||
|
|
||||||
|
'semi': ['error', 'never'],
|
||||||
'space-before-function-paren': 'off',
|
'space-before-function-paren': 'off',
|
||||||
'one-var': 'off',
|
'one-var': 'off',
|
||||||
'quotes': 'off',
|
'quotes': 'off',
|
||||||
@ -36,5 +42,15 @@ module.exports = {
|
|||||||
'object-property-newline': 'off',
|
'object-property-newline': 'off',
|
||||||
'eqeqeq': 'warn',
|
'eqeqeq': 'warn',
|
||||||
'no-multiple-empty-lines': 'off',
|
'no-multiple-empty-lines': 'off',
|
||||||
|
|
||||||
|
'@typescript-eslint/ban-ts-comment': 'warn',
|
||||||
|
// '@typescript-eslint/ban-ts-comment': ['warn', {
|
||||||
|
// 'ts-ignore': 'allow-with-description',
|
||||||
|
// }],
|
||||||
|
// 'vue/valid-template-root': 'off',
|
||||||
|
'vue/no-multiple-template-root': 'off',
|
||||||
|
|
||||||
|
'no-empty-function': 'off',
|
||||||
|
'@typescript-eslint/no-empty-function': ['off'],
|
||||||
},
|
},
|
||||||
};
|
}
|
||||||
|
3
.vscode/settings.json
vendored
Normal file
3
.vscode/settings.json
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"volar.tsPlugin": true
|
||||||
|
}
|
13
api-extractor.json
Normal file
13
api-extractor.json
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"mainEntryPointFilePath": "./types/export.d.ts",
|
||||||
|
"apiReport": {
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
"docModel": {
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
"dtsRollup": {
|
||||||
|
"enabled": true,
|
||||||
|
"publicTrimmedFilePath": "./dist/trois.d.ts"
|
||||||
|
}
|
||||||
|
}
|
@ -7,6 +7,6 @@
|
|||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="app"></div>
|
<div id="app"></div>
|
||||||
<script type="module" src="/src/main.js"></script>
|
<script type="module" src="/src/main.ts"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
20
package.json
20
package.json
@ -3,33 +3,43 @@
|
|||||||
"version": "0.2.3",
|
"version": "0.2.3",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
"build": "vite build",
|
"build": "vue-tsc --noEmit && vite build",
|
||||||
"rollup": "rollup -c"
|
"rollup": "rollup -c"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@microsoft/api-extractor": "^7.14.0",
|
||||||
"@rollup/plugin-buble": "^0.21.3",
|
"@rollup/plugin-buble": "^0.21.3",
|
||||||
"@rollup/plugin-replace": "^2.3.3",
|
"@rollup/plugin-replace": "^2.3.3",
|
||||||
|
"@types/three": "^0.127.1",
|
||||||
|
"@typescript-eslint/eslint-plugin": "^4.22.0",
|
||||||
|
"@typescript-eslint/parser": "^4.22.0",
|
||||||
"@vitejs/plugin-vue": "^1.2.1",
|
"@vitejs/plugin-vue": "^1.2.1",
|
||||||
"@vue/compiler-sfc": "^3.0.11",
|
"@vue/compiler-sfc": "^3.0.11",
|
||||||
|
"@vue/eslint-config-standard": "^5.1.2",
|
||||||
|
"@vue/eslint-config-typescript": "^7.0.0",
|
||||||
"cannon": "^0.6.2",
|
"cannon": "^0.6.2",
|
||||||
"eslint": "^7.7.0",
|
"eslint": "^7.7.0",
|
||||||
"eslint-config-airbnb-base": "^14.2.0",
|
|
||||||
"eslint-config-standard": "^14.1.1",
|
|
||||||
"eslint-plugin-import": "^2.22.0",
|
"eslint-plugin-import": "^2.22.0",
|
||||||
"eslint-plugin-node": "^11.1.0",
|
"eslint-plugin-node": "^11.1.0",
|
||||||
"eslint-plugin-promise": "^4.2.1",
|
"eslint-plugin-promise": "^4.2.1",
|
||||||
"eslint-plugin-standard": "^4.0.1",
|
"eslint-plugin-standard": "^4.0.1",
|
||||||
"eslint-plugin-vue": "^6.2.2",
|
"eslint-plugin-vue": "^7.9.0",
|
||||||
"gsap": "^3.5.1",
|
"gsap": "^3.5.1",
|
||||||
"rollup-plugin-terser": "^7.0.2",
|
"rollup-plugin-terser": "^7.0.2",
|
||||||
|
"rollup-plugin-typescript2": "^0.30.0",
|
||||||
"rollup-plugin-vue": "^6.0.0-beta.11",
|
"rollup-plugin-vue": "^6.0.0-beta.11",
|
||||||
"stats.js": "0.17.0",
|
"stats.js": "0.17.0",
|
||||||
"three": "^0.127",
|
"three": "^0.127",
|
||||||
|
"tslib": "^2.2.0",
|
||||||
|
"typescript": "^4.1.5",
|
||||||
"vite": "^2.1.5",
|
"vite": "^2.1.5",
|
||||||
"vue": "^3.0.11"
|
"vue": "^3.0.11",
|
||||||
|
"vue-eslint-parser": "^7.6.0",
|
||||||
|
"vue-tsc": "^0.0.25"
|
||||||
},
|
},
|
||||||
"main": "build/trois.js",
|
"main": "build/trois.js",
|
||||||
"module": "build/trois.module.js",
|
"module": "build/trois.module.js",
|
||||||
|
"typings": "build/trois.d.ts",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "git+https://github.com/troisjs/trois.git"
|
"url": "git+https://github.com/troisjs/trois.git"
|
||||||
|
129
rollup.config.js
129
rollup.config.js
@ -1,10 +1,14 @@
|
|||||||
// import commonjs from '@rollup/plugin-commonjs';
|
import path from 'path'
|
||||||
import vue from 'rollup-plugin-vue';
|
import vue from 'rollup-plugin-vue'
|
||||||
// import buble from '@rollup/plugin-buble';
|
import typescript from 'rollup-plugin-typescript2'
|
||||||
import { terser } from "rollup-plugin-terser";
|
import replace from '@rollup/plugin-replace'
|
||||||
import replace from '@rollup/plugin-replace';
|
import { terser } from "rollup-plugin-terser"
|
||||||
|
|
||||||
|
// ensure TS checks only once for each build
|
||||||
|
let hasTSChecked = false
|
||||||
|
|
||||||
|
const input = 'src/export.ts'
|
||||||
|
|
||||||
const input = 'src/export.js';
|
|
||||||
const external = [
|
const external = [
|
||||||
'three',
|
'three',
|
||||||
'three/examples/jsm/controls/OrbitControls.js',
|
'three/examples/jsm/controls/OrbitControls.js',
|
||||||
@ -27,86 +31,51 @@ const external = [
|
|||||||
'three/examples/jsm/shaders/FXAAShader.js',
|
'three/examples/jsm/shaders/FXAAShader.js',
|
||||||
'three/examples/jsm/webxr/VRButton.js',
|
'three/examples/jsm/webxr/VRButton.js',
|
||||||
'vue',
|
'vue',
|
||||||
];
|
]
|
||||||
|
|
||||||
const cdnReplaces = {
|
const cdnReplaces = {
|
||||||
'from \'vue\'': 'from \'https://unpkg.com/vue@3.0.11/dist/vue.esm-browser.prod.js\'',
|
'from \'vue\'': 'from \'https://unpkg.com/vue@3.0.11/dist/vue.esm-browser.prod.js\'',
|
||||||
'from \'three\'': 'from \'https://unpkg.com/three@0.127.0/build/three.module.js\'',
|
'from \'three\'': 'from \'https://unpkg.com/three@0.127.0/build/three.module.js\'',
|
||||||
'from \'three/examples': 'from \'https://unpkg.com/three@0.127.0/examples',
|
'from \'three/examples': 'from \'https://unpkg.com/three@0.127.0/examples',
|
||||||
delimiters: ['', ''],
|
delimiters: ['', ''],
|
||||||
};
|
}
|
||||||
|
|
||||||
const plugins = [
|
function createConfig(format, output, plugins = []) {
|
||||||
vue(),
|
const tsPlugin = typescript({
|
||||||
// buble({
|
check: false, // !hasTSChecked,
|
||||||
// transforms: { asyncAwait: false, forOf: false },
|
cacheRoot: path.resolve(__dirname, 'node_modules/.tscache'),
|
||||||
// objectAssign: 'Object.assign',
|
tsconfigOverride: {
|
||||||
// }),
|
compilerOptions: {
|
||||||
];
|
sourceMap: false, // !hasTSChecked,
|
||||||
|
declaration: false, // !hasTSChecked,
|
||||||
|
declarationMap: false, // !hasTSChecked,
|
||||||
|
},
|
||||||
|
include: [input],
|
||||||
|
},
|
||||||
|
})
|
||||||
|
hasTSChecked = true
|
||||||
|
|
||||||
|
return {
|
||||||
|
input,
|
||||||
|
external,
|
||||||
|
output: {
|
||||||
|
format,
|
||||||
|
...output,
|
||||||
|
// exports: 'named',
|
||||||
|
sourcemap: true,
|
||||||
|
},
|
||||||
|
plugins: [
|
||||||
|
tsPlugin,
|
||||||
|
vue(),
|
||||||
|
...plugins,
|
||||||
|
],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export default [
|
export default [
|
||||||
{
|
createConfig('es', { file: 'build/trois.module.cdn.js' }, [replace(cdnReplaces)]),
|
||||||
input,
|
createConfig('es', { file: 'build/trois.module.cdn.min.js' }, [replace(cdnReplaces), terser()]),
|
||||||
external,
|
createConfig('es', { file: 'build/trois.module.js' }),
|
||||||
output: {
|
createConfig('es', { file: 'build/trois.module.min.js' }, [terser()]),
|
||||||
format: 'es',
|
createConfig('cjs', { file: 'build/trois.js' }),
|
||||||
exports: 'named',
|
]
|
||||||
file: 'build/trois.module.cdn.js',
|
|
||||||
sourcemap: true,
|
|
||||||
},
|
|
||||||
plugins: [
|
|
||||||
replace(cdnReplaces),
|
|
||||||
...plugins,
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input,
|
|
||||||
external,
|
|
||||||
output: {
|
|
||||||
format: 'es',
|
|
||||||
exports: 'named',
|
|
||||||
file: 'build/trois.module.cdn.min.js',
|
|
||||||
sourcemap: true,
|
|
||||||
},
|
|
||||||
plugins: [
|
|
||||||
replace(cdnReplaces),
|
|
||||||
...plugins,
|
|
||||||
terser(),
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input,
|
|
||||||
external,
|
|
||||||
output: {
|
|
||||||
format: 'es',
|
|
||||||
exports: 'named',
|
|
||||||
file: 'build/trois.module.js',
|
|
||||||
sourcemap: true,
|
|
||||||
},
|
|
||||||
plugins,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input,
|
|
||||||
external,
|
|
||||||
output: {
|
|
||||||
format: 'es',
|
|
||||||
exports: 'named',
|
|
||||||
file: 'build/trois.module.min.js',
|
|
||||||
sourcemap: true,
|
|
||||||
},
|
|
||||||
plugins: [
|
|
||||||
...plugins,
|
|
||||||
terser(),
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input,
|
|
||||||
external,
|
|
||||||
output: {
|
|
||||||
format: 'cjs',
|
|
||||||
file: 'build/trois.js',
|
|
||||||
sourcemap: false,
|
|
||||||
},
|
|
||||||
plugins,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
32
src/App.vue
32
src/App.vue
@ -1,25 +1,37 @@
|
|||||||
<template>
|
<template>
|
||||||
<Renderer ref="renderer" antialias orbit-ctrl resize="window">
|
<Renderer ref="renderer" antialias :pointer="{ resetOnEnd: true }" :orbit-ctrl="{ enableDamping: true }" resize="window">
|
||||||
<Camera :position="{ z: 10 }" />
|
<Camera :position="{ z: 10 }" />
|
||||||
<Scene>
|
<Scene>
|
||||||
<PointLight :position="{ y: 50, z: 50 }" />
|
<PointLight :position="{ y: 50, z: 50 }" />
|
||||||
<Box ref="box" :rotation="{ y: Math.PI / 4, z: Math.PI / 4 }">
|
<Box :size="1" ref="box" :rotation="{ y: Math.PI / 4, z: Math.PI / 4 }">
|
||||||
<LambertMaterial />
|
<LambertMaterial />
|
||||||
</Box>
|
</Box>
|
||||||
</Scene>
|
</Scene>
|
||||||
</Renderer>
|
</Renderer>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script lang="ts">
|
||||||
export default {
|
import { defineComponent } from 'vue'
|
||||||
|
import Box from './meshes/Box'
|
||||||
|
import Camera from './core/PerspectiveCamera'
|
||||||
|
import LambertMaterial from './materials/LambertMaterial'
|
||||||
|
import { MeshInterface } from './meshes/Mesh'
|
||||||
|
import PointLight from './lights/PointLight'
|
||||||
|
import Renderer, { RendererInterface } from './core/Renderer'
|
||||||
|
import Scene from './core/Scene'
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
components: { Box, Camera, LambertMaterial, PointLight, Renderer, Scene },
|
||||||
mounted() {
|
mounted() {
|
||||||
const renderer = this.$refs.renderer;
|
const renderer = this.$refs.renderer as RendererInterface
|
||||||
const box = this.$refs.box.mesh;
|
const mesh = (this.$refs.box as MeshInterface).mesh
|
||||||
renderer.onBeforeRender(() => {
|
if (renderer && mesh) {
|
||||||
box.rotation.x += 0.01;
|
renderer.onBeforeRender(() => {
|
||||||
});
|
mesh.rotation.x += 0.01
|
||||||
|
})
|
||||||
|
}
|
||||||
},
|
},
|
||||||
};
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
@ -1,9 +0,0 @@
|
|||||||
export { default as NoisyImage } from './noisy/NoisyImage.js';
|
|
||||||
export { default as NoisyPlane } from './noisy/NoisyPlane.js';
|
|
||||||
export { default as NoisySphere } from './noisy/NoisySphere.js';
|
|
||||||
export { default as NoisyText } from './noisy/NoisyText.js';
|
|
||||||
|
|
||||||
export { default as Slider1 } from './sliders/Slider1.vue';
|
|
||||||
export { default as Slider2 } from './sliders/Slider2.vue';
|
|
||||||
|
|
||||||
export { default as GLTFViewer } from './viewers/GLTFViewer.vue';
|
|
@ -9,15 +9,15 @@ import {
|
|||||||
Uniform,
|
Uniform,
|
||||||
Vector2,
|
Vector2,
|
||||||
WebGLRenderTarget,
|
WebGLRenderTarget,
|
||||||
} from 'three';
|
} from 'three'
|
||||||
|
|
||||||
// shaders from https://github.com/evanw/webgl-water
|
// shaders from https://github.com/evanw/webgl-water
|
||||||
function LiquidEffect(renderer) {
|
function LiquidEffect(renderer) {
|
||||||
this.renderer = renderer;
|
this.renderer = renderer
|
||||||
this.width = 512;
|
this.width = 512
|
||||||
this.height = 512;
|
this.height = 512
|
||||||
// this.delta = new Vector2(this.width / Math.pow(width, 2), this.height / Math.pow(height, 2));
|
// this.delta = new Vector2(this.width / Math.pow(width, 2), this.height / Math.pow(height, 2));
|
||||||
this.delta = new Vector2(1 / this.width, 1 / this.height);
|
this.delta = new Vector2(1 / this.width, 1 / this.height)
|
||||||
|
|
||||||
const targetOptions = {
|
const targetOptions = {
|
||||||
minFilter: NearestFilter,
|
minFilter: NearestFilter,
|
||||||
@ -25,14 +25,14 @@ function LiquidEffect(renderer) {
|
|||||||
type: FloatType,
|
type: FloatType,
|
||||||
format: RGBAFormat,
|
format: RGBAFormat,
|
||||||
depthBuffer: false,
|
depthBuffer: false,
|
||||||
};
|
}
|
||||||
|
|
||||||
this.hMap = new WebGLRenderTarget(this.width, this.height, targetOptions);
|
this.hMap = new WebGLRenderTarget(this.width, this.height, targetOptions)
|
||||||
this.hMap1 = new WebGLRenderTarget(this.width, this.height, targetOptions);
|
this.hMap1 = new WebGLRenderTarget(this.width, this.height, targetOptions)
|
||||||
this.fsQuad = new FullScreenQuad();
|
this.fsQuad = new FullScreenQuad()
|
||||||
|
|
||||||
this.initShaders();
|
this.initShaders()
|
||||||
};
|
}
|
||||||
|
|
||||||
LiquidEffect.prototype.initShaders = function () {
|
LiquidEffect.prototype.initShaders = function () {
|
||||||
const defaultVertexShader = `
|
const defaultVertexShader = `
|
||||||
@ -41,7 +41,7 @@ LiquidEffect.prototype.initShaders = function () {
|
|||||||
vUv = uv;
|
vUv = uv;
|
||||||
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
|
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
|
||||||
}
|
}
|
||||||
`;
|
`
|
||||||
|
|
||||||
this.copyMat = new ShaderMaterial({
|
this.copyMat = new ShaderMaterial({
|
||||||
uniforms: { tDiffuse: { value: null } },
|
uniforms: { tDiffuse: { value: null } },
|
||||||
@ -53,7 +53,7 @@ LiquidEffect.prototype.initShaders = function () {
|
|||||||
gl_FragColor = texture2D(tDiffuse, vUv);
|
gl_FragColor = texture2D(tDiffuse, vUv);
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
});
|
})
|
||||||
|
|
||||||
this.updateMat = new ShaderMaterial({
|
this.updateMat = new ShaderMaterial({
|
||||||
uniforms: {
|
uniforms: {
|
||||||
@ -83,7 +83,7 @@ LiquidEffect.prototype.initShaders = function () {
|
|||||||
gl_FragColor = texel;
|
gl_FragColor = texel;
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
});
|
})
|
||||||
|
|
||||||
this.normalsMat = new ShaderMaterial({
|
this.normalsMat = new ShaderMaterial({
|
||||||
uniforms: {
|
uniforms: {
|
||||||
@ -103,7 +103,7 @@ LiquidEffect.prototype.initShaders = function () {
|
|||||||
gl_FragColor = texel;
|
gl_FragColor = texel;
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
});
|
})
|
||||||
|
|
||||||
this.dropMat = new ShaderMaterial({
|
this.dropMat = new ShaderMaterial({
|
||||||
uniforms: {
|
uniforms: {
|
||||||
@ -129,35 +129,35 @@ LiquidEffect.prototype.initShaders = function () {
|
|||||||
gl_FragColor = texel;
|
gl_FragColor = texel;
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
});
|
})
|
||||||
};
|
}
|
||||||
|
|
||||||
LiquidEffect.prototype.update = function () {
|
LiquidEffect.prototype.update = function () {
|
||||||
this.updateHMap();
|
this.updateHMap()
|
||||||
// this.updateHMap();
|
// this.updateHMap();
|
||||||
this.updateHMapNormals();
|
this.updateHMapNormals()
|
||||||
};
|
}
|
||||||
|
|
||||||
LiquidEffect.prototype.updateHMap = function () {
|
LiquidEffect.prototype.updateHMap = function () {
|
||||||
this.updateMat.uniforms.tDiffuse.value = this.hMap.texture;
|
this.updateMat.uniforms.tDiffuse.value = this.hMap.texture
|
||||||
this.renderShaderMat(this.updateMat, this.hMap1);
|
this.renderShaderMat(this.updateMat, this.hMap1)
|
||||||
this.swapBuffers();
|
this.swapBuffers()
|
||||||
};
|
}
|
||||||
|
|
||||||
LiquidEffect.prototype.updateHMapNormals = function () {
|
LiquidEffect.prototype.updateHMapNormals = function () {
|
||||||
this.normalsMat.uniforms.tDiffuse.value = this.hMap.texture;
|
this.normalsMat.uniforms.tDiffuse.value = this.hMap.texture
|
||||||
this.renderShaderMat(this.normalsMat, this.hMap1);
|
this.renderShaderMat(this.normalsMat, this.hMap1)
|
||||||
this.swapBuffers();
|
this.swapBuffers()
|
||||||
};
|
}
|
||||||
|
|
||||||
LiquidEffect.prototype.addDrop = function (x, y, radius, strength) {
|
LiquidEffect.prototype.addDrop = function (x, y, radius, strength) {
|
||||||
this.dropMat.uniforms.tDiffuse.value = this.hMap.texture;
|
this.dropMat.uniforms.tDiffuse.value = this.hMap.texture
|
||||||
this.dropMat.uniforms.center.value.set(x, y);
|
this.dropMat.uniforms.center.value.set(x, y)
|
||||||
this.dropMat.uniforms.radius.value = radius;
|
this.dropMat.uniforms.radius.value = radius
|
||||||
this.dropMat.uniforms.strength.value = strength;
|
this.dropMat.uniforms.strength.value = strength
|
||||||
this.renderShaderMat(this.dropMat, this.hMap1);
|
this.renderShaderMat(this.dropMat, this.hMap1)
|
||||||
this.swapBuffers();
|
this.swapBuffers()
|
||||||
};
|
}
|
||||||
|
|
||||||
// LiquidEffect.prototype.renderBuffer = function (buffer, target) {
|
// LiquidEffect.prototype.renderBuffer = function (buffer, target) {
|
||||||
// this.copyMat.uniforms.tDiffuse.value = buffer.texture;
|
// this.copyMat.uniforms.tDiffuse.value = buffer.texture;
|
||||||
@ -165,40 +165,40 @@ LiquidEffect.prototype.addDrop = function (x, y, radius, strength) {
|
|||||||
// };
|
// };
|
||||||
|
|
||||||
LiquidEffect.prototype.renderShaderMat = function (mat, target) {
|
LiquidEffect.prototype.renderShaderMat = function (mat, target) {
|
||||||
this.fsQuad.material = mat;
|
this.fsQuad.material = mat
|
||||||
const oldTarget = this.renderer.getRenderTarget();
|
const oldTarget = this.renderer.getRenderTarget()
|
||||||
this.renderer.setRenderTarget(target);
|
this.renderer.setRenderTarget(target)
|
||||||
this.fsQuad.render(this.renderer);
|
this.fsQuad.render(this.renderer)
|
||||||
this.renderer.setRenderTarget(oldTarget);
|
this.renderer.setRenderTarget(oldTarget)
|
||||||
};
|
}
|
||||||
|
|
||||||
LiquidEffect.prototype.swapBuffers = function () {
|
LiquidEffect.prototype.swapBuffers = function () {
|
||||||
const temp = this.hMap;
|
const temp = this.hMap
|
||||||
this.hMap = this.hMap1;
|
this.hMap = this.hMap1
|
||||||
this.hMap1 = temp;
|
this.hMap1 = temp
|
||||||
};
|
}
|
||||||
|
|
||||||
// from https://threejs.org/examples/js/postprocessing/EffectComposer.js
|
// from https://threejs.org/examples/js/postprocessing/EffectComposer.js
|
||||||
const FullScreenQuad = (function () {
|
const FullScreenQuad = (function () {
|
||||||
const camera = new OrthographicCamera(-1, 1, 1, -1, 0, 1);
|
const camera = new OrthographicCamera(-1, 1, 1, -1, 0, 1)
|
||||||
const geometry = new PlaneGeometry(2, 2);
|
const geometry = new PlaneGeometry(2, 2)
|
||||||
|
|
||||||
const FullScreenQuad = function (material) {
|
const FullScreenQuad = function (material) {
|
||||||
this._mesh = new Mesh(geometry, material);
|
this._mesh = new Mesh(geometry, material)
|
||||||
};
|
}
|
||||||
|
|
||||||
Object.defineProperty(FullScreenQuad.prototype, 'material', {
|
Object.defineProperty(FullScreenQuad.prototype, 'material', {
|
||||||
get: function () { return this._mesh.material; },
|
get: function () { return this._mesh.material },
|
||||||
set: function (value) { this._mesh.material = value; },
|
set: function (value) { this._mesh.material = value },
|
||||||
});
|
})
|
||||||
|
|
||||||
Object.assign(FullScreenQuad.prototype, {
|
Object.assign(FullScreenQuad.prototype, {
|
||||||
render: function (renderer) {
|
render: function (renderer) {
|
||||||
renderer.render(this._mesh, camera);
|
renderer.render(this._mesh, camera)
|
||||||
},
|
},
|
||||||
});
|
})
|
||||||
|
|
||||||
return FullScreenQuad;
|
return FullScreenQuad
|
||||||
})();
|
})()
|
||||||
|
|
||||||
export default LiquidEffect;
|
export default LiquidEffect
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
import { defineComponent, watch } from 'vue';
|
import { defineComponent, watch } from 'vue'
|
||||||
import { DoubleSide, Mesh, MeshStandardMaterial, PlaneGeometry } from 'three';
|
import { DoubleSide, Mesh, MeshStandardMaterial, PlaneGeometry } from 'three'
|
||||||
import Object3D from '../../core/Object3D.js';
|
import { bindProps, Object3D } from '../../../build/trois.module.js'
|
||||||
import { bindProps } from '../../tools';
|
import LiquidEffect from './LiquidEffect.js'
|
||||||
import LiquidEffect from './LiquidEffect.js';
|
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
extends: Object3D,
|
extends: Object3D,
|
||||||
@ -16,39 +15,40 @@ export default defineComponent({
|
|||||||
roughness: { type: Number, default: 0.25 },
|
roughness: { type: Number, default: 0.25 },
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
this.liquidEffect = new LiquidEffect(this.three.renderer);
|
this.liquidEffect = new LiquidEffect(this.renderer.renderer)
|
||||||
this.rendererComponent.onMounted(() => {
|
this.renderer.onMounted(() => {
|
||||||
this.liquidEffect.renderer = this.rendererComponent.renderer;
|
this.liquidEffect.renderer = this.renderer.renderer
|
||||||
this.rendererComponent.onBeforeRender(this.update);
|
this.renderer.onBeforeRender(this.update)
|
||||||
});
|
})
|
||||||
|
|
||||||
this.material = new MeshStandardMaterial({ color: this.color, side: DoubleSide, metalness: this.metalness, roughness: this.roughness,
|
this.material = new MeshStandardMaterial({
|
||||||
|
color: this.color, side: DoubleSide, metalness: this.metalness, roughness: this.roughness,
|
||||||
onBeforeCompile: shader => {
|
onBeforeCompile: shader => {
|
||||||
shader.uniforms.hmap = { value: this.liquidEffect.hMap.texture };
|
shader.uniforms.hmap = { value: this.liquidEffect.hMap.texture }
|
||||||
shader.vertexShader = "uniform sampler2D hmap;\n" + shader.vertexShader;
|
shader.vertexShader = "uniform sampler2D hmap;\n" + shader.vertexShader
|
||||||
const token = '#include <begin_vertex>';
|
const token = '#include <begin_vertex>'
|
||||||
const customTransform = `
|
const customTransform = `
|
||||||
vec3 transformed = vec3(position);
|
vec3 transformed = vec3(position);
|
||||||
vec4 info = texture2D(hmap, uv);
|
vec4 info = texture2D(hmap, uv);
|
||||||
vNormal = vec3(info.b, sqrt(1.0 - dot(info.ba, info.ba)), info.a).xzy;
|
vNormal = vec3(info.b, sqrt(1.0 - dot(info.ba, info.ba)), info.a).xzy;
|
||||||
transformed.z = 20. * info.r;
|
transformed.z = 20. * info.r;
|
||||||
`;
|
`
|
||||||
shader.vertexShader = shader.vertexShader.replace(token, customTransform);
|
shader.vertexShader = shader.vertexShader.replace(token, customTransform)
|
||||||
},
|
},
|
||||||
});
|
})
|
||||||
bindProps(this, ['metalness', 'roughness'], this.material);
|
bindProps(this, ['metalness', 'roughness'], this.material)
|
||||||
watch(() => this.color, (value) => this.material.color.set(value));
|
watch(() => this.color, (value) => this.material.color.set(value))
|
||||||
|
|
||||||
this.geometry = new PlaneGeometry(this.width, this.height, this.widthSegments, this.heightSegments);
|
this.geometry = new PlaneGeometry(this.width, this.height, this.widthSegments, this.heightSegments)
|
||||||
this.mesh = new Mesh(this.geometry, this.material);
|
this.mesh = new Mesh(this.geometry, this.material)
|
||||||
this.initObject3D(this.mesh);
|
this.initObject3D(this.mesh)
|
||||||
},
|
},
|
||||||
unmounted() {
|
unmounted() {
|
||||||
this.rendererComponent.offBeforeRender(this.update);
|
this.renderer.offBeforeRender(this.update)
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
update() {
|
update() {
|
||||||
this.liquidEffect.update();
|
this.liquidEffect.update()
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
})
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { defineComponent } from 'vue';
|
import { defineComponent } from 'vue'
|
||||||
import {
|
import {
|
||||||
BackSide,
|
BackSide,
|
||||||
CubeCamera,
|
CubeCamera,
|
||||||
@ -7,9 +7,9 @@ import {
|
|||||||
Mesh as TMesh,
|
Mesh as TMesh,
|
||||||
RGBFormat,
|
RGBFormat,
|
||||||
WebGLCubeRenderTarget,
|
WebGLCubeRenderTarget,
|
||||||
} from 'three';
|
} from 'three'
|
||||||
import Mesh from '../../meshes/Mesh.js';
|
|
||||||
import { bindProp } from '../../tools';
|
import { bindProp, Mesh } from '../../../build/trois.module.js'
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
extends: Mesh,
|
extends: Mesh,
|
||||||
@ -20,54 +20,54 @@ export default defineComponent({
|
|||||||
autoUpdate: Boolean,
|
autoUpdate: Boolean,
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
this.initGem();
|
this.initGem()
|
||||||
if (this.autoUpdate) this.rendererComponent.onBeforeRender(this.updateCubeRT);
|
if (this.autoUpdate) this.renderer.onBeforeRender(this.updateCubeRT)
|
||||||
else this.rendererComponent.onMounted(this.updateCubeRT);
|
else this.renderer.onMounted(this.updateCubeRT)
|
||||||
},
|
},
|
||||||
unmounted() {
|
unmounted() {
|
||||||
this.rendererComponent.offBeforeRender(this.updateCubeRT);
|
this.renderer.offBeforeRender(this.updateCubeRT)
|
||||||
if (this.cubeCamera) this.removeFromParent(this.cubeCamera);
|
if (this.cubeCamera) this.removeFromParent(this.cubeCamera)
|
||||||
if (this.meshBack) this.removeFromParent(this.meshBack);
|
if (this.meshBack) this.removeFromParent(this.meshBack)
|
||||||
if (this.materialBack) this.materialBack.dispose();
|
if (this.materialBack) this.materialBack.dispose()
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
initGem() {
|
initGem() {
|
||||||
const cubeRT = new WebGLCubeRenderTarget(this.cubeRTSize, { format: RGBFormat, generateMipmaps: true, minFilter: LinearMipmapLinearFilter });
|
const cubeRT = new WebGLCubeRenderTarget(this.cubeRTSize, { format: RGBFormat, generateMipmaps: true, minFilter: LinearMipmapLinearFilter })
|
||||||
this.cubeCamera = new CubeCamera(this.cubeCameraNear, this.cubeCameraFar, cubeRT);
|
this.cubeCamera = new CubeCamera(this.cubeCameraNear, this.cubeCameraFar, cubeRT)
|
||||||
bindProp(this, 'position', this.cubeCamera);
|
bindProp(this, 'position', this.cubeCamera)
|
||||||
this.addToParent(this.cubeCamera);
|
this.addToParent(this.cubeCamera)
|
||||||
|
|
||||||
this.material.side = FrontSide;
|
this.material.side = FrontSide
|
||||||
this.material.envMap = cubeRT.texture;
|
this.material.envMap = cubeRT.texture
|
||||||
this.material.envMapIntensity = 10;
|
this.material.envMapIntensity = 10
|
||||||
this.material.metalness = 0;
|
this.material.metalness = 0
|
||||||
this.material.roughness = 0;
|
this.material.roughness = 0
|
||||||
this.material.opacity = 0.75;
|
this.material.opacity = 0.75
|
||||||
this.material.transparent = true;
|
this.material.transparent = true
|
||||||
this.material.premultipliedAlpha = true;
|
this.material.premultipliedAlpha = true
|
||||||
this.material.needsUpdate = true;
|
this.material.needsUpdate = true
|
||||||
|
|
||||||
this.materialBack = this.material.clone();
|
this.materialBack = this.material.clone()
|
||||||
this.materialBack.side = BackSide;
|
this.materialBack.side = BackSide
|
||||||
this.materialBack.envMapIntensity = 5;
|
this.materialBack.envMapIntensity = 5
|
||||||
this.materialBack.metalness = 1;
|
this.materialBack.metalness = 1
|
||||||
this.materialBack.roughness = 0;
|
this.materialBack.roughness = 0
|
||||||
this.materialBack.opacity = 0.5;
|
this.materialBack.opacity = 0.5
|
||||||
|
|
||||||
this.meshBack = new TMesh(this.geometry, this.materialBack);
|
this.meshBack = new TMesh(this.geometry, this.materialBack)
|
||||||
|
|
||||||
bindProp(this, 'position', this.meshBack);
|
bindProp(this, 'position', this.meshBack)
|
||||||
bindProp(this, 'rotation', this.meshBack);
|
bindProp(this, 'rotation', this.meshBack)
|
||||||
bindProp(this, 'scale', this.meshBack);
|
bindProp(this, 'scale', this.meshBack)
|
||||||
this.addToParent(this.meshBack);
|
this.addToParent(this.meshBack)
|
||||||
},
|
},
|
||||||
updateCubeRT() {
|
updateCubeRT() {
|
||||||
this.mesh.visible = false;
|
this.mesh.visible = false
|
||||||
this.meshBack.visible = false;
|
this.meshBack.visible = false
|
||||||
this.cubeCamera.update(this.three.renderer, this.scene);
|
this.cubeCamera.update(this.renderer.renderer, this.scene)
|
||||||
this.mesh.visible = true;
|
this.mesh.visible = true
|
||||||
this.meshBack.visible = true;
|
this.meshBack.visible = true
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
__hmrId: 'Gem',
|
__hmrId: 'Gem',
|
||||||
});
|
})
|
||||||
|
@ -1,11 +1,12 @@
|
|||||||
import { defineComponent } from 'vue';
|
import { defineComponent } from 'vue'
|
||||||
import {
|
import {
|
||||||
CubeCamera,
|
CubeCamera,
|
||||||
LinearMipmapLinearFilter,
|
LinearMipmapLinearFilter,
|
||||||
RGBFormat,
|
RGBFormat,
|
||||||
WebGLCubeRenderTarget,
|
WebGLCubeRenderTarget,
|
||||||
} from 'three';
|
} from 'three'
|
||||||
import Mesh from '../../meshes/Mesh.js';
|
|
||||||
|
import { Mesh } from '../../../build/trois.module.js'
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
extends: Mesh,
|
extends: Mesh,
|
||||||
@ -16,28 +17,28 @@ export default defineComponent({
|
|||||||
autoUpdate: Boolean,
|
autoUpdate: Boolean,
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
this.initMirrorMesh();
|
this.initMirrorMesh()
|
||||||
if (this.autoUpdate) this.rendererComponent.onBeforeRender(this.updateCubeRT);
|
if (this.autoUpdate) this.renderer.onBeforeRender(this.updateCubeRT)
|
||||||
else this.rendererComponent.onMounted(this.updateCubeRT);
|
else this.renderer.onMounted(this.updateCubeRT)
|
||||||
},
|
},
|
||||||
unmounted() {
|
unmounted() {
|
||||||
this.rendererComponent.offBeforeRender(this.updateCubeRT);
|
this.renderer.offBeforeRender(this.updateCubeRT)
|
||||||
if (this.cubeCamera) this.removeFromParent(this.cubeCamera);
|
if (this.cubeCamera) this.removeFromParent(this.cubeCamera)
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
initMirrorMesh() {
|
initMirrorMesh() {
|
||||||
const cubeRT = new WebGLCubeRenderTarget(this.cubeRTSize, { format: RGBFormat, generateMipmaps: true, minFilter: LinearMipmapLinearFilter });
|
const cubeRT = new WebGLCubeRenderTarget(this.cubeRTSize, { format: RGBFormat, generateMipmaps: true, minFilter: LinearMipmapLinearFilter })
|
||||||
this.cubeCamera = new CubeCamera(this.cubeCameraNear, this.cubeCameraFar, cubeRT);
|
this.cubeCamera = new CubeCamera(this.cubeCameraNear, this.cubeCameraFar, cubeRT)
|
||||||
this.addToParent(this.cubeCamera);
|
this.addToParent(this.cubeCamera)
|
||||||
|
|
||||||
this.material.envMap = cubeRT.texture;
|
this.material.envMap = cubeRT.texture
|
||||||
this.material.needsUpdate = true;
|
this.material.needsUpdate = true
|
||||||
},
|
},
|
||||||
updateCubeRT() {
|
updateCubeRT() {
|
||||||
this.mesh.visible = false;
|
this.mesh.visible = false
|
||||||
this.cubeCamera.update(this.three.renderer, this.scene);
|
this.cubeCamera.update(this.renderer.renderer, this.scene)
|
||||||
this.mesh.visible = true;
|
this.mesh.visible = true
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
__hmrId: 'MirrorMesh',
|
__hmrId: 'MirrorMesh',
|
||||||
});
|
})
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
import { defineComponent } from 'vue';
|
import { defineComponent } from 'vue'
|
||||||
import {
|
import {
|
||||||
CubeCamera,
|
CubeCamera,
|
||||||
CubeRefractionMapping,
|
CubeRefractionMapping,
|
||||||
LinearMipmapLinearFilter,
|
LinearMipmapLinearFilter,
|
||||||
RGBFormat,
|
RGBFormat,
|
||||||
WebGLCubeRenderTarget,
|
WebGLCubeRenderTarget,
|
||||||
} from 'three';
|
} from 'three'
|
||||||
import Mesh from '../../meshes/Mesh.js';
|
|
||||||
import { bindProp } from '../../tools';
|
import { bindProp, Mesh } from '../../../build/trois.module.js'
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
extends: Mesh,
|
extends: Mesh,
|
||||||
@ -19,30 +19,30 @@ export default defineComponent({
|
|||||||
autoUpdate: Boolean,
|
autoUpdate: Boolean,
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
this.initMirrorMesh();
|
this.initMirrorMesh()
|
||||||
if (this.autoUpdate) this.rendererComponent.onBeforeRender(this.updateCubeRT);
|
if (this.autoUpdate) this.renderer.onBeforeRender(this.updateCubeRT)
|
||||||
else this.rendererComponent.onMounted(this.updateCubeRT);
|
else this.renderer.onMounted(this.updateCubeRT)
|
||||||
},
|
},
|
||||||
unmounted() {
|
unmounted() {
|
||||||
this.rendererComponent.offBeforeRender(this.updateCubeRT);
|
this.renderer.offBeforeRender(this.updateCubeRT)
|
||||||
if (this.cubeCamera) this.removeFromParent(this.cubeCamera);
|
if (this.cubeCamera) this.removeFromParent(this.cubeCamera)
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
initMirrorMesh() {
|
initMirrorMesh() {
|
||||||
const cubeRT = new WebGLCubeRenderTarget(this.cubeRTSize, { mapping: CubeRefractionMapping, format: RGBFormat, generateMipmaps: true, minFilter: LinearMipmapLinearFilter });
|
const cubeRT = new WebGLCubeRenderTarget(this.cubeRTSize, { mapping: CubeRefractionMapping, format: RGBFormat, generateMipmaps: true, minFilter: LinearMipmapLinearFilter })
|
||||||
this.cubeCamera = new CubeCamera(this.cubeCameraNear, this.cubeCameraFar, cubeRT);
|
this.cubeCamera = new CubeCamera(this.cubeCameraNear, this.cubeCameraFar, cubeRT)
|
||||||
bindProp(this, 'position', this.cubeCamera);
|
bindProp(this, 'position', this.cubeCamera)
|
||||||
this.addToParent(this.cubeCamera);
|
this.addToParent(this.cubeCamera)
|
||||||
|
|
||||||
this.material.envMap = cubeRT.texture;
|
this.material.envMap = cubeRT.texture
|
||||||
this.material.refractionRatio = this.refractionRatio;
|
this.material.refractionRatio = this.refractionRatio
|
||||||
this.material.needsUpdate = true;
|
this.material.needsUpdate = true
|
||||||
},
|
},
|
||||||
updateCubeRT() {
|
updateCubeRT() {
|
||||||
this.mesh.visible = false;
|
this.mesh.visible = false
|
||||||
this.cubeCamera.update(this.three.renderer, this.scene);
|
this.cubeCamera.update(this.renderer.renderer, this.scene)
|
||||||
this.mesh.visible = true;
|
this.mesh.visible = true
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
__hmrId: 'RefractionMesh',
|
__hmrId: 'RefractionMesh',
|
||||||
});
|
})
|
||||||
|
@ -1,46 +1,47 @@
|
|||||||
import Stats from 'stats.js';
|
import Stats from 'stats.js'
|
||||||
|
import { RendererInjectionKey } from '../../../build/trois.module.js'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
props: {
|
props: {
|
||||||
noSetup: { type: Boolean, default: false },
|
noSetup: { type: Boolean, default: false },
|
||||||
},
|
},
|
||||||
emits: ['created'],
|
emits: ['created'],
|
||||||
inject: ['rendererComponent'],
|
inject: { renderer: RendererInjectionKey },
|
||||||
setup({ noSetup }) {
|
setup({ noSetup }) {
|
||||||
const stats = new Stats();
|
const stats = new Stats()
|
||||||
if (!noSetup) {
|
if (!noSetup) {
|
||||||
stats.showPanel(0); // 0: fps, 1: ms, 2: mb, 3+: custom
|
stats.showPanel(0) // 0: fps, 1: ms, 2: mb, 3+: custom
|
||||||
document.body.appendChild(stats.dom);
|
document.body.appendChild(stats.dom)
|
||||||
}
|
}
|
||||||
return { stats };
|
return { stats }
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
if (!this.noSetup) {
|
if (!this.noSetup) {
|
||||||
this.rendererComponent.onBeforeRender(this.begin);
|
this.renderer.onBeforeRender(this.begin)
|
||||||
this.rendererComponent.onAfterRender(this.end);
|
this.renderer.onAfterRender(this.end)
|
||||||
}
|
}
|
||||||
this.$emit('created', { stats: this.stats });
|
this.$emit('created', { stats: this.stats })
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
begin() {
|
begin() {
|
||||||
if (this.stats) {
|
if (this.stats) {
|
||||||
this.stats.begin();
|
this.stats.begin()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
end() {
|
end() {
|
||||||
if (this.stats) {
|
if (this.stats) {
|
||||||
this.stats.end();
|
this.stats.end()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
unmounted() {
|
unmounted() {
|
||||||
if (this.stats && this.stats.dom) {
|
if (this.stats && this.stats.dom) {
|
||||||
this.stats.dom.parentElement.removeChild(this.stats.dom);
|
this.stats.dom.parentElement.removeChild(this.stats.dom)
|
||||||
}
|
}
|
||||||
this.rendererComponent.offBeforeRender(this.begin);
|
this.renderer.offBeforeRender(this.begin)
|
||||||
this.rendererComponent.offAfterRender(this.end);
|
this.renderer.offAfterRender(this.end)
|
||||||
},
|
},
|
||||||
render() {
|
render() {
|
||||||
return this.$slots.default ? this.$slots.default() : [];
|
return this.$slots.default ? this.$slots.default() : []
|
||||||
},
|
},
|
||||||
};
|
}
|
||||||
|
@ -14,57 +14,57 @@ export default {
|
|||||||
error: '',
|
error: '',
|
||||||
xrSupport: false,
|
xrSupport: false,
|
||||||
currentSession: null,
|
currentSession: null,
|
||||||
};
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
message() {
|
message() {
|
||||||
if (this.xrSupport) {
|
if (this.xrSupport) {
|
||||||
return this.currentSession ? this.exitMessage : this.enterMessage;
|
return this.currentSession ? this.exitMessage : this.enterMessage
|
||||||
} else if (this.error) {
|
} else if (this.error) {
|
||||||
return this.error;
|
return this.error
|
||||||
}
|
}
|
||||||
return '';
|
return ''
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
if ('xr' in navigator) {
|
if ('xr' in navigator) {
|
||||||
navigator.xr.isSessionSupported('immersive-vr').then((supported) => {
|
navigator.xr.isSessionSupported('immersive-vr').then((supported) => {
|
||||||
this.xrSupport = supported;
|
this.xrSupport = supported
|
||||||
});
|
})
|
||||||
} else {
|
} else {
|
||||||
if (window.isSecureContext === false) {
|
if (window.isSecureContext === false) {
|
||||||
this.error = 'WEBXR NEEDS HTTPS';
|
this.error = 'WEBXR NEEDS HTTPS'
|
||||||
} else {
|
} else {
|
||||||
this.error = 'WEBXR NOT AVAILABLE';
|
this.error = 'WEBXR NOT AVAILABLE'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
init(renderer) {
|
init(renderer) {
|
||||||
this.renderer = renderer;
|
this.renderer = renderer
|
||||||
},
|
},
|
||||||
onClick() {
|
onClick() {
|
||||||
if (!this.xrSupport) return;
|
if (!this.xrSupport) return
|
||||||
if (!this.renderer) return;
|
if (!this.renderer) return
|
||||||
|
|
||||||
if (this.currentSession) {
|
if (this.currentSession) {
|
||||||
this.currentSession.end();
|
this.currentSession.end()
|
||||||
} else {
|
} else {
|
||||||
const sessionInit = { optionalFeatures: ['local-floor', 'bounded-floor', 'hand-tracking'] };
|
const sessionInit = { optionalFeatures: ['local-floor', 'bounded-floor', 'hand-tracking'] }
|
||||||
navigator.xr.requestSession('immersive-vr', sessionInit).then(this.onSessionStarted);
|
navigator.xr.requestSession('immersive-vr', sessionInit).then(this.onSessionStarted)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async onSessionStarted(session) {
|
async onSessionStarted(session) {
|
||||||
session.addEventListener('end', this.onSessionEnded);
|
session.addEventListener('end', this.onSessionEnded)
|
||||||
await this.renderer.xr.setSession(session);
|
await this.renderer.xr.setSession(session)
|
||||||
this.currentSession = session;
|
this.currentSession = session
|
||||||
},
|
},
|
||||||
onSessionEnded() {
|
onSessionEnded() {
|
||||||
this.currentSession.removeEventListener('end', this.onSessionEnded);
|
this.currentSession.removeEventListener('end', this.onSessionEnded)
|
||||||
this.currentSession = null;
|
this.currentSession = null
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import { defineComponent, watch } from 'vue';
|
import { defineComponent, watch } from 'vue'
|
||||||
import { DoubleSide, MeshBasicMaterial, PlaneGeometry } from 'three';
|
import { Image } from '../../../build/trois.module.js'
|
||||||
import Image from '../../meshes/Image.js';
|
import snoise2 from '../../glsl/snoise2.glsl.js'
|
||||||
import snoise2 from '../../glsl/snoise2.glsl.js';
|
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
extends: Image,
|
extends: Image,
|
||||||
@ -15,43 +14,41 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
setup(props) {
|
setup(props) {
|
||||||
// uniforms
|
// uniforms
|
||||||
const uTime = { value: 0 };
|
const uTime = { value: 0 }
|
||||||
const uNoiseCoef = { value: props.noiseCoef };
|
const uNoiseCoef = { value: props.noiseCoef }
|
||||||
watch(() => props.noiseCoef, (value) => { uNoiseCoef.value = value; });
|
watch(() => props.noiseCoef, (value) => { uNoiseCoef.value = value })
|
||||||
const uZCoef = { value: props.zCoef };
|
const uZCoef = { value: props.zCoef }
|
||||||
watch(() => props.zCoef, (value) => { uZCoef.value = value; });
|
watch(() => props.zCoef, (value) => { uZCoef.value = value })
|
||||||
const uDispCoef = { value: props.dispCoef };
|
const uDispCoef = { value: props.dispCoef }
|
||||||
watch(() => props.dispCoef, (value) => { uDispCoef.value = value; });
|
watch(() => props.dispCoef, (value) => { uDispCoef.value = value })
|
||||||
|
|
||||||
return {
|
return {
|
||||||
uTime, uNoiseCoef, uZCoef, uDispCoef,
|
uTime, uNoiseCoef, uZCoef, uDispCoef,
|
||||||
};
|
}
|
||||||
},
|
},
|
||||||
mounted() {
|
created() {
|
||||||
this.startTime = Date.now();
|
this.tweakMaterial()
|
||||||
this.rendererComponent.onBeforeRender(this.updateTime);
|
|
||||||
|
this.startTime = Date.now()
|
||||||
|
this.renderer.onBeforeRender(this.updateTime)
|
||||||
},
|
},
|
||||||
unmounted() {
|
unmounted() {
|
||||||
this.rendererComponent.offBeforeRender(this.updateTime);
|
this.renderer.offBeforeRender(this.updateTime)
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
createGeometry() {
|
tweakMaterial() {
|
||||||
this.geometry = new PlaneGeometry(1, 1, this.widthSegments, this.heightSegments);
|
|
||||||
},
|
|
||||||
createMaterial() {
|
|
||||||
this.material = new MeshBasicMaterial({ side: DoubleSide, map: this.loadTexture() });
|
|
||||||
this.material.onBeforeCompile = (shader) => {
|
this.material.onBeforeCompile = (shader) => {
|
||||||
shader.uniforms.uTime = this.uTime;
|
shader.uniforms.uTime = this.uTime
|
||||||
shader.uniforms.uNoiseCoef = this.uNoiseCoef;
|
shader.uniforms.uNoiseCoef = this.uNoiseCoef
|
||||||
shader.uniforms.uZCoef = this.uZCoef;
|
shader.uniforms.uZCoef = this.uZCoef
|
||||||
shader.uniforms.uDispCoef = this.uDispCoef;
|
shader.uniforms.uDispCoef = this.uDispCoef
|
||||||
shader.vertexShader = `
|
shader.vertexShader = `
|
||||||
uniform float uTime;
|
uniform float uTime;
|
||||||
uniform float uNoiseCoef;
|
uniform float uNoiseCoef;
|
||||||
uniform float uZCoef;
|
uniform float uZCoef;
|
||||||
varying float vNoise;
|
varying float vNoise;
|
||||||
${snoise2}
|
${snoise2}
|
||||||
` + shader.vertexShader;
|
` + shader.vertexShader
|
||||||
|
|
||||||
shader.vertexShader = shader.vertexShader.replace(
|
shader.vertexShader = shader.vertexShader.replace(
|
||||||
'#include <begin_vertex>',
|
'#include <begin_vertex>',
|
||||||
@ -62,12 +59,12 @@ export default defineComponent({
|
|||||||
vec3 transformed = vec3(position);
|
vec3 transformed = vec3(position);
|
||||||
transformed.z += vNoise * uZCoef;
|
transformed.z += vNoise * uZCoef;
|
||||||
`
|
`
|
||||||
);
|
)
|
||||||
|
|
||||||
shader.fragmentShader = `
|
shader.fragmentShader = `
|
||||||
uniform float uDispCoef;
|
uniform float uDispCoef;
|
||||||
varying float vNoise;
|
varying float vNoise;
|
||||||
` + shader.fragmentShader;
|
` + shader.fragmentShader
|
||||||
|
|
||||||
shader.fragmentShader = shader.fragmentShader.replace(
|
shader.fragmentShader = shader.fragmentShader.replace(
|
||||||
'#include <map_fragment>',
|
'#include <map_fragment>',
|
||||||
@ -77,13 +74,13 @@ export default defineComponent({
|
|||||||
texelColor.r = dispTexel.r;
|
texelColor.r = dispTexel.r;
|
||||||
diffuseColor = texelColor;
|
diffuseColor = texelColor;
|
||||||
`
|
`
|
||||||
);
|
)
|
||||||
this.materialShader = shader;
|
this.materialShader = shader
|
||||||
};
|
}
|
||||||
},
|
},
|
||||||
updateTime() {
|
updateTime() {
|
||||||
this.uTime.value = (Date.now() - this.startTime) * this.timeCoef;
|
this.uTime.value = (Date.now() - this.startTime) * this.timeCoef
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
__hmrId: 'NoisyImage',
|
__hmrId: 'NoisyImage',
|
||||||
});
|
})
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import { defineComponent, watch } from 'vue';
|
import { defineComponent, watch } from 'vue'
|
||||||
import { ObjectSpaceNormalMap, ShaderMaterial, Vector2, WebGLRenderTarget } from 'three';
|
import { ObjectSpaceNormalMap, ShaderMaterial, Vector2, WebGLRenderTarget } from 'three'
|
||||||
import { Pass } from 'three/examples/jsm/postprocessing/Pass.js';
|
import { Pass } from 'three/examples/jsm/postprocessing/Pass.js'
|
||||||
import Plane from '../../meshes/Plane.js';
|
import { Plane } from '../../../build/trois.module.js'
|
||||||
import snoise3 from '../../glsl/snoise3.glsl.js';
|
import snoise3 from '../../glsl/snoise3.glsl.js'
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
extends: Plane,
|
extends: Plane,
|
||||||
@ -14,38 +14,38 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
setup(props) {
|
setup(props) {
|
||||||
// uniforms
|
// uniforms
|
||||||
const uTime = { value: 0 };
|
const uTime = { value: 0 }
|
||||||
const uNoiseCoef = { value: props.noiseCoef };
|
const uNoiseCoef = { value: props.noiseCoef }
|
||||||
watch(() => props.noiseCoef, (value) => { uNoiseCoef.value = value; });
|
watch(() => props.noiseCoef, (value) => { uNoiseCoef.value = value })
|
||||||
const uDelta = { value: new Vector2(props.deltaCoef, props.deltaCoef) };
|
const uDelta = { value: new Vector2(props.deltaCoef, props.deltaCoef) }
|
||||||
watch(() => props.deltaCoef, (value) => { uDelta.value.set(value, value); });
|
watch(() => props.deltaCoef, (value) => { uDelta.value.set(value, value) })
|
||||||
|
|
||||||
return {
|
return {
|
||||||
uTime, uNoiseCoef, uDelta,
|
uTime, uNoiseCoef, uDelta,
|
||||||
};
|
}
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
this.init();
|
this.init()
|
||||||
|
|
||||||
watch(() => this.displacementScale, (value) => { this.material.displacementScale = value; });
|
watch(() => this.displacementScale, (value) => { this.material.displacementScale = value })
|
||||||
|
|
||||||
this.startTime = Date.now();
|
this.startTime = Date.now()
|
||||||
this.rendererComponent.onBeforeRender(this.update);
|
this.renderer.onBeforeRender(this.update)
|
||||||
},
|
},
|
||||||
unmounted() {
|
unmounted() {
|
||||||
this.rendererComponent.offBeforeRender(this.update);
|
this.renderer.offBeforeRender(this.update)
|
||||||
this.fsQuad.dispose();
|
this.fsQuad.dispose()
|
||||||
this.dispRT.dispose();
|
this.dispRT.dispose()
|
||||||
this.dispMat.dispose();
|
this.dispMat.dispose()
|
||||||
this.normRT.dispose();
|
this.normRT.dispose()
|
||||||
this.normMat.dispose();
|
this.normMat.dispose()
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
init() {
|
init() {
|
||||||
this.fsQuad = new Pass.FullScreenQuad();
|
this.fsQuad = new Pass.FullScreenQuad()
|
||||||
|
|
||||||
// displacement map
|
// displacement map
|
||||||
this.dispRT = new WebGLRenderTarget(512, 512, { depthBuffer: false, stencilBuffer: false });
|
this.dispRT = new WebGLRenderTarget(512, 512, { depthBuffer: false, stencilBuffer: false })
|
||||||
this.dispMat = new ShaderMaterial({
|
this.dispMat = new ShaderMaterial({
|
||||||
uniforms: {
|
uniforms: {
|
||||||
uTime: this.uTime,
|
uTime: this.uTime,
|
||||||
@ -70,10 +70,10 @@ export default defineComponent({
|
|||||||
gl_FragColor = vec4(noise, 0.0, 0.0, 1.0);
|
gl_FragColor = vec4(noise, 0.0, 0.0, 1.0);
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
});
|
})
|
||||||
|
|
||||||
// normal map
|
// normal map
|
||||||
this.normRT = new WebGLRenderTarget(512, 512, { depthBuffer: false, stencilBuffer: false });
|
this.normRT = new WebGLRenderTarget(512, 512, { depthBuffer: false, stencilBuffer: false })
|
||||||
this.normMat = new ShaderMaterial({
|
this.normMat = new ShaderMaterial({
|
||||||
uniforms: {
|
uniforms: {
|
||||||
dispMap: { value: this.dispRT.texture },
|
dispMap: { value: this.dispRT.texture },
|
||||||
@ -100,30 +100,30 @@ export default defineComponent({
|
|||||||
gl_FragColor = vec4(0.5 + (x1 - x2), 0.5 + (y1 - y2), 1.0, 1.0);
|
gl_FragColor = vec4(0.5 + (x1 - x2), 0.5 + (y1 - y2), 1.0, 1.0);
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
});
|
})
|
||||||
|
|
||||||
this.material.displacementMap = this.dispRT.texture;
|
this.material.displacementMap = this.dispRT.texture
|
||||||
this.material.displacementScale = this.displacementScale;
|
this.material.displacementScale = this.displacementScale
|
||||||
this.material.normalMap = this.normRT.texture;
|
this.material.normalMap = this.normRT.texture
|
||||||
this.material.normalMapType = ObjectSpaceNormalMap;
|
this.material.normalMapType = ObjectSpaceNormalMap
|
||||||
// this.material.needsUpdate = true;
|
// this.material.needsUpdate = true;
|
||||||
},
|
},
|
||||||
update() {
|
update() {
|
||||||
this.uTime.value = (Date.now() - this.startTime) * this.timeCoef;
|
this.uTime.value = (Date.now() - this.startTime) * this.timeCoef
|
||||||
this.renderDisp();
|
this.renderDisp()
|
||||||
},
|
},
|
||||||
renderDisp() {
|
renderDisp() {
|
||||||
this.renderMat(this.dispMat, this.dispRT);
|
this.renderMat(this.dispMat, this.dispRT)
|
||||||
this.renderMat(this.normMat, this.normRT);
|
this.renderMat(this.normMat, this.normRT)
|
||||||
},
|
},
|
||||||
renderMat(mat, target) {
|
renderMat(mat, target) {
|
||||||
const renderer = this.three.renderer;
|
const renderer = this.renderer.renderer
|
||||||
this.fsQuad.material = mat;
|
this.fsQuad.material = mat
|
||||||
const oldTarget = renderer.getRenderTarget();
|
const oldTarget = renderer.getRenderTarget()
|
||||||
renderer.setRenderTarget(target);
|
renderer.setRenderTarget(target)
|
||||||
this.fsQuad.render(renderer);
|
this.fsQuad.render(renderer)
|
||||||
renderer.setRenderTarget(oldTarget);
|
renderer.setRenderTarget(oldTarget)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
__hmrId: 'NoisyPlane',
|
__hmrId: 'NoisyPlane',
|
||||||
});
|
})
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { defineComponent, watch } from 'vue';
|
import { defineComponent, watch } from 'vue'
|
||||||
import Sphere from '../../meshes/Sphere.js';
|
import { Sphere } from '../../../build/trois.module.js'
|
||||||
import snoise4 from '../../glsl/snoise4.glsl.js';
|
import snoise4 from '../../glsl/snoise4.glsl.js'
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
extends: Sphere,
|
extends: Sphere,
|
||||||
@ -14,38 +14,38 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
setup(props) {
|
setup(props) {
|
||||||
// uniforms
|
// uniforms
|
||||||
const uTime = { value: 0 };
|
const uTime = { value: 0 }
|
||||||
const uNoiseCoef = { value: props.noiseCoef };
|
const uNoiseCoef = { value: props.noiseCoef }
|
||||||
watch(() => props.noiseCoef, (value) => { uNoiseCoef.value = value; });
|
watch(() => props.noiseCoef, (value) => { uNoiseCoef.value = value })
|
||||||
const uDispCoef = { value: props.dispCoef };
|
const uDispCoef = { value: props.dispCoef }
|
||||||
watch(() => props.dispCoef, (value) => { uDispCoef.value = value; });
|
watch(() => props.dispCoef, (value) => { uDispCoef.value = value })
|
||||||
|
|
||||||
return {
|
return {
|
||||||
uTime, uNoiseCoef, uDispCoef,
|
uTime, uNoiseCoef, uDispCoef,
|
||||||
};
|
}
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
this.updateMaterial();
|
this.updateMaterial()
|
||||||
|
|
||||||
this.startTime = Date.now();
|
this.startTime = Date.now()
|
||||||
this.rendererComponent.onBeforeRender(this.updateTime);
|
this.renderer.onBeforeRender(this.updateTime)
|
||||||
},
|
},
|
||||||
unmounted() {
|
unmounted() {
|
||||||
this.rendererComponent.offBeforeRender(this.updateTime);
|
this.renderer.offBeforeRender(this.updateTime)
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
updateMaterial() {
|
updateMaterial() {
|
||||||
this.material.onBeforeCompile = (shader) => {
|
this.material.onBeforeCompile = (shader) => {
|
||||||
shader.uniforms.uTime = this.uTime;
|
shader.uniforms.uTime = this.uTime
|
||||||
shader.uniforms.uNoiseCoef = this.uNoiseCoef;
|
shader.uniforms.uNoiseCoef = this.uNoiseCoef
|
||||||
shader.uniforms.uDispCoef = this.uDispCoef;
|
shader.uniforms.uDispCoef = this.uDispCoef
|
||||||
shader.vertexShader = `
|
shader.vertexShader = `
|
||||||
uniform float uTime;
|
uniform float uTime;
|
||||||
uniform float uNoiseCoef;
|
uniform float uNoiseCoef;
|
||||||
uniform float uDispCoef;
|
uniform float uDispCoef;
|
||||||
varying float vNoise;
|
varying float vNoise;
|
||||||
${snoise4}
|
${snoise4}
|
||||||
` + shader.vertexShader;
|
` + shader.vertexShader
|
||||||
|
|
||||||
shader.vertexShader = shader.vertexShader.replace(
|
shader.vertexShader = shader.vertexShader.replace(
|
||||||
'#include <begin_vertex>',
|
'#include <begin_vertex>',
|
||||||
@ -55,14 +55,14 @@ export default defineComponent({
|
|||||||
vec3 transformed = vec3(position);
|
vec3 transformed = vec3(position);
|
||||||
transformed += normalize(position) * vNoise * uDispCoef;
|
transformed += normalize(position) * vNoise * uDispCoef;
|
||||||
`
|
`
|
||||||
);
|
)
|
||||||
this.materialShader = shader;
|
this.materialShader = shader
|
||||||
};
|
}
|
||||||
this.material.needsupdate = true;
|
this.material.needsupdate = true
|
||||||
},
|
},
|
||||||
updateTime() {
|
updateTime() {
|
||||||
this.uTime.value = (Date.now() - this.startTime) * this.timeCoef;
|
this.uTime.value = (Date.now() - this.startTime) * this.timeCoef
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
__hmrId: 'NoisySphere',
|
__hmrId: 'NoisySphere',
|
||||||
});
|
})
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { defineComponent, watch } from 'vue';
|
import { defineComponent, watch } from 'vue'
|
||||||
import Text from '../../meshes/Text.js';
|
import { Text } from '../../../build/trois.module.js'
|
||||||
import snoise2 from '../../glsl/snoise2.glsl.js';
|
import snoise2 from '../../glsl/snoise2.glsl.js'
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
extends: Text,
|
extends: Text,
|
||||||
@ -11,37 +11,37 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
setup(props) {
|
setup(props) {
|
||||||
// uniforms
|
// uniforms
|
||||||
const uTime = { value: 0 };
|
const uTime = { value: 0 }
|
||||||
const uNoiseCoef = { value: props.noiseCoef };
|
const uNoiseCoef = { value: props.noiseCoef }
|
||||||
watch(() => props.noiseCoef, (value) => { uNoiseCoef.value = value; });
|
watch(() => props.noiseCoef, (value) => { uNoiseCoef.value = value })
|
||||||
const uZCoef = { value: props.zCoef };
|
const uZCoef = { value: props.zCoef }
|
||||||
watch(() => props.zCoef, (value) => { uZCoef.value = value; });
|
watch(() => props.zCoef, (value) => { uZCoef.value = value })
|
||||||
|
|
||||||
return {
|
return {
|
||||||
uTime, uNoiseCoef, uZCoef,
|
uTime, uNoiseCoef, uZCoef,
|
||||||
};
|
}
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
this.updateMaterial();
|
this.updateMaterial()
|
||||||
|
|
||||||
this.startTime = Date.now();
|
this.startTime = Date.now()
|
||||||
this.rendererComponent.onBeforeRender(this.updateTime);
|
this.renderer.onBeforeRender(this.updateTime)
|
||||||
},
|
},
|
||||||
unmounted() {
|
unmounted() {
|
||||||
this.rendererComponent.offBeforeRender(this.updateTime);
|
this.renderer.offBeforeRender(this.updateTime)
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
updateMaterial() {
|
updateMaterial() {
|
||||||
this.material.onBeforeCompile = (shader) => {
|
this.material.onBeforeCompile = (shader) => {
|
||||||
shader.uniforms.uTime = this.uTime;
|
shader.uniforms.uTime = this.uTime
|
||||||
shader.uniforms.uNoiseCoef = this.uNoiseCoef;
|
shader.uniforms.uNoiseCoef = this.uNoiseCoef
|
||||||
shader.uniforms.uZCoef = this.uZCoef;
|
shader.uniforms.uZCoef = this.uZCoef
|
||||||
shader.vertexShader = `
|
shader.vertexShader = `
|
||||||
uniform float uTime;
|
uniform float uTime;
|
||||||
uniform float uNoiseCoef;
|
uniform float uNoiseCoef;
|
||||||
uniform float uZCoef;
|
uniform float uZCoef;
|
||||||
${snoise2}
|
${snoise2}
|
||||||
` + shader.vertexShader;
|
` + shader.vertexShader
|
||||||
|
|
||||||
shader.vertexShader = shader.vertexShader.replace(
|
shader.vertexShader = shader.vertexShader.replace(
|
||||||
'#include <begin_vertex>',
|
'#include <begin_vertex>',
|
||||||
@ -52,14 +52,14 @@ export default defineComponent({
|
|||||||
vec3 transformed = vec3(position);
|
vec3 transformed = vec3(position);
|
||||||
transformed.z += noise * uZCoef;
|
transformed.z += noise * uZCoef;
|
||||||
`
|
`
|
||||||
);
|
)
|
||||||
this.materialShader = shader;
|
this.materialShader = shader
|
||||||
};
|
}
|
||||||
this.material.needsupdate = true;
|
this.material.needsupdate = true
|
||||||
},
|
},
|
||||||
updateTime() {
|
updateTime() {
|
||||||
this.uTime.value = (Date.now() - this.startTime) * this.timeCoef;
|
this.uTime.value = (Date.now() - this.startTime) * this.timeCoef
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
__hmrId: 'NoisyText',
|
__hmrId: 'NoisyText',
|
||||||
});
|
})
|
||||||
|
@ -1,63 +1,66 @@
|
|||||||
import { defineComponent } from 'vue';
|
import { defineComponent } from 'vue'
|
||||||
import useCannon from './useCannon.js';
|
import useCannon from './useCannon.js'
|
||||||
// import { bindProp } from '../../tools';
|
import { RendererInjectionKey, SceneInjectionKey } from '../../../build/trois.module.js'
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
inject: ['three', 'scene', 'rendererComponent'],
|
inject: {
|
||||||
|
renderer: RendererInjectionKey,
|
||||||
|
scene: SceneInjectionKey,
|
||||||
|
},
|
||||||
props: {
|
props: {
|
||||||
gravity: { type: Object, default: () => ({ x: 0, y: 0, z: -9.82 }) },
|
gravity: { type: Object, default: () => ({ x: 0, y: 0, z: -9.82 }) },
|
||||||
broadphase: { type: String },
|
broadphase: { type: String },
|
||||||
onBeforeStep: Function,
|
onBeforeStep: Function,
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
this._parent = this.getParent();
|
this._parent = this.getParent()
|
||||||
if (!this._parent) console.error('Missing parent (Scene, Group...)');
|
if (!this._parent) console.error('Missing parent (Scene, Group...)')
|
||||||
|
|
||||||
this.cannon = useCannon({ gravity: this.gravity, broadphase: this.broadphase });
|
this.cannon = useCannon({ gravity: this.gravity, broadphase: this.broadphase })
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
this.rendererComponent.onBeforeRender(this.step);
|
this.renderer.onBeforeRender(this.step)
|
||||||
},
|
},
|
||||||
unmounted() {
|
unmounted() {
|
||||||
this.rendererComponent.offBeforeRender(this.step);
|
this.renderer.offBeforeRender(this.step)
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
step() {
|
step() {
|
||||||
this.onBeforeStep?.(this.cannon);
|
this.onBeforeStep?.(this.cannon)
|
||||||
this.cannon.step();
|
this.cannon.step()
|
||||||
},
|
},
|
||||||
add(o) {
|
add(o) {
|
||||||
this.addToParent(o);
|
this.addToParent(o)
|
||||||
this.cannon.addMesh(o);
|
this.cannon.addMesh(o)
|
||||||
},
|
},
|
||||||
remove(o) {
|
remove(o) {
|
||||||
this.removeFromParent(o);
|
this.removeFromParent(o)
|
||||||
this.cannon.removeMesh(o);
|
this.cannon.removeMesh(o)
|
||||||
},
|
},
|
||||||
getParent() {
|
getParent() {
|
||||||
let parent = this.$parent;
|
let parent = this.$parent
|
||||||
while (parent) {
|
while (parent) {
|
||||||
if (parent.add) return parent;
|
if (parent.add) return parent
|
||||||
parent = parent.$parent;
|
parent = parent.$parent
|
||||||
}
|
}
|
||||||
return false;
|
return false
|
||||||
},
|
},
|
||||||
addToParent(o) {
|
addToParent(o) {
|
||||||
if (this._parent) {
|
if (this._parent) {
|
||||||
this._parent.add(o);
|
this._parent.add(o)
|
||||||
return true;
|
return true
|
||||||
}
|
}
|
||||||
return false;
|
return false
|
||||||
},
|
},
|
||||||
removeFromParent(o) {
|
removeFromParent(o) {
|
||||||
if (this._parent) {
|
if (this._parent) {
|
||||||
this._parent.remove(o);
|
this._parent.remove(o)
|
||||||
return true;
|
return true
|
||||||
}
|
}
|
||||||
return false;
|
return false
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
render() {
|
render() {
|
||||||
return this.$slots.default ? this.$slots.default() : [];
|
return this.$slots.default ? this.$slots.default() : []
|
||||||
},
|
},
|
||||||
});
|
})
|
||||||
|
@ -3,191 +3,191 @@ import {
|
|||||||
Body, World,
|
Body, World,
|
||||||
SAPBroadphase,
|
SAPBroadphase,
|
||||||
Quaternion, Vec3,
|
Quaternion, Vec3,
|
||||||
} from 'cannon';
|
} from 'cannon'
|
||||||
|
|
||||||
export default function useCannon(options) {
|
export default function useCannon(options) {
|
||||||
const {
|
const {
|
||||||
broadphase = null,
|
broadphase = null,
|
||||||
gravity = new Vec3(0, 0, -9.82),
|
gravity = new Vec3(0, 0, -9.82),
|
||||||
// solverIterations = 10,
|
// solverIterations = 10,
|
||||||
} = options;
|
} = options
|
||||||
|
|
||||||
const world = new World();
|
const world = new World()
|
||||||
world.gravity.set(gravity.x, gravity.y, gravity.z);
|
world.gravity.set(gravity.x, gravity.y, gravity.z)
|
||||||
|
|
||||||
if (broadphase === 'sap') {
|
if (broadphase === 'sap') {
|
||||||
world.broadphase = new SAPBroadphase(world);
|
world.broadphase = new SAPBroadphase(world)
|
||||||
}
|
}
|
||||||
// world.solver.iterations = solverIterations;
|
// world.solver.iterations = solverIterations;
|
||||||
|
|
||||||
const meshes = [];
|
const meshes = []
|
||||||
|
|
||||||
const obj = {
|
const obj = {
|
||||||
world,
|
world,
|
||||||
addMesh,
|
addMesh,
|
||||||
removeMesh,
|
removeMesh,
|
||||||
step,
|
step,
|
||||||
};
|
}
|
||||||
return obj;
|
return obj
|
||||||
|
|
||||||
function addMesh(mesh) {
|
function addMesh(mesh) {
|
||||||
const shape = getShape(mesh.geometry);
|
const shape = getShape(mesh.geometry)
|
||||||
if (shape) {
|
if (shape) {
|
||||||
if (mesh.isInstancedMesh) {
|
if (mesh.isInstancedMesh) {
|
||||||
handleInstancedMesh(mesh, shape);
|
handleInstancedMesh(mesh, shape)
|
||||||
} else if (mesh.isMesh) {
|
} else if (mesh.isMesh) {
|
||||||
handleMesh(mesh, shape);
|
handleMesh(mesh, shape)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
console.warn(`Unhandled Mesh geometry ${mesh.geometry.type}`);
|
console.warn(`Unhandled Mesh geometry ${mesh.geometry.type}`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function removeMesh(mesh) {
|
function removeMesh(mesh) {
|
||||||
const index = meshes.indexOf(mesh);
|
const index = meshes.indexOf(mesh)
|
||||||
if (index !== -1) {
|
if (index !== -1) {
|
||||||
meshes.splice(index, 1);
|
meshes.splice(index, 1)
|
||||||
}
|
}
|
||||||
if (mesh.userData.bodies) {
|
if (mesh.userData.bodies) {
|
||||||
mesh.userData.bodies.forEach(body => {
|
mesh.userData.bodies.forEach(body => {
|
||||||
world.removeBody(body);
|
world.removeBody(body)
|
||||||
});
|
})
|
||||||
mesh.userData.bodies = [];
|
mesh.userData.bodies = []
|
||||||
}
|
}
|
||||||
if (mesh.userData.body) {
|
if (mesh.userData.body) {
|
||||||
world.removeBody(mesh.userData.body);
|
world.removeBody(mesh.userData.body)
|
||||||
delete mesh.userData.body;
|
delete mesh.userData.body
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function step() {
|
function step() {
|
||||||
world.step(1 / 60);
|
world.step(1 / 60)
|
||||||
for (let i = 0, l = meshes.length; i < l; i++) {
|
for (let i = 0, l = meshes.length; i < l; i++) {
|
||||||
const mesh = meshes[i];
|
const mesh = meshes[i]
|
||||||
if (mesh.isInstancedMesh) {
|
if (mesh.isInstancedMesh) {
|
||||||
const iMatrix = mesh.instanceMatrix.array;
|
const iMatrix = mesh.instanceMatrix.array
|
||||||
const bodies = mesh.userData.bodies;
|
const bodies = mesh.userData.bodies
|
||||||
for (let j = 0; j < bodies.length; j++) {
|
for (let j = 0; j < bodies.length; j++) {
|
||||||
const body = bodies[j];
|
const body = bodies[j]
|
||||||
compose(body.position, body.quaternion, mesh.userData.scales[j], iMatrix, j * 16);
|
compose(body.position, body.quaternion, mesh.userData.scales[j], iMatrix, j * 16)
|
||||||
}
|
}
|
||||||
mesh.instanceMatrix.needsUpdate = true;
|
mesh.instanceMatrix.needsUpdate = true
|
||||||
} else if (mesh.isMesh) {
|
} else if (mesh.isMesh) {
|
||||||
mesh.position.copy(mesh.userData.body.position);
|
mesh.position.copy(mesh.userData.body.position)
|
||||||
mesh.quaternion.copy(mesh.userData.body.quaternion);
|
mesh.quaternion.copy(mesh.userData.body.quaternion)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
function getShape(geometry) {
|
function getShape(geometry) {
|
||||||
const parameters = geometry.parameters;
|
const parameters = geometry.parameters
|
||||||
switch (geometry.type) {
|
switch (geometry.type) {
|
||||||
case 'BoxGeometry':
|
case 'BoxGeometry':
|
||||||
return new Box(new Vec3(
|
return new Box(new Vec3(
|
||||||
parameters.width / 2,
|
parameters.width / 2,
|
||||||
parameters.height / 2,
|
parameters.height / 2,
|
||||||
parameters.depth / 2
|
parameters.depth / 2
|
||||||
));
|
))
|
||||||
|
|
||||||
case 'PlaneGeometry':
|
case 'PlaneGeometry':
|
||||||
return new Plane();
|
return new Plane()
|
||||||
|
|
||||||
case 'SphereGeometry':
|
case 'SphereGeometry':
|
||||||
return new Sphere(parameters.radius);
|
return new Sphere(parameters.radius)
|
||||||
|
|
||||||
case 'CylinderGeometry':
|
case 'CylinderGeometry':
|
||||||
return new Cylinder(parameters.radiusTop, parameters.radiusBottom, parameters.height, parameters.radialSegments);
|
return new Cylinder(parameters.radiusTop, parameters.radiusBottom, parameters.height, parameters.radialSegments)
|
||||||
}
|
}
|
||||||
return null;
|
return null
|
||||||
};
|
}
|
||||||
|
|
||||||
function handleMesh(mesh, shape) {
|
function handleMesh(mesh, shape) {
|
||||||
const position = new Vec3();
|
const position = new Vec3()
|
||||||
position.copy(mesh.position);
|
position.copy(mesh.position)
|
||||||
|
|
||||||
const quaternion = new Quaternion();
|
const quaternion = new Quaternion()
|
||||||
quaternion.copy(mesh.quaternion);
|
quaternion.copy(mesh.quaternion)
|
||||||
|
|
||||||
const mass = mesh.userData.mass ? mesh.userData.mass : 0;
|
const mass = mesh.userData.mass ? mesh.userData.mass : 0
|
||||||
const damping = mesh.userData.damping ? mesh.userData.damping : 0.01;
|
const damping = mesh.userData.damping ? mesh.userData.damping : 0.01
|
||||||
|
|
||||||
const body = new Body({ shape, position, quaternion, mass, linearDamping: damping, angularDamping: damping });
|
const body = new Body({ shape, position, quaternion, mass, linearDamping: damping, angularDamping: damping })
|
||||||
world.addBody(body);
|
world.addBody(body)
|
||||||
|
|
||||||
mesh.userData.body = body;
|
mesh.userData.body = body
|
||||||
if (mesh.userData.mass > 0) {
|
if (mesh.userData.mass > 0) {
|
||||||
meshes.push(mesh);
|
meshes.push(mesh)
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
function handleInstancedMesh(mesh, shape) {
|
function handleInstancedMesh(mesh, shape) {
|
||||||
const iMatrix = mesh.instanceMatrix.array;
|
const iMatrix = mesh.instanceMatrix.array
|
||||||
const bodies = [];
|
const bodies = []
|
||||||
for (let i = 0; i < mesh.count; i++) {
|
for (let i = 0; i < mesh.count; i++) {
|
||||||
const index = i * 16;
|
const index = i * 16
|
||||||
|
|
||||||
const position = new Vec3();
|
const position = new Vec3()
|
||||||
position.set(iMatrix[index + 12], iMatrix[index + 13], iMatrix[index + 14]);
|
position.set(iMatrix[index + 12], iMatrix[index + 13], iMatrix[index + 14])
|
||||||
|
|
||||||
// handle instance scale
|
// handle instance scale
|
||||||
let scale = 1;
|
let scale = 1
|
||||||
if (mesh.userData.scales?.[i]) scale = mesh.userData.scales?.[i];
|
if (mesh.userData.scales?.[i]) scale = mesh.userData.scales?.[i]
|
||||||
const geoParams = mesh.geometry.parameters;
|
const geoParams = mesh.geometry.parameters
|
||||||
if (mesh.geometry.type === 'SphereGeometry') {
|
if (mesh.geometry.type === 'SphereGeometry') {
|
||||||
shape = new Sphere(scale * geoParams.radius);
|
shape = new Sphere(scale * geoParams.radius)
|
||||||
} else if (mesh.geometry.type === 'BoxGeometry') {
|
} else if (mesh.geometry.type === 'BoxGeometry') {
|
||||||
shape = new Box(new Vec3(
|
shape = new Box(new Vec3(
|
||||||
scale * geoParams.width / 2,
|
scale * geoParams.width / 2,
|
||||||
scale * geoParams.height / 2,
|
scale * geoParams.height / 2,
|
||||||
scale * geoParams.depth / 2
|
scale * geoParams.depth / 2
|
||||||
));
|
))
|
||||||
} else {
|
} else {
|
||||||
console.warn(`Unhandled InstancedMesh geometry ${mesh.geometry.type}`);
|
console.warn(`Unhandled InstancedMesh geometry ${mesh.geometry.type}`)
|
||||||
return;
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
let mass = 0;
|
let mass = 0
|
||||||
if (mesh.userData.masses?.[i]) mass = mesh.userData.masses[i];
|
if (mesh.userData.masses?.[i]) mass = mesh.userData.masses[i]
|
||||||
else if (mesh.userData.mass) mass = mesh.userData.mass;
|
else if (mesh.userData.mass) mass = mesh.userData.mass
|
||||||
|
|
||||||
let damping = 0.01;
|
let damping = 0.01
|
||||||
if (mesh.userData.dampings?.[i]) damping = mesh.userData.dampings?.[i];
|
if (mesh.userData.dampings?.[i]) damping = mesh.userData.dampings?.[i]
|
||||||
else if (mesh.userData.damping) damping = mesh.userData.damping;
|
else if (mesh.userData.damping) damping = mesh.userData.damping
|
||||||
|
|
||||||
const body = new Body({ shape, position, mass, linearDamping: damping, angularDamping: damping });
|
const body = new Body({ shape, position, mass, linearDamping: damping, angularDamping: damping })
|
||||||
world.addBody(body);
|
world.addBody(body)
|
||||||
bodies.push(body);
|
bodies.push(body)
|
||||||
}
|
}
|
||||||
|
|
||||||
mesh.userData.bodies = bodies;
|
mesh.userData.bodies = bodies
|
||||||
meshes.push(mesh);
|
meshes.push(mesh)
|
||||||
};
|
}
|
||||||
|
|
||||||
function compose(position, quaternion, scale, iMatrix, index) {
|
function compose(position, quaternion, scale, iMatrix, index) {
|
||||||
const x = quaternion.x, y = quaternion.y, z = quaternion.z, w = quaternion.w;
|
const x = quaternion.x, y = quaternion.y, z = quaternion.z, w = quaternion.w
|
||||||
const x2 = x + x, y2 = y + y, z2 = z + z;
|
const x2 = x + x, y2 = y + y, z2 = z + z
|
||||||
const xx = x * x2, xy = x * y2, xz = x * z2;
|
const xx = x * x2, xy = x * y2, xz = x * z2
|
||||||
const yy = y * y2, yz = y * z2, zz = z * z2;
|
const yy = y * y2, yz = y * z2, zz = z * z2
|
||||||
const wx = w * x2, wy = w * y2, wz = w * z2;
|
const wx = w * x2, wy = w * y2, wz = w * z2
|
||||||
|
|
||||||
iMatrix[index + 0] = (1 - (yy + zz)) * scale;
|
iMatrix[index + 0] = (1 - (yy + zz)) * scale
|
||||||
iMatrix[index + 1] = (xy + wz) * scale;
|
iMatrix[index + 1] = (xy + wz) * scale
|
||||||
iMatrix[index + 2] = (xz - wy) * scale;
|
iMatrix[index + 2] = (xz - wy) * scale
|
||||||
iMatrix[index + 3] = 0;
|
iMatrix[index + 3] = 0
|
||||||
|
|
||||||
iMatrix[index + 4] = (xy - wz) * scale;
|
iMatrix[index + 4] = (xy - wz) * scale
|
||||||
iMatrix[index + 5] = (1 - (xx + zz)) * scale;
|
iMatrix[index + 5] = (1 - (xx + zz)) * scale
|
||||||
iMatrix[index + 6] = (yz + wx) * scale;
|
iMatrix[index + 6] = (yz + wx) * scale
|
||||||
iMatrix[index + 7] = 0;
|
iMatrix[index + 7] = 0
|
||||||
|
|
||||||
iMatrix[index + 8] = (xz + wy) * scale;
|
iMatrix[index + 8] = (xz + wy) * scale
|
||||||
iMatrix[index + 9] = (yz - wx) * scale;
|
iMatrix[index + 9] = (yz - wx) * scale
|
||||||
iMatrix[index + 10] = (1 - (xx + yy)) * scale;
|
iMatrix[index + 10] = (1 - (xx + yy)) * scale
|
||||||
iMatrix[index + 11] = 0;
|
iMatrix[index + 11] = 0
|
||||||
|
|
||||||
iMatrix[index + 12] = position.x;
|
iMatrix[index + 12] = position.x
|
||||||
iMatrix[index + 13] = position.y;
|
iMatrix[index + 13] = position.y
|
||||||
iMatrix[index + 14] = position.z;
|
iMatrix[index + 14] = position.z
|
||||||
iMatrix[index + 15] = 1;
|
iMatrix[index + 15] = 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,22 +7,22 @@ import {
|
|||||||
Object3D,
|
Object3D,
|
||||||
Vector2,
|
Vector2,
|
||||||
Vector3,
|
Vector3,
|
||||||
} from 'three';
|
} from 'three'
|
||||||
|
|
||||||
import { Geometry, Face3 } from 'three/examples/jsm/deprecated/Geometry.js';
|
import { Geometry, Face3 } from 'three/examples/jsm/deprecated/Geometry.js'
|
||||||
|
|
||||||
export default class AnimatedPlane {
|
export default class AnimatedPlane {
|
||||||
constructor(params) {
|
constructor(params) {
|
||||||
Object.entries(params).forEach(([key, value]) => {
|
Object.entries(params).forEach(([key, value]) => {
|
||||||
this[key] = value;
|
this[key] = value
|
||||||
});
|
})
|
||||||
|
|
||||||
this.o3d = new Object3D();
|
this.o3d = new Object3D()
|
||||||
this.uProgress = { value: 0 };
|
this.uProgress = { value: 0 }
|
||||||
this.uvScale = new Vector2();
|
this.uvScale = new Vector2()
|
||||||
|
|
||||||
this.initMaterial();
|
this.initMaterial()
|
||||||
this.initPlane();
|
this.initPlane()
|
||||||
}
|
}
|
||||||
|
|
||||||
initMaterial() {
|
initMaterial() {
|
||||||
@ -31,8 +31,8 @@ export default class AnimatedPlane {
|
|||||||
transparent: true,
|
transparent: true,
|
||||||
map: this.texture,
|
map: this.texture,
|
||||||
onBeforeCompile: shader => {
|
onBeforeCompile: shader => {
|
||||||
shader.uniforms.progress = this.uProgress;
|
shader.uniforms.progress = this.uProgress
|
||||||
shader.uniforms.uvScale = { value: this.uvScale };
|
shader.uniforms.uvScale = { value: this.uvScale }
|
||||||
shader.vertexShader = `
|
shader.vertexShader = `
|
||||||
uniform float progress;
|
uniform float progress;
|
||||||
uniform vec2 uvScale;
|
uniform vec2 uvScale;
|
||||||
@ -56,12 +56,12 @@ export default class AnimatedPlane {
|
|||||||
sy, -sx * cy, cx * cy
|
sy, -sx * cy, cx * cy
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
` + shader.vertexShader;
|
` + shader.vertexShader
|
||||||
|
|
||||||
shader.vertexShader = shader.vertexShader.replace('#include <uv_vertex>', `
|
shader.vertexShader = shader.vertexShader.replace('#include <uv_vertex>', `
|
||||||
#include <uv_vertex>
|
#include <uv_vertex>
|
||||||
vUv = vUv * uvScale + uvOffset;
|
vUv = vUv * uvScale + uvOffset;
|
||||||
`);
|
`)
|
||||||
|
|
||||||
shader.vertexShader = shader.vertexShader.replace('#include <project_vertex>', `
|
shader.vertexShader = shader.vertexShader.replace('#include <project_vertex>', `
|
||||||
mat3 rotMat = rotationMatrixXYZ(progress * rotation);
|
mat3 rotMat = rotationMatrixXYZ(progress * rotation);
|
||||||
@ -76,124 +76,124 @@ export default class AnimatedPlane {
|
|||||||
|
|
||||||
mvPosition = modelViewMatrix * mvPosition;
|
mvPosition = modelViewMatrix * mvPosition;
|
||||||
gl_Position = projectionMatrix * mvPosition;
|
gl_Position = projectionMatrix * mvPosition;
|
||||||
`);
|
`)
|
||||||
},
|
},
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
initPlane() {
|
initPlane() {
|
||||||
const { width, wWidth, wHeight } = this.screen;
|
const { width, wWidth, wHeight } = this.screen
|
||||||
this.wSize = this.size * wWidth / width;
|
this.wSize = this.size * wWidth / width
|
||||||
this.nx = Math.ceil(wWidth / this.wSize) + 1;
|
this.nx = Math.ceil(wWidth / this.wSize) + 1
|
||||||
this.ny = Math.ceil(wHeight / this.wSize) + 1;
|
this.ny = Math.ceil(wHeight / this.wSize) + 1
|
||||||
this.icount = this.nx * this.ny;
|
this.icount = this.nx * this.ny
|
||||||
|
|
||||||
this.initGeometry();
|
this.initGeometry()
|
||||||
this.initUV();
|
this.initUV()
|
||||||
this.initAnimAttributes();
|
this.initAnimAttributes()
|
||||||
|
|
||||||
if (this.imesh) {
|
if (this.imesh) {
|
||||||
this.o3d.remove(this.imesh);
|
this.o3d.remove(this.imesh)
|
||||||
}
|
}
|
||||||
this.imesh = new InstancedMesh(this.bGeometry, this.material, this.icount);
|
this.imesh = new InstancedMesh(this.bGeometry, this.material, this.icount)
|
||||||
this.o3d.add(this.imesh);
|
this.o3d.add(this.imesh)
|
||||||
|
|
||||||
const dummy = new Object3D();
|
const dummy = new Object3D()
|
||||||
let index = 0;
|
let index = 0
|
||||||
let x = -(wWidth - (wWidth - this.nx * this.wSize)) / 2 + this.dx;
|
let x = -(wWidth - (wWidth - this.nx * this.wSize)) / 2 + this.dx
|
||||||
for (let i = 0; i < this.nx; i++) {
|
for (let i = 0; i < this.nx; i++) {
|
||||||
let y = -(wHeight - (wHeight - this.ny * this.wSize)) / 2 + this.dy;
|
let y = -(wHeight - (wHeight - this.ny * this.wSize)) / 2 + this.dy
|
||||||
for (let j = 0; j < this.ny; j++) {
|
for (let j = 0; j < this.ny; j++) {
|
||||||
dummy.position.set(x, y, 0);
|
dummy.position.set(x, y, 0)
|
||||||
dummy.updateMatrix();
|
dummy.updateMatrix()
|
||||||
this.imesh.setMatrixAt(index++, dummy.matrix);
|
this.imesh.setMatrixAt(index++, dummy.matrix)
|
||||||
y += this.wSize;
|
y += this.wSize
|
||||||
}
|
}
|
||||||
x += this.wSize;
|
x += this.wSize
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
initGeometry() {
|
initGeometry() {
|
||||||
// square
|
// square
|
||||||
const geometry = new Geometry();
|
const geometry = new Geometry()
|
||||||
geometry.vertices.push(new Vector3(0, 0, 0));
|
geometry.vertices.push(new Vector3(0, 0, 0))
|
||||||
geometry.vertices.push(new Vector3(this.wSize, 0, 0));
|
geometry.vertices.push(new Vector3(this.wSize, 0, 0))
|
||||||
geometry.vertices.push(new Vector3(0, this.wSize, 0));
|
geometry.vertices.push(new Vector3(0, this.wSize, 0))
|
||||||
geometry.vertices.push(new Vector3(this.wSize, this.wSize, 0));
|
geometry.vertices.push(new Vector3(this.wSize, this.wSize, 0))
|
||||||
geometry.faces.push(new Face3(0, 2, 1));
|
geometry.faces.push(new Face3(0, 2, 1))
|
||||||
geometry.faces.push(new Face3(2, 3, 1));
|
geometry.faces.push(new Face3(2, 3, 1))
|
||||||
|
|
||||||
geometry.faceVertexUvs[0].push([
|
geometry.faceVertexUvs[0].push([
|
||||||
new Vector2(0, 0),
|
new Vector2(0, 0),
|
||||||
new Vector2(0, 1),
|
new Vector2(0, 1),
|
||||||
new Vector2(1, 0),
|
new Vector2(1, 0),
|
||||||
]);
|
])
|
||||||
geometry.faceVertexUvs[0].push([
|
geometry.faceVertexUvs[0].push([
|
||||||
new Vector2(0, 1),
|
new Vector2(0, 1),
|
||||||
new Vector2(1, 1),
|
new Vector2(1, 1),
|
||||||
new Vector2(1, 0),
|
new Vector2(1, 0),
|
||||||
]);
|
])
|
||||||
|
|
||||||
// geometry.computeFaceNormals();
|
// geometry.computeFaceNormals();
|
||||||
// geometry.computeVertexNormals();
|
// geometry.computeVertexNormals();
|
||||||
|
|
||||||
// center
|
// center
|
||||||
this.dx = this.wSize / 2;
|
this.dx = this.wSize / 2
|
||||||
this.dy = this.wSize / 2;
|
this.dy = this.wSize / 2
|
||||||
geometry.translate(-this.dx, -this.dy, 0);
|
geometry.translate(-this.dx, -this.dy, 0)
|
||||||
|
|
||||||
this.bGeometry = geometry.toBufferGeometry();
|
this.bGeometry = geometry.toBufferGeometry()
|
||||||
}
|
}
|
||||||
|
|
||||||
initAnimAttributes() {
|
initAnimAttributes() {
|
||||||
const { randFloat: rnd, randFloatSpread: rndFS } = MathUtils;
|
const { randFloat: rnd, randFloatSpread: rndFS } = MathUtils
|
||||||
const v3 = new Vector3();
|
const v3 = new Vector3()
|
||||||
|
|
||||||
const offsets = new Float32Array(this.icount * 3);
|
const offsets = new Float32Array(this.icount * 3)
|
||||||
for (let i = 0; i < offsets.length; i += 3) {
|
for (let i = 0; i < offsets.length; i += 3) {
|
||||||
if (this.anim === 1) v3.set(rndFS(10), rnd(50, 100), rnd(20, 50)).toArray(offsets, i);
|
if (this.anim === 1) v3.set(rndFS(10), rnd(50, 100), rnd(20, 50)).toArray(offsets, i)
|
||||||
else v3.set(rndFS(20), rndFS(20), rnd(20, 200)).toArray(offsets, i);
|
else v3.set(rndFS(20), rndFS(20), rnd(20, 200)).toArray(offsets, i)
|
||||||
}
|
}
|
||||||
this.bGeometry.setAttribute('offset', new InstancedBufferAttribute(offsets, 3));
|
this.bGeometry.setAttribute('offset', new InstancedBufferAttribute(offsets, 3))
|
||||||
|
|
||||||
const rotations = new Float32Array(this.icount * 3);
|
const rotations = new Float32Array(this.icount * 3)
|
||||||
const angle = Math.PI * 4;
|
const angle = Math.PI * 4
|
||||||
for (let i = 0; i < rotations.length; i += 3) {
|
for (let i = 0; i < rotations.length; i += 3) {
|
||||||
rotations[i] = rndFS(angle);
|
rotations[i] = rndFS(angle)
|
||||||
rotations[i + 1] = rndFS(angle);
|
rotations[i + 1] = rndFS(angle)
|
||||||
rotations[i + 2] = rndFS(angle);
|
rotations[i + 2] = rndFS(angle)
|
||||||
}
|
}
|
||||||
this.bGeometry.setAttribute('rotation', new InstancedBufferAttribute(rotations, 3));
|
this.bGeometry.setAttribute('rotation', new InstancedBufferAttribute(rotations, 3))
|
||||||
}
|
}
|
||||||
|
|
||||||
initUV() {
|
initUV() {
|
||||||
const ratio = this.nx / this.ny;
|
const ratio = this.nx / this.ny
|
||||||
const tRatio = this.texture.image.width / this.texture.image.height;
|
const tRatio = this.texture.image.width / this.texture.image.height
|
||||||
if (ratio > tRatio) this.uvScale.set(1 / this.nx, (tRatio / ratio) / this.ny);
|
if (ratio > tRatio) this.uvScale.set(1 / this.nx, (tRatio / ratio) / this.ny)
|
||||||
else this.uvScale.set((ratio / tRatio) / this.nx, 1 / this.ny);
|
else this.uvScale.set((ratio / tRatio) / this.nx, 1 / this.ny)
|
||||||
const nW = this.uvScale.x * this.nx;
|
const nW = this.uvScale.x * this.nx
|
||||||
const nH = this.uvScale.y * this.ny;
|
const nH = this.uvScale.y * this.ny
|
||||||
|
|
||||||
const v2 = new Vector2();
|
const v2 = new Vector2()
|
||||||
const uvOffsets = new Float32Array(this.icount * 2);
|
const uvOffsets = new Float32Array(this.icount * 2)
|
||||||
for (let i = 0; i < this.nx; i++) {
|
for (let i = 0; i < this.nx; i++) {
|
||||||
for (let j = 0; j < this.ny; j++) {
|
for (let j = 0; j < this.ny; j++) {
|
||||||
v2.set(
|
v2.set(
|
||||||
this.uvScale.x * i + (1 - nW) / 2,
|
this.uvScale.x * i + (1 - nW) / 2,
|
||||||
this.uvScale.y * j + (1 - nH) / 2
|
this.uvScale.y * j + (1 - nH) / 2
|
||||||
).toArray(uvOffsets, (i * this.ny + j) * 2);
|
).toArray(uvOffsets, (i * this.ny + j) * 2)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.bGeometry.setAttribute('uvOffset', new InstancedBufferAttribute(uvOffsets, 2));
|
this.bGeometry.setAttribute('uvOffset', new InstancedBufferAttribute(uvOffsets, 2))
|
||||||
}
|
}
|
||||||
|
|
||||||
setTexture(texture) {
|
setTexture(texture) {
|
||||||
this.texture = texture;
|
this.texture = texture
|
||||||
this.material.map = texture;
|
this.material.map = texture
|
||||||
this.initUV();
|
this.initUV()
|
||||||
}
|
}
|
||||||
|
|
||||||
resize() {
|
resize() {
|
||||||
this.initPlane();
|
this.initPlane()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,52 +7,47 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { defineComponent } from 'vue';
|
import { defineComponent } from 'vue'
|
||||||
import { Object3D } from 'three';
|
import { Object3D } from 'three'
|
||||||
import { gsap, Power4 } from 'gsap';
|
import { gsap, Power4 } from 'gsap'
|
||||||
|
|
||||||
import Camera from '../../core/PerspectiveCamera.js';
|
import { lerp, useTextures, Camera, Renderer, Scene } from '../../../build/trois.module.js'
|
||||||
import Renderer from '../../core/Renderer.js';
|
import AnimatedPlane from './AnimatedPlane.js'
|
||||||
import Scene from '../../core/Scene.js';
|
|
||||||
|
|
||||||
import { lerp } from '../../tools';
|
|
||||||
import AnimatedPlane from './AnimatedPlane.js';
|
|
||||||
import useTextures from '../../use/useTextures';
|
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
components: { Camera, Renderer, Scene },
|
components: { Camera, Renderer, Scene },
|
||||||
props: {
|
props: {
|
||||||
images: Array,
|
images: Array,
|
||||||
events: { type: Object, default: () => { return { wheel: true, click: true, keyup: true }; } },
|
events: { type: Object, default: () => { return { wheel: true, click: true, keyup: true } } },
|
||||||
},
|
},
|
||||||
setup() {
|
setup() {
|
||||||
const loader = useTextures();
|
const loader = useTextures()
|
||||||
return {
|
return {
|
||||||
loader,
|
loader,
|
||||||
progress: 0,
|
progress: 0,
|
||||||
targetProgress: 0,
|
targetProgress: 0,
|
||||||
};
|
}
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
this.renderer = this.$refs.renderer;
|
this.renderer = this.$refs.renderer
|
||||||
this.three = this.renderer.three;
|
this.three = this.renderer.three
|
||||||
|
|
||||||
if (this.images.length < 2) {
|
if (this.images.length < 2) {
|
||||||
console.error('This slider needs at least 2 images.');
|
console.error('This slider needs at least 2 images.')
|
||||||
} else {
|
} else {
|
||||||
this.loader.loadTextures(this.images, this.init);
|
this.loader.loadTextures(this.images, this.init)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
unmounted() {
|
unmounted() {
|
||||||
this.loader.dispose();
|
this.loader.dispose()
|
||||||
const domElement = this.three.renderer.domElement;
|
const domElement = this.renderer.renderer.domElement
|
||||||
domElement.removeEventListener('click', this.onClick);
|
domElement.removeEventListener('click', this.onClick)
|
||||||
domElement.removeEventListener('wheel', this.onWheel);
|
domElement.removeEventListener('wheel', this.onWheel)
|
||||||
document.removeEventListener('keyup', this.onKeyup);
|
document.removeEventListener('keyup', this.onKeyup)
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
init() {
|
init() {
|
||||||
this.initScene();
|
this.initScene()
|
||||||
|
|
||||||
gsap.fromTo(this.plane1.uProgress,
|
gsap.fromTo(this.plane1.uProgress,
|
||||||
{
|
{
|
||||||
@ -63,105 +58,105 @@ export default defineComponent({
|
|||||||
duration: 2.5,
|
duration: 2.5,
|
||||||
ease: Power4.easeOut,
|
ease: Power4.easeOut,
|
||||||
}
|
}
|
||||||
);
|
)
|
||||||
|
|
||||||
const domElement = this.three.renderer.domElement;
|
const domElement = this.renderer.renderer.domElement
|
||||||
if (this.events.click) domElement.addEventListener('click', this.onClick);
|
if (this.events.click) domElement.addEventListener('click', this.onClick)
|
||||||
if (this.events.wheel) domElement.addEventListener('wheel', this.onWheel);
|
if (this.events.wheel) domElement.addEventListener('wheel', this.onWheel)
|
||||||
if (this.events.keyup) document.addEventListener('keyup', this.onKeyup);
|
if (this.events.keyup) document.addEventListener('keyup', this.onKeyup)
|
||||||
this.renderer.onBeforeRender(this.updateProgress);
|
this.renderer.onBeforeRender(this.updateProgress)
|
||||||
this.renderer.onAfterResize(this.onResize);
|
this.renderer.onResize(this.onResize)
|
||||||
},
|
},
|
||||||
initScene() {
|
initScene() {
|
||||||
const renderer = this.three.renderer;
|
const renderer = this.renderer.renderer
|
||||||
const scene = this.$refs.scene.scene;
|
const scene = this.$refs.scene.scene
|
||||||
|
|
||||||
this.plane1 = new AnimatedPlane({
|
this.plane1 = new AnimatedPlane({
|
||||||
renderer, screen: this.three.size,
|
renderer, screen: this.renderer.size,
|
||||||
size: 10,
|
size: 10,
|
||||||
anim: 1,
|
anim: 1,
|
||||||
texture: this.loader.textures[0],
|
texture: this.loader.textures[0],
|
||||||
});
|
})
|
||||||
|
|
||||||
this.plane2 = new AnimatedPlane({
|
this.plane2 = new AnimatedPlane({
|
||||||
renderer, screen: this.three.size,
|
renderer, screen: this.renderer.size,
|
||||||
size: 10,
|
size: 10,
|
||||||
anim: 2,
|
anim: 2,
|
||||||
texture: this.loader.textures[1],
|
texture: this.loader.textures[1],
|
||||||
});
|
})
|
||||||
|
|
||||||
this.setPlanesProgress(0);
|
this.setPlanesProgress(0)
|
||||||
this.planes = new Object3D();
|
this.planes = new Object3D()
|
||||||
this.planes.add(this.plane1.o3d);
|
this.planes.add(this.plane1.o3d)
|
||||||
this.planes.add(this.plane2.o3d);
|
this.planes.add(this.plane2.o3d)
|
||||||
scene.add(this.planes);
|
scene.add(this.planes)
|
||||||
},
|
},
|
||||||
onResize() {
|
onResize() {
|
||||||
this.plane1.resize();
|
this.plane1.resize()
|
||||||
this.plane2.resize();
|
this.plane2.resize()
|
||||||
},
|
},
|
||||||
onWheel(e) {
|
onWheel(e) {
|
||||||
// e.preventDefault();
|
// e.preventDefault()
|
||||||
if (e.deltaY > 0) {
|
if (e.deltaY > 0) {
|
||||||
this.setTargetProgress(this.targetProgress + 1 / 20);
|
this.setTargetProgress(this.targetProgress + 1 / 20)
|
||||||
} else {
|
} else {
|
||||||
this.setTargetProgress(this.targetProgress - 1 / 20);
|
this.setTargetProgress(this.targetProgress - 1 / 20)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onClick(e) {
|
onClick(e) {
|
||||||
if (e.clientY < this.three.size.height / 2) {
|
if (e.clientY < this.renderer.size.height / 2) {
|
||||||
this.navPrevious();
|
this.navPrevious()
|
||||||
} else {
|
} else {
|
||||||
this.navNext();
|
this.navNext()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onKeyup(e) {
|
onKeyup(e) {
|
||||||
if (e.keyCode === 37 || e.keyCode === 38) {
|
if (e.keyCode === 37 || e.keyCode === 38) {
|
||||||
this.navPrevious();
|
this.navPrevious()
|
||||||
} else if (e.keyCode === 39 || e.keyCode === 40) {
|
} else if (e.keyCode === 39 || e.keyCode === 40) {
|
||||||
this.navNext();
|
this.navNext()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
navNext() {
|
navNext() {
|
||||||
if (Number.isInteger(this.targetProgress)) this.setTargetProgress(this.targetProgress + 1);
|
if (Number.isInteger(this.targetProgress)) this.setTargetProgress(this.targetProgress + 1)
|
||||||
else this.setTargetProgress(Math.ceil(this.targetProgress));
|
else this.setTargetProgress(Math.ceil(this.targetProgress))
|
||||||
},
|
},
|
||||||
navPrevious() {
|
navPrevious() {
|
||||||
if (Number.isInteger(this.targetProgress)) this.setTargetProgress(this.targetProgress - 1);
|
if (Number.isInteger(this.targetProgress)) this.setTargetProgress(this.targetProgress - 1)
|
||||||
else this.setTargetProgress(Math.floor(this.targetProgress));
|
else this.setTargetProgress(Math.floor(this.targetProgress))
|
||||||
},
|
},
|
||||||
setTargetProgress(value) {
|
setTargetProgress(value) {
|
||||||
this.targetProgress = value;
|
this.targetProgress = value
|
||||||
if (this.targetProgress < 0) {
|
if (this.targetProgress < 0) {
|
||||||
this.progress += this.images.length;
|
this.progress += this.images.length
|
||||||
this.targetProgress += this.images.length;
|
this.targetProgress += this.images.length
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
updateProgress() {
|
updateProgress() {
|
||||||
const progress1 = lerp(this.progress, this.targetProgress, 0.1);
|
const progress1 = lerp(this.progress, this.targetProgress, 0.1)
|
||||||
const pdiff = progress1 - this.progress;
|
const pdiff = progress1 - this.progress
|
||||||
if (pdiff === 0) return;
|
if (pdiff === 0) return
|
||||||
|
|
||||||
const p0 = this.progress % 1;
|
const p0 = this.progress % 1
|
||||||
const p1 = progress1 % 1;
|
const p1 = progress1 % 1
|
||||||
if ((pdiff > 0 && p1 < p0) || (pdiff < 0 && p0 < p1)) {
|
if ((pdiff > 0 && p1 < p0) || (pdiff < 0 && p0 < p1)) {
|
||||||
const i = Math.floor(progress1) % this.images.length;
|
const i = Math.floor(progress1) % this.images.length
|
||||||
const j = (i + 1) % this.images.length;
|
const j = (i + 1) % this.images.length
|
||||||
this.plane1.setTexture(this.loader.textures[i]);
|
this.plane1.setTexture(this.loader.textures[i])
|
||||||
this.plane2.setTexture(this.loader.textures[j]);
|
this.plane2.setTexture(this.loader.textures[j])
|
||||||
}
|
}
|
||||||
|
|
||||||
this.progress = progress1;
|
this.progress = progress1
|
||||||
this.setPlanesProgress(this.progress % 1);
|
this.setPlanesProgress(this.progress % 1)
|
||||||
},
|
},
|
||||||
setPlanesProgress(progress) {
|
setPlanesProgress(progress) {
|
||||||
this.plane1.uProgress.value = progress;
|
this.plane1.uProgress.value = progress
|
||||||
this.plane2.uProgress.value = -1 + progress;
|
this.plane2.uProgress.value = -1 + progress
|
||||||
this.plane1.material.opacity = 1 - progress;
|
this.plane1.material.opacity = 1 - progress
|
||||||
this.plane2.material.opacity = progress;
|
this.plane2.material.opacity = progress
|
||||||
this.plane1.o3d.position.z = progress;
|
this.plane1.o3d.position.z = progress
|
||||||
this.plane2.o3d.position.z = progress - 1;
|
this.plane2.o3d.position.z = progress - 1
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
})
|
||||||
</script>
|
</script>
|
||||||
|
@ -6,55 +6,50 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { defineComponent } from 'vue';
|
import { defineComponent } from 'vue'
|
||||||
import { Vector2 } from 'three';
|
import { Vector2 } from 'three'
|
||||||
import { gsap, Power4 } from 'gsap';
|
import { gsap, Power4 } from 'gsap'
|
||||||
|
|
||||||
import OrthographicCamera from '../../core/OrthographicCamera.js';
|
import { lerp, useTextures, OrthographicCamera, Renderer, Scene } from '../../../build/trois.module.js'
|
||||||
import Renderer from '../../core/Renderer.js';
|
import ZoomBlurImage from './ZoomBlurImage.js'
|
||||||
import Scene from '../../core/Scene.js';
|
|
||||||
|
|
||||||
import { lerp, lerpv2 } from '../../tools';
|
|
||||||
import ZoomBlurImage from './ZoomBlurImage.js';
|
|
||||||
import useTextures from '../../use/useTextures.js';
|
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
components: { OrthographicCamera, Renderer, Scene },
|
components: { OrthographicCamera, Renderer, Scene },
|
||||||
props: {
|
props: {
|
||||||
images: Array,
|
images: Array,
|
||||||
events: { type: Object, default: () => { return { wheel: true, click: true, keyup: true }; } },
|
events: { type: Object, default: () => { return { wheel: true, click: true, keyup: true } } },
|
||||||
},
|
},
|
||||||
setup() {
|
setup() {
|
||||||
const center = new Vector2();
|
const center = new Vector2()
|
||||||
const loader = useTextures();
|
const loader = useTextures()
|
||||||
|
|
||||||
return {
|
return {
|
||||||
loader,
|
loader,
|
||||||
center,
|
center,
|
||||||
progress: 0,
|
progress: 0,
|
||||||
targetProgress: 0,
|
targetProgress: 0,
|
||||||
};
|
}
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
this.renderer = this.$refs.renderer;
|
this.renderer = this.$refs.renderer
|
||||||
this.three = this.renderer.three;
|
this.three = this.renderer.three
|
||||||
|
|
||||||
if (this.images.length < 2) {
|
if (this.images.length < 2) {
|
||||||
console.error('This slider needs at least 2 images.');
|
console.error('This slider needs at least 2 images.')
|
||||||
} else {
|
} else {
|
||||||
this.loader.loadTextures(this.images, this.init);
|
this.loader.loadTextures(this.images, this.init)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
unmounted() {
|
unmounted() {
|
||||||
this.loader.dispose();
|
this.loader.dispose()
|
||||||
const domElement = this.three.renderer.domElement;
|
const domElement = this.renderer.renderer.domElement
|
||||||
domElement.removeEventListener('click', this.onClick);
|
domElement.removeEventListener('click', this.onClick)
|
||||||
domElement.removeEventListener('wheel', this.onWheel);
|
domElement.removeEventListener('wheel', this.onWheel)
|
||||||
document.removeEventListener('keyup', this.onKeyup);
|
document.removeEventListener('keyup', this.onKeyup)
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
init() {
|
init() {
|
||||||
this.initScene();
|
this.initScene()
|
||||||
gsap.fromTo(this.image1.uStrength,
|
gsap.fromTo(this.image1.uStrength,
|
||||||
{
|
{
|
||||||
value: -2,
|
value: -2,
|
||||||
@ -64,97 +59,99 @@ export default defineComponent({
|
|||||||
duration: 2.5,
|
duration: 2.5,
|
||||||
ease: Power4.easeOut,
|
ease: Power4.easeOut,
|
||||||
}
|
}
|
||||||
);
|
)
|
||||||
|
|
||||||
const domElement = this.three.renderer.domElement;
|
const domElement = this.renderer.renderer.domElement
|
||||||
if (this.events.click) domElement.addEventListener('click', this.onClick);
|
if (this.events.click) domElement.addEventListener('click', this.onClick)
|
||||||
if (this.events.wheel) domElement.addEventListener('wheel', this.onWheel);
|
if (this.events.wheel) domElement.addEventListener('wheel', this.onWheel)
|
||||||
if (this.events.keyup) document.addEventListener('keyup', this.onKeyup);
|
if (this.events.keyup) document.addEventListener('keyup', this.onKeyup)
|
||||||
this.renderer.onBeforeRender(this.animate);
|
this.renderer.onBeforeRender(this.animate)
|
||||||
this.renderer.onAfterResize(this.onResize);
|
this.renderer.onResize(this.onResize)
|
||||||
},
|
},
|
||||||
initScene() {
|
initScene() {
|
||||||
const scene = this.$refs.scene.scene;
|
const scene = this.$refs.scene.scene
|
||||||
|
|
||||||
this.image1 = new ZoomBlurImage(this.three);
|
this.image1 = new ZoomBlurImage(this.renderer)
|
||||||
this.image1.setMap(this.loader.textures[0]);
|
this.image1.setMap(this.loader.textures[0])
|
||||||
this.image2 = new ZoomBlurImage(this.three);
|
this.image2 = new ZoomBlurImage(this.renderer)
|
||||||
this.image2.setMap(this.loader.textures[1]);
|
this.image2.setMap(this.loader.textures[1])
|
||||||
this.setImagesProgress(0);
|
this.setImagesProgress(0)
|
||||||
|
|
||||||
scene.add(this.image1.mesh);
|
scene.add(this.image1.mesh)
|
||||||
scene.add(this.image2.mesh);
|
scene.add(this.image2.mesh)
|
||||||
},
|
},
|
||||||
animate() {
|
animate() {
|
||||||
const { positionN } = this.three.pointer;
|
const { positionN } = this.renderer.three.pointer
|
||||||
this.center.copy(positionN).divideScalar(2).addScalar(0.5);
|
this.center.copy(positionN).divideScalar(2).addScalar(0.5)
|
||||||
lerpv2(this.image1.uCenter.value, this.center, 0.1);
|
this.image1.uCenter.value.lerp(this.center, 0.1)
|
||||||
lerpv2(this.image2.uCenter.value, this.center, 0.1);
|
this.image2.uCenter.value.lerp(this.center, 0.1)
|
||||||
|
// lerpv2(this.image1.uCenter.value, this.center, 0.1)
|
||||||
|
// lerpv2(this.image2.uCenter.value, this.center, 0.1)
|
||||||
|
|
||||||
this.updateProgress();
|
this.updateProgress()
|
||||||
},
|
},
|
||||||
onResize() {
|
onResize() {
|
||||||
this.image1.updateUV();
|
this.image1.updateUV()
|
||||||
this.image2.updateUV();
|
this.image2.updateUV()
|
||||||
},
|
},
|
||||||
onWheel(e) {
|
onWheel(e) {
|
||||||
// e.preventDefault();
|
// e.preventDefault()
|
||||||
if (e.deltaY > 0) {
|
if (e.deltaY > 0) {
|
||||||
this.setTargetProgress(this.targetProgress + 1 / 20);
|
this.setTargetProgress(this.targetProgress + 1 / 20)
|
||||||
} else {
|
} else {
|
||||||
this.setTargetProgress(this.targetProgress - 1 / 20);
|
this.setTargetProgress(this.targetProgress - 1 / 20)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onClick(e) {
|
onClick(e) {
|
||||||
if (e.clientY < this.three.size.height / 2) {
|
if (e.clientY < this.renderer.size.height / 2) {
|
||||||
this.navPrevious();
|
this.navPrevious()
|
||||||
} else {
|
} else {
|
||||||
this.navNext();
|
this.navNext()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onKeyup(e) {
|
onKeyup(e) {
|
||||||
if (e.keyCode === 37 || e.keyCode === 38) {
|
if (e.keyCode === 37 || e.keyCode === 38) {
|
||||||
this.navPrevious();
|
this.navPrevious()
|
||||||
} else if (e.keyCode === 39 || e.keyCode === 40) {
|
} else if (e.keyCode === 39 || e.keyCode === 40) {
|
||||||
this.navNext();
|
this.navNext()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
navNext() {
|
navNext() {
|
||||||
if (Number.isInteger(this.targetProgress)) this.setTargetProgress(this.targetProgress + 1);
|
if (Number.isInteger(this.targetProgress)) this.setTargetProgress(this.targetProgress + 1)
|
||||||
else this.setTargetProgress(Math.ceil(this.targetProgress));
|
else this.setTargetProgress(Math.ceil(this.targetProgress))
|
||||||
},
|
},
|
||||||
navPrevious() {
|
navPrevious() {
|
||||||
if (Number.isInteger(this.targetProgress)) this.setTargetProgress(this.targetProgress - 1);
|
if (Number.isInteger(this.targetProgress)) this.setTargetProgress(this.targetProgress - 1)
|
||||||
else this.setTargetProgress(Math.floor(this.targetProgress));
|
else this.setTargetProgress(Math.floor(this.targetProgress))
|
||||||
},
|
},
|
||||||
setTargetProgress(value) {
|
setTargetProgress(value) {
|
||||||
this.targetProgress = value;
|
this.targetProgress = value
|
||||||
if (this.targetProgress < 0) {
|
if (this.targetProgress < 0) {
|
||||||
this.progress += this.images.length;
|
this.progress += this.images.length
|
||||||
this.targetProgress += this.images.length;
|
this.targetProgress += this.images.length
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
updateProgress() {
|
updateProgress() {
|
||||||
const progress1 = lerp(this.progress, this.targetProgress, 0.1);
|
const progress1 = lerp(this.progress, this.targetProgress, 0.1)
|
||||||
const pdiff = progress1 - this.progress;
|
const pdiff = progress1 - this.progress
|
||||||
if (pdiff === 0) return;
|
if (pdiff === 0) return
|
||||||
|
|
||||||
const p0 = this.progress % 1;
|
const p0 = this.progress % 1
|
||||||
const p1 = progress1 % 1;
|
const p1 = progress1 % 1
|
||||||
if ((pdiff > 0 && p1 < p0) || (pdiff < 0 && p0 < p1)) {
|
if ((pdiff > 0 && p1 < p0) || (pdiff < 0 && p0 < p1)) {
|
||||||
const i = Math.floor(progress1) % this.images.length;
|
const i = Math.floor(progress1) % this.images.length
|
||||||
const j = (i + 1) % this.images.length;
|
const j = (i + 1) % this.images.length
|
||||||
this.image1.setMap(this.loader.textures[i]);
|
this.image1.setMap(this.loader.textures[i])
|
||||||
this.image2.setMap(this.loader.textures[j]);
|
this.image2.setMap(this.loader.textures[j])
|
||||||
}
|
}
|
||||||
|
|
||||||
this.progress = progress1;
|
this.progress = progress1
|
||||||
this.setImagesProgress(this.progress % 1);
|
this.setImagesProgress(this.progress % 1)
|
||||||
},
|
},
|
||||||
setImagesProgress(progress) {
|
setImagesProgress(progress) {
|
||||||
this.image1.uStrength.value = progress;
|
this.image1.uStrength.value = progress
|
||||||
this.image2.uStrength.value = -1 + progress;
|
this.image2.uStrength.value = -1 + progress
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
})
|
||||||
</script>
|
</script>
|
||||||
|
@ -3,23 +3,23 @@ import {
|
|||||||
PlaneGeometry,
|
PlaneGeometry,
|
||||||
ShaderMaterial,
|
ShaderMaterial,
|
||||||
Vector2,
|
Vector2,
|
||||||
} from 'three';
|
} from 'three'
|
||||||
|
|
||||||
export default function ZoomBlurImage(three) {
|
export default function ZoomBlurImage(renderer) {
|
||||||
let geometry, material, mesh;
|
let geometry, material, mesh
|
||||||
|
|
||||||
const uMap = { value: null };
|
const uMap = { value: null }
|
||||||
const uCenter = { value: new Vector2(0.5, 0.5) };
|
const uCenter = { value: new Vector2(0.5, 0.5) }
|
||||||
const uStrength = { value: 0 };
|
const uStrength = { value: 0 }
|
||||||
const uUVOffset = { value: new Vector2(0, 0) };
|
const uUVOffset = { value: new Vector2(0, 0) }
|
||||||
const uUVScale = { value: new Vector2(1, 1) };
|
const uUVScale = { value: new Vector2(1, 1) }
|
||||||
|
|
||||||
init();
|
init()
|
||||||
|
|
||||||
return { geometry, material, mesh, uCenter, uStrength, setMap, updateUV };
|
return { geometry, material, mesh, uCenter, uStrength, setMap, updateUV }
|
||||||
|
|
||||||
function init() {
|
function init() {
|
||||||
geometry = new PlaneGeometry(2, 2, 1, 1);
|
geometry = new PlaneGeometry(2, 2, 1, 1)
|
||||||
|
|
||||||
material = new ShaderMaterial({
|
material = new ShaderMaterial({
|
||||||
transparent: true,
|
transparent: true,
|
||||||
@ -83,27 +83,27 @@ export default function ZoomBlurImage(three) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
});
|
})
|
||||||
|
|
||||||
mesh = new Mesh(geometry, material);
|
mesh = new Mesh(geometry, material)
|
||||||
}
|
}
|
||||||
|
|
||||||
function setMap(value) {
|
function setMap(value) {
|
||||||
uMap.value = value;
|
uMap.value = value
|
||||||
updateUV();
|
updateUV()
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateUV() {
|
function updateUV() {
|
||||||
const ratio = three.size.ratio;
|
const ratio = renderer.size.ratio
|
||||||
const iRatio = uMap.value.image.width / uMap.value.image.height;
|
const iRatio = uMap.value.image.width / uMap.value.image.height
|
||||||
uUVOffset.value.set(0, 0);
|
uUVOffset.value.set(0, 0)
|
||||||
uUVScale.value.set(1, 1);
|
uUVScale.value.set(1, 1)
|
||||||
if (iRatio > ratio) {
|
if (iRatio > ratio) {
|
||||||
uUVScale.value.x = ratio / iRatio;
|
uUVScale.value.x = ratio / iRatio
|
||||||
uUVOffset.value.x = (1 - uUVScale.value.x) / 2;
|
uUVOffset.value.x = (1 - uUVScale.value.x) / 2
|
||||||
} else {
|
} else {
|
||||||
uUVScale.value.y = iRatio / ratio;
|
uUVScale.value.y = iRatio / ratio
|
||||||
uUVOffset.value.y = (1 - uUVScale.value.y) / 2;
|
uUVOffset.value.y = (1 - uUVScale.value.y) / 2
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,32 +0,0 @@
|
|||||||
<template>
|
|
||||||
<Renderer ref="renderer" antialias resize :orbit-ctrl="{ enableDamping: true, dampingFactor: 0.05 }">
|
|
||||||
<Camera ref="camera" :position="cameraPosition"></Camera>
|
|
||||||
<Scene>
|
|
||||||
<slot></slot>
|
|
||||||
</Scene>
|
|
||||||
</Renderer>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import { defineComponent } from 'vue';
|
|
||||||
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js';
|
|
||||||
import Camera from '../../core/PerspectiveCamera.js';
|
|
||||||
import Renderer from '../../core/Renderer.js';
|
|
||||||
import Scene from '../../core/Scene.js';
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
components: { Camera, Renderer, Scene },
|
|
||||||
props: {
|
|
||||||
src: String,
|
|
||||||
cameraPosition: Object,
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
this.renderer = this.$refs.renderer;
|
|
||||||
|
|
||||||
const loader = new GLTFLoader();
|
|
||||||
loader.load(this.src, (gltf) => {
|
|
||||||
this.renderer.three.scene.add(gltf.scene);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
|
@ -1,12 +0,0 @@
|
|||||||
import { defineComponent } from 'vue';
|
|
||||||
// import Object3D from '../core/Object3D.js';
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
// TODO: eventually extend Object3D, for now: error 'injection "scene" not found'
|
|
||||||
// because camera is a sibling of scene in Trois
|
|
||||||
// extends: Object3D,
|
|
||||||
inject: ['three'],
|
|
||||||
render() {
|
|
||||||
return this.$slots.default ? this.$slots.default() : [];
|
|
||||||
},
|
|
||||||
});
|
|
24
src/core/Camera.ts
Normal file
24
src/core/Camera.ts
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import { defineComponent } from 'vue'
|
||||||
|
// import { Camera } from 'three'
|
||||||
|
// import { RendererInjectionKey, RendererInterface } from './Renderer'
|
||||||
|
// import Object3D from './Object3D'
|
||||||
|
|
||||||
|
// export interface CameraSetupInterface {
|
||||||
|
// renderer?: RendererInterface
|
||||||
|
// camera: Camera
|
||||||
|
// }
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
// TODO: eventually extend Object3D
|
||||||
|
// extends: Object3D,
|
||||||
|
|
||||||
|
// inject: { renderer: RendererInjectionKey as symbol },
|
||||||
|
|
||||||
|
// setup(): CameraSetupInterface {
|
||||||
|
// return {}
|
||||||
|
// },
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return this.$slots.default ? this.$slots.default() : []
|
||||||
|
},
|
||||||
|
})
|
@ -1,13 +0,0 @@
|
|||||||
import { defineComponent } from 'vue';
|
|
||||||
import { Group } from 'three';
|
|
||||||
import Object3D from './Object3D.js';
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
name: 'Group',
|
|
||||||
extends: Object3D,
|
|
||||||
created() {
|
|
||||||
this.group = new Group();
|
|
||||||
this.initObject3D(this.group);
|
|
||||||
},
|
|
||||||
__hmrId: 'Group',
|
|
||||||
});
|
|
17
src/core/Group.ts
Normal file
17
src/core/Group.ts
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import { defineComponent } from 'vue'
|
||||||
|
import { Group } from 'three'
|
||||||
|
import Object3D from './Object3D'
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'Group',
|
||||||
|
extends: Object3D,
|
||||||
|
setup() {
|
||||||
|
return {
|
||||||
|
group: new Group(),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.initObject3D(this.group)
|
||||||
|
},
|
||||||
|
__hmrId: 'Group',
|
||||||
|
})
|
@ -1,70 +0,0 @@
|
|||||||
import { defineComponent, watch } from 'vue';
|
|
||||||
import { bindProp } from '../tools';
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
name: 'Object3D',
|
|
||||||
inject: ['three', 'scene', 'rendererComponent'],
|
|
||||||
emits: ['created', 'ready'],
|
|
||||||
props: {
|
|
||||||
position: { type: Object, default: { x: 0, y: 0, z: 0 } },
|
|
||||||
rotation: { type: Object, default: { x: 0, y: 0, z: 0 } },
|
|
||||||
scale: { type: Object, default: { x: 1, y: 1, z: 1 } },
|
|
||||||
lookAt: { type: Object, default: null },
|
|
||||||
autoRemove: { type: Boolean, default: true },
|
|
||||||
userData: { type: Object, default: () => ({}) },
|
|
||||||
},
|
|
||||||
// can't use setup because it will not be used in sub components
|
|
||||||
// setup() {},
|
|
||||||
unmounted() {
|
|
||||||
if (this.autoRemove) this.removeFromParent();
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
initObject3D(o3d) {
|
|
||||||
this.o3d = o3d;
|
|
||||||
this.o3d.userData = this.userData;
|
|
||||||
this.$emit('created', this.o3d);
|
|
||||||
|
|
||||||
bindProp(this, 'position', this.o3d);
|
|
||||||
bindProp(this, 'rotation', this.o3d);
|
|
||||||
bindProp(this, 'scale', this.o3d);
|
|
||||||
|
|
||||||
// TODO : fix lookat.x
|
|
||||||
if (this.lookAt) this.o3d.lookAt(this.lookAt.x, this.lookAt.y, this.lookAt.z);
|
|
||||||
watch(() => this.lookAt, (v) => { this.o3d.lookAt(v.x, v.y, v.z); }, { deep: true });
|
|
||||||
|
|
||||||
this._parent = this.getParent();
|
|
||||||
if (this.addToParent()) this.$emit('ready', this);
|
|
||||||
else console.error('Missing parent (Scene, Group...)');
|
|
||||||
},
|
|
||||||
getParent() {
|
|
||||||
let parent = this.$parent;
|
|
||||||
while (parent) {
|
|
||||||
if (parent.add) return parent;
|
|
||||||
parent = parent.$parent;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
},
|
|
||||||
addToParent(o) {
|
|
||||||
const o3d = o || this.o3d;
|
|
||||||
if (this._parent) {
|
|
||||||
this._parent.add(o3d);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
},
|
|
||||||
removeFromParent(o) {
|
|
||||||
const o3d = o || this.o3d;
|
|
||||||
if (this._parent) {
|
|
||||||
this._parent.remove(o3d);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
},
|
|
||||||
add(o) { this.o3d.add(o); },
|
|
||||||
remove(o) { this.o3d.remove(o); },
|
|
||||||
},
|
|
||||||
render() {
|
|
||||||
return this.$slots.default ? this.$slots.default() : [];
|
|
||||||
},
|
|
||||||
__hmrId: 'Object3D',
|
|
||||||
});
|
|
120
src/core/Object3D.ts
Normal file
120
src/core/Object3D.ts
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
import { Object3D, Scene } from 'three'
|
||||||
|
import { ComponentPublicInstance, defineComponent, PropType, watch } from 'vue'
|
||||||
|
import { bindProp } from '../tools'
|
||||||
|
import { RendererInjectionKey, RendererInterface } from './Renderer'
|
||||||
|
import { SceneInjectionKey } from './Scene'
|
||||||
|
|
||||||
|
export interface Object3DSetupInterface {
|
||||||
|
renderer?: RendererInterface
|
||||||
|
scene?: Scene
|
||||||
|
o3d?: Object3D
|
||||||
|
parent?: ComponentPublicInstance
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Object3DInterface extends Object3DSetupInterface {
|
||||||
|
addToParent(o?: Object3D): boolean
|
||||||
|
removeFromParent(o?: Object3D): boolean
|
||||||
|
add(o: Object3D): void
|
||||||
|
remove(o: Object3D): void
|
||||||
|
}
|
||||||
|
|
||||||
|
// export function object3DSetup(): Object3DSetupInterface {
|
||||||
|
// const renderer = inject(RendererInjectionKey)
|
||||||
|
// const scene = inject(SceneInjectionKey)
|
||||||
|
// return { scene, renderer }
|
||||||
|
// }
|
||||||
|
|
||||||
|
export interface Vector2PropInterface {
|
||||||
|
x?: number
|
||||||
|
y?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Vector3PropInterface extends Vector2PropInterface {
|
||||||
|
z?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface EulerPropInterface extends Vector3PropInterface {
|
||||||
|
order?: 'XYZ' | 'YZX' | 'ZXY' | 'XZY' | 'YXZ' | 'ZYX'
|
||||||
|
}
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'Object3D',
|
||||||
|
// inject for sub components
|
||||||
|
inject: {
|
||||||
|
renderer: RendererInjectionKey as symbol,
|
||||||
|
scene: SceneInjectionKey as symbol,
|
||||||
|
},
|
||||||
|
emits: ['created', 'ready'],
|
||||||
|
props: {
|
||||||
|
position: { type: Object as PropType<Vector3PropInterface>, default: () => ({ x: 0, y: 0, z: 0 }) },
|
||||||
|
rotation: { type: Object as PropType<EulerPropInterface>, default: () => ({ x: 0, y: 0, z: 0 }) },
|
||||||
|
scale: { type: Object as PropType<Vector3PropInterface>, default: () => ({ x: 1, y: 1, z: 1, order: 'XYZ' }) },
|
||||||
|
lookAt: { type: Object as PropType<Vector3PropInterface>, default: null },
|
||||||
|
autoRemove: { type: Boolean, default: true },
|
||||||
|
userData: { type: Object, default: () => ({}) },
|
||||||
|
},
|
||||||
|
setup(): Object3DSetupInterface {
|
||||||
|
// return object3DSetup()
|
||||||
|
return {}
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
if (!this.renderer) {
|
||||||
|
console.error('Missing parent Renderer')
|
||||||
|
}
|
||||||
|
if (!this.scene) {
|
||||||
|
console.error('Missing parent Scene')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
unmounted() {
|
||||||
|
if (this.autoRemove) this.removeFromParent()
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
initObject3D(o3d: Object3D) {
|
||||||
|
this.o3d = o3d
|
||||||
|
|
||||||
|
this.$emit('created', o3d)
|
||||||
|
|
||||||
|
bindProp(this, 'position', o3d)
|
||||||
|
bindProp(this, 'rotation', o3d)
|
||||||
|
bindProp(this, 'scale', o3d)
|
||||||
|
bindProp(this, 'userData', o3d.userData)
|
||||||
|
|
||||||
|
if (this.lookAt) o3d.lookAt(this.lookAt.x ?? 0, this.lookAt.y, this.lookAt.z)
|
||||||
|
watch(() => this.lookAt, (v) => { o3d.lookAt(v.x ?? 0, v.y, v.z) }, { deep: true })
|
||||||
|
|
||||||
|
this.parent = this.getParent()
|
||||||
|
if (this.addToParent()) this.$emit('ready', this)
|
||||||
|
else console.error('Missing parent (Scene, Group...)')
|
||||||
|
},
|
||||||
|
getParent(): undefined | ComponentPublicInstance {
|
||||||
|
let parent = this.$parent
|
||||||
|
while (parent) {
|
||||||
|
if ((parent as any).add) return parent
|
||||||
|
parent = parent.$parent
|
||||||
|
}
|
||||||
|
return undefined
|
||||||
|
},
|
||||||
|
addToParent(o?: Object3D) {
|
||||||
|
const o3d = o || this.o3d
|
||||||
|
if (this.parent) {
|
||||||
|
(this.parent as any).add(o3d)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
},
|
||||||
|
removeFromParent(o?: Object3D) {
|
||||||
|
const o3d = o || this.o3d
|
||||||
|
if (this.parent) {
|
||||||
|
(this.parent as any).remove(o3d)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
},
|
||||||
|
add(o: Object3D) { this.o3d?.add(o) },
|
||||||
|
remove(o: Object3D) { this.o3d?.remove(o) },
|
||||||
|
},
|
||||||
|
render() {
|
||||||
|
return this.$slots.default ? this.$slots.default() : []
|
||||||
|
},
|
||||||
|
__hmrId: 'Object3D',
|
||||||
|
})
|
@ -1,34 +0,0 @@
|
|||||||
import { defineComponent, watch } from 'vue';
|
|
||||||
import { OrthographicCamera } from 'three';
|
|
||||||
import { bindProp } from '../tools';
|
|
||||||
import Camera from './Camera.js';
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
extends: Camera,
|
|
||||||
name: 'OrthographicCamera',
|
|
||||||
inject: ['three'],
|
|
||||||
props: {
|
|
||||||
left: { type: Number, default: -1 },
|
|
||||||
right: { type: Number, default: 1 },
|
|
||||||
top: { type: Number, default: 1 },
|
|
||||||
bottom: { type: Number, default: -1 },
|
|
||||||
near: { type: Number, default: 0.1 },
|
|
||||||
far: { type: Number, default: 2000 },
|
|
||||||
zoom: { type: Number, default: 1 },
|
|
||||||
position: { type: Object, default: { x: 0, y: 0, z: 0 } },
|
|
||||||
},
|
|
||||||
created() {
|
|
||||||
this.camera = new OrthographicCamera(this.left, this.right, this.top, this.bottom, this.near, this.far);
|
|
||||||
bindProp(this, 'position', this.camera);
|
|
||||||
|
|
||||||
['left', 'right', 'top', 'bottom', 'near', 'far', 'zoom'].forEach(p => {
|
|
||||||
watch(() => this[p], () => {
|
|
||||||
this.camera[p] = this[p];
|
|
||||||
this.camera.updateProjectionMatrix();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
this.three.camera = this.camera;
|
|
||||||
},
|
|
||||||
__hmrId: 'OrthographicCamera',
|
|
||||||
});
|
|
46
src/core/OrthographicCamera.ts
Normal file
46
src/core/OrthographicCamera.ts
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
import { defineComponent, inject, PropType, watch } from 'vue'
|
||||||
|
import { OrthographicCamera } from 'three'
|
||||||
|
import { bindProp } from '../tools'
|
||||||
|
import Camera from './Camera'
|
||||||
|
import { Vector3PropInterface } from './Object3D'
|
||||||
|
import { RendererInjectionKey } from './Renderer'
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
extends: Camera,
|
||||||
|
name: 'OrthographicCamera',
|
||||||
|
props: {
|
||||||
|
left: { type: Number, default: -1 },
|
||||||
|
right: { type: Number, default: 1 },
|
||||||
|
top: { type: Number, default: 1 },
|
||||||
|
bottom: { type: Number, default: -1 },
|
||||||
|
near: { type: Number, default: 0.1 },
|
||||||
|
far: { type: Number, default: 2000 },
|
||||||
|
zoom: { type: Number, default: 1 },
|
||||||
|
position: { type: Object as PropType<Vector3PropInterface>, default: () => ({ x: 0, y: 0, z: 0 }) },
|
||||||
|
},
|
||||||
|
setup(props) {
|
||||||
|
const renderer = inject(RendererInjectionKey)
|
||||||
|
if (!renderer) {
|
||||||
|
console.error('Renderer not found')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const camera = new OrthographicCamera(props.left, props.right, props.top, props.bottom, props.near, props.far)
|
||||||
|
renderer.camera = camera
|
||||||
|
|
||||||
|
bindProp(props, 'position', camera)
|
||||||
|
|
||||||
|
const watchProps = ['left', 'right', 'top', 'bottom', 'near', 'far', 'zoom']
|
||||||
|
watchProps.forEach(p => {
|
||||||
|
// @ts-ignore
|
||||||
|
watch(() => props[p], (value) => {
|
||||||
|
// @ts-ignore
|
||||||
|
camera[p] = value
|
||||||
|
camera.updateProjectionMatrix()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
return { renderer, camera }
|
||||||
|
},
|
||||||
|
__hmrId: 'OrthographicCamera',
|
||||||
|
})
|
@ -1,35 +0,0 @@
|
|||||||
import { defineComponent, watch } from 'vue';
|
|
||||||
import { PerspectiveCamera } from 'three';
|
|
||||||
import { bindProp } from '../tools';
|
|
||||||
import Camera from './Camera.js';
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
extends: Camera,
|
|
||||||
name: 'PerspectiveCamera',
|
|
||||||
inject: ['three'],
|
|
||||||
props: {
|
|
||||||
aspect: { type: Number, default: 1 },
|
|
||||||
far: { type: Number, default: 2000 },
|
|
||||||
fov: { type: Number, default: 50 },
|
|
||||||
near: { type: Number, default: 0.1 },
|
|
||||||
position: { type: Object, default: { x: 0, y: 0, z: 0 } },
|
|
||||||
lookAt: { type: Object, default: null },
|
|
||||||
},
|
|
||||||
created() {
|
|
||||||
this.camera = new PerspectiveCamera(this.fov, this.aspect, this.near, this.far);
|
|
||||||
bindProp(this, 'position', this.camera);
|
|
||||||
|
|
||||||
if (this.lookAt) this.camera.lookAt(this.lookAt.x, this.lookAt.y, this.lookAt.z);
|
|
||||||
watch(() => this.lookAt, (v) => { this.camera.lookAt(v.x, v.y, v.z); }, { deep: true });
|
|
||||||
|
|
||||||
['aspect', 'far', 'fov', 'near'].forEach(p => {
|
|
||||||
watch(() => this[p], () => {
|
|
||||||
this.camera[p] = this[p];
|
|
||||||
this.camera.updateProjectionMatrix();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
this.three.camera = this.camera;
|
|
||||||
},
|
|
||||||
__hmrId: 'PerspectiveCamera',
|
|
||||||
});
|
|
47
src/core/PerspectiveCamera.ts
Normal file
47
src/core/PerspectiveCamera.ts
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
import { defineComponent, inject, PropType, watch } from 'vue'
|
||||||
|
import { PerspectiveCamera } from 'three'
|
||||||
|
import { bindProp } from '../tools'
|
||||||
|
import Camera from './Camera'
|
||||||
|
import { Vector3PropInterface } from './Object3D'
|
||||||
|
import { RendererInjectionKey } from './Renderer'
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
extends: Camera,
|
||||||
|
name: 'PerspectiveCamera',
|
||||||
|
props: {
|
||||||
|
aspect: { type: Number, default: 1 },
|
||||||
|
far: { type: Number, default: 2000 },
|
||||||
|
fov: { type: Number, default: 50 },
|
||||||
|
near: { type: Number, default: 0.1 },
|
||||||
|
position: { type: Object as PropType<Vector3PropInterface>, default: () => ({ x: 0, y: 0, z: 0 }) },
|
||||||
|
lookAt: { type: Object as PropType<Vector3PropInterface>, default: null },
|
||||||
|
},
|
||||||
|
setup(props) {
|
||||||
|
const renderer = inject(RendererInjectionKey)
|
||||||
|
if (!renderer) {
|
||||||
|
console.error('Renderer not found')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const camera = new PerspectiveCamera(props.fov, props.aspect, props.near, props.far)
|
||||||
|
renderer.camera = camera
|
||||||
|
|
||||||
|
bindProp(props, 'position', camera)
|
||||||
|
|
||||||
|
if (props.lookAt) camera.lookAt(props.lookAt.x ?? 0, props.lookAt.y, props.lookAt.z)
|
||||||
|
watch(() => props.lookAt, (v) => { camera.lookAt(v.x ?? 0, v.y, v.z) }, { deep: true })
|
||||||
|
|
||||||
|
const watchProps = ['aspect', 'far', 'fov', 'near']
|
||||||
|
watchProps.forEach(p => {
|
||||||
|
// @ts-ignore
|
||||||
|
watch(() => props[p], (value) => {
|
||||||
|
// @ts-ignore
|
||||||
|
camera[p] = value
|
||||||
|
camera.updateProjectionMatrix()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
return { renderer, camera }
|
||||||
|
},
|
||||||
|
__hmrId: 'PerspectiveCamera',
|
||||||
|
})
|
@ -1,49 +0,0 @@
|
|||||||
import { defineComponent } from 'vue';
|
|
||||||
import usePointer from './usePointer';
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
name: 'Raycaster',
|
|
||||||
inject: ['three', 'rendererComponent'],
|
|
||||||
props: {
|
|
||||||
onPointerEnter: { type: Function, default: () => {} },
|
|
||||||
onPointerOver: { type: Function, default: () => {} },
|
|
||||||
onPointerMove: { type: Function, default: () => {} },
|
|
||||||
onPointerLeave: { type: Function, default: () => {} },
|
|
||||||
onClick: { type: Function, default: () => {} },
|
|
||||||
intersectMode: { type: String, default: 'move' },
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
this.rendererComponent.onMounted(() => {
|
|
||||||
this.pointer = usePointer({
|
|
||||||
camera: this.three.camera,
|
|
||||||
domElement: this.three.renderer.domElement,
|
|
||||||
intersectObjects: this.getIntersectObjects(),
|
|
||||||
onIntersectEnter: this.onPointerEnter,
|
|
||||||
onIntersectOver: this.onPointerOver,
|
|
||||||
onIntersectMove: this.onPointerMove,
|
|
||||||
onIntersectLeave: this.onPointerLeave,
|
|
||||||
onIntersectClick: this.onClick,
|
|
||||||
});
|
|
||||||
this.pointer.addListeners();
|
|
||||||
|
|
||||||
if (this.intersectMode === 'frame') {
|
|
||||||
this.rendererComponent.onBeforeRender(this.pointer.intersect);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
unmounted() {
|
|
||||||
if (this.pointer) {
|
|
||||||
this.pointer.removeListeners();
|
|
||||||
this.rendererComponent.offBeforeRender(this.pointer.intersect);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
getIntersectObjects() {
|
|
||||||
return this.three.scene.children.filter(e => e.type === 'Mesh');
|
|
||||||
},
|
|
||||||
},
|
|
||||||
render() {
|
|
||||||
return [];
|
|
||||||
},
|
|
||||||
__hmrId: 'Raycaster',
|
|
||||||
});
|
|
74
src/core/Raycaster.ts
Normal file
74
src/core/Raycaster.ts
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
import { Object3D } from 'three'
|
||||||
|
import { defineComponent, inject, PropType } from 'vue'
|
||||||
|
import usePointer, { IntersectObject, PointerInterface, PointerIntersectCallbackType } from './usePointer'
|
||||||
|
import { RendererInjectionKey, RendererInterface } from './Renderer'
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||||
|
const emptyCallBack: PointerIntersectCallbackType = () => {}
|
||||||
|
|
||||||
|
interface RaycasterSetupInterface {
|
||||||
|
renderer?: RendererInterface
|
||||||
|
pointer?: PointerInterface
|
||||||
|
}
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'Raycaster',
|
||||||
|
props: {
|
||||||
|
onPointerEnter: { type: Function as PropType<PointerIntersectCallbackType>, default: emptyCallBack },
|
||||||
|
onPointerOver: { type: Function as PropType<PointerIntersectCallbackType>, default: emptyCallBack },
|
||||||
|
onPointerMove: { type: Function as PropType<PointerIntersectCallbackType>, default: emptyCallBack },
|
||||||
|
onPointerLeave: { type: Function as PropType<PointerIntersectCallbackType>, default: emptyCallBack },
|
||||||
|
onClick: { type: Function as PropType<PointerIntersectCallbackType>, default: emptyCallBack },
|
||||||
|
intersectMode: { type: String, default: 'move' },
|
||||||
|
},
|
||||||
|
setup(): RaycasterSetupInterface {
|
||||||
|
const renderer = inject(RendererInjectionKey)
|
||||||
|
return { renderer }
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
if (!this.renderer) {
|
||||||
|
console.error('Renderer not found')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const renderer = this.renderer
|
||||||
|
|
||||||
|
this.renderer.onMounted(() => {
|
||||||
|
if (!renderer.camera) return
|
||||||
|
|
||||||
|
this.pointer = usePointer({
|
||||||
|
camera: renderer.camera,
|
||||||
|
domElement: renderer.canvas,
|
||||||
|
intersectObjects: this.getIntersectObjects(),
|
||||||
|
onIntersectEnter: this.onPointerEnter,
|
||||||
|
onIntersectOver: this.onPointerOver,
|
||||||
|
onIntersectMove: this.onPointerMove,
|
||||||
|
onIntersectLeave: this.onPointerLeave,
|
||||||
|
onIntersectClick: this.onClick,
|
||||||
|
})
|
||||||
|
this.pointer.addListeners()
|
||||||
|
|
||||||
|
if (this.intersectMode === 'frame') {
|
||||||
|
renderer.onBeforeRender(this.pointer.intersect)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
unmounted() {
|
||||||
|
if (this.pointer) {
|
||||||
|
this.pointer.removeListeners()
|
||||||
|
this.renderer?.offBeforeRender(this.pointer.intersect)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
getIntersectObjects() {
|
||||||
|
if (this.renderer && this.renderer.scene) {
|
||||||
|
const children = this.renderer.scene.children.filter((c: Object3D) => ['Mesh', 'InstancedMesh'].includes(c.type))
|
||||||
|
return children as IntersectObject[]
|
||||||
|
}
|
||||||
|
return []
|
||||||
|
},
|
||||||
|
},
|
||||||
|
render() {
|
||||||
|
return []
|
||||||
|
},
|
||||||
|
__hmrId: 'Raycaster',
|
||||||
|
})
|
@ -1,105 +0,0 @@
|
|||||||
import { defineComponent, h } from 'vue';
|
|
||||||
import useThree from './useThree';
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
name: 'Renderer',
|
|
||||||
props: {
|
|
||||||
antialias: Boolean,
|
|
||||||
alpha: Boolean,
|
|
||||||
autoClear: { type: Boolean, default: true },
|
|
||||||
orbitCtrl: { type: [Boolean, Object], default: false },
|
|
||||||
pointer: { type: [Boolean, Object], default: false },
|
|
||||||
resize: { type: [Boolean, String], default: false },
|
|
||||||
shadow: Boolean,
|
|
||||||
width: String,
|
|
||||||
height: String,
|
|
||||||
xr: Boolean,
|
|
||||||
},
|
|
||||||
setup() {
|
|
||||||
return {
|
|
||||||
three: useThree(),
|
|
||||||
raf: true,
|
|
||||||
onMountedCallbacks: [],
|
|
||||||
beforeRenderCallbacks: [],
|
|
||||||
afterRenderCallbacks: [],
|
|
||||||
};
|
|
||||||
},
|
|
||||||
provide() {
|
|
||||||
return {
|
|
||||||
three: this.three,
|
|
||||||
// renderer: this.three.renderer,
|
|
||||||
rendererComponent: this,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
const params = {
|
|
||||||
canvas: this.$el,
|
|
||||||
antialias: this.antialias,
|
|
||||||
alpha: this.alpha,
|
|
||||||
autoClear: this.autoClear,
|
|
||||||
orbit_ctrl: this.orbitCtrl,
|
|
||||||
pointer: this.pointer,
|
|
||||||
resize: this.resize,
|
|
||||||
width: this.width,
|
|
||||||
height: this.height,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (this.three.init(params)) {
|
|
||||||
this.renderer = this.three.renderer;
|
|
||||||
this.renderer.shadowMap.enabled = this.shadow;
|
|
||||||
|
|
||||||
this._render = this.three.composer ? this.three.renderC : this.three.render;
|
|
||||||
|
|
||||||
if (this.xr) {
|
|
||||||
this.renderer.xr.enabled = true;
|
|
||||||
this.renderer.setAnimationLoop(this.render);
|
|
||||||
} else {
|
|
||||||
requestAnimationFrame(this.renderLoop);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
this.onMountedCallbacks.forEach(c => c());
|
|
||||||
},
|
|
||||||
beforeUnmount() {
|
|
||||||
this.beforeRenderCallbacks = [];
|
|
||||||
this.afterRenderCallbacks = [];
|
|
||||||
this.raf = false;
|
|
||||||
this.three.dispose();
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
onMounted(cb) {
|
|
||||||
this.onMountedCallbacks.push(cb);
|
|
||||||
},
|
|
||||||
onBeforeRender(cb) {
|
|
||||||
this.beforeRenderCallbacks.push(cb);
|
|
||||||
},
|
|
||||||
offBeforeRender(cb) {
|
|
||||||
this.beforeRenderCallbacks = this.beforeRenderCallbacks.filter(c => c !== cb);
|
|
||||||
},
|
|
||||||
onAfterRender(cb) {
|
|
||||||
this.afterRenderCallbacks.push(cb);
|
|
||||||
},
|
|
||||||
offAfterRender(cb) {
|
|
||||||
this.afterRenderCallbacks = this.afterRenderCallbacks.filter(c => c !== cb);
|
|
||||||
},
|
|
||||||
onAfterResize(cb) {
|
|
||||||
this.three.onAfterResize(cb);
|
|
||||||
},
|
|
||||||
offAfterResize(cb) {
|
|
||||||
this.three.offAfterResize(cb);
|
|
||||||
},
|
|
||||||
render(time) {
|
|
||||||
this.beforeRenderCallbacks.forEach(c => c({ time }));
|
|
||||||
this._render();
|
|
||||||
this.afterRenderCallbacks.forEach(c => c({ time }));
|
|
||||||
},
|
|
||||||
renderLoop(time) {
|
|
||||||
if (this.raf) requestAnimationFrame(this.renderLoop);
|
|
||||||
this.render(time);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
render() {
|
|
||||||
return h('canvas', {}, this.$slots.default());
|
|
||||||
},
|
|
||||||
__hmrId: 'Renderer',
|
|
||||||
});
|
|
262
src/core/Renderer.ts
Normal file
262
src/core/Renderer.ts
Normal file
@ -0,0 +1,262 @@
|
|||||||
|
/* eslint-disable no-use-before-define */
|
||||||
|
import { Camera, Scene, WebGLRenderer } from 'three'
|
||||||
|
import { EffectComposer } from 'three/examples/jsm/postprocessing/EffectComposer'
|
||||||
|
import { defineComponent, InjectionKey, PropType } from 'vue'
|
||||||
|
import { PointerPublicConfigInterface } from './usePointer'
|
||||||
|
import useThree, { SizeInterface, ThreeConfigInterface, ThreeInterface } from './useThree'
|
||||||
|
|
||||||
|
type CallbackType<T> = (event: T) => void
|
||||||
|
|
||||||
|
// type EventType = 'init' | 'mounted' | 'beforerender' | 'afterrender' | 'resize'
|
||||||
|
|
||||||
|
export interface EventInterface {
|
||||||
|
type: 'init' | 'mounted'
|
||||||
|
renderer: RendererInterface
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RenderEventInterface {
|
||||||
|
type: 'beforerender' | 'afterrender'
|
||||||
|
renderer: RendererInterface
|
||||||
|
time: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ResizeEventInterface {
|
||||||
|
type: 'resize'
|
||||||
|
renderer: RendererInterface
|
||||||
|
size: SizeInterface
|
||||||
|
}
|
||||||
|
|
||||||
|
type InitCallbackType = CallbackType<EventInterface>
|
||||||
|
type MountedCallbackType = CallbackType<EventInterface>
|
||||||
|
type RenderCallbackType = CallbackType<RenderEventInterface>
|
||||||
|
type ResizeCallbackType = CallbackType<ResizeEventInterface>
|
||||||
|
// type CallbackTypes = InitCallbackType | MountedCallbackType | RenderCallbackType | ResizeCallbackType
|
||||||
|
|
||||||
|
// interface EventMap {
|
||||||
|
// 'init': EventInterface;
|
||||||
|
// 'mounted': EventInterface;
|
||||||
|
// 'beforerender': RenderEventInterface;
|
||||||
|
// 'afterrender': RenderEventInterface;
|
||||||
|
// 'resize': ResizeEventInterface;
|
||||||
|
// }
|
||||||
|
|
||||||
|
interface EventCallbackMap {
|
||||||
|
'init': InitCallbackType;
|
||||||
|
'mounted': MountedCallbackType;
|
||||||
|
'beforerender': RenderCallbackType;
|
||||||
|
'afterrender': RenderCallbackType;
|
||||||
|
'resize': ResizeCallbackType;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface RendererSetupInterface {
|
||||||
|
canvas: HTMLCanvasElement
|
||||||
|
three: ThreeInterface
|
||||||
|
renderer: WebGLRenderer
|
||||||
|
size: SizeInterface
|
||||||
|
renderFn(): void
|
||||||
|
raf: boolean
|
||||||
|
|
||||||
|
// pointerPosition?: Vector2
|
||||||
|
// pointerPositionN?: Vector2
|
||||||
|
// pointerPositionV3?: Vector3
|
||||||
|
|
||||||
|
initCallbacks: InitCallbackType[]
|
||||||
|
mountedCallbacks: MountedCallbackType[]
|
||||||
|
beforeRenderCallbacks: RenderCallbackType[]
|
||||||
|
afterRenderCallbacks: RenderCallbackType[]
|
||||||
|
resizeCallbacks: ResizeCallbackType[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RendererInterface extends RendererSetupInterface {
|
||||||
|
scene?: Scene
|
||||||
|
camera?: Camera
|
||||||
|
composer?: EffectComposer
|
||||||
|
|
||||||
|
onInit(cb: InitCallbackType): void
|
||||||
|
onMounted(cb: MountedCallbackType): void
|
||||||
|
|
||||||
|
onBeforeRender(cb: RenderCallbackType): void
|
||||||
|
offBeforeRender(cb: RenderCallbackType): void
|
||||||
|
onAfterRender(cb: RenderCallbackType): void
|
||||||
|
offAfterRender(cb: RenderCallbackType): void
|
||||||
|
|
||||||
|
onResize(cb: ResizeCallbackType): void
|
||||||
|
offResize(cb: ResizeCallbackType): void
|
||||||
|
|
||||||
|
addListener<T extends keyof EventCallbackMap>(t: T, cb: EventCallbackMap[T]): void
|
||||||
|
removeListener<T extends keyof EventCallbackMap>(t: T, cb: EventCallbackMap[T]): void
|
||||||
|
}
|
||||||
|
|
||||||
|
export const RendererInjectionKey: InjectionKey<RendererInterface> = Symbol('Renderer')
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'Renderer',
|
||||||
|
props: {
|
||||||
|
antialias: Boolean,
|
||||||
|
alpha: Boolean,
|
||||||
|
autoClear: { type: Boolean, default: true },
|
||||||
|
orbitCtrl: { type: [Boolean, Object] as PropType<boolean | Record<string, unknown>>, default: false },
|
||||||
|
pointer: { type: [Boolean, Object] as PropType<boolean | PointerPublicConfigInterface>, default: false },
|
||||||
|
resize: { type: [Boolean, String] as PropType<boolean | string>, default: false },
|
||||||
|
shadow: Boolean,
|
||||||
|
width: String,
|
||||||
|
height: String,
|
||||||
|
xr: Boolean,
|
||||||
|
onReady: Function as PropType<(r: RendererInterface) => void>,
|
||||||
|
onClick: Function as PropType<(this: HTMLCanvasElement, ev: MouseEvent) => any>,
|
||||||
|
},
|
||||||
|
setup(props): RendererSetupInterface {
|
||||||
|
const initCallbacks: InitCallbackType[] = []
|
||||||
|
const mountedCallbacks: MountedCallbackType[] = []
|
||||||
|
const beforeRenderCallbacks: RenderCallbackType[] = []
|
||||||
|
const afterRenderCallbacks: RenderCallbackType[] = []
|
||||||
|
const resizeCallbacks: ResizeCallbackType[] = []
|
||||||
|
|
||||||
|
const canvas = document.createElement('canvas')
|
||||||
|
const config: ThreeConfigInterface = {
|
||||||
|
canvas,
|
||||||
|
antialias: props.antialias,
|
||||||
|
alpha: props.alpha,
|
||||||
|
autoClear: props.autoClear,
|
||||||
|
orbitCtrl: props.orbitCtrl,
|
||||||
|
pointer: props.pointer,
|
||||||
|
resize: props.resize,
|
||||||
|
}
|
||||||
|
|
||||||
|
if (props.width) config.width = parseInt(props.width)
|
||||||
|
if (props.height) config.height = parseInt(props.height)
|
||||||
|
|
||||||
|
const three = useThree(config)
|
||||||
|
|
||||||
|
const renderFn: {(): void} = () => {}
|
||||||
|
|
||||||
|
// we have to handle canvas events ourself (it is not rendered by vue)
|
||||||
|
if (props.onClick) {
|
||||||
|
canvas.addEventListener('click', props.onClick)
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
canvas,
|
||||||
|
three,
|
||||||
|
renderer: three.renderer,
|
||||||
|
size: three.size,
|
||||||
|
renderFn,
|
||||||
|
raf: true,
|
||||||
|
initCallbacks,
|
||||||
|
mountedCallbacks,
|
||||||
|
beforeRenderCallbacks,
|
||||||
|
afterRenderCallbacks,
|
||||||
|
resizeCallbacks,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
camera: {
|
||||||
|
get: function(): Camera | undefined { return this.three.camera },
|
||||||
|
set: function(camera: Camera): void { this.three.camera = camera },
|
||||||
|
},
|
||||||
|
scene: {
|
||||||
|
get: function(): Scene | undefined { return this.three.scene },
|
||||||
|
set: function(scene: Scene): void { this.three.scene = scene },
|
||||||
|
},
|
||||||
|
composer: {
|
||||||
|
get: function(): EffectComposer | undefined { return this.three.composer },
|
||||||
|
set: function(composer: EffectComposer): void { this.three.composer = composer },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
provide() {
|
||||||
|
return {
|
||||||
|
[RendererInjectionKey as symbol]: this,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
// appendChild won't work on reload
|
||||||
|
this.$el.parentNode.insertBefore(this.canvas, this.$el)
|
||||||
|
|
||||||
|
if (this.three.init()) {
|
||||||
|
// if (this.three.pointer) {
|
||||||
|
// this.pointerPosition = this.three.pointer.position
|
||||||
|
// this.pointerPositionN = this.three.pointer.positionN
|
||||||
|
// this.pointerPositionV3 = this.three.pointer.positionV3
|
||||||
|
// }
|
||||||
|
|
||||||
|
// TODO : don't use config
|
||||||
|
this.three.config.onResize = (size) => {
|
||||||
|
this.resizeCallbacks.forEach(e => e({ type: 'resize', renderer: this, size }))
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO : improve shadow params
|
||||||
|
this.renderer.shadowMap.enabled = this.shadow
|
||||||
|
|
||||||
|
this.renderFn = this.three.composer ? this.three.renderC : this.three.render
|
||||||
|
|
||||||
|
this.initCallbacks.forEach(e => e({ type: 'init', renderer: this }))
|
||||||
|
this.onReady?.(this as RendererInterface)
|
||||||
|
|
||||||
|
if (this.xr) {
|
||||||
|
this.renderer.xr.enabled = true
|
||||||
|
this.renderer.setAnimationLoop(this.render)
|
||||||
|
} else {
|
||||||
|
requestAnimationFrame(this.renderLoop)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.mountedCallbacks.forEach(e => e({ type: 'mounted', renderer: this }))
|
||||||
|
},
|
||||||
|
beforeUnmount() {
|
||||||
|
this.canvas.remove()
|
||||||
|
this.beforeRenderCallbacks = []
|
||||||
|
this.afterRenderCallbacks = []
|
||||||
|
this.raf = false
|
||||||
|
this.three.dispose()
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
onInit(cb: InitCallbackType) { this.addListener('init', cb) },
|
||||||
|
onMounted(cb: MountedCallbackType) { this.addListener('mounted', cb) },
|
||||||
|
onBeforeRender(cb: RenderCallbackType) { this.addListener('beforerender', cb) },
|
||||||
|
offBeforeRender(cb: RenderCallbackType) { this.removeListener('beforerender', cb) },
|
||||||
|
onAfterRender(cb: RenderCallbackType) { this.addListener('afterrender', cb) },
|
||||||
|
offAfterRender(cb: RenderCallbackType) { this.removeListener('afterrender', cb) },
|
||||||
|
onResize(cb: ResizeCallbackType) { this.addListener('resize', cb) },
|
||||||
|
offResize(cb: ResizeCallbackType) { this.removeListener('resize', cb) },
|
||||||
|
|
||||||
|
addListener(type: string, cb: {(e?: any): void}) {
|
||||||
|
const callbacks = this.getCallbacks(type)
|
||||||
|
callbacks.push(cb)
|
||||||
|
},
|
||||||
|
|
||||||
|
removeListener(type: string, cb: {(e?: any): void}) {
|
||||||
|
const callbacks = this.getCallbacks(type)
|
||||||
|
const index = callbacks.indexOf(cb)
|
||||||
|
if (index) callbacks.splice(index, 1)
|
||||||
|
},
|
||||||
|
|
||||||
|
getCallbacks(type: string) {
|
||||||
|
if (type === 'init') {
|
||||||
|
return this.initCallbacks
|
||||||
|
} else if (type === 'mounted') {
|
||||||
|
return this.mountedCallbacks
|
||||||
|
} else if (type === 'beforerender') {
|
||||||
|
return this.beforeRenderCallbacks
|
||||||
|
} else if (type === 'afterrender') {
|
||||||
|
return this.afterRenderCallbacks
|
||||||
|
} else {
|
||||||
|
return this.resizeCallbacks
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
render(time: number) {
|
||||||
|
this.beforeRenderCallbacks.forEach(e => e({ type: 'beforerender', renderer: this, time }))
|
||||||
|
// this.onFrame?.(cbParams)
|
||||||
|
this.renderFn()
|
||||||
|
this.afterRenderCallbacks.forEach(e => e({ type: 'afterrender', renderer: this, time }))
|
||||||
|
},
|
||||||
|
renderLoop(time: number) {
|
||||||
|
if (this.raf) requestAnimationFrame(this.renderLoop)
|
||||||
|
this.render(time)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
render() {
|
||||||
|
return this.$slots.default ? this.$slots.default() : []
|
||||||
|
},
|
||||||
|
__hmrId: 'Renderer',
|
||||||
|
})
|
@ -1,35 +0,0 @@
|
|||||||
import { defineComponent, watch } from 'vue';
|
|
||||||
import { Scene, Color } from 'three';
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
name: 'Scene',
|
|
||||||
inject: ['three'],
|
|
||||||
props: {
|
|
||||||
id: String,
|
|
||||||
background: [String, Number],
|
|
||||||
},
|
|
||||||
setup(props) {
|
|
||||||
const scene = new Scene();
|
|
||||||
if (props.background) scene.background = new Color(props.background);
|
|
||||||
watch(() => props.background, (value) => { scene.background.set(value); });
|
|
||||||
return { scene };
|
|
||||||
},
|
|
||||||
provide() {
|
|
||||||
return {
|
|
||||||
scene: this.scene,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
if (!this.three.scene) {
|
|
||||||
this.three.scene = this.scene;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
add(o) { this.scene.add(o); },
|
|
||||||
remove(o) { this.scene.remove(o); },
|
|
||||||
},
|
|
||||||
render() {
|
|
||||||
return this.$slots.default ? this.$slots.default() : [];
|
|
||||||
},
|
|
||||||
__hmrId: 'Scene',
|
|
||||||
});
|
|
46
src/core/Scene.ts
Normal file
46
src/core/Scene.ts
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
import { defineComponent, inject, InjectionKey, provide, watch } from 'vue'
|
||||||
|
import { Scene, Color, Object3D, Texture } from 'three'
|
||||||
|
import { RendererInjectionKey } from './Renderer'
|
||||||
|
|
||||||
|
export const SceneInjectionKey: InjectionKey<Scene> = Symbol('Scene')
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'Scene',
|
||||||
|
props: {
|
||||||
|
background: [String, Number, Object],
|
||||||
|
},
|
||||||
|
setup(props) {
|
||||||
|
const renderer = inject(RendererInjectionKey)
|
||||||
|
const scene = new Scene()
|
||||||
|
|
||||||
|
if (!renderer) {
|
||||||
|
console.error('Renderer not found')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
renderer.scene = scene
|
||||||
|
provide(SceneInjectionKey, scene)
|
||||||
|
|
||||||
|
const setBackground = (value: any): void => {
|
||||||
|
if (!value) return
|
||||||
|
if (typeof value === 'string' || typeof value === 'number') {
|
||||||
|
if (scene.background instanceof Color) scene.background.set(value)
|
||||||
|
else scene.background = new Color(value)
|
||||||
|
} else if (value instanceof Texture) {
|
||||||
|
scene.background = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setBackground(props.background)
|
||||||
|
watch(() => props.background, setBackground)
|
||||||
|
|
||||||
|
const add = (o: Object3D): void => { scene.add(o) }
|
||||||
|
const remove = (o: Object3D): void => { scene.remove(o) }
|
||||||
|
|
||||||
|
return { scene, add, remove }
|
||||||
|
},
|
||||||
|
render() {
|
||||||
|
return this.$slots.default ? this.$slots.default() : []
|
||||||
|
},
|
||||||
|
__hmrId: 'Scene',
|
||||||
|
})
|
@ -1,8 +0,0 @@
|
|||||||
export { default as Renderer } from './Renderer.js';
|
|
||||||
export { default as OrthographicCamera } from './OrthographicCamera.js';
|
|
||||||
export { default as PerspectiveCamera } from './PerspectiveCamera.js';
|
|
||||||
export { default as Camera } from './PerspectiveCamera.js';
|
|
||||||
export { default as Group } from './Group.js';
|
|
||||||
export { default as Scene } from './Scene.js';
|
|
||||||
export { default as Object3D } from './Object3D.js';
|
|
||||||
export { default as Raycaster } from './Raycaster.js';
|
|
8
src/core/index.ts
Normal file
8
src/core/index.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
export { default as Renderer, RendererInjectionKey } from './Renderer'
|
||||||
|
export { default as OrthographicCamera } from './OrthographicCamera'
|
||||||
|
export { default as PerspectiveCamera } from './PerspectiveCamera'
|
||||||
|
export { default as Camera } from './PerspectiveCamera'
|
||||||
|
export { default as Group } from './Group'
|
||||||
|
export { default as Scene, SceneInjectionKey } from './Scene'
|
||||||
|
export { default as Object3D } from './Object3D'
|
||||||
|
export { default as Raycaster } from './Raycaster'
|
@ -1,175 +0,0 @@
|
|||||||
import { InstancedMesh, Vector2, Vector3 } from 'three';
|
|
||||||
import useRaycaster from './useRaycaster';
|
|
||||||
|
|
||||||
export default function usePointer(options) {
|
|
||||||
const {
|
|
||||||
camera,
|
|
||||||
domElement,
|
|
||||||
intersectObjects,
|
|
||||||
touch = true,
|
|
||||||
resetOnEnd = false,
|
|
||||||
resetPosition = new Vector2(0, 0),
|
|
||||||
resetPositionV3 = new Vector3(0, 0, 0),
|
|
||||||
onEnter = () => {},
|
|
||||||
onMove = () => {},
|
|
||||||
onLeave = () => {},
|
|
||||||
onIntersectEnter = () => {},
|
|
||||||
onIntersectOver = () => {},
|
|
||||||
onIntersectMove = () => {},
|
|
||||||
onIntersectLeave = () => {},
|
|
||||||
onIntersectClick = () => {},
|
|
||||||
} = options;
|
|
||||||
|
|
||||||
const position = resetPosition.clone();
|
|
||||||
const positionN = new Vector2(0, 0);
|
|
||||||
|
|
||||||
const raycaster = useRaycaster({ camera });
|
|
||||||
const positionV3 = raycaster.position;
|
|
||||||
|
|
||||||
const obj = {
|
|
||||||
position,
|
|
||||||
positionN,
|
|
||||||
positionV3,
|
|
||||||
intersectObjects,
|
|
||||||
listeners: false,
|
|
||||||
addListeners,
|
|
||||||
removeListeners,
|
|
||||||
intersect,
|
|
||||||
};
|
|
||||||
|
|
||||||
return obj;
|
|
||||||
|
|
||||||
function reset() {
|
|
||||||
position.copy(resetPosition);
|
|
||||||
positionV3.copy(resetPositionV3);
|
|
||||||
};
|
|
||||||
|
|
||||||
function updatePosition(event) {
|
|
||||||
let x, y;
|
|
||||||
if (event.touches && event.touches.length > 0) {
|
|
||||||
x = event.touches[0].clientX;
|
|
||||||
y = event.touches[0].clientY;
|
|
||||||
} else {
|
|
||||||
x = event.clientX;
|
|
||||||
y = event.clientY;
|
|
||||||
}
|
|
||||||
|
|
||||||
const rect = domElement.getBoundingClientRect();
|
|
||||||
position.x = x - rect.left;
|
|
||||||
position.y = y - rect.top;
|
|
||||||
positionN.x = (position.x / rect.width) * 2 - 1;
|
|
||||||
positionN.y = -(position.y / rect.height) * 2 + 1;
|
|
||||||
raycaster.updatePosition(positionN);
|
|
||||||
};
|
|
||||||
|
|
||||||
function intersect() {
|
|
||||||
if (intersectObjects.length) {
|
|
||||||
const intersects = raycaster.intersect(positionN, intersectObjects);
|
|
||||||
const offObjects = [...intersectObjects];
|
|
||||||
const iMeshes = [];
|
|
||||||
|
|
||||||
intersects.forEach(intersect => {
|
|
||||||
const { object } = intersect;
|
|
||||||
const { component } = object;
|
|
||||||
|
|
||||||
// only once for InstancedMesh
|
|
||||||
if (object instanceof InstancedMesh) {
|
|
||||||
if (iMeshes.indexOf(object) !== -1) return;
|
|
||||||
iMeshes.push(object);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!object.over) {
|
|
||||||
object.over = true;
|
|
||||||
const overEvent = { type: 'pointerover', over: true, component, intersect };
|
|
||||||
const enterEvent = { ...overEvent, type: 'pointerenter' };
|
|
||||||
onIntersectOver(overEvent);
|
|
||||||
onIntersectEnter(enterEvent);
|
|
||||||
if (component.onPointerOver) component.onPointerOver(overEvent);
|
|
||||||
if (component.onPointerEnter) component.onPointerEnter(enterEvent);
|
|
||||||
}
|
|
||||||
|
|
||||||
const moveEvent = { type: 'pointermove', component, intersect };
|
|
||||||
onIntersectMove(moveEvent);
|
|
||||||
if (component.onPointerMove) component.onPointerMove(moveEvent);
|
|
||||||
|
|
||||||
offObjects.splice(offObjects.indexOf(object), 1);
|
|
||||||
});
|
|
||||||
|
|
||||||
offObjects.forEach(object => {
|
|
||||||
const { component } = object;
|
|
||||||
if (object.over) {
|
|
||||||
object.over = false;
|
|
||||||
const overEvent = { type: 'pointerover', over: false, component };
|
|
||||||
const leaveEvent = { ...overEvent, type: 'pointerleave' };
|
|
||||||
onIntersectOver(overEvent);
|
|
||||||
onIntersectLeave(leaveEvent);
|
|
||||||
if (component.onPointerOver) component.onPointerOver(overEvent);
|
|
||||||
if (component.onPointerLeave) component.onPointerLeave(leaveEvent);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
function pointerEnter(event) {
|
|
||||||
updatePosition(event);
|
|
||||||
onEnter({ type: 'pointerenter', position, positionN, positionV3 });
|
|
||||||
};
|
|
||||||
|
|
||||||
function pointerMove(event) {
|
|
||||||
updatePosition(event);
|
|
||||||
onMove({ type: 'pointermove', position, positionN, positionV3 });
|
|
||||||
intersect();
|
|
||||||
};
|
|
||||||
|
|
||||||
function pointerClick(event) {
|
|
||||||
updatePosition(event);
|
|
||||||
if (intersectObjects.length) {
|
|
||||||
const intersects = raycaster.intersect(positionN, intersectObjects);
|
|
||||||
const iMeshes = [];
|
|
||||||
intersects.forEach(intersect => {
|
|
||||||
const { object } = intersect;
|
|
||||||
const { component } = object;
|
|
||||||
|
|
||||||
// only once for InstancedMesh
|
|
||||||
if (object instanceof InstancedMesh) {
|
|
||||||
if (iMeshes.indexOf(object) !== -1) return;
|
|
||||||
iMeshes.push(object);
|
|
||||||
}
|
|
||||||
|
|
||||||
const event = { type: 'click', component, intersect };
|
|
||||||
onIntersectClick(event);
|
|
||||||
if (component.onClick) component.onClick(event);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
function pointerLeave() {
|
|
||||||
if (resetOnEnd) reset();
|
|
||||||
onLeave({ type: 'pointerleave' });
|
|
||||||
};
|
|
||||||
|
|
||||||
function addListeners() {
|
|
||||||
domElement.addEventListener('mouseenter', pointerEnter);
|
|
||||||
domElement.addEventListener('mousemove', pointerMove);
|
|
||||||
domElement.addEventListener('mouseleave', pointerLeave);
|
|
||||||
domElement.addEventListener('click', pointerClick);
|
|
||||||
if (touch) {
|
|
||||||
domElement.addEventListener('touchstart', pointerEnter);
|
|
||||||
domElement.addEventListener('touchmove', pointerMove);
|
|
||||||
domElement.addEventListener('touchend', pointerLeave);
|
|
||||||
}
|
|
||||||
obj.listeners = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
function removeListeners() {
|
|
||||||
domElement.removeEventListener('mouseenter', pointerEnter);
|
|
||||||
domElement.removeEventListener('mousemove', pointerMove);
|
|
||||||
domElement.removeEventListener('mouseleave', pointerLeave);
|
|
||||||
domElement.removeEventListener('click', pointerClick);
|
|
||||||
|
|
||||||
domElement.removeEventListener('touchstart', pointerEnter);
|
|
||||||
domElement.removeEventListener('touchmove', pointerMove);
|
|
||||||
domElement.removeEventListener('touchend', pointerLeave);
|
|
||||||
obj.listeners = false;
|
|
||||||
};
|
|
||||||
};
|
|
230
src/core/usePointer.ts
Normal file
230
src/core/usePointer.ts
Normal file
@ -0,0 +1,230 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-empty-function */
|
||||||
|
import { Camera, InstancedMesh, Intersection, Mesh, Vector2, Vector3 } from 'three'
|
||||||
|
import useRaycaster from './useRaycaster'
|
||||||
|
|
||||||
|
export interface PointerEventInterface {
|
||||||
|
type: 'pointerenter' | 'pointermove' | 'pointerleave' | 'click'
|
||||||
|
position?: Vector2
|
||||||
|
positionN?: Vector2
|
||||||
|
positionV3?: Vector3
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PointerIntersectEventInterface {
|
||||||
|
type: 'pointerenter' | 'pointerover' | 'pointermove' | 'pointerleave' | 'click'
|
||||||
|
component: any
|
||||||
|
over?: boolean
|
||||||
|
intersect?: Intersection
|
||||||
|
}
|
||||||
|
|
||||||
|
export type PointerCallbackType = (e: PointerEventInterface) => void
|
||||||
|
export type PointerIntersectCallbackType = (e: PointerIntersectEventInterface) => void
|
||||||
|
export type IntersectObject = Mesh | InstancedMesh
|
||||||
|
|
||||||
|
export interface PointerPublicConfigInterface {
|
||||||
|
intersectMode?: 'frame'
|
||||||
|
touch?: boolean
|
||||||
|
resetOnEnd?: boolean
|
||||||
|
resetPosition?: Vector2
|
||||||
|
resetPositionV3?: Vector3
|
||||||
|
onEnter?: PointerCallbackType
|
||||||
|
onMove?: PointerCallbackType
|
||||||
|
onLeave?: PointerCallbackType
|
||||||
|
onClick?: PointerCallbackType
|
||||||
|
onIntersectEnter?: PointerIntersectCallbackType
|
||||||
|
onIntersectOver?: PointerIntersectCallbackType
|
||||||
|
onIntersectMove?: PointerIntersectCallbackType
|
||||||
|
onIntersectLeave?: PointerIntersectCallbackType
|
||||||
|
onIntersectClick?: PointerIntersectCallbackType
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PointerConfigInterface extends PointerPublicConfigInterface {
|
||||||
|
camera: Camera
|
||||||
|
domElement: HTMLCanvasElement
|
||||||
|
intersectObjects: IntersectObject[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PointerInterface {
|
||||||
|
position: Vector2
|
||||||
|
positionN: Vector2
|
||||||
|
positionV3: Vector3
|
||||||
|
intersectObjects: IntersectObject[]
|
||||||
|
listeners: boolean
|
||||||
|
addListeners(cb: void): void
|
||||||
|
removeListeners(cb: void): void
|
||||||
|
intersect(): void
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function usePointer(options: PointerConfigInterface): PointerInterface {
|
||||||
|
const {
|
||||||
|
camera,
|
||||||
|
domElement,
|
||||||
|
intersectObjects,
|
||||||
|
touch = true,
|
||||||
|
resetOnEnd = false,
|
||||||
|
resetPosition = new Vector2(0, 0),
|
||||||
|
resetPositionV3 = new Vector3(0, 0, 0),
|
||||||
|
onEnter = () => {},
|
||||||
|
onMove = () => {},
|
||||||
|
onLeave = () => {},
|
||||||
|
onClick = () => {},
|
||||||
|
onIntersectEnter = () => {},
|
||||||
|
onIntersectOver = () => {},
|
||||||
|
onIntersectMove = () => {},
|
||||||
|
onIntersectLeave = () => {},
|
||||||
|
onIntersectClick = () => {},
|
||||||
|
} = options
|
||||||
|
|
||||||
|
const position = resetPosition.clone()
|
||||||
|
const positionN = new Vector2(0, 0)
|
||||||
|
|
||||||
|
const raycaster = useRaycaster({ camera })
|
||||||
|
const positionV3 = raycaster.position
|
||||||
|
|
||||||
|
const obj: PointerInterface = {
|
||||||
|
position,
|
||||||
|
positionN,
|
||||||
|
positionV3,
|
||||||
|
intersectObjects,
|
||||||
|
listeners: false,
|
||||||
|
addListeners,
|
||||||
|
removeListeners,
|
||||||
|
intersect,
|
||||||
|
}
|
||||||
|
|
||||||
|
return obj
|
||||||
|
|
||||||
|
function reset() {
|
||||||
|
position.copy(resetPosition)
|
||||||
|
positionV3.copy(resetPositionV3)
|
||||||
|
}
|
||||||
|
|
||||||
|
function updatePosition(event: TouchEvent | MouseEvent) {
|
||||||
|
let x, y
|
||||||
|
if (event instanceof TouchEvent && event.touches && event.touches.length > 0) {
|
||||||
|
x = (<TouchEvent>event).touches[0].clientX
|
||||||
|
y = (<TouchEvent>event).touches[0].clientY
|
||||||
|
} else {
|
||||||
|
x = (<MouseEvent>event).clientX
|
||||||
|
y = (<MouseEvent>event).clientY
|
||||||
|
}
|
||||||
|
|
||||||
|
const rect = domElement.getBoundingClientRect()
|
||||||
|
position.x = x - rect.left
|
||||||
|
position.y = y - rect.top
|
||||||
|
positionN.x = (position.x / rect.width) * 2 - 1
|
||||||
|
positionN.y = -(position.y / rect.height) * 2 + 1
|
||||||
|
raycaster.updatePosition(positionN)
|
||||||
|
}
|
||||||
|
|
||||||
|
function intersect() {
|
||||||
|
if (intersectObjects.length) {
|
||||||
|
const intersects = raycaster.intersect(positionN, intersectObjects)
|
||||||
|
const offObjects: IntersectObject[] = [...intersectObjects]
|
||||||
|
const iMeshes: InstancedMesh[] = []
|
||||||
|
|
||||||
|
intersects.forEach(intersect => {
|
||||||
|
const { object } = intersect
|
||||||
|
const { component } = object.userData
|
||||||
|
|
||||||
|
// only once for InstancedMesh
|
||||||
|
if (object instanceof InstancedMesh) {
|
||||||
|
if (iMeshes.indexOf(object) !== -1) return
|
||||||
|
iMeshes.push(object)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!object.userData.over) {
|
||||||
|
object.userData.over = true
|
||||||
|
const overEvent: PointerIntersectEventInterface = { type: 'pointerover', over: true, component, intersect }
|
||||||
|
const enterEvent: PointerIntersectEventInterface = { ...overEvent, type: 'pointerenter' }
|
||||||
|
onIntersectOver(overEvent)
|
||||||
|
onIntersectEnter(enterEvent)
|
||||||
|
component.onPointerOver?.(overEvent)
|
||||||
|
component.onPointerEnter?.(enterEvent)
|
||||||
|
}
|
||||||
|
|
||||||
|
const moveEvent: PointerIntersectEventInterface = { type: 'pointermove', component, intersect }
|
||||||
|
onIntersectMove(moveEvent)
|
||||||
|
component.onPointerMove?.(moveEvent)
|
||||||
|
|
||||||
|
offObjects.splice(offObjects.indexOf((<IntersectObject>object)), 1)
|
||||||
|
})
|
||||||
|
|
||||||
|
offObjects.forEach(object => {
|
||||||
|
const { component } = object.userData
|
||||||
|
if (object.userData.over) {
|
||||||
|
object.userData.over = false
|
||||||
|
const overEvent: PointerIntersectEventInterface = { type: 'pointerover', over: false, component }
|
||||||
|
const leaveEvent: PointerIntersectEventInterface = { ...overEvent, type: 'pointerleave' }
|
||||||
|
onIntersectOver(overEvent)
|
||||||
|
onIntersectLeave(leaveEvent)
|
||||||
|
component.onPointerOver?.(overEvent)
|
||||||
|
component.onPointerLeave?.(leaveEvent)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function pointerEnter(event: TouchEvent | MouseEvent) {
|
||||||
|
updatePosition(event)
|
||||||
|
onEnter({ type: 'pointerenter', position, positionN, positionV3 })
|
||||||
|
}
|
||||||
|
|
||||||
|
function pointerMove(event: TouchEvent | MouseEvent) {
|
||||||
|
updatePosition(event)
|
||||||
|
onMove({ type: 'pointermove', position, positionN, positionV3 })
|
||||||
|
intersect()
|
||||||
|
}
|
||||||
|
|
||||||
|
function pointerClick(event: TouchEvent | MouseEvent) {
|
||||||
|
updatePosition(event)
|
||||||
|
if (intersectObjects.length) {
|
||||||
|
const intersects = raycaster.intersect(positionN, intersectObjects)
|
||||||
|
const iMeshes: InstancedMesh[] = []
|
||||||
|
intersects.forEach(intersect => {
|
||||||
|
const { object } = intersect
|
||||||
|
const { component } = object.userData
|
||||||
|
|
||||||
|
// only once for InstancedMesh
|
||||||
|
if (object instanceof InstancedMesh) {
|
||||||
|
if (iMeshes.indexOf(object) !== -1) return
|
||||||
|
iMeshes.push(object)
|
||||||
|
}
|
||||||
|
|
||||||
|
const event: PointerIntersectEventInterface = { type: 'click', component, intersect }
|
||||||
|
onIntersectClick(event)
|
||||||
|
component.onClick?.(event)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
onClick({ type: 'click', position, positionN, positionV3 })
|
||||||
|
}
|
||||||
|
|
||||||
|
function pointerLeave() {
|
||||||
|
if (resetOnEnd) reset()
|
||||||
|
onLeave({ type: 'pointerleave' })
|
||||||
|
}
|
||||||
|
|
||||||
|
function addListeners() {
|
||||||
|
domElement.addEventListener('mouseenter', pointerEnter)
|
||||||
|
domElement.addEventListener('mousemove', pointerMove)
|
||||||
|
domElement.addEventListener('mouseleave', pointerLeave)
|
||||||
|
domElement.addEventListener('click', pointerClick)
|
||||||
|
if (touch) {
|
||||||
|
domElement.addEventListener('touchstart', pointerEnter)
|
||||||
|
domElement.addEventListener('touchmove', pointerMove)
|
||||||
|
domElement.addEventListener('touchend', pointerLeave)
|
||||||
|
}
|
||||||
|
obj.listeners = true
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeListeners() {
|
||||||
|
domElement.removeEventListener('mouseenter', pointerEnter)
|
||||||
|
domElement.removeEventListener('mousemove', pointerMove)
|
||||||
|
domElement.removeEventListener('mouseleave', pointerLeave)
|
||||||
|
domElement.removeEventListener('click', pointerClick)
|
||||||
|
|
||||||
|
domElement.removeEventListener('touchstart', pointerEnter)
|
||||||
|
domElement.removeEventListener('touchmove', pointerMove)
|
||||||
|
domElement.removeEventListener('touchend', pointerLeave)
|
||||||
|
obj.listeners = false
|
||||||
|
}
|
||||||
|
}
|
@ -1,29 +0,0 @@
|
|||||||
import { Plane, Raycaster, Vector3 } from 'three';
|
|
||||||
|
|
||||||
export default function useRaycaster(options) {
|
|
||||||
const {
|
|
||||||
camera,
|
|
||||||
resetPosition = new Vector3(0, 0, 0),
|
|
||||||
} = options;
|
|
||||||
|
|
||||||
const raycaster = new Raycaster();
|
|
||||||
const position = resetPosition.clone();
|
|
||||||
const plane = new Plane(new Vector3(0, 0, 1), 0);
|
|
||||||
|
|
||||||
const updatePosition = (coords) => {
|
|
||||||
raycaster.setFromCamera(coords, camera);
|
|
||||||
camera.getWorldDirection(plane.normal);
|
|
||||||
raycaster.ray.intersectPlane(plane, position);
|
|
||||||
};
|
|
||||||
|
|
||||||
const intersect = (coords, objects) => {
|
|
||||||
raycaster.setFromCamera(coords, camera);
|
|
||||||
return raycaster.intersectObjects(objects);
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
|
||||||
position,
|
|
||||||
updatePosition,
|
|
||||||
intersect,
|
|
||||||
};
|
|
||||||
};
|
|
41
src/core/useRaycaster.ts
Normal file
41
src/core/useRaycaster.ts
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
import { Camera, Intersection, Plane, Raycaster, Vector2, Vector3 } from 'three'
|
||||||
|
import { IntersectObject } from './usePointer'
|
||||||
|
|
||||||
|
export interface RaycasterInterface {
|
||||||
|
position: Vector3
|
||||||
|
updatePosition(coords: Vector2): void
|
||||||
|
intersect(coords: Vector2, objects: IntersectObject[]): Intersection[],
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RaycasterConfigInterface {
|
||||||
|
camera: Camera
|
||||||
|
resetPosition?: Vector3
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function useRaycaster(options: RaycasterConfigInterface): RaycasterInterface {
|
||||||
|
const {
|
||||||
|
camera,
|
||||||
|
resetPosition = new Vector3(0, 0, 0),
|
||||||
|
} = options
|
||||||
|
|
||||||
|
const raycaster = new Raycaster()
|
||||||
|
const position = resetPosition.clone()
|
||||||
|
const plane = new Plane(new Vector3(0, 0, 1), 0)
|
||||||
|
|
||||||
|
const updatePosition = (coords: Vector2) => {
|
||||||
|
raycaster.setFromCamera(coords, camera)
|
||||||
|
camera.getWorldDirection(plane.normal)
|
||||||
|
raycaster.ray.intersectPlane(plane, position)
|
||||||
|
}
|
||||||
|
|
||||||
|
const intersect = (coords: Vector2, objects: IntersectObject[]) => {
|
||||||
|
raycaster.setFromCamera(coords, camera)
|
||||||
|
return raycaster.intersectObjects(objects)
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
position,
|
||||||
|
updatePosition,
|
||||||
|
intersect,
|
||||||
|
}
|
||||||
|
}
|
@ -1,264 +0,0 @@
|
|||||||
import { WebGLRenderer } from 'three';
|
|
||||||
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';
|
|
||||||
import usePointer from './usePointer';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Three.js helper
|
|
||||||
*/
|
|
||||||
export default function useThree() {
|
|
||||||
// default conf
|
|
||||||
const conf = {
|
|
||||||
canvas: null,
|
|
||||||
antialias: true,
|
|
||||||
alpha: false,
|
|
||||||
autoClear: true,
|
|
||||||
orbit_ctrl: false,
|
|
||||||
pointer: false,
|
|
||||||
resize: false,
|
|
||||||
width: 300,
|
|
||||||
height: 150,
|
|
||||||
};
|
|
||||||
|
|
||||||
// size
|
|
||||||
const size = {
|
|
||||||
width: 1, height: 1,
|
|
||||||
wWidth: 1, wHeight: 1,
|
|
||||||
ratio: 1,
|
|
||||||
};
|
|
||||||
|
|
||||||
// handlers
|
|
||||||
const afterInitCallbacks = [];
|
|
||||||
let afterResizeCallbacks = [];
|
|
||||||
let beforeRenderCallbacks = [];
|
|
||||||
|
|
||||||
const intersectObjects = [];
|
|
||||||
|
|
||||||
// returned object
|
|
||||||
const obj = {
|
|
||||||
conf,
|
|
||||||
renderer: null,
|
|
||||||
camera: null,
|
|
||||||
cameraCtrl: null,
|
|
||||||
scene: null,
|
|
||||||
pointer: null,
|
|
||||||
size,
|
|
||||||
init,
|
|
||||||
dispose,
|
|
||||||
render,
|
|
||||||
renderC,
|
|
||||||
setSize,
|
|
||||||
onAfterInit,
|
|
||||||
onAfterResize, offAfterResize,
|
|
||||||
// onBeforeRender, offBeforeRender,
|
|
||||||
addIntersectObject, removeIntersectObject,
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* init three
|
|
||||||
*/
|
|
||||||
function init(params) {
|
|
||||||
if (params) {
|
|
||||||
Object.entries(params).forEach(([key, value]) => {
|
|
||||||
conf[key] = value;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!obj.scene) {
|
|
||||||
console.error('Missing Scene');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!obj.camera) {
|
|
||||||
console.error('Missing Camera');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
obj.renderer = new WebGLRenderer({ canvas: conf.canvas, antialias: conf.antialias, alpha: conf.alpha });
|
|
||||||
obj.renderer.autoClear = conf.autoClear;
|
|
||||||
|
|
||||||
if (conf.resize) {
|
|
||||||
onResize();
|
|
||||||
window.addEventListener('resize', onResize);
|
|
||||||
} else {
|
|
||||||
setSize(conf.width, conf.height);
|
|
||||||
}
|
|
||||||
|
|
||||||
initPointer();
|
|
||||||
|
|
||||||
if (conf.orbit_ctrl) {
|
|
||||||
obj.orbitCtrl = new OrbitControls(obj.camera, obj.renderer.domElement);
|
|
||||||
if (conf.orbit_ctrl instanceof Object) {
|
|
||||||
Object.entries(conf.orbit_ctrl).forEach(([key, value]) => {
|
|
||||||
obj.orbitCtrl[key] = value;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
afterInitCallbacks.forEach(c => c());
|
|
||||||
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
|
|
||||||
function initPointer() {
|
|
||||||
let pointerConf = {
|
|
||||||
camera: obj.camera,
|
|
||||||
domElement: obj.renderer.domElement,
|
|
||||||
intersectObjects,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (conf.pointer && conf.pointer instanceof Object) {
|
|
||||||
pointerConf = { ...pointerConf, ...conf.pointer };
|
|
||||||
}
|
|
||||||
|
|
||||||
obj.pointer = usePointer(pointerConf);
|
|
||||||
if (conf.pointer || intersectObjects.length) {
|
|
||||||
obj.pointer.addListeners();
|
|
||||||
if (conf.pointer.intersectMode === 'frame') {
|
|
||||||
onBeforeRender(() => {
|
|
||||||
obj.pointer.intersect();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* add after init callback
|
|
||||||
*/
|
|
||||||
function onAfterInit(callback) {
|
|
||||||
afterInitCallbacks.push(callback);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* add after resize callback
|
|
||||||
*/
|
|
||||||
function onAfterResize(callback) {
|
|
||||||
afterResizeCallbacks.push(callback);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* remove after resize callback
|
|
||||||
*/
|
|
||||||
function offAfterResize(callback) {
|
|
||||||
afterResizeCallbacks = afterResizeCallbacks.filter(c => c !== callback);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* add before render callback
|
|
||||||
*/
|
|
||||||
function onBeforeRender(callback) {
|
|
||||||
beforeRenderCallbacks.push(callback);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* remove before render callback
|
|
||||||
*/
|
|
||||||
function offBeforeRender(callback) {
|
|
||||||
beforeRenderCallbacks = beforeRenderCallbacks.filter(c => c !== callback);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* default render
|
|
||||||
*/
|
|
||||||
function render() {
|
|
||||||
if (obj.orbitCtrl) obj.orbitCtrl.update();
|
|
||||||
beforeRenderCallbacks.forEach(c => c());
|
|
||||||
obj.renderer.render(obj.scene, obj.camera);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* composer render
|
|
||||||
*/
|
|
||||||
function renderC() {
|
|
||||||
if (obj.orbitCtrl) obj.orbitCtrl.update();
|
|
||||||
beforeRenderCallbacks.forEach(c => c());
|
|
||||||
obj.composer.render();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* add intersect object
|
|
||||||
*/
|
|
||||||
function addIntersectObject(o) {
|
|
||||||
if (intersectObjects.indexOf(o) === -1) {
|
|
||||||
intersectObjects.push(o);
|
|
||||||
}
|
|
||||||
// add listeners if needed
|
|
||||||
if (obj.pointer && !obj.pointer.listeners) {
|
|
||||||
obj.pointer.addListeners();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* remove intersect object
|
|
||||||
*/
|
|
||||||
function removeIntersectObject(o) {
|
|
||||||
const i = intersectObjects.indexOf(o);
|
|
||||||
if (i !== -1) {
|
|
||||||
intersectObjects.splice(i, 1);
|
|
||||||
}
|
|
||||||
// remove listeners if needed
|
|
||||||
if (obj.pointer && !conf.pointer && intersectObjects.length === 0) {
|
|
||||||
obj.pointer.removeListeners();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* remove listeners and dispose
|
|
||||||
*/
|
|
||||||
function dispose() {
|
|
||||||
beforeRenderCallbacks = [];
|
|
||||||
window.removeEventListener('resize', onResize);
|
|
||||||
if (obj.pointer) obj.pointer.removeListeners();
|
|
||||||
if (obj.orbitCtrl) obj.orbitCtrl.dispose();
|
|
||||||
if (obj.renderer) obj.renderer.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* resize listener
|
|
||||||
*/
|
|
||||||
function onResize() {
|
|
||||||
if (conf.resize === 'window') {
|
|
||||||
setSize(window.innerWidth, window.innerHeight);
|
|
||||||
} else {
|
|
||||||
const elt = obj.renderer.domElement.parentNode;
|
|
||||||
setSize(elt.clientWidth, elt.clientHeight);
|
|
||||||
}
|
|
||||||
afterResizeCallbacks.forEach(c => c());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* update renderer size and camera
|
|
||||||
*/
|
|
||||||
function setSize(width, height) {
|
|
||||||
size.width = width;
|
|
||||||
size.height = height;
|
|
||||||
size.ratio = width / height;
|
|
||||||
|
|
||||||
obj.renderer.setSize(width, height, false);
|
|
||||||
obj.camera.aspect = size.ratio;
|
|
||||||
obj.camera.updateProjectionMatrix();
|
|
||||||
|
|
||||||
if (obj.composer) {
|
|
||||||
obj.composer.setSize(width, height);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (obj.camera.type === 'OrthographicCamera') {
|
|
||||||
size.wWidth = obj.camera.right - obj.camera.left;
|
|
||||||
size.wHeight = obj.camera.top - obj.camera.bottom;
|
|
||||||
} else {
|
|
||||||
const wsize = getCameraSize();
|
|
||||||
size.wWidth = wsize[0]; size.wHeight = wsize[1];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* calculate camera visible area size
|
|
||||||
*/
|
|
||||||
function getCameraSize() {
|
|
||||||
const vFOV = (obj.camera.fov * Math.PI) / 180;
|
|
||||||
const h = 2 * Math.tan(vFOV / 2) * Math.abs(obj.camera.position.z);
|
|
||||||
const w = h * obj.camera.aspect;
|
|
||||||
return [w, h];
|
|
||||||
}
|
|
||||||
|
|
||||||
return obj;
|
|
||||||
}
|
|
285
src/core/useThree.ts
Normal file
285
src/core/useThree.ts
Normal file
@ -0,0 +1,285 @@
|
|||||||
|
import { Camera, OrthographicCamera, PerspectiveCamera, Scene, WebGLRenderer } from 'three'
|
||||||
|
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'
|
||||||
|
import { EffectComposer } from 'three/examples/jsm/postprocessing/EffectComposer.js'
|
||||||
|
import usePointer, { IntersectObject, PointerConfigInterface, PointerPublicConfigInterface, PointerInterface } from './usePointer'
|
||||||
|
|
||||||
|
export interface SizeInterface {
|
||||||
|
width: number
|
||||||
|
height: number
|
||||||
|
wWidth: number
|
||||||
|
wHeight: number
|
||||||
|
ratio: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ThreeConfigInterface {
|
||||||
|
canvas?: HTMLCanvasElement
|
||||||
|
antialias: boolean
|
||||||
|
alpha: boolean
|
||||||
|
autoClear: boolean
|
||||||
|
orbitCtrl: boolean | Record<string, unknown>
|
||||||
|
pointer: boolean | PointerPublicConfigInterface
|
||||||
|
resize: boolean | string
|
||||||
|
width?: number
|
||||||
|
height?: number
|
||||||
|
onResize?(size: SizeInterface): void
|
||||||
|
[index:string]: any
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ThreeInterface {
|
||||||
|
config: ThreeConfigInterface
|
||||||
|
renderer: WebGLRenderer
|
||||||
|
composer?: EffectComposer
|
||||||
|
camera?: Camera
|
||||||
|
cameraCtrl?: OrbitControls
|
||||||
|
scene?: Scene
|
||||||
|
pointer?: PointerInterface
|
||||||
|
size: SizeInterface
|
||||||
|
init(): boolean
|
||||||
|
dispose(): void
|
||||||
|
render(): void
|
||||||
|
renderC(): void
|
||||||
|
setSize(width: number, height: number): void
|
||||||
|
addIntersectObject(o: IntersectObject): void
|
||||||
|
removeIntersectObject(o: IntersectObject): void
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Three.js helper
|
||||||
|
*/
|
||||||
|
export default function useThree(params: ThreeConfigInterface): ThreeInterface {
|
||||||
|
// default config
|
||||||
|
const config: ThreeConfigInterface = {
|
||||||
|
antialias: true,
|
||||||
|
alpha: false,
|
||||||
|
autoClear: true,
|
||||||
|
orbitCtrl: false,
|
||||||
|
pointer: false,
|
||||||
|
resize: false,
|
||||||
|
width: 300,
|
||||||
|
height: 150,
|
||||||
|
}
|
||||||
|
|
||||||
|
if (params) {
|
||||||
|
Object.entries(params).forEach(([key, value]) => {
|
||||||
|
config[key] = value
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// size
|
||||||
|
const size: SizeInterface = {
|
||||||
|
width: 1, height: 1,
|
||||||
|
wWidth: 1, wHeight: 1,
|
||||||
|
ratio: 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
const beforeRenderCallbacks: {(): void}[] = []
|
||||||
|
|
||||||
|
const intersectObjects: IntersectObject[] = []
|
||||||
|
|
||||||
|
const renderer = createRenderer()
|
||||||
|
|
||||||
|
// returned object
|
||||||
|
const obj: ThreeInterface = {
|
||||||
|
config,
|
||||||
|
renderer,
|
||||||
|
size,
|
||||||
|
init,
|
||||||
|
dispose,
|
||||||
|
render,
|
||||||
|
renderC,
|
||||||
|
setSize,
|
||||||
|
addIntersectObject, removeIntersectObject,
|
||||||
|
}
|
||||||
|
|
||||||
|
return obj
|
||||||
|
|
||||||
|
/**
|
||||||
|
* create WebGLRenderer
|
||||||
|
*/
|
||||||
|
function createRenderer(): WebGLRenderer {
|
||||||
|
const renderer = new WebGLRenderer({ canvas: config.canvas, antialias: config.antialias, alpha: config.alpha })
|
||||||
|
renderer.autoClear = config.autoClear
|
||||||
|
return renderer
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* init three
|
||||||
|
*/
|
||||||
|
function init() {
|
||||||
|
if (!obj.scene) {
|
||||||
|
console.error('Missing Scene')
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!obj.camera) {
|
||||||
|
console.error('Missing Camera')
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config.resize) {
|
||||||
|
onResize()
|
||||||
|
window.addEventListener('resize', onResize)
|
||||||
|
} else if (config.width && config.height) {
|
||||||
|
setSize(config.width, config.height)
|
||||||
|
}
|
||||||
|
|
||||||
|
initPointer()
|
||||||
|
|
||||||
|
if (config.orbitCtrl) {
|
||||||
|
const cameraCtrl = new OrbitControls(obj.camera, obj.renderer.domElement)
|
||||||
|
if (config.orbitCtrl instanceof Object) {
|
||||||
|
Object.entries(config.orbitCtrl).forEach(([key, value]) => {
|
||||||
|
// @ts-ignore
|
||||||
|
cameraCtrl[key] = value
|
||||||
|
})
|
||||||
|
}
|
||||||
|
onBeforeRender(() => { cameraCtrl.update() })
|
||||||
|
obj.cameraCtrl = cameraCtrl
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* init pointer
|
||||||
|
*/
|
||||||
|
function initPointer() {
|
||||||
|
let pointerConf: PointerConfigInterface = {
|
||||||
|
camera: obj.camera!,
|
||||||
|
domElement: obj.renderer!.domElement,
|
||||||
|
intersectObjects,
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config.pointer && config.pointer instanceof Object) {
|
||||||
|
pointerConf = { ...pointerConf, ...config.pointer }
|
||||||
|
}
|
||||||
|
|
||||||
|
const pointer = obj.pointer = usePointer(pointerConf)
|
||||||
|
if (config.pointer || intersectObjects.length) {
|
||||||
|
pointer.addListeners()
|
||||||
|
if (pointerConf.intersectMode === 'frame') {
|
||||||
|
onBeforeRender(pointer.intersect)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* add before render callback
|
||||||
|
*/
|
||||||
|
function onBeforeRender(cb: {(): void}) {
|
||||||
|
beforeRenderCallbacks.push(cb)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* default render
|
||||||
|
*/
|
||||||
|
function render() {
|
||||||
|
// if (obj.cameraCtrl) obj.cameraCtrl.update()
|
||||||
|
beforeRenderCallbacks.forEach(c => c())
|
||||||
|
obj.renderer!.render(obj.scene!, obj.camera!)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* composer render
|
||||||
|
*/
|
||||||
|
function renderC() {
|
||||||
|
// if (obj.cameraCtrl) obj.cameraCtrl.update()
|
||||||
|
beforeRenderCallbacks.forEach(c => c())
|
||||||
|
obj.composer!.render()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* add intersect object
|
||||||
|
*/
|
||||||
|
function addIntersectObject(o: IntersectObject) {
|
||||||
|
if (intersectObjects.indexOf(o) === -1) {
|
||||||
|
intersectObjects.push(o)
|
||||||
|
}
|
||||||
|
// add listeners if needed
|
||||||
|
if (obj.pointer && !obj.pointer.listeners) {
|
||||||
|
obj.pointer.addListeners()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* remove intersect object
|
||||||
|
*/
|
||||||
|
function removeIntersectObject(o: IntersectObject) {
|
||||||
|
const i = intersectObjects.indexOf(o)
|
||||||
|
if (i !== -1) {
|
||||||
|
intersectObjects.splice(i, 1)
|
||||||
|
}
|
||||||
|
// remove listeners if needed
|
||||||
|
if (obj.pointer && !config.pointer && intersectObjects.length === 0) {
|
||||||
|
obj.pointer.removeListeners()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* remove listeners and dispose
|
||||||
|
*/
|
||||||
|
function dispose() {
|
||||||
|
// beforeRenderCallbacks = []
|
||||||
|
window.removeEventListener('resize', onResize)
|
||||||
|
if (obj.pointer) obj.pointer.removeListeners()
|
||||||
|
if (obj.cameraCtrl) obj.cameraCtrl.dispose()
|
||||||
|
if (obj.renderer) obj.renderer.dispose()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* resize listener
|
||||||
|
*/
|
||||||
|
function onResize() {
|
||||||
|
if (config.resize === 'window') {
|
||||||
|
setSize(window.innerWidth, window.innerHeight)
|
||||||
|
} else {
|
||||||
|
const elt = obj.renderer!.domElement.parentNode as Element
|
||||||
|
if (elt) setSize(elt.clientWidth, elt.clientHeight)
|
||||||
|
}
|
||||||
|
config.onResize?.(size)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* update renderer size and camera
|
||||||
|
*/
|
||||||
|
function setSize(width: number, height: number) {
|
||||||
|
size.width = width
|
||||||
|
size.height = height
|
||||||
|
size.ratio = width / height
|
||||||
|
|
||||||
|
obj.renderer!.setSize(width, height, false)
|
||||||
|
|
||||||
|
// already done in EffectComposer
|
||||||
|
// if (obj.composer) {
|
||||||
|
// obj.composer.setSize(width, height)
|
||||||
|
// }
|
||||||
|
|
||||||
|
const camera = (<Camera>obj.camera!)
|
||||||
|
if (camera.type === 'PerspectiveCamera') {
|
||||||
|
const pCamera = (<PerspectiveCamera>camera)
|
||||||
|
pCamera.aspect = size.ratio
|
||||||
|
pCamera.updateProjectionMatrix()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (camera.type === 'OrthographicCamera') {
|
||||||
|
const oCamera = (<OrthographicCamera>camera)
|
||||||
|
size.wWidth = oCamera.right - oCamera.left
|
||||||
|
size.wHeight = oCamera.top - oCamera.bottom
|
||||||
|
} else {
|
||||||
|
const wsize = getCameraSize()
|
||||||
|
size.wWidth = wsize[0]
|
||||||
|
size.wHeight = wsize[1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* calculate camera visible area size
|
||||||
|
*/
|
||||||
|
function getCameraSize() {
|
||||||
|
const camera = (<PerspectiveCamera>obj.camera!)
|
||||||
|
const vFOV = (camera.fov * Math.PI) / 180
|
||||||
|
const h = 2 * Math.tan(vFOV / 2) * Math.abs(camera.position.z)
|
||||||
|
const w = h * camera.aspect
|
||||||
|
return [w, h]
|
||||||
|
}
|
||||||
|
}
|
@ -1,44 +0,0 @@
|
|||||||
import { defineComponent } from 'vue';
|
|
||||||
import { BokehPass } from 'three/examples/jsm/postprocessing/BokehPass.js';
|
|
||||||
import EffectPass from './EffectPass.js';
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
extends: EffectPass,
|
|
||||||
props: {
|
|
||||||
focus: {
|
|
||||||
type: Number,
|
|
||||||
default: 1,
|
|
||||||
},
|
|
||||||
aperture: {
|
|
||||||
type: Number,
|
|
||||||
default: 0.025,
|
|
||||||
},
|
|
||||||
maxblur: {
|
|
||||||
type: Number,
|
|
||||||
default: 0.01,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
focus() { this.pass.uniforms.focus.value = this.focus; },
|
|
||||||
aperture() { this.pass.uniforms.aperture.value = this.aperture; },
|
|
||||||
maxblur() { this.pass.uniforms.maxblur.value = this.maxblur; },
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
if (!this.three.scene) {
|
|
||||||
console.error('Missing Scene');
|
|
||||||
}
|
|
||||||
if (!this.three.camera) {
|
|
||||||
console.error('Missing Camera');
|
|
||||||
}
|
|
||||||
const params = {
|
|
||||||
focus: this.focus,
|
|
||||||
aperture: this.aperture,
|
|
||||||
maxblur: this.maxblur,
|
|
||||||
width: this.three.size.width,
|
|
||||||
height: this.three.size.height,
|
|
||||||
};
|
|
||||||
const pass = new BokehPass(this.three.scene, this.three.camera, params);
|
|
||||||
this.completePass(pass);
|
|
||||||
},
|
|
||||||
__hmrId: 'BokehPass',
|
|
||||||
});
|
|
44
src/effects/BokehPass.ts
Normal file
44
src/effects/BokehPass.ts
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
import { defineComponent, watch } from 'vue'
|
||||||
|
import { BokehPass } from 'three/examples/jsm/postprocessing/BokehPass.js'
|
||||||
|
import EffectPass from './EffectPass'
|
||||||
|
|
||||||
|
const props = {
|
||||||
|
focus: { type: Number, default: 1 },
|
||||||
|
aperture: { type: Number, default: 0.025 },
|
||||||
|
maxblur: { type: Number, default: 0.01 },
|
||||||
|
}
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
extends: EffectPass,
|
||||||
|
props,
|
||||||
|
created() {
|
||||||
|
if (!this.renderer) return
|
||||||
|
|
||||||
|
if (!this.renderer.scene) {
|
||||||
|
console.error('Missing Scene')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (!this.renderer.camera) {
|
||||||
|
console.error('Missing Camera')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const params = {
|
||||||
|
focus: this.focus,
|
||||||
|
aperture: this.aperture,
|
||||||
|
maxblur: this.maxblur,
|
||||||
|
width: this.renderer.size.width,
|
||||||
|
height: this.renderer.size.height,
|
||||||
|
}
|
||||||
|
|
||||||
|
const pass = new BokehPass(this.renderer.scene, this.renderer.camera, params)
|
||||||
|
|
||||||
|
Object.keys(props).forEach(p => {
|
||||||
|
// @ts-ignore
|
||||||
|
watch(() => this[p], (value) => { pass.uniforms[p].value = value })
|
||||||
|
})
|
||||||
|
|
||||||
|
this.initEffectPass(pass)
|
||||||
|
},
|
||||||
|
__hmrId: 'BokehPass',
|
||||||
|
})
|
@ -1,41 +0,0 @@
|
|||||||
import { defineComponent } from 'vue';
|
|
||||||
import { EffectComposer } from 'three/examples/jsm/postprocessing/EffectComposer.js';
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
setup() {
|
|
||||||
return {
|
|
||||||
passes: [],
|
|
||||||
};
|
|
||||||
},
|
|
||||||
inject: ['three'],
|
|
||||||
provide() {
|
|
||||||
return {
|
|
||||||
passes: this.passes,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
this.three.onAfterInit(() => {
|
|
||||||
this.composer = new EffectComposer(this.three.renderer);
|
|
||||||
this.three.renderer.autoClear = false;
|
|
||||||
this.passes.forEach(pass => {
|
|
||||||
this.composer.addPass(pass);
|
|
||||||
});
|
|
||||||
this.three.composer = this.composer;
|
|
||||||
|
|
||||||
this.resize();
|
|
||||||
this.three.onAfterResize(this.resize);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
unmounted() {
|
|
||||||
this.three.offAfterResize(this.resize);
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
resize() {
|
|
||||||
this.composer.setSize(this.three.size.width, this.three.size.height);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
render() {
|
|
||||||
return this.$slots.default();
|
|
||||||
},
|
|
||||||
__hmrId: 'EffectComposer',
|
|
||||||
});
|
|
66
src/effects/EffectComposer.ts
Normal file
66
src/effects/EffectComposer.ts
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
import { defineComponent, inject, InjectionKey } from 'vue'
|
||||||
|
import { EffectComposer } from 'three/examples/jsm/postprocessing/EffectComposer.js'
|
||||||
|
import { Pass } from 'three/examples/jsm/postprocessing/Pass'
|
||||||
|
import { RendererInjectionKey, RendererInterface } from '../core/Renderer'
|
||||||
|
|
||||||
|
interface EffectComposerSetupInterface {
|
||||||
|
renderer?: RendererInterface
|
||||||
|
composer?: EffectComposer
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface EffectComposerInterface extends EffectComposerSetupInterface {
|
||||||
|
addPass(pass: Pass): void
|
||||||
|
removePass(pass: Pass): void
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ComposerInjectionKey: InjectionKey<EffectComposerInterface> = Symbol('Composer')
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
setup(): EffectComposerSetupInterface {
|
||||||
|
const renderer = inject(RendererInjectionKey)
|
||||||
|
return { renderer }
|
||||||
|
},
|
||||||
|
provide() {
|
||||||
|
return {
|
||||||
|
[ComposerInjectionKey as symbol]: this,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
if (!this.renderer) {
|
||||||
|
console.error('Renderer not found')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const renderer = this.renderer
|
||||||
|
|
||||||
|
const composer = new EffectComposer(this.renderer.renderer)
|
||||||
|
this.composer = composer
|
||||||
|
this.renderer.composer = composer
|
||||||
|
|
||||||
|
// this.renderer.onInit(() => {
|
||||||
|
renderer.addListener('init', () => {
|
||||||
|
renderer.renderer.autoClear = false
|
||||||
|
this.resize()
|
||||||
|
renderer.addListener('resize', this.resize)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
unmounted() {
|
||||||
|
this.renderer?.removeListener('resize', this.resize)
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
addPass(pass: Pass) {
|
||||||
|
this.composer?.addPass(pass)
|
||||||
|
},
|
||||||
|
removePass(pass: Pass) {
|
||||||
|
this.composer?.removePass(pass)
|
||||||
|
},
|
||||||
|
resize() {
|
||||||
|
if (this.composer && this.renderer) {
|
||||||
|
this.composer.setSize(this.renderer.size.width, this.renderer.size.height)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
render() {
|
||||||
|
return this.$slots.default ? this.$slots.default() : []
|
||||||
|
},
|
||||||
|
__hmrId: 'EffectComposer',
|
||||||
|
})
|
@ -1,25 +0,0 @@
|
|||||||
import { defineComponent } from 'vue';
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
inject: ['three', 'passes'],
|
|
||||||
emits: ['ready'],
|
|
||||||
beforeMount() {
|
|
||||||
if (!this.passes) {
|
|
||||||
console.error('Missing parent EffectComposer');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
unmounted() {
|
|
||||||
if (this.pass.dispose) this.pass.dispose();
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
completePass(pass) {
|
|
||||||
this.passes.push(pass);
|
|
||||||
this.pass = pass;
|
|
||||||
this.$emit('ready', pass);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
render() {
|
|
||||||
return [];
|
|
||||||
},
|
|
||||||
__hmrId: 'EffectPass',
|
|
||||||
});
|
|
47
src/effects/EffectPass.ts
Normal file
47
src/effects/EffectPass.ts
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
import { Pass } from 'three/examples/jsm/postprocessing/Pass'
|
||||||
|
import { defineComponent } from 'vue'
|
||||||
|
import { RendererInjectionKey, RendererInterface } from '../core/Renderer'
|
||||||
|
import { ComposerInjectionKey, EffectComposerInterface } from './EffectComposer'
|
||||||
|
|
||||||
|
export interface EffectSetupInterface {
|
||||||
|
renderer?: RendererInterface
|
||||||
|
composer?: EffectComposerInterface
|
||||||
|
pass?: Pass
|
||||||
|
}
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
// inject for sub components
|
||||||
|
inject: {
|
||||||
|
renderer: RendererInjectionKey as symbol,
|
||||||
|
composer: ComposerInjectionKey as symbol,
|
||||||
|
},
|
||||||
|
emits: ['ready'],
|
||||||
|
setup(): EffectSetupInterface {
|
||||||
|
return {}
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
if (!this.composer) {
|
||||||
|
console.error('Missing parent EffectComposer')
|
||||||
|
}
|
||||||
|
if (!this.renderer) {
|
||||||
|
console.error('Missing parent Renderer')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
unmounted() {
|
||||||
|
if (this.pass) {
|
||||||
|
this.composer?.removePass(this.pass);
|
||||||
|
(this.pass as any).dispose?.()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
initEffectPass(pass: Pass) {
|
||||||
|
this.pass = pass
|
||||||
|
this.composer?.addPass(pass)
|
||||||
|
this.$emit('ready', pass)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
render() {
|
||||||
|
return []
|
||||||
|
},
|
||||||
|
__hmrId: 'EffectPass',
|
||||||
|
})
|
@ -1,26 +0,0 @@
|
|||||||
import { defineComponent } from 'vue';
|
|
||||||
import { ShaderPass } from 'three/examples/jsm/postprocessing/ShaderPass.js';
|
|
||||||
import { FXAAShader } from 'three/examples/jsm/shaders/FXAAShader.js';
|
|
||||||
import EffectPass from './EffectPass.js';
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
extends: EffectPass,
|
|
||||||
mounted() {
|
|
||||||
const pass = new ShaderPass(FXAAShader);
|
|
||||||
this.completePass(pass);
|
|
||||||
|
|
||||||
// resize will be called in three init
|
|
||||||
this.three.onAfterResize(this.resize);
|
|
||||||
},
|
|
||||||
unmounted() {
|
|
||||||
this.three.offAfterResize(this.resize);
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
resize() {
|
|
||||||
const { resolution } = this.pass.material.uniforms;
|
|
||||||
resolution.value.x = 1 / this.three.size.width;
|
|
||||||
resolution.value.y = 1 / this.three.size.height;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
__hmrId: 'FXAAPass',
|
|
||||||
});
|
|
30
src/effects/FXAAPass.ts
Normal file
30
src/effects/FXAAPass.ts
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
import { defineComponent } from 'vue'
|
||||||
|
import { ShaderPass } from 'three/examples/jsm/postprocessing/ShaderPass.js'
|
||||||
|
import { FXAAShader } from 'three/examples/jsm/shaders/FXAAShader.js'
|
||||||
|
import EffectPass from './EffectPass'
|
||||||
|
import { SizeInterface } from '../core/useThree'
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
extends: EffectPass,
|
||||||
|
created() {
|
||||||
|
const pass = new ShaderPass(FXAAShader)
|
||||||
|
|
||||||
|
// resize will be first called in renderer init
|
||||||
|
this.renderer?.addListener('resize', this.resize)
|
||||||
|
|
||||||
|
this.initEffectPass(pass)
|
||||||
|
},
|
||||||
|
unmounted() {
|
||||||
|
this.renderer?.removeListener('resize', this.resize)
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
resize({ size }: { size: SizeInterface }) {
|
||||||
|
if (this.pass) {
|
||||||
|
const { resolution } = (this.pass as ShaderPass).material.uniforms
|
||||||
|
resolution.value.x = 1 / size.width
|
||||||
|
resolution.value.y = 1 / size.height
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
__hmrId: 'FXAAPass',
|
||||||
|
})
|
@ -1,24 +0,0 @@
|
|||||||
import { defineComponent } from 'vue';
|
|
||||||
import { FilmPass } from 'three/examples/jsm/postprocessing/FilmPass.js';
|
|
||||||
import EffectPass from './EffectPass.js';
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
extends: EffectPass,
|
|
||||||
props: {
|
|
||||||
noiseIntensity: { type: Number, default: 0.5 },
|
|
||||||
scanlinesIntensity: { type: Number, default: 0.05 },
|
|
||||||
scanlinesCount: { type: Number, default: 4096 },
|
|
||||||
grayscale: { type: Number, default: 0 },
|
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
noiseIntensity() { this.pass.uniforms.nIntensity.value = this.noiseIntensity; },
|
|
||||||
scanlinesIntensity() { this.pass.uniforms.sIntensity.value = this.scanlinesIntensity; },
|
|
||||||
scanlinesCount() { this.pass.uniforms.sCount.value = this.scanlinesCount; },
|
|
||||||
grayscale() { this.pass.uniforms.grayscale.value = this.grayscale; },
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
const pass = new FilmPass(this.noiseIntensity, this.scanlinesIntensity, this.scanlinesCount, this.grayscale);
|
|
||||||
this.completePass(pass);
|
|
||||||
},
|
|
||||||
__hmrId: 'FilmPass',
|
|
||||||
});
|
|
26
src/effects/FilmPass.ts
Normal file
26
src/effects/FilmPass.ts
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import { defineComponent, watch } from 'vue'
|
||||||
|
import { FilmPass } from 'three/examples/jsm/postprocessing/FilmPass.js'
|
||||||
|
import EffectPass from './EffectPass'
|
||||||
|
|
||||||
|
const props = {
|
||||||
|
noiseIntensity: { type: Number, default: 0.5 },
|
||||||
|
scanlinesIntensity: { type: Number, default: 0.05 },
|
||||||
|
scanlinesCount: { type: Number, default: 4096 },
|
||||||
|
grayscale: { type: Number, default: 0 },
|
||||||
|
}
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
extends: EffectPass,
|
||||||
|
props,
|
||||||
|
created() {
|
||||||
|
const pass = new FilmPass(this.noiseIntensity, this.scanlinesIntensity, this.scanlinesCount, this.grayscale)
|
||||||
|
|
||||||
|
Object.keys(props).forEach(p => {
|
||||||
|
// @ts-ignore
|
||||||
|
watch(() => this[p], (value) => { pass.uniforms[p].value = value })
|
||||||
|
})
|
||||||
|
|
||||||
|
this.initEffectPass(pass)
|
||||||
|
},
|
||||||
|
__hmrId: 'FilmPass',
|
||||||
|
})
|
@ -1,28 +0,0 @@
|
|||||||
import { defineComponent, watch } from 'vue';
|
|
||||||
import { HalftonePass } from 'three/examples/jsm/postprocessing/HalftonePass.js';
|
|
||||||
import EffectPass from './EffectPass.js';
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
extends: EffectPass,
|
|
||||||
props: {
|
|
||||||
shape: { type: Number, default: 1 },
|
|
||||||
radius: { type: Number, default: 4 },
|
|
||||||
rotateR: { type: Number, default: Math.PI / 12 * 1 },
|
|
||||||
rotateG: { type: Number, default: Math.PI / 12 * 2 },
|
|
||||||
rotateB: { type: Number, default: Math.PI / 12 * 3 },
|
|
||||||
scatter: { type: Number, default: 0 },
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
const pass = new HalftonePass(this.three.size.width, this.three.size.height, {});
|
|
||||||
|
|
||||||
['shape', 'radius', 'rotateR', 'rotateG', 'rotateB', 'scatter'].forEach(p => {
|
|
||||||
pass.uniforms[p].value = this[p];
|
|
||||||
watch(() => this[p], () => {
|
|
||||||
pass.uniforms[p].value = this[p];
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
this.completePass(pass);
|
|
||||||
},
|
|
||||||
__hmrId: 'HalftonePass',
|
|
||||||
});
|
|
32
src/effects/HalftonePass.ts
Normal file
32
src/effects/HalftonePass.ts
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
import { defineComponent, watch } from 'vue'
|
||||||
|
import { HalftonePass } from 'three/examples/jsm/postprocessing/HalftonePass.js'
|
||||||
|
import EffectPass from './EffectPass'
|
||||||
|
|
||||||
|
const props = {
|
||||||
|
shape: { type: Number, default: 1 },
|
||||||
|
radius: { type: Number, default: 4 },
|
||||||
|
rotateR: { type: Number, default: Math.PI / 12 * 1 },
|
||||||
|
rotateG: { type: Number, default: Math.PI / 12 * 2 },
|
||||||
|
rotateB: { type: Number, default: Math.PI / 12 * 3 },
|
||||||
|
scatter: { type: Number, default: 0 },
|
||||||
|
}
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
extends: EffectPass,
|
||||||
|
props,
|
||||||
|
created() {
|
||||||
|
if (!this.renderer) return
|
||||||
|
|
||||||
|
const pass = new HalftonePass(this.renderer.size.width, this.renderer.size.height, {})
|
||||||
|
|
||||||
|
Object.keys(props).forEach(p => {
|
||||||
|
// @ts-ignore
|
||||||
|
pass.uniforms[p].value = this[p]
|
||||||
|
// @ts-ignore
|
||||||
|
watch(() => this[p], (value) => { pass.uniforms[p].value = value })
|
||||||
|
})
|
||||||
|
|
||||||
|
this.initEffectPass(pass)
|
||||||
|
},
|
||||||
|
__hmrId: 'HalftonePass',
|
||||||
|
})
|
@ -1,18 +0,0 @@
|
|||||||
import { defineComponent } from 'vue';
|
|
||||||
import { RenderPass } from 'three/examples/jsm/postprocessing/RenderPass.js';
|
|
||||||
import EffectPass from './EffectPass.js';
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
extends: EffectPass,
|
|
||||||
mounted() {
|
|
||||||
if (!this.three.scene) {
|
|
||||||
console.error('Missing Scene');
|
|
||||||
}
|
|
||||||
if (!this.three.camera) {
|
|
||||||
console.error('Missing Camera');
|
|
||||||
}
|
|
||||||
const pass = new RenderPass(this.three.scene, this.three.camera);
|
|
||||||
this.completePass(pass);
|
|
||||||
},
|
|
||||||
__hmrId: 'RenderPass',
|
|
||||||
});
|
|
22
src/effects/RenderPass.ts
Normal file
22
src/effects/RenderPass.ts
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import { defineComponent } from 'vue'
|
||||||
|
import { RenderPass } from 'three/examples/jsm/postprocessing/RenderPass.js'
|
||||||
|
import EffectPass from './EffectPass'
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
extends: EffectPass,
|
||||||
|
created() {
|
||||||
|
if (!this.renderer) return
|
||||||
|
|
||||||
|
if (!this.renderer.scene) {
|
||||||
|
console.error('Missing Scene')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (!this.renderer.camera) {
|
||||||
|
console.error('Missing Camera')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const pass = new RenderPass(this.renderer.scene, this.renderer.camera)
|
||||||
|
this.initEffectPass(pass)
|
||||||
|
},
|
||||||
|
__hmrId: 'RenderPass',
|
||||||
|
})
|
@ -1,13 +0,0 @@
|
|||||||
import { defineComponent } from 'vue';
|
|
||||||
import { SMAAPass } from 'three/examples/jsm/postprocessing/SMAAPass.js';
|
|
||||||
import EffectPass from './EffectPass.js';
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
extends: EffectPass,
|
|
||||||
mounted() {
|
|
||||||
// three size is not set yet, but this pass will be resized by effect composer
|
|
||||||
const pass = new SMAAPass(this.three.size.width, this.three.size.height);
|
|
||||||
this.completePass(pass);
|
|
||||||
},
|
|
||||||
__hmrId: 'SMAAPass',
|
|
||||||
});
|
|
14
src/effects/SMAAPass.ts
Normal file
14
src/effects/SMAAPass.ts
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import { defineComponent } from 'vue'
|
||||||
|
import { SMAAPass } from 'three/examples/jsm/postprocessing/SMAAPass.js'
|
||||||
|
import EffectPass from './EffectPass'
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
extends: EffectPass,
|
||||||
|
created() {
|
||||||
|
if (!this.renderer) return
|
||||||
|
|
||||||
|
const pass = new SMAAPass(this.renderer.size.width, this.renderer.size.height)
|
||||||
|
this.initEffectPass(pass)
|
||||||
|
},
|
||||||
|
__hmrId: 'SMAAPass',
|
||||||
|
})
|
@ -1,30 +0,0 @@
|
|||||||
import { defineComponent } from 'vue';
|
|
||||||
import { SSAOPass } from 'three/examples/jsm/postprocessing/SSAOPass.js';
|
|
||||||
import EffectPass from './EffectPass.js';
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
extends: EffectPass,
|
|
||||||
props: {
|
|
||||||
scene: null,
|
|
||||||
camera: null,
|
|
||||||
options: {
|
|
||||||
type: Object,
|
|
||||||
default: () => ({}),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
const pass = new SSAOPass(
|
|
||||||
this.scene || this.three.scene,
|
|
||||||
this.camera || this.three.camera,
|
|
||||||
this.three.size.width,
|
|
||||||
this.three.size.height
|
|
||||||
);
|
|
||||||
|
|
||||||
for (const key of Object.keys(this.options)) {
|
|
||||||
pass[key] = this.options[key];
|
|
||||||
}
|
|
||||||
|
|
||||||
this.completePass(pass);
|
|
||||||
},
|
|
||||||
__hmrId: 'SSAOPass',
|
|
||||||
});
|
|
40
src/effects/SSAOPass.ts
Normal file
40
src/effects/SSAOPass.ts
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
import { defineComponent } from 'vue'
|
||||||
|
import { SSAOPass } from 'three/examples/jsm/postprocessing/SSAOPass.js'
|
||||||
|
import EffectPass from './EffectPass'
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
extends: EffectPass,
|
||||||
|
props: {
|
||||||
|
options: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
if (!this.renderer) return
|
||||||
|
|
||||||
|
if (!this.renderer.scene) {
|
||||||
|
console.error('Missing Scene')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (!this.renderer.camera) {
|
||||||
|
console.error('Missing Camera')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const pass = new SSAOPass(
|
||||||
|
this.renderer.scene,
|
||||||
|
this.renderer.camera,
|
||||||
|
this.renderer.size.width,
|
||||||
|
this.renderer.size.height
|
||||||
|
)
|
||||||
|
|
||||||
|
Object.keys(this.options).forEach(key => {
|
||||||
|
// @ts-ignore
|
||||||
|
pass[key] = this.options[key]
|
||||||
|
})
|
||||||
|
|
||||||
|
this.initEffectPass(pass)
|
||||||
|
},
|
||||||
|
__hmrId: 'SSAOPass',
|
||||||
|
})
|
@ -1,57 +0,0 @@
|
|||||||
import { defineComponent, watch } from 'vue';
|
|
||||||
import { Vector2 } from 'three';
|
|
||||||
import { ShaderPass } from 'three/examples/jsm/postprocessing/ShaderPass.js';
|
|
||||||
import EffectPass from './EffectPass.js';
|
|
||||||
import TiltShift from '../shaders/TiltShift.js';
|
|
||||||
import { bindProp } from '../tools';
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
extends: EffectPass,
|
|
||||||
props: {
|
|
||||||
blurRadius: { type: Number, default: 10 },
|
|
||||||
gradientRadius: { type: Number, default: 100 },
|
|
||||||
start: { type: Object, default: { x: 0, y: 100 } },
|
|
||||||
end: { type: Object, default: { x: 10, y: 100 } },
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
this.pass = new ShaderPass(TiltShift);
|
|
||||||
this.passes.push(this.pass);
|
|
||||||
|
|
||||||
this.pass1 = new ShaderPass(TiltShift);
|
|
||||||
this.passes.push(this.pass1);
|
|
||||||
|
|
||||||
const uniforms = this.uniforms = this.pass.uniforms;
|
|
||||||
const uniforms1 = this.uniforms1 = this.pass1.uniforms;
|
|
||||||
uniforms1.blurRadius = uniforms.blurRadius;
|
|
||||||
uniforms1.gradientRadius = uniforms.gradientRadius;
|
|
||||||
uniforms1.start = uniforms.start;
|
|
||||||
uniforms1.end = uniforms.end;
|
|
||||||
uniforms1.texSize = uniforms.texSize;
|
|
||||||
|
|
||||||
bindProp(this, 'blurRadius', uniforms.blurRadius, 'value');
|
|
||||||
bindProp(this, 'gradientRadius', uniforms.gradientRadius, 'value');
|
|
||||||
|
|
||||||
this.updateFocusLine();
|
|
||||||
['start', 'end'].forEach(p => {
|
|
||||||
watch(() => this[p], this.updateFocusLine, { deep: true });
|
|
||||||
});
|
|
||||||
|
|
||||||
this.pass.setSize = (width, height) => {
|
|
||||||
uniforms.texSize.value.set(width, height);
|
|
||||||
};
|
|
||||||
|
|
||||||
// emit ready event with two passes - do so manually in this file instead
|
|
||||||
// of calling `completePass` like in other effect types
|
|
||||||
this.$emit('ready', [this.pass, this.pass1]);
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
updateFocusLine() {
|
|
||||||
this.uniforms.start.value.copy(this.start);
|
|
||||||
this.uniforms.end.value.copy(this.end);
|
|
||||||
const dv = new Vector2().copy(this.end).sub(this.start).normalize();
|
|
||||||
this.uniforms.delta.value.copy(dv);
|
|
||||||
this.uniforms1.delta.value.set(-dv.y, dv.x);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
__hmrId: 'TiltShiftPass',
|
|
||||||
});
|
|
75
src/effects/TiltShiftPass.ts
Normal file
75
src/effects/TiltShiftPass.ts
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
import { defineComponent, PropType, watch } from 'vue'
|
||||||
|
import { Vector2 } from 'three'
|
||||||
|
import { ShaderPass } from 'three/examples/jsm/postprocessing/ShaderPass.js'
|
||||||
|
import EffectPass from './EffectPass'
|
||||||
|
import TiltShift from '../shaders/TiltShift'
|
||||||
|
import { Vector2PropInterface } from '../core/Object3D'
|
||||||
|
import { bindProp } from '../tools'
|
||||||
|
|
||||||
|
const props = {
|
||||||
|
blurRadius: { type: Number, default: 10 },
|
||||||
|
gradientRadius: { type: Number, default: 100 },
|
||||||
|
start: { type: Object as PropType<Vector2PropInterface>, default: () => ({ x: 0, y: 100 }) },
|
||||||
|
end: { type: Object as PropType<Vector2PropInterface>, default: () => ({ x: 10, y: 100 }) },
|
||||||
|
}
|
||||||
|
|
||||||
|
interface TiltShiftPassSetupInterface {
|
||||||
|
uniforms1: {[name: string]: { value: any }}
|
||||||
|
uniforms2: {[name: string]: { value: any }}
|
||||||
|
pass1?: ShaderPass
|
||||||
|
pass2?: ShaderPass
|
||||||
|
}
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
extends: EffectPass,
|
||||||
|
props,
|
||||||
|
setup(): TiltShiftPassSetupInterface {
|
||||||
|
return { uniforms1: {}, uniforms2: {} }
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
if (!this.composer) return
|
||||||
|
|
||||||
|
this.pass1 = new ShaderPass(TiltShift)
|
||||||
|
this.pass2 = new ShaderPass(TiltShift)
|
||||||
|
|
||||||
|
const uniforms1 = this.uniforms1 = this.pass1.uniforms
|
||||||
|
const uniforms2 = this.uniforms2 = this.pass2.uniforms
|
||||||
|
|
||||||
|
// shared uniforms
|
||||||
|
uniforms2.blurRadius = uniforms1.blurRadius
|
||||||
|
uniforms2.gradientRadius = uniforms1.gradientRadius
|
||||||
|
uniforms2.start = uniforms1.start
|
||||||
|
uniforms2.end = uniforms1.end
|
||||||
|
uniforms2.texSize = uniforms1.texSize
|
||||||
|
|
||||||
|
bindProp(this, 'blurRadius', uniforms1.blurRadius, 'value')
|
||||||
|
bindProp(this, 'gradientRadius', uniforms1.gradientRadius, 'value')
|
||||||
|
|
||||||
|
this.updateFocusLine();
|
||||||
|
|
||||||
|
['start', 'end'].forEach(p => {
|
||||||
|
// @ts-ignore
|
||||||
|
watch(() => this[p], this.updateFocusLine, { deep: true })
|
||||||
|
})
|
||||||
|
|
||||||
|
this.pass1.setSize = (width: number, height: number) => {
|
||||||
|
uniforms1.texSize.value.set(width, height)
|
||||||
|
}
|
||||||
|
|
||||||
|
this.initEffectPass(this.pass1)
|
||||||
|
this.composer.addPass(this.pass2)
|
||||||
|
},
|
||||||
|
unmounted() {
|
||||||
|
if (this.composer && this.pass2) this.composer.removePass(this.pass2)
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
updateFocusLine() {
|
||||||
|
this.uniforms1.start.value.copy(this.start)
|
||||||
|
this.uniforms1.end.value.copy(this.end)
|
||||||
|
const dv = new Vector2().copy(this.end as Vector2).sub(this.start as Vector2).normalize()
|
||||||
|
this.uniforms1.delta.value.copy(dv)
|
||||||
|
this.uniforms2.delta.value.set(-dv.y, dv.x)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
__hmrId: 'TiltShiftPass',
|
||||||
|
})
|
@ -1,24 +0,0 @@
|
|||||||
import { defineComponent } from 'vue';
|
|
||||||
import { Vector2 } from 'three';
|
|
||||||
import { UnrealBloomPass } from 'three/examples/jsm/postprocessing/UnrealBloomPass.js';
|
|
||||||
import EffectPass from './EffectPass.js';
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
extends: EffectPass,
|
|
||||||
props: {
|
|
||||||
strength: { type: Number, default: 1.5 },
|
|
||||||
radius: { type: Number, default: 0 },
|
|
||||||
threshold: { type: Number, default: 0 },
|
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
strength() { this.pass.strength = this.strength; },
|
|
||||||
radius() { this.pass.radius = this.radius; },
|
|
||||||
threshold() { this.pass.threshold = this.threshold; },
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
const size = new Vector2(this.three.size.width, this.three.size.height);
|
|
||||||
const pass = new UnrealBloomPass(size, this.strength, this.radius, this.threshold);
|
|
||||||
this.completePass(pass);
|
|
||||||
},
|
|
||||||
__hmrId: 'UnrealBloomPass',
|
|
||||||
});
|
|
29
src/effects/UnrealBloomPass.ts
Normal file
29
src/effects/UnrealBloomPass.ts
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
import { defineComponent, watch } from 'vue'
|
||||||
|
import { Vector2 } from 'three'
|
||||||
|
import { UnrealBloomPass } from 'three/examples/jsm/postprocessing/UnrealBloomPass.js'
|
||||||
|
import EffectPass from './EffectPass'
|
||||||
|
|
||||||
|
const props = {
|
||||||
|
strength: { type: Number, default: 1.5 },
|
||||||
|
radius: { type: Number, default: 0 },
|
||||||
|
threshold: { type: Number, default: 0 },
|
||||||
|
}
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
extends: EffectPass,
|
||||||
|
props,
|
||||||
|
created() {
|
||||||
|
if (!this.renderer) return
|
||||||
|
|
||||||
|
const size = new Vector2(this.renderer.size.width, this.renderer.size.height)
|
||||||
|
const pass = new UnrealBloomPass(size, this.strength, this.radius, this.threshold)
|
||||||
|
|
||||||
|
Object.keys(props).forEach(p => {
|
||||||
|
// @ts-ignore
|
||||||
|
watch(() => this[p], (value) => { pass.uniforms[p].value = value })
|
||||||
|
})
|
||||||
|
|
||||||
|
this.initEffectPass(pass)
|
||||||
|
},
|
||||||
|
__hmrId: 'UnrealBloomPass',
|
||||||
|
})
|
@ -1,23 +0,0 @@
|
|||||||
import { defineComponent } from 'vue';
|
|
||||||
import { ShaderPass } from 'three/examples/jsm/postprocessing/ShaderPass.js';
|
|
||||||
import EffectPass from './EffectPass.js';
|
|
||||||
import ZoomBlur from '../shaders/ZoomBlur.js';
|
|
||||||
import { bindProp } from '../tools';
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
extends: EffectPass,
|
|
||||||
props: {
|
|
||||||
center: { type: Object, default: { x: 0.5, y: 0.5 } },
|
|
||||||
strength: { type: Number, default: 0.5 },
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
const pass = new ShaderPass(ZoomBlur);
|
|
||||||
|
|
||||||
const uniforms = this.uniforms = pass.uniforms;
|
|
||||||
bindProp(this, 'center', uniforms.center, 'value');
|
|
||||||
bindProp(this, 'strength', uniforms.strength, 'value');
|
|
||||||
|
|
||||||
this.completePass(pass);
|
|
||||||
},
|
|
||||||
__hmrId: 'ZoomBlurPass',
|
|
||||||
});
|
|
23
src/effects/ZoomBlurPass.ts
Normal file
23
src/effects/ZoomBlurPass.ts
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import { defineComponent, PropType } from 'vue'
|
||||||
|
import { ShaderPass } from 'three/examples/jsm/postprocessing/ShaderPass.js'
|
||||||
|
import EffectPass from './EffectPass'
|
||||||
|
import ZoomBlur from '../shaders/ZoomBlur'
|
||||||
|
import { Vector2PropInterface } from '../core/Object3D'
|
||||||
|
import { bindProp } from '../tools'
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
extends: EffectPass,
|
||||||
|
props: {
|
||||||
|
center: { type: Object as PropType<Vector2PropInterface>, default: () => ({ x: 0.5, y: 0.5 }) },
|
||||||
|
strength: { type: Number, default: 0.5 },
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
const pass = new ShaderPass(ZoomBlur)
|
||||||
|
|
||||||
|
bindProp(this, 'center', pass.uniforms.center, 'value')
|
||||||
|
bindProp(this, 'strength', pass.uniforms.strength, 'value')
|
||||||
|
|
||||||
|
this.initEffectPass(pass)
|
||||||
|
},
|
||||||
|
__hmrId: 'ZoomBlurPass',
|
||||||
|
})
|
@ -1,12 +0,0 @@
|
|||||||
export { default as EffectComposer } from './EffectComposer.js';
|
|
||||||
export { default as RenderPass } from './RenderPass.js';
|
|
||||||
|
|
||||||
export { default as BokehPass } from './BokehPass.js';
|
|
||||||
export { default as FilmPass } from './FilmPass.js';
|
|
||||||
export { default as FXAAPass } from './FXAAPass.js';
|
|
||||||
export { default as HalftonePass } from './HalftonePass.js';
|
|
||||||
export { default as SMAAPass } from './SMAAPass.js';
|
|
||||||
export { default as SSAOPass } from './SSAOPass.js';
|
|
||||||
export { default as TiltShiftPass } from './TiltShiftPass.js';
|
|
||||||
export { default as UnrealBloomPass } from './UnrealBloomPass.js';
|
|
||||||
export { default as ZoomBlurPass } from './ZoomBlurPass.js';
|
|
12
src/effects/index.ts
Normal file
12
src/effects/index.ts
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
export { default as EffectComposer, ComposerInjectionKey } from './EffectComposer'
|
||||||
|
export { default as RenderPass } from './RenderPass'
|
||||||
|
|
||||||
|
export { default as BokehPass } from './BokehPass'
|
||||||
|
export { default as FilmPass } from './FilmPass'
|
||||||
|
export { default as FXAAPass } from './FXAAPass'
|
||||||
|
export { default as HalftonePass } from './HalftonePass'
|
||||||
|
export { default as SMAAPass } from './SMAAPass'
|
||||||
|
export { default as SSAOPass } from './SSAOPass'
|
||||||
|
export { default as TiltShiftPass } from './TiltShiftPass'
|
||||||
|
export { default as UnrealBloomPass } from './UnrealBloomPass'
|
||||||
|
export { default as ZoomBlurPass } from './ZoomBlurPass'
|
@ -1,2 +0,0 @@
|
|||||||
export * from './index.js';
|
|
||||||
export * from './plugin.js';
|
|
4
src/export.ts
Normal file
4
src/export.ts
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
export * from './index'
|
||||||
|
export * from './plugin'
|
||||||
|
|
||||||
|
export { default as useTextures } from './use/useTextures'
|
@ -1,5 +1,5 @@
|
|||||||
import { geometryComponent } from './Geometry.js';
|
import { geometryComponent } from './Geometry'
|
||||||
import { BoxGeometry } from 'three';
|
import { BoxGeometry } from 'three'
|
||||||
|
|
||||||
export const props = {
|
export const props = {
|
||||||
size: Number,
|
size: Number,
|
||||||
@ -9,14 +9,14 @@ export const props = {
|
|||||||
widthSegments: { type: Number, default: 1 },
|
widthSegments: { type: Number, default: 1 },
|
||||||
heightSegments: { type: Number, default: 1 },
|
heightSegments: { type: Number, default: 1 },
|
||||||
depthSegments: { type: Number, default: 1 },
|
depthSegments: { type: Number, default: 1 },
|
||||||
};
|
} as const
|
||||||
|
|
||||||
export function createGeometry(comp) {
|
export function createGeometry(comp: any): BoxGeometry {
|
||||||
if (comp.size) {
|
if (comp.size) {
|
||||||
return new BoxGeometry(comp.size, comp.size, comp.size, comp.widthSegments, comp.heightSegments, comp.depthSegments);
|
return new BoxGeometry(comp.size, comp.size, comp.size, comp.widthSegments, comp.heightSegments, comp.depthSegments)
|
||||||
} else {
|
} else {
|
||||||
return new BoxGeometry(comp.width, comp.height, comp.depth, comp.widthSegments, comp.heightSegments, comp.depthSegments);
|
return new BoxGeometry(comp.width, comp.height, comp.depth, comp.widthSegments, comp.heightSegments, comp.depthSegments)
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
export default geometryComponent('BoxGeometry', props, createGeometry);
|
export default geometryComponent('BoxGeometry', props, createGeometry)
|
@ -1,15 +1,15 @@
|
|||||||
import { geometryComponent } from './Geometry.js';
|
import { geometryComponent } from './Geometry'
|
||||||
import { CircleGeometry } from 'three';
|
import { CircleGeometry } from 'three'
|
||||||
|
|
||||||
export const props = {
|
export const props = {
|
||||||
radius: { type: Number, default: 1 },
|
radius: { type: Number, default: 1 },
|
||||||
segments: { type: Number, default: 8 },
|
segments: { type: Number, default: 8 },
|
||||||
thetaStart: { type: Number, default: 0 },
|
thetaStart: { type: Number, default: 0 },
|
||||||
thetaLength: { type: Number, default: Math.PI * 2 },
|
thetaLength: { type: Number, default: Math.PI * 2 },
|
||||||
};
|
} as const
|
||||||
|
|
||||||
export function createGeometry(comp) {
|
export function createGeometry(comp: any): CircleGeometry {
|
||||||
return new CircleGeometry(comp.radius, comp.segments, comp.thetaStart, comp.thetaLength);
|
return new CircleGeometry(comp.radius, comp.segments, comp.thetaStart, comp.thetaLength)
|
||||||
};
|
}
|
||||||
|
|
||||||
export default geometryComponent('CircleGeometry', props, createGeometry);
|
export default geometryComponent('CircleGeometry', props, createGeometry)
|
@ -1,5 +1,5 @@
|
|||||||
import { geometryComponent } from './Geometry.js';
|
import { geometryComponent } from './Geometry'
|
||||||
import { ConeGeometry } from 'three';
|
import { ConeGeometry } from 'three'
|
||||||
|
|
||||||
export const props = {
|
export const props = {
|
||||||
radius: { type: Number, default: 1 },
|
radius: { type: Number, default: 1 },
|
||||||
@ -9,10 +9,10 @@ export const props = {
|
|||||||
openEnded: { type: Boolean, default: false },
|
openEnded: { type: Boolean, default: false },
|
||||||
thetaStart: { type: Number, default: 0 },
|
thetaStart: { type: Number, default: 0 },
|
||||||
thetaLength: { type: Number, default: Math.PI * 2 },
|
thetaLength: { type: Number, default: Math.PI * 2 },
|
||||||
};
|
} as const
|
||||||
|
|
||||||
export function createGeometry(comp) {
|
export function createGeometry(comp: any): ConeGeometry {
|
||||||
return new ConeGeometry(comp.radius, comp.height, comp.radialSegments, comp.heightSegments, comp.openEnded, comp.thetaStart, comp.thetaLength);
|
return new ConeGeometry(comp.radius, comp.height, comp.radialSegments, comp.heightSegments, comp.openEnded, comp.thetaStart, comp.thetaLength)
|
||||||
};
|
}
|
||||||
|
|
||||||
export default geometryComponent('ConeGeometry', props, createGeometry);
|
export default geometryComponent('ConeGeometry', props, createGeometry)
|
@ -1,5 +1,5 @@
|
|||||||
import { geometryComponent } from './Geometry.js';
|
import { geometryComponent } from './Geometry'
|
||||||
import { CylinderGeometry } from 'three';
|
import { CylinderGeometry } from 'three'
|
||||||
|
|
||||||
export const props = {
|
export const props = {
|
||||||
radiusTop: { type: Number, default: 1 },
|
radiusTop: { type: Number, default: 1 },
|
||||||
@ -10,10 +10,10 @@ export const props = {
|
|||||||
openEnded: { type: Boolean, default: false },
|
openEnded: { type: Boolean, default: false },
|
||||||
thetaStart: { type: Number, default: 0 },
|
thetaStart: { type: Number, default: 0 },
|
||||||
thetaLength: { type: Number, default: Math.PI * 2 },
|
thetaLength: { type: Number, default: Math.PI * 2 },
|
||||||
};
|
} as const
|
||||||
|
|
||||||
export function createGeometry(comp) {
|
export function createGeometry(comp: any): CylinderGeometry {
|
||||||
return new CylinderGeometry(comp.radiusTop, comp.radiusBottom, comp.height, comp.radialSegments, comp.heightSegments, comp.openEnded, comp.thetaStart, comp.thetaLength);
|
return new CylinderGeometry(comp.radiusTop, comp.radiusBottom, comp.height, comp.radialSegments, comp.heightSegments, comp.openEnded, comp.thetaStart, comp.thetaLength)
|
||||||
};
|
}
|
||||||
|
|
||||||
export default geometryComponent('CylinderGeometry', props, createGeometry);
|
export default geometryComponent('CylinderGeometry', props, createGeometry)
|
@ -1,13 +0,0 @@
|
|||||||
import { geometryComponent } from './Geometry.js';
|
|
||||||
import { DodecahedronGeometry } from 'three';
|
|
||||||
|
|
||||||
export const props = {
|
|
||||||
radius: { type: Number, default: 1 },
|
|
||||||
detail: { type: Number, default: 0 },
|
|
||||||
};
|
|
||||||
|
|
||||||
export function createGeometry(comp) {
|
|
||||||
return new DodecahedronGeometry(comp.radius, comp.detail);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default geometryComponent('DodecahedronGeometry', props, createGeometry);
|
|
13
src/geometries/DodecahedronGeometry.ts
Normal file
13
src/geometries/DodecahedronGeometry.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import { geometryComponent } from './Geometry'
|
||||||
|
import { DodecahedronGeometry } from 'three'
|
||||||
|
|
||||||
|
export const props = {
|
||||||
|
radius: { type: Number, default: 1 },
|
||||||
|
detail: { type: Number, default: 0 },
|
||||||
|
} as const
|
||||||
|
|
||||||
|
export function createGeometry(comp: any): DodecahedronGeometry {
|
||||||
|
return new DodecahedronGeometry(comp.radius, comp.detail)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default geometryComponent('DodecahedronGeometry', props, createGeometry)
|
@ -1,64 +0,0 @@
|
|||||||
import { defineComponent, watch } from 'vue';
|
|
||||||
|
|
||||||
const Geometry = defineComponent({
|
|
||||||
inject: ['mesh'],
|
|
||||||
props: {
|
|
||||||
rotateX: Number,
|
|
||||||
rotateY: Number,
|
|
||||||
rotateZ: Number,
|
|
||||||
},
|
|
||||||
created() {
|
|
||||||
if (!this.mesh) {
|
|
||||||
console.error('Missing parent Mesh');
|
|
||||||
}
|
|
||||||
|
|
||||||
this.watchProps = [];
|
|
||||||
Object.entries(this.$props).forEach(e => this.watchProps.push(e[0]));
|
|
||||||
|
|
||||||
this.createGeometry();
|
|
||||||
this.rotateGeometry();
|
|
||||||
this.mesh.setGeometry(this.geometry);
|
|
||||||
|
|
||||||
this.addWatchers();
|
|
||||||
},
|
|
||||||
unmounted() {
|
|
||||||
this.geometry.dispose();
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
addWatchers() {
|
|
||||||
this.watchProps.forEach(prop => {
|
|
||||||
watch(() => this[prop], () => {
|
|
||||||
this.refreshGeometry();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
|
||||||
rotateGeometry() {
|
|
||||||
if (this.rotateX) this.geometry.rotateX(this.rotateX);
|
|
||||||
if (this.rotateY) this.geometry.rotateY(this.rotateY);
|
|
||||||
if (this.rotateZ) this.geometry.rotateZ(this.rotateZ);
|
|
||||||
},
|
|
||||||
refreshGeometry() {
|
|
||||||
const oldGeo = this.geometry;
|
|
||||||
this.createGeometry();
|
|
||||||
this.rotateGeometry();
|
|
||||||
this.mesh.setGeometry(this.geometry);
|
|
||||||
oldGeo.dispose();
|
|
||||||
},
|
|
||||||
},
|
|
||||||
render() { return []; },
|
|
||||||
});
|
|
||||||
|
|
||||||
export default Geometry;
|
|
||||||
|
|
||||||
export function geometryComponent(name, props, createGeometry) {
|
|
||||||
return defineComponent({
|
|
||||||
name,
|
|
||||||
extends: Geometry,
|
|
||||||
props,
|
|
||||||
methods: {
|
|
||||||
createGeometry() {
|
|
||||||
this.geometry = createGeometry(this);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
|
85
src/geometries/Geometry.ts
Normal file
85
src/geometries/Geometry.ts
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
import { ComponentPropsOptions, defineComponent, watch } from 'vue'
|
||||||
|
import { BufferGeometry } from 'three'
|
||||||
|
import { MeshInjectionKey, MeshInterface } from '../meshes/Mesh'
|
||||||
|
|
||||||
|
export interface GeometrySetupInterface {
|
||||||
|
mesh?: MeshInterface
|
||||||
|
geometry?: BufferGeometry
|
||||||
|
watchProps?: string[]
|
||||||
|
}
|
||||||
|
|
||||||
|
// function defaultSetup(): GeometryInterface {
|
||||||
|
// const mesh = inject('mesh') as MeshInterface
|
||||||
|
// const watchProps: string[] = []
|
||||||
|
// return { mesh, watchProps }
|
||||||
|
// }
|
||||||
|
|
||||||
|
const Geometry = defineComponent({
|
||||||
|
props: {
|
||||||
|
rotateX: Number,
|
||||||
|
rotateY: Number,
|
||||||
|
rotateZ: Number,
|
||||||
|
},
|
||||||
|
// inject for sub components
|
||||||
|
inject: {
|
||||||
|
mesh: MeshInjectionKey as symbol,
|
||||||
|
},
|
||||||
|
setup(): GeometrySetupInterface {
|
||||||
|
return {}
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
if (!this.mesh) {
|
||||||
|
console.error('Missing parent Mesh')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this.createGeometry()
|
||||||
|
this.rotateGeometry()
|
||||||
|
if (this.geometry) this.mesh.setGeometry(this.geometry)
|
||||||
|
|
||||||
|
Object.keys(this.$props).forEach(prop => {
|
||||||
|
// @ts-ignore
|
||||||
|
watch(() => this[prop], this.refreshGeometry)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
unmounted() {
|
||||||
|
this.geometry?.dispose()
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
createGeometry() {},
|
||||||
|
rotateGeometry() {
|
||||||
|
if (!this.geometry) return
|
||||||
|
if (this.rotateX) this.geometry.rotateX(this.rotateX)
|
||||||
|
if (this.rotateY) this.geometry.rotateY(this.rotateY)
|
||||||
|
if (this.rotateZ) this.geometry.rotateZ(this.rotateZ)
|
||||||
|
},
|
||||||
|
refreshGeometry() {
|
||||||
|
const oldGeo = this.geometry
|
||||||
|
this.createGeometry()
|
||||||
|
this.rotateGeometry()
|
||||||
|
if (this.geometry && this.mesh) this.mesh.setGeometry(this.geometry)
|
||||||
|
oldGeo?.dispose()
|
||||||
|
},
|
||||||
|
},
|
||||||
|
render() { return [] },
|
||||||
|
})
|
||||||
|
|
||||||
|
export default Geometry
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
||||||
|
export function geometryComponent<P extends Readonly<ComponentPropsOptions>>(
|
||||||
|
name: string,
|
||||||
|
props: P,
|
||||||
|
createGeometry: {(c: any): BufferGeometry}
|
||||||
|
) {
|
||||||
|
return defineComponent({
|
||||||
|
name,
|
||||||
|
extends: Geometry,
|
||||||
|
props,
|
||||||
|
methods: {
|
||||||
|
createGeometry() {
|
||||||
|
this.geometry = createGeometry(this)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
@ -1,13 +0,0 @@
|
|||||||
import { geometryComponent } from './Geometry.js';
|
|
||||||
import { IcosahedronGeometry } from 'three';
|
|
||||||
|
|
||||||
export const props = {
|
|
||||||
radius: { type: Number, default: 1 },
|
|
||||||
detail: { type: Number, default: 0 },
|
|
||||||
};
|
|
||||||
|
|
||||||
export function createGeometry(comp) {
|
|
||||||
return new IcosahedronGeometry(comp.radius, comp.detail);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default geometryComponent('IcosahedronGeometry', props, createGeometry);
|
|
13
src/geometries/IcosahedronGeometry.ts
Normal file
13
src/geometries/IcosahedronGeometry.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import { geometryComponent } from './Geometry'
|
||||||
|
import { IcosahedronGeometry } from 'three'
|
||||||
|
|
||||||
|
export const props = {
|
||||||
|
radius: { type: Number, default: 1 },
|
||||||
|
detail: { type: Number, default: 0 },
|
||||||
|
} as const
|
||||||
|
|
||||||
|
export function createGeometry(comp: any): IcosahedronGeometry {
|
||||||
|
return new IcosahedronGeometry(comp.radius, comp.detail)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default geometryComponent('IcosahedronGeometry', props, createGeometry)
|
@ -1,15 +1,15 @@
|
|||||||
import { geometryComponent } from './Geometry.js';
|
import { geometryComponent } from './Geometry'
|
||||||
import { LatheGeometry } from 'three';
|
import { LatheGeometry } from 'three'
|
||||||
|
|
||||||
export const props = {
|
export const props = {
|
||||||
points: Array,
|
points: Array,
|
||||||
segments: { type: Number, default: 12 },
|
segments: { type: Number, default: 12 },
|
||||||
phiStart: { type: Number, default: 0 },
|
phiStart: { type: Number, default: 0 },
|
||||||
phiLength: { type: Number, default: Math.PI * 2 },
|
phiLength: { type: Number, default: Math.PI * 2 },
|
||||||
};
|
} as const
|
||||||
|
|
||||||
export function createGeometry(comp) {
|
export function createGeometry(comp: any): LatheGeometry {
|
||||||
return new LatheGeometry(comp.points, comp.segments, comp.phiStart, comp.phiLength);
|
return new LatheGeometry(comp.points, comp.segments, comp.phiStart, comp.phiLength)
|
||||||
};
|
}
|
||||||
|
|
||||||
export default geometryComponent('LatheGeometry', props, createGeometry);
|
export default geometryComponent('LatheGeometry', props, createGeometry)
|
@ -1,13 +0,0 @@
|
|||||||
import { geometryComponent } from './Geometry.js';
|
|
||||||
import { OctahedronGeometry } from 'three';
|
|
||||||
|
|
||||||
export const props = {
|
|
||||||
radius: { type: Number, default: 1 },
|
|
||||||
detail: { type: Number, default: 0 },
|
|
||||||
};
|
|
||||||
|
|
||||||
export function createGeometry(comp) {
|
|
||||||
return new OctahedronGeometry(comp.radius, comp.detail);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default geometryComponent('OctahedronGeometry', props, createGeometry);
|
|
13
src/geometries/OctahedronGeometry.ts
Normal file
13
src/geometries/OctahedronGeometry.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import { geometryComponent } from './Geometry'
|
||||||
|
import { OctahedronGeometry } from 'three'
|
||||||
|
|
||||||
|
export const props = {
|
||||||
|
radius: { type: Number, default: 1 },
|
||||||
|
detail: { type: Number, default: 0 },
|
||||||
|
} as const
|
||||||
|
|
||||||
|
export function createGeometry(comp: any): OctahedronGeometry {
|
||||||
|
return new OctahedronGeometry(comp.radius, comp.detail)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default geometryComponent('OctahedronGeometry', props, createGeometry)
|
@ -1,15 +1,15 @@
|
|||||||
import { geometryComponent } from './Geometry.js';
|
import { geometryComponent } from './Geometry'
|
||||||
import { PlaneGeometry } from 'three';
|
import { PlaneGeometry } from 'three'
|
||||||
|
|
||||||
export const props = {
|
export const props = {
|
||||||
width: { type: Number, default: 1 },
|
width: { type: Number, default: 1 },
|
||||||
height: { type: Number, default: 1 },
|
height: { type: Number, default: 1 },
|
||||||
widthSegments: { type: Number, default: 1 },
|
widthSegments: { type: Number, default: 1 },
|
||||||
heightSegments: { type: Number, default: 1 },
|
heightSegments: { type: Number, default: 1 },
|
||||||
};
|
} as const
|
||||||
|
|
||||||
export function createGeometry(comp) {
|
export function createGeometry(comp: any): PlaneGeometry {
|
||||||
return new PlaneGeometry(comp.width, comp.height, comp.widthSegments, comp.heightSegments);
|
return new PlaneGeometry(comp.width, comp.height, comp.widthSegments, comp.heightSegments)
|
||||||
};
|
}
|
||||||
|
|
||||||
export default geometryComponent('PlaneGeometry', props, createGeometry);
|
export default geometryComponent('PlaneGeometry', props, createGeometry)
|
@ -1,15 +1,15 @@
|
|||||||
import { geometryComponent } from './Geometry.js';
|
import { geometryComponent } from './Geometry'
|
||||||
import { PolyhedronGeometry } from 'three';
|
import { PolyhedronGeometry } from 'three'
|
||||||
|
|
||||||
export const props = {
|
export const props = {
|
||||||
vertices: Array,
|
vertices: Array,
|
||||||
indices: Array,
|
indices: Array,
|
||||||
radius: { type: Number, default: 1 },
|
radius: { type: Number, default: 1 },
|
||||||
detail: { type: Number, default: 0 },
|
detail: { type: Number, default: 0 },
|
||||||
};
|
} as const
|
||||||
|
|
||||||
export function createGeometry(comp) {
|
export function createGeometry(comp: any): PolyhedronGeometry {
|
||||||
return new PolyhedronGeometry(comp.vertices, comp.indices, comp.radius, comp.detail);
|
return new PolyhedronGeometry(comp.vertices, comp.indices, comp.radius, comp.detail)
|
||||||
};
|
}
|
||||||
|
|
||||||
export default geometryComponent('PolyhedronGeometry', props, createGeometry);
|
export default geometryComponent('PolyhedronGeometry', props, createGeometry)
|
@ -1,5 +1,5 @@
|
|||||||
import { geometryComponent } from './Geometry.js';
|
import { geometryComponent } from './Geometry'
|
||||||
import { RingGeometry } from 'three';
|
import { RingGeometry } from 'three'
|
||||||
|
|
||||||
export const props = {
|
export const props = {
|
||||||
innerRadius: { type: Number, default: 0.5 },
|
innerRadius: { type: Number, default: 0.5 },
|
||||||
@ -8,10 +8,10 @@ export const props = {
|
|||||||
phiSegments: { type: Number, default: 1 },
|
phiSegments: { type: Number, default: 1 },
|
||||||
thetaStart: { type: Number, default: 0 },
|
thetaStart: { type: Number, default: 0 },
|
||||||
thetaLength: { type: Number, default: Math.PI * 2 },
|
thetaLength: { type: Number, default: Math.PI * 2 },
|
||||||
};
|
} as const
|
||||||
|
|
||||||
export function createGeometry(comp) {
|
export function createGeometry(comp: any): RingGeometry {
|
||||||
return new RingGeometry(comp.innerRadius, comp.outerRadius, comp.thetaSegments, comp.phiSegments, comp.thetaStart, comp.thetaLength);
|
return new RingGeometry(comp.innerRadius, comp.outerRadius, comp.thetaSegments, comp.phiSegments, comp.thetaStart, comp.thetaLength)
|
||||||
};
|
}
|
||||||
|
|
||||||
export default geometryComponent('RingGeometry', props, createGeometry);
|
export default geometryComponent('RingGeometry', props, createGeometry)
|
@ -1,14 +1,14 @@
|
|||||||
import { geometryComponent } from './Geometry.js';
|
import { geometryComponent } from './Geometry'
|
||||||
import { SphereGeometry } from 'three';
|
import { SphereGeometry } from 'three'
|
||||||
|
|
||||||
export const props = {
|
export const props = {
|
||||||
radius: { type: Number, default: 1 },
|
radius: { type: Number, default: 1 },
|
||||||
widthSegments: { type: Number, default: 12 },
|
widthSegments: { type: Number, default: 12 },
|
||||||
heightSegments: { type: Number, default: 12 },
|
heightSegments: { type: Number, default: 12 },
|
||||||
};
|
} as const
|
||||||
|
|
||||||
export function createGeometry(comp) {
|
export function createGeometry(comp: any): SphereGeometry {
|
||||||
return new SphereGeometry(comp.radius, comp.widthSegments, comp.heightSegments);
|
return new SphereGeometry(comp.radius, comp.widthSegments, comp.heightSegments)
|
||||||
};
|
}
|
||||||
|
|
||||||
export default geometryComponent('SphereGeometry', props, createGeometry);
|
export default geometryComponent('SphereGeometry', props, createGeometry)
|
@ -1,13 +0,0 @@
|
|||||||
import { geometryComponent } from './Geometry.js';
|
|
||||||
import { TetrahedronGeometry } from 'three';
|
|
||||||
|
|
||||||
export const props = {
|
|
||||||
radius: { type: Number, default: 1 },
|
|
||||||
detail: { type: Number, default: 0 },
|
|
||||||
};
|
|
||||||
|
|
||||||
export function createGeometry(comp) {
|
|
||||||
return new TetrahedronGeometry(comp.radius, comp.detail);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default geometryComponent('TetrahedronGeometry', props, createGeometry);
|
|
13
src/geometries/TetrahedronGeometry.ts
Normal file
13
src/geometries/TetrahedronGeometry.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import { geometryComponent } from './Geometry'
|
||||||
|
import { TetrahedronGeometry } from 'three'
|
||||||
|
|
||||||
|
export const props = {
|
||||||
|
radius: { type: Number, default: 1 },
|
||||||
|
detail: { type: Number, default: 0 },
|
||||||
|
} as const
|
||||||
|
|
||||||
|
export function createGeometry(comp: any): TetrahedronGeometry {
|
||||||
|
return new TetrahedronGeometry(comp.radius, comp.detail)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default geometryComponent('TetrahedronGeometry', props, createGeometry)
|
@ -1,5 +1,5 @@
|
|||||||
import { geometryComponent } from './Geometry.js';
|
import { geometryComponent } from './Geometry'
|
||||||
import { TorusGeometry } from 'three';
|
import { TorusGeometry } from 'three'
|
||||||
|
|
||||||
export const props = {
|
export const props = {
|
||||||
radius: { type: Number, default: 1 },
|
radius: { type: Number, default: 1 },
|
||||||
@ -7,10 +7,10 @@ export const props = {
|
|||||||
radialSegments: { type: Number, default: 8 },
|
radialSegments: { type: Number, default: 8 },
|
||||||
tubularSegments: { type: Number, default: 6 },
|
tubularSegments: { type: Number, default: 6 },
|
||||||
arc: { type: Number, default: Math.PI * 2 },
|
arc: { type: Number, default: Math.PI * 2 },
|
||||||
};
|
} as const
|
||||||
|
|
||||||
export function createGeometry(comp) {
|
export function createGeometry(comp: any): TorusGeometry {
|
||||||
return new TorusGeometry(comp.radius, comp.tube, comp.radialSegments, comp.tubularSegments, comp.arc);
|
return new TorusGeometry(comp.radius, comp.tube, comp.radialSegments, comp.tubularSegments, comp.arc)
|
||||||
};
|
}
|
||||||
|
|
||||||
export default geometryComponent('TorusGeometry', props, createGeometry);
|
export default geometryComponent('TorusGeometry', props, createGeometry)
|
@ -1,5 +1,5 @@
|
|||||||
import { geometryComponent } from './Geometry.js';
|
import { geometryComponent } from './Geometry'
|
||||||
import { TorusKnotGeometry } from 'three';
|
import { TorusKnotGeometry } from 'three'
|
||||||
|
|
||||||
export const props = {
|
export const props = {
|
||||||
radius: { type: Number, default: 1 },
|
radius: { type: Number, default: 1 },
|
||||||
@ -8,10 +8,10 @@ export const props = {
|
|||||||
radialSegments: { type: Number, default: 8 },
|
radialSegments: { type: Number, default: 8 },
|
||||||
p: { type: Number, default: 2 },
|
p: { type: Number, default: 2 },
|
||||||
q: { type: Number, default: 3 },
|
q: { type: Number, default: 3 },
|
||||||
};
|
} as const
|
||||||
|
|
||||||
export function createGeometry(comp) {
|
export function createGeometry(comp: any): TorusKnotGeometry {
|
||||||
return new TorusKnotGeometry(comp.radius, comp.tube, comp.tubularSegments, comp.radialSegments, comp.p, comp.q);
|
return new TorusKnotGeometry(comp.radius, comp.tube, comp.tubularSegments, comp.radialSegments, comp.p, comp.q)
|
||||||
};
|
}
|
||||||
|
|
||||||
export default geometryComponent('TorusKnotGeometry', props, createGeometry);
|
export default geometryComponent('TorusKnotGeometry', props, createGeometry)
|
@ -1,83 +0,0 @@
|
|||||||
import { defineComponent } from 'vue';
|
|
||||||
import { CatmullRomCurve3, Curve, TubeGeometry, Vector3 } from 'three';
|
|
||||||
import Geometry from './Geometry.js';
|
|
||||||
|
|
||||||
export const props = {
|
|
||||||
points: Array,
|
|
||||||
path: Curve,
|
|
||||||
tubularSegments: { type: Number, default: 64 },
|
|
||||||
radius: { type: Number, default: 1 },
|
|
||||||
radialSegments: { type: Number, default: 8 },
|
|
||||||
closed: { type: Boolean, default: false },
|
|
||||||
};
|
|
||||||
|
|
||||||
export function createGeometry(comp) {
|
|
||||||
let curve;
|
|
||||||
if (comp.points) {
|
|
||||||
curve = new CatmullRomCurve3(comp.points);
|
|
||||||
} else if (comp.path) {
|
|
||||||
curve = comp.path;
|
|
||||||
} else {
|
|
||||||
console.error('Missing path curve or points.');
|
|
||||||
}
|
|
||||||
return new TubeGeometry(curve, comp.tubularSegments, comp.radius, comp.radiusSegments, comp.closed);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
extends: Geometry,
|
|
||||||
props,
|
|
||||||
methods: {
|
|
||||||
createGeometry() {
|
|
||||||
this.geometry = createGeometry(this);
|
|
||||||
},
|
|
||||||
// update points (without using prop, faster)
|
|
||||||
updatePoints(points) {
|
|
||||||
updateTubeGeometryPoints(this.geometry, points);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
export function updateTubeGeometryPoints(tube, points) {
|
|
||||||
const curve = new CatmullRomCurve3(points);
|
|
||||||
const { radialSegments, radius, tubularSegments, closed } = tube.parameters;
|
|
||||||
const frames = curve.computeFrenetFrames(tubularSegments, closed);
|
|
||||||
tube.tangents = frames.tangents;
|
|
||||||
tube.normals = frames.normals;
|
|
||||||
tube.binormals = frames.binormals;
|
|
||||||
tube.parameters.path = curve;
|
|
||||||
|
|
||||||
const pArray = tube.attributes.position.array;
|
|
||||||
const nArray = tube.attributes.normal.array;
|
|
||||||
const normal = new Vector3();
|
|
||||||
let P;
|
|
||||||
|
|
||||||
for (let i = 0; i < tubularSegments; i++) {
|
|
||||||
updateSegment(i);
|
|
||||||
}
|
|
||||||
updateSegment(tubularSegments);
|
|
||||||
|
|
||||||
tube.attributes.position.needsUpdate = true;
|
|
||||||
tube.attributes.normal.needsUpdate = true;
|
|
||||||
|
|
||||||
function updateSegment(i) {
|
|
||||||
P = curve.getPointAt(i / tubularSegments, P);
|
|
||||||
const N = frames.normals[i];
|
|
||||||
const B = frames.binormals[i];
|
|
||||||
for (let j = 0; j <= radialSegments; j++) {
|
|
||||||
const v = j / radialSegments * Math.PI * 2;
|
|
||||||
const sin = Math.sin(v);
|
|
||||||
const cos = -Math.cos(v);
|
|
||||||
normal.x = (cos * N.x + sin * B.x);
|
|
||||||
normal.y = (cos * N.y + sin * B.y);
|
|
||||||
normal.z = (cos * N.z + sin * B.z);
|
|
||||||
normal.normalize();
|
|
||||||
const index = (i * (radialSegments + 1) + j) * 3;
|
|
||||||
nArray[index] = normal.x;
|
|
||||||
nArray[index + 1] = normal.y;
|
|
||||||
nArray[index + 2] = normal.z;
|
|
||||||
pArray[index] = P.x + radius * normal.x;
|
|
||||||
pArray[index + 1] = P.y + radius * normal.y;
|
|
||||||
pArray[index + 2] = P.z + radius * normal.z;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user