cvl-robot's diary

研究ノート メモメモ https://github.com/dotchang/

ofxManipulatorとMSAInterpolatorを組み合わせて、3次元スプラインをお洒落に操作するインターフェースのExample

おとといぐらいから紹介している記事を組み合わせて、ofxManipulatorとMSAInterpolatorがいかに便利かを示すExampleを作ります。

f:id:cvl-robot:20151209204425g:plain

プロジェクトに必要なaddonは、
ofxAssimpModelLoader,
ofxManipulator(改造版)
ofxMSAInterpolator(改造版)
です。
2つのExampleをくっつけただけですが、とても簡単に良い感じの3次元インターフェースが作れます。
この例では,3次元スプラインの制御点をマウスでぐりぐり視点を切り替えながら編集できる機能を作っています。
操作は、

  • マウスの右ボタンで、編集かカメラ視点移動かを切り替え。
  • マウスの左ボタンダブルクリックで、スプラインの制御点を追加。
  • マウスのスクロールで、編集の移動、回転、拡大縮小の切り替え。ただし、この例では移動のみ使用。
  • キーボード1~5のキーで補間の計算方法を変更

main.cpp

#include "ofApp.h"

int main()
{
  ofGLFWWindowSettings settings;
  settings.setGLVersion(2,1);
  auto window = ofCreateWindow(settings);
  auto app = make_shared<ofApp>();
  ofRunApp(window, app);

  return ofRunMainLoop();
}

ofApp.h

#pragma once

#include "ofMain.h"

#include "ofxAssimpModelLoader.h"
#include "ofxManipulator.h"
#include "MSAInterpolator.h"

class ofApp : public ofBaseApp
{
  public:
    void setup();
    void draw();
    void keyPressed(ofKeyEventArgs&);
    void mousePressed(ofMouseEventArgs&);

    ofLight m_light;
    ofImage m_matcapImage;
    ofShader m_matcapShader;
    ofMatrix4x4 m_modelMatrix;
    ofxAssimpModelLoader m_model;
	ofEasyCam m_cam;
	//ofxManipulator m_gizmo;

	void mouseReleased(ofMouseEventArgs & mouse);

	// double clicke
	unsigned long lastTap;
	void mouseDoubleClicked(ofMouseEventArgs &mouse);
	ofEvent<ofMouseEventArgs&> doubleClickEvent;

	// ofxManipulator
	vector<shared_ptr<ofxManipulator> > gizmo;
	typedef std::shared_ptr<ofxManipulator> M_Ptr;
	M_Ptr createManipulator() {	return M_Ptr(new ofxManipulator()); };
	void onTranslateChange(ofxManipulatorEventArgs& m);

	// MSAInterpolator
	msa::Interpolator2D	spline2D;
	msa::Interpolator3D	spline3D;

	msa::InterpolationType	interpolationType;
	bool			useLength;

	float			currentRot;
	bool			rotateView;

	float			spherePosPerc;			// 0....1 percentage of how far along the 3D path the sphere is
	float			sphereSpeed;
};

ofApp.cpp

#include "ofApp.h"

void ofApp::setup()
{
  ofSetWindowPosition(
    ( ofGetScreenWidth() -  ofGetWidth()) / 2.0f,
    (ofGetScreenHeight() - ofGetHeight()) / 2.0f
  );

  ofDisableArbTex();
  ofEnableLighting();
  ofSetBackgroundColor(28, 28, 38);

 // m_cam.movespeed = 3.0f;
  m_cam.setNearClip(5.0f);
  m_cam.setFarClip(10000);
  m_cam.move(120, 120, 250);
  m_cam.lookAt(ofVec3f(0, 120, 0));
  m_cam.setDistance(25.0f);

  m_light.setDirectional();
  m_light.rotate(180, 0, 1, 0);

  m_modelMatrix.scale(1, 1, 1);
  m_modelMatrix.rotate( 90, 1, 0, 0);
  m_modelMatrix.rotate(165, 0, 1, 0);
  m_modelMatrix.translate(0, 140/30, 0);

  m_matcapImage.load("MatCap.png");
  m_matcapShader.load("MatCap.vs", "MatCap.fs");

  m_model.loadModel("Edward_Joseph_Snowden.obj");

  // double click
  lastTap = 1000;
  ofAddListener(doubleClickEvent, this, &ofApp::mouseDoubleClicked);

  // MSAInterpolator
  interpolationType = msa::kInterpolationCubic;
  useLength = false;
  spherePosPerc = 0;
  sphereSpeed = 0.005f;
}

