Transforming 3D character accessories

Anything libgdx related goes here!

Transforming 3D character accessories

Postby Kestas Venslauskas » Mon Feb 17, 2014 11:38 am

Hi, I'm trying to find best solution for 3D character accessories transformation. For example I have my 3D character model and I want to put a gun (lets say it's bazooka, I like bazookas) in his hand. How do I achieve this in the right way? Currently I'm thinking to attach gun model to a characters bone that represents a gun transformation. How do I do that? Should I use the bone node or bone nodeParts or something? Maybe someone has tried something similar? :geek:
Kestas Venslauskas
 
Posts: 37
Joined: Fri Jun 29, 2012 11:36 am
Location: Vilnius, Lithuania

Re: Transforming 3D character accessories

Postby Serapth » Mon Feb 17, 2014 2:59 pm

I'm actually working on and writing about this very process as we speak.

(
http://www.gamefromscratch.com/post/201 ... games.aspx

http://www.gamefromscratch.com/post/201 ... ected.aspx

http://www.gamefromscratch.com/post/201 ... ender.aspx
)


And I can tell you with 100% of my soul that bones are NOT the way to go! That said, they do certainly seem like the most obvious choice!

There are a couple reasons for this.

First, LibGDX doesn't really have a scene graph, so if you are thinking it's as simple as parent bone A on model1 to bone B on model 2, it isn't.

That said, rolling your own functionality here isn't a big deal, but...

Bones are represented as the head of the bone, not the tip. Bones basically home a series of joints once exported. This means if you get the position of a bone, it will be where the bone started, not where it ends. So you think, simple enough, I will just create a bone that extends from the point where I want the weapon to mount! Good call, I thought that too... Problem is, bones that are external to your mesh aren't processed!

In the end, I found creating a null named object where I want to mount as the best current option. Once I get over this ;:&/&&zing cold, I'll write about the process.


Anyways, TL;DR, don't use bones.
Serapth
 
Posts: 178
Joined: Wed Nov 27, 2013 8:05 pm

Re: Transforming 3D character accessories

Postby Kestas Venslauskas » Tue Feb 18, 2014 6:57 am

Looking forward to read about your research :)
BTW I achieved weapon binding to bone. But currently I'm at work so ill post about it later on. Still needs testing before I can publish :)

Problem is, bones that are external to your mesh aren't processed!

What do you mean by that? I can see all of my bones in ModelInstance object.

As I remember I did something like this:
Code: Select all
      
               ModelInstance weaponModel = ...;
      ModelInstance characterModel = ...;
      
      ...

      Node weaponBone = characterModel.getNode("weapon_bone");
      Vector3 possition = Vector3.Zero;
      weaponBone.globalTransform.getTranslation(possition);
      
      weaponModel.transform.set(characterModel.transform.cpy().mul(weaponBone.calculateWorldTransform()));
      weaponModel.transform.setTranslation(possition);


I think this code isn't even working but you get the idea... As I said I'll post a real code later today.
The key here is to set the weapon model origin to the right place where you want it to be attached with the bone.
But the issue here is optimization. Calling last 3 lines of the code every frame isn't so optimal solution I guess. :?
Kestas Venslauskas
 
Posts: 37
Joined: Fri Jun 29, 2012 11:36 am
Location: Vilnius, Lithuania

Re: Transforming 3D character accessories

Postby Serapth » Tue Feb 18, 2014 1:08 pm

What I mean is, only bones within the mesh are updated. So if you create bones in your armature that are external to the models mesh, they aren't updated.

So, I had no issue at all binding a model to a bone within the mesh itself. It's when I tried mounting to bones that were external that everything fell on its ass.

Consider the typical bone structure of an arm and mounting a sword to the players hand. Your armature would typically look like Shoulder->UpperArm->LowerArm->Hand, so what I would want to do is bind to the hand bone. Thing is, at least from Blender, what is exported is the head of the bone, so Hand would actually be at the wrist. The location I would actually like to bind to would be palm, which is actually external to the mesh. If I create an additional bone at the very edge of the mesh, I should be able to bind to it. The problem is, since this bone is external it isn't updated ( doesn't get translated,etc ), so you can't use it. This is where things fell apart for me.
Serapth
 
Posts: 178
Joined: Wed Nov 27, 2013 8:05 pm

Re: Transforming 3D character accessories

Postby xoppa » Tue Feb 18, 2014 4:57 pm

To attach an accessory to a ModelInstance, adding a nodepart or sub-node (use a sub-node, if you need to apply a transformation relative to the parent node), should suffice.

"Updating" of Nodes within a Model (ModelInstance) does not depend on whether the Node is used as a bone for skinning. See also:
http://libgdx.badlogicgames.com/nightli ... ransforms()

Note however, that if you're using skinning, that the (invisible) nodes of the skeleton/armature does not have to match the visible representation. That is: skinning is simply transforming vertices with the transformation of one or more nodes, relative to the transformation of that node at bind position. See also:
https://github.com/libgdx/libgdx/wiki/3 ... d-skinning

