Hi Denis,

On 12/6/2010 4:21 PM, Denis Lila wrote:
Hi Jim.

line 134 - what if numx or numy == 0 because only roots outside [0,1]
were found?

In this case lines 151-162 will execute, and nothing is wrong. The only
problem is when both numx and numy are 0. This is certainly possible in
the general case (but only with quadratic curves), but the way we're using
this function, the intermediate value theorem guarantees at least one root
will be found. Of course, finite precision math doesn't necessarily care
what calculus has to say, but in this case I can't see how the root computation
could fail. On the other hand, one could argue that this is exactly why we need
to defend against this case, so I've added some checks.

I'm sure you will likely find a root, but the method you are using is "roots*inAB*" which may throw the root out because it is out of range, no?

In looking at that method it looks like the cubic handling code tries 0 and 1 in addition to the critical points it finds using a root, but the quadratic code that it chooses if a==0 will throw out all roots outside the 0,1 range and may end up with 0 answers. The cubic code further can reject all of the points (if they are all non-zero and same sign) and also return no answers, but may have fewer cases where it would do that.

Still, my point was not that you might fail to find a root, but that the roots may get rejected and end up with no answers in range.

line 145 - what if d0 and d1 are both 0?  NaN results.  What if you
just used a simple "d0<  d1 ? tx : ty" - how far off would that be?

I tried that out on a curve with very high acceleration, and it makes a 
noticeable
difference. So, instead I'm using
                if (w0 == Float.NaN) {
                        return tx;
                }

Read the IEEE spec on NaN. It's a special value that has this bizarre property that it is the only number that is not equal to itself. ;-) In fact, the test for NaN is usually "if (x == x) <notNaN> else <NaN>". If you want to be explicit and formal then you can use the static Float.isNaN() method (which is essentially that test - x!=x).

Same thing on Dasher line 363 where you also test for NaN.

line 357 - another optimization would be to check the acceleration in
the range and if it is below a threshold then simply use the t from
line 348 as the t for the split

I like this. I tried implementing it. I haven't tested it yet though, and
I still have to pick a good cutoff acceleration. For now I'm using 1/leaflen.
We definitely don't want it to just be a constant, since the longer the leaf,
the worse it will be to allow acceleration, so for longer leaves we want to
skip the getTCloseTo call only if the acceleration is smaller.

A lot of the lines before you test MaxAcc are not needed unless you go into the if. In particular, x,y,[xy][01] are only used if you call getTCloseTo().

Renderer.java:  Is this just a straight copy of what I was working on?

I'm pretty sure it is, yes.

Actually I think you've updated the AFD code so I should really take a look... :-(

;-)

TransformingPathConsumer2D:

Any thoughts on whether we need translation in the scale filter and
whether a 4-element non-translation filter would be useful?  I think
the current code that drives this passes in the full transform and its
inverse, but it could just as easily do delta transforms instead and
save the adding of the translation components...

I thought about this long ago, but I wasn't sure it wouldn't break anything.
Today, I got a bit more formal with the math, and I think it's ok
to eliminate the translation. I've implemented this (though, I haven't had
time to test it yet. That's for tomorrow).

Right now you have (note that the common terminology for "transform without translation" is "delta transform"):

        PathIterator
            => DeltaAT => Normalize
            => DeltaInverseAT => strokers
            => FullAT => renderer

The problem is that normalization needs proper sub-pixel positioning so you need to hand it the true device space coordinates with proper translation. You need this:

        PathIterator
            => FullAT => Normalize
            => DeltaInverseAT => strokers
            => DeltaAT => renderer

I would skip the creation of atNotTranslationPart and just inverse the original transform (since I don't think the inversion is affected by translation - you can see this in the calculations in AT.createInverse()), and then have the transforming consumers apply a delta transform - i.e. create a "TPC2D.deltaTransformConsumer()" method which would apply just the non-translation parts of AT to the consumer.

If you want to get really fancy with optimizations you could have an "inverseDeltaTransformConsumer() method that would calculate the inversions on the fly to avoid construction of a scratch AT. Since it is just "weird transpose with signs and divide by the determinant" in the most general case and even simpler (invert Mxx and Myy) in the scale-only case it would be trivial to incorporate these calculations. It would also eliminate a need to catch NonInvertible... ;-)

    public static PathConsumer2D
        deltaTransformConsumer(PathConsumer2D out,
                               AffineTransform at)
    {
        if (at == null) {
            return out;
        }
        float Mxx = (float) at.getScaleX();
        float Mxy = (float) at.getShearX();
        float Myx = (float) at.getShearY();
        float Myy = (float) at.getScaleY();
        if (Mxy == 0f && Myx == 0f) {
            if (Mxx == 1f && Myy == 1f) {
                return out;
            } else {
                return new DeltaScaleFilter(out, Mxx, Myy);
            }
        } else {
            return new DeltaTransformFilter(out, Mxx, Mxy, Myx, Myy);
        }
    }

    public static PathConsumer2D
        inverseDeltaTransformConsumer(PathConsumer2D out,
                                      AffineTransform at)
    {
        if (at == null) {
            return out;
        }
        float Mxx = (float) at.getScaleX();
        float Mxy = (float) at.getShearX();
        float Myx = (float) at.getShearY();
        float Myy = (float) at.getScaleY();
        if (Mxy == 0f && Myx == 0f) {
            if (Mxx == 1f && Myy == 1f) {
                return out;
            } else {
                return new DeltaScaleFilter(out, 1.0f/Mxx, 1.0f/Myy);
            }
        } else {
            det = Mxx * Myy - Mxy * Myx;
            return new DeltaTransformFilter(out,
                                            Myy / det,
                                            -Mxy / det,
                                            -Myx / det,
                                            Mxx / det);
        }
    }

Using these two methods I don't think you need any transforms other than the original one - so all you need is "strokerat" which replaces both outat and inat and is either null (no special transform needed) or the original AT when special transforms are needed...

                        ...jim

Reply via email to