MAINT-2268 Add right-click->"Mute Particle Owner" feature

master
Dave Parks 2013-01-25 17:29:44 -06:00
parent ac6a19d456
commit b8f74b42a7
15 changed files with 380 additions and 23 deletions

View File

@ -121,7 +121,7 @@ BOOL LLToolPie::handleMouseDown(S32 x, S32 y, MASK mask)
BOOL LLToolPie::handleRightMouseDown(S32 x, S32 y, MASK mask)
{
// don't pick transparent so users can't "pay" transparent objects
mPick = gViewerWindow->pickImmediate(x, y, FALSE);
mPick = gViewerWindow->pickImmediate(x, y, FALSE, TRUE);
mPick.mKeyMask = mask;
// claim not handled so UI focus stays same
@ -1711,6 +1711,13 @@ BOOL LLToolPie::handleRightClickPick()
showVisualContextMenuEffect();
}
}
else if (mPick.mParticleOwnerID.notNull())
{
if (gMenuMuteParticle && mPick.mParticleOwnerID != gAgent.getID())
{
gMenuMuteParticle->show(x,y);
}
}
LLTool::handleRightMouseDown(x, y, mask);
// We handled the event.

View File

@ -165,6 +165,7 @@ LLContextMenu *gMenuObject = NULL;
LLContextMenu *gMenuAttachmentSelf = NULL;
LLContextMenu *gMenuAttachmentOther = NULL;
LLContextMenu *gMenuLand = NULL;
LLContextMenu *gMenuMuteParticle = NULL;
const std::string SAVE_INTO_TASK_INVENTORY("Save Object Back to Object Contents");
@ -423,6 +424,9 @@ void init_menus()
gMenuLand = LLUICtrlFactory::createFromFile<LLContextMenu>(
"menu_land.xml", gMenuHolder, registry);
gMenuMuteParticle = LLUICtrlFactory::createFromFile<LLContextMenu>(
"menu_mute_particle.xml", gMenuHolder, registry);
///
/// set up the colors
///
@ -2457,6 +2461,9 @@ void cleanup_menus()
delete gMenuLand;
gMenuLand = NULL;
delete gMenuMuteParticle;
gMenuMuteParticle = NULL;
delete gMenuBarView;
gMenuBarView = NULL;
@ -2810,6 +2817,13 @@ bool enable_object_edit()
return enable;
}
bool enable_mute_particle()
{
const LLPickInfo& pick = LLToolPie::getInstance()->getPick();
return pick.mParticleOwnerID != LLUUID::null && pick.mParticleOwnerID != gAgent.getID();
}
// mutually exclusive - show either edit option or build in menu
bool enable_object_build()
{
@ -6245,6 +6259,33 @@ class LLLandEdit : public view_listener_t
}
};
class LLMuteParticle : public view_listener_t
{
bool handleEvent(const LLSD& userdata)
{
LLUUID id = LLToolPie::getInstance()->getPick().mParticleOwnerID;
if (id.notNull())
{
std::string name;
gCacheName->getFullName(id, name);
LLMute mute(id, name, LLMute::AGENT);
if (LLMuteList::getInstance()->isMuted(mute.mID))
{
LLMuteList::getInstance()->remove(mute);
}
else
{
LLMuteList::getInstance()->add(mute);
LLPanelBlockedList::showPanelAndSelect(mute.mID);
}
}
return true;
}
};
class LLWorldEnableBuyLand : public view_listener_t
{
bool handleEvent(const LLSD& userdata)
@ -8657,6 +8698,9 @@ void initialize_menus()
view_listener_t::addMenu(new LLLandBuyPass(), "Land.BuyPass");
view_listener_t::addMenu(new LLLandEdit(), "Land.Edit");
// Particle muting
view_listener_t::addMenu(new LLMuteParticle(), "Particle.Mute");
view_listener_t::addMenu(new LLLandEnableBuyPass(), "Land.EnableBuyPass");
commit.add("Land.Buy", boost::bind(&handle_buy_land));
@ -8679,6 +8723,7 @@ void initialize_menus()
enable.add("EnablePayObject", boost::bind(&enable_pay_object));
enable.add("EnablePayAvatar", boost::bind(&enable_pay_avatar));
enable.add("EnableEdit", boost::bind(&enable_object_edit));
enable.add("EnableMuteParticle", boost::bind(&enable_mute_particle));
enable.add("VisibleBuild", boost::bind(&enable_object_build));
commit.add("Pathfinding.Linksets.Select", boost::bind(&LLFloaterPathfindingLinksets::openLinksetsWithSelectedObjects));
enable.add("EnableSelectInPathfindingLinksets", boost::bind(&enable_object_select_in_pathfinding_linksets));

View File

@ -172,6 +172,7 @@ extern LLContextMenu *gMenuObject;
extern LLContextMenu *gMenuAttachmentSelf;
extern LLContextMenu *gMenuAttachmentOther;
extern LLContextMenu *gMenuLand;
extern LLContextMenu *gMenuMuteParticle;
// Needed to build menus when attachment site list available
extern LLMenuGL* gAttachSubMenu;

View File

@ -225,6 +225,8 @@ LLFrameTimer gAwayTriggerTimer;
BOOL gShowOverlayTitle = FALSE;
LLViewerObject* gDebugRaycastObject = NULL;
LLVOPartGroup* gDebugRaycastParticle = NULL;
LLVector3 gDebugRaycastParticleIntersection;
LLVector3 gDebugRaycastIntersection;
LLVector2 gDebugRaycastTexCoord;
LLVector3 gDebugRaycastNormal;
@ -2811,6 +2813,8 @@ void LLViewerWindow::updateUI()
&gDebugRaycastBinormal,
&gDebugRaycastStart,
&gDebugRaycastEnd);
gDebugRaycastParticle = gPipeline.lineSegmentIntersectParticle(gDebugRaycastStart, gDebugRaycastEnd, &gDebugRaycastParticleIntersection, NULL);
}
updateMouseDelta();
@ -3633,7 +3637,7 @@ void LLViewerWindow::pickAsync(S32 x, S32 y_from_bot, MASK mask, void (*callback
pick_transparent = TRUE;
}
LLPickInfo pick_info(LLCoordGL(x, y_from_bot), mask, pick_transparent, TRUE, callback);
LLPickInfo pick_info(LLCoordGL(x, y_from_bot), mask, pick_transparent, FALSE, TRUE, callback);
schedulePick(pick_info);
}
@ -3689,7 +3693,7 @@ void LLViewerWindow::returnEmptyPicks()
}
// Performs the GL object/land pick.
LLPickInfo LLViewerWindow::pickImmediate(S32 x, S32 y_from_bot, BOOL pick_transparent)
LLPickInfo LLViewerWindow::pickImmediate(S32 x, S32 y_from_bot, BOOL pick_transparent, BOOL pick_particle)
{
BOOL in_build_mode = LLFloaterReg::instanceVisible("build");
if (in_build_mode || LLDrawPoolAlpha::sShowDebugAlpha)
@ -3698,10 +3702,10 @@ LLPickInfo LLViewerWindow::pickImmediate(S32 x, S32 y_from_bot, BOOL pick_trans
// "Show Debug Alpha" means no object actually transparent
pick_transparent = TRUE;
}
// shortcut queueing in mPicks and just update mLastPick in place
MASK key_mask = gKeyboard->currentMask(TRUE);
mLastPick = LLPickInfo(LLCoordGL(x, y_from_bot), key_mask, pick_transparent, TRUE, NULL);
mLastPick = LLPickInfo(LLCoordGL(x, y_from_bot), key_mask, pick_transparent, pick_particle, TRUE, NULL);
mLastPick.fetchResults();
return mLastPick;
@ -5107,13 +5111,15 @@ LLPickInfo::LLPickInfo()
mNormal(),
mBinormal(),
mHUDIcon(NULL),
mPickTransparent(FALSE)
mPickTransparent(FALSE),
mPickParticle(FALSE)
{
}
LLPickInfo::LLPickInfo(const LLCoordGL& mouse_pos,
MASK keyboard_mask,
BOOL pick_transparent,
BOOL pick_particle,
BOOL pick_uv_coords,
void (*pick_callback)(const LLPickInfo& pick_info))
: mMousePt(mouse_pos),
@ -5128,7 +5134,8 @@ LLPickInfo::LLPickInfo(const LLCoordGL& mouse_pos,
mNormal(),
mBinormal(),
mHUDIcon(NULL),
mPickTransparent(pick_transparent)
mPickTransparent(pick_transparent),
mPickParticle(pick_particle)
{
}
@ -5142,20 +5149,34 @@ void LLPickInfo::fetchResults()
LLHUDIcon* hit_icon = gViewerWindow->cursorIntersectIcon(mMousePt.mX, mMousePt.mY, 512.f, &intersection);
F32 icon_dist = 0.f;
LLVector3 start;
LLVector3 end;
LLVector3 particle_end;
if (hit_icon)
{
icon_dist = (LLViewerCamera::getInstance()->getOrigin()-intersection).magVec();
}
LLViewerObject* hit_object = gViewerWindow->cursorIntersect(mMousePt.mX, mMousePt.mY, 512.f,
NULL, -1, mPickTransparent, &face_hit,
&intersection, &uv, &normal, &binormal);
&intersection, &uv, &normal, &binormal, &start, &end);
mPickPt = mMousePt;
U32 te_offset = face_hit > -1 ? face_hit : 0;
//unproject relative clicked coordinate from window coordinate using GL
if (mPickParticle)
{ //get the end point of line segement to use for particle raycast
if (hit_object)
{
particle_end = intersection;
}
else
{
particle_end = end;
}
}
LLViewerObject* objectp = hit_object;
if (hit_icon &&
@ -5166,6 +5187,7 @@ void LLPickInfo::fetchResults()
mHUDIcon = hit_icon;
mPickType = PICK_ICON;
mPosGlobal = mHUDIcon->getPositionGlobal();
}
else if (objectp)
{
@ -5210,6 +5232,18 @@ void LLPickInfo::fetchResults()
}
}
if (mPickParticle)
{ //search for closest particle to click origin out to intersection point
S32 part_face = -1;
LLVOPartGroup* group = gPipeline.lineSegmentIntersectParticle(start, particle_end, NULL, &part_face);
if (group)
{
mParticleOwnerID = group->getPartOwner(part_face);
mParticleSourceID = group->getPartSource(part_face);
}
}
if (mPickCallback)
{
mPickCallback(*this);

View File

@ -64,6 +64,7 @@ class LLWindow;
class LLRootView;
class LLWindowListener;
class LLViewerWindowListener;
class LLVOPartGroup;
class LLPopupView;
#define PICK_HALF_WIDTH 5
@ -86,7 +87,8 @@ public:
LLPickInfo();
LLPickInfo(const LLCoordGL& mouse_pos,
MASK keyboard_mask,
BOOL pick_transparent,
BOOL pick_transparent,
BOOL pick_particle,
BOOL pick_surface_info,
void (*pick_callback)(const LLPickInfo& pick_info));
@ -107,6 +109,8 @@ public:
LLVector3d mPosGlobal;
LLVector3 mObjectOffset;
LLUUID mObjectID;
LLUUID mParticleOwnerID;
LLUUID mParticleSourceID;
S32 mObjectFace;
LLHUDIcon* mHUDIcon;
LLVector3 mIntersection;
@ -116,6 +120,7 @@ public:
LLVector3 mNormal;
LLVector3 mBinormal;
BOOL mPickTransparent;
BOOL mPickParticle;
void getSurfaceInfo();
private:
@ -354,7 +359,7 @@ public:
void returnEmptyPicks();
void pickAsync(S32 x, S32 y_from_bot, MASK mask, void (*callback)(const LLPickInfo& pick_info), BOOL pick_transparent = FALSE);
LLPickInfo pickImmediate(S32 x, S32 y, BOOL pick_transparent);
LLPickInfo pickImmediate(S32 x, S32 y, BOOL pick_transparent, BOOL pick_particle = FALSE);
LLHUDIcon* cursorIntersectIcon(S32 mouse_x, S32 mouse_y, F32 depth,
LLVector3* intersection);
@ -494,6 +499,8 @@ extern LLFrameTimer gAwayTimer; // tracks time before setting the avatar awa
extern LLFrameTimer gAwayTriggerTimer; // how long the avatar has been away
extern LLViewerObject* gDebugRaycastObject;
extern LLVOPartGroup* gDebugRaycastParticle;
extern LLVector3 gDebugRaycastParticleIntersection;
extern LLVector3 gDebugRaycastIntersection;
extern LLVector2 gDebugRaycastTexCoord;
extern LLVector3 gDebugRaycastNormal;

View File

@ -232,6 +232,37 @@ LLDrawable* LLVOPartGroup::createDrawable(LLPipeline *pipeline)
const F32 MAX_PARTICLE_AREA_SCALE = 0.02f; // some tuned constant, limits on how much particle area to draw
LLUUID LLVOPartGroup::getPartOwner(S32 idx)
{
LLUUID ret = LLUUID::null;
if (idx < (S32) mViewerPartGroupp->mParticles.size())
{
ret = mViewerPartGroupp->mParticles[idx]->mPartSourcep->getOwnerUUID();
}
return ret;
}
LLUUID LLVOPartGroup::getPartSource(S32 idx)
{
LLUUID ret = LLUUID::null;
if (idx < (S32) mViewerPartGroupp->mParticles.size())
{
LLViewerPart* part = mViewerPartGroupp->mParticles[idx];
if (part && part->mPartSourcep.notNull() &&
part->mPartSourcep->mSourceObjectp.notNull())
{
LLViewerObject* source = part->mPartSourcep->mSourceObjectp;
ret = source->getID();
}
}
return ret;
}
F32 LLVOPartGroup::getPartSize(S32 idx)
{
if (idx < (S32) mViewerPartGroupp->mParticles.size())
@ -385,20 +416,68 @@ BOOL LLVOPartGroup::updateGeometry(LLDrawable *drawable)
return TRUE;
}
void LLVOPartGroup::getGeometry(S32 idx,
LLStrider<LLVector4a>& verticesp,
LLStrider<LLVector3>& normalsp,
LLStrider<LLVector2>& texcoordsp,
LLStrider<LLColor4U>& colorsp,
LLStrider<U16>& indicesp)
BOOL LLVOPartGroup::lineSegmentIntersect(const LLVector3& start, const LLVector3& end,
S32 face,
BOOL pick_transparent,
S32* face_hit,
LLVector3* intersection,
LLVector2* tex_coord,
LLVector3* normal,
LLVector3* bi_normal)
{
if (idx >= (S32) mViewerPartGroupp->mParticles.size())
LLVector4a starta, enda;
starta.load3(start.mV);
enda.load3(end.mV);
LLVector4a dir;
dir.setSub(enda, starta);
F32 closest_t = 2.f;
BOOL ret = FALSE;
for (U32 idx = 0; idx < mViewerPartGroupp->mParticles.size(); ++idx)
{
return;
const LLViewerPart &part = *((LLViewerPart*) (mViewerPartGroupp->mParticles[idx]));
LLVector4a v[4];
LLStrider<LLVector4a> verticesp;
verticesp = v;
getGeometry(part, verticesp);
F32 a,b,t;
if (LLTriangleRayIntersect(v[0], v[1], v[2], starta, dir, a,b,t) ||
LLTriangleRayIntersect(v[1], v[3], v[2], starta, dir, a,b,t))
{
if (t >= 0.f &&
t <= 1.f &&
t < closest_t)
{
ret = TRUE;
closest_t = t;
if (face_hit)
{
*face_hit = idx;
}
if (intersection)
{
LLVector4a intersect = dir;
intersect.mul(closest_t);
intersect.add(starta);
intersection->set(intersect.getF32ptr());
}
}
}
}
const LLViewerPart &part = *((LLViewerPart*) (mViewerPartGroupp->mParticles[idx]));
return ret;
}
void LLVOPartGroup::getGeometry(const LLViewerPart& part,
LLStrider<LLVector4a>& verticesp)
{
LLVector4a part_pos_agent;
part_pos_agent.load3(part.mPosAgent.mV);
LLVector4a camera_agent;
@ -449,8 +528,6 @@ void LLVOPartGroup::getGeometry(S32 idx,
up.mul(0.5f*part.mScale.mV[1]);
LLVector3 normal = -LLViewerCamera::getInstance()->getXAxis();
//HACK -- the verticesp->mV[3] = 0.f here are to set the texture index to 0 (particles don't use texture batching, maybe they should)
// this works because there is actually a 4th float stored after the vertex position which is used as a texture index
// also, somebody please VECTORIZE THIS
@ -469,6 +546,25 @@ void LLVOPartGroup::getGeometry(S32 idx,
(*verticesp++).getF32ptr()[3] = 0.f;
verticesp->setAdd(ppamu, right);
(*verticesp++).getF32ptr()[3] = 0.f;
}
void LLVOPartGroup::getGeometry(S32 idx,
LLStrider<LLVector4a>& verticesp,
LLStrider<LLVector3>& normalsp,
LLStrider<LLVector2>& texcoordsp,
LLStrider<LLColor4U>& colorsp,
LLStrider<U16>& indicesp)
{
if (idx >= (S32) mViewerPartGroupp->mParticles.size())
{
return;
}
const LLViewerPart &part = *((LLViewerPart*) (mViewerPartGroupp->mParticles[idx]));
getGeometry(part, verticesp);
*colorsp++ = part.mColor;
*colorsp++ = part.mColor;
@ -477,6 +573,7 @@ void LLVOPartGroup::getGeometry(S32 idx,
if (!(part.mFlags & LLPartData::LL_PART_EMISSIVE_MASK))
{ //not fullbright, needs normal
LLVector3 normal = -LLViewerCamera::getInstance()->getXAxis();
*normalsp++ = normal;
*normalsp++ = normal;
*normalsp++ = normal;

View File

@ -69,11 +69,23 @@ public:
virtual void updateSpatialExtents(LLVector4a& newMin, LLVector4a& newMax);
virtual U32 getPartitionType() const;
/*virtual*/ BOOL lineSegmentIntersect(const LLVector3& start, const LLVector3& end,
S32 face,
BOOL pick_transparent,
S32* face_hit,
LLVector3* intersection,
LLVector2* tex_coord,
LLVector3* normal,
LLVector3* bi_normal);
/*virtual*/ void setPixelAreaAndAngle(LLAgent &agent);
/*virtual*/ void updateTextures();
/*virtual*/ LLDrawable* createDrawable(LLPipeline *pipeline);
/*virtual*/ BOOL updateGeometry(LLDrawable *drawable);
void getGeometry(const LLViewerPart& part,
LLStrider<LLVector4a>& verticesp);
void getGeometry(S32 idx,
LLStrider<LLVector4a>& verticesp,
LLStrider<LLVector3>& normalsp,
@ -83,6 +95,9 @@ public:
void updateFaceSize(S32 idx) { }
F32 getPartSize(S32 idx);
LLUUID getPartOwner(S32 idx);
LLUUID getPartSource(S32 idx);
void setViewerPartGroup(LLViewerPartGroup *part_groupp) { mViewerPartGroupp = part_groupp; }
LLViewerPartGroup* getViewerPartGroup() { return mViewerPartGroupp; }

View File

@ -5023,6 +5023,42 @@ void LLPipeline::renderDebug()
gUIProgram.bind();
}
if (hasRenderDebugMask(LLPipeline::RENDER_DEBUG_RAYCAST) && !hud_only)
{ //draw crosshairs on particle intersection
if (gDebugRaycastParticle)
{
if (LLGLSLShader::sNoFixedFunction)
{ //this debug display requires shaders
gDebugProgram.bind();
gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE);
LLVector3 center = gDebugRaycastParticleIntersection;
LLVector3 size(0.1f, 0.1f, 0.1f);
LLVector3 p[6];
p[0] = center + size.scaledVec(LLVector3(1,0,0));
p[1] = center + size.scaledVec(LLVector3(-1,0,0));
p[2] = center + size.scaledVec(LLVector3(0,1,0));
p[3] = center + size.scaledVec(LLVector3(0,-1,0));
p[4] = center + size.scaledVec(LLVector3(0,0,1));
p[5] = center + size.scaledVec(LLVector3(0,0,-1));
gGL.begin(LLRender::LINES);
gGL.diffuseColor3f(1.f, 1.f, 0.f);
for (U32 i = 0; i < 6; i++)
{
gGL.vertex3fv(p[i].mV);
}
gGL.end();
gGL.flush();
gDebugProgram.unbind();
}
}
}
if (hasRenderDebugMask(LLPipeline::RENDER_DEBUG_SHADOW_FRUSTA))
{
LLVertexBuffer::unbind();
@ -6600,6 +6636,48 @@ BOOL LLPipeline::getRenderHighlights(void*)
return sRenderHighlight;
}
LLVOPartGroup* LLPipeline::lineSegmentIntersectParticle(const LLVector3& start, const LLVector3& end, LLVector3* intersection,
S32* face_hit)
{
LLVector3 local_end = end;
LLVector3 position;
LLDrawable* drawable = NULL;
for (LLWorld::region_list_t::const_iterator iter = LLWorld::getInstance()->getRegionList().begin();
iter != LLWorld::getInstance()->getRegionList().end(); ++iter)
{
LLViewerRegion* region = *iter;
LLSpatialPartition* part = region->getSpatialPartition(LLViewerRegion::PARTITION_PARTICLE);
if (part && hasRenderType(part->mDrawableType))
{
LLDrawable* hit = part->lineSegmentIntersect(start, local_end, TRUE, face_hit, &position, NULL, NULL, NULL);
if (hit)
{
drawable = hit;
local_end = position;
}
}
}
LLVOPartGroup* ret = NULL;
if (drawable)
{
//make sure we're returning an LLVOPartGroup
llassert(drawable->getVObj()->getPCode() == LLViewerObject::LL_VO_PART_GROUP);
ret = (LLVOPartGroup*) drawable->getVObj().get();
}
if (intersection)
{
*intersection = position;
}
return ret;
}
LLViewerObject* LLPipeline::lineSegmentIntersectInWorld(const LLVector3& start, const LLVector3& end,
BOOL pick_transparent,
S32* face_hit,

View File

@ -57,6 +57,7 @@ class LLRenderFunc;
class LLCubeMap;
class LLCullResult;
class LLVOAvatar;
class LLVOPartGroup;
class LLGLSLShader;
class LLCurlRequest;
@ -193,6 +194,12 @@ public:
LLVector3* normal = NULL, // return the surface normal at the intersection point
LLVector3* bi_normal = NULL // return the surface bi-normal at the intersection point
);
//get the closest particle to start between start and end, returns the LLVOPartGroup and particle index
LLVOPartGroup* lineSegmentIntersectParticle(const LLVector3& start, const LLVector3& end, LLVector3* intersection,
S32* face_hit);
LLViewerObject* lineSegmentIntersectInHUD(const LLVector3& start, const LLVector3& end,
BOOL pick_transparent,
S32* face_hit, // return the face hit

View File

@ -104,4 +104,15 @@
<menu_item_call.on_enable
function="Object.EnableInspect" />
</menu_item_call>
<menu_item_separator
layout="topleft" />
<menu_item_call
enabled="false"
label="Mute Particle Owner"
name="Mute Particle">
<menu_item_call.on_click
function="Particle.Mute" />
<menu_item_call.on_enable
function="EnableMuteParticle" />
</menu_item_call>
</context_menu>

View File

@ -135,4 +135,15 @@ name="Edit Outfit">
<menu_item_call.on_enable
function="Attachment.EnableDrop" />
</menu_item_call>
<menu_item_separator
layout="topleft" />
<menu_item_call
enabled="false"
label="Mute Particle Owner"
name="Mute Particle">
<menu_item_call.on_click
function="Particle.Mute" />
<menu_item_call.on_enable
function="EnableMuteParticle" />
</menu_item_call>
</context_menu>

View File

@ -95,4 +95,15 @@
<menu_item_call.on_enable
function="EnablePayAvatar" />
</menu_item_call>
<menu_item_separator
layout="topleft" />
<menu_item_call
enabled="false"
label="Mute Particle Owner"
name="Mute Particle">
<menu_item_call.on_click
function="Particle.Mute" />
<menu_item_call.on_enable
function="EnableMuteParticle" />
</menu_item_call>
</context_menu>

View File

@ -261,4 +261,15 @@
<menu_item_call.on_visible
function="IsGodCustomerService"/>
</menu_item_call>
<menu_item_separator
layout="topleft" />
<menu_item_call
enabled="false"
label="Mute Particle Owner"
name="Mute Particle">
<menu_item_call.on_click
function="Particle.Mute" />
<menu_item_call.on_enable
function="EnableMuteParticle" />
</menu_item_call>
</context_menu>

View File

@ -61,4 +61,15 @@
<menu_item_call.on_enable
function="EnableEdit" />
</menu_item_call>
<menu_item_separator
layout="topleft" />
<menu_item_call
enabled="false"
label="Mute Particle Owner"
name="Mute Particle">
<menu_item_call.on_click
function="Particle.Mute" />
<menu_item_call.on_enable
function="EnableMuteParticle" />
</menu_item_call>
</context_menu>

View File

@ -196,4 +196,15 @@
<menu_item_call.on_enable
function="Object.EnableDelete" />
</menu_item_call>
<menu_item_separator
layout="topleft" />
<menu_item_call
enabled="false"
label="Mute Particle Owner"
name="Mute Particle">
<menu_item_call.on_click
function="Particle.Mute" />
<menu_item_call.on_enable
function="EnableMuteParticle" />
</menu_item_call>
</context_menu>