ngx-acuw
Version:
Angular components using WEBGL (threejs)
1,190 lines (1,144 loc) • 139 kB
JavaScript
import * as i0 from '@angular/core';
import { Component, ChangeDetectionStrategy, ViewChild, Input, HostListener, NgModule, EventEmitter, Output, Directive, ContentChildren } from '@angular/core';
import { Texture, WebGLRenderer, Scene, Clock, Vector2, Raycaster, PerspectiveCamera, TextureLoader, LinearFilter, RGBAFormat, RawShaderMaterial, InstancedBufferGeometry, BufferAttribute, InstancedBufferAttribute, Mesh, Object3D, PlaneGeometry, MeshBasicMaterial, PlaneBufferGeometry, ShaderMaterial, DoubleSide, Vector4, Quaternion, Euler, Group, Vector3 } from 'three';
import { Observable, interval, Subscription, Subject, of, zip, NEVER } from 'rxjs';
import * as i1$1 from '@angular/animations';
import { trigger, transition, style, animate, query, stagger, state } from '@angular/animations';
import * as i2 from '@angular/common';
import { CommonModule } from '@angular/common';
import { switchMap, delay, tap } from 'rxjs/operators';
import { ComponentPortal } from '@angular/cdk/portal';
import * as i1 from '@angular/cdk/overlay';
import { OverlayModule } from '@angular/cdk/overlay';
import { CSS3DRenderer, CSS3DObject } from 'three/examples/jsm/renderers/CSS3DRenderer';
class TouchTexture {
constructor() {
this.size = 64;
this.maxAge = 120;
this.radius = 0.15;
this.trail = new Array();
this.initTexture();
}
/**
* Initializes the texture for the touch area
*/
initTexture() {
this.canvas = document.createElement('canvas');
this.canvas.width = this.canvas.height = this.size;
this.ctx = this.canvas.getContext('2d');
this.ctx.fillStyle = 'black';
this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
this.texture = new Texture(this.canvas);
this.canvas.id = 'touchTexture';
this.canvas.style.width = this.canvas.style.height = `${this.canvas.width}px`;
}
/**
* Updates the trail
*/
update() {
this.clear();
// age points
this.trail.forEach((point, i) => {
point.age++;
// remove old
if (point.age > this.maxAge) {
this.trail.splice(i, 1);
}
});
this.trail.forEach((point, i) => {
this.drawTouch(point);
});
this.texture.needsUpdate = true;
}
clear() {
this.ctx.fillStyle = 'black';
this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
}
addTouch(px, py) {
let force = 0;
const last = this.trail[this.trail.length - 1];
if (last) {
const dx = last.x - px;
const dy = last.y - py;
const dd = dx * dx + dy * dy;
force = Math.min(dd * 10000, 1);
}
this.trail.push({ x: px, y: py, age: 0, force });
}
drawTouch(point) {
const pos = {
x: point.x * this.size,
y: (1 - point.y) * this.size
};
let intensity = 1;
if (point.age < this.maxAge * 0.3) {
intensity = this.easeOutSine(point.age / (this.maxAge * 0.3), 0, 1, 1);
}
else {
intensity = this.easeOutSine(1 - (point.age - this.maxAge * 0.3) / (this.maxAge * 0.7), 0, 1, 1);
}
intensity *= point.force;
const radius = this.size * this.radius * intensity;
const grd = this.ctx.createRadialGradient(pos.x, pos.y, radius * 0.25, pos.x, pos.y, radius);
grd.addColorStop(0, `rgba(255, 255, 255, 0.2)`);
grd.addColorStop(1, 'rgba(0, 0, 0, 0.0)');
this.ctx.beginPath();
this.ctx.fillStyle = grd;
this.ctx.arc(pos.x, pos.y, radius, 0, Math.PI * 2);
this.ctx.fill();
}
easeOutSine(t, b, c, d) {
return c * Math.sin(t / d * (Math.PI / 2)) + b;
}
}
class Shaders {
constructor() {
this.particleVertex = `
// @author brunoimbrizi / http://brunoimbrizi.com
precision highp float;
#define GLSLIFY 1
attribute float pindex;
attribute vec3 position;
attribute vec3 offset;
attribute vec2 uv;
attribute float angle;
uniform mat4 modelViewMatrix;
uniform mat4 projectionMatrix;
uniform float uTime;
uniform float uRandom;
uniform float uDepth;
uniform float uSize;
uniform vec2 uTextureSize;
uniform sampler2D uTexture;
uniform sampler2D uTouch;
varying vec2 vPUv;
varying vec2 vUv;
//
// Description : Array and textureless GLSL 2D simplex noise function.
// Author : Ian McEwan, Ashima Arts.
// Maintainer : ijm
// Lastmod : 20110822 (ijm)
// License : Copyright (C) 2011 Ashima Arts. All rights reserved.
// Distributed under the MIT License. See LICENSE file.
// https://github.com/ashima/webgl-noise
//
vec3 mod289_1_0(vec3 x) {
return x - floor(x * (1.0 / 289.0)) * 289.0;
}
vec2 mod289_1_0(vec2 x) {
return x - floor(x * (1.0 / 289.0)) * 289.0;
}
vec3 permute_1_1(vec3 x) {
return mod289_1_0(((x*34.0)+1.0)*x);
}
float snoise_1_2(vec2 v)
{
const vec4 C = vec4(0.211324865405187, // (3.0-sqrt(3.0))/6.0
0.366025403784439, // 0.5*(sqrt(3.0)-1.0)
-0.577350269189626, // -1.0 + 2.0 * C.x
0.024390243902439); // 1.0 / 41.0
// First corner
vec2 i = floor(v + dot(v, C.yy) );
vec2 x0 = v - i + dot(i, C.xx);
// Other corners
vec2 i1;
//i1.x = step( x0.y, x0.x ); // x0.x > x0.y ? 1.0 : 0.0
//i1.y = 1.0 - i1.x;
i1 = (x0.x > x0.y) ? vec2(1.0, 0.0) : vec2(0.0, 1.0);
// x0 = x0 - 0.0 + 0.0 * C.xx ;
// x1 = x0 - i1 + 1.0 * C.xx ;
// x2 = x0 - 1.0 + 2.0 * C.xx ;
vec4 x12 = x0.xyxy + C.xxzz;
x12.xy -= i1;
// Permutations
i = mod289_1_0(i); // Avoid truncation effects in permutation
vec3 p = permute_1_1( permute_1_1( i.y + vec3(0.0, i1.y, 1.0 ))
+ i.x + vec3(0.0, i1.x, 1.0 ));
vec3 m = max(0.5 - vec3(dot(x0,x0), dot(x12.xy,x12.xy), dot(x12.zw,x12.zw)), 0.0);
m = m*m ;
m = m*m ;
// Gradients: 41 points uniformly over a line, mapped onto a diamond.
// The ring size 17*17 = 289 is close to a multiple of 41 (41*7 = 287)
vec3 x = 2.0 * fract(p * C.www) - 1.0;
vec3 h = abs(x) - 0.5;
vec3 ox = floor(x + 0.5);
vec3 a0 = x - ox;
// Normalise gradients implicitly by scaling m
// Approximation of: m *= inversesqrt( a0*a0 + h*h );
m *= 1.79284291400159 - 0.85373472095314 * ( a0*a0 + h*h );
// Compute final noise value at P
vec3 g;
g.x = a0.x * x0.x + h.x * x0.y;
g.yz = a0.yz * x12.xz + h.yz * x12.yw;
return 130.0 * dot(m, g);
}
float random(float n) {
return fract(sin(n) * 43758.5453123);
}
void main() {
vUv = uv;
// particle uv
vec2 puv = offset.xy / uTextureSize;
vPUv = puv;
// pixel color
vec4 colA = texture2D(uTexture, puv);
float grey = colA.r * 0.21 + colA.g * 0.71 + colA.b * 0.07;
// displacement
vec3 displaced = offset;
// randomise
displaced.xy += vec2(random(pindex) - 0.5, random(offset.x + pindex) - 0.5) * uRandom;
float rndz = (random(pindex) + snoise_1_2(vec2(pindex * 0.1, uTime * 0.1)));
displaced.z += rndz * (random(pindex) * 2.0 * uDepth);
// center
displaced.xy -= uTextureSize * 0.5;
// touch
float t = texture2D(uTouch, puv).r;
displaced.z += t * 20.0 * rndz;
displaced.x += cos(angle) * t * 20.0 * rndz;
displaced.y += sin(angle) * t * 20.0 * rndz;
// particle size
float psize = (snoise_1_2(vec2(uTime, pindex) * 0.5) + 2.0);
psize *= max(grey, 0.2);
psize *= uSize;
// final position
vec4 mvPosition = modelViewMatrix * vec4(displaced, 1.0);
mvPosition.xyz += position * psize;
vec4 finalPosition = projectionMatrix * mvPosition;
gl_Position = finalPosition;
}
`;
this.particleFragment = `
// @author brunoimbrizi / http://brunoimbrizi.com
precision highp float;
#define GLSLIFY 1
uniform sampler2D uTexture;
varying vec2 vPUv;
varying vec2 vUv;
void main() {
vec4 color = vec4(0.0);
vec2 uv = vUv;
vec2 puv = vPUv;
// pixel color
vec4 colA = texture2D(uTexture, puv);
// greyscale
float grey = colA.r * 0.21 + colA.g * 0.71 + colA.b * 0.07;
//vec4 colB = vec4(grey, grey, grey, 1.0);
vec4 colB = vec4(colA.r, colA.g, colA.b, 1.0);
// circle
float border = 0.3;
float radius = 0.5;
float dist = radius - distance(uv, vec2(0.5));
float t = smoothstep(0.0, border, dist);
// final color
color = colB;
color.a = t;
gl_FragColor = color;
}
`;
}
}
var RxjsTween;
(function (RxjsTween) {
function createTween(easingFunction, b, c, d, s) {
return new Observable((observer) => {
let startTime;
const sample = (time) => {
startTime = startTime || time;
const t = time - startTime;
if (t < d) {
if (Array.isArray(b) && Array.isArray(c)) {
const tweenVals = new Array();
for (let idx = 0; idx < b.length; idx++) {
tweenVals.push(easingFunction(t, b[idx], c[idx], d, s));
}
observer.next(tweenVals);
}
else {
observer.next(easingFunction(t, b, c, d, s));
}
// Request the animation frame again
requestAnimationFrame(sample);
}
else {
// End value reached
if (Array.isArray(b) && Array.isArray(c)) {
const tweenVals = new Array();
for (let idx = 0; idx < b.length; idx++) {
tweenVals.push(c[idx]);
}
// Emitt end value of arry
observer.next(tweenVals);
}
else {
// Emitt end value
observer.next(c);
}
// Complete the observable
observer.complete();
}
};
// Initially request the animation frame
requestAnimationFrame(sample);
});
}
RxjsTween.createTween = createTween;
function linear(t, b, pc, d) {
const c = pc - b;
return c * t / d + b;
}
RxjsTween.linear = linear;
function easeInOutQuad(t, b, pc, d) {
const c = pc - b;
if ((t /= d / 2) < 1) {
return c / 2 * t * t + b;
}
else {
return -c / 2 * ((--t) * (t - 2) - 1) + b;
}
}
RxjsTween.easeInOutQuad = easeInOutQuad;
})(RxjsTween || (RxjsTween = {}));
class PerformanceMonitorComponent {
constructor(changeDetector) {
this.changeDetector = changeDetector;
this.fps = -1;
this.fpsMin = -1;
this.fpsMax = -1;
this.framesCnt = 0;
this.timeLastFpsCalc = 0;
this.fpsHistory = new Array();
this.maxHistoryLength = 60;
}
/**
* Method to be called at the end of a frame
*/
end() {
// When called the first time, set the current time and return
if (this.timeLastFpsCalc === 0) {
this.timeLastFpsCalc = Date.now();
return;
}
// Increase the frames counter
this.framesCnt++;
// Get the milliseconds elapsed since January 1, 1970 for the current frame
const currentFrameTime = Date.now();
// Calculate the FPS only every second
if (currentFrameTime >= this.timeLastFpsCalc + 1000) {
// Calculate the frames per second
this.fps = this.framesCnt / (currentFrameTime - this.timeLastFpsCalc) * 1000;
// Calculate the min. frames per second
if (this.fpsMin === -1 || this.fps < this.fpsMin) {
this.fpsMin = this.fps;
}
// Calculate the max. frames per second
if (this.fpsMax === -1 || this.fps > this.fpsMax) {
this.fpsMax = this.fps;
}
// Trigger the change detection
this.changeDetector.detectChanges();
// Set the elapsed time to the timeLastFpsCalc
this.timeLastFpsCalc = currentFrameTime;
// Reset the frames counter
this.framesCnt = 0;
// Add fps to the history array
this.fpsHistory.push(this.fps);
if (this.fpsHistory.length >= this.maxHistoryLength) {
this.fpsHistory.shift();
}
// Create / Update chart
const canvasEl = this.chart.nativeElement;
const ctx = canvasEl.getContext('2d');
if (ctx) {
ctx.fillStyle = 'rgb(30, 30, 30)';
ctx.fillRect(0, 0, canvasEl.width, canvasEl.height);
ctx.strokeStyle = 'rgb(255, 255, 255)';
ctx.fillStyle = 'rgb(255, 255, 255)';
ctx.beginPath();
ctx.moveTo(0, canvasEl.height);
for (let idx = 0; idx <= this.fpsHistory.length; idx++) {
ctx.lineTo(canvasEl.width / this.maxHistoryLength * idx, canvasEl.height - (this.fpsHistory[idx] / this.fpsMax * canvasEl.height));
if (idx === this.fpsHistory.length - 1) {
ctx.lineTo(canvasEl.width / this.maxHistoryLength * idx, canvasEl.height);
}
}
ctx.fill();
}
}
}
}
PerformanceMonitorComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.1.1", ngImport: i0, type: PerformanceMonitorComponent, deps: [{ token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component });
PerformanceMonitorComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "13.1.1", type: PerformanceMonitorComponent, selector: "acuw-performance-monitor", viewQueries: [{ propertyName: "chart", first: true, predicate: ["chart"], descendants: true }], ngImport: i0, template: "<div id=\"pm-container\">\r\n <div id=\"fps-display-container\">\r\n <span id=\"fps-display\">FPS: {{ fps != -1 ? (fps | number: '1.0-0') : '-' }}</span>\r\n <div id=\"min-max-display\">\r\n <span id=\"max-fps-display\">{{ fpsMax != -1 ? (fpsMax | number: '1.0-0') : '-' }} max</span>\r\n <span id=\"min-fps-display\">{{ fpsMin != -1 ? (fpsMin | number: '1.0-0') : '-' }} min</span>\r\n </div>\r\n </div>\r\n <canvas #chart width=\"90\" height=\"40\"></canvas>\r\n</div>", styles: [":host{--position: absolute;--color: rgb(255, 255, 255);--background-color: rgba(0, 0, 0, .8);--top: 5px;--left: 5px;--width: 100px;--padding: 5px;--display: flex;--flex-direction: column;--align-items: center}#pm-container{position:var(--position);color:var(--color);background-color:var(--background-color);top:5px;left:5px;width:100px;padding:5px;display:var(--display);flex-direction:var(--flex-direction);align-items:var(--align-items)}#fps-display-container{display:flex;flex-direction:row;align-items:center}#min-max-display{display:flex;flex-direction:column;font-size:10px;padding-left:5px}\n"], pipes: { "number": i2.DecimalPipe }, changeDetection: i0.ChangeDetectionStrategy.OnPush });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.1.1", ngImport: i0, type: PerformanceMonitorComponent, decorators: [{
type: Component,
args: [{ selector: 'acuw-performance-monitor', changeDetection: ChangeDetectionStrategy.OnPush, template: "<div id=\"pm-container\">\r\n <div id=\"fps-display-container\">\r\n <span id=\"fps-display\">FPS: {{ fps != -1 ? (fps | number: '1.0-0') : '-' }}</span>\r\n <div id=\"min-max-display\">\r\n <span id=\"max-fps-display\">{{ fpsMax != -1 ? (fpsMax | number: '1.0-0') : '-' }} max</span>\r\n <span id=\"min-fps-display\">{{ fpsMin != -1 ? (fpsMin | number: '1.0-0') : '-' }} min</span>\r\n </div>\r\n </div>\r\n <canvas #chart width=\"90\" height=\"40\"></canvas>\r\n</div>", styles: [":host{--position: absolute;--color: rgb(255, 255, 255);--background-color: rgba(0, 0, 0, .8);--top: 5px;--left: 5px;--width: 100px;--padding: 5px;--display: flex;--flex-direction: column;--align-items: center}#pm-container{position:var(--position);color:var(--color);background-color:var(--background-color);top:5px;left:5px;width:100px;padding:5px;display:var(--display);flex-direction:var(--flex-direction);align-items:var(--align-items)}#fps-display-container{display:flex;flex-direction:row;align-items:center}#min-max-display{display:flex;flex-direction:column;font-size:10px;padding-left:5px}\n"] }]
}], ctorParameters: function () { return [{ type: i0.ChangeDetectorRef }]; }, propDecorators: { chart: [{
type: ViewChild,
args: ['chart']
}] } });
class ImageAsParticlesComponent {
constructor(ngZone) {
this.ngZone = ngZone;
// Declare variables
this.renderer = new WebGLRenderer({
antialias: true,
alpha: true,
});
this.scene = new Scene();
this.clock = new Clock(true);
this.texture = new Texture();
this.width = 0;
this.height = 0;
this.touch = new TouchTexture();
this.mouse = new Vector2();
this.raycaster = new Raycaster();
this.pImageUrl = '';
this.pImageChanging = false;
this.gestureInfo$ = interval(2000);
this.gestureInfoSubscription = new Subscription();
this.showTouchGestureInfo = false;
this.justifyContent = 'center';
this.alignItems = 'center';
this.backgroundColor = '#000000';
this.imageWidth = '100%';
this.imageHeight = '100%';
this.animationEnabled = true;
this.showPerformanceMonitor = false;
}
// Inputs
set imageUrl(imageUrl) {
this.pImageUrl = imageUrl;
if (this.pImageChanging === true) {
return;
}
if (this.mesh != null) {
this.pImageChanging = true;
this.triggerImageChange();
}
}
get imageUrl() {
return this.pImageUrl;
}
set horizontalAlignment(horizontalAlignment) {
switch (horizontalAlignment) {
case 'start':
this.justifyContent = 'flex-start';
break;
case 'center':
this.justifyContent = 'center';
break;
case 'end':
this.justifyContent = 'flex-end';
break;
default:
this.justifyContent = 'center';
break;
}
}
get horizontalAlignment() {
return this.justifyContent;
}
set verticalAlignment(verticalAlignment) {
switch (verticalAlignment) {
case 'top':
this.alignItems = 'flex-start';
break;
case 'center':
this.alignItems = 'center';
break;
case 'bottom':
this.alignItems = 'flex-end';
break;
default:
this.alignItems = 'center';
break;
}
}
get verticalAlignment() {
return this.alignItems;
}
ngAfterViewInit() {
if (this.pImageUrl === '') {
return;
}
const canvasWidth = this.canvasRef.nativeElement.clientWidth;
const canvasHeight = this.canvasRef.nativeElement.clientHeight;
// Set camera
this.camera = new PerspectiveCamera(50, canvasWidth / canvasHeight, 1, 10000);
this.camera.position.z = 300;
// Init particles
this.initParticles(this.pImageUrl);
// Init renderer
this.renderer.setSize(canvasWidth - 1, canvasHeight);
this.canvasRef.nativeElement.appendChild(this.renderer.domElement);
// Start animation
this.animate();
}
ngOnDestroy() {
this.scene.clear();
this.renderer.clear();
this.texture.dispose();
this.renderer.dispose();
}
/**
* Creates the particles depending on the image and initializes the touch canvas
* @param url url of the image
*/
initParticles(url) {
const loader = new TextureLoader();
loader.load(url, (texture) => {
this.texture = texture;
this.texture.minFilter = LinearFilter;
this.texture.magFilter = LinearFilter;
this.texture.format = RGBAFormat;
this.width = texture.image.width;
this.height = texture.image.height;
this.initPoints(true);
this.initHitArea();
this.initTouch();
this.resize();
this.show();
});
}
/**
* Initializes the points
* @param discard discard pixels darker than threshold #22
*/
initPoints(discard) {
const numPoints = this.width * this.height;
let numVisible = numPoints;
let threshold = 0;
let originalColors = new Float32Array();
if (discard) {
// discard pixels darker than threshold #22
numVisible = 0;
threshold = 34;
const img = this.texture.image;
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
canvas.width = this.width;
canvas.height = this.height;
if (ctx != null) {
ctx.scale(1, -1);
ctx.drawImage(img, 0, 0, this.width, this.height * -1);
const imgData = ctx.getImageData(0, 0, canvas.width, canvas.height);
originalColors = Float32Array.from(imgData.data);
for (let i = 0; i < numPoints; i++) {
if (originalColors[i * 4 + 0] > threshold) {
numVisible++;
}
}
}
}
const uniforms = {
uTime: { value: 0 },
uRandom: { value: 1.0 },
uDepth: { value: 2.0 },
uSize: { value: 0.0 },
uTextureSize: { value: new Vector2(this.width, this.height) },
uTexture: { value: this.texture },
uTouch: { value: null },
};
const shaders = new Shaders();
const material = new RawShaderMaterial({
uniforms,
vertexShader: shaders.particleVertex,
fragmentShader: shaders.particleFragment,
depthTest: false,
transparent: true,
// blending: THREE.AdditiveBlending
});
const geometry = new InstancedBufferGeometry();
// positions
const positions = new BufferAttribute(new Float32Array(4 * 3), 3);
positions.setXYZ(0, -0.5, 0.5, 0.0);
positions.setXYZ(1, 0.5, 0.5, 0.0);
positions.setXYZ(2, -0.5, -0.5, 0.0);
positions.setXYZ(3, 0.5, -0.5, 0.0);
geometry.setAttribute('position', positions);
// uvs
const uvs = new BufferAttribute(new Float32Array(4 * 2), 2);
uvs.setXY(0, 0.0, 0.0);
uvs.setXY(1, 1.0, 0.0);
uvs.setXY(2, 0.0, 1.0);
uvs.setXY(3, 1.0, 1.0);
geometry.setAttribute('uv', uvs);
// index
geometry.setIndex(new BufferAttribute(new Uint16Array([0, 2, 1, 2, 3, 1]), 1));
const indices = new Uint16Array(numVisible);
const offsets = new Float32Array(numVisible * 3);
const angles = new Float32Array(numVisible);
for (let i = 0, j = 0; i < numPoints; i++) {
if (discard && originalColors[i * 4 + 0] <= threshold) {
continue;
}
offsets[j * 3 + 0] = i % this.width;
offsets[j * 3 + 1] = Math.floor(i / this.width);
indices[j] = i;
angles[j] = Math.random() * Math.PI;
j++;
}
geometry.setAttribute('pindex', new InstancedBufferAttribute(indices, 1, false));
geometry.setAttribute('offset', new InstancedBufferAttribute(offsets, 3, false));
geometry.setAttribute('angle', new InstancedBufferAttribute(angles, 1, false));
this.mesh = new Mesh(geometry, material);
const object3d = new Object3D();
object3d.add(this.mesh);
this.scene.add(object3d);
}
/**
* Initializes the touch area
*/
initTouch() {
this.mesh.material.uniforms.uTouch.value =
this.touch.texture;
}
/**
* Initializes the hit area
*/
initHitArea() {
const geometry = new PlaneGeometry(this.width, this.height, 1, 1);
const material = new MeshBasicMaterial({
color: 0xffffff,
wireframe: true,
depthTest: false,
});
material.visible = false;
this.hitArea = new Mesh(geometry, material);
this.mesh.add(this.hitArea);
}
/**
* animation for showing the particles
* @param time time of animation in ms
*/
show(time = 1000) {
// Tween in
this.ngZone.runOutsideAngular(() => {
RxjsTween.createTween(RxjsTween.easeInOutQuad, [0.5, 0.0, 70.0], [1.5, 2.0, 4.0], time).subscribe((val) => {
this.mesh.material.uniforms.uSize.value =
val[0];
this.mesh.material.uniforms.uRandom.value =
val[1];
this.mesh.material.uniforms.uDepth.value =
val[2];
}, () => { }, () => {
this.pImageChanging = false;
});
});
}
/**
* animation for tween out the particles and destroy everything
* @param time time of animation in ms
*/
triggerImageChange(time = 1000) {
const uSizeStart = this.mesh.material.uniforms.uSize
.value;
const uRandomStart = this.mesh.material.uniforms
.uRandom.value;
const uDepth = this.mesh.material.uniforms.uDepth
.value;
this.ngZone.runOutsideAngular(() => {
// Tween out
RxjsTween.createTween(RxjsTween.easeInOutQuad, [uSizeStart, uRandomStart, uDepth], [0.0, 5.0, -20.0], time).subscribe((val) => {
this.mesh.material.uniforms.uSize.value =
val[0];
this.mesh.material.uniforms.uRandom.value =
val[1];
this.mesh.material.uniforms.uDepth.value =
val[2];
}, () => { }, () => {
if (this.mesh != null) {
if (this.mesh.parent != null) {
this.mesh.parent.remove(this.mesh);
}
this.mesh.geometry.dispose();
this.mesh.material.dispose();
}
if (this.hitArea != null) {
if (this.hitArea.parent != null) {
this.hitArea.parent.remove(this.hitArea);
}
this.hitArea.geometry.dispose();
this.hitArea.material.dispose();
}
this.initParticles(this.pImageUrl);
this.pImageChanging = false;
});
});
}
/**
* Method for triggering the animation
*/
animate() {
this.ngZone.runOutsideAngular(() => {
window.requestAnimationFrame(() => this.animate());
if (this.animationEnabled === true) {
const delta = this.clock.getDelta();
if (this.mesh != null) {
if (this.touch) {
this.touch.update();
}
this.mesh.material.uniforms.uTime.value +=
delta;
}
this.renderer.render(this.scene, this.camera);
}
if (this.performanceMonitor && this.showPerformanceMonitor) {
this.performanceMonitor.end();
}
});
}
/**
* Handle mouse move event
* @param event mouse event
*/
onMouseMove(event) {
// getBoundingClientRect retruns the distance in pixels of the top left corner of the element
// to the top left corner of the viewport
const domRect = this.canvasRef.nativeElement.getBoundingClientRect();
// get the offset distance between the canvas, which contains the particles, to the outer container element
const canvasEl = this.canvasRef.nativeElement
.children[0];
// Calculate the relative mouse position
this.mouse.x =
((event.clientX - domRect.left - canvasEl.offsetLeft) /
canvasEl.clientWidth) *
2 -
1;
this.mouse.y =
(-(event.clientY - domRect.top - canvasEl.offsetTop) /
canvasEl.clientHeight) *
2 +
1;
// console.info('raw: x= ' + event.clientX + ' , y= ' + event.clientY);
// console.info('normalized: x= ' + this.mouse.x + ' , y= ' + this.mouse.y);
this.raycaster.setFromCamera(this.mouse, this.camera);
if (this.hitArea === undefined) {
return;
}
const intersects = this.raycaster.intersectObject(this.hitArea);
if (intersects !== undefined &&
intersects.length > 0 &&
this.touch &&
intersects[0].uv !== undefined) {
this.touch.addTouch(intersects[0].uv.x, intersects[0].uv.y);
}
}
/**
* Handle touch move envent
* @param event mouse event
*/
onTouchMove(event) {
if (event.touches.length < 2) {
this.showTouchGestureInfo = true;
this.gestureInfoSubscription.unsubscribe();
this.gestureInfoSubscription = this.gestureInfo$.subscribe({
next: () => {
this.showTouchGestureInfo = false;
this.gestureInfoSubscription.unsubscribe();
},
});
return;
}
event.preventDefault();
// getBoundingClientRect retruns the distance in pixels of the top left corner of the element
// to the top left corner of the viewport
const domRect = this.canvasRef.nativeElement.getBoundingClientRect();
// get the offset distance between the canvas, which contains the particles, to the outer container element
const canvasEl = this.canvasRef.nativeElement
.children[0];
// Calculate the relative mouse position
this.mouse.x =
((event.touches[0].clientX - domRect.left - canvasEl.offsetLeft) /
canvasEl.clientWidth) *
2 -
1;
this.mouse.y =
(-(event.touches[0].clientY - domRect.top - canvasEl.offsetTop) /
canvasEl.clientHeight) *
2 +
1;
this.raycaster.setFromCamera(this.mouse, this.camera);
const intersects = this.raycaster.intersectObject(this.hitArea);
if (intersects !== undefined &&
intersects.length > 0 &&
this.touch &&
intersects[0].uv !== undefined) {
this.touch.addTouch(intersects[0].uv.x, intersects[0].uv.y);
}
}
resize() {
if (this.height !== undefined) {
this.camera.aspect =
this.canvasRef.nativeElement.clientWidth /
this.canvasRef.nativeElement.clientHeight;
this.camera.updateProjectionMatrix();
const fovHeight = 2 *
Math.tan((this.camera.fov * Math.PI) / 180 / 2) *
this.camera.position.z;
const scale = fovHeight / this.height;
this.mesh.scale.set(scale, scale, 1);
// this.hitArea.scale.set(scale, scale, 1);
if (this.renderer !== undefined) {
const width = this.imageWidth == null
? this.canvasRef.nativeElement.clientWidth
: this.distanceAsNumber(this.imageWidth, this.canvasRef.nativeElement.clientWidth);
const height = this.imageHeight == null
? this.canvasRef.nativeElement.clientHeight
: this.distanceAsNumber(this.imageHeight, this.canvasRef.nativeElement.clientHeight);
this.renderer.setSize(width, height);
}
}
}
distanceAsNumber(distance, parentDistance) {
let returnVal = 0;
if (distance.includes('px')) {
returnVal = Number.parseInt(distance.replace('px', ''), 10);
}
else if (distance.includes('%')) {
returnVal =
(Number.parseInt(distance.replace('%', ''), 10) / 100) * parentDistance;
}
else {
returnVal = Number.parseInt(distance, 10);
}
return returnVal;
}
}
ImageAsParticlesComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.1.1", ngImport: i0, type: ImageAsParticlesComponent, deps: [{ token: i0.NgZone }], target: i0.ɵɵFactoryTarget.Component });
ImageAsParticlesComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "13.1.1", type: ImageAsParticlesComponent, selector: "lib-image-as-particles", inputs: { imageUrl: "imageUrl", backgroundColor: "backgroundColor", imageWidth: "imageWidth", imageHeight: "imageHeight", horizontalAlignment: "horizontalAlignment", verticalAlignment: "verticalAlignment", animationEnabled: "animationEnabled", showPerformanceMonitor: "showPerformanceMonitor" }, host: { listeners: { "window:resize": "resize()" } }, viewQueries: [{ propertyName: "canvasRef", first: true, predicate: ["container"], descendants: true }, { propertyName: "performanceMonitor", first: true, predicate: ["performanceMonitor"], descendants: true }], ngImport: i0, template: `
<div
#container
class="threejs-container"
[style.background-color]="backgroundColor"
[style.justify-content]="justifyContent"
[style.align-items]="alignItems"
(mousemove)="onMouseMove($event)"
(touchmove)="onTouchMove($event)"
></div>
<div
*ngIf="showTouchGestureInfo == true"
class="touch-gesture-info"
[@showHideGestureInformation]
>
<div>
<span>Use two fingers for touch animation</span>
<svg
xmlns="http://www.w3.org/2000/svg"
enable-background="new 0 0 24 24"
viewBox="0 0 24 24"
fill="white"
width="18px"
height="18px"
>
<g><rect fill="none" height="24" width="24" x="0" /></g>
<g>
<g>
<g>
<path
d="M9,11.24V7.5C9,6.12,10.12,5,11.5,5S14,6.12,14,7.5v3.74c1.21-0.81,2-2.18,2-3.74C16,5.01,13.99,3,11.5,3S7,5.01,7,7.5 C7,9.06,7.79,10.43,9,11.24z M18.84,15.87l-4.54-2.26c-0.17-0.07-0.35-0.11-0.54-0.11H13v-6C13,6.67,12.33,6,11.5,6 S10,6.67,10,7.5v10.74c-3.6-0.76-3.54-0.75-3.67-0.75c-0.31,0-0.59,0.13-0.79,0.33l-0.79,0.8l4.94,4.94 C9.96,23.83,10.34,24,10.75,24h6.79c0.75,0,1.33-0.55,1.44-1.28l0.75-5.27c0.01-0.07,0.02-0.14,0.02-0.2 C19.75,16.63,19.37,16.09,18.84,15.87z"
/>
</g>
</g>
</g>
</svg>
</div>
</div>
<acuw-performance-monitor
*ngIf="showPerformanceMonitor"
#performanceMonitor
></acuw-performance-monitor>
`, isInline: true, styles: [".threejs-container{position:relative;display:flex;justify-content:center;align-items:center;width:100%;height:100%;background-color:#222}.touch-gesture-info{position:absolute;width:100%;display:flex;flex-direction:row;justify-content:center;top:20px;color:#fff}.touch-gesture-info div{background-color:#0000004d;display:flex;flex-direction:row;padding:6px 10px;border-radius:5px}\n"], components: [{ type: PerformanceMonitorComponent, selector: "acuw-performance-monitor" }], directives: [{ type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }], animations: [
trigger('showHideGestureInformation', [
transition(':enter', [
style({ opacity: '0' }),
animate('300ms ease-in', style({ opacity: '1' })),
]),
transition(':leave', [
style({ opacity: '1' }),
animate('300ms ease-in', style({ opacity: '0' })),
]),
]),
] });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.1.1", ngImport: i0, type: ImageAsParticlesComponent, decorators: [{
type: Component,
args: [{
selector: 'lib-image-as-particles',
template: `
<div
#container
class="threejs-container"
[style.background-color]="backgroundColor"
[style.justify-content]="justifyContent"
[style.align-items]="alignItems"
(mousemove)="onMouseMove($event)"
(touchmove)="onTouchMove($event)"
></div>
<div
*ngIf="showTouchGestureInfo == true"
class="touch-gesture-info"
[@showHideGestureInformation]
>
<div>
<span>Use two fingers for touch animation</span>
<svg
xmlns="http://www.w3.org/2000/svg"
enable-background="new 0 0 24 24"
viewBox="0 0 24 24"
fill="white"
width="18px"
height="18px"
>
<g><rect fill="none" height="24" width="24" x="0" /></g>
<g>
<g>
<g>
<path
d="M9,11.24V7.5C9,6.12,10.12,5,11.5,5S14,6.12,14,7.5v3.74c1.21-0.81,2-2.18,2-3.74C16,5.01,13.99,3,11.5,3S7,5.01,7,7.5 C7,9.06,7.79,10.43,9,11.24z M18.84,15.87l-4.54-2.26c-0.17-0.07-0.35-0.11-0.54-0.11H13v-6C13,6.67,12.33,6,11.5,6 S10,6.67,10,7.5v10.74c-3.6-0.76-3.54-0.75-3.67-0.75c-0.31,0-0.59,0.13-0.79,0.33l-0.79,0.8l4.94,4.94 C9.96,23.83,10.34,24,10.75,24h6.79c0.75,0,1.33-0.55,1.44-1.28l0.75-5.27c0.01-0.07,0.02-0.14,0.02-0.2 C19.75,16.63,19.37,16.09,18.84,15.87z"
/>
</g>
</g>
</g>
</svg>
</div>
</div>
<acuw-performance-monitor
*ngIf="showPerformanceMonitor"
#performanceMonitor
></acuw-performance-monitor>
`,
styles: [
`
.threejs-container {
position: relative;
display: flex;
justify-content: center;
align-items: center;
width: 100%;
height: 100%;
background-color: #222222;
}
.touch-gesture-info {
position: absolute;
width: 100%;
display: flex;
flex-direction: row;
justify-content: center;
top: 20px;
color: white;
}
.touch-gesture-info div {
background-color: rgba(0, 0, 0, 0.3);
display: flex;
flex-direction: row;
padding: 6px 10px 6px 10px;
border-radius: 5px;
}
`,
],
animations: [
trigger('showHideGestureInformation', [
transition(':enter', [
style({ opacity: '0' }),
animate('300ms ease-in', style({ opacity: '1' })),
]),
transition(':leave', [
style({ opacity: '1' }),
animate('300ms ease-in', style({ opacity: '0' })),
]),
]),
],
}]
}], ctorParameters: function () { return [{ type: i0.NgZone }]; }, propDecorators: { imageUrl: [{
type: Input
}], backgroundColor: [{
type: Input
}], imageWidth: [{
type: Input
}], imageHeight: [{
type: Input
}], horizontalAlignment: [{
type: Input
}], verticalAlignment: [{
type: Input
}], animationEnabled: [{
type: Input
}], showPerformanceMonitor: [{
type: Input
}], canvasRef: [{
type: ViewChild,
args: ['container']
}], performanceMonitor: [{
type: ViewChild,
args: ['performanceMonitor']
}], resize: [{
type: HostListener,
args: ['window:resize']
}] } });
class PerformanceMonitorModule {
}
PerformanceMonitorModule.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.1.1", ngImport: i0, type: PerformanceMonitorModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
PerformanceMonitorModule.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "12.0.0", version: "13.1.1", ngImport: i0, type: PerformanceMonitorModule, declarations: [PerformanceMonitorComponent], imports: [CommonModule], exports: [PerformanceMonitorComponent] });
PerformanceMonitorModule.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "13.1.1", ngImport: i0, type: PerformanceMonitorModule, imports: [[
CommonModule
]] });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.1.1", ngImport: i0, type: PerformanceMonitorModule, decorators: [{
type: NgModule,
args: [{
declarations: [
PerformanceMonitorComponent
],
imports: [
CommonModule
],
exports: [
PerformanceMonitorComponent
]
}]
}] });
class ImageAsParticlesModule {
}
ImageAsParticlesModule.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.1.1", ngImport: i0, type: ImageAsParticlesModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
ImageAsParticlesModule.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "12.0.0", version: "13.1.1", ngImport: i0, type: ImageAsParticlesModule, declarations: [ImageAsParticlesComponent], imports: [CommonModule, PerformanceMonitorModule], exports: [ImageAsParticlesComponent] });
ImageAsParticlesModule.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "13.1.1", ngImport: i0, type: ImageAsParticlesModule, imports: [[
CommonModule, PerformanceMonitorModule
]] });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.1.1", ngImport: i0, type: ImageAsParticlesModule, decorators: [{
type: NgModule,
args: [{
declarations: [ImageAsParticlesComponent],
imports: [
CommonModule, PerformanceMonitorModule
],
exports: [ImageAsParticlesComponent]
}]
}] });
var Direction;
(function (Direction) {
Direction[Direction["forward"] = 0] = "forward";
Direction[Direction["backward"] = 1] = "backward";
})(Direction || (Direction = {}));
class ImageTransitionShaders {
constructor() {
this.vertex = `varying vec2 vUv;void main() {vUv = uv;gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );}`;
this.splitTransitionFrag = `
uniform float progress;
uniform float intensity;
uniform sampler2D texture1;
uniform sampler2D texture2;
uniform vec4 resolution1;
uniform vec4 resolution2;
varying vec2 vUv;
mat2 rotate(float a) {
float s = sin(a);
float c = cos(a);
return mat2(c, -s, s, c);
}
void main() {
vec2 newUV1 = (vUv - vec2(0.5,0.5))*resolution1.zw + vec2(0.5,0.5);
vec2 newUV2 = (vUv - vec2(0.5,0.5))*resolution2.zw + vec2(0.5,0.5);
vec2 uvDivided1 = fract(newUV1*vec2(intensity,1.));
vec2 uvDivided2 = fract(newUV2*vec2(intensity,1.));
vec2 uvDisplaced1 = newUV1 + rotate(3.1415926/4.)*uvDivided1*progress*0.1;
vec2 uvDisplaced2 = newUV2 + rotate(3.1415926/4.)*uvDivided2*(1. - progress)*0.1;
vec4 t1 = texture2D(texture1,uvDisplaced1);
vec4 t2 = texture2D(texture2,uvDisplaced2);
// Use black background color
// Top right
vec2 tr1 = step(newUV1, vec2(1.0, 1.0));
vec2 tr2 = step(newUV2, vec2(1.0, 1.0));
float pct1 = tr1.x * tr1.y;
float pct2 = tr2.x * tr2.y;
// Bottom left
vec2 bl1 = step(vec2(0.0, 0.0), newUV1);
vec2 bl2 = step(vec2(0.0, 0.0), newUV2);
pct1 *= bl1.x * bl1.y;
pct2 *= bl2.x * bl2.y;
vec4 t1wb = t1 * vec4(pct1,pct1,pct1,1.0);
vec4 t2wb = t2 * vec4(pct2,pct2,pct2,1.0);
gl_FragColor = mix(t1wb, t2wb, progress);
}
`;
this.fadeFrag = `
uniform float progress;
uniform sampler2D texture1;
uniform sampler2D texture2;
uniform vec4 resolution1;
uniform vec4 resolution2;
varying vec2 vUv;
mat2 rotate(float a) {
float s = sin(a);
float c = cos(a);
return mat2(c, -s, s, c);
}
void main() {
vec2 newUV1 = (vUv - vec2(0.5,0.5))*resolution1.zw + vec2(0.5,0.5);
vec2 newUV2 = (vUv - vec2(0.5,0.5))*resolution2.zw + vec2(0.5,0.5);
vec2 uvDisplaced1 = newUV1 + vec2(1.0,0)*progress*0.1;
vec2 uvDisplaced2 = newUV2 + vec2(1.0,0)*(1. - progress)*0.1;
vec4 t1 = texture2D(texture1,uvDisplaced1);
vec4 t2 = texture2D(texture2,uvDisplaced2);
// Use black background color
// Top right
vec2 tr1 = step(newUV1, vec2(1.0, 1.0));
vec2 tr2 = step(newUV2, vec2(1.0, 1.0));
float pct1 = tr1.x * tr1.y;
float pct2 = tr2.x * tr2.y;
// Bottom left
vec2 bl1 = step(vec2(0.0, 0.0), newUV1);
vec2 bl2 = step(vec2(0.0, 0.0), newUV2);
pct1 *= bl1.x * bl1.y;
pct2 *= bl2.x * bl2.y;
vec4 t1wb = t1 * vec4(pct1,pct1,pct1,1.0);
vec4 t2wb = t2 * vec4(pct2,pct2,pct2,1.0);
gl_FragColor = mix(t1wb, t2wb, progress);
}
`;
this.noiseFrag = `
uniform float time;
uniform float progress;
uniform float width;
uniform float scaleX;
uniform float scaleY;
uniform sampler2D texture1;
uniform sampler2D texture2;
uniform sampler2D displacement;
uniform vec4 resolution1;
uniform vec4 resolution2;
varying vec2 vUv;
varying vec4 vPosition;
// Classic Perlin 3D Noise
// by Stefan Gustavson
//
vec4 permute(vec4 x){return mod(((x*34.0)+1.0)*x, 289.0);}
vec4 taylorInvSqrt(vec4 r){return 1.79284291400159 - 0.85373472095314 * r;}
vec4 fade(vec4 t) {return t*t*t*(t*(t*6.0-15.0)+10.0);}
float cnoise(vec4 P){
;
vec4 Pi0 = floor(P); // Integer part for indexing
vec4 Pi1 = Pi0 + 1.0; // Integer part + 1
Pi0 = mod(Pi0, 289.0);
Pi1 = mod(Pi1, 289.0);
vec4 Pf0 = fract(P); // Fractional part for interpolation
vec4 Pf1 = Pf0 - 1.0; // Fractional part - 1.0
vec4 ix = vec4(Pi0.x, Pi1.x, Pi0.x, Pi1.x);
vec4 iy = vec4(Pi0.yy, Pi1.yy);
vec4 iz0 = vec4(Pi0.zzzz);
vec4 iz1 = vec4(Pi1.zzzz);
vec4 iw0 = vec4(Pi0.wwww);
vec4 iw1 = vec4(Pi1.wwww);
vec4 ixy = permute(permute(ix) + iy);
vec4 ixy0 = permute(ixy + iz0);
vec4 ixy1 = permute(ixy + iz1);
vec4 ixy00 = permute(ixy0 + iw0);
vec4 ixy01 = permute(ixy0 + iw1);
vec4 ixy10 = permute(ixy1 + iw0);
vec4 ixy11 = permute(ixy1 + iw1);
vec4 gx00 = ixy00 / 7.0;
vec4 gy00 = floor(gx00) / 7.0;
vec4 gz00 = floor(gy00) / 6.0;
gx00 = fract(gx00) - 0.5;
gy00 = fract(gy00) - 0.5;
gz00 = fract(gz00) - 0.5;
vec4 gw00 = vec4(0.75) - abs(gx00) - abs(gy00) - abs(gz00);
vec4 sw00 = step(gw00, vec4(0.0));
gx00