diff --git a/Engine/source/T3D/Entity.cpp b/Engine/source/T3D/Entity.cpp new file mode 100644 index 000000000..e80f4e8b8 --- /dev/null +++ b/Engine/source/T3D/Entity.cpp @@ -0,0 +1,1898 @@ +//----------------------------------------------------------------------------- +// Copyright (c) 2012 GarageGames, LLC +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "T3D/Entity.h" +#include "core/stream/bitStream.h" +#include "console/consoleTypes.h" +#include "console/consoleObject.h" +#include "sim/netConnection.h" +#include "scene/sceneRenderState.h" +#include "scene/sceneManager.h" +#include "T3D/gameBase/gameProcess.h" +#include "console/engineAPI.h" +#include "T3D/gameBase/gameConnection.h" +#include "math/mathIO.h" +#include "math/mTransform.h" + +#include "T3D/Components/coreInterfaces.h" +#include "T3D/Components/render/renderComponentInterface.h" +#include "T3D/Components/Collision/collisionInterfaces.h" + +#include "gui/controls/guiTreeViewCtrl.h" + +#include "console/consoleInternal.h" +#include "T3D/gameBase/std/stdMoveList.h" + +#include "T3D/prefab.h" + +// +#include "gfx/sim/debugDraw.h" +// + +extern bool gEditingMission; + +// Client prediction +static F32 sMinWarpTicks = 0.5f; // Fraction of tick at which instant warp occurs +static S32 sMaxWarpTicks = 3; // Max warp duration in ticks +static S32 sMaxPredictionTicks = 30; // Number of ticks to predict + +IMPLEMENT_CO_NETOBJECT_V1(Entity); + +ConsoleDocClass(Entity, + "@brief Base Entity class.\n\n" + + "Entity is typically made up of a shape and up to two particle emitters. In most cases Entity objects are " + "not created directly. They are usually produced automatically by other means, such as through the Explosion " + "class. When an explosion goes off, its ExplosionData datablock determines what Entity to emit.\n" + + "@tsexample\n" + "datablock ExplosionData(GrenadeLauncherExplosion)\n" + "{\n" + " // Assiging Entity data\n" + " Entity = GrenadeEntity;\n\n" + " // Adjust how Entity is ejected\n" + " EntityThetaMin = 10;\n" + " EntityThetaMax = 60;\n" + " EntityNum = 4;\n" + " EntityNumVariance = 2;\n" + " EntityVelocity = 25;\n" + " EntityVelocityVariance = 5;\n\n" + " // Note: other ExplosionData properties are not listed for this example\n" + "};\n" + "@endtsexample\n\n" + + "@note Entity are client side only objects.\n" + + "@see EntityData\n" + "@see ExplosionData\n" + "@see Explosion\n" + + "@ingroup FX\n" + ); + +Entity::Entity() +{ + //mTypeMask |= DynamicShapeObjectType | StaticObjectType | ; + mTypeMask |= EntityObjectType; + mNetFlags.set(Ghostable | ScopeAlways); + + mPos = Point3F(0, 0, 0); + mRot = Point3F(0, 0, 0); + + mDelta.pos = mDelta.posVec = Point3F::Zero; + mDelta.rot[0].identity(); + mDelta.rot[1].identity(); + mDelta.warpOffset.set(0.0f, 0.0f, 0.0f); + + mDelta.warpTicks = mDelta.warpCount = 0; + mDelta.dt = 1.0f; + mDelta.move = NullMove; + + mComponents.clear(); + + mStartComponentUpdate = false; + + mInitialized = false; + +} + +Entity::~Entity() +{ + +} + +void Entity::initPersistFields() +{ + Parent::initPersistFields(); + + removeField("DataBlock"); + + addGroup("Transform"); + + removeField("Position"); + addProtectedField("Position", TypePoint3F, Offset(mPos, Entity), &_setPosition, &_getPosition, "Object world orientation."); + + removeField("Rotation"); + addProtectedField("Rotation", TypeRotationF, Offset(mRot, Entity), &_setRotation, &_getRotation, "Object world orientation."); + + //These are basically renamed mountPos/Rot. pretty much there for conveinence + addField("LocalPosition", TypeMatrixPosition, Offset(mMount.xfm, Entity), "Position we are mounted at ( object space of our mount object )."); + addField("LocalRotation", TypeMatrixRotation, Offset(mMount.xfm, Entity), "Rotation we are mounted at ( object space of our mount object )."); + + endGroup("Transform"); +} + +// +bool Entity::_setPosition(void *object, const char *index, const char *data) +{ + Entity* so = static_cast(object); + if (so) + { + Point3F pos; + + if (!dStrcmp(data, "")) + pos = Point3F(0, 0, 0); + else + Con::setData(TypePoint3F, &pos, 0, 1, &data); + + so->setTransform(pos, so->mRot); + } + return false; +} + +const char * Entity::_getPosition(void* obj, const char* data) +{ + Entity* so = static_cast(obj); + if (so) + { + Point3F pos = so->getPosition(); + + static const U32 bufSize = 256; + char* returnBuffer = Con::getReturnBuffer(bufSize); + dSprintf(returnBuffer, bufSize, "%g %g %g", pos.x, pos.y, pos.z); + return returnBuffer; + } + return "0 0 0"; +} + +bool Entity::_setRotation(void *object, const char *index, const char *data) +{ + Entity* so = static_cast(object); + if (so) + { + RotationF rot; + Con::setData(TypeRotationF, &rot, 0, 1, &data); + + //so->mRot = rot; + //MatrixF mat = rot.asMatrixF(); + //mat.setPosition(so->getPosition()); + //so->setTransform(mat); + so->setTransform(so->getPosition(), rot); + } + return false; +} + +const char * Entity::_getRotation(void* obj, const char* data) +{ + Entity* so = static_cast(obj); + if (so) + { + EulerF eulRot = so->mRot.asEulerF(); + + static const U32 bufSize = 256; + char* returnBuffer = Con::getReturnBuffer(bufSize); + dSprintf(returnBuffer, bufSize, "%g %g %g", mRadToDeg(eulRot.x), mRadToDeg(eulRot.y), mRadToDeg(eulRot.z)); + return returnBuffer; + } + return "0 0 0"; +} + +bool Entity::onAdd() +{ + if (!Parent::onAdd()) + return false; + + mObjBox = Box3F(Point3F(-1, -1, -1), Point3F(1, 1, 1)); + + resetWorldBox(); + setObjectBox(mObjBox); + + addToScene(); + + //Make sure we get positioned + setMaskBits(TransformMask); + + return true; +} + +void Entity::onRemove() +{ + clearComponents(false); + + removeFromScene(); + + onDataSet.removeAll(); + + Parent::onRemove(); +} + +void Entity::onPostAdd() +{ + mInitialized = true; + + //everything's done and added. go ahead and initialize the components + for (U32 i = 0; i < mComponents.size(); i++) + { + mComponents[i]->onComponentAdd(); + } + + if (isMethod("onAdd")) + Con::executef(this, "onAdd"); +} + +void Entity::setDataField(StringTableEntry slotName, const char *array, const char *value) +{ + Parent::setDataField(slotName, array, value); + + onDataSet.trigger(this, slotName, value); +} + +void Entity::onStaticModified(const char* slotName, const char* newValue) +{ + Parent::onStaticModified(slotName, newValue); + + onDataSet.trigger(this, slotName, newValue); +} + +//Updating +void Entity::processTick(const Move* move) +{ + if (!isHidden()) + { + if (mDelta.warpCount < mDelta.warpTicks) + { + mDelta.warpCount++; + + // Set new pos. + mObjToWorld.getColumn(3, &mDelta.pos); + mDelta.pos += mDelta.warpOffset; + mDelta.rot[0] = mDelta.rot[1]; + mDelta.rot[1].interpolate(mDelta.warpRot[0], mDelta.warpRot[1], F32(mDelta.warpCount) / mDelta.warpTicks); + setTransform(mDelta.pos, mDelta.rot[1]); + + // Pos backstepping + mDelta.posVec.x = -mDelta.warpOffset.x; + mDelta.posVec.y = -mDelta.warpOffset.y; + mDelta.posVec.z = -mDelta.warpOffset.z; + } + else + { + if (isMounted()) + { + MatrixF mat; + mMount.object->getMountTransform(mMount.node, mMount.xfm, &mat); + Parent::setTransform(mat); + Parent::setRenderTransform(mat); + } + else + { + if (!move) + { + if (isGhost()) + { + // If we haven't run out of prediction time, + // predict using the last known move. + if (mPredictionCount-- <= 0) + return; + + move = &mDelta.move; + } + else + { + move = &NullMove; + } + } + } + } + + Move prevMove = lastMove; + + if (move != NULL) + lastMove = *move; + else + lastMove = NullMove; + + if (move && isServerObject()) + { + if ((move->y != 0 || prevMove.y != 0) + || (move->x != 0 || prevMove.x != 0) + || (move->z != 0 || prevMove.x != 0)) + { + if (isMethod("moveVectorEvent")) + Con::executef(this, "moveVectorEvent", move->x, move->y, move->z); + } + + if (move->yaw != 0) + { + if (isMethod("moveYawEvent")) + Con::executef(this, "moveYawEvent", move->yaw); + } + + if (move->pitch != 0) + { + if (isMethod("movePitchEvent")) + Con::executef(this, "movePitchEvent", move->pitch); + } + + if (move->roll != 0) + { + if (isMethod("moveRollEvent")) + Con::executef(this, "moveRollEvent", move->roll); + } + + for (U32 i = 0; i < MaxTriggerKeys; i++) + { + if (move->trigger[i] != prevMove.trigger[i]) + { + if (isMethod("moveTriggerEvent")) + Con::executef(this, "moveTriggerEvent", i, move->trigger[i]); + } + } + } + + if (isMethod("processTick")) + Con::executef(this, "processTick"); + } +} + +void Entity::advanceTime(F32 dt) +{ +} + +void Entity::interpolateTick(F32 dt) +{ + if (dt == 0.0f) + { + setRenderTransform(mDelta.pos, mDelta.rot[1]); + } + else + { + QuatF rot; + rot.interpolate(mDelta.rot[1], mDelta.rot[0], dt); + Point3F pos = mDelta.pos + mDelta.posVec * dt; + + setRenderTransform(pos, rot); + } + + mDelta.dt = dt; +} + +//Render +void Entity::prepRenderImage(SceneRenderState *state) +{ +} + +//Networking +U32 Entity::packUpdate(NetConnection *con, U32 mask, BitStream *stream) +{ + U32 retMask = Parent::packUpdate(con, mask, stream); + + if (stream->writeFlag(mask & TransformMask)) + { + //mathWrite( *stream, getScale() ); + //stream->writeAffineTransform(mObjToWorld); + //mathWrite(*stream, getPosition()); + mathWrite(*stream, mPos); + + //mathWrite(*stream, getRotation()); + mathWrite(*stream, getRotation().asEulerF()); + + mDelta.move.pack(stream); + + stream->writeFlag(!(mask & NoWarpMask)); + } + + /*if (stream->writeFlag(mask & MountedMask)) + { + mathWrite(*stream, mMount.xfm.getPosition()); + mathWrite(*stream, mMount.xfm.toEuler()); + }*/ + + if (stream->writeFlag(mask & BoundsMask)) + { + mathWrite(*stream, mObjBox); + } + + //pass our behaviors around + if (mask & ComponentsMask || mask & InitialUpdateMask) + { + stream->writeFlag(true); + //now, we run through a list of our to-be-sent behaviors and begin sending them + //if any fail, we keep our list and re-queue the mask + S32 componentCount = mToLoadComponents.size(); + + //build our 'ready' list + //This requires both the instance and the instances' template to be prepped(if the template hasn't been ghosted, + //then we know we shouldn't be passing the instance's ghosts around yet) + U32 ghostedCompCnt = 0; + for (U32 i = 0; i < componentCount; i++) + { + if (con->getGhostIndex(mToLoadComponents[i]) != -1) + ghostedCompCnt++; + } + + if (ghostedCompCnt != 0) + { + stream->writeFlag(true); + + stream->writeFlag(mStartComponentUpdate); + + //if not all the behaviors have been ghosted, we'll need another pass + if (ghostedCompCnt != componentCount) + retMask |= ComponentsMask; + + //write the currently ghosted behavior count + stream->writeInt(ghostedCompCnt, 16); + + for (U32 i = 0; i < mToLoadComponents.size(); i++) + { + //now fetch them and pass the ghost + S32 ghostIndex = con->getGhostIndex(mToLoadComponents[i]); + if (ghostIndex != -1) + { + stream->writeInt(ghostIndex, NetConnection::GhostIdBitSize); + mToLoadComponents.erase(i); + i--; + + mStartComponentUpdate = false; + } + } + } + else if (componentCount) + { + //on the odd chance we have behaviors to ghost, but NONE of them have been yet, just set the flag now + stream->writeFlag(false); + retMask |= ComponentsMask; + } + else + stream->writeFlag(false); + } + else + stream->writeFlag(false); + + return retMask; +} + +void Entity::unpackUpdate(NetConnection *con, BitStream *stream) +{ + Parent::unpackUpdate(con, stream); + + if (stream->readFlag()) + { + /*Point3F scale; + mathRead( *stream, &scale ); + setScale( scale);*/ + + //MatrixF objToWorld; + //stream->readAffineTransform(&objToWorld); + + Point3F pos; + + mathRead(*stream, &pos); + + RotationF rot; + + EulerF eRot; + mathRead(*stream, &eRot); + + rot = RotationF(eRot); + + mDelta.move.unpack(stream); + + if (stream->readFlag() && isProperlyAdded()) + { + // Determine number of ticks to warp based on the average + // of the client and server velocities. + /*mDelta.warpOffset = pos - mDelta.pos; + + F32 dt = mDelta.warpOffset.len() / (0.5f * TickSec); + + mDelta.warpTicks = (S32)((dt > sMinWarpTicks) ? getMax(mFloor(dt + 0.5f), 1.0f) : 0.0f); + + //F32 as = (speed + mVelocity.len()) * 0.5f * TickSec; + //F32 dt = (as > 0.00001f) ? mDelta.warpOffset.len() / as : sMaxWarpTicks; + //mDelta.warpTicks = (S32)((dt > sMinWarpTicks) ? getMax(mFloor(dt + 0.5f), 1.0f) : 0.0f); + + //mDelta.warpTicks = (S32)((dt > sMinWarpTicks) ? getMax(mFloor(dt + 0.5f), 1.0f) : 0.0f); + + //mDelta.warpTicks = sMaxWarpTicks; + + mDelta.warpTicks = 0; + + if (mDelta.warpTicks) + { + // Setup the warp to start on the next tick. + if (mDelta.warpTicks > sMaxWarpTicks) + mDelta.warpTicks = sMaxWarpTicks; + mDelta.warpOffset /= (F32)mDelta.warpTicks; + + mDelta.rot[0] = rot.asQuatF(); + mDelta.rot[1] = rot.asQuatF(); + + mDelta.rotOffset = rot.asEulerF() - mDelta.rot.asEulerF(); + + // Ignore small rotation differences + if (mFabs(mDelta.rotOffset.x) < 0.001f) + mDelta.rotOffset.x = 0; + + if (mFabs(mDelta.rotOffset.y) < 0.001f) + mDelta.rotOffset.y = 0; + + if (mFabs(mDelta.rotOffset.z) < 0.001f) + mDelta.rotOffset.z = 0; + + mDelta.rotOffset /= (F32)mDelta.warpTicks; + } + else + { + // Going to skip the warp, server and client are real close. + // Adjust the frame interpolation to move smoothly to the + // new position within the current tick. + Point3F cp = mDelta.pos + mDelta.posVec * mDelta.dt; + if (mDelta.dt == 0) + { + mDelta.posVec.set(0.0f, 0.0f, 0.0f); + mDelta.rotVec.set(0.0f, 0.0f, 0.0f); + } + else + { + F32 dti = 1.0f / mDelta.dt; + mDelta.posVec = (cp - pos) * dti; + mDelta.rotVec.z = mRot.z - rot.z; + + mDelta.rotVec.z *= dti; + } + + mDelta.pos = pos; + mDelta.rot = rot; + + setTransform(pos, rot); + }*/ + + Point3F cp = mDelta.pos + mDelta.posVec * mDelta.dt; + mDelta.warpOffset = pos - cp; + + // Calc the distance covered in one tick as the average of + // the old speed and the new speed from the server. + VectorF vel = pos - mDelta.pos; + F32 dt, as = vel.len() * 0.5 * TickSec; + + // Cal how many ticks it will take to cover the warp offset. + // If it's less than what's left in the current tick, we'll just + // warp in the remaining time. + if (!as || (dt = mDelta.warpOffset.len() / as) > sMaxWarpTicks) + dt = mDelta.dt + sMaxWarpTicks; + else + dt = (dt <= mDelta.dt) ? mDelta.dt : mCeil(dt - mDelta.dt) + mDelta.dt; + + // Adjust current frame interpolation + if (mDelta.dt) + { + mDelta.pos = cp + (mDelta.warpOffset * (mDelta.dt / dt)); + mDelta.posVec = (cp - mDelta.pos) / mDelta.dt; + QuatF cr; + cr.interpolate(mDelta.rot[1], mDelta.rot[0], mDelta.dt); + mDelta.rot[1].interpolate(cr, pos, mDelta.dt / dt); + mDelta.rot[0].extrapolate(mDelta.rot[1], cr, mDelta.dt); + } + + // Calculated multi-tick warp + mDelta.warpCount = 0; + mDelta.warpTicks = (S32)(mFloor(dt)); + if (mDelta.warpTicks) + { + mDelta.warpOffset = pos - mDelta.pos; + mDelta.warpOffset /= mDelta.warpTicks; + mDelta.warpRot[0] = mDelta.rot[1]; + mDelta.warpRot[1] = rot.asQuatF(); + } + } + else + { + // Set the entity to the server position + mDelta.dt = 0; + mDelta.pos = pos; + mDelta.posVec.set(0, 0, 0); + mDelta.rot[1] = mDelta.rot[0] = rot.asQuatF(); + mDelta.warpCount = mDelta.warpTicks = 0; + setTransform(pos, rot); + } + } + + /*if (stream->readFlag()) + { + Point3F mountOffset; + EulerF mountRot; + mathRead(*stream, &mountOffset); + mathRead(*stream, &mountRot); + + RotationF rot = RotationF(mountRot); + mountRot = rot.asEulerF(RotationF::Degrees); + + setMountOffset(mountOffset); + setMountRotation(mountRot); + }*/ + + if (stream->readFlag()) + { + mathRead(*stream, &mObjBox); + resetWorldBox(); + } + + if (stream->readFlag()) + { + //are we passing any behaviors currently? + if (stream->readFlag()) + { + //if we've just started the update, clear our behaviors + if (stream->readFlag()) + clearComponents(false); + + S32 componentCount = stream->readInt(16); + + for (U32 i = 0; i < componentCount; i++) + { + S32 gIndex = stream->readInt(NetConnection::GhostIdBitSize); + addComponent(dynamic_cast(con->resolveGhost(gIndex))); + } + } + } +} + +//Manipulation +void Entity::setTransform(const MatrixF &mat) +{ + //setMaskBits(TransformMask); + setMaskBits(TransformMask | NoWarpMask); + + if (isMounted()) + { + // Use transform from mounted object + Point3F newPos = mat.getPosition(); + Point3F parentPos = mMount.object->getTransform().getPosition(); + + Point3F newOffset = newPos - parentPos; + + if (!newOffset.isZero()) + { + //setMountOffset(newOffset); + mPos = newOffset; + } + + Point3F matEul = mat.toEuler(); + + //mRot = Point3F(mRadToDeg(matEul.x), mRadToDeg(matEul.y), mRadToDeg(matEul.z)); + + if (matEul != Point3F(0, 0, 0)) + { + Point3F mountEul = mMount.object->getTransform().toEuler(); + Point3F diff = matEul - mountEul; + + //setMountRotation(Point3F(mRadToDeg(diff.x), mRadToDeg(diff.y), mRadToDeg(diff.z))); + mRot = diff; + } + else + { + //setMountRotation(Point3F(0, 0, 0)); + mRot = Point3F(0, 0, 0); + } + + RotationF addRot = mRot + RotationF(mMount.object->getTransform()); + MatrixF transf = addRot.asMatrixF(); + transf.setPosition(mPos + mMount.object->getPosition()); + + Parent::setTransform(transf); + } + else + { + //Are we part of a prefab? + /*Prefab* p = Prefab::getPrefabByChild(this); + if (p) + { + //just let our prefab know we moved + p->childTransformUpdated(this, mat); + }*/ + //else + { + //mRot.set(mat); + //Parent::setTransform(mat); + + RotationF rot = RotationF(mat); + + EulerF tempRot = rot.asEulerF(RotationF::Degrees); + + Point3F pos; + + mat.getColumn(3,&pos); + + setTransform(pos, rot); + } + } +} + +void Entity::setTransform(Point3F position, RotationF rotation) +{ + if (isMounted()) + { + mPos = position; + mRot = rotation; + + RotationF addRot = mRot + RotationF(mMount.object->getTransform()); + MatrixF transf = addRot.asMatrixF(); + transf.setPosition(mPos + mMount.object->getPosition()); + + Parent::setTransform(transf); + + setMaskBits(TransformMask); + } + else + { + /*MatrixF newMat, imat, xmat, ymat, zmat; + Point3F radRot = Point3F(mDegToRad(rotation.x), mDegToRad(rotation.y), mDegToRad(rotation.z)); + xmat.set(EulerF(radRot.x, 0, 0)); + ymat.set(EulerF(0.0f, radRot.y, 0.0f)); + zmat.set(EulerF(0, 0, radRot.z)); + imat.mul(zmat, xmat); + newMat.mul(imat, ymat);*/ + + MatrixF newMat = rotation.asMatrixF(); + + newMat.setColumn(3, position); + + mPos = position; + mRot = rotation; + + setMaskBits(TransformMask); + //if (isServerObject()) + // setMaskBits(TransformMask); + + //setTransform(temp); + + // This test is a bit expensive so turn it off in release. +#ifdef TORQUE_DEBUG + //AssertFatal( mat.isAffine(), "SceneObject::setTransform() - Bad transform (non affine)!" ); +#endif + + //PROFILE_SCOPE(Entity_setTransform); + + // Update the transforms. + + Parent::setTransform(newMat); + + onTransformSet.trigger(&newMat); + + /*mObjToWorld = mWorldToObj = newMat; + mWorldToObj.affineInverse(); + // Update the world-space AABB. + resetWorldBox(); + // If we're in a SceneManager, sync our scene state. + if (mSceneManager != NULL) + mSceneManager->notifyObjectDirty(this); + setRenderTransform(newMat);*/ + } +} + +void Entity::setRenderTransform(const MatrixF &mat) +{ + Parent::setRenderTransform(mat); +} + +void Entity::setRenderTransform(Point3F position, RotationF rotation) +{ + if (isMounted()) + { + mPos = position; + mRot = rotation; + + RotationF addRot = mRot + RotationF(mMount.object->getTransform()); + MatrixF transf = addRot.asMatrixF(); + transf.setPosition(mPos + mMount.object->getPosition()); + + Parent::setRenderTransform(transf); + } + else + { + MatrixF newMat = rotation.asMatrixF(); + + newMat.setColumn(3, position); + + mPos = position; + mRot = rotation; + + Parent::setRenderTransform(newMat); + + onTransformSet.trigger(&newMat); + } +} + +MatrixF Entity::getTransform() +{ + if (isMounted()) + { + MatrixF mat; + + //Use transform from mount + mMount.object->getMountTransform(mMount.node, mMount.xfm, &mat); + + Point3F transPos = mat.getPosition() + mPos; + + mat.mul(mRot.asMatrixF()); + + mat.setPosition(transPos); + + return mat; + } + else + { + return Parent::getTransform(); + } +} + +void Entity::setMountOffset(Point3F posOffset) +{ + if (isMounted()) + { + mMount.xfm.setColumn(3, posOffset); + //mPos = posOffset; + setMaskBits(MountedMask); + } +} + +void Entity::setMountRotation(EulerF rotOffset) +{ + if (isMounted()) + { + MatrixF temp, imat, xmat, ymat, zmat; + + Point3F radRot = Point3F(mDegToRad(rotOffset.x), mDegToRad(rotOffset.y), mDegToRad(rotOffset.z)); + xmat.set(EulerF(radRot.x, 0, 0)); + ymat.set(EulerF(0.0f, radRot.y, 0.0f)); + zmat.set(EulerF(0, 0, radRot.z)); + + imat.mul(zmat, xmat); + temp.mul(imat, ymat); + + temp.setColumn(3, mMount.xfm.getPosition()); + + mMount.xfm = temp; + //mRot = RotationF(temp); + setMaskBits(MountedMask); + } +} +// +void Entity::getCameraTransform(F32* pos, MatrixF* mat) +{ + Vector updaters = getComponents(); + for (Vector::iterator it = updaters.begin(); it != updaters.end(); it++) { + if ((*it)->getCameraTransform(pos, mat)) { + return; + } + } +} + +void Entity::getMountTransform(S32 index, const MatrixF &xfm, MatrixF *outMat) +{ + RenderComponentInterface* renderInterface = getComponent(); + + if (renderInterface) + { + renderInterface->getShapeInstance()->animate(); + S32 nodeCount = renderInterface->getShapeInstance()->getShape()->nodes.size(); + + if (index >= 0 && index < nodeCount) + { + MatrixF mountTransform = renderInterface->getShapeInstance()->mNodeTransforms[index]; + mountTransform.mul(xfm); + const Point3F& scale = getScale(); + + // The position of the mount point needs to be scaled. + Point3F position = mountTransform.getPosition(); + position.convolve(scale); + mountTransform.setPosition(position); + + // Also we would like the object to be scaled to the model. + outMat->mul(mObjToWorld, mountTransform); + return; + } + } + + // Then let SceneObject handle it. + Parent::getMountTransform(index, xfm, outMat); +} + +void Entity::getRenderMountTransform(F32 delta, S32 index, const MatrixF &xfm, MatrixF *outMat) +{ + RenderComponentInterface* renderInterface = getComponent(); + + if (renderInterface && renderInterface->getShapeInstance()) + { + renderInterface->getShapeInstance()->animate(); + S32 nodeCount = renderInterface->getShapeInstance()->getShape()->nodes.size(); + + if (index >= 0 && index < nodeCount) + { + MatrixF mountTransform = renderInterface->getShapeInstance()->mNodeTransforms[index]; + mountTransform.mul(xfm); + const Point3F& scale = getScale(); + + // The position of the mount point needs to be scaled. + Point3F position = mountTransform.getPosition(); + position.convolve(scale); + mountTransform.setPosition(position); + + // Also we would like the object to be scaled to the model. + outMat->mul(getRenderTransform(), mountTransform); + return; + } + } + + // Then let SceneObject handle it. + Parent::getMountTransform(index, xfm, outMat); +} + +void Entity::setForwardVector(VectorF newForward, VectorF upVector) +{ + MatrixF mat = getTransform(); + + VectorF up(0.0f, 0.0f, 1.0f); + VectorF axisX; + VectorF axisY = newForward; + VectorF axisZ; + + if (upVector != VectorF::Zero) + up = upVector; + + // Validate and normalize input: + F32 lenSq; + lenSq = axisY.lenSquared(); + if (lenSq < 0.000001f) + { + axisY.set(0.0f, 1.0f, 0.0f); + Con::errorf("Entity::setForwardVector() - degenerate forward vector"); + } + else + { + axisY /= mSqrt(lenSq); + } + + + lenSq = up.lenSquared(); + if (lenSq < 0.000001f) + { + up.set(0.0f, 0.0f, 1.0f); + Con::errorf("SceneObject::setForwardVector() - degenerate up vector - too small"); + } + else + { + up /= mSqrt(lenSq); + } + + if (fabsf(mDot(up, axisY)) > 0.9999f) + { + Con::errorf("SceneObject::setForwardVector() - degenerate up vector - same as forward"); + // i haven't really tested this, but i think it generates something which should be not parallel to the previous vector: + F32 tmp = up.x; + up.x = -up.y; + up.y = up.z; + up.z = tmp; + } + + // construct the remaining axes: + mCross(axisY, up, &axisX); + mCross(axisX, axisY, &axisZ); + + mat.setColumn(0, axisX); + mat.setColumn(1, axisY); + mat.setColumn(2, axisZ); + + setTransform(mat); +} +// +//These basically just redirect to any collision behaviors we have +bool Entity::castRay(const Point3F &start, const Point3F &end, RayInfo* info) +{ + Vector updaters = getComponents(); + for (Vector::iterator it = updaters.begin(); it != updaters.end(); it++) + { + if ((*it)->castRay(start, end, info)) + { + return true; + } + } + return false; +} + +bool Entity::castRayRendered(const Point3F &start, const Point3F &end, RayInfo *info) +{ + Vector updaters = getComponents(); + for (Vector::iterator it = updaters.begin(); it != updaters.end(); it++) + { + if ((*it)->castRayRendered(start, end, info)) + { + return true; + } + } + return false; +} + +bool Entity::buildPolyList(PolyListContext context, AbstractPolyList* polyList, const Box3F &box, const SphereF &sphere) +{ + Vector updaters = getComponents(); + for (Vector::iterator it = updaters.begin(); it != updaters.end(); it++) + { + return (*it)->buildPolyList(context, polyList, box, sphere); + } + + return false; +} + +void Entity::buildConvex(const Box3F& box, Convex* convex) +{ + Vector updaters = getComponents(); + for (Vector::iterator it = updaters.begin(); it != updaters.end(); it++) + { + (*it)->buildConvex(box, convex); + } +} + +// +// Mounting and heirarchy manipulation +void Entity::mountObject(SceneObject* objB, MatrixF txfm) +{ + Parent::mountObject(objB, -1, txfm); + Parent::addObject(objB); +} + +void Entity::mountObject(SceneObject *obj, S32 node, const MatrixF &xfm) +{ + Parent::mountObject(obj, node, xfm); +} + +void Entity::onMount(SceneObject *obj, S32 node) +{ + deleteNotify(obj); + + // Are we mounting to a GameBase object? + Entity *entityObj = dynamic_cast(obj); + + if (entityObj && entityObj->getControlObject() != this) + processAfter(entityObj); + + if (!isGhost()) { + setMaskBits(MountedMask); + + //TODO implement this callback + //onMount_callback( this, obj, node ); + } +} + +void Entity::onUnmount(SceneObject *obj, S32 node) +{ + clearNotify(obj); + + Entity *entityObj = dynamic_cast(obj); + + if (entityObj && entityObj->getControlObject() != this) + clearProcessAfter(); + + if (!isGhost()) { + setMaskBits(MountedMask); + + //TODO implement this callback + //onUnmount_callback( this, obj, node ); + } +} + +//Heirarchy stuff +void Entity::addObject(SimObject* object) +{ + Component* component = dynamic_cast(object); + if (component) + { + addComponent(component); + return; + } + + Entity* e = dynamic_cast(object); + if (e) + { + MatrixF offset; + + //offset.mul(getWorldTransform(), e->getWorldTransform()); + + //check if we're mounting to a node on a shape we have + String node = e->getDataField("mountNode", NULL); + if (!node.isEmpty()) + { + RenderComponentInterface *renderInterface = getComponent(); + if (renderInterface) + { + TSShape* shape = renderInterface->getShape(); + S32 nodeIdx = shape->findNode(node); + + mountObject(e, nodeIdx, MatrixF::Identity); + } + else + { + mountObject(e, MatrixF::Identity); + } + } + else + { + /*Point3F posOffset = mPos - e->getPosition(); + mPos = posOffset; + + RotationF rotOffset = mRot - e->getRotation(); + mRot = rotOffset; + setMaskBits(TransformMask); + mountObject(e, MatrixF::Identity);*/ + + mountObject(e, MatrixF::Identity); + } + + //e->setMountOffset(e->getPosition() - getPosition()); + + //Point3F diff = getWorldTransform().toEuler() - e->getWorldTransform().toEuler(); + + //e->setMountRotation(Point3F(mRadToDeg(diff.x),mRadToDeg(diff.y),mRadToDeg(diff.z))); + + //mountObject(e, offset); + } + else + { + SceneObject* so = dynamic_cast(object); + if (so) + { + //get the difference and build it as our offset! + Point3F posOffset = so->getPosition() - mPos; + RotationF rotOffset = RotationF(so->getTransform()) - mRot; + + MatrixF offset = rotOffset.asMatrixF(); + offset.setPosition(posOffset); + + mountObject(so, offset); + return; + } + } + + Parent::addObject(object); +} + +void Entity::removeObject(SimObject* object) +{ + Entity* e = dynamic_cast(object); + if (e) + { + mPos = mPos + e->getPosition(); + mRot = mRot + e->getRotation(); + unmountObject(e); + setMaskBits(TransformMask); + } + else + { + SceneObject* so = dynamic_cast(object); + if (so) + unmountObject(so); + } + + Parent::removeObject(object); +} + +bool Entity::addComponent(Component *comp) +{ + if (comp == NULL || !comp->isProperlyAdded()) + return false; + + //double-check were not re-adding anything + mComponents.push_back(comp); + + // Register the component with this owner. + comp->setOwner(this); + + //if we've already been added and this is being added after the fact(at runtime), + //then just go ahead and call it's onComponentAdd so it can get to work + if (mInitialized) + comp->onComponentAdd(); + + onComponentAdded.trigger(comp); + + return true; +} + +SimObject* Entity::findObjectByInternalName(StringTableEntry internalName, bool searchChildren) +{ + for (U32 i = 0; i < mComponents.size(); i++) + { + if (mComponents[i]->getInternalName() == internalName) + { + return mComponents[i]; + } + } + + return Parent::findObjectByInternalName(internalName, searchChildren); +} + +////////////////////////////////////////////////////////////////////////// + +bool Entity::removeComponent(Component *comp, bool deleteComponent) +{ + if (comp == NULL) + return false; + + if(mComponents.remove(comp)) + { + AssertFatal(comp->isProperlyAdded(), "Don't know how but a component is not registered w/ the sim"); + + //setComponentsDirty(); + + onComponentRemoved.trigger(comp); + + comp->setOwner(NULL); + + comp->onComponentRemove(); //in case the behavior needs to do cleanup on the owner + + if (deleteComponent) + comp->safeDeleteObject(); + + return true; + } + + return false; +} + +////////////////////////////////////////////////////////////////////////// +//NOTE: +//The actor class calls this and flags the deletion of the behaviors to false so that behaviors that should no longer be attached during +//a network update will indeed be removed from the object. The reason it doesn't delete them is because when clearing the local behavior +//list, it would delete them, purging the ghost, and causing a crash when the unpack update tried to fetch any existing behaviors' ghosts +//to re-add them. Need to implement a clean clear function that will clear the local list, and only delete unused behaviors during an update. +void Entity::clearComponents(bool deleteComponents) +{ + bool srv = isServerObject(); + if (!deleteComponents) + { + while (mComponents.size() > 0) + { + removeComponent(mComponents.first(), deleteComponents); + } + } + else + { + while (mComponents.size() > 0) + { + Component* comp = mComponents.first(); + + if (comp) + { + comp->onComponentRemove(); //in case the behavior needs to do cleanup on the owner + + bool removed = mComponents.remove(comp); + + //we only need to delete them on the server side. they'll be cleaned up on the client side + //via the ghosting system for us + if (isServerObject()) + comp->deleteObject(); + } + } + } +} + +////////////////////////////////////////////////////////////////////////// +Component *Entity::getComponent(const U32 index) const +{ + if (index < mComponents.size()) + return mComponents[index]; + + return NULL; +} + +Component *Entity::getComponent(String componentType) +{ + for (U32 i = 0; i < mComponents.size(); i++) + { + Component* comp = mComponents[i]; + + /*String namespaceName = comp->getNamespace()->mName; + //check our namespace first + if (namespaceName == componentType) + { + return comp; + } + else + {*/ + //lets scan up, just to be sure + Namespace *NS = comp->getNamespace(); + + //we shouldn't ever go past Component into net object, as we're no longer dealing with component classes + while (dStrcmp(NS->getName(), "NetObject")) + { + String namespaceName = NS->getName(); + + if (namespaceName == componentType) + { + return comp; + } + else + { + NS = NS->getParent(); + } + } + //} + } + + return NULL; +} + +void Entity::onInspect() +{ + Vector updaters = getComponents(); + for (Vector::iterator it = updaters.begin(); it != updaters.end(); it++) { + (*it)->onInspect(); + } + + GuiTreeViewCtrl *editorTree = dynamic_cast(Sim::findObject("EditorTree")); + if (!editorTree) + return; + + GuiTreeViewCtrl::Item *newItem, *parentItem; + + parentItem = editorTree->getItem(editorTree->findItemByObjectId(getId())); + + S32 componentID = editorTree->insertItem(parentItem->getID(), "Components"); + + newItem = editorTree->getItem(componentID); + newItem->mState.set(GuiTreeViewCtrl::Item::VirtualParent); + newItem->mState.set(GuiTreeViewCtrl::Item::DenyDrag); + //newItem->mState.set(GuiTreeViewCtrl::Item::InspectorData); + newItem->mState.set(GuiTreeViewCtrl::Item::ForceItemName); + //newItem->mInspectorInfo.mObject = this; + + for (U32 i = 0; i < mComponents.size(); i++) + { + String compName = mComponents[i]->getFriendlyName(); + S32 compID = editorTree->insertItem(componentID, compName); + newItem = editorTree->getItem(compID); + newItem->mInspectorInfo.mObject = mComponents[i]; + newItem->mState.set(GuiTreeViewCtrl::Item::ForceItemName); + newItem->mState.set(GuiTreeViewCtrl::Item::DenyDrag); + newItem->mState.set(GuiTreeViewCtrl::Item::InspectorData); + } + + editorTree->buildVisibleTree(true); +} + +void Entity::onEndInspect() +{ + Vector updaters = getComponents(); + for (Vector::iterator it = updaters.begin(); it != updaters.end(); it++) { + (*it)->onEndInspect(); + } + + GuiTreeViewCtrl *editorTree = dynamic_cast(Sim::findObject("EditorTree")); + if (!editorTree) + return; + + S32 componentItemIdx = editorTree->findItemByName("Components"); + + editorTree->removeItem(componentItemIdx, false); +} + +static void writeTabs(Stream &stream, U32 count) +{ + char tab[] = " "; + while (count--) + stream.write(3, (void*)tab); +} + +void Entity::write(Stream &stream, U32 tabStop, U32 flags) +{ + // Do *not* call parent on this + + /*VectorPtr &componentList = lockComponentList(); + // export selected only? + if( ( flags & SelectedOnly ) && !isSelected() ) + { + for( BehaviorObjectIterator i = componentList.begin(); i != componentList.end(); i++ ) + (*i)->write(stream, tabStop, flags); + + goto write_end; + }*/ + + //catch if we have any written behavior fields already in the file, and clear them. We don't need to double-up + //the entries for no reason. + /*if(getFieldDictionary()) + { + //get our dynamic field count, then parse through them to see if they're a behavior or not + + //reset it + SimFieldDictionary* fieldDictionary = getFieldDictionary(); + SimFieldDictionaryIterator itr(fieldDictionary); + for (S32 i = 0; i < fieldDictionary->getNumFields(); i++) + { + if (!(*itr)) + break; + + SimFieldDictionary::Entry* entry = *itr; + if(strstr(entry->slotName, "_behavior")) + { + entry->slotName = ""; + entry->value = ""; + } + + ++itr; + } + }*/ + //all existing written behavior fields should be cleared. now write the object block + + writeTabs(stream, tabStop); + + char buffer[1024]; + dSprintf(buffer, sizeof(buffer), "new %s(%s) {\r\n", getClassName(), getName() ? getName() : ""); + stream.write(dStrlen(buffer), buffer); + writeFields(stream, tabStop + 1); + + stream.write(1, "\n"); + ////first, write out our behavior objects + + // NOW we write the behavior fields proper + if (mComponents.size() > 0) + { + // Pack out the behaviors into fields + U32 i = 0; + for (U32 i = 0; i < mComponents.size(); i++) + { + writeTabs(stream, tabStop + 1); + char buffer[1024]; + dSprintf(buffer, sizeof(buffer), "new %s() {\r\n", mComponents[i]->getClassName()); + stream.write(dStrlen(buffer), buffer); + //bi->writeFields( stream, tabStop + 2 ); + + mComponents[i]->packToStream(stream, tabStop + 2, i - 1, flags); + + writeTabs(stream, tabStop + 1); + stream.write(4, "};\r\n"); + } + } + + // + //if (size() > 0) + // stream.write(2, "\r\n"); + + for (U32 i = 0; i < size(); i++) + { + SimObject* child = (*this)[i]; + if (child->getCanSave()) + child->write(stream, tabStop + 1, flags); + } + + //stream.write(2, "\r\n"); + + writeTabs(stream, tabStop); + stream.write(4, "};\r\n"); + + //write_end: + //unlockComponentList(); +} + +SimObject* Entity::getTamlChild(const U32 childIndex) const +{ + // Sanity! + AssertFatal(childIndex < getTamlChildCount(), "SimSet::getTamlChild() - Child index is out of range."); + + // For when the assert is not used. + if (childIndex >= getTamlChildCount()) + return NULL; + + //we always order components first, child objects second + if (childIndex >= getComponentCount()) + return at(childIndex - getComponentCount()); + else + return getComponent(childIndex); +} +// +void Entity::onCameraScopeQuery(NetConnection* connection, CameraScopeQuery* query) +{ + // Object itself is in scope. + Parent::onCameraScopeQuery(connection, query); + + if (CameraInterface* cI = getComponent()) + { + cI->onCameraScopeQuery(connection, query); + } +} +// +void Entity::setObjectBox(Box3F objBox) +{ + mObjBox = objBox; + resetWorldBox(); + + if (isServerObject()) + setMaskBits(BoundsMask); +} + +void Entity::updateContainer() +{ + PROFILE_SCOPE(Entity_updateContainer); + + // Update container drag and buoyancy properties + containerInfo.box = getWorldBox(); + //containerInfo.mass = mMass; + + getContainer()->findObjects(containerInfo.box, WaterObjectType | PhysicalZoneObjectType, findRouter, &containerInfo); + + //mWaterCoverage = info.waterCoverage; + //mLiquidType = info.liquidType; + //mLiquidHeight = info.waterHeight; + //setCurrentWaterObject( info.waterObject ); + + // This value might be useful as a datablock value, + // This is what allows the player to stand in shallow water (below this coverage) + // without jiggling from buoyancy + /*if (info.waterCoverage >= 0.25f) + { + // water viscosity is used as drag for in water. + // ShapeBaseData drag is used for drag outside of water. + // Combine these two components to calculate this ShapeBase object's + // current drag. + mDrag = (info.waterCoverage * info.waterViscosity) + + (1.0f - info.waterCoverage) * mDrag; + //mBuoyancy = (info.waterDensity / mDataBlock->density) * info.waterCoverage; + } + + //mAppliedForce = info.appliedForce; + mGravityMod = info.gravityScale;*/ +} +// + +void Entity::setComponentsDirty() +{ + if (mToLoadComponents.empty()) + mStartComponentUpdate = true; + + //we need to build a list of behaviors that need to be pushed across the network + for (U32 i = 0; i < mComponents.size(); i++) + { + // We can do this because both are in the string table + Component *comp = mComponents[i]; + + if (comp->isNetworked()) + { + bool unique = true; + for (U32 i = 0; i < mToLoadComponents.size(); i++) + { + if (mToLoadComponents[i]->getId() == comp->getId()) + { + unique = false; + break; + } + } + if (unique) + mToLoadComponents.push_back(comp); + } + } + + setMaskBits(ComponentsMask); +} + +void Entity::setComponentDirty(Component *comp, bool forceUpdate) +{ + bool found = false; + for (U32 i = 0; i < mComponents.size(); i++) + { + if (mComponents[i]->getId() == comp->getId()) + { + mComponents[i]->setOwner(this); + return; + } + } + + if (!found) + return; + + //if(mToLoadComponents.empty()) + // mStartComponentUpdate = true; + + /*if (comp->isNetworked() || forceUpdate) + { + bool unique = true; + for (U32 i = 0; i < mToLoadComponents.size(); i++) + { + if (mToLoadComponents[i]->getId() == comp->getId()) + { + unique = false; + break; + } + } + if (unique) + mToLoadComponents.push_back(comp); + } + + setMaskBits(ComponentsMask);*/ + +} + +DefineEngineMethod(Entity, mountObject, bool, + (SceneObject* objB, TransformF txfm), (MatrixF::Identity), + "@brief Mount objB to this object at the desired slot with optional transform.\n\n" + + "@param objB Object to mount onto us\n" + "@param slot Mount slot ID\n" + "@param txfm (optional) mount offset transform\n" + "@return true if successful, false if failed (objB is not valid)") +{ + if (objB) + { + //BUG: Unsure how it broke, but atm the default transform passed in here is rotated 180 degrees. This doesn't happen + //for the SceneObject mountobject method. Hackish, but for now, just default to a clean MatrixF::Identity + object->mountObject(objB, /*MatrixF::Identity*/txfm.getMatrix()); + return true; + } + return false; +} + +DefineEngineMethod(Entity, setMountOffset, void, + (Point3F posOffset), (Point3F(0, 0, 0)), + "@brief Mount objB to this object at the desired slot with optional transform.\n\n" + + "@param objB Object to mount onto us\n" + "@param slot Mount slot ID\n" + "@param txfm (optional) mount offset transform\n" + "@return true if successful, false if failed (objB is not valid)") +{ + object->setMountOffset(posOffset); +} + +DefineEngineMethod(Entity, setMountRotation, void, + (EulerF rotOffset), (EulerF(0, 0, 0)), + "@brief Mount objB to this object at the desired slot with optional transform.\n\n" + + "@param objB Object to mount onto us\n" + "@param slot Mount slot ID\n" + "@param txfm (optional) mount offset transform\n" + "@return true if successful, false if failed (objB is not valid)") +{ + object->setMountRotation(rotOffset); +} + +DefineEngineMethod(Entity, getMountTransform, TransformF, (), , + "@brief Mount objB to this object at the desired slot with optional transform.\n\n" + + "@param objB Object to mount onto us\n" + "@param slot Mount slot ID\n" + "@param txfm (optional) mount offset transform\n" + "@return true if successful, false if failed (objB is not valid)") +{ + MatrixF mat; + object->getMountTransform(0, MatrixF::Identity, &mat); + return mat; +} + +DefineEngineMethod(Entity, setBox, void, + (Point3F box), (Point3F(1, 1, 1)), + "@brief Mount objB to this object at the desired slot with optional transform.\n\n" + + "@param objB Object to mount onto us\n" + "@param slot Mount slot ID\n" + "@param txfm (optional) mount offset transform\n" + "@return true if successful, false if failed (objB is not valid)") +{ + object->setObjectBox(Box3F(-box, box)); +} + + +/*DefineConsoleMethod(Entity, callOnComponents, void, (const char* functionName), , + "Get the number of static fields on the object.\n" + "@return The number of static fields defined on the object.") +{ + object->callOnComponents(functionName); +} + +ConsoleMethod(Entity, callMethod, void, 3, 64, "(methodName, argi) Calls script defined method\n" + "@param methodName The method's name as a string\n" + "@param argi Any arguments to pass to the method\n" + "@return No return value" + "@note %obj.callMethod( %methodName, %arg1, %arg2, ... );\n") + +{ + object->callMethodArgList(argc - 1, argv + 2); +} + +ConsoleMethod(Entity, addComponents, void, 2, 2, "() - Add all fielded behaviors\n" + "@return No return value") +{ + object->addComponents(); +}*/ + +ConsoleMethod(Entity, addComponent, bool, 3, 3, "(ComponentInstance bi) - Add a behavior to the object\n" + "@param bi The behavior instance to add" + "@return (bool success) Whether or not the behavior was successfully added") +{ + Component *comp = dynamic_cast(Sim::findObject(argv[2])); + + if (comp != NULL) + { + bool success = object->addComponent(comp); + + if (success) + { + //Placed here so we can differentiate against adding a new behavior during runtime, or when we load all + //fielded behaviors on mission load. This way, we can ensure that we only call the callback + //once everything is loaded. This avoids any problems with looking for behaviors that haven't been added yet, etc. + if (comp->isMethod("onBehaviorAdd")) + Con::executef(comp, "onBehaviorAdd"); + + return true; + } + } + + return false; +} + +ConsoleMethod(Entity, removeComponent, bool, 3, 4, "(ComponentInstance bi, [bool deleteBehavior = true])\n" + "@param bi The behavior instance to remove\n" + "@param deleteBehavior Whether or not to delete the behavior\n" + "@return (bool success) Whether the behavior was successfully removed") +{ + bool deleteComponent = true; + if (argc > 3) + deleteComponent = dAtob(argv[3]); + + return object->removeComponent(dynamic_cast(Sim::findObject(argv[2])), deleteComponent); +} + +ConsoleMethod(Entity, clearComponents, void, 2, 2, "() - Clear all behavior instances\n" + "@return No return value") +{ + object->clearComponents(); +} + +ConsoleMethod(Entity, getComponentByIndex, S32, 3, 3, "(int index) - Gets a particular behavior\n" + "@param index The index of the behavior to get\n" + "@return (ComponentInstance bi) The behavior instance you requested") +{ + Component *comp = object->getComponent(dAtoi(argv[2])); + + return (comp != NULL) ? comp->getId() : 0; +} + +DefineConsoleMethod(Entity, getComponent, S32, (String componentName), (""), + "Get the number of static fields on the object.\n" + "@return The number of static fields defined on the object.") +{ + Component *comp = object->getComponent(componentName); + + return (comp != NULL) ? comp->getId() : 0; + return 0; +} + +/*ConsoleMethod(Entity, getBehaviorByType, S32, 3, 3, "(string BehaviorTemplateName) - gets a behavior\n" + "@param BehaviorTemplateName The name of the template of the behavior instance you want\n" + "@return (ComponentInstance bi) The behavior instance you requested") +{ + ComponentInstance *bInstance = object->getComponentByType(StringTable->insert(argv[2])); + + return (bInstance != NULL) ? bInstance->getId() : 0; +}*/ + +/*ConsoleMethod(Entity, reOrder, bool, 3, 3, "(ComponentInstance inst, [int desiredIndex = 0])\n" + "@param inst The behavior instance you want to reorder\n" + "@param desiredIndex The index you want the behavior instance to be reordered to\n" + "@return (bool success) Whether or not the behavior instance was successfully reordered") +{ + Component *inst = dynamic_cast(Sim::findObject(argv[1])); + + if (inst == NULL) + return false; + + U32 idx = 0; + if (argc > 2) + idx = dAtoi(argv[2]); + + return object->reOrder(inst, idx); +}*/ + +ConsoleMethod(Entity, getComponentCount, S32, 2, 2, "() - Get the count of behaviors on an object\n" + "@return (int count) The number of behaviors on an object") +{ + return object->getComponentCount(); +} + +DefineConsoleMethod(Entity, setComponentDirty, void, (S32 componentID, bool forceUpdate), (0, false), + "Get the number of static fields on the object.\n" + "@return The number of static fields defined on the object.") +{ + /*Component* comp; + if (Sim::findObject(componentID, comp)) + object->setComponentDirty(comp, forceUpdate);*/ +} + +DefineConsoleMethod(Entity, getMoveVector, VectorF, (),, + "Get the number of static fields on the object.\n" + "@return The number of static fields defined on the object.") +{ + if (object->getControllingClient() != NULL) + { + //fetch our last move + if (object->lastMove.x != 0 || object->lastMove.y != 0 || object->lastMove.z != 0) + return VectorF(object->lastMove.x, object->lastMove.y, object->lastMove.z); + } + + return VectorF::Zero; +} + +DefineConsoleMethod(Entity, getMoveRotation, VectorF, (), , + "Get the number of static fields on the object.\n" + "@return The number of static fields defined on the object.") +{ + if(object->getControllingClient() != NULL) + { + //fetch our last move + if (object->lastMove.pitch != 0 || object->lastMove.roll != 0 || object->lastMove.yaw != 0) + return VectorF(object->lastMove.pitch, object->lastMove.roll, object->lastMove.yaw); + } + + return VectorF::Zero; +} + +DefineConsoleMethod(Entity, getMoveTrigger, bool, (S32 triggerNum), (0), + "Get the number of static fields on the object.\n" + "@return The number of static fields defined on the object.") +{ + if (object->getControllingClient() != NULL && triggerNum < MaxTriggerKeys) + { + return object->lastMove.trigger[triggerNum]; + } + + return false; +} + +DefineConsoleMethod(Entity, setForwardVector, void, (VectorF newForward), (VectorF(0,0,0)), + "Get the number of static fields on the object.\n" + "@return The number of static fields defined on the object.") +{ + object->setForwardVector(newForward); +} + +DefineConsoleMethod(Entity, lookAt, void, (Point3F lookPosition),, + "Get the number of static fields on the object.\n" + "@return The number of static fields defined on the object.") +{ + //object->setForwardVector(newForward); +} + +DefineConsoleMethod(Entity, rotateTo, void, (Point3F lookPosition, F32 degreePerSecond), (1.0), + "Get the number of static fields on the object.\n" + "@return The number of static fields defined on the object.") +{ + //object->setForwardVector(newForward); +} \ No newline at end of file diff --git a/Engine/source/T3D/Entity.h b/Engine/source/T3D/Entity.h new file mode 100644 index 000000000..e2a35bf9f --- /dev/null +++ b/Engine/source/T3D/Entity.h @@ -0,0 +1,287 @@ +//----------------------------------------------------------------------------- +// Copyright (c) 2012 GarageGames, LLC +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//----------------------------------------------------------------------------- + +#ifndef ENTITY_H +#define ENTITY_H + +#ifndef _GAMEBASE_H_ +#include "T3D/gameBase/gameBase.h" +#endif +#ifndef _MOVEMANAGER_H_ +#include "T3D/gameBase/moveManager.h" +#endif +#ifndef COMPONENT_H +#include "T3D/Components/Component.h" +#endif +#ifndef MROTATION_H +#include "math/mRotation.h" +#endif +#ifndef _CONTAINERQUERY_H_ +#include "T3D/containerQuery.h" +#endif + +//************************************************************************** +// Entity +//************************************************************************** +class Entity : public GameBase +{ + typedef GameBase Parent; + friend class Component; + +private: + Point3F mPos; + RotationF mRot; + + Vector mComponents; + + Vector mToLoadComponents; + + bool mStartComponentUpdate; + + ContainerQueryInfo containerInfo; + + bool mInitialized; + + Signal< void(Component*) > Entity::onComponentAdded; + Signal< void(Component*) > Entity::onComponentRemoved; + + Signal< void(MatrixF*) > Entity::onTransformSet; + +protected: + + virtual void processTick(const Move* move); + virtual void advanceTime(F32 dt); + virtual void interpolateTick(F32 delta); + + void prepRenderImage(SceneRenderState *state); + + virtual bool onAdd(); + virtual void onRemove(); + +public: + struct StateDelta + { + Move move; ///< Last move from server + F32 dt; ///< Last interpolation time + // Interpolation data + Point3F pos; + Point3F posVec; + QuatF rot[2]; + // Warp data + S32 warpTicks; ///< Number of ticks to warp + S32 warpCount; ///< Current pos in warp + Point3F warpOffset; + QuatF warpRot[2]; + }; + + enum MaskBits + { + TransformMask = Parent::NextFreeMask << 0, + BoundsMask = Parent::NextFreeMask << 1, + ComponentsMask = Parent::NextFreeMask << 2, + NoWarpMask = Parent::NextFreeMask << 3, + NextFreeMask = Parent::NextFreeMask << 4 + }; + + StateDelta mDelta; + S32 mPredictionCount; ///< Number of ticks to predict + + Move lastMove; + + // + Entity(); + ~Entity(); + + static void initPersistFields(); + virtual void onPostAdd(); + + virtual void setTransform(const MatrixF &mat); + virtual void setRenderTransform(const MatrixF &mat); + + void setTransform(Point3F position, RotationF rotation); + + void setRenderTransform(Point3F position, RotationF rotation); + + virtual MatrixF getTransform(); + virtual Point3F getPosition() const { return mPos; } + + //void setTransform(Point3F position, RotationF rot); + + //void setRotation(RotationF rotation); + + void setRotation(RotationF rotation) { + mRot = rotation; + setMaskBits(TransformMask); + }; + RotationF getRotation() { return mRot; } + + void setMountOffset(Point3F posOffset); + void setMountRotation(EulerF rotOffset); + + //static bool _setEulerRotation( void *object, const char *index, const char *data ); + static bool _setPosition(void *object, const char *index, const char *data); + static const char * _getPosition(void* obj, const char* data); + + static bool _setRotation(void *object, const char *index, const char *data); + static const char * _getRotation(void* obj, const char* data); + + virtual void getMountTransform(S32 index, const MatrixF &xfm, MatrixF *outMat); + virtual void getRenderMountTransform(F32 delta, S32 index, const MatrixF &xfm, MatrixF *outMat); + + void setForwardVector(VectorF newForward, VectorF upVector = VectorF::Zero); + + virtual void mountObject(SceneObject *obj, S32 node, const MatrixF &xfm = MatrixF::Identity); + void mountObject(SceneObject* objB, MatrixF txfm); + void onMount(SceneObject *obj, S32 node); + void onUnmount(SceneObject *obj, S32 node); + + // NetObject + U32 packUpdate(NetConnection *conn, U32 mask, BitStream *stream); + void unpackUpdate(NetConnection *conn, BitStream *stream); + + void setComponentsDirty(); + void setComponentDirty(Component *comp, bool forceUpdate = false); + + //Components + virtual bool deferAddingComponents() const { return true; } + + template + T* getComponent(); + template + Vector getComponents(); + + Component* getComponent(String componentType); + + U32 getComponentCount() const + { + return mComponents.size(); + } + + virtual void setObjectBox(Box3F objBox); + + void resetWorldBox() { Parent::resetWorldBox(); } + void resetObjectBox() { Parent::resetObjectBox(); } + void resetRenderWorldBox() { Parent::resetRenderWorldBox(); } + + //function redirects for collisions + bool castRay(const Point3F &start, const Point3F &end, RayInfo* info); + bool castRayRendered(const Point3F &start, const Point3F &end, RayInfo* info); + bool buildPolyList(PolyListContext context, AbstractPolyList* polyList, const Box3F &box, const SphereF &sphere); + virtual void buildConvex(const Box3F& box, Convex* convex); + + Signal< void(SimObject*, String, String) > onDataSet; + virtual void setDataField(StringTableEntry slotName, const char *array, const char *value); + virtual void onStaticModified(const char* slotName, const char* newValue); + + //void pushEvent(const char* eventName, Vector eventParams); + + void updateContainer(); + + ContainerQueryInfo getContainerInfo() { return containerInfo; } + + //camera stuff + virtual void getCameraTransform(F32* pos, MatrixF* mat); + virtual void onCameraScopeQuery(NetConnection* connection, CameraScopeQuery* query); + + //Heirarchy stuff + virtual void addObject(SimObject* object); + virtual void removeObject(SimObject* object); + + virtual SimObject* findObjectByInternalName(StringTableEntry internalName, bool searchChildren); + + //component stuff + bool addComponent(Component *comp); + bool removeComponent(Component *comp, bool deleteComponent); + void clearComponents(bool deleteComponents = true); + Component* getComponent(const U32 index) const; + + void onInspect(); + void onEndInspect(); + + virtual void write(Stream &stream, U32 tabStop, U32 flags); + + // TamlChildren + virtual U32 getTamlChildCount(void) const + { + U32 componentCount = getComponentCount(); + U32 childSize = (U32)size(); + return componentCount + childSize; + } + + virtual SimObject* getTamlChild(const U32 childIndex) const; + + virtual void addTamlChild(SimObject* pSimObject) + { + // Sanity! + AssertFatal(pSimObject != NULL, "SimSet::addTamlChild() - Cannot add a NULL child object."); + + addObject(pSimObject); + } + + Box3F getObjectBox() { return mObjBox; } + MatrixF getWorldToObj() { return mWorldToObj; } + MatrixF getObjToWorld() { return mObjToWorld; } + + DECLARE_CONOBJECT(Entity); + +}; + +template +T *Entity::getComponent() +{ + U32 componentCount = getComponentCount(); + for (U32 i = 0; i < componentCount; i++) + { + T* t = dynamic_cast(mComponents[i]); + + if (t) + { + return t; + } + } + return NULL; +} + +template +Vector Entity::getComponents() +{ + Vector foundObjects; + + T *curObj; + Component* comp; + + // Loop through our child objects. + for (U32 i = 0; i < mComponents.size(); i++) + { + if (!mComponents[i]->isEnabled()) + continue; + + curObj = dynamic_cast(mComponents[i]); + + // Add this child object if appropriate. + if (curObj) + foundObjects.push_back(curObj); + } + + return foundObjects; +} +#endif //ENTITY_H diff --git a/Engine/source/T3D/objectTypes.h b/Engine/source/T3D/objectTypes.h index e65745e1b..5f3266708 100644 --- a/Engine/source/T3D/objectTypes.h +++ b/Engine/source/T3D/objectTypes.h @@ -147,21 +147,25 @@ enum SceneObjectTypes /// @see PhysicalZone PhysicalZoneObjectType = BIT( 22 ), + EntityObjectType = BIT(23), /// @} }; enum SceneObjectTypeMasks { - STATIC_COLLISION_TYPEMASK = StaticShapeObjectType, + STATIC_COLLISION_TYPEMASK = (StaticShapeObjectType | + EntityObjectType), DAMAGEABLE_TYPEMASK = ( PlayerObjectType | + EntityObjectType | VehicleObjectType ), /// Typemask for objects that should be rendered into shadow passes. /// These should be all objects that are either meant to receive or cast /// shadows or both. SHADOW_TYPEMASK = ( StaticShapeObjectType | - DynamicShapeObjectType ), + DynamicShapeObjectType | + EntityObjectType), /// Typemask for objects that should be subjected to more fine-grained /// culling tests. Anything that is trivial rendering stuff or doesn't @@ -172,6 +176,7 @@ enum SceneObjectTypeMasks CULLING_INCLUDE_TYPEMASK = ( GameBaseObjectType | // Includes most other renderable types; but broader than we ideally want. StaticShapeObjectType | DynamicShapeObjectType | + EntityObjectType | ZoneObjectType ), // This improves the result of zone traversals. /// Mask for objects that should be specifically excluded from zone culling. @@ -185,7 +190,9 @@ enum SceneObjectTypeMasks StaticShapeObjectType | DynamicShapeObjectType | LightObjectType | // Flares. - GameBaseObjectType ), + GameBaseObjectType | + TriggerObjectType | + EntityObjectType), /// Typemask to use for rendering when inside the editor. EDITOR_RENDER_TYPEMASK = U32( -1 ),