/*
 *                            COPYRIGHT
 *
 *  pcb-rnd, interactive printed circuit board design
 *  (this file is based on PCB, interactive printed circuit board design)
 *  Copyright (C) 1994,1995,1996 Thomas Nau
 *  Copyright (C) 2023 Tibor 'Igor2' Palinkas
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 31 Milk Street, # 960789 Boston, MA 02196 USA.
 *
 *  Contact:
 *    Project page: http://repo.hu/projects/pcb-rnd
 *    lead developer: http://repo.hu/projects/pcb-rnd/contact.html
 *    mailing list: pcb-rnd (at) list.repo.hu (send "subscribe")
 *
 */


/* search routines
 * some of the functions use dummy parameters
 */

#include "config.h"
#include "conf_core.h"

#include <math.h>

#include "actions_pcb.h"
#include "board.h"
#include "data.h"
#include <librnd/core/error.h>
#include <librnd/core/actions.h>
#include <librnd/poly/polyarea.h>
#include "find.h"
#include "polygon.h"
#include "search.h"
#include "select.h"
#include "obj_subc_parent.h"
#include "obj_pstk_inlines.h"

#define PCB_MAX_POLYGON_POINT_DISTANCE  0   /* maximum distance when searching polygon points */
#define PCB_MAX_LINE_POINT_DISTANCE     0   /* maximum distance when searching line points; same for arc point */

static double PosX, PosY;				/* search position for subroutines */
static rnd_coord_t SearchRadius;
static rnd_box_t SearchBox;
static pcb_layer_t *SearchLayer;

/* ---------------------------------------------------------------------------
 * The first parameter includes PCB_OBJ_LOCKED if we
 * want to include locked object in the search and PCB_OBJ_SUBC_PART if
 * objects that are part of a subcircuit should be found.
 */
static rnd_bool SearchLineByLocation(unsigned long, unsigned long, pcb_layer_t **, pcb_line_t **, pcb_line_t **);
static rnd_bool SearchArcByLocation(unsigned long, unsigned long, pcb_layer_t **, pcb_arc_t **, pcb_arc_t **);
static rnd_bool SearchGfxByLocation(unsigned long, unsigned long, pcb_layer_t **, pcb_gfx_t **, pcb_gfx_t **, int);
static rnd_bool SearchRatLineByLocation(unsigned long, unsigned long, pcb_rat_t **, pcb_rat_t **, pcb_rat_t **);
static rnd_bool SearchTextByLocation(unsigned long, unsigned long, pcb_layer_t **, pcb_text_t **, pcb_text_t **);
static rnd_bool SearchPolygonByLocation(unsigned long, unsigned long, pcb_layer_t **, pcb_poly_t **, pcb_poly_t **);
static rnd_bool SearchLinePointByLocation(unsigned long, unsigned long, pcb_layer_t **, pcb_line_t **, rnd_point_t **);
static rnd_bool SearchArcPointByLocation(unsigned long, unsigned long, pcb_layer_t **, pcb_arc_t **, int **);
static rnd_bool SearchPointByLocation(unsigned long, unsigned long, unsigned long, pcb_layer_t **, pcb_poly_t **, rnd_point_t **);
static rnd_bool SearchSubcByLocation(unsigned long, unsigned long, pcb_subc_t **, pcb_subc_t **, pcb_subc_t **, rnd_bool);

/* Return not-found for subc parts and locked items unless objst says otherwise
   obj is the object to be checked if part of subc; check lock on locked_obj
   Also return not-found if flags are required but the object doesn't have them */
