├── .gitignore ├── src ├── assets │ └── photo.jpg ├── js │ ├── bidello │ │ ├── index.js │ │ ├── viewport.js │ │ ├── raf.js │ │ └── pointer.js │ ├── postfx │ │ ├── postfx.vert │ │ ├── postfx.js │ │ └── postfx.frag │ ├── utils │ │ ├── deferred.js │ │ ├── triangle.js │ │ ├── trail.js │ │ └── fbo.js │ ├── scene.js │ ├── renderer.js │ ├── main.js │ ├── settings.js │ ├── camera.js │ ├── assets.js │ └── cube │ │ └── cube.js └── index.html ├── README.md └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | .DS_Store 4 | .cache 5 | package-lock.json -------------------------------------------------------------------------------- /src/assets/photo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luruke/antipasto/HEAD/src/assets/photo.jpg -------------------------------------------------------------------------------- /src/js/bidello/index.js: -------------------------------------------------------------------------------- 1 | export * from './raf'; 2 | export * from './viewport'; 3 | export * from './pointer'; -------------------------------------------------------------------------------- /src/js/postfx/postfx.vert: -------------------------------------------------------------------------------- 1 | precision highp float; 2 | attribute vec2 position; 3 | 4 | void main() { 5 | gl_Position = vec4(position, 1.0, 1.0); 6 | } -------------------------------------------------------------------------------- /src/js/utils/deferred.js: -------------------------------------------------------------------------------- 1 | export default () => { 2 | let _resolve = undefined; 3 | let _reject = undefined; 4 | const promise = new Promise((resolve, reject) => { 5 | _resolve = resolve; 6 | _reject = reject; 7 | }); 8 | 9 | promise.resolve = _resolve; 10 | promise.reject = _reject; 11 | 12 | return promise; 13 | }; 14 | -------------------------------------------------------------------------------- /src/js/utils/triangle.js: -------------------------------------------------------------------------------- 1 | import { 2 | BufferGeometry, 3 | BufferAttribute, 4 | } from 'three'; 5 | 6 | const vertices = new Float32Array([ 7 | -1.0, -1.0, 8 | 3.0, -1.0, 9 | -1.0, 3.0 10 | ]); 11 | 12 | const geometry = new BufferGeometry(); 13 | geometry.setAttribute('position', new BufferAttribute(vertices, 2)); 14 | 15 | export default geometry; -------------------------------------------------------------------------------- /src/js/scene.js: -------------------------------------------------------------------------------- 1 | // import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader'; 2 | import { Scene } from 'three'; 3 | import { component } from 'bidello'; 4 | import Cube from './cube/cube'; 5 | import camera from './camera'; 6 | 7 | class Stage extends component(Scene) { 8 | init() { 9 | this.add(new Cube()); 10 | this.add(camera); 11 | } 12 | } 13 | 14 | export default new Stage(); -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | antipasto 4 | 5 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/js/renderer.js: -------------------------------------------------------------------------------- 1 | import { WebGLRenderer } from 'three'; 2 | import { component } from 'bidello'; 3 | import settings from './settings'; 4 | 5 | class Renderer extends component(WebGLRenderer) { 6 | constructor() { 7 | super({ 8 | powerPreference: 'high-performance', 9 | antialiasing: false, 10 | }); 11 | 12 | // this.gammaFactor = 2.2; 13 | // this.gammaInput = true; 14 | // this.gammaOutput = true; 15 | 16 | this.setPixelRatio(settings.dpr); 17 | } 18 | 19 | onResize({ width, height }) { 20 | this.setSize(width, height); 21 | } 22 | } 23 | 24 | export default new Renderer(); 25 | -------------------------------------------------------------------------------- /src/js/main.js: -------------------------------------------------------------------------------- 1 | import * as helpers from './bidello'; 2 | import renderer from './renderer'; 3 | import camera from './camera'; 4 | import scene from './scene'; 5 | import { component } from 'bidello'; 6 | import settings from './settings'; 7 | import postfx from './postfx/postfx'; 8 | import assets from './assets'; 9 | 10 | class Site extends component() { 11 | init() { 12 | assets.load(); 13 | document.body.appendChild(renderer.domElement); 14 | } 15 | 16 | onRaf() { 17 | // renderer.render(scene, camera); 18 | postfx.render(scene, camera); 19 | } 20 | 21 | onLoadEnd() { 22 | console.log('finished loader!'); 23 | } 24 | } 25 | 26 | new Site(); -------------------------------------------------------------------------------- /src/js/settings.js: -------------------------------------------------------------------------------- 1 | import { getGPUTier } from 'detect-gpu'; 2 | 3 | const tier = getGPUTier({ 4 | mobileBenchmarkPercentages: [10, 40, 30, 20], // (Default) [TIER_0, TIER_1, TIER_2, TIER_3] 5 | desktopBenchmarkPercentages: [10, 40, 30, 20], // (Default) [TIER_0, TIER_1, TIER_2, TIER_3] 6 | // forceRendererString: 'Apple A11 GPU', // (Development) Force a certain renderer string 7 | // forceMobile: true, // (Development) Force the use of mobile benchmarking scores 8 | }); 9 | 10 | const dpr = Math.min(1.5, window.devicePixelRatio || 1); 11 | 12 | const settings = { 13 | tier, 14 | dpr, 15 | fxaa: true, 16 | }; 17 | 18 | console.log(`⚙️ settings`, settings); 19 | 20 | export default settings; 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 🍽 antipasto 2 | 3 | > ⚠️ probably won't work with modern version of threejs, last time I tested was with r114 4 | > pr is welcome 5 | 6 | ### Boilerplate for three.js, using some juicy stuff: 7 | 8 | - [bidello](https://github.com/luruke/bidello) 9 | - [magicshader](https://github.com/luruke/magicshader) 10 | - [postfx](https://medium.com/@luruke/simple-postprocessing-in-three-js-91936ecadfb7) 11 | - [detect-gpu](https://github.com/TimvanScherpenzeel/detect-gpu) 12 | - [parcel-bundler](https://parceljs.org/) 13 | - [resource-loader](https://github.com/englercj/resource-loader) 14 | - [glsl-fxaa](https://github.com/mattdesl/glsl-fxaa) 15 | - [orbit-control-es6](https://github.com/silviopaganini/orbit-controls-es6) 16 | - [GPGPU/FBO utility](https://github.com/luruke/antipasto/blob/master/src/js/utils/fbo.js) 17 | 18 | 19 | ``` 20 | npm install 21 | npm run dev 22 | ``` 23 | -------------------------------------------------------------------------------- /src/js/bidello/viewport.js: -------------------------------------------------------------------------------- 1 | import bidello from 'bidello'; 2 | 3 | class Viewport { 4 | constructor() { 5 | this.width = this.calculateWidth(); 6 | this.height = this.calculateHeight(); 7 | this.ratio = this.width / this.height; 8 | 9 | this.onResize = this.onResize.bind(this); 10 | this.onResize(); 11 | 12 | window.addEventListener('resize', this.onResize); 13 | } 14 | 15 | calculateWidth() { 16 | return window.innerWidth; 17 | } 18 | 19 | calculateHeight() { 20 | return window.innerHeight; 21 | } 22 | 23 | onResize() { 24 | this.width = this.calculateWidth(); 25 | this.height = this.calculateHeight(); 26 | this.ratio = this.width / this.height; 27 | 28 | bidello.trigger({ name: 'resize', fireAtStart: true }, { 29 | width: this.width, 30 | height: this.height, 31 | ratio: this.ratio, 32 | }) 33 | } 34 | } 35 | 36 | export const viewport = new Viewport(); 37 | -------------------------------------------------------------------------------- /src/js/bidello/raf.js: -------------------------------------------------------------------------------- 1 | import bidello from 'bidello'; 2 | 3 | class Raf { 4 | constructor() { 5 | this.time = window.performance.now(); 6 | 7 | this.start = this.start.bind(this); 8 | this.pause = this.pause.bind(this); 9 | this.onTick = this.onTick.bind(this); 10 | this.start(); 11 | } 12 | 13 | start() { 14 | this.startTime = window.performance.now(); 15 | this.oldTime = this.startTime; 16 | this.isPaused = false; 17 | 18 | this.onTick(this.startTime); 19 | } 20 | 21 | pause() { 22 | this.isPaused = true; 23 | } 24 | 25 | onTick(now) { 26 | this.time = now; 27 | 28 | if (!this.isPaused) { 29 | this.delta = (now - this.oldTime) / 1000; 30 | this.oldTime = now; 31 | 32 | bidello.trigger({ name: 'raf' }, { 33 | delta: this.delta, 34 | now 35 | }); 36 | } 37 | 38 | window.requestAnimationFrame(this.onTick); 39 | } 40 | } 41 | 42 | export const raf = new Raf(); 43 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "antipasto", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "dev": "parcel src/index.html --no-autoinstall --open", 9 | "build": "rm -rf ./docs; parcel build src/index.html -d docs --public-url '.' --no-source-maps" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "git+https://github.com/luruke/antipasto.git" 14 | }, 15 | "author": "", 16 | "license": "ISC", 17 | "bugs": { 18 | "url": "https://github.com/luruke/antipasto/issues" 19 | }, 20 | "homepage": "https://github.com/luruke/antipasto#readme", 21 | "devDependencies": { 22 | "bidello": "1.0.1", 23 | "detect-gpu": "^1.1.4", 24 | "glsl-fxaa": "^3.0.0", 25 | "glslify-bundle": "^5.1.1", 26 | "glslify-deps": "^1.3.1", 27 | "magicshader": "^0.1.4", 28 | "orbit-controls-es6": "^2.0.1", 29 | "parcel-bundler": "^1.12.4", 30 | "resource-loader": "^3.0.1", 31 | "math-toolbox": "^1.12.0", 32 | "three": "^0.111.0" 33 | }, 34 | "dependencies": {} 35 | } 36 | -------------------------------------------------------------------------------- /src/js/camera.js: -------------------------------------------------------------------------------- 1 | import { PerspectiveCamera, Vector3 } from 'three'; 2 | import { component } from 'bidello'; 3 | import OrbitControls from 'orbit-controls-es6'; 4 | import renderer from '/js/renderer'; 5 | 6 | class Camera extends component(PerspectiveCamera) { 7 | constructor() { 8 | super(35, 0, 0.1, 500); 9 | } 10 | 11 | init() { 12 | this.position.set(0, 0, 10); 13 | this.lookAt(new Vector3(0, 0, 0)); 14 | this.initOrbitControl(); 15 | } 16 | 17 | initOrbitControl() { 18 | const controls = new OrbitControls(this, renderer.domElement); 19 | 20 | controls.enabled = true; 21 | controls.maxDistance = 1500; 22 | controls.minDistance = 0; 23 | } 24 | 25 | calculateUnitSize(distance = this.position.z) { 26 | const vFov = this.fov * Math.PI / 180; 27 | const height = 2 * Math.tan(vFov / 2) * distance; 28 | const width = height * this.aspect; 29 | 30 | return { 31 | width, 32 | height 33 | }; 34 | } 35 | 36 | onResize({ ratio }) { 37 | this.aspect = ratio; 38 | this.unit = this.calculateUnitSize(); 39 | this.updateProjectionMatrix(); 40 | } 41 | } 42 | 43 | export default new Camera(); 44 | -------------------------------------------------------------------------------- /src/js/assets.js: -------------------------------------------------------------------------------- 1 | import { Loader } from 'resource-loader'; 2 | import bidello from 'bidello'; 3 | import deferred from '/js/utils/deferred'; 4 | 5 | const RESOURCES = [ 6 | { 7 | name: 'photo', 8 | url: require('/assets/photo.jpg') 9 | }, 10 | 11 | // { 12 | // name: 'photo', 13 | // url: require('/assets/photo.glb'), 14 | // loadType: Resource.LOAD_TYPE.XHR, 15 | // xhrType: Resource.XHR_RESPONSE_TYPE.BLOB, 16 | // }, 17 | ]; 18 | 19 | /* 20 | assets.resources.photo.loading.then(res => { 21 | console.log(res.meta.data); 22 | }); 23 | */ 24 | 25 | class Assets { 26 | constructor() { 27 | this.resources = {}; 28 | 29 | RESOURCES.forEach(entry => { 30 | this.resources[entry.name] = entry; 31 | this.resources[entry.name].loading = deferred(); 32 | }); 33 | } 34 | 35 | load() { 36 | this.deferred = deferred(); 37 | this.loader = new Loader(); 38 | 39 | bidello.trigger({ name: 'loadStart' }); 40 | 41 | RESOURCES.forEach(res => { 42 | this.loader.add(res); 43 | }); 44 | 45 | this.loader.onProgress.add(this.onProgress.bind(this)); 46 | this.loader.load(this.finish.bind(this)); 47 | 48 | return deferred; 49 | } 50 | 51 | onProgress(loader, meta) { 52 | bidello.trigger({ name: 'loadProgress' }, { progress: this.loader.progress }); 53 | const res = this.resources[meta.name]; 54 | res.meta = meta; 55 | res.loading.resolve(res); 56 | } 57 | 58 | finish() { 59 | this.deferred.resolve(); 60 | bidello.trigger({ name: 'loadEnd' }, { resources: this.resources }); 61 | } 62 | } 63 | 64 | export default new Assets(); 65 | -------------------------------------------------------------------------------- /src/js/utils/trail.js: -------------------------------------------------------------------------------- 1 | import { component } from 'bidello'; 2 | import FBO from './fbo'; 3 | import { Vector2, LinearFilter } from 'three'; 4 | 5 | const shader = ` 6 | precision highp float; 7 | 8 | uniform sampler2D texture; 9 | uniform vec2 uPointer; 10 | 11 | float circle(vec2 uv, vec2 disc_center, float disc_radius, float border_size) { 12 | uv -= disc_center; 13 | float dist = sqrt(dot(uv, uv)); 14 | return smoothstep(disc_radius+border_size, disc_radius-border_size, dist); 15 | } 16 | 17 | void main() { 18 | vec2 uv = gl_FragCoord.xy / RESOLUTION.xy; 19 | vec4 color = texture2D(texture, uv); 20 | 21 | color.rgb += circle(uv, uPointer, 0.0, 0.1); 22 | color.rgb = mix(color.rgb, vec3(0.0), .009); 23 | color.rgb = clamp(color.rgb, vec3(0.0), vec3(1.0)); 24 | color.a = 1.0; 25 | 26 | gl_FragColor = color; 27 | } 28 | `; 29 | 30 | class Trail extends component() { 31 | init() { 32 | this.fbo = new FBO({ 33 | width: 256, 34 | height: 256, 35 | name: 'trail', 36 | shader, 37 | uniforms: { 38 | uPointer: { value: new Vector2() } 39 | }, 40 | rtOptions: { 41 | minFilter: LinearFilter, 42 | magFilter: LinearFilter, 43 | }, 44 | // debug: true, 45 | }); 46 | 47 | this.pointerTarget = new Vector2(); 48 | } 49 | 50 | onPointerMove({ pointer }) { 51 | this.pointerTarget.set( 52 | pointer.x / window.innerWidth, 53 | 1 - (pointer.y / window.innerHeight) 54 | ); 55 | } 56 | 57 | onRaf({ delta }) { 58 | // this.fbo.uniforms.uPointer.value.lerp(this.pointerTarget, 7 * delta); 59 | this.fbo.uniforms.uPointer.value.copy(this.pointerTarget); 60 | this.fbo.update(); 61 | } 62 | } 63 | 64 | export default new Trail(); -------------------------------------------------------------------------------- /src/js/postfx/postfx.js: -------------------------------------------------------------------------------- 1 | import { 2 | WebGLRenderTarget, 3 | Camera, 4 | RGBFormat, 5 | Mesh, 6 | Scene, 7 | RawShaderMaterial, 8 | Vector2, 9 | } from 'three'; 10 | 11 | import { component } from 'bidello'; 12 | import renderer from '/js/renderer'; 13 | import settings from '/js/settings'; 14 | import triangle from '/js/utils/triangle'; 15 | import vertexShader from './postfx.vert'; 16 | import fragmentShader from './postfx.frag'; 17 | 18 | class PostFX extends component() { 19 | init() { 20 | this.renderer = renderer; 21 | this.scene = new Scene(); 22 | this.dummyCamera = new Camera(); 23 | this.resolution = new Vector2(); 24 | this.renderer.getDrawingBufferSize(this.resolution); 25 | 26 | this.target = new WebGLRenderTarget(this.resolution.x, this.resolution.y, { 27 | format: RGBFormat, 28 | stencilBuffer: false, 29 | depthBuffer: true, 30 | }); 31 | 32 | const defines = {}; 33 | 34 | settings.fxaa && (defines.FXAA = true); 35 | 36 | this.material = new RawShaderMaterial({ 37 | defines, 38 | fragmentShader, 39 | vertexShader, 40 | uniforms: { 41 | uScene: { value: this.target.texture }, 42 | uResolution: { value: this.resolution }, 43 | }, 44 | }); 45 | 46 | this.triangle = new Mesh(triangle, this.material); 47 | this.triangle.frustumCulled = false; 48 | this.scene.add(this.triangle); 49 | } 50 | 51 | onResize() { 52 | this.renderer.getDrawingBufferSize(this.resolution); 53 | this.target.setSize(this.resolution.x, this.resolution.y); 54 | } 55 | 56 | render(scene, camera) { 57 | this.renderer.setRenderTarget(this.target); 58 | this.renderer.render(scene, camera); 59 | this.renderer.setRenderTarget(null); 60 | this.renderer.render(this.scene, this.dummyCamera); 61 | } 62 | } 63 | 64 | export default new PostFX(); -------------------------------------------------------------------------------- /src/js/cube/cube.js: -------------------------------------------------------------------------------- 1 | import { 2 | Object3D, 3 | BoxBufferGeometry, 4 | Mesh 5 | } from 'three'; 6 | 7 | import { component } from 'bidello'; 8 | import MagicShader from 'magicshader'; 9 | import trail from '/js/utils/trail'; 10 | 11 | export default class extends component(Object3D) { 12 | init() { 13 | this.geometry = new BoxBufferGeometry(3, 3, 3, 18, 18, 18); 14 | this.material = new MagicShader({ 15 | wireframe: true, 16 | name: 'Cube', 17 | vertexShader: ` 18 | precision highp float; 19 | 20 | attribute vec3 normal; 21 | attribute vec3 position; 22 | 23 | uniform sampler2D uTrail; 24 | uniform mat3 normalMatrix; 25 | uniform mat4 modelMatrix; 26 | uniform mat4 viewMatrix; 27 | uniform mat4 modelViewMatrix; 28 | uniform mat4 projectionMatrix; 29 | 30 | varying float vForce; 31 | 32 | float quarticOut(float t) { 33 | return pow(t - 1.0, 3.0) * (1.0 - t) + 1.0; 34 | } 35 | 36 | void main() { 37 | vec4 clipSpace = projectionMatrix * modelViewMatrix * vec4(position, 1.0); 38 | 39 | vec2 uv = ((clipSpace.xy / clipSpace.w) + 1.0) / 2.0; 40 | float pointer = texture2D(uTrail, uv).r; 41 | vec4 pos = modelMatrix * vec4(position, 1.0); 42 | 43 | float force = quarticOut(pointer); 44 | vForce = force; 45 | 46 | vec3 norm = normalMatrix * normal; 47 | pos.rgb += (norm * force) * .5; 48 | 49 | gl_Position = projectionMatrix * viewMatrix * pos; 50 | } 51 | `, 52 | fragmentShader: ` 53 | precision highp float; 54 | 55 | uniform vec3 colorA; // ms({ value: '#ff0000' }) 56 | uniform vec3 colorB; // ms({ value: '#00ff00' }) 57 | varying float vForce; 58 | 59 | void main() { 60 | gl_FragColor = vec4(mix(colorA, colorB, vForce), 1.0); 61 | } 62 | `, 63 | uniforms: { 64 | uTrail: { value: trail.fbo.target } 65 | } 66 | }); 67 | 68 | this.mesh = new Mesh(this.geometry, this.material); 69 | 70 | this.add(this.mesh); 71 | } 72 | 73 | onRaf({ delta }) { 74 | this.mesh.rotation.x += 0.3 * delta; 75 | this.mesh.rotation.y += 0.3 * delta; 76 | // this.material.uniforms.uTrail.value = trail.target; 77 | } 78 | } -------------------------------------------------------------------------------- /src/js/postfx/postfx.frag: -------------------------------------------------------------------------------- 1 | precision highp float; 2 | uniform sampler2D uScene; 3 | uniform vec2 uResolution; 4 | 5 | #ifdef FXAA 6 | #pragma glslify: fxaa = require(glsl-fxaa) 7 | #endif 8 | 9 | const float gamma = 2.2; 10 | 11 | vec3 linearToneMapping(vec3 color) { 12 | float exposure = 1.; 13 | color = clamp(exposure * color, 0., 1.); 14 | color = pow(color, vec3(1. / gamma)); 15 | return color; 16 | } 17 | 18 | vec3 simpleReinhardToneMapping(vec3 color) { 19 | float exposure = 1.5; 20 | color *= exposure/(1. + color / exposure); 21 | color = pow(color, vec3(1. / gamma)); 22 | return color; 23 | } 24 | 25 | vec3 lumaBasedReinhardToneMapping(vec3 color) { 26 | float luma = dot(color, vec3(0.2126, 0.7152, 0.0722)); 27 | float toneMappedLuma = luma / (1. + luma); 28 | color *= toneMappedLuma / luma; 29 | color = pow(color, vec3(1. / gamma)); 30 | return color; 31 | } 32 | 33 | vec3 whitePreservingLumaBasedReinhardToneMapping(vec3 color) { 34 | float white = 2.; 35 | float luma = dot(color, vec3(0.2126, 0.7152, 0.0722)); 36 | float toneMappedLuma = luma * (1. + luma / (white*white)) / (1. + luma); 37 | color *= toneMappedLuma / luma; 38 | color = pow(color, vec3(1. / gamma)); 39 | return color; 40 | } 41 | 42 | vec3 RomBinDaHouseToneMapping(vec3 color) { 43 | color = exp( -1.0 / ( 2.72*color + 0.15 ) ); 44 | color = pow(color, vec3(1. / gamma)); 45 | return color; 46 | } 47 | 48 | vec3 filmicToneMapping(vec3 color) { 49 | color = max(vec3(0.), color - vec3(0.004)); 50 | color = (color * (6.2 * color + .5)) / (color * (6.2 * color + 1.7) + 0.06); 51 | return color; 52 | } 53 | 54 | vec3 Uncharted2ToneMapping(vec3 color) { 55 | float A = 0.15; 56 | float B = 0.50; 57 | float C = 0.10; 58 | float D = 0.20; 59 | float E = 0.02; 60 | float F = 0.30; 61 | float W = 11.2; 62 | float exposure = 2.; 63 | color *= exposure; 64 | color = ((color * (A * color + C * B) + D * E) / (color * (A * color + B) + D * F)) - E / F; 65 | float white = ((W * (A * W + C * B) + D * E) / (W * (A * W + B) + D * F)) - E / F; 66 | color /= white; 67 | color = pow(color, vec3(1. / gamma)); 68 | return color; 69 | } 70 | 71 | void main() { 72 | #ifdef FXAA 73 | vec3 color = fxaa(uScene, gl_FragCoord.xy, uResolution).rgb; 74 | #else 75 | vec2 uv = gl_FragCoord.xy / uResolution.xy; 76 | vec3 color = texture2D(uScene, uv).rgb; 77 | #endif 78 | 79 | color = linearToneMapping(color); 80 | 81 | gl_FragColor = vec4(color, 1.0); 82 | } -------------------------------------------------------------------------------- /src/js/bidello/pointer.js: -------------------------------------------------------------------------------- 1 | import bidello from 'bidello'; 2 | import { Vector2, Vector3} from 'three'; 3 | import { clamp } from 'math-toolbox'; 4 | import camera from '/js/camera'; 5 | import { viewport } from './viewport'; 6 | 7 | class Pointer { 8 | constructor() { 9 | this.x = 0; 10 | this.y = 0; 11 | this.isTouching = true; 12 | this.distance = 0; 13 | 14 | this.hold = new Vector2(); 15 | this.last = new Vector2(); 16 | this.delta = new Vector2(); 17 | this.move = new Vector2(); 18 | this.world = new Vector3(); 19 | this.normalized = new Vector2(); 20 | this._tmp = new Vector3(); 21 | 22 | this.bind(); 23 | } 24 | 25 | bind() { 26 | const container = window; 27 | 28 | container.addEventListener('touchstart', this.onStart.bind(this), { 29 | passive: false 30 | }); 31 | container.addEventListener('touchmove', this.onMove.bind(this), { 32 | passive: false 33 | }); 34 | container.addEventListener('touchend', this.onEnd.bind(this), { 35 | passive: false 36 | }); 37 | container.addEventListener('touchcancel', this.onEnd.bind(this), { 38 | passive: false 39 | }); 40 | 41 | container.addEventListener('mousedown', this.onStart.bind(this)); 42 | container.addEventListener('mousemove', this.onMove.bind(this)); 43 | container.addEventListener('mouseup', this.onEnd.bind(this)); 44 | container.addEventListener('contextmenu', this.onEnd.bind(this)); 45 | } 46 | 47 | convertEvent(e) { 48 | e.preventDefault(); 49 | e.stopPropagation(); 50 | 51 | const t = { 52 | x: 0, 53 | y: 0, 54 | }; 55 | 56 | if (!e) { 57 | return t; 58 | } 59 | 60 | if (e.windowsPointer) { 61 | return e; 62 | } 63 | 64 | if (e.touches || e.changedTouches) { 65 | if (e.touches.length) { 66 | t.x = e.touches[0].pageX; 67 | t.y = e.touches[0].pageY; 68 | } else { 69 | t.x = e.changedTouches[0].pageX; 70 | t.y = e.changedTouches[0].pageY; 71 | } 72 | } else { 73 | t.x = e.pageX; 74 | t.y = e.pageY; 75 | } 76 | 77 | t.x = clamp(0, viewport.width, t.x); 78 | t.y = clamp(0, viewport.height, t.y); 79 | 80 | return t; 81 | } 82 | 83 | onStart(event) { 84 | const e = this.convertEvent(event); 85 | 86 | this.isTouching = true; 87 | this.x = e.x; 88 | this.y = e.y; 89 | 90 | this.hold.set(e.x, e.y); 91 | this.last.set(e.x, e.y); 92 | this.delta.set(0, 0); 93 | this.move.set(0, 0); 94 | 95 | this.normalized.x = ((this.x / viewport.width) * 2) - 1; 96 | this.normalized.y = (-(this.y / viewport.height) * 2) + 1; 97 | this.distance = 0; 98 | 99 | bidello.trigger({ 100 | name: 'pointerStart' 101 | }, { 102 | pointer: this, 103 | }); 104 | } 105 | 106 | onMove(event) { 107 | const e = this.convertEvent(event); 108 | 109 | if (this.isTouching) { 110 | this.move.x = e.x - this.hold.x; 111 | this.move.y = e.y - this.hold.y; 112 | } 113 | 114 | // if (this.last.x !== e.x || this.last.y !== e.y) { 115 | // this.last.set(this.x, this.y); 116 | // } 117 | 118 | this.x = e.x; 119 | this.y = e.y; 120 | this.delta.x = e.x - this.last.x; 121 | this.delta.y = e.y - this.last.y; 122 | 123 | this.distance += this.delta.length(); 124 | 125 | this.normalized.x = ((this.x / viewport.width) * 2) - 1; 126 | this.normalized.y = (-(this.y / viewport.height) * 2) + 1; 127 | 128 | this._tmp.x = this.normalized.x; 129 | this._tmp.y = this.normalized.y; 130 | this._tmp.z = 0.5; 131 | this._tmp.unproject(camera); 132 | const dir = this._tmp.sub(camera.position).normalize(); 133 | const dist = -camera.position.z / dir.z; 134 | this.world.copy(camera.position).add(dir.multiplyScalar(dist)); 135 | 136 | bidello.trigger({ 137 | name: 'pointerMove' 138 | }, { 139 | pointer: this, 140 | }); 141 | 142 | if (this.isTouching) { 143 | bidello.trigger({ 144 | name: 'pointerDrag' 145 | }, { 146 | pointer: this, 147 | }); 148 | } 149 | } 150 | 151 | onEnd() { 152 | this.isTouching = false; 153 | this.move.set(0, 0); 154 | 155 | bidello.trigger({ 156 | name: 'pointerEnd' 157 | }, { 158 | pointer: this, 159 | }); 160 | } 161 | } 162 | 163 | export const pointer = new Pointer(); -------------------------------------------------------------------------------- /src/js/utils/fbo.js: -------------------------------------------------------------------------------- 1 | /* 2 | this.position = new FBO({ 3 | width: 128, 4 | height: 128, 5 | name: 'position', 6 | shader: require('./position.frag'), 7 | uniforms: { 8 | uTime: { 9 | value: 0 10 | }, 11 | }, 12 | }); 13 | 14 | this.position.target 15 | this.position.update() 16 | */ 17 | 18 | import { 19 | WebGLRenderTarget, 20 | NearestFilter, 21 | DataTexture, 22 | RGBAFormat, 23 | FloatType, 24 | HalfFloatType, 25 | Camera, 26 | Scene, 27 | Mesh, 28 | PlaneBufferGeometry, 29 | MeshBasicMaterial, 30 | } from 'three'; 31 | import MagicShader from 'magicshader'; 32 | import renderer from '/js/renderer'; 33 | import triangle from './triangle'; 34 | import camera from '/js/camera'; 35 | 36 | export const isAvailable = (() => { 37 | const gl = renderer.getContext(); 38 | 39 | if (!gl.getExtension('OES_texture_float')) { 40 | return false; 41 | } 42 | 43 | if (gl.getParameter(gl.MAX_VERTEX_TEXTURE_IMAGE_UNITS) === 0) { 44 | return false; 45 | } 46 | 47 | return true; 48 | })(); 49 | 50 | const iOS = /(iPad|iPhone|iPod)/g.test(navigator.userAgent); 51 | const type = iOS ? HalfFloatType : FloatType; 52 | 53 | export default class FBO { 54 | constructor({ 55 | width, 56 | height, 57 | data, 58 | name, 59 | shader, 60 | texture, 61 | uniforms = {}, 62 | rtOptions = {}, 63 | debug = false 64 | }) { 65 | this.options = arguments[0]; 66 | this.renderer = renderer; 67 | this.camera = new Camera(); 68 | this.scene = new Scene(); 69 | this.index = 0; 70 | this.copyData = true; 71 | this.texture = texture || new DataTexture( 72 | data || new Float32Array((width * height * 4)), 73 | width, 74 | height, 75 | RGBAFormat, 76 | FloatType, 77 | // type, 78 | ); 79 | this.texture.needsUpdate = true; 80 | 81 | this.rt = [this.createRT(), this.createRT()]; 82 | 83 | this.material = new MagicShader({ 84 | name: name || 'FBO', 85 | defines: { 86 | RESOLUTION: `vec2(${width.toFixed(1)}, ${height.toFixed(1)})` 87 | }, 88 | uniforms: { 89 | ...uniforms, 90 | texture: { 91 | value: this.texture 92 | } 93 | }, 94 | vertexShader: ` 95 | precision highp float; 96 | attribute vec3 position; 97 | 98 | void main() { 99 | gl_Position = vec4(position, 1.0); 100 | } 101 | `, 102 | fragmentShader: shader || ` 103 | precision highp float; 104 | uniform sampler2D texture; 105 | 106 | void main() { 107 | vec2 uv = gl_FragCoord.xy / RESOLUTION.xy; 108 | gl_FragColor = texture2D(texture, uv); 109 | } 110 | `, 111 | }); 112 | 113 | this.mesh = new Mesh(triangle, this.material); 114 | this.mesh.frustumCulled = false; 115 | this.scene.add(this.mesh); 116 | 117 | if (this.options.debug) { 118 | this.initDebug(); 119 | } 120 | } 121 | 122 | initDebug() { 123 | this.debugGeometry = new PlaneBufferGeometry(2, 2); 124 | this.debugMaterial = new MeshBasicMaterial({ 125 | map: this.target, 126 | }); 127 | 128 | this.debugMesh = new Mesh(this.debugGeometry, this.debugMaterial); 129 | this.debugMesh.position.set(0, 0, -5); 130 | 131 | camera.add(this.debugMesh); 132 | } 133 | 134 | createRT() { 135 | return new WebGLRenderTarget(this.options.width, this.options.height, Object.assign({ 136 | minFilter: NearestFilter, 137 | magFilter: NearestFilter, 138 | stencilBuffer: false, 139 | depthBuffer: false, 140 | depthWrite: false, 141 | depthTest: false, 142 | type, 143 | }, this.options.rtOptions)); 144 | } 145 | 146 | get target() { 147 | return this.rt[this.index].texture; 148 | } 149 | 150 | get uniforms() { 151 | return this.material.uniforms; 152 | } 153 | 154 | // TODO: test... 155 | resize(width, height) { 156 | this.material.defines.RESOLUTION = `vec2(${width.toFixed(1)}, ${height.toFixed(1)})`; 157 | this.options.width = width; 158 | this.options.height = height; 159 | 160 | this.rt.forEach(rt => { 161 | rt.setSize(width, height); 162 | }); 163 | } 164 | 165 | update(switchBack = true) { 166 | const destIndex = this.index === 0 ? 1 : 0; 167 | const old = this.rt[this.index]; 168 | const dest = this.rt[destIndex]; 169 | 170 | this.material.uniforms.texture.value = this.copyData ? this.texture : old.texture; 171 | 172 | const oldMainTarget = this.renderer.getRenderTarget(); 173 | this.renderer.setRenderTarget(dest); 174 | this.renderer.render(this.scene, this.camera); 175 | switchBack && this.renderer.setRenderTarget(oldMainTarget); 176 | 177 | this.index = destIndex; 178 | this.copyData = false; 179 | } 180 | } 181 | --------------------------------------------------------------------------------