Affine transforms - matrix algebra
Pavel Safrata
pavel.safrata at oracle.com
Thu Jul 12 08:15:45 PDT 2012
Should the array-accepting methods accept also arrays of length 6
(containing only 2D-relevant elements)?
Should the array-returning methods return 12 elements or 16?
Thanks,
Pavel
On 12.7.2012 8:35, Pavel Safrata wrote:
> Hi Kirill,
> you are not right with the performance, the elements are (possibly
> invalid) properties so we need to call all the getters anyway, plus
> construct the array, so the performance is actually worse. But the
> use-case seems to be valid, especially if the other libraries support
> such conversion. I just wouldn't call it 'asArray' which suggests that
> the array will keep updating with the matrix changes but rather
> 'toArray'. To be consistent we need to use doubles instead of floats
> and we should also add a similar setter. So I propose:
>
> public double[] toArray()
> public double[] toArray(double[] a)
> public Affine(double[] matrix)
> public void setTransform(double[] matrix)
>
> Where the first two will behave similarly as if it was a List of 12
> doubles, the latter two will throw IllegalArgumentException if length
> of the array is not 12.
>
> Regards,
> Pavel
>
> On 12.7.2012 1:20, Kirill.Prazdnikov wrote:
>> Hi Pavel,
>>
>> My typical use-case was to transfer matrix data between JavaFX and
>> different libraries like 3D or physics.
>> Use of one array with 12 (16) elements is by far simpler then passing
>> 12 (16) arguments.
>> And performance of one getter is better then 16 separate martix
>> elements getters.
>>
>> Thanks
>> -Kirill
>>
>> On 7/12/2012 12:17 AM, Pavel Safrata wrote:
>>> Hi Kirill,
>>> what is the use-case? I suspect that if you want the raw matrix to
>>> do some math with it we are rather missing some operations doing the
>>> math directly.
>>> Thanks,
>>> Pavel
>>>
>>> On 11.7.2012 21:35, Kirill.Prazdnikov wrote:
>>>> Hi Pavel,
>>>>
>>>> I`m not sure, but many time times I found the following
>>>> conversions as very useful:
>>>>
>>>> public float [] asArray(float data[12]);
>>>> public Affine(float data[]);
>>>>
>>>> along with 12-arguments ctor.
>>>>
>>>> Thanks
>>>> -Kirill
>>>>
>>>> On 7/11/2012 6:57 PM, Pavel Safrata wrote:
>>>>> Hi Martin,
>>>>> thank you for your input.
>>>>>
>>>>> On 10.7.2012 18:36, Martin Desruisseaux wrote:
>>>>>> Hello Pavel
>>>>>>
>>>>>> Many thanks for taking care of this task!
>>>>>>
>>>>>>
>>>>>> Le 10/07/12 17:00, Pavel Safrata a écrit :
>>>>>>> On Transform class:
>>>>>>> public Transform getConcatenation(Transform transform) //
>>>>>>> multiplication
>>>>>>> public Transform getInverse() throws
>>>>>>> NoninvertibleTransformException // negation
>>>>>>> public Transform copy()
>>>>>> Sound fine to me, while I'm not sure why a 'copy' method instead
>>>>>> than overriding the 'clone()' method?
>>>>>
>>>>> Good point, we can use clone().
>>>>>
>>>>>>
>>>>>>
>>>>>>> Constructors:
>>>>>>> public Affine(Transform transform)
>>>>>>> public Affine(double mxx, double mxy, double mxz, double tx,
>>>>>>> double myx, double myy, double myz, double ty,
>>>>>>> double mzx, double mzy, double mzz, double tz)
>>>>>> Look fine.
>>>>>>
>>>>>>
>>>>>>> Setters of the entire matrix:
>>>>>>> (...snip...)
>>>>>> I don't know for JavaFX, but in my experience with Java2D, I
>>>>>> wasn't using the setter methods often, except 'setToIdentity' and
>>>>>> 'setToTransform'. For example rather than invoking
>>>>>> 'setToTranslation', I strongly push our developers to use
>>>>>> 'translate' (or 'concatWithTranslation' in this proposal)
>>>>>> instead. If a developer really wants the functionality of
>>>>>> 'setToTranslation', he can get it by invoking 'setToIdentity()'
>>>>>> followed by 'concatWithTranslation'. Or yet better,
>>>>>> 'setToTransform(...)' instead than 'setToIdentity' with the
>>>>>> coefficients of some previous state that the user saved.
>>>>>
>>>>> I don't insist on having the setters, anybody wants them?
>>>>>
>>>>>>
>>>>>> The rational is that in many cases, the affine transform is
>>>>>> already initialized to some important value. For example in
>>>>>> Java2D, AffineTransform is initialized to the transform from
>>>>>> 'dot' to whatever units the underlying device uses. When
>>>>>> rendering on screen, this is the identity transform. But when
>>>>>> printing, this is something different that depends on the printer
>>>>>> resolution. In GIS applications, it depends on the zoom level.
>>>>>> Other applications may use magnifier glass over some areas.
>>>>>> Because the initial transform is often (but not always) the
>>>>>> identity one, developers with limited experience with affine
>>>>>> transforms often use 'setTranslate' or 'setScale' in situations
>>>>>> where they should really use 'translate' or 'scale', and do not
>>>>>> realize their bug before late in the development process. For
>>>>>> this reason, I would be inclined to discourage every setter
>>>>>> methods except 'setToIdentity()' and 'setToTransform'. Keeping in
>>>>>> mind that it is often easier to fill a hole later than to fix
>>>>>> something broken, I think it would be safer to leave out all
>>>>>> other setter methods for now, and revisit later if experience
>>>>>> show that they are really needed.
>>>>>>
>>>>>
>>>>> While I don't think our Affine class will ever have varying
>>>>> initial values I'm ok with keeping only setToIdentity() and
>>>>> setToTransform() if there is no demand for the other setters right
>>>>> now.
>>>>>
>>>>>>
>>>>>>> Operations on the matrix (modifying it in place):
>>>>>>> (...snip...)
>>>>>> Sound good, minus the unfortunate 'concatWith*' naming :-(.
>>>>>>
>>>>>>
>>>>>>> Instead of "concatWithTranslation" it would be more natural to
>>>>>>> use just "translate" (and similarly for the others), but
>>>>>>> unfortunately these method names are already taken by the static
>>>>>>> factory methods on Transform class. This is unpleasant but we
>>>>>>> need to be backward compatible so we have to introduce different
>>>>>>> names. We'll be happy to hear better naming suggestions than the
>>>>>>> concatWith* (which is pretty descriptive I think but not really
>>>>>>> nice).
>>>>>> Hard to say... A consistency with "preTranslate" would be nice,
>>>>>> but "postTranslate" doesn't look very nice... What about the
>>>>>> following?
>>>>>>
>>>>>> * Rename "preTranslate" as "appendTranslation"
>>>>>> * Rename "concatWithTranslation" as "prependTranslation"
>>>>>>
>>>>>> The "preConcatenate(Tx)" name in Java2D was actually misleading
>>>>>> to some developers, because it works as if points were first
>>>>>> transformed by the original transform, then transformed by 'Tx'.
>>>>>> Maybe "appendTranslation(Tx)" would make clear that the
>>>>>> translation is applied after the original transform. This would
>>>>>> also make operation order clearer. The following code using
>>>>>> Java2D API:
>>>>>>
>>>>>> tr.translate(...)
>>>>>> tr.scale(...)
>>>>>> tr.rotate(...)
>>>>>>
>>>>>> must be read from bottom to up: it is as if points were rotated
>>>>>> first, then scaled, then translated. So maybe the above
>>>>>> proposition would make that more obvious:
>>>>>>
>>>>>> tr.prependTranslation(....)
>>>>>> tr.prependScale(...)
>>>>>> tr.prependRotate(...)
>>>>>> tr.appendTranslation(...) // Just for fun.
>>>>>>
>>>>>
>>>>> These names indeed sound way better.
>>>>>
>>>>> If we are going this way, shouldn't we do also this?
>>>>> * Rename 'concatenate' as 'prepend'
>>>>> * Rename 'preConcatenate' as 'append'
>>>>> * Rename 'getConcatenation' as .. well .. 'getPrependage' :-)
>>>>>
>>>>> As a black-box transformation composing this makes sense. As a
>>>>> matrix algebra, it may be also confusing: tA.append(tB) means
>>>>> matrix multiplication 'B x A', does it sound good? Maybe yes, I'm
>>>>> not sure. Anyway, so far the best proposal I think.
>>>>>
>>>>>>
>>>>>>> Would you want static factory methods on Affine (creating Affine
>>>>>>> instances with the simple transforms)?
>>>>>> I don't think it is necessary. I found the static factory methods
>>>>>> of Java2D AffineTransform rarely used.
>>>>>>
>>>>>>> Would it be important to you whether or not matrix changes are
>>>>>>> atomic? If you call one of the methods that modify the entire
>>>>>>> matrix, can be listeners for each member called immediately as
>>>>>>> the members are set, or do they need to be called after all the
>>>>>>> members are updated?
>>>>>> I don't have experience in this area. But naively, it seems to we
>>>>>> that it would be better to be notified only after the full matrix
>>>>>> has been updated...
>>>>>
>>>>> Yes, I would also pick this option. It's just that it would mean
>>>>> writing much more complicated (and a bit less effective) code so
>>>>> the question is whether or not you think it is important.
>>>>>
>>>>> Thanks,
>>>>> Pavel
>>>>>
>>>>>>
>>>>>> Regards,
>>>>>>
>>>>>> Martin
>>>>>>
>>>>>
>>>>>
>>>>
>>>
>>>
>>
>
>
More information about the openjfx-dev
mailing list