VASSAL.build.module.map.MapShader.java Source code

Java tutorial

Introduction

Here is the source code for VASSAL.build.module.map.MapShader.java

Source

/*
 * $Id$
 *
 * Copyright (c) 2005 by Rodney Kinney, Brent Easton
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License (LGPL) as published by the Free Software Foundation.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, copies are available
 * at http://www.opensource.org.
 */

package VASSAL.build.module.map;

import java.awt.AlphaComposite;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Composite;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Paint;
import java.awt.Rectangle;
import java.awt.Stroke;
import java.awt.TexturePaint;
import java.awt.event.ActionListener;
import java.awt.geom.AffineTransform;
import java.awt.geom.Area;
import java.awt.image.BufferedImage;
import java.io.File;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;

import org.apache.commons.lang.builder.HashCodeBuilder;

import VASSAL.build.AbstractConfigurable;
import VASSAL.build.AutoConfigurable;
import VASSAL.build.Buildable;
import VASSAL.build.GameModule;
import VASSAL.build.module.GameComponent;
import VASSAL.build.module.IMap;
import VASSAL.build.module.Map;
import VASSAL.build.module.documentation.HelpFile;
import VASSAL.build.module.map.boardPicker.Board;
import VASSAL.command.Command;
import VASSAL.configure.ColorConfigurer;
import VASSAL.configure.Configurer;
import VASSAL.configure.ConfigurerFactory;
import VASSAL.configure.IconConfigurer;
import VASSAL.configure.StringArrayConfigurer;
import VASSAL.configure.StringEnum;
import VASSAL.configure.VisibilityCondition;
import VASSAL.counters.Decorator;
import VASSAL.counters.GamePiece;
import VASSAL.counters.Stack;
import VASSAL.i18n.Resources;
import VASSAL.tools.LaunchButton;
import VASSAL.tools.NamedKeyStroke;
import VASSAL.tools.UniqueIdManager;
import VASSAL.tools.image.ImageUtils;
import VASSAL.tools.imageop.AbstractTileOpImpl;
import VASSAL.tools.imageop.ImageOp;
import VASSAL.tools.imageop.Op;

/**
 * Draw shaded regions on a map.
 *
 * @author Brent Easton
 */
