简介
上一篇博文我主要写了用向量来实现笛卡尔之心的形态变化。这一次我们以此为基础,把这种向量变化作为“颜料”来制作一个非常有趣的交互式绘画系统。
另外,这次我还使用了一种新的充满艺术美感的数学图案——玫瑰曲线。这种曲线是如此适合用于图形变换,以至于能创造出我意想不到的艺术效果。
综上,本文将详细介绍这个系统的设计思路、代码结构设计以及从多个角度进行的总结。
设计思路
嗯,多说无益,先看看最终效果图吧!
展览模式
此模式用于欣赏(或者说观察)各种参数对图形变化的影响,这些参数将在代码章节具体介绍。
绘画模式
调整各种参数绘制动态画面
拖动绘画
点击绘画
GUI界面
这里是设计思路
一般来讲,绘画有四种要素:材料、作画者、交互方式和作品。
传统的绘画是艺术家使用笔、颜料、画笔等材料,在画布上直接画出图案。
更加宽泛的来讲,只要满足绘画的四要素,创作出可以表达情感的作品,都可以被称之为绘画。比如用延时摄影的技术画“灯影”,在白纸上用裁纸刀“画”线等。
本文的绘画系统即是对“绘画”概念的扩展:
1.在材料上,本系统和传统绘画有很大的不同。传统绘画的颜料是静止的,画完怎么样就是怎么样;本文系统的颜料是向量组成的图形,能够动态周期性变化。
2.在作画者角度上,两者相似。
3.在交互方式上,抽象的来看,鼠标和屏幕,或者数位板和屏幕与传统绘画的画笔与画布没有本质性的不同。打个比方,我用数位板控制机械手在画布上画画和用数位板在屏幕上画画在交互方式上有任何不同吗,这只有“感觉”上的不同。
4.在作品上,传统绘画是静止的,本文的系统是运动的,为的是表现出一种充满活力的动感还有酷炫的变化效果。
总的来讲,就是在材料和展现效果上的对传统绘画进行扩展。
以下是新花样——玫瑰曲线的介绍。
在上一篇博文中我实现了笛卡尔之心的变换,而这一次我找到了另一个有趣的玩意——玫瑰曲线(Rose Curve)。这是一种可以生成类似花瓣样图形的函数。
其极坐标方程如下:
其参数方程如下:
k这个参数决定了玫瑰图案的花瓣数:
花瓣数为2k如果k是偶数
花瓣数为k如果k是奇数
以下是不同参数下的玫瑰图案。这张图中的玫瑰曲线是由极坐标方程定义的,参数k=n/d。
仔细看,里面有笛卡尔之心的样子。遗憾的是,在系统中的参数k只能取整数,所以看不到那个熟悉的身影。
关于玫瑰曲线的资料和图片均来自维基百科。
维基百科——玫瑰曲线介绍
代码
和上次一样,本次的开发平台依然是好用的Processing(3.4版本)。开发语言是java。
封装的类
通过封装核心代码,实现简化代码编写的目的。同时,这种方法也增强了程序的可扩展性和可读性。以下是实现核心功能的两个类:Morph_Color和Morph_Brush
Morph_Color
这个类是用于初始化图形的向量位置的,跟上一次相比新增了一个初始化玫瑰曲线的方法。我已经在上一篇博文中详细介绍了用向量构建图形的原理,这里就不赘述了。
// this class is used to initiative the graph's Vectors
class Morph_Color{
// save the vectors of a morph profile
ArrayList<PVector> morphList;
Morph_Color()
{
morphList = new ArrayList<PVector>();
}
// init rect
void addPolygonVectors(int vectorNum, int pWidth, float part)
{
// Top of square
for (int x = -pWidth/2; x < pWidth/2; x += pWidth/(vectorNum/4)) {
morphList.add(new PVector(x, -(int)pWidth*part));
}
// Right side
for (int y = -pWidth/2; y < pWidth/2; y += pWidth/(vectorNum/4)) {
morphList.add(new PVector((int)pWidth*part, y));
}
// Bottom
for (int x = pWidth/2; x > -pWidth/2; x -= pWidth/(vectorNum/4)) {
morphList.add(new PVector(x, (int)pWidth*part));
}
// Left side
for (int y = pWidth/2; y > -pWidth/2; y -= pWidth/(vectorNum/4)) {
morphList.add(new PVector(-(int)pWidth*part, y));
}
}
// init heart
void addHeartVectors(int vectorNum, int r)
{
float x,y,a;
float crossOver;
crossOver = 1.6;
x=y=0;a=r;
pushMatrix();
translate(width/2,height/2);
int count = 0;
for(float t = -PI;t < PI;t += 2*PI/vectorNum){
if(count == vectorNum)
{
break;
}
x = a*(crossOver*cos(t)-cos(2*t));
y = a*(crossOver*sin(t)-sin(2*t));
morphList.add(new PVector(x,y));
count++;
}
print("heart"+count);
popMatrix();
}
// init circle
void addCircleVectors(int vectorNum, int radius)
{
float angleSector = 2*PI/vectorNum;
int count = 0;
for (float angle = -PI; angle < PI; angle += angleSector) {
if(count == vectorNum)
{
break;
}
PVector v = PVector.fromAngle(angle);
v.mult(radius);
morphList.add(v);
count++;
}
}
// init rose curve
void addRoseCurveVectors(int vectorNum, int a, int k)
{
float x,y;
x=y=0;
pushMatrix();
translate(width/2,height/2);
int count = 0;
for(float t = -PI;t < PI;t += 2*PI/vectorNum){
if(count == vectorNum)
{
break;
}
x = a*sin(k*t)*cos(t);
y = a*sin(k*t)*sin(t);
morphList.add(new PVector(x,y));
count++;
}
popMatrix();
}
}
Morph_Brush
这个类是用于实现形态切换的。切换是靠向量的线性插值实现的。原理详见上篇博文,链接在参考文献中。
// this class is used to draw the process of the
// graph's morph change
class Morph_Brush{
// the two state of one graph
ArrayList<PVector> morphList1;
ArrayList<PVector> morphList2;
boolean state = false;
boolean state2 = true;
int delayValue = 0;
ArrayList<PVector> morph = new ArrayList<PVector>();
Morph_Brush(ArrayList<PVector> ml1)
{
morphList1 = ml1;
for (int i = 0; i < morphList1.