Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
207 views
in Technique[技术] by (71.8m points)

awt - How to do 2D shadow casting in Java?

I'm currently trying to implement a 2D shadow casting method in Java by following this tutorial: http://ncase.me/sight-and-light/

I want to stick to Line2D and Polygon objects. Here is the main part of my code so far:

 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);
    }

Which gives a result of this: enter image description here

I get confused when it comes down to building the parametric form of the lines. I don't know how to implement the math with Java's methods. Could someone point me in the right direction, code-wise, for implementing this?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

It's not entirely clear what your actual question is. There are quite some operations that you frequently need when doing this sort of graphics programming, and the built-in functionality of Java2D is rather rudimentary here. You can create Point2D and Line2D objects, and basically have all structures available that you need, but some computations are ... inconvenient (to say the least), and some are not properly supported at all. For example, you can only check whether two Line2D objects intersect. But there is no built-in way to check where they intersect.

However, when I saw the site that you linked, I thought, "Hey, that could be fun".

And it WAS fun :-)

Shadows

I guess most of the questions that you could have about this are implicitly answered by the code below (sorry if the comments are not sufficient - but feel free to ask a more focussed question about the parts that are not clear).

For the reasons mentioned above, I started creating a small library of "frequently used geometry operation utilities". Some of the classes from this library are partially included in the example below, so that it is a 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>>();
        for (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);
        for (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);
        for (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();
        for (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);
        for (Line2D ray : rays)
        {
            g.draw(ray);
        }
    }

    private void paintClosestIntersections(Graphics2D g,
        List<Point2D> 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<Point2D> closestIntersections)
    {
        g.setColor(Color.RED);
        for (Point2D p : closestIntersections)
        {
            g.draw(new Line2D.Double(lightPosition, p));
        }
    }



    private List<Point2D> computeClosestIntersections(List<Line2D> rays)
    {
        List<Point2D> closestIntersections = new ArrayList<Point2D>();
        for (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>();
        for (List<Line2D> 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

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...