振荡
出自FlashWiki
目录 |
参考
- Trigonometry, What is it good for? (follow along to 7 parts)
三角学
三角学是一门研究边角关系的数学学科。 Eric W. Weisstein. "Trigonometry." http://mathworld.wolfram.com/Trigonometry.html From MathWorld–A Wolfram Web Resource.
笛卡尔坐标 vs.极坐标
在我们的程序中,为了描述物体的运动或行为常常使用了笛卡尔坐标系统。比如,每一个物体都有一对坐标值X 和 Y(相对于坐标原点)。事实上,我们也可以用绕原点的角度和距离原点的长度来描述一个点的位置,这被称为极坐标系统。三角学中告诉了我们一个方法如何转换这两种坐标系统。
这便是使用正弦,余弦和正切(勾股定理中的知识),了解这些我们就可以写出转换这两种坐标系统所需要的公式。
float r = 50.0f; float theta = radians(30); // Convert from polar to cartesian float x = r * cos(theta); float y = r * sin(theta); println(x + " " + y); // Convert from cartesian to polar r = sqrt(x*x + y*y); theta = atan2(y,x); println(r + " " + degrees(theta));
在Processing中,我们都是用笛卡尔坐标系统来描述一个点的位置,但是,上面的公式可以让我们把它转化成极坐标系统来描述角速度,角加速度之类的。
振动
振动是指一个物体周期性的在两个点之间运动,典型的例子是钟摆。要用程序模拟振动,我们通常把它简化成简谐振动的模拟。因为简谐振动的运动轨迹附加上时间轴会形成标准的正弦曲线。而标准的正弦曲线可以由下面三个参数来描述:
- 振幅: 曲线上每一个波峰波谷距离原点的垂直距离。
- 周期: 走完一段完整的正弦曲线所需要的时间.
- 频率: 在一段单位时间内能走完的正弦曲线段数 (1 / 周期).
用时间为变量的函数x表示如下:
x (t) = 振幅 * cos( 2 * PI * t / 周期)
在程序的世界中,因为我们的时间概念并不像真实世界那样连续不断的,程序中的时间都是一帧接一帧的。所以我们可以把动画走一帧所用的时间定义为单位时间。下面的代码就是在程序中描述正弦曲线的方法:
float period = 600.0f; float amplitude = 75.0f; float x = amplitude * cos(TWO_PI * framecounter / period); framecounter++;
钟摆
钟摆是振动中一个典型的特例。在这种情况里,小球受到重力和一根摆绳的约束,绕摆绳另一个端点做左右摆动的运动。要研究这种情况,我们先分析它的受力情况,我们知道小球只受两个力的作用,重力和摆绳的拉力,在钟摆运行的任意时刻,我们有这样一些值:
- θ = 摆绳和重力的夹角
- R = 摆绳的长度
- T = 摆绳的拉力(注意这个力会不停的变化,唯一固定的是这个力和速度方向永远保持垂直)
- m = 小球的质量
- F = 小球的重力
- g = 重力加速度
- τ = 力矩
- I = 转动惯量
- α = 角加速度
讨论这个问题有很多种办法,其中有一种最简单的方法就是使用力矩,这里计算力矩有两种方法:
τ = I * α (转动惯量 * 角速度) τ = - F * sinθ * R (重力产生的力矩,因为拉力始终和速度方向永远保持垂直,所以不产生力矩)
推导出
I * α = - F * sinθ * R
然后因为
I = m * R * R F = m * g
所以
m * R * R * α = - m * g * sinθ * R
结果
α = -(g ⁄ R) * sinθ
更多介绍可以访问: http://www.myphysicslab.com/pendulum1.html
使用这个公式,我们可以创建自己钟摆 class,定义好所需要的属性
class Pendulum {
PVector loc; //location of pendulum ball
PVector origin; //location of arm origin
float r; //length of arm
float theta; //pendulum arm angle
float theta_vel; //angle velocity
float theta_acc; //angle acceleration
float ballr; //ball radius
float damping; //arbitary damping amount
etc. . .
在定义一个叫update的方法,用来计算角速度,再根据角速度计算出当前的角度,再通过极坐标转笛卡尔坐标获得当前的位置。
void update() {
float G = 0.2; //arbitrary universal gravitational constant
theta_acc = (-1 * G / r) * sin(theta); //calculate acceleration (see: http://www.myphysicslab.com/pendulum1.html)
theta_vel += theta_acc; //increment velocity
theta_vel *= damping; //arbitrary damping
theta += theta_vel; //increment theta
loc.set(r*sin(theta),r*cos(theta),0); //polar to cartesian conversion
loc.add(origin); //make sure the location is relative to the pendulum's origin
}
Waves
To start, we will look at creating a wave pattern by simply graphing the sine function. To do this, we will need to consider the following elements:
- amplitude: height of the wave (y-axis)
- x spacing: # of pixels between each point along the x-axis
- width: # of pixels for the entire wave (in our examples, will be the screen width)
- period: # of pixels for one cycle of the wave
Our technique will be to fill an array with height values, i.e. for every x value (along the x-axis), we will calculate a y-value via sine or cosine and store it in an array. Since 360 degrees (or two pi) is one cycle of a sine wave, we calculate how x will increment according to the period via this formula:
dx * ( period / xspacing) = 2*PI dx = (2*PI*xspacing) / period
int xspacing = 8; //note the use of integer here int w = width; //as these two values will be used to specify the size of the array
float period = 100.0f; float amplitude = 50.0f; //arbitrary amplitude float dx = (TWO_PI / period) * xspacing; //One period = 360 degrees = TWO_PI float[] yvalues = new float[w/xspacing]; //an array for the number of height values we need
//we use a for statement to run through every element of the array //incrementing x as we go for the sine calculation float x = 0.0f; //our values start at angle value 0 for (int i = 0; i < yvalues.length; i++) {
yvalues[i] = sin(x)*amplitude; x+=dx;
}
One we have the height values stored in an array, we can choose to graph them via any type of drawing technique. Here’s one boring solution, simply rendering ellipses at each location. Note that it is not entirely necessary to use an array in this case as we could have rendered the ellipses as we calculated the sine wave. Nevertheless, having the array allows for more flexibility and keeps our code well organized, with the various parts kept modular.
for (int x = 0; x < yvalues.length; x++) {
noStroke(); fill(0,0,255,50); ellipseMode(CENTER); ellipse(x*xspacing,yvalues[x],16,16);
}
The above code will draw a static representation of a wave, to put one in motion, we simply start x off at a different “angle” each time (see the examples for an implementation of the above with a wave in motion).
Making more complex waves:
- Using Noise: We can implement a noise function (instead of sine or cosine) to calculate the height values and create a randomish-looking wave.
/***also try adding a second dimension for noise here to change the wave over time!!***/ for (int i = 0; i < yvalues.length; i++) {
float n = 2*noise(x)-1.0f; //scale noise to be between 1 and -1 yvalues[i] = n*amplitude; yvalues[i] = n*amplitude; x+=dx;
}
- Additive Waves: By adding two or more waves together of different amplitudes and frequencies, we can create more complex wave patterns. See examples above for full implementation (code snippet below).
// Set all height values to zero for (int i = 0; i < yvalues.length; i++) {
yvalues[i] = 0.0f;
}
// Accumulate wave height values for (int j = 0; j < maxwaves; j++) {
float x = theta;
for (int i = 0; i < yvalues.length; i++) {
// Every other wave is cosine instead of sine
if (j % 2 == 0) yvalues[i] += sin(x)*amplitude[j];
else yvalues[i] += cos(x)*amplitude[j];
x+=dx[j];
}
}
Graphing in Two Dimensions
As we saw with perlin noise, by visiting every pixel in the window and calculating a value based on the x,y coordinate, one can create interesting patterns and textures. Similar interesting effects can be developed by applying the same technique to trigonometric functions.
size(100,100); loadPixels(); for (int i = 0; i < width; i++) {
for (int j = 0; j < height; j++) {
float x = i;
float y = j;
pixels[i+j*width] = color(x+y);
}
} updatePixels();
For every x & y coordinate we create a grayscale color based on the x, y value, the result is a simple gradient effect. Graphing trig functions based on polar coordinates can produce much more interesting results. However, we have to map the screen width and height to values that are more useful. In other words instead of the top left being(0,0) and the bottom right being (100,100), we would want (0,0) to be in the center with (-4,-4) top left and (4,4) bottom right.
float w = 8.0f; //2D space width float h = 8.0f; //2D space height float dx = w / width; //increment x this amount per pixel float dy = h / height; //increment y this amount per pixel float x = -w/2; //start x at -1 * width / 2 loadPixels(); for (int i = 0; i < width; i++) {
float y = -h/2; //start y at -1 * height / 2
for (int j = 0; j < height; j++) {
float r = sqrt((x*x) + (y*y)); //convert cartesian to polar
float theta = atan2(y,x); //convert cartesian to polar
float val = cos(r); //calculate a value between -1 and 1
pixels[i+j*width] = color((val + 1.0f) * (255.0/2)); //scale that value to 0 and 255
y += dy; //increment y
}
x += dx; //increment x
} updatePixels();
A more interesting one to try:
float val = sin(6 * cos(r) + 5 * theta); //calculate a value between -1 and 1
