読者です 読者をやめる 読者になる 読者になる

cvl-robot's diary

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

ofxManipulatorを使いやすく改造する.

3次元物体操作にとても便利に使えるopenFrameworksのaddonをもっと便利に使えるように、勝手に改造します.ofxFirstPersonCameraをofEasyCamに置き換えるのは前回紹介していますので、その続き。対象は、OF0.9(正式版以降)です。

前回はこちら。cvl-robot.hateblo.jp

  1. マウスカーソルが近づくと勝手に表示されるオートフォーカス機能
  2. X軸だけ無効とかYZ平面は無効とか、フラグによって操作に有効な軸や平面を指定できるフラグ機能
  3. マウスホイールのスクロールによる、併進、回転、拡大縮小のターゲットの切り替え機能
  4. イベント通知のために、ofxManipulatorEventArgsクラスを追加(2015.12.10)
初版から少し手を加えています。

ofxManipulator.h

#pragma once

#include "ofMain.h"

class ofxManipulatorEventArgs
{
public:
	ofVec3f scale;
	ofQuaternion rotation;
	ofVec3f translation;

	int id;
	float dist;
	float z;
};

class ofxManipulator
{
  public:
    ofxManipulator();
   ~ofxManipulator();

    enum MANIPULATOR_TYPE
    {
      MANIPULATOR_NONE = 0,
      MANIPULATOR_SCALE = 1,
      MANIPULATOR_ROTATION = 2,
      MANIPULATOR_TRANSLATION = 3
    };

    void draw(ofEasyCam cam);

    ofMatrix4x4 getMatrix();

    ofVec3f getScale();
    ofQuaternion getRotation();
    ofVec3f getTranslation();

    void setScale(ofVec3f);
    void setRotation(ofQuaternion);
    void setTranslation(ofVec3f);

    void scale(ofVec3f);
    void rotate(ofQuaternion);
    void translate(ofVec3f);

    void toggleScale();
    void toggleRotation();
    void toggleTranslation();

    float getManipulatorScale();
    void setManipulatorScale(float scale);
    MANIPULATOR_TYPE getManipulatorType();
    void setManipulatorType(MANIPULATOR_TYPE type);
    void setManipulatorColors(ofColor x, ofColor y, ofColor z, ofColor w, ofColor select);

public:
	ofxManipulatorEventArgs m_ea;

	bool m_focus;
	bool m_auto_focus;
	bool m_cameraMouseInputEnabled;
	MANIPULATOR_TYPE m_lastManipulator;
	bool getAutoFocus() { return m_auto_focus; }
	void setAutoFocus(bool on) { m_auto_focus = on; }

	float getID() { return m_ea.id; }
	void setID(float v) { m_ea.id = v; }

	float m_onFocusThreshold;
	float getOnFocusThreshold() { return m_onFocusThreshold; }
	void setOnFocusThreshold(float t) { m_onFocusThreshold = t; }

	float m_offFocusThreshold;
	float getOffFocusThreshold() { return m_offFocusThreshold; }
	void setOffFocusThreshold(float t) { m_offFocusThreshold = t; }
	
	ofEvent<ofxManipulatorEventArgs&> onFocus, outFocus;
	ofEvent<ofxManipulatorEventArgs&> onScaleChange;
	ofEvent<ofxManipulatorEventArgs&> onTranslateChange;
	ofEvent<ofxManipulatorEventArgs&> onRotateChange;

  private:
	ofColor convGrayscale(ofColor rgb, float alpha = 255.0) {
		return ofColor(0.299f * (float)rgb.r + 0.587f * (float)rgb.g + 0.114f * (float)rgb.b, alpha);
	}

  protected:
    void mouseMoved(ofMouseEventArgs&);
    void mousePressed(ofMouseEventArgs&);
    void mouseDragged(ofMouseEventArgs&);
    void mouseReleased(ofMouseEventArgs&);
	void mouseScrolled(ofMouseEventArgs&);

  private:
    enum SCALE_TYPE
    {
      SCALE_NONE,
      SCALE_X,
      SCALE_Y,
      SCALE_Z,
      SCALE_XY,
      SCALE_XZ,
      SCALE_YZ,
      SCALE_XYZ
    };

    enum ROTATION_TYPE
    {
      ROTATION_NONE,
      ROTATION_X,
      ROTATION_Y,
      ROTATION_Z,
      ROTATION_SCREEN,
    };

    enum TRANSLATION_TYPE
    {
      TRANSLATION_NONE,
      TRANSLATION_X,
      TRANSLATION_Y,
      TRANSLATION_Z,
      TRANSLATION_XY,
      TRANSLATION_XZ,
      TRANSLATION_YZ,
      TRANSLATION_XYZ
    };

public:
	struct {
		ofParameter<bool> none = false;
		ofParameter<bool> x = true;
		ofParameter<bool> y = true;
		ofParameter<bool> z = true;
		ofParameter<bool> screen = true;
	} flag_rotation;

	struct {
		ofParameter<bool> none = false;
		ofParameter<bool> x = true;
		ofParameter<bool> y = true;
		ofParameter<bool> z = true;
		ofParameter<bool> xy = true;
		ofParameter<bool> xz = true;
		ofParameter<bool> yz = true;
		ofParameter<bool> xyz = true;
	} flag_scale;

	struct {
		ofParameter<bool> none = false;
		ofParameter<bool> x = true;
		ofParameter<bool> y = true;
		ofParameter<bool> z = true;
		ofParameter<bool> xy = true;
		ofParameter<bool> xz = true;
		ofParameter<bool> yz = true;
		ofParameter<bool> xyz = true;
	} flag_translation;

private:
    float computeScreenFactor();

    void getCurrScale(SCALE_TYPE &type, unsigned int x, unsigned int y);
    void getCurrRotation(ROTATION_TYPE &type, unsigned int x, unsigned int y);
    void getCurrTranslation(TRANSLATION_TYPE &type, unsigned int x, unsigned int y);

    void createRay(float x, float y, ofVec3f &ray_origin, ofVec3f &ray_direction);
    void intersectRay(ofVec4f &plane, ofVec3f &inter_point, ofVec3f position, ofVec3f direction);
    ofVec3f raytrace(ofVec3f ray_origin, ofVec3f ray_direction, ofVec3f normal, ofMatrix4x4 matrix);

    ofVec4f createPlane(ofVec3f p, ofVec3f n);
    bool checkRotationPlane(ofVec3f normal, float factor, ofVec3f ray_origin, ofVec3f ray_direction);

