Citro3D: How to get 3D objects to always face the camera while picking up?

Discussion in '3DS - Homebrew Development and Emulators' started by delete12345, Aug 2, 2016.

  1. delete12345
    OP

    delete12345 GBAtemp Fan

    Member
    433
    179
    Feb 27, 2010
    United States
    Taipei, Taiwan
    You know about the Companion Cube in Portal, and noticed how when you pick the cube up and move the camera around, the cube's facing will always stay the same to the player's camera view.

    I'm working on something similar to that using matrix math.

    This is my current progress:

    [​IMG]

    And here's my current code:

    Code:
    void Player::Manipulate(std::shared_ptr<GameObject> obj, C3D_Mtx& currentViewMatrix, C3D_Mtx& modelMatrix){
        if (this->cameraManipulateFlag){
            //Matrix and C3D_Mtx.
            //C3D_Mtx requires all rows to be [WZYX], instead of the other way around (XYZW).
            //This is due to how the GPU reads the matrix data.
           
            C3D_Mtx inverse, result;
            Mtx_Copy(&inverse, &currentViewMatrix);
            Mtx_Inverse(&inverse);
           
            Mtx_Multiply(&result, &modelMatrix, &this->oldViewMatrix);
            Mtx_Multiply(&modelMatrix, &result, &inverse);
        }
        else {
            Mtx_Copy(&this->oldViewMatrix, &currentViewMatrix);
        }
    }
    
    As you can see, I am using the following algorithm to calculate the rotation:

    worldRotNew = worldRotOrig * viewRotOrig * inv(viewRotCurrent)

    However, it doesn't seem to be correct. When I set cameraManipulateFlag to true, the cube is slanted like a forward slash ( / ), which is when I'm picking up the cube. If the variable is set to false, I am not picking up the cube, so things are normal. I'm just trying to get it to rotate, so the cube always look towards the camera. I'm not worried about translation and scaling for now.

    My question is: How to get the cube to always look at the camera when rotating the player's camera?

    Attached is the current source code. I really do appreciate any help! Thanks!
     

    Attached Files:

  2. Urbanshadow

    Urbanshadow GBAtemp Maniac

    Member
    1,299
    476
    Oct 16, 2015
    Uhm. There's two aproximations to achieve this:

    1) You cancel the rotations and translations made to the scene in the object to center it on the screen. This makes the cube center itself in the viewport, while the scene moves. When you want to stop moving the cube you just, well, stop canceling the scene rotation on the cube (stop moving the cube, duh)

    2) You implement it so you can choose to perform the rotations and translations of the scene before or after drawing the object. This allows you to rotate the scene but it won't affect the object (since it's rendered after the translate/rotate). It will look centered on the screen until you decide otherwise. You ned update it's coordinates to where its new location is before choosing to be rendered before the rotations again.

    Either way is good, but #2 looks less intensive to the gpu to me.
     
    Last edited by Urbanshadow, Aug 2, 2016
    Quantumcat likes this.
  3. delete12345
    OP

    delete12345 GBAtemp Fan

    Member
    433
    179
    Feb 27, 2010
    United States
    Taipei, Taiwan
    Thanks for the approximations.

    However, I'm currently focused only on the rotations, and nothing but the rotations. No translations and scaling. The code will definitely be optimized later on.

    It's just the matrix math not giving me the right rotation when looking up, down, left, and right, that I'm not sure if the code I implemented for the rotation is correct or not.

    Maybe I should ask if the matrix math operations is programmed correctly or not...?
     
  4. Urbanshadow

    Urbanshadow GBAtemp Maniac

    Member
    1,299
    476
    Oct 16, 2015
    I have read the code, and I suggest you to change the way you work with the modelview matrix to a MtxStack instead.
    To setup two MtxStacks (one for projection and another from modelview) you can:
    Code:
       C3D_MtxStack projection, modelview;
    
       MtxStack_Init(&projection);
       MtxStack_Bind(&projection, GPU_VERTEX_SHADER, VSH_FVEC_projMtx, VSH_ULEN_projMtx);
       Mtx_Identity(MtxStack_Cur(&projection));
       MtxStack_Update(&projection);
    
       MtxStack_Init(&modelview);
       MtxStack_Bind(&modelview, GPU_VERTEX_SHADER, VSH_FVEC_mdlvMtx, VSH_ULEN_mdlvMtx);
       Mtx_Identity(MtxStack_Cur(&modelview));
       MtxStack_Update(&modelview);
    
    With MtxStacks you should be able to directly:
    Code:
    void Entity::Render(C3D_MtxStack *projection, C3D_MtxStack *modelview){
     
       // This makes a copy of the current modelview C3D_Matrix and puts it on top of the stack
        MtxStack_Push(modelview);
    
        // Rotation of just this object in space, MtxStack_Cur(modelview) gets the top modelview matrix from the stack
        Mtx_RotateX(MtxStack_Cur(modelview), rotation[0], true);
        Mtx_RotateY(MtxStack_Cur(modelview), rotation[1], true);
        Mtx_RotateZ(MtxStack_Cur(modelview), rotation[2], true);
    
        // Position of just this object in space
        Mtx_Translate(MtxStack_Cur(modelview),position[0],position[1],position[2]);
    
        MtxStack_Update(modelview);
    
        // Bind the texture if this object has a valid texture pointer
        if(texture != 0)
            C3D_TexBind(0, texture);
    
        // Any kind of draw should be placed here, inmediate or object buffer, doesn't matter.
        // (Even another entity! -->  anotherentity.Render(projection,modelview))
    
        // Once we do the pop, every change to modelview since the push is reverted.
        MtxStack_Pop(modelview);
    }
    You can see something similar to this code in action in the example provided by DevKitPro: graphics\gpu\gputest

    And spicing it up a little bit:

    If you declare an Entity class as pure virtual like this:
    Code:
      class Entity{
         public:
           virtual void Render(C3D_MtxStack*,C3D_MtxStack*) = 0;
       };
    
    Every class that inherits from entity, like Player (class Player : public Entity{}) has to provide the implementation of Render, as is pure virtual. Since every render-able class will inherit from Entity you can assume every Entity is render-able and is possible to make a vector or an array of Entities that can simply be rendered in order as in a Zbuffer this way:
    Code:
      void RenderEntities(C3D_MtxStack *projection, C3D_MtxStack *modelview, Entity *entities, int numEntities)
      {
        int i;
        for(i = 0; i < numEntities;i++)
          entities[i].Render(projection, modelview);
      }
    
    And the entities in said array can be anything that implements Entity: player, objects, particles... whatever.
    Check more info in pure virtual classes for c++ here.
     
    Last edited by Urbanshadow, Aug 2, 2016
  5. delete12345
    OP

    delete12345 GBAtemp Fan

    Member
    433
    179
    Feb 27, 2010
    United States
    Taipei, Taiwan
    Should the MtxStack class member be maintained in the engine.cpp (as part of the Core Engine)? Or it should be maintained as a Player Entity's class member? The way you put it, feels like it can be separated into a Player Entity's class member, instead of a "global Entity, Core Engine tiered" class member, therefore allowing all entities to have access to the MtxStack.

    EDIT: Also, what is VSH_FVEC_projMtx, and VSH_ULEN_projMtx? Where did you get this?

    EDIT 2: Nevermind, it's a uniform variable declared in the vertex shader of type .fvec .

    EDIT 3: No, that's not it. Couldn't get it to generate the VSH_FVEC_... macros and values.
     
    Last edited by delete12345, Aug 3, 2016
  6. delete12345
    OP

    delete12345 GBAtemp Fan

    Member
    433
    179
    Feb 27, 2010
    United States
    Taipei, Taiwan
    Is it possible not to use the C3D_MtxStack? A few reasons:
    • I get compiler errors telling me VSH_FVEC_projMtx and VSH_ULEN_projMtx is not defined nor declared. I have no idea how to produce these two values, let along binding the ModelView matrix stack afterwards.
    • I have already separated the ModelView matrices into 4 components: the projection matrix, the view matrix, the model matrix, and the world vertex positions set at origin of (0, 0, 0) in the source code.
    • I cannot fix errors related to setting the vertices to the right coordinates/positions in the vertex shaders.
    What I'm doing is I reset the model matrix (matrix that handles individual entity matrix transformations) to identity matrix each time I am rendering the entity. The end results look just like how matrix stacks work for the entities, and it works just the same because I am resetting every matrices that I touch upon. This is used in order to implement the Entity-Component System in the source codes.

    I'm still going to look into just the rotation part of the whole matrix transformation. I'm just wondering if the code to apply the matrix math rotation is correct or not.
     
  7. Urbanshadow

    Urbanshadow GBAtemp Maniac

    Member
    1,299
    476
    Oct 16, 2015
    You can check the example mentioned for vertex shader setup and you can still use Mtx_Rotate with a C3D_Matrix to compare the results with your code.
     
  8. delete12345
    OP

    delete12345 GBAtemp Fan

    Member
    433
    179
    Feb 27, 2010
    United States
    Taipei, Taiwan
    Please wait. I got notified Mtx_Inverse() is busted in Citro3D. I need to verify this first before continuing on.
     
  9. Urbanshadow

    Urbanshadow GBAtemp Maniac

    Member
    1,299
    476
    Oct 16, 2015
    Yeah, thats like the whole point of using MtxStack: to raise the matrix setup to the core, or managers and each entity have separated, individual render code compatible in any situation. Its just a responsability change.
     
    Last edited by Urbanshadow, Aug 3, 2016
  10. delete12345
    OP

    delete12345 GBAtemp Fan

    Member
    433
    179
    Feb 27, 2010
    United States
    Taipei, Taiwan
    Thanks!

    And yes, mtheall confirmed the Mtx_Inverse() I wrote for Citro3D is broken. :P I'm so sorry for all the troubles. The rotation math works.