Package dwlab.shapes.maps

Source Code of dwlab.shapes.maps.SpriteMap

/* Digital Wizard's Lab - game development framework
* Copyright (C) 2013, Matt Merkulov

* All rights reserved. Use of this code is allowed under the
* Artistic License 2.0 terms, as specified in the license.txt
* file distributed with this code, or available from
* http://www.opensource.org/licenses/artistic-license-2.0.php */

package dwlab.shapes.maps;

import dwlab.base.service.Service;
import dwlab.base.service.Service.Margins;
import dwlab.base.XMLObject;
import static dwlab.platform.Functions.*;
import dwlab.shapes.Shape;
import dwlab.shapes.sprites.Sprite;
import dwlab.visualizers.Color;
import dwlab.visualizers.Visualizer;
import java.util.*;

/**
* Sprite map is a structure which can contain sprites, draw and perform collision checks between them and other shapes.
* Operations like drawing and checking collision between large groups of sprites will be faster with use of collision maps.
*/
public class SpriteMap extends Map {
  public HashSet<Sprite> sprites = new HashSet<Sprite>();
  public Sprite lists[][][];
  public int listSize[][];

  /**
   * Width of sprite map cell in units.
   * @see #setCellSize
   */
  public double cellWidth = 1d;

  /**
   * Height of sprite map cell in units.
   * @see #setCellSize
   */
  public double cellHeight = 1d;

  /**
   * Margins for drawing sprite map in units.
   * When drawing sprite map, margins define the size of rectangular frame around camera's rectangle in which objects will be also drawn.
   * Will be handy if you draw sprite map with objects with XScale / YScale parameters greater than 1.0.
   */
  public double leftMargin, rightMargin, topMargin, bottomMargin;

  /**
   * Flag which defines will be the sprite map sorted by sprite Y coordinates.
   * False by default.
   */
  public boolean sorted = false;

  /**
   * Flag which defines will be all objects recognized as pivots.
   * False by default.
   */
  public boolean pivotMode = false;

  public int initialArraysSize = 8;


  @Override
  public int wrapX( int value ) {
    return value & xMask;
  }


  @Override
  public int wrapY( int value ) {
    return value & yMask;
  }


  @Override
  public String getClassTitle() {
    return "Sprite map";
  }

 
  @Override
  public SpriteMap toSpriteMap() {
    return this;
  }


  /**
   * Creates collision map.
   * You should specify cell quantities and size.
   *
   * @see #createForShape
   */
  public SpriteMap( int xQuantity, int yQuantity, double cellWidth, double cellHeight, boolean sorted, boolean pivotMode ) {
    this.setResolution( xQuantity, yQuantity );
    this.cellWidth = cellWidth;
    this.cellHeight = cellHeight;
    this.sorted = sorted;
    this.pivotMode = pivotMode;
  }

  /**
   * Creates collision map using existing shape.
   * Collision map with size not less than shape size will be created. You can specify cell size either.
   * Use this function ob layer bounds or layer tilemap which are covers all level to maximize performance.
   */
  public SpriteMap( Shape shape, double cellSize, boolean sorted, boolean pivotMode ) {
    this.setResolution( Service.toPowerOf2( (int) Math.ceil( shape.getWidth() / cellSize ) ), Service.toPowerOf2( (int) Math.ceil( shape.getHeight() / cellSize ) ) );
    this.cellWidth = cellSize;
    this.cellHeight = cellSize;
    this.sorted = sorted;
    this.pivotMode = pivotMode;
  }
 
  public SpriteMap( Shape shape, double cellSize ) {
    this.setResolution( Service.toPowerOf2( (int) Math.ceil( shape.getWidth() / cellSize ) ), Service.toPowerOf2( (int) Math.ceil( shape.getHeight() / cellSize ) ) );
    this.cellWidth = cellSize;
    this.cellHeight = cellSize;
  }
 
  public SpriteMap() {
  }

  // ==================== Parameters ====================

  @Override
  public final void setResolution( int newXQuantity, int newYQuantity ) {
    super.setResolution( newXQuantity, newYQuantity );

    if( debug ) if( ! masked ) error( "Map resoluton must be power of 2" );

    lists = new Sprite[ newYQuantity ][][];
    listSize = new int[ newYQuantity ][];
    for( int yy = 0; yy < newYQuantity; yy++ ) {
      lists[ yy ] = new Sprite[ newXQuantity ][];
      listSize[ yy ] = new int[ newXQuantity ];
      for( int xx = 0; xx < newXQuantity; xx++ ) lists[ yy ][ xx ] = new Sprite[ initialArraysSize ];
    }
  }


