SVG or Vector base data to Shape - java

So I am trying to create a tool that can convert a .svg file type to a Java Shape or Some kind of class that will allow me to do .contains(x, y) or .contains(Rectangle2D). However I have been unable to find any methods of doing such. I found this post SVG to Java's Path2d parser this seems to give the answer but doesn’t explicitly describe how. I took a look at the classes and don't see how I would load a file then convert it to a shape. I was originally doing this with any kind of image but it turned out to be impractical and really slow. Code for that:
public static Area toArea(URL url, Color color, int tolerance) {
return toArea(toBufferedImage(url), color, tolerance);
}
public static Area toArea(Image image, Color color, int tolerance) {
return toArea(toBufferedImage(image), color, tolerance);
}
/**
* Creates an Area with PixelPerfect precision
*
* #param image
* #param color The color that is draws the Custom Shape
* #param tolerance The color tolerance
* #return Area
*/
public static Area toArea(BufferedImage image, Color color, int tolerance) {
if (image == null) {
return null;
}
Area area = new Area();
for (int x = 0; x < image.getWidth(); x++) {
for (int y = 0; y < image.getHeight(); y++) {
Color pixel = new Color(image.getRGB(x, y));
if (isIncluded(color, pixel, tolerance)) {
Rectangle r = new Rectangle(x, y, 1, 1);
area.add(new Area(r));
}
}
}
return area;
}
public static Area toArea(URL url) {
return toArea(toBufferedImage(url));
}
public static Area toArea(Image image) {
return toArea(toBufferedImage(image));
}
public static Area toArea(BufferedImage image) {
//Assumes Black as Shape Color
if (image == null) {
return null;
}
Area area = new Area();
Rectangle r;
int y1, y2;
for (int x = 0; x < image.getWidth(); x++) {
y1 = 99;
y2 = -1;
for (int y = 0; y < image.getHeight(); y++) {
Color pixel = new Color(image.getRGB(x, y));
//-16777216 entspricht RGB(0,0,0)
if (pixel.getRGB() == -16777216) {
if (y1 == 99) {
y1 = y;
y2 = y;
}
if (y > (y2 + 1)) {
r = new Rectangle(x, y1, 1, y2 - y1);
area.add(new Area(r));
y1 = y;
y2 = y;
}
y2 = y;
}
}
if ((y2 - y1) >= 0) {
r = new Rectangle(x, y1, 1, y2 - y1);
area.add(new Area(r));
}
}
return area;
}
private static boolean isIncluded(Color target, Color pixel, int tolerance) {
int rT = target.getRed();
int gT = target.getGreen();
int bT = target.getBlue();
int rP = pixel.getRed();
int gP = pixel.getGreen();
int bP = pixel.getBlue();
return ((rP - tolerance <= rT) && (rT <= rP + tolerance)
&& (gP - tolerance <= gT) && (gT <= gP + tolerance)
&& (bP - tolerance <= bT) && (bT <= bP + tolerance));
}
public static BufferedImage toBufferedImage(Image image) {
BufferedImage buffer = new BufferedImage(image.getHeight(null), image.getHeight(null), BufferedImage.TYPE_INT_RGB);
buffer.createGraphics().drawImage(image, null, null);
return buffer;
}
public static BufferedImage toBufferedImage(URL url) {
try {
return toBufferedImage(ImageIO.read(url));
} catch (IOException ex) {
return null;
}
}
private ImageShaper() {
}
Basically I am trying to write a function that can load a file that stores an irregular shape like a batman logo and then have it able to run a contains function to see if something hit it.

Related

Converting monochrome image to minimum number of 2d shapes

