UNSOLVED GetMg() returns not the actual coordinates

User Information:
Cinema 4D Version: R23
Platform: Windows ;
Language(s) : C++ ;


Good day.

I am creating the Cube as a child of an object. I set its local position as the SetRelPos(0, x, 0) where X = the half of the distance toward the next child-object.
After the command InsertUnder(), I am trying to find the point which lies on the Cube's local X-axis (+ 10 from its root). And then I need to get the point's:

  1. world coordinates.
  2. coordinates relative to some another object.

How to achieve this?

Right now I even can't found the +10 local point. I tried to get it via MatrixMove() and GetMg() but GetMg() returns not the actual Cube coordinates.

Hello @yaya,

thank you for reaching out to us. I must first point out that it is better to provide explicit code for such questions, as we then have to write less boiler plate code when answering, and also assume less.

A point in the local coordinate system of an object (or anything else as for example a vertex or polygon) is just that point. So, the point (0, 10, 0) in local coordinates for the object cube, is just that, (0, 10, 0). To convert such local point back to global coordinates, one must multiply it with the transform of the coordinate system it is associated with, e.g., the global matrix of an object. I have provided below a code example which goes over some basics for your specific case.

Such questions are all more less just vector math/linear algebra questions which are formally out of scope of support. We have the Matrix Manal in Python where I went over some stuff, but we cannot explain all the math in our APIs. We recognize that there are more important mathematical concepts where it is in our interest to explain them, but for now C++ receives less attention, as we assume users there to be more technically oriented than our Python users. I must point out that I cannot lead you step by step to following linear algebra questions.

I know that is likely not what you wanted to hear, but we must throttle user requests in some form. Questions about our APIs are of course still welcome.

Thank you for your understanding,
Ferdinand

The result:
r23_matrix_stuff.gif

The code:

/*Simple example for transforming points in and out of coordinate systems.

The example will look for the first object in the scene, place cube between the position of that
object and its first child, then compute two points in relation to these objects, and finally
insert two speheres, representing these points.

As discussed in:
  https://plugincafe.maxon.net/topic/13895/
*/
#include "c4d.h"
#include "c4d_symbols.h"
#include "main.h"
#include "maxon/lib_math.h"

const maxon::Int32 PID_1059349 = 1059349;

// A boiler-plate CommandData plugin, it just runs the example.
class PC1059349: public CommandData
{
  INSTANCEOF(PC1059349, CommandData)

public:
  virtual Bool Execute(BaseDocument* doc, GeDialog* parentManager);
};