    void drawCircle(ofVec3f origin, ofVec3f vtx, ofVec3f vty, ofColor color);
    void drawAxis(ofVec3f origin, ofVec3f axis, ofVec3f vtx, ofVec3f vty, float fct, float fct2, ofColor color);
    void drawScaleAxis(ofVec3f origin, ofVec3f axis, ofVec3f vtx, ofColor color);
    void drawCamembert(ofVec3f origin, ofVec3f vtx, ofVec3f vty, float ng, ofColor color);
    void drawQuad(ofVec3f origin, float size, bool is_selected, ofVec3f axis_u, ofVec3f axis_v, ofColor color);
    void drawTriangle(ofVec3f origin, float size, bool is_selected, ofVec3f axis_u, ofVec3f axis_v, ofColor color);

    SCALE_TYPE m_currScale;
    SCALE_TYPE m_currScalePredict;

    ROTATION_TYPE m_currRotation;
    ROTATION_TYPE m_currRotationPredict;

    TRANSLATION_TYPE m_currTranslation;
    TRANSLATION_TYPE m_currTranslationPredict;

    MANIPULATOR_TYPE m_currManipulator;

    float m_angleRad;
    float m_screenFactor;
    float m_manipulatorScale;
    float m_lockedCursor_x;
    float m_lockedCursor_y;

    ofVec3f m_lockedPoint;
    ofVec3f m_axis;
    ofVec3f m_rotate_x;
    ofVec3f m_rotate_y;

    //ofVec3f m_scale;         // moved to ofxManipulatorEventArgs
    ofVec3f m_scaleSaved;
    //ofVec3f m_translation;   // moved to ofxManipulatorEventArgs
    ofVec3f m_translationSaved;
    //ofQuaternion m_rotation; // moved to ofxManipulatorEventArgs
    ofQuaternion m_rotationSaved;

    ofColor m_x_color;
    ofColor m_y_color;
    ofColor m_z_color;
    ofColor m_w_color;
    ofColor m_select_color;

    ofVec4f m_plane;

    ofMatrix4x4 m_view;
    ofMatrix4x4 m_viewInverse;
    ofMatrix4x4 m_proj;
};

ofxManipulator.cpp

#include "ofxManipulator.h"

ofxManipulator::ofxManipulator()
:m_currScale              (SCALE_NONE)
,m_currScalePredict       (SCALE_NONE)
,m_currRotation           (ROTATION_NONE)
,m_currRotationPredict    (ROTATION_NONE)
,m_currTranslation        (TRANSLATION_NONE)
,m_currTranslationPredict (TRANSLATION_NONE)
,m_currManipulator        (MANIPULATOR_NONE)
,m_x_color                (0xff, 0x41, 0x36)
,m_y_color                (0x2e, 0xcc, 0x40)
,m_z_color                (0x00, 0x74, 0xd9)
,m_w_color                (0xff, 0x4b, 0xff)
,m_select_color           (0xff, 0xdc, 0x00)
//,m_scale                  (1.0f, 1.0f, 1.0f)
,m_manipulatorScale       (1.0f)
,m_screenFactor           (0.0f)
,m_angleRad               (0.0f)
, m_onFocusThreshold(100.0f) // added
, m_offFocusThreshold(200.0f) // added
, m_focus(false) // added
, m_auto_focus(true) // added
//, m_id(0) // added
, m_lastManipulator(MANIPULATOR_TRANSLATION) // added
{
	m_ea.scale = ofVec3f(1.0f, 1.0f, 1.0f);
	m_ea.id = 0;

  auto &events = ofEvents();
  ofAddListener(events.mouseMoved   , this, &ofxManipulator::mouseMoved   , OF_EVENT_ORDER_BEFORE_APP);
  ofAddListener(events.mousePressed , this, &ofxManipulator::mousePressed , OF_EVENT_ORDER_BEFORE_APP);
  ofAddListener(events.mouseDragged , this, &ofxManipulator::mouseDragged , OF_EVENT_ORDER_BEFORE_APP);
  ofAddListener(events.mouseReleased, this, &ofxManipulator::mouseReleased, OF_EVENT_ORDER_BEFORE_APP);
  ofAddListener(events.mouseScrolled, this, &ofxManipulator::mouseScrolled, OF_EVENT_ORDER_BEFORE_APP); // added
}

ofxManipulator::~ofxManipulator()
{
  auto &events = ofEvents();
  ofRemoveListener(events.mouseMoved   , this, &ofxManipulator::mouseMoved   , OF_EVENT_ORDER_BEFORE_APP);
  ofRemoveListener(events.mousePressed , this, &ofxManipulator::mousePressed , OF_EVENT_ORDER_BEFORE_APP);
  ofRemoveListener(events.mouseDragged , this, &ofxManipulator::mouseDragged , OF_EVENT_ORDER_BEFORE_APP);
  ofRemoveListener(events.mouseReleased, this, &ofxManipulator::mouseReleased, OF_EVENT_ORDER_BEFORE_APP);
  ofRemoveListener(events.mouseScrolled, this, &ofxManipulator::mouseScrolled, OF_EVENT_ORDER_BEFORE_APP); // added
}

////////////////////////////////////////////////////////////////////////////////
// PUBLIC FUNCTIONS
////////////////////////////////////////////////////////////////////////////////