#define TEST_OBJST_(objst, req_flag, locality, obj, locked_obj, reject) \
do { \
	if ((req_flag != 0) && (!PCB_FLAG_TEST(req_flag, obj))) \
		reject; \
	if (!(objst & PCB_OBJ_SUBC_PART) && (pcb_ ## locality ## obj_parent_subc(obj->parent_type, &obj->parent))) \
		reject; \
	if (!(objst & PCB_OBJ_LOCKED) && (PCB_FLAG_TEST(objst & PCB_FLAG_LOCK, locked_obj))) \
		reject; \
} while(0)

#define TEST_OBJST(objst, req_flag, locality, obj, locked_obj) \
	TEST_OBJST_(objst, req_flag, locality, obj, locked_obj, return rnd_RTREE_DIR_NOT_FOUND_CONT)

struct ans_info {
	void **ptr1, **ptr2, **ptr3;
	rnd_bool BackToo;
	double area;
	unsigned long objst, req_flag;
	int on_current;
	pcb_layer_type_t on_lyt;
};

/* ---------------------------------------------------------------------------
 * searches a padstack
 */
static rnd_rtree_dir_t padstack_callback(void *cl, void *obj, const rnd_rtree_box_t *box)
{
	struct ans_info *i = (struct ans_info *) cl;
	pcb_pstk_t *ps = (pcb_pstk_t *)obj;
	pcb_data_t *pdata;

	TEST_OBJST(i->objst, i->req_flag, g, ps, ps);

	if (!pcb_is_point_in_pstk(PosX, PosY, SearchRadius, ps, NULL))
		return rnd_RTREE_DIR_NOT_FOUND_CONT;
	if ((i->on_current) && (pcb_pstk_shape_at(PCB, ps, PCB_CURRLAYER(PCB)) == NULL) && (!pcb_pstk_bb_drills(PCB, ps, PCB_CURRLAYER(PCB)->meta.real.grp, NULL)))
		return rnd_RTREE_DIR_NOT_FOUND_CONT;
	if ((i->on_lyt != 0) && (pcb_pstk_shape(ps, i->on_lyt, 0) == NULL))
		return rnd_RTREE_DIR_NOT_FOUND_CONT;
	*i->ptr2 = *i->ptr3 = ps;
	assert(ps->parent_type == PCB_PARENT_DATA);
	pdata = ps->parent.data;
	if (pdata->parent_type == PCB_PARENT_SUBC)
		*i->ptr1 = pdata->parent.subc; /* padstack of a subcircuit */
	else
		*i->ptr1 = ps; /* padstack on a board (e.g. via) */
	return rnd_RTREE_DIR_FOUND_STOP; /* found, stop searching */
}

static rnd_bool SearchPadstackByLocation(unsigned long objst, unsigned long req_flag, pcb_pstk_t **ps, pcb_pstk_t **Dummy1, pcb_pstk_t **Dummy2, int on_current, pcb_layer_type_t on_lyt)
{
	struct ans_info info;

	/* search only if via-layer is visible */
	if (!PCB->pstk_on)
		return rnd_false;

	info.ptr1 = (void **)ps;
	info.ptr2 = (void **)Dummy1;
	info.ptr3 = (void **)Dummy2;
	info.objst = objst;
	info.req_flag = req_flag;
	info.on_current = on_current;
	info.on_lyt = on_lyt;

	return (rnd_rtree_search_any(PCB->Data->padstack_tree, (rnd_rtree_box_t *)&SearchBox, NULL, padstack_callback, &info, NULL) & rnd_RTREE_DIR_FOUND);
}

/* ---------------------------------------------------------------------------
 * searches ordinary line on the SearchLayer
 */

struct line_info {
	pcb_line_t **Line;
	rnd_point_t **Point;
	double least;
	unsigned long objst, req_flag;
};


static rnd_rtree_dir_t line_callback(void *cl, void *obj, const rnd_rtree_box_t *box)
{
	struct line_info *i = (struct line_info *) cl;
	pcb_line_t *l = (pcb_line_t *)obj;

	TEST_OBJST(i->objst, i->req_flag, l, l, l);

	if (!pcb_is_point_in_line(PosX, PosY, SearchRadius, (pcb_any_line_t *)l))
		return rnd_RTREE_DIR_NOT_FOUND_CONT;
	*i->Line = l;
	*i->Point = (rnd_point_t *) l;

	return rnd_RTREE_DIR_FOUND_STOP; /* found what we were looking for */
}


static rnd_bool SearchLineByLocation(unsigned long objst, unsigned long req_flag, pcb_layer_t ** Layer, pcb_line_t ** Line, pcb_line_t ** Dummy)
{
	struct line_info info;

	info.Line = Line;
	info.Point = (rnd_point_t **) Dummy;
	info.objst = objst;
	info.req_flag = req_flag;

	*Layer = SearchLayer;
	return (rnd_rtree_search_any(SearchLayer->line_tree, (rnd_rtree_box_t *)&SearchBox, NULL, line_callback, &info, NULL) & rnd_RTREE_DIR_FOUND);
}

static rnd_rtree_dir_t rat_callback(void *cl, void *obj, const rnd_rtree_box_t *box)
{
	pcb_line_t *line = (pcb_line_t *)obj;
	struct ans_info *i = (struct ans_info *) cl;

	TEST_OBJST(i->objst, i->req_flag, l, line, line);

	if (PCB_FLAG_TEST(PCB_FLAG_VIA, line) ?
			(rnd_distance(line->Point1.X, line->Point1.Y, PosX, PosY) <=
			 line->Thickness * 2 + SearchRadius) : pcb_is_point_on_line(PosX, PosY, SearchRadius, line)) {
		*i->ptr1 = *i->ptr2 = *i->ptr3 = line;
		return rnd_RTREE_DIR_FOUND_STOP;
	}
	return rnd_RTREE_DIR_NOT_FOUND_CONT;
}

/* ---------------------------------------------------------------------------
 * searches rat lines if they are visible
 */
static rnd_bool SearchRatLineByLocation(unsigned long objst, unsigned long req_flag, pcb_rat_t ** Line, pcb_rat_t ** Dummy1, pcb_rat_t ** Dummy2)
{
	struct ans_info info;

	info.ptr1 = (void **) Line;
	info.ptr2 = (void **) Dummy1;
	info.ptr3 = (void **) Dummy2;
	info.objst = objst;
	info.req_flag = req_flag;

	return (rnd_rtree_search_any(PCB->Data->rat_tree, (rnd_rtree_box_t *)&SearchBox, NULL, rat_callback, &info, NULL) & rnd_RTREE_DIR_FOUND);
}

/* ---------------------------------------------------------------------------
 * searches arc on the SearchLayer
 */
struct arc_info {
	pcb_arc_t **Arc, **Dummy;
	unsigned long objst, req_flag;
	int **arc_pt; /* 0=start, 1=end (start+delta) */
	double least;
};

static rnd_rtree_dir_t arc_callback(void *cl, void *obj, const rnd_rtree_box_t *box)
{
	struct arc_info *i = (struct arc_info *) cl;
	pcb_arc_t *a = (pcb_arc_t *)obj;

	TEST_OBJST(i->objst, i->req_flag, l, a, a);

	if (!pcb_is_point_on_arc(PosX, PosY, SearchRadius, a))
		return rnd_RTREE_DIR_NOT_FOUND_CONT;
	*i->Arc = a;
	*i->Dummy = a;
	return rnd_RTREE_DIR_FOUND_STOP;
}


static rnd_bool SearchArcByLocation(unsigned long objst, unsigned long req_flag, pcb_layer_t ** Layer, pcb_arc_t ** Arc, pcb_arc_t ** Dummy)
{
	struct arc_info info;

	info.Arc = Arc;
	info.Dummy = Dummy;
	info.objst = objst;
	info.req_flag = req_flag;

	*Layer = SearchLayer;
	return (rnd_rtree_search_any(SearchLayer->arc_tree, (rnd_rtree_box_t *)&SearchBox, NULL, arc_callback, &info, NULL) & rnd_RTREE_DIR_FOUND);
}

/* ---------------------------------------------------------------------------
 * searches gfx on the SearchLayer
 */
struct gfx_info {
	pcb_gfx_t **Gfx, **Dummy;
	unsigned long objst, req_flag;
	double least;
	int level;
};

static rnd_rtree_dir_t gfx_callback(void *cl, void *obj, const rnd_rtree_box_t *box)
{
	struct gfx_info *i = (struct gfx_info *)cl;
	pcb_gfx_t *g = (pcb_gfx_t *)obj;

	TEST_OBJST(i->objst, i->req_flag, l, g, g);

	/* cheap: ignore gfx if it doesn't match requested rendering level */
	if ((i->level < 0) && !g->render_under) return 0;
	if ((i->level > 0) && g->render_under) return 0;

	if (!pcb_is_point_in_gfx(PosX, PosY, SearchRadius, g))
		return rnd_RTREE_DIR_NOT_FOUND_CONT;
	*i->Gfx = g;
	*i->Dummy = g;
	return rnd_RTREE_DIR_FOUND_STOP;
}

/* level: -1 returns only "render_under", +1 returns only render-above, 0
   returns anything */
static rnd_bool SearchGfxByLocation(unsigned long objst, unsigned long req_flag, pcb_layer_t **Layer, pcb_gfx_t **gfx, pcb_gfx_t **Dummy, int level)
{
	struct gfx_info info;

	info.Gfx = gfx;
	info.Dummy = Dummy;
	info.objst = objst;
	info.req_flag = req_flag;
	info.level = level;

	*Layer = SearchLayer;
	return (rnd_rtree_search_any(SearchLayer->gfx_tree, (rnd_rtree_box_t *)&SearchBox, NULL, gfx_callback, &info, NULL) & rnd_RTREE_DIR_FOUND);
}

static rnd_rtree_dir_t text_callback(void *cl, void *obj, const rnd_rtree_box_t *box)
{
	pcb_text_t *text = (pcb_text_t *)obj;
	struct ans_info *i = (struct ans_info *)cl;

	TEST_OBJST(i->objst, i->req_flag, l, text, text);

	if (PCB_POINT_IN_BOX(PosX, PosY, &text->BoundingBox)) {
		*i->ptr2 = *i->ptr3 = text;
		return rnd_RTREE_DIR_FOUND_STOP;
	}
	return rnd_RTREE_DIR_NOT_FOUND_CONT;
}

/* ---------------------------------------------------------------------------
 * searches text on the SearchLayer
 */
static rnd_bool SearchTextByLocation(unsigned long objst, unsigned long req_flag, pcb_layer_t ** Layer, pcb_text_t ** Text, pcb_text_t ** Dummy)
{
	struct ans_info info;

	*Layer = SearchLayer;
	info.ptr2 = (void **) Text;
	info.ptr3 = (void **) Dummy;
	info.objst = objst;
	info.req_flag = req_flag;

	return (rnd_rtree_search_any(SearchLayer->text_tree, (rnd_rtree_box_t *)&SearchBox, NULL, text_callback, &info, NULL) & rnd_RTREE_DIR_FOUND);
}

static rnd_rtree_dir_t polygon_callback(void *cl, void *obj, const rnd_rtree_box_t *box)
{
	pcb_poly_t *polygon = (pcb_poly_t *)obj;
	struct ans_info *i = (struct ans_info *)cl;

	TEST_OBJST(i->objst, i->req_flag, l, polygon, polygon);

	if (polygon->Clipped == NULL)
		return rnd_RTREE_DIR_NOT_FOUND_CONT; /* polygon cleared out of existence */

	if (pcb_poly_is_point_in_p(PosX, PosY, SearchRadius, polygon)) {
		*i->ptr2 = *i->ptr3 = polygon;
		return rnd_RTREE_DIR_FOUND_STOP; /* found */
	}
	return rnd_RTREE_DIR_NOT_FOUND_CONT;
}


/* ---------------------------------------------------------------------------
 * searches a polygon on the SearchLayer
 */
static rnd_bool SearchPolygonByLocation(unsigned long objst, unsigned long req_flag, pcb_layer_t ** Layer, pcb_poly_t ** Polygon, pcb_poly_t ** Dummy)
{
	struct ans_info info;

	*Layer = SearchLayer;
	info.ptr2 = (void **) Polygon;
	info.ptr3 = (void **) Dummy;
	info.objst = objst;
	info.req_flag = req_flag;

	return (rnd_rtree_search_any(SearchLayer->polygon_tree, (rnd_rtree_box_t *)&SearchBox, NULL, polygon_callback, &info, NULL) & rnd_RTREE_DIR_FOUND);
}

static rnd_rtree_dir_t linepoint_callback(void *cl, void *obj, const rnd_rtree_box_t *box)
{
	pcb_line_t *line = (pcb_line_t *) obj;
	struct line_info *i = (struct line_info *) cl;
	rnd_rtree_dir_t ret_val = rnd_RTREE_DIR_NOT_FOUND_CONT;
	double d;

	TEST_OBJST(i->objst, i->req_flag, l, line, line);

	/* some stupid code to check both points */
	d = rnd_distance(PosX, PosY, line->Point1.X, line->Point1.Y);
	if (d < i->least) {
		i->least = d;
		*i->Line = line;
		*i->Point = &line->Point1;
		ret_val = rnd_RTREE_DIR_FOUND_CONT;
	}

	d = rnd_distance(PosX, PosY, line->Point2.X, line->Point2.Y);
	if (d < i->least) {
		i->least = d;
		*i->Line = line;
		*i->Point = &line->Point2;
		ret_val = rnd_RTREE_DIR_FOUND_CONT;
	}
	return ret_val;
}

static rnd_rtree_dir_t arcpoint_callback(void *cl, void *obj, const rnd_rtree_box_t *box)
{
	rnd_box_t ab;
	pcb_arc_t *arc = (pcb_arc_t *)obj;
	struct arc_info *i = (struct arc_info *) cl;
	rnd_rtree_dir_t ret_val = rnd_RTREE_DIR_NOT_FOUND_CONT;
	double d;

	TEST_OBJST(i->objst, i->req_flag, l, arc, arc);

	/* some stupid code to check both points */
	pcb_arc_get_end(arc, 0, &ab.X1, &ab.Y1);
	pcb_arc_get_end(arc, 1, &ab.X2, &ab.Y2);

	d = rnd_distance(PosX, PosY, ab.X1, ab.Y1);
	if (d < i->least) {
		i->least = d;
		*i->Arc = arc;
		*i->arc_pt = pcb_arc_start_ptr;
		ret_val = rnd_RTREE_DIR_FOUND_CONT;
	}

	d = rnd_distance(PosX, PosY, ab.X2, ab.Y2);
	if (d < i->least) {
		i->least = d;
		*i->Arc = arc;
		*i->arc_pt = pcb_arc_end_ptr;
		ret_val = rnd_RTREE_DIR_FOUND_CONT;
	}
	return ret_val;
}

/* ---------------------------------------------------------------------------
 * searches a line-point on all the search layer
 */
static rnd_bool SearchLinePointByLocation(unsigned long objst, unsigned long req_flag, pcb_layer_t ** Layer, pcb_line_t ** Line, rnd_point_t ** Point)
{
	struct line_info info;
	*Layer = SearchLayer;
	info.Line = Line;
	info.Point = Point;
	*Point = NULL;
	info.least = PCB_MAX_LINE_POINT_DISTANCE + SearchRadius;
	info.objst = objst;
	info.req_flag = req_flag;

	return (rnd_rtree_search_any(SearchLayer->line_tree, (rnd_rtree_box_t *)&SearchBox, NULL, linepoint_callback, &info, NULL) & rnd_RTREE_DIR_FOUND);
}

/* ---------------------------------------------------------------------------
 * searches a line-point on all the search layer
 */
static rnd_bool SearchArcPointByLocation(unsigned long objst, unsigned long req_flag, pcb_layer_t ** Layer, pcb_arc_t ** Arc, int **Point)
{
	struct arc_info info;
	*Layer = SearchLayer;
	info.Arc = Arc;
	info.arc_pt = Point;
	*Point = NULL;
	info.least = PCB_MAX_LINE_POINT_DISTANCE + SearchRadius;
	info.objst = objst;
	info.req_flag = req_flag;

	return (rnd_rtree_search_any(SearchLayer->arc_tree, (rnd_rtree_box_t *)&SearchBox, NULL, arcpoint_callback, &info, NULL) & rnd_RTREE_DIR_FOUND);
}

/* ---------------------------------------------------------------------------
 * searches a polygon-point on all layers that are switched on
 * in layerstack order
 */
typedef struct {
	double least;
	rnd_bool found;
	unsigned long Type;

	/* result */
	pcb_poly_t **Polygon;
	rnd_point_t **Point;
} ptcb_t;

static rnd_rtree_dir_t polypoint_callback(void *cl, void *obj, const rnd_rtree_box_t *box)
{
	pcb_poly_t *polygon = (pcb_poly_t *)obj;
	ptcb_t *ctx = (ptcb_t *)cl;
	double d;
	pcb_data_t *dt;

	dt = polygon->parent.layer->parent.data; /* polygon -> layer -> data */
	if ((dt != NULL) && (dt->parent_type == PCB_PARENT_SUBC)) {
		/* do not find subc part poly points if not explicitly requested */
		if (!(ctx->Type & PCB_OBJ_SUBC_PART))
			return rnd_RTREE_DIR_NOT_FOUND_CONT;

		/* don't find subc poly points even as subc part unless we are editing a subc */
		if (!PCB->loose_subc)
			return rnd_RTREE_DIR_NOT_FOUND_CONT;
	}

	PCB_POLY_POINT_LOOP(polygon);
	{
		d = rnd_distance2(point->X, point->Y, PosX, PosY);
		if (d < ctx->least) {
			ctx->least = d;
			*ctx->Polygon = polygon;
			*ctx->Point = point;
			ctx->found = rnd_true;
		}
	}
	PCB_END_LOOP;

	return rnd_RTREE_DIR_NOT_FOUND_CONT;
}

static rnd_bool SearchPointByLocation(unsigned long Type, unsigned long objst, unsigned long req_flag, pcb_layer_t ** Layer, pcb_poly_t ** Polygon, rnd_point_t ** Point)
{
	ptcb_t ctx;

	*Layer = SearchLayer;
	ctx.Type = Type;
	ctx.Polygon = Polygon;
	ctx.Point = Point;
	ctx.found = rnd_false;
	ctx.least = SearchRadius + PCB_MAX_POLYGON_POINT_DISTANCE;
	ctx.least = ctx.least * ctx.least;
	rnd_rtree_search_any(SearchLayer->polygon_tree, (rnd_rtree_box_t *)&SearchBox, NULL, polypoint_callback, &ctx, NULL);

	if (ctx.found)
		return rnd_true;
	return rnd_false;
}

/* ---------------------------------------------------------------------------
 * searches a gfx-point on all layers that are switched on
 * in layerstack order
 */
typedef struct {
	double least;
	rnd_bool found;
	unsigned long Type;

	/* result */
	pcb_gfx_t **gfx;
	rnd_point_t **Point;
} gfxptcb_t;

static rnd_rtree_dir_t gfxpoint_callback(void *cl, void *obj, const rnd_rtree_box_t *box)
{
	pcb_gfx_t *gfx = (pcb_gfx_t *)obj;
	gfxptcb_t *ctx = (gfxptcb_t *)cl;
	double d;
	pcb_data_t *dt;
	int n;

	dt = gfx->parent.layer->parent.data; /* gfx -> layer -> data */
	if ((dt != NULL) && (dt->parent_type == PCB_PARENT_SUBC)) {
		/* do not find subc part poly points if not explicitly requested */
		if (!(ctx->Type & PCB_OBJ_SUBC_PART))
			return rnd_RTREE_DIR_NOT_FOUND_CONT;

		/* don't find subc poly points even as subc part unless we are editing a subc */
		if (!PCB->loose_subc)
			return rnd_RTREE_DIR_NOT_FOUND_CONT;
	}

	for(n = 0; n < 4; n++) {
		d = rnd_distance2(gfx->corner[n].X, gfx->corner[n].Y, PosX, PosY);
		if (d < ctx->least) {
			ctx->least = d;
			*ctx->gfx = gfx;
			*ctx->Point = &gfx->corner[n];
			ctx->found = rnd_true;
		}
	}

	return rnd_RTREE_DIR_NOT_FOUND_CONT;
}

static rnd_bool SearchGfxPointByLocation(unsigned long Type, unsigned long objst, unsigned long req_flag, pcb_layer_t **Layer, pcb_gfx_t **gfx, rnd_point_t **Point)
{
	gfxptcb_t ctx;

	*Layer = SearchLayer;
	ctx.Type = Type;
	ctx.gfx = gfx;
	ctx.Point = Point;
	ctx.found = rnd_false;
	ctx.least = SearchRadius + PCB_MAX_POLYGON_POINT_DISTANCE;
	ctx.least = ctx.least * ctx.least;
	rnd_rtree_search_any(SearchLayer->gfx_tree, (rnd_rtree_box_t *)&SearchBox, NULL, gfxpoint_callback, &ctx, NULL);

	if (ctx.found)
		return rnd_true;
	return rnd_false;
}


static rnd_rtree_dir_t subc_callback(void *cl, void *obj, const rnd_rtree_box_t *box)
{
	pcb_subc_t *subc = (pcb_subc_t *)obj;
	struct ans_info *i = (struct ans_info *) cl;
	double newarea;
	int subc_on_bottom = 0, front;

	pcb_subc_get_side(subc, &subc_on_bottom);
	front = subc_on_bottom == conf_core.editor.show_solder_side;

	TEST_OBJST(i->objst, i->req_flag, g, subc, subc);

	if ((front || i->BackToo) && PCB_POINT_IN_BOX(PosX, PosY, &subc->BoundingBox)) {
		rnd_coord_t ox, oy;
		if ((subc->extobj != NULL) && (pcb_subc_get_origin(subc, &ox, &oy) == 0)) {
			/* extended objects are special case: only the origin should be clickable
			   to avoid problems with large extended objects that cover the board */
			if ((PosX < ox) || (PosX > ox + PCB_SUBC_AUX_UNIT) || (PosY < oy) || (PosY > oy + PCB_SUBC_AUX_UNIT))
				return rnd_RTREE_DIR_NOT_FOUND_CONT;
		}
		/* use the subcircuit with the smallest bounding box */
		newarea = (subc->BoundingBox.X2 - subc->BoundingBox.X1) * (double) (subc->BoundingBox.Y2 - subc->BoundingBox.Y1);
		if (newarea < i->area) {
			i->area = newarea;
			*i->ptr1 = *i->ptr2 = *i->ptr3 = subc;
			return rnd_RTREE_DIR_FOUND_CONT;
		}
	}
	return rnd_RTREE_DIR_NOT_FOUND_CONT;
}


/* ---------------------------------------------------------------------------
 * searches a subcircuit
 * if more than one subc matches, the smallest one is taken
 */
static rnd_bool
SearchSubcByLocation(unsigned long objst, unsigned long req_flag, pcb_subc_t **subc, pcb_subc_t ** Dummy1, pcb_subc_t ** Dummy2, rnd_bool BackToo)
{
	struct ans_info info;

	/* Both package layers have to be switched on */
	info.ptr1 = (void **) subc;
	info.ptr2 = (void **) Dummy1;
	info.ptr3 = (void **) Dummy2;
	info.area = RND_SQUARE(RND_MAX_COORD);
	info.BackToo = (BackToo && PCB->InvisibleObjectsOn);
	info.objst = objst;
	info.req_flag = req_flag;

	return (rnd_rtree_search_any(PCB->Data->subc_tree, (rnd_rtree_box_t *)&SearchBox, NULL, subc_callback, &info, NULL) & rnd_RTREE_DIR_FOUND);
}

/* find the first floater on any layer */
static rnd_bool SearchSubcFloaterByLocation(unsigned long objst, unsigned long req_flag, pcb_subc_t **out_subc, pcb_text_t **out_txt, void **dummy, rnd_bool other_side)
{
	rnd_rtree_it_t it;
	void *obj;
	int n;
	pcb_layer_type_t my_side_lyt;

	if (other_side)
		my_side_lyt = conf_core.editor.show_solder_side ? PCB_LYT_TOP : PCB_LYT_BOTTOM;
	else
		my_side_lyt = conf_core.editor.show_solder_side ? PCB_LYT_BOTTOM : PCB_LYT_TOP;

	for(n = 0; n < PCB->Data->LayerN; n++)  {
		pcb_layer_t *ly = &PCB->Data->Layer[n];
		pcb_layer_type_t lyt;

		if ((ly->text_tree == NULL) || (!ly->meta.real.vis))
			continue;

		lyt = pcb_layer_flags_(ly);
		if (!(lyt & my_side_lyt))
			continue;

		for(obj = rnd_rtree_first(&it, ly->text_tree, (rnd_rtree_box_t *)&SearchBox); obj != NULL; obj = rnd_rtree_next(&it)) {
			pcb_text_t *txt = (pcb_text_t *)obj;
			pcb_subc_t *subc;

			if (PCB_FLAG_TEST(PCB_FLAG_FLOATER, txt) == 0)
				continue;

			subc = pcb_lobj_parent_subc(txt->parent_type, &txt->parent);
			if (subc == NULL)
				continue;

			TEST_OBJST_(objst, req_flag, l, txt, subc, continue);

			*out_subc = subc;
			*out_txt = txt;
			*dummy = txt;
			return rnd_true;
		}
	}

	return rnd_false;
}

/* for checking if a rat-line end is on a PV */
rnd_bool pcb_is_point_on_line_end(rnd_coord_t X, rnd_coord_t Y, pcb_rat_t *Line)
{
	if (((X == Line->Point1.X) && (Y == Line->Point1.Y)) || ((X == Line->Point2.X) && (Y == Line->Point2.Y)))
		return rnd_true;
	return rnd_false;
}

/* ---------------------------------------------------------------------------
 * Calculate the distance (squared) from a point to a line.
 *
 * let the point be (X,Y) and the line (X1,Y1)(X2,Y2)
 *
 * Calculate the position along the line that is closest to X,Y. The value 
 * ranges from 0 to 1 when the point is within the line boundaries with a 
 * value of 0 being at point X1,Y1 and a value of 1.0 being at point X2,Y2.
  *
 *   t = ((X-X1)*(X2-X1)) + ((Y-Y1)*(Y2-Y1))
 *       -----------------------------------
 *             (X2-X1)^2 + (Y2-Y1)^2
 *
 * let Q be the point of perpendicular projection of (X,Y) onto the line
 *
 *   QX = X1 + t * (X2-X1)
 *   QY = Y1 + t * (Y2-Y1)
 *
 * Calculate the distace (squared) using Pythagorean theorem.
 *
 *   distance^2 = (X-QX)^2 + (Y-QY)^2
 */

RND_INLINE double pcb_point_line_dist2_abstract(rnd_coord_t px, rnd_coord_t py, rnd_coord_t lx1, rnd_coord_t ly1, rnd_coord_t lx2, rnd_coord_t ly2, double *offs_out, double *prjx_out, double *prjy_out)
{
	const double abx = lx2 - lx1;
	const double aby = ly2 - ly1;
	const double apx = px - lx1;
	const double apy = py - ly1;
	double qx,qy,dx,dy;

	/* Calculate t, the normalised position along the line of the closest point 
	 * to (X,Y). A value between 0 and 1 indicates that the closest point on the 
	 * line is within the bounds of the end points. 
	 */
	double t = ( (apx * abx) + (apy * aby) ) / ( (abx * abx) + (aby * aby) );

	if (offs_out != NULL) *offs_out = t;
	if (prjx_out != NULL) *prjx_out = lx1 + (t * abx);
	if (prjy_out != NULL) *prjy_out = ly1 + (t * aby);

	/* Clamp 't' to the line bounds */
	if(t < 0.0)		t = 0.0;
	if(t > 1.0)		t = 1.0;

	/* Calculate the point Q on the line that is closest to (X,Y) */
	qx = lx1 + (t * abx);
	qy = ly1 + (t * aby);

	/* Return the distance from Q to (X,Y), squared */
	dx = px - qx;
	dy = py - qy;

	return (dx * dx) + (dy * dy);
}

double pcb_point_line_dist2(rnd_coord_t X, rnd_coord_t Y, pcb_line_t *Line)
{
	return pcb_point_line_dist2_abstract(X, Y, Line->Point1.X, Line->Point1.Y, Line->Point2.X, Line->Point2.Y, NULL, NULL, NULL);
}

double pcb_geo_point_line_dist2(rnd_coord_t px, rnd_coord_t py, rnd_coord_t lx1, rnd_coord_t ly1, rnd_coord_t lx2, rnd_coord_t ly2, double *offs_out, double *prjx_out, double *prjy_out)
{
	return pcb_point_line_dist2_abstract(px, py, lx1, ly1, lx2, ly2, offs_out, prjx_out, prjy_out);
}


rnd_bool pcb_is_point_on_line(rnd_coord_t X, rnd_coord_t Y, rnd_coord_t Radius, pcb_line_t *Line)
{
	double max = Radius + Line->Thickness / 2;

	return pcb_point_line_dist2(X, Y, Line) < (max * max);
}

static int is_point_on_line(rnd_coord_t px, rnd_coord_t py, rnd_coord_t lx1, rnd_coord_t ly1, rnd_coord_t lx2, rnd_coord_t ly2)
{
	/* ohh well... let's hope the optimizer does something clever with inlining... */
	pcb_line_t l;
	l.Point1.X = lx1;
	l.Point1.Y = ly1;
	l.Point2.X = lx2;
	l.Point2.Y = ly2;
	l.Thickness = 1;
	return pcb_is_point_on_line(px, py, 1, &l);
}

rnd_bool pcb_is_point_on_thinline( rnd_coord_t X, rnd_coord_t Y, rnd_coord_t X1, rnd_coord_t Y1, rnd_coord_t X2,rnd_coord_t Y2 )
{
	/* Calculate the cross product of the vector from the first line point to the test point
	 * and the vector from the first line point to the second line point. If the result is 
	 * not 0 then the point does not lie on the line.
	 */
	const rnd_coord_t lx = X2 - X1;
	const rnd_coord_t ly = Y2 - Y1;
	const rnd_coord_t cross = ((X-X1) * ly) - ((Y-Y1) * lx);

	if(cross != 0)
		return rnd_false;

	/* If the point does lie on the line then we do a simple check to see if the point is
	 * between the end points. Use the longest side of the line to perform the check.
	 */
	if(abs(lx) > abs(ly))
		return (X >= MIN(X1,X2)) && (X <= MAX(X1,X2)) ? rnd_true : rnd_false;

	return (Y >= MIN(Y1,Y2)) && (Y <= MAX(Y1,Y2)) ? rnd_true : rnd_false;
}

/* checks if a line crosses a rectangle or is within the rectangle */
rnd_bool pcb_is_line_in_rectangle(rnd_coord_t X1, rnd_coord_t Y1, rnd_coord_t X2, rnd_coord_t Y2, pcb_line_t *Line)
{
	pcb_line_t line;

	/* first, see if point 1 is inside the rectangle */
	/* in case the whole line is inside the rectangle */
	if (X1 < Line->Point1.X && X2 > Line->Point1.X && Y1 < Line->Point1.Y && Y2 > Line->Point1.Y)
		return rnd_true;
	/* construct a set of dummy lines and check each of them */
	line.Thickness = 0;
	line.Flags = pcb_no_flags();

	/* upper-left to upper-right corner */
	line.Point1.Y = line.Point2.Y = Y1;
	line.Point1.X = X1;
	line.Point2.X = X2;
	if (pcb_isc_line_line(pcb_find0, &line, Line))
		return rnd_true;

	/* upper-right to lower-right corner */
	line.Point1.X = X2;
	line.Point1.Y = Y1;
	line.Point2.Y = Y2;
	if (pcb_isc_line_line(pcb_find0, &line, Line))
		return rnd_true;

	/* lower-right to lower-left corner */
	line.Point1.Y = Y2;
	line.Point1.X = X1;
	line.Point2.X = X2;
	if (pcb_isc_line_line(pcb_find0, &line, Line))
		return rnd_true;

	/* lower-left to upper-left corner */
	line.Point2.X = X1;
	line.Point1.Y = Y1;
	line.Point2.Y = Y2;
	if (pcb_isc_line_line(pcb_find0, &line, Line))
		return rnd_true;

	return rnd_false;
}

/*checks if a point (of null radius) is in a slanted rectangle */
RND_INLINE int IsPointInQuadrangle(rnd_point_t p[4], rnd_point_t *l)
{
	rnd_coord_t dx, dy, x, y;
	double prod0, prod1;

	dx = p[1].X - p[0].X;
	dy = p[1].Y - p[0].Y;
	x = l->X - p[0].X;
	y = l->Y - p[0].Y;
	prod0 = (double) x *dx + (double) y *dy;
	x = l->X - p[1].X;
	y = l->Y - p[1].Y;
	prod1 = (double) x *dx + (double) y *dy;
	if (prod0 * prod1 <= 0) {
		dx = p[1].X - p[2].X;
		dy = p[1].Y - p[2].Y;
		prod0 = (double) x *dx + (double) y *dy;
		x = l->X - p[2].X;
		y = l->Y - p[2].Y;
		prod1 = (double) x *dx + (double) y *dy;
		if (prod0 * prod1 <= 0)
			return rnd_true;
	}
	return rnd_false;
}

int pcb_geo_pt_in_quadrangle(rnd_point_t p[4], rnd_point_t *l)
{
	return IsPointInQuadrangle(p, l);
}


/* ---------------------------------------------------------------------------
 * checks if a line crosses a quadrangle or is within the quadrangle: almost
 * copied from pcb_is_line_in_rectangle()
 * Note: actually this quadrangle is a slanted rectangle
 */
rnd_bool pcb_is_line_in_quadrangle(rnd_point_t p[4], pcb_line_t *Line)
{
	pcb_line_t line;

	/* first, see if point 1 is inside the rectangle */
	/* in case the whole line is inside the rectangle */
	if (IsPointInQuadrangle(p, &(Line->Point1)))
		return rnd_true;
	if (IsPointInQuadrangle(p, &(Line->Point2)))
		return rnd_true;
	/* construct a set of dummy lines and check each of them */
	line.Thickness = 0;
	line.Flags = pcb_no_flags();

	/* upper-left to upper-right corner */
	line.Point1.X = p[0].X;
	line.Point1.Y = p[0].Y;
	line.Point2.X = p[1].X;
	line.Point2.Y = p[1].Y;
	if (pcb_isc_line_line(pcb_find0, &line, Line))
		return rnd_true;

	/* upper-right to lower-right corner */
	line.Point1.X = p[2].X;
	line.Point1.Y = p[2].Y;
	if (pcb_isc_line_line(pcb_find0, &line, Line))
		return rnd_true;

	/* lower-right to lower-left corner */
	line.Point2.X = p[3].X;
	line.Point2.Y = p[3].Y;
	if (pcb_isc_line_line(pcb_find0, &line, Line))
		return rnd_true;

	/* lower-left to upper-left corner */
	line.Point1.X = p[0].X;
	line.Point1.Y = p[0].Y;
	if (pcb_isc_line_line(pcb_find0, &line, Line))
		return rnd_true;

	return rnd_false;
}

/* ---------------------------------------------------------------------------
 * checks if an arc crosses a rectangle (or arc is within the rectangle)
 */
rnd_bool pcb_is_arc_in_rectangle(rnd_coord_t X1, rnd_coord_t Y1, rnd_coord_t X2, rnd_coord_t Y2, pcb_arc_t *Arc)
{
	pcb_line_t line;
	rnd_coord_t x, y;
	rnd_box_t box;
	
	/* check if any of arc endpoints is inside the rectangle */
	box.X1 = X1; box.Y1 = Y1;
	box.X2 = X2; box.Y2 = Y2;
	pcb_arc_get_end (Arc, 0, &x, &y);
	if (PCB_POINT_IN_BOX(x, y, &box))
		return rnd_true;
	pcb_arc_get_end (Arc, 1, &x, &y);
	if (PCB_POINT_IN_BOX(x, y, &box))
		return rnd_true;

	/* construct a set of dummy lines and check each of them */
	line.Thickness = 0;
	line.Flags = pcb_no_flags();

	/* upper-left to upper-right corner */
	line.Point1.Y = line.Point2.Y = Y1;
	line.Point1.X = X1;
	line.Point2.X = X2;
	if (pcb_isc_line_arc(pcb_find0, &line, Arc))
		return rnd_true;

	/* upper-right to lower-right corner */
	line.Point1.X = line.Point2.X = X2;
	line.Point1.Y = Y1;
	line.Point2.Y = Y2;
	if (pcb_isc_line_arc(pcb_find0, &line, Arc))
		return rnd_true;

	/* lower-right to lower-left corner */
	line.Point1.Y = line.Point2.Y = Y2;
	line.Point1.X = X1;
	line.Point2.X = X2;
	if (pcb_isc_line_arc(pcb_find0, &line, Arc))
		return rnd_true;

	/* lower-left to upper-left corner */
	line.Point1.X = line.Point2.X = X1;
	line.Point1.Y = Y1;
	line.Point2.Y = Y2;
	if (pcb_isc_line_arc(pcb_find0, &line, Arc))
		return rnd_true;

	return rnd_false;
}

/* ---------------------------------------------------------------------------
 * checks if an gfx crosses a rectangle (or gfx is within the rectangle)
 */
rnd_bool pcb_is_gfx_in_rectangle(const rnd_box_t *b, const pcb_gfx_t *gfx)
{
	pcb_line_t l;
	int n, m;

	l.Thickness = 1;
	for(n = 0; n < 4; n++) {
		m = n+1;
		if (m == 4)
			m = 0;
		l.Point1.X = gfx->corner[n].X; l.Point1.Y = gfx->corner[n].Y;
		l.Point2.X = gfx->corner[m].X; l.Point2.Y = gfx->corner[m].Y;
		if (PCB_LINE_TOUCHES_BOX(&l, b))
			return rnd_true;
	}

	return rnd_false;
}

/* ---------------------------------------------------------------------------
 * Check if a circle of Radius with center at (X, Y) intersects a line.
 * Written to enable arbitrary line directions; for rounded/square lines, too.
 */
rnd_bool pcb_is_point_in_line(rnd_coord_t X, rnd_coord_t Y, rnd_coord_t Radius, pcb_any_line_t *Pad)
{
	double r, Sin, Cos;
	rnd_coord_t x;

	/* Also used from line_callback with line type smaller than pad type;
	   use the smallest common subset; ->Thickness is still ok. */
	rnd_coord_t t2 = (Pad->Thickness + 1) / 2, range;
	pcb_any_line_t pad = *Pad;


	/* series of transforms saving range */
	/* move Point1 to the origin */
	X -= pad.Point1.X;
	Y -= pad.Point1.Y;

	pad.Point2.X -= pad.Point1.X;
	pad.Point2.Y -= pad.Point1.Y;
	/* so, pad.Point1.X = pad.Point1.Y = 0; */

	/* rotate round (0, 0) so that Point2 coordinates be (r, 0) */
	r = rnd_distance(0, 0, pad.Point2.X, pad.Point2.Y);
	if (r < .1) {
		Cos = 1;
		Sin = 0;
	}
	else {
		Sin = pad.Point2.Y / r;
		Cos = pad.Point2.X / r;
	}
	x = X;
	X = X * Cos + Y * Sin;
	Y = Y * Cos - x * Sin;
	/* now pad.Point2.X = r; pad.Point2.Y = 0; */

	/* take into account the ends */
	if (PCB_FLAG_TEST(PCB_FLAG_SQUARE, Pad)) {
		r += Pad->Thickness;
		X += t2;
	}
	if (Y < 0)
		Y = -Y;											/* range value is evident now */

	if (PCB_FLAG_TEST(PCB_FLAG_SQUARE, Pad)) {
		if (X <= 0) {
			if (Y <= t2)
				range = -X;
			else
				return Radius > rnd_distance(0, t2, X, Y);
		}
		else if (X >= r) {
			if (Y <= t2)
				range = X - r;
			else
				return Radius > rnd_distance(r, t2, X, Y);
		}
		else
			range = Y - t2;
	}
	else {												/*Rounded pad: even more simple */

		if (X <= 0)
			return (Radius + t2) > rnd_distance(0, 0, X, Y);
		else if (X >= r)
			return (Radius + t2) > rnd_distance(r, 0, X, Y);
		else
			range = Y - t2;
	}
	return range < Radius;
}

rnd_bool pcb_is_point_in_box(rnd_coord_t X, rnd_coord_t Y, rnd_box_t *box, rnd_coord_t Radius)
{
	rnd_coord_t width, height, range;

	/* NB: Assumes box has point1 with numerically lower X and Y coordinates */

	/* Compute coordinates relative to Point1 */
	X -= box->X1;
	Y -= box->Y1;

	width = box->X2 - box->X1;
	height = box->Y2 - box->Y1;

	if (X <= 0) {
		if (Y < 0)
			return Radius > rnd_distance(0, 0, X, Y);
		else if (Y > height)
			return Radius > rnd_distance(0, height, X, Y);
		else
			range = -X;
	}
	else if (X >= width) {
		if (Y < 0)
			return Radius > rnd_distance(width, 0, X, Y);
		else if (Y > height)
			return Radius > rnd_distance(width, height, X, Y);
		else
			range = X - width;
	}
	else {
		if (Y < 0)
			range = -Y;
		else if (Y > height)
			range = Y - height;
		else
			return rnd_true;
	}

	return range < Radius;
}

rnd_bool pcb_arc_in_box(pcb_arc_t *arc, rnd_box_t *b)
{
	rnd_box_t ab = pcb_arc_mini_bbox(arc);
	return PCB_BOX_IN_BOX(&ab, b);
}

rnd_bool pcb_gfx_in_box(pcb_gfx_t *gfx, rnd_box_t *b)
{
	int n;

	for(n = 0; n < 4; n++)
		if (!PCB_POINT_IN_BOX(gfx->corner[n].X, gfx->corner[n].Y, b))
			return rnd_false;

	return rnd_true;
}

/* TODO: this code is BROKEN in the case of non-circular arcs,
 *       and in the case that the arc thickness is greater than
 *       the radius.
 */
rnd_bool pcb_is_point_on_arc(rnd_coord_t X, rnd_coord_t Y, rnd_coord_t Radius, pcb_arc_t *Arc)
{
	/* Calculate angle of point from arc center */
	double p_dist = rnd_distance(X, Y, Arc->X, Arc->Y);
	double p_cos = (X - Arc->X) / p_dist;
	rnd_angle_t p_ang = acos(p_cos) * RND_RAD_TO_DEG;
	rnd_angle_t ang1, ang2;
#define angle_in_range(r1, r2, ang) (((ang) >= (r1)) && ((ang) <= (r2)))


	/* Convert StartAngle, Delta into bounding angles in [0, 720) */
	if (Arc->Delta > 0) {
		ang1 = rnd_normalize_angle(Arc->StartAngle);
		ang2 = rnd_normalize_angle(Arc->StartAngle + Arc->Delta);
	}
	else {
		ang1 = rnd_normalize_angle(Arc->StartAngle + Arc->Delta);
		ang2 = rnd_normalize_angle(Arc->StartAngle);
	}
	if (ang1 > ang2)
		ang2 += 360;
	/* Make sure full circles aren't treated as zero-length arcs */
	if (Arc->Delta == 360 || Arc->Delta == -360)
		ang2 = ang1 + 360;

	if (Y > Arc->Y)
		p_ang = -p_ang;
	p_ang += 180;


	/* Check point is outside arc range, check distance from endpoints
	   Need to check p_ang+360 too, because after the angle swaps above ang2
	   might be larger than 360 and that section of the arc shouldn't be missed
	   either. */
	if (!angle_in_range(ang1, ang2, p_ang) && !angle_in_range(ang1, ang2, p_ang+360)) {
		rnd_coord_t ArcX, ArcY;
		ArcX = Arc->X + Arc->Width * cos((Arc->StartAngle + 180) / RND_RAD_TO_DEG);
		ArcY = Arc->Y - Arc->Height * sin((Arc->StartAngle + 180) / RND_RAD_TO_DEG);
		if (rnd_distance(X, Y, ArcX, ArcY) < Radius + Arc->Thickness / 2)
			return rnd_true;

		ArcX = Arc->X + Arc->Width * cos((Arc->StartAngle + Arc->Delta + 180) / RND_RAD_TO_DEG);
		ArcY = Arc->Y - Arc->Height * sin((Arc->StartAngle + Arc->Delta + 180) / RND_RAD_TO_DEG);
		if (rnd_distance(X, Y, ArcX, ArcY) < Radius + Arc->Thickness / 2)
			return rnd_true;
		return rnd_false;
	}

	if ((Arc->Width == Arc->Height) || (RND_ABS(Arc->Width - Arc->Height) < RND_MM_TO_COORD(0.1))) {
		/* Simple circular case: if point is inside the arc range, just compare it to the arc */
		return fabs(rnd_distance(X, Y, Arc->X, Arc->Y) - Arc->Width) < Radius + Arc->Thickness / 2;
	}
	else {
		/* elliptical case: guess where the arc would be in that point, by angle */
		double ang, dx, dy;
		rnd_coord_t ax, ay, d;

TODO(": elliptical arc: rewrite this, as it does not work properly on extreme cases")
		dy = (double)(Y - Arc->Y) / (double)Arc->Height;
		dx = (double)(X - Arc->X) / (double)Arc->Width;
		ang = -atan2(dy, dx);

		ax = Arc->X + cos(ang) * Arc->Width;
		ay = Arc->Y - sin(ang) * Arc->Height;

		d = fabs(rnd_distance(X, Y, ax, ay));

		return d < Arc->Thickness / 2;
	}
#undef angle_in_range
}

rnd_bool pcb_is_point_in_gfx(rnd_coord_t X, rnd_coord_t Y, rnd_coord_t Radius, pcb_gfx_t *gfx)
{
	rnd_vector_t pt, a, b, c;
	int n, m;

	pt[0] = X; pt[1] = Y;
	a[0] = gfx->corner[0].X; a[1] = gfx->corner[0].Y;
	b[0] = gfx->corner[2].X; b[1] = gfx->corner[2].Y;

	c[0] = gfx->corner[1].X; c[1] = gfx->corner[1].Y;
	if (rnd_point_in_triangle(a, b, c, pt))
		return rnd_true;

	c[0] = gfx->corner[3].X; c[1] = gfx->corner[3].Y;
	if (rnd_point_in_triangle(a, b, c, pt))
		return rnd_true;

	/* the above triangle checks will miss points that are exactly on the triangle edges */
	for(n = 0; n < 4; n++) {
		m = n+1;
		if (m == 4)
			m = 0;
		if (pcb_is_point_on_thinline(X, Y, gfx->corner[n].X, gfx->corner[n].Y, gfx->corner[m].X, gfx->corner[m].Y))
			return rnd_true;
	}
	if (pcb_is_point_on_thinline(X, Y, gfx->corner[0].X, gfx->corner[0].Y, gfx->corner[2].X, gfx->corner[2].Y))
		return rnd_true;
	if (pcb_is_point_on_thinline(X, Y, gfx->corner[1].X, gfx->corner[1].Y, gfx->corner[3].X, gfx->corner[3].Y))
		return rnd_true;

	return rnd_false;
}


rnd_bool pcb_is_point_on_obj(rnd_coord_t X, rnd_coord_t Y, rnd_coord_t Radius, pcb_any_obj_t *obj)
{
	switch(obj->type) {
		case PCB_OBJ_ARC:  return pcb_is_point_on_arc(X, Y, Radius, (pcb_arc_t *)obj);
		case PCB_OBJ_LINE: return pcb_is_point_on_line(X, Y, Radius, (pcb_line_t *)obj);
		case PCB_OBJ_POLY: return pcb_poly_is_point_in_p(X, Y, Radius, (pcb_poly_t *)obj);
		case PCB_OBJ_GFX:  return pcb_is_point_in_gfx(X, Y, Radius, (pcb_gfx_t *)obj);
		case PCB_OBJ_RAT:
			{
				pcb_rat_t *rat = (pcb_rat_t *)obj;
				return ((rat->Point1.X == X) && (rat->Point1.Y == Y)) || ((rat->Point2.X == X) && (rat->Point2.Y == Y));
			}
		case PCB_OBJ_PSTK:
			{
				pcb_line_t l = {0};
				static const pcb_find_t ctx = {0};
				l.Point1.X = l.Point2.X = X;
				l.Point1.Y = l.Point2.Y = Y;
				l.Thickness = Radius;
				return pcb_isc_pstk_line(&ctx, (pcb_pstk_t *)obj, &l, 1);
			}
		case PCB_OBJ_TEXT:
		case PCB_OBJ_SUBC: case PCB_OBJ_VOID: case PCB_OBJ_NET: case PCB_OBJ_NET_TERM: case PCB_OBJ_LAYER: case PCB_OBJ_LAYERGRP: return 0;
	}

	return 0;
}


pcb_line_t *pcb_line_center_cross_point(pcb_layer_t *layer, rnd_coord_t x, rnd_coord_t y, rnd_angle_t *ang, rnd_bool no_subc_part, rnd_bool no_term)
{
	rnd_rtree_it_t it;
	rnd_rtree_box_t pt;
	pcb_line_t *l;

	pt.x1 = x; pt.y1 = y;
	pt.x2 = x+1; pt.y2 = y+1;

	for(l = rnd_rtree_first(&it, layer->line_tree, &pt); l != NULL; l = rnd_rtree_next(&it)) {
		/* check if line needs to be ignored and "continue;" if so:
		   l->term is non-NULL if it's a terminal,
		   pcb_lobj_parent_subc(pcb_parenttype_t pt, pcb_parent_t *p) returns non-NULL if it's in a subc
		 */
		/* if the line shouldn't be ignored, check if x;y is on the centerline and
		   "continue;" if bad */
		/* check if angle is required and is correct; "continue;" on mismatch */
		return l;
	}
	return NULL; /* found nothing */
}

pcb_layer_type_t pstk_vis_layers(pcb_board_t *pcb, pcb_layer_type_t material)
{
	pcb_layer_type_t res = 0, lyt;
	rnd_layer_id_t lid;
	for(lid = 0; lid < pcb->Data->LayerN; lid++) {
		pcb_layer_t *ly = &pcb->Data->Layer[lid];
		lyt = pcb_layer_flags_(ly);
		if ((ly->meta.real.vis) && (lyt & material))
			res |= (lyt & PCB_LYT_ANYWHERE);
	}
	return res | material;
}

/* Ignores render-below gfx objects */
static int pcb_search_obj_by_loc_layer(unsigned long Type, void **Result1, void **Result2, void **Result3, unsigned long req_flag, pcb_layer_t *SearchLayer, int HigherAvail, double HigherBound, int objst)
{
	if (SearchLayer->meta.real.vis) {
		/* GFX searched first because if it is rendered above, it is visible over
		   the others */
		if ((HigherAvail & (PCB_OBJ_PSTK)) == 0 &&
				Type & PCB_OBJ_GFX_POINT &&
				SearchGfxPointByLocation(Type, objst, req_flag, (pcb_layer_t **) Result1, (pcb_gfx_t **) Result2, (rnd_point_t **) Result3))
			return PCB_OBJ_GFX_POINT;

		if ((HigherAvail & (PCB_OBJ_PSTK)) == 0 && Type & PCB_OBJ_GFX &&
				SearchGfxByLocation(objst, req_flag, (pcb_layer_t **)Result1, (pcb_gfx_t **)Result2, (pcb_gfx_t **)Result3, +1))
			return PCB_OBJ_GFX;

		if ((HigherAvail & (PCB_OBJ_PSTK)) == 0 &&
				Type & PCB_OBJ_POLY_POINT &&
				SearchPointByLocation(Type, objst, req_flag, (pcb_layer_t **) Result1, (pcb_poly_t **) Result2, (rnd_point_t **) Result3))
			return PCB_OBJ_POLY_POINT;

		if ((HigherAvail & (PCB_OBJ_PSTK)) == 0 &&
				Type & PCB_OBJ_LINE_POINT &&
				SearchLinePointByLocation(objst, req_flag, (pcb_layer_t **) Result1, (pcb_line_t **) Result2, (rnd_point_t **) Result3))
			return PCB_OBJ_LINE_POINT;

		if ((HigherAvail & (PCB_OBJ_PSTK)) == 0 &&
				Type & PCB_OBJ_ARC_POINT &&
				SearchArcPointByLocation(objst, req_flag, (pcb_layer_t **) Result1, (pcb_arc_t **) Result2, (int **) Result3))
			return PCB_OBJ_ARC_POINT;

		if ((HigherAvail & (PCB_OBJ_PSTK)) == 0 && Type & PCB_OBJ_LINE
				&& SearchLineByLocation(objst, req_flag, (pcb_layer_t **) Result1, (pcb_line_t **) Result2, (pcb_line_t **) Result3))
			return PCB_OBJ_LINE;

		if ((HigherAvail & (PCB_OBJ_PSTK)) == 0 && Type & PCB_OBJ_ARC &&
				SearchArcByLocation(objst, req_flag, (pcb_layer_t **) Result1, (pcb_arc_t **) Result2, (pcb_arc_t **) Result3))
			return PCB_OBJ_ARC;

		if ((HigherAvail & (PCB_OBJ_PSTK)) == 0 && Type & PCB_OBJ_TEXT
				&& SearchTextByLocation(objst, req_flag, (pcb_layer_t **) Result1, (pcb_text_t **) Result2, (pcb_text_t **) Result3))
			return PCB_OBJ_TEXT;

		if (Type & PCB_OBJ_POLY &&
				SearchPolygonByLocation(objst, req_flag, (pcb_layer_t **) Result1, (pcb_poly_t **) Result2, (pcb_poly_t **) Result3)) {
			if (HigherAvail) {
				rnd_box_t *box = &(*(pcb_poly_t **) Result2)->BoundingBox;
				double area = (double) (box->X2 - box->X1) * (double) (box->X2 - box->X1);
				if (HigherBound < area)
					return -1;
				else
					return PCB_OBJ_POLY;
			}
			else
				return PCB_OBJ_POLY;
		}


	}
	return 0;
}

/* Searches only for render_below gfx objects on the given layer */
static int pcb_search_by_loc_below_gfx(unsigned long Type, void **Result1, void **Result2, void **Result3, unsigned long req_flag, pcb_layer_t *SearchLayer, int HigherAvail, double HigherBound, int objst)
{
	if (SearchLayer->meta.real.vis) {
		if ((HigherAvail & (PCB_OBJ_PSTK)) == 0 && Type & PCB_OBJ_GFX &&
				SearchGfxByLocation(objst, req_flag, (pcb_layer_t **)Result1, (pcb_gfx_t **)Result2, (pcb_gfx_t **)Result3, -1))
			return PCB_OBJ_GFX;
	}
	return 0;
}

static int pcb_search_obj_by_loc_group(unsigned long Type, void **Result1, void **Result2, void **Result3, unsigned long req_flag, const pcb_layergrp_t *grp, int HigherAvail, double HigherBound, int objst)
{
	rnd_cardinal_t n;
	for(n = 0; n < grp->len; n++) {
		int found;
		pcb_layer_t *oldly, *layer = pcb_get_layer(PCB->Data, grp->lid[n]);
		if (layer == NULL)
			continue;
		oldly = SearchLayer;
		SearchLayer = layer;
		found = pcb_search_obj_by_loc_layer(Type, Result1, Result2, Result3, req_flag, layer, HigherAvail, HigherBound, objst);
		SearchLayer = oldly;
		if (found != 0)
			return found;
	}
	return 0;
}

static int pcb_search_obj_by_loc_group_id(unsigned long Type, void **Result1, void **Result2, void **Result3, unsigned long req_flag, const rnd_layergrp_id_t gid, int HigherAvail, double HigherBound, int objst)
{
	pcb_layergrp_t *grp = pcb_get_layergrp(PCB, gid);
	if (grp == NULL)
		return 0;
	return pcb_search_obj_by_loc_group(Type, Result1, Result2, Result3, req_flag, grp, HigherAvail, HigherBound, objst);
}


/* ---------------------------------------------------------------------------
 * searches for any kind of object or for a set of object types
 * the calling routine passes two pointers to allocated memory for storing
 * the results.
 * A type value is returned too which is PCB_OBJ_VOID if no objects has been found.
 * A set of object types is passed in.
 * The object is located by it's position.
 *
 * The layout is checked in the following order:
 *   polygon-point, padstack, line, text, polygon, subcircuit
 *
 * Note that if Type includes PCB_OBJ_LOCKED, then the search includes
 * locked items.  Otherwise, locked items are ignored.

 * Note that if Type includes PCB_OBJ_SUBC_PART, then the search includes
 * objects that are part of subcircuits, else they are ignored.
 */
static int pcb_search_obj_by_location_(unsigned long Type, void **Result1, void **Result2, void **Result3, rnd_coord_t X, rnd_coord_t Y, rnd_coord_t Radius, unsigned long req_flag)
{
	void *r1, *r2, *r3;
	void **pr1 = &r1, **pr2 = &r2, **pr3 = &r3;
	int i;
	double HigherBound = 0;
	int HigherAvail = PCB_OBJ_VOID;
	int objst = Type & (PCB_OBJ_LOCKED | PCB_OBJ_SUBC_PART);

	/* setup variables used by local functions */
	PosX = X;
	PosY = Y;
	SearchRadius = Radius;
	if (Radius) {
		SearchBox.X1 = X - Radius;
		SearchBox.Y1 = Y - Radius;
		SearchBox.X2 = X + Radius;
		SearchBox.Y2 = Y + Radius;
	}
	else {
		SearchBox = rnd_point_box(X, Y);
	}

	if (conf_core.editor.only_names) {
		Type &= PCB_OBJ_TEXT;
	}

	if (conf_core.editor.thin_draw || conf_core.editor.thin_draw_poly) {
		Type &= ~PCB_OBJ_POLY;
	}

	if (Type & PCB_OBJ_RAT && PCB->RatOn &&
			SearchRatLineByLocation(objst, req_flag, (pcb_rat_t **) Result1, (pcb_rat_t **) Result2, (pcb_rat_t **) Result3))
		return PCB_OBJ_RAT;

	if (Type & PCB_OBJ_PSTK && SearchPadstackByLocation(objst, req_flag, (pcb_pstk_t **) Result1, (pcb_pstk_t **) Result2, (pcb_pstk_t **) Result3, 1, 0))
		return PCB_OBJ_PSTK;

	if (Type & PCB_OBJ_PSTK && SearchPadstackByLocation(objst, req_flag, (pcb_pstk_t **) Result1, (pcb_pstk_t **) Result2, (pcb_pstk_t **) Result3, 0, PCB_LYT_VISIBLE_SIDE() | PCB_LYT_COPPER))
		return PCB_OBJ_PSTK;

	if (Type & PCB_OBJ_PSTK && SearchPadstackByLocation(objst, req_flag, (pcb_pstk_t **) Result1, (pcb_pstk_t **) Result2, (pcb_pstk_t **) Result3, 0, (PCB_LYT_VISIBLE_SIDE() | PCB_LYT_ANYTHING) & pstk_vis_layers(PCB, PCB_LYT_ANYTHING)))
		return PCB_OBJ_PSTK;

	if (Type & PCB_OBJ_PSTK && SearchPadstackByLocation(objst, req_flag, (pcb_pstk_t **) Result1, (pcb_pstk_t **) Result2, (pcb_pstk_t **) Result3, 0, pstk_vis_layers(PCB, PCB_LYT_MASK)))
		return PCB_OBJ_PSTK;

	if (Type & PCB_OBJ_PSTK && SearchPadstackByLocation(objst, req_flag, (pcb_pstk_t **) Result1, (pcb_pstk_t **) Result2, (pcb_pstk_t **) Result3, 0, pstk_vis_layers(PCB, PCB_LYT_COPPER)))
		return PCB_OBJ_PSTK;

	if (Type & PCB_OBJ_PSTK && SearchPadstackByLocation(objst, req_flag, (pcb_pstk_t **) Result1, (pcb_pstk_t **) Result2, (pcb_pstk_t **) Result3, 0, pstk_vis_layers(PCB, PCB_LYT_PASTE)))
		return PCB_OBJ_PSTK;

	if (!HigherAvail && (Type & PCB_OBJ_FLOATER) && (Type & PCB_OBJ_TEXT) &&
			SearchSubcFloaterByLocation(objst, req_flag, (pcb_subc_t **)pr1, (pcb_text_t **) pr2, pr3, rnd_false)) {
		*Result1 = ((pcb_text_t *)r2)->parent.layer;
		*Result2 = r2;
		*Result3 = r3;
		return PCB_OBJ_TEXT;
	}

	if (!HigherAvail && Type & PCB_OBJ_SUBC && PCB->SubcOn &&
			SearchSubcByLocation(objst, req_flag, (pcb_subc_t **) pr1, (pcb_subc_t **) pr2, (pcb_subc_t **) pr3, rnd_false)) {
		rnd_box_t *box = &((pcb_subc_t *) r1)->BoundingBox;
		HigherBound = (double) (box->X2 - box->X1) * (double) (box->Y2 - box->Y1);
		HigherAvail = PCB_OBJ_SUBC;
	}

	{ /* search the front silk layer first */
		int found;
		rnd_layergrp_id_t front_silk_id = conf_core.editor.show_solder_side ? pcb_layergrp_get_bottom_silk() : pcb_layergrp_get_top_silk();
		found = pcb_search_obj_by_loc_group_id(Type, Result1, Result2, Result3, req_flag, front_silk_id, HigherAvail, HigherBound, objst);
		if (found > 0)
			return found;
	}

	for(i = 0; i < pcb_max_layer(PCB); i++) {
		int found;
		if (pcb_layer_flags(PCB, i) & PCB_LYT_SILK) /* special order: silks are searched before/after this loop, if we meet them elsewhere, skip */
			continue;
		SearchLayer = PCB_STACKLAYER(PCB, i);

		found = pcb_search_obj_by_loc_layer(Type, Result1, Result2, Result3, req_flag, SearchLayer, HigherAvail, HigherBound, objst);
		if (found < 0)
			break;
		if (found != 0)
			return found;
	}

	if (PCB->InvisibleObjectsOn) { /* search the back silk layer last, unless it's invisible */
		int found;
		rnd_layergrp_id_t back_silk_id = conf_core.editor.show_solder_side ? pcb_layergrp_get_top_silk() : pcb_layergrp_get_bottom_silk();
		found = pcb_search_obj_by_loc_group_id(Type, Result1, Result2, Result3, req_flag, back_silk_id, HigherAvail, HigherBound, objst);
		if (found > 0)
			return found;
	}


	/* return any previously found objects */
	if (HigherAvail & PCB_OBJ_PSTK) {
		*Result1 = r1;
		*Result2 = r2;
		*Result3 = r3;
		return PCB_OBJ_PSTK;
	}

	if (HigherAvail & PCB_OBJ_SUBC) {
		*Result1 = r1;
		*Result2 = r2;
		*Result3 = r3;
		return PCB_OBJ_SUBC;
	}

	/* search the 'invisible objects' last */
	if (!PCB->InvisibleObjectsOn)
		return PCB_OBJ_VOID;

	if ((Type & PCB_OBJ_FLOATER) && (Type & PCB_OBJ_TEXT) &&
			SearchSubcFloaterByLocation(objst, req_flag, (pcb_subc_t **)pr1, (pcb_text_t **) pr2, pr3, rnd_true)) {
		*Result1 = ((pcb_text_t *)r2)->parent.layer;
		*Result2 = r2;
		*Result3 = r3;
		return PCB_OBJ_TEXT;
	}

	if (Type & PCB_OBJ_SUBC && PCB->SubcOn &&
			SearchSubcByLocation(objst, req_flag, (pcb_subc_t **) Result1, (pcb_subc_t **) Result2, (pcb_subc_t **) Result3, rnd_true))
		return PCB_OBJ_SUBC;

	/* render-below gfx objects last, as they are surely under anything else */
	for(i = 0; i < pcb_max_layer(PCB); i++) {
		int found;
		SearchLayer = PCB_STACKLAYER(PCB, i);
		found = pcb_search_by_loc_below_gfx(Type, Result1, Result2, Result3, req_flag, SearchLayer, HigherAvail, HigherBound, objst);
		if (found < 0)
			break;
		if (found != 0)
			return found;
	}

	return PCB_OBJ_VOID;
}

int pcb_search_obj_by_location(unsigned long Type, void **Result1, void **Result2, void **Result3, rnd_coord_t X, rnd_coord_t Y, rnd_coord_t Radius)
{
	int res;

	if ((conf_core.editor.lock_names) || (conf_core.editor.hide_names))
		Type &= ~ PCB_OBJ_FLOATER;

	res = pcb_search_obj_by_location_(Type, Result1, Result2, Result3, X, Y, Radius, 0);
	if ((res != PCB_OBJ_VOID) && (res != PCB_OBJ_SUBC))
		return res;

	/* found a subc because it was still the best option over all the plain
	   objects; if floaters can be found, repeat the search on them, they
	   have higher prio than subc, but lower prio than plain objects */
	if ((!conf_core.editor.lock_names) && (!conf_core.editor.hide_names)) {
		int fres;
		void *fr1, *fr2, *fr3;
		Type &= ~PCB_OBJ_SUBC;
		Type |= PCB_OBJ_SUBC_PART;
		fres = pcb_search_obj_by_location_(Type, &fr1, &fr2, &fr3, X, Y, Radius, PCB_FLAG_FLOATER);
		if (fres != PCB_OBJ_VOID) {
			*Result1 = fr1;
			*Result2 = fr2;
			*Result3 = fr3;
			return fres;
		}
	}
	
	return res;
}


/* ---------------------------------------------------------------------------
 * searches for a object by it's unique ID. It doesn't matter if
 * the object is visible or not. The search is performed on a PCB, a
 * buffer or on the remove list.
 * The calling routine passes two pointers to allocated memory for storing
 * the results.
 * A type value is returned too which is PCB_OBJ_VOID if no objects has been found.
 */
int pcb_search_obj_by_id_(pcb_data_t *Base, void **Result1, void **Result2, void **Result3, int ID, int type)
{
	if (type == PCB_OBJ_LINE || type == PCB_OBJ_LINE_POINT) {
		PCB_LINE_ALL_LOOP(Base);
		{
			if (line->ID == ID) {
				*Result1 = (void *) layer;
				*Result2 = *Result3 = (void *) line;
				return PCB_OBJ_LINE;
			}
			if (line->Point1.ID == ID) {
				*Result1 = (void *) layer;
				*Result2 = (void *) line;
				*Result3 = (void *) &line->Point1;
				return PCB_OBJ_LINE_POINT;
			}
			if (line->Point2.ID == ID) {
				*Result1 = (void *) layer;
				*Result2 = (void *) line;
				*Result3 = (void *) &line->Point2;
				return PCB_OBJ_LINE_POINT;
			}
		}
		PCB_ENDALL_LOOP;
	}
	if (type == PCB_OBJ_ARC || type == PCB_OBJ_ARC_POINT) {
		PCB_ARC_ALL_LOOP(Base);
		{
			if (arc->ID == ID) {
				*Result1 = (void *) layer;
				*Result2 = *Result3 = (void *) arc;
				return PCB_OBJ_ARC;
			}
		}
		PCB_ENDALL_LOOP;
	}

	if (type == PCB_OBJ_TEXT) {
		PCB_TEXT_ALL_LOOP(Base);
		{
			if (text->ID == ID) {
				*Result1 = (void *) layer;
				*Result2 = *Result3 = (void *) text;
				return PCB_OBJ_TEXT;
			}
		}
		PCB_ENDALL_LOOP;
	}

	if (type == PCB_OBJ_POLY || type == PCB_OBJ_POLY_POINT) {
		PCB_POLY_ALL_LOOP(Base);
		{
			if (polygon->ID == ID) {
				*Result1 = (void *) layer;
				*Result2 = *Result3 = (void *) polygon;
				return PCB_OBJ_POLY;
			}
			if (type == PCB_OBJ_POLY_POINT)
				PCB_POLY_POINT_LOOP(polygon);
			{
				if (point->ID == ID) {
					*Result1 = (void *) layer;
					*Result2 = (void *) polygon;
					*Result3 = (void *) point;
					return PCB_OBJ_POLY_POINT;
				}
			}
			PCB_END_LOOP;
		}
		PCB_ENDALL_LOOP;
	}

	if ((type == PCB_OBJ_GFX) || (type == PCB_OBJ_GFX_POINT)) {
		PCB_GFX_ALL_LOOP(Base);
		{
			if (gfx->ID == ID) {
				*Result1 = (void *)layer;
				*Result2 = *Result3 = (void *)gfx;
				return PCB_OBJ_GFX;
			}
			if (type == PCB_OBJ_GFX_POINT) {
				TODO("gfx: id on point?");
			}
		}
		PCB_ENDALL_LOOP;
	}

	if (type == PCB_OBJ_PSTK) {
		PCB_PADSTACK_LOOP(Base);
		{
			if (padstack->ID == ID) {
				*Result1 = *Result2 = *Result3 = (void *)padstack;
				return PCB_OBJ_PSTK;
			}
		}
		PCB_END_LOOP;
	}

	if (type == PCB_OBJ_RAT || type == PCB_OBJ_LINE_POINT) {
		PCB_RAT_LOOP(Base);
		{
			if (line->ID == ID) {
				*Result1 = *Result2 = *Result3 = (void *) line;
				return PCB_OBJ_RAT;
			}
			if (line->Point1.ID == ID) {
				*Result1 = (void *) NULL;
				*Result2 = (void *) line;
				*Result3 = (void *) &line->Point1;
				return PCB_OBJ_LINE_POINT;
			}
			if (line->Point2.ID == ID) {
				*Result1 = (void *) NULL;
				*Result2 = (void *) line;
				*Result3 = (void *) &line->Point2;
				return PCB_OBJ_LINE_POINT;
			}
		}
		PCB_END_LOOP;
	}

TODO("subc: once elements are gone, rewrite these to search the rtree instead of recursion")
	PCB_SUBC_LOOP(Base);
	{
		int res;
		if (type == PCB_OBJ_SUBC) {
			if (subc->ID == ID) {
				*Result1 = *Result2 = *Result3 = (void *)subc;
				return PCB_OBJ_SUBC;
			}
		}

		res = pcb_search_obj_by_id_(subc->data, Result1, Result2, Result3, ID, type);
		if (res != PCB_OBJ_VOID)
			return res;
	}
	PCB_END_LOOP;

	return PCB_OBJ_VOID;
}

int pcb_search_obj_by_id(pcb_data_t *Base, void **Result1, void **Result2, void **Result3, int ID, int type)
{
	int res = pcb_search_obj_by_id_(Base, Result1, Result2, Result3, ID, type);
	if (res == PCB_OBJ_VOID)
		rnd_message(RND_MSG_ERROR, "pcb_search_obj_by_id(): internal error, search for ID %d failed\n", ID);
	return res;
}

int pcb_search_obj_by_id_buf2(pcb_data_t *Base, void **Result1, void **Result2, void **Result3, int ID, int type)
{
	int bid = 0;
	long res;

	res = pcb_search_obj_by_id_(Base, Result1, Result2, Result3, ID, type);
	if (res != PCB_OBJ_VOID)
		return res;

	while(res == PCB_OBJ_VOID) {
		res = pcb_search_obj_by_id_(pcb_buffers[bid].Data, Result1, Result2, Result3, ID, type);
		if (res != PCB_OBJ_VOID)
			return res;
		bid++;
		if (bid >= PCB_MAX_BUFFER)
			break;
	}

	rnd_message(RND_MSG_ERROR, "pcb_search_obj_by_id_buf2(): internal error, search for ID %d failed\n", ID);
	return PCB_OBJ_VOID;
}

/* ---------------------------------------------------------------------------
 * searches the cursor position for the type
 */
int pcb_search_screen(rnd_coord_t X, rnd_coord_t Y, int Type, void **Result1, void **Result2, void **Result3)
{
	return pcb_search_obj_by_location(Type, Result1, Result2, Result3, X, Y, PCB_SLOP * rnd_pixel_slop);
}

/* ---------------------------------------------------------------------------
 * searches the cursor position for the type
 */
int pcb_search_grid_slop(rnd_coord_t X, rnd_coord_t Y, int Type, void **Result1, void **Result2, void **Result3)
{
	int ans, n;

	for(n = 8; n >= 2; n = n/2) {
		ans = pcb_search_obj_by_location(Type, Result1, Result2, Result3, X, Y, PCB->hidlib.grid / n);
		if (ans != PCB_OBJ_VOID) return ans;
	}

	return ans;
}

vtp0_t pcb_obj_list_vect;
pcb_board_t *pcb_obj_list_board;
int pcb_search_screen_selector(rnd_coord_t X, rnd_coord_t Y, int Type, void **Result1, void **Result2, void **Result3)
{
	rnd_box_t box;
	rnd_coord_t radius;
	pcb_any_obj_t *obj;

	if ((!conf_core.editor.click_objlist) || (!RND_HAVE_GUI_ATTR_DLG))
		return PCB_OBJ_VOID;

	radius = PCB_SLOP * rnd_pixel_slop;
	box.X2 = X - radius;
	box.Y2 = Y - radius;
	box.X1 = X + radius;
	box.Y1 = Y + radius;

	vtp0_init(&pcb_obj_list_vect);
	pcb_obj_list_board = PCB;
	pcb_list_block_cb(PCB, &box, (void *(*)(void *, pcb_any_obj_t *))vtp0_append, &pcb_obj_list_vect);

	if (pcb_obj_list_vect.used == 1) {
		obj = pcb_obj_list_vect.array[0];
		goto found;
	}

	if (pcb_obj_list_vect.used > 1) {
		long idx = rnd_actionva(&PCB->hidlib, "dlg_obj_list", NULL);
		if ((idx >= 0) && (idx < pcb_obj_list_vect.used)) {
			obj = pcb_obj_list_vect.array[idx];
			goto found;
		}
	}

	vtp0_uninit(&pcb_obj_list_vect);
	pcb_obj_list_board = NULL;
	return PCB_OBJ_VOID;

	found:;
	vtp0_uninit(&pcb_obj_list_vect);
	pcb_obj_list_board = NULL;
	*Result1 = obj->parent.any;
	*Result2 = *Result3 = obj;
	return obj->type;
}

int pcb_search_screen_maybe_selector(rnd_coord_t X, rnd_coord_t Y, int Type, void **Result1, void **Result2, void **Result3)
{
	int sr = pcb_search_screen_selector(X, Y, Type, Result1, Result2, Result3);
	if (sr != PCB_OBJ_VOID) return sr;
	return pcb_search_screen(X, Y, Type, Result1, Result2, Result3);
}


int pcb_lines_intersect_at(rnd_coord_t ax1, rnd_coord_t ay1, rnd_coord_t ax2, rnd_coord_t ay2, rnd_coord_t bx1, rnd_coord_t by1, rnd_coord_t bx2, rnd_coord_t by2, rnd_coord_t *iscx, rnd_coord_t *iscy)
{
/* TODO: this should be long double if rnd_coord_t is 64 bits */
	double ua, xi, yi, X1, Y1, X2, Y2, X3, Y3, X4, Y4, tmp;
	int is_a_pt, is_b_pt;

	/* degenerate cases: a line is actually a point */
	is_a_pt = (ax1 == ax2) && (ay1 == ay2);
	is_b_pt = (bx1 == bx2) && (by1 == by2);

	if (is_a_pt && is_b_pt)
		return (ax1 == bx1) && (ay1 == by1);

	if (is_a_pt)
		return is_point_on_line(ax1, ay1, bx1, by1, bx2, by2);
	if (is_b_pt)
		return is_point_on_line(bx1, by1, ax1, ay1, ax2, ay2);

	/* maths from http://local.wasp.uwa.edu.au/~pbourke/geometry/lineline2d/ */

	X1 = ax1;
	X2 = ax2;
	X3 = bx1;
	X4 = bx2;
	Y1 = ay1;
	Y2 = ay2;
	Y3 = by1;
	Y4 = by2;

	tmp = ((Y4 - Y3) * (X2 - X1) - (X4 - X3) * (Y2 - Y1));

	if (tmp == 0) {
		/* Corner case: parallel lines; intersect only if the endpoint of either line
		   is on the other line */
		return
			is_point_on_line(ax1, ay1,  bx1, by1, bx2, by2) ||
			is_point_on_line(ax2, ay2,  bx1, by1, bx2, by2) ||
			is_point_on_line(bx1, by1,  ax1, ay1, ax2, ay2) ||
			is_point_on_line(bx2, by2,  ax1, ay1, ax2, ay2);
	}


	ua = ((X4 - X3) * (Y1 - Y3) - (Y4 - Y3) * (X1 - X3)) / tmp;
/*	ub = ((X2 - X1) * (Y1 - Y3) - (Y2 - Y1) * (X1 - X3)) / tmp;*/
	xi = X1 + ua * (X2 - X1);
	yi = Y1 + ua * (Y2 - Y1);

	if (iscx != NULL)
		*iscx = xi;
	if (iscy != NULL)
		*iscy = yi;

#define check(e1, e2, i) \
	if (e1 < e2) { \
		if ((i < e1) || (i > e2)) \
			return 0; \
	} \
	else { \
		if ((i > e1) || (i < e2)) \
			return 0; \
	}

	check(ax1, ax2, xi);
	check(bx1, bx2, xi);
	check(ay1, ay2, yi);
	check(by1, by2, yi);
	return 1;
}

int pcb_lines_intersect(rnd_coord_t ax1, rnd_coord_t ay1, rnd_coord_t ax2, rnd_coord_t ay2, rnd_coord_t bx1, rnd_coord_t by1, rnd_coord_t bx2, rnd_coord_t by2)
{
	return pcb_lines_intersect_at(ax1, ay1, ax2, ay2, bx1, by1, bx2, by2, NULL, NULL);
}

rnd_rtree_dir_t pcb_search_data_by_loc(pcb_data_t *data, pcb_objtype_t type, const rnd_box_t *query_box, rnd_rtree_dir_t (*cb_)(void *closure, pcb_any_obj_t *obj, void *box), void *closure)
{
	pcb_layer_t *ly;
	rnd_layer_id_t lid;
	rnd_rtree_dir_t res;
	const rnd_rtree_box_t *query = (const rnd_rtree_box_t *)query_box;
	rnd_rtree_dir_t (*cb)(void *, void *, const rnd_rtree_box_t *) = (rnd_rtree_dir_t(*)(void *, void *, const rnd_rtree_box_t *))cb_;

	if ((type & PCB_OBJ_PSTK) && (data->padstack_tree != NULL)) {
		res = rnd_rtree_search_any(data->padstack_tree, query, NULL, cb, closure, NULL);
		if (res & rnd_RTREE_DIR_STOP)
			return res;
	}

	if ((type & PCB_OBJ_SUBC) && (data->subc_tree != NULL)) {
		res = rnd_rtree_search_any(data->subc_tree, query, NULL, cb, closure, NULL);
		if (res & rnd_RTREE_DIR_STOP)
			return res;
	}

	if ((type & PCB_OBJ_RAT) && (data->rat_tree != NULL)) {
		res = rnd_rtree_search_any(data->rat_tree, query, NULL, cb, closure, NULL);
		if (res & rnd_RTREE_DIR_STOP)
			return res;
	}

	for(lid = 0, ly = data->Layer; lid < data->LayerN; lid++,ly++) {

		if ((type & PCB_OBJ_ARC) && (ly->arc_tree != NULL)) {
			res = rnd_rtree_search_any(ly->arc_tree, query, NULL, cb, closure, NULL);
			if (res & rnd_RTREE_DIR_STOP)
				return res;
		}
		if ((type & PCB_OBJ_LINE) && (ly->line_tree != NULL)) {
			res = rnd_rtree_search_any(ly->line_tree, query, NULL, cb, closure, NULL);
			if (res & rnd_RTREE_DIR_STOP)
				return res;
		}
		if ((type & PCB_OBJ_POLY) && (ly->polygon_tree != NULL)) {
			res = rnd_rtree_search_any(ly->polygon_tree, query, NULL, cb, closure, NULL);
			if (res & rnd_RTREE_DIR_STOP)
				return res;
		}
		if ((type & PCB_OBJ_TEXT) && (ly->text_tree != NULL)) {
			res = rnd_rtree_search_any(ly->text_tree, query, NULL, cb, closure, NULL);
			if (res & rnd_RTREE_DIR_STOP)
				return res;
		}
		if ((type & PCB_OBJ_GFX) && (ly->gfx_tree != NULL)) {
			res = rnd_rtree_search_any(ly->gfx_tree, query, NULL, cb, closure, NULL);
			if (res & rnd_RTREE_DIR_STOP)
				return res;
		}

	}
	return 0;
}

