/*==LICENSE==* CyanWorlds.com Engine - MMOG client, server and tools Copyright (C) 2011 Cyan Worlds, Inc. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . You can contact Cyan Worlds, Inc. by email legal@cyan.com or by snail mail at: Cyan Worlds, Inc. 14617 N Newport Hwy Mead, WA 99021 *==LICENSE==*/ #include "hsTypes.h" #include "plCoordinateInterface.h" #include "plDrawInterface.h" #include "plSimulationInterface.h" #include "plAudioInterface.h" #include "pnMessage/plWarpMsg.h" #include "pnMessage/plTimeMsg.h" #include "pnMessage/plCorrectionMsg.h" #include "pnMessage/plIntRefMsg.h" #include "pnNetCommon/plSDLTypes.h" #include "plSceneObject.h" #include "hsResMgr.h" #include "plgDispatch.h" #include "pnKeyedObject/plKey.h" #include "hsStream.h" #include "plProfile.h" UInt8 plCoordinateInterface::fTransformPhase = plCoordinateInterface::kTransformPhaseNormal; hsBool plCoordinateInterface::fDelayedTransformsEnabled = true; plCoordinateInterface::plCoordinateInterface() : fParent(nil), fReason(kReasonUnknown) { fLocalToParent.Reset(); fParentToLocal.Reset(); fLocalToWorld.Reset(); fWorldToLocal.Reset(); fState = 0; } plCoordinateInterface::~plCoordinateInterface() { if( fParent ) fParent->IRemoveChild(IGetOwner()); int i; for( i = fChildren.GetCount()-1; i >= 0; i-- ) IRemoveChild(i); } void plCoordinateInterface::ISetSceneNode(plKey newNode) { int i; for( i = 0; i < fChildren.GetCount(); i++ ) { if( fChildren[i] ) fChildren[i]->SetSceneNode(newNode); } } void plCoordinateInterface::ISetOwner(plSceneObject* so) { plObjInterface::ISetOwner(so); IDirtyTransform(); fReason |= kReasonUnknown; } void plCoordinateInterface::ISetParent(plCoordinateInterface* par) { fParent = par; // This won't have any effect if my owner is NetGroupConstant if( fParent ) ISetNetGroupRecur(fParent->GetNetGroup()); IDirtyTransform(); fReason |= kReasonUnknown; } plCoordinateInterface* plCoordinateInterface::GetChild(int i) const { return fChildren[i] ? fChildren[i]->GetVolatileCoordinateInterface() : nil; } void plCoordinateInterface::IRemoveChild(int i) { if( fChildren[i] ) { plCoordinateInterface* childCI = fChildren[i]->GetVolatileCoordinateInterface(); if( childCI ) childCI->ISetParent(nil); } fChildren.Remove(i); } void plCoordinateInterface::IRemoveChild(plSceneObject* child) { int idx = fChildren.Find(child); if( idx != fChildren.kMissingIndex ) IRemoveChild(idx); } void plCoordinateInterface::ISetChild(plSceneObject* child, int which) { hsAssert(child, "Setting a nil child"); plCoordinateInterface* childCI = child->GetVolatileCoordinateInterface(); hsAssert(childCI, "Child with no coordinate interface"); childCI->ISetParent(this); if( which < 0 ) which = fChildren.GetCount(); fChildren.ExpandAndZero(which+1); fChildren[which] = child; // If we can't delay our transform update, neither can any of our parents. if (!childCI->GetProperty(kDelayedTransformEval)) { plCoordinateInterface *current = childCI->GetParent(); while (current) { current->SetProperty(kDelayedTransformEval, false); current = current->GetParent(); } } } void plCoordinateInterface::IAddChild(plSceneObject* child) { ISetChild(child, -1); } void plCoordinateInterface::IAttachChild(plSceneObject* child, UInt8 flags) { hsAssert(child, "Attaching a nil child"); plCoordinateInterface* childCI = child->GetVolatileCoordinateInterface(); hsAssert(childCI, "Owner without CoordinateInterface being attached"); if (childCI->GetParent() == this) return; // We're already attached! Who told us to do this? hsMatrix44 l2w = childCI->GetLocalToWorld(); hsMatrix44 w2l = childCI->GetWorldToLocal(); if( childCI->GetParent() ) childCI->GetParent()->IDetachChild(child, flags | kAboutToAttach); childCI->IUnRegisterForTransformMessage(); IAddChild(child); if( flags & kMaintainWorldPosition ) childCI->WarpToWorld(l2w,w2l); } void plCoordinateInterface::IDetachChild(plSceneObject* child, UInt8 flags) { hsAssert(child, "Detaching a nil child"); plCoordinateInterface* childCI = child->GetVolatileCoordinateInterface(); hsAssert(childCI, "Owner without CoordinateInterface being attached"); hsMatrix44 l2w = childCI->GetLocalToWorld(); hsMatrix44 w2l = childCI->GetWorldToLocal(); GetKey()->Release(child->GetKey()); if( IGetOwner() && IGetOwner()->GetKey() ) IGetOwner()->GetKey()->Release(child->GetKey()); IRemoveChild(child); if( flags & kMaintainWorldPosition ) childCI->WarpToWorld(l2w,w2l); // If the child was keeping us from delaying our transform, // maybe we can, now that it's gone. if (!childCI->GetProperty(kDelayedTransformEval)) IUpdateDelayProp(); } /* * A few notes on the delay transform properties... * * The kCanEverDelayTransform prop is independent of any parents/children. * It means this particular node must always update its transform in response * to a plTransformMsg. It is intended for objects with physics, because they * need to be up-to-date before the simulationMgr updates the physical world. * * The kDelayedTransformEval prop is for nodes that are free of physics. (So no * physical descendants either). If the property is set, we won't update our * transform until AFTER the simulationMgr does its work. * * When we attach a child that can't delay its eval (at the moment), we recurse * up to the root, turning off the kDelayedTransformEval prop as we go. When we * remove such a child, we check if that child was the only reason we weren't * delaying our transform. If so, we update ourself and tell our parent to check. * * BTW: The POINT of all this is that when we update our l2w transforms because * we're animated, and then we update AGAIN after a parent node of ours involved * in physics gets a slight nudge, the first update becomes pointless. The * delay prop bookkeeping keeps us from doing the wasted calculations. And since * nearly all bones on the avatar are in this exact situation, it's worth doing. */ void plCoordinateInterface::IUpdateDelayProp() { int i; if (!GetProperty(kCanEverDelayTransform)) return; for (i = 0; i < GetNumChildren(); i++) { // If we still have a child that needs the delay... if (!GetChild(i)->GetProperty(kDelayedTransformEval)) return; } // Cool, we can delay now, which means maybe our parent can too. SetProperty(kDelayedTransformEval, true); if (GetParent()) GetParent()->IUpdateDelayProp(); } plCoordinateInterface* plCoordinateInterface::IGetRoot() { return fParent ? fParent->IGetRoot() : this; } void plCoordinateInterface::IRegisterForTransformMessage(hsBool delayed) { if( IGetOwner() ) { if ((delayed || fTransformPhase == kTransformPhaseDelayed) && fDelayedTransformsEnabled) plgDispatch::Dispatch()->RegisterForExactType(plDelayedTransformMsg::Index(), IGetOwner()->GetKey()); else plgDispatch::Dispatch()->RegisterForExactType(plTransformMsg::Index(), IGetOwner()->GetKey()); } } void plCoordinateInterface::IUnRegisterForTransformMessage() { if( IGetOwner() ) plgDispatch::Dispatch()->UnRegisterForExactType(plTransformMsg::Index(), IGetOwner()->GetKey()); } void plCoordinateInterface::IDirtyTransform() { fState |= kTransformDirty; IGetRoot()->IRegisterForTransformMessage(GetProperty(kDelayedTransformEval)); } void plCoordinateInterface::MultTransformLocal(const hsMatrix44& move, const hsMatrix44& invMove) { fReason |= kReasonUnknown; fLocalToParent = move * fLocalToParent; fParentToLocal = fParentToLocal * invMove; IDirtyTransform(); } void plCoordinateInterface::SetTransform(const hsMatrix44& l2w, const hsMatrix44& w2l) { fReason |= kReasonUnknown; if( fParent ) { SetLocalToParent(fParent->GetWorldToLocal() * l2w, w2l * fParent->GetLocalToWorld()); } else { SetLocalToParent(l2w, w2l); } } void plCoordinateInterface::SetTransformPhysical(const hsMatrix44& l2w, const hsMatrix44& w2l) { // since we use public interfaces to do the details // AND those public interfaces could be called by anyone // AND those public interfaces therefore have to set their own "reason" for the transform change // THEREFORE: we need to preserve the "reason" flags before we call the public interfaces // so that we don't get reasonPhysics + reasonUnknown, just reasonPhysics UInt16 oldReason = fReason; if( fParent ) { SetLocalToParent(fParent->GetWorldToLocal() * l2w, w2l * fParent->GetLocalToWorld()); } else { SetLocalToParent(l2w, w2l); } fReason = oldReason | kReasonPhysics; } UInt16 plCoordinateInterface::GetReasons() { return fReason; } void plCoordinateInterface::ClearReasons() { fReason = nil; } void plCoordinateInterface::SetLocalToParent(const hsMatrix44& l2p, const hsMatrix44& p2l) { fReason |= kReasonUnknown; fLocalToParent = l2p; fParentToLocal = p2l; IDirtyTransform(); } void plCoordinateInterface::WarpToLocal(const hsMatrix44& l2p, const hsMatrix44& p2l) { fReason |= kReasonUnknown; SetLocalToParent(l2p, p2l); // update physical state when an object is warped if (IGetOwner()) IGetOwner()->DirtySynchState(kSDLPhysical, 0); } void plCoordinateInterface::WarpToWorld(const hsMatrix44& l2w, const hsMatrix44& w2l) { fReason |= kReasonUnknown; if( fParent ) { hsMatrix44 l2p = fParent->GetWorldToLocal() * l2w; hsMatrix44 p2l = w2l * fParent->GetLocalToWorld(); WarpToLocal(l2p, p2l); } else { WarpToLocal(l2w, w2l); } } plProfile_CreateCounter("CITrans", "Object", CITrans); plProfile_CreateCounter(" CIRecalc", "Object", CIRecalc); plProfile_CreateCounter(" CIDirty", "Object", CIDirty); plProfile_CreateCounter(" CISet", "Object", CISet); plProfile_CreateTimer("CITransT", "Object", CITransT); plProfile_CreateTimer(" CIRecalcT", "Object", CIRecalcT); plProfile_CreateTimer(" CIDirtyT", "Object", CIDirtyT); plProfile_CreateTimer(" CISetT", "Object", CISetT); static inline hsMatrix44 IMatrixMul34(const hsMatrix44& lhs, const hsMatrix44& rhs) { hsMatrix44 ret; ret.NotIdentity(); ret.fMap[3][0] = ret.fMap[3][1] = ret.fMap[3][2] = 0; ret.fMap[3][3] = 1.f; ret.fMap[0][0] = lhs.fMap[0][0] * rhs.fMap[0][0] + lhs.fMap[0][1] * rhs.fMap[1][0] + lhs.fMap[0][2] * rhs.fMap[2][0]; ret.fMap[0][1] = lhs.fMap[0][0] * rhs.fMap[0][1] + lhs.fMap[0][1] * rhs.fMap[1][1] + lhs.fMap[0][2] * rhs.fMap[2][1]; ret.fMap[0][2] = lhs.fMap[0][0] * rhs.fMap[0][2] + lhs.fMap[0][1] * rhs.fMap[1][2] + lhs.fMap[0][2] * rhs.fMap[2][2]; ret.fMap[0][3] = lhs.fMap[0][0] * rhs.fMap[0][3] + lhs.fMap[0][1] * rhs.fMap[1][3] + lhs.fMap[0][2] * rhs.fMap[2][3] + lhs.fMap[0][3]; ret.fMap[1][0] = lhs.fMap[1][0] * rhs.fMap[0][0] + lhs.fMap[1][1] * rhs.fMap[1][0] + lhs.fMap[1][2] * rhs.fMap[2][0]; ret.fMap[1][1] = lhs.fMap[1][0] * rhs.fMap[0][1] + lhs.fMap[1][1] * rhs.fMap[1][1] + lhs.fMap[1][2] * rhs.fMap[2][1]; ret.fMap[1][2] = lhs.fMap[1][0] * rhs.fMap[0][2] + lhs.fMap[1][1] * rhs.fMap[1][2] + lhs.fMap[1][2] * rhs.fMap[2][2]; ret.fMap[1][3] = lhs.fMap[1][0] * rhs.fMap[0][3] + lhs.fMap[1][1] * rhs.fMap[1][3] + lhs.fMap[1][2] * rhs.fMap[2][3] + lhs.fMap[1][3]; ret.fMap[2][0] = lhs.fMap[2][0] * rhs.fMap[0][0] + lhs.fMap[2][1] * rhs.fMap[1][0] + lhs.fMap[2][2] * rhs.fMap[2][0]; ret.fMap[2][1] = lhs.fMap[2][0] * rhs.fMap[0][1] + lhs.fMap[2][1] * rhs.fMap[1][1] + lhs.fMap[2][2] * rhs.fMap[2][1]; ret.fMap[2][2] = lhs.fMap[2][0] * rhs.fMap[0][2] + lhs.fMap[2][1] * rhs.fMap[1][2] + lhs.fMap[2][2] * rhs.fMap[2][2]; ret.fMap[2][3] = lhs.fMap[2][0] * rhs.fMap[0][3] + lhs.fMap[2][1] * rhs.fMap[1][3] + lhs.fMap[2][2] * rhs.fMap[2][3] + lhs.fMap[2][3]; return ret; } void plCoordinateInterface::IRecalcTransforms() { plProfile_IncCount(CIRecalc, 1); plProfile_BeginTiming(CIRecalcT); if( fParent ) { #if 0 fLocalToWorld = fParent->GetLocalToWorld() * fLocalToParent; fWorldToLocal = fParentToLocal * fParent->GetWorldToLocal(); #else fLocalToWorld = IMatrixMul34(fParent->GetLocalToWorld(), fLocalToParent); fWorldToLocal = IMatrixMul34(fParentToLocal, fParent->GetWorldToLocal()); #endif } else { fLocalToWorld = fLocalToParent; fWorldToLocal = fParentToLocal; } plProfile_EndTiming(CIRecalcT); } void plCoordinateInterface::ITransformChanged(hsBool force, UInt16 reasons, hsBool checkForDelay) { plProfile_IncCount(CITrans, 1); plProfile_BeginTiming(CITransT); // inherit reasons for transform change from our parents fReason |= reasons; UInt16 propagateReasons = fReason; hsBool process = !(checkForDelay && GetProperty(kDelayedTransformEval)) || !fDelayedTransformsEnabled; if (process) { if( fState & kTransformDirty ) force = true; } if( force ) { IRecalcTransforms(); plProfile_IncCount(CISet, 1); plProfile_BeginTiming(CISetT); if( IGetOwner() ) { IGetOwner()->ISetTransform(fLocalToWorld, fWorldToLocal); } plProfile_EndTiming(CISetT); fState &= ~kTransformDirty; } plProfile_EndTiming(CITransT); if (process) { int i; for( i = 0; i < fChildren.GetCount(); i++ ) { if( fChildren[i] && fChildren[i]->GetVolatileCoordinateInterface() ) fChildren[i]->GetVolatileCoordinateInterface()->ITransformChanged(force, propagateReasons, checkForDelay); } } else if (force) { plProfile_IncCount(CIDirty, 1); plProfile_BeginTiming(CITransT); // Our parent is dirty and we're bailing out on evaluating right now. // Need to ensure we'll be evaluated in the delay pass plProfile_BeginTiming(CIDirtyT); IDirtyTransform(); plProfile_EndTiming(CIDirtyT); plProfile_EndTiming(CITransT); } } void plCoordinateInterface::FlushTransform(hsBool fromRoot) { if( fromRoot ) IGetRoot()->ITransformChanged(false, 0, false); else ITransformChanged(false, 0, false); } void plCoordinateInterface::ISetNetGroupRecur(plNetGroupId netGroup) { if( !IGetOwner() ) return; if( IGetOwner()->GetSynchFlags() & kHasConstantNetGroup ) return; IGetOwner()->plSynchedObject::SetNetGroup(netGroup); int i; for( i = 0; i < GetNumChildren(); i++ ) { if( GetChild(i) ) { GetChild(i)->ISetNetGroupRecur(netGroup); } } } void plCoordinateInterface::Read(hsStream* stream, hsResMgr* mgr) { plObjInterface::Read(stream, mgr); fLocalToParent.Read(stream); fParentToLocal.Read(stream); fLocalToWorld.Read(stream); fWorldToLocal.Read(stream); int n = stream->ReadSwap32(); int i; for( i = 0; i < n; i++ ) { plIntRefMsg* refMsg = TRACKED_NEW plIntRefMsg(GetKey(), plRefMsg::kOnCreate, -1, plIntRefMsg::kChildObject); mgr->ReadKeyNotifyMe(stream,refMsg, plRefFlags::kPassiveRef); } } void plCoordinateInterface::Write(hsStream* stream, hsResMgr* mgr) { plObjInterface::Write(stream, mgr); fLocalToParent.Write(stream); fParentToLocal.Write(stream); fLocalToWorld.Write(stream); fWorldToLocal.Write(stream); stream->WriteSwap32(fChildren.GetCount()); int i; for( i = 0; i < fChildren.GetCount(); i++ ) mgr->WriteKey(stream, fChildren[i]); } hsBool plCoordinateInterface::MsgReceive(plMessage* msg) { hsBool retVal = false; plIntRefMsg* intRefMsg; plCorrectionMsg* corrMsg; // warp message plWarpMsg* pWarpMsg = plWarpMsg::ConvertNoRef(msg); if (pWarpMsg) { hsMatrix44 l2w = pWarpMsg->GetTransform(); hsMatrix44 inv; l2w.GetInverse(&inv); WarpToWorld(l2w,inv); if (pWarpMsg->GetWarpFlags() & plWarpMsg::kFlushTransform) ITransformChanged(false, kReasonUnknown, false); return true; } else if( intRefMsg = plIntRefMsg::ConvertNoRef(msg) ) { switch( intRefMsg->fType ) { case plIntRefMsg::kChildObject: case plIntRefMsg::kChild: { plSceneObject* co = nil; if( intRefMsg->fType == plIntRefMsg::kChildObject ) { co = plSceneObject::ConvertNoRef(intRefMsg->GetRef()); } else { plCoordinateInterface* ci = plCoordinateInterface::ConvertNoRef(intRefMsg->GetRef()); co = ci ? ci->IGetOwner() : nil; } if( intRefMsg->GetContext() & (plRefMsg::kOnCreate|plRefMsg::kOnReplace) ) { ISetChild(co, intRefMsg->fWhich); } else if( intRefMsg->GetContext() & plRefMsg::kOnDestroy ) { IRemoveChild(co); } else if( intRefMsg->GetContext() & plRefMsg::kOnRequest ) { IAttachChild(co, kMaintainWorldPosition|kMaintainSceneNode); } else if( intRefMsg->GetContext() & plRefMsg::kOnRemove ) { IDetachChild(co, kMaintainWorldPosition|kMaintainSceneNode); } } return true; default: break; } } else if( corrMsg = plCorrectionMsg::ConvertNoRef(msg) ) { SetTransformPhysical(corrMsg->fLocalToWorld, corrMsg->fWorldToLocal); if(corrMsg->fDirtySynch) { if (IGetOwner()) IGetOwner()->DirtySynchState(kSDLPhysical, 0); } return true; } return plObjInterface::MsgReceive(msg); }