void ofApp::draw()
{
  m_cam.begin();

    ofDisableLighting();
      ofDrawGrid(50/30, 10, false, true, true, false);
    ofEnableLighting();

	// MSAInterpolator
	int numSteps = floor(mouseX / (float)ofGetWidth() * 1000);
	if (numSteps<10) numSteps = 10;

	ofDisableLighting();
	// draw raw spline3D
	glColor3f(1, 1, 1);
	drawInterpolatorRaw(spline3D);

	// draw interpolated spline3D
	ofColor c = ofColor::yellow;
	glColor3f(c.r, c.g, c.b);
	drawInterpolatorSmooth(spline3D, numSteps);

	// draw sphere moving along 3D path
	ofVec3f spherePos = spline3D.sampleAt(spherePosPerc);
	glPushMatrix();
	glColor3f(1, 1, 0);
	glTranslatef(spherePos.x, spherePos.y, spherePos.z);

	m_light.enable();

	m_matcapShader.begin();
	m_matcapShader.setUniformTexture("litsphereTexture", m_matcapImage, 1);
	ofPushMatrix();
	ofMultMatrix(m_modelMatrix); // *m_gizmo.getMatrix());
	m_model.getMesh("").drawFaces();
	ofPopMatrix();
	m_matcapShader.end();

	m_light.disable();

	glPopMatrix();
	
	// move sphere
	// if it reaches the edges, bounce back
	spherePosPerc += sphereSpeed;
	if (spherePosPerc > 1) {
		spherePosPerc = 1;
		sphereSpeed *= -1;
	}
	else if (spherePosPerc < 0) {
		spherePosPerc = 0;
		sphereSpeed *= -1;
	}

	// ofxManipulator
	ofDisableDepthTest();
	ofDisableLighting();
	for (int i = 0; i < gizmo.size(); i++) {
		shared_ptr<ofxManipulator> m_ptr = gizmo[i];
		m_ptr->draw(m_cam);
	}
	ofEnableLighting();

  m_cam.end();

  ofPushStyle();
  ofDisableDepthTest();
  ofSetColor(ofColor::white);
  string uiLin = interpolationType == msa::kInterpolationLinear ? "* " : "  ";
  string uiCub = interpolationType == msa::kInterpolationCubic ? "* " : "  ";
  string uiCos = interpolationType == msa::kInterpolationCosine ? "* " : "  ";
  string uiCat = interpolationType == msa::kInterpolationCatmullRom ? "* " : "  ";
  string uiHer = interpolationType == msa::kInterpolationHermite ? "* " : "  ";
  string uiDist = spline3D.getUseLength() ? "* " : "  ";
  ofDrawBitmapString(ofToString(ofGetFrameRate(), 2) + "\n"
	  + "numSteps (resampling resolution - mouseX to change): " + ofToString(numSteps) + "\n"
	  + "mouse click around the area to draw a 3D spline (length = " + ofToString(spline3D.getLength()) + "\n"
	  + "\n"
	  + uiLin + "'1' to use linear interpolation\n"
	  + uiCub + "'2' to use cubic (catmull rom) interpolation\n"
	  + uiCos + "'3' to use cosine interpolation\n"
	  + uiCat + "'4' to use catmull rom interpolation\n"
	  + uiHer + "'5' to use hermite interpolation with random tension and bias\n"
	  + "\n"
	  + uiDist + "'d' to toggle 'using Length in interpolation'\n"
	  + "\n"
	  + "'c' to clear 3D spline\n"
	  , 20, 20);
  ofEnableDepthTest();
  ofPopStyle();
}

