Skip to content

MeshGroups. (Again) - #120

Open
ArcturusEmrys wants to merge 11 commits into
Inochi2D:mainfrom
ArcturusEmrys:meshgroup
Open

MeshGroups. (Again)#120
ArcturusEmrys wants to merge 11 commits into
Inochi2D:mainfrom
ArcturusEmrys:meshgroup

Conversation

@ArcturusEmrys

Copy link
Copy Markdown
Contributor

This is an extension of #103 as the original author confirmed they are no longer interested in continuing their version of the PR.

Most of my additions are work-arounds for various error states caused by the output of Inochi Creator's exports. Specifically, if you do not add a mesh to a MeshGroup (which I do a lot), Inochi Creator will create incomplete mesh information for that MeshGroup. Inochi Session will ignore the information but Inox2D will complain and panic.

I'm not sure if this is 100% accurate, but it seems to work for what I'm doing so far. I may have to come back to this with more stringent tests.

Richardn2002 and others added 7 commits September 30, 2024 09:13
Three parts:
- Testing whether a point is in a triangle.
- Efficient (but not guaranteed correct) testing of a point is in which triangle among those in a given mesh, with a bitmask constructed for the mesh.
- Given parent mesh and parent deformation, deform child mesh. Essentially letting one parent triangle drag a child vertex that is contained in it, and this drag can be chacterized by an inverse matrix, which can be reused for efficiency.
Intensive test for bitmask included (with a .png depicting the base test case), testing that bitmask yields correct test results for points with the whole test space transforming.
However, as mentioned, this bitmask method is not guaranteed to be correct, thus it is hard to define a "good enough" test case. It is easy to construct counterexamples.
An experimental attempt on dynamic bitmask step size is commented out cause test suites do not like it. A step size of `1` should be usable in actual rendering anyways.
I have a Puppet file with meshgroups that do not have valid mesh data. Inochi appears to handle this by creating a Mesh block with empty verts/indices, but NO uvs. Inox can handle the former but not the latter.

(The origin field is unaffected, if you're wondering.)
@ArcturusEmrys

Copy link
Copy Markdown
Contributor Author

So, for some reason it turns out most of me works with this PR, but not all. More notably, none of the maths stuff added by the MeshGroups PR appears to actually be used? I habitually apply all my deforms to MeshGroups, so I feel like I should be way more broken than I am without MeshGroup deforms actually applying to their children. Either that, or it's just the "dynamic" deforms that are unimplemented (which the magic circles in my eyes use, and which don't work) and the "static" ones just so happen to work by some other mechanism?

Either way, I'm marking this as draft until I figure out what's up with that. Probably will need to trawl the Inochi2D source as well...

@ArcturusEmrys
ArcturusEmrys marked this pull request as draft August 10, 2026 16:58
machinery.

This still doesn't work, because while MeshGroups have a DeformStack, there is
nowhere to push the deforms so they never get combined and the individual Mesh
parts don't get to read them. We'll need to create a separate deform scratch
area for MeshGroups that the DeformStack can read and write instead.

Additionall, I may want to add functionality for renderers to compute the
deforms themselves, so that it can be done with GPU compute. AFAIK, nothing
but the vertex shaders actually read the deforms.
* We create a DeformStack for all children of a deformable node, just in case.

* We now account for child node transforms when deforming them with their
parents' mesh deformations.

* Mesh bitmasks used for testing are now cached across frames.

* Only dynamic MeshGroups actually trigger dynamic deformation.

(This is probably not true for INX files - which by the way, did you know
those just load in Inox2D?)

Additionally:

* We account for meshes that are children of other meshes. I don't know if
this actually uses the dynamic deformation code, I don't have a test case for
this, but I've done this in my model.

* Vertices that are on the edge of a triangle are treated as being part of the
triangle in Inochi2D, but our triangle test code rejects those vertices. I HAD
a test case for this as I'd accidentally triggered this behavior, but I
deliberately removed it from the test to verify the behavior.
@ArcturusEmrys

ArcturusEmrys commented Aug 16, 2026

Copy link
Copy Markdown
Contributor Author

I think I have a working implementation of dynamic deformation. (For context, the magic circles in my eyes used to float around aimlessly in space without dynamic deformation active.)

A couple things to note:

  1. There's a literal edge case we're not handling yet: verts on the edge of a deforming triangle DO get treated as being in that triangle; the math code in this PR treats that as "undefined" according to its comments. I triggered this behavior by mistake making a test case for deforms and had to remove it, but I probably should actually support this.
  2. Static deformation appears to require no runtime support; all the deforms are precalculated (which is why most of me worked just by parsing MeshGroup nodes). Of course, this is going away in 0.9 anyway soooo
  3. I still need to add the aforementioned test case to Inox2D, which means I need to write a framework to load an INP and assert things about it