You should make sure this matches in your modeling application. If you're using Blender, keep in mind that Blender uses a Z-up axis system. See also: https://github.com/libgdx/libgdx/wiki/I ... -in-LibGDX

The tests include a SkeletonTest, which you can use to visualize the (normally invisible) skeleton/armature nodes:
https://github.com/libgdx/libgdx/blob/m ... nTest.java
See it in action: http://www.youtube.com/watch?v=8E0gtpBTfA0
xoppa
 
Posts: 689
Joined: Thu Aug 23, 2012 11:27 pm

Re: Transforming 3D character accessories

Postby Serapth » Tue Feb 18, 2014 6:09 pm

Hi Xoppa, as this is something I have been fighting with the last week and what i've experienced seems to contradict some of what you are saying I would love your insight on a few things here.

When you have two completely seperate ModelInstances, what is the best way to parent the root node of one to a node in the other? From what I saw/experienced, this would require the child to track and update the actions of a parent on a frame by frame basis, is this not the case?

Most importantly, this is the situation I am running into trouble with. If I have a hierarchy like this:

Image

The position of that final node, the one external to the mesh, seems to never be updated. If I get the position of any other bone it returns correctly, just not ones external to the mesh. I notice in exports, this bone isnt included in the bones collection of the geometry ( although is in the armature ), which is what led me to believe external bones are not updated. Manually calling CalculateTransforms seems to have no effect either way. I've tried this scenario with a dozen different exported meshes, always the same behaviour.
Serapth
 
Posts: 178
Joined: Wed Nov 27, 2013 8:05 pm

Re: Transforming 3D character accessories

Postby Kestas Venslauskas » Tue Feb 18, 2014 6:15 pm

The magic lines below:

Call this every frame
Code: Select all
         Matrix4 boneWorldTransform = character.transform.cpy().mul(character.getNode("gun_L").globalTransform);
         //Probably no need to flip axes in fbx-conv but since we did ill rotate transform for now manualy.
         boneWorldTransform.rotate(Vector3.Z, -90);
         gun.transform.set(boneWorldTransform);


Here's the result:
http://youtu.be/C9EjeMyjlnA

I found this solution easiest. Also I tried adding gun nodes to character gun bone as a child but couldn't get the result I wanted.
Kestas Venslauskas
 
Posts: 37
Joined: Fri Jun 29, 2012 11:36 am
Location: Vilnius, Lithuania

Re: Transforming 3D character accessories

Postby Serapth » Tue Feb 18, 2014 8:03 pm

Ok, this is going to drive me to drink!

I set out to create a sample illustrating the problem I've been having.

Basically I created a simple model like this, with an external bone:

Image

Then I used this following sample program. All I did was draw a sphere around each bone in the armature:

Code: Select all
package com.gamefromscratch;

import com.badlogic.gdx.ApplicationListener;
import com.badlogic.gdx.Files.FileType;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.GL10;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.PerspectiveCamera;
import com.badlogic.gdx.graphics.VertexAttributes.Usage;
import com.badlogic.gdx.graphics.g3d.Environment;
import com.badlogic.gdx.graphics.g3d.Material;
import com.badlogic.gdx.graphics.g3d.Model;
import com.badlogic.gdx.graphics.g3d.ModelBatch;
import com.badlogic.gdx.graphics.g3d.ModelInstance;
import com.badlogic.gdx.graphics.g3d.attributes.ColorAttribute;
import com.badlogic.gdx.graphics.g3d.loader.G3dModelLoader;
import com.badlogic.gdx.graphics.g3d.model.Node;
import com.badlogic.gdx.utils.JsonReader;
import com.badlogic.gdx.graphics.g3d.utils.AnimationController;
import com.badlogic.gdx.graphics.g3d.utils.ModelBuilder;
import com.badlogic.gdx.math.Vector3;


public class TankDemo implements ApplicationListener {
    private PerspectiveCamera camera;
    private ModelBatch modelBatch;
    private AnimationController animationController;
   
    private Model model;
    private ModelInstance modelInstance;

    private Model pivot;
    private ModelInstance p1, p2, p3;
    private Node bone1,bone2,bone3;
   
    private Environment environment;
   
