Why is transforming vectors different from transforming points?

This caused me a lot of confusion and headaches when I was writing my code.

Seems that if you translate a vector using `Geom::Vector3d#transform(transform)` or `Geom::Transformation#*(vector)`, you get the equivalent of the matrix operation `TV`, but if you translate a point using `Geom::Point3d#transform(transform)` or `Geom::Transformation#*(point)`, you get the equivalent of the matrix operation `PT`. What is the point of this?

I guess there are none of the method exist in Ruby API that you are referring to. You maybe wanted to mention e.g. Vector3d#transform -instance_method ā¦

Most probably Iām not the best to explain, but there is what I recognised:

If you check both of the Geom::Vector3d and Geom::Point3d Constructor detailsā¦

`#initialize(x, y, z) ā Geom::Vector3d vs #initialize(x, y, z) ā Geom::Vector3d`
`Parameters:`
`x (Numeric)`
`y (Numeric)`
`z (Numeric)`
OR:
`#initialize(array3d) ā Geom::Point3d vs #initialize(array3d) ā Geom::Vector3d`
`Parameters:`
`array3d (Array(Numeric, Numeric, Numeric))`

``````pt1 = Geom::Point3d.new(100,200,300)
vec1 = Geom::Vector3d.new(100,200,300)
pt2 = [100,200,300] #only the context will tell what it is
vec2 = [100,200,300] #only the context will tell what it is
``````

ā¦you should not surprise about the similarity of matrix operations.

I hope there will come another real expert who will explain betterā¦

1 Like

Um, yeah. It was a long night last night fighting with the APIā¦ Iāve updated the question.

The issue is that transformations using vectors are like when multiplying transformation matrix times a vector matrix (`TV`), but transformations using points are like when multiplying a point by a transformation matrix (`PT`). I would have expected they would be both like the latter, not different. That, or that the order could be controlled by a clearer idiom, such as to have a `Vector3d#*(transformation)`/`Point3d#*(transformation)` for `VT`/`PT` and `Transformation#*(vector)`/`Transformation#*(point)` for `TV`/`TP` and all vector/point transformations through a member function called `transform` being `VT`/`PT`.

Iām not even sure what the point of multiplying a transformation matrix to a vector/point would even get you (but then again, my matrix algebra is pretty rusty).

For future reference and use with the Ruby language:

It is customary to refer to only module or class methods using the `::` scope operator as the method prefix. Ie ā¦
`Sketchup::active_model()`

ā¦ and to use `#` in place of a dot or scope operator only for instance methods. Ie ā¦
`Geom::Vector3d#transform()`

This way we know immediately what kind of method is being discussed. And we can go to the proper section in the documentation for the class to find the method definition. (Class methods are usually listed above instance methods.)

You will see this followed throughout the Ruby Core documentation.

What dezmo is getting at is with the SketchUp API, they wrote many of the methods in the classes to use Arrays, Vectors, Points and Transforms interchangeably. Often a simple literal array is used to represent a vector for a translational transform, and the API methods accept this. Ex:

``````# Where old_pt is a Geom::Point3d object:
new_pt = old_pt.transform([0,0,23.7])
``````

ADD: The result is a new `Geom::Point3d` object that is `23.7` inches above (Z-wise) the old one.

This code might clear things up a little (or not).

``````pt1 = Geom::Point3d.new(100,200,300)
vc1 = Geom::Vector3d.new(100,200,300)
offset = Geom::Point3d.new(10,20,30)

# create a translating Transformation
puts "\nTransformation Matrix"
tr = Geom::Transformation.new(offset)
tr.to_a.each_slice(4) {|a| p a}

# translate the Point3d object by the Transformation and by an Array
puts "\nPoint operations"
puts  pt1.transform(tr)
puts  pt1.transform(offset.to_a)
puts  pt1 + offset.to_a

