I succeed installing Box2D into my project. But how can I render a body? Assume I'm using something that supports drawing polygons. I just want to find out the current positions of the vertices of the body-polygon, to draw it with the engine.
If you can help me, I will be very thankful.
I found it!!!
void Box2DUtils::DrawBody(SDL_Surface *buffer, b2Body *body, int fr, int fg, int fb, int falpha, int lr, int lg, int lb, int lalpha, bool aa)
{
const b2Transform& xf = body->GetTransform();
for (b2Fixture* f = body->GetFixtureList(); f; f = f->GetNext())
{
switch (f->GetType())
{
case b2Shape::e_circle:
{
b2CircleShape* circle = (b2CircleShape*) f->GetShape();
b2Vec2 center = b2Mul(xf, circle->m_p);
float32 radius = circle->m_radius;
b2Vec2 axis = xf.R.col1;
//m_debugDraw->DrawSolidCircle(center, radius, axis, color);
if (falpha > 0)
{
filledCircleRGBA(buffer, center.x, center.y, (int) radius, fr, fg, fb, falpha);
}
if (lalpha > 0)
{
if (aa)
{
aacircleRGBA(buffer, center.x, center.y, (int) radius, lr, lg, lb, lalpha);
} else
{
aacircleRGBA(buffer, center.x, center.y, (int) radius, lr, lg, lb, lalpha);
}
} else if (aa)
{
aacircleRGBA(buffer, center.x, center.y, (int) radius, fr, fg, fb, falpha);
}
}
break;
case b2Shape::e_polygon:
{
b2PolygonShape* poly = (b2PolygonShape*) f->GetShape();
int32 vertexCount = poly->m_vertexCount;
b2Assert(vertexCount <= b2_maxPolygonVertices);
b2Vec2 vertices[b2_maxPolygonVertices];
Sint16 xv[b2_maxPolygonVertices];
Sint16 yv[b2_maxPolygonVertices];
for (int32 i = 0; i < vertexCount; ++i)
{
vertices[i] = b2Mul(xf, poly->m_vertices[i]);
xv[i] = (int) vertices[i].x;
yv[i] = (int) vertices[i].y;
}
if (falpha > 0)
{
filledPolygonRGBA(buffer, xv, yv, (Sint16) vertexCount, fr, fg, fb, falpha);
}
if (lalpha > 0)
{
if (aa)
{
aapolygonRGBA(buffer, xv, yv, (Sint16) vertexCount, lr, lg, lb, lalpha);
} else
{
polygonRGBA(buffer, xv, yv, (Sint16) vertexCount, lr, lg, lb, lalpha);
}
} else if (aa)
{
aapolygonRGBA(buffer, xv, yv, (Sint16) vertexCount, fr, fg, fb, falpha);
}
//m_debugDraw->DrawSolidPolygon(vertices, vertexCount, color);
}
break;
}
}
}
The Box2D manual refers to a HelloWorld project that is bundled in the download. The same documentation also goes through it step by step. Quoting the manual:
The program
creates a large ground box and a small
dynamic box. This code does not
contain any graphics. All you will see
is text output in the console of the
box's position over time.
If you haven't got a rectangle to work, this should help you get started.
You should use World->SetDebugDraw(&myDebugDraw) and set the appropriate drawing flags to render the various aspects of the physics world (shapes, joints, center of gravity, etc.) Drawing flags are set via myDebugDraw.SetDebugFlags(flags).
myDebugDraw is an instance of b2Draw (b2DebugDraw in versions 2.1.2 and prior) which most "platforms" have a readily available implementation. During your rendering callback, use World->DrawDebugData() and the appropriate content will be drawn via your instance of myDebugData.
Hope this wasn't to confusing given the shortness of the post. All this is covered in the Box2d documentation.
Related
I am using a while loop, rotate and translate in order to get the effect I want for my program. I want to be able to contain the loop within the boundaries of the sketch. Can anyone explain to me how that can be done, please?
Here is the code:
float x, y, r, g, b, radius;
void setup()
{
size(800, 700);
smooth();
frameRate(15);
}
void draw()
{
handleRedBox();
}
void handleRedBox() {
background(255);
stroke(255, 0, 0);
color from = color(100, random(255), 2);
color to = color(0, 200, 0);
color interA = lerpColor (to, from, .44);
int x = 100;
while (x < width/2 || x> width/2 ) {
int y = 100;
while (y <height/2 || y > height/2) {
blendMode(DIFFERENCE);
noStroke();
fill(interA);
quadstuff();
strokeWeight(5);
stroke(0, random(255), 0);
line(width/2, height/2, mouseY, mouseX);
translate(width, height);
rotate(radians(frameCount));
y = y + 50;
}
x = x + 50;
}
ghostcirc();
ghostcirc2();
}
void ghostcirc() {
int w = 0;
while (w < width) {
int q = 0;
while (q <height) {
blendMode(ADD);
fill(random(61), random(90), random(250));
ellipse(255, 255, 100, 100);
;
noStroke();
translate(width, height);
rotate(radians(frameCount));
q = q + 100;
}
w = w + 50;
}
}
void ghostcirc2() {
for (int w= 0; w < width; w+=10) {
blendMode(ADD);
fill(random(61), random(90), random(250));
ellipse(50, 50, 75, 75);
;
noStroke();
translate(width, height);
rotate(radians(frameCount));
//if (keyPressed == true){
// fill(random(100), random(90), random(250));
}
}
void quadstuff() {
int rad = 60; // Width of the shape
float xpos, ypos; // Starting position of shape
float xspeed = 2.8; // Speed of the shape
float yspeed = 2.2; // Speed of the shape
xpos = width/2;
ypos = height/2;
//ellipse(mouseX+x, mouseY+y, 100,100);
quad(xpos, ypos, rad, rad, mouseX+rad, mouseY+rad, xspeed, yspeed);
stroke(0);
strokeWeight(5);
}
Your question is still pretty broad, and that's still a lot of code to try to debug. But I appreciate that you went through the trouble of narrowing it down, so I'm going to try to help in general terms.
Your code involves a lot of stuff that I don't really understand, so I'm going to start with a simpler example. Honestly you might be better off doing the same- start over with something more basic, and add the bounding logic from the beginning. That's going to be much easier than trying to add it in after you've already written everything.
So, there are two main ways to do this type of animation in Processing. I'll cover both.
Option 1: Rely on translate() and rotate() to position stuff for you.
This is what your code does. Here is a simpler example that shows an ellipse rotating around the mouse position:
float angle = 0;
void setup() {
size(500, 500);
}
void draw() {
angle+=.1;
background(0);
translate(mouseX, mouseY);
rotate(angle);
translate(100, 0);
ellipse(0, 0, 50, 50);
}
Now, if you want to bound the ellipse to stay inside the window, first you need to determine where the ellipse will be on the screen. This could be complicated since we're using the translate() and rotate() functions, which are a little like moving the camera instead of moving the ellipse- the ellipse "thinks" it's still at position 0,0. So we need to get the position of the ellipse after we move the camera. Luckily Processing gives us the screenX() and screenY() functions:
float screenX = screenX(0, 0);
float screenY = screenY(0, 0);
This will tell us where on the screen the ellipse will be drawn (or more accurately, where position 0,0 will be after the transforms are applied). We can use this to check whether these go outside the bounds of the window, and then do whatever bounding you want.
Exactly what type of bounding you do depends on what you want to happen. You could wrap the animation around the screen so that when it goes off the right side it reappears on the left side. You could limit the positions so they only go to the border of the window instead of moving past it. Here is that:
float angle = 0;
void setup() {
size(500, 500);
}
void draw() {
angle+=.1;
background(0);
translate(mouseX, mouseY);
rotate(angle);
translate(100, 0);
float screenX = screenX(0, 0);
float screenY = screenY(0, 0);
if (screenX < 25) {
rotate(-angle);
translate(25-screenX, 0);
rotate(angle);
} else if (screenX > 475) {
rotate(-angle);
translate(475-screenX, 0);
rotate(angle);
}
if (screenY < 25) {
rotate(-angle);
translate(0, 25-screenY);
rotate(angle);
} else if (screenY > 475) {
rotate(-angle);
translate(0, 475-screenY);
rotate(angle);
}
ellipse(0, 0, 50, 50);
}
This code is the same as above, except now it uses screenX() and screenY() to determine when the ellipse will be off the screen, and then uses translate() to move it back inside the bounds of the screen.
Option 2: Keep track of the position yourself.
Instead of relying on translate() and rotate() to do the positioning for you, you could also just use some basic algebra and trig to do the positioning yourself.
Here is the simple program, without bounding yet:
float angle = 0;
void setup() {
size(500, 500);
}
void draw() {
angle+=.1;
background(0);
float circleX = mouseX + cos(angle)*100;
float circleY = mouseY + sin(angle)*100;
ellipse(circleX, circleY, 50, 50);
}
Notice that I'm calculating the position of the ellipse myself instead of relying on translate() and rotate(). Now it might be easier to think about exactly where the circle will be, so I can do the bounding:
float angle = 0;
void setup() {
size(500, 500);
}
void draw() {
angle+=.1;
background(0);
float circleX = mouseX + cos(angle)*100;
float circleY = mouseY + sin(angle)*100;
if (circleX < 25) {
circleX = 25;
} else if (circleX > 475) {
circleX = 475;
}
if (circleY < 25) {
circleY = 25;
} else if (circleY > 475) {
circleY = 475;
}
ellipse(circleX, circleY, 50, 50);
}
This might be a little easier to think about, since you can work with the screen coordinates directly. Both options do the same thing, they're just different ways of thinking about it.
From here it's just a matter of defining exactly how your bounding should work. I've given you one example, but you could do anything you want.
You might also want to restrict your input variables (in my case, mouseX and mouseY) so the animation never leaves the window. Adding this at the top of the draw() function of either one of the above options will prevent the animation from leaving the screen:
if (mouseX < 150) {
mouseX = 150;
} else if (mouseX > 350) {
mouseX = 350;
}
if (mouseY < 150) {
mouseY = 150;
} else if (mouseY > 350) {
mouseY = 350;
}
Again, how you do this is really up to you and what you want to happen. It will probably be easier if you start over with a basic program like mine and then add one small thing at a time instead of trying to add it to your existing huge sketch. Good luck.
I want to do infinite zoom on this fractal. but after few translation on z axis, the image dis appears.
zoom keys are f5 and f6. press shift for fast zoom. f7 is for detailing.
Plz check what i am doing wrong.
// mandelbrot.cpp : Defines the entry point for the console application.
#include "stdafx.h"
int _tmain(int argc, _TCHAR* argv[])
{
return 0;
}
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <glut.h>
float dx=0,dy=0,dz=1.72;
double deltax = 4/300,g=0.01;//this means 4/300
double deltay = 4/300;// this means 4/300
double dividecubesby = 700;
double left = -2.0;
double right = 2.0;
double bottom = -2.0;
double top = 2.0;
int maxiteration = 90;
float c1=0.5,c2=0.5,c3=0.5;
int mandtest(double Cr, double Ci)
{
double Zr = 0.0;
double Zi = 0.0;
int times = 0;
Zr = Zr+Cr;
Zi = Zi+Ci;
while ((((Zr*Zr)+(Zi*Zi))<4) && (times < maxiteration))
{
double temp = (Zr*Zr)-(Zi*Zi);
Zi = 2*Zr*Zi;
Zr = temp+Cr;
Zi = Zi+Ci;
times = times+1;
}
return times;
}
void display(void)
{
glLoadIdentity();
glViewport(0,0,700,700);
glOrtho(-2.0, 2.0, -2.0, 2.0,-1,1);
glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE | GLUT_DEPTH);
glClear(GL_COLOR_BUFFER_BIT);
glColor3f(1.0, 1.0, 1.0);
glTranslated(dx,dy,dz);
double real = left;//this is the real part of the order-pair in the cube
double image = top;// this is the image part of the order-pair in the cube
double deltax = ((right - left)/(dividecubesby));//this means 4/300
double deltay = ((top- bottom)/(dividecubesby));// this means 4/300
glBegin(GL_POINTS);
for(double x= left;x<=right;x += g )
{
for(double y= bottom; y<=top;y += g )
{
if((mandtest(x,y))==maxiteration)
{
glColor3f(c1,c2,c3);
glVertex2f(x,y);
}
else
{
glColor3f((float)mandtest(x,y)/maxiteration,0,(float)mandtest(x,y)/maxiteration);
glVertex2f(x,y);
}
}
}
glEnd();
glFlush();
}
void init(void)
{
glClearColor(0.0, 0.0, 0.0, 0.0);
glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE | GLUT_DEPTH);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluPerspective(45, 700/700, -100,0);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glOrtho(-2.0, 2.0, -2.0, 2.0,-1,1);
}
void processSpecialKeys(int key, int x, int y)
{
int mod = glutGetModifiers();
if(key == GLUT_KEY_F1)
{
if (mod == GLUT_ACTIVE_SHIFT)
maxiteration=maxiteration+20;
maxiteration=maxiteration+5;
dividecubesby+=100;
}
if(key == GLUT_KEY_F2)
{
if (mod == GLUT_ACTIVE_SHIFT)
maxiteration=maxiteration-20;
maxiteration=maxiteration-5;
deltax=deltax*5;
deltay=deltay*5;
dividecubesby-=100;
}
if(key == GLUT_KEY_UP)
{
if (mod == GLUT_ACTIVE_SHIFT)
{
dy+=.5;top-=0.5;bottom-=0.5;
}
dy+=0.01;
top-=0.01;
bottom-=0.01;
}
if(key == GLUT_KEY_DOWN)
{
if (mod == GLUT_ACTIVE_SHIFT)
{
dy-=.5;top+=0.5;bottom+=.5;
}
dy-=0.01;
top+=0.01;
bottom+=.01;
}
if(key == GLUT_KEY_LEFT)
{
if (mod == GLUT_ACTIVE_SHIFT)
{
dx-=.5;right+=0.5;left+=0.5;
}
dx-=0.01;
right+=0.01;
left+=0.01;
}
if(key == GLUT_KEY_RIGHT)
{
if (mod == GLUT_ACTIVE_SHIFT)
{
dx+=.5;right-=0.5;left-=0.5;
}
dx+=0.01;
right-=0.01;
left-=0.01;
}
if(key == GLUT_KEY_F5)
{
if (mod == GLUT_ACTIVE_SHIFT)
{
dz+=1;left-=1.2;
right+=1.2;
top+=1.2;
bottom-=1.2;
}
dz+=.01;
left-=0.01099;
right+=0.01099;
top+=0.01099;
bottom-=.01099;
}
if(key == GLUT_KEY_F6) {
if (mod == GLUT_ACTIVE_SHIFT)
{
dz-=1;left+=1.2;
right-=1.2;
top-=1.2;
bottom+=1.2;
}
dz-=.01;
left+=0.01099;
right-=0.01099;
top-=0.01099;
bottom+=.01099;
}
if(key == GLUT_KEY_F4)
{
if (mod == GLUT_ACTIVE_ALT)
exit(0);
}
if(key == GLUT_KEY_F9)
{
c1+=0.01;
}
if(key == GLUT_KEY_F10)
{
c1-=0.01;
}
if(key == GLUT_KEY_F11)
{
c2+=0.01;
}
if(key == GLUT_KEY_F12)
{
c2-=0.01;
}
if(key == GLUT_KEY_F7)
{
g=g/2;
}
if(key == GLUT_KEY_F8)
{
g=g*2;
}
glutPostRedisplay();
}
void processNormalKeys(unsigned char key, int x, int y)
{
if (key == 27) exit(0);
}
int main(int argc, char ** argv)
{
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB);
glutInitWindowSize(dividecubesby,dividecubesby);
glutCreateWindow("Mandelbrot Set");
init();
glutDisplayFunc(display);
glutSpecialFunc(processSpecialKeys);
glutKeyboardFunc(processNormalKeys);
glutMainLoop();
return 0;
}
You're almost certainly running out of bits of precision - double has 53 bits, enough to zoom in to at most about 2^-43 or 1e-13 in a 1024px window - beyond that you don't even have enough bits to distinguish the C values for different pixels. This leads to grainy rectangles when you zoom in "too far".
You might look at libqd or libmpfr or other higher precision numerical libraries, which give you floating point with more bits. They're quite a lot slower though.
I'm working on a custom geometry library adapted to Quartz Composer and trying to draw some concave polygons in a Plug In.
I implemented the poly2tri library, so the user can choose to triangulate or not, but it's not suitable for a per-frame polygon transformations rendering.
I'm a noob in OpenGL and I've been reading and testing a lot, about stencil buffer and odd/even operations, but even code that seem to work for other people, doesn't work for me.
The render context is a CGLContextObj, and I'm working on a MacBook Pro Retina Display, with NVidia GEForce GT650. I read that all configurations don't have stencil buffers, but it look like it works sometimes, though not as I would like it to.
I was wondering if someone with the same kind of config was using a code that works and could take a look at my code. In particular, I'm curious too about the number of passes requested, according to the number of vertices or "convexity defects" I guess...
I took my infos from :
http://fly.cc.fer.hr/~unreal/theredbook/chapter13.html
http://commaexcess.com/articles/7/concave-polygon-triangulation-shortcut
http://graphicsbb.itgo.com/solutions/extrude.html
http://analysesmusings.wordpress.com/2012/07/13/drawing-filled-concave-polygons-using-the-stencil-buffer/
... but still not clear...
Here is my code (one of them in fact, as I tested so much configurations) and a picture of the result. Actually I use to put the actual rendering in a method called for each polygon, but I rewrote it to be much clear :
EDIT
In fact I understood that I have to draw each triangle, in order to invert the bit value in the stencil buffer. So I rewrote my code into this :
CGLContextObj cgl_ctx = [context CGLContextObj];
CGLLockContext(cgl_ctx);
GLenum error;
if(cgl_ctx == NULL)
return NO;
glPushAttrib(GL_ALL_ATTRIB_BITS);
glPushClientAttrib(GL_CLIENT_ALL_ATTRIB_BITS);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glDisable(GL_CULL_FACE);
glClear(GL_STENCIL_BUFFER_BIT);
glClearStencil(0);
glEnable(GL_STENCIL_TEST);
glStencilOp(GL_INVERT, GL_INVERT, GL_INVERT);
glStencilFunc(GL_ALWAYS, 1, 1);
glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
// glColor4d(1., 1., 1., 1.); ----> does it make sense ?
glBegin(GL_TRIANGLE_FAN); {
for (int i = 1; i < [vertices count] - 1; i++) {
// Allways drawing the first vertex
glVertex2d([[[vertices objectAtIndex:0] objectAtIndex:0] doubleValue], [[[vertices objectAtIndex:0] objectAtIndex:1] doubleValue]);
// Then two others to make a triangle
glVertex2d([[[vertices objectAtIndex:i] objectAtIndex:0] doubleValue], [[[vertices objectAtIndex:i] objectAtIndex:1] doubleValue]);
glVertex2d([[[vertices objectAtIndex:i+1] objectAtIndex:0] doubleValue], [[[vertices objectAtIndex:i+1] objectAtIndex:1] doubleValue]);
}
}
glEnd();
glStencilOp(GL_ZERO, GL_ZERO, GL_ZERO);
glStencilFunc(GL_EQUAL, 1, 1);
glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
glColor4d(1., 0., 0., 0.5);
glBegin(GL_TRIANGLE_FAN); {
for (id vertex in vertices) {
glVertex2d([[vertex objectAtIndex:0] doubleValue], [[vertex objectAtIndex:1] doubleValue]);
}
glVertex2d([[[vertices objectAtIndex:0] objectAtIndex:0] doubleValue], [[[vertices objectAtIndex:0] objectAtIndex:1] doubleValue]);
}
glEnd();
glDisable (GL_STENCIL_TEST);
glDisable(GL_BLEND);
glPopClientAttrib();
glPopAttrib();
if((error = glGetError()))
NSLog(#"OpenGL error %04X", error);
CGLUnlockContext(cgl_ctx);
return (error ? NO : YES);
But it still doesn't work. Here is my result and the link to the original image and the explanation.
http://what-when-how.com/opengl-programming-guide/drawing-filled-concave-polygons-using-the-stencil-buffer-opengl-programming/
EDIT 2 :
In fact, the context enabled by Quartz Composer doesn't implement a stencil buffer. It seems impossible to render directly in OpenGL with the stencil buffer.
...
glClearStencil(0);
...
Be aware that glClearStencil() just sets a bit of state and doesn't actually clear the stencil buffer.
Try adding a glClear( GL_STENCIL_BUFFER_BIT ) somewhere before each polygon.
EDIT: Like this:
#include <GL/glut.h>
#include <glm/glm.hpp>
#include <glm/gtc/type_ptr.hpp>
#include <vector>
std::vector< glm::vec2 > pts;
bool leftHeld = true;
glm::vec2* dragPt = NULL;
void mouse( int button, int state, int x, int y )
{
glm::vec2 pt( x, glutGet( GLUT_WINDOW_HEIGHT ) - y );
// left mouse button starts dragging a point
dragPt = NULL;
leftHeld = false;
if( button == GLUT_LEFT_BUTTON && state == GLUT_DOWN )
{
leftHeld = true;
size_t minIdx = 0;
for( size_t i = 0; i < pts.size(); ++i )
{
float newDist = glm::distance( pt, pts[ i ] );
float oldDist = glm::distance( pt, pts[ minIdx ] );
if( newDist <= oldDist && newDist < 15.0f )
{
minIdx = i;
dragPt = &pts[ minIdx ];
}
}
}
// middle mouse button clears all points
if( button == GLUT_MIDDLE_BUTTON && state == GLUT_UP )
{
pts.clear();
}
// right mouse button adds a point
if( button == GLUT_RIGHT_BUTTON && state == GLUT_UP )
{
pts.push_back( pt );
}
glutPostRedisplay();
}
void motion( int x, int y )
{
glm::vec2 pt( x, glutGet( GLUT_WINDOW_HEIGHT ) - y );
if( dragPt && leftHeld )
{
*dragPt = pt;
glutPostRedisplay();
}
}
void glLine( const std::vector< glm::vec2 >& line, GLenum mode )
{
glBegin( mode );
for( size_t i = 0; i < line.size(); ++i )
{
glVertex2f( line[i].x, line[i].y );
}
glEnd();
}
void display()
{
glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
glMatrixMode( GL_PROJECTION );
glLoadIdentity();
double w = glutGet( GLUT_WINDOW_WIDTH );
double h = glutGet( GLUT_WINDOW_HEIGHT );
glOrtho( 0, w, 0, h, -1, 1 );
glMatrixMode( GL_MODELVIEW );
glLoadIdentity();
// draw polygon
glClear( GL_STENCIL_BUFFER_BIT );
{
// fill stencil buffer
glEnable( GL_STENCIL_TEST );
glColorMask( GL_FALSE,GL_FALSE,GL_FALSE,GL_FALSE );
glStencilOp( GL_KEEP, GL_KEEP, GL_INVERT );
glStencilFunc( GL_ALWAYS, 0x1, 0x1 );
glBegin( GL_TRIANGLES );
for( size_t i = 1; i+1 < pts.size(); ++i )
{
glVertex2fv( glm::value_ptr( pts[ 0 ] ) );
glVertex2fv( glm::value_ptr( pts[ i ] ) );
glVertex2fv( glm::value_ptr( pts[ i+1 ] ) );
}
glEnd();
// fill color buffer
glColor3ub( 0, 128, 0 );
glColorMask( GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE );
glStencilFunc( GL_EQUAL, 0x1, 0x1 );
glBegin( GL_TRIANGLES );
for( size_t i = 1; i+1 < pts.size(); ++i )
{
glVertex2fv( glm::value_ptr( pts[ 0 ] ) );
glVertex2fv( glm::value_ptr( pts[ i ] ) );
glVertex2fv( glm::value_ptr( pts[ i+1 ] ) );
}
glEnd();
glDisable( GL_STENCIL_TEST );
}
// draw polygon boundary
glLineWidth( 1 );
glColor3ub( 255, 255, 255 );
glLine( pts, GL_LINE_LOOP );
// draw vertexes
glPointSize( 9 );
glColor3ub( 255, 0, 0 );
glLine( pts, GL_POINTS );
glutSwapBuffers();
}
int main( int argc, char **argv )
{
glutInit( &argc, argv );
glutInitDisplayMode( GLUT_RGBA | GLUT_DEPTH | GLUT_DOUBLE | GLUT_STENCIL );
glutInitWindowSize( 640, 480 );
glutCreateWindow( "GLUT" );
glutMouseFunc( mouse );
glutMotionFunc( motion );
glutDisplayFunc( display );
glutMainLoop();
return 0;
}
I am trying to develop a logic to recognize a circle which is made by users right hand, I got the code to draw the skeleton and track from the sample code,
private void SensorSkeletonFrameReady(object sender, SkeletonFrameReadyEventArgs e)
{
Skeleton[] skeletons = new Skeleton[0];
using (SkeletonFrame skeletonFrame = e.OpenSkeletonFrame())
{
if (skeletonFrame != null)
{
skeletons = new Skeleton[skeletonFrame.SkeletonArrayLength];
skeletonFrame.CopySkeletonDataTo(skeletons);
}
}
using (DrawingContext dc = this.drawingGroup.Open())
{
// Draw a transparent background to set the render size
dc.DrawRectangle(Brushes.Black, null, new Rect(0.0, 0.0, RenderWidth, RenderHeight));
if (skeletons.Length != 0)
{
foreach (Skeleton skel in skeletons)
{
RenderClippedEdges(skel, dc);
if (skel.TrackingState == SkeletonTrackingState.Tracked)
{
this.DrawBonesAndJoints(skel, dc);
}
else if (skel.TrackingState == SkeletonTrackingState.PositionOnly)
{
dc.DrawEllipse(
this.centerPointBrush,
null,
this.SkeletonPointToScreen(skel.Position),
BodyCenterThickness,
BodyCenterThickness);
}
}
}
// prevent drawing outside of our render area
this.drawingGroup.ClipGeometry = new RectangleGeometry(new Rect(0.0, 0.0, RenderWidth, RenderHeight));
}
}
What I want to do now is to track the coordinates of users right hand for gesture recognition,
Here is how I am planning to get the job done:
Start the gesture
Draw the circled gesture, Make sure to store the coordinates for start and then keep noting the coordinates for every 45 degree shift of the Joint from the start, for 8 octants we will get 8 samples.
For making a decision that a circle was drawn we can just check the relation ship between the eight samples.
Also, in the depthimage I want to show the locus of the drawn gesture, so as the handpoint moves it leaves a trace behind so at the end we will get a figure which was drawn by an user. I have no idea how to achieve this.
Coordinates for each joint are available for each tracked skeleton during each SkeletonFrameReady event. Inside your foreach loop...
foreach (Skeleton skeleton in skeletons) {
// get the joint
Joint rightHand = skeleton.Joints[JointType.HandRight];
// get the individual points of the right hand
double rightX = rightHand.Position.X;
double rightY = rightHand.Position.Y;
double rightZ = rightHand.Position.Z;
}
You can look at the JointType enum to pull out any of the joints and work with the individual coordinates.
To draw your gesture trail you can use the DrawContext you have in your example or use another way to draw a Path onto the visual layer. With your x/y/z values, you would need to scale them to the window coordinates. The "Coding4Fun" library offers a pre-built function to do it; alternatively you can write your own, for example:
private static double ScaleY(Joint joint)
{
double y = ((SystemParameters.PrimaryScreenHeight / 0.4) * -joint.Position.Y) + (SystemParameters.PrimaryScreenHeight / 2);
return y;
}
private static void ScaleXY(Joint shoulderCenter, bool rightHand, Joint joint, out int scaledX, out int scaledY)
{
double screenWidth = SystemParameters.PrimaryScreenWidth;
double x = 0;
double y = ScaleY(joint);
// if rightHand then place shouldCenter on left of screen
// else place shouldCenter on right of screen
if (rightHand)
{
x = (joint.Position.X - shoulderCenter.Position.X) * screenWidth * 2;
}
else
{
x = screenWidth - ((shoulderCenter.Position.X - joint.Position.X) * (screenWidth * 2));
}
if (x < 0)
{
x = 0;
}
else if (x > screenWidth - 5)
{
x = screenWidth - 5;
}
if (y < 0)
{
y = 0;
}
scaledX = (int)x;
scaledY = (int)y;
}
I have been working with Processing and Cinder to modify Kinect input on the fly. However, I would also like to record the full stream (depth+color+accelerometer values, and whatever else is in there). I'm recording so I can try out different effects/treatments on the same material.
Because I am still just learning Cinder and Processing is quite slow/laggy, I have had trouble finding advice on a strategy for capturing the stream - anything (preferably in Cinder, oF, or Processing) would be really helpful.
I've tried both Processing and OpenFrameworks. Processing is slower when displaying both images (depth and colour). OpenFrameworks slows a bit while writing the data to disk, but here's the basic approach:
Setup Openframeworks (open and compile any sample to make sure you're up and running)
Download the ofxKinect addon and copy the example project as described on github.
Once you've got OF and the ofxKinect example running, it's just a matter of adding a few variable to save your data:
In this basic setup, I've created a couple of ofImage instances and a boolean to toggle saving. In the example the depth and RGB buffers are saved into ofxCvGrayscaleImage instances, but I haven't used OF and OpenCV enough to know how to do something as simple as saving an image to disk, which is why I've used two ofImage instances.
I don't know how comfortable you are with Processing, OF, Cinder, so, for arguments' sake I'll assume you know you're way around Processing, but you're still tackling C++.
OF is pretty similar to Processing, but there are a few differences:
In Processing you have variables declaration and they're usage in the same file. In OF you've got a .h file where you declare you're variables and the .cpp file where you initialize and use your variables.
In Processing you have the setup()(initialize variables) and draw()(update variables and draw to screen) methods, while in OF you have setup() (same as in Processing), update() (update variables only, nothing visual) and draw() (draw to screen using updated values)
When working with images, you since you're coding in C++, you need to allocate memory first, as opposed to Processing/Java where you have memory management.
There's more differences that I won'te detail here. Do check out OF for Processing Users on the wiki
Back to the exampleKinect example, here my basic setup:
.h file:
#pragma once
#include "ofMain.h"
#include "ofxOpenCv.h"
#include "ofxKinect.h"
class testApp : public ofBaseApp {
public:
void setup();
void update();
void draw();
void exit();
void drawPointCloud();
void keyPressed (int key);
void mouseMoved(int x, int y );
void mouseDragged(int x, int y, int button);
void mousePressed(int x, int y, int button);
void mouseReleased(int x, int y, int button);
void windowResized(int w, int h);
ofxKinect kinect;
ofxCvColorImage colorImg;
ofxCvGrayscaleImage grayImage;
ofxCvGrayscaleImage grayThresh;
ofxCvGrayscaleImage grayThreshFar;
ofxCvContourFinder contourFinder;
ofImage colorData;//to save RGB data to disk
ofImage grayData;//to save depth data to disk
bool bThreshWithOpenCV;
bool drawPC;
bool saveData;//save to disk toggle
int nearThreshold;
int farThreshold;
int angle;
int pointCloudRotationY;
int saveCount;//counter used for naming 'frames'
};
and the .cpp file:
#include "testApp.h"
//--------------------------------------------------------------
void testApp::setup() {
//kinect.init(true); //shows infrared image
kinect.init();
kinect.setVerbose(true);
kinect.open();
colorImg.allocate(kinect.width, kinect.height);
grayImage.allocate(kinect.width, kinect.height);
grayThresh.allocate(kinect.width, kinect.height);
grayThreshFar.allocate(kinect.width, kinect.height);
//allocate memory for these ofImages which will be saved to disk
colorData.allocate(kinect.width, kinect.height, OF_IMAGE_COLOR);
grayData.allocate(kinect.width, kinect.height, OF_IMAGE_GRAYSCALE);
nearThreshold = 230;
farThreshold = 70;
bThreshWithOpenCV = true;
ofSetFrameRate(60);
// zero the tilt on startup
angle = 0;
kinect.setCameraTiltAngle(angle);
// start from the front
pointCloudRotationY = 180;
drawPC = false;
saveCount = 0;//init frame counter
}
//--------------------------------------------------------------
void testApp::update() {
ofBackground(100, 100, 100);
kinect.update();
if(kinect.isFrameNew()) // there is a new frame and we are connected
{
grayImage.setFromPixels(kinect.getDepthPixels(), kinect.width, kinect.height);
if(saveData){
//if toggled, set depth and rgb pixels to respective ofImage, save to disk and update the 'frame' counter
grayData.setFromPixels(kinect.getDepthPixels(), kinect.width, kinect.height,true);
colorData.setFromPixels(kinect.getCalibratedRGBPixels(), kinect.width, kinect.height,true);
grayData.saveImage("depth"+ofToString(saveCount)+".png");
colorData.saveImage("color"+ofToString(saveCount)+".png");
saveCount++;
}
//we do two thresholds - one for the far plane and one for the near plane
//we then do a cvAnd to get the pixels which are a union of the two thresholds.
if( bThreshWithOpenCV ){
grayThreshFar = grayImage;
grayThresh = grayImage;
grayThresh.threshold(nearThreshold, true);
grayThreshFar.threshold(farThreshold);
cvAnd(grayThresh.getCvImage(), grayThreshFar.getCvImage(), grayImage.getCvImage(), NULL);
}else{
//or we do it ourselves - show people how they can work with the pixels
unsigned char * pix = grayImage.getPixels();
int numPixels = grayImage.getWidth() * grayImage.getHeight();
for(int i = 0; i < numPixels; i++){
if( pix[i] < nearThreshold && pix[i] > farThreshold ){
pix[i] = 255;
}else{
pix[i] = 0;
}
}
}
//update the cv image
grayImage.flagImageChanged();
// find contours which are between the size of 20 pixels and 1/3 the w*h pixels.
// also, find holes is set to true so we will get interior contours as well....
contourFinder.findContours(grayImage, 10, (kinect.width*kinect.height)/2, 20, false);
}
}
//--------------------------------------------------------------
void testApp::draw() {
ofSetColor(255, 255, 255);
if(drawPC){
ofPushMatrix();
ofTranslate(420, 320);
// we need a proper camera class
drawPointCloud();
ofPopMatrix();
}else{
kinect.drawDepth(10, 10, 400, 300);
kinect.draw(420, 10, 400, 300);
grayImage.draw(10, 320, 400, 300);
contourFinder.draw(10, 320, 400, 300);
}
ofSetColor(255, 255, 255);
stringstream reportStream;
reportStream << "accel is: " << ofToString(kinect.getMksAccel().x, 2) << " / "
<< ofToString(kinect.getMksAccel().y, 2) << " / "
<< ofToString(kinect.getMksAccel().z, 2) << endl
<< "press p to switch between images and point cloud, rotate the point cloud with the mouse" << endl
<< "using opencv threshold = " << bThreshWithOpenCV <<" (press spacebar)" << endl
<< "set near threshold " << nearThreshold << " (press: + -)" << endl
<< "set far threshold " << farThreshold << " (press: < >) num blobs found " << contourFinder.nBlobs
<< ", fps: " << ofGetFrameRate() << endl
<< "press c to close the connection and o to open it again, connection is: " << kinect.isConnected() << endl
<< "press s to toggle saving depth and color data. currently saving: " << saveData << endl
<< "press UP and DOWN to change the tilt angle: " << angle << " degrees";
ofDrawBitmapString(reportStream.str(),20,656);
}
void testApp::drawPointCloud() {
ofScale(400, 400, 400);
int w = 640;
int h = 480;
ofRotateY(pointCloudRotationY);
float* distancePixels = kinect.getDistancePixels();
glBegin(GL_POINTS);
int step = 2;
for(int y = 0; y < h; y += step) {
for(int x = 0; x < w; x += step) {
ofPoint cur = kinect.getWorldCoordinateFor(x, y);
ofColor color = kinect.getCalibratedColorAt(x,y);
glColor3ub((unsigned char)color.r,(unsigned char)color.g,(unsigned char)color.b);
glVertex3f(cur.x, cur.y, cur.z);
}
}
glEnd();
}
//--------------------------------------------------------------
void testApp::exit() {
kinect.setCameraTiltAngle(0); // zero the tilt on exit
kinect.close();
}
//--------------------------------------------------------------
void testApp::keyPressed (int key) {
switch (key) {
case ' ':
bThreshWithOpenCV = !bThreshWithOpenCV;
break;
case'p':
drawPC = !drawPC;
break;
case '>':
case '.':
farThreshold ++;
if (farThreshold > 255) farThreshold = 255;
break;
case '<':
case ',':
farThreshold --;
if (farThreshold < 0) farThreshold = 0;
break;
case '+':
case '=':
nearThreshold ++;
if (nearThreshold > 255) nearThreshold = 255;
break;
case '-':
nearThreshold --;
if (nearThreshold < 0) nearThreshold = 0;
break;
case 'w':
kinect.enableDepthNearValueWhite(!kinect.isDepthNearValueWhite());
break;
case 'o':
kinect.setCameraTiltAngle(angle); // go back to prev tilt
kinect.open();
break;
case 'c':
kinect.setCameraTiltAngle(0); // zero the tilt
kinect.close();
break;
case 's'://s to toggle saving data
saveData = !saveData;
break;
case OF_KEY_UP:
angle++;
if(angle>30) angle=30;
kinect.setCameraTiltAngle(angle);
break;
case OF_KEY_DOWN:
angle--;
if(angle<-30) angle=-30;
kinect.setCameraTiltAngle(angle);
break;
}
}
//--------------------------------------------------------------
void testApp::mouseMoved(int x, int y) {
pointCloudRotationY = x;
}
//--------------------------------------------------------------
void testApp::mouseDragged(int x, int y, int button)
{}
//--------------------------------------------------------------
void testApp::mousePressed(int x, int y, int button)
{}
//--------------------------------------------------------------
void testApp::mouseReleased(int x, int y, int button)
{}
//--------------------------------------------------------------
void testApp::windowResized(int w, int h)
{}
This is a very basic setup. Feel free to modify (add tilt angle to the saved data, etc.)
I'm pretty sure there are ways to improve this speedwise (e.g. don't update ofxCvGrayscaleImage instances and don't draw images to screen while saving, or stack a few frames and write them at interval as opposed to on every frame, etc.)
Goodluck