mirror of
https://github.com/TorqueGameEngines/Torque3D.git
synced 2026-01-20 04:34:48 +00:00
Adds Component, the various main component classes and their interfaces.
This commit is contained in:
parent
2e339bafba
commit
fa78a2f354
715
Engine/source/T3D/components/Animation/animationComponent.cpp
Normal file
715
Engine/source/T3D/components/Animation/animationComponent.cpp
Normal file
|
|
@ -0,0 +1,715 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// 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 "T3D/Components/Animation/AnimationComponent.h"
|
||||
#include "T3D/Components/Animation/AnimationComponent_ScriptBinding.h"
|
||||
#include "T3D/components/Render/MeshComponent.h"
|
||||
|
||||
#include "platform/platform.h"
|
||||
#include "console/consoleTypes.h"
|
||||
#include "core/util/safeDelete.h"
|
||||
#include "core/resourceManager.h"
|
||||
#include "core/stream/fileStream.h"
|
||||
#include "console/consoleTypes.h"
|
||||
#include "console/consoleObject.h"
|
||||
#include "ts/tsShapeInstance.h"
|
||||
#include "core/stream/bitStream.h"
|
||||
#include "sim/netConnection.h"
|
||||
#include "gfx/gfxTransformSaver.h"
|
||||
#include "console/engineAPI.h"
|
||||
#include "lighting/lightQuery.h"
|
||||
#include "gfx/sim/debugDraw.h"
|
||||
|
||||
extern bool gEditingMission;
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// Callbacks
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
IMPLEMENT_CALLBACK( AnimationComponent, onAnimationStart, void, ( Component* obj, const String& animName ), ( obj, animName ),
|
||||
"@brief Called when we collide with another object.\n\n"
|
||||
"@param obj The ShapeBase object\n"
|
||||
"@param collObj The object we collided with\n"
|
||||
"@param vec Collision impact vector\n"
|
||||
"@param len Length of the impact vector\n" );
|
||||
|
||||
IMPLEMENT_CALLBACK(AnimationComponent, onAnimationEnd, void, (Component* obj, const char* animName), (obj, animName),
|
||||
"@brief Called when we collide with another object.\n\n"
|
||||
"@param obj The ShapeBase object\n"
|
||||
"@param collObj The object we collided with\n"
|
||||
"@param vec Collision impact vector\n"
|
||||
"@param len Length of the impact vector\n" );
|
||||
|
||||
IMPLEMENT_CALLBACK(AnimationComponent, onAnimationTrigger, void, (Component* obj, const String& animName, S32 triggerID), (obj, animName, triggerID),
|
||||
"@brief Called when we collide with another object.\n\n"
|
||||
"@param obj The ShapeBase object\n"
|
||||
"@param collObj The object we collided with\n"
|
||||
"@param vec Collision impact vector\n"
|
||||
"@param len Length of the impact vector\n" );
|
||||
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// Constructor/Destructor
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
AnimationComponent::AnimationComponent() : Component()
|
||||
{
|
||||
mNetworked = true;
|
||||
mNetFlags.set(Ghostable | ScopeAlways);
|
||||
|
||||
mFriendlyName = "Animation(Component)";
|
||||
mComponentType = "Render";
|
||||
|
||||
mDescription = getDescriptionText("Allows a rendered mesh to be animated");
|
||||
|
||||
mOwnerRenderInst = NULL;
|
||||
|
||||
mOwnerShapeInstance = NULL;
|
||||
|
||||
for (U32 i = 0; i < MaxScriptThreads; i++)
|
||||
{
|
||||
mAnimationThreads[i].sequence = -1;
|
||||
mAnimationThreads[i].thread = 0;
|
||||
mAnimationThreads[i].sound = 0;
|
||||
mAnimationThreads[i].state = Thread::Stop;
|
||||
mAnimationThreads[i].atEnd = false;
|
||||
mAnimationThreads[i].timescale = 1.f;
|
||||
mAnimationThreads[i].position = -1.f;
|
||||
mAnimationThreads[i].transition = true;
|
||||
}
|
||||
}
|
||||
|
||||
AnimationComponent::~AnimationComponent()
|
||||
{
|
||||
for(S32 i = 0;i < mFields.size();++i)
|
||||
{
|
||||
ComponentField &field = mFields[i];
|
||||
SAFE_DELETE_ARRAY(field.mFieldDescription);
|
||||
}
|
||||
|
||||
SAFE_DELETE_ARRAY(mDescription);
|
||||
}
|
||||
|
||||
IMPLEMENT_CO_NETOBJECT_V1(AnimationComponent);
|
||||
|
||||
bool AnimationComponent::onAdd()
|
||||
{
|
||||
if (!Parent::onAdd())
|
||||
return false;
|
||||
|
||||
//we need at least one layer
|
||||
for (U32 i = 0; i < MaxScriptThreads; i++)
|
||||
{
|
||||
Thread& st = mAnimationThreads[i];
|
||||
|
||||
if (st.sequence != -1)
|
||||
{
|
||||
// TG: Need to see about suppressing non-cyclic sounds
|
||||
// if the sequences were activated before the object was
|
||||
// ghosted.
|
||||
// TG: Cyclic animations need to have a random pos if
|
||||
// they were started before the object was ghosted.
|
||||
|
||||
// If there was something running on the old shape, the thread
|
||||
// needs to be reset. Otherwise we assume that it's been
|
||||
// initialized either by the constructor or from the server.
|
||||
bool reset = st.thread != 0;
|
||||
st.thread = 0;
|
||||
|
||||
if (st.sequence != -1)
|
||||
{
|
||||
setThreadSequence(i, st.sequence, reset);
|
||||
}
|
||||
}
|
||||
|
||||
if (st.thread)
|
||||
updateThread(st);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void AnimationComponent::onRemove()
|
||||
{
|
||||
Parent::onRemove();
|
||||
}
|
||||
|
||||
void AnimationComponent::onComponentAdd()
|
||||
{
|
||||
//test if this is a shape component!
|
||||
RenderComponentInterface *shapeInstanceInterface = mOwner->getComponent<RenderComponentInterface>();
|
||||
if (shapeInstanceInterface)
|
||||
{
|
||||
shapeInstanceInterface->onShapeInstanceChanged.notify(this, &AnimationComponent::targetShapeChanged);
|
||||
targetShapeChanged(shapeInstanceInterface);
|
||||
}
|
||||
}
|
||||
|
||||
void AnimationComponent::componentAddedToOwner(Component *comp)
|
||||
{
|
||||
if (comp->getId() == getId())
|
||||
return;
|
||||
|
||||
//test if this is a shape component!
|
||||
RenderComponentInterface *shapeInstanceInterface = dynamic_cast<RenderComponentInterface*>(comp);
|
||||
if (shapeInstanceInterface)
|
||||
{
|
||||
shapeInstanceInterface->onShapeInstanceChanged.notify(this, &AnimationComponent::targetShapeChanged);
|
||||
targetShapeChanged(shapeInstanceInterface);
|
||||
}
|
||||
}
|
||||
|
||||
void AnimationComponent::componentRemovedFromOwner(Component *comp)
|
||||
{
|
||||
if (comp->getId() == getId()) //?????????
|
||||
return;
|
||||
|
||||
//test if this is a shape component!
|
||||
RenderComponentInterface *shapeInstanceInterface = dynamic_cast<RenderComponentInterface*>(comp);
|
||||
if (shapeInstanceInterface)
|
||||
{
|
||||
shapeInstanceInterface->onShapeInstanceChanged.remove(this, &AnimationComponent::targetShapeChanged);
|
||||
mOwnerRenderInst = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void AnimationComponent::targetShapeChanged(RenderComponentInterface* instanceInterface)
|
||||
{
|
||||
mOwnerRenderInst = instanceInterface;
|
||||
|
||||
if (!mOwnerRenderInst || !getShape())
|
||||
return;
|
||||
|
||||
MeshComponent* meshComp = dynamic_cast<MeshComponent*>(mOwnerRenderInst);
|
||||
|
||||
mOwnerShapeInstance = meshComp->getShapeInstance();
|
||||
|
||||
if (!mOwnerShapeInstance)
|
||||
return;
|
||||
|
||||
for (U32 i = 0; i < MaxScriptThreads; i++)
|
||||
{
|
||||
Thread& st = mAnimationThreads[i];
|
||||
|
||||
st.thread = mOwnerShapeInstance->addThread();
|
||||
}
|
||||
}
|
||||
|
||||
void AnimationComponent::initPersistFields()
|
||||
{
|
||||
Parent::initPersistFields();
|
||||
}
|
||||
|
||||
U32 AnimationComponent::packUpdate(NetConnection *con, U32 mask, BitStream *stream)
|
||||
{
|
||||
U32 retMask = Parent::packUpdate(con, mask, stream);
|
||||
|
||||
//early test if we lack an owner, ghost-wise
|
||||
//no point in trying, just re-queue the mask and go
|
||||
if (!mOwner || con->getGhostIndex(mOwner) == -1)
|
||||
{
|
||||
stream->writeFlag(false);
|
||||
return retMask |= ThreadMask;
|
||||
}
|
||||
else
|
||||
{
|
||||
stream->writeFlag(true);
|
||||
|
||||
for (int i = 0; i < MaxScriptThreads; i++)
|
||||
{
|
||||
Thread& st = mAnimationThreads[i];
|
||||
if (stream->writeFlag( (st.sequence != -1 || st.state == Thread::Destroy) && (mask & (ThreadMaskN << i)) ) )
|
||||
{
|
||||
stream->writeInt(st.sequence,ThreadSequenceBits);
|
||||
stream->writeInt(st.state,2);
|
||||
stream->write(st.timescale);
|
||||
stream->write(st.position);
|
||||
stream->writeFlag(st.atEnd);
|
||||
stream->writeFlag(st.transition);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return retMask;
|
||||
}
|
||||
|
||||
void AnimationComponent::unpackUpdate(NetConnection *con, BitStream *stream)
|
||||
{
|
||||
Parent::unpackUpdate(con, stream);
|
||||
|
||||
if (stream->readFlag())
|
||||
{
|
||||
for (S32 i = 0; i < MaxScriptThreads; i++)
|
||||
{
|
||||
if (stream->readFlag())
|
||||
{
|
||||
Thread& st = mAnimationThreads[i];
|
||||
U32 seq = stream->readInt(ThreadSequenceBits);
|
||||
st.state = stream->readInt(2);
|
||||
stream->read( &st.timescale );
|
||||
stream->read( &st.position );
|
||||
st.atEnd = stream->readFlag();
|
||||
bool transition = stream->readFlag();
|
||||
|
||||
if (!st.thread || st.sequence != seq && st.state != Thread::Destroy)
|
||||
setThreadSequence(i, seq, false, transition);
|
||||
else
|
||||
updateThread(st);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
void AnimationComponent::processTick()
|
||||
{
|
||||
Parent::processTick();
|
||||
|
||||
if (!isActive())
|
||||
return;
|
||||
|
||||
if (isServerObject())
|
||||
{
|
||||
// Server only...
|
||||
advanceThreads(TickSec);
|
||||
}
|
||||
}
|
||||
|
||||
void AnimationComponent::advanceTime(F32 dt)
|
||||
{
|
||||
Parent::advanceTime(dt);
|
||||
|
||||
// On the client, the shape threads and images are
|
||||
// advanced at framerate.
|
||||
advanceThreads(dt);
|
||||
}
|
||||
//
|
||||
const char *AnimationComponent::getThreadSequenceName(U32 slot)
|
||||
{
|
||||
Thread& st = mAnimationThreads[slot];
|
||||
if (st.sequence == -1)
|
||||
{
|
||||
// Invalid Animation.
|
||||
return "";
|
||||
}
|
||||
|
||||
// Name Index
|
||||
TSShape* shape = getShape();
|
||||
|
||||
if (shape)
|
||||
{
|
||||
const U32 nameIndex = shape->sequences[st.sequence].nameIndex;
|
||||
|
||||
// Return Name.
|
||||
return shape->getName(nameIndex);
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
bool AnimationComponent::setThreadSequence(U32 slot, S32 seq, bool reset, bool transition, F32 transTime)
|
||||
{
|
||||
if (!mOwnerShapeInstance)
|
||||
return false;
|
||||
|
||||
Thread& st = mAnimationThreads[slot];
|
||||
if (st.thread && st.sequence == seq && st.state == Thread::Play && !reset)
|
||||
return true;
|
||||
|
||||
// Handle a -1 sequence, as this may be set when a thread has been destroyed.
|
||||
if (seq == -1)
|
||||
return true;
|
||||
|
||||
if (seq < MaxSequenceIndex)
|
||||
{
|
||||
setMaskBits(-1);
|
||||
setMaskBits(ThreadMaskN << slot);
|
||||
st.sequence = seq;
|
||||
st.transition = transition;
|
||||
|
||||
if (reset)
|
||||
{
|
||||
st.state = Thread::Play;
|
||||
st.atEnd = false;
|
||||
st.timescale = 1.f;
|
||||
st.position = 0.f;
|
||||
}
|
||||
|
||||
if (mOwnerShapeInstance)
|
||||
{
|
||||
if (!st.thread)
|
||||
st.thread = mOwnerShapeInstance->addThread();
|
||||
|
||||
if (transition)
|
||||
{
|
||||
mOwnerShapeInstance->transitionToSequence(st.thread, seq, st.position, transTime, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
mOwnerShapeInstance->setSequence(st.thread, seq, 0);
|
||||
stopThreadSound(st);
|
||||
}
|
||||
|
||||
updateThread(st);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
S32 AnimationComponent::getThreadSequenceID(S32 slot)
|
||||
{
|
||||
if (slot >= 0 && slot < AnimationComponent::MaxScriptThreads)
|
||||
{
|
||||
return mAnimationThreads[slot].sequence;
|
||||
}
|
||||
else
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
void AnimationComponent::updateThread(Thread& st)
|
||||
{
|
||||
if (!mOwnerShapeInstance)
|
||||
return;
|
||||
|
||||
switch (st.state)
|
||||
{
|
||||
case Thread::Stop:
|
||||
{
|
||||
mOwnerShapeInstance->setTimeScale(st.thread, 1.f);
|
||||
mOwnerShapeInstance->setPos(st.thread, (st.timescale > 0.f) ? 0.0f : 1.0f);
|
||||
} // Drop through to pause state
|
||||
|
||||
case Thread::Pause:
|
||||
{
|
||||
if (st.position != -1.f)
|
||||
{
|
||||
mOwnerShapeInstance->setTimeScale(st.thread, 1.f);
|
||||
mOwnerShapeInstance->setPos(st.thread, st.position);
|
||||
}
|
||||
|
||||
mOwnerShapeInstance->setTimeScale(st.thread, 0.f);
|
||||
stopThreadSound(st);
|
||||
} break;
|
||||
|
||||
case Thread::Play:
|
||||
{
|
||||
if (st.atEnd)
|
||||
{
|
||||
mOwnerShapeInstance->setTimeScale(st.thread, 1);
|
||||
mOwnerShapeInstance->setPos(st.thread, (st.timescale > 0.f) ? 1.0f : 0.0f);
|
||||
mOwnerShapeInstance->setTimeScale(st.thread, 0);
|
||||
stopThreadSound(st);
|
||||
st.state = Thread::Stop;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (st.position != -1.f)
|
||||
{
|
||||
mOwnerShapeInstance->setTimeScale(st.thread, 1.f);
|
||||
mOwnerShapeInstance->setPos(st.thread, st.position);
|
||||
}
|
||||
|
||||
mOwnerShapeInstance->setTimeScale(st.thread, st.timescale);
|
||||
if (!st.sound)
|
||||
{
|
||||
startSequenceSound(st);
|
||||
}
|
||||
}
|
||||
} break;
|
||||
|
||||
case Thread::Destroy:
|
||||
{
|
||||
stopThreadSound(st);
|
||||
st.atEnd = true;
|
||||
st.sequence = -1;
|
||||
if (st.thread)
|
||||
{
|
||||
mOwnerShapeInstance->destroyThread(st.thread);
|
||||
st.thread = 0;
|
||||
}
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
bool AnimationComponent::stopThread(U32 slot)
|
||||
{
|
||||
Thread& st = mAnimationThreads[slot];
|
||||
if (st.sequence != -1 && st.state != Thread::Stop)
|
||||
{
|
||||
setMaskBits(ThreadMaskN << slot);
|
||||
st.state = Thread::Stop;
|
||||
updateThread(st);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool AnimationComponent::destroyThread(U32 slot)
|
||||
{
|
||||
Thread& st = mAnimationThreads[slot];
|
||||
if (st.sequence != -1 && st.state != Thread::Destroy)
|
||||
{
|
||||
setMaskBits(ThreadMaskN << slot);
|
||||
st.state = Thread::Destroy;
|
||||
updateThread(st);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool AnimationComponent::pauseThread(U32 slot)
|
||||
{
|
||||
Thread& st = mAnimationThreads[slot];
|
||||
if (st.sequence != -1 && st.state != Thread::Pause)
|
||||
{
|
||||
setMaskBits(ThreadMaskN << slot);
|
||||
st.state = Thread::Pause;
|
||||
updateThread(st);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool AnimationComponent::playThread(U32 slot)
|
||||
{
|
||||
Thread& st = mAnimationThreads[slot];
|
||||
if (st.sequence != -1 && st.state != Thread::Play)
|
||||
{
|
||||
setMaskBits(ThreadMaskN << slot);
|
||||
st.state = Thread::Play;
|
||||
updateThread(st);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool AnimationComponent::playThread(U32 slot, const char* name, bool transition, F32 transitionTime)
|
||||
{
|
||||
if (slot < AnimationComponent::MaxScriptThreads)
|
||||
{
|
||||
if (!dStrEqual(name, ""))
|
||||
{
|
||||
if (TSShape* shape = getShape())
|
||||
{
|
||||
S32 seq = shape->findSequence(name);
|
||||
if (seq != -1 && setThreadSequence(slot, seq, true, transition, transitionTime))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else if (seq == -1)
|
||||
{
|
||||
//We tried to play a non-existaint sequence, so stop the thread just in case
|
||||
destroyThread(slot);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (playThread(slot))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool AnimationComponent::setThreadAnimation(U32 slot, const char* name)
|
||||
{
|
||||
if (slot < AnimationComponent::MaxScriptThreads)
|
||||
{
|
||||
if (!dStrEqual(name, ""))
|
||||
{
|
||||
if (TSShape* shape = getShape())
|
||||
{
|
||||
S32 seq = shape->findSequence(name);
|
||||
if (seq != -1 && setThreadSequence(slot, seq, false, false))
|
||||
{
|
||||
Thread& st = mAnimationThreads[slot];
|
||||
if (st.position == -1)
|
||||
st.position = 0;
|
||||
//st.state = Thread::Pause;
|
||||
return true;
|
||||
}
|
||||
else if (seq == -1)
|
||||
{
|
||||
//We tried to play a non-existaint sequence, so stop the thread just in case
|
||||
destroyThread(slot);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (playThread(slot))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool AnimationComponent::setThreadPosition(U32 slot, F32 pos)
|
||||
{
|
||||
Thread& st = mAnimationThreads[slot];
|
||||
if (st.sequence != -1)
|
||||
{
|
||||
setMaskBits(ThreadMaskN << slot);
|
||||
st.position = pos;
|
||||
st.atEnd = false;
|
||||
updateThread(st);
|
||||
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool AnimationComponent::setThreadDir(U32 slot, bool forward)
|
||||
{
|
||||
Thread& st = mAnimationThreads[slot];
|
||||
if (st.sequence != -1)
|
||||
{
|
||||
if ((st.timescale >= 0.f) != forward)
|
||||
{
|
||||
setMaskBits(ThreadMaskN << slot);
|
||||
st.timescale *= -1.f;
|
||||
st.atEnd = false;
|
||||
updateThread(st);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool AnimationComponent::setThreadTimeScale(U32 slot, F32 timeScale)
|
||||
{
|
||||
Thread& st = mAnimationThreads[slot];
|
||||
if (st.sequence != -1)
|
||||
{
|
||||
if (st.timescale != timeScale)
|
||||
{
|
||||
setMaskBits(ThreadMaskN << slot);
|
||||
st.timescale = timeScale;
|
||||
updateThread(st);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void AnimationComponent::stopThreadSound(Thread& thread)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
void AnimationComponent::startSequenceSound(Thread& thread)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
void AnimationComponent::advanceThreads(F32 dt)
|
||||
{
|
||||
if (!mOwnerShapeInstance)
|
||||
return;
|
||||
|
||||
for (U32 i = 0; i < MaxScriptThreads; i++)
|
||||
{
|
||||
Thread& st = mAnimationThreads[i];
|
||||
if (st.thread && st.sequence != -1)
|
||||
{
|
||||
bool cyclic = getShape()->sequences[st.sequence].isCyclic();
|
||||
|
||||
if (!getShape()->sequences[st.sequence].isCyclic() &&
|
||||
!st.atEnd &&
|
||||
((st.timescale > 0.f) ? mOwnerShapeInstance->getPos(st.thread) >= 1.0 : mOwnerShapeInstance->getPos(st.thread) <= 0))
|
||||
{
|
||||
st.atEnd = true;
|
||||
updateThread(st);
|
||||
|
||||
if (!isGhost())
|
||||
{
|
||||
Con::executef(this, "onAnimationEnd", st.thread->getSequenceName());
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure the thread is still valid after the call to onEndSequence_callback().
|
||||
// Someone could have called destroyThread() while in there.
|
||||
if (st.thread)
|
||||
{
|
||||
mOwnerShapeInstance->advanceTime(dt, st.thread);
|
||||
}
|
||||
|
||||
if (mOwnerShapeInstance && !isGhost())
|
||||
{
|
||||
for (U32 i = 1; i < 32; i++)
|
||||
{
|
||||
if (mOwnerShapeInstance->getTriggerState(i))
|
||||
{
|
||||
const char* animName = st.thread->getSequenceName().c_str();
|
||||
onAnimationTrigger_callback(this, animName, i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isGhost())
|
||||
mOwnerShapeInstance->animate();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TSShape* AnimationComponent::getShape()
|
||||
{
|
||||
if (mOwner == NULL)
|
||||
return NULL;
|
||||
|
||||
if (mOwnerRenderInst == NULL)
|
||||
return NULL;
|
||||
|
||||
return mOwnerRenderInst->getShape();
|
||||
}
|
||||
|
||||
S32 AnimationComponent::getAnimationCount()
|
||||
{
|
||||
if (getShape())
|
||||
return getShape()->sequences.size();
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
S32 AnimationComponent::getAnimationIndex(const char* name)
|
||||
{
|
||||
if (getShape())
|
||||
return getShape()->findSequence(name);
|
||||
else
|
||||
return -1;
|
||||
}
|
||||
|
||||
const char* AnimationComponent::getAnimationName(S32 index)
|
||||
{
|
||||
if (getShape())
|
||||
{
|
||||
if (index >= 0 && index < getShape()->sequences.size())
|
||||
return getShape()->getName(getShape()->sequences[index].nameIndex);
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
138
Engine/source/T3D/components/Animation/animationComponent.h
Normal file
138
Engine/source/T3D/components/Animation/animationComponent.h
Normal file
|
|
@ -0,0 +1,138 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// 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 ANIMATION_COMPONENT_H
|
||||
#define ANIMATION_COMPONENT_H
|
||||
|
||||
#ifndef COMPONENT_H
|
||||
#include "T3D/Components/Component.h"
|
||||
#endif
|
||||
#ifndef _TSSHAPE_H_
|
||||
#include "ts/tsShapeInstance.h"
|
||||
#endif
|
||||
#ifndef ENTITY_H
|
||||
#include "T3D/Entity.h"
|
||||
#endif
|
||||
#ifndef RENDER_COMPONENT_INTERFACE_H
|
||||
#include "T3D/Components/render/renderComponentInterface.h"
|
||||
#endif
|
||||
|
||||
class SceneRenderState;
|
||||
|
||||
class AnimationComponent : public Component
|
||||
{
|
||||
typedef Component Parent;
|
||||
public:
|
||||
enum PublicConstants {
|
||||
ThreadSequenceBits = 6,
|
||||
MaxSequenceIndex = (1 << ThreadSequenceBits) - 1,
|
||||
MaxScriptThreads = 16, ///< Should be a power of 2
|
||||
};
|
||||
|
||||
enum MaskBits {
|
||||
ThreadMaskN = Parent::NextFreeMask << 0,
|
||||
ThreadMask = (ThreadMaskN << MaxScriptThreads) - ThreadMaskN,
|
||||
NextFreeMask = ThreadMaskN << MaxScriptThreads
|
||||
};
|
||||
|
||||
protected:
|
||||
|
||||
struct Thread
|
||||
{
|
||||
/// State of the animation thread.
|
||||
enum State
|
||||
{
|
||||
Play, Stop, Pause, Destroy
|
||||
};
|
||||
TSThread* thread; ///< Pointer to 3space data.
|
||||
U32 state; ///< State of the thread
|
||||
///
|
||||
/// @see Thread::State
|
||||
S32 sequence; ///< The animation sequence which is running in this thread.
|
||||
F32 timescale; ///< Timescale
|
||||
U32 sound; ///< Handle to sound.
|
||||
bool atEnd; ///< Are we at the end of this thread?
|
||||
F32 position;
|
||||
bool transition;
|
||||
};
|
||||
|
||||
Thread mAnimationThreads[MaxScriptThreads];
|
||||
|
||||
protected:
|
||||
RenderComponentInterface * mOwnerRenderInst;
|
||||
|
||||
TSShapeInstance *mOwnerShapeInstance;
|
||||
|
||||
public:
|
||||
AnimationComponent();
|
||||
virtual ~AnimationComponent();
|
||||
DECLARE_CONOBJECT(AnimationComponent);
|
||||
|
||||
virtual bool onAdd();
|
||||
virtual void onRemove();
|
||||
static void initPersistFields();
|
||||
|
||||
virtual void onComponentAdd();
|
||||
|
||||
virtual void componentAddedToOwner(Component *comp);
|
||||
virtual void componentRemovedFromOwner(Component *comp);
|
||||
|
||||
virtual U32 packUpdate(NetConnection *con, U32 mask, BitStream *stream);
|
||||
virtual void unpackUpdate(NetConnection *con, BitStream *stream);
|
||||
|
||||
TSShape* getShape();
|
||||
|
||||
void targetShapeChanged(RenderComponentInterface* instanceInterface);
|
||||
|
||||
virtual void processTick();
|
||||
virtual void advanceTime(F32 dt);
|
||||
|
||||
const char *getThreadSequenceName(U32 slot);
|
||||
bool setThreadSequence(U32 slot, S32 seq, bool reset = true, bool transition = true, F32 transitionTime = 0.5);
|
||||
void updateThread(Thread& st);
|
||||
bool stopThread(U32 slot);
|
||||
bool destroyThread(U32 slot);
|
||||
bool pauseThread(U32 slot);
|
||||
bool playThread(U32 slot);
|
||||
bool playThread(U32 slot, const char* name, bool transition, F32 transitionTime);
|
||||
bool setThreadAnimation(U32 slot, const char* name);
|
||||
bool setThreadPosition(U32 slot, F32 pos);
|
||||
bool setThreadDir(U32 slot, bool forward);
|
||||
bool setThreadTimeScale(U32 slot, F32 timeScale);
|
||||
void stopThreadSound(Thread& thread);
|
||||
void startSequenceSound(Thread& thread);
|
||||
void advanceThreads(F32 dt);
|
||||
|
||||
S32 getThreadSequenceID(S32 slot);
|
||||
|
||||
//other helper functions
|
||||
S32 getAnimationCount();
|
||||
S32 getAnimationIndex(const char* name);
|
||||
const char* getAnimationName(S32 index);
|
||||
|
||||
//callbacks
|
||||
DECLARE_CALLBACK(void, onAnimationStart, (Component* obj, const String& animName));
|
||||
DECLARE_CALLBACK(void, onAnimationEnd, (Component* obj, const char* animName));
|
||||
DECLARE_CALLBACK(void, onAnimationTrigger, (Component* obj, const String& animName, S32 triggerID));
|
||||
};
|
||||
|
||||
#endif //_ANIMATION_COMPONENT_H
|
||||
|
|
@ -0,0 +1,222 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// 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 "console/engineAPI.h"
|
||||
#include "T3D/Components/Animation/animationComponent.h"
|
||||
|
||||
DefineEngineMethod(AnimationComponent, playThread, bool, (S32 slot, const char* name, bool transition, F32 transitionTime), (-1, "", true, 0.5),
|
||||
"@brief Start a new animation thread, or restart one that has been paused or "
|
||||
"stopped.\n\n"
|
||||
|
||||
"@param slot thread slot to play. Valid range is 0 - 3)\n" // 3 = AnimationComponent::MaxScriptThreads-1
|
||||
"@param name name of the animation sequence to play in this slot. If not "
|
||||
"specified, the paused or stopped thread in this slot will be resumed.\n"
|
||||
"@return true if successful, false if failed\n\n"
|
||||
|
||||
"@tsexample\n"
|
||||
"%obj.playThread( 0, \"ambient\" ); // Play the ambient sequence in slot 0\n"
|
||||
"%obj.setThreadTimeScale( 0, 0.5 ); // Play at half-speed\n"
|
||||
"%obj.pauseThread( 0 ); // Pause the sequence\n"
|
||||
"%obj.playThread( 0 ); // Resume playback\n"
|
||||
"%obj.playThread( 0, \"spin\" ); // Replace the sequence in slot 0\n"
|
||||
"@endtsexample\n"
|
||||
|
||||
"@see pauseThread()\n"
|
||||
"@see stopThread()\n"
|
||||
"@see setThreadDir()\n"
|
||||
"@see setThreadTimeScale()\n"
|
||||
"@see destroyThread()\n")
|
||||
{
|
||||
return object->playThread(slot, name, transition, transitionTime);
|
||||
}
|
||||
|
||||
DefineEngineMethod(AnimationComponent, setThreadDir, bool, (S32 slot, bool fwd), ,
|
||||
"@brief Set the playback direction of an animation thread.\n\n"
|
||||
|
||||
"@param slot thread slot to modify\n"
|
||||
"@param fwd true to play the animation forwards, false to play backwards\n"
|
||||
"@return true if successful, false if failed\n\n"
|
||||
|
||||
"@see playThread()\n")
|
||||
{
|
||||
if (slot >= 0 && slot < AnimationComponent::MaxScriptThreads)
|
||||
{
|
||||
if (object->setThreadDir(slot, fwd))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
DefineEngineMethod(AnimationComponent, setThreadTimeScale, bool, (S32 slot, F32 scale), ,
|
||||
"@brief Set the playback time scale of an animation thread.\n\n"
|
||||
|
||||
"@param slot thread slot to modify\n"
|
||||
"@param scale new thread time scale (1=normal speed, 0.5=half speed etc)\n"
|
||||
"@return true if successful, false if failed\n\n"
|
||||
|
||||
"@see playThread\n")
|
||||
{
|
||||
if (slot >= 0 && slot < AnimationComponent::MaxScriptThreads)
|
||||
{
|
||||
if (object->setThreadTimeScale(slot, scale))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
DefineEngineMethod(AnimationComponent, setThreadPosition, bool, (S32 slot, F32 pos), ,
|
||||
"@brief Set the position within an animation thread.\n\n"
|
||||
|
||||
"@param slot thread slot to modify\n"
|
||||
"@param pos position within thread\n"
|
||||
"@return true if successful, false if failed\n\n"
|
||||
|
||||
"@see playThread\n")
|
||||
{
|
||||
if (slot >= 0 && slot < AnimationComponent::MaxScriptThreads)
|
||||
{
|
||||
if (object->setThreadPosition(slot, pos))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
DefineEngineMethod(AnimationComponent, setThreadAnimation, bool, (S32 slot, const char* name), (""),
|
||||
"@brief Force-sets the animation in a particular thread without starting it playing."
|
||||
|
||||
"@param slot thread slot to play. Valid range is 0 - 3)\n" // 3 = AnimationComponent::MaxScriptThreads-1
|
||||
"@param name name of the animation sequence to play in this slot. If not "
|
||||
"specified, the paused or stopped thread in this slot will be resumed.\n"
|
||||
"@return true if successful, false if failed\n\n")
|
||||
{
|
||||
return object->setThreadAnimation(slot, name);
|
||||
}
|
||||
|
||||
DefineEngineMethod(AnimationComponent, getThreadAnimation, String, (S32 slot), ,
|
||||
"@brief Force-sets the animation in a particular thread without starting it playing."
|
||||
|
||||
"@param slot thread slot to play. Valid range is 0 - 3)\n" // 3 = AnimationComponent::MaxScriptThreads-1
|
||||
"@param name name of the animation sequence to play in this slot. If not "
|
||||
"specified, the paused or stopped thread in this slot will be resumed.\n"
|
||||
"@return true if successful, false if failed\n\n")
|
||||
{
|
||||
if (slot >= 0 && slot < AnimationComponent::MaxScriptThreads)
|
||||
{
|
||||
if (TSShape* shape = object->getShape())
|
||||
{
|
||||
S32 seq = object->getThreadSequenceID(slot);
|
||||
if (seq != -1)
|
||||
{
|
||||
String animationName = object->getAnimationName(seq);
|
||||
return animationName;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
DefineEngineMethod(AnimationComponent, stopThread, bool, (S32 slot), ,
|
||||
"@brief Stop an animation thread.\n\n"
|
||||
|
||||
"If restarted using playThread, the animation "
|
||||
"will start from the beginning again.\n"
|
||||
"@param slot thread slot to stop\n"
|
||||
"@return true if successful, false if failed\n\n"
|
||||
|
||||
"@see playThread\n")
|
||||
{
|
||||
if (slot >= 0 && slot < AnimationComponent::MaxScriptThreads)
|
||||
{
|
||||
if (object->stopThread(slot))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
DefineEngineMethod(AnimationComponent, destroyThread, bool, (S32 slot), ,
|
||||
"@brief Destroy an animation thread, which prevents it from playing.\n\n"
|
||||
|
||||
"@param slot thread slot to destroy\n"
|
||||
"@return true if successful, false if failed\n\n"
|
||||
|
||||
"@see playThread\n")
|
||||
{
|
||||
if (slot >= 0 && slot < AnimationComponent::MaxScriptThreads)
|
||||
{
|
||||
if (object->destroyThread(slot))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
DefineEngineMethod(AnimationComponent, pauseThread, bool, (S32 slot), ,
|
||||
"@brief Pause an animation thread.\n\n"
|
||||
|
||||
"If restarted using playThread, the animation "
|
||||
"will resume from the paused position.\n"
|
||||
"@param slot thread slot to stop\n"
|
||||
"@return true if successful, false if failed\n\n"
|
||||
|
||||
"@see playThread\n")
|
||||
{
|
||||
if (slot >= 0 && slot < AnimationComponent::MaxScriptThreads)
|
||||
{
|
||||
if (object->pauseThread(slot))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
DefineEngineMethod(AnimationComponent, getAnimationCount, S32, (), ,
|
||||
"Get the total number of sequences in the shape.\n"
|
||||
"@return the number of sequences in the shape\n\n")
|
||||
{
|
||||
return object->getAnimationCount();
|
||||
}
|
||||
|
||||
DefineEngineMethod(AnimationComponent, getAnimationIndex, S32, (const char* name), ,
|
||||
"Find the index of the sequence with the given name.\n"
|
||||
"@param name name of the sequence to lookup\n"
|
||||
"@return index of the sequence with matching name, or -1 if not found\n\n"
|
||||
"@tsexample\n"
|
||||
"// Check if a given sequence exists in the shape\n"
|
||||
"if ( %this.getSequenceIndex( \"walk\" ) == -1 )\n"
|
||||
" echo( \"Could not find 'walk' sequence\" );\n"
|
||||
"@endtsexample\n")
|
||||
{
|
||||
return object->getAnimationIndex(name);
|
||||
}
|
||||
|
||||
DefineEngineMethod(AnimationComponent, getAnimationName, const char*, (S32 index), ,
|
||||
"Get the name of the indexed sequence.\n"
|
||||
"@param index index of the sequence to query (valid range is 0 - getSequenceCount()-1)\n"
|
||||
"@return the name of the sequence\n\n"
|
||||
"@tsexample\n"
|
||||
"// print the name of all sequences in the shape\n"
|
||||
"%count = %this.getSequenceCount();\n"
|
||||
"for ( %i = 0; %i < %count; %i++ )\n"
|
||||
" echo( %i SPC %this.getSequenceName( %i ) );\n"
|
||||
"@endtsexample\n")
|
||||
{
|
||||
return object->getAnimationName(index);
|
||||
}
|
||||
483
Engine/source/T3D/components/Camera/CameraComponent.cpp
Normal file
483
Engine/source/T3D/components/Camera/CameraComponent.cpp
Normal file
|
|
@ -0,0 +1,483 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// 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 "T3D/Components/camera/CameraComponent.h"
|
||||
#include "T3D/Components/Camera/CameraComponent_ScriptBinding.h"
|
||||
#include "platform/platform.h"
|
||||
#include "console/consoleTypes.h"
|
||||
#include "core/util/safeDelete.h"
|
||||
#include "core/resourceManager.h"
|
||||
#include "core/stream/fileStream.h"
|
||||
#include "console/consoleTypes.h"
|
||||
#include "console/consoleObject.h"
|
||||
#include "ts/tsShapeInstance.h"
|
||||
#include "core/stream/bitStream.h"
|
||||
#include "gfx/gfxTransformSaver.h"
|
||||
#include "console/engineAPI.h"
|
||||
#include "lighting/lightQuery.h"
|
||||
#include "T3D/gameBase/gameConnection.h"
|
||||
#include "T3D/gameFunctions.h"
|
||||
#include "math/mathUtils.h"
|
||||
#include "T3D/Components/render/renderComponentInterface.h"
|
||||
|
||||
IMPLEMENT_CALLBACK( CameraComponent, validateCameraFov, F32, (F32 fov), (fov),
|
||||
"@brief Called on the server when the client has requested a FOV change.\n\n"
|
||||
|
||||
"When the client requests that its field of view should be changed (because "
|
||||
"they want to use a sniper scope, for example) this new FOV needs to be validated "
|
||||
"by the server. This method is called if it exists (it is optional) to validate "
|
||||
"the requested FOV, and modify it if necessary. This could be as simple as checking "
|
||||
"that the FOV falls within a correct range, to making sure that the FOV matches the "
|
||||
"capabilities of the current weapon.\n\n"
|
||||
|
||||
"Following this method, ShapeBase ensures that the given FOV still falls within "
|
||||
"the datablock's mCameraMinFov and mCameraMaxFov. If that is good enough for your "
|
||||
"purposes, then you do not need to define the validateCameraFov() callback for "
|
||||
"your ShapeBase.\n\n"
|
||||
|
||||
"@param fov The FOV that has been requested by the client.\n"
|
||||
"@return The FOV as validated by the server.\n\n"
|
||||
|
||||
"@see ShapeBaseData\n\n");
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// Constructor/Destructor
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
CameraComponent::CameraComponent() : Component()
|
||||
{
|
||||
mClientScreen = Point2F(1, 1);
|
||||
|
||||
mCameraFov = mCameraDefaultFov = 80;
|
||||
mCameraMinFov = 5;
|
||||
mCameraMaxFov = 175;
|
||||
|
||||
mTargetNodeIdx = -1;
|
||||
|
||||
mPosOffset = Point3F(0, 0, 0);
|
||||
mRotOffset = EulerF(0, 0, 0);
|
||||
|
||||
mTargetNode = "";
|
||||
|
||||
mUseParentTransform = true;
|
||||
|
||||
mFriendlyName = "Camera(Component)";
|
||||
}
|
||||
|
||||
CameraComponent::~CameraComponent()
|
||||
{
|
||||
for(S32 i = 0;i < mFields.size();++i)
|
||||
{
|
||||
ComponentField &field = mFields[i];
|
||||
SAFE_DELETE_ARRAY(field.mFieldDescription);
|
||||
}
|
||||
|
||||
SAFE_DELETE_ARRAY(mDescription);
|
||||
}
|
||||
|
||||
IMPLEMENT_CO_NETOBJECT_V1(CameraComponent);
|
||||
|
||||
bool CameraComponent::onAdd()
|
||||
{
|
||||
if(! Parent::onAdd())
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void CameraComponent::onRemove()
|
||||
{
|
||||
Parent::onRemove();
|
||||
}
|
||||
|
||||
void CameraComponent::initPersistFields()
|
||||
{
|
||||
Parent::initPersistFields();
|
||||
|
||||
addProtectedField("FOV", TypeF32, Offset(mCameraFov, CameraComponent), &_setCameraFov, defaultProtectedGetFn, "");
|
||||
|
||||
addField("MinFOV", TypeF32, Offset(mCameraMinFov, CameraComponent), "");
|
||||
|
||||
addField("MaxFOV", TypeF32, Offset(mCameraMaxFov, CameraComponent), "");
|
||||
|
||||
addField("ScreenAspect", TypePoint2I, Offset(mClientScreen, CameraComponent), "");
|
||||
|
||||
addProtectedField("targetNode", TypeString, Offset(mTargetNode, CameraComponent), &_setNode, defaultProtectedGetFn, "");
|
||||
|
||||
addProtectedField("positionOffset", TypePoint3F, Offset(mPosOffset, CameraComponent), &_setPosOffset, defaultProtectedGetFn, "");
|
||||
|
||||
addProtectedField("rotationOffset", TypeRotationF, Offset(mRotOffset, CameraComponent), &_setRotOffset, defaultProtectedGetFn, "");
|
||||
|
||||
addField("useParentTransform", TypeBool, Offset(mUseParentTransform, CameraComponent), "");
|
||||
}
|
||||
|
||||
bool CameraComponent::_setNode(void *object, const char *index, const char *data)
|
||||
{
|
||||
CameraComponent *mcc = static_cast<CameraComponent*>(object);
|
||||
|
||||
mcc->mTargetNode = StringTable->insert(data);
|
||||
mcc->setMaskBits(OffsetMask);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CameraComponent::_setPosOffset(void *object, const char *index, const char *data)
|
||||
{
|
||||
CameraComponent *mcc = static_cast<CameraComponent*>(object);
|
||||
|
||||
if (mcc)
|
||||
{
|
||||
Point3F pos;
|
||||
Con::setData(TypePoint3F, &pos, 0, 1, &data);
|
||||
|
||||
mcc->mPosOffset = pos;
|
||||
mcc->setMaskBits(OffsetMask);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool CameraComponent::_setRotOffset(void *object, const char *index, const char *data)
|
||||
{
|
||||
CameraComponent *mcc = static_cast<CameraComponent*>(object);
|
||||
|
||||
if (mcc)
|
||||
{
|
||||
RotationF rot;
|
||||
Con::setData(TypeRotationF, &rot, 0, 1, &data);
|
||||
|
||||
mcc->mRotOffset = rot;
|
||||
mcc->setMaskBits(OffsetMask);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool CameraComponent::isValidCameraFov(F32 fov)
|
||||
{
|
||||
return((fov >= mCameraMinFov) && (fov <= mCameraMaxFov));
|
||||
}
|
||||
|
||||
bool CameraComponent::_setCameraFov(void *object, const char *index, const char *data)
|
||||
{
|
||||
CameraComponent *cCI = static_cast<CameraComponent*>(object);
|
||||
cCI->setCameraFov(dAtof(data));
|
||||
return true;
|
||||
}
|
||||
|
||||
void CameraComponent::setCameraFov(F32 fov)
|
||||
{
|
||||
mCameraFov = mClampF(fov, mCameraMinFov, mCameraMaxFov);
|
||||
|
||||
if (isClientObject())
|
||||
GameSetCameraTargetFov(mCameraFov);
|
||||
|
||||
if (isServerObject())
|
||||
setMaskBits(FOVMask);
|
||||
}
|
||||
|
||||
void CameraComponent::onCameraScopeQuery(NetConnection *cr, CameraScopeQuery * query)
|
||||
{
|
||||
// update the camera query
|
||||
query->camera = this;
|
||||
|
||||
if(GameConnection * con = dynamic_cast<GameConnection*>(cr))
|
||||
{
|
||||
// get the fov from the connection (in deg)
|
||||
F32 fov;
|
||||
if (con->getControlCameraFov(&fov))
|
||||
{
|
||||
query->fov = mDegToRad(fov/2);
|
||||
query->sinFov = mSin(query->fov);
|
||||
query->cosFov = mCos(query->fov);
|
||||
}
|
||||
else
|
||||
{
|
||||
query->fov = mDegToRad(mCameraFov/2);
|
||||
query->sinFov = mSin(query->fov);
|
||||
query->cosFov = mCos(query->fov);
|
||||
}
|
||||
}
|
||||
|
||||
// use eye rather than camera transform (good enough and faster)
|
||||
MatrixF camTransform = mOwner->getTransform();
|
||||
camTransform.getColumn(3, &query->pos);
|
||||
camTransform.getColumn(1, &query->orientation);
|
||||
|
||||
// Get the visible distance.
|
||||
if (mOwner->getSceneManager() != NULL)
|
||||
query->visibleDistance = mOwner->getSceneManager()->getVisibleDistance();
|
||||
}
|
||||
|
||||
bool CameraComponent::getCameraTransform(F32* pos,MatrixF* mat)
|
||||
{
|
||||
// Returns camera to world space transform
|
||||
// Handles first person / third person camera position
|
||||
bool isServer = isServerObject();
|
||||
|
||||
if (mTargetNodeIdx == -1)
|
||||
{
|
||||
if (mUseParentTransform)
|
||||
{
|
||||
MatrixF rMat = mOwner->getRenderTransform();
|
||||
|
||||
rMat.mul(mRotOffset.asMatrixF());
|
||||
|
||||
mat->set(rMat.toEuler(), rMat.getPosition() + mPosOffset);
|
||||
}
|
||||
else
|
||||
{
|
||||
mat->set(mRotOffset.asEulerF(), mPosOffset);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
RenderComponentInterface *renderInterface = mOwner->getComponent<RenderComponentInterface>();
|
||||
|
||||
if (!renderInterface)
|
||||
return false;
|
||||
|
||||
if (mUseParentTransform)
|
||||
{
|
||||
MatrixF rMat = mOwner->getRenderTransform();
|
||||
|
||||
Point3F position = rMat.getPosition();
|
||||
|
||||
RotationF rot = mRotOffset;
|
||||
|
||||
if (mTargetNodeIdx != -1)
|
||||
{
|
||||
Point3F nodPos;
|
||||
MatrixF nodeTrans = renderInterface->getNodeTransform(mTargetNodeIdx);
|
||||
nodeTrans.getColumn(3, &nodPos);
|
||||
|
||||
// Scale the camera position before applying the transform
|
||||
const Point3F& scale = mOwner->getScale();
|
||||
nodPos.convolve(scale);
|
||||
|
||||
mOwner->getRenderTransform().mulP(nodPos, &position);
|
||||
|
||||
nodeTrans.mul(rMat);
|
||||
|
||||
rot = nodeTrans;
|
||||
}
|
||||
|
||||
position += mPosOffset;
|
||||
|
||||
MatrixF rotMat = rot.asMatrixF();
|
||||
|
||||
MatrixF rotOffsetMat = mRotOffset.asMatrixF();
|
||||
|
||||
rotMat.mul(rotOffsetMat);
|
||||
|
||||
rot = RotationF(rotMat);
|
||||
|
||||
mat->set(rot.asEulerF(), position);
|
||||
}
|
||||
else
|
||||
{
|
||||
MatrixF rMat = mOwner->getRenderTransform();
|
||||
|
||||
Point3F position = rMat.getPosition();
|
||||
|
||||
RotationF rot = mRotOffset;
|
||||
|
||||
if (mTargetNodeIdx != -1)
|
||||
{
|
||||
Point3F nodPos;
|
||||
MatrixF nodeTrans = renderInterface->getNodeTransform(mTargetNodeIdx);
|
||||
nodeTrans.getColumn(3, &nodPos);
|
||||
|
||||
// Scale the camera position before applying the transform
|
||||
const Point3F& scale = mOwner->getScale();
|
||||
nodPos.convolve(scale);
|
||||
|
||||
position = nodPos;
|
||||
}
|
||||
|
||||
position += mPosOffset;
|
||||
|
||||
mat->set(rot.asEulerF(), position);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
void CameraComponent::getCameraParameters(F32 *min, F32* max, Point3F* off, MatrixF* rot)
|
||||
{
|
||||
*min = 0.2f;
|
||||
*max = 0.f;
|
||||
off->set(0, 0, 0);
|
||||
rot->identity();
|
||||
}
|
||||
|
||||
U32 CameraComponent::packUpdate(NetConnection *con, U32 mask, BitStream *stream)
|
||||
{
|
||||
U32 retmask = Parent::packUpdate(con, mask, stream);
|
||||
|
||||
if (stream->writeFlag(mask & FOVMask))
|
||||
{
|
||||
stream->write(mCameraFov);
|
||||
}
|
||||
|
||||
if (stream->writeFlag(mask & OffsetMask))
|
||||
{
|
||||
RenderComponentInterface* renderInterface = getOwner()->getComponent<RenderComponentInterface>();
|
||||
|
||||
if (renderInterface && renderInterface->getShape())
|
||||
{
|
||||
S32 nodeIndex = renderInterface->getShape()->findNode(mTargetNode);
|
||||
|
||||
mTargetNodeIdx = nodeIndex;
|
||||
}
|
||||
|
||||
stream->writeInt(mTargetNodeIdx, 32);
|
||||
//send offsets here
|
||||
|
||||
stream->writeCompressedPoint(mPosOffset);
|
||||
stream->writeCompressedPoint(mRotOffset.asEulerF());
|
||||
|
||||
stream->writeFlag(mUseParentTransform);
|
||||
}
|
||||
|
||||
return retmask;
|
||||
}
|
||||
|
||||
void CameraComponent::unpackUpdate(NetConnection *con, BitStream *stream)
|
||||
{
|
||||
Parent::unpackUpdate(con, stream);
|
||||
|
||||
if (stream->readFlag())
|
||||
{
|
||||
F32 fov;
|
||||
stream->read(&fov);
|
||||
setCameraFov(fov);
|
||||
}
|
||||
|
||||
if(stream->readFlag())
|
||||
{
|
||||
mTargetNodeIdx = stream->readInt(32);
|
||||
|
||||
stream->readCompressedPoint(&mPosOffset);
|
||||
|
||||
EulerF rot;
|
||||
stream->readCompressedPoint(&rot);
|
||||
|
||||
mRotOffset = RotationF(rot);
|
||||
|
||||
mUseParentTransform = stream->readFlag();
|
||||
}
|
||||
}
|
||||
|
||||
void CameraComponent::setForwardVector(VectorF newForward, VectorF upVector)
|
||||
{
|
||||
MatrixF mat;
|
||||
F32 pos = 0;
|
||||
getCameraTransform(&pos, &mat);
|
||||
|
||||
mPosOffset = mat.getPosition();
|
||||
|
||||
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);
|
||||
|
||||
mRotOffset = RotationF(mat.toEuler());
|
||||
mRotOffset.y = 0;
|
||||
|
||||
setMaskBits(OffsetMask);
|
||||
}
|
||||
|
||||
void CameraComponent::setPosition(Point3F newPos)
|
||||
{
|
||||
mPosOffset = newPos;
|
||||
setMaskBits(OffsetMask);
|
||||
}
|
||||
|
||||
void CameraComponent::setRotation(RotationF newRot)
|
||||
{
|
||||
mRotOffset = newRot;
|
||||
setMaskBits(OffsetMask);
|
||||
}
|
||||
|
||||
Frustum CameraComponent::getFrustum()
|
||||
{
|
||||
Frustum visFrustum;
|
||||
F32 left, right, top, bottom;
|
||||
F32 aspectRatio = mClientScreen.x / mClientScreen.y;
|
||||
|
||||
visFrustum.set(false, mDegToRad(mCameraFov), aspectRatio, 0.1f, 1000, mOwner->getTransform());
|
||||
|
||||
return visFrustum;
|
||||
}
|
||||
159
Engine/source/T3D/components/Camera/CameraComponent.h
Normal file
159
Engine/source/T3D/components/Camera/CameraComponent.h
Normal file
|
|
@ -0,0 +1,159 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// 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 CAMERA_COMPONENT_H
|
||||
#define CAMERA_COMPONENT_H
|
||||
|
||||
#ifndef COMPONENT_H
|
||||
#include "T3D/Components/Component.h"
|
||||
#endif
|
||||
#ifndef _SCENERENDERSTATE_H_
|
||||
#include "scene/sceneRenderState.h"
|
||||
#endif
|
||||
#ifndef _MBOX_H_
|
||||
#include "math/mBox.h"
|
||||
#endif
|
||||
#ifndef ENTITY_H
|
||||
#include "T3D/Entity.h"
|
||||
#endif
|
||||
#ifndef CORE_INTERFACES_H
|
||||
#include "T3D/Components/coreInterfaces.h"
|
||||
#endif
|
||||
|
||||
class SceneRenderState;
|
||||
struct CameraScopeQuery;
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
///
|
||||
///
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
class CameraComponent : public Component, public CameraInterface
|
||||
{
|
||||
typedef Component Parent;
|
||||
|
||||
F32 mCameraFov; ///< The camera vertical FOV in degrees.
|
||||
|
||||
Point2F mClientScreen; ///< The dimensions of the client's screen. Used to calculate the aspect ratio.
|
||||
|
||||
F32 mCameraDefaultFov; ///< Default vertical FOV in degrees.
|
||||
F32 mCameraMinFov; ///< Min vertical FOV allowed in degrees.
|
||||
F32 mCameraMaxFov; ///< Max vertical FOV allowed in degrees.
|
||||
|
||||
protected:
|
||||
Point3F mPosOffset;
|
||||
RotationF mRotOffset;
|
||||
|
||||
StringTableEntry mTargetNode;
|
||||
S32 mTargetNodeIdx;
|
||||
|
||||
bool mUseParentTransform;
|
||||
|
||||
enum
|
||||
{
|
||||
FOVMask = Parent::NextFreeMask,
|
||||
OffsetMask = Parent::NextFreeMask << 1,
|
||||
NextFreeMask = Parent::NextFreeMask << 2,
|
||||
};
|
||||
|
||||
public:
|
||||
CameraComponent();
|
||||
virtual ~CameraComponent();
|
||||
DECLARE_CONOBJECT(CameraComponent);
|
||||
|
||||
virtual bool onAdd();
|
||||
virtual void onRemove();
|
||||
static void initPersistFields();
|
||||
|
||||
static bool _setCameraFov(void *object, const char *index, const char *data);
|
||||
|
||||
virtual U32 packUpdate(NetConnection *con, U32 mask, BitStream *stream);
|
||||
virtual void unpackUpdate(NetConnection *con, BitStream *stream);
|
||||
|
||||
static bool _setNode(void *object, const char *index, const char *data);
|
||||
static bool _setPosOffset(void *object, const char *index, const char *data);
|
||||
static bool _setRotOffset(void *object, const char *index, const char *data);
|
||||
|
||||
void setRotOffset(RotationF rot)
|
||||
{
|
||||
mRotOffset = rot;
|
||||
setMaskBits(OffsetMask);
|
||||
}
|
||||
|
||||
RotationF getRotOffset()
|
||||
{
|
||||
return mRotOffset;
|
||||
}
|
||||
|
||||
Point3F getPosOffset()
|
||||
{
|
||||
return mPosOffset;
|
||||
}
|
||||
|
||||
/// Gets the minimum viewing distance, maximum viewing distance, camera offsetand rotation
|
||||
/// for this object, if the world were to be viewed through its eyes
|
||||
/// @param min Minimum viewing distance
|
||||
/// @param max Maximum viewing distance
|
||||
/// @param offset Offset of the camera from the origin in local space
|
||||
/// @param rot Rotation matrix
|
||||
virtual void getCameraParameters(F32 *min, F32* max, Point3F* offset, MatrixF* rot);
|
||||
|
||||
/// Gets the camera to world space transform matrix
|
||||
/// @todo Find out what pos does
|
||||
/// @param pos TODO: Find out what this does
|
||||
/// @param mat Camera transform (out)
|
||||
virtual bool getCameraTransform(F32* pos, MatrixF* mat);
|
||||
|
||||
/// Returns the vertical field of view in degrees for
|
||||
/// this object if used as a camera.
|
||||
virtual F32 getCameraFov() { return mCameraFov; }
|
||||
|
||||
/// Returns the default vertical field of view in degrees
|
||||
/// if this object is used as a camera.
|
||||
virtual F32 getDefaultCameraFov() { return mCameraDefaultFov; }
|
||||
|
||||
/// Sets the vertical field of view in degrees for this
|
||||
/// object if used as a camera.
|
||||
/// @param yfov The vertical FOV in degrees to test.
|
||||
virtual void setCameraFov(F32 fov);
|
||||
|
||||
/// Returns true if the vertical FOV in degrees is within
|
||||
/// allowable parameters of the datablock.
|
||||
/// @param yfov The vertical FOV in degrees to test.
|
||||
/// @see ShapeBaseData::cameraMinFov
|
||||
/// @see ShapeBaseData::cameraMaxFov
|
||||
virtual bool isValidCameraFov(F32 fov);
|
||||
/// @}
|
||||
|
||||
virtual Frustum getFrustum();
|
||||
|
||||
/// Control object scoping
|
||||
void onCameraScopeQuery(NetConnection *cr, CameraScopeQuery *camInfo);
|
||||
|
||||
void setForwardVector(VectorF newForward, VectorF upVector = VectorF::Zero);
|
||||
void setPosition(Point3F newPos);
|
||||
void setRotation(RotationF newRot);
|
||||
|
||||
protected:
|
||||
DECLARE_CALLBACK(F32, validateCameraFov, (F32 fov));
|
||||
};
|
||||
|
||||
#endif // CAMERA_BEHAVIOR_H
|
||||
|
|
@ -0,0 +1,91 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// 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 "console/engineAPI.h"
|
||||
#include "T3D/Components/Camera/CameraComponent.h"
|
||||
|
||||
//Basically, this only exists for backwards compatibility for parts of the editors
|
||||
ConsoleMethod(CameraComponent, getMode, const char*, 2, 2, "() - We get the first behavior of the requested type on our owner object.\n"
|
||||
"@return (string name) The type of the behavior we're requesting")
|
||||
{
|
||||
return "fly";
|
||||
}
|
||||
|
||||
DefineConsoleMethod(CameraComponent, getForwardVector, VectorF, (), ,
|
||||
"Get the number of static fields on the object.\n"
|
||||
"@return The number of static fields defined on the object.")
|
||||
{
|
||||
F32 pos = 0;
|
||||
MatrixF cameraMat;
|
||||
object->getCameraTransform(&pos, &cameraMat);
|
||||
|
||||
VectorF returnVec = cameraMat.getForwardVector();
|
||||
returnVec = VectorF(mRadToDeg(returnVec.x), mRadToDeg(returnVec.y), mRadToDeg(returnVec.z));
|
||||
returnVec.normalize();
|
||||
return returnVec;
|
||||
}
|
||||
|
||||
DefineConsoleMethod(CameraComponent, getRightVector, VectorF, (), ,
|
||||
"Get the number of static fields on the object.\n"
|
||||
"@return The number of static fields defined on the object.")
|
||||
{
|
||||
F32 pos = 0;
|
||||
MatrixF cameraMat;
|
||||
object->getCameraTransform(&pos, &cameraMat);
|
||||
|
||||
VectorF returnVec = cameraMat.getRightVector();
|
||||
returnVec = VectorF(mRadToDeg(returnVec.x), mRadToDeg(returnVec.y), mRadToDeg(returnVec.z));
|
||||
returnVec.normalize();
|
||||
return returnVec;
|
||||
}
|
||||
|
||||
DefineConsoleMethod(CameraComponent, getUpVector, VectorF, (), ,
|
||||
"Get the number of static fields on the object.\n"
|
||||
"@return The number of static fields defined on the object.")
|
||||
{
|
||||
F32 pos = 0;
|
||||
MatrixF cameraMat;
|
||||
object->getCameraTransform(&pos, &cameraMat);
|
||||
|
||||
VectorF returnVec = cameraMat.getUpVector();
|
||||
returnVec = VectorF(mRadToDeg(returnVec.x), mRadToDeg(returnVec.y), mRadToDeg(returnVec.z));
|
||||
returnVec.normalize();
|
||||
return returnVec;
|
||||
}
|
||||
|
||||
DefineConsoleMethod(CameraComponent, 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(CameraComponent, getWorldPosition, Point3F, (), ,
|
||||
"Get the number of static fields on the object.\n"
|
||||
"@return The number of static fields defined on the object.")
|
||||
{
|
||||
F32 pos = 0;
|
||||
MatrixF mat;
|
||||
object->getCameraTransform(&pos, &mat);
|
||||
|
||||
return mat.getPosition();
|
||||
}
|
||||
146
Engine/source/T3D/components/Camera/CameraOrbiterComponent.cpp
Normal file
146
Engine/source/T3D/components/Camera/CameraOrbiterComponent.cpp
Normal file
|
|
@ -0,0 +1,146 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// 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 "T3D/Components/Camera/CameraOrbiterComponent.h"
|
||||
#include "core/util/safeDelete.h"
|
||||
#include "console/consoleTypes.h"
|
||||
#include "console/consoleObject.h"
|
||||
#include "core/stream/bitStream.h"
|
||||
#include "console/engineAPI.h"
|
||||
#include "sim/netConnection.h"
|
||||
#include "math/mathUtils.h"
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// Constructor/Destructor
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
CameraOrbiterComponent::CameraOrbiterComponent() : Component()
|
||||
{
|
||||
mMinOrbitDist = 0.0f;
|
||||
mMaxOrbitDist = 0.0f;
|
||||
mCurOrbitDist = 8.0f;
|
||||
mPosition.set(0.0f, 0.0f, 0.0f);
|
||||
|
||||
mMaxPitchAngle = 70;
|
||||
mMinPitchAngle = -10;
|
||||
|
||||
mRotation.set(0, 0, 0);
|
||||
|
||||
mCamera = NULL;
|
||||
}
|
||||
|
||||
CameraOrbiterComponent::~CameraOrbiterComponent()
|
||||
{
|
||||
}
|
||||
|
||||
IMPLEMENT_CO_NETOBJECT_V1(CameraOrbiterComponent);
|
||||
|
||||
bool CameraOrbiterComponent::onAdd()
|
||||
{
|
||||
if (!Parent::onAdd())
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void CameraOrbiterComponent::onRemove()
|
||||
{
|
||||
Parent::onRemove();
|
||||
}
|
||||
void CameraOrbiterComponent::initPersistFields()
|
||||
{
|
||||
Parent::initPersistFields();
|
||||
|
||||
addField("orbitDistance", TypeF32, Offset(mCurOrbitDist, CameraOrbiterComponent), "Object world orientation.");
|
||||
addField("Rotation", TypeRotationF, Offset(mRotation, CameraOrbiterComponent), "Object world orientation.");
|
||||
addField("maxPitchAngle", TypeF32, Offset(mMaxPitchAngle, CameraOrbiterComponent), "Object world orientation.");
|
||||
addField("minPitchAngle", TypeF32, Offset(mMinPitchAngle, CameraOrbiterComponent), "Object world orientation.");
|
||||
}
|
||||
|
||||
//This is mostly a catch for situations where the behavior is re-added to the object and the like and we may need to force an update to the behavior
|
||||
void CameraOrbiterComponent::onComponentAdd()
|
||||
{
|
||||
Parent::onComponentAdd();
|
||||
|
||||
CameraComponent *cam = mOwner->getComponent<CameraComponent>();
|
||||
if (cam)
|
||||
{
|
||||
mCamera = cam;
|
||||
}
|
||||
}
|
||||
|
||||
void CameraOrbiterComponent::onComponentRemove()
|
||||
{
|
||||
Parent::onComponentRemove();
|
||||
}
|
||||
|
||||
U32 CameraOrbiterComponent::packUpdate(NetConnection *con, U32 mask, BitStream *stream)
|
||||
{
|
||||
U32 retMask = Parent::packUpdate(con, mask, stream);
|
||||
return retMask;
|
||||
}
|
||||
|
||||
void CameraOrbiterComponent::unpackUpdate(NetConnection *con, BitStream *stream)
|
||||
{
|
||||
Parent::unpackUpdate(con, stream);
|
||||
}
|
||||
|
||||
void CameraOrbiterComponent::processTick()
|
||||
{
|
||||
Parent::processTick();
|
||||
|
||||
if (!mOwner)
|
||||
return;
|
||||
|
||||
if (mCamera)
|
||||
{
|
||||
//Clamp our pitch to whatever range we allow, first.
|
||||
mRotation.x = mClampF(mRotation.x, mDegToRad(mMinPitchAngle), mDegToRad(mMaxPitchAngle));
|
||||
|
||||
MatrixF ownerTrans = mOwner->getRenderTransform();
|
||||
Point3F ownerPos = ownerTrans.getPosition();
|
||||
|
||||
Point3F pos;
|
||||
pos.x = mCurOrbitDist * mSin(mRotation.x + M_HALFPI_F) * mCos(-1.0f * (mRotation.z + M_HALFPI_F));
|
||||
pos.y = mCurOrbitDist * mSin(mRotation.x + M_HALFPI_F) * mSin(-1.0f * (mRotation.z + M_HALFPI_F));
|
||||
pos.z = mCurOrbitDist * mSin(mRotation.x);
|
||||
|
||||
//orient the camera towards the owner
|
||||
VectorF ownerVec = ownerPos - pos;
|
||||
ownerVec.normalize();
|
||||
|
||||
MatrixF xRot, zRot, cameraMatrix;
|
||||
xRot.set(EulerF(mRotation.x, 0.0f, 0.0f));
|
||||
zRot.set(EulerF(0.0f, 0.0f, mRotation.z));
|
||||
|
||||
cameraMatrix.mul(zRot, xRot);
|
||||
cameraMatrix.getColumn(1, &ownerVec);
|
||||
cameraMatrix.setColumn(3, pos - ownerVec * pos);
|
||||
|
||||
RotationF camRot = RotationF(cameraMatrix);
|
||||
|
||||
if (camRot != mCamera->getRotOffset())
|
||||
mCamera->setRotation(camRot);
|
||||
|
||||
if (pos != mCamera->getPosOffset())
|
||||
mCamera->setPosition(pos);
|
||||
}
|
||||
}
|
||||
71
Engine/source/T3D/components/Camera/CameraOrbiterComponent.h
Normal file
71
Engine/source/T3D/components/Camera/CameraOrbiterComponent.h
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// 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 CAMERA_ORBITER_COMPONENT_H
|
||||
#define CAMERA_ORBITER_COMPONENT_H
|
||||
|
||||
#ifndef COMPONENT_H
|
||||
#include "T3D/Components/Component.h"
|
||||
#endif
|
||||
#ifndef CAMERA_COMPONENT_H
|
||||
#include "T3D/Components/camera/cameraComponent.h"
|
||||
#endif
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
///
|
||||
///
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
class CameraOrbiterComponent : public Component
|
||||
{
|
||||
typedef Component Parent;
|
||||
|
||||
F32 mMinOrbitDist;
|
||||
F32 mMaxOrbitDist;
|
||||
F32 mCurOrbitDist;
|
||||
Point3F mPosition;
|
||||
|
||||
F32 mMaxPitchAngle;
|
||||
F32 mMinPitchAngle;
|
||||
|
||||
RotationF mRotation;
|
||||
|
||||
CameraComponent* mCamera;
|
||||
|
||||
public:
|
||||
CameraOrbiterComponent();
|
||||
virtual ~CameraOrbiterComponent();
|
||||
DECLARE_CONOBJECT(CameraOrbiterComponent);
|
||||
|
||||
virtual bool onAdd();
|
||||
virtual void onRemove();
|
||||
static void initPersistFields();
|
||||
|
||||
virtual void onComponentAdd();
|
||||
virtual void onComponentRemove();
|
||||
|
||||
virtual U32 packUpdate(NetConnection *con, U32 mask, BitStream *stream);
|
||||
virtual void unpackUpdate(NetConnection *con, BitStream *stream);
|
||||
|
||||
virtual void processTick();
|
||||
};
|
||||
|
||||
#endif // EXAMPLEBEHAVIOR_H
|
||||
|
|
@ -0,0 +1,172 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// 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 "console/engineAPI.h"
|
||||
#include "T3D/Components/Collision/CollisionComponent.h"
|
||||
#include "materials/baseMatInstance.h"
|
||||
|
||||
DefineConsoleMethod(CollisionComponent, getNumberOfContacts, S32, (), ,
|
||||
"Gets the number of contacts this collider has hit.\n"
|
||||
"@return The number of static fields defined on the object.")
|
||||
{
|
||||
return object->getCollisionList()->getCount();
|
||||
}
|
||||
|
||||
DefineConsoleMethod(CollisionComponent, getBestContact, S32, (), ,
|
||||
"Gets the number of contacts this collider has hit.\n"
|
||||
"@return The number of static fields defined on the object.")
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
DefineConsoleMethod(CollisionComponent, getContactNormal, Point3F, (), ,
|
||||
"Gets the number of contacts this collider has hit.\n"
|
||||
"@return The number of static fields defined on the object.")
|
||||
{
|
||||
if (object->getContactInfo())
|
||||
{
|
||||
if (object->getContactInfo()->contactObject)
|
||||
{
|
||||
return object->getContactInfo()->contactNormal;
|
||||
}
|
||||
}
|
||||
|
||||
return Point3F::Zero;
|
||||
}
|
||||
|
||||
DefineConsoleMethod(CollisionComponent, getContactMaterial, S32, (), ,
|
||||
"Gets the number of contacts this collider has hit.\n"
|
||||
"@return The number of static fields defined on the object.")
|
||||
{
|
||||
if (object->getContactInfo())
|
||||
{
|
||||
if (object->getContactInfo()->contactObject)
|
||||
{
|
||||
if (object->getContactInfo()->contactMaterial != NULL)
|
||||
return object->getContactInfo()->contactMaterial->getMaterial()->getId();
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
DefineConsoleMethod(CollisionComponent, getContactObject, S32, (), ,
|
||||
"Gets the number of contacts this collider has hit.\n"
|
||||
"@return The number of static fields defined on the object.")
|
||||
{
|
||||
if (object->getContactInfo())
|
||||
{
|
||||
return object->getContactInfo()->contactObject != NULL ? object->getContactInfo()->contactObject->getId() : 0;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
DefineConsoleMethod(CollisionComponent, getContactPoint, Point3F, (), ,
|
||||
"Gets the number of contacts this collider has hit.\n"
|
||||
"@return The number of static fields defined on the object.")
|
||||
{
|
||||
if (object->getContactInfo())
|
||||
{
|
||||
if (object->getContactInfo()->contactObject)
|
||||
{
|
||||
return object->getContactInfo()->contactPoint;
|
||||
}
|
||||
}
|
||||
|
||||
return Point3F::Zero;
|
||||
}
|
||||
|
||||
DefineConsoleMethod(CollisionComponent, getContactTime, S32, (), ,
|
||||
"Gets the number of contacts this collider has hit.\n"
|
||||
"@return The number of static fields defined on the object.")
|
||||
{
|
||||
if (object->getContactInfo())
|
||||
{
|
||||
if (object->getContactInfo()->contactObject)
|
||||
{
|
||||
return object->getContactInfo()->contactTimer;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
DefineEngineMethod(CollisionComponent, hasContact, bool, (), ,
|
||||
"@brief Apply an impulse to this object as defined by a world position and velocity vector.\n\n"
|
||||
|
||||
"@param pos impulse world position\n"
|
||||
"@param vel impulse velocity (impulse force F = m * v)\n"
|
||||
"@return Always true\n"
|
||||
|
||||
"@note Not all objects that derrive from GameBase have this defined.\n")
|
||||
{
|
||||
return object->hasContact();
|
||||
}
|
||||
|
||||
DefineEngineMethod(CollisionComponent, getCollisionCount, S32, (), ,
|
||||
"@brief Apply an impulse to this object as defined by a world position and velocity vector.\n\n"
|
||||
|
||||
"@param pos impulse world position\n"
|
||||
"@param vel impulse velocity (impulse force F = m * v)\n"
|
||||
"@return Always true\n"
|
||||
|
||||
"@note Not all objects that derrive from GameBase have this defined.\n")
|
||||
{
|
||||
return object->getCollisionCount();
|
||||
}
|
||||
|
||||
DefineEngineMethod(CollisionComponent, getCollisionNormal, Point3F, (S32 collisionIndex), ,
|
||||
"@brief Apply an impulse to this object as defined by a world position and velocity vector.\n\n"
|
||||
|
||||
"@param pos impulse world position\n"
|
||||
"@param vel impulse velocity (impulse force F = m * v)\n"
|
||||
"@return Always true\n"
|
||||
|
||||
"@note Not all objects that derrive from GameBase have this defined.\n")
|
||||
{
|
||||
return object->getCollisionNormal(collisionIndex);
|
||||
}
|
||||
|
||||
DefineEngineMethod(CollisionComponent, getCollisionAngle, F32, (S32 collisionIndex, VectorF upVector), ,
|
||||
"@brief Apply an impulse to this object as defined by a world position and velocity vector.\n\n"
|
||||
|
||||
"@param pos impulse world position\n"
|
||||
"@param vel impulse velocity (impulse force F = m * v)\n"
|
||||
"@return Always true\n"
|
||||
|
||||
"@note Not all objects that derrive from GameBase have this defined.\n")
|
||||
{
|
||||
return object->getCollisionAngle(collisionIndex, upVector);
|
||||
}
|
||||
|
||||
DefineEngineMethod(CollisionComponent, getBestCollisionAngle, F32, (VectorF upVector), ,
|
||||
"@brief Apply an impulse to this object as defined by a world position and velocity vector.\n\n"
|
||||
|
||||
"@param pos impulse world position\n"
|
||||
"@param vel impulse velocity (impulse force F = m * v)\n"
|
||||
"@return Always true\n"
|
||||
|
||||
"@note Not all objects that derrive from GameBase have this defined.\n")
|
||||
{
|
||||
return object->getBestCollisionAngle(upVector);
|
||||
}
|
||||
582
Engine/source/T3D/components/Collision/collisionComponent.cpp
Normal file
582
Engine/source/T3D/components/Collision/collisionComponent.cpp
Normal file
|
|
@ -0,0 +1,582 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// 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 "T3D/Components/Collision/collisionComponent.h"
|
||||
#include "T3D/Components/Collision/collisionComponent_ScriptBinding.h"
|
||||
#include "T3D/Components/Physics/physicsBehavior.h"
|
||||
#include "console/consoleTypes.h"
|
||||
#include "core/util/safeDelete.h"
|
||||
#include "core/resourceManager.h"
|
||||
#include "console/consoleTypes.h"
|
||||
#include "console/consoleObject.h"
|
||||
#include "core/stream/bitStream.h"
|
||||
#include "scene/sceneRenderState.h"
|
||||
#include "gfx/gfxTransformSaver.h"
|
||||
#include "gfx/gfxDrawUtil.h"
|
||||
#include "console/engineAPI.h"
|
||||
#include "T3D/physics/physicsPlugin.h"
|
||||
#include "T3D/physics/physicsBody.h"
|
||||
#include "T3D/physics/physicsCollision.h"
|
||||
#include "T3D/gameBase/gameConnection.h"
|
||||
#include "collision/extrudedPolyList.h"
|
||||
#include "math/mathIO.h"
|
||||
#include "gfx/sim/debugDraw.h"
|
||||
#include "collision/concretePolyList.h"
|
||||
|
||||
#include "T3D/trigger.h"
|
||||
#include "opcode/Opcode.h"
|
||||
#include "opcode/Ice/IceAABB.h"
|
||||
#include "opcode/Ice/IcePoint.h"
|
||||
#include "opcode/OPC_AABBTree.h"
|
||||
#include "opcode/OPC_AABBCollider.h"
|
||||
|
||||
#include "math/mathUtils.h"
|
||||
#include "materials/baseMatInstance.h"
|
||||
#include "collision/vertexPolyList.h"
|
||||
|
||||
extern bool gEditingMission;
|
||||
|
||||
static bool sRenderColliders = false;
|
||||
|
||||
//Docs
|
||||
ConsoleDocClass(CollisionComponent,
|
||||
"@brief The Box Collider component uses a box or rectangular convex shape for collisions.\n\n"
|
||||
|
||||
"Colliders are individualized components that are similarly based off the CollisionInterface core.\n"
|
||||
"They are basically the entire functionality of how Torque handles collisions compacted into a single component.\n"
|
||||
"A collider will both collide against and be collided with, other entities.\n"
|
||||
"Individual colliders will offer different shapes. This box collider will generate a box/rectangle convex, \n"
|
||||
"while the mesh collider will take the owner Entity's rendered shape and do polysoup collision on it, etc.\n\n"
|
||||
|
||||
"The general flow of operations for how collisions happen is thus:\n"
|
||||
" -When the component is added(or updated) prepCollision() is called.\n"
|
||||
" This will set up our initial convex shape for usage later.\n\n"
|
||||
|
||||
" -When we update via processTick(), we first test if our entity owner is mobile.\n"
|
||||
" If our owner isn't mobile(as in, they have no components that provide it a velocity to move)\n"
|
||||
" then we skip doing our active collision checks. Collisions are checked by the things moving, as\n"
|
||||
" opposed to being reactionary. If we're moving, we call updateWorkingCollisionSet().\n"
|
||||
" updateWorkingCollisionSet() estimates our bounding space for our current ticket based on our position and velocity.\n"
|
||||
" If our bounding space has changed since the last tick, we proceed to call updateWorkingList() on our convex.\n"
|
||||
" This notifies any object in the bounding space that they may be collided with, so they will call buildConvex().\n"
|
||||
" buildConvex() will set up our ConvexList with our collision convex info.\n\n"
|
||||
|
||||
" -When the component that is actually causing our movement, such as SimplePhysicsBehavior, updates, it will check collisions.\n"
|
||||
" It will call checkCollisions() on us. checkCollisions() will first build a bounding shape for our convex, and test\n"
|
||||
" if we can early out because we won't hit anything based on our starting point, velocity, and tick time.\n"
|
||||
" If we don't early out, we proceed to call updateCollisions(). This builds an ExtrudePolyList, which is then extruded\n"
|
||||
" based on our velocity. We then test our extruded polies on our working list of objects we build\n"
|
||||
" up earlier via updateWorkingCollisionSet. Any collisions that happen here will be added to our mCollisionList.\n"
|
||||
" Finally, we call handleCollisionList() on our collisionList, which then queues out the colliison notice\n"
|
||||
" to the object(s) we collided with so they can do callbacks and the like. We also report back on if we did collide\n"
|
||||
" to the physics component via our bool return in checkCollisions() so it can make the physics react accordingly.\n\n"
|
||||
|
||||
"One interesting point to note is the usage of mBlockColliding.\n"
|
||||
"This is set so that it dictates the return on checkCollisions(). If set to false, it will ensure checkCollisions()\n"
|
||||
"will return false, regardless if we actually collided. This is useful, because even if checkCollisions() returns false,\n"
|
||||
"we still handle the collisions so the callbacks happen. This enables us to apply a collider to an object that doesn't block\n"
|
||||
"objects, but does have callbacks, so it can act as a trigger, allowing for arbitrarily shaped triggers, as any collider can\n"
|
||||
"act as a trigger volume(including MeshCollider).\n\n"
|
||||
|
||||
"@tsexample\n"
|
||||
"new CollisionComponentInstance()\n"
|
||||
"{\n"
|
||||
" template = CollisionComponentTemplate;\n"
|
||||
" colliderSize = \"1 1 2\";\n"
|
||||
" blockColldingObject = \"1\";\n"
|
||||
"};\n"
|
||||
"@endtsexample\n"
|
||||
|
||||
"@see SimplePhysicsBehavior\n"
|
||||
"@ingroup Collision\n"
|
||||
"@ingroup Components\n"
|
||||
);
|
||||
//Docs
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
ImplementEnumType(CollisionMeshMeshType,
|
||||
"Type of mesh data available in a shape.\n"
|
||||
"@ingroup gameObjects")
|
||||
{ CollisionComponent::None, "None", "No mesh data." },
|
||||
{ CollisionComponent::Bounds, "Bounds", "Bounding box of the shape." },
|
||||
{ CollisionComponent::CollisionMesh, "Collision Mesh", "Specifically desingated \"collision\" meshes." },
|
||||
{ CollisionComponent::VisibleMesh, "Visible Mesh", "Rendered mesh polygons." },
|
||||
EndImplementEnumType;
|
||||
|
||||
//
|
||||
CollisionComponent::CollisionComponent() : Component()
|
||||
{
|
||||
mNetFlags.set(Ghostable | ScopeAlways);
|
||||
|
||||
mFriendlyName = "Collision(Component)";
|
||||
|
||||
mOwnerRenderInterface = NULL;
|
||||
mOwnerPhysicsInterface = NULL;
|
||||
|
||||
mBlockColliding = true;
|
||||
|
||||
mCollisionType = CollisionMesh;
|
||||
mLOSType = CollisionMesh;
|
||||
mDecalType = CollisionMesh;
|
||||
|
||||
colisionMeshPrefix = StringTable->insert("Collision");
|
||||
|
||||
CollisionMoveMask = (TerrainObjectType | PlayerObjectType |
|
||||
StaticShapeObjectType | VehicleObjectType |
|
||||
VehicleBlockerObjectType | DynamicShapeObjectType | StaticObjectType | EntityObjectType | TriggerObjectType);
|
||||
|
||||
mPhysicsRep = NULL;
|
||||
mPhysicsWorld = NULL;
|
||||
|
||||
mTimeoutList = NULL;
|
||||
}
|
||||
|
||||
CollisionComponent::~CollisionComponent()
|
||||
{
|
||||
for (S32 i = 0; i < mFields.size(); ++i)
|
||||
{
|
||||
ComponentField &field = mFields[i];
|
||||
SAFE_DELETE_ARRAY(field.mFieldDescription);
|
||||
}
|
||||
|
||||
SAFE_DELETE_ARRAY(mDescription);
|
||||
}
|
||||
|
||||
IMPLEMENT_CO_NETOBJECT_V1(CollisionComponent);
|
||||
|
||||
void CollisionComponent::onComponentAdd()
|
||||
{
|
||||
Parent::onComponentAdd();
|
||||
|
||||
RenderComponentInterface *renderInterface = mOwner->getComponent<RenderComponentInterface>();
|
||||
if (renderInterface)
|
||||
{
|
||||
renderInterface->onShapeInstanceChanged.notify(this, &CollisionComponent::targetShapeChanged);
|
||||
mOwnerRenderInterface = renderInterface;
|
||||
}
|
||||
|
||||
//physicsInterface
|
||||
PhysicsComponentInterface *physicsInterface = mOwner->getComponent<PhysicsComponentInterface>();
|
||||
if (!physicsInterface)
|
||||
{
|
||||
mPhysicsRep = PHYSICSMGR->createBody();
|
||||
}
|
||||
|
||||
prepCollision();
|
||||
}
|
||||
|
||||
void CollisionComponent::onComponentRemove()
|
||||
{
|
||||
SAFE_DELETE(mPhysicsRep);
|
||||
|
||||
Parent::onComponentRemove();
|
||||
}
|
||||
|
||||
void CollisionComponent::componentAddedToOwner(Component *comp)
|
||||
{
|
||||
if (comp->getId() == getId())
|
||||
return;
|
||||
|
||||
//test if this is a shape component!
|
||||
RenderComponentInterface *renderInterface = dynamic_cast<RenderComponentInterface*>(comp);
|
||||
if (renderInterface)
|
||||
{
|
||||
renderInterface->onShapeInstanceChanged.notify(this, &CollisionComponent::targetShapeChanged);
|
||||
mOwnerRenderInterface = renderInterface;
|
||||
prepCollision();
|
||||
}
|
||||
|
||||
PhysicsComponentInterface *physicsInterface = dynamic_cast<PhysicsComponentInterface*>(comp);
|
||||
if (physicsInterface)
|
||||
{
|
||||
if (mPhysicsRep)
|
||||
SAFE_DELETE(mPhysicsRep);
|
||||
|
||||
prepCollision();
|
||||
}
|
||||
}
|
||||
|
||||
void CollisionComponent::componentRemovedFromOwner(Component *comp)
|
||||
{
|
||||
if (comp->getId() == getId()) //?????????
|
||||
return;
|
||||
|
||||
//test if this is a shape component!
|
||||
RenderComponentInterface *renderInterface = dynamic_cast<RenderComponentInterface*>(comp);
|
||||
if (renderInterface)
|
||||
{
|
||||
renderInterface->onShapeInstanceChanged.remove(this, &CollisionComponent::targetShapeChanged);
|
||||
mOwnerRenderInterface = NULL;
|
||||
prepCollision();
|
||||
}
|
||||
|
||||
//physicsInterface
|
||||
PhysicsComponentInterface *physicsInterface = dynamic_cast<PhysicsComponentInterface*>(comp);
|
||||
if (physicsInterface)
|
||||
{
|
||||
mPhysicsRep = PHYSICSMGR->createBody();
|
||||
|
||||
prepCollision();
|
||||
}
|
||||
}
|
||||
|
||||
void CollisionComponent::checkDependencies()
|
||||
{
|
||||
}
|
||||
|
||||
void CollisionComponent::initPersistFields()
|
||||
{
|
||||
Parent::initPersistFields();
|
||||
|
||||
addGroup("Collision");
|
||||
|
||||
addField("CollisionType", TypeCollisionMeshMeshType, Offset(mCollisionType, CollisionComponent),
|
||||
"The type of mesh data to use for collision queries.");
|
||||
|
||||
addField("LineOfSightType", TypeCollisionMeshMeshType, Offset(mLOSType, CollisionComponent),
|
||||
"The type of mesh data to use for collision queries.");
|
||||
|
||||
addField("DecalType", TypeCollisionMeshMeshType, Offset(mDecalType, CollisionComponent),
|
||||
"The type of mesh data to use for collision queries.");
|
||||
|
||||
addField("CollisionMeshPrefix", TypeString, Offset(colisionMeshPrefix, CollisionComponent),
|
||||
"The type of mesh data to use for collision queries.");
|
||||
|
||||
addField("BlockCollisions", TypeBool, Offset(mBlockColliding, CollisionComponent), "");
|
||||
|
||||
endGroup("Collision");
|
||||
}
|
||||
|
||||
void CollisionComponent::inspectPostApply()
|
||||
{
|
||||
// Apply any transformations set in the editor
|
||||
Parent::inspectPostApply();
|
||||
|
||||
if (isServerObject())
|
||||
{
|
||||
setMaskBits(ColliderMask);
|
||||
prepCollision();
|
||||
}
|
||||
}
|
||||
|
||||
U32 CollisionComponent::packUpdate(NetConnection *con, U32 mask, BitStream *stream)
|
||||
{
|
||||
U32 retMask = Parent::packUpdate(con, mask, stream);
|
||||
|
||||
if (stream->writeFlag(mask & (ColliderMask | InitialUpdateMask)))
|
||||
{
|
||||
stream->write((U32)mCollisionType);
|
||||
stream->writeString(colisionMeshPrefix);
|
||||
}
|
||||
|
||||
return retMask;
|
||||
}
|
||||
|
||||
void CollisionComponent::unpackUpdate(NetConnection *con, BitStream *stream)
|
||||
{
|
||||
Parent::unpackUpdate(con, stream);
|
||||
|
||||
if (stream->readFlag()) // UpdateMask
|
||||
{
|
||||
U32 collisionType = CollisionMesh;
|
||||
|
||||
stream->read(&collisionType);
|
||||
|
||||
// Handle it if we have changed CollisionType's
|
||||
if ((MeshType)collisionType != mCollisionType)
|
||||
{
|
||||
mCollisionType = (MeshType)collisionType;
|
||||
|
||||
prepCollision();
|
||||
}
|
||||
|
||||
char readBuffer[1024];
|
||||
|
||||
stream->readString(readBuffer);
|
||||
colisionMeshPrefix = StringTable->insert(readBuffer);
|
||||
}
|
||||
}
|
||||
|
||||
void CollisionComponent::ownerTransformSet(MatrixF *mat)
|
||||
{
|
||||
if (mPhysicsRep)
|
||||
mPhysicsRep->setTransform(mOwner->getTransform());
|
||||
}
|
||||
|
||||
void CollisionComponent::targetShapeChanged(RenderComponentInterface* instanceInterface)
|
||||
{
|
||||
prepCollision();
|
||||
}
|
||||
|
||||
void CollisionComponent::prepCollision()
|
||||
{
|
||||
if (!mOwner)
|
||||
return;
|
||||
|
||||
// Let the client know that the collision was updated
|
||||
setMaskBits(ColliderMask);
|
||||
|
||||
mOwner->disableCollision();
|
||||
|
||||
if ((!PHYSICSMGR || mCollisionType == None) ||
|
||||
(mOwnerRenderInterface == NULL && (mCollisionType == CollisionMesh || mCollisionType == VisibleMesh)))
|
||||
return;
|
||||
|
||||
PhysicsCollision *colShape = NULL;
|
||||
|
||||
if (mCollisionType == Bounds)
|
||||
{
|
||||
MatrixF offset(true);
|
||||
|
||||
if (mOwnerRenderInterface && mOwnerRenderInterface->getShape())
|
||||
offset.setPosition(mOwnerRenderInterface->getShape()->center);
|
||||
|
||||
colShape = PHYSICSMGR->createCollision();
|
||||
colShape->addBox(mOwner->getObjBox().getExtents() * 0.5f * mOwner->getScale(), offset);
|
||||
}
|
||||
else if (mCollisionType == CollisionMesh || (mCollisionType == VisibleMesh /*&& !mOwner->getComponent<AnimatedMesh>()*/))
|
||||
{
|
||||
colShape = buildColShapes();
|
||||
}
|
||||
|
||||
if (colShape)
|
||||
{
|
||||
mPhysicsWorld = PHYSICSMGR->getWorld(isServerObject() ? "server" : "client");
|
||||
|
||||
if (mPhysicsRep)
|
||||
{
|
||||
if (mBlockColliding)
|
||||
mPhysicsRep->init(colShape, 0, 0, mOwner, mPhysicsWorld);
|
||||
else
|
||||
mPhysicsRep->init(colShape, 0, PhysicsBody::BF_TRIGGER, mOwner, mPhysicsWorld);
|
||||
|
||||
mPhysicsRep->setTransform(mOwner->getTransform());
|
||||
}
|
||||
}
|
||||
|
||||
mOwner->enableCollision();
|
||||
|
||||
onCollisionChanged.trigger(colShape);
|
||||
}
|
||||
|
||||
void CollisionComponent::processTick()
|
||||
{
|
||||
if (!isActive())
|
||||
return;
|
||||
|
||||
//ProcessTick is where our collision testing begins!
|
||||
|
||||
//callback if we have a persisting contact
|
||||
if (mContactInfo.contactObject)
|
||||
{
|
||||
if (mContactInfo.contactTimer > 0)
|
||||
{
|
||||
if (isMethod("updateContact"))
|
||||
Con::executef(this, "updateContact");
|
||||
|
||||
if (mOwner->isMethod("updateContact"))
|
||||
Con::executef(mOwner, "updateContact");
|
||||
}
|
||||
|
||||
++mContactInfo.contactTimer;
|
||||
}
|
||||
else if (mContactInfo.contactTimer != 0)
|
||||
mContactInfo.clear();
|
||||
}
|
||||
|
||||
void CollisionComponent::updatePhysics()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
PhysicsCollision* CollisionComponent::getCollisionData()
|
||||
{
|
||||
if ((!PHYSICSMGR || mCollisionType == None) || mOwnerRenderInterface == NULL)
|
||||
return NULL;
|
||||
|
||||
PhysicsCollision *colShape = NULL;
|
||||
if (mCollisionType == Bounds)
|
||||
{
|
||||
MatrixF offset(true);
|
||||
offset.setPosition(mOwnerRenderInterface->getShape()->center);
|
||||
colShape = PHYSICSMGR->createCollision();
|
||||
colShape->addBox(mOwner->getObjBox().getExtents() * 0.5f * mOwner->getScale(), offset);
|
||||
}
|
||||
else if (mCollisionType == CollisionMesh || (mCollisionType == VisibleMesh/* && !mOwner->getComponent<AnimatedMesh>()*/))
|
||||
{
|
||||
colShape = buildColShapes();
|
||||
//colShape = mOwnerShapeInstance->getShape()->buildColShape(mCollisionType == VisibleMesh, mOwner->getScale());
|
||||
}
|
||||
/*else if (mCollisionType == VisibleMesh && !mOwner->getComponent<AnimatedMesh>())
|
||||
{
|
||||
//We don't have support for visible mesh collisions with animated meshes currently in the physics abstraction layer
|
||||
//so we don't generate anything if we're set to use a visible mesh but have an animated mesh component.
|
||||
colShape = mOwnerShapeInstance->getShape()->buildColShape(mCollisionType == VisibleMesh, mOwner->getScale());
|
||||
}*/
|
||||
else if (mCollisionType == VisibleMesh/* && mOwner->getComponent<AnimatedMesh>()*/)
|
||||
{
|
||||
Con::printf("CollisionComponent::updatePhysics: Cannot use visible mesh collisions with an animated mesh!");
|
||||
}
|
||||
|
||||
return colShape;
|
||||
}
|
||||
|
||||
bool CollisionComponent::castRay(const Point3F &start, const Point3F &end, RayInfo* info)
|
||||
{
|
||||
if (!mCollisionType == None)
|
||||
{
|
||||
if (mPhysicsWorld)
|
||||
{
|
||||
return mPhysicsWorld->castRay(start, end, info, Point3F::Zero);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
PhysicsCollision* CollisionComponent::buildColShapes()
|
||||
{
|
||||
PROFILE_SCOPE(CollisionComponent_buildColShapes);
|
||||
|
||||
PhysicsCollision *colShape = NULL;
|
||||
U32 surfaceKey = 0;
|
||||
|
||||
TSShape* shape = mOwnerRenderInterface->getShape();
|
||||
|
||||
if (mCollisionType == VisibleMesh)
|
||||
{
|
||||
// Here we build triangle collision meshes from the
|
||||
// visible detail levels.
|
||||
|
||||
// A negative subshape on the detail means we don't have geometry.
|
||||
const TSShape::Detail &detail = shape->details[0];
|
||||
if (detail.subShapeNum < 0)
|
||||
return NULL;
|
||||
|
||||
// We don't try to optimize the triangles we're given
|
||||
// and assume the art was created properly for collision.
|
||||
ConcretePolyList polyList;
|
||||
polyList.setTransform(&MatrixF::Identity, mOwner->getScale());
|
||||
|
||||
// Create the collision meshes.
|
||||
S32 start = shape->subShapeFirstObject[detail.subShapeNum];
|
||||
S32 end = start + shape->subShapeNumObjects[detail.subShapeNum];
|
||||
for (S32 o = start; o < end; o++)
|
||||
{
|
||||
const TSShape::Object &object = shape->objects[o];
|
||||
if (detail.objectDetailNum >= object.numMeshes)
|
||||
continue;
|
||||
|
||||
// No mesh or no verts.... nothing to do.
|
||||
TSMesh *mesh = shape->meshes[object.startMeshIndex + detail.objectDetailNum];
|
||||
if (!mesh || mesh->mNumVerts == 0)
|
||||
continue;
|
||||
|
||||
// Gather the mesh triangles.
|
||||
polyList.clear();
|
||||
mesh->buildPolyList(0, &polyList, surfaceKey, NULL);
|
||||
|
||||
// Create the collision shape if we haven't already.
|
||||
if (!colShape)
|
||||
colShape = PHYSICSMGR->createCollision();
|
||||
|
||||
// Get the object space mesh transform.
|
||||
MatrixF localXfm;
|
||||
shape->getNodeWorldTransform(object.nodeIndex, &localXfm);
|
||||
|
||||
colShape->addTriangleMesh(polyList.mVertexList.address(),
|
||||
polyList.mVertexList.size(),
|
||||
polyList.mIndexList.address(),
|
||||
polyList.mIndexList.size() / 3,
|
||||
localXfm);
|
||||
}
|
||||
|
||||
// Return what we built... if anything.
|
||||
return colShape;
|
||||
}
|
||||
else if (mCollisionType == CollisionMesh)
|
||||
{
|
||||
|
||||
// Scan out the collision hulls...
|
||||
//
|
||||
// TODO: We need to support LOS collision for physics.
|
||||
//
|
||||
for (U32 i = 0; i < shape->details.size(); i++)
|
||||
{
|
||||
const TSShape::Detail &detail = shape->details[i];
|
||||
const String &name = shape->names[detail.nameIndex];
|
||||
|
||||
// Is this a valid collision detail.
|
||||
if (!dStrStartsWith(name, colisionMeshPrefix) || detail.subShapeNum < 0)
|
||||
continue;
|
||||
|
||||
// Now go thru the meshes for this detail.
|
||||
S32 start = shape->subShapeFirstObject[detail.subShapeNum];
|
||||
S32 end = start + shape->subShapeNumObjects[detail.subShapeNum];
|
||||
if (start >= end)
|
||||
continue;
|
||||
|
||||
for (S32 o = start; o < end; o++)
|
||||
{
|
||||
const TSShape::Object &object = shape->objects[o];
|
||||
const String &meshName = shape->names[object.nameIndex];
|
||||
|
||||
if (object.numMeshes <= detail.objectDetailNum)
|
||||
continue;
|
||||
|
||||
// No mesh, a flat bounds, or no verts.... nothing to do.
|
||||
TSMesh *mesh = shape->meshes[object.startMeshIndex + detail.objectDetailNum];
|
||||
if (!mesh || mesh->getBounds().isEmpty() || mesh->mNumVerts == 0)
|
||||
continue;
|
||||
|
||||
// We need the default mesh transform.
|
||||
MatrixF localXfm;
|
||||
shape->getNodeWorldTransform(object.nodeIndex, &localXfm);
|
||||
|
||||
// We have some sort of collision shape... so allocate it.
|
||||
if (!colShape)
|
||||
colShape = PHYSICSMGR->createCollision();
|
||||
|
||||
// Any other mesh name we assume as a generic convex hull.
|
||||
//
|
||||
// Collect the verts using the vertex polylist which will
|
||||
// filter out duplicates. This is importaint as the convex
|
||||
// generators can sometimes fail with duplicate verts.
|
||||
//
|
||||
VertexPolyList polyList;
|
||||
MatrixF meshMat(localXfm);
|
||||
|
||||
Point3F t = meshMat.getPosition();
|
||||
t.convolve(mOwner->getScale());
|
||||
meshMat.setPosition(t);
|
||||
|
||||
polyList.setTransform(&MatrixF::Identity, mOwner->getScale());
|
||||
mesh->buildPolyList(0, &polyList, surfaceKey, NULL);
|
||||
colShape->addConvex(polyList.getVertexList().address(),
|
||||
polyList.getVertexList().size(),
|
||||
meshMat);
|
||||
} // objects
|
||||
} // details
|
||||
}
|
||||
|
||||
return colShape;
|
||||
}
|
||||
208
Engine/source/T3D/components/Collision/collisionComponent.h
Normal file
208
Engine/source/T3D/components/Collision/collisionComponent.h
Normal file
|
|
@ -0,0 +1,208 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// 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 COLLISION_COMPONENT_H
|
||||
#define COLLISION_COMPONENT_H
|
||||
|
||||
#ifndef __RESOURCE_H__
|
||||
#include "core/resource.h"
|
||||
#endif
|
||||
#ifndef _TSSHAPE_H_
|
||||
#include "ts/tsShape.h"
|
||||
#endif
|
||||
#ifndef _SCENERENDERSTATE_H_
|
||||
#include "scene/sceneRenderState.h"
|
||||
#endif
|
||||
#ifndef _MBOX_H_
|
||||
#include "math/mBox.h"
|
||||
#endif
|
||||
#ifndef ENTITY_H
|
||||
#include "T3D/Entity.h"
|
||||
#endif
|
||||
#ifndef CORE_INTERFACES_H
|
||||
#include "T3D/Components/coreInterfaces.h"
|
||||
#endif
|
||||
#ifndef COLLISION_INTERFACES_H
|
||||
#include "T3D/Components/collision/collisionInterfaces.h"
|
||||
#endif
|
||||
#ifndef RENDER_COMPONENT_INTERFACE_H
|
||||
#include "T3D/Components/render/renderComponentInterface.h"
|
||||
#endif
|
||||
#ifndef PHYSICS_COMPONENT_INTERFACE_H
|
||||
#include "T3D/Components/physics/physicsComponentInterface.h"
|
||||
#endif
|
||||
#ifndef _T3D_PHYSICSCOMMON_H_
|
||||
#include "T3D/physics/physicsCommon.h"
|
||||
#endif
|
||||
#ifndef _T3D_PHYSICS_PHYSICSWORLD_H_
|
||||
#include "T3D/physics/physicsWorld.h"
|
||||
#endif
|
||||
|
||||
class TSShapeInstance;
|
||||
class SceneRenderState;
|
||||
class CollisionComponent;
|
||||
class PhysicsBody;
|
||||
class PhysicsWorld;
|
||||
|
||||
class CollisionComponent : public Component,
|
||||
public CollisionInterface,
|
||||
public CastRayInterface
|
||||
{
|
||||
typedef Component Parent;
|
||||
public:
|
||||
enum MeshType
|
||||
{
|
||||
None = 0, ///< No mesh
|
||||
Bounds = 1, ///< Bounding box of the shape
|
||||
CollisionMesh = 2, ///< Specifically designated collision meshes
|
||||
VisibleMesh = 3 ///< Rendered mesh polygons
|
||||
};
|
||||
|
||||
PhysicsWorld* mPhysicsWorld;
|
||||
PhysicsBody* mPhysicsRep;
|
||||
|
||||
protected:
|
||||
MeshType mCollisionType;
|
||||
MeshType mDecalType;
|
||||
MeshType mLOSType;
|
||||
|
||||
Vector<S32> mCollisionDetails;
|
||||
Vector<S32> mLOSDetails;
|
||||
|
||||
StringTableEntry colisionMeshPrefix;
|
||||
|
||||
RenderComponentInterface* mOwnerRenderInterface;
|
||||
|
||||
PhysicsComponentInterface* mOwnerPhysicsInterface;
|
||||
|
||||
//only really relevent for the collision mesh type
|
||||
//if we note an animation component is added, we flag as being animated.
|
||||
//This way, if we're using collision meshes, we can set it up to update their transforms
|
||||
//as needed
|
||||
bool mAnimated;
|
||||
|
||||
enum
|
||||
{
|
||||
ColliderMask = Parent::NextFreeMask,
|
||||
};
|
||||
|
||||
public:
|
||||
CollisionComponent();
|
||||
virtual ~CollisionComponent();
|
||||
DECLARE_CONOBJECT(CollisionComponent);
|
||||
|
||||
virtual U32 packUpdate(NetConnection *con, U32 mask, BitStream *stream);
|
||||
virtual void unpackUpdate(NetConnection *con, BitStream *stream);
|
||||
|
||||
virtual void componentAddedToOwner(Component *comp);
|
||||
virtual void componentRemovedFromOwner(Component *comp);
|
||||
virtual void ownerTransformSet(MatrixF *mat);
|
||||
void targetShapeChanged(RenderComponentInterface* instanceInterface);
|
||||
|
||||
virtual void onComponentRemove();
|
||||
virtual void onComponentAdd();
|
||||
|
||||
virtual void checkDependencies();
|
||||
|
||||
static void initPersistFields();
|
||||
|
||||
void inspectPostApply();
|
||||
|
||||
virtual void processTick();
|
||||
|
||||
void prepCollision();
|
||||
|
||||
PhysicsCollision* buildColShapes();
|
||||
|
||||
void updatePhysics();
|
||||
|
||||
virtual bool castRay(const Point3F &start, const Point3F &end, RayInfo* info);
|
||||
|
||||
virtual bool buildPolyList(PolyListContext context, AbstractPolyList* polyList, const Box3F &box, const SphereF &sphere){ return false; }
|
||||
|
||||
virtual PhysicsCollision* getCollisionData();
|
||||
|
||||
//Utility functions, mostly for script
|
||||
Point3F getContactNormal() { return mContactInfo.contactNormal; }
|
||||
bool hasContact()
|
||||
{
|
||||
if (mContactInfo.contactObject)
|
||||
return true;
|
||||
else
|
||||
return false;
|
||||
}
|
||||
S32 getCollisionCount()
|
||||
{
|
||||
return mCollisionList.getCount();
|
||||
}
|
||||
|
||||
Point3F getCollisionNormal(S32 collisionIndex)
|
||||
{
|
||||
if (collisionIndex < 0 || mCollisionList.getCount() < collisionIndex)
|
||||
return Point3F::Zero;
|
||||
|
||||
return mCollisionList[collisionIndex].normal;
|
||||
}
|
||||
|
||||
F32 getCollisionAngle(S32 collisionIndex, Point3F upVector)
|
||||
{
|
||||
if (collisionIndex < 0 || mCollisionList.getCount() < collisionIndex)
|
||||
return 0.0f;
|
||||
|
||||
return mRadToDeg(mAcos(mDot(mCollisionList[collisionIndex].normal, upVector)));
|
||||
}
|
||||
|
||||
S32 getBestCollision(Point3F upVector)
|
||||
{
|
||||
S32 bestCollision = -1;
|
||||
|
||||
F32 bestAngle = 360.f;
|
||||
S32 count = mCollisionList.getCount();
|
||||
for (U32 i = 0; i < count; ++i)
|
||||
{
|
||||
F32 angle = mRadToDeg(mAcos(mDot(mCollisionList[i].normal, upVector)));
|
||||
|
||||
if (angle < bestAngle)
|
||||
{
|
||||
bestCollision = i;
|
||||
bestAngle = angle;
|
||||
}
|
||||
}
|
||||
|
||||
return bestCollision;
|
||||
}
|
||||
|
||||
F32 getBestCollisionAngle(VectorF upVector)
|
||||
{
|
||||
S32 bestCol = getBestCollision(upVector);
|
||||
|
||||
if (bestCol == -1)
|
||||
return 0;
|
||||
|
||||
return getCollisionAngle(bestCol, upVector);
|
||||
}
|
||||
};
|
||||
|
||||
typedef CollisionComponent::MeshType CollisionMeshMeshType;
|
||||
DefineEnumType(CollisionMeshMeshType);
|
||||
|
||||
#endif // COLLISION_COMPONENT_H
|
||||
258
Engine/source/T3D/components/Collision/collisionInterfaces.cpp
Normal file
258
Engine/source/T3D/components/Collision/collisionInterfaces.cpp
Normal file
|
|
@ -0,0 +1,258 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// 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 "T3D/Components/collision/collisionInterfaces.h"
|
||||
#include "scene/sceneObject.h"
|
||||
#include "T3D/Entity.h"
|
||||
#include "console/engineAPI.h"
|
||||
#include "T3D/trigger.h"
|
||||
#include "materials/baseMatInstance.h"
|
||||
|
||||
void CollisionInterface::handleCollisionList( CollisionList &collisionList, VectorF velocity )
|
||||
{
|
||||
Collision bestCol;
|
||||
|
||||
mCollisionList = collisionList;
|
||||
|
||||
for (U32 i=0; i < collisionList.getCount(); ++i)
|
||||
{
|
||||
Collision& colCheck = collisionList[i];
|
||||
|
||||
if (colCheck.object)
|
||||
{
|
||||
if (colCheck.object->getTypeMask() & PlayerObjectType)
|
||||
{
|
||||
handleCollision( colCheck, velocity );
|
||||
}
|
||||
else if (colCheck.object->getTypeMask() & TriggerObjectType)
|
||||
{
|
||||
// We've hit it's bounding box, that's close enough for triggers
|
||||
Trigger* pTrigger = static_cast<Trigger*>(colCheck.object);
|
||||
|
||||
Component *comp = dynamic_cast<Component*>(this);
|
||||
pTrigger->potentialEnterObject(comp->getOwner());
|
||||
}
|
||||
else if (colCheck.object->getTypeMask() & DynamicShapeObjectType)
|
||||
{
|
||||
Con::printf("HIT A GENERICALLY DYNAMIC OBJECT");
|
||||
handleCollision(colCheck, velocity);
|
||||
}
|
||||
else if(colCheck.object->getTypeMask() & EntityObjectType)
|
||||
{
|
||||
Entity* ent = dynamic_cast<Entity*>(colCheck.object);
|
||||
if (ent)
|
||||
{
|
||||
CollisionInterface *colObjectInterface = ent->getComponent<CollisionInterface>();
|
||||
if (colObjectInterface)
|
||||
{
|
||||
//convert us to our component
|
||||
Component *thisComp = dynamic_cast<Component*>(this);
|
||||
if (thisComp)
|
||||
{
|
||||
colObjectInterface->onCollisionSignal.trigger(thisComp->getOwner());
|
||||
|
||||
//TODO: properly do this
|
||||
Collision oppositeCol = colCheck;
|
||||
oppositeCol.object = thisComp->getOwner();
|
||||
|
||||
colObjectInterface->handleCollision(oppositeCol, velocity);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
handleCollision(colCheck, velocity);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CollisionInterface::handleCollision( Collision &col, VectorF velocity )
|
||||
{
|
||||
if (col.object && (mContactInfo.contactObject == NULL ||
|
||||
col.object->getId() != mContactInfo.contactObject->getId()))
|
||||
{
|
||||
queueCollision(col.object, velocity - col.object->getVelocity());
|
||||
|
||||
//do the callbacks to script for this collision
|
||||
Component *comp = dynamic_cast<Component*>(this);
|
||||
if (comp->isMethod("onCollision"))
|
||||
{
|
||||
S32 matId = col.material != NULL ? col.material->getMaterial()->getId() : 0;
|
||||
Con::executef(comp, "onCollision", col.object, col.normal, col.point, matId, velocity);
|
||||
}
|
||||
|
||||
if (comp->getOwner()->isMethod("onCollisionEvent"))
|
||||
{
|
||||
S32 matId = col.material != NULL ? col.material->getMaterial()->getId() : 0;
|
||||
Con::executef(comp->getOwner(), "onCollisionEvent", col.object, col.normal, col.point, matId, velocity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CollisionInterface::handleCollisionNotifyList()
|
||||
{
|
||||
//special handling for any collision components we should notify that a collision happened.
|
||||
for (U32 i = 0; i < mCollisionNotifyList.size(); ++i)
|
||||
{
|
||||
//convert us to our component
|
||||
Component *thisComp = dynamic_cast<Component*>(this);
|
||||
if (thisComp)
|
||||
{
|
||||
mCollisionNotifyList[i]->onCollisionSignal.trigger(thisComp->getOwner());
|
||||
}
|
||||
}
|
||||
|
||||
mCollisionNotifyList.clear();
|
||||
}
|
||||
|
||||
Chunker<CollisionInterface::CollisionTimeout> sTimeoutChunker;
|
||||
CollisionInterface::CollisionTimeout* CollisionInterface::sFreeTimeoutList = 0;
|
||||
|
||||
void CollisionInterface::queueCollision( SceneObject *obj, const VectorF &vec)
|
||||
{
|
||||
// Add object to list of collisions.
|
||||
SimTime time = Sim::getCurrentTime();
|
||||
S32 num = obj->getId();
|
||||
|
||||
CollisionTimeout** adr = &mTimeoutList;
|
||||
CollisionTimeout* ptr = mTimeoutList;
|
||||
while (ptr)
|
||||
{
|
||||
if (ptr->objectNumber == num)
|
||||
{
|
||||
if (ptr->expireTime < time)
|
||||
{
|
||||
ptr->expireTime = time + CollisionTimeoutValue;
|
||||
ptr->object = obj;
|
||||
ptr->vector = vec;
|
||||
}
|
||||
return;
|
||||
}
|
||||
// Recover expired entries
|
||||
if (ptr->expireTime < time)
|
||||
{
|
||||
CollisionTimeout* cur = ptr;
|
||||
*adr = ptr->next;
|
||||
ptr = ptr->next;
|
||||
cur->next = sFreeTimeoutList;
|
||||
sFreeTimeoutList = cur;
|
||||
}
|
||||
else
|
||||
{
|
||||
adr = &ptr->next;
|
||||
ptr = ptr->next;
|
||||
}
|
||||
}
|
||||
|
||||
// New entry for the object
|
||||
if (sFreeTimeoutList != NULL)
|
||||
{
|
||||
ptr = sFreeTimeoutList;
|
||||
sFreeTimeoutList = ptr->next;
|
||||
ptr->next = NULL;
|
||||
}
|
||||
else
|
||||
{
|
||||
ptr = sTimeoutChunker.alloc();
|
||||
}
|
||||
|
||||
ptr->object = obj;
|
||||
ptr->objectNumber = obj->getId();
|
||||
ptr->vector = vec;
|
||||
ptr->expireTime = time + CollisionTimeoutValue;
|
||||
ptr->next = mTimeoutList;
|
||||
|
||||
mTimeoutList = ptr;
|
||||
}
|
||||
|
||||
bool CollisionInterface::checkEarlyOut(Point3F start, VectorF velocity, F32 time, Box3F objectBox, Point3F objectScale,
|
||||
Box3F collisionBox, U32 collisionMask, CollisionWorkingList &colWorkingList)
|
||||
{
|
||||
Point3F end = start + velocity * time;
|
||||
Point3F distance = end - start;
|
||||
|
||||
Box3F scaledBox = objectBox;
|
||||
scaledBox.minExtents.convolve(objectScale);
|
||||
scaledBox.maxExtents.convolve(objectScale);
|
||||
|
||||
if (mFabs(distance.x) < objectBox.len_x() &&
|
||||
mFabs(distance.y) < objectBox.len_y() &&
|
||||
mFabs(distance.z) < objectBox.len_z())
|
||||
{
|
||||
// We can potentially early out of this. If there are no polys in the clipped polylist at our
|
||||
// end position, then we can bail, and just set start = end;
|
||||
Box3F wBox = scaledBox;
|
||||
wBox.minExtents += end;
|
||||
wBox.maxExtents += end;
|
||||
|
||||
static EarlyOutPolyList eaPolyList;
|
||||
eaPolyList.clear();
|
||||
eaPolyList.mNormal.set(0.0f, 0.0f, 0.0f);
|
||||
eaPolyList.mPlaneList.clear();
|
||||
eaPolyList.mPlaneList.setSize(6);
|
||||
eaPolyList.mPlaneList[0].set(wBox.minExtents,VectorF(-1.0f, 0.0f, 0.0f));
|
||||
eaPolyList.mPlaneList[1].set(wBox.maxExtents,VectorF(0.0f, 1.0f, 0.0f));
|
||||
eaPolyList.mPlaneList[2].set(wBox.maxExtents,VectorF(1.0f, 0.0f, 0.0f));
|
||||
eaPolyList.mPlaneList[3].set(wBox.minExtents,VectorF(0.0f, -1.0f, 0.0f));
|
||||
eaPolyList.mPlaneList[4].set(wBox.minExtents,VectorF(0.0f, 0.0f, -1.0f));
|
||||
eaPolyList.mPlaneList[5].set(wBox.maxExtents,VectorF(0.0f, 0.0f, 1.0f));
|
||||
|
||||
// Build list from convex states here...
|
||||
CollisionWorkingList& rList = colWorkingList;
|
||||
CollisionWorkingList* pList = rList.wLink.mNext;
|
||||
while (pList != &rList)
|
||||
{
|
||||
Convex* pConvex = pList->mConvex;
|
||||
|
||||
if (pConvex->getObject()->getTypeMask() & collisionMask)
|
||||
{
|
||||
Box3F convexBox = pConvex->getBoundingBox();
|
||||
|
||||
if (wBox.isOverlapped(convexBox))
|
||||
{
|
||||
// No need to separate out the physical zones here, we want those
|
||||
// to cause a fallthrough as well...
|
||||
pConvex->getPolyList(&eaPolyList);
|
||||
}
|
||||
}
|
||||
pList = pList->wLink.mNext;
|
||||
}
|
||||
|
||||
if (eaPolyList.isEmpty())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
Collision* CollisionInterface::getCollision(S32 col)
|
||||
{
|
||||
if(col < mCollisionList.getCount() && col >= 0)
|
||||
return &mCollisionList[col];
|
||||
else
|
||||
return NULL;
|
||||
}
|
||||
167
Engine/source/T3D/components/Collision/collisionInterfaces.h
Normal file
167
Engine/source/T3D/components/Collision/collisionInterfaces.h
Normal file
|
|
@ -0,0 +1,167 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// 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 COLLISION_INTERFACES_H
|
||||
#define COLLISION_INTERFACES_H
|
||||
|
||||
#ifndef _CONVEX_H_
|
||||
#include "collision/convex.h"
|
||||
#endif
|
||||
#ifndef _COLLISION_H_
|
||||
#include "collision/collision.h"
|
||||
#endif
|
||||
#ifndef _EARLYOUTPOLYLIST_H_
|
||||
#include "collision/earlyOutPolyList.h"
|
||||
#endif
|
||||
#ifndef _SIM_H_
|
||||
#include "console/sim.h"
|
||||
#endif
|
||||
#ifndef _SCENECONTAINER_H_
|
||||
#include "scene/sceneContainer.h"
|
||||
#endif
|
||||
#ifndef _T3D_PHYSICSCOMMON_H_
|
||||
#include "T3D/physics/physicsCommon.h"
|
||||
#endif
|
||||
|
||||
struct ContactInfo
|
||||
{
|
||||
bool contacted, move;
|
||||
SceneObject *contactObject;
|
||||
VectorF idealContactNormal;
|
||||
VectorF contactNormal;
|
||||
Point3F contactPoint;
|
||||
F32 contactTime;
|
||||
S32 contactTimer;
|
||||
BaseMatInstance *contactMaterial;
|
||||
|
||||
void clear()
|
||||
{
|
||||
contacted=move=false;
|
||||
contactObject = NULL;
|
||||
contactNormal.set(0,0,0);
|
||||
contactTime = 0.f;
|
||||
contactTimer = 0;
|
||||
idealContactNormal.set(0, 0, 1);
|
||||
contactMaterial = NULL;
|
||||
}
|
||||
|
||||
ContactInfo() { clear(); }
|
||||
|
||||
};
|
||||
|
||||
class CollisionInterface// : public Interface<CollisionInterface>
|
||||
{
|
||||
public:
|
||||
// CollisionTimeout
|
||||
// This struct lets us track our collisions and estimate when they've have timed out and we'll need to act on it.
|
||||
struct CollisionTimeout
|
||||
{
|
||||
CollisionTimeout* next;
|
||||
SceneObject* object;
|
||||
U32 objectNumber;
|
||||
SimTime expireTime;
|
||||
VectorF vector;
|
||||
};
|
||||
|
||||
Signal< void( SceneObject* ) > CollisionInterface::onCollisionSignal;
|
||||
Signal< void( SceneObject* ) > CollisionInterface::onContactSignal;
|
||||
|
||||
protected:
|
||||
CollisionTimeout* mTimeoutList;
|
||||
static CollisionTimeout* sFreeTimeoutList;
|
||||
|
||||
CollisionList mCollisionList;
|
||||
Vector<CollisionInterface*> mCollisionNotifyList;
|
||||
|
||||
ContactInfo mContactInfo;
|
||||
|
||||
Box3F mWorkingQueryBox;
|
||||
|
||||
U32 CollisionMoveMask;
|
||||
|
||||
Convex *mConvexList;
|
||||
|
||||
bool mBlockColliding;
|
||||
|
||||
void handleCollisionNotifyList();
|
||||
|
||||
void queueCollision( SceneObject *obj, const VectorF &vec);
|
||||
|
||||
/// checkEarlyOut
|
||||
/// This function lets you trying and early out of any expensive collision checks by using simple extruded poly boxes representing our objects
|
||||
/// If it returns true, we know we won't hit with the given parameters and can successfully early out. If it returns false, our test case collided
|
||||
/// and we should do the full collision sim.
|
||||
bool checkEarlyOut(Point3F start, VectorF velocity, F32 time, Box3F objectBox, Point3F objectScale,
|
||||
Box3F collisionBox, U32 collisionMask, CollisionWorkingList &colWorkingList);
|
||||
|
||||
public:
|
||||
/// checkCollisions
|
||||
// This is our main function for checking if a collision is happening based on the start point, velocity and time
|
||||
// We do the bulk of the collision checking in here
|
||||
//virtual bool checkCollisions( const F32 travelTime, Point3F *velocity, Point3F start )=0;
|
||||
|
||||
CollisionList *getCollisionList() { return &mCollisionList; }
|
||||
|
||||
void clearCollisionList() { mCollisionList.clear(); }
|
||||
|
||||
void clearCollisionNotifyList() { mCollisionNotifyList.clear(); }
|
||||
|
||||
Collision *getCollision(S32 col);
|
||||
|
||||
ContactInfo* getContactInfo() { return &mContactInfo; }
|
||||
|
||||
Convex *getConvexList() { return mConvexList; }
|
||||
|
||||
virtual bool buildPolyList(PolyListContext context, AbstractPolyList* polyList, const Box3F &box, const SphereF &sphere) = 0;
|
||||
|
||||
enum PublicConstants {
|
||||
CollisionTimeoutValue = 250
|
||||
};
|
||||
|
||||
bool doesBlockColliding() { return mBlockColliding; }
|
||||
|
||||
/// handleCollisionList
|
||||
/// This basically takes in a CollisionList and calls handleCollision for each.
|
||||
void handleCollisionList(CollisionList &collisionList, VectorF velocity);
|
||||
|
||||
/// handleCollision
|
||||
/// This will take a collision and queue the collision info for the object so that in knows about the collision.
|
||||
void handleCollision(Collision &col, VectorF velocity);
|
||||
|
||||
virtual PhysicsCollision* getCollisionData() = 0;
|
||||
|
||||
Signal< void(PhysicsCollision* collision) > CollisionInterface::onCollisionChanged;
|
||||
};
|
||||
|
||||
class BuildConvexInterface //: public Interface<CollisionInterface>
|
||||
{
|
||||
public:
|
||||
virtual void buildConvex(const Box3F& box, Convex* convex)=0;
|
||||
};
|
||||
|
||||
class BuildPolyListInterface// : public Interface<CollisionInterface>
|
||||
{
|
||||
public:
|
||||
virtual bool buildPolyList(PolyListContext context, AbstractPolyList* polyList, const Box3F &box, const SphereF &sphere) = 0;
|
||||
};
|
||||
|
||||
#endif
|
||||
619
Engine/source/T3D/components/Collision/collisionTrigger.cpp
Normal file
619
Engine/source/T3D/components/Collision/collisionTrigger.cpp
Normal file
|
|
@ -0,0 +1,619 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// 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/Components/Collision/CollisionTrigger.h"
|
||||
|
||||
#include "scene/sceneRenderState.h"
|
||||
#include "console/consoleTypes.h"
|
||||
#include "console/engineAPI.h"
|
||||
#include "collision/boxConvex.h"
|
||||
|
||||
#include "core/stream/bitStream.h"
|
||||
#include "math/mathIO.h"
|
||||
#include "gfx/gfxTransformSaver.h"
|
||||
#include "renderInstance/renderPassManager.h"
|
||||
#include "gfx/gfxDrawUtil.h"
|
||||
#include "T3D/physics/physicsPlugin.h"
|
||||
#include "T3D/physics/physicsBody.h"
|
||||
#include "T3D/physics/physicsCollision.h"
|
||||
|
||||
|
||||
bool CollisionTrigger::smRenderCollisionTriggers = false;
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
IMPLEMENT_CO_NETOBJECT_V1(CollisionTrigger);
|
||||
|
||||
ConsoleDocClass(CollisionTrigger,
|
||||
"@brief A CollisionTrigger is a volume of space that initiates script callbacks "
|
||||
"when objects pass through the CollisionTrigger.\n\n"
|
||||
|
||||
"CollisionTriggerData provides the callbacks for the CollisionTrigger when an object enters, stays inside "
|
||||
"or leaves the CollisionTrigger's volume.\n\n"
|
||||
|
||||
"@see CollisionTriggerData\n"
|
||||
"@ingroup gameObjects\n"
|
||||
);
|
||||
|
||||
IMPLEMENT_CALLBACK(CollisionTrigger, onAdd, void, (U32 objectId), (objectId),
|
||||
"@brief Called when the CollisionTrigger is being created.\n\n"
|
||||
"@param objectId the object id of the CollisionTrigger being created\n");
|
||||
|
||||
IMPLEMENT_CALLBACK(CollisionTrigger, onRemove, void, (U32 objectId), (objectId),
|
||||
"@brief Called just before the CollisionTrigger is deleted.\n\n"
|
||||
"@param objectId the object id of the CollisionTrigger being deleted\n");
|
||||
|
||||
CollisionTrigger::CollisionTrigger()
|
||||
{
|
||||
// Don't ghost by default.
|
||||
mNetFlags.set(Ghostable | ScopeAlways);
|
||||
|
||||
mTypeMask |= TriggerObjectType;
|
||||
|
||||
mObjScale.set(1, 1, 1);
|
||||
mObjToWorld.identity();
|
||||
mWorldToObj.identity();
|
||||
|
||||
mLastThink = 0;
|
||||
mCurrTick = 0;
|
||||
|
||||
mConvexList = new Convex;
|
||||
|
||||
mPhysicsRep = NULL;
|
||||
}
|
||||
|
||||
CollisionTrigger::~CollisionTrigger()
|
||||
{
|
||||
delete mConvexList;
|
||||
mConvexList = NULL;
|
||||
SAFE_DELETE(mPhysicsRep);
|
||||
}
|
||||
|
||||
bool CollisionTrigger::castRay(const Point3F &start, const Point3F &end, RayInfo* info)
|
||||
{
|
||||
// Collide against bounding box
|
||||
F32 st, et, fst = 0, fet = 1;
|
||||
F32 *bmin = &mObjBox.minExtents.x;
|
||||
F32 *bmax = &mObjBox.maxExtents.x;
|
||||
F32 const *si = &start.x;
|
||||
F32 const *ei = &end.x;
|
||||
|
||||
for (S32 i = 0; i < 3; i++)
|
||||
{
|
||||
if (*si < *ei)
|
||||
{
|
||||
if (*si > *bmax || *ei < *bmin)
|
||||
return false;
|
||||
F32 di = *ei - *si;
|
||||
st = (*si < *bmin) ? (*bmin - *si) / di : 0;
|
||||
et = (*ei > *bmax) ? (*bmax - *si) / di : 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (*ei > *bmax || *si < *bmin)
|
||||
return false;
|
||||
F32 di = *ei - *si;
|
||||
st = (*si > *bmax) ? (*bmax - *si) / di : 0;
|
||||
et = (*ei < *bmin) ? (*bmin - *si) / di : 1;
|
||||
}
|
||||
if (st > fst) fst = st;
|
||||
if (et < fet) fet = et;
|
||||
if (fet < fst)
|
||||
return false;
|
||||
bmin++; bmax++;
|
||||
si++; ei++;
|
||||
}
|
||||
|
||||
info->normal = start - end;
|
||||
info->normal.normalizeSafe();
|
||||
getTransform().mulV(info->normal);
|
||||
|
||||
info->t = fst;
|
||||
info->object = this;
|
||||
info->point.interpolate(start, end, fst);
|
||||
info->material = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
void CollisionTrigger::consoleInit()
|
||||
{
|
||||
Con::addVariable("$CollisionTrigger::renderCollisionTriggers", TypeBool, &smRenderCollisionTriggers,
|
||||
"@brief Forces all CollisionTrigger's to render.\n\n"
|
||||
"Used by the Tools and debug render modes.\n"
|
||||
"@ingroup gameObjects");
|
||||
}
|
||||
|
||||
void CollisionTrigger::initPersistFields()
|
||||
{
|
||||
addField("polyhedron", TypeTriggerPolyhedron, Offset(mCollisionTriggerPolyhedron, CollisionTrigger),
|
||||
"@brief Defines a non-rectangular area for the CollisionTrigger.\n\n"
|
||||
"Rather than the standard rectangular bounds, this optional parameter defines a quadrilateral "
|
||||
"CollisionTrigger area. The quadrilateral is defined as a corner point followed by three vectors "
|
||||
"representing the edges extending from the corner.\n");
|
||||
|
||||
addProtectedField("enterCommand", TypeCommand, Offset(mEnterCommand, CollisionTrigger), &setEnterCmd, &defaultProtectedGetFn,
|
||||
"The command to execute when an object enters this CollisionTrigger. Object id stored in %%obj. Maximum 1023 characters.");
|
||||
addProtectedField("leaveCommand", TypeCommand, Offset(mLeaveCommand, CollisionTrigger), &setLeaveCmd, &defaultProtectedGetFn,
|
||||
"The command to execute when an object leaves this CollisionTrigger. Object id stored in %%obj. Maximum 1023 characters.");
|
||||
addProtectedField("tickCommand", TypeCommand, Offset(mTickCommand, CollisionTrigger), &setTickCmd, &defaultProtectedGetFn,
|
||||
"The command to execute while an object is inside this CollisionTrigger. Maximum 1023 characters.");
|
||||
|
||||
Parent::initPersistFields();
|
||||
}
|
||||
|
||||
bool CollisionTrigger::setEnterCmd(void *object, const char *index, const char *data)
|
||||
{
|
||||
static_cast<CollisionTrigger*>(object)->setMaskBits(EnterCmdMask);
|
||||
return true; // to update the actual field
|
||||
}
|
||||
|
||||
bool CollisionTrigger::setLeaveCmd(void *object, const char *index, const char *data)
|
||||
{
|
||||
static_cast<CollisionTrigger*>(object)->setMaskBits(LeaveCmdMask);
|
||||
return true; // to update the actual field
|
||||
}
|
||||
|
||||
bool CollisionTrigger::setTickCmd(void *object, const char *index, const char *data)
|
||||
{
|
||||
static_cast<CollisionTrigger*>(object)->setMaskBits(TickCmdMask);
|
||||
return true; // to update the actual field
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
bool CollisionTrigger::onAdd()
|
||||
{
|
||||
if (!Parent::onAdd())
|
||||
return false;
|
||||
|
||||
onAdd_callback(getId());
|
||||
|
||||
Polyhedron temp = mCollisionTriggerPolyhedron;
|
||||
setTriggerPolyhedron(temp);
|
||||
|
||||
addToScene();
|
||||
|
||||
if (isServerObject())
|
||||
scriptOnAdd();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void CollisionTrigger::onRemove()
|
||||
{
|
||||
onRemove_callback(getId());
|
||||
|
||||
mConvexList->nukeList();
|
||||
|
||||
removeFromScene();
|
||||
Parent::onRemove();
|
||||
}
|
||||
|
||||
bool CollisionTrigger::onNewDataBlock(GameBaseData *dptr, bool reload)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
void CollisionTrigger::onDeleteNotify(SimObject *obj)
|
||||
{
|
||||
GameBase* pScene = dynamic_cast<GameBase*>(obj);
|
||||
|
||||
if (pScene != NULL)
|
||||
{
|
||||
for (U32 i = 0; i < mObjects.size(); i++)
|
||||
{
|
||||
if (pScene == mObjects[i])
|
||||
{
|
||||
mObjects.erase(i);
|
||||
//onLeaveCollisionTrigger_callback(this, pScene);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Parent::onDeleteNotify(obj);
|
||||
}
|
||||
|
||||
void CollisionTrigger::inspectPostApply()
|
||||
{
|
||||
setTriggerPolyhedron(mCollisionTriggerPolyhedron);
|
||||
setMaskBits(PolyMask);
|
||||
Parent::inspectPostApply();
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
void CollisionTrigger::buildConvex(const Box3F& box, Convex* convex)
|
||||
{
|
||||
// These should really come out of a pool
|
||||
mConvexList->collectGarbage();
|
||||
|
||||
Box3F realBox = box;
|
||||
mWorldToObj.mul(realBox);
|
||||
realBox.minExtents.convolveInverse(mObjScale);
|
||||
realBox.maxExtents.convolveInverse(mObjScale);
|
||||
|
||||
if (realBox.isOverlapped(getObjBox()) == false)
|
||||
return;
|
||||
|
||||
// Just return a box convex for the entire shape...
|
||||
Convex* cc = 0;
|
||||
CollisionWorkingList& wl = convex->getWorkingList();
|
||||
for (CollisionWorkingList* itr = wl.wLink.mNext; itr != &wl; itr = itr->wLink.mNext) {
|
||||
if (itr->mConvex->getType() == BoxConvexType &&
|
||||
itr->mConvex->getObject() == this) {
|
||||
cc = itr->mConvex;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (cc)
|
||||
return;
|
||||
|
||||
// Create a new convex.
|
||||
BoxConvex* cp = new BoxConvex;
|
||||
mConvexList->registerObject(cp);
|
||||
convex->addToWorkingList(cp);
|
||||
cp->init(this);
|
||||
|
||||
mObjBox.getCenter(&cp->mCenter);
|
||||
cp->mSize.x = mObjBox.len_x() / 2.0f;
|
||||
cp->mSize.y = mObjBox.len_y() / 2.0f;
|
||||
cp->mSize.z = mObjBox.len_z() / 2.0f;
|
||||
}
|
||||
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
void CollisionTrigger::setTransform(const MatrixF & mat)
|
||||
{
|
||||
Parent::setTransform(mat);
|
||||
|
||||
if (mPhysicsRep)
|
||||
mPhysicsRep->setTransform(mat);
|
||||
|
||||
if (isServerObject()) {
|
||||
MatrixF base(true);
|
||||
base.scale(Point3F(1.0 / mObjScale.x,
|
||||
1.0 / mObjScale.y,
|
||||
1.0 / mObjScale.z));
|
||||
base.mul(mWorldToObj);
|
||||
mClippedList.setBaseTransform(base);
|
||||
|
||||
setMaskBits(TransformMask | ScaleMask);
|
||||
}
|
||||
}
|
||||
|
||||
void CollisionTrigger::prepRenderImage(SceneRenderState *state)
|
||||
{
|
||||
// only render if selected or render flag is set
|
||||
if (!smRenderCollisionTriggers && !isSelected())
|
||||
return;
|
||||
|
||||
ObjectRenderInst *ri = state->getRenderPass()->allocInst<ObjectRenderInst>();
|
||||
ri->renderDelegate.bind(this, &CollisionTrigger::renderObject);
|
||||
ri->type = RenderPassManager::RIT_Editor;
|
||||
ri->translucentSort = true;
|
||||
ri->defaultKey = 1;
|
||||
state->getRenderPass()->addInst(ri);
|
||||
}
|
||||
|
||||
void CollisionTrigger::renderObject(ObjectRenderInst *ri,
|
||||
SceneRenderState *state,
|
||||
BaseMatInstance *overrideMat)
|
||||
{
|
||||
if (overrideMat)
|
||||
return;
|
||||
|
||||
GFXStateBlockDesc desc;
|
||||
desc.setZReadWrite(true, false);
|
||||
desc.setBlend(true);
|
||||
|
||||
// CollisionTrigger polyhedrons are set up with outward facing normals and CCW ordering
|
||||
// so can't enable backface culling.
|
||||
desc.setCullMode(GFXCullNone);
|
||||
|
||||
GFXTransformSaver saver;
|
||||
|
||||
MatrixF mat = getRenderTransform();
|
||||
mat.scale(getScale());
|
||||
|
||||
GFX->multWorld(mat);
|
||||
|
||||
GFXDrawUtil *drawer = GFX->getDrawUtil();
|
||||
|
||||
drawer->drawPolyhedron(desc, mCollisionTriggerPolyhedron, ColorI(255, 192, 0, 45));
|
||||
|
||||
// Render wireframe.
|
||||
|
||||
desc.setFillModeWireframe();
|
||||
drawer->drawPolyhedron(desc, mCollisionTriggerPolyhedron, ColorI::BLACK);
|
||||
}
|
||||
|
||||
void CollisionTrigger::setTriggerPolyhedron(const Polyhedron& rPolyhedron)
|
||||
{
|
||||
mCollisionTriggerPolyhedron = rPolyhedron;
|
||||
|
||||
if (mCollisionTriggerPolyhedron.pointList.size() != 0) {
|
||||
mObjBox.minExtents.set(1e10, 1e10, 1e10);
|
||||
mObjBox.maxExtents.set(-1e10, -1e10, -1e10);
|
||||
for (U32 i = 0; i < mCollisionTriggerPolyhedron.pointList.size(); i++) {
|
||||
mObjBox.minExtents.setMin(mCollisionTriggerPolyhedron.pointList[i]);
|
||||
mObjBox.maxExtents.setMax(mCollisionTriggerPolyhedron.pointList[i]);
|
||||
}
|
||||
}
|
||||
else {
|
||||
mObjBox.minExtents.set(-0.5, -0.5, -0.5);
|
||||
mObjBox.maxExtents.set(0.5, 0.5, 0.5);
|
||||
}
|
||||
|
||||
MatrixF xform = getTransform();
|
||||
setTransform(xform);
|
||||
|
||||
mClippedList.clear();
|
||||
mClippedList.mPlaneList = mCollisionTriggerPolyhedron.planeList;
|
||||
// for (U32 i = 0; i < mClippedList.mPlaneList.size(); i++)
|
||||
// mClippedList.mPlaneList[i].neg();
|
||||
|
||||
MatrixF base(true);
|
||||
base.scale(Point3F(1.0 / mObjScale.x,
|
||||
1.0 / mObjScale.y,
|
||||
1.0 / mObjScale.z));
|
||||
base.mul(mWorldToObj);
|
||||
|
||||
mClippedList.setBaseTransform(base);
|
||||
|
||||
SAFE_DELETE(mPhysicsRep);
|
||||
|
||||
if (PHYSICSMGR)
|
||||
{
|
||||
PhysicsCollision *colShape = PHYSICSMGR->createCollision();
|
||||
|
||||
MatrixF colMat(true);
|
||||
colMat.displace(Point3F(0, 0, mObjBox.getExtents().z * 0.5f * mObjScale.z));
|
||||
|
||||
colShape->addBox(mObjBox.getExtents() * 0.5f * mObjScale, colMat);
|
||||
//MatrixF colMat( true );
|
||||
//colMat.scale( mObjScale );
|
||||
//colShape->addConvex( mCollisionTriggerPolyhedron.pointList.address(), mCollisionTriggerPolyhedron.pointList.size(), colMat );
|
||||
|
||||
PhysicsWorld *world = PHYSICSMGR->getWorld(isServerObject() ? "server" : "client");
|
||||
mPhysicsRep = PHYSICSMGR->createBody();
|
||||
mPhysicsRep->init(colShape, 0, PhysicsBody::BF_TRIGGER | PhysicsBody::BF_KINEMATIC, this, world);
|
||||
mPhysicsRep->setTransform(getTransform());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
bool CollisionTrigger::testObject(GameBase* enter)
|
||||
{
|
||||
if (mCollisionTriggerPolyhedron.pointList.size() == 0)
|
||||
return false;
|
||||
|
||||
mClippedList.clear();
|
||||
|
||||
SphereF sphere;
|
||||
sphere.center = (mWorldBox.minExtents + mWorldBox.maxExtents) * 0.5;
|
||||
VectorF bv = mWorldBox.maxExtents - sphere.center;
|
||||
sphere.radius = bv.len();
|
||||
|
||||
enter->buildPolyList(PLC_Collision, &mClippedList, mWorldBox, sphere);
|
||||
return mClippedList.isEmpty() == false;
|
||||
}
|
||||
|
||||
|
||||
void CollisionTrigger::potentialEnterObject(GameBase* enter)
|
||||
{
|
||||
for (U32 i = 0; i < mObjects.size(); i++) {
|
||||
if (mObjects[i] == enter)
|
||||
return;
|
||||
}
|
||||
|
||||
if (testObject(enter) == true) {
|
||||
mObjects.push_back(enter);
|
||||
deleteNotify(enter);
|
||||
|
||||
if (!mEnterCommand.isEmpty())
|
||||
{
|
||||
String command = String("%obj = ") + enter->getIdString() + ";" + mEnterCommand;
|
||||
Con::evaluate(command.c_str());
|
||||
}
|
||||
|
||||
//onEnterCollisionTrigger_callback(this, enter);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void CollisionTrigger::processTick(const Move* move)
|
||||
{
|
||||
Parent::processTick(move);
|
||||
|
||||
//
|
||||
if (mObjects.size() == 0)
|
||||
return;
|
||||
|
||||
if (mLastThink + 100 < mCurrTick)
|
||||
{
|
||||
mCurrTick = 0;
|
||||
mLastThink = 0;
|
||||
|
||||
for (S32 i = S32(mObjects.size() - 1); i >= 0; i--)
|
||||
{
|
||||
if (testObject(mObjects[i]) == false)
|
||||
{
|
||||
GameBase* remove = mObjects[i];
|
||||
mObjects.erase(i);
|
||||
clearNotify(remove);
|
||||
|
||||
if (!mLeaveCommand.isEmpty())
|
||||
{
|
||||
String command = String("%obj = ") + remove->getIdString() + ";" + mLeaveCommand;
|
||||
Con::evaluate(command.c_str());
|
||||
}
|
||||
|
||||
//onLeaveCollisionTrigger_callback(this, remove);
|
||||
}
|
||||
}
|
||||
|
||||
if (!mTickCommand.isEmpty())
|
||||
Con::evaluate(mTickCommand.c_str());
|
||||
|
||||
//if (mObjects.size() != 0)
|
||||
// onTickCollisionTrigger_callback(this);
|
||||
}
|
||||
else
|
||||
{
|
||||
mCurrTick += TickMs;
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
U32 CollisionTrigger::packUpdate(NetConnection* con, U32 mask, BitStream* stream)
|
||||
{
|
||||
U32 i;
|
||||
U32 retMask = Parent::packUpdate(con, mask, stream);
|
||||
|
||||
if (stream->writeFlag(mask & TransformMask))
|
||||
{
|
||||
stream->writeAffineTransform(mObjToWorld);
|
||||
}
|
||||
|
||||
// Write the polyhedron
|
||||
if (stream->writeFlag(mask & PolyMask))
|
||||
{
|
||||
stream->write(mCollisionTriggerPolyhedron.pointList.size());
|
||||
for (i = 0; i < mCollisionTriggerPolyhedron.pointList.size(); i++)
|
||||
mathWrite(*stream, mCollisionTriggerPolyhedron.pointList[i]);
|
||||
|
||||
stream->write(mCollisionTriggerPolyhedron.planeList.size());
|
||||
for (i = 0; i < mCollisionTriggerPolyhedron.planeList.size(); i++)
|
||||
mathWrite(*stream, mCollisionTriggerPolyhedron.planeList[i]);
|
||||
|
||||
stream->write(mCollisionTriggerPolyhedron.edgeList.size());
|
||||
for (i = 0; i < mCollisionTriggerPolyhedron.edgeList.size(); i++) {
|
||||
const Polyhedron::Edge& rEdge = mCollisionTriggerPolyhedron.edgeList[i];
|
||||
|
||||
stream->write(rEdge.face[0]);
|
||||
stream->write(rEdge.face[1]);
|
||||
stream->write(rEdge.vertex[0]);
|
||||
stream->write(rEdge.vertex[1]);
|
||||
}
|
||||
}
|
||||
|
||||
if (stream->writeFlag(mask & EnterCmdMask))
|
||||
stream->writeLongString(CMD_SIZE - 1, mEnterCommand.c_str());
|
||||
if (stream->writeFlag(mask & LeaveCmdMask))
|
||||
stream->writeLongString(CMD_SIZE - 1, mLeaveCommand.c_str());
|
||||
if (stream->writeFlag(mask & TickCmdMask))
|
||||
stream->writeLongString(CMD_SIZE - 1, mTickCommand.c_str());
|
||||
|
||||
return retMask;
|
||||
}
|
||||
|
||||
void CollisionTrigger::unpackUpdate(NetConnection* con, BitStream* stream)
|
||||
{
|
||||
Parent::unpackUpdate(con, stream);
|
||||
|
||||
U32 i, size;
|
||||
|
||||
// Transform
|
||||
if (stream->readFlag())
|
||||
{
|
||||
MatrixF temp;
|
||||
stream->readAffineTransform(&temp);
|
||||
setTransform(temp);
|
||||
}
|
||||
|
||||
// Read the polyhedron
|
||||
if (stream->readFlag())
|
||||
{
|
||||
Polyhedron tempPH;
|
||||
stream->read(&size);
|
||||
tempPH.pointList.setSize(size);
|
||||
for (i = 0; i < tempPH.pointList.size(); i++)
|
||||
mathRead(*stream, &tempPH.pointList[i]);
|
||||
|
||||
stream->read(&size);
|
||||
tempPH.planeList.setSize(size);
|
||||
for (i = 0; i < tempPH.planeList.size(); i++)
|
||||
mathRead(*stream, &tempPH.planeList[i]);
|
||||
|
||||
stream->read(&size);
|
||||
tempPH.edgeList.setSize(size);
|
||||
for (i = 0; i < tempPH.edgeList.size(); i++) {
|
||||
Polyhedron::Edge& rEdge = tempPH.edgeList[i];
|
||||
|
||||
stream->read(&rEdge.face[0]);
|
||||
stream->read(&rEdge.face[1]);
|
||||
stream->read(&rEdge.vertex[0]);
|
||||
stream->read(&rEdge.vertex[1]);
|
||||
}
|
||||
setTriggerPolyhedron(tempPH);
|
||||
}
|
||||
|
||||
if (stream->readFlag())
|
||||
{
|
||||
char buf[CMD_SIZE];
|
||||
stream->readLongString(CMD_SIZE - 1, buf);
|
||||
mEnterCommand = buf;
|
||||
}
|
||||
if (stream->readFlag())
|
||||
{
|
||||
char buf[CMD_SIZE];
|
||||
stream->readLongString(CMD_SIZE - 1, buf);
|
||||
mLeaveCommand = buf;
|
||||
}
|
||||
if (stream->readFlag())
|
||||
{
|
||||
char buf[CMD_SIZE];
|
||||
stream->readLongString(CMD_SIZE - 1, buf);
|
||||
mTickCommand = buf;
|
||||
}
|
||||
}
|
||||
|
||||
//ConsoleMethod( CollisionTrigger, getNumObjects, S32, 2, 2, "")
|
||||
DefineEngineMethod(CollisionTrigger, getNumObjects, S32, (), ,
|
||||
"@brief Get the number of objects that are within the CollisionTrigger's bounds.\n\n"
|
||||
"@see getObject()\n")
|
||||
{
|
||||
return object->getNumCollisionTriggeringObjects();
|
||||
}
|
||||
|
||||
//ConsoleMethod( CollisionTrigger, getObject, S32, 3, 3, "(int idx)")
|
||||
DefineEngineMethod(CollisionTrigger, getObject, S32, (S32 index), ,
|
||||
"@brief Retrieve the requested object that is within the CollisionTrigger's bounds.\n\n"
|
||||
"@param index Index of the object to get (range is 0 to getNumObjects()-1)\n"
|
||||
"@returns The SimObjectID of the object, or -1 if the requested index is invalid.\n"
|
||||
"@see getNumObjects()\n")
|
||||
{
|
||||
if (index >= object->getNumCollisionTriggeringObjects() || index < 0)
|
||||
return -1;
|
||||
else
|
||||
return object->getObject(U32(index))->getId();
|
||||
}
|
||||
145
Engine/source/T3D/components/Collision/collisionTrigger.h
Normal file
145
Engine/source/T3D/components/Collision/collisionTrigger.h
Normal file
|
|
@ -0,0 +1,145 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// 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 _H_CollisionTrigger
|
||||
#define _H_CollisionTrigger
|
||||
|
||||
#ifndef _GAMEBASE_H_
|
||||
#include "T3D/gameBase/gameBase.h"
|
||||
#endif
|
||||
#ifndef _MBOX_H_
|
||||
#include "math/mBox.h"
|
||||
#endif
|
||||
#ifndef _EARLYOUTPOLYLIST_H_
|
||||
#include "collision/earlyOutPolyList.h"
|
||||
#endif
|
||||
#ifndef _MPOLYHEDRON_H_
|
||||
#include "math/mPolyhedron.h"
|
||||
#endif
|
||||
#ifndef _TRIGGER_H_
|
||||
#include "T3D/trigger.h"
|
||||
#endif
|
||||
|
||||
class Convex;
|
||||
class PhysicsBody;
|
||||
class TriggerPolyhedronType;
|
||||
|
||||
class CollisionTrigger : public GameBase
|
||||
{
|
||||
typedef GameBase Parent;
|
||||
|
||||
/// CollisionTrigger polyhedron with *outward* facing normals and CCW ordered
|
||||
/// vertices.
|
||||
Polyhedron mCollisionTriggerPolyhedron;
|
||||
|
||||
EarlyOutPolyList mClippedList;
|
||||
Vector<GameBase*> mObjects;
|
||||
|
||||
PhysicsBody *mPhysicsRep;
|
||||
|
||||
U32 mLastThink;
|
||||
U32 mCurrTick;
|
||||
Convex *mConvexList;
|
||||
|
||||
String mEnterCommand;
|
||||
String mLeaveCommand;
|
||||
String mTickCommand;
|
||||
|
||||
enum CollisionTriggerUpdateBits
|
||||
{
|
||||
TransformMask = Parent::NextFreeMask << 0,
|
||||
PolyMask = Parent::NextFreeMask << 1,
|
||||
EnterCmdMask = Parent::NextFreeMask << 2,
|
||||
LeaveCmdMask = Parent::NextFreeMask << 3,
|
||||
TickCmdMask = Parent::NextFreeMask << 4,
|
||||
NextFreeMask = Parent::NextFreeMask << 5,
|
||||
};
|
||||
|
||||
static const U32 CMD_SIZE = 1024;
|
||||
|
||||
protected:
|
||||
|
||||
static bool smRenderCollisionTriggers;
|
||||
bool testObject(GameBase* enter);
|
||||
void processTick(const Move *move);
|
||||
|
||||
void buildConvex(const Box3F& box, Convex* convex);
|
||||
|
||||
static bool setEnterCmd(void *object, const char *index, const char *data);
|
||||
static bool setLeaveCmd(void *object, const char *index, const char *data);
|
||||
static bool setTickCmd(void *object, const char *index, const char *data);
|
||||
|
||||
public:
|
||||
CollisionTrigger();
|
||||
~CollisionTrigger();
|
||||
|
||||
// SimObject
|
||||
DECLARE_CONOBJECT(CollisionTrigger);
|
||||
|
||||
DECLARE_CALLBACK(void, onAdd, (U32 objectId));
|
||||
DECLARE_CALLBACK(void, onRemove, (U32 objectId));
|
||||
|
||||
static void consoleInit();
|
||||
static void initPersistFields();
|
||||
bool onAdd();
|
||||
void onRemove();
|
||||
void onDeleteNotify(SimObject*);
|
||||
void inspectPostApply();
|
||||
|
||||
// NetObject
|
||||
U32 packUpdate(NetConnection *conn, U32 mask, BitStream* stream);
|
||||
void unpackUpdate(NetConnection *conn, BitStream* stream);
|
||||
|
||||
// SceneObject
|
||||
void setTransform(const MatrixF &mat);
|
||||
void prepRenderImage(SceneRenderState* state);
|
||||
|
||||
// GameBase
|
||||
bool onNewDataBlock(GameBaseData *dptr, bool reload);
|
||||
|
||||
// CollisionTrigger
|
||||
void setTriggerPolyhedron(const Polyhedron&);
|
||||
|
||||
void potentialEnterObject(GameBase*);
|
||||
U32 getNumCollisionTriggeringObjects() const;
|
||||
GameBase* getObject(const U32);
|
||||
const Vector<GameBase*>& getObjects() const { return mObjects; }
|
||||
|
||||
void renderObject(ObjectRenderInst *ri, SceneRenderState *state, BaseMatInstance *overrideMat);
|
||||
|
||||
bool castRay(const Point3F &start, const Point3F &end, RayInfo* info);
|
||||
};
|
||||
|
||||
inline U32 CollisionTrigger::getNumCollisionTriggeringObjects() const
|
||||
{
|
||||
return mObjects.size();
|
||||
}
|
||||
|
||||
inline GameBase* CollisionTrigger::getObject(const U32 index)
|
||||
{
|
||||
AssertFatal(index < getNumCollisionTriggeringObjects(), "Error, out of range object index");
|
||||
|
||||
return mObjects[index];
|
||||
}
|
||||
|
||||
#endif // _H_CollisionTrigger
|
||||
|
||||
638
Engine/source/T3D/components/Component.cpp
Normal file
638
Engine/source/T3D/components/Component.cpp
Normal file
|
|
@ -0,0 +1,638 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// 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 "console/simBase.h"
|
||||
#include "console/consoleTypes.h"
|
||||
#include "T3D/Components/Component.h"
|
||||
#include "core/util/safeDelete.h"
|
||||
#include "core/resourceManager.h"
|
||||
#include "core/stream/fileStream.h"
|
||||
#include "core/stream/bitStream.h"
|
||||
#include "console/engineAPI.h"
|
||||
#include "sim/netConnection.h"
|
||||
#include "console/consoleInternal.h"
|
||||
|
||||
#define DECLARE_NATIVE_COMPONENT( ComponentType ) \
|
||||
Component* staticComponentTemplate = new ComponentType; \
|
||||
Sim::gNativeComponentSet->addObject(staticComponentTemplate);
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// Constructor/Destructor
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
Component::Component()
|
||||
{
|
||||
mFriendlyName = StringTable->lookup("");
|
||||
mFromResource = StringTable->lookup("");
|
||||
mComponentType = StringTable->lookup("");
|
||||
mComponentGroup = StringTable->lookup("");
|
||||
mNetworkType = StringTable->lookup("");
|
||||
mTemplateName = StringTable->lookup("");
|
||||
//mDependency = StringTable->lookup("");
|
||||
|
||||
mNetworked = false;
|
||||
|
||||
|
||||
// [tom, 1/12/2007] We manage the memory for the description since it
|
||||
// could be loaded from a file and thus massive. This is accomplished with
|
||||
// protected fields, but since they still call Con::getData() the field
|
||||
// needs to always be valid. This is pretty lame.
|
||||
mDescription = new char[1];
|
||||
((char *)mDescription)[0] = 0;
|
||||
|
||||
mOwner = NULL;
|
||||
|
||||
mCanSaveFieldDictionary = false;
|
||||
|
||||
mNetFlags.set(Ghostable);
|
||||
}
|
||||
|
||||
Component::~Component()
|
||||
{
|
||||
for (S32 i = 0; i < mFields.size(); ++i)
|
||||
{
|
||||
ComponentField &field = mFields[i];
|
||||
SAFE_DELETE_ARRAY(field.mFieldDescription);
|
||||
}
|
||||
|
||||
SAFE_DELETE_ARRAY(mDescription);
|
||||
}
|
||||
|
||||
IMPLEMENT_CO_NETOBJECT_V1(Component);
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void Component::initPersistFields()
|
||||
{
|
||||
addGroup("Component");
|
||||
addField("componentType", TypeCaseString, Offset(mComponentType, Component), "The type of behavior.", AbstractClassRep::FieldFlags::FIELD_HideInInspectors);
|
||||
addField("networkType", TypeCaseString, Offset(mNetworkType, Component), "The type of behavior.", AbstractClassRep::FieldFlags::FIELD_HideInInspectors);
|
||||
addField("friendlyName", TypeCaseString, Offset(mFriendlyName, Component), "Human friendly name of this behavior", AbstractClassRep::FieldFlags::FIELD_HideInInspectors);
|
||||
addProtectedField("description", TypeCaseString, Offset(mDescription, Component), &setDescription, &getDescription,
|
||||
"The description of this behavior which can be set to a \"string\" or a fileName\n", AbstractClassRep::FieldFlags::FIELD_HideInInspectors);
|
||||
|
||||
addField("networked", TypeBool, Offset(mNetworked, Component), "Is this behavior ghosted to clients?", AbstractClassRep::FieldFlags::FIELD_HideInInspectors);
|
||||
|
||||
addProtectedField("Owner", TypeSimObjectPtr, Offset(mOwner, Component), &setOwner, &defaultProtectedGetFn, "", AbstractClassRep::FieldFlags::FIELD_HideInInspectors);
|
||||
|
||||
//addField("hidden", TypeBool, Offset(mHidden, Component), "Flags if this behavior is shown in the editor or not", AbstractClassRep::FieldFlags::FIELD_HideInInspectors);
|
||||
addProtectedField("enabled", TypeBool, Offset(mEnabled, Component), &_setEnabled, &defaultProtectedGetFn, "");
|
||||
endGroup("Component");
|
||||
|
||||
Parent::initPersistFields();
|
||||
|
||||
//clear out irrelevent fields
|
||||
removeField("name");
|
||||
//removeField("internalName");
|
||||
removeField("parentGroup");
|
||||
//removeField("class");
|
||||
removeField("superClass");
|
||||
removeField("hidden");
|
||||
removeField("canSave");
|
||||
removeField("canSaveDynamicFields");
|
||||
removeField("persistentId");
|
||||
}
|
||||
|
||||
bool Component::_setEnabled(void *object, const char *index, const char *data)
|
||||
{
|
||||
Component *c = static_cast<Component*>(object);
|
||||
|
||||
c->mEnabled = dAtob(data);
|
||||
c->setMaskBits(EnableMask);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
bool Component::setDescription(void *object, const char *index, const char *data)
|
||||
{
|
||||
Component *bT = static_cast<Component *>(object);
|
||||
SAFE_DELETE_ARRAY(bT->mDescription);
|
||||
bT->mDescription = bT->getDescriptionText(data);
|
||||
|
||||
// We return false since we don't want the console to mess with the data
|
||||
return false;
|
||||
}
|
||||
|
||||
const char * Component::getDescription(void* obj, const char* data)
|
||||
{
|
||||
Component *object = static_cast<Component *>(obj);
|
||||
|
||||
return object->mDescription ? object->mDescription : "";
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
bool Component::onAdd()
|
||||
{
|
||||
if (!Parent::onAdd())
|
||||
return false;
|
||||
|
||||
setMaskBits(UpdateMask);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Component::onRemove()
|
||||
{
|
||||
onDataSet.removeAll();
|
||||
|
||||
if (mOwner)
|
||||
{
|
||||
//notify our removal to the owner, so we have no loose ends
|
||||
mOwner->removeComponent(this, false);
|
||||
}
|
||||
|
||||
Parent::onRemove();
|
||||
}
|
||||
|
||||
void Component::onComponentAdd()
|
||||
{
|
||||
if (isServerObject())
|
||||
{
|
||||
if (isMethod("onAdd"))
|
||||
Con::executef(this, "onAdd");
|
||||
}
|
||||
|
||||
mEnabled = true;
|
||||
}
|
||||
|
||||
void Component::onComponentRemove()
|
||||
{
|
||||
mEnabled = false;
|
||||
|
||||
if (isServerObject())
|
||||
{
|
||||
if (isMethod("onRemove"))
|
||||
Con::executef(this, "onRemove");
|
||||
}
|
||||
|
||||
if (mOwner)
|
||||
{
|
||||
mOwner->onComponentAdded.remove(this, &Component::componentAddedToOwner);
|
||||
mOwner->onComponentRemoved.remove(this, &Component::componentRemovedFromOwner);
|
||||
mOwner->onTransformSet.remove(this, &Component::ownerTransformSet);
|
||||
}
|
||||
|
||||
mOwner = NULL;
|
||||
setDataField("owner", NULL, "");
|
||||
}
|
||||
|
||||
void Component::setOwner(Entity* owner)
|
||||
{
|
||||
//first, catch if we have an existing owner, and we're changing from it
|
||||
if (mOwner && mOwner != owner)
|
||||
{
|
||||
mOwner->onComponentAdded.remove(this, &Component::componentAddedToOwner);
|
||||
mOwner->onComponentRemoved.remove(this, &Component::componentRemovedFromOwner);
|
||||
mOwner->onTransformSet.remove(this, &Component::ownerTransformSet);
|
||||
|
||||
mOwner->removeComponent(this, false);
|
||||
}
|
||||
|
||||
mOwner = owner;
|
||||
|
||||
if (mOwner != NULL)
|
||||
{
|
||||
mOwner->onComponentAdded.notify(this, &Component::componentAddedToOwner);
|
||||
mOwner->onComponentRemoved.notify(this, &Component::componentRemovedFromOwner);
|
||||
mOwner->onTransformSet.notify(this, &Component::ownerTransformSet);
|
||||
}
|
||||
|
||||
if (isServerObject())
|
||||
setMaskBits(OwnerMask);
|
||||
}
|
||||
|
||||
void Component::componentAddedToOwner(Component *comp)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
void Component::componentRemovedFromOwner(Component *comp)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
void Component::ownerTransformSet(MatrixF *mat)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
U32 Component::packUpdate(NetConnection *con, U32 mask, BitStream *stream)
|
||||
{
|
||||
U32 retMask = Parent::packUpdate(con, mask, stream);
|
||||
|
||||
if (mask & OwnerMask)
|
||||
{
|
||||
if (mOwner != NULL)
|
||||
{
|
||||
S32 ghostIndex = con->getGhostIndex(mOwner);
|
||||
|
||||
if (ghostIndex == -1)
|
||||
{
|
||||
stream->writeFlag(false);
|
||||
retMask |= OwnerMask;
|
||||
}
|
||||
else
|
||||
{
|
||||
stream->writeFlag(true);
|
||||
stream->writeFlag(true);
|
||||
stream->writeInt(ghostIndex, NetConnection::GhostIdBitSize);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
stream->writeFlag(true);
|
||||
stream->writeFlag(false);
|
||||
}
|
||||
}
|
||||
else
|
||||
stream->writeFlag(false);
|
||||
|
||||
if (stream->writeFlag(mask & EnableMask))
|
||||
{
|
||||
stream->writeFlag(mEnabled);
|
||||
}
|
||||
|
||||
return retMask;
|
||||
}
|
||||
|
||||
void Component::unpackUpdate(NetConnection *con, BitStream *stream)
|
||||
{
|
||||
Parent::unpackUpdate(con, stream);
|
||||
|
||||
if (stream->readFlag())
|
||||
{
|
||||
if (stream->readFlag())
|
||||
{
|
||||
//we have an owner object, so fetch it
|
||||
S32 gIndex = stream->readInt(NetConnection::GhostIdBitSize);
|
||||
|
||||
Entity *e = dynamic_cast<Entity*>(con->resolveGhost(gIndex));
|
||||
if (e)
|
||||
e->addComponent(this);
|
||||
}
|
||||
else
|
||||
{
|
||||
//it's being nulled out
|
||||
setOwner(NULL);
|
||||
}
|
||||
}
|
||||
|
||||
if (stream->readFlag())
|
||||
{
|
||||
mEnabled = stream->readFlag();
|
||||
}
|
||||
}
|
||||
|
||||
void Component::packToStream(Stream &stream, U32 tabStop, S32 behaviorID, U32 flags /* = 0 */)
|
||||
{
|
||||
char buffer[1024];
|
||||
|
||||
writeFields(stream, tabStop);
|
||||
|
||||
// Write out the fields which the behavior template knows about
|
||||
for (int i = 0; i < getComponentFieldCount(); i++)
|
||||
{
|
||||
ComponentField *field = getComponentField(i);
|
||||
const char *objFieldValue = getDataField(field->mFieldName, NULL);
|
||||
|
||||
// If the field holds the same value as the template's default value than it
|
||||
// will get initialized by the template, and so it won't be included just
|
||||
// to try to keep the object files looking as non-horrible as possible.
|
||||
if (dStrcmp(field->mDefaultValue, objFieldValue) != 0)
|
||||
{
|
||||
dSprintf(buffer, sizeof(buffer), "%s = \"%s\";\n", field->mFieldName, (dStrlen(objFieldValue) > 0 ? objFieldValue : "0"));
|
||||
|
||||
stream.writeTabs(tabStop);
|
||||
stream.write(dStrlen(buffer), buffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Component::processTick()
|
||||
{
|
||||
if (isServerObject() && mEnabled)
|
||||
{
|
||||
if (mOwner != NULL && isMethod("Update"))
|
||||
Con::executef(this, "Update");
|
||||
}
|
||||
}
|
||||
|
||||
void Component::setDataField(StringTableEntry slotName, const char *array, const char *value)
|
||||
{
|
||||
Parent::setDataField(slotName, array, value);
|
||||
|
||||
onDataSet.trigger(this, slotName, value);
|
||||
}
|
||||
|
||||
|
||||
//catch any behavior field updates
|
||||
void Component::onStaticModified(const char* slotName, const char* newValue)
|
||||
{
|
||||
Parent::onStaticModified(slotName, newValue);
|
||||
|
||||
//If we don't have an owner yet, then this is probably the initial setup, so we don't need the console callbacks yet.
|
||||
if (!mOwner)
|
||||
return;
|
||||
|
||||
onDataSet.trigger(this, slotName, newValue);
|
||||
|
||||
checkComponentFieldModified(slotName, newValue);
|
||||
}
|
||||
|
||||
void Component::onDynamicModified(const char* slotName, const char* newValue)
|
||||
{
|
||||
Parent::onDynamicModified(slotName, newValue);
|
||||
|
||||
//If we don't have an owner yet, then this is probably the initial setup, so we don't need the console callbacks yet.
|
||||
if (!mOwner)
|
||||
return;
|
||||
|
||||
checkComponentFieldModified(slotName, newValue);
|
||||
}
|
||||
|
||||
void Component::checkComponentFieldModified(const char* slotName, const char* newValue)
|
||||
{
|
||||
StringTableEntry slotNameEntry = StringTable->insert(slotName);
|
||||
|
||||
//find if it's a behavior field
|
||||
for (int i = 0; i < mFields.size(); i++)
|
||||
{
|
||||
ComponentField *field = getComponentField(i);
|
||||
if (field->mFieldName == slotNameEntry)
|
||||
{
|
||||
//we have a match, do the script callback that we updated a field
|
||||
if (isMethod("onInspectorUpdate"))
|
||||
Con::executef(this, "onInspectorUpdate", slotName);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
void Component::addComponentField(const char *fieldName, const char *desc, const char *type, const char *defaultValue /* = NULL */, const char *userData /* = NULL */, /*const char* dependency /* = NULL *//*,*/ bool hidden /* = false */)
|
||||
{
|
||||
StringTableEntry stFieldName = StringTable->insert(fieldName);
|
||||
|
||||
for (S32 i = 0; i < mFields.size(); ++i)
|
||||
{
|
||||
if (mFields[i].mFieldName == stFieldName)
|
||||
return;
|
||||
}
|
||||
|
||||
ComponentField field;
|
||||
field.mFieldName = stFieldName;
|
||||
|
||||
//find the field type
|
||||
S32 fieldTypeMask = -1;
|
||||
StringTableEntry fieldType = StringTable->insert(type);
|
||||
|
||||
if (fieldType == StringTable->insert("TypeS32"))
|
||||
fieldTypeMask = TypeS32;
|
||||
else if (fieldType == StringTable->insert("TypeF32"))
|
||||
fieldTypeMask = TypeF32;
|
||||
else if (fieldType == StringTable->insert("TypePoint3F"))
|
||||
fieldTypeMask = TypePoint3F;
|
||||
else if (fieldType == StringTable->insert("TypeMaterialName"))
|
||||
fieldTypeMask = TypeMaterialName;
|
||||
else if (fieldType == StringTable->insert("TypeImageFilename"))
|
||||
fieldTypeMask = TypeImageFilename;
|
||||
else if (fieldType == StringTable->insert("TypeShapeFilename"))
|
||||
fieldTypeMask = TypeShapeFilename;
|
||||
else if (fieldType == StringTable->insert("TypeBool"))
|
||||
fieldTypeMask = TypeBool;
|
||||
else
|
||||
fieldTypeMask = TypeString;
|
||||
|
||||
field.mFieldType = fieldTypeMask;
|
||||
|
||||
field.mUserData = StringTable->insert(userData ? userData : "");
|
||||
field.mDefaultValue = StringTable->insert(defaultValue ? defaultValue : "");
|
||||
field.mFieldDescription = getDescriptionText(desc);
|
||||
|
||||
field.mGroup = mComponentGroup;
|
||||
|
||||
field.mHidden = hidden;
|
||||
|
||||
mFields.push_back(field);
|
||||
|
||||
//Before we set this, we need to do a test to see if this field was already set, like from the mission file or a taml file
|
||||
const char* curFieldData = getDataField(field.mFieldName, NULL);
|
||||
|
||||
if (dStrIsEmpty(curFieldData))
|
||||
setDataField(field.mFieldName, NULL, field.mDefaultValue);
|
||||
}
|
||||
|
||||
ComponentField* Component::getComponentField(const char *fieldName)
|
||||
{
|
||||
StringTableEntry stFieldName = StringTable->insert(fieldName);
|
||||
|
||||
for (S32 i = 0; i < mFields.size(); ++i)
|
||||
{
|
||||
if (mFields[i].mFieldName == stFieldName)
|
||||
return &mFields[i];
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
const char * Component::getDescriptionText(const char *desc)
|
||||
{
|
||||
if (desc == NULL)
|
||||
return NULL;
|
||||
|
||||
char *newDesc;
|
||||
|
||||
// [tom, 1/12/2007] If it isn't a file, just do it the easy way
|
||||
if (!Platform::isFile(desc))
|
||||
{
|
||||
newDesc = new char[dStrlen(desc) + 1];
|
||||
dStrcpy(newDesc, desc);
|
||||
|
||||
return newDesc;
|
||||
}
|
||||
|
||||
FileStream str;
|
||||
str.open(desc, Torque::FS::File::Read);
|
||||
|
||||
Stream *stream = &str;
|
||||
if (stream == NULL){
|
||||
str.close();
|
||||
return NULL;
|
||||
}
|
||||
|
||||
U32 size = stream->getStreamSize();
|
||||
if (size > 0)
|
||||
{
|
||||
newDesc = new char[size + 1];
|
||||
if (stream->read(size, (void *)newDesc))
|
||||
newDesc[size] = 0;
|
||||
else
|
||||
{
|
||||
SAFE_DELETE_ARRAY(newDesc);
|
||||
}
|
||||
}
|
||||
|
||||
str.close();
|
||||
delete stream;
|
||||
|
||||
return newDesc;
|
||||
}
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
void Component::beginFieldGroup(const char* groupName)
|
||||
{
|
||||
if (dStrcmp(mComponentGroup, ""))
|
||||
{
|
||||
Con::errorf("Component: attempting to begin new field group with a group already begun!");
|
||||
return;
|
||||
}
|
||||
|
||||
mComponentGroup = StringTable->insert(groupName);
|
||||
}
|
||||
|
||||
void Component::endFieldGroup()
|
||||
{
|
||||
mComponentGroup = StringTable->insert("");
|
||||
}
|
||||
|
||||
void Component::addDependency(StringTableEntry name)
|
||||
{
|
||||
mDependencies.push_back_unique(name);
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// Console Methods
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
ConsoleMethod(Component, beginGroup, void, 3, 3, "(groupName)\n"
|
||||
"Starts the grouping for following fields being added to be grouped into\n"
|
||||
"@param groupName The name of this group\n"
|
||||
"@param desc The Description of this field\n"
|
||||
"@param type The DataType for this field (default, int, float, Point2F, bool, enum, Object, keybind, color)\n"
|
||||
"@param defaultValue The Default value for this field\n"
|
||||
"@param userData An extra data field that can be used for custom data on a per-field basis<br>Usage for default types<br>"
|
||||
"-enum: a TAB separated list of possible values<br>"
|
||||
"-object: the T2D object type that are valid choices for the field. The object types observe inheritance, so if you have a t2dSceneObject field you will be able to choose t2dStaticSrpites, t2dAnimatedSprites, etc.\n"
|
||||
"@return Nothing\n")
|
||||
{
|
||||
object->beginFieldGroup(argv[2]);
|
||||
}
|
||||
|
||||
ConsoleMethod(Component, endGroup, void, 2, 2, "()\n"
|
||||
"Ends the grouping for prior fields being added to be grouped into\n"
|
||||
"@param groupName The name of this group\n"
|
||||
"@param desc The Description of this field\n"
|
||||
"@param type The DataType for this field (default, int, float, Point2F, bool, enum, Object, keybind, color)\n"
|
||||
"@param defaultValue The Default value for this field\n"
|
||||
"@param userData An extra data field that can be used for custom data on a per-field basis<br>Usage for default types<br>"
|
||||
"-enum: a TAB separated list of possible values<br>"
|
||||
"-object: the T2D object type that are valid choices for the field. The object types observe inheritance, so if you have a t2dSceneObject field you will be able to choose t2dStaticSrpites, t2dAnimatedSprites, etc.\n"
|
||||
"@return Nothing\n")
|
||||
{
|
||||
object->endFieldGroup();
|
||||
}
|
||||
|
||||
DefineConsoleMethod(Component, addComponentField, void, (String fieldName, String fieldDesc, String fieldType, String defValue, String userData, bool hidden),
|
||||
("", "", "", "", "", false),
|
||||
"Get the number of static fields on the object.\n"
|
||||
"@return The number of static fields defined on the object.")
|
||||
{
|
||||
object->addComponentField(fieldName, fieldDesc, fieldType, defValue, userData, hidden);
|
||||
}
|
||||
|
||||
ConsoleMethod(Component, getComponentFieldCount, S32, 2, 2, "() - Get the number of ComponentField's on this object\n"
|
||||
"@return Returns the number of BehaviorFields as a nonnegative integer\n")
|
||||
{
|
||||
return object->getComponentFieldCount();
|
||||
}
|
||||
|
||||
// [tom, 1/12/2007] Field accessors split into multiple methods to allow space
|
||||
// for long descriptions and type data.
|
||||
|
||||
ConsoleMethod(Component, getComponentField, const char *, 3, 3, "(int index) - Gets a Tab-Delimited list of information about a ComponentField specified by Index\n"
|
||||
"@param index The index of the behavior\n"
|
||||
"@return FieldName, FieldType and FieldDefaultValue, each separated by a TAB character.\n")
|
||||
{
|
||||
ComponentField *field = object->getComponentField(dAtoi(argv[2]));
|
||||
if (field == NULL)
|
||||
return "";
|
||||
|
||||
char *buf = Con::getReturnBuffer(1024);
|
||||
dSprintf(buf, 1024, "%s\t%s\t%s\t%s", field->mFieldName, field->mFieldType, field->mDefaultValue, field->mGroup);
|
||||
|
||||
return buf;
|
||||
}
|
||||
|
||||
ConsoleMethod(Component, setComponentield, const char *, 3, 3, "(int index) - Gets a Tab-Delimited list of information about a ComponentField specified by Index\n"
|
||||
"@param index The index of the behavior\n"
|
||||
"@return FieldName, FieldType and FieldDefaultValue, each separated by a TAB character.\n")
|
||||
{
|
||||
ComponentField *field = object->getComponentField(dAtoi(argv[2]));
|
||||
if (field == NULL)
|
||||
return "";
|
||||
|
||||
char *buf = Con::getReturnBuffer(1024);
|
||||
dSprintf(buf, 1024, "%s\t%s\t%s", field->mFieldName, field->mFieldType, field->mDefaultValue);
|
||||
|
||||
return buf;
|
||||
}
|
||||
|
||||
ConsoleMethod(Component, getBehaviorFieldUserData, const char *, 3, 3, "(int index) - Gets the UserData associated with a field by index in the field list\n"
|
||||
"@param index The index of the behavior\n"
|
||||
"@return Returns a string representing the user data of this field\n")
|
||||
{
|
||||
ComponentField *field = object->getComponentField(dAtoi(argv[2]));
|
||||
if (field == NULL)
|
||||
return "";
|
||||
|
||||
return field->mUserData;
|
||||
}
|
||||
|
||||
ConsoleMethod(Component, getComponentFieldDescription, const char *, 3, 3, "(int index) - Gets a field description by index\n"
|
||||
"@param index The index of the behavior\n"
|
||||
"@return Returns a string representing the description of this field\n")
|
||||
{
|
||||
ComponentField *field = object->getComponentField(dAtoi(argv[2]));
|
||||
if (field == NULL)
|
||||
return "";
|
||||
|
||||
return field->mFieldDescription ? field->mFieldDescription : "";
|
||||
}
|
||||
|
||||
ConsoleMethod(Component, addDependency, void, 3, 3, "(string behaviorName) - Gets a field description by index\n"
|
||||
"@param index The index of the behavior\n"
|
||||
"@return Returns a string representing the description of this field\n")
|
||||
{
|
||||
object->addDependency(argv[2]);
|
||||
}
|
||||
|
||||
ConsoleMethod(Component, setDirty, void, 2, 2, "() - Gets a field description by index\n"
|
||||
"@param index The index of the behavior\n"
|
||||
"@return Returns a string representing the description of this field\n")
|
||||
{
|
||||
object->setMaskBits(Component::OwnerMask);
|
||||
}
|
||||
197
Engine/source/T3D/components/Component.h
Normal file
197
Engine/source/T3D/components/Component.h
Normal file
|
|
@ -0,0 +1,197 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// 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 COMPONENT_H
|
||||
#define COMPONENT_H
|
||||
|
||||
#ifndef _NETOBJECT_H_
|
||||
#include "sim/netObject.h"
|
||||
#endif
|
||||
#ifndef ENTITY_H
|
||||
#include "T3D/Entity.h"
|
||||
#endif
|
||||
#ifndef CORE_INTERFACES_H
|
||||
#include "T3D/Components/coreInterfaces.h"
|
||||
#endif
|
||||
|
||||
class Entity;
|
||||
|
||||
struct ComponentField
|
||||
{
|
||||
StringTableEntry mFieldName;
|
||||
StringTableEntry mFieldDescription;
|
||||
|
||||
S32 mFieldType;
|
||||
StringTableEntry mUserData;
|
||||
|
||||
StringTableEntry mDefaultValue;
|
||||
|
||||
StringTableEntry mGroup;
|
||||
|
||||
StringTableEntry mDependency;
|
||||
|
||||
bool mHidden;
|
||||
};
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
///
|
||||
///
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
class Component : public NetObject, public UpdateInterface
|
||||
{
|
||||
typedef NetObject Parent;
|
||||
|
||||
protected:
|
||||
StringTableEntry mFriendlyName;
|
||||
StringTableEntry mDescription;
|
||||
|
||||
StringTableEntry mFromResource;
|
||||
StringTableEntry mComponentGroup;
|
||||
StringTableEntry mComponentType;
|
||||
StringTableEntry mNetworkType;
|
||||
StringTableEntry mTemplateName;
|
||||
|
||||
Vector<StringTableEntry> mDependencies;
|
||||
Vector<ComponentField> mFields;
|
||||
|
||||
bool mNetworked;
|
||||
|
||||
U32 componentIdx;
|
||||
|
||||
Entity* mOwner;
|
||||
bool mHidden;
|
||||
bool mEnabled;
|
||||
|
||||
public:
|
||||
Component();
|
||||
virtual ~Component();
|
||||
DECLARE_CONOBJECT(Component);
|
||||
|
||||
virtual bool onAdd();
|
||||
virtual void onRemove();
|
||||
static void initPersistFields();
|
||||
|
||||
virtual void packToStream(Stream &stream, U32 tabStop, S32 behaviorID, U32 flags = 0);
|
||||
|
||||
//This is called when we are added to an entity
|
||||
virtual void onComponentAdd();
|
||||
//This is called when we are removed from an entity
|
||||
virtual void onComponentRemove();
|
||||
|
||||
//This is called when a different component is added to our owner entity
|
||||
virtual void componentAddedToOwner(Component *comp);
|
||||
//This is called when a different component is removed from our owner entity
|
||||
virtual void componentRemovedFromOwner(Component *comp);
|
||||
|
||||
virtual void ownerTransformSet(MatrixF *mat);
|
||||
|
||||
void setOwner(Entity* pOwner);
|
||||
inline Entity *getOwner() { return mOwner ? mOwner : NULL; }
|
||||
static bool setOwner(void *object, const char *index, const char *data) { return true; }
|
||||
|
||||
bool isEnabled() { return mEnabled; }
|
||||
void setEnabled(bool toggle) { mEnabled = toggle; setMaskBits(EnableMask); }
|
||||
|
||||
bool isActive() { return mEnabled && mOwner != NULL; }
|
||||
|
||||
static bool _setEnabled(void *object, const char *index, const char *data);
|
||||
|
||||
virtual void processTick();
|
||||
virtual void interpolateTick(F32 dt){}
|
||||
virtual void advanceTime(F32 dt){}
|
||||
|
||||
/// @name Adding Named Fields
|
||||
/// @{
|
||||
|
||||
/// Adds a named field to a Component that can specify a description, data type, default value and userData
|
||||
///
|
||||
/// @param fieldName The name of the Field
|
||||
/// @param desc The Description of the Field
|
||||
/// @param type The Type of field that this is, example 'Text' or 'Bool'
|
||||
/// @param defaultValue The Default value of this field
|
||||
/// @param userData An extra optional field that can be used for user data
|
||||
void addComponentField(const char *fieldName, const char *desc, const char *type, const char *defaultValue = NULL, const char *userData = NULL, bool hidden = false);
|
||||
|
||||
/// Returns the number of ComponentField's on this template
|
||||
inline S32 getComponentFieldCount() { return mFields.size(); };
|
||||
|
||||
/// Gets a ComponentField by its index in the mFields vector
|
||||
/// @param idx The index of the field in the mField vector
|
||||
inline ComponentField *getComponentField(S32 idx)
|
||||
{
|
||||
if (idx < 0 || idx >= mFields.size())
|
||||
return NULL;
|
||||
|
||||
return &mFields[idx];
|
||||
}
|
||||
|
||||
ComponentField *getComponentField(const char* fieldName);
|
||||
|
||||
const char* getComponentType() { return mComponentType; }
|
||||
|
||||
const char *getDescriptionText(const char *desc);
|
||||
|
||||
const char *getName() { return mTemplateName; }
|
||||
|
||||
const char *getFriendlyName() { return mFriendlyName; }
|
||||
|
||||
bool isNetworked() { return mNetworked; }
|
||||
|
||||
void beginFieldGroup(const char* groupName);
|
||||
void endFieldGroup();
|
||||
|
||||
void addDependency(StringTableEntry name);
|
||||
/// @}
|
||||
|
||||
/// @name Description
|
||||
/// @{
|
||||
static bool setDescription(void *object, const char *index, const char *data);
|
||||
static const char* getDescription(void* obj, const char* data);
|
||||
|
||||
/// @Primary usage functions
|
||||
/// @These are used by the various engine-based behaviors to integrate with the component classes
|
||||
enum NetMaskBits
|
||||
{
|
||||
InitialUpdateMask = BIT(0),
|
||||
OwnerMask = BIT(1),
|
||||
UpdateMask = BIT(2),
|
||||
EnableMask = BIT(3),
|
||||
NextFreeMask = BIT(4)
|
||||
};
|
||||
|
||||
virtual U32 packUpdate(NetConnection *con, U32 mask, BitStream *stream);
|
||||
virtual void unpackUpdate(NetConnection *con, BitStream *stream);
|
||||
/// @}
|
||||
|
||||
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); ///< Called when a static field is modified.
|
||||
virtual void onDynamicModified(const char* slotName, const char*newValue = NULL); ///< Called when a dynamic field is modified.
|
||||
|
||||
/// This is what we actually use to check if the modified field is one of our behavior fields. If it is, we update and make the correct callbacks
|
||||
void checkComponentFieldModified(const char* slotName, const char* newValue);
|
||||
|
||||
virtual void checkDependencies(){}
|
||||
};
|
||||
|
||||
#endif // COMPONENT_H
|
||||
215
Engine/source/T3D/components/Game/StateMachineComponent.cpp
Normal file
215
Engine/source/T3D/components/Game/StateMachineComponent.cpp
Normal file
|
|
@ -0,0 +1,215 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// 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 "T3D/Components/game/StateMachineComponent.h"
|
||||
|
||||
#include "platform/platform.h"
|
||||
#include "console/consoleTypes.h"
|
||||
#include "core/util/safeDelete.h"
|
||||
#include "core/resourceManager.h"
|
||||
#include "core/stream/fileStream.h"
|
||||
#include "console/consoleTypes.h"
|
||||
#include "console/consoleObject.h"
|
||||
#include "ts/tsShapeInstance.h"
|
||||
#include "core/stream/bitStream.h"
|
||||
#include "gfx/gfxTransformSaver.h"
|
||||
#include "console/engineAPI.h"
|
||||
#include "lighting/lightQuery.h"
|
||||
|
||||
IMPLEMENT_CALLBACK( StateMachineComponent, onStateChange, void, (), (),
|
||||
"@brief Called when we collide with another object.\n\n"
|
||||
"@param obj The ShapeBase object\n"
|
||||
"@param collObj The object we collided with\n"
|
||||
"@param vec Collision impact vector\n"
|
||||
"@param len Length of the impact vector\n" );
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// Constructor/Destructor
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
StateMachineComponent::StateMachineComponent() : Component()
|
||||
{
|
||||
mFriendlyName = "State Machine";
|
||||
mComponentType = "Game";
|
||||
|
||||
mDescription = getDescriptionText("A generic state machine.");
|
||||
|
||||
mStateMachineFile = "";
|
||||
|
||||
//doesn't need to be networked
|
||||
mNetworked = false;
|
||||
mNetFlags.clear();
|
||||
}
|
||||
|
||||
StateMachineComponent::~StateMachineComponent()
|
||||
{
|
||||
for(S32 i = 0;i < mFields.size();++i)
|
||||
{
|
||||
ComponentField &field = mFields[i];
|
||||
SAFE_DELETE_ARRAY(field.mFieldDescription);
|
||||
}
|
||||
|
||||
SAFE_DELETE_ARRAY(mDescription);
|
||||
}
|
||||
|
||||
IMPLEMENT_CO_NETOBJECT_V1(StateMachineComponent);
|
||||
|
||||
bool StateMachineComponent::onAdd()
|
||||
{
|
||||
if(! Parent::onAdd())
|
||||
return false;
|
||||
|
||||
// Register for the resource change signal.
|
||||
ResourceManager::get().getChangedSignal().notify(this, &StateMachineComponent::_onResourceChanged);
|
||||
|
||||
mStateMachine.onStateChanged.notify(this, &StateMachineComponent::onStateChanged);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void StateMachineComponent::onRemove()
|
||||
{
|
||||
Parent::onRemove();
|
||||
}
|
||||
|
||||
U32 StateMachineComponent::packUpdate(NetConnection *con, U32 mask, BitStream *stream)
|
||||
{
|
||||
U32 retMask = Parent::packUpdate(con, mask, stream);
|
||||
return retMask;
|
||||
}
|
||||
|
||||
void StateMachineComponent::unpackUpdate(NetConnection *con, BitStream *stream)
|
||||
{
|
||||
Parent::unpackUpdate(con, stream);
|
||||
}
|
||||
|
||||
//This is mostly a catch for situations where the behavior is re-added to the object and the like and we may need to force an update to the behavior
|
||||
void StateMachineComponent::onComponentAdd()
|
||||
{
|
||||
Parent::onComponentAdd();
|
||||
}
|
||||
|
||||
void StateMachineComponent::onComponentRemove()
|
||||
{
|
||||
Parent::onComponentRemove();
|
||||
}
|
||||
|
||||
void StateMachineComponent::initPersistFields()
|
||||
{
|
||||
Parent::initPersistFields();
|
||||
|
||||
addProtectedField("stateMachineFile", TypeFilename, Offset(mStateMachineFile, StateMachineComponent),
|
||||
&_setSMFile, &defaultProtectedGetFn, "The sim time of when we started this state");
|
||||
}
|
||||
|
||||
bool StateMachineComponent::_setSMFile(void *object, const char *index, const char *data)
|
||||
{
|
||||
StateMachineComponent* smComp = static_cast<StateMachineComponent*>(object);
|
||||
if (smComp)
|
||||
{
|
||||
smComp->setStateMachineFile(data);
|
||||
smComp->loadStateMachineFile();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void StateMachineComponent::_onResourceChanged(const Torque::Path &path)
|
||||
{
|
||||
if (path != Torque::Path(mStateMachineFile))
|
||||
return;
|
||||
|
||||
loadStateMachineFile();
|
||||
}
|
||||
|
||||
void StateMachineComponent::loadStateMachineFile()
|
||||
{
|
||||
if (!dStrIsEmpty(mStateMachineFile))
|
||||
{
|
||||
mStateMachine.mStateMachineFile = mStateMachineFile;
|
||||
mStateMachine.loadStateMachineFile();
|
||||
|
||||
//now that it's loaded, we need to parse the SM's fields and set them as script vars on ourselves
|
||||
S32 smFieldCount = mStateMachine.getFieldsCount();
|
||||
|
||||
for (U32 i = 0; i < smFieldCount; i++)
|
||||
{
|
||||
StateMachine::StateField field = mStateMachine.getField(i);
|
||||
|
||||
char buffer[128];
|
||||
|
||||
if (field.fieldType == StateMachine::StateField::BooleanType)
|
||||
{
|
||||
dSprintf(buffer, sizeof(buffer), "%b", field.triggerBoolVal);
|
||||
setDataField(field.name, NULL, buffer);
|
||||
}
|
||||
else if (field.fieldType == StateMachine::StateField::NumberType)
|
||||
{
|
||||
dSprintf(buffer, sizeof(buffer), "%g", field.triggerNumVal);
|
||||
setDataField(field.name, NULL, buffer);
|
||||
}
|
||||
else if (field.fieldType == StateMachine::StateField::StringType)
|
||||
{
|
||||
setDataField(field.name, NULL, field.triggerStringVal);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void StateMachineComponent::processTick()
|
||||
{
|
||||
if (!isServerObject() || !isActive())
|
||||
return;
|
||||
|
||||
mStateMachine.update();
|
||||
}
|
||||
|
||||
void StateMachineComponent::onDynamicModified( const char* slotName, const char* newValue )
|
||||
{
|
||||
Parent::onDynamicModified(slotName, newValue);
|
||||
|
||||
StringTableEntry fieldName = StringTable->insert(slotName);
|
||||
mStateMachine.checkTransitions(fieldName, newValue);
|
||||
}
|
||||
|
||||
void StateMachineComponent::onStaticModified( const char* slotName, const char* newValue )
|
||||
{
|
||||
Parent::onStaticModified(slotName, newValue);
|
||||
|
||||
StringTableEntry fieldName = StringTable->insert(slotName);
|
||||
mStateMachine.checkTransitions(fieldName, newValue);
|
||||
}
|
||||
|
||||
void StateMachineComponent::onStateChanged(StateMachine* sm, S32 stateIdx)
|
||||
{
|
||||
//do a script callback, if we have one
|
||||
//check if we have a function for that, and then also check if our owner does
|
||||
StringTableEntry callbackName = mStateMachine.getCurrentState().callbackName;
|
||||
|
||||
if (isMethod(callbackName))
|
||||
Con::executef(this, callbackName);
|
||||
|
||||
if (mOwner->isMethod(callbackName))
|
||||
Con::executef(mOwner, callbackName);
|
||||
}
|
||||
81
Engine/source/T3D/components/Game/StateMachineComponent.h
Normal file
81
Engine/source/T3D/components/Game/StateMachineComponent.h
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// 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 STATE_MACHINE_COMPONENT_H
|
||||
#define STATE_MACHINE_COMPONENT_H
|
||||
|
||||
#ifndef COMPONENT_H
|
||||
#include "T3D/Components/Component.h"
|
||||
#endif
|
||||
#ifndef STATE_MACHINE_H
|
||||
#include "T3D/components/Game/stateMachine.h"
|
||||
#endif
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
///
|
||||
///
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
class StateMachineComponent : public Component
|
||||
{
|
||||
typedef Component Parent;
|
||||
|
||||
public:
|
||||
StateMachine mStateMachine;
|
||||
|
||||
protected:
|
||||
StringTableEntry mStateMachineFile;
|
||||
|
||||
public:
|
||||
StateMachineComponent();
|
||||
virtual ~StateMachineComponent();
|
||||
DECLARE_CONOBJECT(StateMachineComponent);
|
||||
|
||||
virtual bool onAdd();
|
||||
virtual void onRemove();
|
||||
static void initPersistFields();
|
||||
|
||||
virtual void onComponentAdd();
|
||||
virtual void onComponentRemove();
|
||||
|
||||
void _onResourceChanged(const Torque::Path &path);
|
||||
|
||||
virtual U32 packUpdate(NetConnection *con, U32 mask, BitStream *stream);
|
||||
virtual void unpackUpdate(NetConnection *con, BitStream *stream);
|
||||
|
||||
virtual void processTick();
|
||||
|
||||
virtual void onDynamicModified(const char* slotName, const char* newValue);
|
||||
virtual void onStaticModified(const char* slotName, const char* newValue);
|
||||
|
||||
virtual void loadStateMachineFile();
|
||||
|
||||
void setStateMachineFile(const char* fileName) { mStateMachineFile = StringTable->insert(fileName); }
|
||||
|
||||
static bool _setSMFile(void *object, const char *index, const char *data);
|
||||
|
||||
void onStateChanged(StateMachine* sm, S32 stateIdx);
|
||||
|
||||
//Callbacks
|
||||
DECLARE_CALLBACK(void, onStateChange, ());
|
||||
};
|
||||
|
||||
#endif
|
||||
434
Engine/source/T3D/components/Game/stateMachine.cpp
Normal file
434
Engine/source/T3D/components/Game/stateMachine.cpp
Normal file
|
|
@ -0,0 +1,434 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// 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 "T3D/Components/Game/stateMachine.h"
|
||||
|
||||
StateMachine::StateMachine()
|
||||
{
|
||||
mStateStartTime = -1;
|
||||
mStateTime = 0;
|
||||
|
||||
mStartingState = "";
|
||||
|
||||
mCurCreateState = NULL;
|
||||
}
|
||||
|
||||
StateMachine::~StateMachine()
|
||||
{
|
||||
}
|
||||
|
||||
void StateMachine::loadStateMachineFile()
|
||||
{
|
||||
if (!mXMLReader)
|
||||
{
|
||||
SimXMLDocument *xmlrdr = new SimXMLDocument();
|
||||
xmlrdr->registerObject();
|
||||
|
||||
mXMLReader = xmlrdr;
|
||||
}
|
||||
|
||||
bool hasStartState = false;
|
||||
|
||||
if (!dStrIsEmpty(mStateMachineFile))
|
||||
{
|
||||
//use our xml reader to parse the file!
|
||||
SimXMLDocument *reader = mXMLReader.getObject();
|
||||
if (!reader->loadFile(mStateMachineFile))
|
||||
Con::errorf("Could not load state machine file: &s", mStateMachineFile);
|
||||
|
||||
if (!reader->pushFirstChildElement("StateMachine"))
|
||||
return;
|
||||
|
||||
//find our starting state
|
||||
if (reader->pushFirstChildElement("StartingState"))
|
||||
{
|
||||
mStartingState = reader->getData();
|
||||
reader->popElement();
|
||||
hasStartState = true;
|
||||
}
|
||||
|
||||
readStates();
|
||||
}
|
||||
|
||||
if (hasStartState)
|
||||
mCurrentState = getStateByName(mStartingState);
|
||||
|
||||
mStateStartTime = -1;
|
||||
mStateTime = 0;
|
||||
}
|
||||
|
||||
void StateMachine::readStates()
|
||||
{
|
||||
SimXMLDocument *reader = mXMLReader.getObject();
|
||||
|
||||
//iterate through our states now!
|
||||
if (reader->pushFirstChildElement("State"))
|
||||
{
|
||||
//get our first state
|
||||
State firstState;
|
||||
|
||||
readStateName(&firstState, reader);
|
||||
readStateScriptFunction(&firstState, reader);
|
||||
|
||||
readTransitions(firstState);
|
||||
|
||||
mStates.push_back(firstState);
|
||||
|
||||
//now, iterate the siblings
|
||||
while (reader->nextSiblingElement("State"))
|
||||
{
|
||||
State newState;
|
||||
readStateName(&newState, reader);
|
||||
readStateScriptFunction(&newState, reader);
|
||||
|
||||
readTransitions(newState);
|
||||
|
||||
mStates.push_back(newState);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void StateMachine::readTransitions(State ¤tState)
|
||||
{
|
||||
SimXMLDocument *reader = mXMLReader.getObject();
|
||||
|
||||
//iterate through our states now!
|
||||
if (reader->pushFirstChildElement("Transition"))
|
||||
{
|
||||
//get our first state
|
||||
StateTransition firstTransition;
|
||||
|
||||
readTransitonTarget(&firstTransition, reader);
|
||||
|
||||
readConditions(firstTransition);
|
||||
|
||||
currentState.mTransitions.push_back(firstTransition);
|
||||
|
||||
//now, iterate the siblings
|
||||
while (reader->nextSiblingElement("Transition"))
|
||||
{
|
||||
StateTransition newTransition;
|
||||
readTransitonTarget(&newTransition, reader);
|
||||
|
||||
readConditions(newTransition);
|
||||
|
||||
currentState.mTransitions.push_back(newTransition);
|
||||
}
|
||||
|
||||
reader->popElement();
|
||||
}
|
||||
}
|
||||
|
||||
void StateMachine::readConditions(StateTransition ¤tTransition)
|
||||
{
|
||||
SimXMLDocument *reader = mXMLReader.getObject();
|
||||
|
||||
//iterate through our states now!
|
||||
if (reader->pushFirstChildElement("Rule"))
|
||||
{
|
||||
//get our first state
|
||||
StateTransition::Condition firstCondition;
|
||||
StateField firstField;
|
||||
bool fieldRead = false;
|
||||
|
||||
readFieldName(&firstField, reader);
|
||||
firstCondition.field = firstField;
|
||||
|
||||
readFieldComparitor(&firstCondition, reader);
|
||||
|
||||
readFieldValue(&firstCondition.field, reader);
|
||||
|
||||
currentTransition.mTransitionRules.push_back(firstCondition);
|
||||
|
||||
//now, iterate the siblings
|
||||
while (reader->nextSiblingElement("Transition"))
|
||||
{
|
||||
StateTransition::Condition newCondition;
|
||||
StateField newField;
|
||||
|
||||
readFieldName(&newField, reader);
|
||||
newCondition.field = newField;
|
||||
|
||||
readFieldComparitor(&newCondition, reader);
|
||||
|
||||
readFieldValue(&newCondition.field, reader);
|
||||
|
||||
currentTransition.mTransitionRules.push_back(newCondition);
|
||||
}
|
||||
|
||||
reader->popElement();
|
||||
}
|
||||
}
|
||||
|
||||
S32 StateMachine::parseComparitor(const char* comparitorName)
|
||||
{
|
||||
S32 targetType = -1;
|
||||
|
||||
if (!dStrcmp("GreaterThan", comparitorName))
|
||||
targetType = StateMachine::StateTransition::Condition::GeaterThan;
|
||||
else if (!dStrcmp("GreaterOrEqual", comparitorName))
|
||||
targetType = StateMachine::StateTransition::Condition::GreaterOrEqual;
|
||||
else if (!dStrcmp("LessThan", comparitorName))
|
||||
targetType = StateMachine::StateTransition::Condition::LessThan;
|
||||
else if (!dStrcmp("LessOrEqual", comparitorName))
|
||||
targetType = StateMachine::StateTransition::Condition::LessOrEqual;
|
||||
else if (!dStrcmp("Equals", comparitorName))
|
||||
targetType = StateMachine::StateTransition::Condition::Equals;
|
||||
else if (!dStrcmp("True", comparitorName))
|
||||
targetType = StateMachine::StateTransition::Condition::True;
|
||||
else if (!dStrcmp("False", comparitorName))
|
||||
targetType = StateMachine::StateTransition::Condition::False;
|
||||
else if (!dStrcmp("Negative", comparitorName))
|
||||
targetType = StateMachine::StateTransition::Condition::Negative;
|
||||
else if (!dStrcmp("Positive", comparitorName))
|
||||
targetType = StateMachine::StateTransition::Condition::Positive;
|
||||
else if (!dStrcmp("DoesNotEqual", comparitorName))
|
||||
targetType = StateMachine::StateTransition::Condition::DoesNotEqual;
|
||||
|
||||
return targetType;
|
||||
}
|
||||
|
||||
void StateMachine::update()
|
||||
{
|
||||
//we always check if there's a timout transition, as that's the most generic transition possible.
|
||||
F32 curTime = Sim::getCurrentTime();
|
||||
|
||||
if (mStateStartTime == -1)
|
||||
mStateStartTime = curTime;
|
||||
|
||||
mStateTime = curTime - mStateStartTime;
|
||||
|
||||
char buffer[64];
|
||||
dSprintf(buffer, sizeof(buffer), "%g", mStateTime);
|
||||
|
||||
checkTransitions("stateTime", buffer);
|
||||
}
|
||||
|
||||
void StateMachine::checkTransitions(const char* slotName, const char* newValue)
|
||||
{
|
||||
//because we use our current state's fields as dynamic fields on the instance
|
||||
//we'll want to catch any fields being set so we can treat changes as transition triggers if
|
||||
//any of the transitions on this state call for it
|
||||
|
||||
//One example would be in order to implement burst fire on a weapon state machine.
|
||||
//The behavior instance has a dynamic variable set up like: GunStateMachine.burstShotCount = 0;
|
||||
|
||||
//We also have a transition in our fire state, as: GunStateMachine.addTransition("FireState", "burstShotCount", "DoneShooting", 3);
|
||||
//What that does is for our fire state, we check the dynamicField burstShotCount if it's equal or greater than 3. If it is, we perform the transition.
|
||||
|
||||
//As state fields are handled as dynamicFields for the instance, regular dynamicFields are processed as well as state fields. So we can use the regular
|
||||
//dynamic fields for our transitions, to act as 'global' variables that are state-agnostic. Alternately, we can use state-specific fields, such as a transition
|
||||
//like this:
|
||||
//GunStateMachine.addTransition("IdleState", "Fidget", "Timeout", ">=", 5000);
|
||||
|
||||
//That uses the the timeout field, which is reset each time the state changes, and so state-specific, to see if it's been 5 seconds. If it has been, we transition
|
||||
//to our fidget state
|
||||
|
||||
//so, lets check our current transitions
|
||||
//now that we have the type, check our transitions!
|
||||
for (U32 t = 0; t < mCurrentState.mTransitions.size(); t++)
|
||||
{
|
||||
//if (!dStrcmp(mCurrentState.mTransitions[t]., slotName))
|
||||
{
|
||||
//found a transition looking for this variable, so do work
|
||||
//first, figure out what data type thie field is
|
||||
//S32 type = getVariableType(newValue);
|
||||
|
||||
bool fail = false;
|
||||
bool match = false;
|
||||
S32 ruleCount = mCurrentState.mTransitions[t].mTransitionRules.size();
|
||||
|
||||
for (U32 r = 0; r < ruleCount; r++)
|
||||
{
|
||||
const char* fieldName = mCurrentState.mTransitions[t].mTransitionRules[r].field.name;
|
||||
if (!dStrcmp(fieldName, slotName))
|
||||
{
|
||||
match = true;
|
||||
//now, check the value with the comparitor and see if we do the transition.
|
||||
if (!passComparitorCheck(newValue, mCurrentState.mTransitions[t].mTransitionRules[r]))
|
||||
{
|
||||
fail = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//If we do have a transition rule for this field, and we didn't fail on the condition, go ahead and switch states
|
||||
if (match && !fail)
|
||||
{
|
||||
setState(mCurrentState.mTransitions[t].mStateTarget);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool StateMachine::passComparitorCheck(const char* var, StateTransition::Condition transitionRule)
|
||||
{
|
||||
F32 num = dAtof(var);
|
||||
switch (transitionRule.field.fieldType)
|
||||
{
|
||||
case StateField::Type::VectorType:
|
||||
switch (transitionRule.triggerComparitor)
|
||||
{
|
||||
case StateTransition::Condition::Equals:
|
||||
case StateTransition::Condition::GeaterThan:
|
||||
case StateTransition::Condition::GreaterOrEqual:
|
||||
case StateTransition::Condition::LessThan:
|
||||
case StateTransition::Condition::LessOrEqual:
|
||||
case StateTransition::Condition::DoesNotEqual:
|
||||
//do
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
};
|
||||
case StateField::Type::StringType:
|
||||
switch (transitionRule.triggerComparitor)
|
||||
{
|
||||
case StateTransition::Condition::Equals:
|
||||
if (!dStrcmp(var, transitionRule.field.triggerStringVal))
|
||||
return true;
|
||||
else
|
||||
return false;
|
||||
case StateTransition::Condition::DoesNotEqual:
|
||||
if (dStrcmp(var, transitionRule.field.triggerStringVal))
|
||||
return true;
|
||||
else
|
||||
return false;
|
||||
default:
|
||||
return false;
|
||||
};
|
||||
case StateField::Type::BooleanType:
|
||||
switch (transitionRule.triggerComparitor)
|
||||
{
|
||||
case StateTransition::Condition::TriggerValueTarget::True:
|
||||
if (dAtob(var))
|
||||
return true;
|
||||
else
|
||||
return false;
|
||||
case StateTransition::Condition::TriggerValueTarget::False:
|
||||
if (dAtob(var))
|
||||
return false;
|
||||
else
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
};
|
||||
case StateField::Type::NumberType:
|
||||
switch (transitionRule.triggerComparitor)
|
||||
{
|
||||
case StateTransition::Condition::TriggerValueTarget::Equals:
|
||||
if (num == transitionRule.field.triggerNumVal)
|
||||
return true;
|
||||
else
|
||||
return false;
|
||||
case StateTransition::Condition::TriggerValueTarget::GeaterThan:
|
||||
if (num > transitionRule.field.triggerNumVal)
|
||||
return true;
|
||||
else
|
||||
return false;
|
||||
case StateTransition::Condition::TriggerValueTarget::GreaterOrEqual:
|
||||
if (num >= transitionRule.field.triggerNumVal)
|
||||
return true;
|
||||
else
|
||||
return false;
|
||||
case StateTransition::Condition::TriggerValueTarget::LessThan:
|
||||
if (num < transitionRule.field.triggerNumVal)
|
||||
return true;
|
||||
else
|
||||
return false;
|
||||
case StateTransition::Condition::TriggerValueTarget::LessOrEqual:
|
||||
if (num <= transitionRule.field.triggerNumVal)
|
||||
return true;
|
||||
else
|
||||
return false;
|
||||
case StateTransition::Condition::TriggerValueTarget::DoesNotEqual:
|
||||
if (num != transitionRule.field.triggerNumVal)
|
||||
return true;
|
||||
else
|
||||
return false;
|
||||
case StateTransition::Condition::TriggerValueTarget::Positive:
|
||||
if (num > 0)
|
||||
return true;
|
||||
else
|
||||
return false;
|
||||
case StateTransition::Condition::TriggerValueTarget::Negative:
|
||||
if (num < 0)
|
||||
return true;
|
||||
else
|
||||
return false;
|
||||
default:
|
||||
return false;
|
||||
};
|
||||
default:
|
||||
return false;
|
||||
};
|
||||
}
|
||||
|
||||
void StateMachine::setState(const char* stateName, bool clearFields)
|
||||
{
|
||||
State oldState = mCurrentState;
|
||||
StringTableEntry sName = StringTable->insert(stateName);
|
||||
for (U32 i = 0; i < mStates.size(); i++)
|
||||
{
|
||||
//if(!dStrcmp(mStates[i]->stateName, stateName))
|
||||
if (!dStrcmp(mStates[i].stateName,sName))
|
||||
{
|
||||
mCurrentState = mStates[i];
|
||||
mStateStartTime = Sim::getCurrentTime();
|
||||
|
||||
onStateChanged.trigger(this, i);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const char* StateMachine::getStateByIndex(S32 index)
|
||||
{
|
||||
if (index >= 0 && mStates.size() > index)
|
||||
return mStates[index].stateName;
|
||||
else
|
||||
return "";
|
||||
}
|
||||
|
||||
StateMachine::State& StateMachine::getStateByName(const char* name)
|
||||
{
|
||||
StringTableEntry stateName = StringTable->insert(name);
|
||||
|
||||
for (U32 i = 0; i < mStates.size(); i++)
|
||||
{
|
||||
if (!dStrcmp(stateName, mStates[i].stateName))
|
||||
return mStates[i];
|
||||
}
|
||||
}
|
||||
|
||||
S32 StateMachine::findFieldByName(const char* name)
|
||||
{
|
||||
for (U32 i = 0; i < mFields.size(); i++)
|
||||
{
|
||||
if (!dStrcmp(mFields[i].name, name))
|
||||
return i;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
259
Engine/source/T3D/components/Game/stateMachine.h
Normal file
259
Engine/source/T3D/components/Game/stateMachine.h
Normal file
|
|
@ -0,0 +1,259 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// 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 STATE_MACHINE_H
|
||||
#define STATE_MACHINE_H
|
||||
|
||||
#ifndef _SIMBASE_H_
|
||||
#include "console/simBase.h"
|
||||
#endif
|
||||
#ifndef _OBJECTTYPES_H_
|
||||
#include "T3D/objectTypes.h"
|
||||
#endif
|
||||
#ifndef _MMATH_H_
|
||||
#include "math/mMath.h"
|
||||
#endif
|
||||
#ifndef _XMLDOC_H_
|
||||
#include "console/SimXMLDocument.h"
|
||||
#endif
|
||||
|
||||
class StateMachine
|
||||
{
|
||||
public:
|
||||
struct StateField
|
||||
{
|
||||
StringTableEntry name;
|
||||
|
||||
bool triggerBoolVal;
|
||||
float triggerNumVal;
|
||||
Point3F triggerVectorVal;
|
||||
String triggerStringVal;
|
||||
|
||||
enum Type
|
||||
{
|
||||
BooleanType = 0,
|
||||
NumberType,
|
||||
VectorType,
|
||||
StringType
|
||||
}fieldType;
|
||||
};
|
||||
|
||||
struct UniqueReference
|
||||
{
|
||||
SimObject* referenceObj;
|
||||
const char* referenceVar;
|
||||
const char* uniqueName;
|
||||
};
|
||||
|
||||
struct StateTransition
|
||||
{
|
||||
struct Condition
|
||||
{
|
||||
enum TriggerValueTarget
|
||||
{
|
||||
Equals = 0,
|
||||
GeaterThan,
|
||||
LessThan,
|
||||
GreaterOrEqual,
|
||||
LessOrEqual,
|
||||
True,
|
||||
False,
|
||||
Positive,
|
||||
Negative,
|
||||
DoesNotEqual
|
||||
};
|
||||
|
||||
StateField field;
|
||||
|
||||
TriggerValueTarget triggerComparitor;
|
||||
|
||||
UniqueReference *valUniqueRef;
|
||||
};
|
||||
|
||||
StringTableEntry mName;
|
||||
StringTableEntry mStateTarget;
|
||||
Vector<Condition> mTransitionRules;
|
||||
};
|
||||
|
||||
struct State
|
||||
{
|
||||
Vector<StateTransition> mTransitions;
|
||||
|
||||
StringTableEntry stateName;
|
||||
|
||||
StringTableEntry callbackName;
|
||||
};
|
||||
|
||||
StringTableEntry mStateMachineFile;
|
||||
|
||||
protected:
|
||||
Vector<State> mStates;
|
||||
|
||||
Vector<StateField> mFields;
|
||||
|
||||
Vector<UniqueReference> mUniqueReferences;
|
||||
|
||||
State mCurrentState;
|
||||
|
||||
F32 mStateStartTime;
|
||||
F32 mStateTime;
|
||||
|
||||
StringTableEntry mStartingState;
|
||||
|
||||
State *mCurCreateSuperState;
|
||||
State *mCurCreateState;
|
||||
|
||||
SimObjectPtr<SimXMLDocument> mXMLReader;
|
||||
|
||||
public:
|
||||
StateMachine();
|
||||
virtual ~StateMachine();
|
||||
|
||||
void update();
|
||||
|
||||
void loadStateMachineFile();
|
||||
void readStates();
|
||||
void readTransitions(State ¤tState);
|
||||
void readConditions(StateTransition &newTransition);
|
||||
|
||||
void setState(const char* stateName, bool clearFields = true);
|
||||
|
||||
const char* getCurrentStateName() { return mCurrentState.stateName; }
|
||||
State& getCurrentState() {
|
||||
return mCurrentState;
|
||||
}
|
||||
|
||||
S32 getStateCount() { return mStates.size(); }
|
||||
const char* getStateByIndex(S32 index);
|
||||
State& getStateByName(const char* name);
|
||||
|
||||
void checkTransitions(const char* slotName, const char* newValue);
|
||||
|
||||
bool passComparitorCheck(const char* var, StateTransition::Condition transitionRule);
|
||||
|
||||
S32 findFieldByName(const char* name);
|
||||
|
||||
S32 getFieldsCount() { return mFields.size(); }
|
||||
|
||||
StateField getField(U32 index)
|
||||
{
|
||||
if (index <= mFields.size())
|
||||
return mFields[index];
|
||||
}
|
||||
|
||||
Signal< void(StateMachine*, S32 stateIdx) > StateMachine::onStateChanged;
|
||||
|
||||
//
|
||||
inline bool readStateName(State* state, SimXMLDocument* reader)
|
||||
{
|
||||
if (reader->pushFirstChildElement("Name"))
|
||||
{
|
||||
state->stateName = reader->getData();
|
||||
reader->popElement();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
inline bool readStateScriptFunction(State* state, SimXMLDocument* reader)
|
||||
{
|
||||
if (reader->pushFirstChildElement("ScriptFunction"))
|
||||
{
|
||||
state->callbackName = reader->getData();
|
||||
reader->popElement();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
inline bool readTransitonTarget(StateTransition* transition, SimXMLDocument* reader)
|
||||
{
|
||||
if (reader->pushFirstChildElement("StateTarget"))
|
||||
{
|
||||
transition->mStateTarget = reader->getData();
|
||||
reader->popElement();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
//
|
||||
inline bool readFieldName(StateField* newField, SimXMLDocument* reader)
|
||||
{
|
||||
if (reader->pushFirstChildElement("FieldName"))
|
||||
{
|
||||
newField->name = reader->getData();
|
||||
reader->popElement();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
inline bool readFieldComparitor(StateTransition::Condition* condition, SimXMLDocument* reader)
|
||||
{
|
||||
if (reader->pushFirstChildElement("Comparitor"))
|
||||
{
|
||||
S32 compIdx = parseComparitor(reader->getData());
|
||||
condition->triggerComparitor = static_cast<StateTransition::Condition::TriggerValueTarget>(compIdx);
|
||||
reader->popElement();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
inline bool readFieldValue(StateField* field, SimXMLDocument* reader)
|
||||
{
|
||||
if (reader->pushFirstChildElement("NumValue"))
|
||||
{
|
||||
field->fieldType = StateField::NumberType;
|
||||
field->triggerNumVal = dAtof(reader->getData());
|
||||
reader->popElement();
|
||||
return true;
|
||||
}
|
||||
else if (reader->pushFirstChildElement("StringValue"))
|
||||
{
|
||||
field->fieldType = StateField::StringType;
|
||||
field->triggerStringVal = reader->getData();
|
||||
reader->popElement();
|
||||
return true;
|
||||
}
|
||||
else if (reader->pushFirstChildElement("BoolValue"))
|
||||
{
|
||||
field->fieldType = StateField::BooleanType;
|
||||
field->triggerBoolVal = dAtob(reader->getData());
|
||||
reader->popElement();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private:
|
||||
S32 parseComparitor(const char* comparitorName);
|
||||
};
|
||||
|
||||
#endif
|
||||
358
Engine/source/T3D/components/Game/triggerComponent.cpp
Normal file
358
Engine/source/T3D/components/Game/triggerComponent.cpp
Normal file
|
|
@ -0,0 +1,358 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// Torque Game Engine
|
||||
// Copyright (C) GarageGames.com, Inc.
|
||||
//-----------------------------------------------------------------------------
|
||||
#include "console/consoleTypes.h"
|
||||
#include "T3D/Components/game/TriggerComponent.h"
|
||||
#include "core/util/safeDelete.h"
|
||||
#include "console/consoleTypes.h"
|
||||
#include "console/consoleObject.h"
|
||||
#include "core/stream/bitStream.h"
|
||||
#include "console/engineAPI.h"
|
||||
#include "sim/netConnection.h"
|
||||
#include "T3D/gameBase/gameConnection.h"
|
||||
#include "T3D/Components/coreInterfaces.h"
|
||||
#include "math/mathUtils.h"
|
||||
#include "collision/concretePolyList.h"
|
||||
#include "collision/clippedPolyList.h"
|
||||
|
||||
#include "gfx/sim/debugDraw.h"
|
||||
|
||||
IMPLEMENT_CALLBACK( TriggerComponent, onEnterViewCmd, void,
|
||||
( Entity* cameraEnt, bool firstTimeSeeing ), ( cameraEnt, firstTimeSeeing ),
|
||||
"@brief Called when an object enters the volume of the Trigger instance using this TriggerData.\n\n"
|
||||
|
||||
"@param trigger the Trigger instance whose volume the object entered\n"
|
||||
"@param obj the object that entered the volume of the Trigger instance\n" );
|
||||
|
||||
IMPLEMENT_CALLBACK( TriggerComponent, onExitViewCmd, void,
|
||||
( Entity* cameraEnt ), ( cameraEnt ),
|
||||
"@brief Called when an object enters the volume of the Trigger instance using this TriggerData.\n\n"
|
||||
|
||||
"@param trigger the Trigger instance whose volume the object entered\n"
|
||||
"@param obj the object that entered the volume of the Trigger instance\n" );
|
||||
|
||||
IMPLEMENT_CALLBACK( TriggerComponent, onUpdateInViewCmd, void,
|
||||
( Entity* cameraEnt ), ( cameraEnt ),
|
||||
"@brief Called when an object enters the volume of the Trigger instance using this TriggerData.\n\n"
|
||||
|
||||
"@param trigger the Trigger instance whose volume the object entered\n"
|
||||
"@param obj the object that entered the volume of the Trigger instance\n" );
|
||||
|
||||
IMPLEMENT_CALLBACK( TriggerComponent, onUpdateOutOfViewCmd, void,
|
||||
( Entity* cameraEnt ), ( cameraEnt ),
|
||||
"@brief Called when an object enters the volume of the Trigger instance using this TriggerData.\n\n"
|
||||
|
||||
"@param trigger the Trigger instance whose volume the object entered\n"
|
||||
"@param obj the object that entered the volume of the Trigger instance\n" );
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// Constructor/Destructor
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
TriggerComponent::TriggerComponent() : Component()
|
||||
{
|
||||
mObjectList.clear();
|
||||
|
||||
mVisible = false;
|
||||
|
||||
mFriendlyName = "Trigger";
|
||||
mComponentType = "Trigger";
|
||||
|
||||
mDescription = getDescriptionText("Calls trigger events when a client starts and stops seeing it. Also ticks while visible to clients.");
|
||||
}
|
||||
|
||||
TriggerComponent::~TriggerComponent()
|
||||
{
|
||||
for(S32 i = 0;i < mFields.size();++i)
|
||||
{
|
||||
ComponentField &field = mFields[i];
|
||||
SAFE_DELETE_ARRAY(field.mFieldDescription);
|
||||
}
|
||||
|
||||
SAFE_DELETE_ARRAY(mDescription);
|
||||
}
|
||||
|
||||
IMPLEMENT_CO_NETOBJECT_V1(TriggerComponent);
|
||||
|
||||
|
||||
bool TriggerComponent::onAdd()
|
||||
{
|
||||
if(! Parent::onAdd())
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void TriggerComponent::onRemove()
|
||||
{
|
||||
Parent::onRemove();
|
||||
}
|
||||
|
||||
//This is mostly a catch for situations where the behavior is re-added to the object and the like and we may need to force an update to the behavior
|
||||
void TriggerComponent::onComponentAdd()
|
||||
{
|
||||
Parent::onComponentAdd();
|
||||
|
||||
CollisionInterface *colInt = mOwner->getComponent<CollisionInterface>();
|
||||
|
||||
if(colInt)
|
||||
{
|
||||
colInt->onCollisionSignal.notify(this, &TriggerComponent::potentialEnterObject);
|
||||
}
|
||||
}
|
||||
|
||||
void TriggerComponent::onComponentRemove()
|
||||
{
|
||||
CollisionInterface *colInt = mOwner->getComponent<CollisionInterface>();
|
||||
|
||||
if(colInt)
|
||||
{
|
||||
colInt->onCollisionSignal.remove(this, &TriggerComponent::potentialEnterObject);
|
||||
}
|
||||
|
||||
Parent::onComponentRemove();
|
||||
}
|
||||
|
||||
void TriggerComponent::componentAddedToOwner(Component *comp)
|
||||
{
|
||||
if (comp->getId() == getId())
|
||||
return;
|
||||
|
||||
CollisionInterface *colInt = mOwner->getComponent<CollisionInterface>();
|
||||
|
||||
if (colInt)
|
||||
{
|
||||
colInt->onCollisionSignal.notify(this, &TriggerComponent::potentialEnterObject);
|
||||
}
|
||||
}
|
||||
|
||||
void TriggerComponent::componentRemovedFromOwner(Component *comp)
|
||||
{
|
||||
if (comp->getId() == getId()) //?????????
|
||||
return;
|
||||
|
||||
CollisionInterface *colInt = mOwner->getComponent<CollisionInterface>();
|
||||
|
||||
if (colInt)
|
||||
{
|
||||
colInt->onCollisionSignal.remove(this, &TriggerComponent::potentialEnterObject);
|
||||
}
|
||||
}
|
||||
|
||||
void TriggerComponent::initPersistFields()
|
||||
{
|
||||
Parent::initPersistFields();
|
||||
|
||||
addField("visibile", TypeBool, Offset( mVisible, TriggerComponent ), "" );
|
||||
|
||||
addField("onEnterViewCmd", TypeCommand, Offset(mEnterCommand, TriggerComponent), "");
|
||||
addField("onExitViewCmd", TypeCommand, Offset(mOnExitCommand, TriggerComponent), "");
|
||||
addField("onUpdateInViewCmd", TypeCommand, Offset(mOnUpdateInViewCmd, TriggerComponent), "");
|
||||
}
|
||||
|
||||
U32 TriggerComponent::packUpdate(NetConnection *con, U32 mask, BitStream *stream)
|
||||
{
|
||||
U32 retMask = Parent::packUpdate(con, mask, stream);
|
||||
return retMask;
|
||||
}
|
||||
|
||||
void TriggerComponent::unpackUpdate(NetConnection *con, BitStream *stream)
|
||||
{
|
||||
Parent::unpackUpdate(con, stream);
|
||||
}
|
||||
|
||||
void TriggerComponent::potentialEnterObject(SceneObject *collider)
|
||||
{
|
||||
if(testObject(collider))
|
||||
{
|
||||
bool found = false;
|
||||
for(U32 i=0; i < mObjectList.size(); i++)
|
||||
{
|
||||
if(mObjectList[i]->getId() == collider->getId())
|
||||
{
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found)
|
||||
{
|
||||
mObjectList.push_back(collider);
|
||||
|
||||
if (!mEnterCommand.isEmpty())
|
||||
{
|
||||
String command = String("%obj = ") + collider->getIdString() + ";" +
|
||||
String("%this = ") + getIdString() + ";" + mEnterCommand;
|
||||
Con::evaluate(command.c_str());
|
||||
}
|
||||
|
||||
//onEnterTrigger_callback(this, enter);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool TriggerComponent::testObject(SceneObject* enter)
|
||||
{
|
||||
//First, test to early out
|
||||
Box3F enterBox = enter->getWorldBox();
|
||||
|
||||
//if(!mOwner->getWorldBox().intersect(enterBox) || !)
|
||||
// return false;
|
||||
|
||||
//We're still here, so we should do actual work
|
||||
//We're going to be
|
||||
ConcretePolyList mClippedList;
|
||||
|
||||
SphereF sphere;
|
||||
sphere.center = (mOwner->getWorldBox().minExtents + mOwner->getWorldBox().maxExtents) * 0.5;
|
||||
VectorF bv = mOwner->getWorldBox().maxExtents - sphere.center;
|
||||
sphere.radius = bv.len();
|
||||
|
||||
Entity* enterEntity = dynamic_cast<Entity*>(enter);
|
||||
if(enterEntity)
|
||||
{
|
||||
//quick early out. If the bounds don't overlap, it cannot be colliding or inside
|
||||
if (!mOwner->getWorldBox().isOverlapped(enterBox))
|
||||
return false;
|
||||
|
||||
//check if the entity has a collision shape
|
||||
CollisionInterface *cI = enterEntity->getComponent<CollisionInterface>();
|
||||
if (cI)
|
||||
{
|
||||
cI->buildPolyList(PLC_Collision, &mClippedList, mOwner->getWorldBox(), sphere);
|
||||
|
||||
if (!mClippedList.isEmpty())
|
||||
{
|
||||
//well, it's clipped with, or inside, our bounds
|
||||
//now to test the clipped list against our own collision mesh
|
||||
CollisionInterface *myCI = mOwner->getComponent<CollisionInterface>();
|
||||
|
||||
//wait, how would we NOT have this?
|
||||
if (myCI)
|
||||
{
|
||||
//anywho, build our list and then we'll check intersections
|
||||
ClippedPolyList myList;
|
||||
|
||||
myList.setTransform(&(mOwner->getTransform()), mOwner->getScale());
|
||||
myList.setObject(mOwner);
|
||||
|
||||
myCI->buildPolyList(PLC_Collision, &myList, enterBox, sphere);
|
||||
|
||||
bool test = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return mClippedList.isEmpty() == false;
|
||||
}
|
||||
|
||||
void TriggerComponent::processTick()
|
||||
{
|
||||
Parent::processTick();
|
||||
|
||||
if (!isActive())
|
||||
return;
|
||||
|
||||
//get our list of active clients, and see if they have cameras, if they do, build a frustum and see if we exist inside that
|
||||
mVisible = false;
|
||||
if(isServerObject())
|
||||
{
|
||||
for(U32 i=0; i < mObjectList.size(); i++)
|
||||
{
|
||||
if(!testObject(mObjectList[i]))
|
||||
{
|
||||
if (!mOnExitCommand.isEmpty())
|
||||
{
|
||||
String command = String("%obj = ") + mObjectList[i]->getIdString() + ";" +
|
||||
String("%this = ") + getIdString() + ";" + mOnExitCommand;
|
||||
Con::evaluate(command.c_str());
|
||||
}
|
||||
|
||||
mObjectList.erase(i);
|
||||
//mDataBlock->onLeaveTrigger_callback( this, remove );
|
||||
//onLeaveTrigger_callback(this, remove);
|
||||
}
|
||||
}
|
||||
|
||||
/*if (!mTickCommand.isEmpty())
|
||||
Con::evaluate(mTickCommand.c_str());
|
||||
|
||||
if (mObjects.size() != 0)
|
||||
onTickTrigger_callback(this);*/
|
||||
}
|
||||
}
|
||||
|
||||
void TriggerComponent::visualizeFrustums(F32 renderTimeMS)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
GameConnection* TriggerComponent::getConnection(S32 connectionID)
|
||||
{
|
||||
for(NetConnection *conn = NetConnection::getConnectionList(); conn; conn = conn->getNext())
|
||||
{
|
||||
GameConnection* gameConn = dynamic_cast<GameConnection*>(conn);
|
||||
|
||||
if (!gameConn || (gameConn && gameConn->isAIControlled()))
|
||||
continue;
|
||||
|
||||
if(connectionID == gameConn->getId())
|
||||
return gameConn;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void TriggerComponent::addClient(S32 clientID)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void TriggerComponent::removeClient(S32 clientID)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
DefineEngineMethod( TriggerComponent, addClient, void,
|
||||
( S32 clientID ), ( -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)" )
|
||||
{
|
||||
if(clientID == -1)
|
||||
return;
|
||||
|
||||
object->addClient( clientID );
|
||||
}
|
||||
|
||||
DefineEngineMethod( TriggerComponent, removeClient, void,
|
||||
( S32 clientID ), ( -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)" )
|
||||
{
|
||||
if(clientID == -1)
|
||||
return;
|
||||
|
||||
object->removeClient( clientID );
|
||||
}
|
||||
|
||||
DefineEngineMethod( TriggerComponent, visualizeFrustums, void,
|
||||
(F32 renderTime), (1000),
|
||||
"@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->visualizeFrustums(renderTime);
|
||||
}
|
||||
74
Engine/source/T3D/components/Game/triggerComponent.h
Normal file
74
Engine/source/T3D/components/Game/triggerComponent.h
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// Torque Game Engine
|
||||
// Copyright (C) GarageGames.com, Inc.
|
||||
//-----------------------------------------------------------------------------
|
||||
#ifndef _TRIGGER_COMPONENT_H_
|
||||
#define _TRIGGER_COMPONENT_H_
|
||||
|
||||
#ifndef _COMPONENT_H_
|
||||
#include "T3D/Components/Component.h"
|
||||
#endif
|
||||
|
||||
#ifndef _ENTITY_H_
|
||||
#include "T3D/Entity.h"
|
||||
#endif
|
||||
|
||||
#ifndef _COLLISION_INTERFACES_H_
|
||||
#include "T3D/Components/collision/collisionInterfaces.h"
|
||||
#endif
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
///
|
||||
///
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
class TriggerComponent : public Component
|
||||
{
|
||||
typedef Component Parent;
|
||||
|
||||
protected:
|
||||
Vector<SceneObject*> mObjectList;
|
||||
|
||||
bool mVisible;
|
||||
|
||||
String mEnterCommand;
|
||||
String mOnExitCommand;
|
||||
String mOnUpdateInViewCmd;
|
||||
|
||||
public:
|
||||
TriggerComponent();
|
||||
virtual ~TriggerComponent();
|
||||
DECLARE_CONOBJECT(TriggerComponent);
|
||||
|
||||
virtual bool onAdd();
|
||||
virtual void onRemove();
|
||||
static void initPersistFields();
|
||||
|
||||
virtual void onComponentAdd();
|
||||
virtual void onComponentRemove();
|
||||
|
||||
virtual void componentAddedToOwner(Component *comp);
|
||||
virtual void componentRemovedFromOwner(Component *comp);
|
||||
|
||||
virtual U32 packUpdate(NetConnection *con, U32 mask, BitStream *stream);
|
||||
virtual void unpackUpdate(NetConnection *con, BitStream *stream);
|
||||
|
||||
void potentialEnterObject(SceneObject *collider);
|
||||
|
||||
bool testObject(SceneObject* enter);
|
||||
|
||||
virtual void processTick();
|
||||
|
||||
GameConnection* getConnection(S32 connectionID);
|
||||
|
||||
void addClient(S32 clientID);
|
||||
void removeClient(S32 clientID);
|
||||
|
||||
void visualizeFrustums(F32 renderTimeMS);
|
||||
|
||||
DECLARE_CALLBACK(void, onEnterViewCmd, (Entity* cameraEnt, bool firstTimeSeeing));
|
||||
DECLARE_CALLBACK(void, onExitViewCmd, (Entity* cameraEnt));
|
||||
DECLARE_CALLBACK(void, onUpdateInViewCmd, (Entity* cameraEnt));
|
||||
DECLARE_CALLBACK(void, onUpdateOutOfViewCmd, (Entity* cameraEnt));
|
||||
};
|
||||
|
||||
#endif // _EXAMPLEBEHAVIOR_H_
|
||||
368
Engine/source/T3D/components/Physics/physicsBehavior.cpp
Normal file
368
Engine/source/T3D/components/Physics/physicsBehavior.cpp
Normal file
|
|
@ -0,0 +1,368 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// 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 "T3D/Components/Physics/physicsBehavior.h"
|
||||
#include "platform/platform.h"
|
||||
#include "console/consoleTypes.h"
|
||||
#include "core/util/safeDelete.h"
|
||||
#include "core/resourceManager.h"
|
||||
#include "core/stream/fileStream.h"
|
||||
#include "console/consoleTypes.h"
|
||||
#include "console/consoleObject.h"
|
||||
#include "ts/tsShapeInstance.h"
|
||||
#include "core/stream/bitStream.h"
|
||||
#include "gfx/gfxTransformSaver.h"
|
||||
#include "console/engineAPI.h"
|
||||
#include "lighting/lightQuery.h"
|
||||
#include "T3D/gameBase/gameConnection.h"
|
||||
#include "T3D/containerQuery.h"
|
||||
#include "math/mathIO.h"
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// Constructor/Destructor
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
PhysicsComponent::PhysicsComponent() : Component()
|
||||
{
|
||||
addComponentField("isStatic", "If enabled, object will not simulate physics", "bool", "0", "");
|
||||
addComponentField("gravity", "The direction of gravity affecting this object, as a vector", "vector", "0 0 -9", "");
|
||||
addComponentField("drag", "The drag coefficient that constantly affects the object", "float", "0.7", "");
|
||||
addComponentField("mass", "The mass of the object", "float", "1", "");
|
||||
|
||||
mStatic = false;
|
||||
mAtRest = false;
|
||||
mAtRestCounter = 0;
|
||||
|
||||
mGravity = VectorF(0, 0, 0);
|
||||
mVelocity = VectorF(0, 0, 0);
|
||||
mDrag = 0.7f;
|
||||
mMass = 1.f;
|
||||
|
||||
mGravityMod = 1.f;
|
||||
|
||||
csmAtRestTimer = 64;
|
||||
sAtRestVelocity = 0.15f;
|
||||
|
||||
mDelta.pos = Point3F(0, 0, 0);
|
||||
mDelta.posVec = Point3F(0, 0, 0);
|
||||
mDelta.warpTicks = mDelta.warpCount = 0;
|
||||
mDelta.dt = 1;
|
||||
mDelta.move = NullMove;
|
||||
mPredictionCount = 0;
|
||||
}
|
||||
|
||||
PhysicsComponent::~PhysicsComponent()
|
||||
{
|
||||
for(S32 i = 0;i < mFields.size();++i)
|
||||
{
|
||||
ComponentField &field = mFields[i];
|
||||
SAFE_DELETE_ARRAY(field.mFieldDescription);
|
||||
}
|
||||
|
||||
SAFE_DELETE_ARRAY(mDescription);
|
||||
}
|
||||
|
||||
IMPLEMENT_CO_NETOBJECT_V1(PhysicsComponent);
|
||||
|
||||
void PhysicsComponent::onComponentAdd()
|
||||
{
|
||||
Parent::onComponentAdd();
|
||||
|
||||
// Initialize interpolation vars.
|
||||
mDelta.rot[1] = mDelta.rot[0] = QuatF(mOwner->getTransform());
|
||||
mDelta.pos = mOwner->getPosition();
|
||||
mDelta.posVec = Point3F(0,0,0);
|
||||
}
|
||||
|
||||
void PhysicsComponent::initPersistFields()
|
||||
{
|
||||
Parent::initPersistFields();
|
||||
|
||||
addField("gravity", TypePoint3F, Offset(mGravity, PhysicsComponent));
|
||||
addField("velocity", TypePoint3F, Offset(mVelocity, PhysicsComponent));
|
||||
addField("isStatic", TypeBool, Offset(mStatic, PhysicsComponent));
|
||||
}
|
||||
|
||||
U32 PhysicsComponent::packUpdate(NetConnection *con, U32 mask, BitStream *stream)
|
||||
{
|
||||
U32 retMask = Parent::packUpdate(con, mask, stream);
|
||||
|
||||
if(stream->writeFlag(mask & VelocityMask))
|
||||
mathWrite( *stream, mVelocity );
|
||||
|
||||
if(stream->writeFlag(mask & UpdateMask))
|
||||
{
|
||||
stream->writeFlag(mStatic);
|
||||
stream->writeFlag(mAtRest);
|
||||
stream->writeInt(mAtRestCounter,8);
|
||||
|
||||
mathWrite( *stream, mGravity );
|
||||
|
||||
stream->writeFloat(mDrag, 12);
|
||||
//stream->writeFloat(mMass, 12);
|
||||
|
||||
stream->writeFloat(mGravityMod, 12);
|
||||
}
|
||||
return retMask;
|
||||
}
|
||||
|
||||
void PhysicsComponent::unpackUpdate(NetConnection *con, BitStream *stream)
|
||||
{
|
||||
Parent::unpackUpdate(con, stream);
|
||||
|
||||
if(stream->readFlag())
|
||||
mathRead( *stream, &mVelocity );
|
||||
|
||||
if(stream->readFlag())
|
||||
{
|
||||
mStatic = stream->readFlag();
|
||||
mAtRest = stream->readFlag();
|
||||
mAtRestCounter = stream->readInt(8);
|
||||
|
||||
mathRead( *stream, &mGravity );
|
||||
|
||||
mDrag = stream->readFloat(12);
|
||||
//mMass = stream->readFloat(12);
|
||||
|
||||
mGravityMod = stream->readFloat(12);
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
void PhysicsComponent::interpolateTick(F32 dt)
|
||||
{
|
||||
Point3F pos = mDelta.pos + mDelta.posVec * dt;
|
||||
//Point3F rot = mDelta.rot + mDelta.rotVec * dt;
|
||||
|
||||
setRenderPosition(pos,dt);
|
||||
}
|
||||
|
||||
//
|
||||
void PhysicsComponent::updateContainer()
|
||||
{
|
||||
PROFILE_SCOPE( PhysicsBehaviorInstance_updateContainer );
|
||||
|
||||
// Update container drag and buoyancy properties
|
||||
|
||||
// Set default values.
|
||||
//mDrag = mDataBlock->drag;
|
||||
//mBuoyancy = 0.0f;
|
||||
//mGravityMod = 1.0;
|
||||
//mAppliedForce.set(0,0,0);
|
||||
|
||||
ContainerQueryInfo info;
|
||||
info.box = mOwner->getWorldBox();
|
||||
info.mass = mMass;
|
||||
|
||||
mOwner->getContainer()->findObjects(info.box, WaterObjectType|PhysicalZoneObjectType,findRouter,&info);
|
||||
|
||||
//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 PhysicsComponent::_updatePhysics()
|
||||
{
|
||||
/*SAFE_DELETE( mOwner->mPhysicsRep );
|
||||
|
||||
if ( !PHYSICSMGR )
|
||||
return;
|
||||
|
||||
if (mDataBlock->simpleServerCollision)
|
||||
{
|
||||
// We only need the trigger on the server.
|
||||
if ( isServerObject() )
|
||||
{
|
||||
PhysicsCollision *colShape = PHYSICSMGR->createCollision();
|
||||
colShape->addBox( mObjBox.getExtents() * 0.5f, MatrixF::Identity );
|
||||
|
||||
PhysicsWorld *world = PHYSICSMGR->getWorld( isServerObject() ? "server" : "client" );
|
||||
mPhysicsRep = PHYSICSMGR->createBody();
|
||||
mPhysicsRep->init( colShape, 0, PhysicsBody::BF_TRIGGER | PhysicsBody::BF_KINEMATIC, this, world );
|
||||
mPhysicsRep->setTransform( getTransform() );
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if ( !mShapeInstance )
|
||||
return;
|
||||
|
||||
PhysicsCollision* colShape = mShapeInstance->getShape()->buildColShape( false, getScale() );
|
||||
|
||||
if ( colShape )
|
||||
{
|
||||
PhysicsWorld *world = PHYSICSMGR->getWorld( isServerObject() ? "server" : "client" );
|
||||
mPhysicsRep = PHYSICSMGR->createBody();
|
||||
mPhysicsRep->init( colShape, 0, PhysicsBody::BF_KINEMATIC, this, world );
|
||||
mPhysicsRep->setTransform( getTransform() );
|
||||
}
|
||||
}*/
|
||||
return;
|
||||
}
|
||||
|
||||
PhysicsBody *PhysicsComponent::getPhysicsRep()
|
||||
{
|
||||
/*if(mOwner)
|
||||
{
|
||||
Entity* ac = dynamic_cast<Entity*>(mOwner);
|
||||
if(ac)
|
||||
return ac->mPhysicsRep;
|
||||
}*/
|
||||
return NULL;
|
||||
}
|
||||
//
|
||||
void PhysicsComponent::setTransform(const MatrixF& mat)
|
||||
{
|
||||
mOwner->setTransform(mat);
|
||||
|
||||
if (!mStatic)
|
||||
{
|
||||
mAtRest = false;
|
||||
mAtRestCounter = 0;
|
||||
}
|
||||
|
||||
if ( getPhysicsRep() )
|
||||
getPhysicsRep()->setTransform( mOwner->getTransform() );
|
||||
|
||||
setMaskBits(UpdateMask);
|
||||
}
|
||||
|
||||
void PhysicsComponent::setPosition(const Point3F& pos)
|
||||
{
|
||||
MatrixF mat = mOwner->getTransform();
|
||||
if (mOwner->isMounted()) {
|
||||
// Use transform from mounted object
|
||||
//mOwner->getObjectMount()->getMountTransform( mOwner->getMountNode(), mMount.xfm, &mat );
|
||||
return;
|
||||
}
|
||||
else {
|
||||
mat.setColumn(3,pos);
|
||||
}
|
||||
|
||||
mOwner->setTransform(mat);
|
||||
|
||||
if ( getPhysicsRep() )
|
||||
getPhysicsRep()->setTransform( mat );
|
||||
}
|
||||
|
||||
|
||||
void PhysicsComponent::setRenderPosition(const Point3F& pos, F32 dt)
|
||||
{
|
||||
MatrixF mat = mOwner->getRenderTransform();
|
||||
if (mOwner->isMounted()) {
|
||||
// Use transform from mounted object
|
||||
//mOwner->getObjectMount()->getMountRenderTransform( dt, mOwner->getMountNode(), mMount.xfm, &mat );
|
||||
return;
|
||||
}
|
||||
else {
|
||||
mat.setColumn(3,pos);
|
||||
}
|
||||
|
||||
mOwner->setRenderTransform(mat);
|
||||
}
|
||||
|
||||
void PhysicsComponent::updateVelocity(const F32 dt)
|
||||
{
|
||||
}
|
||||
|
||||
void PhysicsComponent::setVelocity(const VectorF& vel)
|
||||
{
|
||||
mVelocity = vel;
|
||||
|
||||
mAtRest = false;
|
||||
mAtRestCounter = 0;
|
||||
setMaskBits(VelocityMask);
|
||||
}
|
||||
|
||||
void PhysicsComponent::getVelocity(const Point3F& r, Point3F* v)
|
||||
{
|
||||
*v = mVelocity;
|
||||
}
|
||||
|
||||
void PhysicsComponent::getOriginVector(const Point3F &p,Point3F* r)
|
||||
{
|
||||
*r = p - mOwner->getObjBox().getCenter();
|
||||
}
|
||||
|
||||
F32 PhysicsComponent::getZeroImpulse(const Point3F& r,const Point3F& normal)
|
||||
{
|
||||
Point3F a,b,c;
|
||||
|
||||
//set up our inverse matrix
|
||||
MatrixF iv,qmat;
|
||||
MatrixF inverse = MatrixF::Identity;
|
||||
qmat = mOwner->getTransform();
|
||||
iv.mul(qmat,inverse);
|
||||
qmat.transpose();
|
||||
inverse.mul(iv,qmat);
|
||||
|
||||
mCross(r, normal, &a);
|
||||
inverse.mulV(a, &b);
|
||||
mCross(b, r, &c);
|
||||
|
||||
return 1 / ((1/mMass) + mDot(c, normal));
|
||||
}
|
||||
|
||||
void PhysicsComponent::accumulateForce(F32 dt, Point3F force)
|
||||
{
|
||||
mVelocity += force * dt;
|
||||
}
|
||||
|
||||
void PhysicsComponent::applyImpulse(const Point3F&,const VectorF& vec)
|
||||
{
|
||||
// Items ignore angular velocity
|
||||
VectorF vel;
|
||||
vel.x = vec.x / mMass;
|
||||
vel.y = vec.y / mMass;
|
||||
vel.z = vec.z / mMass;
|
||||
setVelocity(mVelocity + vel);
|
||||
}
|
||||
|
||||
DefineEngineMethod( PhysicsComponent, applyImpulse, bool, ( Point3F pos, VectorF vel ),,
|
||||
"@brief Apply an impulse to this object as defined by a world position and velocity vector.\n\n"
|
||||
|
||||
"@param pos impulse world position\n"
|
||||
"@param vel impulse velocity (impulse force F = m * v)\n"
|
||||
"@return Always true\n"
|
||||
|
||||
"@note Not all objects that derrive from GameBase have this defined.\n")
|
||||
{
|
||||
object->applyImpulse(pos,vel);
|
||||
return true;
|
||||
}
|
||||
135
Engine/source/T3D/components/Physics/physicsBehavior.h
Normal file
135
Engine/source/T3D/components/Physics/physicsBehavior.h
Normal file
|
|
@ -0,0 +1,135 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// Torque Game Engine
|
||||
// Copyright (C) GarageGames.com, Inc.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
#ifndef _PHYSICSBEHAVIOR_H_
|
||||
#define _PHYSICSBEHAVIOR_H_
|
||||
#include "T3D/Components/Component.h"
|
||||
|
||||
#ifndef __RESOURCE_H__
|
||||
#include "core/resource.h"
|
||||
#endif
|
||||
#ifndef _TSSHAPE_H_
|
||||
#include "ts/tsShape.h"
|
||||
#endif
|
||||
#ifndef _SCENERENDERSTATE_H_
|
||||
#include "scene/sceneRenderState.h"
|
||||
#endif
|
||||
#ifndef _MBOX_H_
|
||||
#include "math/mBox.h"
|
||||
#endif
|
||||
#ifndef _ENTITY_H_
|
||||
#include "T3D/Entity.h"
|
||||
#endif
|
||||
#ifndef _CONVEX_H_
|
||||
#include "collision/convex.h"
|
||||
#endif
|
||||
#ifndef _BOXCONVEX_H_
|
||||
#include "collision/boxConvex.h"
|
||||
#endif
|
||||
#ifndef _RIGID_H_
|
||||
#include "T3D/rigid.h"
|
||||
#endif
|
||||
#ifndef _T3D_PHYSICS_PHYSICSBODY_H_
|
||||
#include "T3D/physics/physicsBody.h"
|
||||
#endif
|
||||
|
||||
#ifndef _RENDER_COMPONENT_INTERFACE_H_
|
||||
#include "T3D/Components/render/renderComponentInterface.h"
|
||||
#endif
|
||||
|
||||
class TSShapeInstance;
|
||||
class SceneRenderState;
|
||||
class PhysicsBody;
|
||||
class PhysicsBehaviorInstance;
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
///
|
||||
///
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
class PhysicsComponent : public Component
|
||||
{
|
||||
typedef Component Parent;
|
||||
|
||||
protected:
|
||||
bool mStatic;
|
||||
bool mAtRest;
|
||||
S32 mAtRestCounter;
|
||||
|
||||
VectorF mGravity;
|
||||
VectorF mVelocity;
|
||||
F32 mDrag;
|
||||
F32 mMass;
|
||||
|
||||
F32 mGravityMod;
|
||||
|
||||
S32 csmAtRestTimer;
|
||||
F32 sAtRestVelocity; // Min speed after collisio
|
||||
|
||||
public:
|
||||
enum MaskBits {
|
||||
PositionMask = Parent::NextFreeMask << 0,
|
||||
FreezeMask = Parent::NextFreeMask << 1,
|
||||
ForceMoveMask = Parent::NextFreeMask << 2,
|
||||
VelocityMask = Parent::NextFreeMask << 3,
|
||||
NextFreeMask = Parent::NextFreeMask << 4
|
||||
};
|
||||
|
||||
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];
|
||||
};
|
||||
|
||||
StateDelta mDelta;
|
||||
S32 mPredictionCount; ///< Number of ticks to predict
|
||||
|
||||
public:
|
||||
PhysicsComponent();
|
||||
virtual ~PhysicsComponent();
|
||||
DECLARE_CONOBJECT(PhysicsComponent);
|
||||
|
||||
static void initPersistFields();
|
||||
|
||||
virtual void interpolateTick(F32 dt);
|
||||
virtual void updatePos(const U32 /*mask*/, const F32 dt){}
|
||||
virtual void _updatePhysics();
|
||||
virtual PhysicsBody *getPhysicsRep();
|
||||
|
||||
virtual U32 packUpdate(NetConnection *con, U32 mask, BitStream *stream);
|
||||
virtual void unpackUpdate(NetConnection *con, BitStream *stream);
|
||||
|
||||
virtual void onComponentAdd();
|
||||
|
||||
void updateContainer();
|
||||
|
||||
virtual void updateVelocity(const F32 dt);
|
||||
virtual Point3F getVelocity() { return mVelocity; }
|
||||
virtual void getOriginVector(const Point3F &p, Point3F* r);
|
||||
virtual void getVelocity(const Point3F& r, Point3F* v);
|
||||
virtual void setVelocity(const VectorF& vel);
|
||||
virtual void setTransform(const MatrixF& mat);
|
||||
virtual void setPosition(const Point3F& pos);
|
||||
void setRenderPosition(const Point3F& pos, F32 dt);
|
||||
|
||||
virtual void applyImpulse(const Point3F&, const VectorF& vec);
|
||||
virtual F32 getZeroImpulse(const Point3F& r, const Point3F& normal);
|
||||
virtual void accumulateForce(F32 dt, Point3F force);
|
||||
|
||||
//Rigid Body Collision Conveinence Hooks
|
||||
virtual bool updateCollision(F32 dt, Rigid& ns, CollisionList &cList) { return false; }
|
||||
virtual bool resolveContacts(Rigid& ns, CollisionList& cList, F32 dt) { return false; }
|
||||
//virtual bool resolveCollision(Rigid& ns, CollisionList& cList) { return false; }
|
||||
virtual bool resolveCollision(const Point3F& p, const Point3F &normal) { return false; }
|
||||
};
|
||||
|
||||
#endif // _COMPONENT_H_
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// 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 PHYSICS_COMPONENT_INTERFACE_H
|
||||
#define PHYSICS_COMPONENT_INTERFACE_H
|
||||
|
||||
#ifndef CORE_INTERFACES_H
|
||||
#include "T3D/Components/coreInterfaces.h"
|
||||
#endif
|
||||
|
||||
class PhysicsComponentInterface : public Interface<PhysicsComponentInterface>
|
||||
{
|
||||
protected:
|
||||
VectorF mVelocity;
|
||||
F32 mMass;
|
||||
|
||||
F32 mGravityMod;
|
||||
|
||||
public:
|
||||
void updateForces();
|
||||
|
||||
VectorF getVelocity() { return mVelocity; }
|
||||
void setVelocity(VectorF vel) { mVelocity = vel; }
|
||||
|
||||
F32 getMass() { return mMass; }
|
||||
|
||||
Signal< void(VectorF normal, Vector<SceneObject*> overlappedObjects) > PhysicsComponentInterface::onPhysicsCollision;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,863 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// 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 "T3D/Components/Physics/playerControllerComponent.h"
|
||||
#include "platform/platform.h"
|
||||
#include "console/consoleTypes.h"
|
||||
#include "core/util/safeDelete.h"
|
||||
#include "core/resourceManager.h"
|
||||
#include "core/stream/fileStream.h"
|
||||
#include "console/consoleTypes.h"
|
||||
#include "console/consoleObject.h"
|
||||
#include "ts/tsShapeInstance.h"
|
||||
#include "core/stream/bitStream.h"
|
||||
#include "gfx/gfxTransformSaver.h"
|
||||
#include "console/engineAPI.h"
|
||||
#include "lighting/lightQuery.h"
|
||||
#include "T3D/gameBase/gameConnection.h"
|
||||
#include "collision/collision.h"
|
||||
#include "T3D/physics/physicsPlayer.h"
|
||||
#include "T3D/physics/physicsPlugin.h"
|
||||
#include "T3D/Components/Collision/collisionInterfaces.h"
|
||||
#include "T3D/trigger.h"
|
||||
#include "T3D/components/collision/collisionTrigger.h"
|
||||
|
||||
// Movement constants
|
||||
static F32 sVerticalStepDot = 0.173f; // 80
|
||||
static F32 sMinFaceDistance = 0.01f;
|
||||
static F32 sTractionDistance = 0.04f;
|
||||
static F32 sNormalElasticity = 0.01f;
|
||||
static U32 sMoveRetryCount = 5;
|
||||
static F32 sMaxImpulseVelocity = 200.0f;
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// Callbacks
|
||||
IMPLEMENT_CALLBACK(PlayerControllerComponent, updateMove, void, (PlayerControllerComponent* obj), (obj),
|
||||
"Called when the player updates it's movement, only called if object is set to callback in script(doUpdateMove).\n"
|
||||
"@param obj the Player object\n");
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// Constructor/Destructor
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
PlayerControllerComponent::PlayerControllerComponent() : Component()
|
||||
{
|
||||
addComponentField("isStatic", "If enabled, object will not simulate physics", "bool", "0", "");
|
||||
addComponentField("gravity", "The direction of gravity affecting this object, as a vector", "vector", "0 0 -9", "");
|
||||
addComponentField("drag", "The drag coefficient that constantly affects the object", "float", "0.7", "");
|
||||
addComponentField("mass", "The mass of the object", "float", "1", "");
|
||||
|
||||
mBuoyancy = 0.f;
|
||||
mFriction = 0.3f;
|
||||
mElasticity = 0.4f;
|
||||
mMaxVelocity = 3000.f;
|
||||
mSticky = false;
|
||||
|
||||
mFalling = false;
|
||||
mSwimming = false;
|
||||
mInWater = false;
|
||||
|
||||
mDelta.pos = mDelta.posVec = Point3F::Zero;
|
||||
mDelta.warpTicks = mDelta.warpCount = 0;
|
||||
mDelta.rot[0].identity();
|
||||
mDelta.rot[1].identity();
|
||||
mDelta.dt = 1;
|
||||
|
||||
mUseDirectMoveInput = false;
|
||||
|
||||
mFriendlyName = "Player Controller";
|
||||
mComponentType = "Physics";
|
||||
|
||||
mDescription = getDescriptionText("A general-purpose physics player controller.");
|
||||
|
||||
mNetFlags.set(Ghostable | ScopeAlways);
|
||||
|
||||
mMass = 9.0f; // from ShapeBase
|
||||
mDrag = 1.0f; // from ShapeBase
|
||||
|
||||
maxStepHeight = 1.0f;
|
||||
moveSurfaceAngle = 60.0f;
|
||||
contactSurfaceAngle = 85.0f;
|
||||
|
||||
fallingSpeedThreshold = -10.0f;
|
||||
|
||||
horizMaxSpeed = 80.0f;
|
||||
horizMaxAccel = 100.0f;
|
||||
horizResistSpeed = 38.0f;
|
||||
horizResistFactor = 1.0f;
|
||||
|
||||
upMaxSpeed = 80.0f;
|
||||
upMaxAccel = 100.0f;
|
||||
upResistSpeed = 38.0f;
|
||||
upResistFactor = 1.0f;
|
||||
|
||||
// Air control
|
||||
airControl = 0.0f;
|
||||
|
||||
//Grav mod
|
||||
mGravityMod = 1;
|
||||
|
||||
mInputVelocity = Point3F(0, 0, 0);
|
||||
|
||||
mPhysicsRep = NULL;
|
||||
mPhysicsWorld = NULL;
|
||||
}
|
||||
|
||||
PlayerControllerComponent::~PlayerControllerComponent()
|
||||
{
|
||||
for (S32 i = 0; i < mFields.size(); ++i)
|
||||
{
|
||||
ComponentField &field = mFields[i];
|
||||
SAFE_DELETE_ARRAY(field.mFieldDescription);
|
||||
}
|
||||
|
||||
SAFE_DELETE_ARRAY(mDescription);
|
||||
}
|
||||
|
||||
IMPLEMENT_CO_NETOBJECT_V1(PlayerControllerComponent);
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
bool PlayerControllerComponent::onAdd()
|
||||
{
|
||||
if (!Parent::onAdd())
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void PlayerControllerComponent::onRemove()
|
||||
{
|
||||
Parent::onRemove();
|
||||
|
||||
SAFE_DELETE(mPhysicsRep);
|
||||
}
|
||||
|
||||
void PlayerControllerComponent::onComponentAdd()
|
||||
{
|
||||
Parent::onComponentAdd();
|
||||
|
||||
updatePhysics();
|
||||
}
|
||||
|
||||
void PlayerControllerComponent::componentAddedToOwner(Component *comp)
|
||||
{
|
||||
if (comp->getId() == getId())
|
||||
return;
|
||||
|
||||
//test if this is a shape component!
|
||||
CollisionInterface *collisionInterface = dynamic_cast<CollisionInterface*>(comp);
|
||||
if (collisionInterface)
|
||||
{
|
||||
collisionInterface->onCollisionChanged.notify(this, &PlayerControllerComponent::updatePhysics);
|
||||
mOwnerCollisionInterface = collisionInterface;
|
||||
updatePhysics();
|
||||
}
|
||||
}
|
||||
|
||||
void PlayerControllerComponent::componentRemovedFromOwner(Component *comp)
|
||||
{
|
||||
if (comp->getId() == getId()) //?????????
|
||||
return;
|
||||
|
||||
//test if this is a shape component!
|
||||
CollisionInterface *collisionInterface = dynamic_cast<CollisionInterface*>(comp);
|
||||
if (collisionInterface)
|
||||
{
|
||||
collisionInterface->onCollisionChanged.remove(this, &PlayerControllerComponent::updatePhysics);
|
||||
mOwnerCollisionInterface = NULL;
|
||||
updatePhysics();
|
||||
}
|
||||
}
|
||||
|
||||
void PlayerControllerComponent::updatePhysics(PhysicsCollision *collision)
|
||||
{
|
||||
if (!PHYSICSMGR)
|
||||
return;
|
||||
|
||||
mPhysicsWorld = PHYSICSMGR->getWorld(isServerObject() ? "server" : "client");
|
||||
|
||||
//first, clear the old physRep
|
||||
SAFE_DELETE(mPhysicsRep);
|
||||
|
||||
mPhysicsRep = PHYSICSMGR->createPlayer();
|
||||
|
||||
F32 runSurfaceCos = mCos(mDegToRad(moveSurfaceAngle));
|
||||
|
||||
Point3F ownerBounds = mOwner->getObjBox().getExtents() * mOwner->getScale();
|
||||
|
||||
mPhysicsRep->init("", ownerBounds, runSurfaceCos, maxStepHeight, mOwner, mPhysicsWorld);
|
||||
|
||||
mPhysicsRep->setTransform(mOwner->getTransform());
|
||||
}
|
||||
|
||||
void PlayerControllerComponent::initPersistFields()
|
||||
{
|
||||
Parent::initPersistFields();
|
||||
|
||||
addField("inputVelocity", TypePoint3F, Offset(mInputVelocity, PlayerControllerComponent), "");
|
||||
addField("useDirectMoveInput", TypePoint3F, Offset(mUseDirectMoveInput, PlayerControllerComponent), "");
|
||||
}
|
||||
|
||||
U32 PlayerControllerComponent::packUpdate(NetConnection *con, U32 mask, BitStream *stream)
|
||||
{
|
||||
U32 retMask = Parent::packUpdate(con, mask, stream);
|
||||
|
||||
return retMask;
|
||||
}
|
||||
|
||||
void PlayerControllerComponent::unpackUpdate(NetConnection *con, BitStream *stream)
|
||||
{
|
||||
Parent::unpackUpdate(con, stream);
|
||||
}
|
||||
|
||||
//
|
||||
void PlayerControllerComponent::processTick()
|
||||
{
|
||||
Parent::processTick();
|
||||
|
||||
if (!isServerObject() || !isActive())
|
||||
return;
|
||||
|
||||
// Warp to catch up to server
|
||||
if (mDelta.warpCount < mDelta.warpTicks)
|
||||
{
|
||||
mDelta.warpCount++;
|
||||
|
||||
// Set new pos.
|
||||
mDelta.pos = mOwner->getPosition();
|
||||
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);
|
||||
|
||||
MatrixF trans;
|
||||
mDelta.rot[1].setMatrix(&trans);
|
||||
trans.setPosition(mDelta.pos);
|
||||
|
||||
mOwner->setTransform(trans);
|
||||
|
||||
// Pos backstepping
|
||||
mDelta.posVec.x = -mDelta.warpOffset.x;
|
||||
mDelta.posVec.y = -mDelta.warpOffset.y;
|
||||
mDelta.posVec.z = -mDelta.warpOffset.z;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Save current rigid state interpolation
|
||||
mDelta.posVec = mOwner->getPosition();
|
||||
mDelta.rot[0] = mOwner->getTransform();
|
||||
|
||||
updateMove();
|
||||
updatePos(TickSec);
|
||||
|
||||
// Wrap up interpolation info
|
||||
mDelta.pos = mOwner->getPosition();
|
||||
mDelta.posVec -= mOwner->getPosition();
|
||||
mDelta.rot[1] = mOwner->getTransform();
|
||||
|
||||
// Update container database
|
||||
setTransform(mOwner->getTransform());
|
||||
|
||||
setMaskBits(VelocityMask);
|
||||
setMaskBits(PositionMask);
|
||||
}
|
||||
}
|
||||
|
||||
void PlayerControllerComponent::interpolateTick(F32 dt)
|
||||
{
|
||||
}
|
||||
|
||||
void PlayerControllerComponent::ownerTransformSet(MatrixF *mat)
|
||||
{
|
||||
if (mPhysicsRep)
|
||||
mPhysicsRep->setTransform(mOwner->getTransform());
|
||||
}
|
||||
|
||||
void PlayerControllerComponent::setTransform(const MatrixF& mat)
|
||||
{
|
||||
mOwner->setTransform(mat);
|
||||
|
||||
setMaskBits(UpdateMask);
|
||||
}
|
||||
|
||||
//
|
||||
void PlayerControllerComponent::updateMove()
|
||||
{
|
||||
if (!PHYSICSMGR)
|
||||
return;
|
||||
|
||||
Move *move = &mOwner->lastMove;
|
||||
|
||||
//If we're not set to use mUseDirectMoveInput, then we allow for an override in the form of mInputVelocity
|
||||
if (!mUseDirectMoveInput)
|
||||
{
|
||||
move->x = mInputVelocity.x;
|
||||
move->y = mInputVelocity.y;
|
||||
move->z = mInputVelocity.z;
|
||||
}
|
||||
|
||||
// Is waterCoverage high enough to be 'swimming'?
|
||||
{
|
||||
bool swimming = mOwner->getContainerInfo().waterCoverage > 0.65f/* && canSwim()*/;
|
||||
|
||||
if (swimming != mSwimming)
|
||||
{
|
||||
mSwimming = swimming;
|
||||
}
|
||||
}
|
||||
|
||||
// Update current orientation
|
||||
bool doStandardMove = true;
|
||||
GameConnection* con = mOwner->getControllingClient();
|
||||
|
||||
#ifdef TORQUE_EXTENDED_MOVE
|
||||
// Work with an absolute rotation from the ExtendedMove class?
|
||||
if (con && con->getControlSchemeAbsoluteRotation())
|
||||
{
|
||||
doStandardMove = false;
|
||||
const ExtendedMove* emove = dynamic_cast<const ExtendedMove*>(move);
|
||||
U32 emoveIndex = smExtendedMoveHeadPosRotIndex;
|
||||
if (emoveIndex >= ExtendedMove::MaxPositionsRotations)
|
||||
emoveIndex = 0;
|
||||
|
||||
if (emove->EulerBasedRotation[emoveIndex])
|
||||
{
|
||||
// Head pitch
|
||||
mHead.x += (emove->rotX[emoveIndex] - mLastAbsolutePitch);
|
||||
|
||||
// Do we also include the relative yaw value?
|
||||
if (con->getControlSchemeAddPitchToAbsRot())
|
||||
{
|
||||
F32 x = move->pitch;
|
||||
if (x > M_PI_F)
|
||||
x -= M_2PI_F;
|
||||
|
||||
mHead.x += x;
|
||||
}
|
||||
|
||||
// Constrain the range of mHead.x
|
||||
while (mHead.x < -M_PI_F)
|
||||
mHead.x += M_2PI_F;
|
||||
while (mHead.x > M_PI_F)
|
||||
mHead.x -= M_2PI_F;
|
||||
|
||||
// Rotate (heading) head or body?
|
||||
if (move->freeLook && ((isMounted() && getMountNode() == 0) || (con && !con->isFirstPerson())))
|
||||
{
|
||||
// Rotate head
|
||||
mHead.z += (emove->rotZ[emoveIndex] - mLastAbsoluteYaw);
|
||||
|
||||
// Do we also include the relative yaw value?
|
||||
if (con->getControlSchemeAddYawToAbsRot())
|
||||
{
|
||||
F32 z = move->yaw;
|
||||
if (z > M_PI_F)
|
||||
z -= M_2PI_F;
|
||||
|
||||
mHead.z += z;
|
||||
}
|
||||
|
||||
// Constrain the range of mHead.z
|
||||
while (mHead.z < 0.0f)
|
||||
mHead.z += M_2PI_F;
|
||||
while (mHead.z > M_2PI_F)
|
||||
mHead.z -= M_2PI_F;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Rotate body
|
||||
mRot.z += (emove->rotZ[emoveIndex] - mLastAbsoluteYaw);
|
||||
|
||||
// Do we also include the relative yaw value?
|
||||
if (con->getControlSchemeAddYawToAbsRot())
|
||||
{
|
||||
F32 z = move->yaw;
|
||||
if (z > M_PI_F)
|
||||
z -= M_2PI_F;
|
||||
|
||||
mRot.z += z;
|
||||
}
|
||||
|
||||
// Constrain the range of mRot.z
|
||||
while (mRot.z < 0.0f)
|
||||
mRot.z += M_2PI_F;
|
||||
while (mRot.z > M_2PI_F)
|
||||
mRot.z -= M_2PI_F;
|
||||
}
|
||||
mLastAbsoluteYaw = emove->rotZ[emoveIndex];
|
||||
mLastAbsolutePitch = emove->rotX[emoveIndex];
|
||||
|
||||
// Head bank
|
||||
mHead.y = emove->rotY[emoveIndex];
|
||||
|
||||
// Constrain the range of mHead.y
|
||||
while (mHead.y > M_PI_F)
|
||||
mHead.y -= M_2PI_F;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
MatrixF zRot;
|
||||
zRot.set(EulerF(0.0f, 0.0f, mOwner->getRotation().asEulerF().z));
|
||||
|
||||
// Desired move direction & speed
|
||||
VectorF moveVec;
|
||||
F32 moveSpeed = mInputVelocity.len();
|
||||
|
||||
zRot.getColumn(0, &moveVec);
|
||||
moveVec *= move->x;
|
||||
VectorF tv;
|
||||
zRot.getColumn(1, &tv);
|
||||
moveVec += tv * move->y;
|
||||
|
||||
// Acceleration due to gravity
|
||||
VectorF acc(mPhysicsWorld->getGravity() * mGravityMod * TickSec);
|
||||
|
||||
// Determine ground contact normal. Only look for contacts if
|
||||
// we can move and aren't mounted.
|
||||
mContactInfo.contactNormal = VectorF::Zero;
|
||||
mContactInfo.jump = false;
|
||||
mContactInfo.run = false;
|
||||
|
||||
bool jumpSurface = false, runSurface = false;
|
||||
if (!mOwner->isMounted())
|
||||
findContact(&mContactInfo.run, &mContactInfo.jump, &mContactInfo.contactNormal);
|
||||
if (mContactInfo.jump)
|
||||
mJumpSurfaceNormal = mContactInfo.contactNormal;
|
||||
|
||||
// If we don't have a runSurface but we do have a contactNormal,
|
||||
// then we are standing on something that is too steep.
|
||||
// Deflect the force of gravity by the normal so we slide.
|
||||
// We could also try aligning it to the runSurface instead,
|
||||
// but this seems to work well.
|
||||
if (!mContactInfo.run && !mContactInfo.contactNormal.isZero())
|
||||
acc = (acc - 2 * mContactInfo.contactNormal * mDot(acc, mContactInfo.contactNormal));
|
||||
|
||||
// Acceleration on run surface
|
||||
if (mContactInfo.run && !mSwimming)
|
||||
{
|
||||
mContactTimer = 0;
|
||||
|
||||
VectorF pv = moveVec;
|
||||
|
||||
// Adjust the player's requested dir. to be parallel
|
||||
// to the contact surface.
|
||||
F32 pvl = pv.len();
|
||||
|
||||
// Convert to acceleration
|
||||
if (pvl)
|
||||
pv *= moveSpeed / pvl;
|
||||
VectorF runAcc = pv - (mVelocity + acc);
|
||||
F32 runSpeed = runAcc.len();
|
||||
|
||||
// Clamp acceleration, player also accelerates faster when
|
||||
// in his hard landing recover state.
|
||||
F32 maxAcc;
|
||||
|
||||
maxAcc = (horizMaxAccel / mMass) * TickSec;
|
||||
|
||||
if (runSpeed > maxAcc)
|
||||
runAcc *= maxAcc / runSpeed;
|
||||
|
||||
acc += runAcc;
|
||||
}
|
||||
else if (!mSwimming && airControl > 0.0f)
|
||||
{
|
||||
VectorF pv;
|
||||
pv = moveVec;
|
||||
F32 pvl = pv.len();
|
||||
|
||||
if (pvl)
|
||||
pv *= moveSpeed / pvl;
|
||||
|
||||
VectorF runAcc = pv - (mVelocity + acc);
|
||||
runAcc.z = 0;
|
||||
runAcc.x = runAcc.x * airControl;
|
||||
runAcc.y = runAcc.y * airControl;
|
||||
F32 runSpeed = runAcc.len();
|
||||
|
||||
// We don't test for sprinting when performing air control
|
||||
F32 maxAcc = (horizMaxAccel / mMass) * TickSec * 0.3f;
|
||||
|
||||
if (runSpeed > maxAcc)
|
||||
runAcc *= maxAcc / runSpeed;
|
||||
|
||||
acc += runAcc;
|
||||
|
||||
// There are no special air control animations
|
||||
// so... increment this unless you really want to
|
||||
// play the run anims in the air.
|
||||
mContactTimer++;
|
||||
}
|
||||
else if (mSwimming)
|
||||
{
|
||||
// Remove acc into contact surface (should only be gravity)
|
||||
// Clear out floating point acc errors, this will allow
|
||||
// the player to "rest" on the ground.
|
||||
F32 vd = -mDot(acc, mContactInfo.contactNormal);
|
||||
if (vd > 0.0f)
|
||||
{
|
||||
VectorF dv = mContactInfo.contactNormal * (vd + 0.002f);
|
||||
acc += dv;
|
||||
if (acc.len() < 0.0001f)
|
||||
acc.set(0.0f, 0.0f, 0.0f);
|
||||
}
|
||||
|
||||
// get the head pitch and add it to the moveVec
|
||||
// This more accurate swim vector calc comes from Matt Fairfax
|
||||
MatrixF xRot, zRot;
|
||||
xRot.set(EulerF(mOwner->getRotation().asEulerF().x, 0, 0));
|
||||
zRot.set(EulerF(0, 0, mOwner->getRotation().asEulerF().z));
|
||||
MatrixF rot;
|
||||
rot.mul(zRot, xRot);
|
||||
rot.getColumn(0, &moveVec);
|
||||
|
||||
moveVec *= move->x;
|
||||
VectorF tv;
|
||||
rot.getColumn(1, &tv);
|
||||
moveVec += tv * move->y;
|
||||
rot.getColumn(2, &tv);
|
||||
moveVec += tv * move->z;
|
||||
|
||||
// Force a 0 move if there is no energy, and only drain
|
||||
// move energy if we're moving.
|
||||
VectorF swimVec = moveVec;
|
||||
|
||||
// If we are swimming but close enough to the shore/ground
|
||||
// we can still have a surface-normal. In this case align the
|
||||
// velocity to the normal to make getting out of water easier.
|
||||
|
||||
moveVec.normalize();
|
||||
F32 isSwimUp = mDot(moveVec, mContactInfo.contactNormal);
|
||||
|
||||
if (!mContactInfo.contactNormal.isZero() && isSwimUp < 0.1f)
|
||||
{
|
||||
F32 pvl = swimVec.len();
|
||||
|
||||
if (pvl)
|
||||
{
|
||||
VectorF nn;
|
||||
mCross(swimVec, VectorF(0.0f, 0.0f, 1.0f), &nn);
|
||||
nn *= 1.0f / pvl;
|
||||
VectorF cv = mContactInfo.contactNormal;
|
||||
cv -= nn * mDot(nn, cv);
|
||||
swimVec -= cv * mDot(swimVec, cv);
|
||||
}
|
||||
}
|
||||
|
||||
F32 swimVecLen = swimVec.len();
|
||||
|
||||
// Convert to acceleration.
|
||||
if (swimVecLen)
|
||||
swimVec *= moveSpeed / swimVecLen;
|
||||
VectorF swimAcc = swimVec - (mVelocity + acc);
|
||||
F32 swimSpeed = swimAcc.len();
|
||||
|
||||
// Clamp acceleration.
|
||||
F32 maxAcc = (horizMaxAccel / mMass) * TickSec;
|
||||
if (swimSpeed > maxAcc)
|
||||
swimAcc *= maxAcc / swimSpeed;
|
||||
|
||||
acc += swimAcc;
|
||||
|
||||
mContactTimer++;
|
||||
}
|
||||
else
|
||||
mContactTimer++;
|
||||
|
||||
// Add in force from physical zones...
|
||||
acc += (mOwner->getContainerInfo().appliedForce / mMass) * TickSec;
|
||||
|
||||
// Adjust velocity with all the move & gravity acceleration
|
||||
// TG: I forgot why doesn't the TickSec multiply happen here...
|
||||
mVelocity += acc;
|
||||
|
||||
// apply horizontal air resistance
|
||||
|
||||
F32 hvel = mSqrt(mVelocity.x * mVelocity.x + mVelocity.y * mVelocity.y);
|
||||
|
||||
if (hvel > horizResistSpeed)
|
||||
{
|
||||
F32 speedCap = hvel;
|
||||
if (speedCap > horizMaxSpeed)
|
||||
speedCap = horizMaxSpeed;
|
||||
speedCap -= horizResistFactor * TickSec * (speedCap - horizResistSpeed);
|
||||
F32 scale = speedCap / hvel;
|
||||
mVelocity.x *= scale;
|
||||
mVelocity.y *= scale;
|
||||
}
|
||||
if (mVelocity.z > upResistSpeed)
|
||||
{
|
||||
if (mVelocity.z > upMaxSpeed)
|
||||
mVelocity.z = upMaxSpeed;
|
||||
mVelocity.z -= upResistFactor * TickSec * (mVelocity.z - upResistSpeed);
|
||||
}
|
||||
|
||||
// Apply drag
|
||||
mVelocity -= mVelocity * mDrag * TickSec;
|
||||
|
||||
// Clamp very small velocity to zero
|
||||
if (mVelocity.isZero())
|
||||
mVelocity = Point3F::Zero;
|
||||
|
||||
// If we are not touching anything and have sufficient -z vel,
|
||||
// we are falling.
|
||||
if (mContactInfo.run)
|
||||
{
|
||||
mFalling = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
VectorF vel;
|
||||
mOwner->getWorldToObj().mulV(mVelocity, &vel);
|
||||
mFalling = vel.z < fallingSpeedThreshold;
|
||||
}
|
||||
|
||||
// Enter/Leave Liquid
|
||||
if (!mInWater && mOwner->getContainerInfo().waterCoverage > 0.0f)
|
||||
{
|
||||
mInWater = true;
|
||||
}
|
||||
else if (mInWater && mOwner->getContainerInfo().waterCoverage <= 0.0f)
|
||||
{
|
||||
mInWater = false;
|
||||
}
|
||||
}
|
||||
|
||||
void PlayerControllerComponent::updatePos(const F32 travelTime)
|
||||
{
|
||||
if (!PHYSICSMGR)
|
||||
return;
|
||||
|
||||
PROFILE_SCOPE(PlayerControllerComponent_UpdatePos);
|
||||
|
||||
Point3F newPos;
|
||||
|
||||
Collision col;
|
||||
dMemset(&col, 0, sizeof(col));
|
||||
|
||||
static CollisionList collisionList;
|
||||
collisionList.clear();
|
||||
|
||||
newPos = mPhysicsRep->move(mVelocity * travelTime, collisionList);
|
||||
|
||||
bool haveCollisions = false;
|
||||
bool wasFalling = mFalling;
|
||||
if (collisionList.getCount() > 0)
|
||||
{
|
||||
mFalling = false;
|
||||
haveCollisions = true;
|
||||
|
||||
//TODO: clean this up so the phys component doesn't have to tell the col interface to do this
|
||||
CollisionInterface* colInterface = mOwner->getComponent<CollisionInterface>();
|
||||
if (colInterface)
|
||||
{
|
||||
colInterface->handleCollisionList(collisionList, mVelocity);
|
||||
}
|
||||
}
|
||||
|
||||
if (haveCollisions)
|
||||
{
|
||||
// Pick the collision that most closely matches our direction
|
||||
VectorF velNormal = mVelocity;
|
||||
velNormal.normalizeSafe();
|
||||
const Collision *collision = &collisionList[0];
|
||||
F32 collisionDot = mDot(velNormal, collision->normal);
|
||||
const Collision *cp = collision + 1;
|
||||
const Collision *ep = collision + collisionList.getCount();
|
||||
for (; cp != ep; cp++)
|
||||
{
|
||||
F32 dp = mDot(velNormal, cp->normal);
|
||||
if (dp < collisionDot)
|
||||
{
|
||||
collisionDot = dp;
|
||||
collision = cp;
|
||||
}
|
||||
}
|
||||
|
||||
// Modify our velocity based on collisions
|
||||
for (U32 i = 0; i<collisionList.getCount(); ++i)
|
||||
{
|
||||
F32 bd = -mDot(mVelocity, collisionList[i].normal);
|
||||
VectorF dv = collisionList[i].normal * (bd + sNormalElasticity);
|
||||
mVelocity += dv;
|
||||
}
|
||||
|
||||
// Store the last collision for use later on. The handle collision
|
||||
// code only expects a single collision object.
|
||||
if (collisionList.getCount() > 0)
|
||||
col = collisionList[collisionList.getCount() - 1];
|
||||
|
||||
// We'll handle any player-to-player collision, and the last collision
|
||||
// with other obejct types.
|
||||
for (U32 i = 0; i<collisionList.getCount(); ++i)
|
||||
{
|
||||
Collision& colCheck = collisionList[i];
|
||||
if (colCheck.object)
|
||||
{
|
||||
col = colCheck;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MatrixF newMat;
|
||||
newMat.setPosition(newPos);
|
||||
mPhysicsRep->setTransform(newMat);
|
||||
|
||||
mOwner->setPosition(newPos);
|
||||
}
|
||||
|
||||
//
|
||||
void PlayerControllerComponent::setVelocity(const VectorF& vel)
|
||||
{
|
||||
mVelocity = vel;
|
||||
|
||||
// Clamp against the maximum velocity.
|
||||
if (mMaxVelocity > 0)
|
||||
{
|
||||
F32 len = mVelocity.magnitudeSafe();
|
||||
if (len > mMaxVelocity)
|
||||
{
|
||||
Point3F excess = mVelocity * (1.0f - (mMaxVelocity / len));
|
||||
mVelocity -= excess;
|
||||
}
|
||||
}
|
||||
|
||||
setMaskBits(VelocityMask);
|
||||
}
|
||||
|
||||
void PlayerControllerComponent::findContact(bool *run, bool *jump, VectorF *contactNormal)
|
||||
{
|
||||
SceneObject *contactObject = NULL;
|
||||
|
||||
Vector<SceneObject*> overlapObjects;
|
||||
|
||||
mPhysicsRep->findContact(&contactObject, contactNormal, &overlapObjects);
|
||||
|
||||
F32 vd = (*contactNormal).z;
|
||||
*run = vd > mCos(mDegToRad(moveSurfaceAngle));
|
||||
*jump = vd > mCos(mDegToRad(contactSurfaceAngle));
|
||||
|
||||
// Check for triggers
|
||||
for (U32 i = 0; i < overlapObjects.size(); i++)
|
||||
{
|
||||
SceneObject *obj = overlapObjects[i];
|
||||
U32 objectMask = obj->getTypeMask();
|
||||
|
||||
// Check: triggers, corpses and items...
|
||||
//
|
||||
if (objectMask & TriggerObjectType)
|
||||
{
|
||||
if (Trigger* pTrigger = dynamic_cast<Trigger*>(obj))
|
||||
{
|
||||
pTrigger->potentialEnterObject(mOwner);
|
||||
}
|
||||
else if (CollisionTrigger* pTriggerEx = dynamic_cast<CollisionTrigger*>(obj))
|
||||
{
|
||||
if (pTriggerEx)
|
||||
pTriggerEx->potentialEnterObject(mOwner);
|
||||
}
|
||||
//Add any other custom classes and the sort here that should be filtered against
|
||||
/*else if (TriggerExample* pTriggerEx = dynamic_cast<TriggerExample*>(obj))
|
||||
{
|
||||
if (pTriggerEx)
|
||||
pTriggerEx->potentialEnterObject(mOwner);
|
||||
}*/
|
||||
}
|
||||
}
|
||||
|
||||
mContactInfo.contacted = contactObject != NULL;
|
||||
mContactInfo.contactObject = contactObject;
|
||||
|
||||
if (mContactInfo.contacted)
|
||||
mContactInfo.contactNormal = *contactNormal;
|
||||
}
|
||||
|
||||
void PlayerControllerComponent::applyImpulse(const Point3F &pos, const VectorF &vec)
|
||||
{
|
||||
|
||||
AssertFatal(!mIsNaN(vec), "Player::applyImpulse() - The vector is NaN!");
|
||||
|
||||
// Players ignore angular velocity
|
||||
VectorF vel;
|
||||
vel.x = vec.x / getMass();
|
||||
vel.y = vec.y / getMass();
|
||||
vel.z = vec.z / getMass();
|
||||
|
||||
// Make sure the impulse isn't too bigg
|
||||
F32 len = vel.magnitudeSafe();
|
||||
if (len > sMaxImpulseVelocity)
|
||||
{
|
||||
Point3F excess = vel * (1.0f - (sMaxImpulseVelocity / len));
|
||||
vel -= excess;
|
||||
}
|
||||
|
||||
setVelocity(mVelocity + vel);
|
||||
}
|
||||
|
||||
DefineEngineMethod(PlayerControllerComponent, applyImpulse, bool, (Point3F pos, VectorF vel), ,
|
||||
"@brief Apply an impulse to this object as defined by a world position and velocity vector.\n\n"
|
||||
|
||||
"@param pos impulse world position\n"
|
||||
"@param vel impulse velocity (impulse force F = m * v)\n"
|
||||
"@return Always true\n"
|
||||
|
||||
"@note Not all objects that derrive from GameBase have this defined.\n")
|
||||
{
|
||||
object->applyImpulse(pos, vel);
|
||||
return true;
|
||||
}
|
||||
|
||||
DefineEngineMethod(PlayerControllerComponent, getContactNormal, Point3F, (), ,
|
||||
"@brief Apply an impulse to this object as defined by a world position and velocity vector.\n\n"
|
||||
|
||||
"@param pos impulse world position\n"
|
||||
"@param vel impulse velocity (impulse force F = m * v)\n"
|
||||
"@return Always true\n"
|
||||
|
||||
"@note Not all objects that derrive from GameBase have this defined.\n")
|
||||
{
|
||||
return object->getContactNormal();
|
||||
}
|
||||
|
||||
DefineEngineMethod(PlayerControllerComponent, getContactObject, SceneObject*, (), ,
|
||||
"@brief Apply an impulse to this object as defined by a world position and velocity vector.\n\n"
|
||||
|
||||
"@param pos impulse world position\n"
|
||||
"@param vel impulse velocity (impulse force F = m * v)\n"
|
||||
"@return Always true\n"
|
||||
|
||||
"@note Not all objects that derrive from GameBase have this defined.\n")
|
||||
{
|
||||
return object->getContactObject();
|
||||
}
|
||||
|
||||
DefineEngineMethod(PlayerControllerComponent, isContacted, bool, (), ,
|
||||
"@brief Apply an impulse to this object as defined by a world position and velocity vector.\n\n"
|
||||
|
||||
"@param pos impulse world position\n"
|
||||
"@param vel impulse velocity (impulse force F = m * v)\n"
|
||||
"@return Always true\n"
|
||||
|
||||
"@note Not all objects that derrive from GameBase have this defined.\n")
|
||||
{
|
||||
return object->isContacted();
|
||||
}
|
||||
212
Engine/source/T3D/components/Physics/playerControllerComponent.h
Normal file
212
Engine/source/T3D/components/Physics/playerControllerComponent.h
Normal file
|
|
@ -0,0 +1,212 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// 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 PLAYER_CONTORLLER_COMPONENT_H
|
||||
#define PLAYER_CONTORLLER_COMPONENT_H
|
||||
|
||||
#ifndef PHYSICSBEHAVIOR_H
|
||||
#include "T3D/Components/Physics/physicsBehavior.h"
|
||||
#endif
|
||||
#ifndef __RESOURCE_H__
|
||||
#include "core/resource.h"
|
||||
#endif
|
||||
#ifndef _TSSHAPE_H_
|
||||
#include "ts/tsShape.h"
|
||||
#endif
|
||||
#ifndef _SCENERENDERSTATE_H_
|
||||
#include "scene/sceneRenderState.h"
|
||||
#endif
|
||||
#ifndef _MBOX_H_
|
||||
#include "math/mBox.h"
|
||||
#endif
|
||||
#ifndef ENTITY_H
|
||||
#include "T3D/Entity.h"
|
||||
#endif
|
||||
#ifndef _CONVEX_H_
|
||||
#include "collision/convex.h"
|
||||
#endif
|
||||
#ifndef _BOXCONVEX_H_
|
||||
#include "collision/boxConvex.h"
|
||||
#endif
|
||||
#ifndef _T3D_PHYSICSCOMMON_H_
|
||||
#include "T3D/physics/physicsCommon.h"
|
||||
#endif
|
||||
#ifndef _T3D_PHYSICS_PHYSICSWORLD_H_
|
||||
#include "T3D/physics/physicsWorld.h"
|
||||
#endif
|
||||
#ifndef PHYSICS_COMPONENT_INTERFACE_H
|
||||
#include "T3D/Components/physics/physicsComponentInterface.h"
|
||||
#endif
|
||||
#ifndef COLLISION_INTERFACES_H
|
||||
#include "T3D/Components/collision/collisionInterfaces.h"
|
||||
#endif
|
||||
|
||||
class SceneRenderState;
|
||||
class PhysicsWorld;
|
||||
class PhysicsPlayer;
|
||||
class SimplePhysicsBehaviorInstance;
|
||||
class CollisionInterface;
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
///
|
||||
///
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
class PlayerControllerComponent : public Component,
|
||||
public PhysicsComponentInterface
|
||||
{
|
||||
typedef Component Parent;
|
||||
|
||||
enum MaskBits {
|
||||
VelocityMask = Parent::NextFreeMask << 0,
|
||||
PositionMask = Parent::NextFreeMask << 1,
|
||||
NextFreeMask = Parent::NextFreeMask << 2
|
||||
};
|
||||
|
||||
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];
|
||||
};
|
||||
|
||||
StateDelta mDelta;
|
||||
|
||||
PhysicsPlayer *mPhysicsRep;
|
||||
PhysicsWorld *mPhysicsWorld;
|
||||
|
||||
CollisionInterface* mOwnerCollisionInterface;
|
||||
|
||||
struct ContactInfo
|
||||
{
|
||||
bool contacted, jump, run;
|
||||
SceneObject *contactObject;
|
||||
VectorF contactNormal;
|
||||
F32 contactTime;
|
||||
|
||||
void clear()
|
||||
{
|
||||
contacted = jump = run = false;
|
||||
contactObject = NULL;
|
||||
contactNormal.set(1, 1, 1);
|
||||
}
|
||||
|
||||
ContactInfo() { clear(); }
|
||||
|
||||
} mContactInfo;
|
||||
|
||||
protected:
|
||||
F32 mDrag;
|
||||
F32 mBuoyancy;
|
||||
F32 mFriction;
|
||||
F32 mElasticity;
|
||||
F32 mMaxVelocity;
|
||||
bool mSticky;
|
||||
|
||||
bool mFalling;
|
||||
bool mSwimming;
|
||||
bool mInWater;
|
||||
|
||||
S32 mContactTimer; ///< Ticks since last contact
|
||||
|
||||
U32 mIntegrationCount;
|
||||
|
||||
Point3F mJumpSurfaceNormal; ///< Normal of the surface the player last jumped on
|
||||
|
||||
F32 maxStepHeight; ///< Maximum height the player can step up
|
||||
F32 moveSurfaceAngle; ///< Maximum angle from vertical in degrees the player can run up
|
||||
F32 contactSurfaceAngle; ///< Maximum angle from vertical in degrees we consider having real 'contact'
|
||||
|
||||
F32 horizMaxSpeed; ///< Max speed attainable in the horizontal
|
||||
F32 horizMaxAccel;
|
||||
F32 horizResistSpeed; ///< Speed at which resistance will take place
|
||||
F32 horizResistFactor; ///< Factor of resistance once horizResistSpeed has been reached
|
||||
|
||||
F32 upMaxSpeed; ///< Max vertical speed attainable
|
||||
F32 upMaxAccel;
|
||||
F32 upResistSpeed; ///< Speed at which resistance will take place
|
||||
F32 upResistFactor; ///< Factor of resistance once upResistSpeed has been reached
|
||||
|
||||
F32 fallingSpeedThreshold; ///< Downward speed at which we consider the player falling
|
||||
|
||||
// Air control
|
||||
F32 airControl;
|
||||
|
||||
Point3F mInputVelocity;
|
||||
|
||||
bool mUseDirectMoveInput;
|
||||
|
||||
public:
|
||||
PlayerControllerComponent();
|
||||
virtual ~PlayerControllerComponent();
|
||||
DECLARE_CONOBJECT(PlayerControllerComponent);
|
||||
|
||||
virtual bool onAdd();
|
||||
virtual void onRemove();
|
||||
static void initPersistFields();
|
||||
|
||||
virtual void onComponentAdd();
|
||||
|
||||
virtual void componentAddedToOwner(Component *comp);
|
||||
virtual void componentRemovedFromOwner(Component *comp);
|
||||
|
||||
virtual void ownerTransformSet(MatrixF *mat);
|
||||
|
||||
virtual U32 packUpdate(NetConnection *con, U32 mask, BitStream *stream);
|
||||
virtual void unpackUpdate(NetConnection *con, BitStream *stream);
|
||||
|
||||
void updatePhysics(PhysicsCollision *collision = NULL);
|
||||
|
||||
virtual void processTick();
|
||||
virtual void interpolateTick(F32 dt);
|
||||
virtual void updatePos(const F32 dt);
|
||||
void updateMove();
|
||||
|
||||
virtual VectorF getVelocity() { return mVelocity; }
|
||||
virtual void setVelocity(const VectorF& vel);
|
||||
virtual void setTransform(const MatrixF& mat);
|
||||
|
||||
void findContact(bool *run, bool *jump, VectorF *contactNormal);
|
||||
Point3F getContactNormal() { return mContactInfo.contactNormal; }
|
||||
SceneObject* getContactObject() { return mContactInfo.contactObject; }
|
||||
bool isContacted() { return mContactInfo.contacted; }
|
||||
|
||||
//
|
||||
void applyImpulse(const Point3F &pos, const VectorF &vec);
|
||||
|
||||
//This is a weird artifact of the PhysicsReps. We want the collision component to be privvy to any events that happen
|
||||
//so when the physics components do a findContact test during their update, they'll have a signal collision components
|
||||
//can be listening to to update themselves with that info
|
||||
Signal< void(SceneObject*) > PlayerControllerComponent::onContactSignal;
|
||||
|
||||
//
|
||||
DECLARE_CALLBACK(void, updateMove, (PlayerControllerComponent* obj));
|
||||
};
|
||||
|
||||
#endif // _COMPONENT_H_
|
||||
467
Engine/source/T3D/components/Physics/rigidBodyComponent.cpp
Normal file
467
Engine/source/T3D/components/Physics/rigidBodyComponent.cpp
Normal file
|
|
@ -0,0 +1,467 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// 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 "T3D/Components/physics/RigidBodyComponent.h"
|
||||
#include "core/util/safeDelete.h"
|
||||
#include "console/consoleTypes.h"
|
||||
#include "console/consoleObject.h"
|
||||
#include "core/stream/bitStream.h"
|
||||
#include "console/engineAPI.h"
|
||||
#include "sim/netConnection.h"
|
||||
#include "T3D/physics/physicsBody.h"
|
||||
#include "T3D/physics/physicsPlugin.h"
|
||||
#include "T3D/physics/physicsWorld.h"
|
||||
#include "T3D/physics/physicsCollision.h"
|
||||
#include "T3D/Components/Collision/collisionComponent.h"
|
||||
|
||||
bool RigidBodyComponent::smNoCorrections = false;
|
||||
bool RigidBodyComponent::smNoSmoothing = false;
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// Constructor/Destructor
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
RigidBodyComponent::RigidBodyComponent() : Component()
|
||||
{
|
||||
mMass = 20;
|
||||
mDynamicFriction = 1;
|
||||
mStaticFriction = 0.1f;
|
||||
mRestitution = 10;
|
||||
mLinearDamping = 0;
|
||||
mAngularDamping = 0;
|
||||
mLinearSleepThreshold = 1;
|
||||
mAngularSleepThreshold = 1;
|
||||
mWaterDampingScale = 0.1f;
|
||||
mBuoyancyDensity = 1;
|
||||
|
||||
mSimType = SimType_ServerOnly;
|
||||
|
||||
mPhysicsRep = NULL;
|
||||
mResetPos = MatrixF::Identity;
|
||||
|
||||
mOwnerColComponent = NULL;
|
||||
|
||||
mFriendlyName = "RigidBody(Component)";
|
||||
}
|
||||
|
||||
RigidBodyComponent::~RigidBodyComponent()
|
||||
{
|
||||
}
|
||||
|
||||
IMPLEMENT_CO_NETOBJECT_V1(RigidBodyComponent);
|
||||
|
||||
bool RigidBodyComponent::onAdd()
|
||||
{
|
||||
if(! Parent::onAdd())
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void RigidBodyComponent::onRemove()
|
||||
{
|
||||
Parent::onRemove();
|
||||
}
|
||||
void RigidBodyComponent::initPersistFields()
|
||||
{
|
||||
Parent::initPersistFields();
|
||||
}
|
||||
|
||||
//This is mostly a catch for situations where the behavior is re-added to the object and the like and we may need to force an update to the behavior
|
||||
void RigidBodyComponent::onComponentAdd()
|
||||
{
|
||||
Parent::onComponentAdd();
|
||||
|
||||
if (isServerObject())
|
||||
{
|
||||
storeRestorePos();
|
||||
PhysicsPlugin::getPhysicsResetSignal().notify(this, &RigidBodyComponent::_onPhysicsReset);
|
||||
}
|
||||
|
||||
CollisionComponent *colComp = mOwner->getComponent<CollisionComponent>();
|
||||
if (colComp)
|
||||
{
|
||||
colComp->onCollisionChanged.notify(this, &RigidBodyComponent::updatePhysics);
|
||||
updatePhysics(colComp->getCollisionData());
|
||||
}
|
||||
else
|
||||
updatePhysics();
|
||||
}
|
||||
|
||||
void RigidBodyComponent::onComponentRemove()
|
||||
{
|
||||
Parent::onComponentRemove();
|
||||
|
||||
if (isServerObject())
|
||||
{
|
||||
PhysicsPlugin::getPhysicsResetSignal().remove(this, &RigidBodyComponent::_onPhysicsReset);
|
||||
}
|
||||
|
||||
CollisionComponent *colComp = mOwner->getComponent<CollisionComponent>();
|
||||
if (colComp)
|
||||
{
|
||||
colComp->onCollisionChanged.remove(this, &RigidBodyComponent::updatePhysics);
|
||||
}
|
||||
|
||||
SAFE_DELETE(mPhysicsRep);
|
||||
}
|
||||
|
||||
void RigidBodyComponent::componentAddedToOwner(Component *comp)
|
||||
{
|
||||
CollisionComponent *colComp = dynamic_cast<CollisionComponent*>(comp);
|
||||
if (colComp)
|
||||
{
|
||||
colComp->onCollisionChanged.notify(this, &RigidBodyComponent::updatePhysics);
|
||||
updatePhysics(colComp->getCollisionData());
|
||||
}
|
||||
}
|
||||
|
||||
void RigidBodyComponent::componentRemovedFromOwner(Component *comp)
|
||||
{
|
||||
//test if this is a shape component!
|
||||
CollisionComponent *colComp = dynamic_cast<CollisionComponent*>(comp);
|
||||
if (colComp)
|
||||
{
|
||||
colComp->onCollisionChanged.remove(this, &RigidBodyComponent::updatePhysics);
|
||||
updatePhysics();
|
||||
}
|
||||
}
|
||||
|
||||
void RigidBodyComponent::ownerTransformSet(MatrixF *mat)
|
||||
{
|
||||
if (mPhysicsRep)
|
||||
mPhysicsRep->setTransform(mOwner->getTransform());
|
||||
}
|
||||
|
||||
void RigidBodyComponent::updatePhysics(PhysicsCollision* collision)
|
||||
{
|
||||
SAFE_DELETE(mPhysicsRep);
|
||||
|
||||
if (!PHYSICSMGR)
|
||||
return;
|
||||
|
||||
mWorld = PHYSICSMGR->getWorld(isServerObject() ? "server" : "client");
|
||||
|
||||
if (!collision)
|
||||
return;
|
||||
|
||||
mPhysicsRep = PHYSICSMGR->createBody();
|
||||
|
||||
mPhysicsRep->init(collision, mMass, 0, mOwner, mWorld);
|
||||
|
||||
mPhysicsRep->setMaterial(mRestitution, mDynamicFriction, mStaticFriction);
|
||||
|
||||
mPhysicsRep->setDamping(mLinearDamping, mAngularDamping);
|
||||
mPhysicsRep->setSleepThreshold(mLinearSleepThreshold, mAngularSleepThreshold);
|
||||
|
||||
mPhysicsRep->setTransform(mOwner->getTransform());
|
||||
|
||||
// The reset position is the transform on the server
|
||||
// at creation time... its not used on the client.
|
||||
if (isServerObject())
|
||||
{
|
||||
storeRestorePos();
|
||||
PhysicsPlugin::getPhysicsResetSignal().notify(this, &RigidBodyComponent::_onPhysicsReset);
|
||||
}
|
||||
}
|
||||
|
||||
U32 RigidBodyComponent::packUpdate(NetConnection *con, U32 mask, BitStream *stream)
|
||||
{
|
||||
U32 retMask = Parent::packUpdate(con, mask, stream);
|
||||
|
||||
if (stream->writeFlag(mask & StateMask))
|
||||
{
|
||||
// This will encode the position relative to the control
|
||||
// object position.
|
||||
//
|
||||
// This will compress the position to as little as 6.25
|
||||
// bytes if the position is within about 30 meters of the
|
||||
// control object.
|
||||
//
|
||||
// Worst case its a full 12 bytes + 2 bits if the position
|
||||
// is more than 500 meters from the control object.
|
||||
//
|
||||
stream->writeCompressedPoint(mState.position);
|
||||
|
||||
// Use only 3.5 bytes to send the orientation.
|
||||
stream->writeQuat(mState.orientation, 9);
|
||||
|
||||
// If the server object has been set to sleep then
|
||||
// we don't need to send any velocity.
|
||||
if (!stream->writeFlag(mState.sleeping))
|
||||
{
|
||||
// This gives me ~0.015f resolution in velocity magnitude
|
||||
// while only costing me 1 bit of the velocity is zero length,
|
||||
// <5 bytes in normal cases, and <8 bytes if the velocity is
|
||||
// greater than 1000.
|
||||
AssertWarn(mState.linVelocity.len() < 1000.0f,
|
||||
"PhysicsShape::packUpdate - The linVelocity is out of range!");
|
||||
stream->writeVector(mState.linVelocity, 1000.0f, 16, 9);
|
||||
|
||||
// For angular velocity we get < 0.01f resolution in magnitude
|
||||
// with the most common case being under 4 bytes.
|
||||
AssertWarn(mState.angVelocity.len() < 10.0f,
|
||||
"PhysicsShape::packUpdate - The angVelocity is out of range!");
|
||||
stream->writeVector(mState.angVelocity, 10.0f, 10, 9);
|
||||
}
|
||||
}
|
||||
|
||||
return retMask;
|
||||
}
|
||||
|
||||
void RigidBodyComponent::unpackUpdate(NetConnection *con, BitStream *stream)
|
||||
{
|
||||
Parent::unpackUpdate(con, stream);
|
||||
|
||||
if (stream->readFlag()) // StateMask
|
||||
{
|
||||
PhysicsState state;
|
||||
|
||||
// Read the encoded and compressed position... commonly only 6.25 bytes.
|
||||
stream->readCompressedPoint(&state.position);
|
||||
|
||||
// Read the compressed quaternion... 3.5 bytes.
|
||||
stream->readQuat(&state.orientation, 9);
|
||||
|
||||
state.sleeping = stream->readFlag();
|
||||
if (!state.sleeping)
|
||||
{
|
||||
stream->readVector(&state.linVelocity, 1000.0f, 16, 9);
|
||||
stream->readVector(&state.angVelocity, 10.0f, 10, 9);
|
||||
}
|
||||
|
||||
if (!smNoCorrections && mPhysicsRep && mPhysicsRep->isDynamic())
|
||||
{
|
||||
// Set the new state on the physics object immediately.
|
||||
mPhysicsRep->applyCorrection(state.getTransform());
|
||||
|
||||
mPhysicsRep->setSleeping(state.sleeping);
|
||||
if (!state.sleeping)
|
||||
{
|
||||
mPhysicsRep->setLinVelocity(state.linVelocity);
|
||||
mPhysicsRep->setAngVelocity(state.angVelocity);
|
||||
}
|
||||
|
||||
mPhysicsRep->getState(&mState);
|
||||
}
|
||||
|
||||
// If there is no physics object then just set the
|
||||
// new state... the tick will take care of the
|
||||
// interpolation and extrapolation.
|
||||
if (!mPhysicsRep || !mPhysicsRep->isDynamic())
|
||||
mState = state;
|
||||
}
|
||||
}
|
||||
|
||||
void RigidBodyComponent::processTick()
|
||||
{
|
||||
Parent::processTick();
|
||||
|
||||
if (!mPhysicsRep || !PHYSICSMGR)
|
||||
return;
|
||||
|
||||
// Note that unlike TSStatic, the serverside PhysicsShape does not
|
||||
// need to play the ambient animation because even if the animation were
|
||||
// to move collision shapes it would not affect the physx representation.
|
||||
|
||||
PROFILE_START(RigidBodyComponent_ProcessTick);
|
||||
|
||||
if (!mPhysicsRep->isDynamic())
|
||||
return;
|
||||
|
||||
// SINGLE PLAYER HACK!!!!
|
||||
if (PHYSICSMGR->isSinglePlayer() && isClientObject() && getServerObject())
|
||||
{
|
||||
RigidBodyComponent *servObj = (RigidBodyComponent*)getServerObject();
|
||||
mOwner->setTransform(servObj->mState.getTransform());
|
||||
mRenderState[0] = servObj->mRenderState[0];
|
||||
mRenderState[1] = servObj->mRenderState[1];
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Store the last render state.
|
||||
mRenderState[0] = mRenderState[1];
|
||||
|
||||
// If the last render state doesn't match the last simulation
|
||||
// state then we got a correction and need to
|
||||
Point3F errorDelta = mRenderState[1].position - mState.position;
|
||||
const bool doSmoothing = !errorDelta.isZero() && !smNoSmoothing;
|
||||
|
||||
const bool wasSleeping = mState.sleeping;
|
||||
|
||||
// Get the new physics state.
|
||||
mPhysicsRep->getState(&mState);
|
||||
updateContainerForces();
|
||||
|
||||
// Smooth the correction back into the render state.
|
||||
mRenderState[1] = mState;
|
||||
if (doSmoothing)
|
||||
{
|
||||
F32 correction = mClampF(errorDelta.len() / 20.0f, 0.1f, 0.9f);
|
||||
mRenderState[1].position.interpolate(mState.position, mRenderState[0].position, correction);
|
||||
mRenderState[1].orientation.interpolate(mState.orientation, mRenderState[0].orientation, correction);
|
||||
}
|
||||
|
||||
//Check if any collisions occured
|
||||
findContact();
|
||||
|
||||
// If we haven't been sleeping then update our transform
|
||||
// and set ourselves as dirty for the next client update.
|
||||
if (!wasSleeping || !mState.sleeping)
|
||||
{
|
||||
// Set the transform on the parent so that
|
||||
// the physics object isn't moved.
|
||||
mOwner->setTransform(mState.getTransform());
|
||||
|
||||
// If we're doing server simulation then we need
|
||||
// to send the client a state update.
|
||||
if (isServerObject() && mPhysicsRep && !smNoCorrections &&
|
||||
!PHYSICSMGR->isSinglePlayer() // SINGLE PLAYER HACK!!!!
|
||||
)
|
||||
setMaskBits(StateMask);
|
||||
}
|
||||
|
||||
PROFILE_END();
|
||||
}
|
||||
|
||||
void RigidBodyComponent::findContact()
|
||||
{
|
||||
SceneObject *contactObject = NULL;
|
||||
|
||||
VectorF *contactNormal = new VectorF(0, 0, 0);
|
||||
|
||||
Vector<SceneObject*> overlapObjects;
|
||||
|
||||
mPhysicsRep->findContact(&contactObject, contactNormal, &overlapObjects);
|
||||
|
||||
if (!overlapObjects.empty())
|
||||
{
|
||||
//fire our signal that the physics sim said collisions happened
|
||||
onPhysicsCollision.trigger(*contactNormal, overlapObjects);
|
||||
}
|
||||
}
|
||||
|
||||
void RigidBodyComponent::_onPhysicsReset(PhysicsResetEvent reset)
|
||||
{
|
||||
if (reset == PhysicsResetEvent_Store)
|
||||
mResetPos = mOwner->getTransform();
|
||||
|
||||
else if (reset == PhysicsResetEvent_Restore)
|
||||
{
|
||||
mOwner->setTransform(mResetPos);
|
||||
}
|
||||
}
|
||||
|
||||
void RigidBodyComponent::storeRestorePos()
|
||||
{
|
||||
mResetPos = mOwner->getTransform();
|
||||
}
|
||||
|
||||
void RigidBodyComponent::applyImpulse(const Point3F &pos, const VectorF &vec)
|
||||
{
|
||||
if (mPhysicsRep && mPhysicsRep->isDynamic())
|
||||
mPhysicsRep->applyImpulse(pos, vec);
|
||||
}
|
||||
|
||||
void RigidBodyComponent::applyRadialImpulse(const Point3F &origin, F32 radius, F32 magnitude)
|
||||
{
|
||||
if (!mPhysicsRep || !mPhysicsRep->isDynamic())
|
||||
return;
|
||||
|
||||
// TODO: Find a better approximation of the
|
||||
// force vector using the object box.
|
||||
|
||||
VectorF force = mOwner->getWorldBox().getCenter() - origin;
|
||||
F32 dist = force.magnitudeSafe();
|
||||
force.normalize();
|
||||
|
||||
if (dist == 0.0f)
|
||||
force *= magnitude;
|
||||
else
|
||||
force *= mClampF(radius / dist, 0.0f, 1.0f) * magnitude;
|
||||
|
||||
mPhysicsRep->applyImpulse(origin, force);
|
||||
|
||||
// TODO: There is no simple way to really sync this sort of an
|
||||
// event with the client.
|
||||
//
|
||||
// The best is to send the current physics snapshot, calculate the
|
||||
// time difference from when this event occured and the time when the
|
||||
// client recieves it, and then extrapolate where it should be.
|
||||
//
|
||||
// Even then its impossible to be absolutely sure its synced.
|
||||
//
|
||||
// Bottom line... you shouldn't use physics over the network like this.
|
||||
//
|
||||
}
|
||||
|
||||
void RigidBodyComponent::updateContainerForces()
|
||||
{
|
||||
PROFILE_SCOPE(RigidBodyComponent_updateContainerForces);
|
||||
|
||||
// If we're not simulating don't update forces.
|
||||
PhysicsWorld *world = PHYSICSMGR->getWorld(isServerObject() ? "server" : "client");
|
||||
if (!world || !world->isEnabled())
|
||||
return;
|
||||
|
||||
ContainerQueryInfo info;
|
||||
info.box = mOwner->getWorldBox();
|
||||
info.mass = mMass;
|
||||
|
||||
// Find and retreive physics info from intersecting WaterObject(s)
|
||||
mOwner->getContainer()->findObjects(mOwner->getWorldBox(), WaterObjectType | PhysicalZoneObjectType, findRouter, &info);
|
||||
|
||||
// Calculate buoyancy and drag
|
||||
F32 angDrag = mAngularDamping;
|
||||
F32 linDrag = mLinearDamping;
|
||||
F32 buoyancy = 0.0f;
|
||||
Point3F cmass = mPhysicsRep->getCMassPosition();
|
||||
|
||||
F32 density = mBuoyancyDensity;
|
||||
if (density > 0.0f)
|
||||
{
|
||||
if (info.waterCoverage > 0.0f)
|
||||
{
|
||||
F32 waterDragScale = info.waterViscosity * mWaterDampingScale;
|
||||
F32 powCoverage = mPow(info.waterCoverage, 0.25f);
|
||||
|
||||
angDrag = mLerp(angDrag, angDrag * waterDragScale, powCoverage);
|
||||
linDrag = mLerp(linDrag, linDrag * waterDragScale, powCoverage);
|
||||
}
|
||||
|
||||
buoyancy = (info.waterDensity / density) * mPow(info.waterCoverage, 2.0f);
|
||||
|
||||
// A little hackery to prevent oscillation
|
||||
// Based on this blog post:
|
||||
// (http://reinot.blogspot.com/2005/11/oh-yes-they-float-georgie-they-all.html)
|
||||
// JCF: disabled!
|
||||
Point3F buoyancyForce = buoyancy * -world->getGravity() * TickSec * mMass;
|
||||
mPhysicsRep->applyImpulse(cmass, buoyancyForce);
|
||||
}
|
||||
|
||||
// Update the dampening as the container might have changed.
|
||||
mPhysicsRep->setDamping(linDrag, angDrag);
|
||||
|
||||
// Apply physical zone forces.
|
||||
if (!info.appliedForce.isZero())
|
||||
mPhysicsRep->applyImpulse(cmass, info.appliedForce);
|
||||
}
|
||||
183
Engine/source/T3D/components/Physics/rigidBodyComponent.h
Normal file
183
Engine/source/T3D/components/Physics/rigidBodyComponent.h
Normal file
|
|
@ -0,0 +1,183 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// 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 RIGID_BODY_COMPONENT_H
|
||||
#define RIGID_BODY_COMPONENT_H
|
||||
|
||||
#ifndef COMPONENT_H
|
||||
#include "T3D/Components/Component.h"
|
||||
#endif
|
||||
#ifndef _T3D_PHYSICSCOMMON_H_
|
||||
#include "T3D/physics/physicsCommon.h"
|
||||
#endif
|
||||
#ifndef COLLISION_COMPONENT_H
|
||||
#include "T3D/Components/collision/collisionComponent.h"
|
||||
#endif
|
||||
#ifndef PHYSICS_COMPONENT_INTERFACE_H
|
||||
#include "T3D/Components/physics/physicsComponentInterface.h"
|
||||
#endif
|
||||
|
||||
class PhysicsBody;
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
///
|
||||
///
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
class RigidBodyComponent : public Component, public PhysicsComponentInterface
|
||||
{
|
||||
typedef Component Parent;
|
||||
|
||||
enum SimType
|
||||
{
|
||||
/// This physics representation only exists on the client
|
||||
/// world and the server only does ghosting.
|
||||
SimType_ClientOnly,
|
||||
|
||||
/// The physics representation only exists on the server world
|
||||
/// and the client gets delta updates for rendering.
|
||||
SimType_ServerOnly,
|
||||
|
||||
/// The physics representation exists on the client and the server
|
||||
/// worlds with corrections occuring when the client gets out of sync.
|
||||
SimType_ClientServer,
|
||||
|
||||
/// The bits used to pack the SimType field.
|
||||
SimType_Bits = 3,
|
||||
|
||||
} mSimType;
|
||||
|
||||
//
|
||||
//
|
||||
/// The current physics state.
|
||||
PhysicsState mState;
|
||||
|
||||
/// The previous and current render states.
|
||||
PhysicsState mRenderState[2];
|
||||
|
||||
/// The abstracted physics actor.
|
||||
PhysicsBody *mPhysicsRep;
|
||||
|
||||
PhysicsWorld *mWorld;
|
||||
|
||||
/// The starting position to place the shape when
|
||||
/// the level begins or is reset.
|
||||
MatrixF mResetPos;
|
||||
//
|
||||
//
|
||||
|
||||
/// If true then no corrections are sent from the server
|
||||
/// and/or applied from the client.
|
||||
///
|
||||
/// This is only ment for debugging.
|
||||
///
|
||||
static bool smNoCorrections;
|
||||
|
||||
/// If true then no smoothing is done on the client when
|
||||
/// applying server corrections.
|
||||
///
|
||||
/// This is only ment for debugging.
|
||||
///
|
||||
static bool smNoSmoothing;
|
||||
|
||||
///
|
||||
F32 mMass;
|
||||
|
||||
///
|
||||
F32 mDynamicFriction;
|
||||
|
||||
///
|
||||
F32 mStaticFriction;
|
||||
|
||||
///
|
||||
F32 mRestitution;
|
||||
|
||||
///
|
||||
F32 mLinearDamping;
|
||||
|
||||
///
|
||||
F32 mAngularDamping;
|
||||
|
||||
///
|
||||
F32 mLinearSleepThreshold;
|
||||
|
||||
///
|
||||
F32 mAngularSleepThreshold;
|
||||
|
||||
// A scale applied to the normal linear and angular damping
|
||||
// when the object enters a water volume.
|
||||
F32 mWaterDampingScale;
|
||||
|
||||
// The density of this object used for water buoyancy effects.
|
||||
F32 mBuoyancyDensity;
|
||||
|
||||
CollisionComponent* mOwnerColComponent;
|
||||
|
||||
enum MaskBits {
|
||||
PositionMask = Parent::NextFreeMask << 0,
|
||||
FreezeMask = Parent::NextFreeMask << 1,
|
||||
StateMask = Parent::NextFreeMask << 2,
|
||||
VelocityMask = Parent::NextFreeMask << 3,
|
||||
NextFreeMask = Parent::NextFreeMask << 4
|
||||
};
|
||||
|
||||
public:
|
||||
RigidBodyComponent();
|
||||
virtual ~RigidBodyComponent();
|
||||
DECLARE_CONOBJECT(RigidBodyComponent);
|
||||
|
||||
virtual bool onAdd();
|
||||
virtual void onRemove();
|
||||
static void initPersistFields();
|
||||
|
||||
virtual void onComponentAdd();
|
||||
virtual void onComponentRemove();
|
||||
|
||||
virtual void componentAddedToOwner(Component *comp);
|
||||
virtual void componentRemovedFromOwner(Component *comp);
|
||||
|
||||
virtual void ownerTransformSet(MatrixF *mat);
|
||||
|
||||
inline F32 getMass() { return mMass; }
|
||||
Point3F getVelocity() const { return mState.linVelocity; }
|
||||
void applyImpulse(const Point3F &pos, const VectorF &vec);
|
||||
void applyRadialImpulse(const Point3F &origin, F32 radius, F32 magnitude);
|
||||
|
||||
void updateContainerForces();
|
||||
|
||||
virtual U32 packUpdate(NetConnection *con, U32 mask, BitStream *stream);
|
||||
virtual void unpackUpdate(NetConnection *con, BitStream *stream);
|
||||
|
||||
virtual void processTick();
|
||||
|
||||
void findContact();
|
||||
|
||||
/// Save the current transform as where we return to when a physics reset
|
||||
/// event occurs. This is automatically set in onAdd but some manipulators
|
||||
/// such as Prefab need to make use of this.
|
||||
void storeRestorePos();
|
||||
|
||||
void updatePhysics(PhysicsCollision *collision = NULL);
|
||||
|
||||
void _onPhysicsReset(PhysicsResetEvent reset);
|
||||
};
|
||||
|
||||
#endif // _RIGID_BODY_COMPONENT_H_
|
||||
524
Engine/source/T3D/components/Render/MeshComponent.cpp
Normal file
524
Engine/source/T3D/components/Render/MeshComponent.cpp
Normal file
|
|
@ -0,0 +1,524 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// 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 "console/consoleTypes.h"
|
||||
#include "T3D/Components/Render/MeshComponent.h"
|
||||
#include "core/util/safeDelete.h"
|
||||
#include "core/resourceManager.h"
|
||||
#include "core/stream/fileStream.h"
|
||||
#include "console/consoleTypes.h"
|
||||
#include "console/consoleObject.h"
|
||||
#include "core/stream/bitStream.h"
|
||||
#include "sim/netConnection.h"
|
||||
#include "gfx/gfxTransformSaver.h"
|
||||
#include "console/engineAPI.h"
|
||||
#include "lighting/lightQuery.h"
|
||||
#include "scene/sceneManager.h"
|
||||
#include "gfx/bitmap/ddsFile.h"
|
||||
#include "gfx/bitmap/ddsUtils.h"
|
||||
#include "gfx/gfxTextureManager.h"
|
||||
#include "materials/materialFeatureTypes.h"
|
||||
#include "renderInstance/renderImposterMgr.h"
|
||||
#include "util/imposterCapture.h"
|
||||
#include "gfx/sim/debugDraw.h"
|
||||
#include "gfx/gfxDrawUtil.h"
|
||||
#include "materials/materialManager.h"
|
||||
#include "materials/matInstance.h"
|
||||
#include "core/strings/findMatch.h"
|
||||
#include "T3D/components/Render/MeshComponent_ScriptBinding.h"
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// Constructor/Destructor
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
MeshComponent::MeshComponent() : Component()
|
||||
{
|
||||
mShapeName = StringTable->insert("");
|
||||
mShapeAsset = StringTable->insert("");
|
||||
|
||||
mChangingMaterials.clear();
|
||||
|
||||
mMaterials.clear();
|
||||
|
||||
mFriendlyName = "Mesh Component";
|
||||
mComponentType = "Render";
|
||||
|
||||
mDescription = getDescriptionText("Causes the object to render a non-animating 3d shape using the file provided.");
|
||||
|
||||
mNetworked = true;
|
||||
mNetFlags.set(Ghostable | ScopeAlways);
|
||||
}
|
||||
|
||||
MeshComponent::~MeshComponent(){}
|
||||
|
||||
IMPLEMENT_CO_NETOBJECT_V1(MeshComponent);
|
||||
|
||||
//==========================================================================================
|
||||
void MeshComponent::boneObject::addObject(SimObject* object)
|
||||
{
|
||||
SceneObject* sc = dynamic_cast<SceneObject*>(object);
|
||||
|
||||
if(sc && mOwner)
|
||||
{
|
||||
if(TSShape* shape = mOwner->getShape())
|
||||
{
|
||||
S32 nodeID = shape->findNode(mBoneName);
|
||||
|
||||
//we may have a offset on the shape's center
|
||||
//so make sure we accomodate for that when setting up the mount offsets
|
||||
MatrixF mat = mOwner->getNodeTransform(nodeID);
|
||||
|
||||
mOwner->getOwner()->mountObject(sc, nodeID, mat);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool MeshComponent::onAdd()
|
||||
{
|
||||
if(! Parent::onAdd())
|
||||
return false;
|
||||
|
||||
// Register for the resource change signal.
|
||||
ResourceManager::get().getChangedSignal().notify( this, &MeshComponent::_onResourceChanged );
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void MeshComponent::onComponentAdd()
|
||||
{
|
||||
Parent::onComponentAdd();
|
||||
|
||||
//get the default shape, if any
|
||||
updateShape();
|
||||
}
|
||||
|
||||
void MeshComponent::onRemove()
|
||||
{
|
||||
Parent::onRemove();
|
||||
|
||||
SAFE_DELETE(mShapeInstance);
|
||||
}
|
||||
|
||||
void MeshComponent::onComponentRemove()
|
||||
{
|
||||
if(mOwner)
|
||||
{
|
||||
Point3F pos = mOwner->getPosition(); //store our center pos
|
||||
mOwner->setObjectBox(Box3F(Point3F(-1,-1,-1), Point3F(1,1,1)));
|
||||
mOwner->setPosition(pos);
|
||||
}
|
||||
|
||||
Parent::onComponentRemove();
|
||||
}
|
||||
|
||||
void MeshComponent::initPersistFields()
|
||||
{
|
||||
Parent::initPersistFields();
|
||||
|
||||
//create a hook to our internal variables
|
||||
addGroup("Model");
|
||||
addProtectedField("MeshAsset", TypeAssetId, Offset(mShapeAsset, MeshComponent), &_setMesh, &defaultProtectedGetFn,
|
||||
"The asset Id used for the mesh.", AbstractClassRep::FieldFlags::FIELD_ComponentInspectors);
|
||||
endGroup("Model");
|
||||
}
|
||||
|
||||
bool MeshComponent::_setMesh(void *object, const char *index, const char *data)
|
||||
{
|
||||
MeshComponent *rbI = static_cast<MeshComponent*>(object);
|
||||
|
||||
// Sanity!
|
||||
AssertFatal(data != NULL, "Cannot use a NULL asset Id.");
|
||||
|
||||
return rbI->setMeshAsset(data);
|
||||
}
|
||||
|
||||
bool MeshComponent::_setShape( void *object, const char *index, const char *data )
|
||||
{
|
||||
MeshComponent *rbI = static_cast<MeshComponent*>(object);
|
||||
rbI->mShapeName = StringTable->insert(data);
|
||||
rbI->updateShape(); //make sure we force the update to resize the owner bounds
|
||||
rbI->setMaskBits(ShapeMask);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MeshComponent::setMeshAsset(const char* assetName)
|
||||
{
|
||||
// Fetch the asset Id.
|
||||
mMeshAssetId = StringTable->insert(assetName);
|
||||
mMeshAsset.setAssetId(mMeshAssetId);
|
||||
|
||||
if (mMeshAsset.isNull())
|
||||
{
|
||||
Con::errorf("[MeshComponent] Failed to load mesh asset.");
|
||||
return false;
|
||||
}
|
||||
|
||||
mShapeName = mMeshAssetId;
|
||||
mShapeAsset = mShapeName;
|
||||
updateShape(); //make sure we force the update to resize the owner bounds
|
||||
setMaskBits(ShapeMask);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void MeshComponent::_onResourceChanged( const Torque::Path &path )
|
||||
{
|
||||
if ( path != Torque::Path( mShapeName ) )
|
||||
return;
|
||||
|
||||
updateShape();
|
||||
setMaskBits(ShapeMask);
|
||||
}
|
||||
|
||||
void MeshComponent::inspectPostApply()
|
||||
{
|
||||
Parent::inspectPostApply();
|
||||
}
|
||||
|
||||
U32 MeshComponent::packUpdate(NetConnection *con, U32 mask, BitStream *stream)
|
||||
{
|
||||
U32 retMask = Parent::packUpdate(con, mask, stream);
|
||||
|
||||
if (!mOwner || con->getGhostIndex(mOwner) == -1)
|
||||
{
|
||||
stream->writeFlag(false);
|
||||
stream->writeFlag(false);
|
||||
|
||||
if (mask & ShapeMask)
|
||||
retMask |= ShapeMask;
|
||||
if (mask & MaterialMask)
|
||||
retMask |= MaterialMask;
|
||||
return retMask;
|
||||
}
|
||||
|
||||
if (stream->writeFlag(mask & ShapeMask))
|
||||
{
|
||||
stream->writeString(mShapeName);
|
||||
}
|
||||
|
||||
if (stream->writeFlag( mask & MaterialMask ))
|
||||
{
|
||||
stream->writeInt(mChangingMaterials.size(), 16);
|
||||
|
||||
for(U32 i=0; i < mChangingMaterials.size(); i++)
|
||||
{
|
||||
stream->writeInt(mChangingMaterials[i].slot, 16);
|
||||
con->packNetStringHandleU(stream, NetStringHandle(mChangingMaterials[i].matName));
|
||||
}
|
||||
|
||||
mChangingMaterials.clear();
|
||||
}
|
||||
|
||||
return retMask;
|
||||
}
|
||||
|
||||
void MeshComponent::unpackUpdate(NetConnection *con, BitStream *stream)
|
||||
{
|
||||
Parent::unpackUpdate(con, stream);
|
||||
|
||||
if(stream->readFlag())
|
||||
{
|
||||
mShapeName = stream->readSTString();
|
||||
setMeshAsset(mShapeName);
|
||||
updateShape();
|
||||
}
|
||||
|
||||
if(stream->readFlag())
|
||||
{
|
||||
mChangingMaterials.clear();
|
||||
U32 materialCount = stream->readInt(16);
|
||||
|
||||
for(U32 i=0; i < materialCount; i++)
|
||||
{
|
||||
matMap newMatMap;
|
||||
newMatMap.slot = stream->readInt(16);
|
||||
newMatMap.matName = String(con->unpackNetStringHandleU(stream).getString());
|
||||
|
||||
mChangingMaterials.push_back(newMatMap);
|
||||
}
|
||||
|
||||
updateMaterials();
|
||||
}
|
||||
}
|
||||
|
||||
void MeshComponent::prepRenderImage( SceneRenderState *state )
|
||||
{
|
||||
if (!mEnabled || !mOwner || !mShapeInstance)
|
||||
return;
|
||||
|
||||
Point3F cameraOffset;
|
||||
mOwner->getRenderTransform().getColumn(3, &cameraOffset);
|
||||
cameraOffset -= state->getDiffuseCameraPosition();
|
||||
F32 dist = cameraOffset.len();
|
||||
if (dist < 0.01f)
|
||||
dist = 0.01f;
|
||||
|
||||
Point3F objScale = getOwner()->getScale();
|
||||
F32 invScale = (1.0f / getMax(getMax(objScale.x, objScale.y), objScale.z));
|
||||
|
||||
mShapeInstance->setDetailFromDistance(state, dist * invScale);
|
||||
|
||||
if (mShapeInstance->getCurrentDetail() < 0)
|
||||
return;
|
||||
|
||||
GFXTransformSaver saver;
|
||||
|
||||
// Set up our TS render state.
|
||||
TSRenderState rdata;
|
||||
rdata.setSceneState(state);
|
||||
rdata.setFadeOverride(1.0f);
|
||||
rdata.setOriginSort(false);
|
||||
|
||||
// We might have some forward lit materials
|
||||
// so pass down a query to gather lights.
|
||||
LightQuery query;
|
||||
query.init(mOwner->getWorldSphere());
|
||||
rdata.setLightQuery(&query);
|
||||
|
||||
MatrixF mat = mOwner->getRenderTransform();
|
||||
Point3F renderPos = mat.getPosition();
|
||||
EulerF renderRot = mat.toEuler();
|
||||
mat.scale(objScale);
|
||||
GFX->setWorldMatrix(mat);
|
||||
|
||||
mShapeInstance->render(rdata);
|
||||
}
|
||||
|
||||
void MeshComponent::updateShape()
|
||||
{
|
||||
bool isServer = isServerObject();
|
||||
|
||||
if ((mShapeName && mShapeName[0] != '\0') || (mShapeAsset && mShapeAsset[0] != '\0'))
|
||||
{
|
||||
if (mMeshAsset == NULL)
|
||||
return;
|
||||
|
||||
mShape = mMeshAsset->getShape();
|
||||
|
||||
if (!mShape)
|
||||
return;
|
||||
|
||||
setupShape();
|
||||
|
||||
//Do this on both the server and client
|
||||
S32 materialCount = mShape->materialList->getMaterialNameList().size();
|
||||
|
||||
if(isServerObject())
|
||||
{
|
||||
//we need to update the editor
|
||||
for (U32 i = 0; i < mFields.size(); i++)
|
||||
{
|
||||
//find any with the materialslot title and clear them out
|
||||
if (FindMatch::isMatch("MaterialSlot*", mFields[i].mFieldName, false))
|
||||
{
|
||||
setDataField(mFields[i].mFieldName, NULL, "");
|
||||
mFields.erase(i);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
//next, get a listing of our materials in the shape, and build our field list for them
|
||||
char matFieldName[128];
|
||||
|
||||
if(materialCount > 0)
|
||||
mComponentGroup = StringTable->insert("Materials");
|
||||
|
||||
for(U32 i=0; i < materialCount; i++)
|
||||
{
|
||||
String materialname = mShape->materialList->getMaterialName(i);
|
||||
if(materialname == String("ShapeBounds"))
|
||||
continue;
|
||||
|
||||
dSprintf(matFieldName, 128, "MaterialSlot%d", i);
|
||||
|
||||
addComponentField(matFieldName, "A material used in the shape file", "TypeAssetId", materialname, "");
|
||||
}
|
||||
|
||||
if(materialCount > 0)
|
||||
mComponentGroup = "";
|
||||
}
|
||||
|
||||
if(mOwner != NULL)
|
||||
{
|
||||
Point3F min, max, pos;
|
||||
pos = mOwner->getPosition();
|
||||
|
||||
mOwner->getWorldToObj().mulP(pos);
|
||||
|
||||
min = mShape->bounds.minExtents;
|
||||
max = mShape->bounds.maxExtents;
|
||||
|
||||
mShapeBounds.set(min, max);
|
||||
|
||||
mOwner->setObjectBox(Box3F(min, max));
|
||||
|
||||
if( mOwner->getSceneManager() != NULL )
|
||||
mOwner->getSceneManager()->notifyObjectDirty( mOwner );
|
||||
}
|
||||
|
||||
//finally, notify that our shape was changed
|
||||
onShapeInstanceChanged.trigger(this);
|
||||
}
|
||||
}
|
||||
|
||||
void MeshComponent::setupShape()
|
||||
{
|
||||
mShapeInstance = new TSShapeInstance(mShape, true);
|
||||
}
|
||||
|
||||
void MeshComponent::updateMaterials()
|
||||
{
|
||||
if (mChangingMaterials.empty() || !mShape)
|
||||
return;
|
||||
|
||||
TSMaterialList* pMatList = mShapeInstance->getMaterialList();
|
||||
pMatList->setTextureLookupPath(getShapeResource().getPath().getPath());
|
||||
|
||||
const Vector<String> &materialNames = pMatList->getMaterialNameList();
|
||||
for ( S32 i = 0; i < materialNames.size(); i++ )
|
||||
{
|
||||
const String &pName = materialNames[i];
|
||||
|
||||
for(U32 m=0; m < mChangingMaterials.size(); m++)
|
||||
{
|
||||
if(mChangingMaterials[m].slot == i)
|
||||
{
|
||||
pMatList->renameMaterial( i, mChangingMaterials[m].matName );
|
||||
}
|
||||
}
|
||||
|
||||
mChangingMaterials.clear();
|
||||
}
|
||||
|
||||
// Initialize the material instances
|
||||
mShapeInstance->initMaterialList();
|
||||
}
|
||||
|
||||
MatrixF MeshComponent::getNodeTransform(S32 nodeIdx)
|
||||
{
|
||||
if (mShape)
|
||||
{
|
||||
S32 nodeCount = getShape()->nodes.size();
|
||||
|
||||
if(nodeIdx >= 0 && nodeIdx < nodeCount)
|
||||
{
|
||||
//animate();
|
||||
MatrixF mountTransform = mShapeInstance->mNodeTransforms[nodeIdx];
|
||||
mountTransform.mul(mOwner->getRenderTransform());
|
||||
|
||||
return mountTransform;
|
||||
}
|
||||
}
|
||||
|
||||
return MatrixF::Identity;
|
||||
}
|
||||
|
||||
S32 MeshComponent::getNodeByName(String nodeName)
|
||||
{
|
||||
if (mShape)
|
||||
{
|
||||
S32 nodeIdx = getShape()->findNode(nodeName);
|
||||
|
||||
return nodeIdx;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
bool MeshComponent::castRayRendered(const Point3F &start, const Point3F &end, RayInfo *info)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
void MeshComponent::mountObjectToNode(SceneObject* objB, String node, MatrixF txfm)
|
||||
{
|
||||
const char* test;
|
||||
test = node.c_str();
|
||||
if(dIsdigit(test[0]))
|
||||
{
|
||||
getOwner()->mountObject(objB, dAtoi(node), txfm);
|
||||
}
|
||||
else
|
||||
{
|
||||
if(TSShape* shape = getShape())
|
||||
{
|
||||
S32 idx = shape->findNode(node);
|
||||
getOwner()->mountObject(objB, idx, txfm);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MeshComponent::onDynamicModified(const char* slotName, const char* newValue)
|
||||
{
|
||||
if(FindMatch::isMatch( "materialslot*", slotName, false ))
|
||||
{
|
||||
if(!getShape())
|
||||
return;
|
||||
|
||||
S32 slot = -1;
|
||||
String outStr( String::GetTrailingNumber( slotName, slot ) );
|
||||
|
||||
if(slot == -1)
|
||||
return;
|
||||
|
||||
bool found = false;
|
||||
for(U32 i=0; i < mChangingMaterials.size(); i++)
|
||||
{
|
||||
if(mChangingMaterials[i].slot == slot)
|
||||
{
|
||||
mChangingMaterials[i].matName = String(newValue);
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
|
||||
if(!found)
|
||||
{
|
||||
matMap newMatMap;
|
||||
newMatMap.slot = slot;
|
||||
newMatMap.matName = String(newValue);
|
||||
|
||||
mChangingMaterials.push_back(newMatMap);
|
||||
}
|
||||
|
||||
setMaskBits(MaterialMask);
|
||||
}
|
||||
|
||||
Parent::onDynamicModified(slotName, newValue);
|
||||
}
|
||||
|
||||
void MeshComponent::changeMaterial(U32 slot, const char* newMat)
|
||||
{
|
||||
|
||||
char fieldName[512];
|
||||
|
||||
//update our respective field
|
||||
dSprintf(fieldName, 512, "materialSlot%d", slot);
|
||||
setDataField(fieldName, NULL, newMat);
|
||||
}
|
||||
|
||||
void MeshComponent::onInspect()
|
||||
{
|
||||
}
|
||||
|
||||
void MeshComponent::onEndInspect()
|
||||
{
|
||||
}
|
||||
183
Engine/source/T3D/components/Render/MeshComponent.h
Normal file
183
Engine/source/T3D/components/Render/MeshComponent.h
Normal file
|
|
@ -0,0 +1,183 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// 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 STATIC_MESH_COMPONENT_H
|
||||
#define STATIC_MESH_COMPONENT_H
|
||||
|
||||
#ifndef COMPONENT_H
|
||||
#include "T3D/Components/Component.h"
|
||||
#endif
|
||||
#ifndef __RESOURCE_H__
|
||||
#include "core/resource.h"
|
||||
#endif
|
||||
#ifndef _TSSHAPE_H_
|
||||
#include "ts/tsShape.h"
|
||||
#endif
|
||||
#ifndef _SCENERENDERSTATE_H_
|
||||
#include "scene/sceneRenderState.h"
|
||||
#endif
|
||||
#ifndef _MBOX_H_
|
||||
#include "math/mBox.h"
|
||||
#endif
|
||||
#ifndef ENTITY_H
|
||||
#include "T3D/Entity.h"
|
||||
#endif
|
||||
#ifndef _NETSTRINGTABLE_H_
|
||||
#include "sim/netStringTable.h"
|
||||
#endif
|
||||
#ifndef CORE_INTERFACES_H
|
||||
#include "T3D/Components/coreInterfaces.h"
|
||||
#endif
|
||||
#ifndef RENDER_COMPONENT_INTERFACE_H
|
||||
#include "T3D/Components/Render/renderComponentInterface.h"
|
||||
#endif
|
||||
#ifndef _ASSET_PTR_H_
|
||||
#include "assets/assetPtr.h"
|
||||
#endif
|
||||
#ifndef _SHAPE_ASSET_H_
|
||||
#include "T3D/assets/ShapeAsset.h"
|
||||
#endif
|
||||
#ifndef _GFXVERTEXFORMAT_H_
|
||||
#include "gfx/gfxVertexFormat.h"
|
||||
#endif
|
||||
|
||||
class TSShapeInstance;
|
||||
class SceneRenderState;
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
///
|
||||
///
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
class MeshComponent : public Component,
|
||||
public RenderComponentInterface,
|
||||
public CastRayRenderedInterface,
|
||||
public EditorInspectInterface
|
||||
{
|
||||
typedef Component Parent;
|
||||
|
||||
protected:
|
||||
enum
|
||||
{
|
||||
ShapeMask = Parent::NextFreeMask,
|
||||
MaterialMask = Parent::NextFreeMask << 1,
|
||||
NextFreeMask = Parent::NextFreeMask << 2,
|
||||
};
|
||||
|
||||
StringTableEntry mShapeName;
|
||||
StringTableEntry mShapeAsset;
|
||||
TSShape* mShape;
|
||||
Box3F mShapeBounds;
|
||||
Point3F mCenterOffset;
|
||||
|
||||
struct matMap
|
||||
{
|
||||
String matName;
|
||||
U32 slot;
|
||||
};
|
||||
|
||||
Vector<matMap> mChangingMaterials;
|
||||
Vector<matMap> mMaterials;
|
||||
|
||||
class boneObject : public SimGroup
|
||||
{
|
||||
MeshComponent *mOwner;
|
||||
public:
|
||||
boneObject(MeshComponent *owner){ mOwner = owner; }
|
||||
|
||||
StringTableEntry mBoneName;
|
||||
S32 mItemID;
|
||||
|
||||
virtual void addObject(SimObject *obj);
|
||||
};
|
||||
|
||||
Vector<boneObject*> mNodesList;
|
||||
|
||||
public:
|
||||
StringTableEntry mMeshAssetId;
|
||||
AssetPtr<ShapeAsset> mMeshAsset;
|
||||
|
||||
TSShapeInstance* mShapeInstance;
|
||||
|
||||
public:
|
||||
MeshComponent();
|
||||
virtual ~MeshComponent();
|
||||
DECLARE_CONOBJECT(MeshComponent);
|
||||
|
||||
virtual bool onAdd();
|
||||
virtual void onRemove();
|
||||
static void initPersistFields();
|
||||
|
||||
virtual void inspectPostApply();
|
||||
|
||||
virtual void prepRenderImage(SceneRenderState *state);
|
||||
|
||||
virtual U32 packUpdate(NetConnection *con, U32 mask, BitStream *stream);
|
||||
virtual void unpackUpdate(NetConnection *con, BitStream *stream);
|
||||
|
||||
Box3F getShapeBounds() { return mShapeBounds; }
|
||||
|
||||
virtual MatrixF getNodeTransform(S32 nodeIdx);
|
||||
S32 getNodeByName(String nodeName);
|
||||
|
||||
void setupShape();
|
||||
void updateShape();
|
||||
void updateMaterials();
|
||||
|
||||
virtual void onComponentRemove();
|
||||
virtual void onComponentAdd();
|
||||
|
||||
static bool _setMesh(void *object, const char *index, const char *data);
|
||||
static bool _setShape(void *object, const char *index, const char *data);
|
||||
const char* _getShape(void *object, const char *data);
|
||||
|
||||
bool setMeshAsset(const char* assetName);
|
||||
|
||||
virtual TSShape* getShape() { if (mMeshAsset) return mMeshAsset->getShape(); else return NULL; }
|
||||
virtual TSShapeInstance* getShapeInstance() { return mShapeInstance; }
|
||||
|
||||
Resource<TSShape> getShapeResource() { if (mMeshAsset) return mMeshAsset->getShapeResource(); else return NULL; }
|
||||
|
||||
void _onResourceChanged(const Torque::Path &path);
|
||||
|
||||
virtual bool castRayRendered(const Point3F &start, const Point3F &end, RayInfo *info);
|
||||
|
||||
void mountObjectToNode(SceneObject* objB, String node, MatrixF txfm);
|
||||
|
||||
virtual void onDynamicModified(const char* slotName, const char* newValue);
|
||||
|
||||
void changeMaterial(U32 slot, const char* newMat);
|
||||
|
||||
virtual void onInspect();
|
||||
virtual void onEndInspect();
|
||||
|
||||
virtual Vector<MatrixF> getNodeTransforms()
|
||||
{
|
||||
Vector<MatrixF> bob;
|
||||
return bob;
|
||||
}
|
||||
|
||||
virtual void setNodeTransforms(Vector<MatrixF> transforms)
|
||||
{
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,155 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// 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 "console/engineAPI.h"
|
||||
#include "T3D/components/Render/MeshComponent.h"
|
||||
#include "scene/sceneObject.h"
|
||||
#include "math/mTransform.h"
|
||||
|
||||
DefineEngineMethod(MeshComponent, getShapeBounds, Box3F, (), ,
|
||||
"@brief Get the cobject we're in contact with.\n\n"
|
||||
|
||||
"The controlling client is the one that will send moves to us to act on.\n"
|
||||
|
||||
"@return the ID of the controlling GameConnection, or 0 if this object is not "
|
||||
"controlled by any client.\n"
|
||||
|
||||
"@see GameConnection\n")
|
||||
{
|
||||
return object->getShapeBounds();
|
||||
}
|
||||
|
||||
DefineEngineMethod(MeshComponent, mountObject, bool,
|
||||
(SceneObject* objB, String node, 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->mountObjectToNode(objB, node, /*MatrixF::Identity*/txfm.getMatrix());
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
DefineEngineMethod(MeshComponent, getNodeTransform, TransformF,
|
||||
(S32 node), (-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)")
|
||||
{
|
||||
if (node != -1)
|
||||
{
|
||||
//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->mountObjectToNode( objB, node, /*MatrixF::Identity*/txfm.getMatrix() );
|
||||
MatrixF mat = object->getNodeTransform(node);
|
||||
return mat;
|
||||
}
|
||||
|
||||
return TransformF::Identity;
|
||||
}
|
||||
|
||||
DefineEngineMethod(MeshComponent, getNodeEulerRot, EulerF,
|
||||
(S32 node, bool radToDeg), (-1, true),
|
||||
"@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 (node != -1)
|
||||
{
|
||||
//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->mountObjectToNode( objB, node, /*MatrixF::Identity*/txfm.getMatrix() );
|
||||
MatrixF mat = object->getNodeTransform(node);
|
||||
|
||||
EulerF eul = mat.toEuler();
|
||||
if (radToDeg)
|
||||
eul = EulerF(mRadToDeg(eul.x), mRadToDeg(eul.y), mRadToDeg(eul.z));
|
||||
|
||||
return eul;
|
||||
}
|
||||
|
||||
return EulerF(0, 0, 0);
|
||||
}
|
||||
|
||||
DefineEngineMethod(MeshComponent, getNodePosition, Point3F,
|
||||
(S32 node), (-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)")
|
||||
{
|
||||
if (node != -1)
|
||||
{
|
||||
//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->mountObjectToNode( objB, node, /*MatrixF::Identity*/txfm.getMatrix() );
|
||||
MatrixF mat = object->getNodeTransform(node);
|
||||
|
||||
return mat.getPosition();
|
||||
}
|
||||
|
||||
return Point3F(0, 0, 0);
|
||||
}
|
||||
|
||||
DefineEngineMethod(MeshComponent, getNodeByName, S32,
|
||||
(String nodeName), ,
|
||||
"@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 (!nodeName.isEmpty())
|
||||
{
|
||||
//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->mountObjectToNode( objB, node, /*MatrixF::Identity*/txfm.getMatrix() );
|
||||
S32 node = object->getNodeByName(nodeName);
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
DefineEngineMethod(MeshComponent, changeMaterial, void, (U32 slot, const char* newMat), (0, ""),
|
||||
"@brief Change one of the materials on the shape.\n\n")
|
||||
{
|
||||
object->changeMaterial(slot, newMat);
|
||||
}
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// 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 RENDER_COMPONENT_INTERFACE_H
|
||||
#define RENDER_COMPONENT_INTERFACE_H
|
||||
|
||||
#ifndef _TSSHAPE_H_
|
||||
#include "ts/TSShape.h"
|
||||
#endif
|
||||
#ifndef _TSSHAPEINSTANCE_H_
|
||||
#include "ts/TSShapeInstance.h"
|
||||
#endif
|
||||
#ifndef CORE_INTERFACES_H
|
||||
#include "T3D/Components/coreInterfaces.h"
|
||||
#endif
|
||||
|
||||
class RenderComponentInterface : public Interface < RenderComponentInterface >
|
||||
{
|
||||
public:
|
||||
virtual void prepRenderImage(SceneRenderState *state) = 0;
|
||||
|
||||
virtual TSShape* getShape() = 0;
|
||||
|
||||
Signal< void(RenderComponentInterface*) > RenderComponentInterface::onShapeChanged;
|
||||
|
||||
virtual TSShapeInstance* getShapeInstance() = 0;
|
||||
|
||||
virtual MatrixF getNodeTransform(S32 nodeIdx) = 0;
|
||||
|
||||
virtual Vector<MatrixF> getNodeTransforms() = 0;
|
||||
|
||||
virtual void setNodeTransforms(Vector<MatrixF> transforms) = 0;
|
||||
|
||||
Signal< void(RenderComponentInterface*) > RenderComponentInterface::onShapeInstanceChanged;
|
||||
};
|
||||
|
||||
class CastRayRenderedInterface// : public Interface<CastRayRenderedInterface>
|
||||
{
|
||||
public:
|
||||
virtual bool castRayRendered(const Point3F &start, const Point3F &end, RayInfo* info)=0;
|
||||
};
|
||||
|
||||
#endif
|
||||
101
Engine/source/T3D/components/coreInterfaces.h
Normal file
101
Engine/source/T3D/components/coreInterfaces.h
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// 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 CORE_INTERFACES_H
|
||||
#define CORE_INTERFACES_H
|
||||
|
||||
#ifndef _SCENERENDERSTATE_H_
|
||||
#include "scene/sceneRenderState.h"
|
||||
#endif
|
||||
|
||||
template<typename T>
|
||||
class Interface
|
||||
{
|
||||
public:
|
||||
static Vector<T*> all;
|
||||
|
||||
Interface()
|
||||
{
|
||||
all.push_back((T*)this);
|
||||
}
|
||||
virtual ~Interface()
|
||||
{
|
||||
for (U32 i = 0; i < all.size(); i++)
|
||||
{
|
||||
if (all[i] == (T*)this)
|
||||
{
|
||||
all.erase(i);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
template<typename T> Vector<T*> Interface<T>::all(0);
|
||||
|
||||
//Basically a file for generic interfaces that many behaviors may make use of
|
||||
class SetTransformInterface// : public Interface<SetTransformInterface>
|
||||
{
|
||||
public:
|
||||
virtual void setTransform( MatrixF transform );
|
||||
virtual void setTransform( Point3F pos, EulerF rot );
|
||||
};
|
||||
|
||||
class UpdateInterface : public Interface<UpdateInterface>
|
||||
{
|
||||
public:
|
||||
virtual void processTick(){}
|
||||
virtual void interpolateTick(F32 dt){}
|
||||
virtual void advanceTime(F32 dt){}
|
||||
};
|
||||
|
||||
class BehaviorFieldInterface// : public Interface<BehaviorFieldInterface>
|
||||
{
|
||||
public:
|
||||
virtual void onFieldChange(const char* fieldName, const char* newValue){};
|
||||
};
|
||||
|
||||
class CameraInterface// : public Interface<CameraInterface>
|
||||
{
|
||||
public:
|
||||
virtual bool getCameraTransform(F32* pos,MatrixF* mat)=0;
|
||||
virtual void onCameraScopeQuery(NetConnection *cr, CameraScopeQuery * query)=0;
|
||||
virtual Frustum getFrustum()=0;
|
||||
virtual F32 getCameraFov()=0;
|
||||
virtual void setCameraFov(F32 fov)=0;
|
||||
|
||||
virtual bool isValidCameraFov(F32 fov)=0;
|
||||
};
|
||||
|
||||
class CastRayInterface// : public Interface<CastRayInterface>
|
||||
{
|
||||
public:
|
||||
virtual bool castRay(const Point3F &start, const Point3F &end, RayInfo* info)=0;
|
||||
};
|
||||
|
||||
class EditorInspectInterface// : public Interface<EditorInspectInterface>
|
||||
{
|
||||
public:
|
||||
virtual void onInspect()=0;
|
||||
virtual void onEndInspect()=0;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
@ -473,6 +473,8 @@ public:
|
|||
enum FieldFlags
|
||||
{
|
||||
FIELD_HideInInspectors = BIT( 0 ), ///< Do not show the field in inspectors.
|
||||
FIELD_ComponentInspectors = BIT(1), ///< Custom fields used by components. They are likely to be non-standard size/configuration, so
|
||||
///< They are handled specially
|
||||
};
|
||||
|
||||
struct Field
|
||||
|
|
|
|||
Loading…
Reference in a new issue