From 1888dca4e84381a656f83fb3c84157002973f544 Mon Sep 17 00:00:00 2001 From: Nilstrieb Date: Thu, 17 Jun 2021 10:09:19 +0200 Subject: [PATCH] yes --- .gitignore | 3 ++ .idea/misc.xml | 4 -- src/App.test.tsx | 9 ----- src/App.tsx | 49 +++++++++++++----------- src/draw/MainDraw.ts | 23 ++++++++++++ src/draw/Particles.ts | 49 ++++++++++++++++++++++++ src/draw/Shapes.ts | 14 +++++++ src/draw/classes/Drawable.ts | 7 ++++ src/draw/classes/Particle.ts | 73 ++++++++++++++++++++++++++++++++++++ src/draw/classes/Vector.ts | 38 +++++++++++++++++++ src/index.css | 13 ------- src/index.tsx | 9 +---- src/logo.svg | 1 - src/reportWebVitals.ts | 15 -------- src/setupTests.ts | 5 --- 15 files changed, 236 insertions(+), 76 deletions(-) delete mode 100644 .idea/misc.xml delete mode 100644 src/App.test.tsx create mode 100644 src/draw/MainDraw.ts create mode 100644 src/draw/Particles.ts create mode 100644 src/draw/Shapes.ts create mode 100644 src/draw/classes/Drawable.ts create mode 100644 src/draw/classes/Particle.ts create mode 100644 src/draw/classes/Vector.ts delete mode 100644 src/index.css delete mode 100644 src/logo.svg delete mode 100644 src/reportWebVitals.ts delete mode 100644 src/setupTests.ts diff --git a/.gitignore b/.gitignore index 4d29575..ff7c608 100644 --- a/.gitignore +++ b/.gitignore @@ -21,3 +21,6 @@ npm-debug.log* yarn-debug.log* yarn-error.log* + +*.iml +.idea \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml deleted file mode 100644 index e0151fd..0000000 --- a/.idea/misc.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/src/App.test.tsx b/src/App.test.tsx deleted file mode 100644 index 2a68616..0000000 --- a/src/App.test.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import React from 'react'; -import { render, screen } from '@testing-library/react'; -import App from './App'; - -test('renders learn react link', () => { - render(); - const linkElement = screen.getByText(/learn react/i); - expect(linkElement).toBeInTheDocument(); -}); diff --git a/src/App.tsx b/src/App.tsx index a53698a..8929a42 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,26 +1,33 @@ -import React from 'react'; -import logo from './logo.svg'; -import './App.css'; +import React, {useEffect, useRef} from 'react'; +import {draw, init, update} from "./draw/MainDraw"; + +let CANVAS_WIDTH = 1500; +let CANVAS_HEIGHT = 700; function App() { - return ( -
-
- logo -

- Edit src/App.tsx and save to reload. -

- - Learn React - -
-
- ); + const canvasRef = useRef(null); + + useEffect(() => { + const canvas = canvasRef.current; + if (!canvas) { + return; + } + const context = canvasRef.current!.getContext("2d")!; + init(); + const interval = setInterval(() => { + update(); + }, 1000 / 30); + draw(context); + return () => clearInterval(interval); + }, [canvasRef]); + + return ( +
+

Redox

+ +
+ ); } +export {CANVAS_WIDTH, CANVAS_HEIGHT}; export default App; diff --git a/src/draw/MainDraw.ts b/src/draw/MainDraw.ts new file mode 100644 index 0000000..f36182d --- /dev/null +++ b/src/draw/MainDraw.ts @@ -0,0 +1,23 @@ +import {rect} from "./Shapes"; +import {updateParticles, particlesInit, drawParticles} from "./Particles"; +import {CANVAS_HEIGHT, CANVAS_WIDTH} from "../App"; + +type FillStyle = string | CanvasGradient | CanvasPattern; +type Ctx = CanvasRenderingContext2D; + +function init() { + particlesInit(); +} + +function draw(ctx: Ctx) { + rect(ctx, 0, 0, CANVAS_WIDTH, CANVAS_HEIGHT, "lightgrey"); + drawParticles(ctx); + requestAnimationFrame(() => draw(ctx)); +} + +function update() { + updateParticles(); +} + +export type {Ctx, FillStyle}; +export {update, init, draw}; diff --git a/src/draw/Particles.ts b/src/draw/Particles.ts new file mode 100644 index 0000000..0a3057d --- /dev/null +++ b/src/draw/Particles.ts @@ -0,0 +1,49 @@ +import {Ctx} from "./MainDraw"; +import Particle from "./classes/Particle"; +import Vector from "./classes/Vector"; +import {CANVAS_HEIGHT, CANVAS_WIDTH} from "../App"; + +const particles: Particle[] = []; + + +export function particlesInit() { + for (let i = 0; i < 200; i++) { + particles.push(new Particle(new Vector( + Math.random() * (CANVAS_WIDTH - 100) + 50, + Math.random() * (CANVAS_HEIGHT - 100) + 50 + ), Math.random() < 0.3 ? 0 : Math.random() < 0.6 ? -1 : 1)); + } +} + +export function drawParticles(ctx: Ctx) { + particles.forEach(p => p.draw(ctx)); +} + +export function updateParticles() { + calculateChargedForces(); + particles.forEach(p => p.update()); +} + +function calculateChargedForces() { + for (let i = 0; i < particles.length; i++) { + const particle = particles[i]; + if (particle.charge !== 0) { + for (let j = i + 1; j < particles.length; j++) { + const innerParticle = particles[j]; + if (innerParticle.charge !== 0) { + const dist = particle.position.distance(innerParticle.position); + if (dist < 1000 && dist > 0.30) { + const f1 = innerParticle.position.sub(particle.position).scale(0.5 / dist ** 2); + if (particle.charge === innerParticle.charge) { + particle.applyForce(f1.negated()); + innerParticle.applyForce(f1); + } else { + particle.applyForce(f1); + innerParticle.applyForce(f1.negated()); + } + } + } + } + } + } +} \ No newline at end of file diff --git a/src/draw/Shapes.ts b/src/draw/Shapes.ts new file mode 100644 index 0000000..1277af5 --- /dev/null +++ b/src/draw/Shapes.ts @@ -0,0 +1,14 @@ +import {Ctx, FillStyle} from "./MainDraw"; + + +export function rect(ctx: Ctx, x: number, y: number, w: number, h: number, color: FillStyle = "black"): void { + ctx.fillStyle = color; + ctx.fillRect(x, y, w, h); +} + +export function circle(ctx: Ctx, x: number, y: number, r: number, color: FillStyle = "black"): void { + ctx.fillStyle = color; + ctx.beginPath(); + ctx.ellipse(x, y, r, r, 0, 0, 50); + ctx.fill(); +} \ No newline at end of file diff --git a/src/draw/classes/Drawable.ts b/src/draw/classes/Drawable.ts new file mode 100644 index 0000000..a46d847 --- /dev/null +++ b/src/draw/classes/Drawable.ts @@ -0,0 +1,7 @@ +import {Ctx} from "../MainDraw"; + +export default interface SimObject { + draw(ctx: Ctx): void; + + update(): void; +} \ No newline at end of file diff --git a/src/draw/classes/Particle.ts b/src/draw/classes/Particle.ts new file mode 100644 index 0000000..16d7c6c --- /dev/null +++ b/src/draw/classes/Particle.ts @@ -0,0 +1,73 @@ +import Vector from "./Vector"; +import SimObject from "./Drawable"; +import {Ctx, FillStyle} from "../MainDraw"; +import {circle} from "../Shapes"; +import {CANVAS_HEIGHT, CANVAS_WIDTH} from "../../App"; + +const PARTICLE_SIZE = 5; +const PARTICLE_EDGE_REPULSION_FORCE = 0.1; +const FRICTION = 0.99; +const RANDOM_ACCELERATION = 2; + +export default class Particle implements SimObject { + private _position: Vector; + private _velocity: Vector; + // private _color: FillStyle; + private _charge: number; + + constructor(position: Vector, /*color = "black", */charge = 0) { + this._position = position; + this._velocity = new Vector(); + //this._color = color; + this._charge = charge; + } + + public applyForce(force: Vector) { + this._velocity = this._velocity.add(force); + } + + public draw(ctx: Ctx): void { + circle(ctx, this._position.x, this._position.y, PARTICLE_SIZE, colorFromCharge(this._charge)); + } + + public update(): void { + this._position = this._position.add(this._velocity); + this._velocity = this._velocity.scale(FRICTION); + + // random movement + if (this._velocity.magnitude() < 0.1 && Math.random() > 0.4) { + this.applyForce(new Vector((Math.random() - 0.5) * RANDOM_ACCELERATION, (Math.random() - 0.5) * RANDOM_ACCELERATION)); + } + + if (this._position.x < 50) { + this.applyForce(new Vector(PARTICLE_EDGE_REPULSION_FORCE, 0)); + } + if (this._position.x > CANVAS_WIDTH - 50) { + this.applyForce(new Vector(-PARTICLE_EDGE_REPULSION_FORCE, 0)); + } + if (this._position.y > CANVAS_HEIGHT - 50) { + this.applyForce(new Vector(0, -PARTICLE_EDGE_REPULSION_FORCE)); + } + if (this._position.y < 50) { + this.applyForce(new Vector(0, PARTICLE_EDGE_REPULSION_FORCE)); + } + } + + public get charge() { + return this._charge; + } + + public get position() { + return this._position; + } +} + +function colorFromCharge(charge: number): FillStyle { + if (charge === 0) { + return "black"; + } + if (charge < 0) { + return "blue"; + } + return "red"; +} \ No newline at end of file diff --git a/src/draw/classes/Vector.ts b/src/draw/classes/Vector.ts new file mode 100644 index 0000000..516631b --- /dev/null +++ b/src/draw/classes/Vector.ts @@ -0,0 +1,38 @@ +export default class Vector { + public x: number; + public y: number; + + constructor(x: number = 0, y: number = 0) { + this.x = x; + this.y = y; + } + + public add(b: Vector): Vector { + return new Vector(this.x + b.x, this.y + b.y); + } + + public sub(b: Vector) { + return new Vector(this.x - b.x, this.y - b.y); + } + + public scale(number: number) { + return new Vector(this.x * number, this.y * number); + } + + public magnitude() { + return Math.sqrt(this.x * this.x + this.y * this.y); + } + + public distance(b: Vector) { + return Math.sqrt((this.x - b.x) ** 2 + (this.y - b.y) ** 2) + } + + public negated() { + return new Vector(-this.x, -this.y); + } + + public normalized() { + const factor = this.magnitude(); + return new Vector(this.x / factor, this.y / factor); + } +} \ No newline at end of file diff --git a/src/index.css b/src/index.css deleted file mode 100644 index ec2585e..0000000 --- a/src/index.css +++ /dev/null @@ -1,13 +0,0 @@ -body { - margin: 0; - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', - 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', - sans-serif; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; -} - -code { - font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', - monospace; -} diff --git a/src/index.tsx b/src/index.tsx index ef2edf8..7173ce5 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -1,17 +1,10 @@ import React from 'react'; import ReactDOM from 'react-dom'; -import './index.css'; import App from './App'; -import reportWebVitals from './reportWebVitals'; ReactDOM.render( , document.getElementById('root') -); - -// If you want to start measuring performance in your app, pass a function -// to log results (for example: reportWebVitals(console.log)) -// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals -reportWebVitals(); +); \ No newline at end of file diff --git a/src/logo.svg b/src/logo.svg deleted file mode 100644 index 9dfc1c0..0000000 --- a/src/logo.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/reportWebVitals.ts b/src/reportWebVitals.ts deleted file mode 100644 index 49a2a16..0000000 --- a/src/reportWebVitals.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { ReportHandler } from 'web-vitals'; - -const reportWebVitals = (onPerfEntry?: ReportHandler) => { - if (onPerfEntry && onPerfEntry instanceof Function) { - import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { - getCLS(onPerfEntry); - getFID(onPerfEntry); - getFCP(onPerfEntry); - getLCP(onPerfEntry); - getTTFB(onPerfEntry); - }); - } -}; - -export default reportWebVitals; diff --git a/src/setupTests.ts b/src/setupTests.ts deleted file mode 100644 index 8f2609b..0000000 --- a/src/setupTests.ts +++ /dev/null @@ -1,5 +0,0 @@ -// jest-dom adds custom jest matchers for asserting on DOM nodes. -// allows you to do things like: -// expect(element).toHaveTextContent(/react/i) -// learn more: https://github.com/testing-library/jest-dom -import '@testing-library/jest-dom';