public class MapShader extends AbstractConfigurable
        implements GameComponent, Drawable, UniqueIdManager.Identifyable {

    public static final String NAME = "name";
    public static final String ALWAYS_ON = "alwaysOn";
    public static final String STARTS_ON = "startsOn";
    public static final String HOT_KEY = "hotkey";
    public static final String ICON = "icon";
    public static final String BUTTON_TEXT = "buttonText";
    public static final String TOOLTIP = "tooltip";
    public static final String BOARDS = "boards";
    public static final String BOARD_LIST = "boardList";

    public static final String ALL_BOARDS = "Yes";
    public static final String EXC_BOARDS = "No, exclude Boards in list";
    public static final String INC_BOARDS = "No, only shade Boards in List";

    protected static UniqueIdManager idMgr = new UniqueIdManager("MapShader");

    protected LaunchButton launch;
    protected boolean alwaysOn = false;
    protected boolean startsOn = false;
    protected String boardSelection = ALL_BOARDS;
    protected String[] boardList = new String[0];
    protected boolean shadingVisible;
    protected boolean scaleImage;
    protected Map map;
    protected String id;

    protected Area boardClip = null;

    public static final String TYPE = "type";
    public static final String DRAW_OVER = "drawOver";
    public static final String PATTERN = "pattern";
    public static final String COLOR = "color";
    public static final String IMAGE = "image";
    public static final String SCALE_IMAGE = "scaleImage";
    public static final String OPACITY = "opacity";
    public static final String BORDER = "border";
    public static final String BORDER_COLOR = "borderColor";
    public static final String BORDER_WIDTH = "borderWidth";
    public static final String BORDER_OPACITY = "borderOpacity";

    public static final String BG_TYPE = "Background";
    public static final String FG_TYPE = "Foreground";

    public static final String TYPE_25_PERCENT = "25%";
    public static final String TYPE_50_PERCENT = "50%";
    public static final String TYPE_75_PERCENT = "75%";
    public static final String TYPE_SOLID = "100% (Solid)";
    public static final String TYPE_IMAGE = "Custom Image";

    protected String imageName;
    protected Color color = Color.BLACK;
    protected String type = FG_TYPE;
    protected boolean drawOver = false;
    protected String pattern = TYPE_25_PERCENT;
    protected int opacity = 100;
    protected boolean border = false;
    protected Color borderColor = Color.BLACK;
    protected int borderWidth = 1;
    protected int borderOpacity = 100;

    protected Area shape;
    @Deprecated
    protected BufferedImage shadePattern = null;
    protected Rectangle patternRect = new Rectangle();

    protected ImageOp srcOp;

    protected TexturePaint texture = null;
    protected java.util.Map<Double, TexturePaint> textures = new HashMap<Double, TexturePaint>();
    protected AlphaComposite composite = null;
    protected AlphaComposite borderComposite = null;
    protected BasicStroke stroke = null;

    public void draw(Graphics g, Map map) {
        if (shadingVisible) {

            double zoom = map.getZoom();
            buildStroke(zoom);

            final Graphics2D g2 = (Graphics2D) g;

            final Composite oldComposite = g2.getComposite();
            final Color oldColor = g2.getColor();
            final Paint oldPaint = g2.getPaint();
            final Stroke oldStroke = g2.getStroke();

            g2.setComposite(getComposite());
            g2.setColor(getColor());
            g2.setPaint(scaleImage && pattern.equals(TYPE_IMAGE) && imageName != null ? getTexture(zoom)
                    : getTexture());
            Area area = getShadeShape(map);
            if (zoom != 1.0) {
                area = new Area(AffineTransform.getScaleInstance(zoom, zoom).createTransformedShape(area));
            }
            g2.fill(area);
            if (border) {
                g2.setComposite(getBorderComposite());
                g2.setStroke(getStroke(map.getZoom()));
                g2.setColor(getBorderColor());
                g2.draw(area);
            }

            g2.setComposite(oldComposite);
            g2.setColor(oldColor);
            g2.setPaint(oldPaint);
            g2.setStroke(oldStroke);
        }
    }

    /**
     * Get/Build the AlphaComposite used to draw the semi-transparent shade/
     */
    protected AlphaComposite getComposite() {
        if (composite == null) {
            buildComposite();
        }
        return composite;
    }

    protected void buildComposite() {
        composite = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, opacity / 100.0f);
    }

    protected AlphaComposite getBorderComposite() {
        if (borderComposite == null) {
            borderComposite = buildBorderComposite();
        }
        return borderComposite;
    }

    protected AlphaComposite buildBorderComposite() {
        return AlphaComposite.getInstance(AlphaComposite.SRC_OVER, borderOpacity / 100.0f);
    }

    /**
     * Get/Build the shape of the shade.
     */
    protected Area getShadeShape(Map map) {
        final Area myShape = type.equals(FG_TYPE) ? new Area() : new Area(getBoardClip());

        for (GamePiece p : map.getPieces()) {
            checkPiece(myShape, p);
        }

        return myShape;
    }

    protected void checkPiece(Area area, GamePiece piece) {
        if (piece instanceof Stack) {
            Stack s = (Stack) piece;
            for (int i = 0; i < s.getPieceCount(); i++) {
                checkPiece(area, s.getPieceAt(i));
            }
        } else {
            ShadedPiece shaded = (ShadedPiece) Decorator.getDecorator(piece, ShadedPiece.class);
            if (shaded != null) {
                Area shape = shaded.getArea(this);
                if (shape != null) {
                    if (type.equals(FG_TYPE)) {
                        area.add(shape);
                    } else {
                        area.subtract(shape);
                    }
                }
            }
        }
    }

    /**
     * Get/Build the repeating rectangle used to generate the shade texture
     * pattern.
     */
    protected BufferedImage getShadePattern() {
        if (srcOp == null)
            buildShadePattern();
        return srcOp.getImage();
    }

    protected BufferedImage getShadePattern(double zoom) {
        if (srcOp == null)
            buildShadePattern();
        return Op.scale(srcOp, zoom).getImage();
    }

    protected Rectangle getPatternRect() {
        return patternRect;
    }

    protected Rectangle getPatternRect(double zoom) {
        return new Rectangle((int) Math.round(zoom * patternRect.width),
                (int) Math.round(zoom * patternRect.height));
    }

    protected void buildShadePattern() {
        srcOp = pattern.equals(TYPE_IMAGE) && imageName != null ? Op.load(imageName)
                : new PatternOp(color, pattern);
        patternRect = new Rectangle(srcOp.getSize());
    }

    private static class PatternOp extends AbstractTileOpImpl {
        private final Color color;
        private final String pattern;
        private final int hash;

        public PatternOp(Color color, String pattern) {
            if (color == null || pattern == null)
                throw new IllegalArgumentException();
            this.color = color;
            this.pattern = pattern;

            hash = new HashCodeBuilder().append(color).append(pattern).toHashCode();
        }

        public BufferedImage eval() throws Exception {
            final BufferedImage im = ImageUtils.createCompatibleTranslucentImage(2, 2);
            final Graphics2D g = im.createGraphics();
            g.setColor(color);
            if (TYPE_25_PERCENT.equals(pattern)) {
                g.drawLine(0, 0, 0, 0);
            } else if (TYPE_50_PERCENT.equals(pattern)) {
                g.drawLine(0, 0, 0, 0);
                g.drawLine(1, 1, 1, 1);
            } else if (TYPE_75_PERCENT.equals(pattern)) {
                g.drawLine(0, 0, 1, 0);
                g.drawLine(1, 1, 1, 1);
            } else {
                g.drawLine(0, 0, 1, 0);
                g.drawLine(0, 1, 1, 1);
            }
            g.dispose();
            return im;
        }

        protected void fixSize() {
        }

        @Override
        public Dimension getSize() {
            return new Dimension(2, 2);
        }

        @Override
        public int getWidth() {
            return 2;
        }

        @Override
        public int getHeight() {
            return 2;
        }

        public List<VASSAL.tools.opcache.Op<?>> getSources() {
            return Collections.emptyList();
        }

        @Override
        public boolean equals(Object o) {
            if (this == o)
                return true;
            if (!(o instanceof PatternOp))
                return false;
            return color.equals(((PatternOp) o).color) && pattern.equals(((PatternOp) o).pattern);
        }

        @Override
        public int hashCode() {
            return hash;
        }
    }

    protected BasicStroke getStroke(double zoom) {
        if (stroke == null) {
            buildStroke(zoom);
        }

        return stroke;
    }

    protected void buildStroke(double zoom) {
        stroke = new BasicStroke((float) Math.min(borderWidth * zoom, 1.0), BasicStroke.CAP_ROUND,
                BasicStroke.JOIN_ROUND);
    }

    public Color getBorderColor() {
        return borderColor;
    }

    /**
     * Get/Build the textured paint used to fill in the Shade
     */
    protected TexturePaint getTexture() {
        if (texture == null) {
            buildTexture();
        }
        return texture;
    }

    protected TexturePaint getTexture(double zoom) {
        if (zoom == 1.0) {
            return getTexture();
        }
        TexturePaint texture = textures.get(zoom);
        if (texture == null) {
            texture = new TexturePaint(getShadePattern(zoom), getPatternRect(zoom));
            textures.put(zoom, texture);
        }
        return texture;
    }

    protected void buildTexture() {
        if (getShadePattern() != null) {
            texture = new TexturePaint(getShadePattern(), getPatternRect());
        }
    }

    public Color getColor() {
        return color;
    }

    /**
     * Is this Shade drawn over or under counters?
     */
    public boolean drawAboveCounters() {
        return drawOver;
    }

    public String[] getAttributeNames() {
        return new String[] { NAME, ALWAYS_ON, STARTS_ON, BUTTON_TEXT, TOOLTIP, ICON, HOT_KEY, BOARDS, BOARD_LIST,
                TYPE, DRAW_OVER, PATTERN, COLOR, IMAGE, SCALE_IMAGE, OPACITY, BORDER, BORDER_COLOR, BORDER_WIDTH,
                BORDER_OPACITY };
    }

    public Class<?>[] getAttributeTypes() {
        return new Class<?>[] { String.class, Boolean.class, Boolean.class, String.class, String.class,
                IconConfig.class, NamedKeyStroke.class, BoardPrompt.class, String[].class, TypePrompt.class,
                Boolean.class, PatternPrompt.class, Color.class, Image.class, Boolean.class, Integer.class,
                Boolean.class, Color.class, Integer.class, Integer.class };
    }

    public String[] getAttributeDescriptions() {
        return new String[] { Resources.getString(Resources.NAME_LABEL),
                Resources.getString("Editor.MapShader.shading_on"), //$NON-NLS-1$
                Resources.getString("Editor.MapShader.shading_start"), //$NON-NLS-1$
                Resources.getString(Resources.BUTTON_TEXT), Resources.getString(Resources.TOOLTIP_TEXT),
                Resources.getString(Resources.BUTTON_ICON), Resources.getString(Resources.HOTKEY_LABEL),
                Resources.getString("Editor.MapShader.shade_boards"), //$NON-NLS-1$
                Resources.getString("Editor.MapShader.board_list"), //$NON-NLS-1$
                Resources.getString("Editor.MapShader.type"), //$NON-NLS-1$
                Resources.getString("Editor.MapShader.shade_top"), //$NON-NLS-1$
                Resources.getString("Editor.MapShader.pattern"), //$NON-NLS-1$
                Resources.getString(Resources.COLOR_LABEL), Resources.getString("Editor.MapShader.image"), //$NON-NLS-1$
                Resources.getString("Editor.MapShader.scale"), //$NON-NLS-1$
                Resources.getString("Editor.MapShader.opacity"), //$NON-NLS-1$
                Resources.getString("Editor.MapShader.border"), //$NON-NLS-1$
                Resources.getString("Editor.MapShader.border_color"), //$NON-NLS-1$
                Resources.getString("Editor.MapShader.border_width"), //$NON-NLS-1$
                Resources.getString("Editor.MapShader.border_opacity"), //$NON-NLS-1$
        };
    }

    public static class TypePrompt extends StringEnum {
        public String[] getValidValues(AutoConfigurable target) {
            return new String[] { FG_TYPE, BG_TYPE };
        }
    }

    public static class PatternPrompt extends StringEnum {
        public String[] getValidValues(AutoConfigurable target) {
            return new String[] { TYPE_25_PERCENT, TYPE_50_PERCENT, TYPE_75_PERCENT, TYPE_SOLID, TYPE_IMAGE };
        }
    }

    public Class<?>[] getAllowableConfigureComponents() {
        return new Class<?>[0];
    }

    public static class BoardPrompt extends StringEnum {
        public String[] getValidValues(AutoConfigurable target) {
            return new String[] { ALL_BOARDS, EXC_BOARDS, INC_BOARDS };
        }
    }

    public MapShader() {

        ActionListener al = new ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent e) {
                toggleShading();
            }
        };
        launch = new LaunchButton("Shade", TOOLTIP, BUTTON_TEXT, HOT_KEY, ICON, al);
        launch.setEnabled(false);
        setLaunchButtonVisibility();
        setConfigureName("Shading");
        reset();
    }

    public void reset() {
        shadingVisible = isAlwaysOn() || isStartsOn();
    }

    protected void toggleShading() {
        setShadingVisibility(!shadingVisible);
    }

    public void setShadingVisibility(boolean b) {
        shadingVisible = b;
        map.repaint();
    }

    protected boolean isAlwaysOn() {
        return alwaysOn;
    }

    protected boolean isStartsOn() {
        return startsOn;
    }

    protected IMap getMap() {
        return map;
    }

    public Area getBoardClip() {
        buildBoardClip();
        return boardClip;
    }

    /**
     * Build a clipping region excluding boards that do not needed to be Shaded.
     */
    protected void buildBoardClip() {

        if (boardClip == null) {
            boardClip = new Area();
            for (Board b : map.getBoards()) {
                String boardName = b.getName();
                boolean doShade = false;
                if (boardSelection.equals(ALL_BOARDS)) {
                    doShade = true;
                } else if (boardSelection.equals(EXC_BOARDS)) {
                    doShade = true;
                    for (int i = 0; i < boardList.length && doShade; i++) {
                        doShade = !boardList[i].equals(boardName);
                    }
                } else if (boardSelection.equals(INC_BOARDS)) {
                    for (int i = 0; i < boardList.length && !doShade; i++) {
                        doShade = boardList[i].equals(boardName);
                    }
                }
                if (doShade) {
                    boardClip.add(new Area(b.bounds()));
                }
            }
        }
    }

    public void setLaunchButtonVisibility() {
        launch.setVisible(!isAlwaysOn());
    }

    /*
     * -----------------------------------------------------------------------
     * GameComponent Implementation
     * -----------------------------------------------------------------------
     */
    public void setup(boolean gameStarting) {
        launch.setEnabled(gameStarting);
        if (!gameStarting) {
            boardClip = null;
        }
    }

    public Command getRestoreCommand() {
        return null;
    }

    public static class IconConfig implements ConfigurerFactory {
        public Configurer getConfigurer(AutoConfigurable c, String key, String name) {
            return new IconConfigurer(key, name, ((MapShader) c).launch.getAttributeValueString(ICON));
        }
    }

    public void setAttribute(String key, Object value) {
        if (NAME.equals(key)) {
            setConfigureName((String) value);
            if (launch.getAttributeValueString(TOOLTIP) == null) {
                launch.setAttribute(TOOLTIP, value);
            }
        } else if (ALWAYS_ON.equals(key)) {
            if (value instanceof String) {
                value = Boolean.valueOf((String) value);
            }
            alwaysOn = ((Boolean) value).booleanValue();
            setLaunchButtonVisibility();
            reset();
        } else if (STARTS_ON.equals(key)) {
            if (value instanceof String) {
                value = Boolean.valueOf((String) value);
            }
            startsOn = ((Boolean) value).booleanValue();
            setLaunchButtonVisibility();
            reset();
        } else if (BOARDS.equals(key)) {
            boardSelection = (String) value;
        } else if (BOARD_LIST.equals(key)) {
            if (value instanceof String) {
                value = StringArrayConfigurer.stringToArray((String) value);
            }
            boardList = (String[]) value;
        } else if (TYPE.equals(key)) {
            type = (String) value;
        } else if (DRAW_OVER.equals(key)) {
            if (value instanceof String) {
                value = Boolean.valueOf((String) value);
            }
            drawOver = ((Boolean) value).booleanValue();
        } else if (PATTERN.equals(key)) {
            pattern = (String) value;
            buildShadePattern();
            buildTexture();
        } else if (COLOR.equals(key)) {
            if (value instanceof String) {
                value = ColorConfigurer.stringToColor((String) value);
            }
            color = (Color) value;
            buildShadePattern();
            buildTexture();
        } else if (IMAGE.equals(key)) {
            if (value instanceof File) {
                value = ((File) value).getName();
            }
            imageName = (String) value;
            buildShadePattern();
            textures.clear();
            buildTexture();
        } else if (SCALE_IMAGE.equals(key)) {
            if (value instanceof String) {
                value = Boolean.valueOf((String) value);
            }
            scaleImage = ((Boolean) value).booleanValue();
        } else if (BORDER.equals(key)) {
            if (value instanceof String) {
                value = Boolean.valueOf((String) value);
            }
            border = ((Boolean) value).booleanValue();
        } else if (BORDER_COLOR.equals(key)) {
            if (value instanceof String) {
                value = ColorConfigurer.stringToColor((String) value);
            }
            borderColor = (Color) value;
        } else if (BORDER_WIDTH.equals(key)) {
            if (value instanceof String) {
                value = Integer.valueOf((String) value);
            }
            borderWidth = ((Integer) value).intValue();
            if (borderWidth < 0) {
                borderWidth = 0;
            }
            stroke = null;
        } else if (OPACITY.equals(key)) {
            if (value instanceof String) {
                value = Integer.valueOf((String) value);
            }
            opacity = ((Integer) value).intValue();
            if (opacity < 0 || opacity > 100) {
                opacity = 100;
            }
            buildComposite();
        } else if (BORDER_OPACITY.equals(key)) {
            if (value instanceof String) {
                value = Integer.valueOf((String) value);
            }
            borderOpacity = ((Integer) value).intValue();
            if (borderOpacity < 0 || borderOpacity > 100) {
                borderOpacity = 100;
            }
            buildBorderComposite();
        } else {
            launch.setAttribute(key, value);
        }
    }

    public String getAttributeValueString(String key) {
        if (NAME.equals(key)) {
            return getConfigureName() + "";
        } else if (ALWAYS_ON.equals(key)) {
            return String.valueOf(isAlwaysOn());
        } else if (STARTS_ON.equals(key)) {
            return String.valueOf(isStartsOn());
        } else if (BOARDS.equals(key)) {
            return boardSelection + "";
        } else if (BOARD_LIST.equals(key)) {
            return StringArrayConfigurer.arrayToString(boardList);
        } else if (TYPE.equals(key)) {
            return type + "";
        } else if (DRAW_OVER.equals(key)) {
            return String.valueOf(drawOver);
        } else if (PATTERN.equals(key)) {
            return pattern + "";
        } else if (COLOR.equals(key)) {
            return ColorConfigurer.colorToString(color);
        } else if (IMAGE.equals(key)) {
            return imageName;
        } else if (SCALE_IMAGE.equals(key)) {
            return String.valueOf(scaleImage);
        } else if (BORDER.equals(key)) {
            return String.valueOf(border);
        } else if (BORDER_COLOR.equals(key)) {
            return ColorConfigurer.colorToString(borderColor);
        } else if (BORDER_WIDTH.equals(key)) {
            return borderWidth + "";
        } else if (OPACITY.equals(key)) {
            return opacity + "";
        } else if (BORDER_OPACITY.equals(key)) {
            return borderOpacity + "";
        } else {
            return launch.getAttributeValueString(key);
        }

    }

    public VisibilityCondition getAttributeVisibility(String name) {
        if (ICON.equals(name) || HOT_KEY.equals(name) || BUTTON_TEXT.equals(name) || STARTS_ON.equals(name)
                || TOOLTIP.equals(name)) {
            return new VisibilityCondition() {
                public boolean shouldBeVisible() {
                    return !isAlwaysOn();
                }
            };
        } else if (BOARD_LIST.equals(name)) {
            return new VisibilityCondition() {
                public boolean shouldBeVisible() {
                    return !boardSelection.equals(ALL_BOARDS);
                }
            };
        } else if (COLOR.equals(name)) {
            return new VisibilityCondition() {
                public boolean shouldBeVisible() {
                    return !pattern.equals(TYPE_IMAGE);
                }
            };
        } else if (IMAGE.equals(name)) {
            return new VisibilityCondition() {
                public boolean shouldBeVisible() {
                    return pattern.equals(TYPE_IMAGE);
                }
            };
        } else if (SCALE_IMAGE.equals(name)) {
            return new VisibilityCondition() {
                public boolean shouldBeVisible() {
                    return pattern.equals(TYPE_IMAGE);
                }
            };
        } else if (BORDER_COLOR.equals(name) || BORDER_WIDTH.equals(name) || BORDER_OPACITY.equals(name)) {
            return new VisibilityCondition() {
                public boolean shouldBeVisible() {
                    return border;
                }
            };
        } else {
            return super.getAttributeVisibility(name);
        }
    }

    public static String getConfigureTypeName() {
        return Resources.getString("Editor.MapShader.component_type"); //$NON-NLS-1$
    }

    public void removeFrom(Buildable parent) {
        GameModule.getGameModule().getToolBar().remove(launch);
        GameModule.getGameModule().getGameState().removeGameComponent(this);
        map.removeDrawComponent(this);
        idMgr.remove(this);
    }

    public HelpFile getHelpFile() {
        return HelpFile.getReferenceManualPage("Map.htm", "MapShading");
    }

    public void addTo(Buildable parent) {
        GameModule.getGameModule().getToolBar().add(launch);
        launch.setAlignmentY(0.0F);
        GameModule.getGameModule().getGameState().addGameComponent(this);
        map = (Map) parent;
        map.addDrawComponent(this);
        idMgr.add(this);
        validator = idMgr;
        setAttributeTranslatable(NAME, false);
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    /**
     * Pieces that contribute to shading must implement this interface
     */
    public static interface ShadedPiece {
        /**
         * Returns the Area to add to (or subtract from) the area drawn by the MapShader's.
         * Area is assumed to be at zoom factor 1.0
         * @param shader
         * @return the Area contributed by the piece
         */
        public Area getArea(MapShader shader);
    }
}