diff --git a/src/app/cubic/1/Shape.ts b/src/app/cubic/1/Shape.ts index cef40a2..50bfbac 100644 --- a/src/app/cubic/1/Shape.ts +++ b/src/app/cubic/1/Shape.ts @@ -47,8 +47,8 @@ export default class Shape extends THREE.Object3D { this.cube.position.z = CUBE_SIZE * (z - (CUBE_ROWS / 2 - 0.5)); } - move(progress: number) { - const t = progress * 12 - this.delay; + move(tick: number, delay: number) { + const t = tick - (this.delay * delay); this.rotation.x = PI_D2 * (easeInOutCubic(t) + easeInOutCubic(t - 6)); this.rotation.z = PI_D2 * (easeInOutCubic(t - 2) + easeInOutCubic(t - 8)); this.rotation.y = -PI_D2 * (easeInOutCubic(t - 4) + easeInOutCubic(t - 10)); diff --git a/src/app/cubic/1/page.tsx b/src/app/cubic/1/page.tsx index adf2432..a0c62de 100644 --- a/src/app/cubic/1/page.tsx +++ b/src/app/cubic/1/page.tsx @@ -1,19 +1,25 @@ 'use client'; -import { ThreePlayer } from '@/components'; +import { ExperimentControls, ThreePlayer } from '@/components'; import { PI_D4, PI_D8 } from '@/setup'; +import React from 'react'; import * as THREE from 'three'; import Shape, { CUBE_ROWS } from './Shape'; -export default function Cubic1() { +export default class Cubic1 extends React.Component { - const depth = 1000; - const shapes: Shape[] = []; + depth = 1000; + shapes: Shape[] = []; - function handleInit(scene: THREE.Scene, camera: THREE.PerspectiveCamera) { + state = { + delay: { min: 0, max: 2, value: 1 }, + speed: { min: 1, max: 10, step: 1, value: 1 }, + }; + + handleInit = (scene: THREE.Scene, camera: THREE.PerspectiveCamera) => { // Update camera - camera.far = depth * 2; - camera.position.y = -depth * 0.05; - camera.position.z = depth; + camera.far = this.depth * 2; + camera.position.y = -this.depth * 0.05; + camera.position.z = this.depth; // Update scene scene.rotation.x = PI_D8; @@ -24,9 +30,9 @@ export default function Cubic1() { scene.add(ambient); // Add point light - const light = new THREE.PointLight(0xFFFFFF, depth ** 2); + const light = new THREE.PointLight(0xFFFFFF, this.depth ** 2); scene.add(light); - light.position.z = depth; + light.position.z = this.depth; // Add shapes for (let x = 0; x < CUBE_ROWS; x++) { @@ -34,21 +40,30 @@ export default function Cubic1() { for (let z = 0; z < CUBE_ROWS; z++) { const shape = new Shape(x, y, z); scene.add(shape); - shapes.push(shape); + this.shapes.push(shape); } } } } - function handleTick(progress: number) { - shapes.forEach(shape => shape.move(progress)); + handleTick = (progress: number) => { + const { delay, speed } = this.state; + const tick = (progress * speed.value) % 1 * 12; + this.shapes.forEach(shape => shape.move(tick, delay.value)); } - return ; + render() { + return <> + this.setState(items)} + /> + + ; + } } diff --git a/src/app/cubic/2/Shape.ts b/src/app/cubic/2/Shape.ts index 1835db4..f2efea4 100644 --- a/src/app/cubic/2/Shape.ts +++ b/src/app/cubic/2/Shape.ts @@ -47,8 +47,8 @@ export default class Shape extends THREE.Object3D { this.cube.position.z = CUBE_SIZE * (z - (CUBE_ROWS / 2 - 0.5)); } - move(progress: number) { - const t = progress * 12 - this.delay; + move(tick: number, delay: number) { + const t = tick - (this.delay * delay); this.rotation.x = PI_D2 * (easeInOutCubic(t) + easeInOutCubic(t - 6)); this.rotation.z = PI_D2 * (easeInOutCubic(t - 2) + easeInOutCubic(t - 8)); this.rotation.y = -PI_D2 * (easeInOutCubic(t - 4) + easeInOutCubic(t - 10)); diff --git a/src/app/cubic/2/page.tsx b/src/app/cubic/2/page.tsx index 65cb823..d948e16 100644 --- a/src/app/cubic/2/page.tsx +++ b/src/app/cubic/2/page.tsx @@ -1,25 +1,31 @@ 'use client'; -import { ThreePlayer } from '@/components'; +import { ExperimentControls, ThreePlayer } from '@/components'; +import React from 'react'; import * as THREE from 'three'; import Shape, { CUBE_ROWS } from './Shape'; -export default function Cubic2() { +export default class Cubic2 extends React.Component { - const depth = 1000; - const shapes: Shape[] = []; + depth = 1000; + shapes: Shape[] = []; - function handleInit(scene: THREE.Scene, camera: THREE.PerspectiveCamera) { + state = { + delay: { min: 0, max: 2, value: 1 }, + speed: { min: 1, max: 10, step: 1, value: 1 }, + }; + + handleInit = (scene: THREE.Scene, camera: THREE.PerspectiveCamera) => { // Update camera - camera.position.z = depth; + camera.position.z = this.depth; // Add ambient light const ambient = new THREE.AmbientLight(0xFFFFFF, 0.5); scene.add(ambient); // Add point light - const light = new THREE.PointLight(0xFFFFFF, depth ** 2); + const light = new THREE.PointLight(0xFFFFFF, this.depth ** 2); scene.add(light); - light.position.z = depth; + light.position.z = this.depth; // Add shapes for (let x = 0; x < CUBE_ROWS; x++) { @@ -27,20 +33,30 @@ export default function Cubic2() { for (let z = 0; z < CUBE_ROWS; z++) { const shape = new Shape(x, y, z); scene.add(shape); - shapes.push(shape); + this.shapes.push(shape); } } } } - function handleTick(progress: number) { - shapes.forEach(shape => shape.move(progress)); + handleTick = (progress: number) => { + const { delay, speed } = this.state; + const tick = (progress * speed.value) % 1 * 12; + this.shapes.forEach(shape => shape.move(tick, delay.value)); + } + + render() { + return <> + this.setState(items)} + /> + + ; } - return ; -} +} \ No newline at end of file diff --git a/src/app/cubic/3/Particle.ts b/src/app/cubic/3/Particle.ts index 339c522..d758bee 100644 --- a/src/app/cubic/3/Particle.ts +++ b/src/app/cubic/3/Particle.ts @@ -52,8 +52,8 @@ export default class Particle extends THREE.Object3D { this.cube.position.z = PARTICLE_SIZE * (z - (PARTICLE_ROWS / 2 - 0.5)); } - move(progress: number) { - const t = progress * 12 - this.delay; + move(tick: number, delay: number) { + const t = tick - (this.delay * delay); this.rotation.x = PI_D2 * (easeInOutCubic(t) + easeInOutCubic(t - 6)); this.rotation.z = PI_D2 * (easeInOutCubic(t - 2) + easeInOutCubic(t - 8)); this.rotation.y = -PI_D2 * (easeInOutCubic(t - 4) + easeInOutCubic(t - 10)); diff --git a/src/app/cubic/3/page.tsx b/src/app/cubic/3/page.tsx index baaad05..75a617f 100644 --- a/src/app/cubic/3/page.tsx +++ b/src/app/cubic/3/page.tsx @@ -1,18 +1,26 @@ 'use client'; -import { ThreePlayer } from '@/components'; +import { ExperimentControls, ThreePlayer } from '@/components'; import { PI_D4, PI_D8 } from '@/setup'; +import React from 'react'; import * as THREE from 'three'; import Particle, { PARTICLE_ROWS, PARTICLE_SIZE } from './Particle'; -export default function Cubic3() { +export default class Cubic3 extends React.Component { - const depth = 1000; - const particles: Particle[] = []; + // TODO: Check performance drop - function handleInit(scene: THREE.Scene, camera: THREE.PerspectiveCamera) { + depth = 1000; + particles: Particle[] = []; + + state = { + delay: { min: 0, max: 2, value: 1 }, + speed: { min: 1, max: 10, step: 1, value: 1 }, + }; + + handleInit = (scene: THREE.Scene, camera: THREE.PerspectiveCamera) => { // Update camera - camera.position.z = depth; - + camera.position.z = this.depth; + // Update scene scene.rotation.x = PI_D8; scene.rotation.y = PI_D4; @@ -22,9 +30,9 @@ export default function Cubic3() { scene.add(ambient); // Add point light - const light = new THREE.PointLight(0xFFFFFF, depth ** 2); + const light = new THREE.PointLight(0xFFFFFF, this.depth ** 2); scene.add(light); - light.position.z = depth; + light.position.z = this.depth; // Get shape const shapeGeometry = new THREE.DodecahedronGeometry((PARTICLE_ROWS * PARTICLE_SIZE) / 2); @@ -43,22 +51,32 @@ export default function Cubic3() { raycaster.set(particle.cube.position, ray); if (raycaster.intersectObject(shape).length === 1) { scene.add(particle); - particles.push(particle); + this.particles.push(particle); } } } } } - function handleTick(progress: number) { - particles.forEach(particle => particle.move(progress)); + handleTick = (progress: number) => { + const { delay, speed } = this.state; + const tick = (progress * speed.value) % 1 * 12; + this.particles.forEach(particle => particle.move(tick, delay.value)); } - return ; + + render() { + return <> + this.setState(items)} + /> + + ; + } } diff --git a/src/app/hexagon/1/page.tsx b/src/app/hexagon/1/page.tsx index 5fc09ba..45c3585 100644 --- a/src/app/hexagon/1/page.tsx +++ b/src/app/hexagon/1/page.tsx @@ -1,78 +1,126 @@ 'use client'; -import { PixiPlayer } from '@/components'; +import { ExperimentControls, PixiPlayer } from '@/components'; import { PI_M2 } from '@/setup'; import * as PIXI from 'pixi.js'; +import React from 'react'; -export default function Hexagon1() { +export default class Hexagon1 extends React.Component { - const container = new PIXI.Container(); - const rows: PIXI.Sprite[][] = []; - const size = 30; - const spacing = 5; + container = new PIXI.Container(); + layers: PIXI.Container[] = []; + particles: PIXI.Sprite[] = []; + particleSize = 100; + size = 640; - function handleInit(app: PIXI.Application) { - app.stage.addChild(container); + particleTexture: PIXI.Texture; + + state = { + size: { min: 10, max: 100, step: 1, value: 30 }, + spacing: { min: 0, max: 100, step: 1, value: 5 }, + speed: { min: 1, max: 10, step: 1, value: 1 }, + }; + + handleInit = (app: PIXI.Application) => { + app.stage.addChild(this.container); // Get shape texture const shape = new PIXI.Graphics() - .moveTo(0, 0.5 * size) - .lineTo(0.4330125 * size, 0.25 * size) - .lineTo(0.4330125 * size, -0.25 * size) - .lineTo(0, -0.5 * size) - .lineTo(-0.4330125 * size, -0.25 * size) - .lineTo(-0.4330125 * size, 0.25 * size) + .moveTo(0, 0.5 * this.particleSize) + .lineTo(0.4330125 * this.particleSize, 0.25 * this.particleSize) + .lineTo(0.4330125 * this.particleSize, -0.25 * this.particleSize) + .lineTo(0, -0.5 * this.particleSize) + .lineTo(-0.4330125 * this.particleSize, -0.25 * this.particleSize) + .lineTo(-0.4330125 * this.particleSize, 0.25 * this.particleSize) .fill(0xFFFFFF); - const texture = app.renderer.generateTexture(shape); + this.particleTexture = app.renderer.generateTexture(shape); - // Create add - function add(row: PIXI.Sprite[], x: number, y: number) { - x = (x - 2.625) * ((size * 0.4330125) + spacing) + app.canvas.width / 2; - y = (y - 0.55) * ((size * 0.75) + spacing) + app.canvas.height / 2; + // Trigger state change + this.setState({}); + } - if (x <= -size || x >= app.canvas.width || y < -size || y >= app.canvas.height) { - return; - } + componentDidUpdate() { + const { size } = this.state; + const layerCount = Math.ceil(this.size / size.value); - const hex = PIXI.Sprite.from(texture); - container.addChild(hex); - row.push(hex); + // Create missing layers + for (let i = this.layers.length; i < layerCount; i++) { + const layer = new PIXI.Container(); + this.layers.push(layer); + } - hex.position.x = x; - hex.position.y = y; - }; + // Remove particles from layers and layers from container + this.particles.forEach(particle => particle.removeFromParent()); + this.layers.forEach(layer => layer.removeFromParent()); - // Add rows + // Add layers and particles let x = 0, y = 0; - for (let i = 0; i <= 14; i++) { - const row: PIXI.Sprite[] = []; - rows.push(row); - - add(row, x += 2, y); - for (let j = 0; j < i - 1; j++) add(row, ++x, ++y); - for (let j = 0; j < i; j++) add(row, --x, ++y); - for (let j = 0; j < i; j++) add(row, x -= 2, y); - for (let j = 0; j < i; j++) add(row, --x, --y); - for (let j = 0; j < i; j++) add(row, ++x, --y); - for (let j = 0; j < i; j++) add(row, x += 2, y); + for (let i = 0; i < layerCount; i++) { + // Add layer + const layer = this.layers[i]; + this.container.addChild(layer); + + // Add layer particles + this.addParticle(layer, x += 2, y); + for (let j = 0; j < i - 1; j++) this.addParticle(layer, ++x, ++y); + for (let j = 0; j < i; j++) this.addParticle(layer, --x, ++y); + for (let j = 0; j < i; j++) this.addParticle(layer, x -= 2, y); + for (let j = 0; j < i; j++) this.addParticle(layer, --x, --y); + for (let j = 0; j < i; j++) this.addParticle(layer, ++x, --y); + for (let j = 0; j < i; j++) this.addParticle(layer, x += 2, y); } } - function handleTick(progress: number) { - for (let i = 0; i < rows.length; i++) { - const row = rows[i]; - const alpha = Math.abs(Math.sin(PI_M2 * (progress - i / rows.length))); + addParticle = (layer: PIXI.Container, x: number, y: number) => { + const { size, spacing } = this.state; + + // Get position + x = (x - 2.625) * ((size.value * 0.4330125) + spacing.value) + this.size / 2; + y = (y - 0.55) * ((size.value * 0.75) + spacing.value) + this.size / 2; - for (let j = 0; j < row.length; j++) { - let hex = row[j]; - hex.alpha = alpha; + if (x <= -this.size || x >= this.size || y < -this.size || y >= this.size) { + return; + } + + // Get particle + let particle = this.particles.find(particle => !particle.parent); + if (!particle) { + particle = PIXI.Sprite.from(this.particleTexture); + this.particles.push(particle); + } + + // Update particle + layer.addChild(particle); + particle.position.x = x; + particle.position.y = y; + particle.scale.set(size.value / this.particleSize); + }; + + handleTick = (progress: number) => { + const { speed } = this.state; + + for (let i = 0; i < this.container.children.length; i++) { + const layer = this.container.children[i]; + const alpha = Math.abs(Math.sin(PI_M2 * (progress * speed.value - i / this.container.children.length))); + + for (let j = 0; j < layer.children.length; j++) { + const particle = layer.children[j]; + particle.alpha = alpha; } } } - return ; + render() { + return <> + this.setState(items)} + /> + + ; + } } \ No newline at end of file diff --git a/src/app/hexagon/2/page.tsx b/src/app/hexagon/2/page.tsx index 3a554c8..3c007cd 100644 --- a/src/app/hexagon/2/page.tsx +++ b/src/app/hexagon/2/page.tsx @@ -1,90 +1,134 @@ 'use client'; -import { PixiPlayer } from '@/components'; +import { ExperimentControls, PixiPlayer } from '@/components'; import { PI_M2 } from '@/setup'; import * as PIXI from 'pixi.js'; +import React from 'react'; -export default function Hexagon2() { +export default class Hexagon2 extends React.Component { - const border = 1; - const container = new PIXI.Container(); - const rows: PIXI.Container[][] = []; - const size = 30; - const spacing = 5; + container = new PIXI.Container(); + layers: PIXI.Container[] = []; + particles: PIXI.Container[] = []; + shapeSize = 100; + size = 640; - function handleInit(app: PIXI.Application) { - app.stage.addChild(container); + shapeTexture: PIXI.Texture; + + state = { + size: { min: 10, max: 100, step: 1, value: 30 }, + spacing: { min: 0, max: 100, step: 1, value: 5 }, + speed: { min: 1, max: 10, step: 1, value: 1 }, + }; + + handleInit = (app: PIXI.Application) => { + app.stage.addChild(this.container); // Get shape texture const shape = new PIXI.Graphics() - .moveTo(0, 0.5 * (size + border)) - .lineTo(0.4330125 * (size + border), 0.25 * (size + border)) - .lineTo(0.4330125 * (size + border), -0.25 * (size + border)) - .lineTo(0, -0.5 * (size + border)) - .lineTo(-0.4330125 * (size + border), -0.25 * (size + border)) - .lineTo(-0.4330125 * (size + border), 0.25 * (size + border)) - .fill(0) - .moveTo(0, 0.5 * size) - .lineTo(0.4330125 * size, 0.25 * size) - .lineTo(0.4330125 * size, -0.25 * size) - .lineTo(0, -0.5 * size) - .lineTo(-0.4330125 * size, -0.25 * size) - .lineTo(-0.4330125 * size, 0.25 * size) + .moveTo(0, 0.5 * this.shapeSize) + .lineTo(0.4330125 * this.shapeSize, 0.25 * this.shapeSize) + .lineTo(0.4330125 * this.shapeSize, -0.25 * this.shapeSize) + .lineTo(0, -0.5 * this.shapeSize) + .lineTo(-0.4330125 * this.shapeSize, -0.25 * this.shapeSize) + .lineTo(-0.4330125 * this.shapeSize, 0.25 * this.shapeSize) .fill(0xFFFFFF); - const texture = app.renderer.generateTexture(shape); - - // Create add - function add(row: PIXI.Container[], x: number, y: number) { - x = (x - 2.125) * ((size * 0.4330125) + spacing) + app.canvas.width / 2; - y = (y - 0.05) * ((size * 0.75) + spacing) + app.canvas.height / 2; + this.shapeTexture = app.renderer.generateTexture(shape); - if (x <= -size / 2 || x >= app.canvas.width + size / 2 || y < -size / 2 || y >= app.canvas.height + size / 2) { - return; - } + // Trigger state change + this.setState({}); + } - const hex = new PIXI.Container(); - container.addChild(hex); - row.push(hex); - hex.position.x = x; - hex.position.y = y; + componentDidUpdate() { + const { size } = this.state; + const layerCount = Math.ceil(this.size / size.value); - const sprite = PIXI.Sprite.from(texture); - hex.addChild(sprite); - sprite.position.x = -sprite.width / 2; - sprite.position.y = -sprite.height / 2; + // Create missing layers + for (let i = this.layers.length; i < layerCount; i++) { + const layer = new PIXI.Container(); + this.layers.push(layer); } - // Add rows + // Remove particles from layers and layers from container + this.particles.forEach(particle => particle.removeFromParent()); + this.layers.forEach(layer => layer.removeFromParent()); + + // Add layers and particles let x = 0, y = 0; - for (let i = 0; i <= 13; i++) { - const row: PIXI.Sprite[] = []; - rows.push(row); - - add(row, x += 2, y); - for (let j = 0; j < i - 1; j++) add(row, ++x, ++y); - for (let j = 0; j < i; j++) add(row, --x, ++y); - for (let j = 0; j < i; j++) add(row, x -= 2, y); - for (let j = 0; j < i; j++) add(row, --x, --y); - for (let j = 0; j < i; j++) add(row, ++x, --y); - for (let j = 0; j < i; j++) add(row, x += 2, y); + for (let i = 0; i < layerCount; i++) { + // Add layer + const layer = this.layers[i]; + this.container.addChild(layer); + + // Add layer particles + this.addParticle(layer, x += 2, y); + for (let j = 0; j < i - 1; j++) this.addParticle(layer, ++x, ++y); + for (let j = 0; j < i; j++) this.addParticle(layer, --x, ++y); + for (let j = 0; j < i; j++) this.addParticle(layer, x -= 2, y); + for (let j = 0; j < i; j++) this.addParticle(layer, --x, --y); + for (let j = 0; j < i; j++) this.addParticle(layer, ++x, --y); + for (let j = 0; j < i; j++) this.addParticle(layer, x += 2, y); } } - function handleTick(progress: number) { - for (let i = 0; i < rows.length; i++) { - const row = rows[i]; - const alpha = Math.abs(Math.sin(PI_M2 * (progress - i / rows.length))); + addParticle = (layer: PIXI.Container, x: number, y: number) => { + const { size, spacing } = this.state; + + // Get position + x = (x - 2.125) * ((size.value * 0.4330125) + spacing.value) + this.size / 2; + y = (y - 0.05) * ((size.value * 0.75) + spacing.value) + this.size / 2; + + if (x <= -size.value / 2 || x >= this.size + size.value / 2 || y < -size.value / 2 || y >= this.size + size.value / 2) { + return; + } - for (let j = 0; j < row.length; j++) { - let hex = row[j]; - hex.scale.x = hex.scale.y = alpha; + // Get particle + let particle = this.particles.find(particle => !particle.parent); + if (!particle) { + particle = new PIXI.Container(); + this.particles.push(particle); + + const shape = PIXI.Sprite.from(this.shapeTexture); + particle.addChild(shape); + } + + // Update particle + layer.addChild(particle); + particle.position.x = x; + particle.position.y = y; + + // Update shape + const shape = particle.children[0]; + shape.setSize(size.value); + shape.position.x = -shape.width / 2; + shape.position.y = -shape.height / 2; + } + + handleTick = (progress: number) => { + const { speed } = this.state; + + for (let i = 0; i < this.container.children.length; i++) { + const layer = this.container.children[i]; + const scale = Math.abs(Math.sin(PI_M2 * (progress * speed.value - i / this.container.children.length))); + + for (let j = 0; j < layer.children.length; j++) { + const particle = layer.children[j]; + particle.scale.set(scale); } } } - return ; + render() { + return <> + this.setState(items)} + /> + + ; + } } \ No newline at end of file diff --git a/src/app/hexagon/3/page.tsx b/src/app/hexagon/3/page.tsx index 2d4d495..c877e92 100644 --- a/src/app/hexagon/3/page.tsx +++ b/src/app/hexagon/3/page.tsx @@ -1,90 +1,145 @@ 'use client'; -import { PixiPlayer } from '@/components'; +import { ExperimentControls, PixiPlayer } from '@/components'; import { PI_M2 } from '@/setup'; import * as PIXI from 'pixi.js'; +import React from 'react'; -export default function Hexagon3() { +export default class Hexagon3 extends React.Component { - const border = 2; - const container = new PIXI.Container(); - const rows: PIXI.Container[][] = []; - const size = 50; - const spacing = 5; + container = new PIXI.Container(); + layers: PIXI.Container[] = []; + particles: PIXI.Container[] = []; + shapeSize = 100; + size = 640; - function handleInit(app: PIXI.Application) { - app.stage.addChild(container); + shapeTexture: PIXI.Texture; + + state = { + border: { min: 0, max: 10, step: 1, value: 1 }, + size: { min: 10, max: 100, step: 1, value: 30 }, + spacing: { min: 0, max: 100, step: 1, value: 5 }, + speed: { min: 1, max: 10, step: 1, value: 1 }, + }; + + handleInit = (app: PIXI.Application) => { + app.stage.addChild(this.container); // Get shape texture const shape = new PIXI.Graphics() - .moveTo(0, 0.5 * (size + border)) - .lineTo(0.4330125 * (size + border), 0.25 * (size + border)) - .lineTo(0.4330125 * (size + border), -0.25 * (size + border)) - .lineTo(0, -0.5 * (size + border)) - .lineTo(-0.4330125 * (size + border), -0.25 * (size + border)) - .lineTo(-0.4330125 * (size + border), 0.25 * (size + border)) - .fill(0) - .moveTo(0, 0.5 * size) - .lineTo(0.4330125 * size, 0.25 * size) - .lineTo(0.4330125 * size, -0.25 * size) - .lineTo(0, -0.5 * size) - .lineTo(-0.4330125 * size, -0.25 * size) - .lineTo(-0.4330125 * size, 0.25 * size) + .moveTo(0, 0.5 * this.shapeSize) + .lineTo(0.4330125 * this.shapeSize, 0.25 * this.shapeSize) + .lineTo(0.4330125 * this.shapeSize, -0.25 * this.shapeSize) + .lineTo(0, -0.5 * this.shapeSize) + .lineTo(-0.4330125 * this.shapeSize, -0.25 * this.shapeSize) + .lineTo(-0.4330125 * this.shapeSize, 0.25 * this.shapeSize) .fill(0xFFFFFF); - const texture = app.renderer.generateTexture(shape); - - // Create add - function add(row: PIXI.Container[], x: number, y: number) { - x = (x - 2.125) * ((size * 0.4330125) + spacing) + app.canvas.width / 2; - y = (y - 0.05) * ((size * 0.75) + spacing) + app.canvas.height / 2; + this.shapeTexture = app.renderer.generateTexture(shape); - if (x <= -size / 2 || x >= app.canvas.width + size / 2 || y < -size / 2 || y >= app.canvas.height + size / 2) { - return; - } + // Trigger state change + this.setState({}); + } - const hex = new PIXI.Container(); - container.addChild(hex); - row.push(hex); - hex.position.x = x; - hex.position.y = y; + componentDidUpdate() { + const { size } = this.state; + const layerCount = Math.ceil(this.size / size.value); - const sprite = PIXI.Sprite.from(texture); - hex.addChild(sprite); - sprite.position.x = -sprite.width / 2; - sprite.position.y = -sprite.height / 2; + // Create missing layers + for (let i = this.layers.length; i < layerCount; i++) { + const layer = new PIXI.Container(); + this.layers.push(layer); } - // Add rows + // Remove particles from layers and layers from container + this.particles.forEach(particle => particle.removeFromParent()); + this.layers.forEach(layer => layer.removeFromParent()); + + // Add layers and particles let x = 0, y = 0; - for (let i = 0; i <= 13; i++) { - const row: PIXI.Container[] = []; - rows.push(row); - - add(row, x += 2, y); - for (let j = 0; j < i - 1; j++) add(row, ++x, ++y); - for (let j = 0; j < i; j++) add(row, --x, ++y); - for (let j = 0; j < i; j++) add(row, x -= 2, y); - for (let j = 0; j < i; j++) add(row, --x, --y); - for (let j = 0; j < i; j++) add(row, ++x, --y); - for (let j = 0; j < i; j++) add(row, x += 2, y); + for (let i = 0; i < layerCount; i++) { + // Add layer + const layer = this.layers[i]; + this.container.addChild(layer); + + // Add layer particles + this.addParticle(layer, x += 2, y); + for (let j = 0; j < i - 1; j++) this.addParticle(layer, ++x, ++y); + for (let j = 0; j < i; j++) this.addParticle(layer, --x, ++y); + for (let j = 0; j < i; j++) this.addParticle(layer, x -= 2, y); + for (let j = 0; j < i; j++) this.addParticle(layer, --x, --y); + for (let j = 0; j < i; j++) this.addParticle(layer, ++x, --y); + for (let j = 0; j < i; j++) this.addParticle(layer, x += 2, y); + } + } + + addParticle = (layer: PIXI.Container, x: number, y: number) => { + const { border, size, spacing } = this.state; + + // Get position + x = (x - 2.125) * ((size.value * 0.4330125) + spacing.value) + this.size / 2; + y = (y - 0.05) * ((size.value * 0.75) + spacing.value) + this.size / 2; + + if (x <= -size.value / 2 || x >= this.size + size.value / 2 || y < -size.value / 2 || y >= this.size + size.value / 2) { + return; + } + + // Get particle + let particle = this.particles.find(particle => !particle.parent); + if (!particle) { + particle = new PIXI.Container(); + this.particles.push(particle); + + const shapeOuter = PIXI.Sprite.from(this.shapeTexture); + particle.addChild(shapeOuter); + shapeOuter.tint = 0; + + const shapeInner = PIXI.Sprite.from(this.shapeTexture); + particle.addChild(shapeInner); } + + // Update particle + layer.addChild(particle); + particle.position.x = x; + particle.position.y = y; + + // Update outer shape + const shapeOuter = particle.children[0]; + shapeOuter.setSize(size.value); + shapeOuter.position.x = -shapeOuter.width / 2; + shapeOuter.position.y = -shapeOuter.height / 2; + + // Update inner shape + const shapeInner = particle.children[1]; + shapeInner.setSize(size.value - border.value); + shapeInner.position.x = -shapeInner.width / 2; + shapeInner.position.y = -shapeInner.height / 2; } - function handleTick(progress: number) { - for (let i = 0; i < rows.length; i++) { - const row = rows[i]; - const alpha = Math.sin(PI_M2 * (progress - i / rows.length)) + 0.5; + handleTick = (progress: number) => { + const { speed } = this.state; - for (let j = 0; j < row.length; j++) { - let hex = row[j]; - hex.scale.x = hex.scale.y = alpha; + for (let i = 0; i < this.container.children.length; i++) { + const layer = this.container.children[i]; + const scale = Math.sin(PI_M2 * (progress * speed.value - i / this.container.children.length)) + 0.5; + + for (let j = 0; j < layer.children.length; j++) { + const particle = layer.children[j]; + particle.scale.set(scale); } } } - return ; + render() { + return <> + this.setState(items)} + /> + + ; + } } \ No newline at end of file diff --git a/src/app/hexagon/4/page.tsx b/src/app/hexagon/4/page.tsx index 6511bf1..94793bd 100644 --- a/src/app/hexagon/4/page.tsx +++ b/src/app/hexagon/4/page.tsx @@ -1,91 +1,134 @@ 'use client'; -import { PixiPlayer } from '@/components'; +import { ExperimentControls, PixiPlayer } from '@/components'; import { PI_M2 } from '@/setup'; import * as PIXI from 'pixi.js'; +import React from 'react'; -export default function Hexagon3() { +export default class Hexagon4 extends React.Component { - const border = 1; - const container = new PIXI.Container(); - const rows: PIXI.Container[][] = []; - const size = 25; - const spacing = 2; + container = new PIXI.Container(); + layers: PIXI.Container[] = []; + particles: PIXI.Container[] = []; + shapeSize = 100; + size = 640; - function handleInit(app: PIXI.Application) { - app.stage.addChild(container); + shapeTexture: PIXI.Texture; + + state = { + size: { min: 10, max: 100, step: 1, value: 30 }, + spacing: { min: 0, max: 100, step: 1, value: 5 }, + speed: { min: 1, max: 10, step: 1, value: 1 }, + }; + + handleInit = (app: PIXI.Application) => { + app.stage.addChild(this.container); // Get shape texture const shape = new PIXI.Graphics() - .moveTo(0, 0.5 * size) - .lineTo(0.4330125 * size, 0.25 * size) - .lineTo(0.4330125 * size, -0.25 * size) - .lineTo(0, -0.5 * size) - .lineTo(-0.4330125 * size, -0.25 * size) - .lineTo(-0.4330125 * size, 0.25 * size) - .fill(0) - .moveTo(0, 0.5 * (size - border)) - .lineTo(0.4330125 * (size - border), 0.25 * (size - border)) - .lineTo(0.4330125 * (size - border), -0.25 * (size - border)) - .lineTo(0, -0.5 * (size - border)) - .lineTo(-0.4330125 * (size - border), -0.25 * (size - border)) - .lineTo(-0.4330125 * (size - border), 0.25 * (size - border)) + .moveTo(0, 0.5 * this.shapeSize) + .lineTo(0.4330125 * this.shapeSize, 0.25 * this.shapeSize) + .lineTo(0.4330125 * this.shapeSize, -0.25 * this.shapeSize) + .lineTo(0, -0.5 * this.shapeSize) + .lineTo(-0.4330125 * this.shapeSize, -0.25 * this.shapeSize) + .lineTo(-0.4330125 * this.shapeSize, 0.25 * this.shapeSize) .fill(0xFFFFFF); - const texture = app.renderer.generateTexture(shape); - - // Create add - function add(row: PIXI.Container[], x: number, y: number) { - x = (x - 2) * ((size * 0.4330125) + spacing) + app.canvas.width / 2; - y = y * ((size * 0.75) + spacing) + app.canvas.height / 2; + this.shapeTexture = app.renderer.generateTexture(shape); - if (x <= -size / 2 || x >= app.canvas.width + size / 2 || y < -size / 2 || y >= app.canvas.height + size / 2) { - return; - } + // Trigger state change + this.setState({}); + } - const hex = new PIXI.Container(); - container.addChild(hex); - row.push(hex); - hex.position.x = x; - hex.position.y = y; + componentDidUpdate() { + const { size } = this.state; + const layerCount = Math.ceil(this.size / size.value); - const sprite = PIXI.Sprite.from(texture); - hex.addChild(sprite); - sprite.position.x = -sprite.width / 2; - sprite.position.y = -sprite.height / 2; + // Create missing layers + for (let i = this.layers.length; i < layerCount; i++) { + const layer = new PIXI.Container(); + this.layers.push(layer); } - // Add rows + // Remove particles from layers and layers from container + this.particles.forEach(particle => particle.removeFromParent()); + this.layers.forEach(layer => layer.removeFromParent()); + + // Add layers and particles let x = 0, y = 0; - for (let i = 0; i <= 20; i++) { - const row: PIXI.Container[] = []; - rows.push(row); - - add(row, x += 2, y); - for (let j = 0; j < i - 1; j++) add(row, ++x, ++y); - for (let j = 0; j < i; j++) add(row, --x, ++y); - for (let j = 0; j < i; j++) add(row, x -= 2, y); - for (let j = 0; j < i; j++) add(row, --x, --y); - for (let j = 0; j < i; j++) add(row, ++x, --y); - for (let j = 0; j < i; j++) add(row, x += 2, y); + for (let i = 0; i < layerCount; i++) { + // Add layer + const layer = this.layers[i]; + this.container.addChild(layer); + + // Add layer particles + this.addParticle(layer, x += 2, y); + for (let j = 0; j < i - 1; j++) this.addParticle(layer, ++x, ++y); + for (let j = 0; j < i; j++) this.addParticle(layer, --x, ++y); + for (let j = 0; j < i; j++) this.addParticle(layer, x -= 2, y); + for (let j = 0; j < i; j++) this.addParticle(layer, --x, --y); + for (let j = 0; j < i; j++) this.addParticle(layer, ++x, --y); + for (let j = 0; j < i; j++) this.addParticle(layer, x += 2, y); + } + } + + addParticle = (layer: PIXI.Container, x: number, y: number) => { + const { size, spacing } = this.state; + + // Get position + x = (x - 2.125) * ((size.value * 0.4330125) + spacing.value) + this.size / 2; + y = (y - 0.05) * ((size.value * 0.75) + spacing.value) + this.size / 2; + + if (x <= -size.value / 2 || x >= this.size + size.value / 2 || y < -size.value / 2 || y >= this.size + size.value / 2) { + return; } + // Get particle + let particle = this.particles.find(particle => !particle.parent); + if (!particle) { + particle = new PIXI.Container(); + this.particles.push(particle); + + const shape = PIXI.Sprite.from(this.shapeTexture); + particle.addChild(shape); + } + + // Update particle + layer.addChild(particle); + particle.position.x = x; + particle.position.y = y; + + // Update shape + const shape = particle.children[0]; + shape.setSize(size.value); + shape.position.x = -shape.width / 2; + shape.position.y = -shape.height / 2; } - function handleTick(progress: number) { - for (let i = 0; i < rows.length; i++) { - const row = rows[i]; - const alpha = 1.5 - Math.max(0.5, Math.sin(PI_M2 * (progress - i / rows.length) + (i % 2 ? 1 : 3))); + handleTick = (progress: number) => { + const { speed } = this.state; - for (let j = 0; j < row.length; j++) { - let hex = row[j]; - hex.scale.x = hex.scale.y = alpha; + for (let i = 0; i < this.container.children.length; i++) { + const layer = this.container.children[i]; + const scale = 1.5 - Math.max(0.5, Math.sin(PI_M2 * (progress * speed.value - i / this.container.children.length) + (i % 2 ? 1 : 3))); + + for (let j = 0; j < layer.children.length; j++) { + const particle = layer.children[j]; + particle.scale.set(scale); } } } - return ; + render() { + return <> + this.setState(items)} + /> + + ; + } } \ No newline at end of file diff --git a/src/app/hexagon/5/page.tsx b/src/app/hexagon/5/page.tsx index 2c62dd9..617b552 100644 --- a/src/app/hexagon/5/page.tsx +++ b/src/app/hexagon/5/page.tsx @@ -1,104 +1,158 @@ 'use client'; -import { PixiPlayer } from '@/components'; +import { ExperimentControls, PixiPlayer } from '@/components'; import { PI_M2 } from '@/setup'; import * as PIXI from 'pixi.js'; +import React from 'react'; -export default function Hexagon3() { +export default class Hexagon5 extends React.Component { - const container = new PIXI.Container(); - const rows: PIXI.Container[][] = []; - const size = 25; - const spacing = 2; + container = new PIXI.Container(); + layers: PIXI.Container[] = []; + particles: PIXI.Container[] = []; + shapeSize = 100; + size = 640; - function handleInit(app: PIXI.Application) { - app.stage.addChild(container); + shapeTextures: PIXI.Texture[]; - // Create draw - function draw(color: number) { + colors = [ + 0xc62828, + 0x00e575, + 0x00acc1, + ]; + + state = { + size: { min: 10, max: 100, step: 1, value: 30 }, + spacing: { min: 0, max: 100, step: 1, value: 5 }, + speed: { min: 1, max: 10, step: 1, value: 1 }, + }; + + handleInit = (app: PIXI.Application) => { + app.stage.addChild(this.container); + + // Get shape textures + this.shapeTextures = this.colors.map(color => { const shape = new PIXI.Graphics() - .moveTo(0, 0.5 * size) - .lineTo(0.4330125 * size, 0.25 * size) - .lineTo(0.4330125 * size, -0.25 * size) - .lineTo(0, -0.5 * size) - .lineTo(-0.4330125 * size, -0.25 * size) - .lineTo(-0.4330125 * size, 0.25 * size) + .moveTo(0, 0.5 * this.shapeSize) + .lineTo(0.4330125 * this.shapeSize, 0.25 * this.shapeSize) + .lineTo(0.4330125 * this.shapeSize, -0.25 * this.shapeSize) + .lineTo(0, -0.5 * this.shapeSize) + .lineTo(-0.4330125 * this.shapeSize, -0.25 * this.shapeSize) + .lineTo(-0.4330125 * this.shapeSize, 0.25 * this.shapeSize) .fill(color); return app.renderer.generateTexture(shape); + }); + + // Trigger state change + this.setState({}); + } + + componentDidUpdate() { + const { size } = this.state; + const layerCount = Math.ceil(this.size / size.value); + + // Create missing layers + for (let i = this.layers.length; i < layerCount; i++) { + const layer = new PIXI.Container(); + this.layers.push(layer); } - // Get textures - const textures = [ - draw(0xc62828), - draw(0x00e575), - draw(0x00acc1), - ]; + // Remove particles from layers and layers from container + this.particles.forEach(particle => particle.removeFromParent()); + this.layers.forEach(layer => layer.removeFromParent()); - // Create add - function add(row: PIXI.Container[], x: number, y: number) { - x = (x - 2) * ((size * 0.4330125) + spacing) + app.canvas.width / 2; - y = y * ((size * 0.75) + spacing) + app.canvas.height / 2; + // Add layers and particles + let x = 0, y = 0; + for (let i = 0; i < layerCount; i++) { + // Add layer + const layer = this.layers[i]; + this.container.addChild(layer); + + // Add layer particles + this.addParticle(layer, x += 2, y); + for (let j = 0; j < i - 1; j++) this.addParticle(layer, ++x, ++y); + for (let j = 0; j < i; j++) this.addParticle(layer, --x, ++y); + for (let j = 0; j < i; j++) this.addParticle(layer, x -= 2, y); + for (let j = 0; j < i; j++) this.addParticle(layer, --x, --y); + for (let j = 0; j < i; j++) this.addParticle(layer, ++x, --y); + for (let j = 0; j < i; j++) this.addParticle(layer, x += 2, y); + } + } - if (x <= -size / 2 || x >= app.canvas.width + size / 2 || y < -size / 2 || y >= app.canvas.height + size / 2) { - return; - } + addParticle = (layer: PIXI.Container, x: number, y: number) => { + const { size, spacing } = this.state; + + // Get position + x = (x - 2) * ((size.value * 0.4330125) + spacing.value) + this.size / 2; + y = y * ((size.value * 0.75) + spacing.value) + this.size / 2; + + if (x <= -size.value / 2 || x >= this.size + size.value / 2 || y < -size.value / 2 || y >= this.size + size.value / 2) { + return; + } - const hex = new PIXI.Container(); - container.addChild(hex); - row.push(hex); - hex.position.x = x; - hex.position.y = y; + // Get particle + let particle = this.particles.find(particle => !particle.parent); + if (!particle) { + particle = new PIXI.Container(); + this.particles.push(particle); - for (let i = 0; i < textures.length; i++) { - const sprite = PIXI.Sprite.from(textures[i]); - hex.addChild(sprite); + for (let i = 0; i < this.shapeTextures.length; i++) { + const sprite = PIXI.Sprite.from(this.shapeTextures[i]); + particle.addChild(sprite); sprite.position.x = -sprite.width / 2; sprite.position.y = -sprite.height / 2; } } - // Add rows - let x = 0, y = 0; - for (let i = 0; i <= 20; i++) { - const row: PIXI.Container[] = []; - rows.push(row); - - add(row, x += 2, y); - for (let j = 0; j < i - 1; j++) add(row, ++x, ++y); - for (let j = 0; j < i; j++) add(row, --x, ++y); - for (let j = 0; j < i; j++) add(row, x -= 2, y); - for (let j = 0; j < i; j++) add(row, --x, --y); - for (let j = 0; j < i; j++) add(row, ++x, --y); - for (let j = 0; j < i; j++) add(row, x += 2, y); - } + // Update particle + layer.addChild(particle); + particle.position.x = x; + particle.position.y = y; + + // Update shape + particle.children.forEach(shape => { + shape.setSize(size.value); + shape.position.x = -shape.width / 2; + shape.position.y = -shape.height / 2; + }); } - function handleTick(progress: number) { - for (let i = 0; i < rows.length; i++) { - const row = rows[i]; - const alpha = PI_M2 * (progress - i / rows.length); + handleTick = (progress: number) => { + const { speed } = this.state; + + for (let i = 0; i < this.container.children.length; i++) { + const layer = this.container.children[i]; + const alpha = PI_M2 * (progress * speed.value - i / this.container.children.length); const alphas = [ Math.max(0, Math.sin(alpha)), Math.max(0, Math.sin(2 * alpha)), Math.max(0, Math.sin(3 * alpha)), ]; - const beta = 0.5 * Math.abs(Math.sin(PI_M2 * (progress - i / rows.length))) + 0.5; + const scale = 0.5 * Math.abs(Math.sin(PI_M2 * (progress * speed.value - i / this.container.children.length))) + 0.5; - for (let j = 0; j < row.length; j++) { - let hex = row[j]; - hex.scale.x = hex.scale.y = beta; + for (let j = 0; j < layer.children.length; j++) { + const particle = layer.children[j]; + particle.scale.set(scale); for (let k = 0; k < alphas.length; k++) { - hex.children[k].alpha = alphas[k]; + particle.children[k].alpha = alphas[k]; } } } } - return ; + render() { + return <> + this.setState(items)} + /> + + ; + } } \ No newline at end of file diff --git a/src/app/simplex-noise/1/page.tsx b/src/app/simplex-noise/1/page.tsx index 5acc4e7..f041514 100644 --- a/src/app/simplex-noise/1/page.tsx +++ b/src/app/simplex-noise/1/page.tsx @@ -1,59 +1,92 @@ 'use client'; -import { PixiPlayer } from '@/components'; +import { ExperimentControls, PixiPlayer } from '@/components'; import { PI_M2 } from '@/setup'; import * as PIXI from 'pixi.js'; +import React from 'react'; import { createNoise3D } from 'simplex-noise'; -export default function SimplexNoise1() { - const container = new PIXI.Container(); - const noise3D = createNoise3D(); - const size = 640; - - const center = size / 2; +export default class SimplexNoise1 extends React.Component { - function handleInit(app: PIXI.Application) { - app.stage.addChild(container); + center = 320; + container = new PIXI.Container(); + noise3D = createNoise3D(); + particles: PIXI.Sprite[] = []; + size = 640; + + particleTexture: PIXI.Texture; + + state = { + opacity: { min: 0, max: 1, value: 0.1 }, + noise: { min: 0, max: 10, value: 1 }, + particles: { min: 1, max: 2000, step: 1, value: 1000 }, + }; + + handleInit = (app: PIXI.Application) => { + app.stage.addChild(this.container); // Get particle texture const particleShape = new PIXI.Graphics() .circle(0, 0, 1) - .fill({ - alpha: 0.1, - color: 0xFFFFFF, - }); - const particleTexture = app.renderer.generateTexture(particleShape); - - // Add to container - for (let i = 0; i < 2000; i++) { - const particle = PIXI.Sprite.from(particleTexture); - container.addChild(particle); + .fill(0xFFFFFF); + this.particleTexture = app.renderer.generateTexture(particleShape); + + // Trigger state change + this.setState({}); + } + + componentDidUpdate() { + const { opacity, particles } = this.state; + this.container.alpha = opacity.value; + + // Create missing particles + for (let i = this.particles.length; i < particles.value; i++) { + const particle = PIXI.Sprite.from(this.particleTexture); + this.particles.push(particle); + } + + // Remove particles from container + this.particles.forEach(particle => particle.removeFromParent()); + + // Add visible particles to container + for (let i = 0; i < particles.value; i++) { + const particle = this.particles[i]; + this.container.addChild(particle); } } - function handleTick(progress: number) { - for (let i = 0; i < container.children.length; i++) { + handleTick = (progress: number) => { + const { noise } = this.state; + + for (let i = 0; i < this.container.children.length; i++) { // Get particle - const particle = container.children[i]; - const n = i / container.children.length; + const particle = this.container.children[i]; + const n = i / this.container.children.length; // Get noise const noiseAlpha = PI_M2 * (n + progress); - const noiseX = Math.cos(noiseAlpha); - const noiseY = Math.sin(noiseAlpha); - const noise1 = noise3D(noiseX, noiseY, n) * 50; - const noise2 = noise3D(noiseX + 2, noiseY, n) * 50; + const noiseX = Math.cos(noiseAlpha * noise.value); + const noiseY = Math.sin(noiseAlpha * noise.value); + const noise1 = this.noise3D(noiseX, noiseY, n) * 50; + const noise2 = this.noise3D(noiseX + 2, noiseY, n) * 50; // Update particle - particle.x = window.innerWidth * (i / container.children.length) + noise1; - particle.y = center + noise2; + particle.x = this.size * (i / this.container.children.length) + noise1; + particle.y = this.center + noise2; } } - return ; - + render() { + return <> + this.setState(items)} + /> + + ; + } } \ No newline at end of file diff --git a/src/app/simplex-noise/2/page.tsx b/src/app/simplex-noise/2/page.tsx index 88f79fb..4729d02 100644 --- a/src/app/simplex-noise/2/page.tsx +++ b/src/app/simplex-noise/2/page.tsx @@ -1,67 +1,105 @@ 'use client'; -import { PixiPlayer } from '@/components'; +import { ExperimentControls, PixiPlayer } from '@/components'; import { PI_M2 } from '@/setup'; import alea from 'alea'; import * as PIXI from 'pixi.js'; +import React from 'react'; import { createNoise3D } from 'simplex-noise'; -export default function SimplexNoise2() { +export default class SimplexNoise2 extends React.Component { - const container = new PIXI.Container(); - const loops = 10; - const particles = 1000; - const rows = 3; - const size = 640; - - const center = size / 2; + center = 320; + container = new PIXI.Container(); + layers: PIXI.Container[] = []; + noise3Ds = new Array(10).fill(0).map((_, index) => createNoise3D(alea(index))); + particles: PIXI.Sprite[] = []; - const noise3Ds = new Array(rows).fill(0).map((_, index) => { - return createNoise3D(alea(index)); - }); + particleTexture: PIXI.Texture; - function handleInit(app: PIXI.Application) { - app.stage.addChild(container); + state = { + layers: { min: 1, max: 10, step: 1, value: 3 }, + loops: { min: 1, max: 20, step: 1, value: 10 }, + noise: { min: 0, max: 10, value: 1 }, + opacity: { min: 0, max: 1, value: 1 }, + particles: { min: 1, max: 2000, step: 1, value: 1000 }, + }; + + handleInit = (app: PIXI.Application) => { + app.stage.addChild(this.container); // Get particle texture const particleShape = new PIXI.Graphics() .circle(0, 0, 1) - .fill({ - alpha: 0.2, - color: 0xFFFFFF, - }); - const particleTexture = app.renderer.generateTexture(particleShape); - - // Add to container - for (let i = 0; i < rows; i++) { - for (let j = 0; j < particles; j++) { - const particle = PIXI.Sprite.from(particleTexture); - container.addChild(particle); - particle.alpha = 0.8 * (i / rows) + 0.2; + .fill(0xFFFFFF); + this.particleTexture = app.renderer.generateTexture(particleShape); + + // Trigger state change + this.setState({}); + } + + componentDidUpdate() { + const { layers, opacity, particles } = this.state; + const particleCount = layers.value * particles.value; + + // Create missing layers + for (let i = this.layers.length; i < layers.value; i++) { + const layer = new PIXI.Container(); + this.layers.push(layer); + } + + // Create missing particles + for (let i = this.particles.length; i < particleCount; i++) { + const particle = PIXI.Sprite.from(this.particleTexture); + this.particles.push(particle); + } + + // Remove particles from layers and layers from container + this.particles.forEach(particle => particle.removeFromParent()); + this.layers.forEach(layer => layer.removeFromParent()); + + // Add visible layers to container + for (let i = 0; i < layers.value; i++) { + const layer = this.layers[i]; + this.container.addChild(layer); + layer.alpha = 0.8 - (i / layers.value * opacity.value); + } + + // Add visible particles to layers + for (let i = 0; i < layers.value; i++) { + const layer = this.layers[i]; + for (let j = 0; j < particles.value; j++) { + const particle = this.particles[i * particles.value + j]; + layer.addChild(particle); } } } - function handleTick(progress: number) { - for (let i = 0; i < rows; i++) { + handleTick = (progress: number) => { + const { noise, loops } = this.state; + + for (let i = 0; i < this.container.children.length; i++) { + // Get layer + const layer = this.container.children[i]; + // Get noise - const noise3D = noise3Ds[i]; - const offset = PI_M2 * progress + (i / rows) * PI_M2; + const noise3D = this.noise3Ds[i]; + const offset = PI_M2 * progress + (i / this.container.children.length) * PI_M2; - for (let j = 0; j < particles; j++) { + for (let j = 0; j < layer.children.length; j++) { // Get particle - const particle = container.children[particles * i + j]; - const n = j / particles; + const particle = layer.children[j]; + const n = j / layer.children.length; // Get spiral const spiralAlpha = PI_M2 * n; - const spiralRadius = 50 * Math.sin(PI_M2 * (j / (particles / loops)) + offset) + 200; - const spiralX = spiralRadius * Math.sin(spiralAlpha) + center; - const spiralY = spiralRadius * Math.cos(spiralAlpha) + center; + const spiralRadius = 50 * Math.sin(PI_M2 * (j / (layer.children.length / loops.value)) + offset) + 200; + const spiralX = spiralRadius * Math.sin(spiralAlpha) + this.center; + const spiralY = spiralRadius * Math.cos(spiralAlpha) + this.center; // Get noise const noiseAlpha = PI_M2 * (n + progress); - const noiseX = Math.cos(noiseAlpha); - const noiseY = Math.sin(noiseAlpha); + const noiseX = Math.cos(noiseAlpha * noise.value); + const noiseY = Math.sin(noiseAlpha * noise.value); const noise1 = noise3D(noiseX, noiseY, n) * 50; const noise2 = noise3D(noiseX + 2, noiseY, n) * 50; @@ -72,10 +110,18 @@ export default function SimplexNoise2() { } } - return ; + render() { + return <> + this.setState(items)} + /> + + ; + } } \ No newline at end of file diff --git a/src/components/ExperimentControls/ExperimentControls.tsx b/src/components/ExperimentControls/ExperimentControls.tsx new file mode 100644 index 0000000..911ae32 --- /dev/null +++ b/src/components/ExperimentControls/ExperimentControls.tsx @@ -0,0 +1,81 @@ +'use client'; +import { Close, Settings, SettingsBackupRestore } from '@mui/icons-material'; +import { Box, IconButton, Slider, Typography } from '@mui/material'; +import _ from 'lodash'; +import { useEffect, useState } from 'react'; + +export default function ExperimentControls({ items, onChange }: ExperimentControlsProps) { + + const [initial, setInitial] = useState>(); + const [open, setOpen] = useState(false); + + useEffect(() => { + if (!initial) { + const newInitial = new Map(); + Object.entries(items).forEach(([name, item]) => { + newInitial.set(name, item.value); + }); + setInitial(newInitial); + } + }, [ + initial, + items, + ]); + + function handleChange(name: string, value: number) { + const newItems = { ...items }; + newItems[name].value = value; + onChange(newItems); + } + + function handleReset() { + const newItems = { ...items }; + initial?.forEach((value, name) => { + newItems[name].value = value; + }); + onChange(newItems); + } + + return + {!open && ( + setOpen(true)}> + + + )} + {open && ( + + + + + + setOpen(false)}> + + + + + {Object.entries(items).map(([name, item]) => ( + + + + {_.startCase(name)} + + + {item.value.toLocaleString()} + + + handleChange(name, value as number)} + /> + + ))} + + + )} + ; + +} \ No newline at end of file diff --git a/src/components/ExperimentControls/ExperimentControls.types.d.ts b/src/components/ExperimentControls/ExperimentControls.types.d.ts new file mode 100644 index 0000000..fbdbe00 --- /dev/null +++ b/src/components/ExperimentControls/ExperimentControls.types.d.ts @@ -0,0 +1,15 @@ +interface ExperimentControlsProps { + items: ExperimentControlItems; + onChange(items: ExperimentControlItems): void; +} + +interface ExperimentControlItems { + [index: string]: ExperimentControlItem; +} + +interface ExperimentControlItem { + max: number; + min: number; + step?: number; + value: number; +} \ No newline at end of file diff --git a/src/components/index.tsx b/src/components/index.tsx index f377fc5..fc58745 100644 --- a/src/components/index.tsx +++ b/src/components/index.tsx @@ -1,4 +1,5 @@ export { default as Analytics } from './Analytics/Analytics'; +export { default as ExperimentControls } from './ExperimentControls/ExperimentControls'; export { default as PixiPlayer } from './PixiPlayer/PixiPlayer'; export { default as PlayerControls } from './PlayerControls/PlayerControls'; export { default as Sidebar } from './Sidebar/Sidebar';