# The Vector3d class is used to represent vectors in a 3 dimensional space.
# Vectors in SketchUp have a direction and a length, but not a starting point.
# https://ruby.sketchup.com/Geom/Vector3d.html#%2B-instance_method
#
# The implication here is that if you translate a vector via the tranform method
# you get back the vector unchanged, but, you can add an array to a vector to
# get the expected result as shown in the last example

# translate the vector3d object by
puts "\nVector operations"
puts vc1.transform(tr)
puts vc1.transform(offset.to_a)
puts vc1 + offset.to_a

nil
``````
``````point1 = [10, 0, 0]
point2 = [10, 0, 10]
line1 = [point1, point2]
``````

I supposed line1 will be parallel to Z-axis but was not!!! Finally, I understood Sketchup considers point2 as a vector, not as a point.

1 Like

Yes because a line can be represented as either an Array of a point and a vector, as ādefaultā. (ādefaultā is Not written in documentation, you have to discover it yourselfā¦ )

``````line1 = [Geom::Point3d.new(10, 0, 0), Geom::Vector3d.new(10, 0, 10)]
``````

Since both variable above (`point1 , point2`) are currently an Array the context here will interpret the second āpoint2ā as vector. The line will cross a `point1` and the direction is a 45Ā° vector.

But a āsecond formā is as an Array of two points.

``````line1 = [Geom::Point3d.new(10, 0, 0), Geom::Point3d.new(10, 0, 10)]
``````

Certainly, The line will different: cross a `point1` and the direction is vector from `point1` to `point2` (0,0,10)

If you define more precisely in the begening

``````point1 = Geom::Point3d.new(10, 0, 0)
point2 = Geom::Point3d.new(10, 0, 10)
line1 = [point1, point2]
``````

Then the expected result is clearerā¦

1 Like

Thanks. Iām from a C++ background so this is new to me. Iāll update my posts above.

Oh, thatās an interesting undocumented operation. I thought that you needed to do it this way:

``````new_pt = old_pt.transform(Geom::Transformation.translation(Geom::Point3d.new(0,0,23.7)))
``````

Can an array be used in all places a `Geom::Transformation` object is expected? Or is this specific to `Geom::Point3d#transform`? Iāve noticed that arrays can be used in certain contexts to mean a point or vector, but in others it causes an error, making the use inconsistent, at least (perhaps) at first glance? Itās definitely not fully documented.

Iāve not seen lines yet used in the API. Iāll keep this in mind, even though this isnāt exactly pertaining to my question.

Anyway. Looks like what Iām actually asking is just flying over everyoneās head. I understand these transforms via my matrix algebra that I took many moons ago. To transform a point, you would have the point/vector as a row matrix and multiply it against the transformation matrix. Thus `PT`, where `P` is the point in row form, and the `T` is the transformation matrix. However, matrix multiplication is not transitive, thus `PT != TP`.

The way the SU lib is designed, `Geom::Transformation#*` is specified in such a way as to make one think you are multiplying the transformation matrix against the point, which isnāt the case (youāre multiplying the point against the transformation). You are, however, actually multiplying a transformation against a vector, which is a further surprise. Coming from a CS and Math background, Iām surprised at how this API was constructed. Contextually, this API is a bit all over the place.

Now, given what I just said, please reread what I said in my other posts with this as context and maybe youāll see what I am getting at.

The SketchUp API extends the core Ruby `Array` class thus ā¦

Class: Array ā SketchUp Ruby API Documentation

Overview

The SketchUp Array class adds additional methods to the standard Ruby Array class. Specifically, it contains methods allowing an array to behave just as a Geom::Vector3d or Geom::Point3d object (which can be thought of as arrays of 3 coordinate values). Therefore, you can use the Array class in place of a Geom::Point3d or Geom::Vector3d as a way to pass coordinate values.

Okay? Now many of the API method docs will list Point3ds, Vector3ds and Array as options. But it is not a hard and fast rule that the API team will do so in the documentation.(More about that later.)