void ofxManipulator::draw(ofEasyCam cam)
{
  ofPushStyle();

  ofFill();
  ofSetLineWidth(1.0f);
  ofDisableDepthTest();

  m_view = cam.getModelViewMatrix();
  m_proj = cam.getProjectionMatrix();
  m_viewInverse = m_view.getInverse();
  m_screenFactor = computeScreenFactor();
  m_cameraMouseInputEnabled = cam.getMouseInputEnabled(); // added

  ofVec3f origin = m_ea.translation;
  float screenf = m_screenFactor;
  float screenf_half = screenf * 0.5f;

  ofMatrix4x4 m;
  m.rotate(m_ea.rotation);
  ofVec3f axe_x = m.getRowAsVec3f(0);
  ofVec3f axe_y = m.getRowAsVec3f(1);
  ofVec3f axe_z = m.getRowAsVec3f(2);

  ofColor unit_color_x = m_x_color;
  ofColor unit_color_y = m_y_color;
  ofColor unit_color_z = m_z_color;
  ofColor unit_color_w = m_w_color;
  ofColor select_color = m_select_color;

  switch (m_currManipulator)
  {
  case (MANIPULATOR_SCALE) :
  {
	  SCALE_TYPE curr = m_currScalePredict;

	  ofColor flagged_color_y = (flag_scale.xz || (flag_scale.xyz && (curr == SCALE_XYZ))) ? unit_color_y : convGrayscale(unit_color_y);
	  ofColor flagged_color_z = (flag_scale.xy || (flag_scale.xyz && (curr == SCALE_XYZ))) ? unit_color_z : convGrayscale(unit_color_z);
	  ofColor flagged_color_x = (flag_scale.yz || (flag_scale.xyz && (curr == SCALE_XYZ))) ? unit_color_x : convGrayscale(unit_color_x);

	  drawTriangle(origin, screenf_half, ((curr == SCALE_XZ) || (curr == SCALE_XYZ)), axe_x, axe_z, flagged_color_y);
	  drawTriangle(origin, screenf_half, ((curr == SCALE_XY) || (curr == SCALE_XYZ)), axe_x, axe_y, flagged_color_z);
	  drawTriangle(origin, screenf_half, ((curr == SCALE_YZ) || (curr == SCALE_XYZ)), axe_y, axe_z, flagged_color_x);

      axe_x *= screenf;
      axe_y *= screenf;
      axe_z *= screenf;

	  flagged_color_x = ((curr == SCALE_X) || (curr == SCALE_XYZ)) ? select_color : unit_color_x;
	  flagged_color_y = ((curr == SCALE_Y) || (curr == SCALE_XYZ)) ? select_color : unit_color_y;
	  flagged_color_z = ((curr == SCALE_Z) || (curr == SCALE_XYZ)) ? select_color : unit_color_z;

      drawScaleAxis(origin, axe_x, axe_y, (flag_scale.x || (flag_scale.xyz && (curr == SCALE_XYZ))) ? flagged_color_x : convGrayscale(flagged_color_x));
      drawScaleAxis(origin, axe_y, axe_x, (flag_scale.y || (flag_scale.xyz && (curr == SCALE_XYZ))) ? flagged_color_y : convGrayscale(flagged_color_y));
      drawScaleAxis(origin, axe_z, axe_x, (flag_scale.z || (flag_scale.xyz && (curr == SCALE_XYZ))) ? flagged_color_z : convGrayscale(flagged_color_z));
    }
    break;
    case (MANIPULATOR_ROTATION):
    {
      ROTATION_TYPE type = m_currRotation;
      ROTATION_TYPE curr = m_currRotationPredict;

      ofVec3f right;
      ofVec3f front;
      ofVec3f dir;
      ofVec3f up;

      dir = origin - m_viewInverse.getTranslation();
      dir.normalize();

      right = dir;
      right.cross(axe_y);
      right.normalize();

      up = dir;
      up.cross(right);
      up.normalize();

      right = dir;
      right.cross(up);
      right.normalize();

      {
        ofVec3f vtx = 1.2f * screenf * up;
        ofVec3f vty = 1.2f * screenf * right;
		ofColor flagged_color_w = curr == ROTATION_SCREEN ? select_color : unit_color_w;
        drawCircle(origin, vtx, vty, flag_rotation.screen ? flagged_color_w : convGrayscale(flagged_color_w));
      }

      {
        right = dir;
        right.cross(axe_x);
        right.normalize();
        front = right;
        front.cross(axe_x);
        front.normalize();
        ofVec3f vtx = screenf * right;
        ofVec3f vty = screenf * front;
		ofColor flagged_color_x = curr == ROTATION_X ? select_color : unit_color_x;
        drawCircle(origin, vtx, vty, flag_rotation.x ? flagged_color_x : convGrayscale(flagged_color_x));
      }

      {
        right = dir;
        right.cross(axe_y);
        right.normalize();
        front = right;
        front.cross(axe_y);
        front.normalize();
        ofVec3f vtx = screenf * right;
        ofVec3f vty = screenf * front;
		ofColor flagged_color_y = curr == ROTATION_Y ? select_color : unit_color_y;
        drawCircle(origin, vtx, vty, flag_rotation.y ? flagged_color_y : convGrayscale(flagged_color_y));
      }

      {
        right = dir;
        right.cross(axe_z);
        right.normalize();
        front = right;
        front.cross(axe_z);
        front.normalize();
        ofVec3f vtx = screenf * right;
        ofVec3f vty = screenf * front;
		ofColor flagged_color_z = curr == ROTATION_Z ? select_color : unit_color_z;
        drawCircle(origin, vtx, vty, flag_rotation.z ? flagged_color_z : convGrayscale(flagged_color_z));
      }

      if (type != ROTATION_NONE)
      {
        ofVec3f x = m_rotate_x;
        ofVec3f y = m_rotate_y;
        float angle = m_angleRad;
        x *= screenf;
        y *= screenf;
        switch (curr)
        {
		case (ROTATION_X) : {
			ofColor flagged_color_x = flag_rotation.x ? unit_color_x : convGrayscale(unit_color_x);
			drawCamembert(origin, x, y, -angle, flagged_color_x);
		}
		break;
		case (ROTATION_Y) : {
			ofColor flagged_color_y = flag_rotation.y ? unit_color_y : convGrayscale(unit_color_y);
			drawCamembert(origin, x, y, -angle, flagged_color_y);
		}
		break;
		case (ROTATION_Z) : {
			ofColor flagged_color_z = flag_rotation.z ? unit_color_z : convGrayscale(unit_color_z);
			drawCamembert(origin, x, y, -angle, flagged_color_z);
		}
		break;
		case (ROTATION_SCREEN) : {
			ofColor flagged_color_w = flag_rotation.screen ? unit_color_w : convGrayscale(unit_color_w);
			drawCamembert(origin, x, y, -angle, flagged_color_w);
		}
		break;
        }
      }
    }
    break;
    case (MANIPULATOR_TRANSLATION):
    {
      TRANSLATION_TYPE curr = m_currTranslationPredict;

	  ofColor flagged_color_y = (flag_translation.xz || (flag_translation.xyz && (curr == TRANSLATION_XYZ))) ? unit_color_y : convGrayscale(unit_color_y);
	  ofColor flagged_color_z = (flag_translation.xy || (flag_translation.xyz && (curr == TRANSLATION_XYZ))) ? unit_color_z : convGrayscale(unit_color_z);
	  ofColor flagged_color_x = (flag_translation.yz || (flag_translation.xyz && (curr == TRANSLATION_XYZ))) ? unit_color_x : convGrayscale(unit_color_x);

      drawQuad(origin, screenf_half, ( (curr == TRANSLATION_XZ) || (curr == TRANSLATION_XYZ) ), axe_x, axe_z, flagged_color_y);
      drawQuad(origin, screenf_half, ( (curr == TRANSLATION_XY) || (curr == TRANSLATION_XYZ) ), axe_x, axe_y, flagged_color_z);
      drawQuad(origin, screenf_half, ( (curr == TRANSLATION_YZ) || (curr == TRANSLATION_XYZ) ), axe_y, axe_z, flagged_color_x);

      axe_x *= screenf;
      axe_y *= screenf;
      axe_z *= screenf;

	  flagged_color_x = ((curr == TRANSLATION_X) || (curr == TRANSLATION_XYZ)) ? select_color : unit_color_x;
	  flagged_color_y = ((curr == TRANSLATION_Y) || (curr == TRANSLATION_XYZ)) ? select_color : unit_color_y;
	  flagged_color_z = ((curr == TRANSLATION_Z) || (curr == TRANSLATION_XYZ)) ? select_color : unit_color_z;
      drawAxis(origin, axe_x, axe_y, axe_z, 0.05f, 0.83f, (flag_translation.x || (flag_translation.xyz && (curr == TRANSLATION_XYZ))) ? flagged_color_x : convGrayscale(flagged_color_x));
      drawAxis(origin, axe_y, axe_x, axe_z, 0.05f, 0.83f, (flag_translation.y || (flag_translation.xyz && (curr == TRANSLATION_XYZ))) ? flagged_color_y : convGrayscale(flagged_color_y));
      drawAxis(origin, axe_z, axe_x, axe_y, 0.05f, 0.83f, (flag_translation.z || (flag_translation.xyz && (curr == TRANSLATION_XYZ))) ? flagged_color_z : convGrayscale(flagged_color_z));
    }
    break;
  }

  ofEnableDepthTest();

  ofPopStyle();
}