Basically, what I need to do is take a 2d array of bitflags and produce a list of 2d rectangles to fill the entire area with the minimum number of total shapes required to perfectly fill the space. I am doing this to convert a 2d top-down monochrome of a map into 2d rectangle shapes which perfectly represent the passed in image which will be used to generate a platform in a 3d world. I need to minimize the total number of shapes used, because each shape will represent a separate object, and flooding it with 1 unit sized squares for each pixel would be highly inefficient for that engine.
So far I have read in the image, processed it, and filled a two dimensional array of booleans which tells me if the pixel should be filled or unfilled, but I am unsure of the most efficient approach of continuing.
Here is what I have so far, as reference, if you aren't following:
public static void main(String[] args) {
File file = new File(args[0]);
BufferedImage bi = null;
try {
bi = ImageIO.read(file);
} catch (IOException ex) {
Logger.global.log(Level.SEVERE, null, ex);
}
if (bi != null) {
int[] rgb = bi.getRGB(0, 0, bi.getWidth(), bi.getHeight(), new int[bi.getWidth() * bi.getHeight()], 0, bi.getWidth());
Origin origin = new Origin(bi.getWidth() / 2, bi.getHeight() / 2);
boolean[][] flags = new boolean[bi.getWidth()][bi.getHeight()];
for (int y = 0; y < bi.getHeight(); y++) {
for (int x = 0; x < bi.getWidth(); x++) {
int index = y * bi.getWidth() + x;
int color = rgb[index];
int type = color == Color.WHITE.getRGB() ? 1 : (color == Color.RED.getRGB() ? 2 : 0);
if (type == 2) {
origin = new Origin(x, y);
}
flags[x][y] = type != 1;
}
}
List<Rectangle> list = new ArrayList();
//Fill list with rectangles
}
}
White represents no land. Black or Red represents land. The check for the red pixel marks the origin position of map, which was just for convenience and the rectangles will be offset by the origin position if it is found.
Edit: The processing script does not need to be fast, the produced list of rectangles will be dumped and that will be what will be imported and used later, so the processing of the image does not need to be particularly optimized, it doesn't make a difference.
I also just realized that expecting a 'perfect' solution is expecting too much, since this would qualify as a 'knapsack problem' of the multidimensionally constrained variety, if I am expecting exactly the fewest number of rectangles, so simply an algorithm that produces a minimal number of rectangles will suffice.
Here is a reference image for completion:
Edit 2: It doesn't look like this is such an easy thing to answer given no feedback yet, but I have started making progress, but I am sure I am missing something that would vastly reduce the number of rectangles. Here is the updated progress:
static int mapWidth;
static int mapHeight;
public static void main(String[] args) {
File file = new File(args[0]);
BufferedImage bi = null;
System.out.println("Reading image...");
try {
bi = ImageIO.read(file);
} catch (IOException ex) {
Logger.global.log(Level.SEVERE, null, ex);
}
if (bi != null) {
System.out.println("Complete!");
System.out.println("Interpreting image...");
mapWidth = bi.getWidth();
mapHeight = bi.getHeight();;
int[] rgb = bi.getRGB(0, 0, mapWidth, mapHeight, new int[mapWidth * mapHeight], 0, mapWidth);
Origin origin = new Origin(mapWidth / 2, mapHeight / 2);
boolean[][] flags = new boolean[mapWidth][mapHeight];
for (int y = 0; y < mapHeight; y++) {
for (int x = 0; x < mapWidth; x++) {
int index = y * mapWidth + x;
int color = rgb[index];
int type = color == Color.WHITE.getRGB() ? 1 : (color == Color.RED.getRGB() ? 2 : 0);
if (type == 2) {
origin = new Origin(x, y);
}
flags[x][y] = type != 1;
}
}
System.out.println("Complete!");
System.out.println("Processing...");
//Get Rectangles to fill space...
List<Rectangle> rectangles = getRectangles(flags, origin);
System.out.println("Complete!");
float rectangleCount = rectangles.size();
float totalCount = mapHeight * mapWidth;
System.out.println("Total units: " + (int)totalCount);
System.out.println("Total rectangles: " + (int)rectangleCount);
System.out.println("Rectangle reduction factor: " + ((1 - rectangleCount / totalCount) * 100.0) + "%");
System.out.println("Dumping data...");
try {
file = new File(file.getParentFile(), file.getName() + "_Rectangle_Data.txt");
if(file.exists()){
file.delete();
}
file.createNewFile();
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file)));
for(Rectangle rect: rectangles){
bw.write(rect.x + "," + rect.y + "," + rect.width + ","+ rect.height + "\n");
}
bw.flush();
bw.close();
} catch (Exception ex) {
Logger.global.log(Level.SEVERE, null, ex);
}
System.out.println("Complete!");
}else{
System.out.println("Error!");
}
}
public static void clearRange(boolean[][] flags, int xOff, int yOff, int width, int height) {
for (int y = yOff; y < yOff + height; y++) {
for (int x = xOff; x < xOff + width; x++) {
flags[x][y] = false;
}
}
}
public static boolean checkIfFilled(boolean[][] flags, int xOff, int yOff, int width, int height) {
for (int y = yOff; y < yOff + height; y++) {
for (int x = xOff; x < xOff + width; x++) {
if (!flags[x][y]) {
return false;
}
}
}
return true;
}
public static List<Rectangle> getRectangles(boolean[][] flags, Origin origin) {
List<Rectangle> rectangles = new ArrayList();
for (int y = 0; y < mapHeight; y++) {
for (int x = 0; x < mapWidth; x++) {
if (flags[x][y]) {
int maxWidth = 1;
int maxHeight = 1;
Loop:
//The search size limited to 400x400 so it will complete some time this century.
for (int w = Math.min(400, mapWidth - x); w > 1; w--) {
for (int h = Math.min(400, mapHeight - y); h > 1; h--) {
if (w * h > maxWidth * maxHeight) {
if (checkIfFilled(flags, x, y, w, h)) {
maxWidth = w;
maxHeight = h;
break Loop;
}
}
}
}
//Search also in the opposite direction
Loop:
for (int h = Math.min(400, mapHeight - y); h > 1; h--) {
for (int w = Math.min(400, mapWidth - x); w > 1; w--) {
if (w * h > maxWidth * maxHeight) {
if (checkIfFilled(flags, x, y, w, h)) {
maxWidth = w;
maxHeight = h;
break Loop;
}
}
}
}
rectangles.add(new Rectangle(x - origin.x, y - origin.y, maxWidth, maxHeight));
clearRange(flags, x, y, maxWidth, maxHeight);
}
}
}
return rectangles;
}
My current code's search for larger rectangles is limited to 400x400 to speed up testing, and outputs 17,979 rectangles, which is a 99.9058% total reduction of rectangles if I treated each pixel as a 1x1 square(19,095,720 pixels). So far so good.

Draw intersection of two regions in JavaFX