  /**
   * Sets cell size of sprite map.
   */
  public void setCellSize( double newCellWidth, double newCellHeight ) {
    cellWidth = newCellWidth;
    cellHeight = newCellHeight;
  }


  /**
   * Sets all margins to single value.
   */
  public void setBorder( double border ) {
    setMargins( border, border, border, border );
  }


  /**
   * Sets margins of the map.
   */
  public void setMargins( double newLeftMargin, double newTopMargin, double newRightMargin, double newBottomMargin ) {
    leftMargin = newLeftMargin;
    topMargin = newTopMargin;
    rightMargin = newRightMargin;
    bottomMargin = newBottomMargin;
  }

  // ==================== Drawing =================== 

  /**
   * Draws all objects of sprite map which are in cells under camera's rectangle plus margins.
   */
  @Override
  public void draw( Color drawingColor ) {
    drawUsingVisualizer( null, drawingColor );
  }


  Margins serviceMargins = new Margins();
 
  @Override
  public void drawUsingVisualizer( Visualizer vis, Color drawingColor ) {
    if( visible ) {
      Sprite.checkNum++;
     
      Service.getEscribedRectangle( leftMargin, topMargin, rightMargin, bottomMargin, serviceMargins );

      int mapX1 = Service.floor( serviceMargins.min.x / cellWidth );
      int mapY1 = Service.floor( serviceMargins.min.y / cellHeight );
      int mapX2 = Service.floor( serviceMargins.max.x / cellWidth );
      int mapY2 = Service.floor( serviceMargins.max.y / cellHeight );

      int xN[] = new int[ mapX2 - mapX1 + 1 ];

      for( int yy = mapY1; yy <= mapY2; yy++ ) {
        int maskedY = yy & yMask;
        if( sorted ) {
          double maxY = ( yy + 1 ) * cellHeight;
          while( true ) {
            double minY = 0;
            int storedX = 0;
            Sprite storedSprite = null;
            for( int xx = 0; xx <= mapX2 - mapX1; xx++ ) {
              int maskedX = xx & yMask;
              int n = xN[ xx ];
              if( n >= listSize[ maskedY ][ maskedX ] ) continue;
              Sprite sprite = lists[ maskedY ][ maskedX ][ n ];
              double y0 = sprite.getY();
              if( y0 >= maxY ) continue;
              if( storedSprite == null || y0 < minY ) {
                minY = y0;
                storedX = xx;
                storedSprite = sprite;
              }
            }
            if( storedSprite == null ) break;

            if( storedSprite.checkValue != Sprite.checkNum ) {
              if( vis != null ) {
                vis.drawUsingSprite( storedSprite, storedSprite, drawingColor );
              } else {
                storedSprite.draw( drawingColor );
              }
              storedSprite.checkValue = Sprite.checkNum;
            }

            xN[ storedX ] += 1;
          }
        } else {
          for( int xx = mapX1; xx <= mapX2; xx++ ) {
            int maskedX = xx & xMask;
            Sprite array[] = lists[ maskedY ][ maskedX ];
            for( int n = 0; n < listSize[ maskedY ][ maskedX ]; n++ ) {
              Sprite sprite = array[ n ];
              if( sprite.checkValue != Sprite.checkNum ) {
                if( vis != null ) {
                  sprite.drawUsingVisualizer( vis, drawingColor );
                } else {
                  sprite.draw( drawingColor );
                }
                sprite.checkValue = Sprite.checkNum;
              }
            }
          }
        }
      }
    }
  }

  // ==================== Insert / remove objects ====================

  /**
   * Inserts a sprite into sprite map
   * When PivotMode is set to True, insertion will be faster.
   *
   * @see #removeSprite
   */
  public void insertSprite( Sprite sprite, boolean changeSpriteMapField, boolean modifySet ) {
    if( modifySet ) sprites.add( sprite );
    if( pivotMode ) {
      insertSprite( sprite, ( (int) Service.round( sprite.getX() / cellWidth ) ) & xMask, ( (int) Service.round( sprite.getY() / cellHeight ) ) & yMask );
    } else {
      int mapX1 = Service.floor( ( sprite.getX() - 0.5d * sprite.getWidth() ) / cellWidth );
      int mapY1 = Service.floor( ( sprite.getY() - 0.5d * sprite.getHeight() ) / cellHeight );
      int mapX2 = Service.floor( ( sprite.getX() + 0.5d * sprite.getWidth() - inaccuracy ) / cellWidth );
      int mapY2 = Service.floor( ( sprite.getY() + 0.5d * sprite.getHeight() - inaccuracy ) / cellHeight );

      for( int yy = mapY1; yy <= mapY2; yy++ ) {
        for( int xx = mapX1; xx <= mapX2; xx++ ) {
          insertSprite( sprite, xx & xMask, yy & yMask );
        }
      }
    }
    if( changeSpriteMapField ) sprite.spriteMap = this;
  }