ofMatrix4x4 ofxManipulator::getMatrix()
{
  ofMatrix4x4 m;
  m.scale(m_ea.scale);
  m.rotate(m_ea.rotation);
  m.translate(m_ea.translation);
  return m;
}

ofVec3f ofxManipulator::getScale()
{
  return m_ea.scale;
}

ofQuaternion ofxManipulator::getRotation()
{
  return m_ea.rotation;
}

ofVec3f ofxManipulator::getTranslation()
{
  return m_ea.translation;
}

void ofxManipulator::setScale(ofVec3f scale)
{
  m_ea.scale = scale;
}

void ofxManipulator::setRotation(ofQuaternion rotation)
{
  m_ea.rotation = rotation;
}

void ofxManipulator::setTranslation(ofVec3f translation)
{
  m_ea.translation = translation;
}

void ofxManipulator::scale(ofVec3f scale)
{
  m_ea.scale *= scale;
}

void ofxManipulator::rotate(ofQuaternion rotation)
{
  m_ea.rotation *= rotation;
}

void ofxManipulator::translate(ofVec3f translation)
{
  m_ea.translation += translation;
}

void ofxManipulator::toggleScale()
{
  m_currManipulator = (m_currManipulator == MANIPULATOR_SCALE) ? MANIPULATOR_NONE : MANIPULATOR_SCALE;
}

void ofxManipulator::toggleRotation()
{
  m_currManipulator = (m_currManipulator == MANIPULATOR_ROTATION) ? MANIPULATOR_NONE : MANIPULATOR_ROTATION;
}

void ofxManipulator::toggleTranslation()
{
  m_currManipulator = (m_currManipulator == MANIPULATOR_TRANSLATION) ? MANIPULATOR_NONE : MANIPULATOR_TRANSLATION;
}

float ofxManipulator::getManipulatorScale()
{
  return m_manipulatorScale;
}

void ofxManipulator::setManipulatorScale(float scale)
{
  m_manipulatorScale = scale;
}

ofxManipulator::MANIPULATOR_TYPE ofxManipulator::getManipulatorType()
{
  return m_currManipulator;
}

void ofxManipulator::setManipulatorType(MANIPULATOR_TYPE type)
{
  m_currManipulator = type;
}

void ofxManipulator::setManipulatorColors(ofColor x, ofColor y, ofColor z, ofColor w, ofColor select)
{
  m_x_color = x;
  m_y_color = y;
  m_z_color = z;
  m_w_color = w;
  m_select_color = select;
}

////////////////////////////////////////////////////////////////////////////////
// PROTECTED FUNCTIONS
////////////////////////////////////////////////////////////////////////////////

void ofxManipulator::mouseMoved(ofMouseEventArgs &mouse)
{
  int x = mouse.x;
  int y = mouse.y;

  // Focus
  ofCamera cam;
  cam.setTransformMatrix(m_viewInverse);
  ofVec3f cur = cam.worldToScreen(m_ea.translation);
  float dist = cur.distance(mouse);
  cur.x = m_ea.id;
  cur.y = dist;
  // cur.z = cur.z;
  m_ea.dist = dist;
  m_ea.z = cur.z;

  // Change to onFocus
  if ((dist < m_onFocusThreshold * m_manipulatorScale) && !m_cameraMouseInputEnabled) {
	  m_focus = true;
	  if (m_auto_focus) {
		  if (m_currManipulator == MANIPULATOR_NONE) {
			  m_currManipulator = m_lastManipulator;
		  }
	  }
  }
  // OnFocus process
  if ((dist < m_offFocusThreshold * m_manipulatorScale) && !m_cameraMouseInputEnabled) {
	  if (m_focus) {
		  ofNotifyEvent(onFocus, m_ea);
	  }
  }
  // Change to outFocus
  else {
	  if (m_focus) {
		  ofNotifyEvent(outFocus, m_ea);
		  m_focus = false;
		  if (m_auto_focus) {
			  m_currManipulator = MANIPULATOR_NONE;
		  }
	  }
  }
  if (m_currManipulator != MANIPULATOR_NONE) {
	  m_lastManipulator = m_currManipulator;
  }

  switch (m_currManipulator)
  {
    case (MANIPULATOR_SCALE):
      getCurrScale(m_currScalePredict, x, y);
      break;
    case (MANIPULATOR_ROTATION):
      getCurrRotation(m_currRotationPredict, x, y);
      break;
    case (MANIPULATOR_TRANSLATION):
      getCurrTranslation(m_currTranslationPredict, x, y);
      break;
  }
}

void ofxManipulator::mousePressed(ofMouseEventArgs &mouse)
{
  int x = mouse.x;
  int y = mouse.y;
  int button = mouse.button;

  if(button != OF_MOUSE_BUTTON_LEFT)
    return;

  switch (m_currManipulator)
  {
    case (MANIPULATOR_SCALE):
      getCurrScale(m_currScale, x, y);
      break;
    case (MANIPULATOR_ROTATION):
      getCurrRotation(m_currRotation, x, y);
      break;
    case (MANIPULATOR_TRANSLATION):
      getCurrTranslation(m_currTranslation, x, y);
      break;
  }
}

