Package com.threerings.stage.util

Source Code of com.threerings.stage.util.PlacementConstraints

//
// $Id$
//
// Vilya library - tools for developing networked games
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
// http://code.google.com/p/vilya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// 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
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA

package com.threerings.stage.util;

import java.util.HashMap;
import java.util.List;

import java.awt.Rectangle;

import com.google.common.collect.Lists;
import com.google.common.collect.Maps;

import com.samskivert.util.ListUtil;

import com.threerings.util.DirectionCodes;
import com.threerings.util.DirectionUtil;
import com.threerings.util.MessageBundle;

import com.threerings.media.tile.ObjectTile;
import com.threerings.media.tile.ObjectTileSet;
import com.threerings.media.tile.TileManager;

import com.threerings.miso.data.ObjectInfo;

import com.threerings.stage.data.StageCodes;
import com.threerings.stage.data.StageMisoSceneModel;
import com.threerings.stage.data.StageScene;

import static com.threerings.stage.Log.log;

/**
* Maintains extra information on objects in a scene and checks proposed
* placement operations for constraint violations.  When the constraints
* object is in use, all placement operations (object additions and removals)
* must go through the constraints object so that the object's internal state
* remains consistent.
*/
public class PlacementConstraints
    implements DirectionCodes, StageCodes
{
    /**
     * Default constructor.
     */
    public PlacementConstraints (TileManager tilemgr, StageScene scene)
    {
        _tilemgr = tilemgr;
        _scene = scene;
        _mmodel = StageMisoSceneModel.getSceneModel(scene.getSceneModel());

        // add all the objects in the scene
        StageMisoSceneModel.ObjectVisitor visitor =
            new StageMisoSceneModel.ObjectVisitor() {
            public void visit (ObjectInfo info) {
                ObjectData data = createObjectData(info);
                if (data != null) {
                    // clone the map key, as the visit method reuses a
                    // single ObjectInfo instance for uninteresting objects
                    // in a section
                    _objectData.put(info.clone(), data);
                }
            }
        };
        _mmodel.visitObjects(visitor);
    }

    /**
     * Determines whether the constraints allow the specified object to be
     * added to the scene.
     *
     * @return <code>null</code> if the constraints allow the operation,
     * otherwise a translatable string explaining why the object can't be
     * added
     */
    public String allowAddObject (ObjectInfo info)
    {
        return allowModifyObjects(new ObjectInfo[] { info },
            new ObjectInfo[0]);
    }

    /**
     * Adds the specified object through the constraints.
     */
    public void addObject (ObjectInfo info)
    {
        ObjectData data = createObjectData(info);
        if (data != null) {
            _scene.addObject(info);
            _objectData.put(info, data);
        }
    }

    /**
     * Determines whether the constraints allow the specified object to be
     * removed from the scene.
     *
     * @return <code>null</code> if the constraints allow the operation,
     * otherwise a translatable string explaining why the object can't be
     * removed
     */
    public String allowRemoveObject (ObjectInfo info)
    {
        return allowModifyObjects(new ObjectInfo[0],
            new ObjectInfo[] { info });
    }

    /**
     * Removes the specified object through the constraints.
     */
    public void removeObject (ObjectInfo info)
    {
        _scene.removeObject(info);
        _objectData.remove(info);
    }

    /**
     * Determines whether the constraints allow the specified objects to be
     * added and removed simultaneously.
     *
     * @return <code>null</code> if the constraints allow the operation,
     * otherwise a translatable string explaining why the objects can't be
     * modified
     */
    public String allowModifyObjects (ObjectInfo[] added,
        ObjectInfo[] removed)
    {
        ObjectData[] addedData = new ObjectData[added.length];
        for (int ii = 0; ii < added.length; ii++) {
            addedData[ii] = createObjectData(added[ii]);
            if (addedData[ii] == null) {
                return INTERNAL_ERROR;
            }
        }

        ObjectData[] removedData = getObjectDataFromInfo(removed);
        if (removedData == null) {
            return INTERNAL_ERROR;
        }

        return allowModifyObjects(addedData, removedData);
    }

    /**
     * Returns an ObjectData array that corresponds to the supplied
     * ObjectInfo array.  Returns null on error.
     */
    protected ObjectData[] getObjectDataFromInfo (ObjectInfo[] info)
    {
        if (info == null) {
            return null;
        }
        ObjectData[] data = new ObjectData[info.length];
        for (int ii = 0; ii < info.length; ii++) {
            data[ii] = _objectData.get(info[ii]);
            if (data[ii] == null) {
                log.warning("Couldn't match object info up to data [info=" +
                        info[ii] + "].");
                return null;
            }
        }
        return data;
    }

    /**
     * Determines whether the constraints allow the specified objects to be
     * added and removed simultaneously.  Subclasses that wish to define
     * additional constraints should override this method.
     *
     * @return <code>null</code> if the constraints allow the operation,
     * otherwise a qualified translatable string explaining why the objects
     * can't be modified
     */
    protected String allowModifyObjects (ObjectData[] added,
        ObjectData[] removed)
    {
        DirectionType dirtype = new DirectionType();

        for (int ii = 0; ii < added.length; ii++) {
            if (added[ii].tile.hasConstraint(ObjectTileSet.ON_SURFACE) &&
                !isOnSurface(added[ii], added, removed)) {
                return MessageBundle.qualify(STAGE_MESSAGE_BUNDLE,
                    "m.not_on_surface");
            }

            if (getConstraintDirectionType(added[ii], ObjectTileSet.ON_WALL,
                dirtype) && !isOnWall(added[ii], added, removed, dirtype.dir, dirtype.type)) {
                return MessageBundle.qualify(STAGE_MESSAGE_BUNDLE,
                    "m.not_on_wall");
            }

            if (getConstraintDirectionType(added[ii], ObjectTileSet.ATTACH,
                    dirtype) && !isAttached(added[ii], added, removed,
                        dirtype.dir, dirtype.type)) {
                return MessageBundle.qualify(STAGE_MESSAGE_BUNDLE,
                    "m.not_attached");
            }

            int dir = getConstraintDirection(added[ii], ObjectTileSet.SPACE);
            if (dir != NONE && !hasSpace(added[ii], added, removed, dir)) {
                return MessageBundle.qualify(STAGE_MESSAGE_BUNDLE,
                    "m.no_space");
            }

            if (hasSpaceConstrainedAdjacent(added[ii], added, removed)) {
                return MessageBundle.qualify(STAGE_MESSAGE_BUNDLE,
                    "m.no_space_adj");
            }
        }

        for (ObjectData element : removed) {
            if (element.tile.hasConstraint(ObjectTileSet.SURFACE) &&
                hasOnSurface(element, added, removed)) {
                return MessageBundle.qualify(STAGE_MESSAGE_BUNDLE,
                    "m.has_on_surface");
            }

            int dir = getConstraintDirection(element, ObjectTileSet.WALL);
            if (dir != NONE) {
                if (hasOnWall(element, added, removed, dir)) {
                    return MessageBundle.qualify(STAGE_MESSAGE_BUNDLE,
                        "m.has_on_wall");

                } else if (hasAttached(element, added, removed, dir)) {
                    return MessageBundle.qualify(STAGE_MESSAGE_BUNDLE,
                        "m.has_attached");
                }
            }
        }

        return null;
    }

    /**
     * Determines whether the specified surface has anything on it that won't
     * be held up if the surface is removed.
     */
    protected boolean hasOnSurface (ObjectData data, ObjectData[] added,
        ObjectData[] removed)
    {
        List<ObjectData> objects = getObjectData(data.bounds, added, removed);
        for (int ii = 0, size = objects.size(); ii < size; ii++) {
            ObjectData odata = objects.get(ii);
            if (odata.tile.hasConstraint(ObjectTileSet.ON_SURFACE) &&
                !isOnSurface(odata, added, removed)) {
                return true;
            }
        }
        return false;
    }

    /**
     * Determines whether the specified wall has anything on it that won't be
     * held up if the wall is removed.
     */
    protected boolean hasOnWall (ObjectData data, ObjectData[] added,
        ObjectData[] removed, int dir)
    {
        DirectionType dirtype = new DirectionType();

        List<ObjectData> objects = getObjectData(data.bounds, added, removed);
        for (int ii = 0, size = objects.size(); ii < size; ii++) {
            ObjectData odata = objects.get(ii);
            if (getConstraintDirectionType(odata, ObjectTileSet.ON_WALL,
                dirtype) && !isAttached(odata, added, removed,
                    dirtype.dir, dirtype.type)) {
                return true;
            }
        }
        return false;
    }

    /**
     * Determines whether the specified wall has anything attached to it that
     * won't be held up if the wall is removed.
     */
    protected boolean hasAttached (ObjectData data, ObjectData[] added,
        ObjectData[] removed, int dir)
    {
        DirectionType dirtype = new DirectionType();

        List<ObjectData> objects = getObjectData(getAdjacentEdge(data.bounds,
            DirectionUtil.getOpposite(dir)), added, removed);
        for (int ii = 0, size = objects.size(); ii < size; ii++) {
            ObjectData odata = objects.get(ii);
            if (getConstraintDirectionType(odata, ObjectTileSet.ATTACH,
                    dirtype) && !isAttached(odata, added, removed,
                        dirtype.dir, dirtype.type)) {
                return true;
            }
        }
        return false;
    }

    /**
     * Verifies that the objects adjacent to the given object will still have
     * their space constraints met if the object is added.
     */
    protected boolean hasSpaceConstrainedAdjacent (ObjectData data,
        ObjectData[] added, ObjectData[] removed)
    {
        Rectangle r = data.bounds;
        // grow the ObjectData bounds 1 square in each direction
        _constrainRect.setBounds(r.x - 1, r.y - 1, r.width + 2, r.height + 2);

        List<ObjectData> objects = getObjectData(_constrainRect, added, removed);
        for (int ii = 0, size = objects.size(); ii < size; ii++) {
            ObjectData odata = objects.get(ii);
            int dir = getConstraintDirection(odata, ObjectTileSet.SPACE);
            if (dir != NONE && !hasSpace(odata, added, removed, dir)) {
                return true;
            }
        }
        return false;
    }

    /**
     * Determines whether the specified object has empty space in the specified
     * direction.
     */
    protected boolean hasSpace (ObjectData data, ObjectData[] added,
        ObjectData[] removed, int dir)
    {
        return getObjectData(getAdjacentEdge(data.bounds, dir), added,
            removed).size() == 0;
    }

    /**
     * Determines whether the specified object is on a surface.
     */
    protected boolean isOnSurface (ObjectData data, ObjectData[] added,
        ObjectData[] removed)
    {
        return isCovered(data.bounds, added, removed, ObjectTileSet.SURFACE,
            null);
    }

    /**
     * Determines whether the specified object is on a wall facing the
     * specified direction.
     */
    protected boolean isOnWall (ObjectData data, ObjectData[] added,
        ObjectData[] removed, int dir, int type)
    {
        return isCovered(data.bounds, added, removed,
            getDirectionalConstraint(ObjectTileSet.WALL, dir), (type == DirectionType.LOW) ?
                getDirectionalConstraint(ObjectTileSet.WALL, dir, DirectionType.LOW) : null);
    }

    /**
     * Determines whether the specified object is attached to another object in
     * the specified direction and at the specified height.
     */
    protected boolean isAttached (ObjectData data, ObjectData[] added,
        ObjectData[] removed, int dir, int type)
    {
        return isCovered(getAdjacentEdge(data.bounds, dir), added, removed,
            getDirectionalConstraint(ObjectTileSet.WALL, dir), (type == DirectionType.LOW) ?
            getDirectionalConstraint(ObjectTileSet.WALL, dir, DirectionType.LOW) : null);
    }

    /**
     * Given a rectangle, determines whether all of the tiles within
     * the rectangle intersect an object.  If the constraint parameter is
     * non-null, the intersected objects must have that constraint (or the
     * alternate constraint, if specified).
     */
    protected boolean isCovered (Rectangle rect, ObjectData[] added,
        ObjectData[] removed, String constraint, String altstraint)
    {
        List<ObjectData> objects = getObjectData(rect, added, removed);
        for (int y = rect.y, ymax = rect.y + rect.height; y < ymax; y++) {
            for (int x = rect.x, xmax = rect.x + rect.width; x < xmax; x++) {
                boolean covered = false;
                for (int ii = 0, size = objects.size(); ii < size; ii++) {
                    ObjectData data = objects.get(ii);
                    if (data.bounds.contains(x, y) && (constraint == null ||
                            data.tile.hasConstraint(constraint) ||
                            (altstraint != null &&
                                data.tile.hasConstraint(altstraint)))) {
                        covered = true;
                        break;
                    }
                }
                if (!covered) {
                    return false;
                }
            }
        }
        return true;
    }

    /**
     * Creates and returns a rectangle that covers the specified rectangle's
     * adjacent edge (the squares one tile beyond the bounds) in the specified
     * direction.
     */
    protected Rectangle getAdjacentEdge (Rectangle rect, int dir)
    {
        switch (dir) {
            case NORTH:
                return new Rectangle(rect.x - 1, rect.y, 1, rect.height);

            case EAST:
                return new Rectangle(rect.x, rect.y - 1, rect.width, 1);

            case SOUTH:
                return new Rectangle(rect.x + rect.width, rect.y, 1,
                    rect.height);

            case WEST:
                return new Rectangle(rect.x, rect.y + rect.height,
                    rect.width, 1);

            default:
                return null;
        }
    }

    /**
     * Returns the direction in which the specified object is constrained by
     * appending "[NESW]" to the given constraint prefix.  Returns
     * <code>NONE</code> if there is no such directional constraint.
     */
    protected int getConstraintDirection (ObjectData data, String prefix)
    {
        DirectionType dirtype = new DirectionType();
        return getConstraintDirectionType(data, prefix, dirtype) ?
            dirtype.dir : NONE;
    }

    /**
     * Populates the supplied {@link ObjectData} object with the direction and height of the
     * constraint identified by the given prefix.
     *
     * @return true if the object was successfully populated, false if there is
     * no such constraint
     */
    protected boolean getConstraintDirectionType (ObjectData data,
        String prefix, DirectionType dirtype)
    {
        String[] constraints = data.tile.getConstraints();
        if (constraints == null) {
            return false;
        }

        for (String constraint : constraints) {
            if (constraint.startsWith(prefix)) {
                int fromidx = prefix.length(),
                    toidx = constraint.indexOf('_', fromidx);
                dirtype.dir = DirectionUtil.fromShortString(toidx == -1 ?
                    constraint.substring(fromidx) :
                    constraint.substring(fromidx, toidx));
                dirtype.type = getConstraintWallType(constraint);
                return true;
            }
        }
        return false;
    }

    protected int getConstraintWallType (String constraint)
    {
        return constraint.endsWith(ObjectTileSet.LOW) ? DirectionType.LOW : DirectionType.NORM;
    }

    /**
     * Given a constraint prefix and a direction, returns the directional
     * constraint.
     */
    protected String getDirectionalConstraint (String prefix, int dir)
    {
        return getDirectionalConstraint(prefix, dir, DirectionType.NORM);
    }

    /**
     * Given a constraint prefix, direction, and height, returns the
     * directional constraint.
     */
    protected String getDirectionalConstraint (String prefix, int dir, int type)
    {
        return prefix + DirectionUtil.toShortString(dir) +
            (type == DirectionType.LOW ? ObjectTileSet.LOW : "");
    }

    /**
     * Finds all objects whose bounds intersect the given rectangle and
     * returns a list containing their {@link ObjectData} elements.
     *
     * @param added an array of objects to add to the search
     * @param removed an array of objects to exclude from the search
     */
    protected List<ObjectData> getObjectData (Rectangle rect, ObjectData[] added,
        ObjectData[] removed)
    {
        List<ObjectData> list = Lists.newArrayList();

        for (ObjectData data : _objectData.values()) {
            if (rect.intersects(data.bounds) && !ListUtil.contains(removed,
                    data)) {
                list.add(data);
            }
        }

        for (ObjectData element : added) {
            if (rect.intersects(element.bounds)) {
                list.add(element);
            }
        }

        return list;
    }

    /**
     * Using the tile manager, computes and returns the specified object's
     * data.
     */
    protected ObjectData createObjectData (ObjectInfo info)
    {
        try {
            ObjectTile tile = (ObjectTile)_tilemgr.getTile(info.tileId);
            Rectangle bounds = new Rectangle(info.x, info.y, tile.getBaseWidth(),
                tile.getBaseHeight());
            bounds.translate(1 - bounds.width, 1 - bounds.height);
            return new ObjectData(bounds, tile);

        } catch (Exception e) {
            log.warning("Error retrieving tile for object [info=" + info + "].", e);
            return null;
        }
    }

    /** Contains information about an object used in checking constraints. */
    protected class ObjectData
    {
        public Rectangle bounds;
        public ObjectTile tile;

        public ObjectData (Rectangle bounds, ObjectTile tile)
        {
            this.bounds = bounds;
            this.tile = tile;
        }
    }

    /** Contains the direction and height of a constraint. */
    protected static class DirectionType
    {
        public int dir;
        public int type;

        public static int NORM = 0;
        public static int LOW = 1;
    }

    /** The tile manager to use for object dimensions and constraints. */
    protected TileManager _tilemgr;

    /** The scene being checked for constraints. */
    protected StageScene _scene;

    /** The Miso scene model. */
    protected StageMisoSceneModel _mmodel;

    /** For all objects in the scene, maps {@link ObjectInfo}s to {@link ObjectData}s. */
    protected HashMap<ObjectInfo, ObjectData> _objectData = Maps.newHashMap();

    /** One rectangle we'll re-use for all constraints ops. */
    protected static final Rectangle _constrainRect = new Rectangle();
}
TOP

Related Classes of com.threerings.stage.util.PlacementConstraints

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.