diff --git a/codepen/arcs.html b/codepen/arcs.html new file mode 100644 index 0000000..85071ef --- /dev/null +++ b/codepen/arcs.html @@ -0,0 +1,124 @@ + + + \ No newline at end of file diff --git a/codepen/cubes.html b/codepen/cubes.html new file mode 100644 index 0000000..b66d64e --- /dev/null +++ b/codepen/cubes.html @@ -0,0 +1,139 @@ + + \ No newline at end of file diff --git a/codepen/drops.html b/codepen/drops.html new file mode 100644 index 0000000..b624009 --- /dev/null +++ b/codepen/drops.html @@ -0,0 +1,165 @@ +
+ + + + + \ No newline at end of file diff --git a/codepen/face.html b/codepen/face.html new file mode 100644 index 0000000..382260b --- /dev/null +++ b/codepen/face.html @@ -0,0 +1,157 @@ +
+
+
+ + + + \ No newline at end of file diff --git a/codepen/foggy-forest.html b/codepen/foggy-forest.html new file mode 100644 index 0000000..1e1eab5 --- /dev/null +++ b/codepen/foggy-forest.html @@ -0,0 +1,102 @@ + + + \ No newline at end of file diff --git a/codepen/followers.html b/codepen/followers.html new file mode 100644 index 0000000..15ced05 --- /dev/null +++ b/codepen/followers.html @@ -0,0 +1,101 @@ + + \ No newline at end of file diff --git a/codepen/morbius.html b/codepen/morbius.html new file mode 100644 index 0000000..ad3cd85 --- /dev/null +++ b/codepen/morbius.html @@ -0,0 +1,66 @@ + \ No newline at end of file diff --git a/codepen/morph.html b/codepen/morph.html new file mode 100644 index 0000000..d0fafe5 --- /dev/null +++ b/codepen/morph.html @@ -0,0 +1,239 @@ + + + \ No newline at end of file diff --git a/codepen/mother-box.html b/codepen/mother-box.html new file mode 100644 index 0000000..9314b21 --- /dev/null +++ b/codepen/mother-box.html @@ -0,0 +1,209 @@ + + + \ No newline at end of file diff --git a/codepen/nebula.html b/codepen/nebula.html new file mode 100644 index 0000000..1dd40b4 --- /dev/null +++ b/codepen/nebula.html @@ -0,0 +1,166 @@ + + + + + \ No newline at end of file diff --git a/codepen/pixeliser.html b/codepen/pixeliser.html new file mode 100644 index 0000000..14da605 --- /dev/null +++ b/codepen/pixeliser.html @@ -0,0 +1,123 @@ + + + + + + \ No newline at end of file diff --git a/codepen/rings.html b/codepen/rings.html new file mode 100644 index 0000000..38eb5c3 --- /dev/null +++ b/codepen/rings.html @@ -0,0 +1,137 @@ + + + \ No newline at end of file diff --git a/codepen/ripples.html b/codepen/ripples.html new file mode 100644 index 0000000..4a51ef2 --- /dev/null +++ b/codepen/ripples.html @@ -0,0 +1,90 @@ +
+ + + + \ No newline at end of file diff --git a/codepen/solar.html b/codepen/solar.html new file mode 100644 index 0000000..bc27c53 --- /dev/null +++ b/codepen/solar.html @@ -0,0 +1,84 @@ +
+

Position of the moon, sun and planets in our solar system, as seen from earth