void ofxManipulator::mouseDragged(ofMouseEventArgs &mouse)
{
  int x = mouse.x;
  int y = mouse.y;
  int button = mouse.button;

  if (button != OF_MOUSE_BUTTON_LEFT)
    return;

  switch (m_currManipulator)
  {
    case (MANIPULATOR_SCALE):
    {
      if (m_currScale == SCALE_NONE) {
        getCurrScale(m_currScalePredict, x, y);
        break;
      }

      ofVec3f ray_origin;
      ofVec3f ray_direction;
      ofVec3f intersection;

      createRay(x, y, ray_origin, ray_direction);
      intersectRay(m_plane, intersection, ray_origin, ray_direction);

      ofMatrix4x4 m;
      m.rotate(m_ea.rotation);
      ofVec3f axe_x = m.getRowAsVec3f(0);
      ofVec3f axe_y = m.getRowAsVec3f(1);
      ofVec3f axe_z = m.getRowAsVec3f(2);

      ofVec3f df;

      if (m_currScale == SCALE_XYZ)
      {
        float scale = x - m_lockedCursor_x;
        df = ofVec3f(scale);
      } else {
        df = intersection - m_lockedPoint;
        switch (m_currScale)
        {
          case SCALE_X:  df = ofVec3f(df.dot(axe_x),0,0); break;
          case SCALE_Y:  df = ofVec3f(0,df.dot(axe_y),0); break;
          case SCALE_Z:  df = ofVec3f(0,0,df.dot(axe_z)); break;
          case SCALE_XY: df = ofVec3f(df.dot(axe_x+axe_y),df.dot(axe_x+axe_y),0); break;
          case SCALE_XZ: df = ofVec3f(df.dot(axe_x+axe_z),0,df.dot(axe_x+axe_z)); break;
          case SCALE_YZ: df = ofVec3f(0,df.dot(axe_y+axe_z),df.dot(axe_y+axe_z)); break;
        }
      }

      float len = (sqrtf(df.x * df.x + df.y * df.y + df.z * df.z)) / 100.f;

      m_ea.scale = m_scaleSaved + (df.normalize() * len);

	  ofNotifyEvent(onScaleChange, m_ea);
    }
    break;
    case (MANIPULATOR_ROTATION):
    {
      if (m_currRotation == ROTATION_NONE) {
        getCurrRotation(m_currRotationPredict, x, y);
        break;
      }

      ofVec3f ray_origin;
      ofVec3f ray_direction;
      ofVec3f intersection;

      createRay(x, y, ray_origin, ray_direction);
      intersectRay(m_plane, intersection, ray_origin, ray_direction);

      ofVec3f df = intersection - m_ea.translation;
      df.normalize();

      float acos_angle = df.dot(m_lockedPoint);
      m_angleRad = (acos_angle < -0.99999f) || (acos_angle > 0.99999f) ? 0.0f : acos(acos_angle);

      if (df.dot(m_rotate_y) > 0)
        m_angleRad = -m_angleRad;

      ofMatrix4x4 m;
      m.rotate(m_rotationSaved);
      m.rotateRad(m_angleRad, m_axis.x, m_axis.y, m_axis.z);

      m_ea.rotation = m.getRotate();

	  ofNotifyEvent(onRotateChange, m_ea);
    }
    break;
    case (MANIPULATOR_TRANSLATION):
    {
      if (m_currTranslation == TRANSLATION_NONE) {
        getCurrTranslation(m_currTranslationPredict, x, y);
        break;
      }

      ofVec3f ray_origin;
      ofVec3f ray_direction;
      ofVec3f intersection;

      createRay(x, y, ray_origin, ray_direction);
      intersectRay(m_plane, intersection, ray_origin, ray_direction);

      ofMatrix4x4 m;
      m.rotate(m_ea.rotation);
      ofVec3f axe_x = m.getRowAsVec3f(0);
      ofVec3f axe_y = m.getRowAsVec3f(1);
      ofVec3f axe_z = m.getRowAsVec3f(2);

      if (m_currTranslation == TRANSLATION_XYZ)
      {
        ofCamera cam;
        cam.setTransformMatrix(m_viewInverse);
        ofVec3f mat_pos = m_translationSaved;
        ofVec3f cam_pos = cam.getPosition();
        ofVec3f cam_n = cam.getLookAtDir();

        ofVec3f v = mat_pos - cam_pos;
        float dist = v.dot(cam_n);
        ofVec3f mat_plane_pos = mat_pos - (cam_n * dist);

        ofVec3f n = mat_pos - mat_plane_pos;
        n.normalize();

        ofVec3f ray_end = cam.screenToWorld(ofVec3f(x + m_lockedCursor_x, y + m_lockedCursor_y, 1.0f));
        ofVec3f ray_dir = cam_pos - ray_end;
        ray_dir.normalize();

        float t = -((mat_pos - cam_pos).dot(n)) / n.dot(ray_dir);

        m_ea.translation = cam_pos - (ray_dir * t);
      } else {
        ofVec3f df = intersection - m_lockedPoint;
        switch (m_currTranslation)
        {
          case TRANSLATION_X:  df = ofVec3f(df.dot(axe_x),0,0); break;
          case TRANSLATION_Y:  df = ofVec3f(0,df.dot(axe_y),0); break;
          case TRANSLATION_Z:  df = ofVec3f(0,0,df.dot(axe_z)); break;
          case TRANSLATION_XY: df = ofVec3f(df.dot(axe_x),df.dot(axe_y),0); break;
          case TRANSLATION_XZ: df = ofVec3f(df.dot(axe_x),0,df.dot(axe_z)); break;
          case TRANSLATION_YZ: df = ofVec3f(0,df.dot(axe_y),df.dot(axe_z)); break;
        }
        ofVec3f vec = (df.x * axe_x) + (df.y * axe_y) + (df.z * axe_z);

        m_ea.translation = m_translationSaved + vec;
      }

	  ofNotifyEvent(onTranslateChange, m_ea);
    }
    break;
  }
}

void ofxManipulator::mouseReleased(ofMouseEventArgs &mouse)
{
  int x = mouse.x;
  int y = mouse.y;
  int button = mouse.button;

  if (button != OF_MOUSE_BUTTON_LEFT)
    return;

  m_currScale = SCALE_NONE;
  m_currRotation = ROTATION_NONE;
  m_currTranslation = TRANSLATION_NONE;
}