/// Provides the example.
Bool PC1059349::Execute(BaseDocument* doc, GeDialog* parentManager)
{
  if (doc == nullptr)
    return false;

  // Get the first object, its first child, and allocate a cube object.
  BaseObject* const parent = doc->GetFirstObject();
  if (parent == nullptr)
    return true;

  BaseObject* const child = parent->GetDown();
  if (child == nullptr)
    return true;

  BaseObject* cube = BaseObject::Alloc(Ocube);
  if (child == nullptr)
    return true;

  // Insert the cube as the first child under #parent and set its global position to be half way 
  // between #parent and #child.
  cube->InsertUnder(parent);

  Matrix globalCubeTransform;
  // The offset in global space of the cube is being set as the t=.5 linear interpolation between
  // the global offset of the parent and the child.
  globalCubeTransform.off = maxon::Blend(parent->GetMg().off, child->GetMg().off, 0.5);
  cube->SetMg(globalCubeTransform);

  // Print out the local and global transform of #cube.

  // This is the transform of #cube in relation to parent.
  ApplicationOutput("Cube.GetMl(): @", cube->GetMl());
  // This is the transform of #cube in relation to the world coordinate system. It is equal to the
  // product of the local transform of #parent and #cube. One should use global transform when
  // ever possible, as operating in local coordinate systems can complicate things.
  ApplicationOutput("Cube.GetMg(): @", cube->GetMg());

  // Constructing the point (0, 200, 0) in the local coordinate system of #cube. There is nothing 
  // else to it, the purpose of a local coordinate system is that it is in relation to some 'thing'.
  const Vector pLocal(0, 200, 0);

  // Translate that point into global coordinates: For that, the point must be transformed by the
  // coordinate system (i.e., transform/matrix) it is living in, i.e., the global transform of
  // #cube in this case. Note that matrix multiplication in the C++ API forces one to use the
  // Matrix first form for matrix-vector transforms. It must be M * p and cannot be p * M.
  const Vector pGlobal = cube->GetMg() * pLocal;

  // Print out both coordinates.
  ApplicationOutput("pLocal: @", pLocal);
  ApplicationOutput("pGlobal: @", pGlobal);

  // Insert that point as a red sphere into the document.
  BaseObject* sphereRed = BaseObject::Alloc(Osphere);
  if (sphereRed == nullptr)
    return true;

  // Turn on the display color and set it to red.
  sphereRed->SetParameter(DescID(ID_BASEOBJECT_USECOLOR), true, DESCFLAGS_SET::NONE);
  sphereRed->SetParameter(DescID(ID_BASEOBJECT_COLOR), Vector(1, 0, 0), DESCFLAGS_SET::NONE);

  sphereRed->InsertUnder(cube);

  // Even though a global coordinate is being set for #sphereRed, the object can be still parented 
  // to another object, Cinema 4D will resolve the details. This will result in #sphereRed being
  // located directly 'above' #cube (i.e., (0, 200, 0) in local coordinates of #cube).

  Matrix mgPointRed;
  mgPointRed.off = pGlobal;

  sphereRed->SetMg(mgPointRed);

  // Now we are going to convert the global point #pGlobal to a local coordinate system. We choose
  // #child for that, i.e., express #pGlobal in the coordinate system of #child.
 
  // This can be done by multiplying the point by the inverse global transform of the object we want
  // to convert the global point to. The ~ operator returns the inverse of a matrix/transform.
 
  Vector pLocalChild = ~child->GetMg() * pGlobal;

  // But when we now would insert another sphere with #pLocalChild as the offset of its local 
  // transform, and with this sphere was being parented to #child, this would result in the red 
  // sphere and the other sphere overlapping, as #pGlobal and #pLocalChild would then be same point,
  // just expressed in different coordinate systems. 

  // Instead, we are going to first compute the global point #-pLocal, i.e., a point which will fit
  // a sphere directly below the cube, and then convert that point into the local coordinate system 
  // of child. Note that matrix multiplication is not commutative, i.e., A * B != B * A, so the
  // order "~child->GetMg() * cube->GetMg()" matters here.
  pLocalChild = ~child->GetMg() * cube->GetMg() * -pLocal;

  // Insert a sphere object under #child for the second point.
  BaseObject* sphereBlue = BaseObject::Alloc(Osphere);
  if (sphereBlue == nullptr)
    return true;

  sphereBlue->SetParameter(DescID(ID_BASEOBJECT_USECOLOR), true, DESCFLAGS_SET::NONE);
  sphereBlue->SetParameter(DescID(ID_BASEOBJECT_COLOR), Vector(0, 0, 1), DESCFLAGS_SET::NONE);

  sphereBlue->InsertUnder(child);

  // Set the local transform of #sphereBlue with SetMl() to pLocalChild.
  Matrix mlPointBlue;
  mlPointBlue.off = pLocalChild;

  sphereBlue->SetMl(mlPointBlue);

  // Let Cinema 4D catch up to the document modifications which have been made.
  EventAdd();

  return true;
}

// Register the plugin.
PC1059349* g_cmd;

Bool RegisterPC1059349()
{
  g_cmd = NewObjClear(PC1059349);
	return RegisterCommandPlugin(PID_1059349, "pc1059349"_s, 0, nullptr, ""_s, g_cmd);
}

Hi @ferdinand
Thank you for your answer. I will learn it now carefully and will try to adapt it to C++ and my task.

I understand that the algebra question is not the case of this forum. I just ran into a problem with the GetMg() command which has returned not the expected world position of the child-cube.

I will add my code below:

Vector64 direction = parent->GetMg().off - child->GetMg().off; 
Vector64 middlePos = parent->GetMg().off - direction / 2;
// here I get the middlePos. Normally it is the world coordinates of the Cube Rel-position (0, 10, 0), 
// I need middlePos for the GeRayCollider as its ray source. Next I need to get the ray direction. 
// I need it 1) be relative to Cube local X-axis, 2) add to it some random to use it in the loop (the second is not the part of the problem)
// so here where I get the problem
Matrix shiftX = MatrixMove(Vector(-10, floatrandom(-2, 2), floatrandom(-2, 2)));
Matrix RayPosX = cube->GetMg() * shiftX; // !!! I don`t understand why this shifts the ray along the parent space (X-axis), but not the Cube's X-axis which might not match?
Vector rayDirectionX = middlePos + cube->GetMg().off - RayPosX.off; 
if (colliderX->Intersect(middlePos, rayDirectionX, 555)) 
// etc.

If I will print the current Cube->GetMg().off and the middlePos - it will be not the same. And in the result the whole code is not working as expected. 9ef14df7-fa1d-4c5a-aff3-0bddf21a702f-image.png

Hey @yaya,

I cannot tell you much about your code, especially the printouts, since you do not include where and when you print stuff and how you set the global matrix of cube (your code never calls cube->SetMg()).

But I can tell you why this:

Matrix shiftX = MatrixMove(Vector(-10, floatrandom(-2, 2), floatrandom(-2, 2)));
Matrix RayPosX = cube->GetMg() * shiftX; // !!! I don`t understand why this shifts the ray along the parent space (X-axis), but not the Cube's X-axis which might not match?

