diff --git a/autogen/lua_definitions/functions.lua b/autogen/lua_definitions/functions.lua
index 21a0e2888..9123e4015 100644
--- a/autogen/lua_definitions/functions.lua
+++ b/autogen/lua_definitions/functions.lua
@@ -7082,6 +7082,14 @@ function get_pos_from_transform_mtx(dest, objMtx, camMtx)
-- ...
end
+--- @param dest Mat4
+--- @param objMtx Mat4
+--- @param camMtx Mat4
+--- Strip the camera-view matrix `camMtx` off of a model-view matrix `objMtx` and store the resulting matrix in `dest`. This can be used to get the object's transforms in world space.
+function get_world_mtx_from_transform(dest, objMtx, camMtx)
+ -- ...
+end
+
--- @param value number
--- @param replacement number
--- @return number
diff --git a/docs/lua/functions-4.md b/docs/lua/functions-4.md
index 114faf99d..11bef099b 100644
--- a/docs/lua/functions-4.md
+++ b/docs/lua/functions-4.md
@@ -5634,6 +5634,31 @@ Extracts the position (translation component) from the transformation matrix `ob
+## [get_world_mtx_from_transform](#get_world_mtx_from_transform)
+
+### Description
+Strip the camera-view matrix `camMtx` off of a model-view matrix `objMtx` and store the resulting matrix in `dest`. This can be used to get the object's transforms in world space.
+
+### Lua Example
+`get_world_mtx_from_transform(dest, objMtx, camMtx)`
+
+### Parameters
+| Field | Type |
+| ----- | ---- |
+| dest | [Mat4](structs.md#Mat4) |
+| objMtx | [Mat4](structs.md#Mat4) |
+| camMtx | [Mat4](structs.md#Mat4) |
+
+### Returns
+- None
+
+### C Prototype
+`void get_world_mtx_from_transform(VEC_OUT Mat4 dest, Mat4 objMtx, Mat4 camMtx);`
+
+[:arrow_up_small:](#)
+
+
+
---
# functions from math_util.inl
diff --git a/docs/lua/functions.md b/docs/lua/functions.md
index 0c8e1ceb9..35ac3c04e 100644
--- a/docs/lua/functions.md
+++ b/docs/lua/functions.md
@@ -1261,6 +1261,7 @@
- [mtxf_inverse](functions-4.md#mtxf_inverse)
- [mtxf_inverse_non_affine](functions-4.md#mtxf_inverse_non_affine)
- [get_pos_from_transform_mtx](functions-4.md#get_pos_from_transform_mtx)
+ - [get_world_mtx_from_transform](functions-4.md#get_world_mtx_from_transform)
diff --git a/src/engine/math_util.c b/src/engine/math_util.c
index 04c4ad3d6..a04382331 100644
--- a/src/engine/math_util.c
+++ b/src/engine/math_util.c
@@ -883,3 +883,16 @@ OPTIMIZE_O3 Vec3fp get_pos_from_transform_mtx(VEC_OUT Vec3f dest, Mat4 objMtx, M
return dest;
}
+/**
+ * Extract world-space transformations given an object's model-view matrix and a
+ * camera matrix. A model-view matrix generated during rendering is a combination
+ * of world-space and camera transforms to position and orient objects in a scene
+ * relative to the camera view. By multiplying this matrix with the inverse of the
+ * camera matrix, the camera transforms can be removed to get the position and
+ * orientation of an object relative to world-space.
+ */
+OPTIMIZE_O3 void get_world_mtx_from_transform(VEC_OUT Mat4 dest, Mat4 objMtx, Mat4 camMtx) {
+ Mat4 invCamMtx;
+ mtxf_inverse(invCamMtx, camMtx);
+ mtxf_mul(dest, objMtx, invCamMtx);
+}
\ No newline at end of file
diff --git a/src/engine/math_util.h b/src/engine/math_util.h
index 888a64adf..31adca783 100644
--- a/src/engine/math_util.h
+++ b/src/engine/math_util.h
@@ -291,5 +291,9 @@ Extracts the position (translation component) from the transformation matrix `ob
|descriptionEnd| */
OPTIMIZE_O3 Vec3fp get_pos_from_transform_mtx(VEC_OUT Vec3f dest, Mat4 objMtx, Mat4 camMtx);
+/* |description|
+Strip the camera-view matrix `camMtx` off of a model-view matrix `objMtx` and store the resulting matrix in `dest`. This can be used to get the object's transforms in world space.
+|descriptionEnd| */
+OPTIMIZE_O3 void get_world_mtx_from_transform(VEC_OUT Mat4 dest, Mat4 objMtx, Mat4 camMtx);
#endif // MATH_UTIL_H
diff --git a/src/game/rendering_graph_node.c b/src/game/rendering_graph_node.c
index 50d0b87fc..8f4394657 100644
--- a/src/game/rendering_graph_node.c
+++ b/src/game/rendering_graph_node.c
@@ -1213,7 +1213,12 @@ static void geo_process_animated_part(struct GraphNodeAnimatedPart *node) {
Vec3s rot = { rotation[2], rotation[0], rotation[1] };
vec3s_copy(gCurMarioBodyState->animPartsRot[gCurMarioBodyState->currAnimPart], rot);
- mtxf_copy(gCurMarioBodyState->animPartsMtx[gCurMarioBodyState->currAnimPart], gMatStack[gMatStackIndex]);
+
+ get_world_mtx_from_transform(
+ gCurMarioBodyState->animPartsMtx[gCurMarioBodyState->currAnimPart],
+ gMatStack[gMatStackIndex],
+ *gCurGraphNodeCamera->matrixPtr
+ );
}
if (gCurGraphNodeMarioState != NULL) {
@@ -1872,7 +1877,12 @@ static void geo_process_bone(struct GraphNodeBone *node) {
Vec3s rot = { rotation[2], rotation[0], rotation[1] };
vec3s_copy(gCurMarioBodyState->animPartsRot[gCurMarioBodyState->currAnimPart], rot);
- mtxf_copy(gCurMarioBodyState->animPartsMtx[gCurMarioBodyState->currAnimPart], gMatStack[gMatStackIndex]);
+
+ get_world_mtx_from_transform(
+ gCurMarioBodyState->animPartsMtx[gCurMarioBodyState->currAnimPart],
+ gMatStack[gMatStackIndex],
+ *gCurGraphNodeCamera->matrixPtr
+ );
}
if (gCurGraphNodeMarioState != NULL) {
diff --git a/src/pc/lua/smlua_functions_autogen.c b/src/pc/lua/smlua_functions_autogen.c
index 91917e2f7..b60c934aa 100644
--- a/src/pc/lua/smlua_functions_autogen.c
+++ b/src/pc/lua/smlua_functions_autogen.c
@@ -20321,6 +20321,35 @@ int smlua_func_get_pos_from_transform_mtx(lua_State* L) {
return 1;
}
+int smlua_func_get_world_mtx_from_transform(lua_State* L) {
+ if (L == NULL) { return 0; }
+
+ int top = lua_gettop(L);
+ if (top != 3) {
+ LOG_LUA_LINE("Improper param count for '%s': Expected %u, Received %u", "get_world_mtx_from_transform", 3, top);
+ return 0;
+ }
+
+
+ Mat4 dest;
+ smlua_get_mat4(dest, 1);
+ if (!gSmLuaConvertSuccess) { LOG_LUA("Failed to convert parameter %u for function '%s'", 1, "get_world_mtx_from_transform"); return 0; }
+
+ Mat4 objMtx;
+ smlua_get_mat4(objMtx, 2);
+ if (!gSmLuaConvertSuccess) { LOG_LUA("Failed to convert parameter %u for function '%s'", 2, "get_world_mtx_from_transform"); return 0; }
+
+ Mat4 camMtx;
+ smlua_get_mat4(camMtx, 3);
+ if (!gSmLuaConvertSuccess) { LOG_LUA("Failed to convert parameter %u for function '%s'", 3, "get_world_mtx_from_transform"); return 0; }
+
+ get_world_mtx_from_transform(dest, objMtx, camMtx);
+
+ smlua_push_mat4(dest, 1);
+
+ return 1;
+}
+
///////////////////
// math_util.inl //
///////////////////
@@ -38418,6 +38447,7 @@ void smlua_bind_functions_autogen(void) {
smlua_bind_function(L, "mtxf_inverse", smlua_func_mtxf_inverse);
smlua_bind_function(L, "mtxf_inverse_non_affine", smlua_func_mtxf_inverse_non_affine);
smlua_bind_function(L, "get_pos_from_transform_mtx", smlua_func_get_pos_from_transform_mtx);
+ smlua_bind_function(L, "get_world_mtx_from_transform", smlua_func_get_world_mtx_from_transform);
// math_util.inl
smlua_bind_function(L, "replace_value_if_not_zero", smlua_func_replace_value_if_not_zero);