From: Paul Keinanen on
On Wed, 16 Jun 2010 07:45:51 -0700, D Yuniskis
<not.going.to.be(a)seen.com> wrote:

>Hi Paul,
>
>Paul Keinanen wrote:
>> On Mon, 14 Jun 2010 18:23:44 -0700, D Yuniskis
>> <not.going.to.be(a)seen.com> wrote:
>>
>>> But, that's still a lot of number crunching! If I work
>>> in a quasi fixed point representation of the space, it
>>> still leaves me with lots of sqrt() operations (which
>>> are hard to reduce to table lookups in all but a trivial
>>> space! :< )


>> If the sqrt argument is in floating point format, the exponent is
>> divided by 2 and the normalized could go through a table lookup. A
>> single step normalization (increment exponent by one, shift right
>> mantissa by one bit) may be required.
>
>I think you can cheat even more *knowing* what subsequent operations
>*will* be. E.g., postpone the normalization until after the sums
>have been computed, etc. Small optimizations but they might
>save a bit of time and precision.

Adding/subtracting is easy to do in integer/fixed point arithmetic,
but very nasty to do in floating point format due to demoralization
and normalization stages. Also integer/fixed to float conversion is
nasty due to normalization.

Doing sqrt((Xs-Xe)^2+(Ys-Ye)^2) might be easiest to implement by doing
the differences in integer, take abs of both (to reduce table size)
and use a table for squaring and using integer arithmetic for the sum
and then convert to float for sqrt, especially if the result is needed
in floating point representation.

Single cycle normalisation/denormalisation in hardware requires a
barrel shifter capable of shifting multiple bits left or right. For
normalization (to [0.5..1.0] or to [1..2]) a priority encoder is
required to detect the highest bit set, so that the correct shift can
be applied in the barrel shifter.

In order to take the sqrt from a integer/fixed point argument and
generate floating point sqrt, group two bits at a time starting from
the binary point (left and right), take OR of each 2 bit group and
feed the outputs to a priority encoder and use the barrel shifter to
normalize the result to [0.25..1.0] or [1..4]. Use the normalized
mantissa for table lookup and use the shift count (after shifting out
the LSB) as the exponent.

>
>> A suitable floating point representation would be 1 byte 2's
>> complement exponent and 16 bit mantissa (without any hidden bit
>> tricks).
>
>The problem is deciding what constitutes "suitable" from a
>computational aspect -- as you accumulate "length", you can't
>sacrifice "resolution". E.g., even if N is only 10-12 bits,
>you quickly eat up that 16 bits as you are often summing
>very small segment lengths where you want to *carry* that
>precision in your sum (yet are forced to discard it as
>the accumulated length increases and consumes bits in your
>representation).

The floating point representation is very bad if you try to accumulate
small increments to a huge sum. Sooner or later adding a small value
does not change the sum at all.

If you really have to use such incremental system, at least accumulate
(say 100..1000) samples into a temporary sum, before adding to a final
sum.


From: Bob on
D Yuniskis wrote:
> Hi,
>
> Not the *ideal* group for this post -- but the "right"
> group would undoubtedly throw *way* more resources at
> the solution... resources that I don't *have*! :<
>
> I'm looking for a reasonably efficient (time + space)
> technique at approximating Bezier curve lengths from
> folks who may have already "been there"...
if you're also drawing the curves, have the drawBezier() function add it
up. Adjacent pixels in x or y are worth 1; diagonally adjacent are worth
root 2. Almost free, computationally, but is it accurate enough?

Bob
From: George Neuner on
On Tue, 15 Jun 2010 15:23:45 -0700, D Yuniskis
<not.going.to.be(a)seen.com> wrote:

>Hi George,
>
>George Neuner wrote:
>> On Mon, 14 Jun 2010 18:23:44 -0700, D Yuniskis
>> <not.going.to.be(a)seen.com> wrote:
>>
>>> I'm looking for a reasonably efficient (time + space)
>>> technique at approximating Bezier curve lengths from
>>> folks who may have already "been there"...
>>>
>>> So, obviously, I need a "trick". Something to throw away
>>> that gives me a "good enough" answer -- the sort of thing
>>> the "ideal" group wouldn't consider settling for ;-)
>>>
>>> Suggestions? Pointers?
>>
>> I don't know if the code works but here's possible solution that
>> doesn't look too involved:
>>
>> http://www.gamedev.net/community/forums/viewreply.asp?ID=2003096
>
>That's a pretty straightforward "subdivide-and-sum-chords"
>approach. Note that it hides lots of expensive math!
>
> flt l0 = p0.length();
> flt l1 = p1.length();
> flt l3 = p3.length();
>
>(i.e., the "length" method computes euclidean distance...
>two mults and a sqrt!)

What chip?

This is an article about fast computation of curve sections using
fixed point. The discussion is quite interesting.

http://www.mactech.com/articles/mactech/Vol.05/05.01/BezierCurve/


