Screwdriver Set, 25 In 1 With 24 Piece Mini Pocket Screwdriver Set, Sm - Acrone | Screwdriver Set, 25 In 1 With 24 Piece Mini Pocket Screwdriver Set, Sm - Acrone | Screwdriver Set, 25 In 1 With 24 Piece Mini Pocket Screwdriver Set, Sm - Acrone | Screwdriver Set, 25 In 1 With 24 Piece Mini Pocket Screwdriver Set, Sm - Acrone | Screwdriver Set, 25 In 1 With 24 Piece Mini Pocket Screwdriver Set, Sm - Acrone | Screwdriver Set, 25 In 1 With 24 Piece Mini Pocket Screwdriver Set, Sm - Acrone | Screwdriver Set, 25 In 1 With 24 Piece Mini Pocket Screwdriver Set, Sm - Acrone | Screwdriver Set, 25 In 1 With 24 Piece Mini Pocket Screwdriver Set, Sm - Acrone | Screwdriver Set, 25 In 1 With 24 Piece Mini Pocket Screwdriver Set, Sm - Acrone | Screwdriver Set, 25 In 1 With 24 Piece Mini Pocket Screwdriver Set, Sm - Acrone | Screwdriver Set, 25 In 1 With 24 Piece Mini Pocket Screwdriver Set, Sm - Acrone | Screwdriver Set, 25 In 1 With 24 Piece Mini Pocket Screwdriver Set, Sm - Acrone | Screwdriver Set, 25 In 1 With 24 Piece Mini Pocket Screwdriver Set, Sm - Acrone | Screwdriver Set, 25 In 1 With 24 Piece Mini Pocket Screwdriver Set, Sm - Acrone | Screwdriver Set, 25 In 1 With 24 Piece Mini Pocket Screwdriver Set, Sm - Acrone | Screwdriver Set, 25 In 1 With 24 Piece Mini Pocket Screwdriver Set, Sm - Acrone | Screwdriver Set, 25 In 1 With 24 Piece Mini Pocket Screwdriver Set, Sm - Acrone | Screwdriver Set, 25 In 1 With 24 Piece Mini Pocket Screwdriver Set, Sm - Acrone | Screwdriver Set, 25 In 1 With 24 Piece Mini Pocket Screwdriver Set, Sm - Acrone | Screwdriver Set, 25 In 1 With 24 Piece Mini Pocket Screwdriver Set, Sm - Acrone | Screwdriver Set, 25 In 1 With 24 Piece Mini Pocket Screwdriver Set, Sm - Acrone | Screwdriver Set, 25 In 1 With 24 Piece Mini Pocket Screwdriver Set, Sm - Acrone | Screwdriver Set, 25 In 1 With 24 Piece Mini Pocket Screwdriver Set, Sm - Acrone | Screwdriver Set, 25 In 1 With 24 Piece Mini Pocket Screwdriver Set, Sm - Acrone | Screwdriver Set, 25 In 1 With 24 Piece Mini Pocket Screwdriver Set, Sm - Acrone | Screwdriver Set, 25 In 1 With 24 Piece Mini Pocket Screwdriver Set, Sm - Acrone | Screwdriver Set, 25 In 1 With 24 Piece Mini Pocket Screwdriver Set, Sm - Acrone | Screwdriver Set, 25 In 1 With 24 Piece Mini Pocket Screwdriver Set, Sm - Acrone | Screwdriver Set, 25 In 1 With 24 Piece Mini Pocket Screwdriver Set, Sm - Acrone | Screwdriver Set, 25 In 1 With 24 Piece Mini Pocket Screwdriver Set, Sm - Acrone | Screwdriver Set, 25 In 1 With 24 Piece Mini Pocket Screwdriver Set, Sm - Acrone | Screwdriver Set, 25 In 1 With 24 Piece Mini Pocket Screwdriver Set, Sm - Acrone | Screwdriver Set, 25 In 1 With 24 Piece Mini Pocket Screwdriver Set, Sm - Acrone | Screwdriver Set, 25 In 1 With 24 Piece Mini Pocket Screwdriver Set, Sm - Acrone | Screwdriver Set, 25 In 1 With 24 Piece Mini Pocket Screwdriver Set, Sm - Acrone | Screwdriver Set, 25 In 1 With 24 Piece Mini Pocket Screwdriver Set, Sm - Acrone | Screwdriver Set, 25 In 1 With 24 Piece Mini Pocket Screwdriver Set, Sm - Acrone | Screwdriver Set, 25 In 1 With 24 Piece Mini Pocket Screwdriver Set, Sm - Acrone | Screwdriver Set, 25 In 1 With 24 Piece Mini Pocket Screwdriver Set, Sm - Acrone | Screwdriver Set, 25 In 1 With 24 Piece Mini Pocket Screwdriver Set, Sm - Acrone | Screwdriver Set, 25 In 1 With 24 Piece Mini Pocket Screwdriver Set, Sm - Acrone | Screwdriver Set, 25 In 1 With 24 Piece Mini Pocket Screwdriver Set, Sm - Acrone | Screwdriver Set, 25 In 1 With 24 Piece Mini Pocket Screwdriver Set, Sm - Acrone | Screwdriver Set, 25 In 1 With 24 Piece Mini Pocket Screwdriver Set, Sm - Acrone | Screwdriver Set, 25 In 1 With 24 Piece Mini Pocket Screwdriver Set, Sm - Acrone | Screwdriver Set, 25 In 1 With 24 Piece Mini Pocket Screwdriver Set, Sm - Acrone | Screwdriver Set, 25 In 1 With 24 Piece Mini Pocket Screwdriver Set, Sm - Acrone | Screwdriver Set, 25 In 1 With 24 Piece Mini Pocket Screwdriver Set, Sm - Acrone | Screwdriver Set, 25 In 1 With 24 Piece Mini Pocket Screwdriver Set, Sm - Acrone | Screwdriver Set, 25 In 1 With 24 Piece Mini Pocket Screwdriver Set, Sm - Acrone | Screwdriver Set, 25 In 1 With 24 Piece Mini Pocket Screwdriver Set, Sm - Acrone | Screwdriver Set, 25 In 1 With 24 Piece Mini Pocket Screwdriver Set, Sm - Acrone
Direkt zum Inhalt
Product Features
Engineered to protect the most critical areas of your iPhone 17, this case safeguards the top, bottom, and camera while maintaining a slim profile. Designed for durability and precision, it ensures full access to buttons, ports, and wireless charging without adding bulk.
How to Use
How to Apply Your Case:
1. Clean Your Phone: Wipe your iPhone 17 with a soft, dry cloth to remove dust and fingerprints.
2. Align the Case: Position the case so the top, bottom, and camera openings match your phone.
3. Snap On Carefully: Gently press the top and bottom edges onto your phone until it clicks securely in place.
4. Check Fit: Make sure the camera and ports are fully aligned and accessible.
5. Ready to Use: Your phone is now protected and fully functional.
Why You'll Love It
Designed to enhance your lifestyle, this product is reliable, efficient, and built to last. Whether for work or leisure, it’s a perfect fit for your needs.
Why You’ll Love It
This product seamlessly blends functionality and style, offering the perfect solution for your modern lifestyle. Packed with innovative features, durable design, and user-friendly functionality, it’s tailored to meet your everyday needs. Whether it’s work, play, or staying connected, this Case enhances your experience.
Be Sure of Your Choice
This product is designed with precision and reliability, delivering top-notch performance and advanced features that set it apart. Backed by excellent reviews and a commitment to quality, it's the perfect choice for work, play, or everyday convenience. Invest in a product that combines innovation, durability, and user satisfaction.
 