@ArcturusEmrys
ArcturusEmrys marked this pull request as ready for review August 16, 2026 14:20
Comment on lines +80 to +81
// Meshes can impose a deform on their children anyway
comps.get::<Mesh>(ancestor).is_some()

@mikz30 mikz30 Aug 17, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A Part with Mesh doens't impose a deform but their transform on their chirldren how I understand it. Otherwise it would make any Part behave like a Meshgroup. It currently triggers

/// deform_stack.rs L100-117
if let Some(foreign_render_ctx) = node_comps.get::<TexturedMeshRenderCtx>(*source_node) {
								let foreign_offset = foreign_render_ctx.vert_offset as usize;
								let foreign_len = foreign_render_ctx.vert_len;

								let Some((my_deform, foreign_deform)) = split_ranges(
									result,
									vert_offset..(vert_offset + vert_len),
									foreign_offset..(foreign_offset + foreign_len),
								) else {
									eprintln!(
										"They're disjoint?! {:?}, {:?}, {:?}",
										vert_offset..(vert_offset + vert_len),
										foreign_offset..(foreign_offset + foreign_len),
										*source_node,
									);
									continue;
								};

								Some((my_deform, &*foreign_deform))

which often gives boundary checking error inside split_ranges because vert_offset is 0.

/// render.rs L231-234
if let Some(deform_stack) = comps.get::<DeformStack>(node.uuid) {
					let len = deform.len();
					deform_stack.combine(node.uuid, nodes, comps, &mut deform, 0, len);
				}

Would you mind sharing what kind of scenario this is supposed to handle?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, a couple days ago, I was adding art to my model when I realized I'd made the mistake of applying a param deform directly to a Mesh. Inochi Creator doesn't have a way to convert a Mesh to a MeshGroup (that I know of) nor a way to copy the mesh data and deforms over to a new MeshGroup; so I'd have to re-rig all the art just to add a second layer to it. In desperation I just put the new art as a child Mesh of the original one and... it worked exactly as you'd expect a MeshGroup to act.

Granted, I haven't made a test case for this yet, it could just be a bug in Creator 0.8. I also don't know if it is applying static or dynamic deformation, if it's the former we technically don't need to account for it (and if so, that part of my model will probably break when 0.9 comes out).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After further testing with my deform test I don't actually think this behavior actually happens and I'll be removing the code to support it.

The thing I saw happening in Inochi Creator had nothing to do with deforms and I confused the two things. Sorry!

@ArcturusEmrys

Copy link
Copy Markdown
Contributor Author

@Speykious @mikz30 Ugh. It turns out I've hit a bit of an architectural problem. MeshGroups not only deform mesh vertices, they also deform node translations. The natural place to put such code would be in DeformStack::combine, unfortunately we can't take &mut World inside of an ECS component.

In #116 I added a partitioning scheme specifically to allow multithreading param application, but it would also work in this case: you could partition the world into "just DeformStacks" and "everything else," then give the DeformStack you're combining the "everything else" partition. So I'm going to cherry-pick that part of #116 over, and hopefully the inevitable rebase will remove the duplicate commits later.

If you don't like this idea please tell me now.

@ArcturusEmrys

Copy link
Copy Markdown
Contributor Author

...actually, to make matters worse, we're computing deforms AFTER the transform context is updated, which is too late to change transforms! Deforms have to be computed BEFORE the absolute transforms are calculated. So I have to move deform processing out of RenderCtx and into TransformCtx too.

This also has some performance implications: deforms will get computed twice (on my model they take several ms alone), and since deforms can influence physics now moving them over to GPU compute (something I wanted to do eventually) is going to be way more difficult, if not impossible.

(For context as to why it's difficult: moving work from the GPU to the CPU means the CPU has to wait, and GPUs aren't great at finishing things quickly. Ideally you only ever send work to the GPU. So I'd either have to move all the physics to the GPU, which is overkill, or download the deforms back to the CPU and hope the performance hit is less.)

@ArcturusEmrys

Copy link
Copy Markdown
Contributor Author

I have the MeshGroup "translate children" behavior ostensibly implemented, but it doesn't actually work because all the MeshGroups in my face that have it enabled in the .INX get exported with it disabled in the .INP. It just happens to magically work in Session.

I don't know what the hell is even going on anymore 🤪 All I know is that I have magic circles in my eyes that deform correctly, until they rotate, and then moving my head left or right makes them fly wildly off course. It's like their center of rotation doesn't move along with the mesh, which is why I looked at the translation children behavior - but it CAN'T be translate children, because Inochi2D turns that off on export.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants