Compute Objects Moving with Arrows and Mouse

Compute objects moving with arrows and mouse

Here small ugly but very simple C++ example of what I had in mind for this:

//---------------------------------------------------------------------------
#include <vcl.h>
#include <math.h>
#pragma hdrstop
#include "Unit1.h"
#include "gl_simple.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
//---------------------------------------------------------------------------
double mview[16],mmodel[16],mproj[16]; // OpenGL matrices
//---------------------------------------------------------------------------
// vector/matrix math
//---------------------------------------------------------------------------
double vector_len(double *a) { return sqrt((a[0]*a[0])+(a[1]*a[1])+(a[2]*a[2])); } // = |vector[3]|
void vector_sub(double *c,double *a,double *b) { for (int i=0;i<3;i++) c[i]=a[i]-b[i]; } // c[3] = a[3] - b[3]
void matrix_mul(double *c,double *a,double *b) // c[4x4] = a[4x4] * b [4x4]
{
double q[16];
q[ 0]=(a[ 0]*b[ 0])+(a[ 1]*b[ 4])+(a[ 2]*b[ 8])+(a[ 3]*b[12]);
q[ 1]=(a[ 0]*b[ 1])+(a[ 1]*b[ 5])+(a[ 2]*b[ 9])+(a[ 3]*b[13]);
q[ 2]=(a[ 0]*b[ 2])+(a[ 1]*b[ 6])+(a[ 2]*b[10])+(a[ 3]*b[14]);
q[ 3]=(a[ 0]*b[ 3])+(a[ 1]*b[ 7])+(a[ 2]*b[11])+(a[ 3]*b[15]);
q[ 4]=(a[ 4]*b[ 0])+(a[ 5]*b[ 4])+(a[ 6]*b[ 8])+(a[ 7]*b[12]);
q[ 5]=(a[ 4]*b[ 1])+(a[ 5]*b[ 5])+(a[ 6]*b[ 9])+(a[ 7]*b[13]);
q[ 6]=(a[ 4]*b[ 2])+(a[ 5]*b[ 6])+(a[ 6]*b[10])+(a[ 7]*b[14]);
q[ 7]=(a[ 4]*b[ 3])+(a[ 5]*b[ 7])+(a[ 6]*b[11])+(a[ 7]*b[15]);
q[ 8]=(a[ 8]*b[ 0])+(a[ 9]*b[ 4])+(a[10]*b[ 8])+(a[11]*b[12]);
q[ 9]=(a[ 8]*b[ 1])+(a[ 9]*b[ 5])+(a[10]*b[ 9])+(a[11]*b[13]);
q[10]=(a[ 8]*b[ 2])+(a[ 9]*b[ 6])+(a[10]*b[10])+(a[11]*b[14]);
q[11]=(a[ 8]*b[ 3])+(a[ 9]*b[ 7])+(a[10]*b[11])+(a[11]*b[15]);
q[12]=(a[12]*b[ 0])+(a[13]*b[ 4])+(a[14]*b[ 8])+(a[15]*b[12]);
q[13]=(a[12]*b[ 1])+(a[13]*b[ 5])+(a[14]*b[ 9])+(a[15]*b[13]);
q[14]=(a[12]*b[ 2])+(a[13]*b[ 6])+(a[14]*b[10])+(a[15]*b[14]);
q[15]=(a[12]*b[ 3])+(a[13]*b[ 7])+(a[14]*b[11])+(a[15]*b[15]);
for(int i=0;i<16;i++) c[i]=q[i];
}
void matrix_mul_vector(double *c,double *a,double *b) // c[3] = a[4x4] * (b[3],1)
{
double q[3];
q[0]=(a[ 0]*b[0])+(a[ 4]*b[1])+(a[ 8]*b[2])+(a[12]);
q[1]=(a[ 1]*b[0])+(a[ 5]*b[1])+(a[ 9]*b[2])+(a[13]);
q[2]=(a[ 2]*b[0])+(a[ 6]*b[1])+(a[10]*b[2])+(a[14]);
for(int i=0;i<3;i++) c[i]=q[i];
}
//---------------------------------------------------------------------------
class DragDrop3D // arrow translation controls (you need one for each objet)
{
public:
double *mm; // model matrix
double *mv; // view matrix
double *mp; // projection matrix

double o[3]; // start point
double l[3]; // length of the arrows
double a,b; // size of the arrow head a length, b width/2
int sel; // selected axis

DragDrop3D()
{
mm=NULL; mv=NULL; mp=NULL;
o[0]=-1.0; l[0]=3.5; a=0.5;
o[1]=-1.0; l[1]=3.5; b=0.25;
o[2]=-1.0; l[2]=3.5; sel=-1;
}
DragDrop3D(DragDrop3D& a) { *this=a; }
~DragDrop3D() {}
DragDrop3D* operator = (const DragDrop3D *a) { *this=*a; return this; }
//DragDrop3D* operator = (const DragDrop3D &a) { ...copy... return this; }

void project(double *scr,double *mmvp,double *obj) // obj -> scr
{
matrix_mul_vector(scr,mmvp,obj);
if (fabs(scr[2])>1e-10) scr[2]=1.0/scr[2]; else scr[2]=0.0;
scr[0]*=scr[2];
scr[1]*=scr[2];
}
void draw() // render arrows
{
int e;
double ang,dang=2.0*M_PI/36.0,u0,u1,v0,v1,q=1.0/sqrt((a*a)+(b*b));
// axis lines
glBegin(GL_LINES);
glColor3f(1.0,0.0,0.0); glVertex3dv(o); glVertex3d(o[0]+l[0],o[1],o[2]);
glColor3f(0.0,1.0,0.0); glVertex3dv(o); glVertex3d(o[0],o[1]+l[1],o[2]);
glColor3f(0.0,0.0,1.0); glVertex3dv(o); glVertex3d(o[0],o[1],o[2]+l[2]);
glEnd();
// cones
glBegin(GL_TRIANGLES);
u1=b*cos(-dang);
v1=b*sin(-dang);
for (e=1,ang=0.0;e;ang+=dang)
{
if (ang>=2.0*M_PI) { ang=2.0*M_PI; e=0; }
u0=u1; u1=b*cos(ang);
v0=v1; v1=b*sin(ang);
// X
if (sel==0) glColor3f(1.0,0.0,0.0); else glColor3f(0.5,0.1,0.1);
glNormal3f(a*q,(u0+u1)*0.5*q,(v0+v1)*0.5*q);
glVertex3d(o[0]+l[0] ,o[1] ,o[2] );
glVertex3d(o[0]+l[0]-a,o[1]+u0,o[2]+v0);
glVertex3d(o[0]+l[0]-a,o[1]+u1,o[2]+v1);
glNormal3f(-1.0,0.0,0.0);
glVertex3d(o[0]+l[0]-a,o[1],o[2]);
glVertex3d(o[0]+l[0]-a,o[1]+u1,o[2]+v1);
glVertex3d(o[0]+l[0]-a,o[1]+u0,o[2]+v0);
// Y
if (sel==1) glColor3f(0.0,1.0,0.0); else glColor3f(0.1,0.5,0.1);
glNormal3f((u0+u1)*0.5*q,a*q,(v0+v1)*0.5*q);
glVertex3d(o[0] ,o[1]+l[1] ,o[2] );
glVertex3d(o[0]+u1,o[1]+l[1]-a,o[2]+v1);
glVertex3d(o[0]+u0,o[1]+l[1]-a,o[2]+v0);
glNormal3f(0.0,-1.0,0.0);
glVertex3d(o[0] ,o[1]+l[1]-a,o[2] );
glVertex3d(o[0]+u0,o[1]+l[1]-a,o[2]+v0);
glVertex3d(o[0]+u1,o[1]+l[1]-a,o[2]+v1);
// Z
if (sel==2) glColor3f(0.0,0.0,1.0); else glColor3f(0.1,0.1,0.5);
glNormal3f((v0+v1)*0.5*q,(u0+u1)*0.5*q,a*q);
glVertex3d(o[0] ,o[1] ,o[2]+l[2] );
glVertex3d(o[0]+v1,o[1]+u1,o[2]+l[2]-a);
glVertex3d(o[0]+v0,o[1]+u0,o[2]+l[2]-a);
glNormal3f(0.0,0.0,-1.0);
glVertex3d(o[0] ,o[1] ,o[2]+l[2]-a);
glVertex3d(o[0]+v0,o[1]+u0,o[2]+l[2]-a);
glVertex3d(o[0]+v1,o[1]+u1,o[2]+l[2]-a);
}
glEnd();
}
bool mouse(double mx,double my,TShiftState sh) // handle mouse events return if redraw is needed
{
if ((mm==NULL)||(mv==NULL)||(mp==NULL)) return false;
int ql; double p[3],mmvp[16]; bool _redraw=false;
// MVP = M*V*P
matrix_mul(mmvp,mm,mv);
matrix_mul(mmvp,mmvp,mp);
// convert screen coords to <-1,+1> GL NDC
mx= (2.0*mx/double(xs))-1.0;
my=1.0-(2.0*my/double(ys)) ;
// mouse left button state (last,actual)
ql=sh.Contains(ssLeft);
// select (no mouse button)
if (!ql)
{
int sel0=sel; sel=-1;
// arrowhead center screen x,y distance to mouse select if close
p[0]=o[0]+l[0]-0.5*a; p[1]=o[1]; p[2]=o[2]; project(p,mmvp,p); p[0]-=mx; p[1]-=my; p[2]=0.0; if (vector_len(p)<=0.5*b) sel=0; // X
p[1]=o[1]+l[1]-0.5*a; p[0]=o[0]; p[2]=o[2]; project(p,mmvp,p); p[0]-=mx; p[1]-=my; p[2]=0.0; if (vector_len(p)<=0.5*b) sel=1; // Y
p[2]=o[2]+l[2]-0.5*a; p[1]=o[1]; p[0]=o[0]; project(p,mmvp,p); p[0]-=mx; p[1]-=my; p[2]=0.0; if (vector_len(p)<=0.5*b) sel=2; // Z
_redraw=(sel0!=sel);
}
// drag arrowhead center into mouse position (active button)
if ((ql)&&(sel>=0)&&(sel<3))
{
int i;
double len0,len;
double p0[3],dp[3]={0.0,0.0,0.0},t,dt,q[3]={mx,my,0.0};
// selected arrowhead position,direction
for (i=0;i<3;i++) p0[i]=o[i];
p0[sel]+=l[sel]-0.5*a;
dp[sel]=1.0;
// "closest" intersection between axis/mouse_ray
for (len0=-1.0,t=0.0,dt=1.0;fabs(dt)>1e-5;t+=dt)
{
// position on axis p(t) = p0 + t*dp
for (i=0;i<3;i++) p[i]=p0[i]+(t*dp[i]);
// len = distance to mouse
project(p,mmvp,p);
vector_sub(p,p,q); p[2]=0.0;
len=vector_len(p);
// handle iteration step
if (len0<-0.5) len0=len;
if (len>len0) dt=-0.1*dt;
len0=len;
}
// translate by t
double m[16]=
{
1.0,0.0,0.0,0.0,
0.0,1.0,0.0,0.0,
0.0,0.0,1.0,0.0,
0.0,0.0,0.0,1.0,
};
m[12+sel]=t;
matrix_mul(mm,m,mm);
_redraw=true;
}
return _redraw;
}
};
//---------------------------------------------------------------------------
DragDrop3D ctrl; // I got single cube so single arrow drag drop control suffice
//---------------------------------------------------------------------------
void gl_draw() // main rendering code
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glEnable(GL_CULL_FACE);
glEnable(GL_LIGHTING);
glEnable(GL_LIGHT0);
glEnable(GL_COLOR_MATERIAL);