    @Override
    public void create() { 
       camera = new PerspectiveCamera(
             75,
                Gdx.graphics.getWidth(),
                Gdx.graphics.getHeight());
       
        camera.position.set(0f,0f,-8f);
        camera.lookAt(0f,0f,0f);
        camera.near = 0.1f;
        camera.far = 300.0f;

        modelBatch = new ModelBatch();
       
        JsonReader jsonReader = new JsonReader();
        G3dModelLoader modelLoader = new G3dModelLoader(jsonReader);
        model = modelLoader.loadModel(Gdx.files.getFileHandle("data/demo.g3dj", FileType.Internal));
        modelInstance = new ModelInstance(model);
       
        animationController = new AnimationController(modelInstance);
        animationController.animate("Default Take",-1,null,0);
       
        bone1 = modelInstance.getNode("Bone");
        bone2 = modelInstance.getNode("Bone_001");
        bone3 = modelInstance.getNode("Bone_002");
       
        ModelBuilder mb = new ModelBuilder();
       
        pivot = mb.createSphere(0.5f,0.5f,0.5f,10,10,GL20.GL_LINES,new Material(ColorAttribute.createDiffuse(Color.RED)),Usage.Position | Usage.Normal);
        p1 = new ModelInstance(pivot);
        p2 = new ModelInstance(pivot);
        p3 = new ModelInstance(pivot);
       

       
        environment = new Environment();
        environment.set(new ColorAttribute(ColorAttribute.AmbientLight, 0.8f, 0.8f, 0.8f, 1.0f));
    }

    @Override
    public void dispose() {
        modelBatch.dispose();
        model.dispose();
    }

    @Override
    public void render() {
        Gdx.gl.glViewport(0, 0, Gdx.graphics.getWidth(), Gdx.graphics.getHeight());
        Gdx.gl.glClearColor(1, 1, 1, 1);
        Gdx.gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);
       
        animationController.update(Gdx.graphics.getDeltaTime());
        Vector3 pos = new Vector3();
        bone1.globalTransform.getTranslation(pos);
        p1.transform.set(bone1.globalTransform);
        p2.transform.set(bone2.globalTransform);
        p3.transform.set(bone3.globalTransform);
       
        camera.update();
        modelBatch.begin(camera);
        modelBatch.render(modelInstance, environment);
        modelBatch.render(p1, environment);
        modelBatch.render(p2, environment);
        modelBatch.render(p3, environment);
       
        modelBatch.end();
    }

    @Override
    public void resize(int width, int height) {
    }

    @Override
    public void pause() {
    }

    @Override
    public void resume() {
    }

}


My expectations was for the first two bones to be updated, while the third bone ( the one external ) wouldn't. Instead it would be positioned at 0,0,0.

The result:

Image

or without the main mesh rendering:

Image


FU#@#$WEFSDFSDFSF@#$KK!!!! What you see is exactly what you should expect to happen.

Seriously I have done this a few dozen times before now and this is the first time I've seen this ( proper ) behavior. I am on a different machine than normal, but that simply shouldn't matter. Hell, im sure I tried the same thing on this computer a few dozen times as well.

I am simply flabbergasted at the results. Frankly all of my experiences since working with Blender export have been along this lines... animations that suddenly stop working, textures that export from 2.68 but not 2.69, but I dont know what magical part of my process is causing the gremlins.

Quite literally every single other time I've done this, the GlobalTransform returned by the external bone(s) was 0,0,0. EVERY single time.


ARGGGHHHHH.


As a secondary question... is there a simply way to render a ModelInstance as wireframe?
Serapth
 
Posts: 178
Joined: Wed Nov 27, 2013 8:05 pm

Re: Transforming 3D character accessories

Postby Kestas Venslauskas » Tue Feb 18, 2014 8:20 pm

is there a simply way to render a ModelInstance as wireframe?


Here xoppa talks about primitiveTypes of renderable objects.
Maybe there is a type for wireframe?

http://blog.xoppa.com/creating-a-shader-with-libgdx/
Kestas Venslauskas
 
Posts: 37
Joined: Fri Jun 29, 2012 11:36 am
Location: Vilnius, Lithuania

Re: Transforming 3D character accessories

Postby Serapth » Tue Feb 18, 2014 8:54 pm

Thanks for that, Yeah, I actually got it working going almost exactly that way, using the same example.

Code: Select all
renderable = new Renderable();
        modelInstance.nodes.get(0).parts.get(0).setRenderable(renderable);
        renderable.environment = environment;
        renderable.primitiveType = GL20.GL_LINES;
       
        shader = new DefaultShader(renderable);
        shader.init();
       
        renderContext = new RenderContext(new DefaultTextureBinder(DefaultTextureBinder.WEIGHTED));


Then:
Code: Select all
        camera.update();
        modelBatch.begin(camera);
       
        modelBatch.render(p1, environment);
        modelBatch.render(p2, environment);
        modelBatch.render(p3, environment);
       
        modelBatch.end();
        shader.begin(camera, renderContext);
        shader.render(renderable);
        shader.end();


And this does exactly what I want, but it feels like I am performing a gross hack. It certainly didn't feel "simple" :)

I have a feeling there is a simpler way to do it, something that can be done as part of the existing ModelBatch. Something like

modelInstance.primitiveType = GL20.GL_LINES;

But ive not found it. Maybe this is the way it is done.
Serapth
 
Posts: 178
Joined: Wed Nov 27, 2013 8:05 pm

Next

Return to Libgdx

Who is online

Users browsing this forum: MSN [Bot] and 1 guest