如何在Java中进行2D阴影投射?

我正在尝试按照本教程在Java中实现2D阴影投射方法: http : //ncase.me/sight-and-light/

我想坚持使用Line2D和Polygon对象。 到目前为止,这是我的代码的主要部分:

for (Polygon p : Quads.polygons) { for (int i = 0; i < 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); } 

结果如下: 在此处输入图像描述

当涉及到构建线的参数forms时,我感到困惑。 我不知道如何用Java的方法实现数学。 有人能指出我在正确的方向,代码方面,实现这一点?

你的实际问题并不完全清楚。 在进行这种图形编程时,您经常需要一些操作,而Java2D的内置function在这里相当简陋。 你可以创建Point2DLine2D对象,并且基本上可以使用你需要的所有结构 ,但是有些计算是……不方便(至少可以说),而有些则根本没有得到适当的支持。 例如,您只能检查两个Line2D对象是否相交。 但是没有内置的方法来检查它们相交的位置。

然而,当我看到你链接的网站时,我想,“嘿,这可能很有趣”。

它很有趣:-)

阴影

我猜你可以提出的大部分问题都是由下面的代码隐含地回答的(对不起,如果评论不充分 – 但是可以随意提出一个关于不清楚的部分的更集中的问题)。

由于上述原因,我开始创建一个“常用几何操作实用程序”的小型库。 此库中的某些类部分包含在下面的示例中,因此它是一个独立的示例。

 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 shapes; private final Point2D lightPosition; private final List borderLineSegments; private final List> shapesLineSegments; private final BufferedImage smileyImage; private final BufferedImage skullImage; private final BufferedImage blendedImage; ShadowsTestPanel() { addMouseMotionListener(this); shapes = new ArrayList(); 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>(); for (Shape shape : shapes) { shapesLineSegments.add(Shapes.computeLineSegments(shape, 1.0)); } borderLineSegments = new ArrayList(); 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 lineSegments = Shapes.computeLineSegments(mouth, 2); for (int i=0; i rays = createRays(lightPosition); //paintRays(g, rays); List 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 closestIntersections) { Path2D shadowShape = new Path2D.Double(); for (int i=0; i rays) { g.setColor(Color.YELLOW); for (Line2D ray : rays) { g.draw(ray); } } private void paintClosestIntersections(Graphics2D g, List closestIntersections) { g.setColor(Color.RED); double r = 3; for (Point2D p : closestIntersections) { g.fill(new Ellipse2D.Double( p.getX()-r, p.getY()-r, r+r, r+r)); } } private void paintLinesToIntersections(Graphics2D g, List closestIntersections) { g.setColor(Color.RED); for (Point2D p : closestIntersections) { g.draw(new Line2D.Double(lightPosition, p)); } } private List computeClosestIntersections(List rays) { List closestIntersections = new ArrayList(); for (Line2D ray : rays) { Point2D closestIntersection = computeClosestIntersection(ray); if (closestIntersection != null) { closestIntersections.add(closestIntersection); } } return closestIntersections; } private List createRays(Point2D lightPosition) { final double deltaRad = 0.0001; List rays = new ArrayList(); for (List shapeLineSegments : shapesLineSegments) { for (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; for (List lineSegments : shapesLineSegments) { for (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 a comparator that compares points by the * angle of the line between the point and the given * center * * @param center The center * @return The comparator */ public static Comparator byAngleComparator( final Point2D center) { return new Comparator() { @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 the given line around its starting point, by * the given angle, and stores the result in the given * result line. If the result line is null, * then a new line will be created and returned. * * @param line The line * @param angleRad The rotation angle * @param The result line * @return The 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 for floating point computations */ private static final double EPSILON = 1e-6; /** * Computes the intersection of the given lines. * * @param line0 The first line * @param line1 The second line * @param relativeLocation Optional location that stores the * relative location of the intersection point on * the given line segments * @param absoluteLocation Optional location that stores the * absolute location of the intersection point * @return Whether the 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 the intersection of the specified lines. * * Ported from * 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 that stores the * relative location of the intersection point on * the given line segments * @param absoluteLocation Optional location that stores the * absolute location of the intersection point * @return Whether the 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 the perpendicular dot product, ie the length * of the 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 The length of the cross product vector */ private static double dotPerp(double x0, double y0, double x1, double y1) { return x0*y1 - y0*x1; } } class Shapes { /** * Create a list containing line segments that approximate the given * shape. * * @param shape The shape * @param flatness The allowed flatness * @return The list of line segments */ static List computeLineSegments(Shape shape, double flatness) { List result = new ArrayList(); 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; } } 

我为我的游戏采用了不同的方法:我首先将视点周围的圆形区域定义为可见,然后切割不可见区域(阴影),而不是确定可见区域。

这里有我的方法的简短描述:

http://cartridgeslot0.blogspot.com.es/2014/07/improving-shadowcaster-short-tutorial.html

此外,您可以在此处找到此方法与平铺贴图和2D相机相结合:

http://cartridgeslot0.blogspot.com.es/2014/07/managing-2d-camera.html