1
0
mirror of https://github.com/troisjs/trois.git synced 2024-11-24 04:12:02 +08:00
This commit is contained in:
Kevin Levron 2021-04-22 00:46:52 +02:00
parent 6f64c8b859
commit d15b90e4b2
17 changed files with 583 additions and 584 deletions

View File

@ -8,7 +8,6 @@ import {
ShaderMaterial, ShaderMaterial,
Uniform, Uniform,
Vector2, Vector2,
WebGLRenderer,
WebGLRenderTarget, WebGLRenderTarget,
} from 'three' } from 'three'

View File

@ -1,8 +1,8 @@
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'; import Object3D from '../../core/Object3D'
import { bindProps } from '../../tools'; 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 +16,40 @@ export default defineComponent({
roughness: { type: Number, default: 0.25 }, roughness: { type: Number, default: 0.25 },
}, },
mounted() { mounted() {
this.liquidEffect = new LiquidEffect(this.renderer.renderer); this.liquidEffect = new LiquidEffect(this.renderer.renderer)
this.renderer.onMounted(() => { this.renderer.onMounted(() => {
this.liquidEffect.renderer = this.renderer.renderer; this.liquidEffect.renderer = this.renderer.renderer
this.renderer.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.renderer.offBeforeRender(this.update); this.renderer.offBeforeRender(this.update)
}, },
methods: { methods: {
update() { update() {
this.liquidEffect.update(); this.liquidEffect.update()
}, },
}, },
}); })

View File

@ -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'; import Mesh from '../../meshes/Mesh'
import { bindProp } from '../../tools'; import { bindProp } from '../../tools'
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.renderer.onBeforeRender(this.updateCubeRT); if (this.autoUpdate) this.renderer.onBeforeRender(this.updateCubeRT)
else this.renderer.onMounted(this.updateCubeRT); else this.renderer.onMounted(this.updateCubeRT)
}, },
unmounted() { unmounted() {
this.renderer.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.renderer.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',
}); })

View File

@ -1,11 +1,11 @@
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'; import Mesh from '../../meshes/Mesh'
export default defineComponent({ export default defineComponent({
extends: Mesh, extends: Mesh,
@ -16,28 +16,28 @@ export default defineComponent({
autoUpdate: Boolean, autoUpdate: Boolean,
}, },
mounted() { mounted() {
this.initMirrorMesh(); this.initMirrorMesh()
if (this.autoUpdate) this.renderer.onBeforeRender(this.updateCubeRT); if (this.autoUpdate) this.renderer.onBeforeRender(this.updateCubeRT)
else this.renderer.onMounted(this.updateCubeRT); else this.renderer.onMounted(this.updateCubeRT)
}, },
unmounted() { unmounted() {
this.renderer.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.renderer.renderer, this.scene); this.cubeCamera.update(this.renderer.renderer, this.scene)
this.mesh.visible = true; this.mesh.visible = true
}, },
}, },
__hmrId: 'MirrorMesh', __hmrId: 'MirrorMesh',
}); })

View File

@ -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'; import Mesh from '../../meshes/Mesh'
import { bindProp } from '../../tools'; import { bindProp } from '../../tools'
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.renderer.onBeforeRender(this.updateCubeRT); if (this.autoUpdate) this.renderer.onBeforeRender(this.updateCubeRT)
else this.renderer.onMounted(this.updateCubeRT); else this.renderer.onMounted(this.updateCubeRT)
}, },
unmounted() { unmounted() {
this.renderer.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.renderer.renderer, this.scene); this.cubeCamera.update(this.renderer.renderer, this.scene)
this.mesh.visible = true; this.mesh.visible = true
}, },
}, },
__hmrId: 'RefractionMesh', __hmrId: 'RefractionMesh',
}); })

View File

