Related
I want to make random rectangles on Processing. So far, I used for loops for making window size rectangle but I can't figure out how to make only 10 rectangle randomly. Here is my sample code for you:
void setup()
{
size(400, 400);
}
void draw()
{
background(0); // Black Background
stroke(255); // White lines
for (int j = 0; j <= height; j += 40)
{
for (int i = 0; i < width; i += 40)
{
fill(0);
rect(i, j, 40, 40);
}
}
}
It shows 100 black rectangles but I want to see only 10 black rectangles. For example: The first line will get random 1 rectangle, second line will get 2 , third line will get 1 and it goes till the 10.
There are multiple ways to solve this fun homework/exercise.
First thing is drawing the right number of boxes per column:
void setup()
{
size(400, 400);
background(0); // Black Background
fill(0);
stroke(255); // White lines
int boxSize = 40;
int maxBoxes = 1;
for (int j = 0; j <= height; j += boxSize)
{
// box count per row
int boxCount = 0;
for (int i = 0; i < width; i += boxSize)
{
// only draw the max number of boxes
if(boxCount < maxBoxes){
rect(i, j, 40, 40);
// increment per row box count
boxCount++;
}
}
// increment max boxes per box
maxBoxes++;
}
}
Secondly the positions for the drawn boxes per column need to be randomized, but ideally not overlap. One option is to split the full solution space to sections: each section having it's own range of positions so it won't overlap the next.
void setup()
{
size(400, 400);
background(0); // Black Background
fill(0);
stroke(255); // White lines
int boxSize = 40;
int maxBoxes = 1;
int totalBoxes = width / boxSize;
for (int j = 0; j <= height; j += boxSize)
{
// box count per row
int boxCount = 0;
// a list of box indices of where to draw a box (as opposed
int[] randomXIndices = new int[maxBoxes];
// how many index ranges to span per row
int indexRangePerBox = totalBoxes / maxBoxes;
// for each random index
for(int k = 0 ; k < maxBoxes; k++)
{
// pre-calculate which random index to select
// using separate ranges per box to avoid overlaps
randomXIndices[k] = (int)random(indexRangePerBox * k, indexRangePerBox * (k + 1));
}
for (int i = 0; i < width; i += boxSize)
{
// only draw the max number of boxes
if(boxCount < maxBoxes)
{
int randomX = randomXIndices[boxCount] * boxSize;
rect(randomX, j, 40, 40);
// increment per row box count
boxCount++;
}
}
// increment max boxes per box
maxBoxes++;
}
}
void draw(){
}
void mousePressed(){
setup();
}
Click to reset. Notice that the bottom rows almost always look the same:
there is less wiggle room to pick a random position
random() is a rough pseudo-random number generator, but there are better ones out there like randomGaussian(), noise(), etc.
overall there are other strategies to explore picking random positions and avoiding overlaps
Live demo bellow:
function setup()
{
createCanvas(400, 400);
reset();
// reset once per second
setInterval(reset, 1000);
}
function reset(){
background(0); // Black Background
fill(0);
stroke(255); // White lines
var boxSize = 40;
var maxBoxes = 1;
var totalBoxes = width / boxSize;
for (var j = 0; j <= height; j += boxSize)
{
// box count per row
var boxCount = 0;
// a list of box indices of where to draw a box (as opposed
var randomXIndices = new Array(maxBoxes);
// how many index ranges to span per row
var indexRangePerBox = totalBoxes / maxBoxes;
// for each random index
for(var k = 0 ; k < maxBoxes; k++)
{
// pre-calculate which random index to select
// using separate ranges per box to avoid overlaps
randomXIndices[k] = floor(random(indexRangePerBox * k, indexRangePerBox * (k + 1)));
}
for (var i = 0; i < width; i += boxSize)
{
// only draw the max number of boxes
if(boxCount < maxBoxes)
{
var randomX = randomXIndices[boxCount] * boxSize;
rect(randomX, j, 40, 40);
// increment per row box count
boxCount++;
}
}
// increment max boxes per box
maxBoxes++;
}
}
function draw(){
}
function mousePressed(){
reset();
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.0.0/p5.min.js"></script>
Basically, I want to achieve this, and so far, I've written the following Java code...
// Display the camera frame
public Mat onCameraFrame(CvCameraViewFrame inputFrame) {
// The object's width and height are set to 0
objectWidth = objectHeight = 0;
// frame is captured as a coloured image
frame = inputFrame.rgba();
/** Since the Canny algorithm only works on greyscale images and the captured image is
* coloured, we transform the captured cam image into a greyscale one
*/
Imgproc.cvtColor(frame, grey, Imgproc.COLOR_RGB2GRAY);
// Calculating borders of image using the Canny algorithm
Imgproc.Canny(grey, canny, 180, 210);
/** To avoid background noise (given by the camera) that makes the system too sensitive
* small variations, the image is blurred to a small extent. Blurring is one of the
* required steps before any image transformation because this eliminates small details
* that are of no use. Blur is a low-pass filter.
*/
Imgproc.GaussianBlur(canny, canny, new Size(5, 5), 5);
// Calculate the contours
Imgproc.findContours(canny, contours, new Mat(), Imgproc.RETR_EXTERNAL, Imgproc.CHAIN_APPROX_SIMPLE);
/** The contours come in different sequences
* 1 sequence for each connected component.
* Taking the assumption only 1 object is in view, if we have more than 1 connected
* component, this'll be considered part of the details of the object.
*
* For this, we put all contours together in a single sequence
* If there is at least 1 contour, I can continue processing
*/
for (MatOfPoint mat : contours) {
// Retrieve and store all contours in one giant map
mat.copyTo(allContours);
}
MatOfPoint2f allCon = new MatOfPoint2f(allContours.toArray());
// Calculating the minimal rectangle to contain the contours
RotatedRect box = Imgproc.minAreaRect(allCon);
// Getting the vertices of the rectangle
Point[] vertices = initialiseWithDefaultPointInstances(4);
box.points(vertices);
// Now the vertices are in possession, temporal smoothing can be performed.
for (int i = 0; i < 4; i++) {
// Smooth coordinate x of the vertex
vertices[i].x = alpha * lastVertices[i].x + (1.0 - alpha) * vertices[i].x;
// Smooth coordinate y of the vertex
vertices[i].y = alpha * lastVertices[i].y + (1.0 - alpha) * vertices[i].y;
// Assign the present smoothed values as lastVertices for the next smooth
lastVertices[i] = vertices[i];
}
/** With the vertices, the object size is calculated.
* The object size is calculated through pythagoras theorm. In addition, it gives
* the distance between 2 points in a bi-dimensional space.
*
* For a rectangle, considering any vertex V, its two sizes (width and height) can
* be calculated by calculating the distance of V from the previous vertex and
* calculating the distance of V from the next vertex. This is the reason why I
* calculate the distance between vertici[0]/vertici[3] and vertici[0]/vertici[1]
*/
objectWidth = (int) (conversionFactor * Math.sqrt((vertices[0].x - vertices[3].x) * (vertices[0].x - vertices[3].x) + (vertices[0].y - vertices[3].y) * (vertices[0].y - vertices[3].y)));
objectHeight = (int) (conversionFactor * Math.sqrt((vertices[0].x - vertices[1].x) * (vertices[0].x - vertices[1].x) + (vertices[0].y - vertices[1].y) * (vertices[0].y - vertices[1].y)));
/** Draw the rectangle containing the contours. The line method draws a line from 1
* point to the next, and accepts only integer coordinates; for this reason, 2
* temporary Points have been created and why I used Math.round method.
*/
Point pt1 = new Point();
Point pt2 = new Point();
for (int i = 0; i < 4; i++) {
pt1.x = Math.round(vertices[i].x);
pt1.y = Math.round(vertices[i].y);
pt2.x = Math.round(vertices[(i + 1) % 4].x);
pt2.y = Math.round(vertices[(i + 1) % 4].y);
Imgproc.line(frame, pt1, pt2, red, 3);
}
//If the width and height are non-zero, then print the object size on-screen
if (objectWidth != 0 && objectHeight != 0) {
String text;
text = String.format("%d x %d", objectWidth, objectHeight);
widthValue.setText(text);
}
// This function must return
return frame;
}
// Initialising an array of points
public static Point[] initialiseWithDefaultPointInstances(int length) {
Point[] array = new Point[length];
for (int i = 0; i < length; i++) {
array[i] = new Point();
}
return array;
}
What I want to achieve is drawing a rectangle on-screen that contains the object's contours (edges). If anyone knows the answer to my question, please feel free to comment below, as I have been stuck on this for a couple of hours
Here's the code referenced in the comment How to draw a rectangle containing an object in Android (Java, OpenCV)
public Mat onCameraFrame(CameraBridgeViewBase.CvCameraViewFrame inputFrame) {
// The object's width and height are set to 0
List<Integer> objectWidth = new ArrayList<>();
List<Integer> objectHeight = new ArrayList<>();
// frame is captured as a coloured image
Mat frame = inputFrame.rgba();
Mat gray = new Mat();
Mat canny = new Mat();
List<MatOfPoint> contours = new ArrayList<>();
/** Since the Canny algorithm only works on greyscale images and the captured image is
* coloured, we transform the captured cam image into a greyscale one
*/
Imgproc.cvtColor(frame, gray, Imgproc.COLOR_RGB2GRAY);
// Calculating borders of image using the Canny algorithm
Imgproc.Canny(gray, canny, 180, 210);
/** To avoid background noise (given by the camera) that makes the system too sensitive
* small variations, the image is blurred to a small extent. Blurring is one of the
* required steps before any image transformation because this eliminates small details
* that are of no use. Blur is a low-pass filter.
*/
Imgproc.GaussianBlur(canny, canny, new Size(5, 5), 5);
// Calculate the contours
Imgproc.findContours(canny, contours, new Mat(), Imgproc.RETR_EXTERNAL, Imgproc.CHAIN_APPROX_SIMPLE);
/** The contours come in different sequences
* 1 sequence for each connected component.
* Taking the assumption only 1 object is in view, if we have more than 1 connected
* component, this'll be considered part of the details of the object.
*
* For this, we put all contours together in a single sequence
* If there is at least 1 contour, I can continue processing
*/
if(contours.size() > 0){
// Calculating the minimal rectangle to contain the contours
List<RotatedRect> boxes = new ArrayList<>();
for(MatOfPoint contour : contours){
RotatedRect box = Imgproc.minAreaRect(new MatOfPoint2f(contour.toArray()));
boxes.add(box);
}
// Getting the vertices of the rectangle
List<Point[]> vertices = initialiseWithDefaultPointInstances(boxes.size(), 4);
for(int i=0; i<boxes.size(); i++){
boxes.get(i).points(vertices.get(i));
}
/*
double alpha = 0.5;
// Now the vertices are in possession, temporal smoothing can be performed.
for(int i = 0; i<vertices.size(); i++){
for (int j = 0; j < 4; j++) {
// Smooth coordinate x of the vertex
vertices.get(i)[j].x = alpha * lastVertices.get(i)[j].x + (1.0 - alpha) * vertices.get(i)[j].x;
// Smooth coordinate y of the vertex
vertices.get(i)[j].y = alpha * lastVertices.get(i)[j].y + (1.0 - alpha) * vertices.get(i)[j].y;
// Assign the present smoothed values as lastVertices for the next smooth
lastVertices.get(i)[j] = vertices.get(i)[j];
}
}*/
/** With the vertices, the object size is calculated.
* The object size is calculated through pythagoras theorm. In addition, it gives
* the distance between 2 points in a bi-dimensional space.
*
* For a rectangle, considering any vertex V, its two sizes (width and height) can
* be calculated by calculating the distance of V from the previous vertex and
* calculating the distance of V from the next vertex. This is the reason why I
* calculate the distance between vertici[0]/vertici[3] and vertici[0]/vertici[1]
*/
double conversionFactor = 1.0;
for(Point[] points : vertices){
int width = (int) (conversionFactor * Math.sqrt((points[0].x - points[3].x) * (points[0].x - points[3].x) + (points[0].y - points[3].y) * (points[0].y - points[3].y)));
int height = (int) (conversionFactor * Math.sqrt((points[0].x - points[1].x) * (points[0].x - points[1].x) + (points[0].y - points[1].y) * (points[0].y - points[1].y)));
objectWidth.add(width);
objectHeight.add(height);
}
/** Draw the rectangle containing the contours. The line method draws a line from 1
* point to the next, and accepts only integer coordinates; for this reason, 2
* temporary Points have been created and why I used Math.round method.
*/
Scalar red = new Scalar(255, 0, 0, 255);
for (int i=0; i<vertices.size(); i++){
Point pt1 = new Point();
Point pt2 = new Point();
for (int j = 0; j < 4; j++) {
pt1.x = Math.round(vertices.get(i)[j].x);
pt1.y = Math.round(vertices.get(i)[j].y);
pt2.x = Math.round(vertices.get(i)[(j + 1) % 4].x);
pt2.y = Math.round(vertices.get(i)[(j + 1) % 4].y);
Imgproc.line(frame, pt1, pt2, red, 3);
}
if (objectWidth.get(i) != 0 && objectHeight.get(i) != 0){
Imgproc.putText(frame, "width: " + objectWidth + ", height: " + objectHeight, new Point(Math.round(vertices.get(i)[1].x), Math.round(vertices.get(i)[1].y)), 1, 1, red);
}
}
}
// This function must return
return frame;
}
// Initialising an array of points
public static List<Point[]> initialiseWithDefaultPointInstances(int n_Contours, int n_Points) {
List<Point[]> pointsList = new ArrayList<>();
for(int i=0; i<n_Contours; i++){
Point[] array = new Point[n_Points];
for (int j = 0; j < n_Points; j++) {
array[j] = new Point();
}
pointsList.add(array);
}
return pointsList;
}
To improve my knowledge of imaging and get some experience working with the topics, I decided to create a license plate recognition algorithm on the Android platform.
The first step is detection, for which I decided to implement a recent paper titled "A Robust and Efficient Approach to License Plate Detection". The paper presents their idea very well and uses quite simple techniques to achieve detection. Besides some details lacking in the paper, I implemented the bilinear downsampling, converting to gray scale, and the edging + adaptive thresholding as described in Section 3A, 3B.1, and 3B.2.
Unfortunately, I am not getting the output this paper presents in e.g. figure 3 and 6.
The image I use for testing is as follows:
The gray scale (and downsampled) version looks fine (see the bottom of this post for the actual implementation), I used a well-known combination of the RGB components to produce it (paper does not mention how, so I took a guess).
Next is the initial edge detection using the Sobel filter outlined. This produces an image similar to the ones presented in figure 6 of the paper.
And finally, the remove the "weak edges" they apply adaptive thresholding using a 20x20 window. Here is where things go wrong.
As you can see, it does not function properly, even though I am using their stated parameter values. Additionally I have tried:
Changing the beta parameter.
Use a 2d int array instead of Bitmap objects to simplify creating the integral image.
Try a higher Gamma parameter so the initial edge detection allows more "edges".
Change the window to e.g. 10x10.
Yet none of the changes made an improvement; it keeps producing images as the one above. My question is: what am I doing different than what is outlined in the paper? and how can I get the desired output?
Code
The (cleaned) code I use:
public int[][] toGrayscale(Bitmap bmpOriginal) {
int width = bmpOriginal.getWidth();
int height = bmpOriginal.getHeight();
// color information
int A, R, G, B;
int pixel;
int[][] greys = new int[width][height];
// scan through all pixels
for (int x = 0; x < width; ++x) {
for (int y = 0; y < height; ++y) {
// get pixel color
pixel = bmpOriginal.getPixel(x, y);
R = Color.red(pixel);
G = Color.green(pixel);
B = Color.blue(pixel);
int gray = (int) (0.2989 * R + 0.5870 * G + 0.1140 * B);
greys[x][y] = gray;
}
}
return greys;
}
The code for edge detection:
private int[][] detectEges(int[][] detectionBitmap) {
int width = detectionBitmap.length;
int height = detectionBitmap[0].length;
int[][] edges = new int[width][height];
// Loop over all pixels in the bitmap
int c1 = 0;
int c2 = 0;
for (int y = 0; y < height; y++) {
for (int x = 2; x < width -2; x++) {
// Calculate d0 for each pixel
int p0 = detectionBitmap[x][y];
int p1 = detectionBitmap[x-1][y];
int p2 = detectionBitmap[x+1][y];
int p3 = detectionBitmap[x-2][y];
int p4 = detectionBitmap[x+2][y];
int d0 = Math.abs(p1 + p2 - 2*p0) + Math.abs(p3 + p4 - 2*p0);
if(d0 >= Gamma) {
c1++;
edges[x][y] = Gamma;
} else {
c2++;
edges[x][y] = d0;
}
}
}
return edges;
}
The code for adaptive thresholding. The SAT implementation is taken from here:
private int[][] AdaptiveThreshold(int[][] detectionBitmap) {
// Create the integral image
processSummedAreaTable(detectionBitmap);
int width = detectionBitmap.length;
int height = detectionBitmap[0].length;
int[][] binaryImage = new int[width][height];
int white = 0;
int black = 0;
int h_w = 20; // The window size
int half = h_w/2;
// Loop over all pixels in the bitmap
for (int y = half; y < height - half; y++) {
for (int x = half; x < width - half; x++) {
// Calculate d0 for each pixel
int sum = 0;
for(int k = -half; k < half - 1; k++) {
for (int j = -half; j < half - 1; j++) {
sum += detectionBitmap[x + k][y + j];
}
}
if(detectionBitmap[x][y] >= (sum / (h_w * h_w)) * Beta) {
binaryImage[x][y] = 255;
white++;
} else {
binaryImage[x][y] = 0;
black++;
}
}
}
return binaryImage;
}
/**
* Process given matrix into its summed area table (in-place)
* O(MN) time, O(1) space
* #param matrix source matrix
*/
private void processSummedAreaTable(int[][] matrix) {
int rowSize = matrix.length;
int colSize = matrix[0].length;
for (int i=0; i<rowSize; i++) {
for (int j=0; j<colSize; j++) {
matrix[i][j] = getVal(i, j, matrix);
}
}
}
/**
* Helper method for processSummedAreaTable
* #param row current row number
* #param col current column number
* #param matrix source matrix
* #return sub-matrix sum
*/
private int getVal (int row, int col, int[][] matrix) {
int leftSum; // sub matrix sum of left matrix
int topSum; // sub matrix sum of top matrix
int topLeftSum; // sub matrix sum of top left matrix
int curr = matrix[row][col]; // current cell value
/* top left value is itself */
if (row == 0 && col == 0) {
return curr;
}
/* top row */
else if (row == 0) {
leftSum = matrix[row][col - 1];
return curr + leftSum;
}
/* left-most column */
if (col == 0) {
topSum = matrix[row - 1][col];
return curr + topSum;
}
else {
leftSum = matrix[row][col - 1];
topSum = matrix[row - 1][col];
topLeftSum = matrix[row - 1][col - 1]; // overlap between leftSum and topSum
return curr + leftSum + topSum - topLeftSum;
}
}
Marvin provides an approach to find text regions. Perhaps it can be a start point for you:
Find Text Regions in Images:
http://marvinproject.sourceforge.net/en/examples/findTextRegions.html
This approach was also used in this question:
How do I separates text region from image in java
Using your image I got this output:
Source Code:
package textRegions;
import static marvin.MarvinPluginCollection.findTextRegions;
import java.awt.Color;
import java.util.List;
import marvin.image.MarvinImage;
import marvin.image.MarvinSegment;
import marvin.io.MarvinImageIO;
public class FindVehiclePlate {
public FindVehiclePlate() {
MarvinImage image = MarvinImageIO.loadImage("./res/vehicle.jpg");
image = findText(image, 30, 20, 100, 170);
MarvinImageIO.saveImage(image, "./res/vehicle_out.png");
}
public MarvinImage findText(MarvinImage image, int maxWhiteSpace, int maxFontLineWidth, int minTextWidth, int grayScaleThreshold){
List<MarvinSegment> segments = findTextRegions(image, maxWhiteSpace, maxFontLineWidth, minTextWidth, grayScaleThreshold);
for(MarvinSegment s:segments){
if(s.height >= 10){
s.y1-=20;
s.y2+=20;
image.drawRect(s.x1, s.y1, s.x2-s.x1, s.y2-s.y1, Color.red);
image.drawRect(s.x1+1, s.y1+1, (s.x2-s.x1)-2, (s.y2-s.y1)-2, Color.red);
image.drawRect(s.x1+2, s.y1+2, (s.x2-s.x1)-4, (s.y2-s.y1)-4, Color.red);
}
}
return image;
}
public static void main(String[] args) {
new FindVehiclePlate();
}
}
I am attempting to create a new color palette for an image turned to gray scale, and then apply the palette to the gray scale image. I began the method that I wanted to use to apply the palette, but I ran into the error mentioned in the title. I used "java.awt.Color" already in my code, so I am not sure why I am getting the error. Also, as you will see, I placed a color inside the parenthesis.
/**
* This program takes an image, converts it to grayscale, and uses a color palette to create new colors for the image.
*
* #author Dylan Hubbs
* #version 08/02/16
*/
import java.awt.Color ;
class ColorPalette
{
public void grayscaleEffect(Picture pictureObj)
{
int redValue = 0; int greenValue = 0; int blueValue = 0;
Pixel grayscaleTargetPixel = new Pixel(pictureObj, 0,0);
Color grayscalePixelColor = null;
for(int y=0; y < pictureObj.getHeight(); y++)
{
for(int x = 0; x < pictureObj.getWidth(); x++)
{
grayscaleTargetPixel = pictureObj.getPixel(x,y);
grayscalePixelColor = grayscaleTargetPixel.getColor(); //gets the color of the target pixel
grayscalePixelColor = new Color((grayscaleTargetPixel.getRed() + grayscaleTargetPixel.getGreen() + grayscaleTargetPixel.getBlue()) / 3, (grayscaleTargetPixel.getRed() + grayscaleTargetPixel.getGreen() + grayscaleTargetPixel.getBlue()) / 3, (grayscaleTargetPixel.getRed() + grayscaleTargetPixel.getGreen() + grayscaleTargetPixel.getBlue()) / 3);
grayscaleTargetPixel.setColor(grayscalePixelColor); //sets the new color of the target pixel
}//end of the inner for loop
}//end of the outer for loop
pictureObj.explore(); //explore the Picture object which is now the altered image
pictureObj.write("grayscaleWashingtonMonument.jpg"); //write the altered Picture object to a new file
pictureObj.show();
}
public void paletteEffect(Picture pictureObj)
{
int redValue = 0; int greenValue = 0; int blueValue = 0;
Pixel paletteTargetPixel = new Pixel(pictureObj, 0,0);
Color palettePixelColor = null;
Color [] palette = {Color.RED, Color.BLUE, Color.CYAN, Color.GREEN, Color.YELLOW, Color.GRAY, Color.PINK, Color.ORANGE};
for(int y=0; y < pictureObj.getHeight(); y++)
{
for(int x = 0; x < pictureObj.getWidth(); x++)
{
paletteTargetPixel = pictureObj.getPixel(x,y);
palettePixelColor = paletteTargetPixel.getColor();
if(paletteTargetPixel.getRed() >= 1 && paletteTargetPixel.getRed() <= 31)
palettePixelColor.setColor(palette[0]);
else if(paletteTargetPixel.getRed() >= 32 && paletteTargetPixel.getRed() <= 62)
palettePixelColor.setColor(palette[1]);
else if(paletteTargetPixel.retRed() >= 63 && paletteTargetPixel.getRed() <=93)
palettePixelColor.setColor(palette[2]);
}
}
}
}
public class ColorPaletteTester
{
public static void main(String[] args)
{
Picture pictureObj = new Picture("washingtonmonument.jpg"); //creates a new Picture object representing the file in the parameter list
pictureObj.explore();
ColorPalette cp = new ColorPalette();
cp.grayscaleEffect(pictureObj);
cp.paletteEffect(pictureObj);
}
}
So, the error is coming at
palettePixelColor.setColor(palette[0]);
Does anyone know why this would be happening?
palettePixelColor is declared as java.awt.Color, which happens to be an immutable class with no setters. Depending on what Pixel is, it may have such a method.
You are probably trying to do something like
palettePixelColor = palette[0];
or
paletteTargetPixel.setColor(palette[0]);
I have a 3D array of booleans that represents some 3D terrain. Currently i can draw it by drawing a point at the position specified by its x y and z in the array, it looks like this.
What i can't figure out is how i would draw this using triangles, so it looks like actual terrain. I don't want to draw each on as a cube either.
Are there any algorithms to obtain which points to draw to (bear in mind that for the sake of efficiency only points on the exterior of a landmass should be drawn)?
Absolutely amazing question! I couldn't resist but to play with boxes, since boxes are sexy. It is actually fairly easy to produce boxes with the hidden faces omitted.
The following algorithm takes a list of 3D positions of true in your grid, which is easy to obtain by simply scanning through the grid and filling an array. Also with this data format you can store much larger grid, provided that the terrain is reasonably sparse. Up front, apologies for my spartan for-each loops, I just wanted to have the code in-place and avoid writing dozens of function objects or using lambdas. Also apologies for using C++ instead of Java, don't have javac at home and I'm not really good with it anyway. Here goes:
#include <vector>
#include <map>
#include <set>
#include <utility>
#include <assert.h>
#include <math.h>
struct Pos3 {
int x, y, z;
Pos3(int _x = 0, int _y = 0, int _z = 0);
bool operator <(const Pos3 &other) const;
};
std::vector<int> index_buffer;
std::vector<float> vertex_buffer;
void onInitialize()
{
const int N = 32;
std::vector<Pos3> points;
GeneratePoints(points, N);
// input: bunch of points in NxNxN box (easy to get from a boolean array,
// can have much larger terrains if stored like this)
std::set<Pos3> point_set;
point_set.insert(points.begin(), points.end());
// put all the points to a set to be able to lookup neighbors (not needed with an array)
std::vector<std::vector<int> > polygons;
polygons.reserve(3 * points.size()); // guess
std::map<Pos3, int> vertex_map;
for(size_t i = 0, n = points.size(); i < n; ++ i) {
Pos3 p = points[i], corners[8] = {
p, Pos3(p.x + 1, p.y, p.z), Pos3(p.x + 1, p.y + 1, p.z), Pos3(p.x, p.y + 1, p.z),
Pos3(p.x, p.y, p.z + 1), Pos3(p.x + 1, p.y, p.z + 1), Pos3(p.x + 1, p.y + 1, p.z + 1),
Pos3(p.x, p.y + 1, p.z + 1)
};
// get corners of a cube
static const int sides[][3 + 4] = {
0, -1, 0, 4, 5, 1, 0, 1, 0, 0, 5, 6, 2, 1,
0, 1, 0, 6, 7, 3, 2, -1, 0, 0, 7, 4, 0, 3,
0, 0, -1, 0, 1, 2, 3, 0, 0, 1, 7, 6, 5, 4
};
// directions and side quad indices
for(int j = 0; j < 6; ++ j) {
Pos3 n(p.x + sides[j][0], p.y + sides[j][1], p.z + sides[j][2]); // position of a neighbor
if(point_set.find(n) != point_set.end())
continue; // have a neighbor, will not triangulate this side
polygons.resize(polygons.size() + 1);
std::vector<int> &poly = polygons.back(); // or use emplace_back() in c++11
poly.resize(4); // form quads
for(int v = 0; v < 4; ++ v) {
Pos3 vert = corners[sides[j][3 + v]];
std::map<Pos3, int>::iterator it; // use map to reuse vertices
if((it = vertex_map.find(vert)) == vertex_map.end())
vertex_map[vert] = poly[v] = vertex_map.size(); // new vertex
else
poly[v] = (*it).second; // existing vertex
}
}
// generate sides, skip invisible sides
// note that this still triangulates cavities, would have to flood-fill
// outside area and then set all that is not outside to opaque (did not
// solve that as this is also a valid behavior)
}
vertex_buffer.resize(vertex_map.size() * 3);
for(std::map<Pos3, int>::const_iterator it = vertex_map.begin(), e = vertex_map.end(); it != e; ++ it) {
size_t i = (*it).second * 3;
vertex_buffer[i + 0] = ((*it).first.x + .5f) / (N + 1) * 2 - 1;
vertex_buffer[i + 1] = ((*it).first.y + .5f) / (N + 1) * 2 - 1;
vertex_buffer[i + 2] = ((*it).first.z + .5f) / (N + 1) * 2 - 1;
}
// convert points from the discrete domain
// to a unit 3D cube centered around the origin
index_buffer.reserve(polygons.size() * 2 * 3); // approximate number of triangles
for(size_t i = 0, n = polygons.size(); i < n; ++ i) {
const std::vector<int> &poly = polygons[i];
for(size_t j = 2, n = poly.size(); j < n; ++ j) {
index_buffer.push_back(poly[0]);
index_buffer.push_back(poly[j]);
index_buffer.push_back(poly[j - 1]);
}
}
// convert polygons (those are actually quads) to triangles
}
There is also some more code that generates normals (ommited for the sake of clarity), the output looks like this:
The shape is a Julia set generated on a discrete lattice, you might recognize the shape when you turn it arround.
This is actually pretty similar to what you would get by the Delaunay triangulation if you could easily remove your interior points. The generated shape is hollow. There can be some "bubbles" in the shape, in case the booleans also contain a bubble (does not occur with Julia). This is easily fixed by flood-filling the booleans in order to fill those up.
Next, we can apply Catmull-Clark subdivision in order to get a smoother mesh:
typedef std::map<std::pair<int, int>, std::pair<size_t, int> > EdgeMap;
static bool Get_EdgeID(size_t &eid, int a, int b, EdgeMap &edges)
{
std::pair<int, int> e(std::min(a, b), std::max(a, b));
EdgeMap::iterator it = edges.find(e);
if(it == edges.end()) {
edges[e] = std::make_pair(eid = edges.size(), 1); // id, count
return true; // new edge
} else {
eid = (*it).second.first; // get id
++ (*it).second.second; // increase count
return false; // no new edge
}
}
void CatClark(std::vector<std::vector<int> > &src_quads, std::vector<float> &src_verts)
{
const static float vpw[4] = {9.0f, 3.0f, 1.0f, 3.0f};
const static float epw[4] = {3.0f, 3.0f, 1.0f, 1.0f};
std::vector<std::vector<int> > dst_quads(src_quads.size() * 4, std::vector<int>(4)); // will produce quads
std::vector<float> dst_verts(src_verts.size() + src_quads.size() * 3, 0); // alloc s¨pace for vertices
EdgeMap edges;
std::vector<int> face_valences(src_verts.size() / 3, 0);
const size_t off_vp = src_quads.size(), off_ep = off_vp + src_verts.size() / 3;
for(size_t j = 0; j < off_vp; ++ j) {
assert(src_quads[j].size() == 4); // otherwise won't work
size_t eid[4];
for(int k = 0; k < 4; ++ k) {
int quad[4];
for(int i = 0; i < 4; ++ i)
quad[i] = src_quads[j][(i + k) & 3]; // get the 4 vertices (but rotate them each k iteration)
if(Get_EdgeID(eid[k], quad[0], quad[1], edges)) // create edges
dst_verts.insert(dst_verts.end(), 3, .0f); // must add new vertex to accomodate subdivided edge point
++ face_valences[quad[0]]; // update face-valence
for(int n = 0; n < 3; ++ n)
dst_verts[j * 3 + n] += 0.25f * src_verts[quad[0] * 3 + n]; // increment face point
for(int i = 0; i < 4; ++ i) {
for(int n = 0; n < 3; ++ n) {
dst_verts[(off_vp + quad[0]) * 3 + n] += vpw[i] * src_verts[quad[i] * 3 + n]; // incremente vertex point
dst_verts[(off_ep + eid[k]) * 3 + n] += epw[i] * src_verts[quad[i] * 3 + n]; // increment edge point
}
}
}
for(int k = 0; k < 4; ++ k) { // make child faces
dst_quads[4 * j + k][0] = j;
dst_quads[4 * j + k][4] = off_ep + eid[(3 + k) & 3];
dst_quads[4 * j + k][5] = off_ep + eid[(0 + k) & 3];
dst_quads[4 * j + k][6] = off_vp + src_quads[j][k];
}
}
for(size_t j = 0, n = src_verts.size() / 3; j < n; ++ j) {
for(int n = 0; n < 3; ++ n)
dst_verts[(off_vp + j) * 3 + n] *= 0.0625f / float(face_valences[j]);
}
for(EdgeMap::const_iterator it = edges.begin(), e = edges.end(); it != e; ++ it) {
size_t j = (*it).second.first;
float rvalence = 0.1250f / float((*it).second.second);
for(int n = 0; n < 3; ++ n)
dst_verts[(off_ep + j) * 3 + n] *= rvalence;
}
dst_quads.swap(src_quads);
dst_verts.swap(src_verts);
}
This algorithm was adapted to work with STL containers from Iñigo 'iq' Quilez / rgba, "Tricks and techniques for rgba's past and future intros", Breakpoint, 2007.
This gives output quite similar to what you would get with marching cubes / marching tetrahedrons / marching triangles, except that it is always higher resolution than that of the original lattice (with the above methods you can easily change the triangulation resolution). A slightly different view of the same data:
Or without wireframe:
The complete source code, along with Visual Studio workspace and win32 binaries can be found at here. It uses GLUT and the old fixed-function pipeline to display the generated geometry (only for simplicity and portability, otherwise I'd have to include GLEW or the like). I really enjoyed toying around with your question, I hope you will like the output ...
If you want to use marching cubes, you can find many demos online, or check out this real-time water simulation I did a couple years ago.
If you have the points describing the outer shell of your terrain, this package is VERY good (fast) for calculating the Delaunay Triangulation of that set of points:
http://www.cs.bgu.ac.il/~benmoshe/DT/
You can later draw each of the triangles from the triangulation. I made a couple or methods for converting everything back to doubles for use with JOGL that you might find helpful:
public static ArrayList<Point_dt[]> DTtoTriListDT(Delaunay_Triangulation DT){
ArrayList<Point_dt[]> triangles = new ArrayList<Point_dt[]>();
Point_dt[] triangle = new Point_dt[3];
Iterator<Triangle_dt> surface = DT.trianglesIterator();
while(surface.hasNext()){
Triangle_dt tri = surface.next();
triangle[0] = tri.p1();
triangle[1] = tri.p2();
triangle[2] = tri.p3();
triangles.add(triangle);
}
return triangles;}
and
public static ArrayList<double[][]> DTtoTriList(Delaunay_Triangulation DT){
ArrayList<Point_dt[]> trianglesdt = Algebra.DTtoTriListDT(DT);
ArrayList<double[][]> triangles = new ArrayList<double[][]>();
double[][] triangle = new double[3][3];
Iterator<Point_dt[]> surface = trianglesdt.iterator();
while(surface.hasNext()){
Point_dt[] tri = surface.next();
triangle[0][0] = tri[0].x();
triangle[0][1] = tri[0].y();
triangle[0][2] = tri[0].z();
triangle[1][0] = tri[1].x();
triangle[1][1] = tri[1].y();
triangle[1][2] = tri[1].z();
triangle[2][0] = tri[2].x();
triangle[2][1] = tri[2].y();
triangle[2][2] = tri[2].z();
triangles.add(triangle);
}
return triangles;
}
I have it all in an Algebra class (FYI).
With the last method, you get an ArrayList with tree sets of three doubles on each entry (each set contains the three coordinates of each point and each entry is one triangle).