void ofxManipulator::mouseScrolled(ofMouseEventArgs &mouse)
{
	if (m_cameraMouseInputEnabled) return;
	if (m_auto_focus && !m_focus) return;

	int x = mouse.x;
	int y = mouse.y;
	int scroll_x = mouse.scrollX;
	int scroll_y = mouse.scrollY;

	// flag_check
	deque<MANIPULATOR_TYPE> deqc;
	deqc.push_back(MANIPULATOR_NONE);
	if (!flag_scale.none) deqc.push_back(MANIPULATOR_SCALE);
	if (!flag_rotation.none) deqc.push_back(MANIPULATOR_ROTATION);
	if (!flag_translation.none) deqc.push_back(MANIPULATOR_TRANSLATION);

	// init check
	int init = 0;
	if (m_currManipulator == MANIPULATOR_NONE) init = 0;
	else if (m_currManipulator == MANIPULATOR_SCALE) {
		if (!flag_scale.none) init = 1;
		else init = 0;
	}
	else if (m_currManipulator == MANIPULATOR_ROTATION) {
		if (!flag_rotation.none && !flag_scale.none) init = 2;
		else if (!flag_rotation.none && flag_scale.none) init = 1;
		else /* if (flag_rotation.none && flag_scale.none)*/ init = 0;
	}
	else if (m_currManipulator == MANIPULATOR_TRANSLATION) {
		if (!flag_translation.none && deqc.size() == 4) init = 3;
		else if (!flag_translation.none && deqc.size() == 2) init = 1;
		else if (!flag_translation.none && deqc.size() == 3) init = 2;
		else /* if(flag_translation.none && flag_rotation.none && flag_scale.none*/ init = 0;
	}

	// scroll_y
	if (scroll_y == 0) return;
	int desired = init + scroll_y;
	if (desired < 0) desired = deqc.size() - 1;
	else if (desired >= deqc.size()) desired = 0;
	m_currManipulator = deqc[desired];
}

////////////////////////////////////////////////////////////////////////////////
// PRIVATE FUNCTIONS
////////////////////////////////////////////////////////////////////////////////

float ofxManipulator::computeScreenFactor()
{
  ofMatrix4x4 viewproj = m_view * m_proj;
  ofVec3f origin = m_ea.translation;
  ofVec4f transform = ofVec4f(origin.x, origin.y, origin.z, 1.0f);
  transform = transform * viewproj;
  return transform.w * 0.2f * m_manipulatorScale;
}

void ofxManipulator::getCurrScale(SCALE_TYPE &type, unsigned int x, unsigned int y)
{
  m_lockedCursor_x = x;
  m_lockedCursor_y = y;
  m_scaleSaved = m_ea.scale;

  ofVec3f ray_origin;
  ofVec3f ray_direction;

  createRay(x, y, ray_origin, ray_direction);

  ofMatrix4x4 m;
  m.rotate(m_ea.rotation);
  m.translate(m_ea.translation);
  ofVec3f axe_x = m.getRowAsVec3f(0);
  ofVec3f axe_y = m.getRowAsVec3f(1);
  ofVec3f axe_z = m.getRowAsVec3f(2);

  ofVec3f df;

  df = raytrace(ray_origin, ray_direction, axe_y, m);

  if ((df.x < 0.1f) && (df.z < 0.1f) && (df.x > -0.1f) && (df.z > -0.1f)) { type = SCALE_XYZ; if (!flag_scale.xyz) type = SCALE_NONE; return; }
  else
  {
    df = raytrace(ray_origin, ray_direction, axe_z, m);

    if( (df.x < 0.1f) && (df.y < 0.1f) && (df.x > -0.1f) && (df.y > -0.1f) ) { type = SCALE_XYZ; if (!flag_scale.xyz) type = SCALE_NONE; return; }
    else
    {
      df = raytrace(ray_origin, ray_direction, axe_x, m);

      if( (df.y < 0.1f) && (df.z < 0.1f) && (df.y > -0.1f) && (df.z > -0.1f) ) { type = SCALE_XYZ; if (!flag_scale.xyz) type = SCALE_NONE; return; }
    }
  }

  df = raytrace(ray_origin, ray_direction, axe_y, m);

  if      ( (df.x >= 0.f) && (df.x <= 1.f) && (fabs(df.z) < 0.1f) )          { type = SCALE_X;  if (!flag_scale.x) type = SCALE_NONE; return; }
  else if ( (df.z >= 0.f) && (df.z <= 1.f) && (fabs(df.x) < 0.1f) )          { type = SCALE_Z;  if (!flag_scale.z) type = SCALE_NONE; return; }
  else if ( (df.x < 0.5f) && (df.z < 0.5f) && (df.x > 0.f) && (df.z > 0.f) ) { type = SCALE_XZ; if (!flag_scale.xz) type = SCALE_NONE; return; }
  else
  {
    df = raytrace(ray_origin, ray_direction, axe_z, m);

    if      ( (df.x >= 0.f) && (df.x <= 1.f) && (fabs(df.y) < 0.1f) )          { type = SCALE_X;  if (!flag_scale.x) type = SCALE_NONE; return; }
    else if ( (df.y >= 0.f) && (df.y <= 1.f) && (fabs(df.x) < 0.1f) )          { type = SCALE_Y;  if (!flag_scale.y) type = SCALE_NONE; return; }
    else if ( (df.x < 0.5f) && (df.y < 0.5f) && (df.x > 0.f) && (df.y > 0.f) ) { type = SCALE_XY; if (!flag_scale.xy) type = SCALE_NONE; return; }
    else
    {
      df = raytrace(ray_origin, ray_direction, axe_x, m);

      if      ( (df.y >= 0.f) && (df.y <= 1.f) && (fabs(df.z) < 0.1f) )          { type = SCALE_Y;  if (!flag_scale.y) type = SCALE_NONE; return; }
      else if ( (df.z >= 0.f) && (df.z <= 1.f) && (fabs(df.y) < 0.1f) )          { type = SCALE_Z;  if (!flag_scale.z) type = SCALE_NONE; return; }
      else if ( (df.y < 0.5f) && (df.z < 0.5f) && (df.y > 0.f) && (df.z > 0.f) ) { type = SCALE_YZ; if (!flag_scale.yz) type = SCALE_NONE; return; }
    }
  }

  type = SCALE_NONE;
}

void ofxManipulator::getCurrRotation(ROTATION_TYPE &type, unsigned int x, unsigned int y)
{
  ofVec3f ray_origin;
  ofVec3f ray_direction;

  createRay(x, y, ray_origin, ray_direction);

  ofMatrix4x4 m;
  m.rotate(m_ea.rotation);
  ofVec3f axe_x = m.getRowAsVec3f(0);
  ofVec3f axe_y = m.getRowAsVec3f(1);
  ofVec3f axe_z = m.getRowAsVec3f(2);

  if (checkRotationPlane(axe_x, 1.0f, ray_origin, ray_direction)) { type = ROTATION_X; if (!flag_rotation.x) type = ROTATION_NONE; return; }
  if (checkRotationPlane(axe_y, 1.0f, ray_origin, ray_direction)) { type = ROTATION_Y; if (!flag_rotation.y) type = ROTATION_NONE; return; }
  if (checkRotationPlane(axe_z, 1.0f, ray_origin, ray_direction)) { type = ROTATION_Z; if (!flag_rotation.z) type = ROTATION_NONE; return; }

  ofVec3f direction = m_ea.translation - m_viewInverse.getTranslation();
  direction.normalize();

  if (checkRotationPlane(direction, 1.2f, ray_origin, ray_direction)) { type = ROTATION_SCREEN; if (!flag_rotation.screen) type = ROTATION_NONE; return; }

  type = ROTATION_NONE;
}