Our brand
Others
High-quality craftsmanship and durability
Compact and travel-friendly
Everyday-enhancing features
Trusted by customers and built to last
Wenn du dich für eine Auswahl entscheidest, wird die Seite komplett aktualisiert.
Wird in einem neuen Fenster geöffnet.
import { useEffect, useRef } from 'react';
import * as THREE from 'three';
import './LiquidEther.css';
export default function LiquidEther({
mouseForce = 20,
cursorSize = 100,
isViscous = false,
viscous = 30,
iterationsViscous = 32,
iterationsPoisson = 32,
dt = 0.014,
BFECC = true,
resolution = 0.5,
isBounce = false,
colors = ['#5227FF', '#FF9FFC', '#B19EEF'],
style = {},
className = '',
autoDemo = true,
autoSpeed = 0.5,
autoIntensity = 2.2,
takeoverDuration = 0.25,
autoResumeDelay = 1000,
autoRampDuration = 0.6
}) {
const mountRef = useRef(null);
const webglRef = useRef(null);
const resizeObserverRef = useRef(null);
const rafRef = useRef(null);
const intersectionObserverRef = useRef(null);
const isVisibleRef = useRef(true);
const resizeRafRef = useRef(null);
useEffect(() => {
if (!mountRef.current) return;
function makePaletteTexture(stops) {
let arr;
if (Array.isArray(stops) && stops.length > 0) {
if (stops.length === 1) {
arr = [stops[0], stops[0]];
} else {
arr = stops;
}
} else {
arr = ['#ffffff', '#ffffff'];
}
const w = arr.length;
const data = new Uint8Array(w * 4);
for (let i = 0; i <
; i++) {
const c = new THREE.Color(arr[i]);
data[i * 4 + 0] = Math.round(c.r * 255);
data[i * 4 + 1] = Math.round(c.g * 255);
data[i * 4 + 2] = Math.round(c.b * 255);
data[i * 4 + 3] = 255;
}
const tex = new THREE.DataTexture(data, w, 1, THREE.RGBAFormat);
tex.magFilter = THREE.LinearFilter;
tex.minFilter = THREE.LinearFilter;
tex.wrapS = THREE.ClampToEdgeWrapping;
tex.wrapT = THREE.ClampToEdgeWrapping;
tex.generateMipmaps = false;
tex.needsUpdate = true;
return tex;
}
const paletteTex = makePaletteTexture(colors);
const bgVec4 = new THREE.Vector4(0, 0, 0, 0); // always transparent
class CommonClass {
constructor() {
this.width = 0;
this.height = 0;
this.aspect = 1;
this.pixelRatio = 1;
this.isMobile = false;
this.breakpoint = 768;
this.fboWidth = null;
this.fboHeight = null;
this.time = 0;
this.delta = 0;
this.container = null;
this.renderer = null;
this.clock = null;
}
init(container) {
this.container = container;
this.pixelRatio = Math.min(window.devicePixelRatio || 1, 2);
this.resize();
this.renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
this.renderer.autoClear = false;
this.renderer.setClearColor(new THREE.Color(0x000000), 0);
this.renderer.setPixelRatio(this.pixelRatio);
this.renderer.setSize(this.width, this.height);
this.renderer.domElement.style.width = '100%';
this.renderer.domElement.style.height = '100%';
this.renderer.domElement.style.display = 'block';
this.clock = new THREE.Clock();
this.clock.start();
}
resize() {
if (!this.container) return;
const rect = this.container.getBoundingClientRect();
this.width = Math.max(1, Math.floor(rect.width));
this.height = Math.max(1, Math.floor(rect.height));
this.aspect = this.width / this.height;
if (this.renderer) this.renderer.setSize(this.width, this.height, false);
}
update() {
this.delta = this.clock.getDelta();
this.time += this.delta;
}
}
const Common = new CommonClass();
class MouseClass {
constructor() {
this.mouseMoved = false;
this.coords = new THREE.Vector2();
this.coords_old = new THREE.Vector2();
this.diff = new THREE.Vector2();
this.timer = null;
this.container = null;
this._onMouseMove = this.onDocumentMouseMove.bind(this);
this._onTouchStart = this.onDocumentTouchStart.bind(this);
this._onTouchMove = this.onDocumentTouchMove.bind(this);
this._onMouseEnter = this.onMouseEnter.bind(this);
this._onMouseLeave = this.onMouseLeave.bind(this);
this._onTouchEnd = this.onTouchEnd.bind(this);
this.isHoverInside = false;
this.hasUserControl = false;
this.isAutoActive = false;
this.autoIntensity = 2.0;
this.takeoverActive = false;
this.takeoverStartTime = 0;
this.takeoverDuration = 0.25;
this.takeoverFrom = new THREE.Vector2();
this.takeoverTo = new THREE.Vector2();
this.onInteract = null;
}
init(container) {
this.container = container;
container.addEventListener('mousemove', this._onMouseMove, false);
container.addEventListener('touchstart', this._onTouchStart, false);
container.addEventListener('touchmove', this._onTouchMove, false);
container.addEventListener('mouseenter', this._onMouseEnter, false);
container.addEventListener('mouseleave', this._onMouseLeave, false);
container.addEventListener('touchend', this._onTouchEnd, false);
}
dispose() {
if (!this.container) return;
this.container.removeEventListener('mousemove', this._onMouseMove, false);
this.container.removeEventListener('touchstart', this._onTouchStart, false);
this.container.removeEventListener('touchmove', this._onTouchMove, false);
this.container.removeEventListener('mouseenter', this._onMouseEnter, false);
this.container.removeEventListener('mouseleave', this._onMouseLeave, false);
this.container.removeEventListener('touchend', this._onTouchEnd, false);
}
setCoords(x, y) {
if (!this.container) return;
if (this.timer) clearTimeout(this.timer);
const rect = this.container.getBoundingClientRect();
const nx = (x - rect.left) / rect.width;
const ny = (y - rect.top) / rect.height;
this.coords.set(nx * 2 - 1, -(ny * 2 - 1));
this.mouseMoved = true;
this.timer = setTimeout(() => {
this.mouseMoved = false;
}, 100);
}
setNormalized(nx, ny) {
this.coords.set(nx, ny);
this.mouseMoved = true;
}
onDocumentMouseMove(event) {
if (this.onInteract) this.onInteract();
if (this.isAutoActive && !this.hasUserControl && !this.takeoverActive) {
const rect = this.container.getBoundingClientRect();
const nx = (event.clientX - rect.left) / rect.width;
const ny = (event.clientY - rect.top) / rect.height;
this.takeoverFrom.copy(this.coords);
this.takeoverTo.set(nx * 2 - 1, -(ny * 2 - 1));
this.takeoverStartTime = performance.now();
this.takeoverActive = true;
this.hasUserControl = true;
this.isAutoActive = false;
return;
}
this.setCoords(event.clientX, event.clientY);
this.hasUserControl = true;
}
onDocumentTouchStart(event) {
if (event.touches.length === 1) {
const t = event.touches[0];
if (this.onInteract) this.onInteract();
this.setCoords(t.pageX, t.pageY);
this.hasUserControl = true;
}
}
onDocumentTouchMove(event) {
if (event.touches.length === 1) {
const t = event.touches[0];
if (this.onInteract) this.onInteract();
this.setCoords(t.pageX, t.pageY);
}
}
onTouchEnd() {
this.isHoverInside = false;
}
onMouseEnter() {
this.isHoverInside = true;
}
onMouseLeave() {
this.isHoverInside = false;
}
update() {
if (this.takeoverActive) {
const t = (performance.now() - this.takeoverStartTime) / (this.takeoverDuration * 1000);
if (t >= 1) {
this.takeoverActive = false;
this.coords.copy(this.takeoverTo);
this.coords_old.copy(this.coords);
this.diff.set(0, 0);
} else {
const k = t * t * (3 - 2 * t);
this.coords.copy(this.takeoverFrom).lerp(this.takeoverTo, k);
}
}
this.diff.subVectors(this.coords, this.coords_old);
this.coords_old.copy(this.coords);
if (this.coords_old.x === 0 && this.coords_old.y === 0) this.diff.set(0, 0);
if (this.isAutoActive && !this.takeoverActive) this.diff.multiplyScalar(this.autoIntensity);
}
}
const Mouse = new MouseClass();
class AutoDriver {
constructor(mouse, manager, opts) {
this.mouse = mouse;
this.manager = manager;
this.enabled = opts.enabled;
this.speed = opts.speed; // normalized units/sec
this.resumeDelay = opts.resumeDelay || 3000; // ms
this.rampDurationMs = (opts.rampDuration || 0) * 1000;
this.active = false;
this.current = new THREE.Vector2(0, 0);
this.target = new THREE.Vector2();
this.lastTime = performance.now();
this.activationTime = 0;
this.margin = 0.2;
this._tmpDir = new THREE.Vector2(); // reuse temp vector to avoid per-frame alloc
this.pickNewTarget();
}
pickNewTarget() {
const r = Math.random;
this.target.set((r() * 2 - 1) * (1 - this.margin), (r() * 2 - 1) * (1 - this.margin));
}
forceStop() {
this.active = false;
this.mouse.isAutoActive = false;
}
update() {
if (!this.enabled) return;
const now = performance.now();
const idle = now - this.manager.lastUserInteraction;
if (idle < this.resumeDelay) {
if (this.active) this.forceStop();
return;
}
if (this.mouse.isHoverInside) {
if (this.active) this.forceStop();
return;
}
if (!this.active) {
this.active = true;
this.current.copy(this.mouse.coords);
this.lastTime = now;
this.activationTime = now;
}
if (!this.active) return;
this.mouse.isAutoActive = true;
let dtSec = (now - this.lastTime) / 1000;
this.lastTime = now;
if (dtSec > 0.2) dtSec = 0.016;
const dir = this._tmpDir.subVectors(this.target, this.current);
const dist = dir.length();
if (dist < 0.01) {
this.pickNewTarget();
return;
}
dir.normalize();
let ramp = 1;
if (this.rampDurationMs > 0) {
const t = Math.min(1, (now - this.activationTime) / this.rampDurationMs);
ramp = t * t * (3 - 2 * t);
}
const step = this.speed * dtSec * ramp;
const move = Math.min(step, dist);
this.current.addScaledVector(dir, move);
this.mouse.setNormalized(this.current.x, this.current.y);
}
}
const face_vert = `
attribute vec3 position;
uniform vec2 px;
uniform vec2 boundarySpace;
varying vec2 uv;
precision highp float;
void main(){
vec3 pos = position;
vec2 scale = 1.0 - boundarySpace * 2.0;
pos.xy = pos.xy * scale;
uv = vec2(0.5)+(pos.xy)*0.5;
gl_Position = vec4(pos, 1.0);
}
`;
const line_vert = `
attribute vec3 position;
uniform vec2 px;
precision highp float;
varying vec2 uv;
void main(){
vec3 pos = position;
uv = 0.5 + pos.xy * 0.5;
vec2 n = sign(pos.xy);
pos.xy = abs(pos.xy) - px * 1.0;
pos.xy *= n;
gl_Position = vec4(pos, 1.0);
}
`;
const mouse_vert = `
precision highp float;
attribute vec3 position;
attribute vec2 uv;
uniform vec2 center;
uniform vec2 scale;
uniform vec2 px;
varying vec2 vUv;
void main(){
vec2 pos = position.xy * scale * 2.0 * px + center;
vUv = uv;
gl_Position = vec4(pos, 0.0, 1.0);
}
`;
const advection_frag = `
precision highp float;
uniform sampler2D velocity;
uniform float dt;
uniform bool isBFECC;
uniform vec2 fboSize;
uniform vec2 px;
varying vec2 uv;
void main(){
vec2 ratio = max(fboSize.x, fboSize.y) / fboSize;
if(isBFECC == false){
vec2 vel = texture2D(velocity, uv).xy;
vec2 uv2 = uv - vel * dt * ratio;
vec2 newVel = texture2D(velocity, uv2).xy;
gl_FragColor = vec4(newVel, 0.0, 0.0);
} else {
vec2 spot_new = uv;
vec2 vel_old = texture2D(velocity, uv).xy;
vec2 spot_old = spot_new - vel_old * dt * ratio;
vec2 vel_new1 = texture2D(velocity, spot_old).xy;
vec2 spot_new2 = spot_old + vel_new1 * dt * ratio;
vec2 error = spot_new2 - spot_new;
vec2 spot_new3 = spot_new - error / 2.0;
vec2 vel_2 = texture2D(velocity, spot_new3).xy;
vec2 spot_old2 = spot_new3 - vel_2 * dt * ratio;
vec2 newVel2 = texture2D(velocity, spot_old2).xy;
gl_FragColor = vec4(newVel2, 0.0, 0.0);
}
}
`;
const color_frag = `
precision highp float;
uniform sampler2D velocity;
uniform sampler2D palette;
uniform vec4 bgColor;
varying vec2 uv;
void main(){
vec2 vel = texture2D(velocity, uv).xy;
float lenv = clamp(length(vel), 0.0, 1.0);
vec3 c = texture2D(palette, vec2(lenv, 0.5)).rgb;
vec3 outRGB = mix(bgColor.rgb, c, lenv);
float outA = mix(bgColor.a, 1.0, lenv);
gl_FragColor = vec4(outRGB, outA);
}
`;
const divergence_frag = `
precision highp float;
uniform sampler2D velocity;
uniform float dt;
uniform vec2 px;
varying vec2 uv;
void main(){
float x0 = texture2D(velocity, uv-vec2(px.x, 0.0)).x;
float x1 = texture2D(velocity, uv+vec2(px.x, 0.0)).x;
float y0 = texture2D(velocity, uv-vec2(0.0, px.y)).y;
float y1 = texture2D(velocity, uv+vec2(0.0, px.y)).y;
float divergence = (x1 - x0 + y1 - y0) / 2.0;
gl_FragColor = vec4(divergence / dt);
}
`;
const externalForce_frag = `
precision highp float;
uniform vec2 force;
uniform vec2 center;
uniform vec2 scale;
uniform vec2 px;
varying vec2 vUv;
void main(){
vec2 circle = (vUv - 0.5) * 2.0;
float d = 1.0 - min(length(circle), 1.0);
d *= d;
gl_FragColor = vec4(force * d, 0.0, 1.0);
}
`;
const poisson_frag = `
precision highp float;
uniform sampler2D pressure;
uniform sampler2D divergence;
uniform vec2 px;
varying vec2 uv;
void main(){
float p0 = texture2D(pressure, uv + vec2(px.x * 2.0, 0.0)).r;
float p1 = texture2D(pressure, uv - vec2(px.x * 2.0, 0.0)).r;
float p2 = texture2D(pressure, uv + vec2(0.0, px.y * 2.0)).r;
float p3 = texture2D(pressure, uv - vec2(0.0, px.y * 2.0)).r;
float div = texture2D(divergence, uv).r;
float newP = (p0 + p1 + p2 + p3) / 4.0 - div;
gl_FragColor = vec4(newP);
}
`;
const pressure_frag = `
precision highp float;
uniform sampler2D pressure;
uniform sampler2D velocity;
uniform vec2 px;
uniform float dt;
varying vec2 uv;
void main(){
float step = 1.0;
float p0 = texture2D(pressure, uv + vec2(px.x * step, 0.0)).r;
float p1 = texture2D(pressure, uv - vec2(px.x * step, 0.0)).r;
float p2 = texture2D(pressure, uv + vec2(0.0, px.y * step)).r;
float p3 = texture2D(pressure, uv - vec2(0.0, px.y * step)).r;
vec2 v = texture2D(velocity, uv).xy;
vec2 gradP = vec2(p0 - p1, p2 - p3) * 0.5;
v = v - gradP * dt;
gl_FragColor = vec4(v, 0.0, 1.0);
}
`;
const viscous_frag = `
precision highp float;
uniform sampler2D velocity;
uniform sampler2D velocity_new;
uniform float v;
uniform vec2 px;
uniform float dt;
varying vec2 uv;
void main(){
vec2 old = texture2D(velocity, uv).xy;
vec2 new0 = texture2D(velocity_new, uv + vec2(px.x * 2.0, 0.0)).xy;
vec2 new1 = texture2D(velocity_new, uv - vec2(px.x * 2.0, 0.0)).xy;
vec2 new2 = texture2D(velocity_new, uv + vec2(0.0, px.y * 2.0)).xy;
vec2 new3 = texture2D(velocity_new, uv - vec2(0.0, px.y * 2.0)).xy;
vec2 newv = 4.0 * old + v * dt * (new0 + new1 + new2 + new3);
newv /= 4.0 * (1.0 + v * dt);
gl_FragColor = vec4(newv, 0.0, 0.0);
}
`;
class ShaderPass {
constructor(props) {
this.props = props || {};
this.uniforms = this.props.material?.uniforms;
this.scene = null;
this.camera = null;
this.material = null;
this.geometry = null;
this.plane = null;
}
init() {
this.scene = new THREE.Scene();
this.camera = new THREE.Camera();
if (this.uniforms) {
this.material = new THREE.RawShaderMaterial(this.props.material);
this.geometry = new THREE.PlaneGeometry(2.0, 2.0);
this.plane = new THREE.Mesh(this.geometry, this.material);
this.scene.add(this.plane);
}
}
update() {
Common.renderer.setRenderTarget(this.props.output || null);
Common.renderer.render(this.scene, this.camera);
Common.renderer.setRenderTarget(null);
}
}
class Advection extends ShaderPass {
constructor(simProps) {
super({
material: {
vertexShader: face_vert,
fragmentShader: advection_frag,
uniforms: {
boundarySpace: { value: simProps.cellScale },
px: { value: simProps.cellScale },
fboSize: { value: simProps.fboSize },
velocity: { value: simProps.src.texture },
dt: { value: simProps.dt },
isBFECC: { value: true }
}
},
output: simProps.dst
});
this.uniforms = this.props.material.uniforms;
this.init();
}
init() {
super.init();
this.createBoundary();
}
createBoundary() {
const boundaryG = new THREE.BufferGeometry();
const vertices_boundary = new Float32Array([
-1, -1, 0, -1, 1, 0, -1, 1, 0, 1, 1, 0, 1, 1, 0, 1, -1, 0, 1, -1, 0, -1, -1, 0
]);
boundaryG.setAttribute('position', new THREE.BufferAttribute(vertices_boundary, 3));
const boundaryM = new THREE.RawShaderMaterial({
vertexShader: line_vert,
fragmentShader: advection_frag,
uniforms: this.uniforms
});
this.line = new THREE.LineSegments(boundaryG, boundaryM);
this.scene.add(this.line);
}
update({ dt, isBounce, BFECC }) {
this.uniforms.dt.value = dt;
this.line.visible = isBounce;
this.uniforms.isBFECC.value = BFECC;
super.update();
}
}
class ExternalForce extends ShaderPass {
constructor(simProps) {
super({ output: simProps.dst });
this.init(simProps);
}
init(simProps) {
super.init();
const mouseG = new THREE.PlaneGeometry(1, 1);
const mouseM = new THREE.RawShaderMaterial({
vertexShader: mouse_vert,
fragmentShader: externalForce_frag,
blending: THREE.AdditiveBlending,
depthWrite: false,
uniforms: {
px: { value: simProps.cellScale },
force: { value: new THREE.Vector2(0.0, 0.0) },
center: { value: new THREE.Vector2(0.0, 0.0) },
scale: { value: new THREE.Vector2(simProps.cursor_size, simProps.cursor_size) }
}
});
this.mouse = new THREE.Mesh(mouseG, mouseM);
this.scene.add(this.mouse);
}
update(props) {
const forceX = (Mouse.diff.x / 2) * props.mouse_force;
const forceY = (Mouse.diff.y / 2) * props.mouse_force;
const cursorSizeX = props.cursor_size * props.cellScale.x;
const cursorSizeY = props.cursor_size * props.cellScale.y;
const centerX = Math.min(
Math.max(Mouse.coords.x, -1 + cursorSizeX + props.cellScale.x * 2),
1 - cursorSizeX - props.cellScale.x * 2
);
const centerY = Math.min(
Math.max(Mouse.coords.y, -1 + cursorSizeY + props.cellScale.y * 2),
1 - cursorSizeY - props.cellScale.y * 2
);
const uniforms = this.mouse.material.uniforms;
uniforms.force.value.set(forceX, forceY);
uniforms.center.value.set(centerX, centerY);
uniforms.scale.value.set(props.cursor_size, props.cursor_size);
super.update();
}
}
class Viscous extends ShaderPass {
constructor(simProps) {
super({
material: {
vertexShader: face_vert,
fragmentShader: viscous_frag,
uniforms: {
boundarySpace: { value: simProps.boundarySpace },
velocity: { value: simProps.src.texture },
velocity_new: { value: simProps.dst_.texture },
v: { value: simProps.viscous },
px: { value: simProps.cellScale },
dt: { value: simProps.dt }
}
},
output: simProps.dst,
output0: simProps.dst_,
output1: simProps.dst
});
this.init();
}
update({ viscous, iterations, dt }) {
let fbo_in, fbo_out;
this.uniforms.v.value = viscous;
for (let i = 0; i < iterations; i++) {
if (i % 2 === 0) {
fbo_in = this.props.output0;
fbo_out = this.props.output1;
} else {
fbo_in = this.props.output1;
fbo_out = this.props.output0;
}
this.uniforms.velocity_new.value = fbo_in.texture;
this.props.output = fbo_out;
this.uniforms.dt.value = dt;
super.update();
}
return fbo_out;
}
}
class Divergence extends ShaderPass {
constructor(simProps) {
super({
material: {
vertexShader: face_vert,
fragmentShader: divergence_frag,
uniforms: {
boundarySpace: { value: simProps.boundarySpace },
velocity: { value: simProps.src.texture },
px: { value: simProps.cellScale },
dt: { value: simProps.dt }
}
},
output: simProps.dst
});
this.init();
}
update({ vel }) {
this.uniforms.velocity.value = vel.texture;
super.update();
}
}
class Poisson extends ShaderPass {
constructor(simProps) {
super({
material: {
vertexShader: face_vert,
fragmentShader: poisson_frag,
uniforms: {
boundarySpace: { value: simProps.boundarySpace },
pressure: { value: simProps.dst_.texture },
divergence: { value: simProps.src.texture },
px: { value: simProps.cellScale }
}
},
output: simProps.dst,
output0: simProps.dst_,
output1: simProps.dst
});
this.init();
}
update({ iterations }) {
let p_in, p_out;
for (let i = 0; i < iterations; i++) {
if (i % 2 === 0) {
p_in = this.props.output0;
p_out = this.props.output1;
} else {
p_in = this.props.output1;
p_out = this.props.output0;
}
this.uniforms.pressure.value = p_in.texture;
this.props.output = p_out;
super.update();
}
return p_out;
}
}
class Pressure extends ShaderPass {
constructor(simProps) {
super({
material: {
vertexShader: face_vert,
fragmentShader: pressure_frag,
uniforms: {
boundarySpace: { value: simProps.boundarySpace },
pressure: { value: simProps.src_p.texture },
velocity: { value: simProps.src_v.texture },
px: { value: simProps.cellScale },
dt: { value: simProps.dt }
}
},
output: simProps.dst
});
this.init();
}
update({ vel, pressure }) {
this.uniforms.velocity.value = vel.texture;
this.uniforms.pressure.value = pressure.texture;
super.update();
}
}
class Simulation {
constructor(options) {
this.options = {
iterations_poisson: 32,
iterations_viscous: 32,
mouse_force: 20,
resolution: 0.5,
cursor_size: 100,
viscous: 30,
isBounce: false,
dt: 0.014,
isViscous: false,
BFECC: true,
...options
};
this.fbos = {
vel_0: null,
vel_1: null,
vel_viscous0: null,
vel_viscous1: null,
div: null,
pressure_0: null,
pressure_1: null
};
this.fboSize = new THREE.Vector2();
this.cellScale = new THREE.Vector2();
this.boundarySpace = new THREE.Vector2();
this.init();
}
init() {
this.calcSize();
this.createAllFBO();
this.createShaderPass();
}
getFloatType() {
const isIOS = /(iPad|iPhone|iPod)/i.test(navigator.userAgent);
return isIOS ? THREE.HalfFloatType : THREE.FloatType;
}
createAllFBO() {
const type = this.getFloatType();
const opts = {
type,
depthBuffer: false,
stencilBuffer: false,
minFilter: THREE.LinearFilter,
magFilter: THREE.LinearFilter,
wrapS: THREE.ClampToEdgeWrapping,
wrapT: THREE.ClampToEdgeWrapping
};
for (let key in this.fbos) {
this.fbos[key] = new THREE.WebGLRenderTarget(this.fboSize.x, this.fboSize.y, opts);
}
}
createShaderPass() {
this.advection = new Advection({
cellScale: this.cellScale,
fboSize: this.fboSize,
dt: this.options.dt,
src: this.fbos.vel_0,
dst: this.fbos.vel_1
});
this.externalForce = new ExternalForce({
cellScale: this.cellScale,
cursor_size: this.options.cursor_size,
dst: this.fbos.vel_1
});
this.viscous = new Viscous({
cellScale: this.cellScale,
boundarySpace: this.boundarySpace,
viscous: this.options.viscous,
src: this.fbos.vel_1,
dst: this.fbos.vel_viscous1,
dst_: this.fbos.vel_viscous0,
dt: this.options.dt
});
this.divergence = new Divergence({
cellScale: this.cellScale,
boundarySpace: this.boundarySpace,
src: this.fbos.vel_viscous0,
dst: this.fbos.div,
dt: this.options.dt
});
this.poisson = new Poisson({
cellScale: this.cellScale,
boundarySpace: this.boundarySpace,
src: this.fbos.div,
dst: this.fbos.pressure_1,
dst_: this.fbos.pressure_0
});
this.pressure = new Pressure({
cellScale: this.cellScale,
boundarySpace: this.boundarySpace,
src_p: this.fbos.pressure_0,
src_v: this.fbos.vel_viscous0,
dst: this.fbos.vel_0,
dt: this.options.dt
});
}
calcSize() {
const width = Math.max(1, Math.round(this.options.resolution * Common.width));
const height = Math.max(1, Math.round(this.options.resolution * Common.height));
const px_x = 1.0 / width;
const px_y = 1.0 / height;
this.cellScale.set(px_x, px_y);
this.fboSize.set(width, height);
}
resize() {
this.calcSize();
for (let key in this.fbos) {
this.fbos[key].setSize(this.fboSize.x, this.fboSize.y);
}
}
update() {
if (this.options.isBounce) {
this.boundarySpace.set(0, 0);
} else {
this.boundarySpace.copy(this.cellScale);
}
this.advection.update({
dt: this.options.dt,
isBounce: this.options.isBounce,
BFECC: this.options.BFECC
});
this.externalForce.update({
cursor_size: this.options.cursor_size,
mouse_force: this.options.mouse_force,
cellScale: this.cellScale
});
let vel = this.fbos.vel_1;
if (this.options.isViscous) {
vel = this.viscous.update({
viscous: this.options.viscous,
iterations: this.options.iterations_viscous,
dt: this.options.dt
});
}
this.divergence.update({ vel });
const pressure = this.poisson.update({
iterations: this.options.iterations_poisson
});
this.pressure.update({ vel, pressure });
}
}
class Output {
constructor() {
this.init();
}
init() {
this.simulation = new Simulation();
this.scene = new THREE.Scene();
this.camera = new THREE.Camera();
this.output = new THREE.Mesh(
new THREE.PlaneGeometry(2, 2),
new THREE.RawShaderMaterial({
vertexShader: face_vert,
fragmentShader: color_frag,
transparent: true,
depthWrite: false,
uniforms: {
velocity: { value: this.simulation.fbos.vel_0.texture },
boundarySpace: { value: new THREE.Vector2() },
palette: { value: paletteTex },
bgColor: { value: bgVec4 }
}
})
);
this.scene.add(this.output);
}
addScene(mesh) {
this.scene.add(mesh);
}
resize() {
this.simulation.resize();
}
render() {
Common.renderer.setRenderTarget(null);
Common.renderer.render(this.scene, this.camera);
}
update() {
this.simulation.update();
this.render();
}
}
class WebGLManager {
constructor(props) {
this.props = props;
Common.init(props.$wrapper);
Mouse.init(props.$wrapper);
Mouse.autoIntensity = props.autoIntensity;
Mouse.takeoverDuration = props.takeoverDuration;
this.lastUserInteraction = performance.now();
Mouse.onInteract = () => {
this.lastUserInteraction = performance.now();
if (this.autoDriver) this.autoDriver.forceStop();
};
this.autoDriver = new AutoDriver(Mouse, this, {
enabled: props.autoDemo,
speed: props.autoSpeed,
resumeDelay: props.autoResumeDelay,
rampDuration: props.autoRampDuration
});
this.init();
this._loop = this.loop.bind(this);
this._resize = this.resize.bind(this);
window.addEventListener('resize', this._resize);
this._onVisibility = () => {
const hidden = document.hidden;
if (hidden) {
this.pause();
} else if (isVisibleRef.current) {
this.start();
}
};
document.addEventListener('visibilitychange', this._onVisibility);
this.running = false;
}
init() {
this.props.$wrapper.prepend(Common.renderer.domElement);
this.output = new Output();
}
resize() {
Common.resize();
this.output.resize();
}
render() {
if (this.autoDriver) this.autoDriver.update();
Mouse.update();
Common.update();
this.output.update();
}
loop() {
if (!this.running) return; // safety
this.render();
rafRef.current = requestAnimationFrame(this._loop);
}
start() {
if (this.running) return;
this.running = true;
this._loop();
}
pause() {
this.running = false;
if (rafRef.current) {
cancelAnimationFrame(rafRef.current);
rafRef.current = null;
}
}
dispose() {
try {
window.removeEventListener('resize', this._resize);
document.removeEventListener('visibilitychange', this._onVisibility);
Mouse.dispose();
if (Common.renderer) {
const canvas = Common.renderer.domElement;
if (canvas && canvas.parentNode) canvas.parentNode.removeChild(canvas);
Common.renderer.dispose();
}
} catch (e) {
void 0;
}
}
}
const container = mountRef.current;
container.style.position = container.style.position || 'relative';
container.style.overflow = container.style.overflow || 'hidden';
const webgl = new WebGLManager({
$wrapper: container,
autoDemo,
autoSpeed,
autoIntensity,
takeoverDuration,
autoResumeDelay,
autoRampDuration
});
webglRef.current = webgl;
const applyOptionsFromProps = () => {
if (!webglRef.current) return;
const sim = webglRef.current.output?.simulation;
if (!sim) return;
const prevRes = sim.options.resolution;
Object.assign(sim.options, {
mouse_force: mouseForce,
cursor_size: cursorSize,
isViscous,
viscous,
iterations_viscous: iterationsViscous,
iterations_poisson: iterationsPoisson,
dt,
BFECC,
resolution,
isBounce
});
if (resolution !== prevRes) {
sim.resize();
}
};
applyOptionsFromProps();
webgl.start();
// IntersectionObserver to pause rendering when not visible
const io = new IntersectionObserver(
entries => {
const entry = entries[0];
const isVisible = entry.isIntersecting && entry.intersectionRatio > 0;
isVisibleRef.current = isVisible;
if (!webglRef.current) return;
if (isVisible && !document.hidden) {
webglRef.current.start();
} else {
webglRef.current.pause();
}
},
{ threshold: [0, 0.01, 0.1] }
);
io.observe(container);
intersectionObserverRef.current = io;
const ro = new ResizeObserver(() => {
if (!webglRef.current) return;
if (resizeRafRef.current) cancelAnimationFrame(resizeRafRef.current);
resizeRafRef.current = requestAnimationFrame(() => {
if (!webglRef.current) return;
webglRef.current.resize();
});
});
ro.observe(container);
resizeObserverRef.current = ro;
return () => {
if (rafRef.current) cancelAnimationFrame(rafRef.current);
if (resizeObserverRef.current) {
try {
resizeObserverRef.current.disconnect();
} catch (e) {
void 0;
}
}
if (intersectionObserverRef.current) {
try {
intersectionObserverRef.current.disconnect();
} catch (e) {
void 0;
}
}
if (webglRef.current) {
webglRef.current.dispose();
}
webglRef.current = null;
};
}, [
BFECC,
cursorSize,
dt,
isBounce,
isViscous,
iterationsPoisson,
iterationsViscous,
mouseForce,
resolution,
viscous,
colors,
autoDemo,
autoSpeed,
autoIntensity,
takeoverDuration,
autoResumeDelay,
autoRampDuration
]);
useEffect(() => {
const webgl = webglRef.current;
if (!webgl) return;
const sim = webgl.output?.simulation;
if (!sim) return;
const prevRes = sim.options.resolution;
Object.assign(sim.options, {
mouse_force: mouseForce,
cursor_size: cursorSize,
isViscous,
viscous,
iterations_viscous: iterationsViscous,
iterations_poisson: iterationsPoisson,
dt,
BFECC,
resolution,
isBounce
});
if (webgl.autoDriver) {
webgl.autoDriver.enabled = autoDemo;
webgl.autoDriver.speed = autoSpeed;
webgl.autoDriver.resumeDelay = autoResumeDelay;
webgl.autoDriver.rampDurationMs = autoRampDuration * 1000;
if (webgl.autoDriver.mouse) {
webgl.autoDriver.mouse.autoIntensity = autoIntensity;
webgl.autoDriver.mouse.takeoverDuration = takeoverDuration;
}
}
if (resolution !== prevRes) {
sim.resize();
}
}, [
mouseForce,
cursorSize,
isViscous,
viscous,
iterationsViscous,
iterationsPoisson,
dt,
BFECC,
resolution,
isBounce,
autoDemo,
autoSpeed,
autoIntensity,
takeoverDuration,
autoResumeDelay,
autoRampDuration
]);
return
;
}