  public void insertSprite( Sprite sprite ) {
    insertSprite( sprite, true, true );
  }


  public void insertSprite( Sprite sprite, int mapX, int mapY ) {
    Sprite array[] = lists[ mapY ][ mapX ];
    int size = listSize[ mapY ][ mapX ];
    if( array.length == size ) {
      array = Arrays.copyOf( array, size * 2 );
      lists[ mapY ][ mapX ] = array;
    }

    array[ size ] = sprite;
    if( sorted ) {
      for( int n=0; n <= size; n++ ) {
        if( sprite.getY() < array[ n ].getY() ) {
          for( int m=size - 1; m <= n; m += -1 ) array[ m + 1 ] = array[ m ];
          array[ n ] = sprite;
          break;
        }
      }
    } else {
      array[ size ] = sprite;
    }

    listSize[ mapY ][ mapX ] += 1;
  }



  /**
   * Removes sprite from sprite map.
   * When PivotMode is set to True, removal will be faster.
   *
   * @see #insertSprite
   */
  public void removeSprite( Sprite sprite, boolean changeSpriteMapField, boolean modifySet ) {
    if( modifySet ) sprites.remove( sprite );
    if( pivotMode ) {
      removeSprite( sprite, ( (int) Service.round( sprite.getX() / cellWidth ) ) & xMask, ( (int) Service.round( sprite.getY() / cellHeight ) ) & yMask );
    } else {
      int mapX1 = Service.floor( ( sprite.getX() - 0.5d * sprite.getWidth() ) / cellWidth );
      int mapY1 = Service.floor( ( sprite.getY() - 0.5d * sprite.getHeight() ) / cellHeight );
      int mapX2 = Service.floor( ( sprite.getX() + 0.5d * sprite.getWidth() - inaccuracy ) / cellWidth );
      int mapY2 = Service.floor( ( sprite.getY() + 0.5d * sprite.getHeight() - inaccuracy ) / cellHeight );

      for( int yy = mapY1; yy <= mapY2; yy++ ) {
        for( int xx = mapX1; xx <= mapX2; xx++ ) {
          removeSprite( sprite, xx & xMask, yy & yMask );
        }
      }
    }
    if( changeSpriteMapField ) sprite.spriteMap = null;
  }
 
  public void removeSprite( Sprite sprite ) {
    removeSprite( sprite, true, true );
  }



  public void removeSprite( Sprite sprite, int mapX, int mapY ) {
    Sprite array[] = lists[ mapY ][ mapX ];
    int size = listSize[ mapY ][ mapX ];
    for( int n = 0; n <= size; n++ ) {
      if( array[ n ] == sprite ) {
        if( sorted ) {
          for( int m=n + 1; m <= size; m++ ) array[ m - 1 ] = array[ m ];
        } else {
          array[ n ] = array[ size - 1 ];
        }
        listSize[ mapY ][ mapX ] -= 1;
        return;
      }
    }
  }


  /**
   * Clears sprite map.
   */
  public void clear() {
    sprites.clear();
    for( int yy = 0; yy <= yQuantity; yy++ ) {
      for( int xx = 0; xx <= xQuantity; xx++ ) {
        listSize[ yy ][ xx ] = 0;
      }
    }
  }

  // ==================== Shape management ====================

  @Override
  public Shape load() {
    SpriteMap newSpriteMap = loadShape().toSpriteMap();
    for( Sprite childSprite: sprites ) {
      newSpriteMap.insertSprite( childSprite.loadShape().toSprite(), true, true );
    }
    return newSpriteMap;
  }


  @Override
  public Shape findShape( String parameterName, String parameterValue ) {
    super.findShape( parameterName, parameterValue );
    for( Shape childShape: sprites ) {
      Shape shape = childShape.findShape( parameterName, parameterValue );
      if( shape != null ) return shape;
    }
    return null;
  }


  @Override
  public Shape findShape( Class shapeClass ) {
    super.findShape( shapeClass );
    for( Shape childShape: sprites ) {
      Shape shape = childShape.findShape( shapeClass );
      if( shape != null ) return shape;
    }
    return null;
  }


  @Override
  public Shape findShape( String parameterName, String parameterValue, Class shapeClass ) {
    super.findShape( parameterName, parameterValue, shapeClass );
    for( Shape childShape: sprites ) {
      Shape shape = childShape.findShape( parameterName, parameterValue, shapeClass );
      if( shape != null ) return shape;
    }
    return null;
  }


