/** * @file llflexibleobject.cpp * @brief Flexible object implementation * * $LicenseInfo:firstyear=2006&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 "pipeline.h" #include "lldrawpoolbump.h" #include "llface.h" #include "llflexibleobject.h" #include "llglheaders.h" #include "llrendersphere.h" #include "llviewerobject.h" #include "llagent.h" #include "llsky.h" #include "llviewercamera.h" #include "llviewertexturelist.h" #include "llviewercontrol.h" #include "llviewerobjectlist.h" #include "llviewerregion.h" #include "llworld.h" #include "llvoavatar.h" /*static*/ F32 LLVolumeImplFlexible::sUpdateFactor = 1.0f; std::vector LLVolumeImplFlexible::sInstanceList; std::vector LLVolumeImplFlexible::sUpdateDelay; static LLFastTimer::DeclareTimer FTM_FLEXIBLE_REBUILD("Rebuild"); static LLFastTimer::DeclareTimer FTM_DO_FLEXIBLE_UPDATE("Flexible Update"); // LLFlexibleObjectData::pack/unpack now in llprimitive.cpp //----------------------------------------------- // constructor //----------------------------------------------- LLVolumeImplFlexible::LLVolumeImplFlexible(LLViewerObject* vo, LLFlexibleObjectData* attributes) : mVO(vo), mAttributes(attributes) { static U32 seed = 0; mID = seed++; mInitialized = FALSE; mUpdated = FALSE; mInitializedRes = -1; mSimulateRes = 0; mFrameNum = 0; mCollisionSphereRadius = 0.f; mRenderRes = -1; if(mVO->mDrawable.notNull()) { mVO->mDrawable->makeActive() ; } mInstanceIndex = sInstanceList.size(); sInstanceList.push_back(this); sUpdateDelay.push_back(0); }//----------------------------------------------- LLVolumeImplFlexible::~LLVolumeImplFlexible() { S32 end_idx = sInstanceList.size()-1; if (end_idx != mInstanceIndex) { sInstanceList[mInstanceIndex] = sInstanceList[end_idx]; sInstanceList[mInstanceIndex]->mInstanceIndex = mInstanceIndex; sUpdateDelay[mInstanceIndex] = sUpdateDelay[end_idx]; } sInstanceList.pop_back(); sUpdateDelay.pop_back(); } //static void LLVolumeImplFlexible::updateClass() { std::vector::iterator delay_iter = sUpdateDelay.begin(); for (std::vector::iterator iter = sInstanceList.begin(); iter != sInstanceList.end(); ++iter) { --(*delay_iter); if (*delay_iter <= 0) { (*iter)->doIdleUpdate(); } ++delay_iter; } } LLVector3 LLVolumeImplFlexible::getFramePosition() const { return mVO->getRenderPosition(); } LLQuaternion LLVolumeImplFlexible::getFrameRotation() const { return mVO->getRenderRotation(); } void LLVolumeImplFlexible::onParameterChanged(U16 param_type, LLNetworkData *data, BOOL in_use, bool local_origin) { if (param_type == LLNetworkData::PARAMS_FLEXIBLE) { mAttributes = (LLFlexibleObjectData*)data; setAttributesOfAllSections(); } } void LLVolumeImplFlexible::onShift(const LLVector4a &shift_vector) { //VECTORIZE THIS LLVector3 shift(shift_vector.getF32ptr()); for (int section = 0; section < (1<mDrawable->getScale(); F32 source_section_length = scale.mV[VZ] / (F32)(1< dest_sections) { // Copy, skipping sections S32 num_steps = 1<<(source_sections-dest_sections); // Copy from left to right since it may be an in-place computation for (S32 section=0; section=0; section -= num_steps) { LLFlexibleObjectSection *last_source_section = &source[section>>step_shift]; LLFlexibleObjectSection *source_section = &source[(section>>step_shift)+1]; // Cubic interpolation of position // At^3 + Bt^2 + Ct + D = f(t) LLVector3 D = last_source_section->mPosition; LLVector3 C = last_source_section->mdPosition * source_section_length; LLVector3 Y = source_section->mdPosition * source_section_length - C; // Helper var LLVector3 X = (source_section->mPosition - D - C); // Helper var LLVector3 A = Y - 2*X; LLVector3 B = X - A; F32 t_inc = 1.f/F32(num_steps); F32 t = t_inc; for (S32 step=1; stepmScale, source_section->mScale, t); dest[section+step].mAxisRotation = slerp(t, last_source_section->mAxisRotation, source_section->mAxisRotation); // Evaluate output interpolated values F32 t_sq = t*t; dest[section+step].mPosition = t_sq*(t*A + B) + t*C + D; dest[section+step].mRotation = slerp(t, last_source_section->mRotation, source_section->mRotation); dest[section+step].mVelocity = lerp(last_source_section->mVelocity, source_section->mVelocity, t); dest[section+step].mDirection = lerp(last_source_section->mDirection, source_section->mDirection, t); dest[section+step].mdPosition = lerp(last_source_section->mdPosition, source_section->mdPosition, t); dest[section+num_steps] = *source_section; t += t_inc; } } dest[0] = source[0]; } else { // numbers are equal. copy info for (S32 section=0; section <= num_output_sections; ++section) { dest[section] = source[section]; } } } //----------------------------------------------------------------------------- void LLVolumeImplFlexible::setAttributesOfAllSections(LLVector3* inScale) { LLVector2 bottom_scale, top_scale; F32 begin_rot = 0, end_rot = 0; if (mVO->getVolume()) { const LLPathParams ¶ms = mVO->getVolume()->getParams().getPathParams(); bottom_scale = params.getBeginScale(); top_scale = params.getEndScale(); begin_rot = F_PI * params.getTwistBegin(); end_rot = F_PI * params.getTwist(); } if (!mVO->mDrawable) { return; } S32 num_sections = 1 << mSimulateRes; LLVector3 scale; if (inScale == (LLVector3*)NULL) { scale = mVO->mDrawable->getScale(); } else { scale = *inScale; } mSection[0].mPosition = getAnchorPosition(); mSection[0].mDirection = LLVector3::z_axis * getFrameRotation(); mSection[0].mdPosition = mSection[0].mDirection; mSection[0].mScale.setVec(scale.mV[VX]*bottom_scale.mV[0], scale.mV[VY]*bottom_scale.mV[1]); mSection[0].mVelocity.setVec(0,0,0); mSection[0].mAxisRotation.setQuat(begin_rot,0,0,1); LLVector3 parentSectionPosition = mSection[0].mPosition; LLVector3 last_direction = mSection[0].mDirection; remapSections(mSection, mInitializedRes, mSection, mSimulateRes); mInitializedRes = mSimulateRes; F32 t_inc = 1.f/F32(num_sections); F32 t = t_inc; for ( int i=1; i<= num_sections; i++) { mSection[i].mAxisRotation.setQuat(lerp(begin_rot,end_rot,t),0,0,1); mSection[i].mScale = LLVector2( scale.mV[VX] * lerp(bottom_scale.mV[0], top_scale.mV[0], t), scale.mV[VY] * lerp(bottom_scale.mV[1], top_scale.mV[1], t)); t += t_inc; } }//----------------------------------------------------------------------------------- void LLVolumeImplFlexible::onSetVolume(const LLVolumeParams &volume_params, const S32 detail) { } void LLVolumeImplFlexible::updateRenderRes() { LLDrawable* drawablep = mVO->mDrawable; S32 new_res = mAttributes->getSimulateLOD(); #if 1 //optimal approximation of previous behavior that doesn't rely on atan2 F32 app_angle = mVO->getScale().mV[2]/drawablep->mDistanceWRTCamera; // Rendering sections increases with visible angle on the screen mRenderRes = (S32) (12.f*app_angle); #else //legacy behavior //number of segments only cares about z axis F32 app_angle = llround((F32) atan2( mVO->getScale().mV[2]*2.f, drawablep->mDistanceWRTCamera) * RAD_TO_DEG, 0.01f); // Rendering sections increases with visible angle on the screen mRenderRes = (S32)(FLEXIBLE_OBJECT_MAX_SECTIONS*4*app_angle*DEG_TO_RAD/LLViewerCamera::getInstance()->getView()); #endif mRenderRes = llclamp(mRenderRes, new_res-1, (S32) FLEXIBLE_OBJECT_MAX_SECTIONS); // Throttle back simulation of segments we're not rendering if (mRenderRes < new_res) { new_res = mRenderRes; } if (!mInitialized || (mSimulateRes != new_res)) { mSimulateRes = new_res; setAttributesOfAllSections(); mInitialized = TRUE; } } //--------------------------------------------------------------------------------- // This calculates the physics of the flexible object. Note that it has to be 0 // updated every time step. In the future, perhaps there could be an // optimization similar to what Havok does for objects that are stationary. //--------------------------------------------------------------------------------- static LLFastTimer::DeclareTimer FTM_FLEXIBLE_UPDATE("Update Flexies"); void LLVolumeImplFlexible::doIdleUpdate() { LLDrawable* drawablep = mVO->mDrawable; if (drawablep) { //LLFastTimer ftm(FTM_FLEXIBLE_UPDATE); //ensure drawable is active drawablep->makeActive(); if (gPipeline.hasRenderDebugFeatureMask(LLPipeline::RENDER_DEBUG_FEATURE_FLEXIBLE)) { bool visible = drawablep->isVisible(); if (mRenderRes == -1) { updateRenderRes(); gPipeline.markRebuild(drawablep, LLDrawable::REBUILD_POSITION, FALSE); sUpdateDelay[mInstanceIndex] = 0; } else { F32 pixel_area = mVO->getPixelArea(); U32 update_period = (U32) (llmax((S32) (LLViewerCamera::getInstance()->getScreenPixelArea()*0.01f/(pixel_area*(sUpdateFactor+1.f))),0)+1); // MAINT-1890 Clamp the update period to ensure that the update_period is no greater than 32 frames update_period = llclamp(update_period, 0U, 32U); if (visible) { if (!drawablep->isState(LLDrawable::IN_REBUILD_Q1) && pixel_area > 256.f) { U32 id; if (mVO->isRootEdit()) { id = mID; } else { LLVOVolume* parent = (LLVOVolume*) mVO->getParent(); id = parent->getVolumeInterfaceID(); } if ((LLDrawable::getCurrentFrame()+id)%update_period == 0) { sUpdateDelay[mInstanceIndex] = (S32) update_period-1; updateRenderRes(); gPipeline.markRebuild(drawablep, LLDrawable::REBUILD_POSITION, FALSE); } } } else { sUpdateDelay[mInstanceIndex] = (S32) update_period; } } } } } inline S32 log2(S32 x) { S32 ret = 0; while (x > 1) { ++ret; x >>= 1; } return ret; } void LLVolumeImplFlexible::doFlexibleUpdate() { LLFastTimer ftm(FTM_DO_FLEXIBLE_UPDATE); LLVolume* volume = mVO->getVolume(); LLPath *path = &volume->getPath(); if ((mSimulateRes == 0 || !mInitialized) && mVO->mDrawable->isVisible()) { BOOL force_update = mSimulateRes == 0 ? TRUE : FALSE; doIdleUpdate(); if (!force_update || !gPipeline.hasRenderDebugFeatureMask(LLPipeline::RENDER_DEBUG_FEATURE_FLEXIBLE)) { return; // we did not get updated or initialized, proceeding without can be dangerous } } if(!mInitialized) { //the object is not visible return ; } // stinson 11/12/2012: Need to check with davep on the following. // Skipping the flexible update if render res is negative. If we were to continue with a negative value, // the subsequent S32 num_render_sections = 1< 0.2f) { secondsThisFrame = 0.2f; } LLVector3 BasePosition = getFramePosition(); LLQuaternion BaseRotation = getFrameRotation(); LLQuaternion parentSegmentRotation = BaseRotation; LLVector3 anchorDirectionRotated = LLVector3::z_axis * parentSegmentRotation; LLVector3 anchorScale = mVO->mDrawable->getScale(); F32 section_length = anchorScale.mV[VZ] / (F32)num_sections; F32 inv_section_length = 1.f / section_length; S32 i; // ANCHOR position is offset from BASE position (centroid) by half the length LLVector3 AnchorPosition = BasePosition - (anchorScale.mV[VZ]/2 * anchorDirectionRotated); mSection[0].mPosition = AnchorPosition; mSection[0].mDirection = anchorDirectionRotated; mSection[0].mRotation = BaseRotation; LLQuaternion deltaRotation; LLVector3 lastPosition; // Coefficients which are constant across sections F32 t_factor = mAttributes->getTension() * 0.1f; t_factor = t_factor*(1 - pow(0.85f, secondsThisFrame*30)); if ( t_factor > FLEXIBLE_OBJECT_MAX_INTERNAL_TENSION_FORCE ) { t_factor = FLEXIBLE_OBJECT_MAX_INTERNAL_TENSION_FORCE; } F32 friction_coeff = (mAttributes->getAirFriction()*2+1); friction_coeff = pow(10.f, friction_coeff*secondsThisFrame); friction_coeff = (friction_coeff > 1) ? friction_coeff : 1; F32 momentum = 1.0f / friction_coeff; F32 wind_factor = (mAttributes->getWindSensitivity()*0.1f) * section_length * secondsThisFrame; F32 max_angle = atan(section_length*2.f); F32 force_factor = section_length * secondsThisFrame; // Update simulated sections for (i=1; i<=num_sections; ++i) { LLVector3 parentSectionVector; LLVector3 parentSectionPosition; LLVector3 parentDirection; //--------------------------------------------------- // save value of position as lastPosition //--------------------------------------------------- lastPosition = mSection[i].mPosition; //------------------------------------------------------------------------------------------ // gravity //------------------------------------------------------------------------------------------ mSection[i].mPosition.mV[2] -= mAttributes->getGravity() * force_factor; //------------------------------------------------------------------------------------------ // wind force //------------------------------------------------------------------------------------------ if (mAttributes->getWindSensitivity() > 0.001f) { mSection[i].mPosition += gAgent.getRegion()->mWind.getVelocity( mSection[i].mPosition ) * wind_factor; } //------------------------------------------------------------------------------------------ // user-defined force //------------------------------------------------------------------------------------------ mSection[i].mPosition += mAttributes->getUserForce() * force_factor; //--------------------------------------------------- // tension (rigidity, stiffness) //--------------------------------------------------- parentSectionPosition = mSection[i-1].mPosition; parentDirection = mSection[i-1].mDirection; if ( i == 1 ) { parentSectionVector = mSection[0].mDirection; } else { parentSectionVector = mSection[i-2].mDirection; } LLVector3 currentVector = mSection[i].mPosition - parentSectionPosition; LLVector3 difference = (parentSectionVector*section_length) - currentVector; LLVector3 tensionForce = difference * t_factor; mSection[i].mPosition += tensionForce; //------------------------------------------------------------------------------------------ // sphere collision, currently not used //------------------------------------------------------------------------------------------ /*if ( mAttributes->mUsingCollisionSphere ) { LLVector3 vectorToCenterOfCollisionSphere = mCollisionSpherePosition - mSection[i].mPosition; if ( vectorToCenterOfCollisionSphere.magVecSquared() < mCollisionSphereRadius * mCollisionSphereRadius ) { F32 distanceToCenterOfCollisionSphere = vectorToCenterOfCollisionSphere.magVec(); F32 penetration = mCollisionSphereRadius - distanceToCenterOfCollisionSphere; LLVector3 normalToCenterOfCollisionSphere; if ( distanceToCenterOfCollisionSphere > 0.0f ) { normalToCenterOfCollisionSphere = vectorToCenterOfCollisionSphere / distanceToCenterOfCollisionSphere; } else // rare { normalToCenterOfCollisionSphere = LLVector3::x_axis; // arbitrary } // push the position out to the surface of the collision sphere mSection[i].mPosition -= normalToCenterOfCollisionSphere * penetration; } }*/ //------------------------------------------------------------------------------------------ // inertia //------------------------------------------------------------------------------------------ mSection[i].mPosition += mSection[i].mVelocity * momentum; //------------------------------------------------------------------------------------------ // clamp length & rotation //------------------------------------------------------------------------------------------ mSection[i].mDirection = mSection[i].mPosition - parentSectionPosition; mSection[i].mDirection.normVec(); deltaRotation.shortestArc( parentDirection, mSection[i].mDirection ); F32 angle; LLVector3 axis; deltaRotation.getAngleAxis(&angle, axis); if (angle > F_PI) angle -= 2.f*F_PI; if (angle < -F_PI) angle += 2.f*F_PI; if (angle > max_angle) { //angle = 0.5f*(angle+max_angle); deltaRotation.setQuat(max_angle, axis); } else if (angle < -max_angle) { //angle = 0.5f*(angle-max_angle); deltaRotation.setQuat(-max_angle, axis); } LLQuaternion segment_rotation = parentSegmentRotation * deltaRotation; parentSegmentRotation = segment_rotation; mSection[i].mDirection = (parentDirection * deltaRotation); mSection[i].mPosition = parentSectionPosition + mSection[i].mDirection * section_length; mSection[i].mRotation = segment_rotation; if (i > 1) { // Propogate half the rotation up to the parent LLQuaternion halfDeltaRotation(angle/2, axis); mSection[i-1].mRotation = mSection[i-1].mRotation * halfDeltaRotation; } //------------------------------------------------------------------------------------------ // calculate velocity //------------------------------------------------------------------------------------------ mSection[i].mVelocity = mSection[i].mPosition - lastPosition; if (mSection[i].mVelocity.magVecSquared() > 1.f) { mSection[i].mVelocity.normVec(); } } // Calculate derivatives (not necessary until normals are automagically generated) mSection[0].mdPosition = (mSection[1].mPosition - mSection[0].mPosition) * inv_section_length; // i = 1..NumSections-1 for (i=1; i -1) S32 num_render_sections = 1<getPathLength() != num_render_sections+1) { ((LLVOVolume*) mVO)->mVolumeChanged = TRUE; volume->resizePath(num_render_sections+1); } LLPath::PathPt *new_point; LLFlexibleObjectSection newSection[ (1<mPath[i]; LLVector3 pos = newSection[i].mPosition * rel_xform; LLQuaternion rot = mSection[i].mAxisRotation * newSection[i].mRotation * delta_rot; LLVector3 np(new_point->mPos.getF32ptr()); if (!mUpdated || (np-pos).magVec()/mVO->mDrawable->mDistanceWRTCamera > 0.001f) { new_point->mPos.load3((newSection[i].mPosition * rel_xform).mV); mUpdated = FALSE; } new_point->mRot.loadu(LLMatrix3(rot)); new_point->mScale.set(newSection[i].mScale.mV[0], newSection[i].mScale.mV[1], 0,1); new_point->mTexT = ((F32)i)/(num_render_sections); } LL_CHECK_MEMORY mLastSegmentRotation = parentSegmentRotation; } static LLFastTimer::DeclareTimer FTM_FLEXI_PREBUILD("Flexi Prebuild"); void LLVolumeImplFlexible::preRebuild() { if (!mUpdated) { LLFastTimer t(FTM_FLEXI_PREBUILD); doFlexibleRebuild(); } } void LLVolumeImplFlexible::doFlexibleRebuild() { LLVolume* volume = mVO->getVolume(); volume->regen(); mUpdated = TRUE; } //------------------------------------------------------------------ void LLVolumeImplFlexible::onSetScale(const LLVector3& scale, BOOL damped) { setAttributesOfAllSections((LLVector3*) &scale); } BOOL LLVolumeImplFlexible::doUpdateGeometry(LLDrawable *drawable) { LLVOVolume *volume = (LLVOVolume*)mVO; if (mVO->isAttachment()) { //don't update flexible attachments for impostored avatars unless the //impostor is being updated this frame (w00!) LLViewerObject* parent = (LLViewerObject*) mVO->getParent(); while (parent && !parent->isAvatar()) { parent = (LLViewerObject*) parent->getParent(); } if (parent) { LLVOAvatar* avatar = (LLVOAvatar*) parent; if (avatar->isImpostor() && !avatar->needsImpostorUpdate()) { return TRUE; } } } if (volume->mDrawable.isNull()) { return TRUE; // No update to complete } if (volume->mLODChanged) { LLVolumeParams volume_params = volume->getVolume()->getParams(); volume->setVolume(volume_params, 0); mUpdated = FALSE; } volume->updateRelativeXform(); if (mRenderRes > -1) { LLFastTimer t(FTM_DO_FLEXIBLE_UPDATE); doFlexibleUpdate(); } // Object may have been rotated, which means it needs a rebuild. See SL-47220 BOOL rotated = FALSE; LLQuaternion cur_rotation = getFrameRotation(); if ( cur_rotation != mLastFrameRotation ) { mLastFrameRotation = cur_rotation; rotated = TRUE; } if (volume->mLODChanged || volume->mFaceMappingChanged || volume->mVolumeChanged || drawable->isState(LLDrawable::REBUILD_MATERIAL)) { volume->regenFaces(); volume->mDrawable->setState(LLDrawable::REBUILD_VOLUME); volume->dirtySpatialGroup(); { LLFastTimer t(FTM_FLEXIBLE_REBUILD); doFlexibleRebuild(); } volume->genBBoxes(isVolumeGlobal()); } else if (!mUpdated || rotated) { volume->mDrawable->setState(LLDrawable::REBUILD_POSITION); LLSpatialGroup* group = volume->mDrawable->getSpatialGroup(); if (group) { group->dirtyMesh(); } volume->genBBoxes(isVolumeGlobal()); } volume->mVolumeChanged = FALSE; volume->mLODChanged = FALSE; volume->mFaceMappingChanged = FALSE; // clear UV flag drawable->clearState(LLDrawable::UV); return TRUE; } //---------------------------------------------------------------------------------- void LLVolumeImplFlexible::setCollisionSphere( LLVector3 p, F32 r ) { mCollisionSpherePosition = p; mCollisionSphereRadius = r; }//------------------------------------------------------------------ //---------------------------------------------------------------------------------- void LLVolumeImplFlexible::setUsingCollisionSphere( bool u ) { }//------------------------------------------------------------------ //---------------------------------------------------------------------------------- void LLVolumeImplFlexible::setRenderingCollisionSphere( bool r ) { }//------------------------------------------------------------------ //------------------------------------------------------------------ LLVector3 LLVolumeImplFlexible::getEndPosition() { S32 num_sections = 1 << mAttributes->getSimulateLOD(); return mSection[ num_sections ].mPosition; }//------------------------------------------------------------------ //------------------------------------------------------------------ LLVector3 LLVolumeImplFlexible::getNodePosition( int nodeIndex ) { S32 num_sections = 1 << mAttributes->getSimulateLOD(); if ( nodeIndex > num_sections - 1 ) { nodeIndex = num_sections - 1; } else if ( nodeIndex < 0 ) { nodeIndex = 0; } return mSection[ nodeIndex ].mPosition; }//------------------------------------------------------------------ LLVector3 LLVolumeImplFlexible::getPivotPosition() const { return getAnchorPosition(); } //------------------------------------------------------------------ LLVector3 LLVolumeImplFlexible::getAnchorPosition() const { LLVector3 BasePosition = getFramePosition(); LLQuaternion parentSegmentRotation = getFrameRotation(); LLVector3 anchorDirectionRotated = LLVector3::z_axis * parentSegmentRotation; LLVector3 anchorScale = mVO->mDrawable->getScale(); return BasePosition - (anchorScale.mV[VZ]/2 * anchorDirectionRotated); }//------------------------------------------------------------------ //------------------------------------------------------------------ LLQuaternion LLVolumeImplFlexible::getEndRotation() { return mLastSegmentRotation; }//------------------------------------------------------------------ void LLVolumeImplFlexible::updateRelativeXform(bool force_identity) { LLQuaternion delta_rot; LLVector3 delta_pos, delta_scale; LLVOVolume* vo = (LLVOVolume*) mVO; bool use_identity = vo->mDrawable->isSpatialRoot() || force_identity; //matrix from local space to parent relative/global space delta_rot = use_identity ? LLQuaternion() : vo->mDrawable->getRotation(); delta_pos = use_identity ? LLVector3(0,0,0) : vo->mDrawable->getPosition(); delta_scale = LLVector3(1,1,1); // Vertex transform (4x4) LLVector3 x_axis = LLVector3(delta_scale.mV[VX], 0.f, 0.f) * delta_rot; LLVector3 y_axis = LLVector3(0.f, delta_scale.mV[VY], 0.f) * delta_rot; LLVector3 z_axis = LLVector3(0.f, 0.f, delta_scale.mV[VZ]) * delta_rot; vo->mRelativeXform.initRows(LLVector4(x_axis, 0.f), LLVector4(y_axis, 0.f), LLVector4(z_axis, 0.f), LLVector4(delta_pos, 1.f)); x_axis.normVec(); y_axis.normVec(); z_axis.normVec(); vo->mRelativeXformInvTrans.setRows(x_axis, y_axis, z_axis); } const LLMatrix4& LLVolumeImplFlexible::getWorldMatrix(LLXformMatrix* xform) const { return xform->getWorldMatrix(); }