package jadex.application.space.envsupport.observer.graphics; import jadex.application.space.envsupport.math.IVector2; import jadex.application.space.envsupport.math.Vector2Double; import jadex.application.space.envsupport.math.Vector2Int; import jadex.application.space.envsupport.observer.graphics.layer.Layer; import jadex.application.space.envsupport.observer.perspective.IPerspective; import jadex.commons.service.library.ILibraryService; import java.awt.Canvas; import java.awt.Color; import java.awt.EventQueue; import java.awt.Rectangle; import java.awt.event.MouseEvent; import java.awt.event.MouseWheelEvent; import java.awt.event.MouseWheelListener; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Set; import java.util.SortedSet; import java.util.TreeSet; import javax.swing.event.MouseInputAdapter; public abstract class AbstractViewport implements IViewport { /** Axis inversion flag */ protected IVector2 inversionFlag_; /** Canvas for graphical output. */ protected Canvas canvas_; /** Library service for loading resources. */ protected ILibraryService libService_; /** The background color. */ protected Color bgColor_; /** Virtual Viewport position. */ protected IVector2 position_; /** Pixel-corrected viewport position. */ protected IVector2 pixPosition_; /** Object shift x-coordinate. */ protected float objShiftX_; /** Object shift y-coordinate. */ protected float objShiftY_; /** Flag aspect ratio preservation. */ protected boolean preserveAR_; /** Size of the viewport without padding. */ protected Vector2Double size_; /** Maximum displayable area */ protected Vector2Double areaSize_; /** Real size of the viewport including padding. */ protected Vector2Double paddedSize_; /** Known drawable Objects. */ protected Set drawObjects_; /** Registered object layers. */ protected SortedSet objectLayers_; /** List of objects that should be drawn. */ protected List objectList_; /** Layers applied before drawable rendering */ protected Layer[] preLayers_; /** Layers applied after drawable rendering */ protected Layer[] postLayers_; /** IPropertyObject holding properties for layers. */ protected IPerspective perspective; /** Flag to indicate that rendering is in progress. */ protected volatile boolean rendering; /** The listeners of the viewport. */ private Set listeners_; /** The zoom limit */ private double zoomLimit_; public AbstractViewport(IPerspective perspective) { rendering = false; this.perspective = perspective; bgColor_ = Color.BLACK; inversionFlag_ = new Vector2Int(0); position_ = Vector2Double.ZERO.copy(); preserveAR_ = true; size_ = new Vector2Double(1.0); areaSize_ = new Vector2Double(1.0); paddedSize_ = new Vector2Double(1.0); drawObjects_ = Collections.synchronizedSet(new HashSet()); objectLayers_ = Collections.synchronizedSortedSet(new TreeSet()); objectList_ = Collections.synchronizedList(new ArrayList()); preLayers_ = new Layer[0]; postLayers_ = new Layer[0]; listeners_ = Collections.synchronizedSet(new HashSet()); zoomLimit_ = 20.0; } /** * Sets the background color. * @param bgColor the background color */ public void setBackground(Color bgColor) { bgColor_ = bgColor; } /** * Sets the current objects to draw. * * @param objectList objects that should be drawn */ public void setObjectList(List objectList) { synchronized(objectList_) { objectList_.clear(); objectList_.addAll(objectList); } } /** * Returns the canvas that is used for displaying the objects. */ public Canvas getCanvas() { return canvas_; } /** * Sets the pre-layers for the viewport. * * @param layers the pre-layers */ public void setPreLayers(Layer[] layers) { synchronized(preLayers_) { if(layers != null) { preLayers_ = layers; } else { preLayers_ = new Layer[0]; } } } /** * Sets the post-layers for the viewport. * * @param layers the post-layers */ public void setPostLayers(Layer[] layers) { synchronized(postLayers_) { if(layers != null) { postLayers_ = layers; } else { postLayers_ = new Layer[0]; } } } /** * Gets the size of the display area. * * @return size of the display area, may be padded to preserve aspect * ratio */ public IVector2 getSize() { return size_; } /** * Sets the size of the display area. * * @param size size of the display area, may be padded to preserve aspect * ratio */ public void setSize(IVector2 size) { Canvas canvas = canvas_; if (canvas == null) return; if (areaSize_.copy().divide(size).getMean().getAsDouble() > zoomLimit_) return; size_ = new Vector2Double(size); double width = 1.0; double height = 1.0; if(preserveAR_) { double sizeAR = size.getXAsDouble() / size.getYAsDouble(); double windowAR = (double)canvas.getWidth() / (double)canvas.getHeight(); if(sizeAR > windowAR) { width = size.getXAsDouble(); //height = size.getXAsDouble() / windowAR; double pixX = width / canvas.getWidth(); height = canvas.getHeight() * pixX; } else { //width = size.getYAsDouble() * windowAR; height = size.getYAsDouble(); double pixY = height / canvas.getHeight(); width = canvas.getWidth() * pixY; } } else { width = size.getXAsDouble(); height = size.getYAsDouble(); } paddedSize_ = new Vector2Double(width, height); } /** * Gets the maximum displayable size. * * @return maximum area size. */ public IVector2 getAreaSize() { return areaSize_; } /** * Sets the maximum displayable size. * * @param areaSize maximum area size. */ public void setAreaSize(final IVector2 areaSize) { EventQueue.invokeLater(new Runnable() { public void run() { areaSize_ = new Vector2Double(areaSize); setSize(areaSize.copy()); if (preserveAR_) { setPosition(paddedSize_.copy().subtract(areaSize_).multiply(0.5).negate()); } } }); } /** * Returns the padded size * @return padded size */ public IVector2 getPaddedSize() { return paddedSize_; } /** * Returns the clipping box. * @return clipping box */ public Rectangle getClippingBox() { Rectangle box = new Rectangle(); IVector2 pixSize = getPixelSize(); IVector2 boxStart = pixPosition_.copy().divide(pixSize).negate(); box.x = (int)Math.round(boxStart.getXAsDouble()); box.y = (int)Math.round(boxStart.getYAsDouble()); IVector2 boxSize = areaSize_.copy().divide(pixSize); box.width = (int) Math.ceil(boxSize.getXAsDouble()); box.height = (int) Math.ceil(boxSize.getYAsDouble()); return box; } /** * Returns the size of a pixel. * @retun size of a pixel */ public IVector2 getPixelSize() { Canvas canvas = canvas_; if (canvas == null) return Vector2Double.ZERO; return paddedSize_.copy().divide(new Vector2Double(canvas.getWidth(), canvas.getHeight())); } /** * Returns the size of the canvas as a vector. * @return size of the canvas in pixel */ public IVector2 getCanvasSize() { return new Vector2Double(canvas_.getWidth(), canvas_.getHeight()); } /** * Refreshes the size of the canvas. */ public void refreshCanvasSize() { } /** * Gets the position of the viewport. */ public IVector2 getPosition() { return position_.copy(); } /** * Sets the position of the viewport. */ public void setPosition(IVector2 pos) { position_ = pos; IVector2 pixSize = getPixelSize(); pixPosition_ = position_.copy().divide(pixSize); pixPosition_ = (new Vector2Double(new Vector2Int(pixPosition_))).multiply(pixSize); } public void setPreserveAspectRation(boolean preserveAR) { preserveAR_ = preserveAR; setSize(size_); } /** * Returns true if the x-axis is inverted (right-left instead of * left-right). * * @return true, if the x-axis is inverted */ public boolean getInvertX() { return inversionFlag_.getXAsInteger() > 0; } /** * Returns true if the y-axis is inverted (top-down instead of bottom-up). * * @return true, if the y-axis is inverted */ public boolean getInvertY() { return inversionFlag_.getYAsInteger() > 0; } /** * If set to true, inverts the x-axis (right-left instead of left-right). * * @param b if true, inverts the x-axis */ public void setInvertX(boolean b) { if(b) { inversionFlag_ = new Vector2Int(1, inversionFlag_.getYAsInteger()); } else { inversionFlag_ = new Vector2Int(0, inversionFlag_.getYAsInteger()); } } /** * If set to true, inverts the y-axis (top-down instead of bottom-up). * * @param b if true, inverts the y-axis */ public void setInvertY(boolean b) { if(b) { inversionFlag_ = new Vector2Int(inversionFlag_.getXAsInteger(), 1); } else { inversionFlag_ = new Vector2Int(inversionFlag_.getXAsInteger(), 0); } } /** * Gets the shift of all objects. */ public IVector2 getObjectShift() { return new Vector2Double(objShiftX_, objShiftY_); } /** * Sets the shift of all objects. */ public void setObjectShift(IVector2 objectShift) { objShiftX_ = objectShift.getXAsFloat(); objShiftY_ = objectShift.getYAsFloat(); } /** * Sets the maximum zoom. * @param zoomlimit the zoom limit */ public void setZoomLimit(double zoomlimit) { zoomLimit_ = zoomlimit; } /** * Checks if this IViewport is showing on screen. * * @return true if the IViewport is showing, false otherwise */ public boolean isShowing() { return canvas_.isShowing(); } /** * Adds a IViewportListener * * @param listener new listener */ public void addViewportListener(IViewportListener listener) { listeners_.add(listener); } /** * Removes a IViewportListener * * @param listener the listener */ public void removeViewportListener(IViewportListener listener) { listeners_.remove(listener); } /** * Get the perspective. * @return The perspective. */ public IPerspective getPerspective() { return perspective; } /** * Fires a left mouse click event * * @param position the clicked position */ private void fireLeftMouseClickEvent(IVector2 position) { synchronized(listeners_) { for(Iterator it = listeners_.iterator(); it.hasNext();) { IViewportListener listener = (IViewportListener)it.next(); listener.leftClicked(position.copy()); } } } /** * Converts pixel coordinates into world coordinates * * @param pixelX pixel x-coordinate * @param pixelY pixel y-coordinate * @return world coordinates */ public IVector2 getWorldCoordinates(int pixelX, int pixelY) { if(getInvertX()) { pixelX = canvas_.getWidth() - pixelX; } if(getInvertY()) { pixelY = canvas_.getHeight() - pixelY; } double xFac = (paddedSize_.getXAsDouble()) / canvas_.getWidth(); double yFac = (paddedSize_.getYAsDouble()) / canvas_.getHeight(); IVector2 position = new Vector2Double((xFac * pixelX) + position_.getXAsDouble(), (yFac * (canvas_.getHeight() - pixelY)) + position_.getYAsDouble()); return position; } protected class MouseController extends MouseInputAdapter implements MouseWheelListener { private IVector2 lastDragPos; public MouseController() { lastDragPos = null; } public void mousePressed(MouseEvent e) { if(e.getButton() == MouseEvent.BUTTON1) { IVector2 position = getWorldCoordinates(e.getX(), e.getY()); fireLeftMouseClickEvent(position); } else if(e.getButton() == MouseEvent.BUTTON3) { if (e.getClickCount() == 1) { lastDragPos = (new Vector2Double(e.getX(), e.getY())).multiply(getPixelSize()); } else { setAreaSize(areaSize_); } } } public void mouseWheelMoved(MouseWheelEvent e) { IVector2 oldMousePos = getWorldCoordinates(e.getX(), e.getY()); IVector2 zoomShift = size_.copy().multiply(0.1 * -e.getWheelRotation()); IVector2 size = size_.copy().subtract(zoomShift); setSize(size); IVector2 newMousePos = getWorldCoordinates(e.getX(), e.getY()); IVector2 pos = getPosition().copy().subtract(newMousePos.subtract(oldMousePos)); setPosition(pos); } public void mouseDragged(MouseEvent e) { if (lastDragPos != null) { IVector2 position = (new Vector2Double(e.getX(), e.getY())).multiply(getPixelSize()); IVector2 diff = position.copy().subtract(lastDragPos); if (getInvertX()) diff.negateX(); if (!getInvertY()) diff.negateY(); lastDragPos = position; setPosition(getPosition().copy().subtract(diff)); } } public void mouseReleased(MouseEvent e) { if (e.getButton() == MouseEvent.BUTTON3) lastDragPos = null; } } }