From 014bb4c8e22c637b151b6d81215c905aeee72dbd Mon Sep 17 00:00:00 2001 From: panzergame Date: Tue, 16 Oct 2018 00:28:35 +0200 Subject: [PATCH] UPBGE: Fix object render settings for batching groups. (#861) Previously object render settings such as light layer or object color were not managed for batching groups. For attribute like layers we can choose to make an union of all objects layers, but in case of color there's no meaning of union. To solve this issue one object should be used as reference for the group. The attribute KX_BatchGroup.referenceObject is defined and initialized to the first merged object when creating a batch group. Later the user can change it to an other object of the same group. Internally this object is set in KX_BatchGroup and RAS_BatchGroup, KX_BatchGroup see a KX_GameObject and RAS_BatchGroup only the RAS_MeshUser of this object. This mesh user is retrieved in RAS_DisplayArrayBucket::RunBatchingNode from the first mesh slot mesh user and passed to RAS_IMaerial::ActivateMeshUser which aims the update of material uniforms from object settings. --- .../rst/bge_types/bge.types.KX_BatchGroup.rst | 9 +++ source/gameengine/Ketsji/BL_BlenderShader.cpp | 3 +- source/gameengine/Ketsji/BL_BlenderShader.h | 2 +- source/gameengine/Ketsji/BL_Shader.cpp | 6 +- source/gameengine/Ketsji/BL_Shader.h | 4 +- source/gameengine/Ketsji/KX_BatchGroup.cpp | 75 ++++++++++++++++++- source/gameengine/Ketsji/KX_BatchGroup.h | 8 ++ .../gameengine/Ketsji/KX_BlenderMaterial.cpp | 6 +- source/gameengine/Ketsji/KX_BlenderMaterial.h | 2 +- source/gameengine/Ketsji/KX_TextMaterial.cpp | 2 +- source/gameengine/Ketsji/KX_TextMaterial.h | 2 +- .../gameengine/Rasterizer/RAS_BatchGroup.cpp | 13 +++- source/gameengine/Rasterizer/RAS_BatchGroup.h | 6 ++ .../Rasterizer/RAS_DisplayArrayBucket.cpp | 8 ++ source/gameengine/Rasterizer/RAS_IMaterial.h | 2 +- source/gameengine/Rasterizer/RAS_MeshSlot.cpp | 2 +- 16 files changed, 129 insertions(+), 21 deletions(-) diff --git a/doc/python_api/rst/bge_types/bge.types.KX_BatchGroup.rst b/doc/python_api/rst/bge_types/bge.types.KX_BatchGroup.rst index 456ee869968b..fd198c09a4dd 100644 --- a/doc/python_api/rst/bge_types/bge.types.KX_BatchGroup.rst +++ b/doc/python_api/rst/bge_types/bge.types.KX_BatchGroup.rst @@ -19,12 +19,21 @@ base class --- :class:`EXP_Value` batchGroup = types.KX_BatchGroup([scene.objects["Cube"], scene.objects["Plane"]]) + .. warning:: + + Rendering settings unique to objects such as :data:`KX_GameObject.layer` and :data:`KX_GameObject.color` are shared when using batch groups. + These settings are taken from object :attr:`referenceObject`. + .. attribute:: objects The list of the objects merged. (read only) :type: :class:`EXP_ListValue` of :class:`KX_GameObject` + .. attribute:: referenceObject + + The object used for object rendering settings (layer, color...). + .. method:: merge(objects) Merge meshes using the transformation of the objects using them. diff --git a/source/gameengine/Ketsji/BL_BlenderShader.cpp b/source/gameengine/Ketsji/BL_BlenderShader.cpp index 5a6d15ec4a6e..0c4e01dc27e8 100644 --- a/source/gameengine/Ketsji/BL_BlenderShader.cpp +++ b/source/gameengine/Ketsji/BL_BlenderShader.cpp @@ -160,13 +160,12 @@ void BL_BlenderShader::UpdateLights(RAS_Rasterizer *rasty) GPU_material_update_lamps(m_gpuMat, rasty->GetViewMatrix().Data(), rasty->GetViewInvMatrix().Data()); } -void BL_BlenderShader::Update(RAS_MeshSlot *ms, RAS_Rasterizer *rasty) +void BL_BlenderShader::Update(RAS_MeshUser *meshUser, RAS_Rasterizer *rasty) { if (!GPU_material_bound(m_gpuMat)) { return; } - RAS_MeshUser *meshUser = ms->m_meshUser; const float (&obcol)[4] = meshUser->GetColor().Data(); GPU_material_bind_uniforms(m_gpuMat, meshUser->GetMatrix().Data(), rasty->GetViewMatrix().Data(), diff --git a/source/gameengine/Ketsji/BL_BlenderShader.h b/source/gameengine/Ketsji/BL_BlenderShader.h index 39c9f1af11e4..f7922623ddde 100644 --- a/source/gameengine/Ketsji/BL_BlenderShader.h +++ b/source/gameengine/Ketsji/BL_BlenderShader.h @@ -78,7 +78,7 @@ class BL_BlenderShader RAS_InstancingBuffer::Attrib GetInstancingAttribs() const; void UpdateLights(RAS_Rasterizer *rasty); - void Update(RAS_MeshSlot *ms, RAS_Rasterizer *rasty); + void Update(RAS_MeshUser *meshUser, RAS_Rasterizer *rasty); /// Return true if the shader uses a special vertex shader for geometry instancing. bool UseInstancing() const; diff --git a/source/gameengine/Ketsji/BL_Shader.cpp b/source/gameengine/Ketsji/BL_Shader.cpp index 2ed85c87d5b0..4660ff708691 100644 --- a/source/gameengine/Ketsji/BL_Shader.cpp +++ b/source/gameengine/Ketsji/BL_Shader.cpp @@ -162,17 +162,17 @@ void BL_Shader::BindProg() RAS_Shader::BindProg(); } -void BL_Shader::Update(RAS_Rasterizer *rasty, RAS_MeshSlot *ms) +void BL_Shader::Update(RAS_Rasterizer *rasty, RAS_MeshUser *meshUser) { #ifdef WITH_PYTHON if (PyList_GET_SIZE(m_callbacks[CALLBACKS_OBJECT]) > 0) { - KX_GameObject *gameobj = KX_GameObject::GetClientObject((KX_ClientObjectInfo *)ms->m_meshUser->GetClientObject()); + KX_GameObject *gameobj = KX_GameObject::GetClientObject((KX_ClientObjectInfo *)meshUser->GetClientObject()); PyObject *args[] = {gameobj->GetProxy()}; EXP_RunPythonCallBackList(m_callbacks[CALLBACKS_OBJECT], args, 0, ARRAY_SIZE(args)); } #endif // WITH_PYTHON - RAS_Shader::Update(rasty, mt::mat4(ms->m_meshUser->GetMatrix())); + RAS_Shader::Update(rasty, meshUser->GetMatrix()); } #ifdef WITH_PYTHON diff --git a/source/gameengine/Ketsji/BL_Shader.h b/source/gameengine/Ketsji/BL_Shader.h index 3b94f7a02bb4..9d2d3c4af46d 100644 --- a/source/gameengine/Ketsji/BL_Shader.h +++ b/source/gameengine/Ketsji/BL_Shader.h @@ -60,11 +60,11 @@ class BL_Shader : public EXP_Value, public virtual RAS_Shader void BindProg(); - /** Update the uniform shader for the current rendered mesh slot. + /** Update the uniform shader for the current rendered mesh user (= object). * The python callbacks are executed in this function and at the end * RAS_Shader::Update(rasty, mat) is called. */ - void Update(RAS_Rasterizer *rasty, RAS_MeshSlot *ms); + void Update(RAS_Rasterizer *rasty, RAS_MeshUser *meshUser); // Python interface #ifdef WITH_PYTHON diff --git a/source/gameengine/Ketsji/KX_BatchGroup.cpp b/source/gameengine/Ketsji/KX_BatchGroup.cpp index 658d31a3c5aa..7a5815f6ab51 100644 --- a/source/gameengine/Ketsji/KX_BatchGroup.cpp +++ b/source/gameengine/Ketsji/KX_BatchGroup.cpp @@ -32,6 +32,7 @@ #include "CM_Message.h" KX_BatchGroup::KX_BatchGroup() + :m_referenceObject(nullptr) { m_objects = new EXP_ListValue(); // The objects are not owned by the batching group, so not released on list releasing. @@ -53,6 +54,31 @@ EXP_ListValue *KX_BatchGroup::GetObjects() const return m_objects; } +KX_GameObject *KX_BatchGroup::GetReferenceObject() const +{ + return m_referenceObject; +} + +bool KX_BatchGroup::SetReferenceObject(KX_GameObject *object) +{ + if (object) { + RAS_MeshUser *meshUser = object->GetMeshUser(); + if (!meshUser) { + CM_Error("object \"" << object->GetName() << "\" doesn't contain a mesh"); + return false; + } + if (meshUser->GetBatchGroup() != this) { + CM_Error("object \"" << object->GetName() << "\" is not a part of this batch group"); + return false; + } + } + + m_referenceObject = object; + SetReferenceMeshUser(object ? object->GetMeshUser() : nullptr); + + return true; +} + void KX_BatchGroup::MergeObjects(const std::vector& objects) { for (KX_GameObject *gameobj : objects) { @@ -94,12 +120,22 @@ void KX_BatchGroup::SplitObjects(const std::vector& objects) if (SplitMeshUser(meshUser)) { m_objects->RemoveValue(gameobj); + if (gameobj == m_referenceObject) { + SetReferenceObject(nullptr); + } } else { CM_Error("failed split object \"" << gameobj->GetName() << "\""); } } + // Update the reference mesh user if it was splitted. + if (!m_objects->Empty() && !m_referenceObject) { + // Use the first object as reference for layer and color. + KX_GameObject *firstObject = m_objects->GetFront(); + SetReferenceObject(firstObject); + } + RemoveMeshUser(); } @@ -133,12 +169,18 @@ static PyObject *py_new(PyTypeObject *type, PyObject *args, PyObject *kwds) KX_BatchGroup *batchGroup = new KX_BatchGroup(); batchGroup->MergeObjects(objects); - if (batchGroup->GetObjects()->Empty()) { + + EXP_ListValue *mergedObjects = batchGroup->GetObjects(); + if (mergedObjects->Empty()) { PyErr_SetString(PyExc_SystemError, "KX_BatchGroup(objects): none objects were merged."); delete batchGroup; return nullptr; } + // Use the first object as reference for layer and color. + KX_GameObject *firstObject = mergedObjects->GetFront(); + batchGroup->SetReferenceObject(firstObject); + return batchGroup->GetProxy(); } @@ -173,6 +215,7 @@ PyMethodDef KX_BatchGroup::Methods[] = { PyAttributeDef KX_BatchGroup::Attributes[] = { EXP_PYATTRIBUTE_RO_FUNCTION("objects", KX_BatchGroup, pyattr_get_objects), + EXP_PYATTRIBUTE_RW_FUNCTION("referenceObject", KX_BatchGroup, pyattr_get_referenceObject, pyattr_set_referenceObject), EXP_PYATTRIBUTE_NULL // Sentinel }; @@ -182,6 +225,30 @@ PyObject *KX_BatchGroup::pyattr_get_objects(EXP_PyObjectPlus *self_v, const EXP_ return self->GetObjects()->GetProxy(); } +PyObject *KX_BatchGroup::pyattr_get_referenceObject(EXP_PyObjectPlus *self_v, const EXP_PYATTRIBUTE_DEF *attrdef) +{ + KX_BatchGroup *self = static_cast(self_v); + return self->GetReferenceObject()->GetProxy(); +} + +int KX_BatchGroup::pyattr_set_referenceObject(EXP_PyObjectPlus *self_v, const EXP_PYATTRIBUTE_DEF *attrdef, PyObject *value) +{ + KX_BatchGroup *self = static_cast(self_v); + + KX_GameObject *object; + if (!ConvertPythonToGameObject(KX_GetActiveScene()->GetLogicManager(), + value, &object, false, "KX_BatchGroup.referenceObject")) + { + return PY_SET_ATTR_FAIL; + } + + if (self->SetReferenceObject(object)) { + return PY_SET_ATTR_SUCCESS; + } + + return PY_SET_ATTR_FAIL; +} + EXP_PYMETHODDEF_DOC(KX_BatchGroup, merge, "merge(objects)") { PyObject *pylist; @@ -209,7 +276,7 @@ EXP_PYMETHODDEF_DOC(KX_BatchGroup, merge, "merge(objects)") MergeObjects(objects); - Py_RETURN_NONE;; + Py_RETURN_NONE; } EXP_PYMETHODDEF_DOC(KX_BatchGroup, split, "split(objects)") @@ -239,14 +306,14 @@ EXP_PYMETHODDEF_DOC(KX_BatchGroup, split, "split(objects)") SplitObjects(objects); - Py_RETURN_NONE;; + Py_RETURN_NONE; } EXP_PYMETHODDEF_DOC(KX_BatchGroup, destruct, "destruct()") { Destruct(); - Py_RETURN_NONE;; + Py_RETURN_NONE; } #endif // WITH_PYTHON diff --git a/source/gameengine/Ketsji/KX_BatchGroup.h b/source/gameengine/Ketsji/KX_BatchGroup.h index b6f765182ae7..ae012625b55b 100644 --- a/source/gameengine/Ketsji/KX_BatchGroup.h +++ b/source/gameengine/Ketsji/KX_BatchGroup.h @@ -40,6 +40,8 @@ class KX_BatchGroup : public EXP_Value, public RAS_BatchGroup private: /// The objects currently merged in the batch group. EXP_ListValue *m_objects; + /// Reference object used to retrieve layer and color. + KX_GameObject *m_referenceObject; public: KX_BatchGroup(); @@ -49,6 +51,10 @@ class KX_BatchGroup : public EXP_Value, public RAS_BatchGroup EXP_ListValue *GetObjects() const; + KX_GameObject *GetReferenceObject() const; + /// Set reference object with error checking. Return false on error. + bool SetReferenceObject(KX_GameObject *object); + /** Merge a list of objects using their mesh user and transformation. * \param objects The list of objects to merge. */ @@ -62,6 +68,8 @@ class KX_BatchGroup : public EXP_Value, public RAS_BatchGroup #ifdef WITH_PYTHON static PyObject *pyattr_get_objects(EXP_PyObjectPlus *self_v, const EXP_PYATTRIBUTE_DEF *attrdef); + static PyObject *pyattr_get_referenceObject(EXP_PyObjectPlus *self_v, const EXP_PYATTRIBUTE_DEF *attrdef); + static int pyattr_set_referenceObject(EXP_PyObjectPlus *self_v, const EXP_PYATTRIBUTE_DEF *attrdef, PyObject *value); EXP_PYMETHOD_DOC(KX_BatchGroup, merge); EXP_PYMETHOD_DOC(KX_BatchGroup, split); diff --git a/source/gameengine/Ketsji/KX_BlenderMaterial.cpp b/source/gameengine/Ketsji/KX_BlenderMaterial.cpp index ec8c4a9e8044..5a33f4185e7f 100644 --- a/source/gameengine/Ketsji/KX_BlenderMaterial.cpp +++ b/source/gameengine/Ketsji/KX_BlenderMaterial.cpp @@ -363,16 +363,16 @@ bool KX_BlenderMaterial::UsesLighting() const } } -void KX_BlenderMaterial::ActivateMeshSlot(RAS_MeshSlot *ms, RAS_Rasterizer *rasty, const mt::mat3x4& camtrans) +void KX_BlenderMaterial::ActivateMeshUser(RAS_MeshUser *meshUser, RAS_Rasterizer *rasty, const mt::mat3x4& camtrans) { if (m_shader && m_shader->Ok()) { - m_shader->Update(rasty, ms); + m_shader->Update(rasty, meshUser); m_shader->ApplyShader(); // Update OpenGL lighting builtins. rasty->ProcessLighting(UsesLighting(), camtrans); } else if (m_blenderShader) { - m_blenderShader->Update(ms, rasty); + m_blenderShader->Update(meshUser, rasty); /* we do blend modes here, because they can change per object * with the same material due to obcolor/obalpha */ diff --git a/source/gameengine/Ketsji/KX_BlenderMaterial.h b/source/gameengine/Ketsji/KX_BlenderMaterial.h index 487aba2618ba..922ee9c9b7c3 100644 --- a/source/gameengine/Ketsji/KX_BlenderMaterial.h +++ b/source/gameengine/Ketsji/KX_BlenderMaterial.h @@ -35,7 +35,7 @@ class KX_BlenderMaterial : public EXP_Value, public BL_Resource, public RAS_IMat virtual void Activate(RAS_Rasterizer *rasty); virtual void Desactivate(RAS_Rasterizer *rasty); virtual void ActivateInstancing(RAS_Rasterizer *rasty, RAS_InstancingBuffer *buffer); - virtual void ActivateMeshSlot(RAS_MeshSlot *ms, RAS_Rasterizer *rasty, const mt::mat3x4& camtrans); + virtual void ActivateMeshUser(RAS_MeshUser *meshUser, RAS_Rasterizer *rasty, const mt::mat3x4& camtrans); void UpdateTextures(); void ApplyTextures(); diff --git a/source/gameengine/Ketsji/KX_TextMaterial.cpp b/source/gameengine/Ketsji/KX_TextMaterial.cpp index 4fc224477862..229a2fb18f76 100644 --- a/source/gameengine/Ketsji/KX_TextMaterial.cpp +++ b/source/gameengine/Ketsji/KX_TextMaterial.cpp @@ -59,7 +59,7 @@ void KX_TextMaterial::DesactivateInstancing() { } -void KX_TextMaterial::ActivateMeshSlot(RAS_MeshSlot *ms, RAS_Rasterizer *rasty, const mt::mat3x4& camtrans) +void KX_TextMaterial::ActivateMeshUser(RAS_MeshUser *meshUser, RAS_Rasterizer *rasty, const mt::mat3x4& camtrans) { } diff --git a/source/gameengine/Ketsji/KX_TextMaterial.h b/source/gameengine/Ketsji/KX_TextMaterial.h index ccf9575ba9a5..2432eb82694b 100644 --- a/source/gameengine/Ketsji/KX_TextMaterial.h +++ b/source/gameengine/Ketsji/KX_TextMaterial.h @@ -41,7 +41,7 @@ class KX_TextMaterial : public RAS_IMaterial virtual void Desactivate(RAS_Rasterizer *rasty); virtual void ActivateInstancing(RAS_Rasterizer *rasty, RAS_InstancingBuffer *buffer); virtual void DesactivateInstancing(); - virtual void ActivateMeshSlot(RAS_MeshSlot *ms, RAS_Rasterizer *rasty, const mt::mat3x4& camtrans); + virtual void ActivateMeshUser(RAS_MeshUser *meshUser, RAS_Rasterizer *rasty, const mt::mat3x4& camtrans); virtual const std::string GetTextureName() const; virtual Material *GetBlenderMaterial() const; diff --git a/source/gameengine/Rasterizer/RAS_BatchGroup.cpp b/source/gameengine/Rasterizer/RAS_BatchGroup.cpp index 2ba316246afa..86e692096604 100644 --- a/source/gameengine/Rasterizer/RAS_BatchGroup.cpp +++ b/source/gameengine/Rasterizer/RAS_BatchGroup.cpp @@ -42,7 +42,8 @@ RAS_BatchGroup::Batch::Batch() } RAS_BatchGroup::RAS_BatchGroup() - :m_users(0) + :m_users(0), + m_referenceMeshUser(nullptr) { } @@ -70,6 +71,16 @@ RAS_BatchGroup *RAS_BatchGroup::RemoveMeshUser() return this; } +RAS_MeshUser *RAS_BatchGroup::GetReferenceMeshUser() const +{ + return m_referenceMeshUser; +} + +void RAS_BatchGroup::SetReferenceMeshUser(RAS_MeshUser *meshUser) +{ + m_referenceMeshUser = meshUser; +} + bool RAS_BatchGroup::MergeMeshSlot(RAS_BatchGroup::Batch& batch, RAS_MeshSlot& slot, const mt::mat4& mat) { RAS_DisplayArrayBucket *origArrayBucket = slot.m_displayArrayBucket; diff --git a/source/gameengine/Rasterizer/RAS_BatchGroup.h b/source/gameengine/Rasterizer/RAS_BatchGroup.h index 8f41292961b0..d74d8238f81c 100644 --- a/source/gameengine/Rasterizer/RAS_BatchGroup.h +++ b/source/gameengine/Rasterizer/RAS_BatchGroup.h @@ -37,6 +37,8 @@ class RAS_BatchGroup private: /// The reference counter. short m_users; + /// Reference object used to retrieve layer and color. + RAS_MeshUser *m_referenceMeshUser; /// A batch contained the merged display array for all the display array used for a given material. class Batch @@ -80,6 +82,10 @@ class RAS_BatchGroup /// Notice the batch group that it is unused by one less mesh user. RAS_BatchGroup *RemoveMeshUser(); + RAS_MeshUser *GetReferenceMeshUser() const; + /// Change reference mesh user without error check. + void SetReferenceMeshUser(RAS_MeshUser *meshUser); + /** Merge the display array of the mesh slots contained in the mesh user. * \param meshUser The mesh user to merge mesh slots from. * \param mat The object matrix to use in display array merging. It's not the matrix from diff --git a/source/gameengine/Rasterizer/RAS_DisplayArrayBucket.cpp b/source/gameengine/Rasterizer/RAS_DisplayArrayBucket.cpp index f2732c6ee07b..cc457b8d2716 100644 --- a/source/gameengine/Rasterizer/RAS_DisplayArrayBucket.cpp +++ b/source/gameengine/Rasterizer/RAS_DisplayArrayBucket.cpp @@ -36,7 +36,9 @@ #include "RAS_MaterialBucket.h" #include "RAS_IMaterial.h" #include "RAS_Mesh.h" +#include "RAS_MeshUser.h" #include "RAS_Deformer.h" +#include "RAS_BatchGroup.h" #include "RAS_Rasterizer.h" #include "RAS_InstancingBuffer.h" #include "RAS_BucketManager.h" @@ -321,6 +323,7 @@ void RAS_DisplayArrayBucket::RunBatchingNode(const RAS_DisplayArrayNodeTuple& tu RAS_ManagerNodeData *managerData = tuple.m_managerData; RAS_MaterialNodeData *materialData = tuple.m_materialData; + RAS_IMaterial *material = materialData->m_material; const unsigned int nummeshslots = m_activeMeshSlots.size(); // We must use a int instead of unsigned size to match GLsizei type. @@ -368,6 +371,11 @@ void RAS_DisplayArrayBucket::RunBatchingNode(const RAS_DisplayArrayNodeTuple& tu * To be sure we don't use the old face wise we force it to true. */ rasty->SetFrontFace(true); + // Retrieve batch group from first active mesh slot. + RAS_BatchGroup *group = m_activeMeshSlots.front()->m_meshUser->GetBatchGroup(); + // Use batch group reference mesh user for layer and object color. + material->ActivateMeshUser(group->GetReferenceMeshUser(), rasty, managerData->m_trans); + RAS_AttributeArrayStorage *attribStorage = m_nodeData.m_attribStorage; attribStorage->BindPrimitives(); diff --git a/source/gameengine/Rasterizer/RAS_IMaterial.h b/source/gameengine/Rasterizer/RAS_IMaterial.h index e7920919ac38..c84316370c37 100644 --- a/source/gameengine/Rasterizer/RAS_IMaterial.h +++ b/source/gameengine/Rasterizer/RAS_IMaterial.h @@ -108,7 +108,7 @@ class RAS_IMaterial : public CM_UpdateServer virtual void Activate(RAS_Rasterizer *rasty) = 0; virtual void Desactivate(RAS_Rasterizer *rasty) = 0; virtual void ActivateInstancing(RAS_Rasterizer *rasty, RAS_InstancingBuffer *buffer) = 0; - virtual void ActivateMeshSlot(RAS_MeshSlot *ms, RAS_Rasterizer *rasty, const mt::mat3x4& camtrans) = 0; + virtual void ActivateMeshUser(RAS_MeshUser *meshUser, RAS_Rasterizer *rasty, const mt::mat3x4& camtrans) = 0; bool IsAlpha() const; bool IsAlphaDepth() const; diff --git a/source/gameengine/Rasterizer/RAS_MeshSlot.cpp b/source/gameengine/Rasterizer/RAS_MeshSlot.cpp index bb028d1d93b7..c0962f2c2b9a 100644 --- a/source/gameengine/Rasterizer/RAS_MeshSlot.cpp +++ b/source/gameengine/Rasterizer/RAS_MeshSlot.cpp @@ -90,7 +90,7 @@ void RAS_MeshSlot::RunNode(const RAS_MeshSlotNodeTuple& tuple) RAS_DisplayArrayStorage *storage = displayArrayData->m_arrayStorage; if (!managerData->m_shaderOverride) { - materialData->m_material->ActivateMeshSlot(this, rasty, managerData->m_trans); + materialData->m_material->ActivateMeshUser(m_meshUser, rasty, managerData->m_trans); if (materialData->m_zsort && storage) { displayArrayData->m_array->SortPolygons(