  @Override
  public boolean insert( Shape shape, Shape pivotShape, Relativity relativity ) {
    Sprite sprite = pivotShape.toSprite();
    if( sprite == null ) return false;
    if( sprites.contains( sprite ) ) {
      sprite = shape.toSprite();
      if( sprite != null ) sprites.add( sprite );
      return true;
    } else {
      return false;
    }
  }
 
 
  @Override
  public boolean insert( Collection<Shape> shapes, Shape pivotShape, Relativity relativity ) {
    Sprite sprite = pivotShape.toSprite();
    if( sprite == null ) return false;
    if( sprites.contains( sprite ) ) {
      for( Shape shape : shapes ) {
        sprite = shape.toSprite();
        if( sprite != null ) sprites.add( sprite );
      }
      return true;
    } else {
      return false;
    }
  }
 

  @Override
  public Shape remove( Shape shape ) {
    Sprite sprite = shape.toSprite();
    if( sprite != null ) sprites.remove( sprite );
    return this;
  }


  @Override
  public Shape remove( Class shapeClass ) {
    for ( Iterator<Sprite> iterator = sprites.iterator(); iterator.hasNext(); ) {
      Shape childSprite = iterator.next();
      if( childSprite.getClass() == shapeClass ) iterator.remove(); else childSprite.remove( shapeClass );
    }
    return this;
  }

  // ==================== Other =================== 
 
  @Override
  public void init() {
    for( Sprite sprite: sprites ) sprite.init();
  }


  @Override
  public void act() {
    for( Sprite sprite: sprites ) sprite.act();
  }


  @Override
  public void update() {
    for( Shape obj: sprites ) obj.update();
  }


  @Override
  public Shape clone() {
    SpriteMap newSpriteMap = new SpriteMap();
    copyTo( newSpriteMap );
    for( Sprite sprite: sprites ) newSpriteMap.insertSprite( sprite, true, true );
    return newSpriteMap;
  }


  @Override
  public void copyTo( Shape shape ) {
    copyShapeTo( shape );
    SpriteMap spriteMap =  shape.toSpriteMap();
   
    if( debug ) if( spriteMap == null ) error( "Trying to copy sprite map \"" + shape.getTitle() + "\" data to non-sprite-map" );

    spriteMap.setResolution( xQuantity, yQuantity );
    spriteMap.cellWidth = cellWidth;
    spriteMap.cellHeight = cellHeight;
    spriteMap.leftMargin = leftMargin;
    spriteMap.rightMargin = rightMargin;
    spriteMap.topMargin = topMargin;
    spriteMap.bottomMargin = bottomMargin;
    spriteMap.sorted = sorted;
    spriteMap.pivotMode = pivotMode;
  }


  @Override
  public int showModels( int y, String shift ) {
    if( behaviorModels.isEmpty() ) {
      if( sprites.isEmpty() ) return y;
      drawText( shift + getTitle() + " ", 0, y );
        y += 16;
    } else {
      y = super.showModels( y, shift );
    }
    for( Shape shape: sprites ) y = shape.showModels( y, shift + " " );
    return y;
  }


  @Override
  public void xMLIO( XMLObject xMLObject ) {
    cellWidth = xMLObject.manageDoubleAttribute( "cell-width", cellWidth, 1d );
    cellHeight = xMLObject.manageDoubleAttribute( "cell-height", cellHeight, 1d );
    leftMargin = xMLObject.manageDoubleAttribute( "left-margin", leftMargin );
    rightMargin = xMLObject.manageDoubleAttribute( "right-margin", rightMargin );
    topMargin = xMLObject.manageDoubleAttribute( "top-margin", topMargin );
    bottomMargin = xMLObject.manageDoubleAttribute( "bottom-margin", bottomMargin );
    sorted = xMLObject.manageBooleanAttribute( "sorted", sorted );
    pivotMode = xMLObject.manageBooleanAttribute( "pivot-mode", pivotMode );
    initialArraysSize = xMLObject.manageIntAttribute( "arrays-size", initialArraysSize, 8 );

    super.xMLIO( xMLObject );

    if( XMLObject.xMLGetMode() ) {
      for( XMLObject spriteXMLObject: xMLObject.children ) insertSprite( (Sprite) spriteXMLObject.manageObject( null ), true, true );
    } else {
      for( Sprite sprite: sprites ) {
        XMLObject newXMLObject = new XMLObject();
        newXMLObject.manageObject( sprite );
        xMLObject.children.addLast( newXMLObject );
      }
    }
  }
}
TOP

Related Classes of dwlab.shapes.maps.SpriteMap

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.