其他

算法动画-函数曲线的应用

2018-01-09 Wenzy InsLab

接上篇,算法动画 - 理解函数曲线

作一点应用上的延伸。

函数曲线非常实用,但如果在程序中每次使用都要考虑各种映射关系,显然有点繁琐。更好的做法是把一些常用的函数曲线用一个类把它封装起来。

下面分享一段自己创作时常用到的类(代码基于C++,框架 openframeworks)

class WenzyAni{ public: float ratio; // 内部表示完成进度 (范围一般为 0 到 1) float startVal,endVal; // 开始的数值,结束的数值 float val; // 当前的数值 float time; // 完成整个动画所需的时间 int aniMode; // 决定数值的变化曲线类型 bool startMoving; // 是否开始运动 float startTick; // 开始的时刻记录 WenzyAni(){ } WenzyAni(float time_,float startVal_,float endVal_,int mode_ = 0){    time = time_;    startVal = startVal_;    endVal = endVal_;    aniMode = mode_;    startMoving = false;    val = startVal_; } void update(){    if(startMoving){        ratio = MIN(time,ofGetElapsedTimef() - startTick)/time;        if(aniMode == 0){            // 匀速平滑过渡            val = ofMap(ratio,0,1,startVal,endVal);        }else if(aniMode == 1){            // 先加速后减速(经过 sin 函数处理)            float ratio2 = ofMap(sin(ofMap(ratio,0,1,-PI/2,PI/2)),-1,1,0,1);            val = ofMap(ratio2,0,1,startVal,endVal);        }else if(aniMode == 2){            // 持续减速(指数衰减)            val = ofMap(pow(2,-10 * ratio),1,0,startVal,endVal);        }else if(aniMode == 3){            // 弹簧效果            val = ofMap(cos(ratio * 20) * pow(2,-10 * ratio),1,0,startVal,endVal);        }else if(aniMode == 4){            // cos 式往复            float n = 2; // n 表示往复次数            val = ofMap(cos(ratio * n * 2 * PI + PI),1,-1,startVal,endVal);        }    } } void start(){    startMoving = true;    startTick = ofGetElapsedTimef(); } };

应用范例 01

ofApp.h 内 —-

#include “WenzyAni.h” ... WenzyAni ani; ofEasyCam cam;

ofApp.cpp 内 —-

void ofApp::setup(){    ofSetWindowShape(1000,500);    ofBackground(3,27,93);    ani = WenzyAni(1, -300, 300,3); } void ofApp::update(){    ani.update(); } void ofApp::draw(){    cam.begin();    ofSetColor(233,60,37);    ofDrawBox(ani.val,0,0,100);    cam.end(); } void ofApp::keyPressed(int key){    if(key == '1'){        ani.start();    }    if(key == '2'){        ani = WenzyAni(1, -300, 300,3);        ani.start();    }    if(key == '3'){        ani = WenzyAni(1, 300, -300,3);        ani.start();    }    if(key == '4'){         ani = WenzyAni(1, -300, 300,0);        ani.start();     }    if(key == '5'){        ani = WenzyAni(1, -300, 300,1);        ani.start();    }    if(key == '6'){        ani = WenzyAni(1, -300, 300,2);        ani.start();    } }


代码浅析:

  • start 函数为触发动画的函数。运行程序后按数字键 1 即开始执行,可以看到正方体将从左运动到右,并且带一点弹性动画。这是因为 setup 中有一句

     ani = WenzyAni(1, -300, 300,3)

    它将 ani 对象初始化时。第一个参数表示整个动画的运行时间,第二个参数表示初始的数值,第三个参数表示结束时的数值。第四个参数表示选择应用的曲线类型

  • draw 函数通过 ani.val 来表示正方体的横坐标

  • 每按下一次数字键 1 执行 start 函数时,正方体的运动都会从左变化到右。这是因为 startVal 与 endVal 的值在初始化时已经确定。如果希望正方形实现从右到左的运动,则需要重新初始化。按下数字键 3 就能实现这一效果。而来回按数字键 2,3 则能实现往复运动。