+ + + + \ No newline at end of file diff --git a/codepen/tau.html b/codepen/tau.html new file mode 100644 index 0000000..dba8798 --- /dev/null +++ b/codepen/tau.html @@ -0,0 +1,132 @@ + + + \ No newline at end of file diff --git a/codepen/tree-of-thought.html b/codepen/tree-of-thought.html new file mode 100644 index 0000000..621360b --- /dev/null +++ b/codepen/tree-of-thought.html @@ -0,0 +1,175 @@ + + + \ No newline at end of file diff --git a/codepen/trigonometry.html b/codepen/trigonometry.html new file mode 100644 index 0000000..e4ed479 --- /dev/null +++ b/codepen/trigonometry.html @@ -0,0 +1,278 @@ + + + + + \ No newline at end of file diff --git a/codepen/wander-lost.html b/codepen/wander-lost.html new file mode 100644 index 0000000..0e7d995 --- /dev/null +++ b/codepen/wander-lost.html @@ -0,0 +1,289 @@ + + + \ No newline at end of file diff --git a/codepen/worlds-end.html b/codepen/worlds-end.html new file mode 100644 index 0000000..ed28b0a --- /dev/null +++ b/codepen/worlds-end.html @@ -0,0 +1,203 @@ + + + \ No newline at end of file diff --git a/old/src/experiments/polyhedron/1.ts b/old/src/experiments/polyhedron/1.ts deleted file mode 100644 index a3d2025..0000000 --- a/old/src/experiments/polyhedron/1.ts +++ /dev/null @@ -1,5 +0,0 @@ -import * as THREE from 'three'; -import * as Base from './base'; - -const shape = new THREE.TetrahedronGeometry(200); -export default Base.run(shape, 10, 20); \ No newline at end of file diff --git a/old/src/experiments/polyhedron/2.ts b/old/src/experiments/polyhedron/2.ts deleted file mode 100644 index e3bb52c..0000000 --- a/old/src/experiments/polyhedron/2.ts +++ /dev/null @@ -1,5 +0,0 @@ -import * as THREE from 'three'; -import * as Base from './base'; - -const shape = new THREE.DodecahedronGeometry(200); -export default Base.run(shape, 1, 30); \ No newline at end of file diff --git a/old/src/experiments/polyhedron/base.ts b/old/src/experiments/polyhedron/base.ts deleted file mode 100644 index 50f26da..0000000 --- a/old/src/experiments/polyhedron/base.ts +++ /dev/null @@ -1,123 +0,0 @@ -import { PI2 } from '@/constants'; -import SimplexNoise from 'simplex-noise'; -import * as THREE from 'three'; - -const FRAME = 640; -const PARTICLES = 10000; -const SIZE = 2; - -const scene = new THREE.Scene(); -const camera = new THREE.PerspectiveCamera(75, 1, 0.1, 1000); -camera.position.z = 500; - -const renderer = new THREE.WebGLRenderer(); -renderer.setSize(FRAME, FRAME); - -export function run(shape: THREE.PolyhedronGeometry, layers: number, spread: number) { - const lines = []; - for (let i = 0; i < shape.attributes.position.array.length; i += 9) { - const face = { - a: { - x: shape.attributes.position.array[i], - y: shape.attributes.position.array[i + 1], - z: shape.attributes.position.array[i + 2], - }, - b: { - x: shape.attributes.position.array[i + 3], - y: shape.attributes.position.array[i + 4], - z: shape.attributes.position.array[i + 5], - }, - c: { - x: shape.attributes.position.array[i + 6], - y: shape.attributes.position.array[i + 7], - z: shape.attributes.position.array[i + 8], - }, - }; - - lines.push({ - from: face.a, - to: face.b, - }); - - lines.push({ - from: face.b, - to: face.c, - }); - - lines.push({ - from: face.c, - to: face.a, - }); - } - - const particles = []; - const vertices = []; - const group = Math.ceil(PARTICLES / lines.length / layers); - for (let i = 0; i < PARTICLES; i++) { - const line = lines[Math.floor(i / group / layers)]; - const index = i % group; - - const n = 1.6 * (index / group) - 0.3; - const dX = line.from.x + (line.to.x - line.from.x) * n; - const dY = line.from.y + (line.to.y - line.from.y) * n; - const dZ = line.from.z + (line.to.z - line.from.z) * n; - const position = new THREE.Vector3(dX, dY, dZ); - vertices.push(position.clone()); - - const particle = { - point: vertices[i], - group: Math.floor(i / group), - layer: Math.floor(i / group) % layers, - position, - index, - line, - }; - particles.push(particle); - } - - const geometry = new THREE.BufferGeometry(); - - const material = new THREE.PointsMaterial({ - color: 0xFFFFFF, - opacity: 0.2, - size: SIZE, - transparent: true, - }); - - const points = new THREE.Points(geometry, material); - scene.add(points); - - const simplex = new SimplexNoise(); - - return { - duration: 10, - element: renderer.domElement, - size: SIZE, - onTick: (tick) => { - const d = (0.5 * Math.sin(PI2 * tick) + 0.5); - const p = 1 - 0.75 * d; - const s = spread * d + 5; - - scene.rotation.y = PI2 * tick; - - for (let i = 0; i < particles.length; i++) { - const particle = particles[i]; - - const n = particle.index / group; - const alpha = PI2 * (n + tick); - const cos = Math.cos(alpha); - const sin = Math.sin(alpha); - const noiseX = simplex.noise3D(cos + particle.group + 1, sin, n) * s; - const noiseY = simplex.noise3D(cos + particle.group + 2, sin, n) * s; - const noiseZ = simplex.noise3D(cos + particle.group + 3, sin, n) * s; - particle.point.x = particle.position.x * p + noiseX; - particle.point.y = particle.position.y * p + noiseY; - particle.point.z = particle.position.z * p + noiseZ; - } - - geometry.setFromPoints(vertices); - - renderer.render(scene, camera); - }, - } -} diff --git a/package.json b/package.json index c51b477..873372c 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,7 @@ "@types/lodash": "^4.17.7", "@types/three": "^0.166.0", "alea": "^1.0.1", + "gsap": "^3.12.5", "lodash": "^4.17.21", "next": "14.2.5", "pixi-filters": "^6.0.4", diff --git a/public/assets/images/duality.png b/public/assets/images/duality.png new file mode 100644 index 0000000..43699d6 Binary files /dev/null and b/public/assets/images/duality.png differ diff --git a/public/assets/images/polyhedron-1.png b/public/assets/images/polyhedron-1.png new file mode 100644 index 0000000..20331e6 Binary files /dev/null and b/public/assets/images/polyhedron-1.png differ diff --git a/public/assets/images/polyhedron-2.png b/public/assets/images/polyhedron-2.png new file mode 100644 index 0000000..2d3a056 Binary files /dev/null and b/public/assets/images/polyhedron-2.png differ diff --git a/src/@types/common.d.ts b/src/@types/common.d.ts index 9ea1cab..b49a8ff 100644 --- a/src/@types/common.d.ts +++ b/src/@types/common.d.ts @@ -1,5 +1,7 @@ interface Experiment { + date: string; disabled?: boolean; + featured?: boolean; image: string; index: string; path: string; diff --git a/src/app/cubic/1/page.tsx b/src/app/cubic/1/page.tsx index de52e01..fd64410 100644 --- a/src/app/cubic/1/page.tsx +++ b/src/app/cubic/1/page.tsx @@ -45,6 +45,9 @@ export default class Cubic1 extends React.Component } } } + + // Trigger state change + this.setState({}); } handleTick = (progress: number) => { diff --git a/src/app/cubic/2/page.tsx b/src/app/cubic/2/page.tsx index b195176..da74d6b 100644 --- a/src/app/cubic/2/page.tsx +++ b/src/app/cubic/2/page.tsx @@ -38,6 +38,9 @@ export default class Cubic2 extends React.Component } } } + + // Trigger state change + this.setState({}); } handleTick = (progress: number) => { diff --git a/src/app/cubic/3/page.tsx b/src/app/cubic/3/page.tsx index 63ec232..6562bb9 100644 --- a/src/app/cubic/3/page.tsx +++ b/src/app/cubic/3/page.tsx @@ -57,6 +57,9 @@ export default class Cubic3 extends React.Component } } } + + // Trigger state change + this.setState({}); } handleTick = (progress: number) => { diff --git a/src/app/duality/Shapes.tsx b/src/app/duality/Shapes.tsx new file mode 100644 index 0000000..1946144 --- /dev/null +++ b/src/app/duality/Shapes.tsx @@ -0,0 +1,260 @@ +import { PI_D2, PI_M2 } from '@/setup'; +import gsap, { Back, Power2 } from 'gsap'; +import * as PIXI from 'pixi.js'; +import { Ticker } from 'pixi.js'; + +export class ShapeBase extends PIXI.Container { + + mask = new PIXI.Graphics(); + maskRounded = false; + progress = 0; + shape = new PIXI.Graphics(); + speed = 0.00001; + + length: number; + maskThickness: number; + radius: number; + tickElement: PIXI.Graphics | null; + velocity: number; + + constructor(radius: number, thickness: number, length = 1, velocity = 0) { + super(); + + this.radius = radius; + this.length = length; + this.velocity = velocity; + + this.addChild(this.mask); + this.maskThickness = thickness + 5; + + this.addChild(this.shape); + this.tickElement = this.shape; + } + + intro = (index: number) => { + const delay = index * 0.1; + this.progress = 0; + + gsap.from(this, { + delay, + duration: 0.5, + ease: Power2.easeInOut, + maskThickness: 0, + }); + + gsap.from(this.scale, { + delay, + duration: 3, + ease: Back.easeInOut, + x: 0.9, + y: 0.9, + }); + + gsap.to(this, { + delay, + duration: 3, + ease: Power2.easeInOut, + progress: Math.PI * this.length, + onUpdate: this.introProgress, + }); + + if (this.velocity) { + PIXI.Ticker.shared.add(this.tick); + } + } + + introProgress = () => { + if (this.mask) { + this.mask.clear(); + this.mask.setStrokeStyle({ + color: 0, + width: this.maskThickness, + }); + this.mask.arc(0, 0, this.radius, -this.progress, this.progress); + + if (this.maskRounded) { + this.mask.circle(Math.sin(PI_D2 + this.progress) * this.radius, Math.cos(PI_D2 + this.progress) * this.radius, 1); + this.mask.circle(Math.sin(PI_D2 - this.progress) * this.radius, Math.cos(PI_D2 - this.progress) * this.radius, 1); + } + + this.mask.stroke(); + } + } + + tick = (ticker: Ticker) => { + if (this.tickElement) { + this.tickElement.rotation += this.velocity * ticker.deltaMS * this.speed; + } + } +} + +export class Circle extends ShapeBase { + + constructor(color: number, radius: number, thickness: number, length?: number, velocity?: number) { + super(radius, thickness, length, velocity); + + this.shape.setStrokeStyle({ + color, + width: thickness, + }); + this.shape.circle(0, 0, radius); + this.shape.stroke(); + + this.tickElement = this.mask; + } + +} + +export class Dashed extends ShapeBase { + + color: number; + radius: number; + thickness: number; + dash: number; + gap: number; + velocity: number; + + constructor(color: number, radius: number, thickness: number, dash: number, gap: number, velocity: number) { + super(radius, thickness, 1, velocity); + + this.maskRounded = true; + + const circumference = PI_M2 * radius; + const segments = Math.round(circumference / (dash + gap)); + const radiusInner = radius - thickness / 2; + const radiusOuter = radiusInner + thickness; + + this.shape.setStrokeStyle({ + color, + width: dash, + }); + for (let i = 0; i < segments; i++) { + const n = (i / segments) * PI_M2; + const x = Math.sin(n); + const y = Math.cos(n); + + this.shape.moveTo(x * radiusInner, y * radiusInner); + this.shape.lineTo(x * radiusOuter, y * radiusOuter); + } + this.shape.stroke(); + } + +} + +export class Iterated extends ShapeBase { + + constructor(color: number, radius: number, thickness: number, segments: number, velocity: number, shapesData: string) { + super(radius, thickness, 1, velocity); + + this.maskRounded = true; + + const bounds = { + left: Number.MAX_VALUE, + right: -Number.MAX_VALUE, + }; + + const shapes = shapesData.split(';') + .map(shapeData => { + const shape = shapeData.split(','); + + const data: any = { + type: shape.length === 3 ? 'circle' : 'line', + }; + + if (data.type === 'circle') { + data.x = parseInt(shape[0]) + radius; + data.y = parseInt(shape[1]); + data.radius = parseInt(shape[2]); + + bounds.left = Math.min(bounds.left, -data.radius); + bounds.right = Math.max(bounds.right, data.radius); + + } else { + data.x = []; + data.y = []; + + for (let i = 0; i < shape.length; i++) { + const value = parseInt(shape[i]); + if (i % 2) { + data.x.push(value + radius); + } else { + data.y.push(value); + } + } + + bounds.left = Math.min.apply(this, [ + bounds.left, + ...data.x, + ]); + bounds.right = Math.max.apply(this, [ + bounds.right, + ...data.x, + ]); + } + + return data; + }); + + this.maskThickness = Math.abs(bounds.right - bounds.left) + 5; + + this.shape.setStrokeStyle({ + color, + width: thickness, + }); + for (let i = 0; i < segments; i++) { + const n = (i / segments) * PI_M2; + const cosN = Math.cos(n); + const sinN = Math.sin(n); + + for (const shape of shapes) { + if (shape.type === 'circle') { + this.shape.circle(shape.x * cosN - shape.y * sinN, shape.y * cosN + shape.x * sinN, shape.radius); + + } else { + for (let k = 0; k < shape.x.length; k++) { + const x = shape.x[k]; + const y = shape.y[k]; + if (k) { + this.shape.lineTo(x * cosN - y * sinN, y * cosN + x * sinN); + } else { + this.shape.moveTo(x * cosN - y * sinN, y * cosN + x * sinN); + } + } + + } + + } + } + this.shape.stroke(); + } + +} + +export class Polygon extends ShapeBase { + + constructor(color: number, radius: number, thickness: number, sides: number, velocity: number) { + super(radius, thickness, 1, velocity); + + const angle = PI_M2 / sides; + + this.shape.setStrokeStyle({ + color, + width: thickness, + }); + for (let i = 0; i <= sides; i++) { + const a = i * angle; + const x = Math.sin(a) * radius; + const y = Math.cos(a) * radius; + + if (i) { + this.shape.lineTo(x, y); + } else { + this.shape.moveTo(x, y); + } + } + this.shape.stroke(); + + this.maskThickness = thickness + 20; + } + +} \ No newline at end of file diff --git a/src/app/duality/Sides.tsx b/src/app/duality/Sides.tsx new file mode 100644 index 0000000..153e568 --- /dev/null +++ b/src/app/duality/Sides.tsx @@ -0,0 +1,110 @@ +import { BLACK, PI_D2, WHITE } from '@/setup'; +import * as PIXI from 'pixi.js'; +import { Circle, Dashed, Iterated, Polygon, ShapeBase } from './Shapes'; + +class SideBase extends PIXI.Container { + + background = new PIXI.Graphics(); + shapes = new PIXI.Container(); + size = 1920; + + constructor(primary: number, secondary: number, offset: number) { + super(); + + const center = this.size / 2; + + // Add background + this.addChild(this.background); + this.background.clear(); + this.background.rect(offset * (center), 0, center, this.size); + this.background.fill(primary); + + // Add shapes container + this.addChild(this.shapes); + this.shapes.x = Math.round(center); + this.shapes.y = Math.round(center); + + // Add shapes mask + const mask = new PIXI.Graphics(); + this.shapes.mask = mask; + mask.clear(); + mask.rect(offset * (center), 0, center, this.size); + mask.fill(0); + + // Add shapes + this.add(new Dashed(secondary, 130, 0.5, 2, 5, 1)); + this.add(new Dashed(secondary, 160, 3, 3, 100, -5)); + this.add(new Dashed(secondary, 260, 0.5, 2, 5, -1)); + this.add(new Circle(secondary, 280, 1)); + this.add(new Dashed(secondary, 290, 10, 1, 15, 3)); + this.add(new Circle(secondary, 300, 1)); + this.add(new Circle(secondary, 270, 2, 0.3, 20)); + this.add(new Circle(secondary, 310, 2, 0.4, -30)); + this.add(new Dashed(secondary, 390, 3, 3, 118, 4)); + this.add(new Dashed(secondary, 470, 2, 2, 15, 3)); + this.add(new Dashed(secondary, 480, 2, 2, 7, -2)); + this.add(new Circle(secondary, 490, 2)); + this.add(new Circle(secondary, 555, 1)); + this.add(new Dashed(secondary, 570, 25, 2, 100, -5)); + this.add(new Circle(secondary, 585, 1)); + this.add(new Dashed(secondary, 600, 2, 2, 7, 6)); + this.add(new Circle(secondary, 625, 2, 0.3, -40)); + this.add(new Circle(secondary, 655, 2, 0.4, 30)); + this.add(new Circle(secondary, 710, 2)); + this.add(new Circle(secondary, 871, 1)); + this.add(new Circle(secondary, 890, 1)); + } + + add = (shape: ShapeBase) => { + this.shapes.addChild(shape); + shape.rotation = this.shapes.children.length % 2 ? PI_D2 : -PI_D2; + } + + intro = () => { + for (const shape of this.shapes.children) { + shape.intro(shape.radius / 890 * this.shapes.children.length); + } + } +} + +export class BlackSide extends SideBase { + + constructor() { + super(BLACK, WHITE, 0); + + this.add(new Polygon(WHITE, 80, 0.5, 8, -5)); + this.add(new Polygon(WHITE, 95, 1, 8, -5)); + this.add(new Polygon(WHITE, 100, 0.5, 8, -5)); + this.add(new Iterated(WHITE, 210, 1, 12, 15, '-5,-10,-5,10,8,0,-5,-10')); + this.add(new Iterated(WHITE, 390, 1, 14, -3, '-40,0,-20,-35,20,-35,40,0,20,35,-20,35,-40,0')); + this.add(new Iterated(WHITE, 520, 1, 50, 5, '-5,-5,5,5;-5,5,5,-5')); + this.add(new Iterated(WHITE, 640, 1, 260, 4, '-10,0,0,-10,10,0,0,10,-10,0')); + this.add(new Polygon(WHITE, 690, 2, 20, -5)); + this.add(new Polygon(WHITE, 695, 0.5, 20, -5)); + this.add(new Iterated(WHITE, 787, 1, 100, -2, '0,50,-3,-50,3,-50,0,50')); + + this.intro(); + } + +} + +export class WhiteSide extends SideBase { + + constructor() { + super(WHITE, BLACK, 1); + + this.add(new Circle(BLACK, 80, 0.5)); + this.add(new Circle(BLACK, 95, 1)); + this.add(new Circle(BLACK, 100, 0.5)); + this.add(new Iterated(BLACK, 210, 1, 12, 15, '0,0,10')); + this.add(new Iterated(BLACK, 390, 1, 14, -3, '0,0,40')); + this.add(new Iterated(BLACK, 520, 1, 50, 5, '0,-6,0,6;-6,0,6,0')); + this.add(new Iterated(BLACK, 640, 1, 260, 4, '0,0,10')); + this.add(new Circle(BLACK, 690, 2)); + this.add(new Circle(BLACK, 695, 0.5)); + this.add(new Iterated(BLACK, 787, 1, 100, -2, '0,-50,0,50')); + + this.intro(); + } + +} \ No newline at end of file diff --git a/src/app/duality/page.tsx b/src/app/duality/page.tsx new file mode 100644 index 0000000..12768ac --- /dev/null +++ b/src/app/duality/page.tsx @@ -0,0 +1,28 @@ +'use client'; +import { PixiPlayer } from '@/components'; +import * as PIXI from 'pixi.js'; +import React from 'react'; +import { BlackSide, WhiteSide } from './Sides'; + +export default class Duality extends React.Component { + + // TODO: Add experiment controls + // TODO: Add player controls + + handleInit = (app: PIXI.Application) => { + const black = new BlackSide(); + app.stage.addChild(black); + + const white = new WhiteSide(); + app.stage.addChild(white); + } + + render() { + return ; + } + +} \ No newline at end of file diff --git a/src/app/page.tsx b/src/app/page.tsx index fd1124b..454d3de 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -1,30 +1,71 @@ 'use client'; import { experiments } from '@/setup'; -import { Box, Card, CardActionArea, CardContent, CardHeader, CardMedia, Grid, Typography } from '@mui/material'; +import { Box, Card, CardActionArea, CardContent, CardHeader, CardMedia, Divider, Grid, Typography } from '@mui/material'; export default function Home() { - return - - - - - Just some experimental digital artwork - - - - - {experiments.map((experiment, index) => !experiment.disabled && ( - - - - - - - - - ))} - + return + + + + + + Just some experimental digital artwork + + + + + + + Featured + + + + {experiments.filter(experiment => experiment.featured).map((experiment, index) => ( + + + + + + {experiment.index} + | + {experiment.date} + + )} + title={experiment.title} + /> + + + + ))} + + + + + + {experiments.filter(experiment => !experiment.disabled && !experiment.featured).map((experiment, index) => ( + + + + + + {experiment.index} + | + {experiment.date} + + )} + title={experiment.title} + /> + + + + ))} + + ; } diff --git a/src/app/polyhedron/1/page.tsx b/src/app/polyhedron/1/page.tsx new file mode 100644 index 0000000..e9dc018 --- /dev/null +++ b/src/app/polyhedron/1/page.tsx @@ -0,0 +1,15 @@ +'use client'; +import * as THREE from 'three'; +import PolyhedronBase from '../PolyhedronBase'; + +export default function Polyhedron1() { + + const shape = new THREE.TetrahedronGeometry(200); + + return ; + +} \ No newline at end of file diff --git a/src/app/polyhedron/2/page.tsx b/src/app/polyhedron/2/page.tsx new file mode 100644 index 0000000..e4b6016 --- /dev/null +++ b/src/app/polyhedron/2/page.tsx @@ -0,0 +1,15 @@ +'use client'; +import * as THREE from 'three'; +import PolyhedronBase from '../PolyhedronBase'; + +export default function Polyhedron2() { + + const shape = new THREE.DodecahedronGeometry(200); + + return ; + +} \ No newline at end of file diff --git a/src/app/polyhedron/PolyhedronBase.tsx b/src/app/polyhedron/PolyhedronBase.tsx new file mode 100644 index 0000000..e72228c --- /dev/null +++ b/src/app/polyhedron/PolyhedronBase.tsx @@ -0,0 +1,168 @@ +'use client'; +import { ExperimentControls, ThreePlayer } from '@/components'; +import { PI_M2 } from '@/setup'; +import React from 'react'; +import { createNoise3D } from 'simplex-noise'; +import * as THREE from 'three'; + +export default class PolyhedronBase extends React.Component { + + container = new THREE.Group(); + geometry = new THREE.BufferGeometry(); + groups = 0; + lines: PolyhedronBaseLine[] = []; + noise3D = createNoise3D(); + particles: PolyhedronBaseParticle[] = []; + particleSize = 2; + vertices: THREE.Vector3[] = []; + + constructor(props: PolyhedronBaseProps) { + super(props); + + const { layers, spread } = this.props; + + this.state = { + layers: { min: 1, max: 100, step: 1, value: layers }, + particles: { min: 100, max: 100000, step: 100, value: 50000 }, + speed: { min: 1, max: 10, step: 1, value: 1 }, + spread: { min: 1, max: 100, step: 1, value: spread }, + }; + } + + handleInit = (scene: THREE.Scene, camera: THREE.PerspectiveCamera) => { + const { shape } = this.props; + + // Update camera + camera.position.z = 500; + + // Add container + scene.add(this.container); + + // Add points + const material = new THREE.PointsMaterial({ + color: 0xFFFFFF, + opacity: 0.2, + size: this.particleSize, + transparent: true, + }); + + const points = new THREE.Points(this.geometry, material); + this.container.add(points); + + // Get shape lines + for (let i = 0; i < shape.attributes.position.array.length; i += 9) { + const face = { + a: { + x: shape.attributes.position.array[i], + y: shape.attributes.position.array[i + 1], + z: shape.attributes.position.array[i + 2], + }, + b: { + x: shape.attributes.position.array[i + 3], + y: shape.attributes.position.array[i + 4], + z: shape.attributes.position.array[i + 5], + }, + c: { + x: shape.attributes.position.array[i + 6], + y: shape.attributes.position.array[i + 7], + z: shape.attributes.position.array[i + 8], + }, + }; + + this.lines.push({ + from: face.a, + to: face.b, + }); + + this.lines.push({ + from: face.b, + to: face.c, + }); + + this.lines.push({ + from: face.c, + to: face.a, + }); + } + + // Trigger state change + this.setState({}); + } + + componentDidUpdate() { + const { layers, particles } = this.state; + + // Remove previous particles and vertices + while (this.particles.length) { + this.particles.pop(); + } + while (this.vertices.length) { + this.vertices.pop(); + } + + // Get new particles and vertices + this.groups = Math.ceil(particles.value / this.lines.length / layers.value); + for (let i = 0; i < particles.value; i++) { + const line = this.lines[Math.floor(i / this.groups / layers.value)]; + const index = i % this.groups; + + const n = 1.6 * (index / this.groups) - 0.3; + const dX = line.from.x + (line.to.x - line.from.x) * n; + const dY = line.from.y + (line.to.y - line.from.y) * n; + const dZ = line.from.z + (line.to.z - line.from.z) * n; + const position = new THREE.Vector3(dX, dY, dZ); + this.vertices.push(position.clone()); + + const particle = { + point: this.vertices[i], + group: Math.floor(i / this.groups), + layer: Math.floor(i / this.groups) % layers.value, + position, + index, + line, + }; + this.particles.push(particle); + } + } + + handleTick = (progress: number) => { + const { speed, spread } = this.state; + const tick = (progress * speed.value) % 1; + + this.container.rotation.y = PI_M2 * tick; + + const d = (0.5 * Math.sin(PI_M2 * tick) + 0.5); + const p = 1 - 0.75 * d; + const s = spread.value * d + 5; + + for (const particle of this.particles) { + const n = particle.index / this.groups; + const alpha = PI_M2 * (n + tick); + const cos = Math.cos(alpha); + const sin = Math.sin(alpha); + const noiseX = this.noise3D(cos + particle.group + 1, sin, n) * s; + const noiseY = this.noise3D(cos + particle.group + 2, sin, n) * s; + const noiseZ = this.noise3D(cos + particle.group + 3, sin, n) * s; + particle.point.x = particle.position.x * p + noiseX; + particle.point.y = particle.position.y * p + noiseY; + particle.point.z = particle.position.z * p + noiseZ; + } + + this.geometry.setFromPoints(this.vertices); + } + + render() { + return <> + this.setState(items)} + /> + + ; + } + +} \ No newline at end of file diff --git a/src/app/polyhedron/PolyhedronBase.types.d.ts b/src/app/polyhedron/PolyhedronBase.types.d.ts new file mode 100644 index 0000000..b7c4ce1 --- /dev/null +++ b/src/app/polyhedron/PolyhedronBase.types.d.ts @@ -0,0 +1,27 @@ +interface PolyhedronBaseProps { + layers: number; + spread: number; + shape: THREE.PolyhedronGeometry; +} + +interface PolyhedronBaseLine { + from: { + x: any; + y: any; + z: any; + }; + to: { + x: any; + y: any; + z: any; + }; +} + +interface PolyhedronBaseParticle { + group: number; + index: number; + layer: number; + line: PolyhedronBaseLine; + point: THREE.Vector3; + position: THREE.Vector3; +} \ No newline at end of file diff --git a/src/components/PixiPlayer/PixiPlayer.tsx b/src/components/PixiPlayer/PixiPlayer.tsx index df9a998..035bd65 100644 --- a/src/components/PixiPlayer/PixiPlayer.tsx +++ b/src/components/PixiPlayer/PixiPlayer.tsx @@ -85,7 +85,7 @@ export default class PixiPlayer extends Component { const { duration, startTime } = this.state; - + // Update progress const progress = (((performance.now() / 1000) - startTime) / duration) % 1; this.setState({ @@ -93,7 +93,7 @@ export default class PixiPlayer extends Component { @@ -135,12 +135,14 @@ export default class PixiPlayer extends Component - + {!!this.props.onTick && ( + + )} ; } diff --git a/src/components/PixiPlayer/PixiPlayer.types.d.ts b/src/components/PixiPlayer/PixiPlayer.types.d.ts index 1034adb..4862eaf 100644 --- a/src/components/PixiPlayer/PixiPlayer.types.d.ts +++ b/src/components/PixiPlayer/PixiPlayer.types.d.ts @@ -2,7 +2,7 @@ interface PixiPlayerProps { duration?: number; size?: number; onInit(app: PIXI.Application): void; - onTick(progress: number): void; + onTick?(progress: number): void; } interface PixiPlayerState { diff --git a/src/components/Sidebar/Sidebar.tsx b/src/components/Sidebar/Sidebar.tsx index 4b1f2b7..6f03b0f 100644 --- a/src/components/Sidebar/Sidebar.tsx +++ b/src/components/Sidebar/Sidebar.tsx @@ -19,7 +19,7 @@ export default function Sidebar() { Experiments - + diff --git a/src/setup/constants.ts b/src/setup/constants.ts index e5588ba..8624121 100644 --- a/src/setup/constants.ts +++ b/src/setup/constants.ts @@ -1,3 +1,6 @@ +export const BLACK = 0x000000; +export const WHITE = 0xFFFFFF; + export const PI = Math.PI; export const PI_D2 = Math.PI / 2; export const PI_D4 = Math.PI / 4; diff --git a/src/setup/experiments.ts b/src/setup/experiments.ts index d092d8e..dae0495 100644 --- a/src/setup/experiments.ts +++ b/src/setup/experiments.ts @@ -1,82 +1,75 @@ import _ from 'lodash'; -const items = [ - 'simplex-noise/1', - 'simplex-noise/2', - 'hexagon/1', - 'hexagon/2', - 'hexagon/3', - 'hexagon/4', - 'hexagon/5', - 'sphere/1', - 'sphere/2', - 'polyhedron/1', - 'polyhedron/2', - 'kaleidoscope/1', - 'kaleidoscope/2', - 'kaleidoscope/3', - 'tunnel/1', - 'tunnel/2', - 'cubic/1', - 'cubic/2', - 'cubic/3', - 'wave/1', - 'flocking/1', - 'flocking/2', - 'flocking/3', - 'eye/1', - 'eye/2', - 'eye/3', - 'plotter/1', - 'plotter/2', - 'cubic/4', - 'tunnel/3', - 'wave/2', - 'cubic/5', - 'cubic/6', - 'cubic/7', - 'art/1', - 'wave/3', - 'animal/1', - 'animal/2', - 'sphere/3', +const items: [string, string, boolean?, boolean?][] = [ + ['animal/1', '2019-11-10', false, true], + ['animal/2', '2019-12-05', false, true], + ['arcs', '2018-04-14', false, true], + ['art/1', '2019-10-28', false, true], + ['cubes', '2018-06-02', false, true], + ['cubic/1', '2019-08-20'], + ['cubic/2', '2019-08-20'], + ['cubic/3', '2019-08-20'], + ['cubic/4', '2019-09-27', false, true], + ['cubic/5', '2019-10-07', false, true], + ['cubic/6', '2019-10-10', false, true], + ['cubic/7', '2019-10-15', false, true], + ['drops', '2017-06-02', false, true], + ['duality', '2019-05-20', true], + ['eye/1', '2019-08-23', false, true], + ['eye/2', '2019-08-23', false, true], + ['eye/3', '2019-09-10', false, true], + ['face', '2018-03-27', false, true], + ['flocking/1', '2019-08-21', false, true], + ['flocking/2', '2019-08-21', false, true], + ['flocking/3', '2019-08-23', false, true], + ['foggy-forest', '2018-03-31', false, true], + ['followers', '2018-06-03', false, true], + ['hexagon/1', '2019-08-11'], + ['hexagon/2', '2019-08-11'], + ['hexagon/3', '2019-08-11'], + ['hexagon/4', '2019-08-11'], + ['hexagon/5', '2019-08-11'], + ['kaleidoscope/1', '2019-08-18', false, true], + ['kaleidoscope/2', '2019-08-18', false, true], + ['kaleidoscope/3', '2019-08-18', false, true], + ['mobius', '2017-06-01', false, true], + ['morph', '2017-10-31', false, true], + ['mother-box', '2017-12-04', false, true], + ['nebula', '2017-10-30', false, true], + ['pixeliser', '2018-04-14', false, true], + ['plotter/1', '2019-09-17', false, true], + ['plotter/2', '2019-09-18', false, true], + ['polyhedron/1', '2019-08-14'], + ['polyhedron/2', '2019-08-14'], + ['rings', '2018-06-21', false, true], + ['ripples', '2017-06-07', false, true], + ['simplex-noise/1', '2019-08-09'], + ['simplex-noise/2', '2019-08-11'], + ['solar', '2018-04-02', false, true], + ['sphere/1', '2019-08-13'], + ['sphere/2', '2019-08-13'], + ['sphere/3', '2020-01-30', false, true], + ['tau', '2018-06-15', false, true], + ['tree-of-thoughts', '2017-11-01', false, true], + ['trigonometry', '2017-08-08', false, true], + ['tunnel/1', '2019-08-19', false, true], + ['tunnel/2', '2019-08-19', false, true], + ['tunnel/3', '2019-09-29', false, true], + ['wander-lost', '2019-05-28', false, true], + ['wave/1', '2019-08-21', false, true], + ['wave/2', '2019-10-04', false, true], + ['wave/3', '2019-11-09', false, true], + ['worlds-end', '2018-02-22', false, true], ]; -const disabled = [ - 'polyhedron/1', - 'polyhedron/2', - 'kaleidoscope/1', - 'kaleidoscope/2', - 'kaleidoscope/3', - 'tunnel/1', - 'tunnel/2', - 'wave/1', - 'flocking/1', - 'flocking/2', - 'flocking/3', - 'eye/1', - 'eye/2', - 'eye/3', - 'plotter/1', - 'plotter/2', - 'cubic/4', - 'tunnel/3', - 'wave/2', - 'cubic/5', - 'cubic/6', - 'cubic/7', - 'art/1', - 'wave/3', - 'animal/1', - 'animal/2', - 'sphere/3', -]; +items.sort((a, b) => a[1] < b[1] ? 1 : -1); -items.reverse(); -export const experiments: Experiment[] = items.map((item, index) => ({ - disabled: disabled.includes(item), - image: `/assets/images/${item.replace('/', '-')}.png`, +export const experiments: Experiment[] = items.map(([name, date, featured, disabled], index) => ({ + date, + disabled, + featured, + image: `/assets/images/${name.replace('/', '-')}.png`, index: (items.length - index).toString().padStart(3, '0'), - path: `/${item}`, - title: _.startCase(item), + path: `/${name}`, + title: _.startCase(name), })); \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 4ea8cf9..8d0d745 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1749,6 +1749,11 @@ graphemer@^1.4.0: resolved "https://registry.yarnpkg.com/graphemer/-/graphemer-1.4.0.tgz#fb2f1d55e0e3a1849aeffc90c4fa0dd53a0e66c6" integrity sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag== +gsap@^3.12.5: + version "3.12.5" + resolved "https://registry.yarnpkg.com/gsap/-/gsap-3.12.5.tgz#136c02dad4c673b441bdb1ca00104bfcb4eae7f4" + integrity sha512-srBfnk4n+Oe/ZnMIOXt3gT605BX9x5+rh/prT2F1SsNJsU1XuMiP0E2aptW481OnonOGACZWBqseH5Z7csHxhQ== + has-bigints@^1.0.1, has-bigints@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.0.2.tgz#0871bd3e3d51626f6ca0966668ba35d5602d6eaa"