260813
const W = 800;
const H = 800;
const S = 650;
const GRID = 10;
let palette = [];
let boxSize;
let gridOrigin;
function setup() {
createCanvas(W, H, WEBGL);
palette = colorArray[1].colors;
ortho();
boxSize = (S / (GRID - 1)) * 1.25;
gridOrigin = (S - (GRID - 1) * boxSize) / 2;
setupPartitionFrame({
size: S,
gap: 50,
minPieceCount: 3,
maxPieceCount: 6,
minPieceSize: 100,
layerGap: boxSize / 2,
});
}
function draw() {
background(palette[4]);
drawPartitionFrame(drawPieceContent);
}
function drawPieceContent(ctx, meta, frame) {
let phase = frameCount * 0.005 * TAU;
let amp = boxSize / 2;
let center = amp * sin(0.5 * TAU + phase);
let wave = [];
for (let k = 0; k < GRID; k++) {
wave[k] = amp * sin((k / (GRID - 1)) * TAU + phase) - center;
}
ctx.fillStyle = palette[3];
ctx.fillRect(0, 0, frame.bufferW, frame.bufferH);
ctx.strokeStyle = palette[4];
ctx.lineWidth = 5;
ctx.lineCap = "round";
ctx.beginPath();
for (let j = 0; j < GRID; j++) {
for (let i = 0; i < GRID - 1; i++) {
ctx.moveTo(
gridOrigin + i * boxSize + wave[j] - meta.minX,
gridOrigin + j * boxSize + wave[i] - meta.minY,
);
ctx.lineTo(
gridOrigin + (i + 1) * boxSize + wave[j] - meta.minX,
gridOrigin + j * boxSize + wave[i + 1] - meta.minY,
);
}
}
for (let i = 0; i < GRID; i++) {
for (let j = 0; j < GRID - 1; j++) {
ctx.moveTo(
gridOrigin + i * boxSize + wave[j] - meta.minX,
gridOrigin + j * boxSize + wave[i] - meta.minY,
);
ctx.lineTo(
gridOrigin + i * boxSize + wave[j + 1] - meta.minX,
gridOrigin + (j + 1) * boxSize + wave[i] - meta.minY,
);
}
}
ctx.stroke();
}
const colorArray = [
{
id: 0,
colors: ["#253276", "#dfdad3", "#ffffff", "#000000"],
},
{
id: 1,
colors: [
"#9dbdba",
"#f8b042",
"#e47763",
"#253276",
"#dfdad3",
"#FFFFFF",
"#000000",
],
},
]; let _partSize = 650;
let _partGap = 50;
let _partPieceCount = 4;
let _partMinPieceSize = 200;
let _partMinInsetSize = 150;
function makeSquare() {
return [
{ x: 0, y: 0 },
{ x: _partSize, y: 0 },
{ x: _partSize, y: _partSize },
{ x: 0, y: _partSize },
];
}
function lineSide(x, y, cut) {
return (
(cut.p2.x - cut.p1.x) * (y - cut.p1.y) -
(cut.p2.y - cut.p1.y) * (x - cut.p1.x)
);
}
function intersectEdge(a, b, cut) {
let x1 = a.x;
let y1 = a.y;
let x2 = b.x;
let y2 = b.y;
let x3 = cut.p1.x;
let y3 = cut.p1.y;
let x4 = cut.p2.x;
let y4 = cut.p2.y;
let denom = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4);
if (abs(denom) < 0.0001) {
return null;
}
let t = ((x1 - x3) * (y3 - y4) - (y1 - y3) * (x3 - x4)) / denom;
return { x: x1 + t * (x2 - x1), y: y1 + t * (y2 - y1) };
}
function clipPolygon(poly, cut, keepPositive) {
let out = [];
for (let i = 0; i < poly.length; i++) {
let curr = poly[i];
let prev = poly[(i + poly.length - 1) % poly.length];
let currIn = false;
let prevIn = false;
let currSide = lineSide(curr.x, curr.y, cut);
let prevSide = lineSide(prev.x, prev.y, cut);
if (keepPositive) {
currIn = currSide >= 0;
prevIn = prevSide >= 0;
} else {
currIn = currSide <= 0;
prevIn = prevSide <= 0;
}
if (currIn) {
if (!prevIn) {
let hit = intersectEdge(prev, curr, cut);
if (hit !== null) {
out.push(hit);
}
}
out.push({ x: curr.x, y: curr.y });
} else if (prevIn) {
let hit = intersectEdge(prev, curr, cut);
if (hit !== null) {
out.push(hit);
}
}
}
return out;
}
function splitPolygon(poly, cut) {
return [clipPolygon(poly, cut, true), clipPolygon(poly, cut, false)];
}
function edgePoint(poly, edgeIndex, t) {
let a = poly[edgeIndex];
let b = poly[(edgeIndex + 1) % poly.length];
return {
x: a.x + (b.x - a.x) * t,
y: a.y + (b.y - a.y) * t,
};
}
function edgeLength(a, b) {
let dx = b.x - a.x;
let dy = b.y - a.y;
return sqrt(dx * dx + dy * dy);
}
function minEdgeLength(poly) {
let minLen = 999999;
for (let i = 0; i < poly.length; i++) {
let len = edgeLength(poly[i], poly[(i + 1) % poly.length]);
if (len < minLen) {
minLen = len;
}
}
return minLen;
}
function randomEdgeT(poly, edgeIndex) {
let len = edgeLength(poly[edgeIndex], poly[(edgeIndex + 1) % poly.length]);
let margin = (_partMinPieceSize / len) * 0.55;
if (margin > 0.45) {
margin = 0.45;
}
if (margin < 0.05) {
margin = 0.05;
}
return random(margin, 1 - margin);
}
function pointInsideConvex(poly, px, py) {
let n = poly.length;
let sign = 0;
for (let i = 0; i < n; i++) {
let a = poly[i];
let b = poly[(i + 1) % n];
let cross = (b.x - a.x) * (py - a.y) - (b.y - a.y) * (px - a.x);
if (abs(cross) < 0.001) {
continue;
}
if (sign === 0) {
if (cross > 0) {
sign = 1;
} else {
sign = -1;
}
} else {
if (cross > 0) {
if (sign !== 1) {
return false;
}
} else {
if (sign !== -1) {
return false;
}
}
}
}
return true;
}
function onCutLine(p, cut) {
return abs(lineSide(p.x, p.y, cut)) < 1;
}
function nextSplitLine(poly, prev) {
let p1 = null;
let p2 = null;
if (prev === null) {
let edgeA = int(random(poly.length));
let edgeB = int(random(poly.length));
while (edgeB === edgeA) {
edgeB = int(random(poly.length));
}
p1 = edgePoint(poly, edgeA, randomEdgeT(poly, edgeA));
p2 = edgePoint(poly, edgeB, randomEdgeT(poly, edgeB));
} else {
for (let i = 0; i < poly.length; i++) {
let a = poly[i];
let b = poly[(i + 1) % poly.length];
if (onCutLine(a, prev) && onCutLine(b, prev)) {
p1 = edgePoint(poly, i, randomEdgeT(poly, i));
break;
}
}
if (p1 === null) {
return null;
}
let edgeB = int(random(poly.length));
p2 = edgePoint(poly, edgeB, randomEdgeT(poly, edgeB));
}
return { p1: p1, p2: p2 };
}
function polygonBounds(poly) {
let minX = poly[0].x;
let maxX = poly[0].x;
let minY = poly[0].y;
let maxY = poly[0].y;
for (let i = 1; i < poly.length; i++) {
if (poly[i].x < minX) {
minX = poly[i].x;
}
if (poly[i].x > maxX) {
maxX = poly[i].x;
}
if (poly[i].y < minY) {
minY = poly[i].y;
}
if (poly[i].y > maxY) {
maxY = poly[i].y;
}
}
return {
minX: minX,
minY: minY,
maxX: maxX,
maxY: maxY,
width: maxX - minX,
height: maxY - minY,
};
}
function polygonArea(poly) {
if (poly.length < 3) {
return 0;
}
let area = 0;
for (let i = 0; i < poly.length; i++) {
let j = (i + 1) % poly.length;
area += poly[i].x * poly[j].y - poly[j].x * poly[i].y;
}
return abs(area / 2);
}
function isDrawablePolygon(poly) {
if (poly === null || poly === undefined) {
return false;
}
if (poly.length < 3) {
return false;
}
for (let i = 0; i < poly.length; i++) {
if (poly[i] === null || poly[i] === undefined) {
return false;
}
}
if (polygonArea(poly) < 0.001) {
return false;
}
return true;
}
function meetsMinSize(poly, pieceCount) {
let bounds = polygonBounds(poly);
if (bounds.width < _partMinPieceSize) {
return false;
}
if (bounds.height < _partMinPieceSize) {
return false;
}
if (minEdgeLength(poly) < _partGap * 2) {
return false;
}
if (pieceCount > 1) {
let minArea = pieceCount * _partMinPieceSize * _partMinPieceSize;
if (polygonArea(poly) < minArea) {
return false;
}
}
if (!survivesInset(poly, _partGap)) {
return false;
}
return true;
}
function survivesInset(poly, gap) {
let insetted = insetPolygon(poly, gap / 2);
if (!isDrawablePolygon(insetted)) {
return false;
}
let bounds = polygonBounds(insetted);
if (bounds.width < _partMinInsetSize) {
return false;
}
if (bounds.height < _partMinInsetSize) {
return false;
}
if (minEdgeLength(insetted) < gap) {
return false;
}
for (let i = 0; i < insetted.length; i++) {
if (!pointInsideConvex(poly, insetted[i].x, insetted[i].y)) {
return false;
}
}
return true;
}
function isValidSplit(parts, splitsLeft) {
if (parts[0].length < 3 || parts[1].length < 3) {
return false;
}
if (!meetsMinSize(parts[1], 1)) {
return false;
}
if (!meetsMinSize(parts[0], splitsLeft)) {
return false;
}
return true;
}
function partition(poly, prev, splitsLeft) {
if (splitsLeft === 0) {
if (!meetsMinSize(poly, 1)) {
return null;
}
return [poly];
}
for (let tryCount = 0; tryCount < 30; tryCount++) {
let cut = nextSplitLine(poly, prev);
if (cut === null) {
continue;
}
let parts = splitPolygon(poly, cut);
if (!isValidSplit(parts, splitsLeft)) {
continue;
}
let rest = partition(parts[0], cut, splitsLeft - 1);
if (rest === null) {
continue;
}
let result = [parts[1]];
for (let i = 0; i < rest.length; i++) {
result.push(rest[i]);
}
return result;
}
return null;
}
function validatePieces(pieceList) {
for (let i = 0; i < pieceList.length; i++) {
if (!meetsMinSize(pieceList[i], 1)) {
return false;
}
}
return true;
}
function polygonCentroid(poly) {
let cx = 0;
let cy = 0;
for (let i = 0; i < poly.length; i++) {
cx += poly[i].x;
cy += poly[i].y;
}
return { x: cx / poly.length, y: cy / poly.length };
}
function edgeNormalOutward(a, b, poly) {
let ex = b.x - a.x;
let ey = b.y - a.y;
let len = sqrt(ex * ex + ey * ey);
if (len < 0.0001) {
return { x: 0, y: 0 };
}
let nx = -ey / len;
let ny = ex / len;
let c = polygonCentroid(poly);
let midx = (a.x + b.x) / 2;
let midy = (a.y + b.y) / 2;
let toCenter = (c.x - midx) * nx + (c.y - midy) * ny;
if (toCenter > 0) {
nx = -nx;
ny = -ny;
}
return { x: nx, y: ny };
}
function intersectLines(l1, l2) {
return intersectEdge(l1.p1, l1.p2, l2);
}
function insetPolygon(poly, amount) {
let n = poly.length;
if (n < 3) {
return poly;
}
if (amount === 0) {
return poly;
}
let offsetLines = [];
for (let i = 0; i < n; i++) {
let a = poly[i];
let b = poly[(i + 1) % n];
let norm = edgeNormalOutward(a, b, poly);
offsetLines.push({
p1: { x: a.x - norm.x * amount, y: a.y - norm.y * amount },
p2: { x: b.x - norm.x * amount, y: b.y - norm.y * amount },
});
}
let out = [];
for (let i = 0; i < n; i++) {
let prev = offsetLines[(i + n - 1) % n];
let curr = offsetLines[i];
let hit = intersectLines(prev, curr);
if (hit === null) {
let v = poly[i];
let normPrev = edgeNormalOutward(poly[(i + n - 1) % n], v, poly);
hit = {
x: v.x - normPrev.x * amount,
y: v.y - normPrev.y * amount,
};
}
out.push(hit);
}
if (!isDrawablePolygon(out)) {
return null;
}
return out;
}
function gappedPieces(source, gap) {
let out = [];
// 境界の AA で背景が透けるのを防ぐため、わずかに重ねる
let inset = gap / 2 - 1;
for (let i = 0; i < source.length; i++) {
let shrunk = insetPolygon(source[i], inset);
if (isDrawablePolygon(shrunk)) {
out.push(shrunk);
}
}
return out;
}
function buildPartition(size, gap, pieceCount, minPieceSize) {
_partSize = size;
_partGap = gap;
_partPieceCount = pieceCount;
_partMinPieceSize = minPieceSize;
_partMinInsetSize = minPieceSize - gap;
if (_partMinInsetSize < minPieceSize * 0.5) {
_partMinInsetSize = minPieceSize * 0.5;
}
for (let attempt = 0; attempt < 200; attempt++) {
let result = partition(makeSquare(), null, _partPieceCount - 1);
if (result !== null && validatePieces(result)) {
return result;
}
}
return [makeSquare()];
} const { animate } = anime;
let _frameSize = 650;
let _frameGap = 50;
let _frameMinPieceCount = 3;
let _frameMaxPieceCount = 6;
let _frameMinPieceSize = 100;
let _frameLayerGap = 40;
let _frameZBias = 0.1;
let _frameDuration = 2000;
let _framePieces = [];
let _frameBuffer;
let _frameSeed = 0;
let _frameViewRotX = 0;
let _frameViewRotY = 0;
let _frameAnimeValue = {
count: 0,
};
function _rebuildFramePieces() {
let range = _frameMaxPieceCount - _frameMinPieceCount + 1;
let pieceCount = _frameMinPieceCount + (_frameSeed % range);
randomSeed(_frameSeed);
_framePieces = buildPartition(
_frameSize,
_frameGap,
pieceCount,
_frameMinPieceSize,
);
_frameViewRotX = random(-TAU / 8, TAU / 8);
_frameViewRotY = random(-TAU / 8, TAU / 8);
}
function _handleFrameLoop() {
if (_frameAnimeValue.count > 0.01) {
return;
}
_frameSeed += 1;
_rebuildFramePieces();
}
function _computePieceMeta(poly) {
let bounds = polygonBounds(poly);
return {
minX: bounds.minX,
minY: bounds.minY,
bboxW: bounds.width,
bboxH: bounds.height,
};
}
function _toLocalPolygon(poly, meta) {
let local = [];
for (let i = 0; i < poly.length; i++) {
local.push({
x: poly[i].x - meta.minX,
y: poly[i].y - meta.minY,
});
}
return local;
}
function _pieceLayerZ(index, count) {
return (index - (count - 1) / 2) * _frameLayerGap;
}
function _pieceDrawZ(index, count, animT) {
return _pieceLayerZ(index, count) * animT + index * _frameZBias;
}
function _renderClippedPiece(poly, meta, drawContent, frameState) {
if (!isDrawablePolygon(poly)) {
return;
}
let localPoly = _toLocalPolygon(poly, meta);
let ctx = _frameBuffer.drawingContext;
ctx.save();
ctx.clearRect(0, 0, _frameBuffer.width, _frameBuffer.height);
ctx.beginPath();
ctx.moveTo(localPoly[0].x, localPoly[0].y);
for (let i = 1; i < localPoly.length; i++) {
ctx.lineTo(localPoly[i].x, localPoly[i].y);
}
ctx.closePath();
ctx.clip();
drawContent(ctx, meta, frameState);
ctx.restore();
_frameBuffer.setModified(true);
}
function setupPartitionFrame(options) {
if (options.size !== undefined) {
_frameSize = options.size;
}
if (options.gap !== undefined) {
_frameGap = options.gap;
}
if (options.minPieceCount !== undefined) {
_frameMinPieceCount = options.minPieceCount;
}
if (options.maxPieceCount !== undefined) {
_frameMaxPieceCount = options.maxPieceCount;
}
if (options.minPieceSize !== undefined) {
_frameMinPieceSize = options.minPieceSize;
}
if (options.layerGap !== undefined) {
_frameLayerGap = options.layerGap;
}
if (options.zBias !== undefined) {
_frameZBias = options.zBias;
}
if (options.duration !== undefined) {
_frameDuration = options.duration;
}
_rebuildFramePieces();
_frameBuffer = createImage(ceil(_frameSize), ceil(_frameSize));
animate(_frameAnimeValue, {
count: 1,
duration: _frameDuration,
ease: "inOutExpo",
loop: true,
alternate: true,
onLoop: _handleFrameLoop,
}).play();
}
function drawPartitionFrame(drawContent) {
let animT = _frameAnimeValue.count;
let currentGap = _frameGap * animT;
let offset = _frameSize / 2;
let frameState = {
animT: animT,
bufferW: _frameBuffer.width,
bufferH: _frameBuffer.height,
size: _frameSize,
};
push();
rotateX(_frameViewRotX * animT);
rotateY(_frameViewRotY * animT);
translate(-offset, -offset, 0);
let displayPieces = gappedPieces(_framePieces, currentGap);
for (let i = 0; i < displayPieces.length; i++) {
let poly = displayPieces[i];
let meta = _computePieceMeta(poly);
_renderClippedPiece(poly, meta, drawContent, frameState);
push();
translate(0, 0, _pieceDrawZ(i, displayPieces.length, animT));
image(
_frameBuffer,
meta.minX,
meta.minY,
meta.bboxW,
meta.bboxH,
0,
0,
meta.bboxW,
meta.bboxH,
);
pop();
}
pop();
} const W = 800;
const H = 800;
const S = 650;
const GRID = 10;
let palette = [];
let boxSize;
let gridOrigin;
function setup() {
createCanvas(W, H, WEBGL);
palette = colorArray[1].colors;
ortho();
boxSize = (S / (GRID - 1)) * 1.25;
gridOrigin = (S - (GRID - 1) * boxSize) / 2;
setupPartitionFrame({
size: S,
gap: 50,
minPieceCount: 3,
maxPieceCount: 6,
minPieceSize: 100,
layerGap: boxSize / 2,
});
}
function draw() {
background(palette[4]);
drawPartitionFrame(drawPieceContent);
}
function drawPieceContent(ctx, meta, frame) {
let phase = frameCount * 0.005 * TAU;
let amp = boxSize / 2;
let center = amp * sin(0.5 * TAU + phase);
let wave = [];
for (let k = 0; k < GRID; k++) {
wave[k] = amp * sin((k / (GRID - 1)) * TAU + phase) - center;
}
ctx.fillStyle = palette[3];
ctx.fillRect(0, 0, frame.bufferW, frame.bufferH);
ctx.strokeStyle = palette[4];
ctx.lineWidth = 5;
ctx.lineCap = "round";
ctx.beginPath();
for (let j = 0; j < GRID; j++) {
for (let i = 0; i < GRID - 1; i++) {
ctx.moveTo(
gridOrigin + i * boxSize + wave[j] - meta.minX,
gridOrigin + j * boxSize + wave[i] - meta.minY,
);
ctx.lineTo(
gridOrigin + (i + 1) * boxSize + wave[j] - meta.minX,
gridOrigin + j * boxSize + wave[i + 1] - meta.minY,
);
}
}
for (let i = 0; i < GRID; i++) {
for (let j = 0; j < GRID - 1; j++) {
ctx.moveTo(
gridOrigin + i * boxSize + wave[j] - meta.minX,
gridOrigin + j * boxSize + wave[i] - meta.minY,
);
ctx.lineTo(
gridOrigin + i * boxSize + wave[j + 1] - meta.minX,
gridOrigin + (j + 1) * boxSize + wave[i] - meta.minY,
);
}
}
ctx.stroke();
}
const colorArray = [
{
id: 0,
colors: ["#253276", "#dfdad3", "#ffffff", "#000000"],
},
{
id: 1,
colors: [
"#9dbdba",
"#f8b042",
"#e47763",
"#253276",
"#dfdad3",
"#FFFFFF",
"#000000",
],
},
]; let _partSize = 650;
let _partGap = 50;
let _partPieceCount = 4;
let _partMinPieceSize = 200;
let _partMinInsetSize = 150;
function makeSquare() {
return [
{ x: 0, y: 0 },
{ x: _partSize, y: 0 },
{ x: _partSize, y: _partSize },
{ x: 0, y: _partSize },
];
}
function lineSide(x, y, cut) {
return (
(cut.p2.x - cut.p1.x) * (y - cut.p1.y) -
(cut.p2.y - cut.p1.y) * (x - cut.p1.x)
);
}
function intersectEdge(a, b, cut) {
let x1 = a.x;
let y1 = a.y;
let x2 = b.x;
let y2 = b.y;
let x3 = cut.p1.x;
let y3 = cut.p1.y;
let x4 = cut.p2.x;
let y4 = cut.p2.y;
let denom = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4);
if (abs(denom) < 0.0001) {
return null;
}
let t = ((x1 - x3) * (y3 - y4) - (y1 - y3) * (x3 - x4)) / denom;
return { x: x1 + t * (x2 - x1), y: y1 + t * (y2 - y1) };
}
function clipPolygon(poly, cut, keepPositive) {
let out = [];
for (let i = 0; i < poly.length; i++) {
let curr = poly[i];
let prev = poly[(i + poly.length - 1) % poly.length];
let currIn = false;
let prevIn = false;
let currSide = lineSide(curr.x, curr.y, cut);
let prevSide = lineSide(prev.x, prev.y, cut);
if (keepPositive) {
currIn = currSide >= 0;
prevIn = prevSide >= 0;
} else {
currIn = currSide <= 0;
prevIn = prevSide <= 0;
}
if (currIn) {
if (!prevIn) {
let hit = intersectEdge(prev, curr, cut);
if (hit !== null) {
out.push(hit);
}
}
out.push({ x: curr.x, y: curr.y });
} else if (prevIn) {
let hit = intersectEdge(prev, curr, cut);
if (hit !== null) {
out.push(hit);
}
}
}
return out;
}
function splitPolygon(poly, cut) {
return [clipPolygon(poly, cut, true), clipPolygon(poly, cut, false)];
}
function edgePoint(poly, edgeIndex, t) {
let a = poly[edgeIndex];
let b = poly[(edgeIndex + 1) % poly.length];
return {
x: a.x + (b.x - a.x) * t,
y: a.y + (b.y - a.y) * t,
};
}
function edgeLength(a, b) {
let dx = b.x - a.x;
let dy = b.y - a.y;
return sqrt(dx * dx + dy * dy);
}
function minEdgeLength(poly) {
let minLen = 999999;
for (let i = 0; i < poly.length; i++) {
let len = edgeLength(poly[i], poly[(i + 1) % poly.length]);
if (len < minLen) {
minLen = len;
}
}
return minLen;
}
function randomEdgeT(poly, edgeIndex) {
let len = edgeLength(poly[edgeIndex], poly[(edgeIndex + 1) % poly.length]);
let margin = (_partMinPieceSize / len) * 0.55;
if (margin > 0.45) {
margin = 0.45;
}
if (margin < 0.05) {
margin = 0.05;
}
return random(margin, 1 - margin);
}
function pointInsideConvex(poly, px, py) {
let n = poly.length;
let sign = 0;
for (let i = 0; i < n; i++) {
let a = poly[i];
let b = poly[(i + 1) % n];
let cross = (b.x - a.x) * (py - a.y) - (b.y - a.y) * (px - a.x);
if (abs(cross) < 0.001) {
continue;
}
if (sign === 0) {
if (cross > 0) {
sign = 1;
} else {
sign = -1;
}
} else {
if (cross > 0) {
if (sign !== 1) {
return false;
}
} else {
if (sign !== -1) {
return false;
}
}
}
}
return true;
}
function onCutLine(p, cut) {
return abs(lineSide(p.x, p.y, cut)) < 1;
}
function nextSplitLine(poly, prev) {
let p1 = null;
let p2 = null;
if (prev === null) {
let edgeA = int(random(poly.length));
let edgeB = int(random(poly.length));
while (edgeB === edgeA) {
edgeB = int(random(poly.length));
}
p1 = edgePoint(poly, edgeA, randomEdgeT(poly, edgeA));
p2 = edgePoint(poly, edgeB, randomEdgeT(poly, edgeB));
} else {
for (let i = 0; i < poly.length; i++) {
let a = poly[i];
let b = poly[(i + 1) % poly.length];
if (onCutLine(a, prev) && onCutLine(b, prev)) {
p1 = edgePoint(poly, i, randomEdgeT(poly, i));
break;
}
}
if (p1 === null) {
return null;
}
let edgeB = int(random(poly.length));
p2 = edgePoint(poly, edgeB, randomEdgeT(poly, edgeB));
}
return { p1: p1, p2: p2 };
}
function polygonBounds(poly) {
let minX = poly[0].x;
let maxX = poly[0].x;
let minY = poly[0].y;
let maxY = poly[0].y;
for (let i = 1; i < poly.length; i++) {
if (poly[i].x < minX) {
minX = poly[i].x;
}
if (poly[i].x > maxX) {
maxX = poly[i].x;
}
if (poly[i].y < minY) {
minY = poly[i].y;
}
if (poly[i].y > maxY) {
maxY = poly[i].y;
}
}
return {
minX: minX,
minY: minY,
maxX: maxX,
maxY: maxY,
width: maxX - minX,
height: maxY - minY,
};
}
function polygonArea(poly) {
if (poly.length < 3) {
return 0;
}
let area = 0;
for (let i = 0; i < poly.length; i++) {
let j = (i + 1) % poly.length;
area += poly[i].x * poly[j].y - poly[j].x * poly[i].y;
}
return abs(area / 2);
}
function isDrawablePolygon(poly) {
if (poly === null || poly === undefined) {
return false;
}
if (poly.length < 3) {
return false;
}
for (let i = 0; i < poly.length; i++) {
if (poly[i] === null || poly[i] === undefined) {
return false;
}
}
if (polygonArea(poly) < 0.001) {
return false;
}
return true;
}
function meetsMinSize(poly, pieceCount) {
let bounds = polygonBounds(poly);
if (bounds.width < _partMinPieceSize) {
return false;
}
if (bounds.height < _partMinPieceSize) {
return false;
}
if (minEdgeLength(poly) < _partGap * 2) {
return false;
}
if (pieceCount > 1) {
let minArea = pieceCount * _partMinPieceSize * _partMinPieceSize;
if (polygonArea(poly) < minArea) {
return false;
}
}
if (!survivesInset(poly, _partGap)) {
return false;
}
return true;
}
function survivesInset(poly, gap) {
let insetted = insetPolygon(poly, gap / 2);
if (!isDrawablePolygon(insetted)) {
return false;
}
let bounds = polygonBounds(insetted);
if (bounds.width < _partMinInsetSize) {
return false;
}
if (bounds.height < _partMinInsetSize) {
return false;
}
if (minEdgeLength(insetted) < gap) {
return false;
}
for (let i = 0; i < insetted.length; i++) {
if (!pointInsideConvex(poly, insetted[i].x, insetted[i].y)) {
return false;
}
}
return true;
}
function isValidSplit(parts, splitsLeft) {
if (parts[0].length < 3 || parts[1].length < 3) {
return false;
}
if (!meetsMinSize(parts[1], 1)) {
return false;
}
if (!meetsMinSize(parts[0], splitsLeft)) {
return false;
}
return true;
}
function partition(poly, prev, splitsLeft) {
if (splitsLeft === 0) {
if (!meetsMinSize(poly, 1)) {
return null;
}
return [poly];
}
for (let tryCount = 0; tryCount < 30; tryCount++) {
let cut = nextSplitLine(poly, prev);
if (cut === null) {
continue;
}
let parts = splitPolygon(poly, cut);
if (!isValidSplit(parts, splitsLeft)) {
continue;
}
let rest = partition(parts[0], cut, splitsLeft - 1);
if (rest === null) {
continue;
}
let result = [parts[1]];
for (let i = 0; i < rest.length; i++) {
result.push(rest[i]);
}
return result;
}
return null;
}
function validatePieces(pieceList) {
for (let i = 0; i < pieceList.length; i++) {
if (!meetsMinSize(pieceList[i], 1)) {
return false;
}
}
return true;
}
function polygonCentroid(poly) {
let cx = 0;
let cy = 0;
for (let i = 0; i < poly.length; i++) {
cx += poly[i].x;
cy += poly[i].y;
}
return { x: cx / poly.length, y: cy / poly.length };
}
function edgeNormalOutward(a, b, poly) {
let ex = b.x - a.x;
let ey = b.y - a.y;
let len = sqrt(ex * ex + ey * ey);
if (len < 0.0001) {
return { x: 0, y: 0 };
}
let nx = -ey / len;
let ny = ex / len;
let c = polygonCentroid(poly);
let midx = (a.x + b.x) / 2;
let midy = (a.y + b.y) / 2;
let toCenter = (c.x - midx) * nx + (c.y - midy) * ny;
if (toCenter > 0) {
nx = -nx;
ny = -ny;
}
return { x: nx, y: ny };
}
function intersectLines(l1, l2) {
return intersectEdge(l1.p1, l1.p2, l2);
}
function insetPolygon(poly, amount) {
let n = poly.length;
if (n < 3) {
return poly;
}
if (amount === 0) {
return poly;
}
let offsetLines = [];
for (let i = 0; i < n; i++) {
let a = poly[i];
let b = poly[(i + 1) % n];
let norm = edgeNormalOutward(a, b, poly);
offsetLines.push({
p1: { x: a.x - norm.x * amount, y: a.y - norm.y * amount },
p2: { x: b.x - norm.x * amount, y: b.y - norm.y * amount },
});
}
let out = [];
for (let i = 0; i < n; i++) {
let prev = offsetLines[(i + n - 1) % n];
let curr = offsetLines[i];
let hit = intersectLines(prev, curr);
if (hit === null) {
let v = poly[i];
let normPrev = edgeNormalOutward(poly[(i + n - 1) % n], v, poly);
hit = {
x: v.x - normPrev.x * amount,
y: v.y - normPrev.y * amount,
};
}
out.push(hit);
}
if (!isDrawablePolygon(out)) {
return null;
}
return out;
}
function gappedPieces(source, gap) {
let out = [];
// 境界の AA で背景が透けるのを防ぐため、わずかに重ねる
let inset = gap / 2 - 1;
for (let i = 0; i < source.length; i++) {
let shrunk = insetPolygon(source[i], inset);
if (isDrawablePolygon(shrunk)) {
out.push(shrunk);
}
}
return out;
}
function buildPartition(size, gap, pieceCount, minPieceSize) {
_partSize = size;
_partGap = gap;
_partPieceCount = pieceCount;
_partMinPieceSize = minPieceSize;
_partMinInsetSize = minPieceSize - gap;
if (_partMinInsetSize < minPieceSize * 0.5) {
_partMinInsetSize = minPieceSize * 0.5;
}
for (let attempt = 0; attempt < 200; attempt++) {
let result = partition(makeSquare(), null, _partPieceCount - 1);
if (result !== null && validatePieces(result)) {
return result;
}
}
return [makeSquare()];
} const { animate } = anime;
let _frameSize = 650;
let _frameGap = 50;
let _frameMinPieceCount = 3;
let _frameMaxPieceCount = 6;
let _frameMinPieceSize = 100;
let _frameLayerGap = 40;
let _frameZBias = 0.1;
let _frameDuration = 2000;
let _framePieces = [];
let _frameBuffer;
let _frameSeed = 0;
let _frameViewRotX = 0;
let _frameViewRotY = 0;
let _frameAnimeValue = {
count: 0,
};
function _rebuildFramePieces() {
let range = _frameMaxPieceCount - _frameMinPieceCount + 1;
let pieceCount = _frameMinPieceCount + (_frameSeed % range);
randomSeed(_frameSeed);
_framePieces = buildPartition(
_frameSize,
_frameGap,
pieceCount,
_frameMinPieceSize,
);
_frameViewRotX = random(-TAU / 8, TAU / 8);
_frameViewRotY = random(-TAU / 8, TAU / 8);
}
function _handleFrameLoop() {
if (_frameAnimeValue.count > 0.01) {
return;
}
_frameSeed += 1;
_rebuildFramePieces();
}
function _computePieceMeta(poly) {
let bounds = polygonBounds(poly);
return {
minX: bounds.minX,
minY: bounds.minY,
bboxW: bounds.width,
bboxH: bounds.height,
};
}
function _toLocalPolygon(poly, meta) {
let local = [];
for (let i = 0; i < poly.length; i++) {
local.push({
x: poly[i].x - meta.minX,
y: poly[i].y - meta.minY,
});
}
return local;
}
function _pieceLayerZ(index, count) {
return (index - (count - 1) / 2) * _frameLayerGap;
}
function _pieceDrawZ(index, count, animT) {
return _pieceLayerZ(index, count) * animT + index * _frameZBias;
}
function _renderClippedPiece(poly, meta, drawContent, frameState) {
if (!isDrawablePolygon(poly)) {
return;
}
let localPoly = _toLocalPolygon(poly, meta);
let ctx = _frameBuffer.drawingContext;
ctx.save();
ctx.clearRect(0, 0, _frameBuffer.width, _frameBuffer.height);
ctx.beginPath();
ctx.moveTo(localPoly[0].x, localPoly[0].y);
for (let i = 1; i < localPoly.length; i++) {
ctx.lineTo(localPoly[i].x, localPoly[i].y);
}
ctx.closePath();
ctx.clip();
drawContent(ctx, meta, frameState);
ctx.restore();
_frameBuffer.setModified(true);
}
function setupPartitionFrame(options) {
if (options.size !== undefined) {
_frameSize = options.size;
}
if (options.gap !== undefined) {
_frameGap = options.gap;
}
if (options.minPieceCount !== undefined) {
_frameMinPieceCount = options.minPieceCount;
}
if (options.maxPieceCount !== undefined) {
_frameMaxPieceCount = options.maxPieceCount;
}
if (options.minPieceSize !== undefined) {
_frameMinPieceSize = options.minPieceSize;
}
if (options.layerGap !== undefined) {
_frameLayerGap = options.layerGap;
}
if (options.zBias !== undefined) {
_frameZBias = options.zBias;
}
if (options.duration !== undefined) {
_frameDuration = options.duration;
}
_rebuildFramePieces();
_frameBuffer = createImage(ceil(_frameSize), ceil(_frameSize));
animate(_frameAnimeValue, {
count: 1,
duration: _frameDuration,
ease: "inOutExpo",
loop: true,
alternate: true,
onLoop: _handleFrameLoop,
}).play();
}
function drawPartitionFrame(drawContent) {
let animT = _frameAnimeValue.count;
let currentGap = _frameGap * animT;
let offset = _frameSize / 2;
let frameState = {
animT: animT,
bufferW: _frameBuffer.width,
bufferH: _frameBuffer.height,
size: _frameSize,
};
push();
rotateX(_frameViewRotX * animT);
rotateY(_frameViewRotY * animT);
translate(-offset, -offset, 0);
let displayPieces = gappedPieces(_framePieces, currentGap);
for (let i = 0; i < displayPieces.length; i++) {
let poly = displayPieces[i];
let meta = _computePieceMeta(poly);
_renderClippedPiece(poly, meta, drawContent, frameState);
push();
translate(0, 0, _pieceDrawZ(i, displayPieces.length, animT));
image(
_frameBuffer,
meta.minX,
meta.minY,
meta.bboxW,
meta.bboxH,
0,
0,
meta.bboxW,
meta.bboxH,
);
pop();
}
pop();
}