I have a few restrictions type of a ax+by>=c (1) and x>=z and y>=k (2). Restrictions (1) and (2) make a regions. I need to find the intersection of this regions and fill that other color. How can I do it in JavaFX? Can I use Canvas for solve this?
Sure, just fill everything with the polygon color and then for each area fill everything not in the area with the background color.
Note that this way you fill the whole Canvas except for the polygon with the background color.
public class Area {
private final double x;
private final double y;
private final double threshold;
private final int minCorner;
public Area(double x, double y, double threshold, boolean greater) {
if (x == 0 && y == 0) {
throw new IllegalArgumentException();
}
if (greater) {
x *= -1;
y *= -1;
threshold *= -1;
}
this.x = x;
this.y = y;
this.threshold = threshold;
boolean yPos = y > 0;
// find corner with minimum result for evaluate
this.minCorner = x < 0 ? (yPos ? 1 : 2) : (yPos ? 0 : 3);
}
public Area(double x, double y, double threshold) {
this(x, y, threshold, false);
}
private static final int[][] CORNER_FACTORS = {
{0, 0},
{1, 0},
{1, 1},
{0, 1}
};
public boolean contains(double x, double y) {
return evaluate(x, y) <= threshold;
}
public double hLineIntersection(double y) {
if (x == 0) {
return this.y * y == threshold ? Double.POSITIVE_INFINITY : Double.NaN;
} else {
return (threshold - this.y * y) / this.x;
}
}
public double vLineIntersection(double x) {
if (y == 0) {
return this.x * x == threshold ? Double.POSITIVE_INFINITY : Double.NaN;
} else {
return (threshold - this.x * x) / this.y;
}
}
private double evaluate(double x, double y) {
return this.x * x + this.y * y;
}
public void fillCleanArea(GraphicsContext gc, double w, double h) {
double[] xcoords = new double[5];
double[] ycoords = new double[5];
int[] factors = CORNER_FACTORS[minCorner];
boolean inside = contains(factors[0] * w, factors[1] * h);
int ptIndex = 0;
for (int i = minCorner, max = minCorner + 4; i < max; i++) {
factors = CORNER_FACTORS[i % 4];
double x = factors[0] * w;
double y = factors[1] * h;
boolean nowInside = contains(x, y);
if (inside != nowInside) {
// add intersection point with side
if ((i & 1) == 0) {
ycoords[ptIndex] = vLineIntersection(x);
xcoords[ptIndex++] = x;
} else {
xcoords[ptIndex] = hLineIntersection(y);
ycoords[ptIndex++] = y;
}
inside = nowInside;
// stop, if the end point is inside the area again
if (inside) {
break;
}
}
// add corners outside the bounds to polygon
if (!inside) {
xcoords[ptIndex] = x;
ycoords[ptIndex++] = y;
}
}
// draw polygon
if (ptIndex > 0) {
gc.fillPolygon(xcoords, ycoords, ptIndex);
}
}
}
public static void draw(Canvas canvas, Paint fill, Paint background, Area... areas) {
GraphicsContext gc = canvas.getGraphicsContext2D();
double w = canvas.getWidth();
double h = canvas.getHeight();
// fill everything with polygon color
gc.setFill(fill);
gc.fillRect(0, 0, w, h);
// fill everything outside the polygon with background color
gc.setFill(background);
for (Area area : areas) {
area.fillCleanArea(gc, w, h);
}
}
#Override
public void start(Stage primaryStage) {
Canvas canvas = new Canvas(400, 400);
draw(canvas, Color.BLUE, Color.WHITE,
new Area(1, 1, 400), new Area(1, -1, 100), new Area(1, -1, -100, true), new Area(1, 2, 250, true));
StackPane root = new StackPane(canvas);
Scene scene = new Scene(root);
primaryStage.setScene(scene);
primaryStage.show();
}
This won't give you the polygon's corner points though...

Make a jpg a Shape [duplicate]

I was wondering whether there is any way to convert an image/graphic into a Shape? For example, can I convert the outline of a motorcycle shape into a Shape so I can then use it in Java? I know you can do it with normal squares or with rounded corners, polygons, etc. But is there a way to do a custom shape?
motorcycle.jpg
motorcycle-03.png
ImageOutline.java
This code requires some patience (when running).
import java.awt.*;
import java.awt.image.BufferedImage;
import java.awt.geom.Area;
import javax.imageio.ImageIO;
import java.io.File;
import java.util.Date;
import javax.swing.*;
/* Motorcycle image courtesy of ShutterStock
http://www.shutterstock.com/pic-13585165/stock-vector-travel-motorcycle-silhouette.html */
class ImageOutline {
public static Area getOutline(BufferedImage image, Color color, boolean include, int tolerance) {
Area area = new Area();
for (int x=0; x<image.getWidth(); x++) {
for (int y=0; y<image.getHeight(); y++) {
Color pixel = new Color(image.getRGB(x,y));
if (include) {
if (isIncluded(color, pixel, tolerance)) {
Rectangle r = new Rectangle(x,y,1,1);
area.add(new Area(r));
}
} else {
if (!isIncluded(color, pixel, tolerance)) {
Rectangle r = new Rectangle(x,y,1,1);
area.add(new Area(r));
}
}
}
}
return area;
}
public static boolean isIncluded(Color target, Color pixel, int tolerance) {
int rT = target.getRed();
int gT = target.getGreen();
int bT = target.getBlue();
int rP = pixel.getRed();
int gP = pixel.getGreen();
int bP = pixel.getBlue();
return(
(rP-tolerance<=rT) && (rT<=rP+tolerance) &&
(gP-tolerance<=gT) && (gT<=gP+tolerance) &&
(bP-tolerance<=bT) && (bT<=bP+tolerance) );
}
public static BufferedImage drawOutline(int w, int h, Area area) {
final BufferedImage result = new BufferedImage(
w,
h,
BufferedImage.TYPE_INT_RGB);
Graphics2D g = result.createGraphics();
g.setColor(Color.white);
g.fillRect(0,0,w,h);
g.setClip(area);
g.setColor(Color.red);
g.fillRect(0,0,w,h);
g.setClip(null);
g.setStroke(new BasicStroke(1));
g.setColor(Color.blue);
g.draw(area);
return result;
}
public static BufferedImage createAndWrite(
BufferedImage image,
Color color,
boolean include,
int tolerance,
String name)
throws Exception {
int w = image.getWidth();
int h = image.getHeight();
System.out.println("Get Area: " + new Date() + " - " + name);
Area area = getOutline(image, color, include, tolerance);
System.out.println("Got Area: " + new Date() + " - " + name);
final BufferedImage result = drawOutline(w,h,area);
displayAndWriteImage(result, name);
return result;
}
public static void displayAndWriteImage(BufferedImage image, String fileName) throws Exception {
ImageIO.write(image, "png", new File(fileName));
JOptionPane.showMessageDialog(null, new JLabel(new ImageIcon(image)));
}
public static void main(String[] args) throws Exception {
final BufferedImage outline = ImageIO.read(new File("motorcycle.jpg"));
BufferedImage crop = outline.getSubimage(17,35,420,270);
displayAndWriteImage(crop, "motorcycle-01.png");
BufferedImage crude = createAndWrite(crop, Color.white, false, 60, "motorcycle-02.png");
BufferedImage combo = createAndWrite(crude, Color.red, true, 0, "motorcycle-03.png");
}
}
function getArea_FastHack is build upon Andrew Thompsons work, which was very helpful.
Mine should be faster, however:
(//Edit: and sloppier, too)
import java.awt.*;
import java.awt.geom.Area;
import java.awt.image.BufferedImage;
import javax.imageio.ImageIO;
import java.io.File;
import java.io.IOException;
/**
* CustomShape
* based on a Class from Andrew Thompson *
* Source: http://stackoverflow.com/questions/7052422/image-graphic-into-a-shape-in-java/7059497#7059497
* #author Samuel Schneider, Andrew Thompson
*
*
*/
class CustomShape {
private BufferedImage image=null;
/**
* Creates an Area with PixelPerfect precision
* #param color The color that is draws the Custom Shape
* #param tolerance The color tolerance
* #return Area
*/
public Area getArea(Color color, int tolerance) {
if(image==null) return null;
Area area = new Area();
for (int x=0; x<image.getWidth(); x++) {
for (int y=0; y<image.getHeight(); y++) {
Color pixel = new Color(image.getRGB(x,y));
if (isIncluded(color, pixel, tolerance)) {
Rectangle r = new Rectangle(x,y,1,1);
area.add(new Area(r));
}
}
}
return area;
}
public Area getArea_FastHack() {
//Assumes Black as Shape Color
if(image==null) return null;
Area area = new Area();
Rectangle r;
int y1,y2;
for (int x=0; x<image.getWidth(); x++) {
y1=99;
y2=-1;
for (int y=0; y<image.getHeight(); y++) {
Color pixel = new Color(image.getRGB(x,y));
//-16777216 entspricht RGB(0,0,0)
if (pixel.getRGB()==-16777216) {
if(y1==99) {y1=y;y2=y;}
if(y>(y2+1)) {
r = new Rectangle(x,y1,1,y2-y1);
area.add(new Area(r));
y1=y;y2=y;
}
y2=y;
}
}
if((y2-y1)>=0) {
r = new Rectangle(x,y1,1,y2-y1);
area.add(new Area(r));
}
}
return area;
}
public static boolean isIncluded(Color target, Color pixel, int tolerance) {
int rT = target.getRed();
int gT = target.getGreen();
int bT = target.getBlue();
int rP = pixel.getRed();
int gP = pixel.getGreen();
int bP = pixel.getBlue();
return(
(rP-tolerance<=rT) && (rT<=rP+tolerance) &&
(gP-tolerance<=gT) && (gT<=gP+tolerance) &&
(bP-tolerance<=bT) && (bT<=bP+tolerance) );
}
public CustomShape(String path) {
try {
BufferedImage image = ImageIO.read(new File(path));
this.image = image;
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
Here's something faster but less accurate, useful for collision checking or 2D physics.
Point[] MakePoly(BufferedImage spr,int d,int angle){
//creates an outline of a transparent image, points are stored in an array
//arg0 - BufferedImage source image
//arg1 - Int detail (lower = better)
//arg2 - Int angle threshold in degrees (will remove points with angle differences below this level; 15 is a good value)
// making this larger will make the body faster but less accurate;
int w= spr.getWidth(null); int h= spr.getHeight(null);
// increase array size from 255 if needed
int[] vertex_x=new int[255], vertex_y=new int[255], vertex_k=new int[255];
int numPoints=0, tx=0,ty=0,fy=-1,lx=0,ly=0; vertex_x[0]=0; vertex_y[0]=0; vertex_k[0]=1;
for (tx=0;tx<w;tx+=d) for (ty=0;ty<h;ty+=1) if((spr.getRGB(tx,ty)>>24) != 0x00 )
{vertex_x[numPoints]=tx; vertex_y[numPoints]=h-ty; vertex_k[numPoints]=1; numPoints++; if (fy<0) fy=ty; lx=tx; ly=ty; break; }
for (ty=0;ty<h;ty+=d) for (tx=w-1;tx>=0;tx-=1) if((spr.getRGB(tx,ty)>>24) != 0x00 && ty > ly)
{vertex_x[numPoints]=tx; vertex_y[numPoints]=h-ty; vertex_k[numPoints]=1; numPoints++; lx=tx; ly=ty; break; }
for (tx=w-1;tx>=0;tx-=d) for (ty=h-1;ty>=0;ty-=1) if((spr.getRGB(tx,ty)>>24) != 0x00 && tx < lx)
{vertex_x[numPoints]=tx; vertex_y[numPoints]=h-ty; vertex_k[numPoints]=1; numPoints ++; lx=tx; ly=ty; break; }
for (ty=h-1;ty>=0;ty-=d) for (tx=0;tx<w;tx+=1) if((spr.getRGB(tx,ty)>>24) != 0x00 && ty < ly && ty > fy)
{vertex_x[numPoints]=tx; vertex_y[numPoints]=h-ty; vertex_k[numPoints]=1; numPoints ++; lx=tx; ly=ty; break; }
double ang1,ang2; for (int i=0;i<numPoints-2;i++) {
ang1 = PointDirection(vertex_x[i],vertex_y[i], vertex_x[i+1],vertex_y[i+1]);
ang2 = PointDirection(vertex_x[i+1],vertex_y[i+1], vertex_x[i+2],vertex_y[i+2]);
if (Math.abs(ang1-ang2) <= angle) vertex_k[i+1] = 0; }
ang1 = PointDirection(vertex_x[numPoints-2],vertex_y[numPoints-2], vertex_x[numPoints-1],vertex_y[numPoints-1]);
ang2 = PointDirection(vertex_x[numPoints-1],vertex_y[numPoints-1], vertex_x[0],vertex_y[0]);
if (Math.abs(ang1-ang2) <= angle) vertex_k[numPoints-1] = 0;
ang1 = PointDirection(vertex_x[numPoints-1],vertex_y[numPoints-1], vertex_x[0],vertex_y[0]);
ang2 = PointDirection(vertex_x[0],vertex_y[0], vertex_x[1],vertex_y[1]);
if (Math.abs(ang1-ang2) <= angle) vertex_k[0] = 0;
int n=0;for (int i=0;i<numPoints;i++)if(vertex_k[i]==1)n++;
Point[] poly= new Point[n]; n=0; for (int i=0;i<numPoints;i++) if (vertex_k[i]==1)
{ poly[n]=new Point(); poly[n].x=vertex_x[i]; poly[n].y=h-vertex_y[i];n++;} return poly;
}
double PointDirection(double xfrom,double yfrom,double xto,double yto){
return Math.atan2(yto-yfrom,xto-xfrom)*180/Math.PI ;
}

Reflections not reflecting properly

I am writing a ray tracer.I am currently working on reflections.But the seem not to be reflecting correctly.I keep on getting StackOverflowError.I increased the memory and it runs now but the reflections are not like I thought the would be this.
(source: ageofarmour.com)
I thought it would Reflect the reflections!But it just ends up like this.
Note:This is after moved the normal off the object and changed the color calculations!Check the Cal_Reflection for new color calculation!
Here is my code for my tracer!
public class Tracer {
public boolean Tracing;
public Camera Cam;
public int Width, Height;
public BufferedImage Image;
public Color BackGroundColor;
public int StartX, StartY, EndX, EndY, RowCount, ColCount;
public double AmbientLight;
public double DiffuseLight;
public int MaxReflectionCount;
public ArrayList<GeometricObject> GeoObjects;
public ArrayList<LightObject> LightObjects;
public Tracer(Camera cam, int width, int height, BufferedImage image, Color backGroundColor, int startX, int startY, int endX, int endY, int rowCount, int colCount, double ambientLight, double diffuseLight, int maxReflectionCount, ArrayList<GeometricObject> geoObjects, ArrayList<LightObject> lightObjects) {
super();
Cam = cam;
Width = width;
Height = height;
Image = image;
BackGroundColor = backGroundColor;
StartX = startX;
StartY = startY;
EndX = endX;
EndY = endY;
RowCount = rowCount;
ColCount = colCount;
AmbientLight = ambientLight;
DiffuseLight = diffuseLight;
MaxReflectionCount = maxReflectionCount;
GeoObjects = geoObjects;
LightObjects = lightObjects;
}
public void TracePixelFast(int x, int y) {
Color color = new Color(BackGroundColor.r, BackGroundColor.g, BackGroundColor.b);
for (int o = 0; o < GeoObjects.size(); o++) {
GeometricObject GO = GeoObjects.get(o);
Ray r = new Ray(Cam.GetRayPos(Width, Height, x, y, 1, 1, RowCount, ColCount), Cam.GetRayDir(Width, Height, x, y, 1, 1, RowCount, ColCount));
double hit = GO.hit(r);
if (hit != 0.0) {
color = Cal_Pixel(x, y);
Image.setRGB(x, y, color.toInt());
break;
}
}
}
public void TracePixelSmooth(int x, int y) {
Image.setRGB(x, y, Cal_Pixel(x, y).toInt());
}
public Color Cal_Pixel(int x, int y) {
Color color = new Color(BackGroundColor);
Color colorh = new Color(BackGroundColor);
Color bgc = new Color(BackGroundColor);
int HIT = 0;
int MISS = 0;
for (int row = 0; row < RowCount; row++) {
for (int col = 0; col < ColCount; col++) {
double min = Double.MAX_VALUE;
Ray r = new Ray(Cam.GetRayPos(Width, Height, x, y, row, col, RowCount, ColCount), Cam.GetRayDir(Width, Height, x, y, row, col, RowCount, ColCount));
for (int o = 0; o < GeoObjects.size(); o++) {
GeometricObject GO = GeoObjects.get(o);
double hit = GO.hit(r);
if (hit != 0.0 && hit < min) {
min = hit;
colorh = ShadePixel(0, GO, r, hit);
HIT++;
} else {
double min2 = Double.MAX_VALUE;
for (int o2 = 0; o2 < GeoObjects.size(); o2++) {
if (o != o2) {
GeometricObject GO2 = GeoObjects.get(o2);
double hit2 = GO2.hit(r);
if (hit2 != 0.0 && hit2 < min2) {
min2 = hit2;
bgc = ShadePixel(0, GO2, r, hit2);
}
}
}
MISS++;
}
}
}
}
for (int h = 0; h < HIT; h++) {
color.Add(colorh);
}
for (int m = 0; m < MISS; m++) {
color.Add(bgc);
}
color.Divide(RowCount * ColCount);
return color;
}
public Color ShadePixel(int ReflectionDepthCount, GeometricObject GO, Ray ray, double t) {
Normal normal = GO.Cal_Normal(ray, t);
if (GO.Reflectivity > 0) {
Color GoColor = new Color(Cal_Reflection(GO, ReflectionDepthCount, ray, normal));
Color finalcolor = new Color(Cal_Light(GoColor, normal));
return finalcolor;
} else {
;
Color finalcolor = new Color(Cal_Light(GO.Color, normal));
return finalcolor;
}
}
public Color Cal_Light(Color color, Normal normal) {
ArrayList<Color> PixelShade = new ArrayList<Color>();
Color Final = new Color();
for (int l = 0; l < LightObjects.size(); l++) {
LightObject light = LightObjects.get(l);
Vector3D r_Dir = light.Pos.Sub(normal.Origin);
r_Dir.normalize();
Ray raytolight = new Ray(normal.Origin, r_Dir);
int WAS_HIT = 0;
for (int o = 0; o < GeoObjects.size(); o++) {
GeometricObject NGO = GeoObjects.get(o);
double hit = NGO.hit(raytolight);
if (hit != 0.0) {
WAS_HIT = 1;
}
}
PixelShade.add(light.ShadePixel(WAS_HIT, normal, r_Dir, color, AmbientLight, DiffuseLight));
}
for (int s = 0; s < PixelShade.size(); s++) {
Final.Add(PixelShade.get(s));
}
Final.Divide(PixelShade.size());
return Final;
}
public Color Cal_Reflection(GeometricObject OriginalObject, int ReflectionDepthCount, Ray InRay, Normal normal) {
if (ReflectionDepthCount <= MaxReflectionCount) {
GeometricObject LastGO = null;
Ray LastRay = null;
double LastT = 0.0;
double min = Double.MAX_VALUE;
Vector3D Origin = normal.Origin.Add(normal.Direction.Mul(1E-100));
Vector3D Direction = normal.Direction;
Direction.normalize();
Ray r = new Ray(Origin, Direction);
for (int o = 0; o < GeoObjects.size(); o++) {
GeometricObject GO = GeoObjects.get(o);
double hit = GO.hit(r);
if (hit != 0.0 && hit < min) {
min = hit;
LastGO = GO;
LastRay = r;
LastT = hit;
}
}
if (LastGO != null) {
System.out.println(ReflectionDepthCount);
Color Reflected = new Color(ShadePixel(ReflectionDepthCount++, LastGO, LastRay, LastT));
Color HitColor = new Color(LastGO.Color);
Color FinalColor = new Color(OriginalObject.Color);
Reflected.Mul(OriginalObject.Reflectivity);
HitColor.Mul(OriginalObject.Reflectivity);
FinalColor.Add(HitColor);
FinalColor.Add(Reflected);
FinalColor.Divide(2);
return FinalColor;
}
} else {
return BackGroundColor;
}
return OriginalObject.Color;
}
public void TraceArea(boolean SmoothTracing) {
Tracing = true;
if (SmoothTracing) {
for (int x = StartX; x < EndX; x++) {
for (int y = StartY; y < EndY; y++) {
TracePixelSmooth(x, y);
}
}
} else {
for (int x = StartX; x < EndX; x++) {
for (int y = StartY; y < EndY; y++) {
TracePixelFast(x, y);
}
}
}
}}
Here is my code for my sphere!
public class Sphere extends GeometricObject{
public Vector3D Center;
public double Radius;
public Sphere(Vector3D Center,Color Color,double Radius,double Reflectivity){
this.Center = Center;
this.Radius = Radius;
this.Color = Color;
this.Reflectivity = Reflectivity;
}
public double hit(Ray ray) {
double a = ray.Direction.Dot(ray.Direction);
double b = 2 * ray.Origin.Sub(Center).Dot(ray.Direction);
double c = ray.Origin.Sub(Center).Dot(ray.Origin.Sub(Center))-Radius*Radius;
double discreminant = b*b-4*a*c;
if(discreminant < 0.0f){
return 0.0;
}else{
double t = (-b - Math.sqrt(discreminant))/(2*a);
if(t > 10E-9){
return t;
}else{
return 0.0;
}
}
}
public Normal Cal_Normal(Ray ray,double t) {
Vector3D NPos = new Vector3D(ray.Origin.x + ray.Direction.x*t,ray.Origin.y + ray.Direction.y*t,ray.Origin.z + ray.Direction.z*t);
Vector3D NDir = NPos.Sub(Center).Div(Radius);
NDir.normalize();
return new Normal(NPos,NDir);
}}
And here is my launcher that controls the scene and tracer!
public class Launcher {
public static int Width = 600;
public static int Height = 600;
public static void main(String[] args) {
Scene scene = new Scene(Width, Height, 8, 8, new Color(0, 0, 0), new Camera(new Vector3D(0, 0, 30), new Vector3D(0.3, 0, -1), 1, true, Width, Height, 40), 0.1, 0.2, 2);
// scene.AddObject(new Sphere(new Vector3D(0,0,0),new
// Color(0,255,0),5,1));
// scene.AddObject(new Sphere(new Vector3D(-30,0,0),new
// Color(0,0,255),10,0.5));
// scene.AddObject(new Sphere(new Vector3D(30,0,0),new
// Color(255,0,0),10,0.5));
scene.AddObject(new Sphere(new Vector3D(15, 0, 0), new Color(255, 0, 0), 15, 1));
scene.AddObject(new Sphere(new Vector3D(-15, 0, 0), new Color(0, 0, 255), 15, 1));
scene.AddLight(new NonColoredLight(new Vector3D(0, 0, 20), 0.1));
long Start = System.currentTimeMillis();
BufferedImage Image = scene.Trace(false, 2);
long End = System.currentTimeMillis();
System.out.println("Milli Seconds To Render " + (End - Start));
File ImageFile = new File("TracedImage.png");
try {
ImageIO.write(Image, "PNG", ImageFile);
} catch (IOException e) {
e.printStackTrace();
}
}}
And here is the scene code!
public Scene(int width, int height, int row, int col, Color backGroundColor, Camera cam, double ambientLight, double diffuseLight, int maxReflectionCount) {
super();
Width = width;
Height = height;
Row = row;
Col = col;
BackGroundColor = backGroundColor;
Cam = cam;
AmbientLight = ambientLight;
DiffuseLight = diffuseLight;
MaxReflectionCount = maxReflectionCount;
GeoObjects = new ArrayList<GeometricObject>();
LightObjects = new ArrayList<LightObject>();
if (ambientLight > 1) {
AmbientLight = 1;
} else if (ambientLight < 0) {
AmbientLight = 0;
} else {
AmbientLight = ambientLight;
}
if (diffuseLight > 1) {
DiffuseLight = 1;
} else if (diffuseLight < 0) {
DiffuseLight = 0;
} else {
DiffuseLight = ambientLight;
}
}
public void AddObject(GeometricObject GO) {
GeoObjects.add(GO);
}
public void AddLight(LightObject Light) {
LightObjects.add(Light);
}
public BufferedImage Trace(boolean SmoothTracing, int ThreadCount) {
Image = new BufferedImage(Width, Height, BufferedImage.TYPE_INT_RGB);
Tracer tracer = new Tracer(Cam, Width, Height, Image, BackGroundColor, 0, 0, Width, Height, Row, Col, AmbientLight, DiffuseLight, MaxReflectionCount, GeoObjects, LightObjects);
tracer.TraceArea(SmoothTracing);
return Image;
}}
If you need me to post any more of my code just let me know!
Thanks in advance!
Variables for first reflection!
AmbientLight 0.1
BackGroundColor Color (id=39)
b 0.0
g 0.0
r 0.0
Cam Camera (id=41)
Direction Vector3D (id=104)
x 0.3
y 0.0
z -1.0
Distance 357.526077778263
FOV 40.0
Height 600
Perspective true
PixelSize 1.0
Pos Vector3D (id=115)
x 0.0
y 0.0
z 30.0
u Vector3D (id=116)
x -0.9578262852211514
y 0.0
z -0.28734788556634544
v Vector3D (id=117)
x 0.0
y 1.0
z 0.0
w Vector3D (id=118)
x 0.2873478855663454
y 0.0
z -0.9578262852211513
Width 600
ColCount 8
DiffuseLight 0.1
EndX 600
EndY 600
GeoObjects ArrayList<E> (id=43)
[0] Sphere (id=26)
[1] Sphere (id=34)
Height 600
Image BufferedImage (id=50)
accelerationPriority 0.5
colorModel DirectColorModel (id=120)
imageType 1
osis null
properties null
raster IntegerInterleavedRaster (id=124)
surfaceManager null
LightObjects ArrayList<E> (id=59)
[0] NonColoredLight (id=88)
MaxReflectionCount 2
RowCount 8
StartX 0
StartY 0
Tracing true
Width 600
OriginalObject Sphere (id=26)
Center Vector3D (id=60)
Color Color (id=61)
Radius 15.0
Reflectivity 1.0
ReflectionDepthCount 0
InRay Ray (id=29)
Direction Vector3D (id=62)
Origin Vector3D (id=63)
normal Normal (id=31)
Direction Vector3D (id=38)
Origin Vector3D (id=36)
LastGO Sphere (id=34)
Center Vector3D (id=64)
Color Color (id=65)
Radius 15.0
Reflectivity 1.0
LastRay Ray (id=35)
Direction Vector3D (id=38)
Origin Vector3D (id=36)
LastT 4.2468950498166125
min 4.2468950498166125
Origin Vector3D (id=36)
x 0.5809721247344103
y -0.023188722729640822
z 0.8135930637731328
Direction Vector3D (id=38)
x -0.32494278056317
y -0.02694400302823308
z 0.9453497818589108
r Ray (id=35)
Direction Vector3D (id=38)
Origin Vector3D (id=36)
Project Zip File
You are reflecting your ray exactly on the surface of the object. When checking intersections with the reflected ray, you hit on the same object again. You assume that checking if the distance is equal to 0.0 is enough to avoid this, but FP numbers are trickier than you think...

Raytracing: Dark rings appear

I am getting strange rings of black on my spheres when I render with lighting. I just added lighting and I cannot figure out why the black rings are being created.
Here is my code for my tracer.
public class Tracer {
public Camera Cam;
public int Width, Height;
public BufferedImage Image;
public Color BackGroundColor;
public int StartX, StartY, EndX, EndY,RowCount,ColCount;
public ArrayList<GeometricObject> GeoObjects;
public ArrayList<LightObject> LightObjects;
public boolean Tracing;
public double AmbientLight;
public Tracer(Camera cam, int width, int height, BufferedImage image, Color backGroundColor, int startX, int startY, int endX, int endY, int rowCount, int colCount, ArrayList<GeometricObject> Geoobjects,ArrayList<LightObject> Lightobjects,double ambientLight) {
super();
Cam = cam;
Width = width;
Height = height;
Image = image;
BackGroundColor = backGroundColor;
StartX = startX;
StartY = startY;
EndX = endX;
EndY = endY;
RowCount = rowCount;
ColCount = colCount;
GeoObjects = Geoobjects;
LightObjects = Lightobjects;
if(ambientLight > 1){
AmbientLight = 1;
}else if(ambientLight < 0){
AmbientLight = 0;
}else{
AmbientLight = ambientLight;
}
}
public void TracePixelFast(int x, int y) {
Color color = new Color(BackGroundColor.r,BackGroundColor.g,BackGroundColor.b);
for(int o = 0;o < GeoObjects.size();o++){
GeometricObject GO = GeoObjects.get(o);
Ray r = new Ray(Cam.GetRayPos(Width, Height, x, y, 1, 1, RowCount, ColCount), Cam.GetRayDir(Width, Height, x, y, 1,1, RowCount, ColCount));
double hit = GO.hit(r);
if (hit != 0.0) {
color = Cal_Pixel(x,y);
Image.setRGB(x, y, color.toInt());
break;
}
}
}
public void TracePixelSmooth(int x, int y) {
Image.setRGB(x, y,Cal_Pixel(x,y).toInt());
}
public Color Cal_Pixel(int x,int y){
Color color = new Color(BackGroundColor);
Color colorh = new Color(BackGroundColor);
Color bgc = new Color(BackGroundColor);
int HIT = 0;
int MISS = 0;
for (int row = 0; row < RowCount; row++) {
for (int col = 0; col < ColCount; col++) {
double min = Double.MAX_VALUE;
for (int o = 0; o < GeoObjects.size(); o++) {
GeometricObject GO = GeoObjects.get(o);
Ray r = new Ray(Cam.GetRayPos(Width, Height, x, y, row, col, RowCount, ColCount),Cam.GetRayDir(Width, Height, x, y, row, col, RowCount, ColCount));
double hit = GO.hit(r);
if (hit != 0.0 && hit < min) {
min = hit;
colorh = ShadePixel(GO, r, hit);
HIT++;
} else {
double min2 = Double.MAX_VALUE;
for (int o2 = 0; o2 < GeoObjects.size(); o2++) {
if(o!=o2){
GeometricObject GO2 = GeoObjects.get(o2);
double hit2 = GO2.hit(r);
if (hit2 != 0.0 && hit2 < min2) {
min2 = hit2;
bgc = ShadePixel(GO2, r, hit2);
}
}
}
MISS++;
}
}
}
}
for(int h = 0;h < HIT;h++){
color.Add(colorh);
}
for(int m = 0;m < MISS;m++){
color.Add(bgc);
}
color.Divide(RowCount * ColCount);
return color;
}
public Color ShadePixel(GeometricObject GO,Ray ray,double t){
ArrayList<Color> PixelShade = new ArrayList<Color>();
Normal normal = GO.Cal_Normal(ray, t);
for(int l = 0;l < LightObjects.size();l++){
LightObject light = LightObjects.get(l);
Vector3D r_Dir = light.Pos.Sub(normal.Origin);
r_Dir.normalize();
Ray raytolight = new Ray(normal.Origin,r_Dir);
int WAS_HIT = 0;
for(int o = 0;o < GeoObjects.size();o++){
GeometricObject NGO = GeoObjects.get(o);
double hit = NGO.hit(raytolight);
if (hit != 0.0) {
WAS_HIT = 1;
}
}
if(WAS_HIT == 0){
double Dot = normal.Direction.Dot(r_Dir);
if(Dot < 0){
Dot = 0;
}
double Diffuse = 1 - AmbientLight;
Color color = new Color(GO.Color);
double Shade = AmbientLight + Diffuse*Dot;
color.Mul(Shade);
PixelShade.add(color);
}else{
Color color = new Color(GO.Color);
double Shade = AmbientLight;
color.Mul(Shade);
PixelShade.add(color);
}
}
Color Final = new Color();
for(int s = 0;s < PixelShade.size();s++){
Final.Add(PixelShade.get(s));
}
Final.Divide(PixelShade.size());
return Final;
}
public void TraceArea(boolean SmoothTracing) {
Tracing = true;
if(SmoothTracing){
for (int x = StartX; x < EndX; x++) {
for (int y = StartY; y < EndY; y++) {
TracePixelSmooth(x,y);
}
}
}else{
for (int x = StartX; x < EndX; x++) {
for (int y = StartY; y < EndY; y++) {
TracePixelFast(x,y);
}
}
}
}
}
And here is the code for the sphere.
public class Sphere extends GeometricObject{
public Vector3D Center;
public double Radius;
public Sphere(Vector3D Center,double Radius,Color Color){
this.Center = Center;
this.Radius = Radius;
this.Color = Color;
}
public double hit(Ray ray) {
double a = ray.Direction.Dot(ray.Direction);
double b = 2 * ray.Origin.Sub(Center).Dot(ray.Direction);
double c = ray.Origin.Sub(Center).Dot(ray.Origin.Sub(Center))-Radius*Radius;
double discreminant = b*b-4*a*c;
if(discreminant < 0.0f){
return 0.0;
}else{
double t = (-b - Math.sqrt(discreminant))/(2*a);
if(t > 10E-9){
return t;
}else{
return 0.0;
}
}
}
public Normal Cal_Normal(Ray ray,double t) {
Vector3D NPos = new Vector3D(ray.Origin.x + ray.Direction.x*t,ray.Origin.y + ray.Direction.y*t,ray.Origin.z + ray.Direction.z*t);
Vector3D NDir = NPos.Sub(Center).Div(Radius);
return new Normal(NPos,NDir);
}
}
I am sure the problem is in shadepixel() but I could be wrong.
I just found out that the more objects that I add the more rings there are:
1 object no rings.
2 objects 1 ring.
3 objects 2 rings.
If you need me to post more of my code.Just ask and I will.
When I get back from school I will post my color class and fix the color problem. I still do not understand why the more objects (spheres) I add, the more rings there are. Can anyone explain to me why this is happening?
Here is my Color code.
public class Color {
public float r,g,b;
public Color(){
r = 0.0f;
g = 0.0f;
b = 0.0f;
}
public Color(float fr,float fg,float fb){
r = fr;
g = fg;
b = fb;
}
public Color(Color color){
r = color.r;
g = color.g;
b = color.b;
}
public void Add(Color color){
r += color.r;
g += color.g;
b += color.b;
}
public void Divide(int scalar){
r /= scalar;
g /= scalar;
b /= scalar;
}
public void Mul(double mul){
r *= mul;
g *= mul;
b *= mul;
}
public int toInt(){
return (int) (r*255)<<16 | (int) (g*255)<<8 | (int) (b*255);
}
There are multiple issues with this code, but the direct reason for the rings is that color component values are overflowing 0-255 range. This in turn is caused by incorrect calculations in what I take to be an attempt at antialiasing in Cal_Pixel(), as well as by no control whatsoever of numeric range in ShadePixel().
Think about how you can visually debug this scene.
First are the normals correct? Display them as the colour to see.
Taking the range for each component [-1..1] to the range [0..255]:
r = 255*(n.x + 1)/2;
g = 255*(n.y + 1)/2;
b = 255*(n.z + 1)/2;
Once you think they look correct move on to the next stage and build it up stage by stage.
e.g. you might look at if your dot product is as expected (again [-1..1] because the vectors are supposedly normalised):
r = 255*(dot + 1)/2;
g = 255*(dot + 1)/2;
b = 255*(dot + 1)/2;

Categories

Resources