/* @file DrawingPointLinePath.java * * @author marco corvi * @date nov 2011 * * @brief TopoDroid drawing: line of points * * The area border (line) path id DrawingPath.mPath * * -------------------------------------------------------- * Copyright This sowftare is distributed under GPL-3.0 or later * See the file COPYING. * -------------------------------------------------------- */ package com.topodroid.DistoX; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Path; import android.graphics.Matrix; import java.util.Iterator; // import java.util.List; import java.util.ArrayList; // used by Log.e import android.util.Log; /** */ public class DrawingPointLinePath extends DrawingPath { private boolean mVisible; // visible line private boolean mClosed; // ArrayList< LinePoint > mPoints; // points (scene coordinates) private LinePoint mPrevPoint = null; // previous point while constructing the line LinePoint mFirst; LinePoint mLast; private int mSize; // number of points float mDx, mDy; // unit vector in the direction of this line int size() { return mSize; } /* DEBUG * counts how many points this line overlaps with another line */ int overlap( DrawingPointLinePath other ) { int ret = 0; for (LinePoint l1 = mFirst; l1 != null; l1=l1.mNext ) { for ( LinePoint l2 = other.mFirst; l2 != null; l2 = l2.mNext ) { if ( Math.abs( l1.mX - l2.mX ) < 0.001f && Math.abs( l1.mY - l2.mY ) < 0.001f ) { ++ ret; break; } } } return ret; } // void dump() // DEBUG // { // int k=0; // for (LinePoint l1 = mFirst; l1 != null; l1=l1.mNext ) { // // if ( k < 2 || k > mSize-2 ) // { // Log.v("DistoX", k + ": " + l1.mX + " " + l1.mY ); // } // ++k; // } // } void moveFirstTo( float x, float y ) { mFirst.mX = x; mFirst.mY = y; retracePath(); } @Override public void flipXAxis( float z ) { super.flipXAxis(z); for ( LinePoint lp = mFirst; lp != null; lp = lp.mNext ) { lp.flipXAxis(z); } retracePath(); } void recount() // throws Exception { if ( mFirst == null ) { mSize = 0; mLast = null; return; } mSize = 1; for ( LinePoint p = mFirst.mNext; p != null; p = p.mNext ) { if ( p == mFirst ) break; // Log.v( "DistoX", "[>] " + p.mX + " " + p.mY ); if ( ++ mSize > 100 ) break;; } // CHECK; int size = 1; for ( LinePoint p = mLast.mPrev; p != null; p = p.mPrev ) { if ( p == mLast ) break; // Log.v( "DistoX", "[<] " + p.mX + " " + p.mY ); if ( ++ size > 100 ) break;; } if ( size != mSize ) { TDLog.Error( "recount size mismatch " + mSize + " " + size ); // throw new Exception("size mismatch"); } } public DrawingPointLinePath( int path_type, boolean visible, boolean closed ) { super( path_type, null ); // DrawingPath.DRAWING_PATH_AREA ); // mPoints = new ArrayList< LinePoint >(); clear(); mVisible = visible; mClosed = closed; mFirst = null; mLast = null; mSize = 0; mDx = mDy = 0; } void setClosed( boolean closed ) { mClosed = closed; } boolean isClosed() { return mClosed; } boolean isPathClosed() { if ( mSize < 2 ) return false; float dx = (mFirst.mX - mLast.mX)/3; float dy = (mFirst.mY - mLast.mY)/3; return ( dx*dx + dy*dy < 1.0e-7 ); } void setVisible( boolean visible ) { mVisible = visible; } boolean isVisible() { return mVisible; } void computeUnitNormal() { mDx = mDy = 0; } /** unlink a line_point */ void remove( LinePoint lp ) { if ( lp == mFirst ) { mFirst = lp.mNext; } else if ( lp.mPrev != null ) { lp.mPrev.mNext = lp.mNext; } if ( lp == mLast ) { mLast = lp.mPrev; } else if ( lp.mNext != null ) { lp.mNext.mPrev = lp.mPrev; } lp.mNext = null; lp.mPrev = null; -- mSize; computeUnitNormal(); } LinePoint next( LinePoint lp ) { if ( lp == null ) return null; return lp.mNext; } LinePoint prev( LinePoint lp ) { if ( lp == null ) return null; return lp.mPrev; } void makeSharp( ) { // FIXME this was here: retracePath(); for ( LinePoint lp = mFirst; lp != null; lp = lp.mNext ) { lp.has_cp = false; } retracePath(); } void makeReduce() { if ( mSize > 2 ) { int size = 1; LinePoint prev = mFirst; LinePoint pt = mFirst.mNext; while ( pt != mLast ) { LinePoint next = pt.mNext; // pt.mNext != null because pt < mLast float x1 = pt.mX - prev.mX; float y1 = pt.mY - prev.mY; float x2 = next.mX - pt.mX; float y2 = next.mY - pt.mY; float cos = (x1*x2 + y1*y2)/(float)Math.sqrt((x1*x1+y1*y1)*(x2*x2+y2*y2)); if ( cos >= 0.7 ) { prev.mNext = next; next.mPrev = prev; } else { ++ size; prev = pt; } pt = next; } ++ size; // for the mLast point mSize = size; } retracePath(); } void makeClose( ) { if ( mSize > 2 ) { float dx = (mFirst.mX - mLast.mX)/3; float dy = (mFirst.mY - mLast.mY)/3; if ( dx*dx + dy*dy > 1.0e-7 ) { if ( mLast.has_cp ) { mLast.mX1 += dx; mLast.mY1 += dy; mLast.mX2 += dx*2; mLast.mY2 += dy*2; } mLast.mX = mFirst.mX; mLast.mY = mFirst.mY; retracePath(); } } } private void clear() { mFirst = null; mLast = null; mPath = new Path(); mSize = 0; left = 0; right = 0; top = 0; bottom = 0; mDx = 0; mDy = 0; } // with_arrow: put a arrow-tick before the first point void makeStraight( boolean with_arrow ) { // Log.v( TopoDroidApp.TAG, "make straight with arrow " + with_arrow + " size " + mPoints.size() ); // if ( mPoints.size() < 2 ) return; // LinePoint first = mPoints.get( 0 ); // LinePoint last = mPoints.get( mPoints.size() - 1 ); if ( mSize < 2 ) return; LinePoint first = mFirst; LinePoint last = mLast; clear(); if ( with_arrow ) { float dy = first.mX - last.mX; float dx = - first.mY + last.mY; float d = dx*dx + dy*dy; if ( d > 0.00001f ) { d = TDSetting.mArrowLength * TDSetting.mUnit / (float)Math.sqrt( d ); dx *= d; dy *= d; addStartPoint( first.mX+dx, first.mY+dy ); addPoint( first.mX, first.mY ); } else { addStartPoint( first.mX, first.mY ); } } else { addStartPoint( first.mX, first.mY ); } addPoint( last.mX, last.mY ); if ( with_arrow ) { mDx = mDy = 0; } else { computeUnitNormal(); } // Log.v( TopoDroidApp.TAG, "make straight final size " + mPoints.size() ); } public void addStartPoint( float x, float y ) { // mPrevPoint = new LinePoint(x,y, null); // mPoints.add( mPrevPoint ); mLast = new LinePoint(x,y, null); ++ mSize; mFirst = mLast; mPath.moveTo( x, y ); left = right = x; top = bottom = y; } public void addPoint( float x, float y ) { if ( Float.isNaN(x) || Float.isNaN(y) ) return; if ( mFirst == null ) { addStartPoint( x, y ); } else { // mPrevPoint = new LinePoint(x,y,mPrevPoint); // mPoints.add( mPrevPoint ); mLast = new LinePoint(x, y, mLast); ++ mSize; mPath.lineTo( x, y ); if ( x < left ) { left = x; } else if ( x > right ) { right = x; } if ( y < top ) { top = y; } else if ( y > bottom ) { bottom = y; } } } public void addPoint3( float x1, float y1, float x2, float y2, float x, float y ) { if ( Float.isNaN(x) || Float.isNaN(y) ) return; if ( mFirst == null ) { addStartPoint( x, y ); } else { if ( Float.isNaN( x1 ) || Float.isNaN( y1 ) || Float.isNaN( x2 ) || Float.isNaN( y2 ) ) { mLast = new LinePoint(x, y, mLast); ++ mSize; mPath.lineTo( x, y ); } else { // mPrevPoint = new LinePoint( x1,y1, x2,y2, x,y, mPrevPoint ); // mPoints.add( mPrevPoint ); mLast = new LinePoint( x1,y1, x2,y2, x,y, mLast ); ++mSize; mPath.cubicTo( x1,y1, x2,y2, x,y ); } if ( x < left ) { left = x; } else if ( x > right ) { right = x; } if ( y < top ) { top = y; } else if ( y > bottom ) { bottom = y; } } } // ---------------------------------------------- protected void addStartPointNoPath( float x, float y ) { mFirst = mLast = new LinePoint(x,y, null); ++ mSize; left = right = x; top = bottom = y; } protected void addPointNoPath( float x, float y ) { mLast = new LinePoint(x, y, mLast); ++ mSize; if ( x < left ) { left = x; } else if ( x > right ) { right = x; } if ( y < top ) { top = y; } else if ( y > bottom ) { bottom = y; } } protected void addPoint3NoPath( float x1, float y1, float x2, float y2, float x, float y ) { mLast = new LinePoint( x1,y1, x2,y2, x,y, mLast ); ++mSize; if ( x < left ) { left = x; } else if ( x > right ) { right = x; } if ( y < top ) { top = y; } else if ( y > bottom ) { bottom = y; } } // ---------------------------------------------- void append( DrawingPointLinePath line ) { if ( line.mSize == 0 ) return; LinePoint lp = line.mFirst; addPoint( lp.mX, lp.mY ); for ( lp = lp.mNext; lp != null; lp = lp.mNext ) { if ( lp.has_cp ) { addPoint3( lp.mX1, lp.mY1, lp.mX2, lp.mY2, lp.mX, lp.mY ); } else { addPoint( lp.mX, lp.mY ); } } // computeUnitNormal(); } void resetPath( ArrayList<LinePoint> pts ) { clear(); // if ( pts == null ) return; int size = pts.size(); if ( size <= 1 ) return; LinePoint lp = pts.get(0); addStartPoint( lp.mX, lp.mY ); for ( int k=1; k<size; ++k ) { lp = pts.get(k); if ( lp.has_cp ) { addPoint3( lp.mX1, lp.mY1, lp.mX2, lp.mY2, lp.mX, lp.mY ); } else { addPoint( lp.mX, lp.mY ); } } computeUnitNormal(); } LinePoint insertPointAfter( float x, float y, LinePoint lp ) { if ( Float.isNaN(x) || Float.isNaN(y) ) return null; // int index = mPoints.indexOf(lp); // if ( index < mPoints.size() ) ++index; // insert before next point LinePoint next = lp.mNext; LinePoint pp = new LinePoint(x, y, lp ); ++mSize; pp.mNext = next; if ( next != null ) next.mPrev = pp; // mPoints.add(index, pp); retracePath(); return pp; } void retracePath() { // int size = mPoints.size(); // if ( size == 0 ) return; // mPath = new Path(); // LinePoint lp = mPoints.get(0); // mPath.moveTo( lp.mX, lp.mY ); // for ( int k=1; k<size; ++k ) { // lp = mPoints.get(k); // if ( lp.has_cp ) { // mPath.cubicTo( lp.mX1, lp.mY1, lp.mX2, lp.mY2, lp.mX, lp.mY ); // } else { // mPath.lineTo( lp.mX, lp.mY ); // } // } if ( mSize == 0 ) return; mPath = new Path(); LinePoint lp = mFirst; mPath.moveTo( lp.mX, lp.mY ); for ( lp = lp.mNext; lp != null; lp = lp.mNext ) { if ( lp.has_cp ) { mPath.cubicTo( lp.mX1, lp.mY1, lp.mX2, lp.mY2, lp.mX, lp.mY ); } else { mPath.lineTo( lp.mX, lp.mY ); } } computeUnitNormal(); } void reversePath() { if ( mSize == 0 ) return; LinePoint lf = mFirst; LinePoint ll = mLast; clear(); // mPath = new Path(); // mFirst = null; // mLast = null; LinePoint lp = ll; addStartPoint( lp.mX, lp.mY ); LinePoint prev = lp.mPrev; while ( prev != null ) { if ( lp.has_cp ) { addPoint3( lp.mX2, lp.mY2, lp.mX1, lp.mY1, prev.mX, prev.mY ); } else { addPoint( prev.mX, prev.mY ); } lp = prev; prev = prev.mPrev; } computeUnitNormal(); // FIXME } @Override float distanceToPoint( float x, float y ) { if ( Float.isNaN(x) || Float.isNaN(y) ) return Float.NaN; float dist = 10000000f; // FIXME // for ( LinePoint pt : mPoints ) for ( LinePoint pt=mFirst; pt != null; pt = pt.mNext ) { float dx = x - pt.mX; float dy = y - pt.mY; float d2 = dx*dx + dy*dy; if ( d2 < dist ) dist = d2; } return (dist > 0 )? TDMath.sqrt(dist) : 0; } public void close() { mPath.close(); // Log.v( TopoDroidApp.TAG, "area close path" ); } // public ArrayList< LinePoint > getPoints() { return mPoints; } // public int size() { return mPoints.size(); } // @Override // public void draw( Canvas canvas ) // { // super.draw( canvas ); // // Path path = new Path(); // // path.addCircle( 0, 0, 1, Path.Direction.CCW ); // // for ( LinePoint lp : mPoints ) { // // Path path1 = new Path( path ); // // path1.offset( lp.mX, lp.mY ); // // canvas.drawPath( path1, BrushManager.highlightPaint ); // // } // } // @Override // public void draw( Canvas canvas, Matrix matrix ) // { // super.draw( canvas, matrix ); // // Path path = new Path(); // // path.addCircle( 0, 0, 1, Path.Direction.CCW ); // // for ( LinePoint lp : mPoints ) { // // Path path1 = new Path( path ); // // path1.offset( lp.mX, lp.mY ); // // path1.transform( matrix ); // // canvas.drawPath( path1, BrushManager.highlightPaint ); // // } // } }