查看原文
其他

用莫比乌斯带巧解内接矩形问题:拓扑学的妙用

2016-11-15 Wenzy InsLab

推荐一个昨天看到的视频。深入浅出,相当精彩。即使你已经阔别学校多年,也能从中领略数学特有的美感。

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

视频作者:3Blue1Brown  译制作者:昨梦电羊

整个视频试图阐述拓扑学能解决什么实际问题,并从证明一个命题开始 - 闭合环路中是否能找到四个点组成一个长方形。


贯穿整个视频,其实都在不断类比。最终为了寻找一个更合理,更直观的模型来解答这一问题。先从闭合环路开始,对应到二维平面。再把二维平面扭曲成环面(甜甜圈),最后到莫比乌斯环。

当然,未必所有人都能完全读懂每个细节。但其中有一步,是将闭合环路转成曲面。这个部分比较通俗,用简单而巧妙的方法就可以将平面曲线转化成立体图形,很值得用代码模拟一番。

下面将使用 Openframeworks 实现这一过程。

最终效果

  • 绘制到生成


  • 立体图形(显示模式1)


  • 立体图形(显示模式3)


实现思路:

若不清楚如何确定曲面坐标,可以再回顾视频。


代码实现思路:

1.记录轨迹,绘制曲线

2.确定细分精度。计算闭合环路坐标点,两两组合的所有情况。根据中点与距离,推出曲面的空间坐标

3.提取曲面坐标,进行绘制与渲染

源代码:

— ofApp.h 内

class ofApp : public ofBaseApp{    public:        void setup();        void update();        void draw();        ...            ofEasyCam cam;        ofPolyline poly; // 记录绘制轨迹        int displayMode; // 显示模式        bool startDrawing; // 是否开始绘制        int divideNum;  // 曲线细分数量        vector<ofPoint> bodyPos;  // 记录曲面上的关键点 };

— ofApp.cpp 内

void ofApp::setup(){    // 数量越多,精度越高    divideNum = 80;    // 默认线框模式    displayMode = 0;    // 进入开始绘制    startDrawing = true; } void ofApp::update(){    if(!startDrawing){       // divideNum = mouseX/(float)ofGetWidth() * 80;    } } void ofApp::draw(){    ofBackground(0);    cam.begin();    // 闭合图形,等价于 poly.close();    poly.setClosed(true);    // 绘制原始路径    ofNoFill();    ofSetColor(255,200);    ofSetLineWidth(3);    poly.draw();    ofSetLineWidth(1);    if(!startDrawing){        bodyPos.clear();        for(int i = 0;i < divideNum;i++){            if(displayMode == 1){                ofNoFill();                ofSetColor(255,200);                ofEnableBlendMode(OF_BLENDMODE_ALPHA);            }            if(displayMode == 2){                ofNoFill();                ofSetColor(ofColor::fromHsb(i / (float)divideNum * 255,255,255));                ofEnableBlendMode(OF_BLENDMODE_ALPHA);            }            if(displayMode == 3){                ofEnableBlendMode(OF_BLENDMODE_ADD);                ofFill();                ofSetColor(ofColor::fromHsb((ofGetFrameNum() + i * 3) % 255,255,255),20);            }            ofBeginShape();            for(int j = 0;j < divideNum;j++){                float ratio1 = i/(float)divideNum;                float ratio2 = j/(float)divideNum;                ofPoint newPos,startPos,middlePos;                // 根据百分比获取曲线路径上的关键点                startPos = poly.getPointAtPercent(ratio2);                newPos = poly.getPointAtPercent(ratio1);                middlePos = (startPos + newPos)/2;                // 计算两点间长度                float length = startPos.distance(newPos)/2;                // 将长度设为中点的高度                middlePos.z = length;                bodyPos.push_back(middlePos);                ofVertex(middlePos);            }            ofEndShape(TRUE);        }        ofFill();        ofSetSphereResolution(5);        ofSetColor(255,150);        for(int i = 0;i < bodyPos.size();i++){            ofDrawSphere(bodyPos[i], 2);        }    }    cam.end();    if(startDrawing && ofGetFrameNum() > 1){        // 绘制时,鼠标不影响镜头角度        cam.disableMouseInput();    }else{        cam.enableMouseInput();    } } void ofApp::keyReleased(int key){    if(key == 'c'){        // 按 c 键清除        poly.clear();    }    if(key == ' '){        if(!startDrawing){            startDrawing = true;            // 恢复镜头默认视角            cam.setOrientation(ofPoint(0,0,0));            cam.setPosition(ofPoint(0,0,600));        }else{            startDrawing = false;        }    }    if(key == '1'){        displayMode = 1;    }    if(key == '2'){        displayMode = 2;    }    if(key == '3'){        displayMode = 3;    } } void ofApp::mouseDragged(int x, int y, int button){    if(startDrawing){        poly.curveTo(x - ofGetWidth()/2,-(y - ofGetHeight()/2));    } }

浅析:

  • 使用 ofPolyline 来保存坐标点,是因为其中有一个成员函数 getPointAtPercent,可以很方便地根据百分比来获取坐标

  • 为了让整个图形比例更美观,做了些修改。曲面的高度仅为两点距离的一半

  • 通过开启 update 中的语句,可以通过移动鼠标实时改变细分精度


  • 可以尝试用 ofNoise 函数动态生成基础曲线,相应的立体图形也会实时变化


源码与可执行程序下载(mac):

( https://pan.baidu.com/s/1nv4jeHj )

使用说明:

  • 点击鼠标开始绘制

  • ‘c’ 键清除轨迹

  • 空格键切换绘制模式与 3D 模式

  • 在 3D 模式下拖拽鼠标可缩放或旋转视角

  • 数字键 1,2,3 切换显示模式





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

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