codepens/generative-voronoi-splodges.../dist/script.js

455 lines
10 KiB
JavaScript
Raw Normal View History

2023-10-06 23:12:53 +02:00
import { spline } from "https://cdn.skypack.dev/@georgedoescode/spline@1.0.1";
import { Vector2D } from "https://cdn.skypack.dev/@georgedoescode/vector2d@1.0.1";
import SimplexNoise from "https://cdn.skypack.dev/simplex-noise@2.4.0";
import centroid from "https://cdn.skypack.dev/polygon-centroid@1.1.0";
import polylabel from "https://cdn.skypack.dev/polylabel@1.1.0";
import * as PIXI from "https://cdn.skypack.dev/pixi.js@5.3.4";
import Offset from "https://cdn.skypack.dev/polygon-offset@0.3.1";
import gsap from "https://cdn.skypack.dev/gsap@3.5.1";
import { PixiPlugin } from "https://cdn.skypack.dev/gsap@3.5.1/PixiPlugin";
const simplex = new SimplexNoise();
const offset = new Offset();
gsap.registerPlugin(PixiPlugin);
PixiPlugin.registerPIXI(PIXI);
function random(min, max) {
return Math.random() * (max - min) + min;
}
function map(n, start1, end1, start2, end2) {
return (n - start1) / (end1 - start1) * (end2 - start2) + start2;
}
function noise(x, y) {
return simplex.noise2D(x, y);
}
function getPolygonCentroid(pts) {
const first = pts[0];
const last = pts[pts.length - 1];
if (first.x != last.x || first.y != last.y) pts.push(first);
let twicearea = 0;
let x = 0;
let y = 0;
let nPts = pts.length;
let p1;
let p2;
let f;
for (var i = 0, j = nPts - 1; i < nPts; j = i++) {
p1 = pts[i];
p2 = pts[j];
f = p1.x * p2.y - p2.x * p1.y;
twicearea += f;
x += (p1.x + p2.x) * f;
y += (p1.y + p2.y) * f;
}
f = twicearea * 3;
return { x: x / f, y: y / f };
}
class MetaSpline {
constructor(x, y, shape, app) {
this.app = app;
this.pos = new Vector2D(x, y);
this.shape = shape;
this.centroid = new Vector2D(shape.centroid.x, shape.centroid.y);
this.points = shape.points;
this.graphicsBounds = null;
this.originalPoints = this.points.map(p => p.copy());
this.pointNoise = [...Array(this.points.length)].map((_, i) => {
return { x: random(0, 1000), y: random(0, 1000) };
});
this.xOff = random(0, 1000);
this.baseXOffVertex = random(0.001, 0.005);
this.xOffVertex = this.baseXOffVertex;
this.tension = random(0.4, 0.7);
this.container = new PIXI.Container();
this.graphics = this._createGraphics();
this.app.stage.addChild(this.container);
this.container.addChild(this.graphics);
this.container.x = shape.site.x;
this.container.y = shape.site.y;
this.colors = [
0xd6675a,
0xe09960,
0x6092c4,
0x3c3c3b,
0x64b473,
0xd4676b,
0x96c25b];
this.fill = this.colors[~~random(0, this.colors.length)];
this._animateIn();
// ffffff
this._createFill();
this.outline = new PIXI.Graphics();
this.container.addChild(this.outline);
}
_createFill() {
const graphics1 = new PIXI.Graphics();
const graphics2 = new PIXI.Graphics();
const container1 = new PIXI.Container();
const container2 = new PIXI.Container();
const choices = Object.keys(textures);
const choice = choices[~~random(0, choices.length)];
const mask = new PIXI.Sprite(textures[choice]);
mask.x = this.centroid.x - this.shape.bounds.width / 2;
mask.y = this.centroid.y - this.shape.bounds.height / 2;
mask.width = Math.max(this.shape.bounds.width, this.shape.bounds.height);
mask.height = Math.max(this.shape.bounds.width, this.shape.bounds.height);
this.container.addChild(mask);
graphics1.x = this.centroid.x;
graphics1.y = this.centroid.y;
const padding = 100;
graphics1.beginFill(this.fill);
graphics1.drawRect(
-this.shape.bounds.width / 2 - padding,
-this.shape.bounds.height / 2 - padding,
this.shape.bounds.width + padding * 2,
this.shape.bounds.height + padding * 2);
graphics1.endFill();
graphics2.alpha = 0.25;
graphics2.x = this.centroid.x;
graphics2.y = this.centroid.y;
graphics2.beginFill(this.fill);
graphics2.drawRect(
-this.shape.bounds.width / 2 - padding,
-this.shape.bounds.height / 2 - padding,
this.shape.bounds.width + padding * 2,
this.shape.bounds.height + padding * 2);
graphics2.endFill();
container2.addChild(graphics2);
container1.addChild(graphics1);
container2.addChild(container1);
this.container.addChild(container2);
container1.mask = mask;
container2.mask = this.graphics;
}
_createContainer() {
const container = new PIXI.Container();
this.app.stage.addChild(container);
return container;
}
_createGraphics() {
const graphics = new PIXI.Graphics();
graphics.interactive = true;
const handleOver = () => {
gsap.to(this.container.scale, {
x: 0.825,
y: 0.825,
duration: 1.5,
ease: "elastic.out(1, 0.5)" });
gsap.to(this, {
xOffVertex: 0.015,
tension: 0.9,
duration: 1.5,
ease: "elastic.out(1, 0.5)" });
};
const handleOut = () => {
gsap.to(this.container.scale, {
x: 1,
y: 1,
duration: 1.5,
ease: "elastic.out(1, 0.3)" });
gsap.to(this, {
xOffVertex: this.baseXOffVertex,
tension: 0.6,
duration: 1.5,
ease: "elastic.out(1, 0.3)" });
};
graphics.on("touchstart", handleOver);
graphics.on("mouseover", handleOver);
graphics.on("touchend", handleOut);
graphics.on("mouseout", handleOut);
graphics.cursor = "pointer";
return graphics;
}
_setGraphicsStyles() {
this.graphics.clear();
this.outline.clear();
this.outline.lineStyle(2, this.fill);
this.graphics.beginFill(0x000000);
}
_closeGraphicsStyles() {
this.graphics.endFill();
}
_wobble() {
for (let i = 0; i < this.points.length; i++) {
const originalPoint = this.originalPoints[i].copy();
const currentPoint = this.points[i];
const noiseX = noise(this.pointNoise[i].x, this.pointNoise[i].x);
const newVal = originalPoint.lerp(
this.centroid.x,
this.centroid.y,
map(noiseX, -1, 1, 0.1, 0.3));
this.points[i] = newVal;
this.pointNoise[i].x += this.xOffVertex;
}
}
_animateIn() {
this.container.scale.x = 0;
this.container.scale.y = 0;
gsap.
to(this.container.scale, {
x: 1,
y: 1,
duration: 1,
ease: "elastic.out(1, 0.825)" }).
delay(random(0, 0.75));
}
_animateOut() {
gsap.
to(this.container.scale, {
x: 0,
y: 0,
duration: 1,
ease: "elastic.out(1, 0.825)" }).
delay(random(0, 0.75));
}
update() {
this._wobble();
}
render() {
this._setGraphicsStyles();
spline(this.points, this.tension, true, (CMD, data) => {
if (CMD === "MOVE") {
this.graphics.moveTo(...data);
this.outline.moveTo(...data);
} else {
this.graphics.bezierCurveTo(...data);
this.outline.bezierCurveTo(...data);
}
});
this._closeGraphicsStyles();
}
clear() {
this._animateOut();
setTimeout(() => {
this.graphics.clear();
this.outline.clear();
}, 2000);
}}
class VoronoiDiagram {
constructor(width, height) {
this.bbox = {
xl: 0,
xr: width,
yt: 0,
yb: height };
const numPoints = ~~random(10, 25);
this.sites = [...Array(numPoints)].map(() => {
return {
x: random(0, width),
y: random(0, height) };
});
this.instance = new Voronoi();
this.diagram = this.instance.compute(this.sites, this.bbox);
}
_filterClosePoints(points) {
let lastPoint = null;
if (points[0].dist(points[points.length - 1]) <= 40) {
points.shift();
}
return points.filter(point => {
if (!lastPoint || lastPoint.dist(point) >= 40) {
lastPoint = point;
return true;
}
});
}
getShapes() {
return this.diagram.cells.reduce((acc, cell) => {
const bounds = cell.getBbox();
const shape = {
site: cell.site,
points: [],
bounds: bounds };
const halfEdges = cell.halfedges;
const nHalfEdges = halfEdges.length;
let v = halfEdges[0].getStartpoint();
shape.points.push(new Vector2D(v.x, v.y));
for (let iHalfEdge = 0; iHalfEdge < nHalfEdges - 1; iHalfEdge++) {
v = halfEdges[iHalfEdge].getEndpoint();
shape.points.push(new Vector2D(v.x, v.y));
}
shape.points.forEach(p => {
p.x -= shape.site.x;
p.y -= shape.site.y;
});
shape.points = this._filterClosePoints(shape.points);
shape.centroid = getPolygonCentroid(
shape.points.map(p => p.toObject()));
acc.push(shape);
return acc;
}, []);
}}
function createPIXIApp(width, height) {
const app = new PIXI.Application({
width: width,
height: height,
resolution: Math.max(2, window.devicePixelRatio || 1),
autoDensity: true,
transparent: true,
antialias: false });
document.body.appendChild(app.view);
console.clear();
return app;
}
const canvasWidth = Math.min(window.innerWidth - 32, 640);
const canvasHeight = Math.min(window.innerHeight - 32, 640);
const app = createPIXIApp(canvasWidth, canvasHeight);
const loader = PIXI.Loader.shared;
const textures = {};
app.ticker.stop();
gsap.ticker.add(() => {
app.ticker.update();
});
loader.
add("f1", "https://assets.codepen.io/5367578/f1.png").
add("f2", "https://assets.codepen.io/5367578/f2.png").
add("f3", "https://assets.codepen.io/5367578/f3.png").
add("f4", "https://assets.codepen.io/5367578/f4.png").
add("f5", "https://assets.codepen.io/5367578/f5.png").
add("m1", "https://assets.codepen.io/5367578/m1.png").
add("m2", "https://assets.codepen.io/5367578/m2.png").
add("m3", "https://assets.codepen.io/5367578/m3.png").
add("m4", "https://assets.codepen.io/5367578/m4.png");
loader.load((loader, resources) => {
Object.keys(resources).forEach(t => textures[t] = resources[t].texture);
window.splines = [];
init();
});
function init() {
const voronoi = new VoronoiDiagram(canvasWidth, canvasHeight);
voronoi.getShapes().forEach(shape => {
splines.push(new MetaSpline(0, 0, shape, app));
});
app.ticker.add(() => {
splines.forEach(spline => {
spline.update();
spline.render();
});
});
}