void ofxManipulator::getCurrTranslation(TRANSLATION_TYPE &type, unsigned int x, unsigned int y)
{
  ofCamera cam;
  cam.setTransformMatrix(m_viewInverse);
  ofVec3f mat_screenCoord = cam.worldToScreen(m_translationSaved);
  m_lockedCursor_x = mat_screenCoord.x - x;
  m_lockedCursor_y = mat_screenCoord.y - y;
  m_translationSaved = m_ea.translation;

  ofVec3f ray_origin;
  ofVec3f ray_direction;

  createRay(x, y, ray_origin, ray_direction);

  ofMatrix4x4 m;
  m.rotate(m_ea.rotation);
  m.translate(m_ea.translation);
  ofVec3f axe_x = m.getRowAsVec3f(0);
  ofVec3f axe_y = m.getRowAsVec3f(1);
  ofVec3f axe_z = m.getRowAsVec3f(2);

  ofVec3f df;

  df = raytrace(ray_origin, ray_direction, axe_y, m);

  if ((df.x < 0.1f) && (df.z < 0.1f) && (df.x > -0.1f) && (df.z > -0.1f)) { type = TRANSLATION_XYZ; if (!flag_translation.xyz) type = TRANSLATION_NONE; return; }
  else
  {
    df = raytrace(ray_origin, ray_direction, axe_z, m);

    if( (df.x < 0.1f) && (df.y < 0.1f) && (df.x > -0.1f) && (df.y > -0.1f) ) { type = TRANSLATION_XYZ; if (!flag_translation.xyz) type = TRANSLATION_NONE; return; }
    else
    {
      df = raytrace(ray_origin, ray_direction, axe_x, m);

      if( (df.y < 0.1f) && (df.z < 0.1f) && (df.y > -0.1f) && (df.z > -0.1f) ) { type = TRANSLATION_XYZ; if (!flag_translation.xyz) type = TRANSLATION_NONE; return; }
    }
  }

  df = raytrace(ray_origin, ray_direction, axe_y, m);

  if      ( (df.x >= 0.f) && (df.x <= 1.f) && (fabs(df.z) < 0.1f) )          { type = TRANSLATION_X;  if (!flag_translation.x) type = TRANSLATION_NONE; return; }
  else if ( (df.z >= 0.f) && (df.z <= 1.f) && (fabs(df.x) < 0.1f) )          { type = TRANSLATION_Z;  if (!flag_translation.z) type = TRANSLATION_NONE; return; }
  else if ( (df.x < 0.5f) && (df.z < 0.5f) && (df.x > 0.f) && (df.z > 0.f) ) { type = TRANSLATION_XZ; if (!flag_translation.xz) type = TRANSLATION_NONE; return; }
  else
  {
    df = raytrace(ray_origin, ray_direction, axe_z, m);

    if      ( (df.x >= 0.f) && (df.x <= 1.f) && (fabs(df.y) < 0.1f) )          { type = TRANSLATION_X;  if (!flag_translation.x) type = TRANSLATION_NONE; return; }
    else if ( (df.y >= 0.f) && (df.y <= 1.f) && (fabs(df.x) < 0.1f) )          { type = TRANSLATION_Y;  if (!flag_translation.y) type = TRANSLATION_NONE; return; }
    else if ( (df.x < 0.5f) && (df.y < 0.5f) && (df.x > 0.f) && (df.y > 0.f) ) { type = TRANSLATION_XY; if (!flag_translation.xy) type = TRANSLATION_NONE; return; }
    else
    {
      df = raytrace(ray_origin, ray_direction, axe_x, m);

      if      ( (df.y >= 0.f) && (df.y <= 1.f) && (fabs(df.z) < 0.1f) )          { type = TRANSLATION_Y;  if (!flag_translation.y) type = TRANSLATION_NONE; return; }
      else if ( (df.z >= 0.f) && (df.z <= 1.f) && (fabs(df.y) < 0.1f) )          { type = TRANSLATION_Z;  if (!flag_translation.z) type = TRANSLATION_NONE; return; }
      else if ( (df.y < 0.5f) && (df.z < 0.5f) && (df.y > 0.f) && (df.z > 0.f) ) { type = TRANSLATION_YZ; if (!flag_translation.yz) type = TRANSLATION_NONE; return; }
    }
  }

  type = TRANSLATION_NONE;
}

void ofxManipulator::createRay(float x, float y, ofVec3f &ray_origin, ofVec3f &ray_dir)
{
  ofMatrix4x4 view_inverse = m_viewInverse;
  ofMatrix4x4 proj = m_proj;

  float w = ofGetWidth();
  float h = ofGetHeight();

  ofVec3f screen_space(
     (((2.0f * x) / w) - 1.0f) / proj._mat[0][0],
    -(((2.0f * y) / h) - 1.0f) / proj._mat[1][1],
    -1.0f
  );

  auto transform = [] (ofVec3f v, ofMatrix4x4 m)
  {
    ofVec3f vec;
    vec.x = v.x * m._mat[0][0] + v.y * m._mat[1][0] + v.z * m._mat[2][0];
    vec.y = v.x * m._mat[0][1] + v.y * m._mat[1][1] + v.z * m._mat[2][1];
    vec.z = v.x * m._mat[0][2] + v.y * m._mat[1][2] + v.z * m._mat[2][2];
    return vec;
  };

  ray_origin = view_inverse.getTranslation();

  ray_dir = transform(screen_space, view_inverse);
  ray_dir.normalize();
}

void ofxManipulator::intersectRay(ofVec4f &m_plane, ofVec3f &inter_point, ofVec3f position, ofVec3f direction)
{
  float den = m_plane.dot(direction);
  ofVec3f tmp = (m_plane * m_plane.w) - position;
  inter_point = position + (m_plane.dot(tmp) / den) * direction;
}

ofVec3f ofxManipulator::raytrace(ofVec3f ray_origin, ofVec3f ray_dir, ofVec3f normal, ofMatrix4x4 matrix)
{
  ofVec3f df;
  ofVec3f intersection;

  m_plane = createPlane(m_ea.translation, normal);
  intersectRay(m_plane, intersection, ray_origin, ray_dir);
  df = intersection * matrix.getInverse();
  m_lockedPoint = intersection;
  df /= m_screenFactor;

  return df;
}

ofVec4f ofxManipulator::createPlane(ofVec3f p, ofVec3f n)
{
  ofVec4f m_plane;
  ofVec3f normal;

  float length = 1.0f / sqrtf( (n.x * n.x) + (n.y * n.y) + (n.z * n.z) );

  normal = n * length;

  m_plane.x = normal.x;
  m_plane.y = normal.y;
  m_plane.z = normal.z;
  m_plane.w = normal.dot(p);

  return m_plane;
}

