Author Topic: Some reimplemented Java FOV algorithms (Basic,RPAS)  (Read 7808 times)

Leaf

  • Rogueliker
  • ***
  • Posts: 64
  • Karma: +0/-0
    • View Profile
    • Email
Some reimplemented Java FOV algorithms (Basic,RPAS)
« on: December 03, 2011, 10:31:36 PM »
Howdy folks.  Just wanted to share a bit of FOV code.

It seems to be reasonably fast, though I'd like to remove the need to re-process the returned set of visible points via clever use of generics and inheritance....

The test machine is a 2.1GHz Phenom 8450 Triple-Core Processor (though it's only running on one core) running Linux Morrigan 2.6.38-12-generic-pae #51-Ubuntu SMP Wed Sep 28 16:11:32 UTC 2011 i686 athlon i386 GNU/Linux.

The test environment is Java(TM) SE Runtime Environment (build 1.6.0_26-b03)
Java HotSpot(TM) Server VM (build 20.1-b02, mixed mode)

Code: [Select]
Running speed test (this will give more accurate results than a single run, because the hotspot JIT will have time to kick in).
Test size is 133x133 area.
Field of view object is RPAS.
..........
Average time per pass: 0.5167ms (1935 fields of view per second).

Code: [Select]
Running speed test (this will give more accurate results than a single run, because the hotspot JIT will have time to kick in).
Test size is 133x133 area.
Field of view object is Basic.
..........
Average time per pass: 0.6307ms (1585 fields of view per second).

The source for everything it a teeny bit too long to fit into one post, so I'll put it up in replies...

Leaf

  • Rogueliker
  • ***
  • Posts: 64
  • Karma: +0/-0
    • View Profile
    • Email
Re: Some reimplemented Java FOV algorithms (Basic,RPAS)
« Reply #1 on: December 03, 2011, 10:32:17 PM »
Here's a nice little compact RPAS algorithm.

Edit: Added OUTER_ANGLE_SWEEP factor.

Code: [Select]
/*
 * Copyright (c) 2011, L. Adamson / Dizzy Dragon Games. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer.
 *
 * Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 * We'd love to hear from you if you're using this software for something or
 * have any questions or comments. You can contact us at via email at
 * support@dizzydragon.net.
 */

package retrocrawl.lib.fov;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.Set;

abstract public class FieldOfView2dRpas extends FieldOfView2d
{
private static final long serialVersionUID = 1L;

// This value can be 1, 2, or 3.  It indicates the number of angles that
// must be blocked before a cell is considered opaque.
final private static int PERMISSIVENESS = 2;

// This value spreads the start and end angles a little wider than usual.
// This acts somewhat like permissiveness, but is a more fine-grained
// control.  It helps to eliminate some visual artifacts.
final private static float OUTER_ANGLE_SWEEP = 0.2f;

final private static class AngleRange
{
private float startAngle;
private float endAngle;

private AngleRange( float startAngle, float endAngle )
{
this.startAngle = startAngle;
this.endAngle = endAngle;
}
}

protected FieldOfView2dRpas()
{
super( true );
}

final private ArrayList<AngleRange> blockedAngles = new ArrayList<AngleRange>();

final private boolean isBlockedAngle( float angle )
{
for( AngleRange ar : blockedAngles )
{
if( angle >= ar.startAngle && angle <= ar.endAngle )
{
return true;
}
}
return false;
}

@Override
final public Set<FieldOfView2dCoordinate> getFieldOfViewFromImpl( FieldOfView2dCoordinate origin, int radius )
{
HashSet<FieldOfView2dCoordinate> results = new HashSet<FieldOfView2dCoordinate>();
results.add( origin );
for( boolean flip = false; ; flip = true )
{
for( int xdir = -1; xdir <= 1; xdir += 2 )
{
for( int ydir = -1; ydir <= 1; ydir += 2 )
{
blockedAngles.clear();
boolean lineOpen = true;
for( int line = 1; line <= radius && lineOpen; line++ )
{
lineOpen = false;
for( int cell = 0; cell <= line; cell++ )
{
int x;
int y;
if( flip )
{
x = line*xdir;
y = cell*ydir;
}
else
{
x = cell*xdir;
y = line*ydir;
}
float range = 1.0f/(float)(line+1);
float startAngle = range * (float)(cell);
float midAngle = startAngle + range*0.5f;
float endAngle = startAngle + range;
startAngle -= range * OUTER_ANGLE_SWEEP;
endAngle += range * OUTER_ANGLE_SWEEP;
int nBlockedAngles = 0;
if( isBlockedAngle(startAngle) )
nBlockedAngles++;
if( isBlockedAngle(midAngle) )
nBlockedAngles++;
if( isBlockedAngle(endAngle) )
nBlockedAngles++;
FieldOfView2dCoordinate coordinate = new FieldOfView2dCoordinate(origin.x()+x, origin.y()+y);
if( nBlockedAngles < PERMISSIVENESS )
{
results.add( coordinate );
lineOpen = true;
}
if( cellIsOpaque(coordinate) )
blockedAngles.add( new AngleRange(startAngle,endAngle) );
}
}
}
}
if( flip )
break;
}
return results;
}
}
« Last Edit: December 04, 2011, 05:00:08 PM by Leaf »

Leaf

  • Rogueliker
  • ***
  • Posts: 64
  • Karma: +0/-0
    • View Profile
    • Email
Re: Some reimplemented Java FOV algorithms (Basic,RPAS)
« Reply #2 on: December 03, 2011, 10:32:51 PM »
Here's a tree-based Basic algorithm that reduces the number of multiple visits to cells by about a third.

Code: [Select]
/*
 * Copyright (c) 2011, L. Adamson / Dizzy Dragon Games. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer.
 *
 * Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 * We'd love to hear from you if you're using this software for something or
 * have any questions or comments. You can contact us at via email at
 * support@dizzydragon.net.
 */

package retrocrawl.lib.fov;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Set;

abstract public class FieldOfView2dBasic extends FieldOfView2d
{
private static final long serialVersionUID = 1L;

// Rays are precalculated once for each radius and compressed down into
// trees of nodes that we walk along while exploring the field of view.
// This reduces the number of repeat cell visits by about one third.
//
// For speed optimization, we store node trees in a static threadlocal
// map and only calculate them once per thread. The threadlocal allows
// it to be thread-safe without having to deal with lock contention
// issues during map access, even though it uses a little more memory.
// In the future, we might want to let the user select whether to use
// threadlocal maps or a single static synchronized map...

final private static class Fov2dNode
{
private FieldOfView2dCoordinate position;
final private ArrayList<Fov2dNode> leaves = new ArrayList<Fov2dNode>();
}

transient private static ThreadLocal<HashMap<Integer,Fov2dNode>> rootNodes = null;

protected FieldOfView2dBasic()
{
super( true );
}

@Override
final public Set<FieldOfView2dCoordinate> getFieldOfViewFromImpl( FieldOfView2dCoordinate origin, int radius )
{
HashSet<FieldOfView2dCoordinate> results = new HashSet<FieldOfView2dCoordinate>();
initializeNodeTree( radius );
followNode( radius, origin, rootNodes.get().get( radius ), results );
return results;
}

final private void initializeNodeTree( int radius )
{
if( rootNodes == null )
rootNodes = new ThreadLocal<HashMap<Integer,Fov2dNode>>();
HashMap<Integer,Fov2dNode> map = rootNodes.get();
if( map == null )
{
map = new HashMap<Integer,Fov2dNode>();
rootNodes.set( map );
}
Fov2dNode root = map.get( radius );

if( root == null )
{
root = new Fov2dNode();
map.put( radius, root );
root.position = new FieldOfView2dCoordinate( 0, 0 );

// Cast rays, build a sequence of nodes for each ray.
for( int eY = -radius; eY <= radius; eY++ )
{
Fov2dNode currentEast = root;
Fov2dNode currentWest = root;
Fov2dNode currentNorth = root;
Fov2dNode currentSouth = root;
double slope = (double)eY / (double)radius;
for( int x = 0; x <= radius; x++ )
{
int y = (int)( (double)x * slope );

Fov2dNode node;

node = new Fov2dNode();
node.position = new FieldOfView2dCoordinate( x, y );
currentEast.leaves.add( node );
currentEast = node;

node = new Fov2dNode();
node.position = new FieldOfView2dCoordinate( -x, y );
currentWest.leaves.add( node );
currentWest = node;

node = new Fov2dNode();
node.position = new FieldOfView2dCoordinate( y, x );
currentNorth.leaves.add( node );
currentNorth = node;

node = new Fov2dNode();
node.position = new FieldOfView2dCoordinate( y, -x );
currentSouth.leaves.add( node );
currentSouth = node;
}
}

// Recurse through node sequences and merge leaves with duplicate
// coordinates.
mergeNode( root );
}
}

final private static void mergeNode( Fov2dNode node )
{
boolean dup;
do
{
dup = false;
for( Fov2dNode child : node.leaves )
{
for( Fov2dNode dupNode : node.leaves )
{
if( child != dupNode && child.position.equals( dupNode.position ) )
{
for( Fov2dNode n : dupNode.leaves )
child.leaves.add( n );
node.leaves.remove( dupNode );
dup = true;
break;
}
}
if( dup )
break;
}
} while( dup );

for( Fov2dNode child : node.leaves )
mergeNode( child );
}

final private void followNode( int radius, FieldOfView2dCoordinate origin, Fov2dNode node, HashSet<FieldOfView2dCoordinate> results )
{
if( Math.abs( node.position.x() ) <= radius && Math.abs( node.position.y() ) <= radius )
{
FieldOfView2dCoordinate modifiedPosition = new FieldOfView2dCoordinate( origin.x() + node.position.x(), origin.y() + node.position.y() );
results.add( modifiedPosition );
if( !cellIsOpaque( modifiedPosition ) || ( origin.x() == modifiedPosition.x() && origin.y() == modifiedPosition.y() ) )
for( Fov2dNode child : node.leaves )
followNode( radius, origin, child, results );
}
}
}

Leaf

  • Rogueliker
  • ***
  • Posts: 64
  • Karma: +0/-0
    • View Profile
    • Email
Re: Some reimplemented Java FOV algorithms (Basic,RPAS)
« Reply #3 on: December 03, 2011, 10:33:31 PM »
Here's the abstract class that they both implement.  It does a little post-processing on the concrete-class results to fix visual artifacts.

Edit: Fixed a bug in the second post-process filter (the one that makes corners appear).

Code: [Select]
/*
 * Copyright (c) 2011, L. Adamson / Dizzy Dragon Games. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer.
 *
 * Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 * We'd love to hear from you if you're using this software for something or
 * have any questions or comments. You can contact us at via email at
 * support@dizzydragon.net.
 */

package retrocrawl.lib.fov;

import java.io.Serializable;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Set;

abstract public class FieldOfView2d implements Serializable
{
private static final long serialVersionUID = 1L;

final public static class FieldOfView2dCoordinate implements Serializable
{
private static final long serialVersionUID = 1L;

private int x;
private int y;

/**
* Instantiates a new FieldOfView2dCoordinate with the specified x and y values.
*
* @param x
*            the x coordinate
* @param y
*            the y coordinate
*/
public FieldOfView2dCoordinate( int x, int y )
{
this.x = x;
this.y = y;
}

/**
* @return the x value of this coordinate.
*/
public int x()
{
return x;
}

/**
* @return the y value of this coordinate.
*/
public int y()
{
return y;
}

@Override
public int hashCode()
{
return x ^ Integer.reverse( y );
}

@Override
public boolean equals( Object o )
{
if( o instanceof FieldOfView2dCoordinate )
return ( (FieldOfView2dCoordinate)o ).x == x && ( (FieldOfView2dCoordinate)o ).y == y;
return false;
}

@Override
public String toString()
{
return x + "," + y;
}
}

private boolean requiresPostProcessing = false;
final private HashMap<FieldOfView2dCoordinate,Boolean> opaqueCells = new HashMap<FieldOfView2dCoordinate,Boolean>();
final private HashMap<FieldOfView2dCoordinate,Boolean> cellContainsData = new HashMap<FieldOfView2dCoordinate,Boolean>();

protected FieldOfView2d( boolean requiresPostProcessing )
{
this.requiresPostProcessing = requiresPostProcessing;
}

abstract protected Set<FieldOfView2dCoordinate> getFieldOfViewFromImpl( FieldOfView2dCoordinate origin, int radius );

abstract protected boolean cellIsOpaqueImpl( FieldOfView2dCoordinate coordinate );

abstract protected boolean cellContainsDataImpl( FieldOfView2dCoordinate coordinate );

final protected boolean cellIsOpaque( int x, int y )
{
return cellIsOpaque( new FieldOfView2dCoordinate(x,y) );
}

final protected boolean cellIsOpaque( FieldOfView2dCoordinate coordinate )
{
Boolean isOpaque = opaqueCells.get( coordinate );
if( isOpaque == null )
{
isOpaque = cellIsOpaqueImpl( coordinate );
opaqueCells.put( coordinate, isOpaque );
}
return isOpaque;
}

final protected boolean cellContainsData( int x, int y )
{
return cellContainsData( new FieldOfView2dCoordinate(x,y) );
}

final protected boolean cellContainsData( FieldOfView2dCoordinate coordinate )
{
Boolean containsData = cellContainsData.get( coordinate );
if( containsData == null )
{
containsData = cellContainsDataImpl( coordinate );
cellContainsData.put( coordinate, containsData );
}
return containsData;
}

final public Set<FieldOfView2dCoordinate> getFieldOfViewFrom( FieldOfView2dCoordinate origin, int radius )
{
opaqueCells.clear();
cellContainsData.clear();
Set<FieldOfView2dCoordinate> results = getFieldOfViewFromImpl( origin, radius );
if( requiresPostProcessing )
{
// This filter ensures that opaque cells cardinally adjacent to
// non-opaque cells are drawn.
HashSet<FieldOfView2dCoordinate> resultsBuf = new HashSet<FieldOfView2dCoordinate>( results );
for( FieldOfView2dCoordinate c : resultsBuf )
{
if( !cellIsOpaque(c) )
{
for( int d = -1; d<=1; d += 2 )
{
FieldOfView2dCoordinate c2;
c2 = new FieldOfView2dCoordinate(c.x()+d,c.y());
if( cellIsOpaque(c2) && cellContainsData(c2) )
results.add( c2 );
c2 = new FieldOfView2dCoordinate(c.x(),c.y()+d);
if( cellIsOpaque(c2) && cellContainsData(c2) )
results.add( c2 );
}
}
}
// This filter ensures that ordinal corners next to two adjacent
// cardinal opaque cells which are next to a non-opaque cell in
// a convex fashion are drawn.  (That is, it gets rid of the
// missing corner syndrome.)
resultsBuf = new HashSet<FieldOfView2dCoordinate>( results );
for( FieldOfView2dCoordinate c : resultsBuf )
{
if( !cellIsOpaque(c) )
{
for( int dy=-1; dy<=1; dy+=2 )
{
for( int dx=-1; dx<=1; dx+=2 )
{
FieldOfView2dCoordinate c2 = new FieldOfView2dCoordinate(c.x()+dx,c.y()+dy);
if( cellContainsData(c2) && cellIsOpaque(c2) )
{
FieldOfView2dCoordinate c3a = new FieldOfView2dCoordinate(c2.x()-dx,c2.y());
FieldOfView2dCoordinate c3b = new FieldOfView2dCoordinate(c2.x(),c2.y()-dy);
if( cellIsOpaque(c3a) && resultsBuf.contains( c3a ) && cellContainsData(c3a) && cellIsOpaque(c3b) && resultsBuf.contains( c3b ) && cellContainsData(c3b) )
results.add( c2 );
}
}
}
}
}
}
opaqueCells.clear(); // Allow entries to be GCed.
cellContainsData.clear(); // Allow entries to be GCed.
return results;
}

final public Set<FieldOfView2dCoordinate> getFieldOfViewFrom( int x, int y, int radius )
{
return getFieldOfViewFrom( new FieldOfView2dCoordinate(x,y), radius );
}
}
« Last Edit: December 04, 2011, 05:04:23 PM by Leaf »

Leaf

  • Rogueliker
  • ***
  • Posts: 64
  • Karma: +0/-0
    • View Profile
    • Email
Re: Some reimplemented Java FOV algorithms (Basic,RPAS)
« Reply #4 on: December 03, 2011, 10:34:27 PM »
And here's a main() to test it all (and show you how to use it with your own game).

Code: [Select]
package scratch;

import java.util.Random;
import java.util.Set;

import retrocrawl.lib.fov.FieldOfView2d;
import retrocrawl.lib.fov.FieldOfView2dBasic;
import retrocrawl.lib.fov.FieldOfView2dRpas;
import retrocrawl.lib.fov.FieldOfView2d.FieldOfView2dCoordinate;

public class FovTest
{
final private static int MAP_SIZE = 200;

final private static boolean[][] map = new boolean[MAP_SIZE][MAP_SIZE];

public static void main( String[] args )
{
// Whip up a quicky map.
generateMap();

// Instantiate a FOV processing object.  You can use FieldOfView2dBasic or FieldOfView2dRpas here.  You could also extend the class instead of using an anonymous class, if you wanted.
final FieldOfView2d fov = new FieldOfView2dRpas()
{
private static final long serialVersionUID = 1L;

// Implement this method so that the processing object can look in your map data and tell whether a cell is opaque or not.
@Override
protected boolean cellIsOpaqueImpl( FieldOfView2dCoordinate coordinate )
{
if( !cellContainsData(coordinate) )
return true;
return map[coordinate.x()][coordinate.y()];
}

// Implement this method so that the processing object can look into you map data and tell whether a cell is valid or not.
@Override
protected boolean cellContainsDataImpl( FieldOfView2dCoordinate coordinate )
{
if( coordinate.x() < 0 || coordinate.x() >= MAP_SIZE || coordinate.y() < 0 || coordinate.y() >= MAP_SIZE )
return false;
return true;
}
};

// Obtain a set of points that are visible from the given location (MAP_SIZE/2,MAP_SIZE/2) within the given radius (MAP_SIZE/3).
Set<FieldOfView2dCoordinate> results;
long t;
t = System.currentTimeMillis();
results = fov.getFieldOfViewFrom( MAP_SIZE/2, MAP_SIZE/2, MAP_SIZE/3 );
t = System.currentTimeMillis() - t;
System.out.println( "FOV generation took "+t+"ms." );

// Now that you have a set of visible points, you can draw them however you need to...
char[][] output = new char[MAP_SIZE][MAP_SIZE];
// Initialize the output to a blank map.
for( int y=0; y<MAP_SIZE; y++ )
for( int x=0; x<MAP_SIZE; x++ )
output[x][y] = '.';
// Draw the results into the output.
for( FieldOfView2dCoordinate c : results )
{
if( c.x()==MAP_SIZE/2 && c.y()==MAP_SIZE/2 )
output[c.x()][c.y()] = '@';
else if( map[c.x()][c.y()] )
output[c.x()][c.y()] = '#';
else
output[c.x()][c.y()] = ' ';
}
// Print the output, whee...
for( int y=0; y<MAP_SIZE; y++ )
{
for( int x=0; x<MAP_SIZE; x++ )
{
System.out.print( output[x][y] );
}
System.out.println();
}
}

final static void generateMap()
{
Random r = new Random();
for( int y=0; y<MAP_SIZE; y++ )
for( int x=0; x<MAP_SIZE; x++ )
map[x][y] = false;
for( int i=0; i<(MAP_SIZE*MAP_SIZE)/5; i++ )
map[r.nextInt(MAP_SIZE)][r.nextInt(MAP_SIZE)] = true;
}
}
« Last Edit: December 03, 2011, 11:47:24 PM by Leaf »

Leaf

  • Rogueliker
  • ***
  • Posts: 64
  • Karma: +0/-0
    • View Profile
    • Email
Re: Some reimplemented Java FOV algorithms (Basic,RPAS)
« Reply #5 on: December 03, 2011, 10:46:25 PM »
And here's a screenie of what I'm working on. :3

« Last Edit: December 05, 2011, 03:10:58 AM by Leaf »