// init
static bool _init=true;
if (_init)
{
_init=false;
// M,V init matrices
glMatrixMode(GL_MODELVIEW);
glPushMatrix();
glLoadIdentity();
glTranslated(0.0,0.0,-25.0);
glGetDoublev(GL_MODELVIEW_MATRIX,mview);
glLoadIdentity();
glTranslated(-2.0,-1.0,-1.0);
glRotated(-35.0,0.0,1.0,0.0);
glGetDoublev(GL_MODELVIEW_MATRIX,mmodel);
glPopMatrix();
// matrices -> ctrl
glGetDoublev(GL_PROJECTION_MATRIX,mproj);
ctrl.mp=mproj;
ctrl.mv=mview;
ctrl.mm=mmodel;
}
// matrices -> OpenGL
glMatrixMode(GL_MODELVIEW);
glLoadMatrixd(mview);
glMultMatrixd(mmodel);

// draw VAO cube
ctrl.draw();
vao_draw(); // here render your object instead

glFlush();
SwapBuffers(hdc);
}
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner):TForm(Owner)
{
// application init
gl_init(Handle);
vao_init();
}
//---------------------------------------------------------------------------
void __fastcall TForm1::FormDestroy(TObject *Sender)
{
// application exit
gl_exit();
vao_exit();
}
//---------------------------------------------------------------------------
void __fastcall TForm1::FormResize(TObject *Sender)
{
// window resize
gl_resize(ClientWidth,ClientHeight);
}
//---------------------------------------------------------------------------
void __fastcall TForm1::FormPaint(TObject *Sender)
{
// window repaint
gl_draw();
}
//---------------------------------------------------------------------------
void __fastcall TForm1::FormMouseWheel(TObject *Sender, TShiftState Shift, int WheelDelta, TPoint &MousePos, bool &Handled)
{
// mouse wheel translates camera (like zoom)
GLfloat dz=2.0;
if (WheelDelta<0) dz=-dz;
glMatrixMode(GL_MODELVIEW);
glPushMatrix();
glLoadMatrixd(mview);
glTranslatef(0,0,dz);
glGetDoublev(GL_MODELVIEW_MATRIX,mview);
glPopMatrix();
gl_draw();
}
//---------------------------------------------------------------------------
// mouse events
void __fastcall TForm1::FormMouseDown(TObject *Sender, TMouseButton Button,TShiftState Shift, int X, int Y) { if (ctrl.mouse(X,Y,Shift)) gl_draw(); }
void __fastcall TForm1::FormMouseUp(TObject *Sender, TMouseButton Button,TShiftState Shift, int X, int Y) { if (ctrl.mouse(X,Y,Shift)) gl_draw(); }
void __fastcall TForm1::FormMouseMove(TObject *Sender, TShiftState Shift, int X, int Y) { if (ctrl.mouse(X,Y,Shift)) gl_draw(); }
//---------------------------------------------------------------------------

