// Near Infinity - An Infinity Engine Browser and Editor // Copyright (C) 2001 - 2005 Jon Olav Hauglid // See LICENSE.txt for license information package org.infinity.resource.are.viewer; import java.util.EnumMap; import java.util.List; import org.infinity.gui.layeritem.AbstractLayerItem; import org.infinity.resource.are.AreResource; import org.infinity.resource.are.viewer.ViewerConstants.LayerType; import org.infinity.resource.wed.WedResource; /** * Manages all layer objects of a single ARE map. */ public final class LayerManager { // Defines order of drawing public static final LayerType[] LayerOrdered = { LayerType.ACTOR, LayerType.ENTRANCE, LayerType.AMBIENT, LayerType.PRO_TRAP, LayerType.ANIMATION, LayerType.SPAWN_POINT, LayerType.AUTOMAP, LayerType.CONTAINER, LayerType.DOOR, LayerType.REGION, LayerType.TRANSITION, LayerType.DOOR_POLY, LayerType.WALL_POLY }; private static final EnumMap<LayerType, String> LayerLabels = new EnumMap<LayerType, String>(LayerType.class); static { LayerLabels.put(LayerType.ACTOR, "Actors"); LayerLabels.put(LayerType.REGION, "Regions"); LayerLabels.put(LayerType.ENTRANCE, "Entrances"); LayerLabels.put(LayerType.CONTAINER, "Containers"); LayerLabels.put(LayerType.AMBIENT, "Ambient Sounds"); LayerLabels.put(LayerType.DOOR, "Doors"); LayerLabels.put(LayerType.ANIMATION, "Background Animations"); LayerLabels.put(LayerType.AUTOMAP, "Automap Notes"); LayerLabels.put(LayerType.SPAWN_POINT, "Spawn Points"); LayerLabels.put(LayerType.TRANSITION, "Map Transitions"); LayerLabels.put(LayerType.PRO_TRAP, "Projectile Traps"); LayerLabels.put(LayerType.DOOR_POLY, "Door Polygons"); LayerLabels.put(LayerType.WALL_POLY, "Wall Polygons"); } private final EnumMap<LayerType, BasicLayer<? extends LayerObject>> layers = new EnumMap<LayerType, BasicLayer<? extends LayerObject>>(LayerType.class); private final AreaViewer viewer; private AreResource are; private WedResource wed; private boolean scheduleEnabled, animForcedInterpolation; private int schedule; private Object animInterpolationType; private double animFrameRate; /** * Returns the number of supported layer types. */ public static int getLayerTypeCount() { return LayerType.values().length; } /** * Returns the layer type located at the specified index. */ public static LayerType getLayerType(int index) { if (index >= 0 && index < LayerType.values().length) { return LayerType.values()[index]; } else { return null; } } /** * Returns the index of the specified layer. */ public static int getLayerTypeIndex(LayerType layer) { LayerType[] lt = LayerType.values(); for (int i = 0; i < lt.length; i++) { if (lt[i] == layer) { return i; } } return -1; } /** * Returns the label associated with the specified layer. */ public static String getLayerTypeLabel(LayerType layer) { String s = LayerLabels.get(layer); if (s != null) { return s; } else { return ""; } } /** * Converts hours into scheduled time indices. * @param hour The hour to convert (0..23). * @return The schedule index. */ public static int toSchedule(int hour) { int schedule = hour - 1; while (schedule < 0) { schedule += 24; } schedule %= 24; return schedule; } /** * Converts scheduled time indices into hours. * @param schedule The schedule index to convert (0..23). * @return The hour. */ public static int toHour(int schedule) { int hour = schedule + 1; while (hour < 0) { hour += 24; } hour %= 24; return hour; } public LayerManager(AreResource are, WedResource wed, AreaViewer viewer) { this.viewer = viewer; init(are, wed, true); } /** * Returns the associated area viewer instance. */ public AreaViewer getViewer() { return viewer; } /** * Returns the currently used ARE resource structure. */ public AreResource getAreResource() { return are; } /** * Specify a new ARE resource structure. Automatically reloads related layer objects. * @param are The new ARE resource structure. */ public void setAreResource(AreResource are) { if (this.are != are) { init(are, wed, false); } } /** * Returns the currently used WED resource structure. */ public WedResource getWedResource() { return wed; } /** * Specify a new WED resource structure. Automatically reloads related layer objects. * @param wed The new WED resource structure. */ public void setWedResource(WedResource wed) { if (this.wed != wed) { init(are, wed, false); } } /** * Returns a formatted string that indicates how many objects of the specified layer type have been * loaded. * @param layer The layer to query. * @return A formatted string telling about number of layer objects. */ public String getLayerAvailability(LayerType layer) { if (layers.containsKey(layer)) { return layers.get(layer).getAvailability(); } return ""; } /** * Same as {@link #getLayerAvailability(LayerType)}, but also considers special states for layer * managers that supports it. * @param layer The layer to query. * @param type A layer-specific type (currently only LayerAmbient is supported). * @return A formatted string telling about number of layer objects. */ public String getLayerAvailability(LayerType layer, int type) { if (layer == LayerType.AMBIENT) { return getLayerAvailability(layer, type); } else { return getLayerAvailability(layer); } } /** * Returns whether time schedules for layer items will be considered in their visibility state. */ public boolean isScheduleEnabled() { return scheduleEnabled; } /** * Specify whether time schedules for layer items will be considered. */ public void setScheduleEnabled(boolean enable) { if (enable != scheduleEnabled) { scheduleEnabled = enable; for (int i = 0, ltCount = getLayerTypeCount(); i < ltCount; i++) { BasicLayer<?> bl = getLayer(getLayerType(i)); if (bl != null) { bl.setScheduleEnabled(scheduleEnabled); } } } } /** * Returns the currently defined schedule value (hour = schedule + 1). */ public int getSchedule() { return schedule; } /** * Specifiy the current schedule for layer items. * @param schedule The schedule value (0 = 00:30-01:29, ..., 23 = 23:30-00:29) */ public void setSchedule(int schedule) { while (schedule < 0) { schedule += 24; } schedule %= 24; if (schedule != this.schedule) { this.schedule = schedule; for (int i = 0, ltCount = getLayerTypeCount(); i < ltCount; i++) { BasicLayer<?> bl = getLayer(getLayerType(i)); if (bl != null) { bl.setSchedule(this.schedule); } } } } /** * Returns whether the specified layer object is active at the current scheduled time. * @param obj The layer object to query * @return {@code true} if the layer object is scheduled at the currently set hour or * schedule is disabled, {@code false} otherwise. */ public boolean isScheduled(LayerObject obj) { if (obj != null) { return !isScheduleEnabled() || (isScheduleEnabled() && obj.isScheduled(getSchedule())); } return false; } /** * Reloads all objects in every supported layer. */ public void reload() { init(are, wed, true); } /** * Reloads objects of the specified layer. * @param layer The layer to reload. * @return Number of layer objects found. */ public int reload(LayerType layer) { close(layer); return loadLayer(layer, true); } /** * Removes all layer objects from memory. */ public void close() { for (LayerType layer: LayerType.values()) { BasicLayer<?> bl = layers.get(layer); if (bl != null) { bl.close(); } } layers.clear(); are = null; wed = null; } /** * Removes the specified layer from memory. */ public void close(LayerType layer) { if (layer != null) { BasicLayer<?> bl = layers.get(layer); if (bl != null) { bl.close(); } } } /** * Returns a layer-specific manager. * @param layer * @return */ public BasicLayer<?> getLayer(LayerType layer) { if (layer != null) { return layers.get(layer); } else { return null; } } /** * Returns the number of objects in the specified layer. * @param layer The layer to check. * @return Number of objects in the specified layer. */ public int getLayerObjectCount(LayerType layer) { BasicLayer<?> bl = layers.get(layer); if (bl != null) { return bl.getLayerObjectCount(); } return 0; } /** * Returns a specific layer object. * @param layer The layer of the object. * @param index The index of the object. * @return The layer object if found, {@code null} otherwise. */ public LayerObject getLayerObject(LayerType layer, int index) { BasicLayer<?> bl = layers.get(layer); if (bl != null) { return bl.getLayerObject(index); } return null; } /** * Returns a list of objects associated with the specified layer. * @param layer The layer of the objects * @return A list of objects or {@code null} if not found. */ public List<? extends LayerObject> getLayerObjects(LayerType layer) { BasicLayer<? extends LayerObject> bl = layers.get(layer); if (bl != null) { return bl.getLayerObjects(); } return null; } /** * Returns the current state of the door. * @return Either {@code ViewerConstants.DOOR_OPEN} or {@code ViewerConstants.DOOR_CLOSED}. */ public int getDoorState() { return ((LayerDoor)getLayer(LayerType.DOOR)).getDoorState(); } /** * Sets the door state used by certain layers. Automatically updates the visibility state of * related layers. * @param state Either {@code ViewerConstants.DOOR_OPEN} or {@code ViewerConstants.DOOR_CLOSED}. */ public void setDoorState(int state) { ((LayerDoor)getLayer(LayerType.DOOR)).setDoorState(state); ((LayerDoorPoly)getLayer(LayerType.DOOR_POLY)).setDoorState(state); } /** * Returns whether to show iconic representations of background animations or the real thing. */ public boolean isRealAnimationEnabled() { LayerAnimation layer = (LayerAnimation)getLayer(ViewerConstants.LayerType.ANIMATION); if (layer != null) { return layer.isRealAnimationEnabled(); } return false; } /** * Specify whether to show iconic representations of background animations or the real thing. */ public void setRealAnimationEnabled(boolean enable) { LayerAnimation layer = (LayerAnimation)getLayer(ViewerConstants.LayerType.ANIMATION); if (layer != null) { layer.setRealAnimationEnabled(enable); } } /** * Returns whether real animations are enabled and animated. */ public boolean isRealAnimationPlaying() { LayerAnimation layer = (LayerAnimation)getLayer(ViewerConstants.LayerType.ANIMATION); if (layer != null) { return layer.isRealAnimationPlaying(); } return false; } /** * Specify whether to animate real background animations. Setting to {@code true} implicitly * enables real animations. */ public void setRealAnimationPlaying(boolean play) { LayerAnimation layer = (LayerAnimation)getLayer(ViewerConstants.LayerType.ANIMATION); if (layer != null) { layer.setRealAnimationPlaying(play); } } /** * Returns the currently active interpolation type for real animations. * @return Either one of ViewerConstants.TYPE_NEAREST_NEIGHBOR, ViewerConstants.TYPE_NEAREST_BILINEAR * or ViewerConstants.TYPE_BICUBIC. */ public Object getRealAnimationInterpolation() { return animInterpolationType; } /** * Sets the interpolation type for real animations * @param interpolationType Either one of ViewerConstants.TYPE_NEAREST_NEIGHBOR, * ViewerConstants.TYPE_NEAREST_BILINEAR or ViewerConstants.TYPE_BICUBIC. */ public void setRealAnimationInterpolation(Object interpolationType) { if (interpolationType == ViewerConstants.TYPE_NEAREST_NEIGHBOR || interpolationType == ViewerConstants.TYPE_BILINEAR || interpolationType == ViewerConstants.TYPE_BICUBIC) { animInterpolationType = interpolationType; LayerAnimation layer = (LayerAnimation)getLayer(LayerType.ANIMATION); if (layer != null) { layer.setRealAnimationInterpolation(animInterpolationType); } } } /** * Returns whether to force the specified interpolation type or use the best one available, depending * on the current zoom factor. */ public boolean isRealAnimationForcedInterpolation() { return animForcedInterpolation; } /** * Specify whether to force the specified interpolation type or use the best one available, depending * on the current zoom factor. */ public void setRealAnimationForcedInterpolation(boolean forced) { animForcedInterpolation = forced; LayerAnimation layer = (LayerAnimation)getLayer(LayerType.ANIMATION); if (layer != null) { layer.setRealAnimationForcedInterpolation(animForcedInterpolation); } } /** * Returns the frame rate used for playing back background animations. * @return Frame rate in frames/second. */ public double getRealAnimationFrameRate() { return animFrameRate; } /** * Specify a new frame rate for background animations. * @param frameRate Frame rate in frames/second. */ public void setRealAnimationFrameRate(double frameRate) { frameRate = Math.min(Math.max(frameRate, 1.0), 30.0); if (frameRate != this.animFrameRate) { animFrameRate = frameRate; LayerAnimation layer = (LayerAnimation)getLayer(LayerType.ANIMATION); if (layer != null) { layer.setRealAnimationFrameRate(animFrameRate); } } } /** * Returns whether the items of the specified layer are visible. * @param layer The layer to check for visibility. * @return {@code true} if one or more items of the specified layer are visible, {@code false} otherwise. */ public boolean isLayerVisible(LayerType layer) { if (layer != null) { BasicLayer<?> bl = layers.get(layer); if (bl != null) { return bl.isLayerVisible(); } } return false; } /** * Sets the items of the specified layer to the specified visibility state. * (Note: For items that support opened/closed state, only the current state will be considered.) * @param layer The layer of the items to change. * @param visible The visibility state to set. */ public void setLayerVisible(LayerType layer, boolean visible) { if (layer != null) { BasicLayer<?> bl = layers.get(layer); if (bl != null) { bl.setLayerVisible(visible); } } } /** * Attempts to find the LayerObject instance the specified item belongs to. * @param item The AbstractLayerItem object. * @return A LayerObject instance if a match has been found, {@code null} otherwise. */ public LayerObject getLayerObjectOf(AbstractLayerItem item) { if (item != null) { for (final LayerType type: LayerType.values()) { BasicLayer<?> bl = layers.get(type); if (bl != null) { LayerObject obj = bl.getLayerObjectOf(item); if (obj != null) { return obj; } } } } return null; } // Loads objects for each layer if the parent resource (are, wed) has changed. // If forceReload is true, layer objects are always reloaded. private void init(AreResource are, WedResource wed, boolean forced) { if (are != null && wed != null) { boolean areChanged = (are != this.are); boolean wedChanged = (wed != this.wed); // updating global structures if (this.are != are) { this.are = are; } if (this.wed != wed) { this.wed = wed; } for (final LayerType layer: LayerType.values()) { switch (layer) { case ACTOR: case REGION: case ENTRANCE: case CONTAINER: case AMBIENT: case DOOR: case ANIMATION: case AUTOMAP: case SPAWN_POINT: case TRANSITION: case PRO_TRAP: loadLayer(layer, forced || areChanged); break; case DOOR_POLY: case WALL_POLY: loadLayer(layer, forced || wedChanged); break; default: System.err.println(String.format("Unsupported layer type: %1$s", layer.toString())); } } } } // (Re-)loads the specified layer private int loadLayer(LayerType layer, boolean forced) { int retVal = 0; if (layer != null) { switch (layer) { case ACTOR: { if (layers.containsKey(layer)) { retVal = layers.get(layer).loadLayer(forced); } else { LayerActor obj = new LayerActor(are, getViewer()); layers.put(layer, obj); retVal = obj.getLayerObjectCount(); } break; } case REGION: { if (layers.containsKey(layer)) { retVal = layers.get(layer).loadLayer(forced); } else { LayerRegion obj = new LayerRegion(are, getViewer()); layers.put(layer, obj); retVal = obj.getLayerObjectCount(); } break; } case ENTRANCE: { if (layers.containsKey(layer)) { retVal = layers.get(layer).loadLayer(forced); } else { LayerEntrance obj = new LayerEntrance(are, getViewer()); layers.put(layer, obj); retVal = obj.getLayerObjectCount(); } break; } case CONTAINER: { if (layers.containsKey(layer)) { retVal = layers.get(layer).loadLayer(forced); } else { LayerContainer obj = new LayerContainer(are, getViewer()); layers.put(layer, obj); retVal = obj.getLayerObjectCount(); } break; } case AMBIENT: { if (layers.containsKey(layer)) { retVal = layers.get(layer).loadLayer(forced); } else { LayerAmbient obj = new LayerAmbient(are, getViewer()); layers.put(layer, obj); retVal = obj.getLayerObjectCount(); } break; } case DOOR: { if (layers.containsKey(layer)) { retVal = layers.get(layer).loadLayer(forced); } else { LayerDoor obj = new LayerDoor(are, getViewer()); layers.put(layer, obj); retVal = obj.getLayerObjectCount(); } break; } case ANIMATION: { if (layers.containsKey(layer)) { retVal = layers.get(layer).loadLayer(forced); } else { LayerAnimation obj = new LayerAnimation(are, getViewer()); layers.put(layer, obj); retVal = obj.getLayerObjectCount(); } break; } case AUTOMAP: { if (layers.containsKey(layer)) { retVal = layers.get(layer).loadLayer(forced); } else { LayerAutomap obj = new LayerAutomap(are, getViewer()); layers.put(layer, obj); retVal = obj.getLayerObjectCount(); } break; } case SPAWN_POINT: { if (layers.containsKey(layer)) { retVal = layers.get(layer).loadLayer(forced); } else { LayerSpawnPoint obj = new LayerSpawnPoint(are, getViewer()); layers.put(layer, obj); retVal = obj.getLayerObjectCount(); } break; } case TRANSITION: { if (layers.containsKey(layer)) { retVal = layers.get(layer).loadLayer(forced); } else { LayerTransition obj = new LayerTransition(are, getViewer()); layers.put(layer, obj); retVal = obj.getLayerObjectCount(); } break; } case PRO_TRAP: { if (layers.containsKey(layer)) { retVal = layers.get(layer).loadLayer(forced); } else { LayerProTrap obj = new LayerProTrap(are, getViewer()); layers.put(layer, obj); retVal = obj.getLayerObjectCount(); } break; } case DOOR_POLY: { if (layers.containsKey(layer)) { retVal = layers.get(layer).loadLayer(forced); } else { LayerDoorPoly obj = new LayerDoorPoly(wed, getViewer()); layers.put(layer, obj); retVal = obj.getLayerObjectCount(); } break; } case WALL_POLY: { if (layers.containsKey(layer)) { retVal = layers.get(layer).loadLayer(forced); } else { LayerWallPoly obj = new LayerWallPoly(wed, getViewer()); layers.put(layer, obj); retVal = obj.getLayerObjectCount(); } break; } default: System.err.println(String.format("Unsupported layer type: %1$s", layer.toString())); } } return retVal; } }