/*
* Copyright (c) 2016, Metron, Inc.
* 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.
* * Neither the name of Metron, Inc. nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* 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 METRON, INC. 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.
*/
package com.metsci.glimpse.dnc;
import static com.metsci.glimpse.dnc.convert.Flat2Query.boxContainsPoint;
import static com.metsci.glimpse.dnc.convert.Flat2Query.boxIntersectsLine;
import static com.metsci.glimpse.dnc.convert.Flat2Query.boxIntersectsTriangle;
import static com.metsci.glimpse.dnc.convert.Query.intsPerQueryLineItem;
import static com.metsci.glimpse.dnc.convert.Query.intsPerQueryPointItem;
import static com.metsci.glimpse.dnc.convert.Query.intsPerQueryTriangleItem;
import static com.metsci.glimpse.dnc.util.DncMiscUtils.last;
import static java.lang.Float.intBitsToFloat;
import java.nio.IntBuffer;
import java.util.ArrayList;
import java.util.List;
import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
import it.unimi.dsi.fastutil.ints.IntSet;
public class DncTree
{
protected static interface Node
{
void search( float xMin, float xMax, float yMin, float yMax, IntSet featureNums );
}
protected static class EmptyNode implements Node
{
public EmptyNode( )
{ }
@Override
public void search( float xMin, float xMax, float yMin, float yMax, IntSet featureNums )
{ }
}
protected static class InteriorNode implements Node
{
public final float xDivider;
public final float yDivider;
public final Node child0;
public final Node child1;
public final Node child2;
public final Node child3;
public InteriorNode( float xDivider, float yDivider, Node child0, Node child1, Node child2, Node child3 )
{
this.xDivider = xDivider;
this.yDivider = yDivider;
this.child0 = child0;
this.child1 = child1;
this.child2 = child2;
this.child3 = child3;
}
@Override
public void search( float xMin, float xMax, float yMin, float yMax, IntSet featureNums )
{
// Treating both min and max as inclusive simplifies handling of degenerate items (e.g. points)
boolean includeSmallX = ( xMin <= xDivider );
boolean includeLargeX = ( xMax >= xDivider );
boolean includeSmallY = ( yMin <= yDivider );
boolean includeLargeY = ( yMax >= yDivider );
if ( includeSmallX && includeSmallY ) child0.search( xMin, xMax, yMin, yMax, featureNums );
if ( includeLargeX && includeSmallY ) child1.search( xMin, xMax, yMin, yMax, featureNums );
if ( includeSmallX && includeLargeY ) child2.search( xMin, xMax, yMin, yMax, featureNums );
if ( includeLargeX && includeLargeY ) child3.search( xMin, xMax, yMin, yMax, featureNums );
}
}
protected class LeafNode implements Node
{
public final float xMin;
public final float xMax;
public final float yMin;
public final float yMax;
public final int pointFirst;
public final int pointCount;
public final int lineFirst;
public final int lineCount;
public final int triangleFirst;
public final int triangleCount;
public LeafNode( float xMin, float xMax, float yMin, float yMax, int pointFirst, int pointCount, int lineFirst, int lineCount, int triangleFirst, int triangleCount )
{
this.xMin = xMin;
this.xMax = xMax;
this.yMin = yMin;
this.yMax = yMax;
this.pointFirst = pointFirst;
this.pointCount = pointCount;
this.lineFirst = lineFirst;
this.lineCount = lineCount;
this.triangleFirst = triangleFirst;
this.triangleCount = triangleCount;
}
@Override
public void search( float xMin, float xMax, float yMin, float yMax, IntSet featureNums )
{
boolean xAll = ( xMin <= this.xMin && this.xMax <= xMax );
boolean yAll = ( yMin <= this.yMin && this.yMax <= yMax );
if ( xAll && yAll )
{
for ( int pointNum = pointFirst; pointNum < ( pointFirst + pointCount ); pointNum++ )
{
int featureNum = pointFeatureNum( pointNum );
featureNums.add( featureNum );
}
for ( int lineNum = lineFirst; lineNum < ( lineFirst + lineCount ); lineNum++ )
{
int featureNum = lineFeatureNum( lineNum );
featureNums.add( featureNum );
}
for ( int triangleNum = triangleFirst; triangleNum < ( triangleFirst + triangleCount ); triangleNum++ )
{
int featureNum = triangleFeatureNum( triangleNum );
featureNums.add( featureNum );
}
}
else
{
for ( int pointNum = pointFirst; pointNum < ( pointFirst + pointCount ); pointNum++ )
{
int featureNum = pointFeatureNum( pointNum );
if ( !featureNums.contains( featureNum ) )
{
float x = pointX( pointNum );
float y = pointY( pointNum );
if ( boxContainsPoint( xMin, yMin, xMax, yMax, x, y ) )
{
featureNums.add( featureNum );
}
}
}
for ( int lineNum = lineFirst; lineNum < ( lineFirst + lineCount ); lineNum++ )
{
int featureNum = lineFeatureNum( lineNum );
if ( !featureNums.contains( featureNum ) )
{
float xA = lineXA( lineNum );
float yA = lineYA( lineNum );
float xB = lineXB( lineNum );
float yB = lineYB( lineNum );
if ( boxIntersectsLine( xMin, yMin, xMax, yMax, xA, yA, xB, yB ) )
{
featureNums.add( featureNum );
}
}
}
for ( int triangleNum = triangleFirst; triangleNum < ( triangleFirst + triangleCount ); triangleNum++ )
{
int featureNum = triangleFeatureNum( triangleNum );
if ( !featureNums.contains( featureNum ) )
{
float xA = triangleXA( triangleNum );
float yA = triangleYA( triangleNum );
float xB = triangleXB( triangleNum );
float yB = triangleYB( triangleNum );
float xC = triangleXC( triangleNum );
float yC = triangleYC( triangleNum );
if ( boxIntersectsTriangle( xMin, yMin, xMax, yMax, xA, yA, xB, yB, xC, yC ) )
{
featureNums.add( featureNum );
}
}
}
}
}
}
protected final Node root;
protected final IntBuffer pointsBuf;
protected final IntBuffer linesBuf;
protected final IntBuffer trianglesBuf;
public DncTree( IntBuffer interiorNodesBuf,
IntBuffer leafNodesBuf,
IntBuffer pointsBuf,
IntBuffer linesBuf,
IntBuffer trianglesBuf )
{
List<LeafNode> leafNodes = new ArrayList<>( );
while ( leafNodesBuf.hasRemaining( ) )
{
float xMin = intBitsToFloat( leafNodesBuf.get( ) );
float xMax = intBitsToFloat( leafNodesBuf.get( ) );
float yMin = intBitsToFloat( leafNodesBuf.get( ) );
float yMax = intBitsToFloat( leafNodesBuf.get( ) );
int pointFirst = leafNodesBuf.get( );
int pointCount = leafNodesBuf.get( );
int lineFirst = leafNodesBuf.get( );
int lineCount = leafNodesBuf.get( );
int triangleFirst = leafNodesBuf.get( );
int triangleCount = leafNodesBuf.get( );
leafNodes.add( new LeafNode( xMin, xMax, yMin, yMax, pointFirst, pointCount, lineFirst, lineCount, triangleFirst, triangleCount ) );
}
List<InteriorNode> interiorNodes = new ArrayList<>( );
while ( interiorNodesBuf.hasRemaining( ) )
{
float xDivider = intBitsToFloat( interiorNodesBuf.get( ) );
float yDivider = intBitsToFloat( interiorNodesBuf.get( ) );
int childNum0 = interiorNodesBuf.get( );
int childNum1 = interiorNodesBuf.get( );
int childNum2 = interiorNodesBuf.get( );
int childNum3 = interiorNodesBuf.get( );
// A non-negative child number is an index into the interiorNodes list
// Interior nodes are written post-order depth-first, so children are already in the list
// A negative child number gets bitwise NOT-ed and used as an index into the leaves list
Node child0 = ( childNum0 >= 0 ? interiorNodes.get( childNum0 ) : leafNodes.get( ~childNum0 ) );
Node child1 = ( childNum1 >= 0 ? interiorNodes.get( childNum1 ) : leafNodes.get( ~childNum1 ) );
Node child2 = ( childNum2 >= 0 ? interiorNodes.get( childNum2 ) : leafNodes.get( ~childNum2 ) );
Node child3 = ( childNum3 >= 0 ? interiorNodes.get( childNum3 ) : leafNodes.get( ~childNum3 ) );
interiorNodes.add( new InteriorNode( xDivider, yDivider, child0, child1, child2, child3 ) );
}
if ( !interiorNodes.isEmpty( ) )
{
this.root = last( interiorNodes );
}
else if ( !leafNodes.isEmpty( ) )
{
this.root = last( leafNodes );
}
else
{
this.root = new EmptyNode( );
}
this.pointsBuf = pointsBuf;
this.linesBuf = linesBuf;
this.trianglesBuf = trianglesBuf;
}
public IntSet search( float xMin, float xMax, float yMin, float yMax )
{
IntSet featureNums = new IntOpenHashSet( );
root.search( xMin, xMax, yMin, yMax, featureNums );
return featureNums;
}
/**
* The featureNums arg is a dual input/output arg. Features already in the set can
* be pruned early, avoiding potentially expensive computation. New search results
* are added to the set.
*/
protected void search( float xMin, float xMax, float yMin, float yMax, IntSet featureNums )
{
root.search( xMin, xMax, yMin, yMax, featureNums );
}
// Points
//
protected int pointFeatureNum( int pointNum )
{
return pointsBuf.get( ( pointNum * intsPerQueryPointItem ) + 0 );
}
protected float pointX( int pointNum )
{
return intBitsToFloat( pointsBuf.get( ( pointNum * intsPerQueryPointItem ) + 1 ) );
}
protected float pointY( int pointNum )
{
return intBitsToFloat( pointsBuf.get( ( pointNum * intsPerQueryPointItem ) + 2 ) );
}
// Lines
//
protected int lineFeatureNum( int lineNum )
{
return linesBuf.get( ( lineNum * intsPerQueryLineItem ) + 0 );
}
protected float lineXA( int lineNum )
{
return intBitsToFloat( linesBuf.get( ( lineNum * intsPerQueryLineItem ) + 1 ) );
}
protected float lineYA( int lineNum )
{
return intBitsToFloat( linesBuf.get( ( lineNum * intsPerQueryLineItem ) + 2 ) );
}
protected float lineXB( int lineNum )
{
return intBitsToFloat( linesBuf.get( ( lineNum * intsPerQueryLineItem ) + 3 ) );
}
protected float lineYB( int lineNum )
{
return intBitsToFloat( linesBuf.get( ( lineNum * intsPerQueryLineItem ) + 4 ) );
}
// Triangles
//
protected int triangleFeatureNum( int triangleNum )
{
return trianglesBuf.get( ( triangleNum * intsPerQueryTriangleItem ) + 0 );
}
protected float triangleXA( int triangleNum )
{
return intBitsToFloat( trianglesBuf.get( ( triangleNum * intsPerQueryTriangleItem ) + 1 ) );
}
protected float triangleYA( int triangleNum )
{
return intBitsToFloat( trianglesBuf.get( ( triangleNum * intsPerQueryTriangleItem ) + 2 ) );
}
protected float triangleXB( int triangleNum )
{
return intBitsToFloat( trianglesBuf.get( ( triangleNum * intsPerQueryTriangleItem ) + 3 ) );
}
protected float triangleYB( int triangleNum )
{
return intBitsToFloat( trianglesBuf.get( ( triangleNum * intsPerQueryTriangleItem ) + 4 ) );
}
protected float triangleXC( int triangleNum )
{
return intBitsToFloat( trianglesBuf.get( ( triangleNum * intsPerQueryTriangleItem ) + 5 ) );
}
protected float triangleYC( int triangleNum )
{
return intBitsToFloat( trianglesBuf.get( ( triangleNum * intsPerQueryTriangleItem ) + 6 ) );
}
}