  • 按数字键 4,5,6 可以切换不同的曲线

(数字键 4)


(数字键 5)

(数字键 6)

应用范例 02

下面再附上上篇文章中展示的几个 gif 源码,还是使用同样的类

ofApp.h 内 —-

#include “WenzyAni.h” ... vector<WenzyAni> ani; int showMode;

ofApp.cpp 内 —-

void ofApp::setup(){    ofSetWindowShape(1920,1080);    ofBackground(239,234,228);    for(int i = 0;i < 5;i++){        ani.push_back(WenzyAni(2,0,1,i));    }    showMode = 0; } void ofApp::update(){    for(int i = 0;i < ani.size();i++){        ani[i].update();    } } void ofApp::draw(){    ofColor myColor(50,120,133);    ofSetColor(myColor);    ofSetCircleResolution(50);    if(showMode == 0){        int num = ani.size();        float spaceRatio = 0.8; // 计算间隙占方块的大小比        float rectW = ofGetHeight() / (num + (num + 1) * spaceRatio);        float space = rectW * spaceRatio;        int interval = (ofGetHeight() - space) / num;        ofSetLineWidth(5);        float startPos = ofGetWidth() * 0.1;        float endPos = ofGetWidth() - startPos;        for(int i = 0;i < num;i++){            ofSetColor(myColor);            float x = ofMap(ani[i].val,0,1,startPos,endPos);            float y = space/2 + (i + 0.5)* interval;            ofDrawCircle(x,y,25);            ofDrawLine(startPos,y,endPos,y);        }    }else if(showMode == 1){        int num = ani.size();        float spaceRatio = 0.4; // 计算间隙占方块的大小比        float rectW = ofGetWidth() / (num + (num + 1) * spaceRatio);        float space = rectW * spaceRatio;        float rectY = ofGetHeight() * 0.5;        int interval = (ofGetWidth() - space) / num;        for(int i = 0;i < num;i++){            ofPushMatrix();            float x = space/2 + (i + 0.5) * interval;            ofTranslate(x, ofGetHeight()/2);            ofSetColor(myColor,ofMap(ani[i].val,0,1,255,0));            ofDrawCircle(0,0,rectW/2);            ofPopMatrix();        }    }else if(showMode == 2){        int num = ani.size();        float spaceRatio = 0.4; // 计算间隙占方块的大小比        float rectW = ofGetWidth() / (num + (num + 1) * spaceRatio);        float space = rectW * spaceRatio;        float rectY = ofGetHeight() * 0.5;        int interval = (ofGetWidth() - space) / num;        ofSetLineWidth(4);        for(int i = 0;i < num;i++){            ofSetColor(myColor);            ofPushMatrix();            float x = space/2 + (i + 0.5) * interval;            ofTranslate(x, ofGetHeight()/2);            ofRotate(ofMap(ani[i].val,0,1,0,180));            ofDrawLine(0,rectW/2,0,-rectW/2);            ofDrawCircle(0,rectW/2,30);            ofDrawCircle(0,-rectW/2,30);            ofPopMatrix();        }    }else if(showMode == 3){        int num = ani.size();        float spaceRatio = 0.4; // 计算间隙占方块的大小比        float rectW = ofGetWidth() / (num + (num + 1) * spaceRatio);        float space = rectW * spaceRatio;        float rectY = ofGetHeight() * 0.5;        int interval = (ofGetWidth() - space) / num;        for(int i = 0;i < num;i++){            ofPushMatrix();            float x = space/2 + (i + 0.5) * interval;            ofTranslate(x, ofGetHeight()/2);            ofSetColor(myColor);            float w = ofMap(ani[i].val,0,1,0,rectW);            ofDrawCircle(0,0,w/2);            ofPopMatrix();        }    }    ofSetColor(0);    ofDrawBitmapString("ShowMode:" + ofToString(showMode),50,50); } void ofApp::keyPressed(int key){    if(key == 'r'){        for(int i = 0;i < ani.size();i++){            ani[i].start();        }    }    if(key == OF_KEY_DOWN){        showMode--;        showMode = MAX(0,showMode);    }    if(key == OF_KEY_UP){        showMode++;        showMode = MIN(3,showMode);    } }

运行效果:

代码浅析:

  • 按 r 键开始动画,按方向键上下切换不用的模式

  • 模块中只列举了少数函数曲线,根据个人需要可以拓展补充

End

个人日常中还是倾向于通过自定义函数来使用曲线。如果你不想过于深究各类函数曲线的性质,只希望实现具体的效果。也有办法可以直接采用别人定制好的各类运动曲线。最后再推荐两个插件

OF 插件 - ofxAnimatable

下载地址:https://github.com/armadillu/ofxAnimatable

附带的范例:

Processing 插件 - Ani

在 IDE 的 Libraries 菜单中输入 “animation”

又或是通过以下链接手动下载:

http://www.looksgood.de/libraries/Ani/


:)




您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存