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).
Related
So I am building a project in order to build a tetris style game, and I want to be able to test whether the shape will be able to be added to a 5 x 5 grid. The shape is modelled by a 2D array, where a 1 is considered to be a single block of the shape (the shapes are made of a few blocks). The shapes are modelled with a 3 x 3 grid. The thing I must do is check the grid for whether the shape will be able to fit on top of it. Take for example placing a line shape at the top square of the grid, the line will go out of bounds and should not work, or another example is that the grid may already have a shape on it and so the line should not be able to be put on top of it.
This is the code that I've got thus far, and it is not working, I'm just really having a tough time conceptualising what to do. Thank you in advance.
Please note that cols is the number of columns in the grid (5) and rows is the same (5). Game piece is the shape, and the co-ordinates is where the user has clicked on the 5x5 grid.
Also: The anchor point of the shape is 1,1 of the 3x3 grid (so the anchor point is right in the middle of the grid). And get(int x, int y) method is getting the value stored in the 5x5 grid.
Sorry if this was not made clear in the beginning but I am trying to basically see whether the shape stored in the 3x3 grid (made up of blocks) can be placed on top of the 5x5 grid. The 3x3 grid that contains the block has a centre anchor point, so it would be 1,1 (since arrays start with 0). If the 5x5 grid has other blocks that are at the same co-ordinate of the new shape being added, then I want it to return false or if the shape becomes out of bounds when being placed on the 5x5 grid, but if it can be added successfully then it will return true.
public boolean canPlayPiece (GamePiece piece, int x, int y) {
logger.info("canPlayPiece - Block clicked coordinates: " + x + "," + y);
// Piece co-ordinates are 3 x 3, each element that is 1 means there is a block there
int[][] pieceCoordinates = piece.getBlocks();
// For loop to iterate through the grid
// first looping through x values
for (int i = x - 1; i < cols; i++) {
System.out.println("i= " + i);
// nested for loop to find the y values stored inside the x
for (int j = y - 1; j < rows; j++) {
System.out.println("j: " + j);
if (pieceCoordinates[x][y] == 1 && get(i,j) != 0) {
logger.info("canPlayPiece: FALSE");
return false;
}
}
}
logger.info("canPlayPiece: TRUE");
return true;
}
Ok i made the following for you:
public boolean canPlayPiece(GamePiece piece, int x, int y) {
int[][] pc = piece.getBlocks();
final int w = 3, h = 3, e = w - 1;
final int offX = -1, offY = -1; // The offset of the left top corner from 'x' and 'y'
int i, si, ei, ax, ay, rx, ry;
for (ei = w * h - 1; ei >= 0 && pc[ei / w][ei % w] == 0; ei--);
for (si = 0; si <= ei && pc[si / w][si % w] == 0; si++);
for (i = si + 1, ax = si % w; ax > 0 && i <= ei; i++) if (pc[i / w][rx = i % w] != 0) { si += Math.min(rx - ax, 0); ax = rx; }
for (i = ei - 1, ax = ei % w; ax < e && i >= si; i--) if (pc[i / w][rx = i % w] != 0) { ei += Math.max(rx - ax, 0); ax = rx; }
if (si > ei) return true; // There is no block in the piece's grid
int sx = si % w, sy = si / w, ex = ei % w, ey = ei / w; // The bounds of the shape inside of pc
int asx = x + offX + sx, asy = y + offY + sy, aex = asx + ex - sx, aey = asy + ey - sy;
if ((asx | asy | aex | aey | cols - 1 - aex | rows - 1 - aey) < 0) return false; // Would be out of bounds
for (rx = sx, ax = asx; rx <= ex; rx++, ax++) {
for (ry = sy, ay = asy; ry <= ey; ry++, ay++) {
// if (grid[ay][ax] != 0 && pc[ry][rx] != 0) return false; // Block overlaps another block
if (get(ax, ay) != 0 && pc[ry][rx] != 0) return false; // Block overlaps another block
}
}
return true;
}
First it figues out the bounds of the shape inside of 'pc' grid (the grid returned by 'piece.getBlocks()')
If there is no shape inside of 'pc' it will return true, since an empty shape can be placed anywhere (change the return value to false, if you want to return false in that case)
If the inner shape would go out of bounds, when being inserted, it will return false
In the end it will walk through both the grid (using your 'get(x: int, y: int) function) and 'pc' to check whether the shape in 'pc' overlaps with any preexisting blocks inside the grid. And if it doesn't it returns true.
I really hope that this works for you. I tested it out and it worked at least for me.
I'm currently working on a project with multiple objects (tangible objects).
I have a function called renderFrame() executed each frame that :
takes a pixel array as an argument
iterates through all objects and ask them if they should change a specific pixel
The problem is that every frame, a new pixel array must be generated an instanciated with these values.
To do this, I simply use a loop :
int[] pixels = new int[4 * WIDTH * HEIGHT];
for (int i = 0; i < pixels.length - 3; i += 4) {
pixels[i ] = 0; // red
pixels[i + 1] = 0; // green
pixels[i + 2] = 0; // blue
pixels[i + 3] = 255; // alpha
}
Each time I call the function renderFrame(), is it best (in terms of calculation speed) creating a new array and fill it thanks to my for() loop, or creating a copy of a static pixel "model" array like the following one?
private static int[] pixelsD = new int[4 * WIDTH * HEIGHT];
public void initializeArray() {
for (int i = 0; i < pixelsD.length - 3; i += 4) {
pixelsD[i ] = 0; // red
pixelsD[i + 1] = 0; // green
pixelsD[i + 2] = 0; // blue
pixelsD[i + 3] = 255; // alpha
}
}
public void renderFrame() {
int[] pixels = Arrays.copyOf(pixelsD, 4 * WIDTH * HEIGHT);
}
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 want to make a colorized HorizontalBarChart with rounded edges using MPAndroidChart. I'm trying to change HorizontalBarChartRenderer for my chart for it. Here is my code:
HorizontalBarChart initialization:
List<Float> values = new ArrayList<>();
List<Integer> colors = new ArrayList<>();
// initializing
float[] valuesArray = new float[values.size()];
for (int i = 0; i < values.size(); i++) {
valuesArray[i] = values.get(i);
}
List<BarEntry> yValues = valuesArray.length <= 0
? Collections.<BarEntry>emptyList()
: Collections.singletonList(new BarEntry(valuesArray, 0));
BarDataSet barDataSet = new BarDataSet(yValues, "");
barDataSet.setColors(colors);
barDataSet.setValueFormatter(new ValueFormatter() {
#Override
public String getFormattedValue(float value, Entry entry,
int dataSetIndex, ViewPortHandler viewPortHandler) {
return "";
}
});
chartView.setData(new BarData(new String[]{"sleep"}, barDataSet));
And extended HorizontalBarChartRenderer:
#Override
protected void drawDataSet(Canvas c, IBarDataSet dataSet, int index) {
// ...
c.drawRoundRect(new RectF(buffer.buffer[j], buffer.buffer[j + 1] + 10, buffer.buffer[j + 2], buffer.buffer[j + 3] - 10), 20, 20, mRenderPaint);
}
The result is:
How can I make rounded edges only for outer sides?
Something like this:
I suggest that you draw all bars except the last one in "normal" mode, and start by drawing the top (rounded) bar first and let it stretch across the whole length of all bars.
In that way the other bars which are not rounded will cover up the unwanted roundings at the bottom of the first-drawn (rounded) bar.
I found a solution. My idea is to draw data as usual and after clearing some areas to make the edges rounded.
My code:
public class CircleHorizontalBarChartRenderer extends HorizontalBarChartRenderer {
public CircleHorizontalBarChartRenderer(BarDataProvider chart, ChartAnimator animator, ViewPortHandler viewPortHandler) {
super(chart, animator, viewPortHandler);
}
#Override
protected void drawDataSet(Canvas c, IBarDataSet dataSet, int index) {
Transformer trans = mChart.getTransformer(dataSet.getAxisDependency());
mShadowPaint.setColor(dataSet.getBarShadowColor());
float phaseX = mAnimator.getPhaseX();
float phaseY = mAnimator.getPhaseY();
// initialize the buffer
BarBuffer buffer = mBarBuffers[index];
buffer.setPhases(phaseX, phaseY);
buffer.setBarSpace(dataSet.getBarSpace());
buffer.setDataSet(index);
buffer.setInverted(mChart.isInverted(dataSet.getAxisDependency()));
buffer.feed(dataSet);
trans.pointValuesToPixel(buffer.buffer);
int length = buffer.buffer.length;
float left = 0;
float right = 0;
float top = buffer.buffer[length - 3];
float bot = buffer.buffer[length - 1];
boolean leftSaved = false;
for (int j = 0; j < buffer.size(); j += 4) {
if (!mViewPortHandler.isInBoundsTop(buffer.buffer[j + 3]))
break;
if (!mViewPortHandler.isInBoundsBottom(buffer.buffer[j + 1]))
continue;
// Set the color for the currently drawn value.
// If the index is
// out of bounds, reuse colors.
int color = dataSet.getColor(j / 4);
mRenderPaint.setColor(color);
if (color != 0 && !leftSaved) {
leftSaved = true;
left = buffer.buffer[j];
}
if (j > 4) { // it works but its ugly
right = buffer.buffer[j - 2];
}
c.drawRect(buffer.buffer[j], buffer.buffer[j + 1] + 10, buffer.buffer[j + 2],
buffer.buffer[j + 3] - 10, mRenderPaint);
}
Paint erasePaint = new Paint();
erasePaint.setAntiAlias(true);
erasePaint.setStyle(Paint.Style.STROKE);
int paintWidth = 20;
erasePaint.setStrokeWidth(paintWidth);
erasePaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
c.drawRoundRect(new RectF(left - paintWidth / 2, top, right + paintWidth / 2, bot), 30, 30, erasePaint);
}
}
PorterDuff.Mode.CLEAR will work only if you set layer type as software. So you need to call
setLayerType(View.LAYER_TYPE_SOFTWARE, null);
for chartView or for its parent view. If you don't need a transparent background, and background color in known, you can set this color for erasePaint, and do without sowtware layer type, which can worsen performance. And erasePaint.setXfermode also won't be needed for this case.
Final result:
I edited Nick Kober's solution. Here is the code;
public class CircleHorizontalBarChartRenderer extends HorizontalBarChartRenderer {
public CircleHorizontalBarChartRenderer(BarDataProvider chart, ChartAnimator animator, ViewPortHandler viewPortHandler) {
super(chart, animator, viewPortHandler);
}
#Override
protected void drawDataSet(Canvas c, IBarDataSet dataSet, int index) {
Transformer trans = mChart.getTransformer(dataSet.getAxisDependency());
mShadowPaint.setColor(dataSet.getBarShadowColor());
float phaseX = mAnimator.getPhaseX();
float phaseY = mAnimator.getPhaseY();
// initialize the buffer
BarBuffer buffer = mBarBuffers[index];
buffer.setPhases(phaseX, phaseY);
buffer.setBarWidth(dataSet.getBarBorderWidth());
buffer.setDataSet(index);
buffer.setInverted(mChart.isInverted(dataSet.getAxisDependency()));
buffer.feed(dataSet);
trans.pointValuesToPixel(buffer.buffer);
int timeToChange = buffer.size() / 4;
for (int j = 0; j < buffer.size(); j += 4) {
if (!mViewPortHandler.isInBoundsTop(buffer.buffer[j + 3]))
break;
if (!mViewPortHandler.isInBoundsBottom(buffer.buffer[j + 1]))
continue;
// Set the color for the currently drawn value.
// If the index is
// out of bounds, reuse colors.
int color = dataSet.getColor(j / 4);
mRenderPaint.setColor(color);
if (j/4 == 0) {
c.drawRoundRect(new RectF(buffer.buffer[j], buffer.buffer[j + 1] + 10, buffer.buffer[j+2], buffer.buffer[j + 3] - 10), 20, 20, mRenderPaint);
c.drawRect(new RectF(buffer.buffer[j] + 10 , buffer.buffer[j + 1] + 10, buffer.buffer[j+2], buffer.buffer[j + 3] - 10), mRenderPaint);
}
else if (j/4 < timeToChange - 1) {
c.drawRect(new RectF(buffer.buffer[j] , buffer.buffer[j + 1] + 10, buffer.buffer[j+2], buffer.buffer[j + 3] - 10), mRenderPaint);
}
else if (j/4 == timeToChange - 1) {
c.drawRoundRect(new RectF(buffer.buffer[j], buffer.buffer[j + 1] + 10, buffer.buffer[j+2], buffer.buffer[j + 3] - 10), 20, 20, mRenderPaint);
c.drawRect(new RectF(buffer.buffer[j], buffer.buffer[j + 1] + 10, buffer.buffer[j+2] - 10, buffer.buffer[j + 3] - 10), mRenderPaint);
}
}
}
Again, you should write your own custom Renderer class as Nick does. I added the last part (if-else if blocks).
I thought like this, for the first bar draw rounded rectangle. Then above that draw a rectangle by shifting left side to right. By shifting, left side stays rounded. For the inner ones, draw all of them as rectangles. And for the last one draw rounded rectangle. Then above that draw a rectangle by shifting right side to left. By shifting, right side stays rounded.
I used variable "timeToChange" to understand which bar currently I am drawing.
And the output is like this
I'm looking for some assistance with solving the below equations in Java
(a-x1)^2 + (b-y1)^2 = r1^2 + r^2
(a-x2)^2 + (b-y2)^2 = r2^2 + r^2
(a-x3)^2 + (b-y3)^2 = r3^2 + r^2
Values of x1, y1, r1, x2, y2, r2 & x3, y3, r3 are known.
I need to solve for a, b, r
How to go about doing this in Java? I checked the Commons Maths library but didn't find how I could achieve this. It helps with linear equations though.
I think you need linear equations for Gaussian elimination.
If a, b, and r are what you need to solve for, it's obvious that these are non-linear equations.
You'll need a non-linear solver, like Newton-Raphson.
You'll have to linearize your equations. Calculate the Jacobean for the differentials da, db, and dr.
You'll start with an initial guess
a = a(old)
b = b(old)
r = r(old)
use a linearized version of the equations to calculate an increment
2*(a(old)-x1)*da + 2*(b(old)-y1)*db = 2*r(old)*dr
2*(a(old)-x2)*da + 2*(b(old)-y2)*db = 2*r(old)*dr
2*(a(old)-x3)*da + 2*(b(old)-y3)*db = 2*r(old)*dr
update your guess
a(new) = a(old) + da
b(new) = b(old) + db
r(new) = r(old) + dr
and repeat until it converges (if it converges).
You should never solve linear equations using Gaussian elimination: it suffers from a number of problems. A better idea is to do LU decomposition and forward-back substitution.
If my linearized equations are correct, they take the form A(dx) = 0. What should the boundary condition be?
(a, b) are the coordinates for the center of the circle; r is the radius.
Do you really have three points (x1, y1), (x2, y2), and (x3, y3)? Or do you have lots more points? If it's the latter, you'll need a least squares fit.
hope this method can give you some ideas:
public int[] getCoordinates(float XR_1, float YR_1, float XR_2, float YR_2,
float XR_3, float YR_3, int R1, int R2, int R3) {
//define the positions
int XU_1 = 0, YU_1 = 0, XU_2 = 0, YU_2 = 0, XU, YU;
//define variables and arrays that needed
float D0[][] = new float[17][50];
float D1[][] = new float[17][50];
float f[][] = new float[17][50];
float fmin_1 = 0;
float fmin_2 = 0;
//define columns and rows
int i, j;
//Y goes from 0 to 49
for(j=0; j<=49; j++){
//X goes from 0 to 16
for(i=0; i<=16; i++){
D0[i][j] = (float) (Math.pow((i-XR_1),2) + Math.pow((j-YR_1),2) - Math.pow(R1,2));
D1[i][j] = (float) (Math.pow((i-XR_2),2) + Math.pow((j-YR_2),2) - Math.pow(R2,2));
f[i][j] = (float) Math.sqrt(Math.pow(D0[i][j], 2) + Math.pow(D1[i][j], 2));
//get two position where f[i][j] are the minimum
//initialise the minimum two positions
if(i==0 & j==0){
fmin_1 = f[i][j];
XU_1 = i;
YU_1 = j;
}
else if(j==0 & i==1){
if(f[i][j] < fmin_1){
fmin_2 = fmin_1;
fmin_1 = f[i][j];
XU_2 = XU_1;
XU_1 = i;
YU_2 = YU_1;
YU_1 = j;
}
else {
fmin_2 = f[i][j];
XU_2 = i;
YU_2 = j;
}
}
else{
if(f[i][j] < fmin_1){
fmin_2 = fmin_1;
fmin_1 = f[i][j];
XU_2 = XU_1;
XU_1 = i;
YU_2 = YU_1;
YU_1 = j;
}
else if(f[i][j] < fmin_2){
fmin_2 = f[i][j];
XU_2 = i;
YU_2 = j;
}
}
}
}
this method gives two closest points in the coordinate system, you can use the similar way to get the most ideal one.