George
From: Peter Dickerson on
"D Yuniskis" <not.going.to.be(a)seen.com> wrote in message
news:hvbblq$2d9$1(a)speranza.aioe.org...
> Hi Peter,
>
> Peter Dickerson wrote:
>>>> Note that this says nothing about how you calculate the length only
>>>> about how to get a better estimate from two poor estimates. The point
>>>> being that the size of h you can get away with can be much larger and
>>>> so the overall amount of work can be (much) less.
>>>>
>>>> Still, I'm with Tim on this one. Try some math(s) first.
>>> Working the numbers from "t" leaves you burning through lots of
>>> iterations of "oops, too big!" before you can get to suitably
>>> small increments that give you close enough approximations
>>> (there has been a LOT of research on this problem as it is so
>>> commonly used to represent curves/surfaces :< ).
>>
>> but probably fewer than the naive approach with the same the same degree
>> of non-contraint on the problem.
>
> What's the "naive approach"? The algorithm I outlined drills
> down to increasingly finer segment sizes until it achieves a
> desired level of "fit". If the curve is *linear*, the length
> is computed as the distance between the endpoints!

Yes. Perhaps there is a better word than naive. It's poor on rounding error
control and converges slowly. As a result there are a lot of calculations
that are thown away.

OK, I've had chance to try this out in the case of a cubic. Start with just
the end points then subdivide in smaller steps of the control parameter. For
simplicity one line segment then two, four etc. Then printed out the summed
length and the romberg estimate. In the example below the path happens to be
exactly 2.0, making it easy to see the errors.

Control points
(0.000000,0.000000)
(0.000000,-1.000000)
(1.000000,1.000000)
(1.000000,0.000000)

Control points
(0.000000,0.000000)
(0.000000,1.000000)
(1.000000,1.000000)
(1.000000,0.000000)

sqrts length Romberg
1 1.000000
2 1.802776 2.070368
4 1.950719 2.000034
8 1.987716 2.000048
16 1.996931 2.000003
32 1.999233 2.000000
64 1.999808 2.000000
128 1.999952 2.000000
256 1.999988 2.000000
512 1.999997 2.000000
1024 1.999999 2.000000

so to get to 1 part per million the straightforward ('naive') method uses
1024 square roots for the result, and (assume no better way to start than
from a single line segment) 1023 sqrt used getting there. The romberg method
gets to a slightly better result using 48 square roots (32+16) and wastes
15. That seems like a worthwhile acceleration to me. The convergence power
of h is 2 for the naive method and 4 for the romberg.

>>> Unless you can constrain the shapes of the curves, there is
>>> nothing you can do a priori (in your algorithm) to guarantee
>>> that an approach, choice of t, etc. will work.
>>
>> it isn't an algoritm per se, its an optimization to avoid finer step
>> sizes.
>>
>>> E.g., years ago, I wrote an algorithm to draw lines of
>>> constant time-difference for a LORAN-C pen plotter. Basically,
>>> families of "concentric" (?) hyperbolae. But, you can't know
>>> a priori where on the family of curves you will be operating
>>> so the algorithm had to handle all cases. Computing *a*
>>> point on the curve took a full second -- you can't
>>> compute many such points before the user will grow impatient
>>> with you :>
>>
>> years ago I wrote a rotine to plot mass spectra which involved solving
>> cubics for it data point on an early 68k machine. A couple of thousand
>> data points plotted as fast as anyone would care about, <1s. No hardware
>> FP, avoided sqrt in most cases...
>
> Then you weren't working in a resource starved environment. :>
> (e.g., the plotter ran on a 3MHz 8085, 10KB of code and 512 bytes
> of RAM -- for the entire instrument!).
>
> I've found that failing to meet a ~300ms response time
> WITH A CLEVER ALGORITHM is a good shirtcuff definition for
> resource starved. E.g., if I push a button and can perceive
> the delay in the attendant action, then the code is
> struggling to do more than it can...
>
>>> But, this computation may just end up telling you, "I can't
>>> get to that point with a linear approximation as there is
>>> too much curvature here". So, you just *wasted* a second.
>>> Watching the algorithm creep into regions of increasing
>>> curvature was *painful* (as each successive estimate got
>>> "less good")
>>
>> in this case the previous and current result are used to improve the
>> estimate in the hope of going to higher resolution.
>
> Yes, as does the algorithm I proposed. If the points you end up
> with don't look to be a good fit, you subdivide and try again
> (recurse). Using the first control polygon and the subsequent
> subdivided points as upper and lower limits on the "actual"
> value lets you come up with a numerical factor that bounds
> your error: i.e., the length is in [a,b] - which you can
> examine at run time and use to decide if you need to further
> subdivide. Watching how a and b vary with each refinement
> lets you see when you are just spinning your wheels.

The control points are hardly much of an upper bound in the case of a small
number of control points. In my example, above, the upper board is 3.

