其他

[ 源码分享 ] Test317-用CrankSlider优雅地画弧线

2018-01-11 Wenzy InsLab

今天在 twitter 上闲逛,刷到了 kynd 早期做的一个有关 crank slide 的 Gif,顿时被这个结构迷住了。于是尝试用 C++ 实现一下。这里分享下源码。

(Kynd 的 Gif 原图)

要实现这一效果需要哪些基础知识?只需要了解简单的三角函数(用代码画画-详解三角函数)与基础的向量知识即可。

观察可以发现。有两个点在做圆周运动(这一部分可使用三角函数)。其中一条直线连接了两个点,并且长度固定。直线的左端点始终连接着大圆上的点,而小圆上的点是用作确定朝向的,使用向量知识,根据定点,方向,长度,就可以计算出直线右端点的位置。最后再把直线右端点的轨迹记录,并绘制出来就能还原以上效果。

梳理完思路,就可以开始写代码。但简单的 copy 没有意思。我希望在实现基础效果的同时,延伸一些组合变化。例如将大量的 “Crank Slide” 组装,会形成怎样的轨迹?

下面是一些试验效果

Openframeworks 源码

以上效果都可以通过下述代码实现。需要创建两个 class。一个用于绘制圆,一个用于绘制直线


WenzyCircle 类

class WenzyCircle{ public:    float angle;    float speed;    float basicR;    ofVec2f centerPos;    ofVec2f pos;    WenzyCircle(){    }    WenzyCircle(ofVec2f centerPos_,float basicR_,float startAngle_,float speed_){        centerPos = centerPos_;        angle = startAngle_;        basicR = basicR_;        speed = speed_;    }    void update(){        angle += speed;        pos.x = centerPos.x + cos(angle) * basicR;        pos.y = centerPos.y + sin(angle) * basicR;    }    void drawBackCircle(){        // 底圆        ofNoFill();        ofDrawCircle(centerPos,basicR);    }    void drawFrontCircle(float r){        ofFill();        ofDrawCircle(pos,r);    } };

WenzyConnectLine 类

#include "WenzyCircle.h" class WenzyConnectLine{ public:    float lineL; // 长度    vector<ofVec2f> posList; // 记录轨迹顶点    ofPolyline myPath;    ofVec2f startPos,pathPos;    WenzyConnectLine(){    }    WenzyConnectLine(float lineL_){        lineL = lineL_;    }    void update(WenzyCircle &circleA,WenzyCircle &circleB){        ofVec2f dir;        dir = circleB.pos - circleA.pos;        dir.normalize();        startPos = circleA.pos;        pathPos = circleA.pos + dir * lineL;        if(posList.size() < 700){            posList.push_back(pathPos);        }else{            posList.erase(posList.begin());            posList.push_back(pathPos);        }    }    void drawLine(){        ofDrawLine(startPos,pathPos);    }    void drawPath(){        myPath.clear();        for(int i = 0;i < posList.size();i++){            myPath.curveTo(posList[i]);        }        myPath.draw();    }    void drawPathCircle(float r){        ofFill();        ofDrawCircle(pathPos,r);    } };

主程序

— ofApp.h 内

#include "WenzyCircle.h" #include "WenzyConnectLine.h" ... ... WenzyCircle circleA; vector<WenzyCircle> sideCircles; vector<WenzyConnectLine> connectlines; ofEasyCam cam; bool startMoving; ofColor pathColor,circleColor;

— ofApp.cpp 内

void ofApp::setup(){   // 参数设置    circleA = WenzyCircle(ofVec2f(0,0), 200, 0, 0.015);    int circleNum = 12;    for(int i = 0;i < circleNum;i++){        float d = 200;        float angle = i/(float)circleNum * 2 * PI;        float x = cos(angle) * d;        float y = sin(angle) * d;        sideCircles.push_back(WenzyCircle(ofVec2f(x,y),50,0,0.015 * 5));        connectlines.push_back(WenzyConnectLine(300));    }    // 更新确定端点位置    circleA.update();    for(int i = 0;i < sideCircles.size();i++){        sideCircles[i].update();        connectlines[i].update(circleA, sideCircles[i]);    }    startMoving = false;    pathColor.set(240,203,112);    circleColor.set(255,143); } void ofApp::update(){    if(startMoving){        circleA.update();        for(int i = 0;i < sideCircles.size();i++){            sideCircles[i].update();            connectlines[i].update(circleA, sideCircles[i]);        }    } } void ofApp::draw(){    ofSetCircleResolution(50);    ofBackground(0);    // 绘制背景网格线    int girdW = 25;    ofSetColor(255,30);    for(int x = 0;x < ofGetWidth();x += girdW){        ofDrawLine(x,0,x,ofGetHeight());    }    for(int y = 0;y < ofGetHeight();y += girdW){        ofDrawLine(0,y,ofGetWidth(),y);    }    cam.begin();    ofSetColor(circleColor);    ofSetLineWidth(2);    circleA.drawBackCircle();    ofSetColor(255);    circleA.drawFrontCircle(5);    for(int i = 0;i < sideCircles.size();i++){        ofEnableBlendMode(OF_BLENDMODE_ALPHA);        ofSetColor(circleColor);        ofSetLineWidth(2);        sideCircles[i].drawBackCircle();        ofSetColor(255,230);        sideCircles[i].drawFrontCircle(5);        ofEnableBlendMode(OF_BLENDMODE_ADD);        ofSetLineWidth(4);        ofSetColor(255,ofRandom(200));        connectlines[i].drawLine();        ofSetLineWidth(2);        ofSetColor(pathColor,150);        connectlines[i].drawPath();        ofSetColor(pathColor);        connectlines[i].drawPathCircle(5);    }        cam.end(); } void ofApp::keyPressed(int key){    if(key == 'r'){        startMoving = true;    } }

代码浅析:

  • 运行程序后,按 r 键开始绘制

  • 整个代码非常短,不足 300 行。灵活组合可以有很强的延展性。通过修改 setup 函数内的“参数设置”代码,可以控制小圆的数量,以及大圆小圆的半径,位置以及顶点的起始角度,速度。不同参数产生不同效果

一点延展

上例中采用的链接方式是一个“大圆”带动多个“小圆”。也可以尝试另一种“联动”的组合。例如大圆带小圆 A,生成的端点再去带动小圆 B,如此循环,就像交接接力棒。

(另一种链接逻辑)

  for(int i = 0;i < sideCircles.size();i++){        sideCircles[i].update();        if(i == 0){            connectlines[i].update(circleA, sideCircles[i]);        }else{            connectlines[i].update(connectlines[i - 1].pathPos, sideCircles[i]);        }   }

最后再加入一点“火光”去强化绘制的轨迹,用力去影响火花的速度,可以更好地体现端点变化的韵律感。


一些视频测试合集


https://v.qq.com/txp/iframe/player.html?vid=n0532y0k0ir&width=500&height=375&auto=0

End

Crank-slider mechanism 中文似乎称作“曲柄滑块机构”。 它是一种机械结构,只要有材料,完全可以在现实中制造一个互动装置。下图是在 twitter 中找到的一个动图,非常直观,来源作者 twitter@mechanisms。

如果你从旋转轴的角度望去,剖面的效果其实就有点接近下图了

区别是“红柱”的端点部分没有圆周运动,而且“蓝柱”与“金柱”的衔接处并不是直角。但这种非直角的处理,让整个联动在前后方向多了一丝变化,结果显然更有趣


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

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