void ofApp::keyPressed(ofKeyEventArgs &key)
{
  switch (key.keycode) {
	  case '1':
		  interpolationType = msa::kInterpolationLinear;
		  spline3D.setInterpolation(interpolationType);
		  spline2D.setInterpolation(interpolationType);
		  break;
	  case '2':
		  interpolationType = msa::kInterpolationCubic;
		  spline3D.setInterpolation(interpolationType);
		  spline2D.setInterpolation(interpolationType);
		  break;
	  case '3':
		  interpolationType = msa::kInterpolationCosine;
		  spline3D.setInterpolation(interpolationType);
		  spline2D.setInterpolation(interpolationType);
		  break;
	  case '4':
		  interpolationType = msa::kInterpolationCatmullRom;
		  spline3D.setInterpolation(interpolationType);
		  spline2D.setInterpolation(interpolationType);
		  break;
	  case '5':
		  interpolationType = msa::kInterpolationHermite;
		  spline3D.setInterpolation(interpolationType);
		  spline2D.setInterpolation(interpolationType);
		  spline3D.setTension(ofRandom(-1, 1));
		  spline3D.setBias(ofRandom(-1, 1));
		  spline2D.setTension(spline3D.getTension());
		  spline2D.setTension(spline3D.getBias());
		  cout << "tension = " << spline3D.getTension() << ", bias = " << spline3D.getBias() << endl;
		  break;
	  case 'd':
		  useLength ^= true;
		  spline3D.setUseLength(useLength);
		  spline2D.setUseLength(useLength);
		  break;

	  case 'c':
	  case 'C':
		  spline3D.clear();
		  break;

	  case 'r':
	  case 'R':
		  rotateView ^= true;
		  break;
	  }
}

void ofApp::mousePressed(ofMouseEventArgs &mouse)
{
  switch (mouse.button)
  {
	case (OF_MOUSE_BUTTON_RIGHT) : {
		if (m_cam.getMouseInputEnabled()) m_cam.disableMouseInput();
		else m_cam.enableMouseInput();
	}
  }
}

void ofApp::mouseReleased(ofMouseEventArgs & mouse) {
	// for double clicing
	static const unsigned long doubleclickTime = 400;
	unsigned long curTap = ofGetElapsedTimeMillis();
	if (lastTap != 0 && curTap - lastTap < doubleclickTime) {
		ofNotifyEvent(doubleClickEvent, mouse);
		return;
	}
	lastTap = curTap;
}

void ofApp::mouseDoubleClicked(ofMouseEventArgs & mouse) {
	if (mouse.button == OF_MOUSE_BUTTON_1) {
		// MSAInterpolator
		// when you add a new point, the length of the spline increases
		// so the sphere will jump to a new position because it's position is based on percentage
		// so a little bit of maths to calculate what the new position percentage should be to stay at the same physical location
		// (not precise, but close enough)
		int numPoints = spline3D.size();
		spherePosPerc = spherePosPerc * numPoints / (numPoints + 1);

		ofVec3f pt(mouse.x, mouse.y, 0);
		ofVec3f wrpt = m_cam.screenToWorld(pt);
		spline3D.push_back(wrpt);

		// ofxManipulator
		gizmo.push_back(createManipulator());
		int id = spline3D.size() - 1;
		shared_ptr<ofxManipulator> m_ptr = gizmo.back();
		m_ptr->setID(spline3D.size() - 1);
		m_ptr->setTranslation(wrpt);
		m_ptr->setAutoFocus(true);

		ofAddListener(m_ptr->onTranslateChange, this, &ofApp::onTranslateChange);
	}
}

void ofApp::onTranslateChange(ofxManipulatorEventArgs& m)
{
	ofVec3f& vec = spline3D.at(m.id);
	vec = m.translation;
}

TODO

  • グループ選択の有効化: CTRLキーを押しながら選択したターゲットはまとめて操作できるようにしたい。
  • 排他的選択の有効化: 一番近いターゲットだけにフォーカスを合わせる選択も選べるようにしたい。
  • スケールの補間
  • 姿勢(回転)の補間
  • Splineの制御点の端点だけではなく途中への挿入、消去の編集
  • Splineのファイルへの入出力

今日の漫画

最近面白いご飯漫画がとても多いですね。その中でもこのどんぶり委員長は、食欲に訴えかける力の強いマンガです。