dust-engine / dot_vox

Rust parser for MagicaVoxel .vox files.
MIT License
51 stars 20 forks source link

scene graph parsing #17

Closed m4b closed 2 years ago

m4b commented 5 years ago

It looks like the only remaining major feature left is to extract the scene graph information:

(d) Scene Graph

T : Transform Node
G : Group Node
S : Shape Node

     T
     |
     G
    / \
   T   T
   |   |
   G   S
  / \
 T   T
 |   |
 S   S

=================================
(1) Transform Node Chunk : "nTRN"

int32   : node id
DICT    : node attributes
      (_name : string)
      (_hidden : 0/1)
int32   : child node id
int32   : reserved id (must be -1)
int32   : layer id
int32   : num of frames (must be 1)

// for each frame
{
DICT    : frame attributes
      (_r : int8) ROTATION, see (c)
      (_t : int32x3) translation
}xN

=================================
(2) Group Node Chunk : "nGRP" 

int32   : node id
DICT    : node attributes
int32   : num of children nodes

// for each child
{
int32   : child node id
}xN

=================================
(3) Shape Node Chunk : "nSHP" 

int32   : node id
DICT    : node attributes
int32   : num of models (must be 1)

// for each model
{
int32   : model id
DICT    : model attributes : reserved
}xN

which is an extension https://github.com/ephtracy/voxel-model/blob/master/MagicaVoxel-file-format-vox-extension.txt

This doesn't seem like a whole lot of work since much of the groundwork is already laid, but i'm not really sure?

Sixmorphugus commented 3 years ago

hi..... I've just come across the library and i need this, is anyone working on a PR to do it or should I do it myself?

davidedmonds commented 3 years ago

To the best of my knowledge its not being worked on at the moment - PRs are more than welcome.

Sixmorphugus commented 3 years ago

alright, thank you

Sixmorphugus commented 3 years ago

Okay, I'm reading in the scene graph and layer chunks now over at my fork. I'm not yet entering this information into the DotVoxData because I'm not 100% sure about the best format for a few reasons:

One last thing: I've had to put #![allow(missing_docs)] at the top of scene.rs due to all the pub parse macros generating "requires documentation" errors when I try to compile them. I have no idea why. In model.rs, macros are defined the exact same way and there is no error. I would preferably like to not include this in the final PR but I am not too familliar with this part of Rust besides knowing that, because of how doc comments work, you can't put a documentation comment on a function declared inside a macro from outside it.

Edit: just ran cargo fmt, will push soon.

Sixmorphugus commented 3 years ago

Another problem I have is that when I read in the attributes Dict for a transform node, which is supposed to contain "_t" and "_r", the respective translation and rotation, my debugger tells me that it contains no keys at all. I am not sure why this is.

Edit: Oops! Those are in frame attributes. However, the frame Dicts contain no data either.

virtualritz commented 3 years ago

Another problem I have is that when I read in the attributes Dict for a transform node, which is supposed to contain "_t" and "_r", [...]

@Sixmorphugus Have you looked at the tracker of the voxel-model repo? Specifically this one? That thread mentions a (still open) PR that may help explain things.

I would just file issues with each of your questions in that tracker.

Sixmorphugus commented 3 years ago

Another problem I have is that when I read in the attributes Dict for a transform node, which is supposed to contain "_t" and "_r", [...]

@Sixmorphugus Have you looked at the tracker of the voxel-model repo? Specifically this one? That thread mentions a (still open) PR that may help explain things.

I would just file issues with each of your questions in that tracker.

Ah, that's a good idea. Thanks.

Sixmorphugus commented 3 years ago

Good news! I've imported and rendered out a full scene built in MagicaVoxel in my game with my fork of dot_vox.

image A4hS3lDwoW

This means the work for reading the scene graph node chunks is basically all done, however I do want to add some general utility functions in another file for reading the transform node frame locations as the Dict they come in by default is difficult to comprehend and rather bad to work with. I'm also going to add some tests and then I'll make a PR.

virtualritz commented 3 years ago

@Sixmorphugus Your fork looks good.

Any chance for some example code that uses these new Scene* types?

Sixmorphugus commented 3 years ago

@Sixmorphugus Your fork looks good.

Any chance for some example code that uses these new Scene* types?

Hi, I've been quite busy recently but I can show the function I made in my bevy_vox changes to read scene graphs:

/// Recursive scene reader function
fn read_scene_node(scene: &Vec<SceneNode>, model_handles: &Vec<Handle<Mesh>>, current_node: usize, mut world: &mut World, cumulative_position: Vec3, cumulative_rotation: Quat) {
    match &scene[current_node] {
        SceneNode::Transform {attributes, frames, child} => {
            // For now we care only for the content of the first frame.
            let mut position = Vec3::default();
            let mut rotation = Quat::default();

            if frames.len() == 1 {
                if let Some(value) = frames[0].get("_t") {
                    let values : Vec<&str> = value.split(" ").collect();

                    if values.len() == 3 {
                        position = Vec3::new(values[0].parse::<f32>().unwrap(), values[2].parse::<f32>().unwrap(), values[1].parse::<f32>().unwrap());
                    }
                }

                if let Some(value) = frames[0].get("_r") {
                    let byte_rotation = value.parse::<u8>().unwrap();

                    // .vox stores a row-major rotation in the bits of a byte.
                    //
                    // for example :
                    // R =
                    //  0  1  0
                    //  0  0 -1
                    // -1  0  0
                    // ==>
                    // unsigned char _r = (1 << 0) | (2 << 2) | (0 << 4) | (1 << 5) | (1 << 6)
                    //
                    // bit | value
                    // 0-1 : 1 : index of the non-zero entry in the first row
                    // 2-3 : 2 : index of the non-zero entry in the second row
                    // 4   : 0 : the sign in the first row (0 : positive; 1 : negative)
                    // 5   : 1 : the sign in the second row (0 : positive; 1 : negative)
                    // 6   : 1 : the sign in the third row (0 : positive; 1 : negative)

                    // First two indices
                    let index_nz1 = (byte_rotation & 0b11) as usize;
                    let index_nz2 = ((byte_rotation & 0b1100) >> 2) as usize;

                    // You get the third index out via a process of elimination here. It's the one that wasn't used for the other rows.
                    let possible_thirds = [
                        index_nz1 == 0 || index_nz2 == 0,
                        index_nz1 == 1 || index_nz2 == 1,
                        index_nz1 == 2 || index_nz2 == 2,
                    ];

                    let mut index_nz3 = 0;

                    for i in 0..possible_thirds.len()
                    {
                        if possible_thirds[i] == false
                        {
                            index_nz3 = i;
                        }
                    }

                    // Values of all three columns (1 or 0)
                    let val_1 = if (byte_rotation & 0b1_0000) >> 4 == 1 { -1. } else { 1. };
                    let val_2 = if (byte_rotation & 0b10_0000) >> 5 == 1{ -1. } else { 1. };
                    let val_3 = if (byte_rotation & 0b100_0000) >> 6 == 1 { -1. } else { 1. };

                    // Columns as read from file
                    let mut initial_cols: [[f32; 3]; 3] = [
                        [ 0.,  0.,  0.],
                        [ 0.,  0.,  0.],
                        [ 0.,  0.,  0.],
                    ];

                    initial_cols[0][index_nz1] = val_1;
                    initial_cols[1][index_nz2] = val_2;
                    initial_cols[2][index_nz3] = val_3;

                    let initial_matrix = Mat3::from_cols_array_2d(&initial_cols);

                    // Convert!
                    /*
                    let mut cols : [[f32; 3]; 3] = [
                        [ initial_cols[0][0],  0.,  initial_cols[1][0]],
                        [ 0.,  1.,  0.], // this is ALWAYS this value
                        [ initial_cols[0][1],  0.,  initial_cols[1][1]],
                    ];

                    // Make a Mat3
                    let matrix = Mat3::from_cols_array_2d(&cols);
                    */

                    // We need to rotate the initial matrix by this to make it actually correct in bevy.
                    let magicavoxel_identity = Mat3::from_cols_array_2d(&[
                        [-1.,  0.,  0.],
                        [ 0.,  0.,  1.],
                        [ 0.,  1.,  0.],
                    ]);

                    let matrix = magicavoxel_identity.transpose().mul_mat3(&initial_matrix).mul_mat3(&magicavoxel_identity);

                    // Convert the matrix rotation to a Quat
                    rotation = Quat::from_rotation_mat3(&matrix);
                }
            } else { panic!("Non-1 number of frames in .vox transform scene node ({})", frames.len()) }

            // Update the cululative translation and rotation
            let cumulative_position = cumulative_position + position;
            let cumulative_rotation = rotation; //cumulative_rotation.mul_quat(rotation);

            // Parse the child node
            read_scene_node(scene, model_handles, *child as usize, world, cumulative_position, cumulative_rotation);
        },
        SceneNode::Group {attributes, children} => {
            // In comparrison to transforms (dear god) this one is pretty straightforward.
            for i in children {
                read_scene_node(scene, model_handles, *i as usize, world, cumulative_position, cumulative_rotation);
            }
        },
        SceneNode::Shape {attributes, models} => {
            // For now we only care about the first model.
            if models.len() == 1 {
                // Spawn a voxel model in the world with the correct mesh
                world
                    .spawn()
                    .insert_bundle(MarlaMeshBundle {
                        mesh: model_handles[models[0].model_id as usize].clone(),
                        transform: Transform {
                            translation: cumulative_position,
                            rotation: cumulative_rotation,
                            scale: Vec3::ONE,
                        },
                        ..Default::default()
                    });
            } else { panic!("Non-1 number of models in .vox shape scene node ({})", models.len()) }
        },
    }
}
Sixmorphugus commented 3 years ago

I'm not sure when I will have time to resume work here, so if anyone wants to take what I have and continue on please feel free.

Neo-Zhixing commented 3 years ago

@Sixmorphugus I have a small comment about the example code that you showed here:

// You get the third index out via a process of elimination here. It's the one that wasn't used for the other rows.

Some observations that we can make here:

Therefore, we can prove that index_nz1 + index_nz2 + index_nz3 = 3.

So, maybe you can simply write index_nz3 = 3 - index_nz1 - index_nz2 in your SceneNode::Transform case.

Also @Sixmorphugus your branch looks good. Do you wanna open a PR so that we can do some reviews?

virtualritz commented 2 years ago

Bump! @Neo-Zhixing if @Sixmorphugus doesn't have time to create a PR, someone else could do it.

I could just fork his fork and open a PR if this about process. I.e. the code is there to review since half a year now. Just saying. 😉

Neo-Zhixing commented 2 years ago

@virtualritz PR added!

virtualritz commented 2 years ago

Nice!!! :)

davidedmonds commented 2 years ago

PR merged.