@ -1,4 +1,4 @@
import Stats from 'stats.js'; import Stats from 'stats.js'
export default { export default {
props: { props: {
@ -7,40 +7,40 @@ export default {
emits: ['created'], emits: ['created'],
inject: ['renderer'], inject: ['renderer'],
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.renderer.onBeforeRender(this.begin); this.renderer.onBeforeRender(this.begin)
this.renderer.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.renderer.offBeforeRender(this.begin); this.renderer.offBeforeRender(this.begin)
this.renderer.offAfterRender(this.end); this.renderer.offAfterRender(this.end)
}, },
render() { render() {
return this.$slots.default ? this.$slots.default() : []; return this.$slots.default ? this.$slots.default() : []
}, },
}; }

View File

@ -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>

View File

@ -1,5 +1,4 @@
import { defineComponent, watch } from 'vue' import { defineComponent, watch } from 'vue'
import { DoubleSide, MeshBasicMaterial, PlaneGeometry } from 'three'
import Image from '../../meshes/Image' import Image from '../../meshes/Image'
import snoise2 from '../../glsl/snoise2.glsl.js' import snoise2 from '../../glsl/snoise2.glsl.js'

View File

@ -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'; import Plane from '../../meshes/Plane'
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.renderer.onBeforeRender(this.update); this.renderer.onBeforeRender(this.update)
}, },
unmounted() { unmounted() {
this.renderer.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.renderer.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',
}); })

View File

@ -1,6 +1,6 @@
import { defineComponent, watch } from 'vue'; import { defineComponent, watch } from 'vue'
import Sphere from '../../meshes/Sphere'; import Sphere from '../../meshes/Sphere'
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.renderer.onBeforeRender(this.updateTime); this.renderer.onBeforeRender(this.updateTime)
}, },
unmounted() { unmounted() {
this.renderer.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',
}); })

View File

@ -1,6 +1,6 @@
import { defineComponent, watch } from 'vue'; import { defineComponent, watch } from 'vue'
import Text from '../../meshes/Text'; import Text from '../../meshes/Text'
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.renderer.onBeforeRender(this.updateTime); this.renderer.onBeforeRender(this.updateTime)
}, },
unmounted() { unmounted() {
this.renderer.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',
}); })

View File

@ -1,5 +1,5 @@
import { defineComponent } from 'vue'; import { defineComponent } from 'vue'
import useCannon from './useCannon.js'; import useCannon from './useCannon.js'
// import { bindProp } from '../../tools'; // import { bindProp } from '../../tools';
export default defineComponent({ export default defineComponent({
@ -10,54 +10,54 @@ export default defineComponent({
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.renderer.onBeforeRender(this.step); this.renderer.onBeforeRender(this.step)
}, },
unmounted() { unmounted() {
this.renderer.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() : []
}, },
}); })

View File

@ -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
} }
} }

View File

@ -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()
} }
} }

View File

@ -7,52 +7,52 @@
</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'; import Camera from '../../core/PerspectiveCamera'
import Renderer from '../../core/Renderer'; import Renderer from '../../core/Renderer'
import Scene from '../../core/Scene'; import Scene from '../../core/Scene'
import { lerp } from '../../tools'; import { lerp } from '../../tools'
import AnimatedPlane from './AnimatedPlane.js'; import AnimatedPlane from './AnimatedPlane.js'
import useTextures from '../../use/useTextures'; 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.renderer.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 +63,105 @@ export default defineComponent({
duration: 2.5, duration: 2.5,
ease: Power4.easeOut, ease: Power4.easeOut,
} }
); )
const domElement = this.renderer.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.onResize(this.onResize); this.renderer.onResize(this.onResize)
}, },
initScene() { initScene() {
const renderer = this.renderer.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.renderer.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.renderer.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.renderer.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>

View File

@ -6,55 +6,55 @@
</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'; import OrthographicCamera from '../../core/OrthographicCamera'
import Renderer from '../../core/Renderer'; import Renderer from '../../core/Renderer'
import Scene from '../../core/Scene'; import Scene from '../../core/Scene'
import { lerp, lerpv2 } from '../../tools'; import { lerp, lerpv2 } from '../../tools'
import ZoomBlurImage from './ZoomBlurImage.js'; import ZoomBlurImage from './ZoomBlurImage.js'
import useTextures from '../../use/useTextures'; import useTextures from '../../use/useTextures'
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.renderer.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 +64,97 @@ export default defineComponent({
duration: 2.5, duration: 2.5,
ease: Power4.easeOut, ease: Power4.easeOut,
} }
); )
const domElement = this.renderer.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.onResize(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.renderer); 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.renderer); 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.renderer.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); lerpv2(this.image1.uCenter.value, this.center, 0.1)
lerpv2(this.image2.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.renderer.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>

View File

@ -3,23 +3,23 @@ import {
PlaneGeometry, PlaneGeometry,
ShaderMaterial, ShaderMaterial,
Vector2, Vector2,
} from 'three'; } from 'three'
export default function ZoomBlurImage(renderer) { 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(renderer) {
} }
} }
`, `,
}); })
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 = renderer.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
} }
} }
} }