Background Info:
I am using OpenGL and LWJGL 3 to draw some quads onto the screen. I need to know when the mouse is over a quad. When I render the quads to the screen, I use the OpenGL coordinates, ranging from -1 to 1 for both X and Y and with (0,0) at the center of the screen. When I get the mouse position I use
glfwSetCursorPosCallback();
which gives me the coordinates ranging from 0 to the width or height of the window and with (0,0) at the top left corner (below the title bar). I then take the mouse coordinate and calculate the OpenGL coordinates.
For example if my window size is (800, 600) and my mouse was at (200, 200) I would get (-0.5, 0.33) [since (400, 300) would map to (0, 0) in OpenGL's coordinates].
So here's my problem:
OpenGL includes the title bar in its coordinates, where as glfwSetCursorPosCallback(); does not. This means that if I render a vertex at (-0.5, 0.33) [like in my example] it renders at around (200, ~210).
As you can see, because the two coordinate systems cover different areas, its more difficult to switch between the coordinate systems.
I have searched for ways to exclude the title bar from OpenGL's coordinates, to completely get rid of the title bar and to get the height of the title bar (so I can include it in my calculations and make the correct adjustments). I haven't been able to figure out how to do any of these, so I'm looking for a way to do so, or a different method that will resolve my problem.
EDIT 1: Adding Code
#Nicol Bolas informed me that this is not how OpenGL normally works so there must be something causing this in my code. I believe I've provided the parts of my code that would be responsible for my problem:
Here is my Renderer class [I am using the drawQuad() method]
Note: I am not currently using the view, model, or projection matrices in my shaders.
public class Renderer {
private VertexArrayObject vao;
private VertexBufferObject vbo;
private ShaderProgram shaderProgram;
private FloatBuffer vertices;
private int numVertices;
private boolean drawing;
//private Font font;
//private Font debugFont;
public void drawQuad(float x, float y, float width, float height, Color c) {
/* Calculate Vertex positions */
float x1 = x;
float y1 = y;
float x2 = x + width;
float y2 = y - height;
/* Calculate color */
float r = c.getRed();
float g = c.getGreen();
float b = c.getBlue();
/* Put data into buffer */
vertices.put(x1).put(y1).put(0.0f).put(r).put(g).put(b);
vertices.put(x1).put(y2).put(0.0f).put(r).put(g).put(b);
vertices.put(x2).put(y2).put(0.0f).put(r).put(g).put(b);
vertices.put(x2).put(y1).put(0.0f).put(r).put(g).put(b);
/* We drawed X vertices */
numVertices += 4;
}
// Initialize renderer
public void init(){
// Set up shader programs
setupShaderProgram();
// Enable blending (?????)
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
}
// Clears drawing area
public void clear() {
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
}
// Begin rendering
public void begin() {
if (drawing) throw new IllegalStateException("Renderer is already drawing.");
drawing = true;
numVertices = 0;
}
// End rendering
public void end() {
if (!drawing) throw new IllegalStateException("Renderer is not drawing.");
drawing = false;
flush();
}
// Flushes data to GPU to get rendered
public void flush() {
if (numVertices > 0) {
vertices.flip();
if (vao != null) vao.bind();
else vbo.bind(GL_ARRAY_BUFFER);
specifyVertexAttributes();
}
shaderProgram.use();
// Upload the new vertex data
vbo.bind(GL_ARRAY_BUFFER);
vbo.uploadSubData(GL_ARRAY_BUFFER, 0, vertices);
// Draw batch
glDrawArrays(GL_QUADS, 0, numVertices);
// Clear vertex data for next batch
vertices.clear();
numVertices = 0;
}
private void setupShaderProgram() {
// Generate VertexArrayObject
if (Game.is32Supported()) {
vao = new VertexArrayObject();
vao.bind();
} else {
throw new RuntimeException("OpenGL 3.2 not supported.");
}
// Generate VertexBufferObject
vbo = new VertexBufferObject();
vbo.bind(GL_ARRAY_BUFFER);
// Create FloatBuffer
vertices = MemoryUtil.memAllocFloat(4096);
// Upload null data to allocate storage for the VBO
long size = vertices.capacity() * Float.BYTES;
vbo.uploadData(GL_ARRAY_BUFFER, size, GL_DYNAMIC_DRAW);
// Initialize variables
numVertices = 0;
drawing = false;
// Load Shaders:
Shader vertexShader, fragmentShader;
if (Game.is32Supported()) {
vertexShader = Shader.loadShader(GL_VERTEX_SHADER, "res/shaders/vshader.vert");
fragmentShader = Shader.loadShader(GL_FRAGMENT_SHADER, "res/shaders/fshader.frag");
} else {
throw new RuntimeException("OpenGL 3.2 not supported.");
}
// Create ShaderProgram
shaderProgram = new ShaderProgram();
shaderProgram.attachShader(vertexShader);
shaderProgram.attachShader(fragmentShader);
if (Game.is32Supported()) {
shaderProgram.bindFragmentDataLocation(0, "fragColor");
}
shaderProgram.link();
shaderProgram.use();
// Delete linked shaders
vertexShader.delete();
fragmentShader.delete();
// Get width & height of framebuffer
long window = GLFW.glfwGetCurrentContext();
int width, height;
try (MemoryStack stack = MemoryStack.stackPush()) {
IntBuffer widthBuffer = stack.mallocInt(1);
IntBuffer heightBuffer = stack.mallocInt(1);
GLFW.glfwGetFramebufferSize(window, widthBuffer, heightBuffer);
width = widthBuffer.get();
height = heightBuffer.get();
}
// Specify vertex pointers
specifyVertexAttributes();
// Set Model Matrix to identity matrix
Matrix4f model = new Matrix4f();
int uniModel = shaderProgram.getUniformLocation("model");
shaderProgram.setUniform(uniModel, model);
// Set View Matrix to identity matrix
Matrix4f view = new Matrix4f();
int uniView = shaderProgram.getUniformLocation("view");
shaderProgram.setUniform(uniView, view);
// Set Projection Matrix to an orthographic projection
Matrix4f projection = Matrix4f.orthographic(0f, width, 0f, height, -1f, 1f);
int uniProjection = shaderProgram.getUniformLocation("projection");
shaderProgram.setUniform(uniProjection, projection);
}
// Specifies the vertex shader pointers (attributes)
private void specifyVertexAttributes() {
int posAttrib = shaderProgram.getAttributeLocation("position");
shaderProgram.enableVertexAttribute(posAttrib);
shaderProgram.pointVertexAttribute(posAttrib, 3, 6 * Float.BYTES, 0);
int colAttrib = shaderProgram.getAttributeLocation("color");
shaderProgram.enableVertexAttribute(colAttrib);
shaderProgram.pointVertexAttribute(colAttrib, 3, 6 * Float.BYTES, 3 * Float.BYTES);
}
}
And here is my init() method that creates and sets up my window:
private void init() {
// Setup an error callback. The default implementation
// will print the error message in System.err.
GLFWErrorCallback.createPrint(System.err).set();
// Initialize GLFW. Most GLFW functions will not work before doing this.
if ( !glfwInit() )
throw new IllegalStateException("Unable to initialize GLFW");
// Configure GLFW
glfwDefaultWindowHints(); // optional, the current window hints are already the default
glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE); // the window will stay hidden after creation
glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); // the window will be resizable
// ONLY ON MAC OSX (?)
//glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); // Tell GLFW to use OpenGL verison 3.x
//glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 2); // Tell GLFW to use OpenGL version x.2 (combined -> 3.2)
//glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
//glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GLFW_TRUE); // Should be forward compatible
// Create the window
window = glfwCreateWindow(WIDTH, HEIGHT, "Game_19_v0.0.1", NULL, NULL);
if ( window == NULL )
throw new RuntimeException("Failed to create the GLFW window");
// Setup a key callback. It will be called every time a key is pressed, repeated or released.
glfwSetKeyCallback(window, (window, key, scancode, action, mods) -> {
if ( key == GLFW_KEY_ESCAPE && action == GLFW_RELEASE )
glfwSetWindowShouldClose(window, true); // We will detect this in the rendering loop
});
// Get the thread stack and push a new frame
try ( MemoryStack stack = stackPush() ) {
IntBuffer pWidth = stack.mallocInt(1); // int*
IntBuffer pHeight = stack.mallocInt(1); // int*
// Get the window size passed to glfwCreateWindow
glfwGetWindowSize(window, pWidth, pHeight);
// Get the resolution of the primary monitor
GLFWVidMode vidmode = glfwGetVideoMode(glfwGetPrimaryMonitor());
// Center the window
glfwSetWindowPos(
window,
(vidmode.width() - pWidth.get(0)) / 2,
(vidmode.height() - pHeight.get(0)) / 2
);
} // the stack frame is popped automatically
// Make the OpenGL context current
glfwMakeContextCurrent(window);
// Enable v-sync
glfwSwapInterval(1);
// Make the window visible
glfwShowWindow(window);
// This line is critical for LWJGL's interoperation with GLFW's
// OpenGL context, or any context that is managed externally.
// LWJGL detects the context that is current in the current thread,
// creates the GLCapabilities instance and makes the OpenGL
// bindings available for use.
GL.createCapabilities();
// Input
glfwSetCursorPosCallback(window, cursorPosCallback = new MouseInput());
// Create renderer
renderer = new Renderer();
renderer.init();
// To Render:
buttonManager = new ButtonManager();
}
EDIT 2: Temporary Solution
I was able to use glfwWindowHint(GLFW_DECORATED, GLFW_FALSE); to remove the entire border from the window, title bar included, which fixed the issue. Now however, I obviously don't have the options to close, minimize, etc., on my window, although I suppose I can program those in myself if necessary. Will update if I find out any other solutions.
GLFW functions typically work with the client area of a window (the inside window area not including titlebars, scrollbars, etc.) so glfwSetCursorPosCallback is giving you the expected values. If your OpenGL framebuffer is for some reason rendering things behind the title bar (whether it's an improper setup or just a platform specific detail) you should still be able to get the title bar size using glfwGetWindowFrameSize:
IntBuffer pLeft = stack.mallocInt(1); // int*
IntBuffer pTop = stack.mallocInt(1); // int*
IntBuffer pRight = stack.mallocInt(1); // int*
IntBuffer pBottom = stack.mallocInt(1); // int*
// Get the window border sizes
glfwGetWindowFrameSize(window, pLeft, pTop, pRight, pBottom);
(Disclaimer: I'm just following the syntax from your code above since I'm only familiar with the C++ API.)
The size of the title bar will be stored in the top variable and can then be added to whatever value you get from glfwSetCursorPosCallback and glfwGetWindowSize.
float adjustedYpos = ypos + top;
float adjustedHeight = height + top;
float normalizedY = adjustedYpos / adjustedHeight;
float openglY = normalizedY * -2.0f - 1.0f
This openglY value should be the OpenGL [-1, 1] clip-space coordinate adjusted based on the title bar size.
Related
I'm trying to stick toolbar buttons to the top-right of my screen, but whenever I resize the window, the buttons stay in the same absolute position (relative to the bottom-left).
Before resize:
After resize:
This is the method that generates the UI button (Which is currently only called in the Screen's constructor):
private void generateUiToolbar(){
// Toolbar Table
Table table = new Table();
table.setFillParent(true);
table.top();
table.right();
// Menu button
table.row();
Texture myTexture = new Texture(Gdx.files.internal("ui/stamp.png"));
TextureRegion myTextureRegion = new TextureRegion(myTexture);
TextureRegionDrawable myTexRegionDrawable = new TextureRegionDrawable(myTextureRegion);
ImageButton menuButton = new ImageButton(myTexRegionDrawable);
menuButton.addListener( new ClickListener() {
#Override
public void clicked(InputEvent event, float x, float y) {
stage.clear();
generateUiMenu();
}
});
table.add(menuButton).size(60,60).padTop(10).padRight(10);
stage.addActor(table);
}
This is the resize() method:
public void resize(int width, int height) {
stage.getViewport().update(width, height, true);
stage.getCamera().viewportWidth = width;
stage.getCamera().viewportHeight = height;
stage.getCamera().position.set(stage.getCamera().viewportWidth / 2, stage.getCamera().viewportHeight / 2, 0);
stage.getCamera().update();
}
The variable stage is initiated in the constructor with stage = new Stage();
For some reason stage.getViewport().update(width, height, true); doesn't do anything. When I print stage.getWidth() after resizing, it will return the same number. What's a good way to update the location of the button or Actors in general? Simply clearing the stage and generating the UI again won't work for me, because sometimes when you resize, there are different actors currently displayed. If I missed any information, please let me know.
EDIT 1:
Added table.setFillParent(true); as mentioned in an answer, but that didn't fix it either.
EDIT 2:
After reading the answers and comments, I deleted the stage.getCamera() manipulation, but I'm still not getting the correct result. This is what happens when I resize with just the line of code stage.getViewport().update(width, height, true);:
I think you misunderstand the usage of Viewport.
In your resize method you do:
stage.getViewport().update(width, height, true);
stage.getCamera().viewportWidth = width;
stage.getCamera().viewportHeight = height;
stage.getCamera().position.set(stage.getCamera().viewportWidth / 2, stage.getCamera().viewportHeight / 2, 0);
stage.getCamera().update();
But stage.getViewport().update(width, height, true); already does what you do in the next four lines.
viewport.update(width, height) set the new width and height to the camera and updates the camera. So:
viewport.update(width, height)
is equal to
camera.viewportWidth = width;
camera.viewportHeight = height;
camera.update();
The only difference is that the viewport looks to the aspect ratio depending on which Viewport you use.
And the third parameter: stage.getViewport().update(width, height, true); is a boolean value which says should the camera be centered. So if the third parameter is true the viewport will do:
camera.position.set(camera.viewportWidth / 2, camera.viewportHeight / 2, 0);
In your resize method you override the work of the viewport because with viewport.update(width, height, true) you already have done all of the resize and you don't need the four other lines.
In the resize method this is enough:
public void resize(int width, int height) {
stage.getViewport().update(width, height, true);
}
You can read this for a better understanding of Viewport: How Camera works in Libgdx and together with Viewport
The Viewport object is a camera frustum manager. When you start modifying the camera's parameters (other than position), you are undoing what you did by calling viewport.update().
If you want the table to automatically fill the screen so stuff in the top right corner stays there after a resize, don't set a width and height on the table. Instead, call table.setFillParent(true) so it will automatically update its size to match the viewport size.
I don't understand how I can simply clear the screen in Java while using OpenGL. I have searched all over the internet, there is like no real good resource for OpenGL information. Basically I just want to clear the screen and re-draw a circle. Instead my code decides that it isn't going to clear the screen ever, and it most definitely isn't going to draw anything else.. I want it to clear the screen when I press "e", and then draw a new circle. I have two java files.. I will only post relevant code for the sake of any user's who can help me - but will post more code if needed.
In the beginning of my JOGLEventListener.java file I'm also declaring a global var
// Test
GLAutoDrawable test = null;
JOGLEventListener.java
#Override
public void display(GLAutoDrawable gLDrawable)
{
// Set a global variable to hold the gLDrawable
// May not need this?
test = gLDrawable;
GL2 gl = gLDrawable.getGL().getGL2();
gl.glClearColor(backrgb[0], 0, 1, 1);
gl.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT);
backrgb[0]+=0.0005;
if (backrgb[0]> 1) backrgb[0] = 0;
// =============================================
// Draw my circle here
//
// =============================================
// =============================================
System.out.println("Drawing Circle..");
drawCircle(5.0f, 5.0f, 10.0f);
}
// Draw Circle
void drawCircle(float x, float y, float radius)
{
System.out.println("IN DRAWCIRCLE");
int i;
GL2 gl = test.getGL().getGL2();
int lineAmount = 100; //# of triangles used to draw circle
final
//GLfloat radius = 0.8f; //radius
float twicePi = (float) (2.0f * Math.PI);
gl.glBegin(gl.GL_LINE_LOOP);
for(i = 0; i <= lineAmount;i++) {
gl.glVertex2f(
x + (radius * (float)Math.cos(i * twicePi / lineAmount)),
y + (radius* (float)Math.sin(i * twicePi / lineAmount))
);
}
gl.glEnd();
}
#Override
public void keyTyped(KeyEvent e)
{
char key= e.getKeyChar();
System.out.printf("Key typed: %c\n", key);
GL2 gl = test.getGL().getGL2();
if(key == 'e')
{
// WHY ISNT THIS WORKING
// CLEAR THE SCREEN AND DRAW ME A NEW CIRCLE
gl.glClear( gl.GL_COLOR_BUFFER_BIT | gl.GL_DEPTH_BUFFER_BIT );
gl.glLoadIdentity();
//test
float x = 100.0f;
float y = 100.0f;
float twicePi = (float) (2.0f * Math.PI);
float radius = 100f;
System.out.println("Draw Another Circle...");
gl.glBegin(gl.GL_LINE_LOOP);
for(int i = 0; i <= 360;i++)
{
gl.glVertex2f(
x + (radius * (float)Math.cos(i * twicePi / 360)),
y + (radius* (float)Math.sin(i * twicePi / 360))
);
}
gl.glEnd();
}
1) That's deprecated OpenGL, don't use it
2) Don't save the gl object to one global value, always get it from the drawable or the GLContext
3) Use a shader program to render and a vertex buffer to hold the vertices position. But first, I'd suggest you to start a tutorial to learn the basic of OpenGL. Or if you want to get something working asap, clone this hello triangle of mine and start experiment on that
The problem is apparently that you don't swap the front and back buffers.
I'm not familiar with the OpenGL bindings for Java, but I guess that the library already does that for you after it calls the display() function. It doesn't do that after keyTyped().
The way you are supposed to do this is to always draw the scene from scratch inside the display() function based on some internal state. Then in keyTyped() you shall modify that internal state and invalidate the window, which will cause the display() to be called again and redraw the scene properly.
EDIT: Calling display() yourself won't be enough. I can't find how to invalidate the window in Java (in C this would be so much easier). As a dirty hack you can try calling temp.swapBuffers() manually in display, setting setAutoSwapBufferMode(false) and calling display from keyTyped().
I am writing a 2D traditional animation program in Java using swing and JPen. The application works well. However, i am dissatisfied with the results I am getting when I draw lines.
Using the JPen API, the swing panel listens for the stylus input and its code is:
/**
* This method is called whenever the stylus makes contact with the tablet while inside of the JPanel functioning
* as the Drawing Canvas.
* #param evt
*/
public void penLevelEvent(PLevelEvent evt) {
// Get kind of event: does it come from mouse (CURSOR), STYLUS or ERASER?
PKind kind = evt.pen.getKind();
// Discard events from mouse
if (kind == PKind.valueOf(PKind.Type.CURSOR)){
//System.out.println("returning since this is only a mouse cursor");
return;
}
// Get the current cursor location
// position value is in with respect to entire application window
// Get the tilt values (not with a Bamboo... so untested!)
float curX = evt.pen.getLevelValue(PLevel.Type.X);
float curY = evt.pen.getLevelValue(PLevel.Type.Y);
float pressure = evt.pen.getLevelValue(PLevel.Type.PRESSURE);// 0.0 - 1.0
float xTilt = evt.pen.getLevelValue(PLevel.Type.TILT_X);
float yTilt = evt.pen.getLevelValue(PLevel.Type.TILT_Y);
// Set the brush's size, and darkness relative to the pressure
float darkness = 255 * pressure;
// Transform them to azimuthX and altitude, two angles with the projection of the pen against the X-Y plane
// azimuthX is the angle (clockwise direction) between this projection and the X axis. Range: -pi/2 to 3*pi/2.
// altitude is the angle between this projection and the pen itself. Range: 0 to pi/2.
// Might be more pratical to use than raw x/y tilt values.
double[] aa = { 0.0, 0.0 };
PLevel.Type.evalAzimuthXAndAltitude(aa, xTilt, yTilt);
// or just PLevel.Type.evalAzimuthXAndAltitude(aa, evt.pen);
double azimuthX = aa[0];
double altitude = aa[1];
//-------------------------------------------------------------------------------------
// If the stylus is being pressed down, we want to draw a black
// line onto the screen. If it's the eraser, we want to create
// a white line, effectively "erasing" the black line
//-------------------------------------------------------------------------------------
if (kind == PKind.valueOf(PKind.Type.STYLUS)) {
//System.out.println("Darkness "+darkness);
int alpha = 255 - (int)darkness;
color = new Color(0,0,255, 255 - alpha);
}
else if (kind == PKind.valueOf(PKind.Type.ERASER)) {
System.out.println("Handle eraser");
}
else {
return; // IGNORE or CUSTOM...
}
//If movement of the stylus is occuring
if (evt.isMovement()) {
//and the buttonIsDown(boolean)
if(buttonIsDown) {
//drawingCanvas:JPanel -> instruct the jpanel to draw at the following coordinate using the specified pressure
drawingCanvas.stylusMovementInput( prevXPos,prevYPos, curX,curY, pressure);
}
prevXPos = curX;
prevYPos = curY;
}
prevXPos = curX;
prevYPos = curY;
}
So after the above method is invoked, the jpanel(drawingCanvas) starts to draw on a BufferedImage by obtaining the image's graphics2D. Here is the code stylusMovementInput->calls -> performDrawOnBufferImageGraphic2D :
/**
* Draw on the active frame that is selected. Then call channel refresh, to refresh the the composite image derived
* from call changes related to the current frame
* #param cX current
* #param cY current
* #param oX previous
* #param oY previous
* #param pressure pressure 0 - 1f
*/
private void performDrawOnBufferImageGraphic2D(float oX, float oY, float cX, float cY, float pressure){
//Obtain the current layer that user wants to draw one
//MyImageData is encapsulating a BufferedImage
MyImageData $activeData = getActiveLayer();
//Exit if one is not valid
if( $activeData == null) return;
//if valid layer, get the, get the bufferedImage.getGraphics
Graphics2D $activeDataGFX = $activeData.getImageGraphics();
// Customize the drawing brush (create a BasicStroke)
Stroke thickness = Sys.makeStroke(getPencilSize(pressure), null);
// Determine the tranparency with respect to the pressure
int alpha = (int)(255 * pressure * getPencilOpacityPercentage());
// Get the current color found in the color wheel
Color cwVal = Sys.getColorFromColorWheel();
Color drawingColor ;
if(cwVal != null){
// add alpha value to it
drawingColor = new Color(cwVal.getRed(), cwVal.getGreen(), cwVal.getBlue(), alpha);
}else throw new RuntimeException("ColorWheel is null drawing stylus draw");
//set the brush and drawingColor
$activeDataGFX.setStroke(thickness);
// Save reference to the current bufferedImage graphic component
Composite originalComposite =$activeDataGFX.getComposite();
if(getCurrentTool() == DrawingCanvasTool.ERASER){
//If eraser, set new composite information, to allow erasing to transparency
$activeDataGFX.setPaint( new Color(255,255,255, 0));
$activeDataGFX.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_IN, 0.0f));
}else {
//set the drawing color
$activeDataGFX.setPaint(drawingColor);
}
//---------------------------------------------------------------
// Rotate, Translate, Zoom the image according to the panning, zoom, and rotate
// set by the user
//---------------------------------------------------------------
//Figure out the canvas center, as it is used for rotating
float theta = (float)Math.toRadians(canvasRotation);
Dimension drawingAreaComponentSize = getSize();
float centerX = drawingAreaComponentSize.width/2;
float centerY = drawingAreaComponentSize.height/2;
AffineTransform transform = new AffineTransform();
transform.rotate(-theta, centerX, centerY);
transform.scale(1.0f / canvasZoom, 1.0f / canvasZoom);//erase
transform.translate(-canvasPan.x, -canvasPan.y);//erase
$activeDataGFX.setTransform(transform);
//Now Draw inside of the active data graphics object
Shape line = new Line2D.Float(cX,cY, oX, oY);
Path2D.Float t = new Path2D.Float(line);
$activeDataGFX.draw(t);
//drawing is complete
if(getCurrentTool() ==DrawingCanvasTool.ERASER){
//Restore the old composite object
$activeDataGFX.setComposite(originalComposite);
}
//Refresh basically merges frames along a frame column into a single preview buffered image
//which will later be used to view the tool animation when user clicks "play" button
channelPannel.refreshFrameOut( channelPannel.getCurrentFrame() );
}
I commented a lot of the code, and provided the critical points related to the question. Any help is much appreciated. And again, the problem is how do i draw a smooth line worthy of a descent drawing program.
This is what I want to achieve: I want to draw the global axes (only the positive semiaxes) in a 3D processing sketch as x: red, y: green, g: blue. Then I want to place the camera at xyz coordinates (20,20,30), and have it look at xyz (0,0,0), such that the camera's Up vector is (nearly) colinear with the global z axis. Thus, in the end I want to see red axis (x) to the left, green axis (y) to the right, and blue axis (z) pointing upward - and then I want to have a mouse interaction like PeasyCam, but in respect to this orientation.
Because of Rotating camera around object axis with Peasycam, I know PeasyCam cannot really do something like this, so I tried to use OCD: Obsessive Camera Direction. Below is an MWE which uses it, and emulates some of the PeasyCam mouse interaction.
The problem is this: regardless of how I set the Up vector in the ODC Camera constructor, I get pretty much the same behavior when dragging (the white circle on the gif indicates the mouse position):
Clearly, regardless of how the Up vector is set, the rendering shows the green vector for y pointing downwards.
Actually, if one looks at the updateUp() function in ocd/src/damkjer/ocd/Camera.java, one can see that the originally set Up components are overwritten based on the camera and target location, so I guess, no wonder then why the sketch doesn't react differently on them; the only thing able to change the Up vector is seemingly the roll paramerer, set via roll() method.
My question is: what can I do in the code below, to achieve what I want (camera dragging interaction, but where the 0,0,1 vector is rendered/remains upwards)?
Here is the MWE code, Sketch.pde:
// modification of example on http://mrfeinberg.com/peasycam/
// https://stackoverflow.com/questions/17683602/rotating-camera-around-object-axis-with-peasycam/26755516#26755516
// sdaau, 2014
import damkjer.ocd.*;
DCamera cam1; //Camera cam1; // (see subclass below)
int saveCount=500;
void setup() {
// Setup graphics
size(300, 200, P3D);
// only .roll() seems to be able to manipulate up vector?
cam1 = new DCamera(this, //Camera(this, // (parent,
40, 40, 60, // cameraX, cameraY, cameraZ,
0, 0, 0, // targetX, targetY, targetZ
// 0, 0, 1, // upX, upY, upZ // (seems ignored)
// 0, 1, 0, // upX, upY, upZ // (seems ignored)
1, 0, 0, // upX, upY, upZ // (seems ignored)
10, 500 // nearClip, farClip) //(doesn't clip as peasycam!)
);
//~ cam1.roll(radians(-90));
}
void draw() {
cam1.feed(); //"send what this camera sees to the view port"
// actual drawing:
background(0);
stroke(255,0,0); line(0,0,0, 1000,0,0); // x axis
stroke(0,255,0); line(0,0,0, 0,1000,0); // ... y
stroke(0,0,255); line(0,0,0, 0,0,1000); // ... z
fill(255,0,0);
box(30);
pushMatrix();
translate(0,0,20);
fill(0,0,255);
box(5);
popMatrix();
fill(-1);
// text("U 0,0,1", -20, 30, 6);
// text("U 0,1,0", -20, 30, 6);
text("U 1,0,0", -20, 30, 6);
if (mouseButton == LEFT) {
hint(DISABLE_DEPTH_TEST);
camera(); // must have after disable for 2D draw
ellipse(mouseX,mouseY,20,20);
saveFrame( "images/image_" + saveCount + ".png" );
saveCount++;
hint(ENABLE_DEPTH_TEST);
}
}
// this to emulate peasycam:
// these to replicate the peasycam interaction with OCD:
void mouseDragged() {
if (mouseButton == LEFT) {
// http://www.airtightinteractive.com/demos/processing/bezier_ribbon_p3d/BezierRibbons.pde
cam1.arc(radians(-(mouseY - pmouseY))/4);
cam1.circle(radians(-(mouseX - pmouseX))/4);
} else if (mouseButton == RIGHT) {
cam1.zoom(radians(mouseY - pmouseY) / 2.0);
} else if (mouseButton == CENTER) {
// peasycam calls this .pan(); damkjer.ocd calls it .track()
cam1.track(-(mouseX - pmouseX), -(mouseY - pmouseY));
}
}
void mouseWheel(MouseEvent event) {
float e = event.getCount();
cam1.zoom(e*4.0);
}
public class DCamera extends damkjer.ocd.Camera {
// private final PApplet p; // in peasycam/src/peasy/PeasyCam.java; in ocd/src/damkjer/ocd/Camera.java it is called theParent! both are private!
private PApplet theParent; // replicate as in ocd/.../Camera.java; it helps, even if super has same name (must re-assign in ctor)
// directly from libraries/ocd/src/damkjer/ocd/Camera.java
public DCamera(PApplet aParent,
float aCameraX, float aCameraY, float aCameraZ,
float aTargetX, float aTargetY, float aTargetZ,
float aNearClip, float aFarClip)
{
super(aParent, aCameraX, aCameraY, aCameraZ, aTargetX, aTargetY, aTargetZ, aNearClip, aFarClip);
theParent = aParent;
}
// another constructor, to handle up vector:
public DCamera(PApplet aParent,
float aCameraX, float aCameraY, float aCameraZ,
float aTargetX, float aTargetY, float aTargetZ,
float anUpX, float anUpY, float anUpZ,
float aNearClip, float aFarClip)
{
super(aParent, aCameraX, aCameraY, aCameraZ, aTargetX, aTargetY, aTargetZ, anUpX, anUpY, anUpZ, aNearClip, aFarClip);
theParent = aParent;
}
}
/*
# https://stackoverflow.com/questions/3323619/how-to-sort-files-numerically-from-linux-command-line
# https://stackoverflow.com/questions/246215/how-can-i-list-files-with-their-absolute-path-in-linux
convert -delay 5 -loop 0 $(ls ./images/ | sort --version-sort -f) animate.gif
gifsicle -O2 --colors 8 animate.gif -o animate-O2.gif
*/
Well, I decided to try ProScene (Javadocs: proscene API), and it turns out it does what I want:
... that is, it can start with the z (blue) vector up - and then the mouse can be used to "turn around" that axis (approximately).
ProScene's default interaction is otherwise nearly the same as PeasyCam (mousewheel is opposite, though), so for this purpose, I think I've found a solution (though it would be great to know if the same is achievable with OCD).
Here is the code, Sketch.pde:
// modification of example on http://mrfeinberg.com/peasycam/
// http://stackoverflow.com/questions/17683602/rotating-camera-around-object-axis-with-peasycam/26755516#26755516
// sdaau, 2014
import remixlab.proscene.*;
import remixlab.dandelion.geom.*; // Vec
Scene scene;
int saveCount=500;
//Choose one of P3D for a 3D scene, or P2D or JAVA2D for a 2D scene
String renderer = P3D;
void setup() {
// Setup graphics
size(300, 200, renderer);
//Scene instantiation
scene = new Scene(this);
scene.setGridVisualHint(false);
scene.setAxesVisualHint(false);
// specify starting camera position, orientation and target
scene.camera().setUpVector(new Vec(0, 0, -1), true); // boolean noMove
scene.camera().setPosition(new Vec(30, 30, 50));
scene.camera().lookAt(new Vec(0, 0, 0));
// when damping friction = 0 -> spin
scene.eye().frame().setDampingFriction(0);
}
void draw() {
// actual drawing:
background(0);
stroke(255,0,0); line(0,0,0, 1000,0,0); // x axis
stroke(0,255,0); line(0,0,0, 0,1000,0); // ... y
stroke(0,0,255); line(0,0,0, 0,0,1000); // ... z
fill(255,0,0);
box(30);
pushMatrix();
translate(0,0,20);
fill(0,0,255);
box(5);
popMatrix();
fill(-1);
text("U 0,0,-1", -20, 30, 6);
if (mouseButton == LEFT) {
hint(DISABLE_DEPTH_TEST);
camera(); // must have after disable for 2D draw
ellipse(mouseX,mouseY,20,20);
// saveFrame( "images/image_" + saveCount + ".png" );
saveCount++;
hint(ENABLE_DEPTH_TEST);
}
}
void keyPressed() {
if(scene.eye().frame().dampingFriction() == 0)
scene.eye().frame().setDampingFriction(0.5);
else
scene.eye().frame().setDampingFriction(0);
println("Camera damping friction now is " + scene.eye().frame().dampingFriction());
}
/*
# http://stackoverflow.com/questions/3323619/how-to-sort-files-numerically-from-linux-command-line
# http://stackoverflow.com/questions/246215/how-can-i-list-files-with-their-absolute-path-in-linux
convert -delay 5 -loop 0 $(ls ./images/ | sort --version-sort -f) animate.gif
gifsicle -O2 --colors 8 animate.gif -o animate-O2.gif
*/
I'm using Java OpenGL (JOGL 2.x, built from Git source). I'm rendering my scene to a framebuffer object with a color and a depth attachment. I'd like to convert the [0,1] depth buffer values into world-space distances. My depth attachment is defined as follows:
private void setupDepthFBOs(GL2 gl,
int width,
int height,
int[] frameBufferIds,
int[] colorBufferIds,
int[] depthBufferIds) {
// based on
// http://www.java2s.com/Code/Java/Advanced-Graphics/BloomOpenGL.htm
// generate a framebuffer object
gl.glGenFramebuffers(1, frameBufferIds, 0);
// bind the framebuffer
gl.glBindFramebuffer(GL.GL_FRAMEBUFFER, frameBufferIds[0]);
// generate a texture in memory
gl.glGenTextures(1, colorBufferIds,0);
gl.glBindTexture(GL2.GL_TEXTURE_2D, colorBufferIds[0]);
// this will be an RGBA texture (4 bpp) with width, height..
gl.glTexImage2D(GL2.GL_TEXTURE_2D, // target texture type
0, // mipmap LOD level
GL2.GL_RGBA8, // internal pixel format
width, // width of generated image
height, // height of generated image
0, // border of image
GL2.GL_RGBA, // external pixel format
GL2.GL_UNSIGNED_BYTE, // datatype for each value
null); // buffer to store the texture in memory
// set some texture parameters?
gl.glTexParameteri(GL.GL_TEXTURE_2D,
GL.GL_TEXTURE_MIN_FILTER, GL.GL_LINEAR);
// use the texture we just created in the framebuffer we just created
gl.glFramebufferTexture2D(
GL2.GL_FRAMEBUFFER, // target texture type
GL.GL_COLOR_ATTACHMENT0, // attachment point
GL.GL_TEXTURE_2D, // texture target type
colorBufferIds[0], // on-gpu id for texture
0); // mipmap lod level
gl.glGenTextures(1,depthBufferIds,0);
gl.glBindTexture(GL.GL_TEXTURE_2D,depthBufferIds[0]);
gl.glTexImage2D(GL2.GL_TEXTURE_2D, // target texture type
0, // mipmap LOD level
GL2.GL_DEPTH_COMPONENT24, // internal pixel format
//GL2.GL_DEPTH_COMPONENT
width, // width of generated image
height, // height of generated image
0, // border of image
GL2.GL_DEPTH_COMPONENT, // external pixel format
GL2.GL_UNSIGNED_INT, // datatype for each value
null); // buffer to store the texture in memory
gl.glTexParameteri(GL.GL_TEXTURE_2D,
GL.GL_TEXTURE_MIN_FILTER, GL.GL_NEAREST);
gl.glTexParameteri(GL.GL_TEXTURE_2D,
GL.GL_TEXTURE_MAG_FILTER, GL.GL_NEAREST);
gl.glTexParameteri(GL.GL_TEXTURE_2D,
GL.GL_TEXTURE_WRAP_S, GL.GL_CLAMP_TO_EDGE);
gl.glTexParameteri(GL.GL_TEXTURE_2D,
GL.GL_TEXTURE_WRAP_T, GL.GL_CLAMP_TO_EDGE);
gl.glFramebufferTexture2D(GL.GL_FRAMEBUFFER,
GL.GL_DEPTH_ATTACHMENT,
GL.GL_TEXTURE_2D,
depthBufferIds[0],0);
gl.glBindTexture(GL.GL_TEXTURE_2D, 0);
int status = gl.glCheckFramebufferStatus(GL2.GL_FRAMEBUFFER);
if (status == GL2.GL_FRAMEBUFFER_COMPLETE) {
gl.glBindFramebuffer(GL.GL_FRAMEBUFFER, 0);
} else {
throw new IllegalStateException("Frame Buffer Object not created. Status was: " + status);
}
}
This successfully creates a depth buffer, which I can read as a texture and render to the screen, or use as input to a shader (my intended eventual use case).
After some discussions on the IRC a few nights ago, I came up with the following formula relating the Projection Matrix (denoted here as p) and depth-buffer value to the distance to each point on screen in world-space:
z = (p_33)/(p_34 + depth)
(note: my projection matrix/eye is set up looking in the Z+ direction)
This produces almost-sane z-values, but there's a significant margin of error between the distance to known points in the scene, and the value returned by this equation.
Any ideas what I might be doing wrong here?
Here are my calculations, my result is different to yours:
Defined:
depth = out_z / out_w
out_z = in_z * p_33 + in_w * p_43
out_w = in_z * p_34 + in_w * p_44
Known:
in_w = 1, p_43 = -1, p_44 = 0
Working:
depth = (in_z * p_33 - 1) / (in_z * p_34)
depth = p_33 / p_34 - 1 / (in_z * p_34)
p_33 / p_34 - depth = 1 / (in_z * p_34)
1 / (p_33 / p_34 - depth) = in_z * p_34
1 / (p_33 / p_34 - depth) = in_z * p_34
1 / (p_33 - depth * p_34) = in_z