Exemple #1
0
float float2::MinAreaRectInPlace(float2 *p, int n, float2 &center, float2 &uDir, float2 &vDir, float &minU, float &maxU, float &minV, float &maxV)
{
	assume(p || n == 0);
	if (!p || n <= 0)
	{
		center = uDir = vDir = float2::nan;
		minU = maxU = minV = maxV = FLOAT_NAN;
		return FLOAT_NAN;
	}

	// As a preparation, need to compute the convex hull so that points are CCW-oriented,
	// and this also greatly reduces the number of points for performance.
	n = float2::ConvexHullInPlace(p, n);
	assert(n > 0);
	if (n == 1)
	{
		center = p[0];
		uDir = float2(1,0);
		vDir = float2(0,1);
		minU = maxU = center.x;
		minV = maxV = center.y;
		return 0.f;
	}

	// e[i] point to the antipodal point pairs: e[0] and e[2] are pairs, so are e[1] and e[3].
	float2 *e[4] = { p, p, p, p };

	// Compute the initial AABB rectangle antipodal points for the rotating calipers method.
	// Order the initial vertices minX -> minY -> maxX -> maxY to establish
	// a counter-clockwise orientation.
	for(int i = 1; i < n; ++i)
	{
		if (p[i].x < e[0]->x) e[0] = &p[i];
		else if (p[i].x > e[2]->x) e[2] = &p[i];
		if (p[i].y < e[1]->y) e[1] = &p[i];
		else if (p[i].y > e[3]->y) e[3] = &p[i];
	}

	// Direction vector of the edge that the currently tested rectangle is in contact with.
	// This specifies the reference frame for the rectangle, and this is the direction the
	// convex hull points toward at the antipodal point e[0].
	float2 ed = -float2::unitY;
	float minArea = FLOAT_INF; // Track the area of the best rectangle seen so far.
	const float2 * const pEnd = p + n; // For wraparound testing in NEXT_P().

	// These track directions the convex hull is pointing towards at each antipodal point.
	float2 d[4];
	d[0] = (*NEXT_P(e[0]) - *e[0]).Normalized();
	d[1] = (*NEXT_P(e[1]) - *e[1]).Normalized();
	d[2] = (*NEXT_P(e[2]) - *e[2]).Normalized();
	d[3] = (*NEXT_P(e[3]) - *e[3]).Normalized();

	// Rotate the calipers 90 degrees to see through each possible edge that might support
	// the bounding rectangle.
	while(ed.y <= 0.f)
	{
		// Compute how much each edge can at most rotate before hitting the next vertex in the convex hull.
		float cosA0 =  ed.Dot(d[0]);
		float cosA1 =  ed.PerpDot(d[1]);
		float cosA2 = -ed.Dot(d[2]);
		float cosA3 = -ed.PerpDot(d[3]);

		float maxCos = MATH_NS::Max(MATH_NS::Max(cosA0, cosA1), MATH_NS::Max(cosA2, cosA3));
		// Pick the smallest angle (largest cosine of that angle) and increment the antipodal point index to travel the edge.
		if (cosA0 >= maxCos)      { ed = d[0];                e[0] = NEXT_P(e[0]); d[0] = (*NEXT_P(e[0]) - *e[0]).Normalized(); }
		else if (cosA1 >= maxCos) { ed = d[1].Rotated90CW();  e[1] = NEXT_P(e[1]); d[1] = (*NEXT_P(e[1]) - *e[1]).Normalized(); }
		else if (cosA2 >= maxCos) { ed = -d[2];               e[2] = NEXT_P(e[2]); d[2] = (*NEXT_P(e[2]) - *e[2]).Normalized(); }
		else                      { ed = d[3].Rotated90CCW(); e[3] = NEXT_P(e[3]); d[3] = (*NEXT_P(e[3]) - *e[3]).Normalized(); }

		// Check if the area of the new rectangle is smaller than anything seen so far.
		float minu = ed.PerpDot(*e[0]);
		float maxu = ed.PerpDot(*e[2]);
		float minv = ed.Dot(*e[1]);
		float maxv = ed.Dot(*e[3]);

		float area = MATH_NS::Abs((maxu-minu) * (maxv-minv));
		if (area < minArea)
		{
			vDir = ed;
			minArea = area;
			minU = MATH_NS::Min(minu, maxu);
			maxU = MATH_NS::Max(minu, maxu);
			minV = MATH_NS::Min(minv, maxv);
			maxV = MATH_NS::Max(minv, maxv);
		}
	}
	uDir = vDir.Rotated90CCW();
	center = 0.5f * (uDir * (minU+maxU) + vDir * (minV+maxV));

	return minArea;
}