When we find a method that expects a point or a vector, but will not take an array, we open an issue in the API Issue Tracker and usually it will get fixed.

I can say that it can be used other places ā¦such as:

ā¦ etc., for `Point2d`, `Vector2d`, `Vector3d` classes.

Similarly, ā¦

ā¦ can take 3-element `Arrays` or `Geom::Transformation` objects, in the `vectors` array argument in place of `Geom::Vector3d` objects.

NOW, all that said, the method docs for all these methods do not specifically mention the variable argument types (ie, overloads.)

I asked that this be done in some way, recently in the official API tracker, but was met with resistance. Ie, they said it way too much work to doc all the overloads. I think I then asked that at least put a note in the overview for the `Geom::Transformation` class similar to the Overview blurb for Array. Ie, ā¦ speaking to the interchangeability between transforms, arrays, vectors and points.

Anyway, I canāt find the exact issue where we discussed this. All I remember was (I think it was about a month ago,) and the idea did not go over well.

They are abstract (ie, no actual class.) The description is in the `Geom` module Overview.
Donāt overlook that module level. There are some nifty module methods there.

I havenāt talked specific to that as Iāve not run into an issue.

Hmmmmā¦ I think SketchUp transformation matrices may be in column major order.

Seems like a good time for you to install ThomThomās Transformation Inspector plugin:

Whatās this? A matrix/transformation calculator?

Thx for all the info. Itās appreciated.

And manipulator. You can change individual matrix elements and see the results.

It still ticks me off that āmultiplicationā is specified as it is. Makes for confusing code as it doesnāt jive with the math.

Vectors are a direction, and when a translation type of transformation matrix is applied to it (i.e. linear movement), the translation is not suppose to not affect the vector. Internally, Sketchup performs the multiplication differently depending on whether it is a Point3d or Vector3d to achieve the expect convention in geometry. Perhaps the code would be a bit less confusing with:

``````P_pt.transform( T )
V_vec.transform( T )
``````

Someone looking at the code can expect different outcomes if the transformed object is of a different type.

Hi Bruce,

Yes, I understand that they are performed differently, but, why? What purpose would that serve except to confuse the transcription of matrix multiplication? When overloading operators, one shouldnāt stray from expected results (i.e. make it more confusing).

Further, I still donāt see what is the purpose for multiplying a vector on the right of a transformation matrix. Where would I use such an operation? Even so, if I wanted to right multiply, it would make more sense to do:

``````P_pt * T
T * V_vec
``````

Which would explicitly show the multiplication order. Then, if I wanted to have a vector be operated on like a point, I could simply do:

``````V_vec * T
``````

and it is immediately obvious as to what is expected (vector or point on left of transformation is treated as a row matrix, whereas on the right it is treated as a column matrix, and what is returned is whatever type was put in). Instead we have, `T * P_pt` really meaning `P_pt * T` and `P_pt * T` being undefined (as is also `V_vec * T`).

Why not? I myself have wanted to do such an operation on a vector and I have to do ugly conversions of a vector to a point and back again.

I just find the inconstancy between how matrix multiplication is portrayed and actual matrix multiplication annoying and confusing, with no real justification for it.

Then I suggest you open a documentation issue in the API Issue Tracker.

Please understand that the `Geom` module classes were implemented like 20 years ago and the authors are likely no longer SketchUp employees. So the reasons why things were done may be lost to history if there are no comments in the code.

I donāt know what you were asking, and you didnāt provide any example code of what you are trying to achieve, so I havenāt answered until recently. Mathematicians of some sort long ago decided that we needed separate constructs for the geometric concepts of position and direction. When operations are applied upon them, it is desirable that they behave differently, even though they both can be represented with a triplet of 3 numeric values. Vectors are not line segments on a piece of paper, not geometric lines at a specific position, and not a point. You can think of them like the wind blowing over a field:

That model is just a imperfect metaphor. A translation transformation changes the position of objects, but not direction. How can you change the position of the windās direction blowing over the field? It doesnāt have a position to begin with. Its a direction, not a position.

For the position or point, a similar situation occurs with the rotation transformation. The rotation constructor needs a position, vector and angle. The position and vector define a geometric line that is the rotation axis. If you try to rotate a point that lies on that line, its position wonāt change. The same triplet that is a vector will be rotated by the rotation (unless the vector is parallel to the axis of rotation). Matrix operations affect position and direction differently intentionally.

You said:

You can limit some, but not all ugly conversions with duck typing, including the ā.to_aā method. But, if you start using an array as a Point3d or Vector3d, you need to test to see how the array is being treated. It would be nice to have the Vector3d class have a āto_ptā cast and the Point3d have a āto_vecā cast.

A refinement might be used ā¦

``````# SomeAuthor_RefinedGeom.rb
module SomeAuthor
module RefinedGeom

refine ::Geom::Vector3d do
def to_pt
ORIGIN.transform(self)
end
end

refine ::Geom::Point3d do
def to_vec
ORIGIN.vector_to(self)
end
end

end
end
``````
``````require File.join(__dir__,"SomeAuthor_RefinedGeom")

# Must be called at toplevel for older Ruby versions
# and refinement only applies in the current file:
using SomeAuthor::RefinedGeom

module SomeAuthor
module SomeExtension

# module code using the refinement

end
end
``````

Refinements. Interesting. Iāll have to look into that further when I have time. Thx.

I know some other APIs like that of Unity use a single vector class to represent both directions and positions. In those cases you make the choice of whether the translation part of the transformation should be taken into account when you multiply.

In the SketchUp Ruby API different classes represents directions and positions. The choice of how multiplications are done is made already when you create the object.

I think both designs are valid but both can be confusing if you are used to the other. SketchUp is originally created as a 3D modeler āfor the rest of usā. The program was designed to be more intuitive and require less technical knowledge to be used than existing programs, and I think this can be seen also in the API design. If you took linear algebra in college, thinking in matrices and N-dimensional vectors as abstract mathematical concepts makes sense, but if you have little or no professional math knowledge, thinking in visual concepts like positions, directions and ādifferences in coordinate systemsā makes more sense.

My recommendation is to be explicit when you create an object about what it represent. Use Vector3d for all directions and Point3ds for all positions and transformations should just āmagicallyā work.

Yes, I agree. Having the transformationās multiply operator `*` accept point/vector/plane is strange.

Iāve personally never used those overloads. I use the `*` operator only to multiply against other matrices. And I use the explicit `transform` method on point and vector objects.

Iām not sure what the reasons behind these overloads are. They are Old Code, from long before anyone still on the SU dev team, so that is lost to history. We are alas stuck with it now.

Another thing I have a gripe with is that we overload the meaning of `Array` a lot. It can represent point, vector, line, plane, transformation etc. It can be convenient some times, but can also lead to unfortunate ambiguity. And for the concepts of Line and Plane you have to use arrays. I would have preferred dedicated types. But at this point there is so much existing code and logic that Iām doubtful how valuable introducing new types would be.

It might be that the original implementers added the `*` transformation overloads as a way to allow planes and lines to be transformed. Since they donāt have a type of their own the only way is this backward syntax (`transform * plane`).

It might also be a case of misplaced code re-use. Internally the point and vector class have no transform method. And the API layer reaches out for `transform.*(point)` and `transform.*(vector)`. Maybe the original implementer of the API wasnāt aware this wasnāt aware that the order of operation mattered. Unknown at this point.

Implementation looks like this:

By this point weāre unable to do anything about it. It would break almost every extension out there. But we could improve documentation.

Btw, I would also recommend being explicit in using `Geom::Vector3d`, `Geom::Point3d`, `Geom::Transformation` instead of using the `Array` shorthand notation. Eliminates ambiguity.

2 Likes