Jump to content

Scale, Rescalefactor and mesh volume (all related)


Recommended Posts

I'm trying to determine the volume of a part by using the collider mesh.  I found some code on the net which calculates the volume of a mesh (see below), but it has no relation that I can tell to the actual part volume.  I suspect it has to do with the internal scaling of the part.

Which brings me to the Scale and Rescalefactor values.  

How do they relate to the actual .mu file being used for the model?

What can I do to take the volume calculated by the code below to scale it properly to give me correct data?

I'm using the standard tanks for testing; I can easily get their size by the following call:

p.partPrefab.GetPartRendererBound().size

and just use the formula to calculate volume of a cylinder:

v = h * Pi* r^2

where:

v = volume
h = height
r = radius (I divide the diameter by w)

 

So I know what the approximate volume should be, but the values I'm getting are totally off.

So, here is the code I use to get the mesh of a part (here is the link: http://answers.unity3d.com/questions/52664/how-would-one-calculate-a-3d-mesh-volume-in-unity.html):

GameObject model = GameDatabase.Instance.GetModel(meshstr);
if (model != null)
{
	MeshFilter[] meshFilters = model.GetComponentsInChildren<MeshFilter>(true);
	foreach (var m in meshFilters)
	{
		var mesh = m.sharedMesh;

		if (mesh != null)
			volume += VolumeOfMesh(mesh);
		else
			Debug.Log("Component MeshFilter not found for part: " + p.name);
	}
}

 

and here is the code I found to get the volume of a mesh:

        public float SignedVolumeOfTriangle(Vector3 p1, Vector3 p2, Vector3 p3)
        {
            float v321 = p3.x * p2.y * p1.z;
            float v231 = p2.x * p3.y * p1.z;
            float v312 = p3.x * p1.y * p2.z;
            float v132 = p1.x * p3.y * p2.z;
            float v213 = p2.x * p1.y * p3.z;
            float v123 = p1.x * p2.y * p3.z;
            return (1.0f / 6.0f) * (-v321 + v231 + v312 - v132 - v213 + v123);
        }
        public float VolumeOfMesh(Mesh mesh)
        {
            float volume = 0;
            Vector3[] vertices = mesh.vertices;
            int[] triangles = mesh.triangles;
            
            for (int i = 0; i < mesh.triangles.Length; i += 3)
            {
                Vector3 p1 = vertices[triangles[i + 0]];
                Vector3 p2 = vertices[triangles[i + 1]];
                Vector3 p3 = vertices[triangles[i + 2]];
                volume += SignedVolumeOfTriangle(p1, p2, p3);
            }
            return Mathf.Abs(volume);
        }

 

Edited by linuxgurugamer
Link to comment
Share on other sites

If you're using Blender there's a 3D Mesh Toolbox that has an area and volume calculator - select your model and click the button and it returns the value. I think it's included but not enabled by default - I can dig deeper if you want to try it and can't find it.

Link to comment
Share on other sites

KIS does calculate the volumes of parts, so you could take a gander at their code. Not completely sure if their computed "volume" is accurate or merely an approx ballpark (for gameplay, not physics/realism).

Keep in mind animated/deployable parts and such (volume can change with animation) -- KIS provides a means to supply a manual override value if you find that the computed value is really off the mark.
Likewise for parts that have collider not exactly matching visual model (for whatever reason, e.g. part is surface mount and has a portion that is "embedded" into its parent).

 

rescalefactor is default to 1.25 if not specified <-- this might be the source of your grief?

scale/rescalefactor apply to all 3 axes, so if you need to scale the volume, it should be by scale^3 / rescalefactor^3

iirc scale in MODEL nodes (as opposed to mesh = ...) specifies 3 axes independently.

 

hth

Link to comment
Share on other sites

Okay, now that I actually looked at what your code is doing, and here's the other thing that might be getting you.

 

GameObject model = GameDatabase.Instance.GetModel(meshstr);

Just to confirm, here you're feeding the url for the model, for example, "Squad/Parts/Command/mk1Cockpits/CockpitInline", right?

That pulls the whole model, which could include a whole bunch of things:
- colliders <-- what you want
- visual meshes <-- not guaranteed to be convex, or even closed
- empty transforms
- etc

 

	MeshFilter[] meshFilters = model.GetComponentsInChildren<MeshFilter>(true);
	foreach (var m in meshFilters) {
		// ...
		volume += VolumeOfMesh(mesh);
		// ...
	}

Looks like you're summing the volumes of anything in there that has a mesh, not just the colliders. That's probably what's making things go completely haywire.
Check that the parent GO of the mesh filter has some form of collider component attached to it (that is not set as a trigger). Calculate only using those.

-----

The VolumeOfMesh() code you've got is a sum of tetrahedrons; I'm a bit rusty but it looks fundamentally sound from here.

-----

I made you a plain and simple 1m cube that you can use to calibrate and make sure your code is working. Just gimme a moment to upload and link it...

download link

This is a 1x1x1m cube, you can stack with a mk1 part to confirm. It contains visual mesh (8 vertices, 12 tris) with attached box collider.

Volume should be 1 or 1.953125. Depends on whether rescaleFactor actually gets applied to the model meshes, or if it's a scaling factor only applied to the whole part's root transform. Probably the latter, case (so 1m3) If you get any other result, check to see if it is N x (expected value) or N3 x (expected value) which should be telling re: what went wrong.

I'd suggest trying to get the cube right before moving on to more complex models.

Edited by cake>pie
Link to comment
Share on other sites

9 hours ago, wasml said:

If you're using Blender there's a 3D Mesh Toolbox that has an area and volume calculator - select your model and click the button and it returns the value. I think it's included but not enabled by default - I can dig deeper if you want to try it and can't find it.

No, this needs to be done  in-game

6 hours ago, cake>pie said:

Okay, now that I actually looked at what your code is doing, and here's the other thing that might be getting you.

 


GameObject model = GameDatabase.Instance.GetModel(meshstr);

Just to confirm, here you're feeding the url for the model, for example, "Squad/Parts/Command/mk1Cockpits/CockpitInline", right?

That pulls the whole model, which could include a whole bunch of things:
- colliders <-- what you want
- visual meshes <-- not guaranteed to be convex, or even closed
- empty transforms
- etc

 


	MeshFilter[] meshFilters = model.GetComponentsInChildren<MeshFilter>(true);
	foreach (var m in meshFilters) {
		// ...
		volume += VolumeOfMesh(mesh);
		// ...
	}

Looks like you're summing the volumes of anything in there that has a mesh, not just the colliders. That's probably what's making things go completely haywire.
Check that the parent GO of the mesh filter has some form of collider component attached to it (that is not set as a trigger). Calculate only using those.

-----

The VolumeOfMesh() code you've got is a sum of tetrahedrons; I'm a bit rusty but it looks fundamentally sound from here.

-----

I made you a plain and simple 1m cube that you can use to calibrate and make sure your code is working. Just gimme a moment to upload and link it...

download link

This is a 1x1x1m cube, you can stack with a mk1 part to confirm. It contains visual mesh (8 vertices, 12 tris) with attached box collider.

Volume should be 1 or 1.953125. Depends on whether rescaleFactor actually gets applied to the model meshes, or if it's a scaling factor only applied to the whole part's root transform. Probably the latter, case (so 1m3) If you get any other result, check to see if it is N x (expected value) or N3 x (expected value) which should be telling re: what went wrong.

I'd suggest trying to get the cube right before moving on to more complex models.

Thanks, the cube will be helpful.  I was using the stock tanks because they were easy to figure out.

Link to comment
Share on other sites

9 hours ago, cake>pie said:

KIS does calculate the volumes of parts, so you could take a gander at their code. Not completely sure if their computed "volume" is accurate or merely an approx ballpark (for gameplay, not physics/realism).

Keep in mind animated/deployable parts and such (volume can change with animation) -- KIS provides a means to supply a manual override value if you find that the computed value is really off the mark.
Likewise for parts that have collider not exactly matching visual model (for whatever reason, e.g. part is surface mount and has a portion that is "embedded" into its parent).

 

rescalefactor is default to 1.25 if not specified <-- this might be the source of your grief?

scale/rescalefactor apply to all 3 axes, so if you need to scale the volume, it should be by scale^3 / rescalefactor^3

iirc scale in MODEL nodes (as opposed to mesh = ...) specifies 3 axes independently.

 

hth

I don't care about animated/deployable parts.  This is for fuel and other resources.

So, I can get the following values from the parts regarding scaling:

  • partPrefab.scaleFactor
  • partPrefab.rescaleFactor
  • partPrefab.transform.localScale.x
  • partPrefab.transform.localScale.y
  • partPrefab.transform.localscale.z

So, I don't know what scaleFactor is.  It's usually equal to the rescaleFactor, but there are some parts where it is way different.

The FL-T*00 tanks for example, have a scaleFactor = 0.08, yet the rescaleFactor = 1.25

Finally, in some part cfg files, I see a "scale =", but can't find anything related.  Is this just old and leftover?

Anyway, thanks, this helps a lot.

Link to comment
Share on other sites

7 hours ago, cake>pie said:

Looks like you're summing the volumes of anything in there that has a mesh, not just the colliders. That's probably what's making things go completely haywire.
Check that the parent GO of the mesh filter has some form of collider component attached to it (that is not set as a trigger). Calculate only using those.

Does GO refer to GameObject?

Would this suffice:

var c = m.gameObject.GetComponents<Collider>();
if (c.Count() > 0)

 

Link to comment
Share on other sites

7 hours ago, cake>pie said:

I made you a plain and simple 1m cube that you can use to calibrate and make sure your code is working. Just gimme a moment to upload and link it...

download link

This is a 1x1x1m cube, you can stack with a mk1 part to confirm. It contains visual mesh (8 vertices, 12 tris) with attached box collider.

Looks like it is a 1.25m cube, see the pic (that's a FL-T100 attached):

 

19 minutes ago, linuxgurugamer said:

Does GO refer to GameObject?

Would this suffice:


var c = m.gameObject.GetComponents<Collider>();
if (c.Count() > 0)

 

Nope, it doesn't.  I added this, and many of the tanks failed the check.

On the other hand, I got your cube in, and using the size of 1.25, got the correct volume

Link to comment
Share on other sites

Just now, linuxgurugamer said:

Looks like it is a 1.25m cube, see the pic (that's a FL-T100 attached):

Working exactly as intended. The model is 1m cube, and default rescaleFactor is 1.25. So the cube matches up to the FL-T100.

 

As for your other question:
- yes GO = gameobject. but...
- I did not consider all possibilities earlier, so that advice is flawed: it only works if you're dealing with mesh collider generated from the visual mesh, and both are attached to the same game object

Here's what the FL-T200 looks like dissected:

flt200.png

 

It uses a mesh collider (blue, GO:node_collider) that is a much simplified cylinder. This is what you want to compute your volume with.
The visual mesh is orange (GO:fueltank).

Instead of having those two components reside in the same GO, they are in separate GOs. Here, the two are parented to the same parent transform (GO:fuelTank) but that is not guaranteed to be the case in general.
So I don't think you can use getComponent<MeshFilter>() as your starting point ... it is not guaranteed to help you find the colliders. And you'd want to compute volume using colliders, not with the visual meshes.
mesh = m.sharedMesh only works if it happens to be a mesh collider sharing a mesh with the visual model. You'd often have colliders unrelated to visual mesh -- either simplified meshes, or primitive colliders (cheaper)

I'd suggest trying model.GetComponentsInChildren<Collider>() and handle volume depending on what kind of colliders you find. Exclude colliders that are triggers (check collider.isTrigger, also layer 21 part triggers).

There's a small gotcha there, though -- there may be colliders you actually don't want. On the FL-T200, that's the spherical collider "Surface Attach Collider". Not sure what it's purpose is, but I don't think you'd consider that to be part of the tank's volume, and there is nothing here in tag/layer to help distinguish it from the collider(s) you want. =$

 

 

 

Link to comment
Share on other sites

So, I removed that check, and the numbers are looking much better.  But not yet perfect.

I would expect that if I calculated the volume of the cylinder (back to the FL-T tanks) using the outer bounds, that it would always be greater than or equal to the mesh volume.

This isn't always the case.  The FL-T100 comes up with a wrong volume

Name Title Mesh volume v = h * Pi * r^2
fuelTank3-2
Rockomax Jumbo-64 Fuel Tank
36.37697
39.46097
fuelTankSmallFlat
FL-T100 Fuel Tank
1.323484
0.800261
fuelTankSmall
FL-T200 Fuel Tank
0.955222
1.356731
fuelTank
FL-T400 Fuel Tank
1.973512
2.337787
fuelTank.long
FL-T800 Fuel Tank
3.969568
4.637522
fuelTank2-2
Rockomax X200-16 Fuel Tank
8.878579
10.04194
fuelTank1-2
Rockomax X200-32 Fuel Tank
18.0017
19.85342
fuelTank4-2
Rockomax X200-8 Fuel Tank
4.368142
5.182893

 

4 minutes ago, cake>pie said:

Working exactly as intended. The model is 1m cube, and default rescaleFactor is 1.25. So the cube matches up to the FL-T100.

 

As for your other question:
- yes GO = gameobject. but...
- I did not consider all possibilities earlier, so that advice is flawed: it only works if you're dealing with mesh collider generated from the visual mesh, and both are attached to the same game object

Here's what the FL-T200 looks like dissected:

flt200.png

 

It uses a mesh collider (blue, GO:node_collider) that is a much simplified cylinder. This is what you want to compute your volume with.
The visual mesh is orange (GO:fueltank).

Instead of having those two components reside in the same GO, they are in separate GOs. Here, the two are parented to the same parent transform (GO:fuelTank) but that is not guaranteed to be the case in general.
So I don't think you can use getComponent<MeshFilter>() as your starting point ... it is not guaranteed to help you find the colliders. And you'd want to compute volume using colliders, not with the visual meshes.
mesh = m.sharedMesh only works if it happens to be a mesh collider sharing a mesh with the visual model. You'd often have colliders unrelated to visual mesh -- either simplified meshes, or primitive colliders (cheaper)

I'd suggest trying model.GetComponentsInChildren<Collider>() and handle volume depending on what kind of colliders you find. Exclude colliders that are triggers (check collider.isTrigger, also layer 21 part triggers).

There's a small gotcha there, though -- there may be colliders you actually don't want. On the FL-T200, that's the spherical collider "Surface Attach Collider". Not sure what it's purpose is, but I don't think you'd consider that to be part of the tank's volume, and there is nothing here in tag/layer to help distinguish it from the collider(s) you want. =$

 

 

 

I'll continue this this evening, need to go to work.

That's a nice display, is it an available mod?

Link to comment
Share on other sites

26 minutes ago, cake>pie said:

So I don't think you can use getComponent<MeshFilter>() as your starting point ... it is not guaranteed to help you find the colliders. And you'd want to compute volume using colliders, not with the visual meshes.

Other than a performance issue, why wouldn't I want to use the visual meshes for volume?

Link to comment
Share on other sites

 

1 hour ago, linuxgurugamer said:

Finally, in some part cfg files, I see a "scale =", but can't find anything related.  Is this just old and leftover?

Anyway, thanks, this helps a lot.

In part cfgs, scale and rescale factor both affect the final visual size in the game.

 

44 minutes ago, linuxgurugamer said:

That's a nice display, is it an available mod?

Sarbian's Debug Stuff. Incredibly handy tool.

 

31 minutes ago, linuxgurugamer said:

Other than a performance issue, why wouldn't I want to use the visual meshes for volume?

There is no guarantee that the visual meshes are closed polyhedra. Visual model can be pretty much whatever as long as it "looks fine", and does not have to be physically "correct".

That means there may be breaks, seams, and missing faces, whether for visual reasons (edge split for sharp corners) or cost savings (removal of faces that are not outward facing) or just plain convenience for the 3d artist (using copies/mirroring to save effort; subdividing mesh a certain way for easier texture mapping).

 

Here's a simple example that I cooked up real quick to illustrate why calculating with the visual mesh could really mess things up:
degen_ex.jpg

So here's a part that is fundamentally a 2x2x4 cuboid. boxcollider is what you'd expect: 2x2x4, 6 sided cuboid, 12 tris -- reflects the physical shape of the part. Visually, though, it is made up of two meshes, one for each half. visCube1 is selected (yellow) -- only 5 sides of a cube, 10 tris. visCube2 (orange) on the other half is the same. The "inside" face of each cube is unnecessary in the visual mesh.

If you compute on the visual meshes, I suspect you're going to end up with a volume that is only 5/6 of what it should be -- each mesh is calculated as a sum of 10 tetrahedra about its local origin, so your final total is missing an octohedral volume where the two cubes meet.

This is a very simplified example, of course; a real example would have far more detailed visual meshes.

And to hopefully convince you that this is not just a contrived example -- a real use case that is analogous to this would be a series of parts which share a set of standard end caps, but have a different middle section. Artist would model the end caps once and just reuse them.

 

 

Link to comment
Share on other sites

54 minutes ago, cake>pie said:

 

In part cfgs, scale and rescale factor both affect the final visual size in the game.

 

Sarbian's Debug Stuff. Incredibly handy tool.

 

There is no guarantee that the visual meshes are closed polyhedra. Visual model can be pretty much whatever as long as it "looks fine", and does not have to be physically "correct".

That means there may be breaks, seams, and missing faces, whether for visual reasons (edge split for sharp corners) or cost savings (removal of faces that are not outward facing) or just plain convenience for the 3d artist (using copies/mirroring to save effort; subdividing mesh a certain way for easier texture mapping).

 

Here's a simple example that I cooked up real quick to illustrate why calculating with the visual mesh could really mess things up:
degen_ex.jpg

So here's a part that is fundamentally a 2x2x4 cuboid. boxcollider is what you'd expect: 2x2x4, 6 sided cuboid, 12 tris -- reflects the physical shape of the part. Visually, though, it is made up of two meshes, one for each half. visCube1 is selected (yellow) -- only 5 sides of a cube, 10 tris. visCube2 (orange) on the other half is the same. The "inside" face of each cube is unnecessary in the visual mesh.

If you compute on the visual meshes, I suspect you're going to end up with a volume that is only 5/6 of what it should be -- each mesh is calculated as a sum of 10 tetrahedra about its local origin, so your final total is missing an octohedral volume where the two cubes meet.

This is a very simplified example, of course; a real example would have far more detailed visual meshes.

And to hopefully convince you that this is not just a contrived example -- a real use case that is analogous to this would be a series of parts which share a set of standard end caps, but have a different middle section. Artist would model the end caps once and just reuse them.

 

 

No, I would never think that.  I know my limitations, and appreciate your taking the time to explain the issues.

so. you mentioned that scale & rescale factor affect the final size.  You explained everything else, except the "scale =" for a mesh.

Unless, specifying that for a mesh is the same as specifying all three for a model?

 

Link to comment
Share on other sites

36 minutes ago, linuxgurugamer said:

No, I would never think that.  I know my limitations, and appreciate your taking the time to explain the issues.

so. you mentioned that scale & rescale factor affect the final size.  You explained everything else, except the "scale =" for a mesh.

Unless, specifying that for a mesh is the same as specifying all three for a model?

My bad. I wasn't accusing you or anything, I just like to cover the bases -- in this case illustrate that the toy example does indeed reflect pertinent aspects of real use cases.

Yeah scale and rescaleFactor outside of a MODEL node have a single scalar for all three axes. scale inside a MODEL node specifies each axis independently.
Disclaimer: I've never tried MODEL node in conjunction with non-standard scale/rescaleFactor outside the MODEL node. I don't know if there might be any unforeseen interactions if you do that.

It also occurs to me that you might be able to get away with ignoring whatever is in the part cfgs. The game has already parsed that stuff!
I would suspect that the the final scale multiplier is already in the root transform's localScale; at least, that seems to be the most logical place to look first.

This brings to mind another potential gotcha: child transforms in the part may have scaling applied locally. So to be real safe and/or pedantic, for any mesh/collider of interest, you'd want to trace all the way back up the hierarchy to check for that. (It's not straightforward multiplication all the way, though; things get messy if rotations are involved -- see lossyScale)

Link to comment
Share on other sites

  • 5 months later...
48 minutes ago, gomker said:

@linuxgurugamer did you ever find a solution to this problem?

 I have a similar issue and would like to roughly calculate the surface area and / or volume of a part

Not fully. I can get a rough number but only for things like cylinders squares Etc. What I would really like is for someone to be able to do this for any part, but this involves certain types of knowledge which I don't have.

If someone would be able to get me that code, I have a mod in mind which would be able to automatically set volumes of tanks and masses of Tanks to be fully consistent no matter what mod they come from including squads. But in order to do that, I need to know the volume and surface area of the part in question.

Link to comment
Share on other sites

2 minutes ago, linuxgurugamer said:

order to do that, I need to know the volume and surface area of the part in question.

Yeah, I think that ultimately it will be an approximation unless the game engine does it for us as it would be too difficult due to the variety of shapes. You could limit to just cylinders, spheres...
Anyway, I did find that KIS is just using the bounds

https://github.com/ihsoft/KIS/blob/master/Source/KIS_Shared.cs#L539

 public static float GetPartVolume(AvailablePart partInfo) {
    var p = partInfo.partPrefab;
    float volume;

    // If there is a KIS item volume then use it but still apply scale tweaks. 
    var kisItem = p.GetComponent<ModuleKISItem>();
    if (kisItem && kisItem.volumeOverride > 0) {
      volume = kisItem.volumeOverride;
    } else {
      var boundsSize = PartGeometryUtil.MergeBounds(p.GetRendererBounds(), p.transform).size;
      volume = boundsSize.x * boundsSize.y * boundsSize.z * 1000f;
    }

    // Apply cube of the scale modifier since volume involves all 3 axis.
    return (float) (volume * Math.Pow(GetPartExternalScaleModifier(partInfo), 3));
  }

 

Link to comment
Share on other sites

18 minutes ago, gomker said:

Yeah, I think that ultimately it will be an approximation unless the game engine does it for us as it would be too difficult due to the variety of shapes. You could limit to just cylinders, spheres...
Anyway, I did find that KIS is just using the bounds

https://github.com/ihsoft/KIS/blob/master/Source/KIS_Shared.cs#L539


 public static float GetPartVolume(AvailablePart partInfo) {
    var p = partInfo.partPrefab;
    float volume;

    // If there is a KIS item volume then use it but still apply scale tweaks. 
    var kisItem = p.GetComponent<ModuleKISItem>();
    if (kisItem && kisItem.volumeOverride > 0) {
      volume = kisItem.volumeOverride;
    } else {
      var boundsSize = PartGeometryUtil.MergeBounds(p.GetRendererBounds(), p.transform).size;
      volume = boundsSize.x * boundsSize.y * boundsSize.z * 1000f;
    }

    // Apply cube of the scale modifier since volume involves all 3 axis.
    return (float) (volume * Math.Pow(GetPartExternalScaleModifier(partInfo), 3));
  }

 

So that would be fine for the volume, up to a point, but doesn't do anything for the surface area

Link to comment
Share on other sites

Just now, linuxgurugamer said:

surface area

I was thinking of trying to back track it with a ratio https://en.wikipedia.org/wiki/Surface-area-to-volume_ratio

But same problem, approximation. If its just using the Rectangular bounds, but this will not be accurate for irregular shaped parts... I guess it could work for sphere's, cylinders and rectangular parts

Link to comment
Share on other sites

42 minutes ago, gomker said:

I was thinking of trying to back track it with a ratio https://en.wikipedia.org/wiki/Surface-area-to-volume_ratio

But same problem, approximation. If its just using the Rectangular bounds, but this will not be accurate for irregular shaped parts... I guess it could work for sphere's, cylinders and rectangular parts

Only ish at best as bounding boxes in KSP, appear under my interrogation, to be regardless of actual mesh form. rectangular, seemingly formed  from the tallest and widest vertices and projected into a cube. The only purpose of the bounds is to prevent the object spawning inside another. 

Link to comment
Share on other sites

This thread is quite old. Please consider starting a new thread rather than reviving this one.

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...