awt - How to do 2D shadow casting in Java? -
i'm trying implement 2d shadow casting method in java following tutorial: http://ncase.me/sight-and-light/
i want stick line2d , polygon objects. here main part of code far:
(polygon p : quads.polygons) { (int = 0; < p.npoints; i++) { osgctx.setstroke(new basicstroke(0.1f)); line2d line = new line2d.double(mousepos.getx(), mousepos.gety(), p.xpoints[i], p.ypoints[i]); osgctx.draw(line); } osgctx.setstroke(new basicstroke(1.0f)); osgctx.draw(p); }
which gives result of this:
i confused when comes down building parametric form of lines. don't know how implement math java's methods. point me in right direction, code-wise, implementing this?
it's not entirely clear actual question is. there quite operations need when doing sort of graphics programming, , built-in functionality of java2d rather rudimentary here. can create point2d
, line2d
objects, , have structures available need, computations ... inconvenient (to least), , not supported @ all. example, can check whether 2 line2d
objects intersect. there no built-in way check where intersect.
however, when saw site linked, thought, "hey, fun".
and was fun :-)
i guess of questions have implicitly answered code below (sorry if comments not sufficient - feel free ask more focussed question parts not clear).
for reasons mentioned above, started creating small library of "frequently used geometry operation utilities". of classes library partially included in example below, standalone example.
package stackoverflow.shadows; import java.awt.alphacomposite; import java.awt.basicstroke; import java.awt.color; import java.awt.graphics; import java.awt.graphics2d; import java.awt.rectangle; import java.awt.renderinghints; import java.awt.shape; import java.awt.event.componentadapter; import java.awt.event.componentevent; import java.awt.event.mouseevent; import java.awt.event.mousemotionlistener; import java.awt.geom.affinetransform; import java.awt.geom.arc2d; import java.awt.geom.ellipse2d; import java.awt.geom.flatteningpathiterator; import java.awt.geom.line2d; import java.awt.geom.path2d; import java.awt.geom.pathiterator; import java.awt.geom.point2d; import java.awt.geom.rectangle2d; import java.awt.image.bufferedimage; import java.util.arraylist; import java.util.collections; import java.util.comparator; import java.util.list; import javax.swing.jframe; import javax.swing.jpanel; import javax.swing.swingutilities; public class shadowstest { public static void main(string[] args) { swingutilities.invokelater(new runnable() { @override public void run() { createandshowgui(); } }); } private static void createandshowgui() { jframe f = new jframe(); f.setdefaultcloseoperation(jframe.exit_on_close); f.getcontentpane().add(new shadowstestpanel()); f.setsize(500,500); f.setlocationrelativeto(null); f.setvisible(true); } } class shadowstestpanel extends jpanel implements mousemotionlistener { private final list<shape> shapes; private final point2d lightposition; private final list<line2d> borderlinesegments; private final list<list<line2d>> shapeslinesegments; private final bufferedimage smileyimage; private final bufferedimage skullimage; private final bufferedimage blendedimage; shadowstestpanel() { addmousemotionlistener(this); shapes = new arraylist<shape>(); shapes.add(new rectangle2d.double(160, 70, 80, 50)); shapes.add(new ellipse2d.double(290, 120, 50, 30)); affinetransform at0 = affinetransform.getrotateinstance( math.toradians(45), 320, 290); shapes.add( at0.createtransformedshape( new rectangle2d.double(300, 270, 40, 40))); shapes.add(new ellipse2d.double(60, 240, 80, 110)); shapeslinesegments = new arraylist<list<line2d>>(); (shape shape : shapes) { shapeslinesegments.add(shapes.computelinesegments(shape, 1.0)); } borderlinesegments = new arraylist<line2d>(); shapeslinesegments.add(borderlinesegments); lightposition = new point2d.double(); addcomponentlistener(new componentadapter() { @override public void componentresized(componentevent e) { borderlinesegments.clear(); borderlinesegments.add( new line2d.double(0,0,getwidth(),0)); borderlinesegments.add( new line2d.double(getwidth(),0,getwidth(),getheight())); borderlinesegments.add( new line2d.double(getwidth(),getheight(),0,getheight())); borderlinesegments.add( new line2d.double(0,getheight(),0,0)); } }); smileyimage = createsmileyimage(); skullimage = createskullimage(); blendedimage = createsmileyimage(); } private static bufferedimage createsmileyimage() { bufferedimage image = new bufferedimage(150, 150, bufferedimage.type_int_argb); graphics2d g = image.creategraphics(); g.setrenderinghint( renderinghints.key_antialiasing, renderinghints.value_antialias_on); g.setstroke(new basicstroke(5)); g.setcolor(color.yellow); g.fill(new ellipse2d.double(5, 5, 140, 140)); g.setcolor(color.black); g.draw(new ellipse2d.double(5, 5, 140, 140)); g.fill(new ellipse2d.double( 50-15, 50-15, 30, 30)); g.fill(new ellipse2d.double(100-15, 50-15, 30, 30)); g.draw(new arc2d.double(25, 25, 100, 100, 190, 160, arc2d.open)); g.dispose(); return image; } private static bufferedimage createskullimage() { bufferedimage image = new bufferedimage(150, 150, bufferedimage.type_int_argb); graphics2d g = image.creategraphics(); g.setrenderinghint( renderinghints.key_antialiasing, renderinghints.value_antialias_on); g.setstroke(new basicstroke(5)); g.setcolor(color.white); g.fill(new ellipse2d.double(5, 5, 140, 140)); g.setcolor(color.black); g.draw(new ellipse2d.double(5, 5, 140, 140)); g.fill(new ellipse2d.double( 50-15, 50-15, 30, 30)); g.fill(new ellipse2d.double(100-15, 50-15, 30, 30)); shape mouth = new arc2d.double(25, 25, 100, 100, 190, 160, arc2d.open); list<line2d> linesegments = shapes.computelinesegments(mouth, 2); (int i=0; i<linesegments.size(); i++) { line2d line = linesegments.get(i); rectangle b = line.getbounds(); rectangle r = new rectangle(b.x, b.y-8, b.width, 16); g.setcolor(color.white); g.fill(r); g.setcolor(color.black); g.draw(r); } g.dispose(); return image; } @override protected void paintcomponent(graphics gr) { super.paintcomponent(gr); graphics2d g = (graphics2d)gr; g.setcolor(new color(0,0,0,200)); g.fillrect(0,0,getwidth(),getheight()); g.setrenderinghint( renderinghints.key_antialiasing, renderinghints.value_antialias_on); g.setcolor(color.black); (shape shape : shapes) { g.draw(shape); } list<line2d> rays = createrays(lightposition); //paintrays(g, rays); list<point2d> closestintersections = computeclosestintersections(rays); collections.sort(closestintersections, points.byanglecomparator(lightposition)); //paintclosestintersections(g, closestintersections); //paintlinestointersections(g, closestintersections); shape lightshape = createlightshape(closestintersections); g.setcolor(color.white); g.fill(lightshape); g.drawimage(smileyimage, 150, 150, null); blend(skullimage, 150, 150, lightshape, blendedimage); g.drawimage(blendedimage, 150, 150, null); g.setcolor(color.yellow); double r = 10; g.fill(new ellipse2d.double( lightposition.getx()-r, lightposition.gety()-r, r+r, r+r)); } private static void blend( bufferedimage image, int x, int y, shape lightshape, bufferedimage result) { int w = image.getwidth(); int h = image.getheight(); graphics2d g = result.creategraphics(); g.setcomposite(alphacomposite.srcover); g.setcolor(new color(0,0,0,0)); g.fillrect(0,0,w,h); g.drawimage(image, 0, 0, null); g.translate(-x, -y); g.setcomposite(alphacomposite.srcout); g.fill(lightshape); g.dispose(); } private shape createlightshape( list<point2d> closestintersections) { path2d shadowshape = new path2d.double(); (int i=0; i<closestintersections.size(); i++) { point2d p = closestintersections.get(i); double x = p.getx(); double y = p.gety(); if (i == 0) { shadowshape.moveto(x, y); } else { shadowshape.lineto(x, y); } } shadowshape.closepath(); return shadowshape; } private void paintrays(graphics2d g, list<line2d> rays) { g.setcolor(color.yellow); (line2d ray : rays) { g.draw(ray); } } private void paintclosestintersections(graphics2d g, list<point2d> closestintersections) { g.setcolor(color.red); double r = 3; (point2d p : closestintersections) { g.fill(new ellipse2d.double( p.getx()-r, p.gety()-r, r+r, r+r)); } } private void paintlinestointersections(graphics2d g, list<point2d> closestintersections) { g.setcolor(color.red); (point2d p : closestintersections) { g.draw(new line2d.double(lightposition, p)); } } private list<point2d> computeclosestintersections(list<line2d> rays) { list<point2d> closestintersections = new arraylist<point2d>(); (line2d ray : rays) { point2d closestintersection = computeclosestintersection(ray); if (closestintersection != null) { closestintersections.add(closestintersection); } } return closestintersections; } private list<line2d> createrays(point2d lightposition) { final double deltarad = 0.0001; list<line2d> rays = new arraylist<line2d>(); (list<line2d> shapelinesegments : shapeslinesegments) { (line2d line : shapelinesegments) { line2d ray0 = new line2d.double(lightposition, line.getp1()); line2d ray1 = new line2d.double(lightposition, line.getp2()); rays.add(ray0); rays.add(ray1); rays.add(lines.rotate(ray0, +deltarad, null)); rays.add(lines.rotate(ray0, -deltarad, null)); rays.add(lines.rotate(ray1, +deltarad, null)); rays.add(lines.rotate(ray1, -deltarad, null)); } } return rays; } private point2d computeclosestintersection(line2d ray) { final double epsilon = 1e-6; point2d relativelocation = new point2d.double(); point2d absolutelocation = new point2d.double(); point2d closestintersection = null; double minrelativedistance = double.max_value; (list<line2d> linesegments : shapeslinesegments) { (line2d linesegment : linesegments) { boolean intersect = intersection.intersectlineline( ray, linesegment, relativelocation, absolutelocation); if (intersect) { if (relativelocation.gety() >= -epsilon && relativelocation.gety() <= 1+epsilon) { if (relativelocation.getx() >= -epsilon && relativelocation.getx() < minrelativedistance) { minrelativedistance = relativelocation.getx(); closestintersection = new point2d.double( absolutelocation.getx(), absolutelocation.gety()); } } } } } return closestintersection; } @override public void mousemoved(mouseevent e) { lightposition.setlocation(e.getpoint()); repaint(); } @override public void mousedragged(mouseevent e) { } } class points { /** * creates comparator compares points * angle of line between point , given * center * * @param center center * @return comparator */ public static comparator<point2d> byanglecomparator( final point2d center) { return new comparator<point2d>() { @override public int compare(point2d p0, point2d p1) { double dx0 = p0.getx() - center.getx(); double dy0 = p0.gety() - center.gety(); double dx1 = p1.getx() - center.getx(); double dy1 = p1.gety() - center.gety(); double angle0 = math.atan2(dy0, dx0); double angle1 = math.atan2(dy1, dx1); return double.compare(angle0, angle1); } }; } } class lines { /** * rotate given line around starting point, * given angle, , stores result in given * result line. if result line <code>null</code>, * new line created , returned. * * @param line line * @param anglerad rotation angle * @param result line * @return result line */ static line2d rotate(line2d line, double anglerad, line2d result) { double x0 = line.getx1(); double y0 = line.gety1(); double x1 = line.getx2(); double y1 = line.gety2(); double dx = x1 - x0;; double dy = y1 - y0; double sa = math.sin(anglerad); double ca = math.cos(anglerad); double nx = ca * dx - sa * dy; double ny = sa * dx + ca * dy; if (result == null) { result = new line2d.double(); } result.setline(x0, y0, x0+nx, y0+ny); return result; } } class intersection { /** * epsilon floating point computations */ private static final double epsilon = 1e-6; /** * computes intersection of given lines. * * @param line0 first line * @param line1 second line * @param relativelocation optional location stores * relative location of intersection point on * given line segments * @param absolutelocation optional location stores * absolute location of intersection point * @return whether lines intersect */ public static boolean intersectlineline( line2d line0, line2d line1, point2d relativelocation, point2d absolutelocation) { return intersectlineline( line0.getx1(), line0.gety1(), line0.getx2(), line0.gety2(), line1.getx1(), line1.gety1(), line1.getx2(), line1.gety2(), relativelocation, absolutelocation); } /** * computes intersection of specified lines. * * ported * http://www.geometrictools.com/libmathematics/intersection/ * wm5intrsegment2segment2.cpp * * @param s0x0 x-coordinate of point 0 of line segment 0 * @param s0y0 y-coordinate of point 0 of line segment 0 * @param s0x1 x-coordinate of point 1 of line segment 0 * @param s0y1 y-coordinate of point 1 of line segment 0 * @param s1x0 x-coordinate of point 0 of line segment 1 * @param s1y0 y-coordinate of point 0 of line segment 1 * @param s1x1 x-coordinate of point 1 of line segment 1 * @param s1y1 y-coordinate of point 1 of line segment 1 * @param relativelocation optional location stores * relative location of intersection point on * given line segments * @param absolutelocation optional location stores * absolute location of intersection point * @return whether lines intersect */ public static boolean intersectlineline( double s0x0, double s0y0, double s0x1, double s0y1, double s1x0, double s1y0, double s1x1, double s1y1, point2d relativelocation, point2d absolutelocation) { double dx0 = s0x1 - s0x0; double dy0 = s0y1 - s0y0; double dx1 = s1x1 - s1x0; double dy1 = s1y1 - s1y0; double invlen0 = 1.0 / math.sqrt(dx0*dx0+dy0*dy0); double invlen1 = 1.0 / math.sqrt(dx1*dx1+dy1*dy1); double dir0x = dx0 * invlen0; double dir0y = dy0 * invlen0; double dir1x = dx1 * invlen1; double dir1y = dy1 * invlen1; double c0x = s0x0 + dx0 * 0.5; double c0y = s0y0 + dy0 * 0.5; double c1x = s1x0 + dx1 * 0.5; double c1y = s1y0 + dy1 * 0.5; double cdx = c1x - c0x; double cdy = c1y - c0y; double dot = dotperp(dir0x, dir0y, dir1x, dir1y); if (math.abs(dot) > epsilon) { if (relativelocation != null || absolutelocation != null) { double dot0 = dotperp(cdx, cdy, dir0x, dir0y); double dot1 = dotperp(cdx, cdy, dir1x, dir1y); double invdot = 1.0/dot; double s0 = dot1*invdot; double s1 = dot0*invdot; if (relativelocation != null) { double n0 = (s0 * invlen0) + 0.5; double n1 = (s1 * invlen1) + 0.5; relativelocation.setlocation(n0, n1); } if (absolutelocation != null) { double x = c0x + s0 * dir0x; double y = c0y + s0 * dir0y; absolutelocation.setlocation(x, y); } } return true; } return false; } /** * returns perpendicular dot product, i.e. length * of vector (x0,y0,0)x(x1,y1,0). * * @param x0 coordinate x0 * @param y0 coordinate y0 * @param x1 coordinate x1 * @param y1 coordinate y1 * @return length of cross product vector */ private static double dotperp(double x0, double y0, double x1, double y1) { return x0*y1 - y0*x1; } } class shapes { /** * create list containing line segments approximate given * shape. * * @param shape shape * @param flatness allowed flatness * @return list of line segments */ static list<line2d> computelinesegments(shape shape, double flatness) { list<line2d> result = new arraylist<line2d>(); pathiterator pi = new flatteningpathiterator( shape.getpathiterator(null), flatness); double[] coords = new double[6]; double previous[] = new double[2]; double first[] = new double[2]; while (!pi.isdone()) { int segment = pi.currentsegment(coords); switch (segment) { case pathiterator.seg_moveto: previous[0] = coords[0]; previous[1] = coords[1]; first[0] = coords[0]; first[1] = coords[1]; break; case pathiterator.seg_close: result.add(new line2d.double( previous[0], previous[1], first[0], first[1])); previous[0] = first[0]; previous[1] = first[1]; break; case pathiterator.seg_lineto: result.add(new line2d.double( previous[0], previous[1], coords[0], coords[1])); previous[0] = coords[0]; previous[1] = coords[1]; break; case pathiterator.seg_quadto: // should never occur throw new assertionerror( "seg_quadto in flattened path!"); case pathiterator.seg_cubicto: // should never occur throw new assertionerror( "seg_cubicto in flattened path!"); } pi.next(); } return result; } }
Comments
Post a Comment