Lighting Effect in Java 2D

I’m trying to think of a way to add a lighting type effect to Mini Adventure… (http://laminos.newdawnsoftware.com). Its currently pure Java2D and I’d really like to keep it that way, however I don’t see anyway I can do any sort of alphaing effect while still expecting to keep my graphics accelerated?

Ideally, I’d like to draw everything dark and then alpha a bright area around the player. Any bright (forgive the pun) ideas how I could achieve this or something else to give some sort of lighting effect?

Thanks for any advice/ideas…

Kev

With or without alpha blending? Oh and you do know that you can turn on the opengl and transaccel flags in 1.4.2 to get accelerated alpha blending right?

With alpha blending, you could simply:

  1. Use an implementation of RadialGradientPaint (from Vincent Hardy’s Java 2D API text, source code below) and draw an image of a circular, distance-attenuated light source and use that as your light texture. Simply draw that image where the player is standing each frame to get a nice light attenuation effect.

You can adjust the intensity also by setting AlphaComposites with different float parameters before drawing the light image. A better way would be to have multiple light textures with different distance attenuations cached.

Without alpha blending, you could:

  1. Draw an alternate (or several alternate), “lighted-up” version(s) of the background and draw a circle of that image around the player as he/she moves.

It’s kind of primitive looking but that’s about as flash ( :wink: ) as it can get without alpha blending.

Both options I suggested will also give you hardware accelerated speeds.

Here’s the code for the RadialGradientPaint class I mentioned:

/*
 * @(#)RadialGradientPaint.java
 *
 * Copyright (c) 1999 Sun Microsystems, Inc. All Rights Reserved.
 *
 * Sun grants you ("Licensee") a non-exclusive, royalty free, license to use,
 * modify and redistribute this software in source and binary code form,
 * provided that i) this copyright notice and license appear on all copies of
 * the software; and ii) Licensee does not utilize the software in a manner
 * which is disparaging to Sun.
 *
 * This software is provided "AS IS," without a warranty of any kind. ALL
 * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING ANY
 * IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR
 * NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN AND ITS LICENSORS SHALL NOT BE
 * LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING
 * OR DISTRIBUTING THE SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL SUN OR ITS
 * LICENSORS BE LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT,
 * INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER
 * CAUSED AND REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF
 * OR INABILITY TO USE SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGES.
 *
 * This software is not designed or intended for use in on-line control of
 * aircraft, air traffic, aircraft navigation or aircraft communications; or in
 * the design, construction, operation or maintenance of any nuclear
 * facility. Licensee represents and warrants that it will not use or
 * redistribute the Software for such purposes.
 */

package com.sun.glf.goodies;

import java.awt.*;
import java.awt.geom.*;
import java.awt.image.*;
import javax.swing.JFrame;
import javax.swing.JComponent;

import com.sun.glf.*;
import com.sun.glf.util.*;
import com.sun.glf.util.*;

/**
 * This class provides a way to fill a shape with a radial color gradient
 * pattern.
 * Given an Ellipse and 2 Colors, color1 and color2, the paint will render 
 * a color starting at color1 at the center of the Ellipse to color2 at 
 * its boundary. All pixels lying outside the Ellipse boundary have the
 * color2 value.
 *
 * @author Vincent Hardy
 * @version 1.0, 09.11.1998
 */
public class RadialGradientPaint implements Paint {
  /* The Ellipse controlling colors */
  Color color1, color2;

  /** The Ellipse bounds */
  Rectangle2D.Float gradientBounds;

  /** Transparency */
  int transparency;

  /**
   * @return center gradient color
   */
  public Color getCenterColor(){
    return color1;
  }

  /**
   * @return boundary color
   */
  public Color getBoundaryColor(){
    return color2;
  }

  /**
   * @return gradient bounds
   */
  public Rectangle2D getBounds(){
    return (Rectangle2D)gradientBounds.clone();
  }

  /**
   * @param bounds the bounds of the Ellipse defining the gradient. User space.
   * @param color1 Color at the ellipse focal points.
   * @param color2 Color at the ellipse boundary and beyond.
   */
  public RadialGradientPaint(Rectangle2D bounds, Color color1, Color color2) {
    this.color1 = color1;
    this.color2 = color2;
    this.gradientBounds = new Rectangle2D.Float();
    gradientBounds.setRect((float)bounds.getX(), (float)bounds.getY(), 
                     (float)bounds.getWidth(), (float)bounds.getHeight());

    int a1 = color1.getAlpha();
    int a2 = color2.getAlpha();
    transparency = (((a1 & a2) == 0xff) ? OPAQUE : TRANSLUCENT);
  }
  
  /**
   * Creates and returns a context used to generate the color pattern.
   */
  public PaintContext createContext(ColorModel cm,
                            Rectangle deviceBounds,
                            Rectangle2D userBounds,
                            AffineTransform transform,
                            RenderingHints hints) {
    try{
      return (new RadialGradientPaintContext(gradientBounds, color1, color2, transform));
    }catch(NoninvertibleTransformException e){
      throw new IllegalArgumentException("transform should be invertible");
    }
  }

  /**
   * Return the transparency mode for this GradientPaint.
   * @see Transparency
   */
  public int getTransparency() {
    return transparency;
  }

  /**
   * Unit testing
   */
  public static void main(String args[]){
    //
    // Create a frame and display LayerCompositions containing
    // ShapeLayers rendered with FillRenderer using RadialGradientPaint
    //
    JFrame frame = new JFrame("RadialGradientPaint unit testing");
    frame.getContentPane().setLayout(new GridLayout(0, 2));
    frame.getContentPane().setBackground(Color.white);
    Shape rect = new Rectangle(20, 20, 160, 60);
    Dimension dim = new Dimension(200, 100);

    //
    // First, create paints and renderers
    //
    Color testColor = new Color(20, 40, 20);
    Paint ellipsePaint = new RadialGradientPaint(new Rectangle(40, 20, 70, 60), Color.white, testColor);
    Paint discPaint = new RadialGradientPaint(new Rectangle(40, 20, 60, 60), Color.white, testColor);
    Renderer ellipseFill = new FillRenderer(ellipsePaint);
    Renderer discFill = new FillRenderer(discPaint);

    //
    // Create LayerCompositions using different transforms
    //

    // No transforms
    AffineTransform transform = null;
    frame.getContentPane().add(makeNewComponent(dim, rect, ellipseFill, transform, "No transform"));
    frame.getContentPane().add(makeNewComponent(dim, rect, discFill, transform, "No transform"));

    // Translation
    transform = AffineTransform.getTranslateInstance(40, 40);
    frame.getContentPane().add(makeNewComponent(dim, rect, ellipseFill, transform, "Translation"));
    frame.getContentPane().add(makeNewComponent(dim, rect, discFill, transform, "Translation"));

    // Scale
    transform = AffineTransform.getScaleInstance(2, 2);
    frame.getContentPane().add(makeNewComponent(dim, rect, ellipseFill, transform, "Scale"));
    frame.getContentPane().add(makeNewComponent(dim, rect, discFill, transform, "Scale"));

    // Rotation
    transform = AffineTransform.getRotateInstance(Math.PI/4, 100, 50);
    frame.getContentPane().add(makeNewComponent(dim, rect, ellipseFill, transform, "Rotation"));
    frame.getContentPane().add(makeNewComponent(dim, rect, discFill, transform, "Rotation"));

    // Shear
    transform = AffineTransform.getShearInstance(.5f, 0f);
    frame.getContentPane().add(makeNewComponent(dim, rect, ellipseFill, transform, "Shear"));
    frame.getContentPane().add(makeNewComponent(dim, rect, discFill, transform, "Shear"));

    frame.pack();
    frame.setVisible(true);
  }

  private static JComponent makeNewComponent(Dimension dim, Shape shape, Renderer renderer, AffineTransform transform, String toolTip){
    LayerComposition cmp = new LayerComposition(dim);
    Rectangle rect = new Rectangle(-1, -1, dim.width, dim.height);
    ShapeLayer layer = new ShapeLayer(cmp, shape, renderer);
    layer.setTransform(transform);
    ShapeLayer boundsLayer = new ShapeLayer(cmp, rect, new StrokeRenderer(Color.black, 1));
    cmp.setLayers(new Layer[]{layer, boundsLayer});
    CompositionComponent comp = new CompositionComponent(cmp);
    comp.setToolTipText(toolTip);
    return comp;
  }
}

That’s a sizable chunk. Hope that helps. :slight_smile:

Thanks for the info, but won’t using Alpha of any sort (even a GradientPaint) be pretty slow on some platforms. I’ve tried lots of alphay things in the past and found the performance hit scary. I’ll try it out anyway tho! :slight_smile:

Yep, I’m aware of thse, although the opengl flag is 1.5 only isn’t it (I don’t think the pipeline existed in 1.4)? The transaccel flag only works under windows also. Hence my fear of alpha use :confused:

Kev

[quote]Thanks for the info, but won’t using Alpha of any sort (even a GradientPaint) be pretty slow on some platforms. I’ve tried lots of alphay things in the past and found the performance hit scary. I’ll try it out anyway tho! :slight_smile:
[/quote]
I agree that it’s slow without hardware acceleration, especially if the translucent image is large and it’s worse if the translucent image persists in the scene. However, with a smaller and temporary light source, I won’t expect too much of a PERCEIVED performance hit.

Also, just to clarify, it is definitely better to render your light source as an offscreen image rather then drawing directly on screen using RadialGradientPaint each time.

Maybe you could make use of trans hardware accel if it exists and have an option in your game to turn on/off translucent imagery.

Hmm… For some disturbing reason I’ve always thought that a limited openGL solution existed on the pipeline of the linux version. I wonder what that opengl flag in 1.4.2 does… probably nothing.