does not work. You here multiplying the global transform of cube by another transform, which by definition cannot be a point. If you just want some point p which is offset by random amount from the origin of cube, you should do this:

const Vector p = cube->GetMg() * Vector(-10, floatrandom(-2, 2), floatrandom(-2, 2));

If you indeed want a frame/transform/matrix for your ray origin, which would strike me as odd, I would need to see more context. It is hard to say just from this snippet. Have you tried inverting the order, i.e., shiftX * cube->GetMg() instead of cube->GetMg() * shiftX, since matrix multiplication is not commutative, to see if this will give you the desired result?

Cheers,
Ferdinand

Hey @ferdinand ,
My problem is: when I do this:

const Vector p = cube->GetMg() * Vector(-10, floatrandom(-2, 2), floatrandom(-2, 2));

I get the point shifted by -10 units by X-axis but by parent's X-axis. But I need it in the cube's X-axis.
Is it expected behavior for Cinema 4D?

@ferdinand said in GetMg() returns not the actual coordinates:

If you indeed want a frame/transform/matrix for your ray origin, which would strike me as odd, I would need to see more context.

I am making the plugin which creates the ragdoll from a user's character.
The part we are talking about appears from here https://youtu.be/Lz3u3n001bQ?t=100 after clicking the "Create Ragdoll" button:

So after this step, the code calculates a lot of stuff to create Connectors, tags, colliders etc for the ragdoll structure. And now we are talking about the algorithm for the calculation of the sizes of cube-colliders.
In basic words it uses the GeRayColliders and calculates the distance from the center of the bone to the character-mesh. And then feeds this distance to a Cube-collider sizes. However It is little bit more complicated than it sounds, but basically I need to receive the ray from the cube-origin by its local X-axis (and Z-axis too) and then use this double-distance in the cube x (and z) size.
So I need to get the geRayCollider ray direction vector. This vector should be almost the same as the cube's local x-axis (and z) + some random.

And my problem is when I trying to find the point of the local cube's x-axis using the code snippet above I am receive the shift by the X-axis of parent and not the cube. The axes of cube and its parent may not match. The x-axis of a cube can be the z-axis of its parent, etc. So the problem is something here: cube->GetMg() * Vector(10, 0, 0)

Hey yaya,

@yaya said in GetMg() returns not the actual coordinates:

Hey @ferdinand ,
My problem is: when I do this:

const Vector p = cube->GetMg() * Vector(-10, floatrandom(-2, 2), floatrandom(-2, 2));

I get the point shifted by -10 units by X-axis but by parent's X-axis. But I need it in the cube's X-axis.
Is it expected behavior for Cinema 4D?

No, this is not the expected behavior, and I can say with near 100% certainty that this is also not what will happen (there could always be some very fringe bug, but for something as fundamental as matrices, it is very unlikely).

You are likely making a mistake in another area of your code or in the scene files you use. My example also shows this exact operation, and it works there. This a very straight forward operation, my suspicion would also be that you still misunderstand something, either what global and local means, or more generally how linear transforms work. If you think there is a bug, you can easily test this on a sheet of paper or by pointing out that a frame or offset of a global or local matrix is not what it should be. But I would be surprised if you were able to do that.

I am making the plugin which creates the ragdoll from a user's character ...

I would recommend that you share executable code with us, either here or via e-mail if you are unable to share code publicly. See the Forum Guidelines: Confidential Data for details on the process. I was probably a bit unclear on what I meant with context, but what I meant is why your RayPosX

Matrix RayPosX = cube->GetMg() * shiftX; // !!! I don`t understand why this shifts the ray along the parent space (X-axis), but not the Cube's X-axis which might not match?

is matrix and not a point, as it would be more logical, as it would be more logical from its name and intended purpose. When you have some object obj, and you want to construct a ray with the ray direction rd of the global x-axis of obj from a ray origin ro, the (pseudo) code would be:

// As global points.
const Matrix mg = obj->GetMg();
const Vector ro = mg * Vector(200, 0, 0); // The ray starts at (200, 0, 0) in the coordinate system of #obj
const Vector rd = mg.v1 // For the ray direction we can reuse here just the x-axis of the object

I would also point out that GeRayCollider does operate in point coordinates, i.e., local coordinates, of the object which is being ray-casted. At least to me it is not clear if you are doing that. So, when you have an object cube, and you want to ray-cast from ro, which is at (200, 0, 0) within the coordinate system of cube, into the direction rd, which happens to be equal to the x/v1/i-component of the cube frame, intersection-testing the object target, then you must convert both ro and rd into the coordinate system of target. E.g., something like this:

const Vector roGlobal = mgCube * Vector(200, 0, 0); 
const Vector rdGlobal = mgCube.v1

const Vector roLocalTarget = ~mgTarget * ro;
const Vector rdLocalTarget = ~mgTarget * rd;

PS: I am on vacation now, so someone else will take over this thread.

Cheers,
Ferdinand