// 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.awt.Point; import java.awt.Polygon; import java.awt.Rectangle; import java.awt.event.ActionListener; import java.awt.event.MouseListener; import java.awt.event.MouseMotionListener; import java.util.EventListener; import java.util.List; import org.infinity.datatype.DecNumber; import org.infinity.datatype.Flag; import org.infinity.gui.layeritem.AbstractLayerItem; import org.infinity.gui.layeritem.LayerItemListener; import org.infinity.resource.AbstractStruct; import org.infinity.resource.StructEntry; import org.infinity.resource.Viewable; import org.infinity.resource.vertex.Vertex; /** * Common base class for specific layer types. */ public abstract class LayerObject { private final int resourceType; private final String category; private final Class<? extends AbstractStruct> classType; private final AbstractStruct parent; // base structure (e.g. AreResource or WedResource) private boolean visible; protected LayerObject(int resourceType, String category, Class<? extends AbstractStruct> classType, AbstractStruct parent) { this.resourceType = resourceType; this.category = (category != null && !category.isEmpty()) ? category : "Layer object"; this.classType = (classType != null) ? classType : AbstractStruct.class; this.parent = parent; visible = false; } /** * Closes and cleans up the current layer object. */ public void close() { AbstractLayerItem[] items = getLayerItems(); if (items != null) { for (int i = 0; i < items.length; i++) { // removing listeners from layer item EventListener[][] listeners = new EventListener[4][]; listeners[0] = items[i].getActionListeners(); listeners[1] = items[i].getLayerItemListeners(); listeners[2] = items[i].getMouseListeners(); listeners[3] = items[i].getMouseMotionListeners(); for (int j = 0; j < listeners.length; j++) { if (listeners[j] != null) { for (int k = 0; k < listeners[j].length; k++) { switch (j) { case 0: items[i].removeActionListener((ActionListener)listeners[j][k]); break; case 1: items[i].removeLayerItemListener((LayerItemListener)listeners[j][k]); break; case 2: items[i].removeMouseListener((MouseListener)listeners[j][k]); break; case 3: items[i].removeMouseMotionListener((MouseMotionListener)listeners[j][k]); break; } } } } // removing items from container if (items[i].getParent() != null) { items[i].getParent().remove(items[i]); } } } } /** * Returns the type of the parent resource (either RESOURCE_ARE or RESOURCE_WED). */ public int getResourceType() { return resourceType; } /** * Returns the category description of the layer object. */ public String getCategory() { return category; } /** * Returns the specific class type of the structure associated with the layer object * for identification purposes. */ public Class<? extends AbstractStruct> getClassType() { return classType; } /** * Returns the base structure the current structure belongs to. This can either be an * {@code AreResource} or a {@code WedResource} object. */ public AbstractStruct getParentStructure() { return parent; } /** * Returns whether the layer item is visible on the map. */ public boolean isVisible() { return visible; } /** * Controls the visibility state of the layer item on the map. */ public void setVisible(boolean state) { if (state != visible) { visible = state; AbstractLayerItem[] items = getLayerItems(); if (items != null) { for (int i = 0; i < items.length; i++) { items[i].setVisible(visible); } } } } /** * Returns the structure associated with the layer object. If the layer object consists of * multiple structures, then the first one available will be returned. * @return The structure associated with the layer object. */ public abstract Viewable getViewable(); /** * Returns all structures associated with the layer object. This method is useful for layer objects * consisting of multiple structures. * @return A list of structures associated with the layer object. */ public abstract Viewable[] getViewables(); /** * Returns the layer item associated with the layer object. If the layer object consists of * multiple layer items, then the first one available will be returned. * @return The layer item associated with the layer object. */ public abstract AbstractLayerItem getLayerItem(); /** * Returns the specified layer item. {@code type} is layer type specific, usually defined * as an identifier in {@code ViewerConstants}. * @param type A layer-specific type to identify the item to return. * @return The desired layer item, or {@code null} if not available. */ public abstract AbstractLayerItem getLayerItem(int type); /** * Returns all layer items associated with the layer object. This method is useful for layer objects * consisting of multiple layer items (e.g. door polygons or ambient sounds/sound ranges). * @return A list of layer items associated with the layer object. */ public abstract AbstractLayerItem[] getLayerItems(); /** * Reloads structure data and associated layer item(s). Note: {@link #update(Point, double)} has * to be called afterwards to account for canvas-specific settings. */ public abstract void reload(); /** * Updates the layer item positions. Takes zoom factor into account. * Note: Always call this method after loading/reloading structure data. */ public abstract void update(double zoomFactor); /** * Returns the original map position of the first available layer item (center or top-left, * depending on object type). Note: This is the location specified in the resource structure. * The resulting position on the canvas may be different. */ public abstract Point getMapLocation(); /** * Returns the original map positions of all available layer items (center or top-left, * depending on object type). Note: This is the location specified in the resource structure. * The resulting position on the canvas may be different. * @return */ public abstract Point[] getMapLocations(); /** * Returns whether the layer object is active at a specific scheduled time. * @param time The desired scheduled time index. * @return {@code true} if the animation is active at the specified scheduled time, * {@code false} otherwise. */ public boolean isScheduled(int schedule) { // Default implementation: always active return true; } /** * Loads vertices from the superStruct and stores them in an array of Point objects. * @param superStruct The super structure containing the Vertex entries. * @param index Index of the first vertex. * @param count Number of vertices to load. * @parem type The specific vertex type to look for. * @return Array of Point objects containing vertex data. */ protected Point[] loadVertices(AbstractStruct superStruct, int baseOfs, int index, int count, Class<? extends Vertex> type) { Point[] coords = null; if (superStruct != null && index >= 0 && count > 0 && type != null) { int idx = 0, cnt = 0; coords = new Point[count]; List<StructEntry> list = superStruct.getList(); for (int i = 0, size = list.size(); i < size; i++) { if (list.get(i).getOffset() >= baseOfs && type.isAssignableFrom(list.get(i).getClass())) { if (idx >= index) { Vertex vertex = (Vertex)list.get(i); coords[cnt] = new Point(((DecNumber)vertex.getAttribute(Vertex.VERTEX_X)).getValue(), ((DecNumber)vertex.getAttribute(Vertex.VERTEX_Y)).getValue()); cnt++; if (cnt >= count) { break; } } idx++; } } // filling up remaining coordinates with empty values (if any) for (int i = cnt; i < coords.length; i++) { coords[i] = new Point(); } } else { coords = new Point[0]; } return coords; } /** * Creates a polygon object out of the specified coordinates. Uses zoomFactor to scale the result. * @param coords Array of coordinates for the polygon. * @param zoomFactor Coordinates will be scaled by this value. * @return A {@code Polygon} object. */ protected Polygon createPolygon(Point[] coords, double zoomFactor) { Polygon poly = new Polygon(); if (coords != null) { for (int i = 0; i < coords.length; i++) { poly.addPoint((int)(coords[i].x*zoomFactor + (zoomFactor / 2.0)), (int)(coords[i].y*zoomFactor + (zoomFactor / 2.0))); } } return poly; } /** * Converts the polygon from a global coordinate system into a local coordinate system where the * top/left coordinate is located at [0, 0]. * @param poly The polygon to normalize (will be processed in place). * @return Bounding box of the polygon in global coordinates. */ protected Rectangle normalizePolygon(Polygon poly) { if (poly != null) { Rectangle r = poly.getBounds(); poly.translate(-r.x, -r.y); return r; } else { return new Rectangle(); } } /** * A helper method for easily finding out whether the object is active during the specified day time. * @param flags The appearance schedule. * @param dayTime The desired day time. */ protected boolean isActiveAt(Flag flags, int dayTime) { if (flags != null && flags.getSize() > 2) { if (dayTime == ViewerConstants.LIGHTING_NIGHT) { // Night: 21:30..06:29 return (flags.isFlagSet(0) || flags.isFlagSet(1) || flags.isFlagSet(2) || flags.isFlagSet(3) || flags.isFlagSet(4) || flags.isFlagSet(5) || flags.isFlagSet(21) || flags.isFlagSet(22) || flags.isFlagSet(23)); } else { // Day: 06:30..21:29 return (flags.isFlagSet(6) || flags.isFlagSet(7) || flags.isFlagSet(8) || flags.isFlagSet(9) || flags.isFlagSet(10) || flags.isFlagSet(11) || flags.isFlagSet(12) || flags.isFlagSet(13) || flags.isFlagSet(14) || flags.isFlagSet(15) || flags.isFlagSet(16) || flags.isFlagSet(17) || flags.isFlagSet(18) || flags.isFlagSet(19) || flags.isFlagSet(20)); } } else { return false; } } }