bool ofxManipulator::checkRotationPlane(ofVec3f normal, float factor, ofVec3f ray_origin, ofVec3f ray_direction)
{
  ofVec3f df;
  ofVec3f intersection;
  ofVec3f translation = m_ea.translation;

  m_plane = createPlane(translation, normal);
  intersectRay(m_plane, intersection, ray_origin, ray_direction);
  df = intersection - translation;
  df /= m_screenFactor;

  float dflen = (df.length() / factor);
  if ((dflen > 0.9f) && (dflen < 1.1f))
  {
    m_rotationSaved = m_ea.rotation;
    m_lockedPoint = df;
    m_lockedPoint.normalize();
    m_rotate_x = m_lockedPoint;
    m_rotate_y = m_lockedPoint;
    m_rotate_y.cross(normal);
    m_rotate_y.normalize();
    m_rotate_x *= factor;
    m_rotate_y *= factor;
    m_angleRad = 0.0f;
    m_axis = normal;
    return true;
  }

  return false;
}

////////////////////////////////////////////////////////////////////////////////
// DRAW FUNCTIONS
////////////////////////////////////////////////////////////////////////////////

void ofxManipulator::drawCircle(ofVec3f origin, ofVec3f vtx, ofVec3f vty, ofColor color)
{
  const int size = 50;
  vector<ofVec3f> vertices;
  vertices.resize(size);
  for (int i = 0; i < size; i++) {
    ofVec3f vt;
    vt  = vtx * cos((2 * PI / size) * i);
    vt += vty * sin((2 * PI / size) * i);
    vt += origin;
    vertices[i] = vt;
  }

  ofSetColor(color);
  ofMesh(OF_PRIMITIVE_LINE_LOOP, vertices).draw();
}

void ofxManipulator::drawAxis(ofVec3f origin, ofVec3f m_axis, ofVec3f vtx, ofVec3f vty, float fct, float fct2, ofColor color)
{
  vector<ofVec3f> cone_mesh;
  cone_mesh.resize(93);
  for (int i = 0, j = 0; i <= 30; i++) {
    ofVec3f pt;
    pt  = vtx * cos(((2 * PI) / 30.0f) * i) * fct;
    pt += vty * sin(((2 * PI) / 30.0f) * i) * fct;
    pt += m_axis * fct2;
    pt += origin;
    cone_mesh[j++] = pt;
    pt  = vtx * cos(((2 * PI) / 30.0f) * (i + 1)) * fct;
    pt += vty * sin(((2 * PI) / 30.0f) * (i + 1)) * fct;
    pt += m_axis * fct2;
    pt += origin;
    cone_mesh[j++] = pt;
    cone_mesh[j++] = origin + m_axis;
  }

  ofSetColor(color);
  ofMesh(OF_PRIMITIVE_LINES, {origin, origin + m_axis}).draw();
  ofMesh(OF_PRIMITIVE_TRIANGLE_FAN, cone_mesh).draw();
}

void ofxManipulator::drawScaleAxis(ofVec3f origin, ofVec3f m_axis, ofVec3f vtx, ofColor color)
{
  ofSetColor(color);
  ofMesh(OF_PRIMITIVE_LINES, {origin, origin + m_axis}).draw();

  float box_size = 0.1f * m_screenFactor;

  ofVec3f box_pos = origin;
  box_pos += m_axis.getNormalized() * (m_axis.length() - (box_size * 0.5f));

  ofNode box_node;
  box_node.lookAt(m_axis, vtx);
  box_node.setPosition(box_pos);
  ofPushMatrix();
  ofMultMatrix(box_node.getGlobalTransformMatrix());
  ofDrawBox(box_size);
  ofPopMatrix();
}

void ofxManipulator::drawCamembert(ofVec3f origin, ofVec3f vtx, ofVec3f vty, float ng, ofColor color)
{
  vector<ofVec3f> vertices;
  vertices.resize(52);
  int j = 0;
  vertices[j++] = origin;
  for (int i = 0; i <= 50; i++) {
    ofVec3f vt;
    vt  = vtx * cos((ng / 50) * i);
    vt += vty * sin((ng / 50) * i);
    vt += origin;
    vertices[j++] = vt;
  }

  ofSetColor(color, 255.0f * 0.5f);
  ofMesh(OF_PRIMITIVE_TRIANGLE_FAN, vertices).draw();
  ofMesh(OF_PRIMITIVE_LINE_LOOP, vertices).draw();
}

void ofxManipulator::drawQuad(ofVec3f origin, float size, bool is_selected, ofVec3f m_axis_u, ofVec3f m_axis_v, ofColor color)
{
  origin += ((m_axis_u + m_axis_v) * size) * 0.2f;
  m_axis_u *= 0.8f;
  m_axis_v *= 0.8f;

  vector<ofVec3f> pts;
  pts.resize(4);
  pts[0] = origin;
  pts[1] = origin + (m_axis_u * size);
  pts[2] = origin + (m_axis_u + m_axis_v) * size;
  pts[3] = origin + (m_axis_v * size);

  ofSetColor(color, 255.0f * (is_selected? 0.8f : 0.4f));
  ofMesh(OF_PRIMITIVE_TRIANGLE_FAN, pts).draw();
  ofSetColor(color, 255.0f * (is_selected? 1.0f : 0.8f));
  ofMesh(OF_PRIMITIVE_LINE_LOOP, pts).draw();
}

void ofxManipulator::drawTriangle(ofVec3f origin, float size, bool is_selected, ofVec3f m_axis_u, ofVec3f m_axis_v, ofColor color)
{
  origin += ((m_axis_u + m_axis_v) * size) * 0.2f;
  m_axis_u *= 0.8f;
  m_axis_v *= 0.8f;

  vector<ofVec3f> pts;
  pts.resize(3);
  pts[0] = origin;
  pts[1] = (m_axis_u * size) + origin;
  pts[2] = (m_axis_v * size) + origin;

  ofSetColor(color, 255.0f * (is_selected? 0.8f : 0.4f));
  ofMesh(OF_PRIMITIVE_TRIANGLES, pts).draw();
  ofSetColor(color, 255.0f * (is_selected? 1.0f : 0.8f));
  ofMesh(OF_PRIMITIVE_LINE_LOOP, pts).draw();
}

オリジナルはこちら。github.com

オキシクリーンは、主婦に大人気の衣類の泥汚れなどのための漬け置き用漂白剤。
成分はたいした違いは無いはずなのに効果がかなり違うらしい。中国産とアメリカ産があり、アメリカ産の方が効果が高いらしい。アマゾン以外では、コストコ東急ハンズでも入手できます。