Why does the alpha argument to stroke() cause nothing to be drawn? - java

When I run the following code, my sketch draws as expected:
void draw() {
int[] nextColor = getNextColor();
stroke(nextColor[0], nextColor[1], nextColor[2]);
float[] nextPoint = getNextLocation();
point(nextPoint[0], nextPoint[1]);
}
However, if I add the fourth argument for the alpha transparency value to stroke(), nothing is drawn to the canvas at all:
float alpha = 0.8;
void draw() {
int[] nextColor = getNextColor();
stroke(nextColor[0], nextColor[1], nextColor[2], alpha);
float[] nextPoint = getNextLocation();
point(nextPoint[0], nextPoint[1]);
}
So far I have tried setting the alpha value to 1.0 directly in the argument (rather than using a variable) to be sure that I wasn't accidentally setting it to 0 somewhere. I have also double checked the documentation for stroke() and there is indeed an overridden version matching my arguments.
What am I doing incorrectly?
Here is all of my code in case there is something elsewhere that should be considered. Thank you.
import java.util.Random;
Random generator;
int meanX, stdevX, meanY, stdevY;
int meanR, meanG, meanB, stdevR, stdevG, stdevB;
float alpha = 0.8;
// returns two random numbers (for x, y, coordinates)
float[] getNextLocation() {
float[] retArr = new float[2];
retArr[0] = (float) (generator.nextGaussian() * stdevX + meanX);
retArr[1] = (float) (generator.nextGaussian() * stdevY + meanY);
return retArr;
}
int[] getNextColor() {
int[] retArr = new int[3];
retArr[0] = (int) (generator.nextGaussian() * stdevR + meanR);
retArr[1] = (int) (generator.nextGaussian() * stdevG + meanG);
retArr[2] = (int) (generator.nextGaussian() * stdevB + meanB);
return retArr;
}
void setup() {
background(255);
size(500, 500);
generator = new Random();
strokeWeight(10);
// play around with these
meanX = width/6;
stdevX = width/8;
meanY = height/2;
stdevY = height/30;
meanR = 224;
stdevR = 20;
meanG = 169;
stdevG = 60;
meanB = 20;
stdevB = 5;
}
void draw() {
int[] nextColor = getNextColor();
stroke(nextColor[0], nextColor[1], nextColor[2]);
float[] nextPoint = getNextLocation();
point(nextPoint[0], nextPoint[1]);
}

You are misunderstanding what the value of alpha is.
Alpha means transparency.
Alpha values range from 0 to 255, with 0 being completely transparent (i.e., 0% opaque) and 255 completely opaque (i.e., 100% opaque).
So in your case, when you set your alpha to 0.8, you don't see anything because it's very close to transparent. When you do not set the alpha, it is by default 100% opaque, so you see the drawing.
Take a look here if interested to know more.

Related

How to use AffineTransform with very little coordinates?

