BOUNDS¶
kOS can return information about the “bounding box” around either a whole vessel, or a part on a vessel. When it does so, the structure it uses to tell your script about it is called a “Bounds”.
You can obtain a Bounds
one of two ways:
By calling
Vessel:Bounds
By calling
Part:Bounds
The bounds for a whole vessel and the bounds for one part work almost exactly the same way. Places where they differ will be explained below as they arise.
The reason this could be useful to a kerboscript program is that it lets you get a partial “picture” of the size of a ship, or a part, or check more precisely when landing “what is the altitude of my landing leg’s foot above the ground?” instead of “what is the altitude of my ship in general above the ground?”
Before you use a Bounds be certain you have read the section below on this page about why you should re-use bounds if you can (instead of re-getting them again and again with the :BOUNDS suffix call.)
Quick start 1 - Radar altitude: the most useful thing¶
The explanation of the “Bounds” structure might get a bit involved below.
For most players, probably the only thing you really really care deeply about is the distance of the landing legs to the ground.
And you get that with the suffix BOTTOMALTRADAR
. Here’s an example:
// How to get the distance of the bottom corner of the vessel's
// bounding box to the ground.
// As an example, try running this while you manually land,
// so you can see it working as you fly the lander:
local bounds_box is ship:bounds. // IMPORTANT! do this up front, not in the loop.
until terminal:input:haschar {
clearscreen.
print "PRESS ANY KEY TO QUIT".
print "ALT:RADAR of ship bounding box's bottom corner is:".
print round(bounds_box:BOTTOMALTRADAR, 1).
wait 0.
}
terminal:input:getchar().
It may be more useful to choose a specific landing leg part and test with it instead of the whole vessel’s box, by doing something akin to this at the top of the above script instead:
// DELETE THIS LINE:
// local bounds_box is ship:bounds. // IMPORTANT! do this up front, not in the loop.
//
// REPLACE WITH THESE LINES:
// Assumes you gave the nametag "sensing leg" to one of your landing legs:
//
set leg_part to ship:partstagged("sensing leg")[0].
local bounds_box is leg_part:bounds.
// (rest of the example is the same as above).
Quick start 2 - Finding the corner you care about¶
The most universally helpful suffix of a Bounds
, if you want
to just learn one suffix and ignore the others, is probably the
Bounds:FURTHESTCORNER(ray)
suffix.
It lets you say “I just want to know where is the corner of this bounding box that is the furthest ‘that way’”.
For example, this gives you a position which is the bottom-most vertex of your ship’s bounding box:
set B to ship:BOUNDS.
// The furthest corner of the box in the downward (negative up) direction:
set bottom to B:FURTHESTCORNER( - up:vector ).
With this one suffix you can answer a lot of the relevant “ship size” questions like “Am i getting too close to that ship?, well, lets find out which vertex of its bounding box is closest to me…”.
Quick Start 3 - A complex visual example drawing the bounds¶
There is an example program in the tutorials section that brings this all together to show you the bounds boxes visually on screen:
Click here for an example program that displays bounds
Trying that first (without necessarily understaand it right away) will help give you a visual guide to what is happening here.
A Part:BOUNDS or Vessel:BOUNDS will move and rotate with the object¶
If you get a Bounds
from calling either Part:BOUNDS
or Vessel:BOUNDS
, then that “bounds” is magically tied
to the vessel or part it came from.
Internally, kOS is doing some “magic” to ensure that the
Bounds
structure “remembers” the part, or vessel, that it is
associated with, and keeps itself updated to that part or
vessel’s new moved position and orientation. This means
the values for the “absolute” suffixes described in the
table below (namely ABSMIN
, ABSMAX
,
ABSCENTER
, ABSORIGIN
, FACING
,
FURTHESTCORNER
, BOTTOMALT
, and BOTTOMALTRADAR
)
will always be kept up to date every time you get their value
it will be newly calculated and correct even though
the part or vessel has been rotating or moving since you
last called the :BOUNDS
suffix.
SETTING a suffix of Bounds can break the link to its object¶
This “magically keep updating things” is only guaranteed to
keep happening if you restrict yourself to only using GET access
on Bounds suffixes. If you ever SET the ABSORIGIN
or FACING
suffixes to some other value, then Bounds will
no longer keep updating that suffix to match the object it came from.
(and consequently the other suffixes won’t be updating themselves
properly either, as they depend on calculations from these two).
This is not a bug. It’s intentional. When you SET a suffix of a
Bounds, you are explicitly telling it to use your new value
instead of its usual practice of always re-calculating it from
the part or vessel it came from.
A Bounds structure is re-usable. Please do re-use it.¶
While it may seem like these two examples below are the same, the second example is MUCH less of a burden on the KSP game than the first one:
// Example 1: An expensive example using :BOUNDS again and again:
// (THIS IS A BAD PRACTICE):
// Please set the ship rotating before doing this, to prove that
// it is indeed seeing the new rotated positions:
//
print "100 samples of my min/max corners as I rotate:".
for i in range(0,100) {
print i + ": absmin=" + SHIP:bounds:absmin + ", absmax=" + SHIP:bounds:absmax.
wait 0.
}
// Example 2: The exact same thing, done more efficiently:
// (THIS IS BETTER PRACTICE):
// Please set the ship rotating before doing this, to prove that
// it is indeed seeing the new rotated positions:
//
local B is SHIP:bounds. // get the :bounds suffix ONCE.
print "100 samples of my min/max corners as I rotate:".
for i in range(0,100) {
print i + ": absmin=" + B:absmin + ", absmax=" + B:absmax.
wait 0.
}
The reason example 1 is more expensive is that every time you call
the :bounds suffix, you make kOS under the hood re-run some coordinate
transformations, and the Bounds
structure is explicitly designed
to make it so you don’t have to keep doing that to make it accurate.
It “remembers” which object’s orientation it needs to be using, and
it keeps re-correcting itself to that objects orientation for you
every time you use it.
The expense of calling Part:BOUNDS
isn’t that bad and calling it
repeatedly probably won’t really make your script suffer noticably.
But when you do it for the whole vessel, calling Vessel:BOUNDS
repeatedly, that can definitely result in noticable unnecessary
computations being done by your computer.
For a more in-depth explanation of why it’s expensive to re-call the Bounds suffix over and over, if you care, see The bottom of this page. For now, it is sufficient to say “it’s expensive, don’t do it”.
Things that will invalidate an existing Bounds¶
As explained elsewhere on this page, it is much faster
and less taxing on the KSP game to re-use a Bounds
instead of obtaining a new one. The Bounds
object
contains some internal logic to track rotation and movement
of the ship so the bounds box will rotate properly with it,
and the bounds boxes of individual parts will rotate if
the part rotates.
However, be aware of these following situations that can
cause a previously - obtained Bounds
to become incorrect,
and require you to get a new Bounds with the suffix. Because
doing so is expensive, don’t fall to the temptation of just
making your script easy to write by unconditionally re-getting
the Bounds
suffix all the time. Instead be aware of what
makes you have to get a new Bounds, and don’t do so if these
events aren’t happening:
A list of events that can make a Bounds
become incorrect:
A Part Bounds will need to be recalculated if the part shrinks or grows through actions such as these:
Extending or retracting solar panels.
Extending or retracting Landing Gear.
Opening or closing cargo bay doors.
Moving robotic parts from the Breaking Ground DLC.
etc.
A Vessel Bounds will need to be recalculated if any Part Bounds inside the vessel needs to be recalculated (see above list). In addition, the items on the following list will require a Vessel’s Bounds (but not individual parts’ bounds) to be recaculated:
Anything that adds/removes parts obviously alters the bounding box of the vessel. These are examples but not an exhuastive list:
Docking and Undocking
Decoupling stages
Explosions
Using the asteroid grabber claw.
Anything that changes the vessel’s “control” orientation. (As in anything that makes the navball jump to a new orientation all at once). That invalidates the old bounding box because it swaps the meaning of which axis of the ship is the “fore” and which is the “starboard” and so on. These are examples but not an exhaustive list:
Right-clicking a docking port and saying “control from here”.
Right-clicking a lander can and choosing a new control orientation.
Entering IVA view (which has the side effect of making the game do a “control from here” on the cockpit part).
Also, be aware that getting a new Part:BOUNDS
is a LOT
less expensive than getting a whole new Vessel:BOUNDS
,
so if your script task does need to constantly re-get the
bounds, try writing it in such a way that it only needs to
re-get the bounds of one or two parts, not the whole vessel.
(For example, for a landing script, maybe try to have your script learn
which part of your vessel is the bottom-most part you’ll be landing on,
and just use that one part’s bounds to test the height to the ground
instead of the entire vessel’s bounds.)
Making your own Bounds¶
There are a few suffixes of Bounds that are settable. Doing so isn’t very useful for Bounds coming from the vessel or a part. But the reason they are settable is so you can make your own bounds objects if you feel the need to.
At minimum to make your own bounds you will need these pieces of information:
The ABSORIGIN of the bounds.
The FACING of the bounds.
The RELMIN of the bounds.
The RELMAX of the bounds.
The following function will let you construct your own Bounds, although it’s not clear what use this would have yet:
local my_bounds is BOUNDS( ABSORIGIN, FACING, RELMIN, RELMAX ).
The other suffixes are derived from calculations based on these.
Example:
// Makes a bounds that is centered around a flag,
// oriented in that flag's UP direction, which
// goes a lot further up into the sky than it does down
// into the ground (to demonstrate that the bounds box
// doesn't have to span equally far in all directions
// around the origin, and thus why the origin isn't always
// the center of the box):
local my_flag is vessel("that flag").
local my_bounds is BOUNDS(
my_flag:position,
my_flag:up, // In this facing, Z = up/down, X = north/south, and Y = east/west.
// box is 20x20x502 meters, centered in east/west/north/south terms, but
// extending higher up in the +Z direction than down in the -Z direction:
V(-10,-10, -2),
V(10, 10, 500)
).
Again, it’s unclear how a script might use this, but it’s there for completeness.
Obviously, a bounds box you make manually yourself this way does not have the “magic” linkage to a vessel or part that the ones kOS makes have, and therefore its position is more fixed in space unless your script manually re-assignes its properties.
Diagram¶
When looking at the suffix explanations below, these diagrams may help illustrate what is being talked about:
- structure Bounds¶
Suffix
Type
Access
Description
Get/Set
origin point of box, in absolute ship-raw coords.
Get/Set
The orientation of the box’s own reference frame.
Get/Set
a corner of the box in box’s own reference frame.
Get/Set
opposite corner of the box from RELMIN, in box’s own reference frame.
Get only
a corner of the box in absolute (ship-raw) reference frame.
Get only
opposite corner of the box from RELMIN, in absolute (ship-raw) reference frame.
Get only
center of the box (not its origin), in absolute (ship-raw) frame.
Get only
center of the box (not its origin), in box’s own reference frame.
Get/Set
A vector from box center to max corner, in box’s reference frame.
Get/Set
Exactly 2 times EXTENTS - the vector from min corner to max, in box’s reference frame.
Get only
Position (in absolute ship-raw coords) of the box corner most “that-a-way”.
Get Only
Sea-level altitude of bottom-most corner of box.
Get Only
Radar altitude of bottom-most corner of box.
RELORIGIN is missing
n/a
DOES NOT EXIST
This suffix is deliberately missing because it would always be V(0,0,0).
- Bounds:ABSORIGIN¶
- Type
- Access
Get/Set but read the note below before you SET it.
The position of the origin point of the bounding box, expressed in absolute coordinates (what kOS calls the ship-raw reference frame, that the rest of the position vectors in kOS use.)
If this bounding box came from a Part, this will be the same as that part’s
Part:POSITION
, and will keep being “magically” updated to stay with that part’s position if it moves or rotates (but see note below).If this bounding box came from a vessel, this will be the same as that vessel’s
Vessel:PARTS[0]:POSITION
(the position of its root part) and be “magically” updated to stay with that part’s position if it moves or rotates (but see note below). It is NOTVessel:position
, which is important.Vessel:position
is the center of mass of a vessel. While kOS prefers to use CoM as the official position of a vessel most of the time, the fact that using fuel shifts the position of the CoM within the vessel made it impractical to use CoM for the vessel’s bounding box origin.WARNING about using SET with this suffix: If this bounds box was obtained using :attr:`Part:BOUNDS` or :attr:`Vessel:BOUNDS`, then this suffix keeps changing its value to remain correct as the vessel rotates or moves. But ONLY if you restrict your use of this suffix to GET-only. If you ever SET this suffix, kOS stops that auto-updating so it won’t override the value you gave. Generally, using SET on this suffix was only ever intended for Bounds you created manually with the BOUNDS() function.
- Bounds:RELORIGIN¶
- Type
Nonexistent
- Access
Nonexistent
This suffix does not exist. It is mentioned here simply because you might be trying to look up a suffix with this name, thinking it should exist, wondering “well, there’s an ABSMIN and a RELMIN, an ABSCENTER and a RELCENTER… where’s the RELORIGIN that should go with ABSORIGIN?”.
The reason there is no RELORIGIN is that given how a
Bounds
stores values, by its very definition the RELORIGIN would be V(0,0,0), always. It’s the origin of the bounding box, in the bounding box’s own reference frame - a reference frame with this spot as its origin.
- Bounds:FACING¶
- Type
- Access
Get/Set but read the note below before you SET it.
This defines the orientation of this bounding box’s local reference frame, by providing a rotation that will get you from the bounding-box relative orientation (in which the X, Y, and Z axes are parallel to the bounding box’s edges) to the absolute orientation (the ship-raw orientation the rest of kOS uses).
If this bounding box came from a Part, this will be the same as that part’s
Part:FACING
, and will keep being “magically” updated to stay aligned with that part’s facing if it moves or rotates (but see note below).If this bounding box came from a Vessel, this will be the same as that Vessel’s
Vessel:FACING
, and will keep being “magically” updated to stay aligned with that part’s facing if it moves or rotates (but see note below).WARNING about using SET with this suffix: If this bounds box was obtained using :attr:`Part:BOUNDS` or :attr:`Vessel:BOUNDS`, then this suffix keeps changing its value to remain correct as the vessel rotates or moves. But ONLY if you restrict your use of this suffix to GET-only. If you ever SET this suffix, kOS stops that auto-updating so it won’t override the value you gave. Generally, using SET on this suffix was only ever intended for Bounds you created manually with the BOUNDS() function.
- Bounds:RELMIN¶
- Type
Vector
in bounding-box relative reference frame- Access
Get/Set
A vector expressed in the bounding-box-relative reference frame (where the XYZ axes are parallel to the bounding box’s edges).
This defines one corner of the bounding box. It is the “negative-most” corner of the box. If you drew a vector from the box’s origin point to its “negative-most” corner, that would be this vector. By “negative-most” that simply means the corner where the X, Y, and Z coordinates have their smallest values. (again, in the bounding box’s own reference frame, not the absolute world (ship-raw) frame.)
This corner will always be the diagonally opposite corner from
Bounds:RELMAX
.If you SET this value, you are changing the size of the bounding box, making it larger (or smaller), as well as stretching or shrinking it, depending on the new value you pick. Doing so doesn’t actually change the size of a part or vessel, and is really only useful if you are working with your own
Bounds
you created manually with theBounds()
built-in function.Be careful when trying to “add” the RELMIN vector to other vectors in the game. It’s not oriented in ship-raw coords. To rotate it into ship-raw coords you can multiply it by the bounds facing like so:
MyBounds:FACING * MyBounds:RELMIN
.
- Bounds:RELMAX¶
- Type
Vector
in bounding-box relative reference frame- Access
Get/Set
A vector expressed in the bounding-box-relative reference frame (where the XYZ axes are parallel to the bounding box’s edges).
This defines one corner of the bounding box. It is the “positive-most” corner of the box. If you drew a vector from the box’s origin point to its “positive-most” corner, that would be this vector. By “positive-most” that simply means the corner where the X, Y, and Z coordinates have their greatest values. (again, in the bounding box’s own reference frame, not the absolute world (ship-raw) frame.)
This corner will always be the diagonally opposite corner from
Bounds:RELMIN
.If you SET this value, you are changing the size of the bounding box, making it larger (or smaller), as well as stretching or shrinking it, depending on the new value you pick. Doing so doesn’t actually change the size of a part or vessel, and is really only useful if you are working with your own
Bounds
you created manually with theBounds()
built-in function.Be careful when trying to “add” the RELMAX vector to other vectors in the game. It’s not oriented in ship-raw coords. To rotate it into ship-raw coords you can multiply it by the bounds facing like so:
MyBounds:FACING * MyBounds:RELMAX
.
- Bounds:ABSMIN¶
- Type
- Access
Get
This is the same point as
Bounds:RELMIN
, except it has been rotated and translated into absolute coordinates (what kOS calls the ship-raw reference frame, that the rest of the position vectors in kOS use.)You cannot SET this value, because it is generated from the ABSORIGIN, the FACING, and the RELMIN.
Calculating the ABSMIN could be done in kerboscript from the other Bounds suffixes (see example below), but this is provided for convenience:
// The following two print lines should print // the same vector, within reason. (There may be a // small floating point precision variance between them): set B to ship:bounds. print B:ABSMIN. print B:ABSORIGIN + (B:FACING * B:RELMIN).
- Bounds:ABSMAX¶
- Type
- Access
Get
This is the same point as
Bounds:RELMAX
, except it has been rotated and translated into absolute coordinates (what kOS calls the ship-raw reference frame, that the rest of the position vectors in kOS use.)You cannot SET this value, because it is generated from the ABSORIGIN, the FACING, and the RELMAX.
Calculating the ABSMAX could be done in kerboscript from the other Bounds suffixes (see example below), but this is provided for convenience:
// The following two print lines should print // the same vector, within reason. (There may be a // small floating point precision variance between them): set B to ship:bounds. print B:ABSMAX. print B:ABSORIGIN + (B:FACING * B:RELMAX).
- Bounds:RELCENTER¶
- Type
Vector
in bounding-box relative reference frame- Access
Get-only
The center of the bounding box, in its own relative reference frame. (Not the absolute ship-raw reference frame the rest of kOS uses.)
This is the offset between the bounding box’s origin and its center.
The origin of a bounding box is often not at its center because a bounding box can extend further in one direction than the other. For example a vessel’s root part is often up at the top of the rocket, such a vessel’s bounding box will extend much further in the “aft” direction than it does in the “fore” direction. The wing parts in the game are often defined with their origin point at the base where they glue to the fuselage, not out in the middle of the wing.
Instead of being provided directly, this value could be calculated from the RELMIN and RELMAX. It’s simply the point exactly halfway between those two opposite corners.
- Bounds:ABSCENTER¶
- Type
- Access
Get-only
This is just the same thing as
Bounds:RELCENTER
, but in the absolute (ship-raw) reference frame which scripts might find more useful.It’s exactly equivalent to doing this:
MyBounds:ABSORIGIN + (MyBounds:FACING * MyBounds:RELCENTER).
Instead of being provided directly, this value could be calculated from the ABSMIN and ABSMAX. It’s simply the point exactly halfway between those two opposite corners.
- Bounds:EXTENTS¶
- Type
Vector
in bounding-box relative reference frame- Access
Get-only
A vector (in bounding-box relative reference frame, NOT the absolute (ship-raw) reference frame the rest of kOS uses) that describes where
Bounds:RELMAX
is, relative to to the box’s center (rather than to its origin).Note that the vector in the inverse direction of this one (that you’d get by multiplying it by -1), points from the center to the oppposite corner, the
Bounds:RELMIN
.
- Bounds:SIZE¶
- Type
Vector
in bounding-box relative reference frame- Access
Get-only
A vector (in bounding-box relative reference frame, NOT the absolute (ship-raw) reference frame the rest of kOS uses) that describes the ray from RELMIN to RELMAX that goes diagonally across the whole box. It’s always just the same thing you’d get if you took the
Bounds:EXTENTS
vector and multiplied it by the scalar 2.
- Bounds:FURTHESTCORNER(ray)¶
- Parameters
ray – The “that-a-way”
Vector
in absolute (ship-raw) reference frame.
- Returns
Vector
in absolute (ship-raw) referece frame.
Returns the position (in absolute (ship-raw) reference frame) of whichever of the 8 corners of this bounding box is “furthest” in the direction the ray vector is pointing. Useful when you want to know the furthest a bounding box extends in some gameworld direction.
Examples:
// Assume other_vessel has been set to some vessel nearby // other than the current SHIP: // local ves_box is other_vessel:bounds. local top is ves_box:furthestcorner(up:vector). local bottom is ves_box:furthestcorner(-up:vector). local from_other_to_me is ship:position - other_vessel:position. local nearest is ves_box:furthestcorner(from_other_to_me). print "The closest point on the other vessel's bounds box is this far away:". print nearest:mag. // A more complex example showing how you might use bounds boxes // when trying to figure out how big a target vessel is so you // know how to go around it: // local my_left is -ship:facing:starvector. local leftmost is ves_box:furthestcorner(my_left). print "In order to go around the other vessel, to the left, ". pritn "I would need to shift myself this far to my left:". print vdot(-ship:facing:starvector, leftmost).
- Bounds:BOTTOMALT¶
- Type
- Access
Get-only
The above-sea-level altitude reading from the bottom-most corner of this bounding box, toward whichever Body the current CPU vessel is orbiting.
Note that it’s always using the CPU vessel’s current body to decide which body is the one that defines the bounding box’s “downward” direction for picking its bottom-most corner, and it uses that same body to decide what counts as “altitude”, regardless of wether the bounds box is a bounds box of the current CPU vessel or something else.
To put it another way: You can’t “read” what the altitude of a bounding box above the Mun is if your ship is currently in Kerbin’s sphere of influence. If you are currently orbiting Kerbin, it will assume that the “bottom” of any Bounds box you refer to means “corner closest to Kerbin” and “altitude” means “distance from Kerbin’s Sea level”. Once your CPU vessel moves into the Mun’s sphere of influence this will change it it will now assume that the “bottom” of a Bounds is the corner closest to the Mun and the altitude you care about is the altitude above the Mun.
This may seem like a limitation, but it really isn’t, since you wouldn’t be able to query a vessel or a part for its bounding box if that vessel was far enough away to be outside the loading distance and thus its full set of parts isn’t “there”. It would only be a limitation for cases where you are inventing your own bounds boxes from scratch.
- Bounds:BOTTOMALTRADAR¶
- Type
- Access
Get-only
The radar-altitude reading from the bottom-most corner of this bounding box, toward whichever Body the current CPU vessel is orbiting. Same as
Bounds:BOTTOMALT
except for the difference between above-sea-level altitude versus radar altitude.
Side topic - what is a bounding box and how does kOS know about it?¶
A bounding box is a rectangular box around an item that represents the smallest space that contains all verteces of the item yet is still shaped like a box. It is common in graphics and video games for the GPU and/or game engine to maintain information about the bounding boxes of items in the game. Knowing this information is part of what they do to reduce their large workload. When checking if two items are colliding, or checking if part of object A blocks the line of sight between the camera and part of object B it’s trying to draw, a check against the bounding box first is quick and simple. If the thing you’re checking isn’t even intersecting the bounding box, then it’s impossible for it to be a hit on the actual complex shape inside it. The expensive check that looks at the exact shape of the object’s mesh can be skipped when you’re not even intersecting the bounding box around it.
Why is calling :bounds repeatedly a slow thing to do?¶
The bounding box Unity tracks for items is aligned with the XYZ axes of the game’s world coordinates, what kOS calls the “ship-raw” reference frame. This is vital for the game engine’s needs, as the speedy quick bounding box intersection tests work by doing simple greater-than and less-than tests of the coordinates. This means the box will also be “too big” if the item is rotated from the world’s XYZ axes, as the world-aligned box has to accomodate the item’s “diagonal” corners pushing the box bigger. For the purposes of a graphics engine, that’s fine, since erring on the side of a too-big bounding box is okay, since it’s nothing more than a time savings to short-circuit work, and not the final say-so on whether item A is touching item B.
To get a rotated box like is needed for kOS’s needs, where its tightly snug against the object in question, kOS has to go a bit more low-level and look at the actual meshes that make up the object, and look at their bounding boxes, which are aligned in the mesh’s own locally rotated XYZ axes, rather than world axes. Some ugly transforms of each of the 8 vertices of the mesh bounding box Unity knows about are needed, and there isn’t really a good way to do this without running a loop across all vertices of the box, which is what kOS does internally. For the whole vessel’s bounding box, this means doing those 8 vertices per part, on every part on the ship.
Why then is it faster to re-use the BOUNDS suffix?¶
Internally, kOS stores the relative bounds in the reference frame of the object (the facing suffix), and uses a delegate to keep getting the new orientation and origin of that object every time you ask for a Bounds’ suffix. Thus it doesn’t have to keep re-doing the transformations described above as the part rotates or shifts. It only has to apply the rotation and translation to the existing relative bounds it already calculated the expensive way the first time.
Credit to kRPC¶
The messy problem described above wasn’t solved until peeking at the code inside the kRPC mod, which also offers bounding box information. That peek showed a hint at the similar steps kOS would need to do to support the same thing, and gave an explanation why the “raw” bounding box that Unity gave was always too big and seemed wrong. That led to a lot of looking at answers on the Unity forum from other people who had the same problem, and did similar solutions to what kRPC was doing, indicating that despite looking like a lot of work, this was really the only way to do it. (Since kRPC and kOS are both GPL3, this peeking is totally legit.)