/* @file DrawingPath.java
*
* @author marco corvi
* @date nov 2011
*
* @brief TopoDroid drawing: paths (points, lines, and areas)
*
* FixedPath path is a straight line between the two endpoints
* GridPath paths are also straight lines
* previewPath path is a line with "many" points
*
* --------------------------------------------------------
* Copyright This sowftare is distributed under GPL-3.0 or later
* See the file COPYING.
* --------------------------------------------------------
*/
package com.topodroid.DistoX;
import java.io.PrintWriter;
import java.io.DataOutputStream;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Matrix;
import android.graphics.RectF;
import android.util.Log;
/**
* direct/indirect subclasses:
* - DrawingPointLinePath
* - DrawingLinePath
* - DrawingAreaPath
* - DrawingPointPath
* - DrawingStationPath
*/
public class DrawingPath extends RectF
implements ICanvasCommand
{
public static final int DRAWING_PATH_FIXED = 0; // leg
public static final int DRAWING_PATH_SPLAY = 1; // splay
public static final int DRAWING_PATH_GRID = 2; // grid
public static final int DRAWING_PATH_STATION = 3; // station point (user inserted)
public static final int DRAWING_PATH_POINT = 4; // drawing point
public static final int DRAWING_PATH_LINE = 5;
public static final int DRAWING_PATH_AREA = 6;
public static final int DRAWING_PATH_NAME = 7; // station name (from survey data)
public static final int DRAWING_PATH_NORTH = 8; // north line (5m long)
Path mPath;
Path mTransformedPath;
Paint mPaint;
int mType;
String mOptions;
float x1, y1, x2, y2; // endpoint scene coords (not private just to write the scrap scale using mNorthLine )
// private int dir; // 0 x1 < x2, 1 y1 < y2, 2 x2 < x1, 3 y2 < y1
DistoXDBlock mBlock;
float cx, cy; // midpoint scene coords
// RectF mBBox; // path boundig box (scene coords)
DrawingPath( int type, DistoXDBlock blk )
{
mType = type;
mOptions = null;
mBlock = blk;
// mBBox = new RectF();
mPaint = BrushManager.errorPaint;
// dir = 4;
// x1 = y1 = 0.0f;
// x2 = y2 = 1.0f;
// dx = dy = 1.0f;
}
static boolean isReferenceType( int type )
{
return type < DrawingPath.DRAWING_PATH_STATION || type >= DrawingPath.DRAWING_PATH_NAME;
}
static boolean isDrawingType( int type )
{
return type >= DrawingPath.DRAWING_PATH_STATION && type < DrawingPath.DRAWING_PATH_NAME;
}
void setBBox( float x1, float x2, float y1, float y2 )
{
left = x1;
right = x2;
top = y1;
bottom = y2;
}
protected boolean intersects( RectF bbox )
{
// return true;
if ( bbox == null ) return true;
if ( ( bbox.right < left )
|| ( bbox.left > right )
|| ( bbox.top > bottom )
|| ( bbox.bottom < top ) ) return false;
return true;
}
public void flipXAxis( float z )
{
float dx = 2 * DrawingUtil.CENTER_X;
float offx = dx - 2 * cx; // OK for non-orientable points
cx = dx - cx;
x1 = dx - x1;
x2 = dx - x2;
float r1 = dx - left;
left = dx - right;
right = r1;
boolean flip_path = false;
if ( mType == DRAWING_PATH_POINT ) {
DrawingPointPath dpp = (DrawingPointPath)this;
if ( dpp.mOrientation != 0 ) {
dpp.mOrientation = 360 - dpp.mOrientation;
flip_path = true;
offx = dx;
} else {
}
// Log.v("DistoX", "x0 " + x0 + " offx " + offx + " Cx " + oldcx + " -> " + cx);
if ( mPath != null ) {
if ( flip_path ) {
float m[] = new float[9]; // { -1, 0, 0, 0, 1, 0, 0, 0, 1 };
android.graphics.Matrix mat = new android.graphics.Matrix();
mat.getValues( m );
m[0] = -m[0];
mat.setValues( m );
mPath.transform( mat );
}
mPath.offset( offx, 0 );
}
} else if ( mType == DRAWING_PATH_STATION ) {
if ( mPath != null ) {
mPath.offset( offx, 0 );
}
}
}
// get the path color (or white)
int color() { return ( mPaint != null )? mPaint.getColor() : 0xffffffff; }
// void log()
// {
// Log.v("DistoX", "Path " + x1 + " " + y1 + " " + x2 + " " + y2 );
// }
/** make the path copying from another path
* @param path the path to copy
* @param m transform matrix
* @param off_x offset X
* @param off_y offset Y
*/
void makePath( Path path, Matrix m, float off_x, float off_y )
{
mPath = new Path( path );
mPath.transform( m );
mPath.offset( off_x, off_y );
}
void makeStraightPath( float x1, float y1, float x2, float y2, float off_x, float off_y )
{
mPath = new Path();
mPath.moveTo( x1, y1 );
mPath.lineTo( x2, y2 );
mPath.offset( off_x, off_y );
}
void makeTrianglePath( float x, float y, float r, float off_x, float off_y )
{
float r2 = r * 1.732f;
mPath = new Path();
mPath.moveTo( x1-r, y1 );
mPath.lineTo( x1+r, y1 );
mPath.lineTo( x1, y1-r2 );
mPath.lineTo( x1-r, y1 );
mPath.offset( off_x, off_y );
}
public void setPaint( Paint paint ) { mPaint = paint; }
// x10, y10 first endpoint scene coords
// x20, y20 second endpoint scene coords
public void setEndPoints( float x10, float y10, float x20, float y20 )
{
x1 = x10;
y1 = y10;
x2 = x20;
y2 = y20;
// dir = ( Math.abs( x2-x1 ) >= Math.abs( y2-y1 ) )?
// ( (x2 > x1)? 0 : 2 ) : ( (y2>y1)? 1 : 3 );
// d = Math.sqrt( (x2-x1)*(x2-x1) + (y2-y1)*(y2-y1) );
cx = (x20+x10)/2;
cy = (y20+y10)/2;
if ( x1 < x2 ) {
left = x1;
right = x2;
} else {
left = x2;
right = x1;
}
if ( y1 < y2 ) {
top = y1;
bottom = y2;
} else {
top = y2;
bottom = y1;
}
}
// intersection of
// x = x1 + t*(x2-x1)
// y = y1 + t*(y2-y1)
// and
// x = x10 + s*(x20-x10)
// y = y10 + s*(y20-y10)
//
// t * (x2-x1) - s*(x20-x10) = x10 - x1;
// t * (y2-y1) - s*(y20-y10) = y10 - y1;
// inverse
// t | -(y20-y10) +(x20-x10) | | x10 - x1 |
// s | -(y2-y1) +(x2-x1) | | y10 - y1 |
//
boolean intersect( float x10, float y10, float x20, float y20, Float result )
{
float det = -(x2-x1)*(y20-y10) + (x20-x10)*(y2-y1);
float t = ( -(y20-y10)*(x10 - x1) + (x20-x10)*(y10 - y1) )/det;
float s = ( -(y2-y1)*(x10 - x1) + (x2-x1)*(y10 - y1) )/det;
if ( result != null ) result = t;
return ( t > 0.0f && t < 1.0f && s > 0.0f && s < 1.0f );
}
// DrawingPath by deafult does not shift
void shiftBy( float dx, float dy ) { }
// by default does not rotate (return false)
boolean rotateBy( float dy ) { return false; }
public void shiftPathBy( float dx, float dy )
{
x1 += dx;
y1 += dy;
x2 += dx;
y2 += dy;
cx += dx;
cy += dy;
mPath.offset( dx, dy );
left += dx;
right += dx;
top += dy;
bottom += dy;
}
// this is used only by the Selection
float distanceToPoint( float x, float y )
{
// if ( mBlock == null ) return 1000.0f; // a large number
double dx = x - cx;
double dy = y - cy;
return (float)( Math.sqrt( dx*dx + dy*dy ) );
}
// public int type() { return mType; }
public void draw( Canvas canvas, RectF bbox )
{
if ( intersects( bbox ) ) {
if ( mType == DRAWING_PATH_AREA ) {
// TDLog.Log( TDLog.LOG_PLOT, "DrawingPath::draw area" );
mPath.close();
}
drawPath( mPath, canvas );
}
}
// N.B. canvas is guaranteed ! null
public void draw( Canvas canvas, Matrix matrix, float scale, RectF bbox )
{
if ( intersects( bbox ) )
{
mTransformedPath = new Path( mPath );
mTransformedPath.transform( matrix );
drawPath( mTransformedPath, canvas );
}
}
/** FIXME apparently this can be called when mPaint is still null
* and when fixedBluePaint is null
*/
protected void drawPath( Path path, Canvas canvas )
{
if ( mType == DRAWING_PATH_AREA ) {
if ( mPaint != null ) {
canvas.save();
canvas.clipPath( path );
canvas.drawPaint( mPaint );
canvas.restore();
}
} else {
if ( mType == DRAWING_PATH_SPLAY
&& mBlock != null && mBlock.isRecent( TopoDroidApp.mSecondLastShotId )
&& BrushManager.fixedBluePaint != null ) {
canvas.drawPath( path, BrushManager.fixedBluePaint );
} else if ( mPaint != null ) {
canvas.drawPath( path, mPaint );
}
}
}
public void setOrientation( double angle ) { }
public String toTherion() { return new String("FIXME"); }
void toDataStream( DataOutputStream dos ) { TDLog.Error( "ERROR DrawingPath toDataStream executed"); }
public void toCsurvey( PrintWriter pw, String cave, String branch ) { }
// ICanvasCommand interface
//
public int commandType() { return 0; }
// public void undoCommand() { // TODO this would be changed later }
public void computeBounds( RectF bound, boolean b ) { mPath.computeBounds( bound, b ); }
// public void transform( Matrix matrix ) { mPath.transform( matrix ); }
// ------------------------------------------------------------------
// Therion options
void addOption( String option )
{
// Log.v("DistoX", "add option <" + option +">" );
if ( mOptions == null ) {
mOptions = option;
} else {
mOptions = mOptions + " " + option;
}
}
// String[] getOptions()
// {
// if ( mOptions == null ) return new String[0];
// return mOptions.split(" ");
// }
// key must be not null and start with '-'
String getOption( String key )
{
if ( mOptions == null ) return null;
String vals[] = mOptions.split(" ");
int len = vals.length;
for ( int k = 0; k < len; ++k ) {
if ( key.equals( vals[k] ) ) {
while ( ++k < len ) if ( vals[k].length() > 0 ) return vals[k];
break;
}
}
return null;
}
String getOptionString() { return ( mOptions == null )? "" : mOptions; }
void setOptions( String options ) { mOptions = options; }
}