基于processing实现
观察分析
看这个作品我们不难发现其基本元素为空心半球面;其运动呈现内圈更小的部分幅度更大,外圈越大的部分幅度越小,这种运动规律更加类似水波向外圈衰减的规律;内外圈并不是同步进行的,而是向内的起始时间早,类似水波向外扩散的规律。
临摹实现
建立模型
首先在任意建模软件中建一个空心半球面模型,在此处我是用c4d建模:
由于proccessing支持obj格式模型导入,因此在c4d中将模型导出为obj,注意在此处须将材质选择为没有材质,并且翻转Y轴:
初始化环境
在processing中,setup在程序刚启动时执行一次进行初始化,而draw不断循环进行重绘,‘因此环境的初始化在setup中实现。
目的为了实现随时间变化的动效,因此先定义了一个全局变量time用于记录时间;loopx、loopz分别定义半球相对于x轴与z轴的角度改变次数;sphere_num定义半球的数目。
全局变量hmispheres为一个list,用于存放所有PShape以存放空心半球体
ArrayList<PShape> hmispheres;
int time,loopx,loopz,sphere_num;
void setup() {
size(1000, 600, P3D);
noStroke();
time = 0;
loopx = 20;
loopz = 24;
sphere_num = 12;
}
光照
将光照参数写入一个函数,方便每次调用。其中pointLight为点光源directionalLight为聚光灯,ambientLight为环境光,emissive、lightSpecular、shininess用于设置材质,参数在processing的调试模式下进行调整:
void set_light(){
//Light
pointLight(138, 182, 254, 153, -264, -74);
directionalLight(109, 89, 137, 0.2, 0.5, -0.5);
ambientLight(106, 66, 90);
emissive(219, 130, 155);
lightSpecular(122,51,194);
shininess(3);
}
重绘
在进行重绘时刷新背景、灯光材质渲染,并将原点设置在画布中央,并刷新每个空心半球体的状态,最后将时间加1,达到记录时间与改变球状态的效果:
void draw() {
background(231, 128, 139);
translate(width/2,height/2);
set_light();
hmispheres = GetHmispheres(time);
for (int i = 0; i < hmispheres.size(); i++) {
shape(hmispheres.get(i),0,0);
}
time++;
}
Sigmoid函数
尽管Sigmoid常用于机器学习中作为激活函数使用,但由于其将值映射在[0,1]区间且随着值距0的距离增大梯度趋近于0的特性,在此处我将其用于最大rotate的区间映射。
其公式为:
f
(
x
)
=
1
1
+
e
−
x
f(x) = \frac{1}{{1 + {e^{ - x}}}}
f(x)=1+e−x1
其函数样式为:
代码如下图所示:
double sigMoid(double value) {
double ey = Math.pow(Math.E, -value);
double result = 1 / (1 + ey);
return result;
}
获取空心半球体
GetHmispheres用于重绘函数调用,得到所有的半球体,其中最重要的函数为GetEachHmisphere,获取每一个半球体。
GetHmispheres函数具有一个参数time,根据时间的不同改变球的状态:
ArrayList<PShape> GetHmispheres(int time){
ArrayList<PShape>hmispheres = new ArrayList<PShape>();
for(int i=1;i<=sphere_num;i++){
print(sigMoid((sphere_num-i)/12.0));
PShape s = GetEachHmisphere(i,sigMoid(21.9-i-sphere_num),time);
hmispheres.add(s);
}
return hmispheres;
}
GetHmisphere函数具有三个参数num,max_rotate,time,num为当前球的标号,max_rotate为球的最大旋转角度,time为当前时间。max_rotate的输入为sigMoid(21.9-i-sphere_num),随着球的标号增大,也就是球越像外圈,max_rotate越小:
PShape GetEachHmisphere(int num,double max_rotate,int time){
PShape s = loadShape("hemisphere.obj");
float initX = -0.4;
s.scale(num/7.0);
if(time>=num){
int ax = (time-num)%loopx;
int az = (time-num)%loopz;
if(ax==0){
s.rotateX(initX + (float)(0* max_rotate/5));
}
else if(ax==1){
s.rotateX(initX + (float)(1* max_rotate/5));
}
else if(ax==2){
s.rotateX(initX+ (float)(2* max_rotate/5));
}
else if(ax==3){
s.rotateX(initX+ (float)(3* max_rotate/5));
}
else if(ax==4){
s.rotateX(initX+ (float)(4* max_rotate/5));
}
else if(ax==5){
s.rotateX(initX+ (float)(5* max_rotate/5));
}
else if(ax==6){
s.rotateX(initX+ (float)(4* max_rotate/5));
}
else if(ax==7){
s.rotateX(initX+ (float)(3* max_rotate/5));
}
else if(ax==8){
s.rotateX(initX+ (float)(2* max_rotate/5));
}
else if(ax==9){
s.rotateX(initX+ (float)(1* max_rotate/5));
}
else if(ax==10){
s.rotateX(initX - (float)(0* max_rotate/5));
}
else if(ax==11){
s.rotateX(initX - (float)(1* max_rotate/5));
}
else if(ax==12){
s.rotateX(initX - (float)(2* max_rotate/5));
}
else if(ax==13){
s.rotateX(initX - (float)(3* max_rotate/5));
}
else if(ax==14){
s.rotateX(initX - (float)(4* max_rotate/5));
}
else if(ax==15){
s.rotateX(initX - (float)(5* max_rotate/5));
}
else if(ax==16){
s.rotateX(initX - (float)(4* max_rotate/5));
}
else if(ax==17){
s.rotateX(initX - (float)(3* max_rotate/5));
}
else if(ax==18){
s.rotateX(initX - (float)(2* max_rotate/5));
}
else if(ax==19){
s.rotateX(initX - (float)(1* max_rotate/5));
}
if(az==0){
s.rotateZ(0.0 + (float)(0* max_rotate/6));
}
else if(az==1){
s.rotateZ(0.0+ (float)(1* max_rotate/6));
}
else if(az==2){
s.rotateZ(0.0+ (float)(2* max_rotate/6));
}
else if(az==3){
s.rotateZ(0.0+ (float)(3* max_rotate/6));
}
else if(az==4){
s.rotateZ(0.0+ (float)(4* max_rotate/6));
}
else if(az==5){
s.rotateZ(0.0+ (float)(5* max_rotate/6));
}
else if(az==6){
s.rotateZ(0.0+ (float)(6* max_rotate/6));
}
else if(az==7){
s.rotateZ(0.0+ (float)(5* max_rotate/6));
}
else if(az==8){
s.rotateZ(0.0+ (float)(4* max_rotate/6));
}
else if(az==9){
s.rotateZ(0.0+ (float)(3* max_rotate/6));
}
else if(az==10){
s.rotateZ(0.0 + (float)(2* max_rotate/6));
}
else if(az==11){
s.rotateZ(0.0 + (float)(1* max_rotate/6));
}
else if(az==12){
s.rotateZ(0.0 - (float)(0* max_rotate/6));
}
else if(az==13){
s.rotateZ(0.0 - (float)(1* max_rotate/6));
}
else if(az==14){
s.rotateZ(0.0 - (float)(2* max_rotate/6));
}
else if(az==15){
s.rotateZ(0.0 - (float)(3* max_rotate/6));
}
else if(az==16){
s.rotateZ(0.0 - (float)(4* max_rotate/6));
}
else if(az==17){
s.rotateZ(0.0 - (float)(5* max_rotate/6));
}
else if(az==18){
s.rotateZ(0.0 - (float)(6* max_rotate/6));
}
else if(az==19){
s.rotateZ(0.0 - (float)(5* max_rotate/6));
}
else if(az==20){
s.rotateZ(0.0 - (float)(4* max_rotate/6));
}
else if(az==21){
s.rotateZ(0.0 - (float)(3* max_rotate/6));
}
else if(az==22){
s.rotateZ(0.0 - (float)(2* max_rotate/6));
}
else if(az==23){
s.rotateZ(0.0 - (float)(1* max_rotate/6));
}
}
else{
s.rotateX(initX);
s.rotateY(0.0);
}
return s;
}
效果
创新
临摹的效果并没有添加任何的交互效果,仅仅为单纯的展示,因此我想添加一些有意思的交互方式,让体验者可进行一定操作。
考虑到如果圆形改为椎体其会一定程度穿模,且圆环具有分段的特性,采用圆环作为交互对象。
模型
在C4D中创建一个圆环,调小其导管半径,避免穿模:
分别到处其3、4、5……分段的obj模型:
分别存入obj文件方便后续导入,obj的导出设置与临摹相同:
交互
鼠标滚轮
通过滚轮操作可以改变物体向外扩展的数目。
添加一个全局变量用于记录物体数目:
int sphere_num;
通过滚轮操作进行相加或是相减:
void mouseWheel(MouseEvent event) {
float e = event.getCount();
sphere_num += int(e);
}
鼠标左键
通过鼠标左键点击更改物体的样式,从3段到4段,到5段,直到圆环。
添加一个全局变量记录所有模型的文件名称,并在setup函数中进行初始化,currentFile用于标记当前的所取文件,通过点击加一最终除余进行修改模型:
ArrayList<String> fileNames;
int currentFile;
void setup() {
……
currentFile = 0;
fileNames = new ArrayList<String>();
fileNames.add("loop3.obj");
fileNames.add("loop4.obj");
fileNames.add("loop5.obj");
fileNames.add("loop6.obj");
fileNames.add("loop7.obj");
fileNames.add("loop8.obj");
fileNames.add("loop9.obj");
fileNames.add("loop10.obj");
fileNames.add("loop11.obj");
fileNames.add("loop12.obj");
fileNames.add("loop13.obj");
fileNames.add("loop.obj");
}
void mouseClicked() {
currentFile++;
PShape GetEachHmisphere(int num,double max_rotate,int time){
PShape s = loadShape(fileNames.get(currentFile%12));
……
}
效果
完整代码
临摹
ArrayList<PShape> hmispheres;
int time,loopx,loopz,sphere_num;
void setup() {
size(1000, 600, P3D);
noStroke();
time = 0;
loopx = 20;
loopz = 24;
sphere_num = 12;
}
void draw() {
background(231, 128, 139);
translate(width/2,height/2);
set_light();
hmispheres = GetHmispheres(time);
for (int i = 0; i < hmispheres.size(); i++) {
shape(hmispheres.get(i),0,0);
}
time++;
}
void set_light(){
//Light
pointLight(138, 182, 254, 153, -264, -74);
directionalLight(109, 89, 137, 0.2, 0.5, -0.5);
ambientLight(106, 66, 90);
emissive(219, 130, 155);
lightSpecular(122,51,194);
shininess(3);
}
double sigMoid(double value) {
double ey = Math.pow(Math.E, -value);
double result = 1 / (1 + ey);
return result;
}
ArrayList<PShape> GetHmispheres(int time){
ArrayList<PShape>hmispheres = new ArrayList<PShape>();
for(int i=1;i<=sphere_num;i++){
print(sigMoid((sphere_num-i)/12.0));
PShape s = GetEachHmisphere(i,sigMoid(21.9-i-sphere_num),time);
hmispheres.add(s);
}
return hmispheres;
}
PShape GetEachHmisphere(int num,double max_rotate,int time){
PShape s = loadShape("hemisphere.obj");
float initX = -0.4;
s.scale(num/7.0);
if(time>=num){
int ax = (time-num)%loopx;
int az = (time-num)%loopz;
if(ax==0){
s.rotateX(initX + (float)(0* max_rotate/5));
}
else if(ax==1){
s.rotateX(initX + (float)(1* max_rotate/5));
}
else if(ax==2){
s.rotateX(initX+ (float)(2* max_rotate/5));
}
else if(ax==3){
s.rotateX(initX+ (float)(3* max_rotate/5));
}
else if(ax==4){
s.rotateX(initX+ (float)(4* max_rotate/5));
}
else if(ax==5){
s.rotateX(initX+ (float)(5* max_rotate/5));
}
else if(ax==6){
s.rotateX(initX+ (float)(4* max_rotate/5));
}
else if(ax==7){
s.rotateX(initX+ (float)(3* max_rotate/5));
}
else if(ax==8){
s.rotateX(initX+ (float)(2* max_rotate/5));
}
else if(ax==9){
s.rotateX(initX+ (float)(1* max_rotate/5));
}
else if(ax==10){
s.rotateX(initX - (float)(0* max_rotate/5));
}
else if(ax==11){
s.rotateX(initX - (float)(1* max_rotate/5));
}
else if(ax==12){
s.rotateX(initX - (float)(2* max_rotate/5));
}
else if(ax==13){
s.rotateX(initX - (float)(3* max_rotate/5));
}
else if(ax==14){
s.rotateX(initX - (float)(4* max_rotate/5));
}
else if(ax==15){
s.rotateX(initX - (float)(5* max_rotate/5));
}
else if(ax==16){
s.rotateX(initX - (float)(4* max_rotate/5));
}
else if(ax==17){
s.rotateX(initX - (float)(3* max_rotate/5));
}
else if(ax==18){
s.rotateX(initX - (float)(2* max_rotate/5));
}
else if(ax==19){
s.rotateX(initX - (float)(1* max_rotate/5));
}
if(az==0){
s.rotateZ(0.0 + (float)(0* max_rotate/6));
}
else if(az==1){
s.rotateZ(0.0+ (float)(1* max_rotate/6));
}
else if(az==2){
s.rotateZ(0.0+ (float)(2* max_rotate/6));
}
else if(az==3){
s.rotateZ(0.0+ (float)(3* max_rotate/6));
}
else if(az==4){
s.rotateZ(0.0+ (float)(4* max_rotate/6));
}
else if(az==5){
s.rotateZ(0.0+ (float)(5* max_rotate/6));
}
else if(az==6){
s.rotateZ(0.0+ (float)(6* max_rotate/6));
}
else if(az==7){
s.rotateZ(0.0+ (float)(5* max_rotate/6));
}
else if(az==8){
s.rotateZ(0.0+ (float)(4* max_rotate/6));
}
else if(az==9){
s.rotateZ(0.0+ (float)(3* max_rotate/6));
}
else if(az==10){
s.rotateZ(0.0 + (float)(2* max_rotate/6));
}
else if(az==11){
s.rotateZ(0.0 + (float)(1* max_rotate/6));
}
else if(az==12){
s.rotateZ(0.0 - (float)(0* max_rotate/6));
}
else if(az==13){
s.rotateZ(0.0 - (float)(1* max_rotate/6));
}
else if(az==14){
s.rotateZ(0.0 - (float)(2* max_rotate/6));
}
else if(az==15){
s.rotateZ(0.0 - (float)(3* max_rotate/6));
}
else if(az==16){
s.rotateZ(0.0 - (float)(4* max_rotate/6));
}
else if(az==17){
s.rotateZ(0.0 - (float)(5* max_rotate/6));
}
else if(az==18){
s.rotateZ(0.0 - (float)(6* max_rotate/6));
}
else if(az==19){
s.rotateZ(0.0 - (float)(5* max_rotate/6));
}
else if(az==20){
s.rotateZ(0.0 - (float)(4* max_rotate/6));
}
else if(az==21){
s.rotateZ(0.0 - (float)(3* max_rotate/6));
}
else if(az==22){
s.rotateZ(0.0 - (float)(2* max_rotate/6));
}
else if(az==23){
s.rotateZ(0.0 - (float)(1* max_rotate/6));
}
}
else{
s.rotateX(initX);
s.rotateY(0.0);
}
return s;
}
创新
ArrayList<PShape> hmispheres;
int time,loopx,loopz,sphere_num;
ArrayList<String> fileNames;
String fileName;
int currentFile;
void setup() {
size(1000, 600, P3D);
noStroke();
time = 0;
loopx = 20;
loopz = 24;
sphere_num = 7;
currentFile = 0;
fileName = "hemisphere.obj";
fileName = "loop.obj";
fileNames = new ArrayList<String>();
fileNames.add("loop3.obj");
fileNames.add("loop4.obj");
fileNames.add("loop5.obj");
fileNames.add("loop6.obj");
fileNames.add("loop7.obj");
fileNames.add("loop8.obj");
fileNames.add("loop9.obj");
fileNames.add("loop10.obj");
fileNames.add("loop11.obj");
fileNames.add("loop12.obj");
fileNames.add("loop13.obj");
fileNames.add("loop.obj");
}
void draw() {
background(231, 128, 139);
//translate(width/2,height/2);
set_light();
hmispheres = GetHmispheres(time);
for (int i = 0; i < hmispheres.size(); i++) {
shape(hmispheres.get(i),mouseX,mouseY);
}
time++;
}
void mouseWheel(MouseEvent event) {
float e = event.getCount();
sphere_num += int(e);
}
void mouseClicked() {
currentFile++;
}
void set_light(){
//Light
pointLight(138, 182, 254, 153, -264, -74);
directionalLight(109, 89, 137, 0.2, 0.5, -0.5);
ambientLight(106, 66, 90);
emissive(219, 130, 155);
lightSpecular(122,51,194);
shininess(3);
}
double sigMoid(double value) {
double ey = Math.pow(Math.E, -value);
double result = 1 / (1 + ey);
return result;
}
ArrayList<PShape> GetHmispheres(int time){
ArrayList<PShape>hmispheres = new ArrayList<PShape>();
for(int i=1;i<=sphere_num;i++){
//print(sigMoid((sphere_num-i)/12.0));
PShape s = GetEachHmisphere(i,sigMoid(11.1-i-sphere_num),time);
hmispheres.add(s);
}
return hmispheres;
}
PShape GetEachHmisphere(int num,double max_rotate,int time){
PShape s = loadShape(fileNames.get(currentFile%12));
float initX = -0.4;
s.scale(num/7.0);
if(time>=num){
int ax = (time-num)%loopx;
int az = (time-num)%loopz;
if(ax==0){
s.rotateX(initX + (float)(0* max_rotate/5));
}
else if(ax==1){
s.rotateX(initX + (float)(1* max_rotate/5));
}
else if(ax==2){
s.rotateX(initX+ (float)(2* max_rotate/5));
}
else if(ax==3){
s.rotateX(initX+ (float)(3* max_rotate/5));
}
else if(ax==4){
s.rotateX(initX+ (float)(4* max_rotate/5));
}
else if(ax==5){
s.rotateX(initX+ (float)(5* max_rotate/5));
}
else if(ax==6){
s.rotateX(initX+ (float)(4* max_rotate/5));
}
else if(ax==7){
s.rotateX(initX+ (float)(3* max_rotate/5));
}
else if(ax==8){
s.rotateX(initX+ (float)(2* max_rotate/5));
}
else if(ax==9){
s.rotateX(initX+ (float)(1* max_rotate/5));
}
else if(ax==10){
s.rotateX(initX - (float)(0* max_rotate/5));
}
else if(ax==11){
s.rotateX(initX - (float)(1* max_rotate/5));
}
else if(ax==12){
s.rotateX(initX - (float)(2* max_rotate/5));
}
else if(ax==13){
s.rotateX(initX - (float)(3* max_rotate/5));
}
else if(ax==14){
s.rotateX(initX - (float)(4* max_rotate/5));
}
else if(ax==15){
s.rotateX(initX - (float)(5* max_rotate/5));
}
else if(ax==16){
s.rotateX(initX - (float)(4* max_rotate/5));
}
else if(ax==17){
s.rotateX(initX - (float)(3* max_rotate/5));
}
else if(ax==18){
s.rotateX(initX - (float)(2* max_rotate/5));
}
else if(ax==19){
s.rotateX(initX - (float)(1* max_rotate/5));
}
if(az==0){
s.rotateZ(0.0 + (float)(0* max_rotate/6));
}
else if(az==1){
s.rotateZ(0.0+ (float)(1* max_rotate/6));
}
else if(az==2){
s.rotateZ(0.0+ (float)(2* max_rotate/6));
}
else if(az==3){
s.rotateZ(0.0+ (float)(3* max_rotate/6));
}
else if(az==4){
s.rotateZ(0.0+ (float)(4* max_rotate/6));
}
else if(az==5){
s.rotateZ(0.0+ (float)(5* max_rotate/6));
}
else if(az==6){
s.rotateZ(0.0+ (float)(6* max_rotate/6));
}
else if(az==7){
s.rotateZ(0.0+ (float)(5* max_rotate/6));
}
else if(az==8){
s.rotateZ(0.0+ (float)(4* max_rotate/6));
}
else if(az==9){
s.rotateZ(0.0+ (float)(3* max_rotate/6));
}
else if(az==10){
s.rotateZ(0.0 + (float)(2* max_rotate/6));
}
else if(az==11){
s.rotateZ(0.0 + (float)(1* max_rotate/6));
}
else if(az==12){
s.rotateZ(0.0 - (float)(0* max_rotate/6));
}
else if(az==13){
s.rotateZ(0.0 - (float)(1* max_rotate/6));
}
else if(az==14){
s.rotateZ(0.0 - (float)(2* max_rotate/6));
}
else if(az==15){
s.rotateZ(0.0 - (float)(3* max_rotate/6));
}
else if(az==16){
s.rotateZ(0.0 - (float)(4* max_rotate/6));
}
else if(az==17){
s.rotateZ(0.0 - (float)(5* max_rotate/6));
}
else if(az==18){
s.rotateZ(0.0 - (float)(6* max_rotate/6));
}
else if(az==19){
s.rotateZ(0.0 - (float)(5* max_rotate/6));
}
else if(az==20){
s.rotateZ(0.0 - (float)(4* max_rotate/6));
}
else if(az==21){
s.rotateZ(0.0 - (float)(3* max_rotate/6));
}
else if(az==22){
s.rotateZ(0.0 - (float)(2* max_rotate/6));
}
else if(az==23){
s.rotateZ(0.0 - (float)(1* max_rotate/6));
}
}
else{
s.rotateX(initX);
s.rotateY(0.0);
}
return s;
}