>>> So, you sort out how you can get *some* information from
>>> the datum that, hopefully, makes your next actions a *little*
>>> bit better -- or, maybe not as painfully *bad*!
>>>
>>> This algorithm (bezier length) isn't *nearly* as expensive.
>>> But, the same applies: make every computation "count"
>>> (towards achieving your final goal).

It might help to know the context. What is the purpose. What degree of
polynomials are you talking about. I think you said 2-D in another post
but...
What hardware have you in mind?

peter


From: D Yuniskis on
Hi Peter,

Peter Dickerson wrote:
> OK, I've had chance to try this out in the case of a cubic. Start with just
> the end points then subdivide in smaller steps of the control parameter. For
> simplicity one line segment then two, four etc. Then printed out the summed
> length and the romberg estimate. In the example below the path happens to be
> exactly 2.0, making it easy to see the errors.
>
> Control points
> (0.000000,0.000000)
> (0.000000,-1.000000)
> (1.000000,1.000000)
> (1.000000,0.000000)

I'll assume the above is NOT what you used but, rather, the one
which follows (?)

> Control points
> (0.000000,0.000000)
> (0.000000,1.000000)
> (1.000000,1.000000)
> (1.000000,0.000000)
>
> sqrts length Romberg
> 1 1.000000
> 2 1.802776 2.070368
> 4 1.950719 2.000034
> 8 1.987716 2.000048
> 16 1.996931 2.000003
> 32 1.999233 2.000000
> 64 1.999808 2.000000
> 128 1.999952 2.000000
> 256 1.999988 2.000000
> 512 1.999997 2.000000
> 1024 1.999999 2.000000

I'm not sure we're on the same page, here. :-( (see below)
I think you are playing solely with "t" (or whatever you are
using as the control variable) and subdividing *that* (?)

[did you run this in Maple/Mathematica/etc.? If so, can you
post your code?]

> so to get to 1 part per million the straightforward ('naive') method uses
> 1024 square roots for the result, and (assume no better way to start than
> from a single line segment) 1023 sqrt used getting there. The romberg method
> gets to a slightly better result using 48 square roots (32+16) and wastes
> 15. That seems like a worthwhile acceleration to me. The convergence power
> of h is 2 for the naive method and 4 for the romberg.
>
>>>> Unless you can constrain the shapes of the curves, there is
>>>> nothing you can do a priori (in your algorithm) to guarantee
>>>> that an approach, choice of t, etc. will work.
>>> it isn't an algoritm per se, its an optimization to avoid finer step
>>> sizes.

>>>> But, this computation may just end up telling you, "I can't
>>>> get to that point with a linear approximation as there is
>>>> too much curvature here". So, you just *wasted* a second.
>>>> Watching the algorithm creep into regions of increasing
>>>> curvature was *painful* (as each successive estimate got
>>>> "less good")
>>> in this case the previous and current result are used to improve the
>>> estimate in the hope of going to higher resolution.
>> Yes, as does the algorithm I proposed. If the points you end up
>> with don't look to be a good fit, you subdivide and try again
>> (recurse). Using the first control polygon and the subsequent
>> subdivided points as upper and lower limits on the "actual"
>> value lets you come up with a numerical factor that bounds
>> your error: i.e., the length is in [a,b] - which you can
>> examine at run time and use to decide if you need to further
>> subdivide. Watching how a and b vary with each refinement
>> lets you see when you are just spinning your wheels.
>
> The control points are hardly much of an upper bound in the case of a small
> number of control points. In my example, above, the upper board is 3.

No, you update the upper -- and lower! -- bounds as you subdivide
the curve.

E.g., the first iteration of "my" algorithm has the length
of the segments in the control polygon as 3 (= 1+1+1)
and the length of the *chord* as 1 (obvious). So, my initial
estimate for length is 2 ((3+1)/2).

Next, I subdivide the curve creating two curves:

(0,0) (0,0.5) (0.25,0.75) (0.5,0.75)

and

(0.5,0.75) (0.75,0.75) (1,0.5) (1,0)

(in this example, they are mirror images of each other)

[apologies if I screw this up... I'm doing it in my head
as I type :-/ ]

The sum of the control polygon segments is ~1.103
(1/2 + sqrt(2)/4 + 1/4) and the length of the chord
is ~0.901 (sqrt(0.5^2 + 0.75^2)). The estimate for
the length of *this* curve (which is just the "left
half" of the original curve) would the be ~1.002
((1.103 + 0.901)/2). Since the left and right halves
of the original curve are mirror images, the length
of the *right* curve will be the same leaving the
estimate for the total curve to be ~2.004

[sorry, going any further than that without a calculator
and pen/paper is too hard for my little brain! :> ]

Note that many of the calculations performed in the
first curve's assessment can be salvaged in the
assessment of the second curve. (I haven't sat down and
counted how many sqrts are *required* at each subdivision
of the curve and how many can be "inferred").

The problem with all of these is coming up with criteria
that you can automagically use to stop the recursion.
E.g., I would simply use something like
|estimate[i+1]| < |estimate[i]| - epsilon
But, this assumes the process converges monotonically...