1.对称之美——我的灵感来源
在科学中,对称性是指某种操作下的不变性或者守恒性,对称性常与守恒定律相联系。与空间平移不变性对应的是动量守恒定律;与时间平移不变性对应的是能量守恒定律;与转动变换不变性对应的是角动量守恒;与空间反射(镜像)操作不变性对应的是宇称守恒。在弱相互作用中,“宇称”不守恒,自然界在C或P下不是对称的,在CP下也不是对称的,但却是CPT对称的。这里C表示电荷变号操作,相当于反转变换,如由底片洗出照片,电子变正电子,物质变反物质;P表示镜像反射操作,如人照镜子;T表示时间反演操作,如微观可逆过程。也就是说,当同时把粒子与反粒子互变(C)、左与右互变(P)、过去与未来互变(T),自然界又是对称的。——百度百科
没错,我们生活中处处存在对称,对称也是我非常喜欢的一个性质,甚至对于可以那些明明可以对称但偏偏没有对称的东西有一种执念,就想它对称起来。所以我的创意编程灵感就来源于此。
2.作品展示
2.1表达思想
图片想表现的正是对称的概念,正负相生,阴阳相容,用鼠标作过画的人都知道,别说画根直线或者画得像什么东西了,就写个字都奇丑无比,因为其难以控制。所以,这里鼠标体现的就是杂乱无章的运动,而通过对该运动的复制和对称演绎,实现了将无形无律之运动转变为有形有律之图案。看单个图案或许并不规则,但通过简单的旋转重复对称,整幅图案就变得有规律甚至让人觉得美妙动人。
2.2图片内容和程序功能的介绍:
①在图案中间有两个同心圆环,旋转方向相反;②周围有小球绕圆心做半径不断周期性变化的圆周运动,并有拖影效果;③当鼠标进入时,小球会离开当前位置并去追逐鼠标,该动作在50帧内完成;④鼠标在画布中运动时,小球跟随鼠标运动;⑤当鼠标离开画布时,小球离开当前位置前往自动旋转的位置,该动作也在50帧内完成;⑥通过上下方向键实现画布中小球数目的变化。
3.程序编写及代码
3.1 同心圆环的实现
在P5中并没有找到直接画圆弧的函数,所以这里的实现很简单,就是利用先后绘画图案的遮盖关系,画一大一小两个扇形,一个亮色一个和背景色相同,即可实现圆弧的绘制。然后就是圆弧的简单堆砌。
圆弧旋转的实现用的是上一篇文章的方法,millis()函数记录了程序运行的时长,通过它来控制旋转,不再赘述。
var t = millis()/20;
//draw the circle at the center
for (var n=1; n<=numSlices; n++)
{
fill(0, 255, 255);
for (var i=0; i<4; i++)
{
arc(0, 0, 120, 120, t+10+i*90, t+90-20+i*90);
}
fill(0);
for (i=0; i<4; i++)
{
arc(0, 0, 110, 110, t+10+i*90-5, t+90-20+i*90+5);
}
fill(0, 255, 255);
for (i=0; i<4; i++)
{
arc(0, 0, 80, 80, -t+10+i*90, -t+90-20+i*90);
}
fill(0);
for (i=0; i<4; i++)
{
arc(0, 0, 70, 70, -t+10+i*90-5, -t+90-20+i*90+5);
}
}
3.2 小球拖尾效果的实现
通过全局position数组记录小球的过往位置信息,并将其同步绘画在当前画布上,并按时间的先后顺序调整其相对大小(新点大旧点小)即可。
这段代码参考了CSDN,但优化了其代码,更正了逻辑错误也减少了没用的冗余。
function drawPoints(xs)
{
for (var i = xs.length - 1; i >= 0; i--)
{
var x = xs[i].x;
var y = xs[i].y;
var dia = diameter + diameterStep * (numPositions - i);//基础大小(即最小的)+变大步长
fill(0, 255, 255, 255 * (1 - i / numPositions));
ellipse(x - centerX, y - centerY, dia, dia);
}
}
3.3 拖尾小球周期性运动的实现
这里用了旋转画布的思想来实现其对称,在画布以规定的次数旋转一周后即可得到规定数目的小球,这里用numSlices变量来控制小球数量。
小球的圆周运动同样是借助millis()得到时间来实现,实现半径的变大变小是用了direction变量来控制其半径变化方向,到达规定边界后更改direction的值即可。
//小球自动运动时位置的寻找
if (direction_control == 0)
{
au_positions.unshift( {
x:
sin(-t*1.5) * range_control+400,
y:
cos(-t*1.5) * range_control+400,
}
);
range_control = range_control + 1;
if (range_control>=300)
{
direction_control = 1;
}
} else
{
au_positions.unshift( {
x:
sin(-t*1.5) * range_control+400,
y:
cos(-t*1.5) * range_control+400,
}
);
range_control = range_control - 1;
if (range_control<=100)
{
direction_control = 0;
}
}
au_positions.splice(numPositions);//用来去掉过于陈旧的点,只保留规定数目的点
然后调用画拖尾小球的函数,并将画布旋转规定次数即可。
for (var m = 1; m <= numSlices; m++)
{
rotate(360 / numSlices);
drawPoints(au_positions);
}
3.4 鼠标在画布中时小球运动的实现
和上文实现类似,只不过小球位置的记录不同。
positions.unshift( {
x:
mouseX,
y:
mouseY,
}
);
positions.splice(numPositions);
同样旋转画布实现对称。
for (var i = 0; i < numSlices; i++)
{
drawPoints(positions);
rotate(360 / numSlices);
}
3.5 鼠标进入时小球追逐动画的实现
首先判断鼠标是否在画布中(我的画布大小是800*800在左上角)。
然后判断鼠标是由画布外进入的还是一直都在画布中的。若由画布外进入则进行追逐动画。
实现: ①通过两个变量来控制,一个用来判断鼠标是否为由外部移入,一个用来判断追逐的开始(用来对小球追逐位置初始化),这里借鉴了操作系统中原子操作的思想,即做一个标记来标明我这步工作是否做完,我不不做完就不做下一步才能干的事情。
②小球开始追逐的位置即为小球当前自动运动到的位置,然后将▲d设置为二者之间的差距/(50 - 已经运动帧数)的步长(小球会随鼠标运动越追越快),然后不断绘制小球每步的位置即可。需要注意的是:为了旋转方便我将绘图原点放在了画布中央,但是鼠标位置是以浏览器用户控制区左上角为原点的,所以这其中涉及到一个坐标转化问题(一个初中都应该会的坐标转化结果搞了一晚上……)。
if (mouseX<800 && mouseY<800)
{
if (mouse_first_2_out == 0)
{
mouse_first_2_out = 1;
mouse_first_1_out = 1;
}
if (mouse_first_2_in == 1)//chase the mouse
{
if (mouse_first_1_in == 1)
{
x0 = int(400-au_positions[0].x);
y0 = int(400-au_positions[0].y);
mouse_first_1_in = 0;
}
if (chase_num_in < 50)
{
ddx = (0-(mouseX-400)-x0) / (50 - chase_num_in);
ddy = (0-(mouseY-400)-y0) / (50 - chase_num_in);
x0 = x0 + int(ddx);
y0 = y0 + int(ddy);
var dia = diameter + diameterStep * numPositions;
fill(0, 255, 255);
for (var m = 1; m <= numSlices; m++)
{
rotate(360 / numSlices);
ellipse(-x0, -y0, dia, dia);
}
chase_num_in++;
} else
{
mouse_first_2_in = 0;
chase_num_in = 0;
//print('have chase the mouse');
}
} else
{
// For each slice
for (var i = 0; i < numSlices; i++)
{
drawPoints(positions);
rotate(360 / numSlices);
}
}
}
3.5 鼠标离开时小球归位动画的实现
这里和鼠标进入时极为相似,将其反向即可。
else
{
if (mouse_first_2_in == 0)
{
mouse_first_2_in = 1;
mouse_first_1_in = 1;
}
if (mouse_first_2_out == 1)//chase the mouse
{
if (mouse_first_1_out == 1)
{
x0 = -int(mouseX-400);
y0 = -int(mouseY-400);
mouse_first_1_out = 0;
}
if (chase_num_out < 50)
{
ddx = (0-(au_positions[1].x-400)-x0) / (50 - chase_num_out);
ddy = (0-(au_positions[1].y-400)-y0) / (50 - chase_num_out);
x0 = x0 + int(ddx);
y0 = y0 + int(ddy);
var dia = diameter + diameterStep * numPositions;
fill(0, 255, 255);
for (var m = 1; m <= numSlices; m++)
{
rotate(360 / numSlices);
ellipse(-x0, -y0, dia, dia);
}
chase_num_out++;
}else
{
mouse_first_2_out = 0;
chase_num_out = 0;
//print('have chase the mouse');
}
}
else
{
for (var m = 1; m <= numSlices; m++)
{
rotate(360 / numSlices);
drawPoints(au_positions);
}
}
}
3.6 键盘响应函数
这里用键盘响应函数实现小球数目的增减。需要注意的是keyIsDown()函数会对你的按键操作响应一次,完成规定操作,而keyIsPressed()函数则只会在按下相应按键的时段内进行响应,在放开按键后会恢复成按键前的样子。
if(keyIsDown(UP_ARROW))
{
numSlices++;
time_control_rull = time_control;
}
else if(keyIsDown(DOWN_ARROW))
{
numSlices--;
time_control_rull = time_control;
}
这里还有个问题,即更新速度太快了,往往按以下后会一下子响应好多次。所以加入了按键时间间隔判断,如果这次按下的时间在上次按下的0.5秒之后了才行。优化代码如下:
time_control = millis();
//前面还有一个定义了初始值的time_control_rull。
//print(time_control - time_control_rull);
if(time_control - time_control_rull >=500)
{
if(keyIsDown(UP_ARROW))
{
numSlices++;
time_control_rull = time_control;
}
else if(keyIsDown(DOWN_ARROW))
{
numSlices--;
time_control_rull = time_control;
}
}
4.实验体会
这是我第一个P5小三百行的程序,能实现自己心里所想的功能还是很有成就感的,但是创作时长达十个小时,这是让我没有想到的。其实总结写起来很简单,但做的时候真是大费周章。我认为有两点值得反思学习,首先,头脑中蓝图要清晰,我的蓝图真正清晰起来是在编写了三个小时左右的时候。其次,代码实际上就是逻辑和数学的体现,公式一定要推导清楚,不要急于敲代码尝试!因为很浪费时间。我发现当我敲代码时总是想先敲出来试试,导致思考总是仓促的、马虎的、想当然的、不周到的,导致一直在出错,这无疑是浪费时间的,是应该避免的。