I have a set of two dimensions points. Their X and Y are greater than -2 and lesser than 2. Such point could be : (-0.00012 ; 1.2334 ).
I would want to display these points on a graph, using rectangles (a rectangle illustrates a point, and has its coordinates set to its point's ones - moreover, it has a size of 10*10).
Rectangles like (... ; Y) should be displayed above any rectangles like (... ; Y-1) (positive Y direction is up). Thus, I must set the graph's origin not at the top-left hand-corner, but somewhere else.
I'm trying to use Graphics2D's AffineTransform to do that.
I get the minimal value for all the X coordinates
I get the minimal value for all the Y coordinates
I get the maximal value for all the X coordinates
I get the maximal value for all the Y coordinates
I get the distance xmax-xmin and ymax-ymin
Then, I wrote the code I give you below.
Screenshots
Some days ago, using my own method to scale, I had this graph:
(so as I explained, Y are inverted and that's not a good thing)
For the moment, i.e., with the code I give you below, I have only one point that takes all the graph's place! Not good at all.
I would want to have:
(without lines, and without graph's axis. The important here is that points are correctly displayed, according to their coordinates).
Code
To get min and max coordinates value:
x_min = Double.parseDouble((String) list_all_points.get(0).get(0));
x_max = Double.parseDouble((String) list_all_points.get(0).get(0));
y_min = Double.parseDouble((String) list_all_points.get(0).get(1));
y_max = Double.parseDouble((String) list_all_points.get(0).get(1));
for(StorableData s : list_all_points) {
if(Double.parseDouble((String) s.get(0)) < x_min) {
x_min = Double.parseDouble((String) s.get(0));
}
if(Double.parseDouble((String) s.get(0)) > x_max) {
x_max = Double.parseDouble((String) s.get(0));
}
if(Double.parseDouble((String) s.get(1)) < y_min) {
y_min = Double.parseDouble((String) s.get(1));
}
if(Double.parseDouble((String) s.get(1)) > y_max) {
y_max = Double.parseDouble((String) s.get(1));
}
}
To draw a point:
int x, y;
private void drawPoint(Cupple storable_data) {
//x = (int) (storable_data.getNumber(0) * scaling_coef + move_x);
//y = (int) (storable_data.getNumber(1) * scaling_coef + move_y);
x = storable_data.getNumber(0).intValue();
y = storable_data.getNumber(1).intValue();
graphics.fillRect(x, y, 10, 10);
graphics.drawString(storable_data.toString(), x - 5, y - 5);
}
To paint the graph:
#Override
public void paint(Graphics graphics) {
this.graphics = graphics;
Graphics2D graphics_2d = ((Graphics2D) this.graphics);
AffineTransform affine_transform = graphics_2d.getTransform();
affine_transform.scale(getWidth()/(x_max - x_min), getHeight()/(y_max - y_min));
affine_transform.translate(x_min, y_min);
graphics_2d.transform(affine_transform);
for(StorableData storable_data : list_all_points) {
graphics_2d.setColor(Color.WHITE);
this.drawPoint((Cupple) storable_data);
}
I suggest you map each data point to a point on the screen, thus avoiding the following coordinate system pitfalls. Take your list of points and create from them a list of points to draw. Take into account that:
The drawing is pixel-based, so you will want to scale your points (or you would have rectangles 1 to 4 pixels wide...).
You will need to translate all your points because negative values will be outside the boundaries of the component on which you draw.
The direction of the y axis is reversed in the drawing coordinates.
Once that is done, use the new list of points for the drawing and the initial one for calculations. Here is an example:
public class Graph extends JPanel {
private static int gridSize = 6;
private static int scale = 100;
private static int size = gridSize * scale;
private static int translate = size / 2;
private static int pointSize = 10;
List<Point> dataPoints, scaledPoints;
Graph() {
setBackground(Color.WHITE);
// points taken from your example
Point p1 = new Point(-1, -2);
Point p2 = new Point(-1, 0);
Point p3 = new Point(1, 0);
Point p4 = new Point(1, -2);
dataPoints = Arrays.asList(p1, p2, p3, p4);
scaledPoints = dataPoints.stream()
.map(p -> new Point(p.x * scale + translate, -p.y * scale + translate))
.collect(Collectors.toList());
}
#Override
public Dimension getPreferredSize() {
return new Dimension(size, size);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
// draw a grid
for (int i = 0; i < gridSize; i++) {
g2d.drawLine(i * scale, 0, i * scale, size);
g2d.drawLine(0, i * scale, size, i * scale);
}
// draw the rectangle
g2d.setPaint(Color.RED);
g2d.drawPolygon(scaledPoints.stream().mapToInt(p -> p.x).toArray(),
scaledPoints.stream().mapToInt(p -> p.y).toArray(),
scaledPoints.size());
// draw the points
g2d.setPaint(Color.BLUE);
// origin
g2d.fillRect(translate, translate, pointSize, pointSize);
g2d.drawString("(0, 0)", translate, translate);
// data
for (int i = 0; i < dataPoints.size(); i++) {
Point sp = scaledPoints.get(i);
Point dp = dataPoints.get(i);
g2d.fillRect(sp.x, sp.y, pointSize, pointSize);
g2d.drawString("(" + dp.x + ", " + dp.y + ")", sp.x, sp.y);
}
}
public static void main(String[] args) {
JFrame frame = new JFrame();
frame.setContentPane(new Graph());
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
}
And another:
You might want to have the points aligned on the grid intersections and not below and to the right of them. I trust you will figure this one out.
Also, I ordered the points so that drawPolygon will paint the lines in the correct order. If your points are arbitrarily arranged, look for ways to find the outline. If you want lines between all points like in your example, iterate over all combinations of them with drawLine.

Distribute 32bit float over 4 integers (RGBA) in java

1. Consider a 32bit java float sample in (0.0f .. 1.0f) and four 32bit java integers r, g, b, a each in (0 .. 255) in a vector called RGBA.
2. The sample variable contains normalized measurement data that I wish to present in an ImageIcon in the form of a heat map. The target for the final RGBA values is an integer vector that later is passed as pixel data to a java BufferdImage.
3. The constraints are that when sample==0.0f then RGBA==[0,0,0,255] with uniform distribution so that sample==1.0f represents RGBA==[255,0,0,255] and with sample==0.5f is represented by RGBA==[255,255,255,255]. The alpha channel is always 255.
4. So far I have used a static method by dividing the colors into three separate sections R G B while A remain static at 255. Like so
/* BLUE */
if ( sample <= 0.340000f ){
localSample = (sample/(0.340000f/255.000000f));
sourceLinearData[localIndex] = 0; // R
sourceLinearData[localIndex+1] = 0; // G
sourceLinearData[localIndex+2] = Math.round(localSample); // B
}
5. My questions: A) Are there any suitable java api's/libraries that would help me do this? B) If not then I ask for suggestions to a solution.
6. Thoughts: Since each of the R, G, B, A are in (0 .. 255) I assume I can use bytes instead of integers and then possibly shift these bytes into one variable and then extract the float that way. Though I have not had any success with this method so far.
7. EDIT: Adding example heat map
SOLVED: So, like many other things in software development, this question too holds more than a single answer. In my case I wanted the most direct route with the least amount of additional work. Because of that I decided to go with the answer given by #haraldK. This said though, if you are looking for a formal solution with more control, precision and flexibility, the answer provided by #SashaSalauyou is the more correct one.
To elaborate on my comment above. This doesn't give exactly the colors in the map above, but it is pretty close, and extremely simple:
float sample = ...; // [0...1]
float hue = (1 - sample) * .75f; // limit hue to [0...0.75] to avoid color "loop"
int rgb = Color.getHSBColor(hue, 1, 1).getRGB();
If you want darker tints in "edges" of the scale, you could use a sine function to compute the brightness, for example:
float hue = (1 - sample) * .75f;
float brightness = .5f + (float) Math.sin(Math.PI * sample) / 2;
int rgb = Color.getHSBColor(hue, 1, brightness).getRGB();
I suggest some kind of interpolation in a path that value from 0 to 1 performs in 3D color space:
// black: c = 0.0
// blue: c = 0.3
// white: c = 0.5
// red: c = 1.0
// add more color mappings if needed, keeping c[] sorted
static float[] c = {0.0, 0.3, 0.5, 1.0};
static int[] r = { 0, 0, 255, 255}; // red components
static int[] g = { 0, 0, 255, 0}; // green components
static int[] b = { 0, 255, 255, 0}; // blue components
public int[] getColor(float f) {
int i = 0;
while (c[i] < f)
i++;
if (i == 0)
return new int[] {r[0], g[0], b[0]};
// interpolate
float k = (f - c[i-1]) / (c[i] - c[i-1]);
return new int[] {
Math.round((r[i] - r[i-1]) * k + r[i-1]),
Math.round((g[i] - g[i-1]) * k + g[i-1]),
Math.round((b[i] - b[i-1]) * k + b[i-1])
}
}
}
Lerp should do the trick:
public static void main(String[] args) {
float value = 0.5f;
float[] red = new float[] {1.0f, 0, 0};
float[] white = new float[] {1.0f, 1.0f, 1.0f};
float[] black = new float[] {0, 0, 0};
if (value <= 0.5f) {
float gradientValue = value * 2;
int[] color = new int[] {(int) (lerp(white[0], black[0], gradientValue) * 255), (int) (lerp(white[1], black[1], gradientValue) * 255),
(int) (lerp(white[2], black[2], gradientValue) * 255), 255};
} else if (value > 0.5f) {
float gradientValue = (value + 1) / 2.0f;
int[] color = new int[] {(int) (lerp(white[0], red[0], gradientValue) * 255), (int) (lerp(white[1], red[1], gradientValue) * 255),
(int) (lerp(white[2], red[2], gradientValue) * 255), 255};
}
}
public static float lerp(float v0, float v1, float t) {
return (1-t)*v0 + t*v1;
}
(the order of the lerp arguments might be different, I haven't tested)

Emulate PhotoShop's “Color Range” with Processing

I'm trying to emulate Photoshop's "Color Range" using Processing. I've found some useful information about the algorithm here: Emulate PhotoShop's "Color Range" algorithm
But my problem is to implement it into a processing sketch, as I'm new to Java.
I've managed to do all the calculations but I don't know how to apply them, eg. I would like to change the saturation to the pixels in that range. How do I do that?
By the way here's my code (there's probably something wrong).
// Declaring variables
PImage img;
void setup() {
size(800,800);
colorMode(HSB, 360, 100, 100);
//making the window resizable
// if (frame != null) {
// frame.setResizable(true);
// }
img = loadImage("scanned_texture.jpg");
}
void draw() {
loadPixels();
img.loadPixels();
img.resize(800,800);
//loop through every pixel
for (int x = 0; x < img.width; x++) {
for (int y = 0; y < img.height; y++ ) {
// Calculate the 1D pixel location
int loc = x + y*img.width;
// Get the H,S,B values from image
float h = hue (img.pixels[loc]);
float s = saturation (img.pixels[loc]);
float b = brightness (img.pixels[loc]);;
float Point1hue = hue(27);
float Point1sat = saturation(50);
float Point1bright = brightness(80);
float Point2hue = hue(57);
float Point2sat = saturation(80);
float Point2bright = brightness(100);
//distance between colors
float distance = sqrt(pow((Point2hue - Point1hue),2) + pow((Point2sat - Point1sat),2) + pow((Point2bright - Point1bright),2));
float percentage = distance/sqrt((255)^2+(255)^2+(255)^2);
color c = color(h,s,b);
//change saturation based on color range
if (c == percentage) {
s = +50;
}
pixels[loc] = c;
}
}
updatePixels();
}

Hough circle detection accuracy very low

I am trying to detect a circular shape from an image which appears to have very good definition. I do realize that part of the circle is missing but from what I've read about the Hough transform it doesn't seem like that should cause the problem I'm experiencing.
Input:
Output:
Code:
// Read the image
Mat src = Highgui.imread("input.png");
// Convert it to gray
Mat src_gray = new Mat();
Imgproc.cvtColor(src, src_gray, Imgproc.COLOR_BGR2GRAY);
// Reduce the noise so we avoid false circle detection
//Imgproc.GaussianBlur( src_gray, src_gray, new Size(9, 9), 2, 2 );
Mat circles = new Mat();
/// Apply the Hough Transform to find the circles
Imgproc.HoughCircles(src_gray, circles, Imgproc.CV_HOUGH_GRADIENT, 1, 1, 160, 25, 0, 0);
// Draw the circles detected
for( int i = 0; i < circles.cols(); i++ ) {
double[] vCircle = circles.get(0, i);
Point center = new Point(vCircle[0], vCircle[1]);
int radius = (int) Math.round(vCircle[2]);
// circle center
Core.circle(src, center, 3, new Scalar(0, 255, 0), -1, 8, 0);
// circle outline
Core.circle(src, center, radius, new Scalar(0, 0, 255), 3, 8, 0);
}
// Save the visualized detection.
String filename = "output.png";
System.out.println(String.format("Writing %s", filename));
Highgui.imwrite(filename, src);
I have Gaussian blur commented out because (counter intuitively) it was greatly increasing the number of equally inaccurate circles found.
Is there anything wrong with my input image that would cause Hough to not work as well as I expect? Are my parameters way off?
EDIT: first answer brought up a good point about the min/max radius hint for Hough. I resisted adding those parameters as the example image in this post is just one of thousands of images all with varying radii from ~20 to almost infinity.
I've adjusted my RANSAC algorithm from this answer: Detect semi-circle in opencv
Idea:
choose randomly 3 points from your binary edge image
create a circle from those 3 points
test how "good" this circle is
if it is better than the previously best found circle in this image, remember
loop 1-4 until some number of iterations reached. then accept the best found circle.
remove that accepted circle from the image
repeat 1-6 until you have found all circles
problems:
at the moment you must know how many circles you want to find in the image
tested only for that one image.
c++ code
result:
code:
inline void getCircle(cv::Point2f& p1,cv::Point2f& p2,cv::Point2f& p3, cv::Point2f& center, float& radius)
{
float x1 = p1.x;
float x2 = p2.x;
float x3 = p3.x;
float y1 = p1.y;
float y2 = p2.y;
float y3 = p3.y;
// PLEASE CHECK FOR TYPOS IN THE FORMULA :)
center.x = (x1*x1+y1*y1)*(y2-y3) + (x2*x2+y2*y2)*(y3-y1) + (x3*x3+y3*y3)*(y1-y2);
center.x /= ( 2*(x1*(y2-y3) - y1*(x2-x3) + x2*y3 - x3*y2) );
center.y = (x1*x1 + y1*y1)*(x3-x2) + (x2*x2+y2*y2)*(x1-x3) + (x3*x3 + y3*y3)*(x2-x1);
center.y /= ( 2*(x1*(y2-y3) - y1*(x2-x3) + x2*y3 - x3*y2) );
radius = sqrt((center.x-x1)*(center.x-x1) + (center.y-y1)*(center.y-y1));
}
std::vector<cv::Point2f> getPointPositions(cv::Mat binaryImage)
{
std::vector<cv::Point2f> pointPositions;
for(unsigned int y=0; y<binaryImage.rows; ++y)
{
//unsigned char* rowPtr = binaryImage.ptr<unsigned char>(y);
for(unsigned int x=0; x<binaryImage.cols; ++x)
{
//if(rowPtr[x] > 0) pointPositions.push_back(cv::Point2i(x,y));
if(binaryImage.at<unsigned char>(y,x) > 0) pointPositions.push_back(cv::Point2f(x,y));
}
}
return pointPositions;
}
float verifyCircle(cv::Mat dt, cv::Point2f center, float radius, std::vector<cv::Point2f> & inlierSet)
{
unsigned int counter = 0;
unsigned int inlier = 0;
float minInlierDist = 2.0f;
float maxInlierDistMax = 100.0f;
float maxInlierDist = radius/25.0f;
if(maxInlierDist<minInlierDist) maxInlierDist = minInlierDist;
if(maxInlierDist>maxInlierDistMax) maxInlierDist = maxInlierDistMax;
// choose samples along the circle and count inlier percentage
for(float t =0; t<2*3.14159265359f; t+= 0.05f)
{
counter++;
float cX = radius*cos(t) + center.x;
float cY = radius*sin(t) + center.y;
if(cX < dt.cols)
if(cX >= 0)
if(cY < dt.rows)
if(cY >= 0)
if(dt.at<float>(cY,cX) < maxInlierDist)
{
inlier++;
inlierSet.push_back(cv::Point2f(cX,cY));
}
}
return (float)inlier/float(counter);
}
float evaluateCircle(cv::Mat dt, cv::Point2f center, float radius)
{
float completeDistance = 0.0f;
int counter = 0;
float maxDist = 1.0f; //TODO: this might depend on the size of the circle!
float minStep = 0.001f;
// choose samples along the circle and count inlier percentage
//HERE IS THE TRICK that no minimum/maximum circle is used, the number of generated points along the circle depends on the radius.
// if this is too slow for you (e.g. too many points created for each circle), increase the step parameter, but only by factor so that it still depends on the radius
// the parameter step depends on the circle size, otherwise small circles will create more inlier on the circle
float step = 2*3.14159265359f / (6.0f * radius);
if(step < minStep) step = minStep; // TODO: find a good value here.
//for(float t =0; t<2*3.14159265359f; t+= 0.05f) // this one which doesnt depend on the radius, is much worse!
for(float t =0; t<2*3.14159265359f; t+= step)
{
float cX = radius*cos(t) + center.x;
float cY = radius*sin(t) + center.y;
if(cX < dt.cols)
if(cX >= 0)
if(cY < dt.rows)
if(cY >= 0)
if(dt.at<float>(cY,cX) <= maxDist)
{
completeDistance += dt.at<float>(cY,cX);
counter++;
}
}
return counter;
}
int main()
{
//RANSAC
cv::Mat color = cv::imread("HoughCirclesAccuracy.png");
// convert to grayscale
cv::Mat gray;
cv::cvtColor(color, gray, CV_RGB2GRAY);
// get binary image
cv::Mat mask = gray > 0;
unsigned int numberOfCirclesToDetect = 2; // TODO: if unknown, you'll have to find some nice criteria to stop finding more (semi-) circles
for(unsigned int j=0; j<numberOfCirclesToDetect; ++j)
{
std::vector<cv::Point2f> edgePositions;
edgePositions = getPointPositions(mask);
std::cout << "number of edge positions: " << edgePositions.size() << std::endl;
// create distance transform to efficiently evaluate distance to nearest edge
cv::Mat dt;
cv::distanceTransform(255-mask, dt,CV_DIST_L1, 3);
unsigned int nIterations = 0;
cv::Point2f bestCircleCenter;
float bestCircleRadius;
//float bestCVal = FLT_MAX;
float bestCVal = -1;
//float minCircleRadius = 20.0f; // TODO: if you have some knowledge about your image you might be able to adjust the minimum circle radius parameter.
float minCircleRadius = 0.0f;
//TODO: implement some more intelligent ransac without fixed number of iterations
for(unsigned int i=0; i<2000; ++i)
{
//RANSAC: randomly choose 3 point and create a circle:
//TODO: choose randomly but more intelligent,
//so that it is more likely to choose three points of a circle.
//For example if there are many small circles, it is unlikely to randomly choose 3 points of the same circle.
unsigned int idx1 = rand()%edgePositions.size();
unsigned int idx2 = rand()%edgePositions.size();
unsigned int idx3 = rand()%edgePositions.size();
// we need 3 different samples:
if(idx1 == idx2) continue;
if(idx1 == idx3) continue;
if(idx3 == idx2) continue;
// create circle from 3 points:
cv::Point2f center; float radius;
getCircle(edgePositions[idx1],edgePositions[idx2],edgePositions[idx3],center,radius);
if(radius < minCircleRadius)continue;
//verify or falsify the circle by inlier counting:
//float cPerc = verifyCircle(dt,center,radius, inlierSet);
float cVal = evaluateCircle(dt,center,radius);
if(cVal > bestCVal)
{
bestCVal = cVal;
bestCircleRadius = radius;
bestCircleCenter = center;
}
++nIterations;
}
std::cout << "current best circle: " << bestCircleCenter << " with radius: " << bestCircleRadius << " and nInlier " << bestCVal << std::endl;
cv::circle(color,bestCircleCenter,bestCircleRadius,cv::Scalar(0,0,255));
//TODO: hold and save the detected circle.
//TODO: instead of overwriting the mask with a drawn circle it might be better to hold and ignore detected circles and dont count new circles which are too close to the old one.
// in this current version the chosen radius to overwrite the mask is fixed and might remove parts of other circles too!
// update mask: remove the detected circle!
cv::circle(mask,bestCircleCenter, bestCircleRadius, 0, 10); // here the radius is fixed which isnt so nice.
}
cv::namedWindow("edges"); cv::imshow("edges", mask);
cv::namedWindow("color"); cv::imshow("color", color);
cv::imwrite("detectedCircles.png", color);
cv::waitKey(-1);
return 0;
}
If you'd set minRadius and maxRadius paramaeters properly, it'd give you good results.
For your image, I tried following parameters.
method - CV_HOUGH_GRADIENT
minDist - 100
dp - 1
param1 - 80
param2 - 10
minRadius - 250
maxRadius - 300
I got the following output
Note: I tried this in C++.

Texture Buffers and glMultiDrawElements

Backstory:
I'm trying to draw as many squares the the screen as possible using a single draw call. I'm using a custom glsl vertex shader that is specialized for 2D drawing, and that is supposed to be pulling position data for the vertices of the squares from a samplerBuffer. Since I don't need to worry about rotating or scaling the squares all I should need to do is load the position data into a buffer, bind a texture to that buffer, and then use the sampler to get each vertex's position in the shader. In order to get an index into the texture I store each elements index as the z-component of the vertices.
Everything seems to work really well for a thousand or so squares, but after that I start to get weird blinking. It sort of seems like it's not drawing all of the squares every draw step, or possibly not using all of the positions so that many of the squares are overlapping.
The weird thing is, that if I use drawElements instead of drawElementsMulti, the blinking goes away (but of course then all the squares are drawn as one single object, which I don't want)
One question I have is if my position data is limited to the max texture size, or the max texture buffer size. And if I am limited to the much smaller max texture size, how do I get around it? There's got to be a reason all of that texture buffer space is there, but I obviously don't get how to properly use it.
I'm also thinking maybe glMultiDrawElements is doing something I'm not accounting for with the sampler somehow. Idk, I'm really lost at this point, and yet..it works perfectly for smaller numbers of squares, so I must be doing something right.
[EDIT] Code had changed to reflect suggestions below (and for readability), but the problem persists.
Ok, so here's some code. First the vertex shader:
uniform mat3 projection;
attribute vec3 vertex;
uniform samplerBuffer positionSampler;
attribute vec4 vertex_color;
varying vec4 color;
float positionFetch(int index)
{
// I've tried texelFetch here as well, same effect
float value = texelFetchBuffer(positionSampler, index).r;
return value;
}
void main(void)
{
color = vec4(1, 1, 1, 1);
// use the z-component of the vertex to look up the position of this instance in the texture
vec3 real_position = vec3(vertex.x + positionFetch(int(vertex.z)*2), vertex.y + positionFetch(int(vertex.z)*2+1), 1);
gl_Position = vec4(projection * real_position, 1);
}
And now my GLRenderer, sorry there is so much code, I just really want to make sure there's enough info here to get an answer. This has really been driving me nuts, and examples for java seem to be hard to come by (maybe this code will help someone else on their quest):
public class GLRenderer extends GLCanvas implements GLEventListener, WindowListener
{
private static final long serialVersionUID = -8513201172428486833L;
private static final int bytesPerFloat = Float.SIZE / Byte.SIZE;
private static final int bytesPerShort = Short.SIZE / Byte.SIZE;
public float viewWidth, viewHeight;
public float screenWidth, screenHeight;
private FPSAnimator animator;
private boolean didInit = false;
JFrame the_frame;
SquareGeometry geometry;
// Thought power of 2 might be required, doesn't seem to make a difference
private static final int NUM_THINGS = 2*2*2*2*2*2*2*2*2*2*2*2*2*2;
float[] position = new float[NUM_THINGS*2];
// Shader attributes
private int shaderProgram, projectionAttribute, vertexAttribute, positionAttribute;
public static void main(String[] args)
{
new GLRenderer();
}
public GLRenderer()
{
// setup OpenGL Version 2
super(new GLCapabilities(GLProfile.get(GLProfile.GL2)));
addGLEventListener(this);
setSize(1800, 1000);
the_frame = new JFrame("Hello World");
the_frame.getContentPane().add(this);
the_frame.setSize(the_frame.getContentPane().getPreferredSize());
the_frame.setVisible(true);
the_frame.addWindowListener(this);
animator = new FPSAnimator(this, 60);
animator.start();
}
// Called by the drivers when the gl context is first made available
public void init(GLAutoDrawable d)
{
final GL2 gl = d.getGL().getGL2();
IntBuffer asd = IntBuffer.allocate(1);
gl.glGetIntegerv(GL2.GL_MAX_TEXTURE_BUFFER_SIZE, asd);
System.out.println(asd.get(0));
asd = IntBuffer.allocate(1);
gl.glGetIntegerv(GL2.GL_MAX_TEXTURE_SIZE, asd);
System.out.println(asd.get(0));
shaderProgram = ShaderLoader.compileProgram(gl, "default");
gl.glLinkProgram(shaderProgram);
_getShaderAttributes(gl);
gl.glUseProgram(shaderProgram);
_checkGLCapabilities(gl);
_initGLSettings(gl);
// Calculate batch of vertex data from dirt geometry
geometry = new SquareGeometry(.1f);
geometry.buildGeometry(viewWidth, viewHeight);
geometry.finalizeGeometry(NUM_THINGS);
geometry.vertexBufferID = _generateBufferID(gl);
_loadVertexBuffer(gl, geometry);
geometry.indexBufferID = _generateBufferID(gl);
_loadIndexBuffer(gl, geometry);
geometry.positionBufferID = _generateBufferID(gl);
// initialize buffer object
int size = NUM_THINGS * 2 * bytesPerFloat;
System.out.println(size);
IntBuffer bla = IntBuffer.allocate(1);
gl.glGenTextures(1, bla);
geometry.positionTextureID = bla.get(0);
gl.glUniform1i(positionAttribute, 0);
gl.glActiveTexture(GL2.GL_TEXTURE0);
gl.glBindTexture(GL2.GL_TEXTURE_BUFFER, geometry.positionTextureID);
gl.glBindBuffer(GL2.GL_TEXTURE_BUFFER, geometry.positionBufferID);
gl.glBufferData(GL2.GL_TEXTURE_BUFFER, size, null, GL2.GL_DYNAMIC_DRAW);
gl.glTexBuffer(GL2.GL_TEXTURE_BUFFER, GL2.GL_R32F, geometry.positionBufferID);
}
private void _initGLSettings(GL2 gl)
{
gl.glClearColor(0f, 0f, 0f, 1f);
}
private void _loadIndexBuffer(GL2 gl, SquareGeometry geometry)
{
gl.glBindBuffer(GL2.GL_ELEMENT_ARRAY_BUFFER, geometry.indexBufferID);
gl.glBufferData(GL2.GL_ELEMENT_ARRAY_BUFFER, bytesPerShort*NUM_THINGS*geometry.getNumPoints(), geometry.indexBuffer, GL2.GL_STATIC_DRAW);
}
private void _loadVertexBuffer(GL2 gl, SquareGeometry geometry)
{
int numBytes = geometry.getNumPoints() * 3 * bytesPerFloat * NUM_THINGS;
gl.glBindBuffer(GL2.GL_ARRAY_BUFFER, geometry.vertexBufferID);
gl.glBufferData(GL2.GL_ARRAY_BUFFER, numBytes, geometry.vertexBuffer, GL2.GL_STATIC_DRAW);
gl.glEnableVertexAttribArray(vertexAttribute);
gl.glVertexAttribPointer(vertexAttribute, 3, GL2.GL_FLOAT, false, 0, 0);
}
private int _generateBufferID(GL2 gl)
{
IntBuffer bufferIDBuffer = IntBuffer.allocate(1);
gl.glGenBuffers(1, bufferIDBuffer);
return bufferIDBuffer.get(0);
}
private void _checkGLCapabilities(GL2 gl)
{
// TODO: Respond to this information in a meaningful way.
boolean VBOsupported = gl.isFunctionAvailable("glGenBuffersARB") && gl.isFunctionAvailable("glBindBufferARB")
&& gl.isFunctionAvailable("glBufferDataARB") && gl.isFunctionAvailable("glDeleteBuffersARB");
System.out.println("VBO Supported: " + VBOsupported);
}
private void _getShaderAttributes(GL2 gl)
{
vertexAttribute = gl.glGetAttribLocation(shaderProgram, "vertex");
projectionAttribute = gl.glGetUniformLocation(shaderProgram, "projection");
positionAttribute = gl.glGetUniformLocation(shaderProgram, "positionSampler");
}
// Called by me on the first resize call, useful for things that can't be initialized until the screen size is known
public void viewInit(GL2 gl)
{
for(int i = 0; i < NUM_THINGS; i++)
{
position[i*2] = (float) (Math.random()*viewWidth);
position[i*2+1] = (float) (Math.random()*viewHeight);
}
gl.glUniformMatrix3fv(projectionAttribute, 1, false, Matrix.projection3f, 0);
// Load position data into a texture buffer
gl.glBindBuffer(GL2.GL_TEXTURE_BUFFER, geometry.positionBufferID);
ByteBuffer textureBuffer = gl.glMapBuffer(GL2.GL_TEXTURE_BUFFER, GL2.GL_WRITE_ONLY);
FloatBuffer textureFloatBuffer = textureBuffer.order(ByteOrder.nativeOrder()).asFloatBuffer();
for(int i = 0; i < position.length; i++)
{
textureFloatBuffer.put(position[i]);
}
gl.glUnmapBuffer(GL2.GL_TEXTURE_BUFFER);
gl.glBindBuffer(GL2.GL_TEXTURE_BUFFER, 0);
}
public void display(GLAutoDrawable d)
{
if (!didInit || geometry.vertexBufferID == 0)
{
return;
}
//long startDrawTime = System.currentTimeMillis();
final GL2 gl = d.getGL().getGL2();
gl.glClear(GL2.GL_COLOR_BUFFER_BIT | GL2.GL_DEPTH_BUFFER_BIT);
// If we were drawing any other buffers here we'd need to set this every time
// but instead we just leave them bound after initialization, saves a little render time
// No combination of these seems to fix the problem
//gl.glBindBuffer(GL2.GL_ARRAY_BUFFER, geometry.vertexBufferID);
//gl.glVertexAttribPointer(vertexAttribute, 3, GL2.GL_FLOAT, false, 0, 0);
//gl.glBindBuffer(GL2.GL_ELEMENT_ARRAY_BUFFER, geometry.indexBufferID);
gl.glBindBuffer(GL2.GL_TEXTURE_BUFFER, geometry.positionBufferID);
//gl.glActiveTexture(GL2.GL_TEXTURE0);
//gl.glTexBuffer(GL2.GL_TEXTURE_BUFFER, GL2.GL_R32F, geometry.positionBufferID);
_render(gl, geometry);
// Also tried these
//gl.glFlush();
//gl.glFinish();
}
public void _render(GL2 gl, SquareGeometry geometry)
{
gl.glMultiDrawElements(geometry.drawMode, geometry.countBuffer, GL2.GL_UNSIGNED_SHORT, geometry.offsetBuffer, NUM_THINGS);
// This one works, but isn't what I want
//gl.glDrawElements(GL2.GL_LINE_LOOP, count, GL2.GL_UNSIGNED_SHORT, 0);
}
public void reshape(GLAutoDrawable d, int x, int y, int width, int height)
{
final GL2 gl = d.getGL().getGL2();
gl.glViewport(0, 0, width, height);
float ratio = (float) height / width;
screenWidth = width;
screenHeight = height;
viewWidth = 100;
viewHeight = viewWidth * ratio;
Matrix.ortho3f(0, viewWidth, 0, viewHeight);
if (!didInit)
{
viewInit(gl);
didInit = true;
}
else
{
// respond to view size changing
}
}
}
The final bit is the SquareGeometry class which holds all the bufferIDs and vertex data, but also is responsible for filling the vertex buffer correctly so that each vertex's z component can function as an index into the position texture:
public class SquareGeometry
{
public float[] vertices = null;
ShortBuffer indexBuffer;
IntBuffer countBuffer;
PointerBuffer offsetBuffer;
FloatBuffer vertexBuffer;
public int vertexBufferID = 0;
public int indexBufferID = 0;
public int positionBufferID = 0;
public int positionTextureID = 0;
public int drawMode;
protected float width = 0;
protected float height = 0;
public SquareGeometry(float size)
{
width = size;
height = size;
}
public void buildGeometry(float viewWidth, float viewHeight)
{
vertices = new float[4 * 2];
vertices[0] = -width/2;
vertices[1] = -height/2;
vertices[2] = -width/2;
vertices[3] = height/2;
vertices[4] = width/2;
vertices[5] = height/2;
vertices[6] = width/2;
vertices[7] = -height/2;
drawMode = GL2.GL_POLYGON;
}
public void finalizeGeometry(int numInstances)
{
if(vertices == null) return;
int num_vertices = this.getNumPoints();
int total_num_vertices = numInstances * num_vertices;
// initialize vertex Buffer (# of coordinate values * 4 bytes per float)
ByteBuffer vbb = ByteBuffer.allocateDirect(total_num_vertices * 3 * Float.SIZE);
vbb.order(ByteOrder.nativeOrder());
vertexBuffer = vbb.asFloatBuffer();
for(int i = 0; i < numInstances; i++)
{
for(int v = 0; v < num_vertices; v++)
{
int vertex_index = v * 2;
vertexBuffer.put(vertices[vertex_index]);
vertexBuffer.put(vertices[vertex_index+1]);
vertexBuffer.put(i);
}
}
vertexBuffer.rewind();
// Create the indices
vbb = ByteBuffer.allocateDirect(total_num_vertices * Short.SIZE);
vbb.order(ByteOrder.nativeOrder());
indexBuffer = vbb.asShortBuffer();
for(int i = 0; i < total_num_vertices; i++)
{
indexBuffer.put((short) (i));
}
indexBuffer.rewind();
// Create the counts
vbb = ByteBuffer.allocateDirect(numInstances * Integer.SIZE);
vbb.order(ByteOrder.nativeOrder());
countBuffer = vbb.asIntBuffer();
for(int i = 0; i < numInstances; i++)
{
countBuffer.put(num_vertices);
}
countBuffer.rewind();
// create the offsets
offsetBuffer = PointerBuffer.allocateDirect(numInstances);
for(int i = 0; i < numInstances; i++)
{
offsetBuffer.put(num_vertices*i*2);
}
offsetBuffer.rewind();
}
public int getNumPoints()
{
return vertices.length/2;
}
}
Ok first things first, you are not setting gl_Color in the shader maybe that can be the issue here and you only lucky with small numbers. It is a varying, but do you also have fragment shader that picks up the value?
At no point do you ensure that NUM_THINGS*2 < GL_MAX_TEXTURE_SIZE. I don't know how FloatBuffer.put reacts; being Java probably / hopefully an exception.
Also you bind the positionBufferID buffer, then unbind it but never rebind it.
You create positionTextureID but never put any data there. This also what you put into the sampler positionSampler and try to access.
Yea well lots of issues but my gut tells me the last one may be the real issue here.
Alright, I've got it solved, though I'm still really not clear on what the original problem was. I fixed it by simplifying the drawing to use drawArrays instead of drawElements or multiDrawElements. I'm really not sure why I thought I needed them, as I really don't in this case. I'm pretty sure I was messing up a few things with the indexes and offsets.
Furthermore, as far as the proper way to bind the texture buffer, neither the code I have above, nor example found at the link I posted in a comment are correct at all.
If anyone is interested in the correct way to use the texture buffer like this I just did a pretty extensive write-up on it here http://zebadiah.me/?p=44. Thanks all for the help.

Categories

Resources