一、开发环境:
window10、火狐浏览器63.0.3、vs code1.28.2
二、应用框架:
p5.js
三、开发背景:
近期阅读《上帝掷骰子吗?》,对量子物理中的粒子深深着迷,并认识到现实世界中种种连续性事件在极微观角度中并不连续,称为普朗克现象,既然计算机是对现实世界的抽象,那么只要找到微观粒子的特征值即可还原微观下的粒子运动。基于此,趁着完成作业的机会,好好的研究一番粒子运动。在p5.js的框架下,事实证明,可以很好的展示粒子运动的效果。虽然仅仅只是一些简单的运动效果,还有很多需要完善的地方,但是此次仅仅作为一次研究粒子运动的尝试,会在之后慢慢完善。
四、内容综述:
主要实现了粒子的无规则运动、圆形粒子的墙壁碰撞检测、粒子的聚集、粒子的绕轴旋转等等粒子运动的效果以及创意时钟的实现过程。
五、实现过程(附代码、HTML与CSS代码省略):
①实现按照时间顺序,一步步“作画”
使用loop()函数和noLoop()函数循环主体作画函数draw(),定义变量count记录循环次数,根据循环次数判断作画步骤;
function draw() {
background(0);
fill(228, 58, 41);
count++;
if (count < 200) {
} else if (count < 300) {
} else if (count < 500) {
} else if (count < 1100) {
} else {
}
}
②随机生成“粒子”,并赋予粒子的特征值——位置、速度
宏观物理世界中的运动通常被视作连续的,抽象在二维平面直角坐标系已连续函数展现。以微观物理学的角度解读粒子,其运动可视为不连续,位置坐标值加上微分位移dx即可表现粒子的运动。
随机产生粒子的同时,随机赋予粒子速度。粒子以此速度运动,如果粒子运动到中心圆(圆心在屏幕中心,半径为100),则停止运动。粒子运动到边界发生碰撞,通过两个判断条件判断粒子是否碰撞到边界,若是,则改变微分位移。
function createParticle() {
for (i = 0; i < 1000; i++) {
//计算下一帧粒子的位置
x[i] = x[i] + dx[i];
y[i] = y[i] + dy[i];
//画出粒子
ellipse(x[i], y[i], 10, 10);
//计算粒子距离中心圆的距离
dist = Math.sqrt(Math.pow(x[i] - (width / 2), 2) + Math.pow((y[i] - height / 2), 2));
//粒子在中心圆内,则停止运动
if (dist < 100) {
dx[i] = 0;
dy[i] = 0;
}
//粒子与墙壁的碰撞过程
if (x[i] > 1900 || x[i] < 0) {
dx[i] = -dx[i];
}
if (y[i] > 900 || y[i] < 0) {
dy[i] = -dy[i];
}
}
}
③聚集粒子,收束粒子到屏幕中心
三角函数的知识计算粒子的正确速度。
for (i = 0; i < 1000; i++) {
//计算粒子与屏幕中心的欧式距离
dist = Math.sqrt(Math.pow(x[i] - (width / 2), 2) + Math.pow((y[i] - height / 2), 2));
//计算粒子距屏幕中心横、纵距离
var distX = x[i] - width / 2;
var distY = y[i] - height / 2;
//粒子速度方向为粒子与中心连线方向,根据三角函数计算
dx[i] = -distY / dist;
dy[i] = distX / dist;
ellipse(x[i], y[i], 1, 1);
x[i] += dx[i] * 20;
y[i] += dy[i] * 20;
}
④扩散粒子,由粒子组成时钟盘
代码与上述过程类似。注意,此时粒子的位置并不是屏幕中心,而是会无线接近屏幕中心,dx和dy无限小,表现在js中为一个极小但不为零的数字。所以,此时计算粒子距离屏幕中心横、纵距离是有意义的,并不为零。
for (i = 0; i < 1000; i++) {
//计算粒子与屏幕中心的欧式距离
dist = Math.sqrt(Math.pow(x[i] - (width / 2), 2) + Math.pow((y[i] - height / 2), 2));
//计算粒子距屏幕中心横、纵距离
var distX = x[i] - width / 2;
var distY = y[i] - height / 2;
//粒子x、y轴方向速度互换,并取其中一值为负,使粒子绕中心旋转,过程中旋转半径变大
dx[i] = -distY / dist;
dy[i] = distX / dist;
ellipse(x[i], y[i], 1, 1);
//乘以20使粒子运动速度更快
x[i] += dx[i] * 20;
y[i] += dy[i] * 20;
}
⑤稳定粒子,以恒定半径旋转
代码同④,将后面两行代码中“*20”修改为“*0.2”。
⑥画时钟刻度
此次时钟刻度采用罗马数字,观察罗马数字字形,基础字符有三个——“X”、“V”和“I”。首先利用p5.js中quad()函数和rect()函数画出基础字符,再通过函数调用基础字符,调整位置,画出罗马数字“I~XII”。
function drawI(positionX, positionY) {
rect((width / 2) - 2 + positionX, (height / 2) - 14 + positionY, 4, 28);
rect((width / 2) - 5 + positionX, (height / 2) - 18 + positionY, 10, 4);
rect((width / 2) - 5 + positionX, (height / 2) + 14 + positionY, 10, 4);
}
function drawX(positionX, positionY) {
quad((width / 2) - 11 + positionX, (height / 2) - 14 + positionY, (width / 2) - 5 + positionX, (height / 2) - 14 + positionY, (width / 2) + 5 + positionX, (height / 2) + 14 + positionY, (width / 2) + 11 + positionX, (height / 2) + 14 + positionY);
quad((width / 2) + 5 + positionX, (height / 2) - 14 + positionY, (width / 2) + 11 + positionX, (height / 2) - 14 + positionY, (width / 2) - 10 + positionX, (height / 2) + 14 + positionY, (width / 2) - 4 + positionX, (height / 2) + 14 + positionY);
rect((width / 2) - 13 + positionX, (height / 2) - 18 + positionY, 10, 4);
rect((width / 2) - 13 + positionX, (height / 2) + 14 + positionY, 10, 4);
rect((width / 2) + 3 + positionX, (height / 2) - 18 + positionY, 10, 4);
rect((width / 2) + 3 + positionX, (height / 2) + 14 + positionY, 10, 4);
}
function drawV(positionX, positionY) {
quad((width / 2) - 13 + positionX, (height / 2) - 16 + positionY, (width / 2) - 7 + positionX, (height / 2) - 16 + positionY, (width / 2) + positionX, (height / 2) + 12 + positionY, (width / 2) + positionX, (height / 2) + 18 + positionY);
quad((width / 2) + 7 + positionX, (height / 2) - 16 + positionY, (width / 2) + 13 + positionX, (height / 2) - 16 + positionY, (width / 2) + positionX, (height / 2) + 18 + positionY, (width / 2) + positionX, (height / 2) + 12 + positionY);
rect((width / 2) - 14 + positionX, (height / 2) - 18 + positionY, 8, 4);
rect((width / 2) + 5 + positionX, (height / 2) - 18 + positionY, 8, 4);
}
⑦画秒针
秒针并不是指针形式,以大小圆的形式展现。
function drawSecond() {
s = second();
var secondX, secondY, scaleAngle;
noFill();
//以圆替代指针,落在数字上时,圆形变大
if (s % 5 == 0) {
secondX = scalePos.scaleX[(parseInt(s / 5) + 11) % 12];
secondY = scalePos.scaleY[(parseInt(s / 5) + 11) % 12];
ellipse(width / 2 + secondX, height / 2 + secondY, 60, 60);
} else {
scaleAngle = s * Math.PI / 30;
secondX = (clockSize + 30) * Math.sin(scaleAngle);
secondY = -(clockSize + 30) * Math.cos(scaleAngle);
ellipse(width / 2 + secondX, height / 2 + secondY, 6, 6);
}
}
⑧画时针、分针,并显示数字时间
时针和分针并不会有太大的变化,相对于秒针的变化,慢得多,基于这个逻辑,设计隐藏时针和分针。按住鼠标左键,将会显示时针分针和数字时间。通过mousePressed()函数和mouseReleased()函数判断鼠标键的按下与抬起。
//bool变量mouseDown表示鼠标键按住
if (mouseDown) {
drawHour();
drawMinute();
drawTextTime();
}
至此,整个时钟的刻画已经结束。
六、结果/对比
结果:
码绘视频:https://v.qq.com/x/page/w0801lb8yp5.html
手绘截图:
对比:
①呈现效果:
码绘在表现“运动”这一主题上具有天然优势。一幅画始终是静态的,即使这幅画很灵动,很飘逸,看起来像动态,也始终是静态图像。利用计算机实现绘画则不同,计算机绘画可以看作画了不止一幅图像,将所有可能图像全部画出来,根据条件判断,将哪些图像播放出来,所以易于表现“运动”这一主题。映射到现实世界中,可以类比小人书、定格动画等。
手绘图虽然无法绘出“动图”,但是手绘呈现的效果却是不同的,绝不能因为手绘无法绘出“动图”就认为在运动表现上码绘更有优势。手绘是另一种艺术形式,是触手可及的绘画,可以给人以情感性,相比“码绘”,更亲近人的情感。而以静态图像表现运动又是另一种感觉,似动非动,很奇妙的体验。(参考梵高的《星月夜》)
②创作体验:
码绘在刻画规则图形时很快速,例如圆形、矩形、三角形等,在刻画不规则图形时很难,例如刻画狗的轮廓,需要复杂的数学函数精确地表达。在利用计算机进行创作时,大多数时候是在根据图像形态和大小寻找合适的函数,需要用到大量的数学知识和编程基础,创作过程就是计算过程,会有计算的“过程感”。
手绘创作工具是铅笔和画板,是可以触碰到的实物,有绘画的“过程感”,易于刻画不规则图像,例如刻画狗的轮廓。在掌握技法的基础上,心中所想即为所画。但是对于刻画规则且精细的图像,手绘就会有困难,例如,画出3cm的直线,这种精确规则图像是无法凭心中的感觉描述的,故而也无法精确表现精确值。
两种不同的创作体验都很棒,码绘是心中抽象→数学(据)抽象,手绘是心中抽象→直接描绘。
参考:
1.《用代码画画》:
0.1 用代码画画——搞艺术的学编程有啥用?
https://blog.csdn.net/magicbrushlv/article/details/77922119
1.1 开始第一幅“码绘”——以编程作画的基本方法
https://blog.csdn.net/magicbrushlv/article/details/77840565
2. 以编程的思想来理解绘画—— (一)用”一笔画“表现“过程美”
https://blog.csdn.net/magicbrushlv/article/details/82634189