It is based on this: simple complete GL+VAO/VBO+GLSL+shaders example in C++ where you will find more info and the gl_simple.h I am using for this. The code is VCL based so ignore the VCL stuff and port the events to your kind of programming.

The program is using just OpenGL 1.0 API (appart of the VAO/VBO cube representing my object) so it does not need anything apart math.h for sin,cos,fabs functions.

The idea is to have one control DragDrop3D object per each controlable object in the world/scene. Init each first and then call mouse for each on every mouse event and redraw if needed.

Here small preview:

preview

Sadly my GIF capture SW does not catch mouse cursor but it matches the movement exactly.

I was too lazy to compute the intersection between axises directly instead I iterate to find closest match (that for loop with dt). That part could be replaced by intersection equation.

The axises are bounded to the x,y,z axises of the object coordinate system (mm,mmodel).

For more objects you should also add locks so only one object can be selected ...

PS. see related:
- OpenGL ray OBB intersection

Bleed Area Works with Objects Moved via Mouse Drag, but not Keyboard's Arrow Keys

You need to fire the object:moving event manually.

activeCanvas.trigger('object:moving', {
target: activeObject
})

Check out the demo (also on jsFiddle):

var activeCanvas, front, back, canvas1, canvas2;$(document).ready(function() {
canvas1 = new fabric.Canvas('front'); canvas2 = new fabric.Canvas('back'); canvas1.setHeight(360); canvas1.setWidth(208); canvas2.setHeight(360); canvas2.setWidth(208); changeView(1);
var padding = 20; canvas1.on('object:moving', onObjectMoving); canvas2.on('object:moving', onObjectMoving);
function onObjectMoving(e) { var obj = e.target;
// if object is too big ignore if (obj.currentHeight > obj.canvas.height - padding * 2 || obj.currentWidth > obj.canvas.width - padding * 2) { return; } obj.setCoords();
// top-left corner if (obj.getBoundingRect().top < padding || obj.getBoundingRect().left < padding) { obj.top = Math.max(obj.top, obj.top - obj.getBoundingRect().top + padding); obj.left = Math.max(obj.left, obj.left - obj.getBoundingRect().left + padding); }
// bot-right corner if (obj.getBoundingRect().top + obj.getBoundingRect().height > obj.canvas.height - padding || obj.getBoundingRect().left + obj.getBoundingRect().width > obj.canvas.width - padding) { obj.top = Math.min( obj.top, obj.canvas.height - obj.getBoundingRect().height + obj.top - obj.getBoundingRect().top - padding); obj.left = Math.min( obj.left, obj.canvas.width - obj.getBoundingRect().width + obj.left - obj.getBoundingRect().left - padding); } };});
function changeView(value) { if (value == 1) { activeCanvas = canvas1; $('#front').parent().css('display', 'block'); $('#back').parent().css('display', 'none'); }
if (value == 2) { activeCanvas = canvas2; $('#front').parent().css('display', 'none'); $('#back').parent().css('display', 'block'); }}
function dropText() { var text = new fabric.Text('test'); activeCanvas.add(text);}
// Keyboard Movementconst STEP = 1;
var Direction = { LEFT: 0, UP: 1, RIGHT: 2, DOWN: 3};
fabric.util.addListener(document.body, 'keydown', function(options) { if (options.repeat) { return; } var key = options.which || options.keyCode; // key detection if (key === 37) { // handle Left key moveSelected(Direction.LEFT); } else if (key === 38) { // handle Up key moveSelected(Direction.UP); } else if (key === 39) { // handle Right key moveSelected(Direction.RIGHT); } else if (key === 40) { // handle Down key moveSelected(Direction.DOWN); }});
function moveSelected(direction) {
var activeObject = activeCanvas.getActiveObject(); var activeGroup = activeCanvas.getActiveGroup();
if (activeObject) { switch (direction) { case Direction.LEFT: activeObject.setLeft(activeObject.getLeft() - STEP); break; case Direction.UP: activeObject.setTop(activeObject.getTop() - STEP); break; case Direction.RIGHT: activeObject.setLeft(activeObject.getLeft() + STEP); break; case Direction.DOWN: activeObject.setTop(activeObject.getTop() + STEP); break; } activeObject.setCoords(); activeCanvas.trigger('object:moving', { target: activeObject }) activeCanvas.renderAll(); console.log('selected objects was moved'); } else if (activeGroup) { switch (direction) { case Direction.LEFT: activeGroup.setLeft(activeGroup.getLeft() - STEP); break; case Direction.UP: activeGroup.setTop(activeGroup.getTop() - STEP); break; case Direction.RIGHT: activeGroup.setLeft(activeGroup.getLeft() + STEP); break; case Direction.DOWN: activeGroup.setTop(activeGroup.getTop() + STEP); break; } activeGroup.setCoords(); activeCanvas.trigger('object:moving', { target: activeGroup }) activeCanvas.renderAll(); console.log('selected group was moved'); } else { console.log('no object selected'); }}
canvas {  border: 1px solid #dddddd;  margin-top: 10px;  border-radius: 3px;}
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/1.7.20/fabric.min.js"></script><script src="https://code.jquery.com/jquery-3.2.1.slim.min.js"></script><button onclick="changeView(1);">Front</button><button onclick="changeView(2);">Back</button><button onclick="dropText();">Add Text</button>
<div style="text-align: center"> <canvas id="front"></canvas> <canvas id="back"></canvas></div>

How to implement interactive rotation operations in a decent way

When dragging the mouse, the object must be rotated around an axis that is perpendicular to the direction of movement of the mouse. The pivot is the origin of the model.

Sample Image

Rotate the mouse movement vector by 90 ° in the XY plane of the view. Since this is a vector in view space, the vector must be transformed from view space into world space. The matrix that transforms a vector from view space to world space is the inverse matrix of the upper left 3x3 of the view matrix:

vec2 drag_start;
vec2 drag_end;
glm::mat3 to_world = glm::inverse(glm::mat3(view_matrix));
glm::vec2 drag_vec = glm::vec2(drag_end.x - drag_start.x, drag_start.y - drag_end.y);
glm::vec3 axis_vec = glm::normalize(to_world * glm::vec3(-drag_vec.y, drag_vec.x, 0));

Create a rotation matrix around the axis. The angle depends on the length of the vector (height is the height of the viewport in pixels):

GLfloat angle = glm::length(drag_vec) / height / 2 * M_PI;
drag_rotation = glm::rotate(glm::mat4(1.0f), angle, axis_vec);

Compute a rotation matrix while dragging the mouse. Concatenate the rotation matrix and the model matrix after the drag ends:

glm::mat4 view_matrix(1.0f);
glm::mat4 model_rotation(1.0f);
glm::mat4 drag_rotation(1.0f);
glm::vec2 drag_start(0.0f);
bool drag = false;

void mouse_button_callback(GLFWwindow* window, int button, int action, int mods)
{
if (button != GLFW_MOUSE_BUTTON_LEFT)
return;

if (action == GLFW_PRESS)
{
drag = true;
double xpos, ypos;
glfwGetCursorPos(window, &xpos, &ypos);
drag_start = glm::vec2(xpos, ypos);
}
else if (action == GLFW_RELEASE)
{
drag = false;
model_rotation = drag_rotation * model_rotation;
drag_rotation = glm::mat4(1.0f);
}
}

void cursor_position_callback(GLFWwindow* window, double xpos, double ypos)
{
if (!drag)
return;

glm::mat3 to_world = glm::inverse(glm::mat3(view_matrix));
glm::vec2 drag_vec = glm::vec2(xpos - drag_start.x, drag_start.y - ypos);

glm::vec3 axis_vec = glm::normalize(to_world * glm::vec3(-drag_vec.y, drag_vec.x, 0));
GLfloat angle = glm::length(drag_vec) / height / 2 * M_PI;

drag_rotation = glm::rotate(glm::mat4(1.0f), angle, axis_vec);
}

The model matrix is the concatenation of drag_rotation and model_rotation:

glm::mat4 model = drag_rotation * model_rotation;

See also Orbit


Complete example:

Sample Image

#include <GL/glew.h>
#include <GL/gl.h>
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>
#include <GLFW/glfw3.h>
#include <vector>
#include <string>
#include <stdexcept>
#include <iostream>
#define _USE_MATH_DEFINES
#include <cmath>
#include <math.h>
#ifndef M_PI
#define M_PI 3.14159265358979323846
#endif

std::string sh_vert = R"(
#version 460 core

layout (location = 0) in vec4 a_position;
layout (location = 1) in vec3 a_uvw;

out vec3 v_uvw;

layout (location = 0) uniform mat4 u_projection;
layout (location = 1) uniform mat4 u_view;
layout (location = 2) uniform mat4 u_model;

void main()
{
v_uvw = a_uvw;
gl_Position = u_projection * u_view * u_model * a_position;
}
)";

std::string sh_frag = R"(
#version 460 core

out vec4 frag_color;
in vec3 v_uvw;

vec3 HUEtoRGB(in float H)
{
float R = abs(H * 6.0 - 3.0) - 1.0;
float G = 2.0 - abs(H * 6.0 - 2.0);
float B = 2.0 - abs(H * 6.0 - 4.0);
return clamp(vec3(R, G, B), 0.0, 1.0);
}

void main()
{
frag_color = vec4(HUEtoRGB(v_uvw.z), 1.0);
}
)";

class ShaderProgram
{
public:
GLuint programObject;
static ShaderProgram newProgram(const std::string& vsh, const std::string& fsh);
private:
GLuint compileShader(const std::string& sourceCode, GLenum shaderType);
void linkProgram(std::vector<GLuint> shObjs);
void compileStatus(GLuint shader);
void linkStatus();
};

class VertexArrayObject
{
public:
GLuint vaoObject = 0;
GLsizei noOfVertices = 0;
GLsizei noOfIndices = 0;
static VertexArrayObject newCube();
static VertexArrayObject newCircles();
static VertexArrayObject newVAO(const std::vector<GLfloat>& varray, const std::vector<GLuint>& iarray);
};

int width = 800, height = 600;
glm::mat4 view_matrix(1.0f);
glm::mat4 model_rotation(1.0f);
glm::mat4 drag_rotation(1.0f);
glm::vec2 drag_start(0.0f);
bool drag = false;

void mouse_button_callback(GLFWwindow* window, int button, int action, int mods)
{
if (button != GLFW_MOUSE_BUTTON_LEFT)
return;

if (action == GLFW_PRESS)
{
drag = true;
double xpos, ypos;
glfwGetCursorPos(window, &xpos, &ypos);
drag_start = glm::vec2(xpos, ypos);
}
else if (action == GLFW_RELEASE)
{
drag = false;
model_rotation = drag_rotation * model_rotation;
drag_rotation = glm::mat4(1.0f);
}
}

void cursor_position_callback(GLFWwindow* window, double xpos, double ypos)
{
if (!drag)
return;

glm::mat3 to_world = glm::inverse(glm::mat3(view_matrix));
glm::vec2 drag_vec = glm::vec2(xpos - drag_start.x, drag_start.y - ypos);

glm::vec3 axis_vec = glm::normalize(to_world * glm::vec3(-drag_vec.y, drag_vec.x, 0));
GLfloat angle = glm::length(drag_vec) / height / 2 * M_PI;

drag_rotation = glm::rotate(glm::mat4(1.0f), angle, axis_vec);
}

int main(void)
{
if (glfwInit() == GLFW_FALSE)
throw std::runtime_error( "error initializing glfw" );

glfwWindowHint(GLFW_SAMPLES, 8);
GLFWwindow * window = glfwCreateWindow(width, height, "OGL window", nullptr, nullptr);
if (window == nullptr)
{
glfwTerminate();
throw std::runtime_error( "error initializing window" );
}
glfwSetMouseButtonCallback(window, mouse_button_callback);
glfwSetCursorPosCallback(window, cursor_position_callback);
glfwMakeContextCurrent(window);

if ( glewInit() != GLEW_OK )
throw std::runtime_error( "error initializing glew" );

auto progam = ShaderProgram::newProgram(sh_vert, sh_frag);
auto cube = VertexArrayObject::newCube();
auto circles = VertexArrayObject::newCircles();
glUseProgram(progam.programObject);
glEnable( GL_DEPTH_TEST );
glClearColor(0.1f, 0.3f, 0.2f, 0.0f);

view_matrix = glm::lookAt(glm::vec3(0.0f, 0.0f, 7.0f), glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 1.0f, 0.0f));
glUniformMatrix4fv(1, 1, GL_FALSE, glm::value_ptr(view_matrix));

while (!glfwWindowShouldClose(window))
{
glfwGetFramebufferSize(window, &width, &height);

float ascpect = (float)width / (float)height;
glm::mat4 project = glm::perspective(glm::radians(60.0f), ascpect, 0.1f, 20.0f);
glUniformMatrix4fv(0, 1, GL_FALSE, glm::value_ptr(project));
glm::mat4 model = drag_rotation * model_rotation;

glViewport(0, 0, width, height);
glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );

glUniformMatrix4fv(2, 1, GL_FALSE, glm::value_ptr(model));
glBindVertexArray(cube.vaoObject);
glDrawElements(GL_TRIANGLES, cube.noOfIndices, GL_UNSIGNED_INT, nullptr);

glUniformMatrix4fv(2, 1, GL_FALSE, glm::value_ptr(glm::scale(model, glm::vec3(2.5f))));
glBindVertexArray(circles.vaoObject);
glDrawElements(GL_LINES, circles.noOfIndices, GL_UNSIGNED_INT, nullptr);

glfwSwapBuffers(window);
glfwPollEvents();
}

glfwDestroyWindow(window);
glfwTerminate();

return 0;
}

ShaderProgram ShaderProgram::newProgram(const std::string& vsh, const std::string& fsh)
{
ShaderProgram program;
auto shObjs = std::vector<GLuint>
{
program.compileShader(vsh, GL_VERTEX_SHADER),
program.compileShader(fsh, GL_FRAGMENT_SHADER),
};
for (auto shObj : shObjs)
program.compileStatus(shObj);
program.linkProgram(shObjs);
for (auto shObj : shObjs)
glDeleteShader(shObj);
return program;
}

GLuint ShaderProgram::compileShader(const std::string& sourceCode, GLenum shaderType)
{
auto shaderObj = glCreateShader(shaderType);
const char* srcCodePtr = sourceCode.c_str();
glShaderSource(shaderObj, 1, &srcCodePtr, nullptr);
glCompileShader(shaderObj);
return shaderObj;
}

void ShaderProgram::linkProgram(std::vector<GLuint> shObjs)
{
programObject = glCreateProgram();
for (auto shObj : shObjs)
glAttachShader(programObject, shObj);
glLinkProgram(programObject);
linkStatus();
}

void ShaderProgram::compileStatus(GLuint shader)
{
GLint status = GL_TRUE;
glGetShaderiv(shader, GL_COMPILE_STATUS, &status);
if (status == GL_FALSE)
{
GLint logLen;
glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &logLen);
std::vector< char >log(logLen);
GLsizei written;
glGetShaderInfoLog(shader, logLen, &written, log.data());
std::cout << "compile error:" << std::endl << log.data() << std::endl;
}
}

void ShaderProgram::linkStatus()
{
GLint status = GL_TRUE;
glGetProgramiv(programObject, GL_LINK_STATUS, &status);
if (status == GL_FALSE)
{
GLint logLen;
glGetProgramiv(programObject, GL_INFO_LOG_LENGTH, &logLen);
std::vector< char >log(logLen);
GLsizei written;
glGetProgramInfoLog(programObject, logLen, &written, log.data());
std::cout << "link error:" << std::endl << log.data() << std::endl;
}
}

VertexArrayObject VertexArrayObject::newCube()
{
static const std::vector<GLfloat> vertices{ -1, -1, -1, 1, -1, -1, 1, 1, -1, -1, 1, -1, -1, -1, 1, 1, -1, 1, 1, 1, 1, -1, 1, 1 };
static const std::vector<GLfloat> uv{ 0, 0, 1, 0, 1, 1, 0, 1 };
static const std::vector<size_t> faces{ 0, 1, 2, 3, 1, 5, 6, 2, 5, 4, 7, 6, 4, 0, 3, 7, 3, 2, 6, 7, 1, 0, 4, 5 };

std::vector<GLfloat> varray;
std::vector<GLuint> iarray;
for (auto si = 0; si < faces.size() / 4; si++)
{
for (auto qi = 0; qi < 4; qi++)
{
varray.insert(varray.end(), vertices.begin() + faces[si * 4 + qi] * 3, vertices.begin() + faces[si * 4 + qi] * 3 + 3);
std::vector<GLfloat> uvw{ 0, 0, (GLfloat)si * 4.0f / (GLfloat)faces.size() };
varray.insert(varray.end(), uvw.begin(), uvw.end());
}
std::vector<GLuint> indices{ 4u * si, 4u * si + 1, 4u * si + 2, 4u * si, 4u * si + 2, 4u * si + 3 };
iarray.insert(iarray.end(), indices.begin(), indices.end());
}

return newVAO(varray, iarray);
}

VertexArrayObject VertexArrayObject::newCircles()
{
const GLuint noC = 360;
std::vector<GLfloat> varray;
std::vector<GLuint> iarray;

for (int i = 0; i <= noC; i++)
{
GLfloat angle = static_cast<GLfloat>(i * 2 * M_PI / noC);
GLfloat c = cos(angle), s = sin(angle);
std::vector<GLfloat> va{ 0, c, s, 0, 0, 0, s, 0, c, 0, 0, 1.0f / 3.0f, c, s, 0, 0, 0, 2.0f / 3.0f };
varray.insert(varray.end(), va.begin(), va.end());
}


Related Topics



Leave a reply



Submit