简介
《The Nature of Code》是由 Daniel Shiffman 编写的一本书,致力于帮助读者理解如何在计算机编程中模拟自然系统的行为。它以处理复杂的现象为核心,例如物理、力学、遗传算法、群体行为等自然现象,并通过代码和算法的方式进行解释和模拟。
这本书的特点是以编程为手段,从基础的数学和物理概念出发,逐渐引入复杂的主题,展示如何使用代码模拟自然界中的随机行为、粒子系统、动力学、群体行为等。书中使用的编程语言主要是JavaScript以及基于Processing的库P5.js,但概念是通用的,读者可以应用到其他编程语言中。
书中讨论的几个核心主题包括:
- 向量和运动:如何用代码模拟物体的运动,利用向量进行位置、速度和加速度的计算。
- 力和环境:通过模拟物理力(如重力、摩擦力等)来创建更逼真的动画。
- 随机性与概率:使用随机数生成和概率来模拟不确定性或自然现象中的随机行为。
- 粒子系统:如何使用粒子系统来模拟自然中的复杂系统,比如烟雾、火焰等。
- 群体行为:模拟鸟群、鱼群等集体行为,通过规则和算法来展示复杂的协调性。
- 遗传算法和进化:如何模拟自然选择和进化过程,展示群体如何通过遗传算法“进化”出解决问题的能力。
《The Nature of Code》不仅仅是一本编程指南,还是探索自然现象背后的数学和算法的一种方式。它适合对物理模拟、创意编程以及自然系统感兴趣的开发者或艺术家。
目录
nature of code
我与p5的缘分真的蛮深的,DANIEL SHIFFMAN也算是我的编程启蒙老师了, nature of code,这是一本他撰写的一本关于编程和计算机生成艺术的书籍,详细链接见⬇️
code template(下述代码模版)
template
import type p5 from "p5";
import { useEffect, useRef, memo, useState } from "react";
import {
EnterFullScreenIcon,
ExitFullScreenIcon,
PlayIcon,
StopIcon,
ResumeIcon,
ExclamationTriangleIcon,
} from "@radix-ui/react-icons";
import { DownloadIcon } from "lucide-react";
type Sketch = (p: p5) => void;
const defaultSketch: Sketch = (p: p5) => {
let xoff = 0.0;
p.setup = () => {
p.createCanvas(100, 100);
p.frameRate(60);
};
p.draw = () => {
let noise = p.noise(xoff) * (p.width - 50);
p.background(255);
p.noStroke();
p.fill(34, 39, 54);
xoff += 0.01;
p.ellipse(noise, p.height / 2, 25, 25);
};
};
interface Props {
sketch: Sketch;
showControls?: boolean; // defaults to false
}
const Button = ({
onClick,
children,
}: {
onClick: () => void;
children: React.ReactNode;
}) => (
<button
type="button"
onClick={onClick}
className="inline-block text-skin-inverted rounded bg-skin-inverted px-6 pb-2 pt-2.5 text-xs font-medium uppercase leading-normal shadow-[0_4px_9px_-4px_#cbcbcb] transition duration-150 ease-in-out hover:bg-neutral-100 hover:shadow-[0_8px_9px_-4px_rgba(203,203,203,0.3),0_4px_18px_0_rgba(203,203,203,0.2)] focus:bg-neutral-100 focus:shadow-[0_8px_9px_-4px_rgba(203,203,203,0.3),0_4px_18px_0_rgba(203,203,203,0.2)] focus:outline-none focus:ring-0 active:bg-neutral-200 active:shadow-[0_8px_9px_-4px_rgba(203,203,203,0.3),0_4px_18px_0_rgba(203,203,203,0.2)] dark:shadow-[0_4px_9px_-4px_rgba(251,251,251,0.3)] dark:hover:shadow-[0_8px_9px_-4px_rgba(251,251,251,0.1),0_4px_18px_0_rgba(251,251,251,0.05)] dark:focus:shadow-[0_8px_9px_-4px_rgba(251,251,251,0.1),0_4px_18px_0_rgba(251,251,251,0.05)] dark:active:shadow-[0_8px_9px_-4px_rgba(251,251,251,0.1),0_4px_18px_0_rgba(251,251,251,0.05)]"
>
{children}
</button>
);
let doOnce = false;
const P5Canvas = memo(({ sketch, showControls = false }: Props) => {
const container = useRef<HTMLDivElement>(null);
const trueContainer = useRef<HTMLDivElement>(null);
let [p, setP] = useState<p5>(null);
const [isFullscreen, setIsFullscreen] = useState(false);
const start = async () => {
if (container.current) {
const p5 = await import("p5");
!p && setP(new p5.default(sketch || defaultSketch, container.current));
}
};
const remove = () => {
p && p.remove();
p && setP(null);
};
const stop = () => p && p.isLooping() && p.noLoop();
const begin = () => p && !p.isLooping() && p.loop();
const init = async () => {
remove();
await start();
setLoading(false);
};
const [loading, setLoading] = useState(true);
const clear = () => {
remove();
container.current && (container.current.innerHTML = "");
};
const toggleFullscreen = async () => {
if (trueContainer.current && !isFullscreen) {
if (trueContainer.current.requestFullscreen) {
await trueContainer.current.requestFullscreen();
trueContainer.current.style.backgroundColor = "black";
}
setIsFullscreen(true);
} else {
if (document.exitFullscreen) {
await document.exitFullscreen();
}
trueContainer.current.style.backgroundColor = "";
setIsFullscreen(false);
}
};
const download = () => {
if (!p) return;
p.saveGif("sketch", 5, { delay: 1 });
};
useEffect(() => {
if (p && isFullscreen) {
p.resizeCanvas(window.innerWidth, window.innerHeight - 90);
p.windowResized = async () => {
await init();
p.resizeCanvas(window.innerWidth, window.innerHeight - 90);
container.current.style.minHeight = p?.height + "px";
};
}
const obs = new IntersectionObserver(async ([entry]) => {
if (entry.isIntersecting) {
!doOnce && !container.current.innerHTML && (await init());
!doOnce && !showControls && begin(); //没有按钮自动启动
if (!doOnce && !container.current.innerHTML) doOnce = true;
} else stop();
});
obs.observe(trueContainer.current);
return () => {
obs.disconnect();
remove();
};
}, [isFullscreen, p]);
const handleFullscreenChange = () => {
setLoading(true);
setIsFullscreen(!!document.fullscreenElement);
};
useEffect(() => {
(async () => {
if (!container.current) return;
container.current.innerHTML = "";
await init();
})();
return () => {
remove();
container.current.innerHTML = "";
};
}, [sketch]);
useEffect(() => {
document.addEventListener("fullscreenchange", handleFullscreenChange);
container.current.style.minHeight = p?.height + "px";
trueContainer.current.style.backgroundColor = "";
return () => {
document.removeEventListener("fullscreenchange", handleFullscreenChange);
remove();
};
}, [p]);
return (
<>
<div ref={trueContainer} className="p5_container mt-4 relative">
<div
ref={container}
className="flex w-full max-w-full justify-center"
></div>
{loading && (
<div className="min-h-[200px] flex justify-center items-center text-2xl">
加载中...
</div>
)}
{/* 控制按钮 */}
{!loading && showControls && (
<div
className={
"flex pt-4 select-none justify-around" +
(isFullscreen ? " fixed mb-10 pb-10 left-0 w-full" : "")
}
>
<Button onClick={stop}>
<StopIcon />
</Button>
<Button onClick={begin}>
<PlayIcon />
</Button>
<Button onClick={download}>
<DownloadIcon></DownloadIcon>
</Button>
{/* <Button onClick={init}>
<ResumeIcon></ResumeIcon>
</Button> */}
<Button onClick={clear}>
<ExclamationTriangleIcon></ExclamationTriangleIcon>
</Button>
<Button onClick={toggleFullscreen}>
{isFullscreen ? <ExitFullScreenIcon /> : <EnterFullScreenIcon />}
</Button>
</div>
)}
</div>
</>
);
});
export default P5Canvas;
0. Randomness
随机性
柏林噪声
概率
RandomnessA
RandomnessA
step() {
const choice = p.floor(p.random(4));
switch (choice) {
case 0:
this.x++;
break;
case 1:
this.x--;
break;
case 2:
this.y++;
break;
case 3:
this.y--;
break;
default:
break;
}
}
RandomnessB
RandomnessB
step() {
let xStep = p.random(-1, 1);
let yStep = p.random(-1, 1);
this.x += xStep;
this.y += yStep;
}
RandomnessC
RandomnessC
Probability and Nonuniform Distributions
- Create a random walker that has a greater tendency to move down and to the right. (The solution follows in the next section.)
Probability and Nonuniform Distributions 概率和非均匀分布
let stuff = [1,1,2,4,5,6,7,8,8,8,9,9,9,9,10];
let value = random(stuff);
console.log(value);
let probability = 0.1;
let r = random(1);
if (r < probability) {
console.log("success");
}
RandomnessD
RandomnessD Tends to Move to the Right 趋于向右的随机分布
mouseX,mouseY
上述例子使用鼠标交互,即使用mouseX,mouseY
A Normal Distributions Of Random Numbers
A Normal Distributions Of Random Numbers
柯里化
export default () => {
const common = (p: p5, sd = 0.41) => {
const heights = [];
p.setup = () => {
p.createCanvas(320, 240);
p.background(255);
const e = 2.71828183;
let m = 0;
for (let i = 0; i < p.width + 1; i++) {
let xcoord = p.map(i, 0, p.width, -3, 3);
let sq2pi = p.sqrt(2 * p.PI); //square root of 2 * PI
let xmsq = -1 * (xcoord - m) * (xcoord - m); //-(x - mu)^2
let sdsq = sd * sd; //variance (standard deviation squared)
heights[i] = (1 / (sd * sq2pi)) * p.pow(e, xmsq / sdsq); //P(x) function
}
p.stroke(0);
p.strokeWeight(2);
p.noFill();
p.beginShape();
for (let i = 0; i < heights.length; i++) {
let x = i;
let y = p.map(heights[i], 0, 1, p.height - 2, 2);
p.vertex(x, y);
}
p.endShape();
};
};
const sketchA = (p: p5) => common(p, 0.41);
const sketchB = (p: p5) => common(p, 1.5);
return (
<>
<div className="flex justify-around">
<Basic sketch={sketchA}></Basic>
<Basic sketch={sketchB}></Basic>
</div>
</>
);
};
sigma
正态分布与标准差
正态分布(也称为高斯分布)是统计学中一种重要的概率分布,其特点是对称、钟形曲线.在正态分布中,标准差是一个关键的参数,它衡量数据集中的值与平均值的离散程度.
标准差用来度量数据的离散程度,它越大表示数据的波动越大,越小表示数据的波动越小.标准差的计算公式如下:
其中:
- 是标准差,
- 是数据集中的观测值数量,
- 是每个观测值,
- 是数据集的平均值.
正态分布的特性之一是,约68%的数据在平均值附近的一个标准差范围内,约95%的数据在两个标准差范围内,约99.7%的数据在三个标准差范围内.
如果你有特定的数据集或问题,可以提供更多信息,我可以帮你更具体地理解和解释.
randomGaussian
SimulatePaint
实践一个油漆飞溅模拟
’完美’地在type=range的input使用了useDebouce
import { useDebounce } from "@uidotdev/usehooks";
...
const debouncedSd = useDebounce(sd, 300);
const sketch = useCallback(
(p: p5) => {
...
},
[debouncedSd]
);
const handleSliderChange = (val: string) => {
setSd(parseFloat(val));
};
return (
<>
<input
type="range"
value={sd}
onChange={(e) => { handleSliderChange(e.target.value) }}
/>
...
</>
ResolveReject
接受拒绝分布
PerlinCurve
柏林噪声
柏林噪声就像是把时间停住了,给予它相应tamp返回连续的变化的值
let time = 0;
const draw = () => {
let xoff = time;
for (let i = 0; i < p.width; i++) {
let noise_height = p.noise(xoff) * p.height;
xoff += 0.01;
p.vertex(i, noise_height);
}
p.endShape();
time += 0.01;
};
noise range
noise range
使用map映射noise生成的值
two-dimensional noise
two-dimensional noise
generate marble
generate marble
RandomnessG
RandomnessG
landscape
landscape
wind simulation
wind simulation(actually just noise)
code
import type p5 from "p5";
import Basic from "@components/react/p5/index.tsx";
export default () => {
const sketch = (p: p5) => {
let yoff = 0;
let seed = 5;
let { PI } = p;
const branch = (h: number, xoff: number) => {
let sw = p.map(h, 2, 100, 1, 5);
p.strokeWeight(sw);
p.line(0, 0, 0, -h);
p.translate(0, -h);
h *= 0.7;
xoff += 0.1;
if (h > 4) {
let n = p.floor(p.random(1, 5));
for (let i = 0; i < n; i++) {
let noise = p.noise(xoff + i, yoff);
let theta = p.map(noise, 0, 1, -PI / 2, PI / 2);
if (n % 2 == 0) theta *= -1;
p.stroke(0);
p.push();
p.rotate(theta);
branch(h, xoff);
p.pop();
}
}
};
const setup = () => {
p.createCanvas(p.windowWidth / 4 + 20, 200);
};
const draw = () => {
p.background(255);
p.translate(p.width / 2, p.height);
yoff += 0.005;
p.randomSeed(seed);
branch(60, 0);
};
p.mouseClicked = () => {
yoff = p.random(1000);
seed = p.millis();
};
const resize = () => {
p.resizeCanvas(p.windowWidth / 2, 240);
};
p.setup = setup;
p.draw = draw;
p.windowResized = resize;
};
return <Basic sketch={sketch} showControls></Basic>;
};
todo: flow demo
1. Vectors
向量
力
Mover
ball
traveling around ball
import type p5 from "p5";
import Basic from "@components/react/p5/index.tsx";
export default () => {
const sketch = (p: p5) => {
let x = 100;
let y = 200;
let xSpeed = 1.5;
let ySpeed = 0.8;
const setup = () => {
p.createCanvas(p.windowWidth / 2, 240);
};
const draw = () => {
p.background(255);
x += xSpeed;
y += ySpeed;
if (x + 8 > p.width || x < 8) {
xSpeed *= -1;
}
if (y + 8 > p.height || y < 8) {
ySpeed *= -1;
}
p.stroke(10);
p.fill(127, 30, 30);
p.circle(x, y, 16);
};
const resize = () => {
p.resizeCanvas(p.windowWidth / 2, 240);
};
p.setup = setup;
p.draw = draw;
p.windowResized = resize;
};
return <Basic sketch={sketch} showControls></Basic>;
};
still ball but use p5.Vector
vector add
import type p5 from "p5";
import Basic from "@components/react/p5/index.tsx";
export default () => {
const sketch = (p: p5) => {
let pos, velocity;
const setup = () => {
p.createCanvas(p.windowWidth / 2, 80);
pos = p.createVector(p.width / 2, p.height / 2);
velocity = p.createVector(1.2, 0.8);
};
const draw = () => {
p.background(204);
p.fill(255, 0, 0);
p.stroke(88);
if (pos.x + 8 > p.width || pos.x < 8) {
velocity.x = velocity.x * -1;
}
if (pos.y + 8 > p.height || pos.y < 8) {
velocity.y = velocity.y * -1;
}
pos.add(velocity);
p.circle(pos.x, pos.y, 16);
};
const resize = () => {
p.resizeCanvas(p.windowWidth / 2, 240);
};
p.setup = setup;
p.draw = draw;
p.windowResized = resize;
};
return <Basic sketch={sketch} showControls></Basic>;
};
3d
ball in 3d
import type p5 from "p5";
import Basic from "@components/react/p5/index.tsx";
export default () => {
const sketch = (p: p5) => {
let pos, velocity;
const setup = () => {
p.createCanvas(p.windowWidth / 2, p.windowHeight / 2, p.WEBGL); // 创建一个3D画布
pos = p.createVector(0, 0, 0); // 初始位置
velocity = p.createVector(1, 1, 4); // 初始速度
};
const draw = () => {
p.background(204);
p.fill(255, 0, 0);
p.stroke(88);
console.log(pos, p.width);
p.orbitControl();
// 边界检测
if (pos.x + 16 > p.width / 3 || pos.x - 16 < -p.width / 3) {
velocity.x = velocity.x * -1;
}
if (pos.y + 16 > p.height / 3 || pos.y - 16 < -p.height / 3) {
velocity.y = velocity.y * -1;
}
if (pos.z + 16 > p.width / 3 || pos.z - 16 < -p.width / 3) {
velocity.z = velocity.z * -1;
}
p.noFill();
p.box((p.width * 2) / 3, (p.height * 2) / 3, (p.width * 2) / 3);
pos.add(velocity);
// 在3D空间绘制球体
p.push(); // 保存当前的变换状态
p.translate(pos.x, pos.y, pos.z); // 将坐标系移动到小球的位置
p.fill(126, 127, 128);
p.sphere(16); // 绘制球体
p.pop(); // 恢复之前的变换状态
};
const resize = () => {
p.resizeCanvas(p.windowWidth / 2, 240);
};
p.setup = setup;
p.draw = draw;
p.windowResized = resize;
};
return <Basic sketch={sketch} showControls />;
};
vector subtraction
vector subtraction visualization
vector mul divide
vector mul divide visualization
magnitude
vector magnitude
max:100
normalize
vector normalize
Mover
Mover
import type p5 from "p5";
import Basic from "@components/react/p5/index.tsx";
import { Mover } from "./Mover";
export default () => {
const sketch = (p: p5) => {
let mover: Mover;
const setup = () => {
p.createCanvas(p.windowWidth / 2, 240);
mover = new Mover(p);
};
const draw = () => {
p.background(255);
mover.move();
mover.checkEdges();
mover.show();
};
const resize = () => {
p.resizeCanvas(p.windowWidth / 2, 240);
};
p.setup = setup;
p.draw = draw;
p.windowResized = resize;
};
return <Basic sketch={sketch} showControls></Basic>;
};
InteractiveMotionBall
Mover toggle mouse
小球数量debounceNum:1000
import type p5 from "p5";
import Basic from "@components/react/p5/index.tsx";
class MouseMover {
position: p5.Vector;
velocity: p5.Vector;
circleRadius: number = 20;
acceleration: p5.Vector;
p: p5;
color: p5.Color;
trend: number = 0.2;
constructor(p: p5) {
this.circleRadius = p.random(1, 20);
//球越大,趋势越小
this.trend = p.map(this.circleRadius, 1, 20, 1, 0.5);
this.position = p.createVector(
p.random(this.circleRadius, p.width - this.circleRadius),
p.random(this.circleRadius + 1, p.height - this.circleRadius - 1)
);
this.velocity = p.createVector(p.random(-2, 2), p.random(-2, 2));
this.p = p;
this.color = p.color(p.random(0, 255), p.random(0, 255), p.random(0, 255));
}
move() {
let mouse = this.p.createVector(this.p.mouseX, this.p.mouseY);
let dir = mouse;
dir.sub(this.position);
//靠近的趋势
dir.setMag(this.trend);
// this.acceleration = this.p.createVector(this.p.random(-0.01, 0.01), this.p.random(-0.01, 0.01));
this.acceleration = dir;
// this.acceleration.mult(this.p.random(2));
this.velocity.limit(10);
this.velocity.add(this.acceleration);
this.position.add(this.velocity);
}
show() {
const p = this.p;
p.stroke(0);
p.fill(this.color);
p.strokeWeight(2);
p.circle(this.position.x, this.position.y, this.circleRadius * 2);
}
checkEdges() {
const p = this.p;
if (
this.position.x + this.circleRadius > p.width ||
this.position.x < this.circleRadius
) {
this.velocity.x *= -1;
}
if (
this.position.y + this.circleRadius > p.height ||
this.position.y < this.circleRadius
) {
this.velocity.y *= -1;
}
}
}
export default () => {
const sketch = (p: p5) => {
let movers: MouseMover[];
const setup = () => {
p.createCanvas(p.windowWidth / 2, 240);
movers = new Array(1000).fill(0).map(() => new MouseMover(p));
p.frameRate(120);
};
const draw = () => {
p.background(255);
movers.forEach(m => {
m.move();
m.checkEdges();
m.show();
});
};
const resize = () => {
p.resizeCanvas(p.windowWidth / 2, 240);
};
p.setup = setup;
p.draw = draw;
p.windowResized = resize;
};
return <Basic sketch={sketch} showControls></Basic>;
};
It’s important to note that the moving object has no knowledge about trying to stop at a destination;
2. Forces
牛顿第二定律
牛顿第二定律是经典力学中的一个基本原理,描述了力、质量和加速度之间的关系.它可以用数学公式表示为:
[ F = ma ]
其中,
- 表示作用在物体上的力(单位是牛顿),
- 表示物体的质量(单位是千克),
- 表示物体的加速度(单位是米/秒^2).
这个定律说明,当一个力作用于一个物体时,它会产生该物体的加速度,且这个加速度的大小与作用力成正比,与物体的质量成反比.换句话说,较大的力会导致较大的加速度,而较大的质量会导致较小的加速度.
Using forces, simulate a helium-filled balloon floating upward and bouncing off the top of a window. Can you add a wind force that changes over time, perhaps according to Perlin noise?
exec 2.1
forceA mouse press to model wind
forceA
有一说一,我想要的不是这个,但不管怎么说先留着
让我来看看这个新写的组件会请求几次ForceA
这个组件🙅,内部调用的动态组件不知道为什么不能使用useState
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@shadcn/ui/tabs";
import Code from "@components/react/editor/code";
import { useEffect, useState, useMemo } from "react";
import { ForceA, ForceB } from "@components/react/p5/Forces/index";
import ForceACode from "@components/react/p5/Forces/ForceA.tsx?raw";
import ForceBCode from "@components/react/p5/Forces/ForceB.tsx?raw";
type ComponentName = "ForceA" | "ForceB";
const components: Record<
ComponentName,
{ component: React.ComponentType<any>; code: string }
> = {
ForceA: { component: ForceA, code: ForceACode },
ForceB: { component: ForceB, code: ForceBCode },
};
interface Props {
componentName: ComponentName;
}
export default function DynamicComponent({ componentName }: Props) {
const [code, setCode] = useState<string>("");
const [Component, setComponent] = useState<any>(null);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
let isMounted = true;
const loadComponent = async () => {
try {
if (components[componentName]) {
setCode(components[componentName].code);
setComponent(components[componentName].component);
setError(null);
} else {
setError(`Component not found: ${componentName}`);
}
} catch (error) {
console.error(`Failed to load component: ${componentName}`, error);
setError(`Failed to load component: ${componentName}`);
}
};
loadComponent();
return () => {
isMounted = false;
};
}, [componentName]);
const memoizedComponent = useMemo(() => Component, [Component]);
return (
<>
<Tabs defaultValue="demo" className="w-full">
<TabsList>
<TabsTrigger value="code">Code</TabsTrigger>
<TabsTrigger value="demo">Demo</TabsTrigger>
</TabsList>
<TabsContent value="code">
{error ? error : <Code>{code}</Code>}
</TabsContent>
<TabsContent value="demo">{memoizedComponent}</TabsContent>
</Tabs>
</>
);
}
动态组件的渲染使用了useMemo来避免在每次渲染时重新创建组件.然而,useMemo只是对于组件的引用进行了缓存,但并不会在组件内部创建实际的状态.因此,即使你在动态组件中调用了useState,它也不会正常工作,因为该组件的每次渲染都会重新创建,并且每次重新创建时都会重新初始化状态.(AI)
继续问ai,这个组件中动态组件内部为什么不能调用useState,useRef等
在你的代码中,动态组件被存储在Component
状态中,并在渲染时被调用.然而,React 的规则是在组件的顶层使用 Hook,而不是在条件语句、循环或嵌套函数中使用.动态组件内部可能不能直接调用useState
、useRef
等 Hook,因为它们被认为是在组件的非顶层调用.
为了解决这个问题,你可以通过传递函数作为props给动态组件,然后在动态组件内部调用该函数来更新父组件的状态.下面是一个修改后的示例:
import React, { useEffect, useState, useMemo } from "react";
// 其他导入语句
type ComponentName = "ForceA" | "ForceB";
const components: Record<
ComponentName,
{ component: React.ComponentType<any>; code: string }
> = {
ForceA: { component: ForceA, code: ForceACode },
ForceB: { component: ForceB, code: ForceBCode },
};
interface Props {
componentName: ComponentName;
}
export default function DynamicComponent({ componentName }: Props) {
const [code, setCode] = useState<string>("");
const [Component, setComponent] = useState<any>(null);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
let isMounted = true;
const loadComponent = async () => {
try {
if (components[componentName]) {
setCode(components[componentName].code);
setComponent(components[componentName].component);
setError(null);
} else {
setError(`Component not found: ${componentName}`);
}
} catch (error) {
console.error(`Failed to load component: ${componentName}`, error);
setError(`Failed to load component: ${componentName}`);
}
};
loadComponent();
return () => {
isMounted = false;
};
}, [componentName]);
const memoizedComponent = useMemo(() => Component, [Component]);
const [dynamicState, setDynamicState] = useState<any>(null);
const handleStateChange = (newState: any) => {
setDynamicState(newState);
};
if (memoizedComponent) {
// 如果动态组件已加载,将动态组件渲染出来,并传递一个用于更新父组件状态的函数作为 props
return (
<>
<Tabs defaultValue="demo" className="w-full">
{/* 这里省略 Tabs 相关的代码 */}
<TabsContent value="demo">
<memoizedComponent handleStateChange={handleStateChange} />
</TabsContent>
</Tabs>
</>
);
}
// 如果动态组件尚未加载,渲染加载中的状态或错误消息
return <>{/* 这里省略加载中或错误状态的渲染代码 */}</>;
}
在上述修改中,动态组件被渲染并传递了一个名为handleStateChange
的函数作为props.在动态组件内部,你可以调用handleStateChange
函数来更新父组件的状态.
还是报错🤣,ai果然不能替代人类
React.jsx: type is invalid -- expected a string (for built-in components) or a class/function (for composite components) but got: <Fragment />. Did you accidentally export a JSX literal instead of a component?
wind ForceC(非动态加载组件)
ForceC 回归单独加载组件
小球高度0
最大高度0
为啥这里第一次弹不回去了,但后面又弹到了第一次弹回去的最大高度
a lot of Mover that ForceD
ForceD
可以看到小球比大球“快”多了
exercise2.3
越靠近边缘,受到的反方向的力越大,直到小球掉头
小球高度0
最大高度0
真有意思 真有意思 就像是掉在了水里
let distanceX = p
.createVector(p.width, moverWithWind.position.y)
.dist(moverWithWind.position);
let distanceY = p
.createVector(moverWithWind.position.x, p.height)
.dist(moverWithWind.position);
if (
distanceX < moverWithWind.circleRadius ||
moverWithWind.position.x > p.width
) {
moverWithWind.applyForce(p.createVector(-1, 0));
}
if (
distanceY < moverWithWind.circleRadius ||
moverWithWind.position.y > p.height
) {
moverWithWind.applyForce(p.createVector(0, -1));
}
let distanceXL = p
.createVector(0, moverWithWind.position.y)
.dist(moverWithWind.position);
let distanceYL = p
.createVector(moverWithWind.position.x, 0)
.dist(moverWithWind.position);
if (distanceXL < moverWithWind.circleRadius || moverWithWind.position.x < 0) {
moverWithWind.applyForce(p.createVector(1, 0));
}
if (distanceYL < moverWithWind.circleRadius || moverWithWind.position.y < 0) {
moverWithWind.applyForce(p.createVector(0, 1));
}
优化代码
const applyEdgeForce = (p, mover, position, radius, force) => {
const distance = p.createVector(...position).dist(mover.position);
if (
distance < radius ||
position[0] < 0 ||
position[1] < 0 ||
position[0] > p.width ||
position[1] > p.height
) {
mover.applyForce(force);
}
};
applyEdgeForce(
p,
moverWithWind,
[p.width, moverWithWind.position.y],
moverWithWind.circleRadius,
p.createVector(-1, 0)
);
applyEdgeForce(
p,
moverWithWind,
[0, moverWithWind.position.y],
moverWithWind.circleRadius,
p.createVector(1, 0)
);
applyEdgeForce(
p,
moverWithWind,
[moverWithWind.position.x, p.height],
moverWithWind.circleRadius,
p.createVector(0, -1)
);
applyEdgeForce(
p,
moverWithWind,
[moverWithWind.position.x, 0],
moverWithWind.circleRadius,
p.createVector(0, 1)
);
todo: apply friction
3. Oscillation(振动)
角度
振动
振动相关的物理/数学
振动(Oscillation)是物理学中一个重要的概念,涉及到多个数学和物理知识。振动通常指的是物体围绕某个平衡位置的周期性运动。以下是与振动相关的一些基本概念和知识:
-
简谐振动:简谐振动是最基本的振动形式,描述的是物体在平衡位置附近的运动。其特点是加速度与位移成正比且方向相反,符合胡克定律。简谐振动的数学表达式为: 其中,是位移
A
是振幅, 是角频率, 是初相位。 -
频率和周期:频率是单位时间内完成的振动次数,周期是完成一次振动所需的时间。它们之间的关系为:
-
能量:在振动过程中,系统的能量在动能和势能之间转换。在简谐振动中,势能和动能的总和保持不变,表现为机械能守恒。
-
阻尼振动:实际系统中,振动往往会受到阻力(如摩擦力)的影响,导致振幅逐渐减小,这种现象称为阻尼振动。阻尼振动的方程通常包含一个阻尼项,描述能量的损失。
-
共振:当外部驱动力的频率与系统的自然频率相匹配时,会发生共振现象,导致振幅显著增加。这在许多物理和工程应用中都非常重要。
-
数学工具:振动分析常用到三角函数、微分方程和线性代数等数学工具,帮助描述和解决振动系统的动态行为。
理解这些基本概念有助于深入研究振动现象及其在物理学、工程学和其他相关领域的应用。
angles
angles
在 p5.js 中,默认情况下,角度是以弧度(radians)为单位进行测量的,而不是以度(degrees)为单位。弧度是一种角度的度量方式,它是通过圆弧的长度与圆的半径之间的比率来定义的。具体来说,一个弧度是指弧长等于半径的角度。
以下是一些常见的角度与弧度之间的转换关系:
- 一个完整的圆(360度)等于 弧度。
- 180度等于 弧度。
- 90度等于 弧度。
这意味着在使用 p5.js 进行图形绘制时,如果你想使用度数来指定角度,你需要将度数转换为弧度。例如,可以使用以下公式进行转换:
了解这一点对于在 p5.js 中进行旋转、绘制形状和处理动画等操作非常重要。
const sketch = (p: p5) => {
const setup = () => {
p.createCanvas(p.windowWidth / 2, 240);
p.angleMode(p.DEGREES);
p.frameRate(60);
};
const draw = () => {
p.background(250);
p.push();
p.translate(p.width / 2, p.height / 2);
p.rotate(p.frameCount);
// center
p.line(-50, 0, 50, 0);
//线的两端画两个实心圆
p.fill(255, 0, 0);
p.ellipse(-50, 0, 10);
p.text("开始", -60, 20);
p.ellipse(50, 0, 10);
p.text("结束", 40, 20);
p.fill(0);
p.text(p.frameCount % 360, -5, 20);
p.pop();
};
const resize = () => {
p.resizeCanvas(p.windowWidth / 2, 240);
};
p.setup = () => {
setup();
};
p.draw = draw;
p.windowResized = resize;
};
加速度
加速度
mover add rotate
- mover add force
练习大炮
第1步:建立从大炮中被射出物体的模拟. 每个物体在被击中时(仅一次)和重力(始终存在)应会突然发生力.
第2步:在物体上加上旋转来模拟其从大炮中被射出时的自旋. 做出来的东西看看有多现实
拖动图片至canvas,会生成大炮的背景
三角函数
trigonometry functions
还记得第一章的例子
Click to expand
import type p5 from "p5";
class Mover {
p5: p5;
position: p5.Vector;
acceleration: p5.Vector;
velocity: p5.Vector;
maxVelocity: number;
circleRadius: number;
showLine: boolean = false;
multNum: number;
constructor(p5: p5) {
this.p5 = p5;
this.position = p5.createVector(p5.width / 2, p5.height / 2);
this.acceleration = p5.createVector();
this.velocity = p5.createVector();
this.maxVelocity = 5;
this.circleRadius = 20;
this.showLine = true;
this.multNum = 0.1;
}
checkEdges() {
const p = this.p5;
if (
this.position.x + this.circleRadius > p.width ||
this.position.x < this.circleRadius
) {
this.velocity.x *= -1;
}
if (
this.position.y + this.circleRadius > p.height ||
this.position.y < this.circleRadius
) {
this.velocity.y *= -1;
}
}
update() {
let _ = this.p5;
let mouse = _.createVector(_.mouseX, _.mouseY);
let dir = mouse.sub(this.position).copy();
if (this.showLine) {
_.stroke(0);
_.strokeWeight(2);
_.stroke(40);
_.line(0, 0, _.mouseX, _.mouseY);
_.stroke(80);
_.line(0, 0, this.position.x, this.position.y);
_.stroke(120);
_.line(_.width / 2, _.height / 2, dir.x, dir.y);
}
dir.normalize();
dir.mult(this.multNum);
this.acceleration = dir;
this.velocity.add(this.acceleration);
this.velocity.limit(this.maxVelocity);
this.position.add(this.velocity);
}
show() {
let _ = this.p5;
_.fill(0);
_.stroke(255);
_.ellipse(
this.position.x,
this.position.y,
this.circleRadius,
this.circleRadius
);
}
}
export { Mover };
- 稍微加两行角度控制和show函数更改
Click to expand
import type p5 from "p5";
import { Mover } from "./mouseMover";
export default class MouseLeadingCar extends Mover {
constructor(p5: p5) {
super(p5);
this.showLine = false;
this.multNum = 0.5;
}
show() {
let _ = this.p5;
let angle = _.atan2(this.velocity.y, this.velocity.x);
_.push();
_.rectMode(_.CENTER);
_.fill(0);
_.translate(this.position.x, this.position.y);
_.rotate(angle);
_.rect(0, 0, 30, 10);
_.pop();
}
}
好的,让我们来创建一个简单的车辆模拟程序,使用箭头键控制车辆的移动。左箭头加速向左,右箭头加速向右,同时车辆会朝着它当前移动的方向指向。
- OK
极坐标系
polar system
x= r* cos(ø)
y = r*sin(ø)
exercise 3.5
draw a spiral path start in the center and move outward
草图(手拙,勿怪,😂)
Simulate the spaceship in the game Asteroids. In case you aren’t familiar with Asteroids, here’s a brief description: A spaceship (represented as a triangle) floats in 2D space. The left arrow key turns the spaceship counterclockwise; the right arrow key turns it clockwise. The Z key applies a thrust force in the direction the spaceship is pointing.
- 使用
awsd
控制箭头的方向
maybe something went wrong
和官方的答案好像不太一样捏
Properties of Oscillation
一个周期性振荡的函数
- amplitude 代表振幅,也就是波形从中心线到波峰(或波谷)的最大距离。它决定了波形的强度或大小
- x 代表自变量,通常表示时间或空间位置
- period 代表周期,也就是波形完成一个完整循环所需的时间或距离。它决定了波形的频率,周期越短,频率越高
- 将x除以周期,再乘以2π,确保当x等于周期时,函数值完成一个完整的正弦周期
Exercise 3.7
Using the sine function, create a simulation of a weight (sometimes referred to as a bob) that hangs from a spring from the top of the window. Use the map() function to calculate the vertical position of the bob. In “Spring Forces”, I’ll demonstrate how to create this same simulation by modeling the forces of a spring according to Hooke’s law.
- Oscillator Objects
ok right , wrong again , but not bad
改进改进
Exercise 3.8
Exercise 3.8 Try initializing each Oscillator object with velocities and amplitudes that aren’t random to create some sort of regular pattern. Can you make the oscillators appear to be the legs of an insect-like creature?
😑strange 我做了啥,为啥会变成这样
waves
waves
- use perlin noise
spring forces 弹簧
胡克定律
胡克定律(Hooke’s Law)描述了弹簧的力与其伸长或压缩的距离之间的关系。其数学表达式为:
公式解析
- :弹簧施加的恢复力,单位通常为牛顿(N)。
- :弹簧常数,表示弹簧的刚度,单位为牛顿每米(N/m)。弹簧常数越大,弹簧越硬。
- :弹簧的位移,即弹簧被拉伸或压缩的距离,单位为米(m)。正值表示拉伸,负值表示压缩。
- 负号(-):表示弹簧的力是恢复力,方向与位移相反。也就是说,弹簧会试图将物体拉回到其平衡位置。
物理意义
胡克定律表明,弹簧的恢复力与其变形量成正比。这个定律适用于小范围内的弹性变形,超出这个范围后,弹簧可能会发生永久变形或断裂。
应用示例
假设有一个弹簧常数为 的弹簧,如果它被拉伸了 ,那么弹簧施加的恢复力为:
这个结果表明,弹簧会施加一个20牛顿的力,方向与拉伸方向相反。
嘿嘿,画了个图
模拟弹簧=>多个弹簧=>配置弹簧
spring 模拟弹簧
Create a system of multiple bobs and spring connections. How about connecting a bob to another bob with no fixed anchor?
又是奇怪的结果产生嘞
然后我们得到了这个
- 接下来的模块在records
日常记录
里面学吧