/** * @file llvopartgroup.cpp * @brief Group of particle systems * * $LicenseInfo:firstyear=2001&license=viewerlgpl$ * Second Life Viewer Source Code * Copyright (C) 2010, Linden Research, Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License only. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA * $/LicenseInfo$ */ #include "llviewerprecompiledheaders.h" #include "llvopartgroup.h" #include "lldrawpoolalpha.h" #include "llfasttimer.h" #include "message.h" #include "v2math.h" #include "llagentcamera.h" #include "lldrawable.h" #include "llface.h" #include "llsky.h" #include "llviewercamera.h" #include "llviewerpartsim.h" #include "llviewerregion.h" #include "pipeline.h" #include "llspatialpartition.h" extern U64MicrosecondsImplicit gFrameTime; void LLVOPartGroup::initClass() { } //static void LLVOPartGroup::restoreGL() { } //static void LLVOPartGroup::destroyGL() { } bool ll_is_part_idx_allocated(S32 idx, S32* start, S32* end) { /*while (start < end) { if (*start == idx) { //not allocated (in free list) return false; } ++start; }*/ //allocated (not in free list) return false; } LLVOPartGroup::LLVOPartGroup(const LLUUID &id, const LLPCode pcode, LLViewerRegion *regionp) : LLAlphaObject(id, pcode, regionp), mViewerPartGroupp(NULL) { setNumTEs(1); setTETexture(0, LLUUID::null); mbCanSelect = false; // users can't select particle systems } LLVOPartGroup::~LLVOPartGroup() { } bool LLVOPartGroup::isActive() const { return false; } F32 LLVOPartGroup::getBinRadius() { return mViewerPartGroupp->getBoxSide(); } void LLVOPartGroup::updateSpatialExtents(LLVector4a& newMin, LLVector4a& newMax) { const LLVector3& pos_agent = getPositionAgent(); LLVector4a scale; LLVector4a p; p.load3(pos_agent.mV); scale.splat(mScale.mV[0]+mViewerPartGroupp->getBoxSide()*0.5f); newMin.setSub(p, scale); newMax.setAdd(p,scale); llassert(newMin.isFinite3()); llassert(newMax.isFinite3()); llassert(p.isFinite3()); mDrawable->setPositionGroup(p); } void LLVOPartGroup::idleUpdate(LLAgent &agent, const F64 &time) { } void LLVOPartGroup::setPixelAreaAndAngle(LLAgent &agent) { // mPixelArea is calculated during render F32 mid_scale = getMidScale(); F32 range = (getRenderPosition()-LLViewerCamera::getInstance()->getOrigin()).length(); if (range < 0.001f || isHUDAttachment()) // range == zero { mAppAngle = 180.f; } else { mAppAngle = (F32) atan2( mid_scale, range) * RAD_TO_DEG; } } void LLVOPartGroup::updateTextures() { // Texture stats for particles need to be updated in a different way... } LLDrawable* LLVOPartGroup::createDrawable(LLPipeline *pipeline) { pipeline->allocDrawable(this); mDrawable->setLit(false); mDrawable->setRenderType(LLPipeline::RENDER_TYPE_PARTICLES); return mDrawable; } 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()) { return mViewerPartGroupp->mParticles[idx]->mScale.mV[0]; } return 0.f; } void LLVOPartGroup::getBlendFunc(S32 idx, LLRender::eBlendFactor& src, LLRender::eBlendFactor& dst) { if (idx < (S32) mViewerPartGroupp->mParticles.size()) { LLViewerPart* part = mViewerPartGroupp->mParticles[idx]; src = (LLRender::eBlendFactor) part->mBlendFuncSource; dst = (LLRender::eBlendFactor) part->mBlendFuncDest; } } LLVector3 LLVOPartGroup::getCameraPosition() const { return gAgentCamera.getCameraPositionAgent(); } bool LLVOPartGroup::updateGeometry(LLDrawable *drawable) { LL_PROFILE_ZONE_SCOPED; dirtySpatialGroup(); S32 num_parts = mViewerPartGroupp->getCount(); LLFace *facep; LLSpatialGroup* group = drawable->getSpatialGroup(); if (!group && num_parts) { drawable->movePartition(); group = drawable->getSpatialGroup(); } if (group && group->isVisible()) { dirtySpatialGroup(); } if (!num_parts) { if (group && drawable->getNumFaces()) { group->setState(LLSpatialGroup::GEOM_DIRTY); } drawable->setNumFaces(0, NULL, getTEImage(0)); LLPipeline::sCompiles++; return true; } if (!(gPipeline.hasRenderType(LLPipeline::RENDER_TYPE_PARTICLES))) { return true; } if (num_parts > drawable->getNumFaces()) { drawable->setNumFacesFast(num_parts+num_parts/4, NULL, getTEImage(0)); } F32 tot_area = 0; F32 max_area = LLViewerPartSim::getMaxPartCount() * MAX_PARTICLE_AREA_SCALE; F32 pixel_meter_ratio = LLViewerCamera::getInstance()->getPixelMeterRatio(); pixel_meter_ratio *= pixel_meter_ratio; LLViewerPartSim::checkParticleCount(static_cast(mViewerPartGroupp->mParticles.size())); S32 count=0; mDepth = 0.f; S32 i = 0 ; LLVector3 camera_agent = getCameraPosition(); F32 max_scale = 0.f; for (i = 0 ; i < (S32)mViewerPartGroupp->mParticles.size(); i++) { const LLViewerPart *part = mViewerPartGroupp->mParticles[i]; //remember the largest particle max_scale = llmax(max_scale, part->mScale.mV[0], part->mScale.mV[1]); if (part->mFlags & LLPartData::LL_PART_RIBBON_MASK) { //include ribbon segment length in scale const LLVector3* pos_agent = NULL; if (part->mParent) { pos_agent = &(part->mParent->mPosAgent); } else if (part->mPartSourcep.notNull()) { pos_agent = &(part->mPartSourcep->mPosAgent); } if (pos_agent) { F32 dist = (*pos_agent-part->mPosAgent).length(); max_scale = llmax(max_scale, dist); } } LLVector3 part_pos_agent(part->mPosAgent); LLVector3 at(part_pos_agent - camera_agent); F32 camera_dist_squared = at.lengthSquared(); F32 inv_camera_dist_squared; if (camera_dist_squared > 1.f) inv_camera_dist_squared = 1.f / camera_dist_squared; else inv_camera_dist_squared = 1.f; llassert(llfinite(inv_camera_dist_squared)); llassert(!llisnan(inv_camera_dist_squared)); F32 area = part->mScale.mV[0] * part->mScale.mV[1] * inv_camera_dist_squared; tot_area = llmax(tot_area, area); if (tot_area > max_area) { break; } count++; facep = drawable->getFace(i); if (!facep) { LL_WARNS() << "No face found for index " << i << "!" << LL_ENDL; continue; } facep->setTEOffset(i); const F32 NEAR_PART_DIST_SQ = 5.f*5.f; // Only discard particles > 5 m from the camera const F32 MIN_PART_AREA = .005f*.005f; // only less than 5 mm x 5 mm at 1 m from camera if (camera_dist_squared > NEAR_PART_DIST_SQ && area < MIN_PART_AREA) { facep->setSize(0, 0); continue; } facep->setSize(4, 6); facep->setViewerObject(this); if (part->mFlags & LLPartData::LL_PART_EMISSIVE_MASK) { facep->setState(LLFace::FULLBRIGHT); } else { facep->clearState(LLFace::FULLBRIGHT); } facep->mCenterLocal = part->mPosAgent; facep->setFaceColor(part->mColor); facep->setTexture(part->mImagep); //check if this particle texture is replaced by a parcel media texture. if(part->mImagep.notNull() && part->mImagep->hasParcelMedia()) { part->mImagep->getParcelMedia()->addMediaToFace(facep) ; } mPixelArea = tot_area * pixel_meter_ratio; const F32 area_scale = 10.f; // scale area to increase priority a bit facep->setVirtualSize(mPixelArea*area_scale); } for (i = count; i < drawable->getNumFaces(); i++) { LLFace* facep = drawable->getFace(i); if (!facep) { LL_WARNS() << "No face found for index " << i << "!" << LL_ENDL; continue; } facep->setTEOffset(i); facep->setSize(0, 0); } //record max scale (used to stretch bounding box for visibility culling) mScale.set(max_scale, max_scale, max_scale); mDrawable->movePartition(); LLPipeline::sCompiles++; return true; } bool LLVOPartGroup::lineSegmentIntersect(const LLVector4a& start, const LLVector4a& end, S32 face, bool pick_transparent, bool pick_rigged, bool pick_unselectable, S32* face_hit, LLVector4a* intersection, LLVector2* tex_coord, LLVector4a* normal, LLVector4a* bi_normal) { LLVector4a dir; dir.setSub(end, start); F32 closest_t = 2.f; bool ret = false; for (U32 idx = 0; idx < mViewerPartGroupp->mParticles.size(); ++idx) { const LLViewerPart &part = *((LLViewerPart*) (mViewerPartGroupp->mParticles[idx])); LLVector4a v[4]; LLStrider verticesp; verticesp = v; getGeometry(part, verticesp); F32 a,b,t; if (LLTriangleRayIntersect(v[0], v[1], v[2], start, dir, a,b,t) || LLTriangleRayIntersect(v[1], v[3], v[2], start, 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); intersection->setAdd(intersect, start); } } } } return ret; } void LLVOPartGroup::getGeometry(const LLViewerPart& part, LLStrider& verticesp) { if (part.mFlags & LLPartData::LL_PART_RIBBON_MASK) { LLVector4a axis, pos, paxis, ppos; F32 scale, pscale; pos.load3(part.mPosAgent.mV); axis.load3(part.mAxis.mV); scale = part.mScale.mV[0]; if (part.mParent) { ppos.load3(part.mParent->mPosAgent.mV); paxis.load3(part.mParent->mAxis.mV); pscale = part.mParent->mScale.mV[0]; } else { //use source object as position if (part.mPartSourcep->mSourceObjectp.notNull()) { LLVector3 v = LLVector3(0,0,1); v *= part.mPartSourcep->mSourceObjectp->getRenderRotation(); paxis.load3(v.mV); ppos.load3(part.mPartSourcep->mPosAgent.mV); pscale = part.mStartScale.mV[0]; } else { //no source object, no parent, nothing to draw ppos = pos; pscale = scale; paxis = axis; } } LLVector4a p0, p1, p2, p3; scale *= 0.5f; pscale *= 0.5f; axis.mul(scale); paxis.mul(pscale); p0.setAdd(pos, axis); p1.setSub(pos,axis); p2.setAdd(ppos, paxis); p3.setSub(ppos, paxis); (*verticesp++) = p2; (*verticesp++) = p3; (*verticesp++) = p0; (*verticesp++) = p1; } else { LLVector4a part_pos_agent; part_pos_agent.load3(part.mPosAgent.mV); LLVector4a camera_agent; camera_agent.load3(getCameraPosition().mV); LLVector4a at; at.setSub(part_pos_agent, camera_agent); LLVector4a up(0, 0, 1); LLVector4a right; right.setCross3(at, up); right.normalize3fast(); up.setCross3(right, at); up.normalize3fast(); if (part.mFlags & LLPartData::LL_PART_FOLLOW_VELOCITY_MASK && !part.mVelocity.isExactlyZero()) { LLVector4a normvel; normvel.load3(part.mVelocity.mV); normvel.normalize3fast(); LLVector2 up_fracs; up_fracs.mV[0] = normvel.dot3(right).getF32(); up_fracs.mV[1] = normvel.dot3(up).getF32(); up_fracs.normalize(); LLVector4a new_up; LLVector4a new_right; //new_up = up_fracs.mV[0] * right + up_fracs.mV[1]*up; LLVector4a t = right; t.mul(up_fracs.mV[0]); new_up = up; new_up.mul(up_fracs.mV[1]); new_up.add(t); //new_right = up_fracs.mV[1] * right - up_fracs.mV[0]*up; t = right; t.mul(up_fracs.mV[1]); new_right = up; new_right.mul(up_fracs.mV[0]); t.sub(new_right); up = new_up; right = t; up.normalize3fast(); right.normalize3fast(); } right.mul(0.5f*part.mScale.mV[0]); up.mul(0.5f*part.mScale.mV[1]); //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 LLVector4a ppapu; LLVector4a ppamu; ppapu.setAdd(part_pos_agent, up); ppamu.setSub(part_pos_agent, up); verticesp->setSub(ppapu, right); (*verticesp++).getF32ptr()[3] = 0.f; verticesp->setSub(ppamu, right); (*verticesp++).getF32ptr()[3] = 0.f; verticesp->setAdd(ppapu, right); (*verticesp++).getF32ptr()[3] = 0.f; verticesp->setAdd(ppamu, right); (*verticesp++).getF32ptr()[3] = 0.f; } } void LLVOPartGroup::getGeometry(S32 idx, LLStrider& verticesp, LLStrider& normalsp, LLStrider& texcoordsp, LLStrider& colorsp, LLStrider& emissivep, LLStrider& indicesp) { if (idx >= (S32) mViewerPartGroupp->mParticles.size()) { return; } const LLViewerPart &part = *((LLViewerPart*) (mViewerPartGroupp->mParticles[idx])); getGeometry(part, verticesp); LLColor4U pcolor; LLColor4U color = part.mColor; LLColor4U pglow; if (part.mFlags & LLPartData::LL_PART_RIBBON_MASK) { //make sure color blends properly if (part.mParent) { pglow = part.mParent->mGlow; pcolor = part.mParent->mColor; } else { pglow = LLColor4U(0, 0, 0, (U8) ll_round(255.f*part.mStartGlow)); pcolor = part.mStartColor; } } else { pglow = part.mGlow; pcolor = color; } *colorsp++ = pcolor; *colorsp++ = pcolor; *colorsp++ = color; *colorsp++ = color; //if (pglow.mV[3] || part.mGlow.mV[3]) { //only write glow if it is not zero *emissivep++ = pglow; *emissivep++ = pglow; *emissivep++ = part.mGlow; *emissivep++ = part.mGlow; } if (!(part.mFlags & LLPartData::LL_PART_EMISSIVE_MASK)) { //not fullbright, needs normal LLVector3 normal = -LLViewerCamera::getInstance()->getXAxis(); *normalsp++ = normal; *normalsp++ = normal; *normalsp++ = normal; *normalsp++ = normal; } } U32 LLVOPartGroup::getPartitionType() const { return LLViewerRegion::PARTITION_PARTICLE; } LLParticlePartition::LLParticlePartition(LLViewerRegion* regionp) : LLSpatialPartition(static_cast(LLDrawPoolAlpha::VERTEX_DATA_MASK) | static_cast(LLVertexBuffer::MAP_TEXTURE_INDEX), true, regionp) { mRenderPass = LLRenderPass::PASS_ALPHA; mDrawableType = LLPipeline::RENDER_TYPE_PARTICLES; mPartitionType = LLViewerRegion::PARTITION_PARTICLE; mSlopRatio = 0.f; mLODPeriod = 1; } LLHUDParticlePartition::LLHUDParticlePartition(LLViewerRegion* regionp) : LLParticlePartition(regionp) { mDrawableType = LLPipeline::RENDER_TYPE_HUD_PARTICLES; mPartitionType = LLViewerRegion::PARTITION_HUD_PARTICLE; } void LLParticlePartition::rebuildGeom(LLSpatialGroup* group) { LL_PROFILE_ZONE_SCOPED; LL_PROFILE_GPU_ZONE("particle vbo"); if (group->isDead() || !group->hasState(LLSpatialGroup::GEOM_DIRTY)) { return; } if (group->changeLOD()) { group->mLastUpdateDistance = group->mDistance; group->mLastUpdateViewAngle = group->mViewAngle; } group->clearDrawMap(); //get geometry count U32 index_count = 0; U32 vertex_count = 0; addGeometryCount(group, vertex_count, index_count); if (vertex_count > 0 && index_count > 0) { group->mBuilt = 1.f; if (group->mVertexBuffer.isNull() || group->mVertexBuffer->getNumVerts() < vertex_count || group->mVertexBuffer->getNumIndices() < index_count) { group->mVertexBuffer = new LLVertexBuffer(LLVOPartGroup::VERTEX_DATA_MASK); group->mVertexBuffer->allocateBuffer(vertex_count, index_count); // initialize index and texture coordinates only when buffer is reallocated U16* indicesp = (U16*)group->mVertexBuffer->mapIndexBuffer(0, index_count); U16 geom_idx = 0; for (U32 i = 0; i < index_count; i += 6) { *indicesp++ = geom_idx + 0; *indicesp++ = geom_idx + 1; *indicesp++ = geom_idx + 2; *indicesp++ = geom_idx + 1; *indicesp++ = geom_idx + 3; *indicesp++ = geom_idx + 2; geom_idx += 4; } LLStrider texcoordsp; group->mVertexBuffer->getTexCoord0Strider(texcoordsp); for (U32 i = 0; i < vertex_count; i += 4) { *texcoordsp++ = LLVector2(0.f, 1.f); *texcoordsp++ = LLVector2(0.f, 0.f); *texcoordsp++ = LLVector2(1.f, 1.f); *texcoordsp++ = LLVector2(1.f, 0.f); } } getGeometry(group); } else { group->mVertexBuffer = NULL; group->mBufferMap.clear(); } group->mLastUpdateTime = gFrameTimeSeconds; group->clearState(LLSpatialGroup::GEOM_DIRTY); } void LLParticlePartition::addGeometryCount(LLSpatialGroup* group, U32& vertex_count, U32& index_count) { mFaceList.clear(); LLViewerCamera* camera = LLViewerCamera::getInstance(); for (LLSpatialGroup::element_iter i = group->getDataBegin(); i != group->getDataEnd(); ++i) { LLDrawable* drawablep = (LLDrawable*)(*i)->getDrawable(); if (!drawablep || drawablep->isDead()) { continue; } LLAlphaObject* obj = (LLAlphaObject*) drawablep->getVObj().get(); obj->mDepth = 0.f; U32 count = 0; for (S32 j = 0; j < drawablep->getNumFaces(); ++j) { drawablep->updateFaceSize(j); LLFace* facep = drawablep->getFace(j); if ( !facep || !facep->hasGeometry()) { continue; } vertex_count += facep->getGeomCount(); index_count += facep->getIndicesCount(); count++; facep->mDistance = (facep->mCenterLocal - camera->getOrigin()) * camera->getAtAxis(); obj->mDepth += facep->mDistance; mFaceList.push_back(facep); llassert(facep->getIndicesCount() < 65536); } obj->mDepth /= count; } } void LLParticlePartition::getGeometry(LLSpatialGroup* group) { LL_PROFILE_ZONE_SCOPED; std::sort(mFaceList.begin(), mFaceList.end(), LLFace::CompareDistanceGreater()); group->clearDrawMap(); LLVertexBuffer* buffer = group->mVertexBuffer; LLStrider verticesp; LLStrider normalsp; LLStrider colorsp; LLStrider emissivep; buffer->getVertexStrider(verticesp); buffer->getNormalStrider(normalsp); buffer->getColorStrider(colorsp); buffer->getEmissiveStrider(emissivep); S32 geom_idx = 0; S32 indices_idx = 0; LLSpatialGroup::drawmap_elem_t& draw_vec = group->mDrawMap[mRenderPass]; for (std::vector::iterator i = mFaceList.begin(); i != mFaceList.end(); ++i) { LLFace* facep = *i; LLAlphaObject* object = (LLAlphaObject*) facep->getViewerObject(); facep->setGeomIndex(geom_idx); facep->setIndicesIndex(indices_idx); LLStrider cur_vert = verticesp + geom_idx; LLStrider cur_norm = normalsp + geom_idx; LLStrider cur_col = colorsp + geom_idx; LLStrider cur_glow = emissivep + geom_idx; // not actually used LLStrider cur_tc; LLStrider cur_idx; geom_idx += 4; indices_idx += 6; LLColor4U* start_glow = cur_glow.get(); object->getGeometry(facep->getTEOffset(), cur_vert, cur_norm, cur_tc, cur_col, cur_glow, cur_idx); bool has_glow = false; if (cur_glow.get() != start_glow) { has_glow = true; } llassert(facep->getGeomCount() == 4); llassert(facep->getIndicesCount() == 6); S32 idx = static_cast(draw_vec.size()) - 1; bool fullbright = facep->isState(LLFace::FULLBRIGHT); bool batched = false; LLRender::eBlendFactor bf_src = LLRender::BF_SOURCE_ALPHA; LLRender::eBlendFactor bf_dst = LLRender::BF_ONE_MINUS_SOURCE_ALPHA; object->getBlendFunc(facep->getTEOffset(), bf_src, bf_dst); if (idx >= 0) { LLDrawInfo* info = draw_vec[idx]; if (info->mTexture == facep->getTexture() && info->mHasGlow == has_glow && info->mFullbright == fullbright && info->mBlendFuncDst == bf_dst && info->mBlendFuncSrc == bf_src) { if (draw_vec[idx]->mEnd == facep->getGeomIndex()-1) { batched = true; info->mCount += facep->getIndicesCount(); info->mEnd += facep->getGeomCount(); } else if (draw_vec[idx]->mStart == facep->getGeomIndex()+facep->getGeomCount()+1) { batched = true; info->mCount += facep->getIndicesCount(); info->mStart -= facep->getGeomCount(); info->mOffset = facep->getIndicesStart(); } } } if (!batched) { U32 start = facep->getGeomIndex(); U32 end = start + facep->getGeomCount()-1; U32 offset = facep->getIndicesStart(); U32 count = facep->getIndicesCount(); LLDrawInfo* info = new LLDrawInfo(start,end,count,offset,facep->getTexture(), buffer, fullbright); info->mBlendFuncDst = bf_dst; info->mBlendFuncSrc = bf_src; info->mHasGlow = has_glow; draw_vec.push_back(info); //for alpha sorting facep->setDrawInfo(info); } } mFaceList.clear(); } F32 LLParticlePartition::calcPixelArea(LLSpatialGroup* group, LLCamera& camera) { return 1024.f; } U32 LLVOHUDPartGroup::getPartitionType() const { return LLViewerRegion::PARTITION_HUD_PARTICLE; } LLDrawable* LLVOHUDPartGroup::createDrawable(LLPipeline *pipeline) { pipeline->allocDrawable(this); mDrawable->setLit(false); mDrawable->setRenderType(LLPipeline::RENDER_TYPE_HUD_PARTICLES); return mDrawable; } LLVector3 LLVOHUDPartGroup::getCameraPosition() const { return LLVector3(-1,0,0); }