diff --git a/Engine/source/T3D/assets/ComponentAsset.cpp b/Engine/source/T3D/assets/ComponentAsset.cpp new file mode 100644 index 000000000..2f4d524ac --- /dev/null +++ b/Engine/source/T3D/assets/ComponentAsset.cpp @@ -0,0 +1,138 @@ +//----------------------------------------------------------------------------- +// Copyright (c) 2013 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_ASSET_H +#include "ComponentAsset.h" +#endif + +#ifndef _ASSET_MANAGER_H_ +#include "assets/assetManager.h" +#endif + +#ifndef _CONSOLETYPES_H_ +#include "console/consoleTypes.h" +#endif + +#ifndef _TAML_ +#include "persistence/taml/taml.h" +#endif + +#ifndef _ASSET_PTR_H_ +#include "assets/assetPtr.h" +#endif + +// Debug Profiling. +#include "platform/profiler.h" + +//----------------------------------------------------------------------------- + +IMPLEMENT_CONOBJECT(ComponentAsset); + +ConsoleType(ComponentAssetPtr, TypeComponentAssetPtr, ComponentAsset, ASSET_ID_FIELD_PREFIX) + +//----------------------------------------------------------------------------- + +ConsoleGetType(TypeComponentAssetPtr) +{ + // Fetch asset Id. + return (*((AssetPtr*)dptr)).getAssetId(); +} + +//----------------------------------------------------------------------------- + +ConsoleSetType(TypeComponentAssetPtr) +{ + // Was a single argument specified? + if (argc == 1) + { + // Yes, so fetch field value. + const char* pFieldValue = argv[0]; + + // Fetch asset pointer. + AssetPtr* pAssetPtr = dynamic_cast*>((AssetPtrBase*)(dptr)); + + // Is the asset pointer the correct type? + if (pAssetPtr == NULL) + { + // No, so fail. + //Con::warnf("(TypeTextureAssetPtr) - Failed to set asset Id '%d'.", pFieldValue); + return; + } + + // Set asset. + pAssetPtr->setAssetId(pFieldValue); + + return; + } + + // Warn. + Con::warnf("(TypeTextureAssetPtr) - Cannot set multiple args to a single asset."); +} + +//----------------------------------------------------------------------------- + +ComponentAsset::ComponentAsset() : + mAcquireReferenceCount(0), + mpOwningAssetManager(NULL), + mAssetInitialized(false) +{ + // Generate an asset definition. + mpAssetDefinition = new AssetDefinition(); + + mComponentName = StringTable->lookup(""); + mComponentClass = StringTable->lookup(""); + mFriendlyName = StringTable->lookup(""); + mComponentType = StringTable->lookup(""); + mDescription = StringTable->lookup(""); +} + +//----------------------------------------------------------------------------- + +ComponentAsset::~ComponentAsset() +{ + // If the asset manager does not own the asset then we own the + // asset definition so delete it. + if (!getOwned()) + delete mpAssetDefinition; +} + +//----------------------------------------------------------------------------- + +void ComponentAsset::initPersistFields() +{ + // Call parent. + Parent::initPersistFields(); + + addField("componentName", TypeString, Offset(mComponentName, ComponentAsset), "Unique Name of the component. Defines the namespace of the scripts for the component."); + addField("componentClass", TypeString, Offset(mComponentClass, ComponentAsset), "Class of object this component uses."); + addField("friendlyName", TypeString, Offset(mFriendlyName, ComponentAsset), "The human-readble name for the component."); + addField("componentType", TypeString, Offset(mComponentType, ComponentAsset), "The category of the component for organizing in the editor."); + addField("description", TypeString, Offset(mDescription, ComponentAsset), "Simple description of the component."); +} + +//------------------------------------------------------------------------------ + +void ComponentAsset::copyTo(SimObject* object) +{ + // Call to parent. + Parent::copyTo(object); +} \ No newline at end of file diff --git a/Engine/source/T3D/assets/ComponentAsset.h b/Engine/source/T3D/assets/ComponentAsset.h new file mode 100644 index 000000000..1db53b8c8 --- /dev/null +++ b/Engine/source/T3D/assets/ComponentAsset.h @@ -0,0 +1,83 @@ +#pragma once +//----------------------------------------------------------------------------- +// Copyright (c) 2013 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_ASSET_H +#define COMPONENT_ASSET_H + +#ifndef _ASSET_BASE_H_ +#include "assets/assetBase.h" +#endif + +#ifndef _ASSET_DEFINITION_H_ +#include "assets/assetDefinition.h" +#endif + +#ifndef _STRINGUNIT_H_ +#include "string/stringUnit.h" +#endif + +#ifndef _ASSET_FIELD_TYPES_H_ +#include "assets/assetFieldTypes.h" +#endif + +//----------------------------------------------------------------------------- +class ComponentAsset : public AssetBase +{ + typedef AssetBase Parent; + + AssetManager* mpOwningAssetManager; + bool mAssetInitialized; + AssetDefinition* mpAssetDefinition; + U32 mAcquireReferenceCount; + + StringTableEntry mComponentName; + StringTableEntry mComponentClass; + StringTableEntry mFriendlyName; + StringTableEntry mComponentType; + StringTableEntry mDescription; + +public: + ComponentAsset(); + virtual ~ComponentAsset(); + + /// Engine. + static void initPersistFields(); + virtual void copyTo(SimObject* object); + + /// Declare Console Object. + DECLARE_CONOBJECT(ComponentAsset); + + StringTableEntry getComponentName() { return mComponentName; } + StringTableEntry getComponentClass() { return mComponentClass; } + StringTableEntry getFriendlyName() { return mFriendlyName; } + StringTableEntry getFriendlyType() { return mComponentType; } + StringTableEntry getDescription() { return mDescription; } + +protected: + virtual void initializeAsset(void) {} + virtual void onAssetRefresh(void) {} +}; + +DefineConsoleType(TypeComponentAssetPtr, ComponentAsset) + +#endif // _ASSET_BASE_H_ + diff --git a/Engine/source/T3D/assets/GameObjectAsset.cpp b/Engine/source/T3D/assets/GameObjectAsset.cpp new file mode 100644 index 000000000..07cbb2819 --- /dev/null +++ b/Engine/source/T3D/assets/GameObjectAsset.cpp @@ -0,0 +1,134 @@ +//----------------------------------------------------------------------------- +// Copyright (c) 2013 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 GAME_OBJECT_ASSET_H +#include "GameObjectAsset.h" +#endif + +#ifndef _ASSET_MANAGER_H_ +#include "assets/assetManager.h" +#endif + +#ifndef _CONSOLETYPES_H_ +#include "console/consoleTypes.h" +#endif + +#ifndef _TAML_ +#include "persistence/taml/taml.h" +#endif + +#ifndef _ASSET_PTR_H_ +#include "assets/assetPtr.h" +#endif + +// Debug Profiling. +#include "platform/profiler.h" + +//----------------------------------------------------------------------------- + +IMPLEMENT_CONOBJECT(GameObjectAsset); + +ConsoleType(GameObjectAssetPtr, TypeGameObjectAssetPtr, GameObjectAsset, ASSET_ID_FIELD_PREFIX) + +//----------------------------------------------------------------------------- + +ConsoleGetType(TypeGameObjectAssetPtr) +{ + // Fetch asset Id. + return (*((AssetPtr*)dptr)).getAssetId(); +} + +//----------------------------------------------------------------------------- + +ConsoleSetType(TypeGameObjectAssetPtr) +{ + // Was a single argument specified? + if (argc == 1) + { + // Yes, so fetch field value. + const char* pFieldValue = argv[0]; + + // Fetch asset pointer. + AssetPtr* pAssetPtr = dynamic_cast*>((AssetPtrBase*)(dptr)); + + // Is the asset pointer the correct type? + if (pAssetPtr == NULL) + { + // No, so fail. + //Con::warnf("(TypeTextureAssetPtr) - Failed to set asset Id '%d'.", pFieldValue); + return; + } + + // Set asset. + pAssetPtr->setAssetId(pFieldValue); + + return; + } + + // Warn. + Con::warnf("(TypeTextureAssetPtr) - Cannot set multiple args to a single asset."); +} + +//----------------------------------------------------------------------------- + +GameObjectAsset::GameObjectAsset() : + mAcquireReferenceCount(0), + mpOwningAssetManager(NULL), + mAssetInitialized(false) +{ + // Generate an asset definition. + mpAssetDefinition = new AssetDefinition(); + + mGameObjectName = StringTable->lookup(""); + mScriptFilePath = StringTable->lookup(""); + mTAMLFilePath = StringTable->lookup(""); +} + +//----------------------------------------------------------------------------- + +GameObjectAsset::~GameObjectAsset() +{ + // If the asset manager does not own the asset then we own the + // asset definition so delete it. + if (!getOwned()) + delete mpAssetDefinition; +} + +//----------------------------------------------------------------------------- + +void GameObjectAsset::initPersistFields() +{ + // Call parent. + Parent::initPersistFields(); + + addField("gameObjectName", TypeString, Offset(mGameObjectName, GameObjectAsset), "Name of the game object. Defines the created object's class."); + addField("scriptFilePath", TypeString, Offset(mScriptFilePath, GameObjectAsset), "Path to the script file for the GameObject's script code."); + addField("TAMLFilePath", TypeString, Offset(mTAMLFilePath, GameObjectAsset), "Path to the taml file for the GameObject's heirarchy."); +} + +//------------------------------------------------------------------------------ + +void GameObjectAsset::copyTo(SimObject* object) +{ + // Call to parent. + Parent::copyTo(object); +} \ No newline at end of file diff --git a/Engine/source/T3D/assets/GameObjectAsset.h b/Engine/source/T3D/assets/GameObjectAsset.h new file mode 100644 index 000000000..82230cd5c --- /dev/null +++ b/Engine/source/T3D/assets/GameObjectAsset.h @@ -0,0 +1,75 @@ +#pragma once +//----------------------------------------------------------------------------- +// Copyright (c) 2013 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 GAME_OBJECT_ASSET_H +#define GAME_OBJECT_ASSET_H + +#ifndef _ASSET_BASE_H_ +#include "assets/assetBase.h" +#endif + +#ifndef _ASSET_DEFINITION_H_ +#include "assets/assetDefinition.h" +#endif + +#ifndef _STRINGUNIT_H_ +#include "string/stringUnit.h" +#endif + +#ifndef _ASSET_FIELD_TYPES_H_ +#include "assets/assetFieldTypes.h" +#endif + +//----------------------------------------------------------------------------- +class GameObjectAsset : public AssetBase +{ + typedef AssetBase Parent; + + AssetManager* mpOwningAssetManager; + bool mAssetInitialized; + AssetDefinition* mpAssetDefinition; + U32 mAcquireReferenceCount; + + StringTableEntry mGameObjectName; + StringTableEntry mScriptFilePath; + StringTableEntry mTAMLFilePath; + +public: + GameObjectAsset(); + virtual ~GameObjectAsset(); + + /// Engine. + static void initPersistFields(); + virtual void copyTo(SimObject* object); + + /// Declare Console Object. + DECLARE_CONOBJECT(GameObjectAsset); + +protected: + virtual void initializeAsset(void) {} + virtual void onAssetRefresh(void) {} +}; + +DefineConsoleType(TypeGameObjectAssetPtr, GameObjectAsset) + +#endif // _ASSET_BASE_H_ + diff --git a/Engine/source/T3D/assets/ShapeAsset.cpp b/Engine/source/T3D/assets/ShapeAsset.cpp index 9b2aec3de..ea4882add 100644 --- a/Engine/source/T3D/assets/ShapeAsset.cpp +++ b/Engine/source/T3D/assets/ShapeAsset.cpp @@ -100,8 +100,6 @@ mAcquireReferenceCount(0), mpOwningAssetManager(NULL), mAssetInitialized(false) { - // Generate an asset definition. - mpAssetDefinition = new AssetDefinition(); } //----------------------------------------------------------------------------- @@ -154,4 +152,8 @@ void ShapeAsset::copyTo(SimObject* object) { // Call to parent. Parent::copyTo(object); +} + +void ShapeAsset::onAssetRefresh(void) +{ } \ No newline at end of file diff --git a/Engine/source/T3D/assets/ShapeAsset.h b/Engine/source/T3D/assets/ShapeAsset.h index 7c87cf8de..d727d0a1c 100644 --- a/Engine/source/T3D/assets/ShapeAsset.h +++ b/Engine/source/T3D/assets/ShapeAsset.h @@ -19,8 +19,8 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS // IN THE SOFTWARE. //----------------------------------------------------------------------------- -#ifndef _SHAPE_ASSET_H_ -#define _SHAPE_ASSET_H_ +#ifndef SHAPE_ASSET_H +#define SHAPE_ASSET_H #ifndef _ASSET_BASE_H_ #include "assets/assetBase.h" @@ -76,11 +76,13 @@ public: TSShape* getShape() { return mShape; } + Resource getShapeResource() { return mShape; } + protected: - virtual void onAssetRefresh(void) {} + virtual void onAssetRefresh(void); }; DefineConsoleType(TypeShapeAssetPtr, ShapeAsset) -#endif // _ASSET_BASE_H_ +#endif diff --git a/Engine/source/T3D/components/animation/animationComponent.cpp b/Engine/source/T3D/components/animation/animationComponent.cpp new file mode 100644 index 000000000..e1a71511c --- /dev/null +++ b/Engine/source/T3D/components/animation/animationComponent.cpp @@ -0,0 +1,717 @@ +//----------------------------------------------------------------------------- +// 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(); + + mOwnerRenderInst = NULL; +} + +void AnimationComponent::onComponentAdd() +{ + //test if this is a shape component! + RenderComponentInterface *shapeInstanceInterface = mOwner->getComponent(); + 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(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(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(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 (!mOwnerRenderInst) + 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 (!mOwnerRenderInst) + 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 ""; +} \ No newline at end of file diff --git a/Engine/source/T3D/components/animation/animationComponent.h b/Engine/source/T3D/components/animation/animationComponent.h new file mode 100644 index 000000000..f1c01f40c --- /dev/null +++ b/Engine/source/T3D/components/animation/animationComponent.h @@ -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 \ No newline at end of file diff --git a/Engine/source/T3D/components/animation/animationComponent_ScriptBinding.h b/Engine/source/T3D/components/animation/animationComponent_ScriptBinding.h new file mode 100644 index 000000000..2d26c5da1 --- /dev/null +++ b/Engine/source/T3D/components/animation/animationComponent_ScriptBinding.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); +} \ No newline at end of file diff --git a/Engine/source/T3D/components/camera/cameraComponent.cpp b/Engine/source/T3D/components/camera/cameraComponent.cpp new file mode 100644 index 000000000..60c62fb3a --- /dev/null +++ b/Engine/source/T3D/components/camera/cameraComponent.cpp @@ -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(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(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(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(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(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(); + + 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(); + + 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; +} \ No newline at end of file diff --git a/Engine/source/T3D/components/camera/cameraComponent.h b/Engine/source/T3D/components/camera/cameraComponent.h new file mode 100644 index 000000000..9ae7aea99 --- /dev/null +++ b/Engine/source/T3D/components/camera/cameraComponent.h @@ -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 diff --git a/Engine/source/T3D/components/camera/cameraComponent_ScriptBinding.h b/Engine/source/T3D/components/camera/cameraComponent_ScriptBinding.h new file mode 100644 index 000000000..2c3f1dbef --- /dev/null +++ b/Engine/source/T3D/components/camera/cameraComponent_ScriptBinding.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(); +} \ No newline at end of file diff --git a/Engine/source/T3D/components/camera/cameraOrbiterComponent.cpp b/Engine/source/T3D/components/camera/cameraOrbiterComponent.cpp new file mode 100644 index 000000000..7e53924d3 --- /dev/null +++ b/Engine/source/T3D/components/camera/cameraOrbiterComponent.cpp @@ -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 "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(); + if (cam) + { + mCamera = cam; + } +} + +void CameraOrbiterComponent::onComponentRemove() +{ + Parent::onComponentRemove(); +} + +void CameraOrbiterComponent::componentAddedToOwner(Component *comp) +{ + if (comp->getId() == getId()) + return; + + //test if this is a shape component! + CameraComponent *camComponent = dynamic_cast(comp); + if (camComponent) + { + mCamera = camComponent; + } +} + +void CameraOrbiterComponent::componentRemovedFromOwner(Component *comp) +{ + if (comp->getId() == getId()) //????????? + return; + + //test if this is a shape component! + CameraComponent *camComponent = dynamic_cast(comp); + if (camComponent) + { + mCamera = NULL; + } +} + +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); + } +} \ No newline at end of file diff --git a/Engine/source/component/moreAdvancedComponent.cpp b/Engine/source/T3D/components/camera/cameraOrbiterComponent.h similarity index 51% rename from Engine/source/component/moreAdvancedComponent.cpp rename to Engine/source/T3D/components/camera/cameraOrbiterComponent.h index 7ddfac688..2a091e15e 100644 --- a/Engine/source/component/moreAdvancedComponent.cpp +++ b/Engine/source/T3D/components/camera/cameraOrbiterComponent.h @@ -20,41 +20,55 @@ // IN THE SOFTWARE. //----------------------------------------------------------------------------- -#include "component/moreAdvancedComponent.h" +#ifndef CAMERA_ORBITER_COMPONENT_H +#define CAMERA_ORBITER_COMPONENT_H -// unitTest_runTests("Component/MoreAdvancedComponent"); +#ifndef COMPONENT_H +#include "T3D/components/component.h" +#endif +#ifndef CAMERA_COMPONENT_H +#include "T3D/components/camera/cameraComponent.h" +#endif ////////////////////////////////////////////////////////////////////////// - -IMPLEMENT_CONOBJECT(MoreAdvancedComponent); - -ConsoleDocClass( MoreAdvancedComponent, - "@brief This is a slightly more advanced component which will be used to demonstrate " - "components which are dependent on other components.\n\n" - "Not intended for game development, for editors or internal use only.\n\n " - "@internal"); - -bool MoreAdvancedComponent::onComponentRegister( SimComponent *owner ) +/// +/// +////////////////////////////////////////////////////////////////////////// +class CameraOrbiterComponent : public Component { - if( !Parent::onComponentRegister( owner ) ) - return false; + typedef Component Parent; - // This will return the first interface of type SimpleComponent that is cached - // on the parent object. - mSCInterface = owner->getInterface(); + F32 mMinOrbitDist; + F32 mMaxOrbitDist; + F32 mCurOrbitDist; + Point3F mPosition; - // If we can't find this interface, our component can't function, so false - // will be returned, and this will signify, to the top-level component, that it - // should fail the onAdd call. - return ( mSCInterface != NULL ); -} + F32 mMaxPitchAngle; + F32 mMinPitchAngle; -bool MoreAdvancedComponent::testDependentInterface() -{ - // These two requirements must be met in order for the test to proceed, so - // lets check them. - if( mSCInterface == NULL || !mSCInterface->isValid() ) - return false; + RotationF mRotation; - return mSCInterface->isFortyTwo( 42 ); -} \ No newline at end of file + 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 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); + + virtual void processTick(); +}; + +#endif // EXAMPLEBEHAVIOR_H diff --git a/Engine/source/T3D/components/collision/collisionComponent.cpp b/Engine/source/T3D/components/collision/collisionComponent.cpp new file mode 100644 index 000000000..e680665dc --- /dev/null +++ b/Engine/source/T3D/components/collision/collisionComponent.cpp @@ -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(); + if (renderInterface) + { + renderInterface->onShapeInstanceChanged.notify(this, &CollisionComponent::targetShapeChanged); + mOwnerRenderInterface = renderInterface; + } + + //physicsInterface + PhysicsComponentInterface *physicsInterface = mOwner->getComponent(); + 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(comp); + if (renderInterface) + { + renderInterface->onShapeInstanceChanged.notify(this, &CollisionComponent::targetShapeChanged); + mOwnerRenderInterface = renderInterface; + prepCollision(); + } + + PhysicsComponentInterface *physicsInterface = dynamic_cast(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(comp); + if (renderInterface) + { + renderInterface->onShapeInstanceChanged.remove(this, &CollisionComponent::targetShapeChanged); + mOwnerRenderInterface = NULL; + prepCollision(); + } + + //physicsInterface + PhysicsComponentInterface *physicsInterface = dynamic_cast(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()*/)) + { + 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()*/)) + { + colShape = buildColShapes(); + //colShape = mOwnerShapeInstance->getShape()->buildColShape(mCollisionType == VisibleMesh, mOwner->getScale()); + } + /*else if (mCollisionType == VisibleMesh && !mOwner->getComponent()) + { + //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()*/) + { + 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; +} \ No newline at end of file diff --git a/Engine/source/T3D/components/collision/collisionComponent.h b/Engine/source/T3D/components/collision/collisionComponent.h new file mode 100644 index 000000000..2d1be3098 --- /dev/null +++ b/Engine/source/T3D/components/collision/collisionComponent.h @@ -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 mCollisionDetails; + Vector 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 diff --git a/Engine/source/T3D/components/collision/collisionComponent_ScriptBinding.h b/Engine/source/T3D/components/collision/collisionComponent_ScriptBinding.h new file mode 100644 index 000000000..3b8e83d11 --- /dev/null +++ b/Engine/source/T3D/components/collision/collisionComponent_ScriptBinding.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); +} \ No newline at end of file diff --git a/Engine/source/T3D/components/collision/collisionInterfaces.cpp b/Engine/source/T3D/components/collision/collisionInterfaces.cpp new file mode 100644 index 000000000..c45dbde48 --- /dev/null +++ b/Engine/source/T3D/components/collision/collisionInterfaces.cpp @@ -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(colCheck.object); + + Component *comp = dynamic_cast(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(colCheck.object); + if (ent) + { + CollisionInterface *colObjectInterface = ent->getComponent(); + if (colObjectInterface) + { + //convert us to our component + Component *thisComp = dynamic_cast(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(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(this); + if (thisComp) + { + mCollisionNotifyList[i]->onCollisionSignal.trigger(thisComp->getOwner()); + } + } + + mCollisionNotifyList.clear(); +} + +Chunker sCollisionTimeoutChunker; +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 = sCollisionTimeoutChunker.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; +} \ No newline at end of file diff --git a/Engine/source/T3D/components/collision/collisionInterfaces.h b/Engine/source/T3D/components/collision/collisionInterfaces.h new file mode 100644 index 000000000..6592d6015 --- /dev/null +++ b/Engine/source/T3D/components/collision/collisionInterfaces.h @@ -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 +{ +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* ) > onCollisionSignal; + Signal< void( SceneObject* ) > onContactSignal; + +protected: + CollisionTimeout* mTimeoutList; + static CollisionTimeout* sFreeTimeoutList; + + CollisionList mCollisionList; + Vector 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) > onCollisionChanged; +}; + +class BuildConvexInterface //: public Interface +{ +public: + virtual void buildConvex(const Box3F& box, Convex* convex)=0; +}; + +class BuildPolyListInterface// : public Interface +{ +public: + virtual bool buildPolyList(PolyListContext context, AbstractPolyList* polyList, const Box3F &box, const SphereF &sphere) = 0; +}; + +#endif \ No newline at end of file diff --git a/Engine/source/T3D/components/collision/collisionTrigger.cpp b/Engine/source/T3D/components/collision/collisionTrigger.cpp new file mode 100644 index 000000000..7d1f50706 --- /dev/null +++ b/Engine/source/T3D/components/collision/collisionTrigger.cpp @@ -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(object)->setMaskBits(EnterCmdMask); + return true; // to update the actual field +} + +bool CollisionTrigger::setLeaveCmd(void *object, const char *index, const char *data) +{ + static_cast(object)->setMaskBits(LeaveCmdMask); + return true; // to update the actual field +} + +bool CollisionTrigger::setTickCmd(void *object, const char *index, const char *data) +{ + static_cast(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(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(); + 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(); +} diff --git a/Engine/source/T3D/components/collision/collisionTrigger.h b/Engine/source/T3D/components/collision/collisionTrigger.h new file mode 100644 index 000000000..5673d6162 --- /dev/null +++ b/Engine/source/T3D/components/collision/collisionTrigger.h @@ -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 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& 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 + diff --git a/Engine/source/T3D/components/component.cpp b/Engine/source/T3D/components/component.cpp new file mode 100644 index 000000000..47784d4b2 --- /dev/null +++ b/Engine/source/T3D/components/component.cpp @@ -0,0 +1,640 @@ +//----------------------------------------------------------------------------- +// 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(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(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(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(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("int")) + fieldTypeMask = TypeS32; + else if (fieldType == StringTable->insert("float")) + fieldTypeMask = TypeF32; + else if (fieldType == StringTable->insert("vector")) + fieldTypeMask = TypePoint3F; + else if (fieldType == StringTable->insert("material")) + fieldTypeMask = TypeMaterialName; + else if (fieldType == StringTable->insert("image")) + fieldTypeMask = TypeImageFilename; + else if (fieldType == StringTable->insert("shape")) + fieldTypeMask = TypeShapeFilename; + else if (fieldType == StringTable->insert("bool")) + fieldTypeMask = TypeBool; + else if (fieldType == StringTable->insert("object")) + fieldTypeMask = TypeSimObjectPtr; + 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
Usage for default types
" + "-enum: a TAB separated list of possible values
" + "-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
Usage for default types
" + "-enum: a TAB separated list of possible values
" + "-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); +} diff --git a/Engine/source/T3D/components/component.h b/Engine/source/T3D/components/component.h new file mode 100644 index 000000000..0259bacd0 --- /dev/null +++ b/Engine/source/T3D/components/component.h @@ -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 mDependencies; + Vector 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 diff --git a/Engine/source/T3D/components/coreInterfaces.h b/Engine/source/T3D/components/coreInterfaces.h new file mode 100644 index 000000000..852628778 --- /dev/null +++ b/Engine/source/T3D/components/coreInterfaces.h @@ -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 +class Interface +{ +public: + static Vector 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 Vector Interface::all(0); + +//Basically a file for generic interfaces that many behaviors may make use of +class SetTransformInterface// : public Interface +{ +public: + virtual void setTransform( MatrixF transform ); + virtual void setTransform( Point3F pos, EulerF rot ); +}; + +class UpdateInterface : public Interface +{ +public: + virtual void processTick(){} + virtual void interpolateTick(F32 dt){} + virtual void advanceTime(F32 dt){} +}; + +class BehaviorFieldInterface// : public Interface +{ +public: + virtual void onFieldChange(const char* fieldName, const char* newValue){}; +}; + +class CameraInterface// : public Interface +{ +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 +{ +public: + virtual bool castRay(const Point3F &start, const Point3F &end, RayInfo* info)=0; +}; + +class EditorInspectInterface// : public Interface +{ +public: + virtual void onInspect()=0; + virtual void onEndInspect()=0; +}; + +#endif \ No newline at end of file diff --git a/Engine/source/T3D/components/game/stateMachine.cpp b/Engine/source/T3D/components/game/stateMachine.cpp new file mode 100644 index 000000000..335b69db3 --- /dev/null +++ b/Engine/source/T3D/components/game/stateMachine.cpp @@ -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; +} \ No newline at end of file diff --git a/Engine/source/T3D/components/game/stateMachine.h b/Engine/source/T3D/components/game/stateMachine.h new file mode 100644 index 000000000..9ccc540e8 --- /dev/null +++ b/Engine/source/T3D/components/game/stateMachine.h @@ -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 mTransitionRules; + }; + + struct State + { + Vector mTransitions; + + StringTableEntry stateName; + + StringTableEntry callbackName; + }; + + StringTableEntry mStateMachineFile; + +protected: + Vector mStates; + + Vector mFields; + + Vector mUniqueReferences; + + State mCurrentState; + + F32 mStateStartTime; + F32 mStateTime; + + StringTableEntry mStartingState; + + State *mCurCreateSuperState; + State *mCurCreateState; + + SimObjectPtr 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) > 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(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 \ No newline at end of file diff --git a/Engine/source/T3D/components/game/stateMachineComponent.cpp b/Engine/source/T3D/components/game/stateMachineComponent.cpp new file mode 100644 index 000000000..991d41ce1 --- /dev/null +++ b/Engine/source/T3D/components/game/stateMachineComponent.cpp @@ -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(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); +} \ No newline at end of file diff --git a/Engine/source/T3D/components/game/stateMachineComponent.h b/Engine/source/T3D/components/game/stateMachineComponent.h new file mode 100644 index 000000000..5d4051075 --- /dev/null +++ b/Engine/source/T3D/components/game/stateMachineComponent.h @@ -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 \ No newline at end of file diff --git a/Engine/source/T3D/components/game/triggerComponent.cpp b/Engine/source/T3D/components/game/triggerComponent.cpp new file mode 100644 index 000000000..290fab437 --- /dev/null +++ b/Engine/source/T3D/components/game/triggerComponent.cpp @@ -0,0 +1,359 @@ +//----------------------------------------------------------------------------- +// 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(); + + if(colInt) + { + colInt->onCollisionSignal.notify(this, &TriggerComponent::potentialEnterObject); + } +} + +void TriggerComponent::onComponentRemove() +{ + CollisionInterface *colInt = mOwner->getComponent(); + + if(colInt) + { + colInt->onCollisionSignal.remove(this, &TriggerComponent::potentialEnterObject); + } + + Parent::onComponentRemove(); +} + +void TriggerComponent::componentAddedToOwner(Component *comp) +{ + if (comp->getId() == getId()) + return; + + CollisionInterface *colInt = mOwner->getComponent(); + + if (colInt) + { + colInt->onCollisionSignal.notify(this, &TriggerComponent::potentialEnterObject); + } +} + +void TriggerComponent::componentRemovedFromOwner(Component *comp) +{ + if (comp->getId() == getId()) //????????? + return; + + CollisionInterface *colInt = mOwner->getComponent(); + + 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(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(); + 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(); + + //wait, how would we NOT have this? + if (myCI) + { + //anywho, build our list and then we'll check intersections + ClippedPolyList myList; + + MatrixF ownerTransform = mOwner->getTransform(); + myList.setTransform(&ownerTransform, 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(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); +} \ No newline at end of file diff --git a/Engine/source/T3D/components/game/triggerComponent.h b/Engine/source/T3D/components/game/triggerComponent.h new file mode 100644 index 000000000..bac45b62a --- /dev/null +++ b/Engine/source/T3D/components/game/triggerComponent.h @@ -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 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_ diff --git a/Engine/source/T3D/components/physics/physicsBehavior.cpp b/Engine/source/T3D/components/physics/physicsBehavior.cpp new file mode 100644 index 000000000..f281192c0 --- /dev/null +++ b/Engine/source/T3D/components/physics/physicsBehavior.cpp @@ -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(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; +} \ No newline at end of file diff --git a/Engine/source/T3D/components/physics/physicsBehavior.h b/Engine/source/T3D/components/physics/physicsBehavior.h new file mode 100644 index 000000000..bc09f3108 --- /dev/null +++ b/Engine/source/T3D/components/physics/physicsBehavior.h @@ -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_ diff --git a/Engine/source/T3D/components/physics/physicsComponentInterface.cpp b/Engine/source/T3D/components/physics/physicsComponentInterface.cpp new file mode 100644 index 000000000..e69de29bb diff --git a/Engine/source/component/moreAdvancedComponent.h b/Engine/source/T3D/components/physics/physicsComponentInterface.h similarity index 54% rename from Engine/source/component/moreAdvancedComponent.h rename to Engine/source/T3D/components/physics/physicsComponentInterface.h index d4891202d..57f00b8d4 100644 --- a/Engine/source/component/moreAdvancedComponent.h +++ b/Engine/source/T3D/components/physics/physicsComponentInterface.h @@ -20,36 +20,30 @@ // IN THE SOFTWARE. //----------------------------------------------------------------------------- -#ifndef _MOREADVANCEDCOMPONENT_H_ -#define _MOREADVANCEDCOMPONENT_H_ +#ifndef PHYSICS_COMPONENT_INTERFACE_H +#define PHYSICS_COMPONENT_INTERFACE_H -#ifndef _SIMPLECOMPONENT_H_ -#include "component/simpleComponent.h" +#ifndef CORE_INTERFACES_H +#include "T3D/components/coreInterfaces.h" #endif -/// This is a slightly more advanced component which will be used to demonstrate -/// components which are dependent on other components. -class MoreAdvancedComponent : public SimComponent +class PhysicsComponentInterface : public Interface { - typedef SimComponent Parent; - protected: - // This component is going to be dependent on a SimpleComponentInterface being - // queried off of it's parent object. This will store that interface that - // will get queried during onComponentRegister() - SimpleComponentInterface *mSCInterface; + VectorF mVelocity; + F32 mMass; + + F32 mGravityMod; public: - DECLARE_CONOBJECT(MoreAdvancedComponent); + void updateForces(); - // Firstly, take a look at the documentation for this function in simComponent.h. - // We will be overloading this method to query the component heirarchy for our - // dependent interface, as noted above. - virtual bool onComponentRegister( SimComponent *owner ); + VectorF getVelocity() { return mVelocity; } + void setVelocity(VectorF vel) { mVelocity = vel; } - // This function will try to execute a function through the interface that this - // component is dependent on. - virtual bool testDependentInterface(); + F32 getMass() { return mMass; } + + Signal< void(VectorF normal, Vector overlappedObjects) > onPhysicsCollision; }; #endif \ No newline at end of file diff --git a/Engine/source/T3D/components/physics/playerControllerComponent.cpp b/Engine/source/T3D/components/physics/playerControllerComponent.cpp new file mode 100644 index 000000000..60945f68e --- /dev/null +++ b/Engine/source/T3D/components/physics/playerControllerComponent.cpp @@ -0,0 +1,865 @@ +//----------------------------------------------------------------------------- +// 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; + mVelocity = VectorF::Zero; + mContactTimer = 0; + 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(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(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(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(); + 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 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; isetTransform(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 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(obj)) + { + pTrigger->potentialEnterObject(mOwner); + } + else if (CollisionTrigger* pTriggerEx = dynamic_cast(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(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(); +} \ No newline at end of file diff --git a/Engine/source/T3D/components/physics/playerControllerComponent.h b/Engine/source/T3D/components/physics/playerControllerComponent.h new file mode 100644 index 000000000..49d4c175c --- /dev/null +++ b/Engine/source/T3D/components/physics/playerControllerComponent.h @@ -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*) > onContactSignal; + + // + DECLARE_CALLBACK(void, updateMove, (PlayerControllerComponent* obj)); +}; + +#endif // _COMPONENT_H_ diff --git a/Engine/source/T3D/components/physics/rigidBodyComponent.cpp b/Engine/source/T3D/components/physics/rigidBodyComponent.cpp new file mode 100644 index 000000000..21e9ecdbe --- /dev/null +++ b/Engine/source/T3D/components/physics/rigidBodyComponent.cpp @@ -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(); + 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(); + if (colComp) + { + colComp->onCollisionChanged.remove(this, &RigidBodyComponent::updatePhysics); + } + + SAFE_DELETE(mPhysicsRep); +} + +void RigidBodyComponent::componentAddedToOwner(Component *comp) +{ + CollisionComponent *colComp = dynamic_cast(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(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 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); +} \ No newline at end of file diff --git a/Engine/source/T3D/components/physics/rigidBodyComponent.h b/Engine/source/T3D/components/physics/rigidBodyComponent.h new file mode 100644 index 000000000..4bbe1f927 --- /dev/null +++ b/Engine/source/T3D/components/physics/rigidBodyComponent.h @@ -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_ diff --git a/Engine/source/T3D/components/render/meshComponent.cpp b/Engine/source/T3D/components/render/meshComponent.cpp new file mode 100644 index 000000000..708a48ae1 --- /dev/null +++ b/Engine/source/T3D/components/render/meshComponent.cpp @@ -0,0 +1,530 @@ +//----------------------------------------------------------------------------- +// 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(""); + mShapeInstance = NULL; + + 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(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(); + + mMeshAsset.clear(); + + 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(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(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 = 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); + + NetStringHandle matNameStr = mChangingMaterials[i].matName.c_str(); + con->packNetStringHandleU(stream, matNameStr); + } + + 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 &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() +{ +} \ No newline at end of file diff --git a/Engine/source/T3D/components/render/meshComponent.h b/Engine/source/T3D/components/render/meshComponent.h new file mode 100644 index 000000000..6adc2633e --- /dev/null +++ b/Engine/source/T3D/components/render/meshComponent.h @@ -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 mChangingMaterials; + Vector mMaterials; + + class boneObject : public SimGroup + { + MeshComponent *mOwner; + public: + boneObject(MeshComponent *owner){ mOwner = owner; } + + StringTableEntry mBoneName; + S32 mItemID; + + virtual void addObject(SimObject *obj); + }; + + Vector mNodesList; + +public: + StringTableEntry mMeshAssetId; + AssetPtr 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 getShapeResource() { return mMeshAsset->getShapeResource(); } + + 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 getNodeTransforms() + { + Vector bob; + return bob; + } + + virtual void setNodeTransforms(Vector transforms) + { + return; + } +}; + +#endif diff --git a/Engine/source/T3D/components/render/meshComponent_ScriptBinding.h b/Engine/source/T3D/components/render/meshComponent_ScriptBinding.h new file mode 100644 index 000000000..5a19b8f08 --- /dev/null +++ b/Engine/source/T3D/components/render/meshComponent_ScriptBinding.h @@ -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); +} \ No newline at end of file diff --git a/Engine/source/T3D/components/render/renderComponentInterface.h b/Engine/source/T3D/components/render/renderComponentInterface.h new file mode 100644 index 000000000..ff51098f2 --- /dev/null +++ b/Engine/source/T3D/components/render/renderComponentInterface.h @@ -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*) > onShapeChanged; + + virtual TSShapeInstance* getShapeInstance() = 0; + + virtual MatrixF getNodeTransform(S32 nodeIdx) = 0; + + virtual Vector getNodeTransforms() = 0; + + virtual void setNodeTransforms(Vector transforms) = 0; + + Signal< void(RenderComponentInterface*) > onShapeInstanceChanged; +}; + +class CastRayRenderedInterface// : public Interface +{ +public: + virtual bool castRayRendered(const Point3F &start, const Point3F &end, RayInfo* info)=0; +}; + +#endif \ No newline at end of file diff --git a/Engine/source/T3D/entity.cpp b/Engine/source/T3D/entity.cpp new file mode 100644 index 000000000..00ecedad0 --- /dev/null +++ b/Engine/source/T3D/entity.cpp @@ -0,0 +1,1939 @@ +//----------------------------------------------------------------------------- +// Copyright (c) 2012 GarageGames, LLC +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "T3D/entity.h" +#include "core/stream/bitStream.h" +#include "console/consoleTypes.h" +#include "console/consoleObject.h" +#include "sim/netConnection.h" +#include "scene/sceneRenderState.h" +#include "scene/sceneManager.h" +#include "T3D/gameBase/gameProcess.h" +#include "console/engineAPI.h" +#include "T3D/gameBase/gameConnection.h" +#include "math/mathIO.h" +#include "math/mTransform.h" + +#include "T3D/components/coreInterfaces.h" +#include "T3D/components/render/renderComponentInterface.h" +#include "T3D/components/collision/collisionInterfaces.h" + +#include "gui/controls/guiTreeViewCtrl.h" +#include "assets/assetManager.h" +#include "assets/assetQuery.h" +#include "T3D/assets/ComponentAsset.h" + +#include "console/consoleInternal.h" +#include "T3D/gameBase/std/stdMoveList.h" + +#include "T3D/prefab.h" + +// +#include "gfx/sim/debugDraw.h" +// + +extern bool gEditingMission; + +// Client prediction +static F32 sMinWarpTicks = 0.5f; // Fraction of tick at which instant warp occurs +static S32 sMaxWarpTicks = 3; // Max warp duration in ticks +static S32 sMaxPredictionTicks = 30; // Number of ticks to predict + +IMPLEMENT_CO_NETOBJECT_V1(Entity); + +ConsoleDocClass(Entity, + "@brief Base Entity class.\n\n" + + "Entity is typically made up of a shape and up to two particle emitters. In most cases Entity objects are " + "not created directly. They are usually produced automatically by other means, such as through the Explosion " + "class. When an explosion goes off, its ExplosionData datablock determines what Entity to emit.\n" + + "@tsexample\n" + "datablock ExplosionData(GrenadeLauncherExplosion)\n" + "{\n" + " // Assiging Entity data\n" + " Entity = GrenadeEntity;\n\n" + " // Adjust how Entity is ejected\n" + " EntityThetaMin = 10;\n" + " EntityThetaMax = 60;\n" + " EntityNum = 4;\n" + " EntityNumVariance = 2;\n" + " EntityVelocity = 25;\n" + " EntityVelocityVariance = 5;\n\n" + " // Note: other ExplosionData properties are not listed for this example\n" + "};\n" + "@endtsexample\n\n" + + "@note Entity are client side only objects.\n" + + "@see EntityData\n" + "@see ExplosionData\n" + "@see Explosion\n" + + "@ingroup FX\n" + ); + +Entity::Entity() +{ + //mTypeMask |= DynamicShapeObjectType | StaticObjectType | ; + mTypeMask |= EntityObjectType; + mNetFlags.set(Ghostable | ScopeAlways); + + mPos = Point3F(0, 0, 0); + mRot = Point3F(0, 0, 0); + + mDelta.pos = mDelta.posVec = Point3F::Zero; + mDelta.rot[0].identity(); + mDelta.rot[1].identity(); + mDelta.warpOffset.set(0.0f, 0.0f, 0.0f); + + mDelta.warpTicks = mDelta.warpCount = 0; + mDelta.dt = 1.0f; + mDelta.move = NullMove; + + mComponents.clear(); + + mStartComponentUpdate = false; + + mInitialized = false; + +} + +Entity::~Entity() +{ + +} + +void Entity::initPersistFields() +{ + Parent::initPersistFields(); + + removeField("DataBlock"); + + addGroup("Transform"); + + removeField("Position"); + addProtectedField("Position", TypePoint3F, Offset(mPos, Entity), &_setPosition, &_getPosition, "Object world orientation."); + + removeField("Rotation"); + addProtectedField("Rotation", TypeRotationF, Offset(mRot, Entity), &_setRotation, &_getRotation, "Object world orientation."); + + //These are basically renamed mountPos/Rot. pretty much there for conveinence + addField("LocalPosition", TypeMatrixPosition, Offset(mMount.xfm, Entity), "Position we are mounted at ( object space of our mount object )."); + addField("LocalRotation", TypeMatrixRotation, Offset(mMount.xfm, Entity), "Rotation we are mounted at ( object space of our mount object )."); + + endGroup("Transform"); +} + +// +bool Entity::_setPosition(void *object, const char *index, const char *data) +{ + Entity* so = static_cast(object); + if (so) + { + Point3F pos; + + if (!dStrcmp(data, "")) + pos = Point3F(0, 0, 0); + else + Con::setData(TypePoint3F, &pos, 0, 1, &data); + + so->setTransform(pos, so->mRot); + } + return false; +} + +const char * Entity::_getPosition(void* obj, const char* data) +{ + Entity* so = static_cast(obj); + if (so) + { + Point3F pos = so->getPosition(); + + static const U32 bufSize = 256; + char* returnBuffer = Con::getReturnBuffer(bufSize); + dSprintf(returnBuffer, bufSize, "%g %g %g", pos.x, pos.y, pos.z); + return returnBuffer; + } + return "0 0 0"; +} + +bool Entity::_setRotation(void *object, const char *index, const char *data) +{ + Entity* so = static_cast(object); + if (so) + { + RotationF rot; + Con::setData(TypeRotationF, &rot, 0, 1, &data); + + //so->mRot = rot; + //MatrixF mat = rot.asMatrixF(); + //mat.setPosition(so->getPosition()); + //so->setTransform(mat); + so->setTransform(so->getPosition(), rot); + } + return false; +} + +const char * Entity::_getRotation(void* obj, const char* data) +{ + Entity* so = static_cast(obj); + if (so) + { + EulerF eulRot = so->mRot.asEulerF(); + + static const U32 bufSize = 256; + char* returnBuffer = Con::getReturnBuffer(bufSize); + dSprintf(returnBuffer, bufSize, "%g %g %g", mRadToDeg(eulRot.x), mRadToDeg(eulRot.y), mRadToDeg(eulRot.z)); + return returnBuffer; + } + return "0 0 0"; +} + +bool Entity::onAdd() +{ + if (!Parent::onAdd()) + return false; + + mObjBox = Box3F(Point3F(-1, -1, -1), Point3F(1, 1, 1)); + + resetWorldBox(); + setObjectBox(mObjBox); + + addToScene(); + + //Make sure we get positioned + setMaskBits(TransformMask); + + return true; +} + +void Entity::onRemove() +{ + clearComponents(true); + + removeFromScene(); + + onDataSet.removeAll(); + + Parent::onRemove(); +} + +void Entity::onPostAdd() +{ + mInitialized = true; + + //everything's done and added. go ahead and initialize the components + for (U32 i = 0; i < mComponents.size(); i++) + { + mComponents[i]->onComponentAdd(); + } + + if (isMethod("onAdd")) + Con::executef(this, "onAdd"); +} + +void Entity::setDataField(StringTableEntry slotName, const char *array, const char *value) +{ + Parent::setDataField(slotName, array, value); + + onDataSet.trigger(this, slotName, value); +} + +void Entity::onStaticModified(const char* slotName, const char* newValue) +{ + Parent::onStaticModified(slotName, newValue); + + onDataSet.trigger(this, slotName, newValue); +} + +//Updating +void Entity::processTick(const Move* move) +{ + if (!isHidden()) + { + if (mDelta.warpCount < mDelta.warpTicks) + { + mDelta.warpCount++; + + // Set new pos. + mObjToWorld.getColumn(3, &mDelta.pos); + mDelta.pos += mDelta.warpOffset; + mDelta.rot[0] = mDelta.rot[1]; + mDelta.rot[1].interpolate(mDelta.warpRot[0], mDelta.warpRot[1], F32(mDelta.warpCount) / mDelta.warpTicks); + setTransform(mDelta.pos, mDelta.rot[1]); + + // Pos backstepping + mDelta.posVec.x = -mDelta.warpOffset.x; + mDelta.posVec.y = -mDelta.warpOffset.y; + mDelta.posVec.z = -mDelta.warpOffset.z; + } + else + { + if (isMounted()) + { + MatrixF mat; + mMount.object->getMountTransform(mMount.node, mMount.xfm, &mat); + Parent::setTransform(mat); + Parent::setRenderTransform(mat); + } + else + { + if (!move) + { + if (isGhost()) + { + // If we haven't run out of prediction time, + // predict using the last known move. + if (mPredictionCount-- <= 0) + return; + + move = &mDelta.move; + } + else + { + move = &NullMove; + } + } + } + } + + Move prevMove = lastMove; + + if (move != NULL) + lastMove = *move; + else + lastMove = NullMove; + + if (move && isServerObject()) + { + if ((move->y != 0 || prevMove.y != 0) + || (move->x != 0 || prevMove.x != 0) + || (move->z != 0 || prevMove.x != 0)) + { + if (isMethod("moveVectorEvent")) + Con::executef(this, "moveVectorEvent", move->x, move->y, move->z); + } + + if (move->yaw != 0) + { + if (isMethod("moveYawEvent")) + Con::executef(this, "moveYawEvent", move->yaw); + } + + if (move->pitch != 0) + { + if (isMethod("movePitchEvent")) + Con::executef(this, "movePitchEvent", move->pitch); + } + + if (move->roll != 0) + { + if (isMethod("moveRollEvent")) + Con::executef(this, "moveRollEvent", move->roll); + } + + for (U32 i = 0; i < MaxTriggerKeys; i++) + { + if (move->trigger[i] != prevMove.trigger[i]) + { + if (isMethod("moveTriggerEvent")) + Con::executef(this, "moveTriggerEvent", i, move->trigger[i]); + } + } + } + + if (isMethod("processTick")) + Con::executef(this, "processTick"); + } +} + +void Entity::advanceTime(F32 dt) +{ +} + +void Entity::interpolateTick(F32 dt) +{ + if (dt == 0.0f) + { + setRenderTransform(mDelta.pos, mDelta.rot[1]); + } + else + { + QuatF rot; + rot.interpolate(mDelta.rot[1], mDelta.rot[0], dt); + Point3F pos = mDelta.pos + mDelta.posVec * dt; + + setRenderTransform(pos, rot); + } + + mDelta.dt = dt; +} + +//Render +void Entity::prepRenderImage(SceneRenderState *state) +{ +} + +//Networking +U32 Entity::packUpdate(NetConnection *con, U32 mask, BitStream *stream) +{ + U32 retMask = Parent::packUpdate(con, mask, stream); + + if (stream->writeFlag(mask & TransformMask)) + { + //mathWrite( *stream, getScale() ); + //stream->writeAffineTransform(mObjToWorld); + //mathWrite(*stream, getPosition()); + //mathWrite(*stream, mPos); + + stream->writeCompressedPoint(mPos); + mathWrite(*stream, getRotation()); + + mDelta.move.pack(stream); + + stream->writeFlag(!(mask & NoWarpMask)); + } + + /*if (stream->writeFlag(mask & MountedMask)) + { + mathWrite(*stream, mMount.xfm.getPosition()); + mathWrite(*stream, mMount.xfm.toEuler()); + }*/ + + if (stream->writeFlag(mask & BoundsMask)) + { + mathWrite(*stream, mObjBox); + } + + //pass our behaviors around + if (mask & ComponentsMask || mask & InitialUpdateMask) + { + stream->writeFlag(true); + //now, we run through a list of our to-be-sent behaviors and begin sending them + //if any fail, we keep our list and re-queue the mask + S32 componentCount = mToLoadComponents.size(); + + //build our 'ready' list + //This requires both the instance and the instances' template to be prepped(if the template hasn't been ghosted, + //then we know we shouldn't be passing the instance's ghosts around yet) + U32 ghostedCompCnt = 0; + for (U32 i = 0; i < componentCount; i++) + { + if (con->getGhostIndex(mToLoadComponents[i]) != -1) + ghostedCompCnt++; + } + + if (ghostedCompCnt != 0) + { + stream->writeFlag(true); + + stream->writeFlag(mStartComponentUpdate); + + //if not all the behaviors have been ghosted, we'll need another pass + if (ghostedCompCnt != componentCount) + retMask |= ComponentsMask; + + //write the currently ghosted behavior count + stream->writeInt(ghostedCompCnt, 16); + + for (U32 i = 0; i < mToLoadComponents.size(); i++) + { + //now fetch them and pass the ghost + S32 ghostIndex = con->getGhostIndex(mToLoadComponents[i]); + if (ghostIndex != -1) + { + stream->writeInt(ghostIndex, NetConnection::GhostIdBitSize); + mToLoadComponents.erase(i); + i--; + + mStartComponentUpdate = false; + } + } + } + else if (componentCount) + { + //on the odd chance we have behaviors to ghost, but NONE of them have been yet, just set the flag now + stream->writeFlag(false); + retMask |= ComponentsMask; + } + else + stream->writeFlag(false); + } + else + stream->writeFlag(false); + + return retMask; +} + +void Entity::unpackUpdate(NetConnection *con, BitStream *stream) +{ + Parent::unpackUpdate(con, stream); + + if (stream->readFlag()) + { + /*Point3F scale; + mathRead( *stream, &scale ); + setScale( scale);*/ + + //MatrixF objToWorld; + //stream->readAffineTransform(&objToWorld); + + Point3F pos; + + stream->readCompressedPoint(&pos); + //mathRead(*stream, &pos); + + RotationF rot; + + mathRead(*stream, &rot); + + mDelta.move.unpack(stream); + + if (stream->readFlag() && isProperlyAdded()) + { + // Determine number of ticks to warp based on the average + // of the client and server velocities. + /*mDelta.warpOffset = pos - mDelta.pos; + + F32 dt = mDelta.warpOffset.len() / (0.5f * TickSec); + + mDelta.warpTicks = (S32)((dt > sMinWarpTicks) ? getMax(mFloor(dt + 0.5f), 1.0f) : 0.0f); + + //F32 as = (speed + mVelocity.len()) * 0.5f * TickSec; + //F32 dt = (as > 0.00001f) ? mDelta.warpOffset.len() / as : sMaxWarpTicks; + //mDelta.warpTicks = (S32)((dt > sMinWarpTicks) ? getMax(mFloor(dt + 0.5f), 1.0f) : 0.0f); + + //mDelta.warpTicks = (S32)((dt > sMinWarpTicks) ? getMax(mFloor(dt + 0.5f), 1.0f) : 0.0f); + + //mDelta.warpTicks = sMaxWarpTicks; + + mDelta.warpTicks = 0; + + if (mDelta.warpTicks) + { + // Setup the warp to start on the next tick. + if (mDelta.warpTicks > sMaxWarpTicks) + mDelta.warpTicks = sMaxWarpTicks; + mDelta.warpOffset /= (F32)mDelta.warpTicks; + + mDelta.rot[0] = rot.asQuatF(); + mDelta.rot[1] = rot.asQuatF(); + + mDelta.rotOffset = rot.asEulerF() - mDelta.rot.asEulerF(); + + // Ignore small rotation differences + if (mFabs(mDelta.rotOffset.x) < 0.001f) + mDelta.rotOffset.x = 0; + + if (mFabs(mDelta.rotOffset.y) < 0.001f) + mDelta.rotOffset.y = 0; + + if (mFabs(mDelta.rotOffset.z) < 0.001f) + mDelta.rotOffset.z = 0; + + mDelta.rotOffset /= (F32)mDelta.warpTicks; + } + else + { + // Going to skip the warp, server and client are real close. + // Adjust the frame interpolation to move smoothly to the + // new position within the current tick. + Point3F cp = mDelta.pos + mDelta.posVec * mDelta.dt; + if (mDelta.dt == 0) + { + mDelta.posVec.set(0.0f, 0.0f, 0.0f); + mDelta.rotVec.set(0.0f, 0.0f, 0.0f); + } + else + { + F32 dti = 1.0f / mDelta.dt; + mDelta.posVec = (cp - pos) * dti; + mDelta.rotVec.z = mRot.z - rot.z; + + mDelta.rotVec.z *= dti; + } + + mDelta.pos = pos; + mDelta.rot = rot; + + setTransform(pos, rot); + }*/ + + Point3F cp = mDelta.pos + mDelta.posVec * mDelta.dt; + mDelta.warpOffset = pos - cp; + + // Calc the distance covered in one tick as the average of + // the old speed and the new speed from the server. + VectorF vel = pos - mDelta.pos; + F32 dt, as = vel.len() * 0.5 * TickSec; + + // Cal how many ticks it will take to cover the warp offset. + // If it's less than what's left in the current tick, we'll just + // warp in the remaining time. + if (!as || (dt = mDelta.warpOffset.len() / as) > sMaxWarpTicks) + dt = mDelta.dt + sMaxWarpTicks; + else + dt = (dt <= mDelta.dt) ? mDelta.dt : mCeil(dt - mDelta.dt) + mDelta.dt; + + // Adjust current frame interpolation + if (mDelta.dt) + { + mDelta.pos = cp + (mDelta.warpOffset * (mDelta.dt / dt)); + mDelta.posVec = (cp - mDelta.pos) / mDelta.dt; + QuatF cr; + cr.interpolate(mDelta.rot[1], mDelta.rot[0], mDelta.dt); + + mDelta.rot[1].interpolate(cr, rot.asQuatF(), mDelta.dt / dt); + mDelta.rot[0].extrapolate(mDelta.rot[1], cr, mDelta.dt); + } + + // Calculated multi-tick warp + mDelta.warpCount = 0; + mDelta.warpTicks = (S32)(mFloor(dt)); + if (mDelta.warpTicks) + { + mDelta.warpOffset = pos - mDelta.pos; + mDelta.warpOffset /= mDelta.warpTicks; + mDelta.warpRot[0] = mDelta.rot[1]; + mDelta.warpRot[1] = rot.asQuatF(); + } + } + else + { + // Set the entity to the server position + mDelta.dt = 0; + mDelta.pos = pos; + mDelta.posVec.set(0, 0, 0); + mDelta.rot[1] = mDelta.rot[0] = rot.asQuatF(); + mDelta.warpCount = mDelta.warpTicks = 0; + setTransform(pos, rot); + } + } + + /*if (stream->readFlag()) + { + Point3F mountOffset; + EulerF mountRot; + mathRead(*stream, &mountOffset); + mathRead(*stream, &mountRot); + + RotationF rot = RotationF(mountRot); + mountRot = rot.asEulerF(RotationF::Degrees); + + setMountOffset(mountOffset); + setMountRotation(mountRot); + }*/ + + if (stream->readFlag()) + { + mathRead(*stream, &mObjBox); + resetWorldBox(); + } + + if (stream->readFlag()) + { + //are we passing any behaviors currently? + if (stream->readFlag()) + { + //if we've just started the update, clear our behaviors + if (stream->readFlag()) + clearComponents(false); + + S32 componentCount = stream->readInt(16); + + for (U32 i = 0; i < componentCount; i++) + { + S32 gIndex = stream->readInt(NetConnection::GhostIdBitSize); + addComponent(dynamic_cast(con->resolveGhost(gIndex))); + } + } + } +} + +//Manipulation +void Entity::setTransform(const MatrixF &mat) +{ + //setMaskBits(TransformMask); + setMaskBits(TransformMask | NoWarpMask); + + if (isMounted()) + { + // Use transform from mounted object + Point3F newPos = mat.getPosition(); + Point3F parentPos = mMount.object->getTransform().getPosition(); + + Point3F newOffset = newPos - parentPos; + + if (!newOffset.isZero()) + { + //setMountOffset(newOffset); + mPos = newOffset; + } + + Point3F matEul = mat.toEuler(); + + //mRot = Point3F(mRadToDeg(matEul.x), mRadToDeg(matEul.y), mRadToDeg(matEul.z)); + + if (matEul != Point3F(0, 0, 0)) + { + Point3F mountEul = mMount.object->getTransform().toEuler(); + Point3F diff = matEul - mountEul; + + //setMountRotation(Point3F(mRadToDeg(diff.x), mRadToDeg(diff.y), mRadToDeg(diff.z))); + mRot = diff; + } + else + { + //setMountRotation(Point3F(0, 0, 0)); + mRot = Point3F(0, 0, 0); + } + + RotationF addRot = mRot + RotationF(mMount.object->getTransform()); + MatrixF transf = addRot.asMatrixF(); + transf.setPosition(mPos + mMount.object->getPosition()); + + Parent::setTransform(transf); + } + else + { + //Are we part of a prefab? + /*Prefab* p = Prefab::getPrefabByChild(this); + if (p) + { + //just let our prefab know we moved + p->childTransformUpdated(this, mat); + }*/ + //else + { + //mRot.set(mat); + //Parent::setTransform(mat); + + RotationF rot = RotationF(mat); + + EulerF tempRot = rot.asEulerF(RotationF::Degrees); + + Point3F pos; + + mat.getColumn(3,&pos); + + setTransform(pos, rot); + } + } +} + +void Entity::setTransform(Point3F position, RotationF rotation) +{ + if (isMounted()) + { + mPos = position; + mRot = rotation; + + RotationF addRot = mRot + RotationF(mMount.object->getTransform()); + MatrixF transf = addRot.asMatrixF(); + transf.setPosition(mPos + mMount.object->getPosition()); + + Parent::setTransform(transf); + + setMaskBits(TransformMask); + } + else + { + /*MatrixF newMat, imat, xmat, ymat, zmat; + Point3F radRot = Point3F(mDegToRad(rotation.x), mDegToRad(rotation.y), mDegToRad(rotation.z)); + xmat.set(EulerF(radRot.x, 0, 0)); + ymat.set(EulerF(0.0f, radRot.y, 0.0f)); + zmat.set(EulerF(0, 0, radRot.z)); + imat.mul(zmat, xmat); + newMat.mul(imat, ymat);*/ + + MatrixF newMat = rotation.asMatrixF(); + + newMat.setColumn(3, position); + + mPos = position; + mRot = rotation; + + setMaskBits(TransformMask); + //if (isServerObject()) + // setMaskBits(TransformMask); + + //setTransform(temp); + + // This test is a bit expensive so turn it off in release. +#ifdef TORQUE_DEBUG + //AssertFatal( mat.isAffine(), "SceneObject::setTransform() - Bad transform (non affine)!" ); +#endif + + //PROFILE_SCOPE(Entity_setTransform); + + // Update the transforms. + + Parent::setTransform(newMat); + + onTransformSet.trigger(&newMat); + + /*mObjToWorld = mWorldToObj = newMat; + mWorldToObj.affineInverse(); + // Update the world-space AABB. + resetWorldBox(); + // If we're in a SceneManager, sync our scene state. + if (mSceneManager != NULL) + mSceneManager->notifyObjectDirty(this); + setRenderTransform(newMat);*/ + } +} + +void Entity::setRenderTransform(const MatrixF &mat) +{ + Parent::setRenderTransform(mat); +} + +void Entity::setRenderTransform(Point3F position, RotationF rotation) +{ + if (isMounted()) + { + mPos = position; + mRot = rotation; + + RotationF addRot = mRot + RotationF(mMount.object->getTransform()); + MatrixF transf = addRot.asMatrixF(); + transf.setPosition(mPos + mMount.object->getPosition()); + + Parent::setRenderTransform(transf); + } + else + { + MatrixF newMat = rotation.asMatrixF(); + + newMat.setColumn(3, position); + + mPos = position; + mRot = rotation; + + Parent::setRenderTransform(newMat); + + onTransformSet.trigger(&newMat); + } +} + +MatrixF Entity::getTransform() +{ + if (isMounted()) + { + MatrixF mat; + + //Use transform from mount + mMount.object->getMountTransform(mMount.node, mMount.xfm, &mat); + + Point3F transPos = mat.getPosition() + mPos; + + mat.mul(mRot.asMatrixF()); + + mat.setPosition(transPos); + + return mat; + } + else + { + return Parent::getTransform(); + } +} + +void Entity::setMountOffset(Point3F posOffset) +{ + if (isMounted()) + { + mMount.xfm.setColumn(3, posOffset); + //mPos = posOffset; + setMaskBits(MountedMask); + } +} + +void Entity::setMountRotation(EulerF rotOffset) +{ + if (isMounted()) + { + MatrixF temp, imat, xmat, ymat, zmat; + + Point3F radRot = Point3F(mDegToRad(rotOffset.x), mDegToRad(rotOffset.y), mDegToRad(rotOffset.z)); + xmat.set(EulerF(radRot.x, 0, 0)); + ymat.set(EulerF(0.0f, radRot.y, 0.0f)); + zmat.set(EulerF(0, 0, radRot.z)); + + imat.mul(zmat, xmat); + temp.mul(imat, ymat); + + temp.setColumn(3, mMount.xfm.getPosition()); + + mMount.xfm = temp; + //mRot = RotationF(temp); + setMaskBits(MountedMask); + } +} +// +void Entity::getCameraTransform(F32* pos, MatrixF* mat) +{ + Vector updaters = getComponents(); + for (Vector::iterator it = updaters.begin(); it != updaters.end(); it++) + { + if ((*it)->getCameraTransform(pos, mat)) + { + return; + } + } +} + +void Entity::getMountTransform(S32 index, const MatrixF &xfm, MatrixF *outMat) +{ + RenderComponentInterface* renderInterface = getComponent(); + + if (renderInterface) + { + renderInterface->getShapeInstance()->animate(); + S32 nodeCount = renderInterface->getShapeInstance()->getShape()->nodes.size(); + + if (index >= 0 && index < nodeCount) + { + MatrixF mountTransform = renderInterface->getShapeInstance()->mNodeTransforms[index]; + mountTransform.mul(xfm); + const Point3F& scale = getScale(); + + // The position of the mount point needs to be scaled. + Point3F position = mountTransform.getPosition(); + position.convolve(scale); + mountTransform.setPosition(position); + + // Also we would like the object to be scaled to the model. + outMat->mul(mObjToWorld, mountTransform); + return; + } + } + + // Then let SceneObject handle it. + Parent::getMountTransform(index, xfm, outMat); +} + +void Entity::getRenderMountTransform(F32 delta, S32 index, const MatrixF &xfm, MatrixF *outMat) +{ + RenderComponentInterface* renderInterface = getComponent(); + + if (renderInterface && renderInterface->getShapeInstance()) + { + renderInterface->getShapeInstance()->animate(); + S32 nodeCount = renderInterface->getShapeInstance()->getShape()->nodes.size(); + + if (index >= 0 && index < nodeCount) + { + MatrixF mountTransform = renderInterface->getShapeInstance()->mNodeTransforms[index]; + mountTransform.mul(xfm); + const Point3F& scale = getScale(); + + // The position of the mount point needs to be scaled. + Point3F position = mountTransform.getPosition(); + position.convolve(scale); + mountTransform.setPosition(position); + + // Also we would like the object to be scaled to the model. + outMat->mul(getRenderTransform(), mountTransform); + return; + } + } + + // Then let SceneObject handle it. + Parent::getMountTransform(index, xfm, outMat); +} + +void Entity::setForwardVector(VectorF newForward, VectorF upVector) +{ + MatrixF mat = getTransform(); + + VectorF up(0.0f, 0.0f, 1.0f); + VectorF axisX; + VectorF axisY = newForward; + VectorF axisZ; + + if (upVector != VectorF::Zero) + up = upVector; + + // Validate and normalize input: + F32 lenSq; + lenSq = axisY.lenSquared(); + if (lenSq < 0.000001f) + { + axisY.set(0.0f, 1.0f, 0.0f); + Con::errorf("Entity::setForwardVector() - degenerate forward vector"); + } + else + { + axisY /= mSqrt(lenSq); + } + + + lenSq = up.lenSquared(); + if (lenSq < 0.000001f) + { + up.set(0.0f, 0.0f, 1.0f); + Con::errorf("SceneObject::setForwardVector() - degenerate up vector - too small"); + } + else + { + up /= mSqrt(lenSq); + } + + if (fabsf(mDot(up, axisY)) > 0.9999f) + { + Con::errorf("SceneObject::setForwardVector() - degenerate up vector - same as forward"); + // i haven't really tested this, but i think it generates something which should be not parallel to the previous vector: + F32 tmp = up.x; + up.x = -up.y; + up.y = up.z; + up.z = tmp; + } + + // construct the remaining axes: + mCross(axisY, up, &axisX); + mCross(axisX, axisY, &axisZ); + + mat.setColumn(0, axisX); + mat.setColumn(1, axisY); + mat.setColumn(2, axisZ); + + setTransform(mat); +} +// +//These basically just redirect to any collision behaviors we have +bool Entity::castRay(const Point3F &start, const Point3F &end, RayInfo* info) +{ + Vector updaters = getComponents(); + for (Vector::iterator it = updaters.begin(); it != updaters.end(); it++) + { + if ((*it)->castRay(start, end, info)) + { + return true; + } + } + return false; +} + +bool Entity::castRayRendered(const Point3F &start, const Point3F &end, RayInfo *info) +{ + Vector updaters = getComponents(); + for (Vector::iterator it = updaters.begin(); it != updaters.end(); it++) + { + if ((*it)->castRayRendered(start, end, info)) + { + return true; + } + } + return false; +} + +bool Entity::buildPolyList(PolyListContext context, AbstractPolyList* polyList, const Box3F &box, const SphereF &sphere) +{ + Vector updaters = getComponents(); + for (Vector::iterator it = updaters.begin(); it != updaters.end(); it++) + { + return (*it)->buildPolyList(context, polyList, box, sphere); + } + + return false; +} + +void Entity::buildConvex(const Box3F& box, Convex* convex) +{ + Vector updaters = getComponents(); + for (Vector::iterator it = updaters.begin(); it != updaters.end(); it++) + { + (*it)->buildConvex(box, convex); + } +} + +// +// Mounting and heirarchy manipulation +void Entity::mountObject(SceneObject* objB, MatrixF txfm) +{ + Parent::mountObject(objB, -1, txfm); + Parent::addObject(objB); +} + +void Entity::mountObject(SceneObject *obj, S32 node, const MatrixF &xfm) +{ + Parent::mountObject(obj, node, xfm); +} + +void Entity::onMount(SceneObject *obj, S32 node) +{ + deleteNotify(obj); + + // Are we mounting to a GameBase object? + Entity *entityObj = dynamic_cast(obj); + + if (entityObj && entityObj->getControlObject() != this) + processAfter(entityObj); + + if (!isGhost()) { + setMaskBits(MountedMask); + + //TODO implement this callback + //onMount_callback( this, obj, node ); + } +} + +void Entity::onUnmount(SceneObject *obj, S32 node) +{ + clearNotify(obj); + + Entity *entityObj = dynamic_cast(obj); + + if (entityObj && entityObj->getControlObject() != this) + clearProcessAfter(); + + if (!isGhost()) { + setMaskBits(MountedMask); + + //TODO implement this callback + //onUnmount_callback( this, obj, node ); + } +} + +//Heirarchy stuff +void Entity::addObject(SimObject* object) +{ + Component* component = dynamic_cast(object); + if (component) + { + addComponent(component); + return; + } + + Entity* e = dynamic_cast(object); + if (e) + { + MatrixF offset; + + //offset.mul(getWorldTransform(), e->getWorldTransform()); + + //check if we're mounting to a node on a shape we have + String node = e->getDataField("mountNode", NULL); + if (!node.isEmpty()) + { + RenderComponentInterface *renderInterface = getComponent(); + if (renderInterface) + { + TSShape* shape = renderInterface->getShape(); + S32 nodeIdx = shape->findNode(node); + + mountObject(e, nodeIdx, MatrixF::Identity); + } + else + { + mountObject(e, MatrixF::Identity); + } + } + else + { + /*Point3F posOffset = mPos - e->getPosition(); + mPos = posOffset; + + RotationF rotOffset = mRot - e->getRotation(); + mRot = rotOffset; + setMaskBits(TransformMask); + mountObject(e, MatrixF::Identity);*/ + + mountObject(e, MatrixF::Identity); + } + + //e->setMountOffset(e->getPosition() - getPosition()); + + //Point3F diff = getWorldTransform().toEuler() - e->getWorldTransform().toEuler(); + + //e->setMountRotation(Point3F(mRadToDeg(diff.x),mRadToDeg(diff.y),mRadToDeg(diff.z))); + + //mountObject(e, offset); + } + else + { + SceneObject* so = dynamic_cast(object); + if (so) + { + //get the difference and build it as our offset! + Point3F posOffset = so->getPosition() - mPos; + RotationF rotOffset = RotationF(so->getTransform()) - mRot; + + MatrixF offset = rotOffset.asMatrixF(); + offset.setPosition(posOffset); + + mountObject(so, offset); + return; + } + } + + Parent::addObject(object); +} + +void Entity::removeObject(SimObject* object) +{ + Entity* e = dynamic_cast(object); + if (e) + { + mPos = mPos + e->getPosition(); + mRot = mRot + e->getRotation(); + unmountObject(e); + setMaskBits(TransformMask); + } + else + { + SceneObject* so = dynamic_cast(object); + if (so) + unmountObject(so); + } + + Parent::removeObject(object); +} + +bool Entity::addComponent(Component *comp) +{ + if (comp == NULL) + return false; + + //double-check were not re-adding anything + mComponents.push_back(comp); + + // Register the component with this owner. + comp->setOwner(this); + + //if we've already been added and this is being added after the fact(at runtime), + //then just go ahead and call it's onComponentAdd so it can get to work + if (mInitialized) + comp->onComponentAdd(); + + onComponentAdded.trigger(comp); + + return true; +} + +SimObject* Entity::findObjectByInternalName(StringTableEntry internalName, bool searchChildren) +{ + for (U32 i = 0; i < mComponents.size(); i++) + { + if (mComponents[i]->getInternalName() == internalName) + { + return mComponents[i]; + } + } + + return Parent::findObjectByInternalName(internalName, searchChildren); +} + +////////////////////////////////////////////////////////////////////////// + +bool Entity::removeComponent(Component *comp, bool deleteComponent) +{ + if (comp == NULL) + return false; + + if(mComponents.remove(comp)) + { + AssertFatal(comp->isProperlyAdded(), "Don't know how but a component is not registered w/ the sim"); + + //setComponentsDirty(); + + onComponentRemoved.trigger(comp); + + comp->setOwner(NULL); + + comp->onComponentRemove(); //in case the behavior needs to do cleanup on the owner + + if (deleteComponent) + comp->safeDeleteObject(); + + return true; + } + + return false; +} + +////////////////////////////////////////////////////////////////////////// +//NOTE: +//The actor class calls this and flags the deletion of the behaviors to false so that behaviors that should no longer be attached during +//a network update will indeed be removed from the object. The reason it doesn't delete them is because when clearing the local behavior +//list, it would delete them, purging the ghost, and causing a crash when the unpack update tried to fetch any existing behaviors' ghosts +//to re-add them. Need to implement a clean clear function that will clear the local list, and only delete unused behaviors during an update. +void Entity::clearComponents(bool deleteComponents) +{ + bool srv = isServerObject(); + if (!deleteComponents) + { + while (mComponents.size() > 0) + { + removeComponent(mComponents.first(), deleteComponents); + } + } + else + { + while (mComponents.size() > 0) + { + Component* comp = mComponents.first(); + + if (comp) + { + comp->onComponentRemove(); //in case the behavior needs to do cleanup on the owner + + bool removed = mComponents.remove(comp); + + //we only need to delete them on the server side. they'll be cleaned up on the client side + //via the ghosting system for us + if (isServerObject()) + comp->deleteObject(); + } + } + } +} + +////////////////////////////////////////////////////////////////////////// +Component *Entity::getComponent(const U32 index) const +{ + if (index < mComponents.size()) + return mComponents[index]; + + return NULL; +} + +Component *Entity::getComponent(String componentType) +{ + for (U32 i = 0; i < mComponents.size(); i++) + { + Component* comp = mComponents[i]; + + /*String namespaceName = comp->getNamespace()->mName; + //check our namespace first + if (namespaceName == componentType) + { + return comp; + } + else + {*/ + //lets scan up, just to be sure + Namespace *NS = comp->getNamespace(); + + //we shouldn't ever go past Component into net object, as we're no longer dealing with component classes + while (dStrcmp(NS->getName(), "NetObject")) + { + String namespaceName = NS->getName(); + + if (namespaceName == componentType) + { + return comp; + } + else + { + NS = NS->getParent(); + } + } + //} + } + + return NULL; +} + +void Entity::onInspect() +{ + Vector updaters = getComponents(); + for (Vector::iterator it = updaters.begin(); it != updaters.end(); it++) + { + (*it)->onInspect(); + } + + GuiTreeViewCtrl *editorTree = dynamic_cast(Sim::findObject("EditorTree")); + if (!editorTree) + return; + + GuiTreeViewCtrl::Item *newItem, *parentItem; + + parentItem = editorTree->getItem(editorTree->findItemByObjectId(getId())); + + S32 componentID = editorTree->insertItem(parentItem->getID(), "Components"); + + newItem = editorTree->getItem(componentID); + newItem->mState.set(GuiTreeViewCtrl::Item::VirtualParent); + newItem->mState.set(GuiTreeViewCtrl::Item::DenyDrag); + //newItem->mState.set(GuiTreeViewCtrl::Item::InspectorData); + newItem->mState.set(GuiTreeViewCtrl::Item::ForceItemName); + //newItem->mInspectorInfo.mObject = this; + + AssetManager *assetDB = dynamic_cast(Sim::findObject("AssetDatabase")); + if (!assetDB) + return; + + //This is used in the event of script-created assets, which likely only have + //the name and other 'friendly' properties stored in a ComponentAsset. + //So we'll do a query for those assets and find the asset based on the component's + //class name + AssetQuery* qry = new AssetQuery(); + qry->registerObject(); + + assetDB->findAssetType(qry, "ComponentAsset"); + + for (U32 i = 0; i < mComponents.size(); ++i) + { + String compName = mComponents[i]->getFriendlyName(); + + if (compName == String("")) + { + String componentClass = mComponents[i]->getClassNamespace(); + + //Means that it's a script-derived component and we should consult the asset to try + //to get the info for it + S32 compAssetCount = qry->mAssetList.size(); + for (U32 c = 0; c < compAssetCount; ++c) + { + StringTableEntry assetID = qry->mAssetList[c]; + + ComponentAsset* compAsset = assetDB->acquireAsset(assetID); + + String compAssetClass = compAsset->getComponentName(); + if (componentClass == compAssetClass) + { + compName = compAsset->getFriendlyName(); + break; + } + } + } + + S32 compID = editorTree->insertItem(componentID, compName); + newItem = editorTree->getItem(compID); + newItem->mInspectorInfo.mObject = mComponents[i]; + newItem->mState.set(GuiTreeViewCtrl::Item::ForceItemName); + newItem->mState.set(GuiTreeViewCtrl::Item::DenyDrag); + newItem->mState.set(GuiTreeViewCtrl::Item::InspectorData); + } + + editorTree->buildVisibleTree(true); +} + +void Entity::onEndInspect() +{ + Vector updaters = getComponents(); + for (Vector::iterator it = updaters.begin(); it != updaters.end(); it++) { + (*it)->onEndInspect(); + } + + GuiTreeViewCtrl *editorTree = dynamic_cast(Sim::findObject("EditorTree")); + if (!editorTree) + return; + + S32 componentItemIdx = editorTree->findItemByName("Components"); + + editorTree->removeItem(componentItemIdx, false); +} + +static void writeTabs(Stream &stream, U32 count) +{ + char tab[] = " "; + while (count--) + stream.write(3, (void*)tab); +} + +void Entity::write(Stream &stream, U32 tabStop, U32 flags) +{ + // Do *not* call parent on this + + /*VectorPtr &componentList = lockComponentList(); + // export selected only? + if( ( flags & SelectedOnly ) && !isSelected() ) + { + for( BehaviorObjectIterator i = componentList.begin(); i != componentList.end(); i++ ) + (*i)->write(stream, tabStop, flags); + + goto write_end; + }*/ + + //catch if we have any written behavior fields already in the file, and clear them. We don't need to double-up + //the entries for no reason. + /*if(getFieldDictionary()) + { + //get our dynamic field count, then parse through them to see if they're a behavior or not + + //reset it + SimFieldDictionary* fieldDictionary = getFieldDictionary(); + SimFieldDictionaryIterator itr(fieldDictionary); + for (S32 i = 0; i < fieldDictionary->getNumFields(); i++) + { + if (!(*itr)) + break; + + SimFieldDictionary::Entry* entry = *itr; + if(strstr(entry->slotName, "_behavior")) + { + entry->slotName = ""; + entry->value = ""; + } + + ++itr; + } + }*/ + //all existing written behavior fields should be cleared. now write the object block + + writeTabs(stream, tabStop); + + char buffer[1024]; + dSprintf(buffer, sizeof(buffer), "new %s(%s) {\r\n", getClassName(), getName() ? getName() : ""); + stream.write(dStrlen(buffer), buffer); + writeFields(stream, tabStop + 1); + + stream.write(1, "\n"); + ////first, write out our behavior objects + + // NOW we write the behavior fields proper + if (mComponents.size() > 0) + { + // Pack out the behaviors into fields + U32 i = 0; + for (U32 i = 0; i < mComponents.size(); i++) + { + writeTabs(stream, tabStop + 1); + char buffer[1024]; + dSprintf(buffer, sizeof(buffer), "new %s() {\r\n", mComponents[i]->getClassName()); + stream.write(dStrlen(buffer), buffer); + //bi->writeFields( stream, tabStop + 2 ); + + mComponents[i]->packToStream(stream, tabStop + 2, i - 1, flags); + + writeTabs(stream, tabStop + 1); + stream.write(4, "};\r\n"); + } + } + + // + //if (size() > 0) + // stream.write(2, "\r\n"); + + for (U32 i = 0; i < size(); i++) + { + SimObject* child = (*this)[i]; + if (child->getCanSave()) + child->write(stream, tabStop + 1, flags); + } + + //stream.write(2, "\r\n"); + + writeTabs(stream, tabStop); + stream.write(4, "};\r\n"); + + //write_end: + //unlockComponentList(); +} + +SimObject* Entity::getTamlChild(const U32 childIndex) const +{ + // Sanity! + AssertFatal(childIndex < getTamlChildCount(), "SimSet::getTamlChild() - Child index is out of range."); + + // For when the assert is not used. + if (childIndex >= getTamlChildCount()) + return NULL; + + //we always order components first, child objects second + if (childIndex >= getComponentCount()) + return at(childIndex - getComponentCount()); + else + return getComponent(childIndex); +} +// +void Entity::onCameraScopeQuery(NetConnection* connection, CameraScopeQuery* query) +{ + // Object itself is in scope. + Parent::onCameraScopeQuery(connection, query); + + if (CameraInterface* cI = getComponent()) + { + cI->onCameraScopeQuery(connection, query); + } +} +// +void Entity::setObjectBox(Box3F objBox) +{ + mObjBox = objBox; + resetWorldBox(); + + if (isServerObject()) + setMaskBits(BoundsMask); +} + +void Entity::updateContainer() +{ + PROFILE_SCOPE(Entity_updateContainer); + + // Update container drag and buoyancy properties + containerInfo.box = getWorldBox(); + //containerInfo.mass = mMass; + + getContainer()->findObjects(containerInfo.box, WaterObjectType | PhysicalZoneObjectType, findRouter, &containerInfo); + + //mWaterCoverage = info.waterCoverage; + //mLiquidType = info.liquidType; + //mLiquidHeight = info.waterHeight; + //setCurrentWaterObject( info.waterObject ); + + // This value might be useful as a datablock value, + // This is what allows the player to stand in shallow water (below this coverage) + // without jiggling from buoyancy + /*if (info.waterCoverage >= 0.25f) + { + // water viscosity is used as drag for in water. + // ShapeBaseData drag is used for drag outside of water. + // Combine these two components to calculate this ShapeBase object's + // current drag. + mDrag = (info.waterCoverage * info.waterViscosity) + + (1.0f - info.waterCoverage) * mDrag; + //mBuoyancy = (info.waterDensity / mDataBlock->density) * info.waterCoverage; + } + + //mAppliedForce = info.appliedForce; + mGravityMod = info.gravityScale;*/ +} +// + +void Entity::setComponentsDirty() +{ + if (mToLoadComponents.empty()) + mStartComponentUpdate = true; + + //we need to build a list of behaviors that need to be pushed across the network + for (U32 i = 0; i < mComponents.size(); i++) + { + // We can do this because both are in the string table + Component *comp = mComponents[i]; + + if (comp->isNetworked()) + { + bool unique = true; + for (U32 i = 0; i < mToLoadComponents.size(); i++) + { + if (mToLoadComponents[i]->getId() == comp->getId()) + { + unique = false; + break; + } + } + if (unique) + mToLoadComponents.push_back(comp); + } + } + + setMaskBits(ComponentsMask); +} + +void Entity::setComponentDirty(Component *comp, bool forceUpdate) +{ + bool found = false; + for (U32 i = 0; i < mComponents.size(); i++) + { + if (mComponents[i]->getId() == comp->getId()) + { + mComponents[i]->setOwner(this); + return; + } + } + + if (!found) + return; + + //if(mToLoadComponents.empty()) + // mStartComponentUpdate = true; + + /*if (comp->isNetworked() || forceUpdate) + { + bool unique = true; + for (U32 i = 0; i < mToLoadComponents.size(); i++) + { + if (mToLoadComponents[i]->getId() == comp->getId()) + { + unique = false; + break; + } + } + if (unique) + mToLoadComponents.push_back(comp); + } + + setMaskBits(ComponentsMask);*/ + +} + +DefineEngineMethod(Entity, mountObject, bool, + (SceneObject* objB, TransformF txfm), (MatrixF::Identity), + "@brief Mount objB to this object at the desired slot with optional transform.\n\n" + + "@param objB Object to mount onto us\n" + "@param slot Mount slot ID\n" + "@param txfm (optional) mount offset transform\n" + "@return true if successful, false if failed (objB is not valid)") +{ + if (objB) + { + //BUG: Unsure how it broke, but atm the default transform passed in here is rotated 180 degrees. This doesn't happen + //for the SceneObject mountobject method. Hackish, but for now, just default to a clean MatrixF::Identity + object->mountObject(objB, /*MatrixF::Identity*/txfm.getMatrix()); + return true; + } + return false; +} + +DefineEngineMethod(Entity, setMountOffset, void, + (Point3F posOffset), (Point3F(0, 0, 0)), + "@brief Mount objB to this object at the desired slot with optional transform.\n\n" + + "@param objB Object to mount onto us\n" + "@param slot Mount slot ID\n" + "@param txfm (optional) mount offset transform\n" + "@return true if successful, false if failed (objB is not valid)") +{ + object->setMountOffset(posOffset); +} + +DefineEngineMethod(Entity, setMountRotation, void, + (EulerF rotOffset), (EulerF(0, 0, 0)), + "@brief Mount objB to this object at the desired slot with optional transform.\n\n" + + "@param objB Object to mount onto us\n" + "@param slot Mount slot ID\n" + "@param txfm (optional) mount offset transform\n" + "@return true if successful, false if failed (objB is not valid)") +{ + object->setMountRotation(rotOffset); +} + +DefineEngineMethod(Entity, getMountTransform, TransformF, (), , + "@brief Mount objB to this object at the desired slot with optional transform.\n\n" + + "@param objB Object to mount onto us\n" + "@param slot Mount slot ID\n" + "@param txfm (optional) mount offset transform\n" + "@return true if successful, false if failed (objB is not valid)") +{ + MatrixF mat; + object->getMountTransform(0, MatrixF::Identity, &mat); + return mat; +} + +DefineEngineMethod(Entity, setBox, void, + (Point3F box), (Point3F(1, 1, 1)), + "@brief Mount objB to this object at the desired slot with optional transform.\n\n" + + "@param objB Object to mount onto us\n" + "@param slot Mount slot ID\n" + "@param txfm (optional) mount offset transform\n" + "@return true if successful, false if failed (objB is not valid)") +{ + object->setObjectBox(Box3F(-box, box)); +} + + +/*DefineConsoleMethod(Entity, callOnComponents, void, (const char* functionName), , + "Get the number of static fields on the object.\n" + "@return The number of static fields defined on the object.") +{ + object->callOnComponents(functionName); +} + +ConsoleMethod(Entity, callMethod, void, 3, 64, "(methodName, argi) Calls script defined method\n" + "@param methodName The method's name as a string\n" + "@param argi Any arguments to pass to the method\n" + "@return No return value" + "@note %obj.callMethod( %methodName, %arg1, %arg2, ... );\n") + +{ + object->callMethodArgList(argc - 1, argv + 2); +} + +ConsoleMethod(Entity, addComponents, void, 2, 2, "() - Add all fielded behaviors\n" + "@return No return value") +{ + object->addComponents(); +}*/ + +ConsoleMethod(Entity, addComponent, bool, 3, 3, "(ComponentInstance bi) - Add a behavior to the object\n" + "@param bi The behavior instance to add" + "@return (bool success) Whether or not the behavior was successfully added") +{ + Component *comp = dynamic_cast(Sim::findObject(argv[2])); + + if (comp != NULL) + { + bool success = object->addComponent(comp); + + if (success) + { + //Placed here so we can differentiate against adding a new behavior during runtime, or when we load all + //fielded behaviors on mission load. This way, we can ensure that we only call the callback + //once everything is loaded. This avoids any problems with looking for behaviors that haven't been added yet, etc. + if (comp->isMethod("onBehaviorAdd")) + Con::executef(comp, "onBehaviorAdd"); + + return true; + } + } + + return false; +} + +ConsoleMethod(Entity, removeComponent, bool, 3, 4, "(ComponentInstance bi, [bool deleteBehavior = true])\n" + "@param bi The behavior instance to remove\n" + "@param deleteBehavior Whether or not to delete the behavior\n" + "@return (bool success) Whether the behavior was successfully removed") +{ + bool deleteComponent = true; + if (argc > 3) + deleteComponent = dAtob(argv[3]); + + return object->removeComponent(dynamic_cast(Sim::findObject(argv[2])), deleteComponent); +} + +ConsoleMethod(Entity, clearComponents, void, 2, 2, "() - Clear all behavior instances\n" + "@return No return value") +{ + object->clearComponents(); +} + +ConsoleMethod(Entity, getComponentByIndex, S32, 3, 3, "(int index) - Gets a particular behavior\n" + "@param index The index of the behavior to get\n" + "@return (ComponentInstance bi) The behavior instance you requested") +{ + Component *comp = object->getComponent(dAtoi(argv[2])); + + return (comp != NULL) ? comp->getId() : 0; +} + +DefineConsoleMethod(Entity, getComponent, S32, (String componentName), (""), + "Get the number of static fields on the object.\n" + "@return The number of static fields defined on the object.") +{ + Component *comp = object->getComponent(componentName); + + return (comp != NULL) ? comp->getId() : 0; + return 0; +} + +/*ConsoleMethod(Entity, getBehaviorByType, S32, 3, 3, "(string BehaviorTemplateName) - gets a behavior\n" + "@param BehaviorTemplateName The name of the template of the behavior instance you want\n" + "@return (ComponentInstance bi) The behavior instance you requested") +{ + ComponentInstance *bInstance = object->getComponentByType(StringTable->insert(argv[2])); + + return (bInstance != NULL) ? bInstance->getId() : 0; +}*/ + +/*ConsoleMethod(Entity, reOrder, bool, 3, 3, "(ComponentInstance inst, [int desiredIndex = 0])\n" + "@param inst The behavior instance you want to reorder\n" + "@param desiredIndex The index you want the behavior instance to be reordered to\n" + "@return (bool success) Whether or not the behavior instance was successfully reordered") +{ + Component *inst = dynamic_cast(Sim::findObject(argv[1])); + + if (inst == NULL) + return false; + + U32 idx = 0; + if (argc > 2) + idx = dAtoi(argv[2]); + + return object->reOrder(inst, idx); +}*/ + +ConsoleMethod(Entity, getComponentCount, S32, 2, 2, "() - Get the count of behaviors on an object\n" + "@return (int count) The number of behaviors on an object") +{ + return object->getComponentCount(); +} + +DefineConsoleMethod(Entity, setComponentDirty, void, (S32 componentID, bool forceUpdate), (0, false), + "Get the number of static fields on the object.\n" + "@return The number of static fields defined on the object.") +{ + /*Component* comp; + if (Sim::findObject(componentID, comp)) + object->setComponentDirty(comp, forceUpdate);*/ +} + +DefineConsoleMethod(Entity, getMoveVector, VectorF, (),, + "Get the number of static fields on the object.\n" + "@return The number of static fields defined on the object.") +{ + if (object->getControllingClient() != NULL) + { + //fetch our last move + if (object->lastMove.x != 0 || object->lastMove.y != 0 || object->lastMove.z != 0) + return VectorF(object->lastMove.x, object->lastMove.y, object->lastMove.z); + } + + return VectorF::Zero; +} + +DefineConsoleMethod(Entity, getMoveRotation, VectorF, (), , + "Get the number of static fields on the object.\n" + "@return The number of static fields defined on the object.") +{ + if(object->getControllingClient() != NULL) + { + //fetch our last move + if (object->lastMove.pitch != 0 || object->lastMove.roll != 0 || object->lastMove.yaw != 0) + return VectorF(object->lastMove.pitch, object->lastMove.roll, object->lastMove.yaw); + } + + return VectorF::Zero; +} + +DefineConsoleMethod(Entity, getMoveTrigger, bool, (S32 triggerNum), (0), + "Get the number of static fields on the object.\n" + "@return The number of static fields defined on the object.") +{ + if (object->getControllingClient() != NULL && triggerNum < MaxTriggerKeys) + { + return object->lastMove.trigger[triggerNum]; + } + + return false; +} + +DefineConsoleMethod(Entity, setForwardVector, void, (VectorF newForward), (VectorF(0,0,0)), + "Get the number of static fields on the object.\n" + "@return The number of static fields defined on the object.") +{ + object->setForwardVector(newForward); +} + +DefineConsoleMethod(Entity, lookAt, void, (Point3F lookPosition),, + "Get the number of static fields on the object.\n" + "@return The number of static fields defined on the object.") +{ + //object->setForwardVector(newForward); +} + +DefineConsoleMethod(Entity, rotateTo, void, (Point3F lookPosition, F32 degreePerSecond), (1.0), + "Get the number of static fields on the object.\n" + "@return The number of static fields defined on the object.") +{ + //object->setForwardVector(newForward); +} \ No newline at end of file diff --git a/Engine/source/T3D/entity.h b/Engine/source/T3D/entity.h new file mode 100644 index 000000000..52818f6f9 --- /dev/null +++ b/Engine/source/T3D/entity.h @@ -0,0 +1,283 @@ +//----------------------------------------------------------------------------- +// Copyright (c) 2012 GarageGames, LLC +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//----------------------------------------------------------------------------- + +#ifndef ENTITY_H +#define ENTITY_H + +#ifndef _GAMEBASE_H_ +#include "T3D/gameBase/gameBase.h" +#endif +#ifndef _MOVEMANAGER_H_ +#include "T3D/gameBase/moveManager.h" +#endif +#ifndef COMPONENT_H +#include "T3D/components/component.h" +#endif +#ifndef _CONTAINERQUERY_H_ +#include "T3D/containerQuery.h" +#endif + +class Component; + +//************************************************************************** +// Entity +//************************************************************************** +class Entity : public GameBase +{ + typedef GameBase Parent; + friend class Component; + +private: + Point3F mPos; + RotationF mRot; + + Vector mComponents; + + Vector mToLoadComponents; + + bool mStartComponentUpdate; + + ContainerQueryInfo containerInfo; + + bool mInitialized; + + Signal< void(Component*) > onComponentAdded; + Signal< void(Component*) > onComponentRemoved; + + Signal< void(MatrixF*) > onTransformSet; + +protected: + + virtual void processTick(const Move* move); + virtual void advanceTime(F32 dt); + virtual void interpolateTick(F32 delta); + + void prepRenderImage(SceneRenderState *state); + + virtual bool onAdd(); + virtual void onRemove(); + +public: + struct StateDelta + { + Move move; ///< Last move from server + F32 dt; ///< Last interpolation time + // Interpolation data + Point3F pos; + Point3F posVec; + QuatF rot[2]; + // Warp data + S32 warpTicks; ///< Number of ticks to warp + S32 warpCount; ///< Current pos in warp + Point3F warpOffset; + QuatF warpRot[2]; + }; + + enum MaskBits + { + TransformMask = Parent::NextFreeMask << 0, + BoundsMask = Parent::NextFreeMask << 1, + ComponentsMask = Parent::NextFreeMask << 2, + NoWarpMask = Parent::NextFreeMask << 3, + NextFreeMask = Parent::NextFreeMask << 4 + }; + + StateDelta mDelta; + S32 mPredictionCount; ///< Number of ticks to predict + + Move lastMove; + + // + Entity(); + ~Entity(); + + static void initPersistFields(); + virtual void onPostAdd(); + + virtual void setTransform(const MatrixF &mat); + virtual void setRenderTransform(const MatrixF &mat); + + void setTransform(Point3F position, RotationF rotation); + + void setRenderTransform(Point3F position, RotationF rotation); + + virtual MatrixF getTransform(); + virtual Point3F getPosition() const { return mPos; } + + //void setTransform(Point3F position, RotationF rot); + + //void setRotation(RotationF rotation); + + void setRotation(RotationF rotation) { + mRot = rotation; + setMaskBits(TransformMask); + }; + RotationF getRotation() { return mRot; } + + void setMountOffset(Point3F posOffset); + void setMountRotation(EulerF rotOffset); + + //static bool _setEulerRotation( void *object, const char *index, const char *data ); + static bool _setPosition(void *object, const char *index, const char *data); + static const char * _getPosition(void* obj, const char* data); + + static bool _setRotation(void *object, const char *index, const char *data); + static const char * _getRotation(void* obj, const char* data); + + virtual void getMountTransform(S32 index, const MatrixF &xfm, MatrixF *outMat); + virtual void getRenderMountTransform(F32 delta, S32 index, const MatrixF &xfm, MatrixF *outMat); + + void setForwardVector(VectorF newForward, VectorF upVector = VectorF::Zero); + + virtual void mountObject(SceneObject *obj, S32 node, const MatrixF &xfm = MatrixF::Identity); + void mountObject(SceneObject* objB, MatrixF txfm); + void onMount(SceneObject *obj, S32 node); + void onUnmount(SceneObject *obj, S32 node); + + // NetObject + U32 packUpdate(NetConnection *conn, U32 mask, BitStream *stream); + void unpackUpdate(NetConnection *conn, BitStream *stream); + + void setComponentsDirty(); + void setComponentDirty(Component *comp, bool forceUpdate = false); + + //Components + virtual bool deferAddingComponents() const { return true; } + + template + T* getComponent(); + template + Vector getComponents(); + + Component* getComponent(String componentType); + + U32 getComponentCount() const + { + return mComponents.size(); + } + + virtual void setObjectBox(Box3F objBox); + + void resetWorldBox() { Parent::resetWorldBox(); } + void resetObjectBox() { Parent::resetObjectBox(); } + void resetRenderWorldBox() { Parent::resetRenderWorldBox(); } + + //function redirects for collisions + bool castRay(const Point3F &start, const Point3F &end, RayInfo* info); + bool castRayRendered(const Point3F &start, const Point3F &end, RayInfo* info); + bool buildPolyList(PolyListContext context, AbstractPolyList* polyList, const Box3F &box, const SphereF &sphere); + virtual void buildConvex(const Box3F& box, Convex* convex); + + Signal< void(SimObject*, String, String) > onDataSet; + virtual void setDataField(StringTableEntry slotName, const char *array, const char *value); + virtual void onStaticModified(const char* slotName, const char* newValue); + + //void pushEvent(const char* eventName, Vector eventParams); + + void updateContainer(); + + ContainerQueryInfo getContainerInfo() { return containerInfo; } + + //camera stuff + virtual void getCameraTransform(F32* pos, MatrixF* mat); + virtual void onCameraScopeQuery(NetConnection* connection, CameraScopeQuery* query); + + //Heirarchy stuff + virtual void addObject(SimObject* object); + virtual void removeObject(SimObject* object); + + virtual SimObject* findObjectByInternalName(StringTableEntry internalName, bool searchChildren); + + //component stuff + bool addComponent(Component *comp); + bool removeComponent(Component *comp, bool deleteComponent); + void clearComponents(bool deleteComponents = true); + Component* getComponent(const U32 index) const; + + void onInspect(); + void onEndInspect(); + + virtual void write(Stream &stream, U32 tabStop, U32 flags); + + // TamlChildren + virtual U32 getTamlChildCount(void) const + { + U32 componentCount = getComponentCount(); + U32 childSize = (U32)size(); + return componentCount + childSize; + } + + virtual SimObject* getTamlChild(const U32 childIndex) const; + + virtual void addTamlChild(SimObject* pSimObject) + { + // Sanity! + AssertFatal(pSimObject != NULL, "SimSet::addTamlChild() - Cannot add a NULL child object."); + + addObject(pSimObject); + } + + Box3F getObjectBox() { return mObjBox; } + MatrixF getWorldToObj() { return mWorldToObj; } + MatrixF getObjToWorld() { return mObjToWorld; } + + DECLARE_CONOBJECT(Entity); + +}; + +template +T *Entity::getComponent() +{ + U32 componentCount = getComponentCount(); + for (U32 i = 0; i < componentCount; i++) + { + T* t = dynamic_cast(mComponents[i]); + + if (t) + { + return t; + } + } + return NULL; +} + +template +Vector Entity::getComponents() +{ + Vector foundObjects; + + T *curObj; + Component* comp; + + // Loop through our child objects. + for (U32 i = 0; i < mComponents.size(); i++) + { + curObj = dynamic_cast(mComponents[i]); + + // Add this child object if appropriate. + if (curObj) + foundObjects.push_back(curObj); + } + + return foundObjects; +} +#endif //ENTITY_H diff --git a/Engine/source/T3D/fps/guiCrossHairHud.cpp b/Engine/source/T3D/fps/guiCrossHairHud.cpp index a8cd3e1ba..01761b707 100644 --- a/Engine/source/T3D/fps/guiCrossHairHud.cpp +++ b/Engine/source/T3D/fps/guiCrossHairHud.cpp @@ -117,7 +117,7 @@ void GuiCrossHairHud::onRender(Point2I offset, const RectI &updateRect) GameConnection* conn = GameConnection::getConnectionToServer(); if (!conn) return; - ShapeBase* control = dynamic_cast(conn->getControlObject()); + GameBase* control = dynamic_cast(conn->getCameraObject()); if (!control || !(control->getTypeMask() & ObjectMask) || !conn->isFirstPerson()) return; diff --git a/Engine/source/T3D/gameBase/gameConnection.cpp b/Engine/source/T3D/gameBase/gameConnection.cpp index 7aada8048..0f36d6326 100644 --- a/Engine/source/T3D/gameBase/gameConnection.cpp +++ b/Engine/source/T3D/gameBase/gameConnection.cpp @@ -39,6 +39,11 @@ #include "console/engineAPI.h" #include "math/mTransform.h" +#ifdef TORQUE_EXPERIMENTAL_EC +#include "T3D/entity.h" +#include "T3D/components/coreInterfaces.h" +#endif + #ifdef TORQUE_HIFI_NET #include "T3D/gameBase/hifi/hifiMoveList.h" #elif defined TORQUE_EXTENDED_MOVE @@ -551,7 +556,9 @@ void GameConnection::setControlObject(GameBase *obj) obj->setControllingClient(this); // Update the camera's FOV to match the new control object - setControlCameraFov( obj->getCameraFov() ); + //but only if we don't have a specific camera object + if (!mCameraObject) + setControlCameraFov(obj->getCameraFov()); } // Okay, set our control object. @@ -729,7 +736,21 @@ bool GameConnection::getControlCameraFov(F32 * fov) } if (cObj) { +#ifdef TORQUE_EXPERIMENTAL_EC + if (Entity* ent = dynamic_cast(cObj)) + { + if (CameraInterface* camInterface = ent->getComponent()) + { + *fov = camInterface->getCameraFov(); + } + } + else + { *fov = cObj->getCameraFov(); + } +#else + *fov = cObj->getCameraFov(); +#endif return(true); } @@ -747,7 +768,26 @@ bool GameConnection::isValidControlCameraFov(F32 fov) obj = obj->getControlObject(); } - return cObj ? cObj->isValidCameraFov(fov) : NULL; + if (cObj) + { +#ifdef TORQUE_EXPERIMENTAL_EC + if (Entity* ent = dynamic_cast(cObj)) + { + if (CameraInterface* camInterface = ent->getComponent()) + { + return camInterface->isValidCameraFov(fov); + } + } + else + { + return cObj->isValidCameraFov(fov); + } +#else + return cObj->isValidCameraFov(fov); +#endif + } + + return NULL; } bool GameConnection::setControlCameraFov(F32 fov) @@ -762,9 +802,32 @@ bool GameConnection::setControlCameraFov(F32 fov) } if (cObj) { + +#ifdef TORQUE_EXPERIMENTAL_EC + F32 newFov = 90.f; + if (Entity* ent = dynamic_cast(cObj)) + { + if (CameraInterface* camInterface = ent->getComponent()) + { + camInterface->setCameraFov(mClampF(fov, MinCameraFov, MaxCameraFov)); + newFov = camInterface->getCameraFov(); + } + else + { + Con::errorf("Attempted to setControlCameraFov, but we don't have a camera!"); + } + } + else + { + // allow shapebase to clamp fov to its datablock values + cObj->setCameraFov(mClampF(fov, MinCameraFov, MaxCameraFov)); + newFov = cObj->getCameraFov(); + } +#else // allow shapebase to clamp fov to its datablock values cObj->setCameraFov(mClampF(fov, MinCameraFov, MaxCameraFov)); F32 newFov = cObj->getCameraFov(); +#endif // server fov of client has 1degree resolution if( S32(newFov) != S32(mCameraFov) || newFov != fov ) @@ -1147,10 +1210,17 @@ void GameConnection::readPacket(BitStream *bstream) if (bstream->readFlag()) { + bool callScript = false; + if (mCameraObject.isNull()) + callScript = true; + S32 gIndex = bstream->readInt(NetConnection::GhostIdBitSize); GameBase* obj = dynamic_cast(resolveGhost(gIndex)); setCameraObject(obj); obj->readPacketData(this, bstream); + + if (callScript) + initialControlSet_callback(); } else setCameraObject(0); @@ -1727,6 +1797,13 @@ DefineEngineMethod( GameConnection, transmitDataBlocks, void, (S32 sequence),, // Ensure that the client knows that the datablock send is done... object->sendConnectionMessage(GameConnection::DataBlocksDone, object->getDataBlockSequence()); } + + if (iCount == 0) + { + //if we have no datablocks to send, we still need to be able to complete the level load process + //so fire off our callback anyways + object->sendConnectionMessage(GameConnection::DataBlocksDone, object->getDataBlockSequence()); + } } else { diff --git a/Engine/source/T3D/gameBase/processList.cpp b/Engine/source/T3D/gameBase/processList.cpp index 32b04ca3c..8e524a205 100644 --- a/Engine/source/T3D/gameBase/processList.cpp +++ b/Engine/source/T3D/gameBase/processList.cpp @@ -27,6 +27,10 @@ #include "platform/profiler.h" #include "console/consoleTypes.h" +#ifdef TORQUE_EXPERIMENTAL_EC +#include "T3D/components/coreInterfaces.h" +#include "T3D/components/component.h" +#endif //---------------------------------------------------------------------------- ProcessObject::ProcessObject() @@ -268,6 +272,13 @@ void ProcessList::advanceObjects() onTickObject(pobj); } +#ifdef TORQUE_EXPERIMENTAL_EC + for (U32 i = 0; i < UpdateInterface::all.size(); i++) + { + UpdateInterface::all[i]->processTick(); + } +#endif + mTotalTicks++; PROFILE_END(); diff --git a/Engine/source/T3D/gameBase/std/stdGameProcess.cpp b/Engine/source/T3D/gameBase/std/stdGameProcess.cpp index 63122b223..b9c3a27f1 100644 --- a/Engine/source/T3D/gameBase/std/stdGameProcess.cpp +++ b/Engine/source/T3D/gameBase/std/stdGameProcess.cpp @@ -37,6 +37,11 @@ #include "T3D/gameBase/std/stdMoveList.h" #include "T3D/fx/cameraFXMgr.h" +#ifdef TORQUE_EXPERIMENTAL_EC +#include "T3D/components/coreInterfaces.h" +#include "T3D/components/component.h" +#endif + MODULE_BEGIN( ProcessList ) MODULE_INIT @@ -132,6 +137,18 @@ bool StdClientProcessList::advanceTime( SimTime timeDelta ) obj = obj->mProcessLink.next; } +#ifdef TORQUE_EXPERIMENTAL_EC + for (U32 i = 0; i < UpdateInterface::all.size(); i++) + { + Component *comp = dynamic_cast(UpdateInterface::all[i]); + + if (!comp->isClientObject() || !comp->isActive()) + continue; + + UpdateInterface::all[i]->interpolateTick(mLastDelta); + } +#endif + // Inform objects of total elapsed delta so they can advance // client side animations. F32 dt = F32(timeDelta) / 1000; @@ -146,6 +163,21 @@ bool StdClientProcessList::advanceTime( SimTime timeDelta ) obj = obj->mProcessLink.next; } +#ifdef TORQUE_EXPERIMENTAL_EC + for (U32 i = 0; i < UpdateInterface::all.size(); i++) + { + Component *comp = dynamic_cast(UpdateInterface::all[i]); + + if (comp) + { + if (!comp->isClientObject() || !comp->isActive()) + continue; + } + + UpdateInterface::all[i]->advanceTime(dt); + } +#endif + return ret; } diff --git a/Engine/source/T3D/gameFunctions.cpp b/Engine/source/T3D/gameFunctions.cpp index 7def7c252..3c71b57b5 100644 --- a/Engine/source/T3D/gameFunctions.cpp +++ b/Engine/source/T3D/gameFunctions.cpp @@ -339,7 +339,7 @@ bool GameProcessCameraQuery(CameraQuery *query) if (connection && connection->getControlCameraTransform(0.032f, &query->cameraMatrix)) { - query->object = dynamic_cast(connection->getControlObject()); + query->object = dynamic_cast(connection->getCameraObject()); query->nearPlane = gClientSceneGraph->getNearClip(); // Scale the normal visible distance by the performance diff --git a/Engine/source/T3D/objectTypes.h b/Engine/source/T3D/objectTypes.h index e65745e1b..5f3266708 100644 --- a/Engine/source/T3D/objectTypes.h +++ b/Engine/source/T3D/objectTypes.h @@ -147,21 +147,25 @@ enum SceneObjectTypes /// @see PhysicalZone PhysicalZoneObjectType = BIT( 22 ), + EntityObjectType = BIT(23), /// @} }; enum SceneObjectTypeMasks { - STATIC_COLLISION_TYPEMASK = StaticShapeObjectType, + STATIC_COLLISION_TYPEMASK = (StaticShapeObjectType | + EntityObjectType), DAMAGEABLE_TYPEMASK = ( PlayerObjectType | + EntityObjectType | VehicleObjectType ), /// Typemask for objects that should be rendered into shadow passes. /// These should be all objects that are either meant to receive or cast /// shadows or both. SHADOW_TYPEMASK = ( StaticShapeObjectType | - DynamicShapeObjectType ), + DynamicShapeObjectType | + EntityObjectType), /// Typemask for objects that should be subjected to more fine-grained /// culling tests. Anything that is trivial rendering stuff or doesn't @@ -172,6 +176,7 @@ enum SceneObjectTypeMasks CULLING_INCLUDE_TYPEMASK = ( GameBaseObjectType | // Includes most other renderable types; but broader than we ideally want. StaticShapeObjectType | DynamicShapeObjectType | + EntityObjectType | ZoneObjectType ), // This improves the result of zone traversals. /// Mask for objects that should be specifically excluded from zone culling. @@ -185,7 +190,9 @@ enum SceneObjectTypeMasks StaticShapeObjectType | DynamicShapeObjectType | LightObjectType | // Flares. - GameBaseObjectType ), + GameBaseObjectType | + TriggerObjectType | + EntityObjectType), /// Typemask to use for rendering when inside the editor. EDITOR_RENDER_TYPEMASK = U32( -1 ), diff --git a/Engine/source/T3D/physics/bullet/btBody.cpp b/Engine/source/T3D/physics/bullet/btBody.cpp index 95625d520..c71a0cc12 100644 --- a/Engine/source/T3D/physics/bullet/btBody.cpp +++ b/Engine/source/T3D/physics/bullet/btBody.cpp @@ -379,6 +379,70 @@ void BtBody::setSimulationEnabled( bool enabled ) mIsEnabled = enabled; } +void BtBody::findContact(SceneObject **contactObject, + VectorF *contactNormal, + Vector *outOverlapObjects) const +{ + AssertFatal(mActor, "BtPlayer::findContact - The controller is null!"); + + VectorF normal; + F32 maxDot = -1.0f; + + // Go thru the contact points... get the first contact. + //mWorld->getDynamicsWorld()->computeOverlappingPairs(); + btOverlappingPairCache *pairCache = mWorld->getDynamicsWorld()->getBroadphase()->getOverlappingPairCache(); + + btBroadphasePairArray& pairArray = pairCache->getOverlappingPairArray(); + U32 numPairs = pairArray.size(); + btManifoldArray manifoldArray; + + for (U32 i = 0; i < numPairs; i++) + { + const btBroadphasePair &pair = pairArray[i]; + + btBroadphasePair *collisionPair = pairCache->findPair(pair.m_pProxy0, pair.m_pProxy1); + if (!collisionPair || !collisionPair->m_algorithm) + continue; + + btCollisionObject *other = (btCollisionObject*)pair.m_pProxy0->m_clientObject; + if (other == mActor) + other = (btCollisionObject*)pair.m_pProxy1->m_clientObject; + + // AssertFatal(!outOverlapObjects->contains(PhysicsUserData::getObject(other->getUserPointer())), + // "Got multiple pairs of the same object!"); + outOverlapObjects->push_back(PhysicsUserData::getObject(other->getUserPointer())); + + if (other->getCollisionFlags() & btCollisionObject::CF_NO_CONTACT_RESPONSE) + continue; + + manifoldArray.clear(); + collisionPair->m_algorithm->getAllContactManifolds(manifoldArray); + + for (U32 j = 0; j < manifoldArray.size(); j++) + { + btPersistentManifold *manifold = manifoldArray[j]; + btScalar directionSign = manifold->getBody0() == mActor ? 1.0f : -1.0f; + + for (U32 p = 0; p < manifold->getNumContacts(); p++) + { + const btManifoldPoint &pt = manifold->getContactPoint(p); + + // Test the normal... is it the most vertical one we got? + normal = btCast(pt.m_normalWorldOnB * directionSign); + F32 dot = mDot(normal, VectorF(0, 0, 1)); + if (dot > maxDot) + { + maxDot = dot; + + btCollisionObject *colObject = (btCollisionObject*)collisionPair->m_pProxy0->m_clientObject; + *contactObject = PhysicsUserData::getObject(colObject->getUserPointer()); + *contactNormal = normal; + } + } + } + } +} + void BtBody::moveKinematicTo(const MatrixF &transform) { AssertFatal(mActor, "BtBody::moveKinematicTo - The actor is null!"); diff --git a/Engine/source/T3D/physics/bullet/btBody.h b/Engine/source/T3D/physics/bullet/btBody.h index fa6561c27..2d138e7e1 100644 --- a/Engine/source/T3D/physics/bullet/btBody.h +++ b/Engine/source/T3D/physics/bullet/btBody.h @@ -111,6 +111,8 @@ public: F32 staticFriction ); virtual void applyCorrection( const MatrixF &xfm ); virtual void applyImpulse( const Point3F &origin, const Point3F &force ); + + virtual void findContact(SceneObject **contactObject, VectorF *contactNormal, Vector *outOverlapObjects) const; virtual void moveKinematicTo(const MatrixF &xfm); }; diff --git a/Engine/source/T3D/physics/physicsBody.h b/Engine/source/T3D/physics/physicsBody.h index a5250dea6..8d5a3e05f 100644 --- a/Engine/source/T3D/physics/physicsBody.h +++ b/Engine/source/T3D/physics/physicsBody.h @@ -114,6 +114,10 @@ public: /// virtual void applyImpulse( const Point3F &origin, const Point3F &force ) = 0; + virtual void findContact(SceneObject **contactObject, + VectorF *contactNormal, + Vector *outOverlapObjects) const = 0; + /// virtual void moveKinematicTo(const MatrixF &xfm) = 0; diff --git a/Engine/source/T3D/physics/physx3/px3Body.cpp b/Engine/source/T3D/physics/physx3/px3Body.cpp index e2fc3916f..14094e956 100644 --- a/Engine/source/T3D/physics/physx3/px3Body.cpp +++ b/Engine/source/T3D/physics/physx3/px3Body.cpp @@ -417,7 +417,58 @@ void Px3Body::applyImpulse( const Point3F &origin, const Point3F &force ) } -void Px3Body::moveKinematicTo(const MatrixF &transform) +void Px3Body::findContact(SceneObject **contactObject, + VectorF *contactNormal, + Vector *outOverlapObjects) const +{ + // Calculate the sweep motion... + F32 halfCapSize = mOriginOffset; + F32 halfSmallCapSize = halfCapSize * 0.8f; + F32 diff = halfCapSize - halfSmallCapSize; + + F32 distance = diff + mSkinWidth + 0.01f; + physx::PxVec3 dir(0, 0, -1); + + physx::PxScene *scene = mWorld->getScene(); + physx::PxHitFlags hitFlags(physx::PxHitFlag::eDEFAULT); + physx::PxQueryFilterData filterData(physx::PxQueryFlag::eDYNAMIC | physx::PxQueryFlag::eSTATIC); + filterData.data.word0 = PX3_DEFAULT; + physx::PxSweepHit sweepHit; + physx::PxRigidDynamic *actor = mController->getActor(); + physx::PxU32 shapeIndex; + + bool hit = physx::PxRigidBodyExt::linearSweepSingle(*actor, *scene, dir, distance, hitFlags, sweepHit, shapeIndex, filterData); + if (hit) + { + PhysicsUserData *data = PhysicsUserData::cast(sweepHit.actor->userData); + if (data) + { + *contactObject = data->getObject(); + *contactNormal = px3Cast(sweepHit.normal); + } + } + + // Check for overlapped objects ( triggers ) + + if (!outOverlapObjects) + return; + + filterData.data.word0 = PX3_TRIGGER; + + const physx::PxU32 bufferSize = 10; + physx::PxOverlapBufferN hitBuffer; + hit = scene->overlap(mGeometry, actor->getGlobalPose(), hitBuffer, filterData); + if (hit) + { + for (U32 i = 0; i < hitBuffer.nbTouches; i++) + { + PhysicsUserData *data = PhysicsUserData::cast(hitBuffer.touches[i].actor->userData); + if (data) + outOverlapObjects->push_back(data->getObject()); + } + } + +}void Px3Body::moveKinematicTo(const MatrixF &transform) { AssertFatal(mActor, "Px3Body::moveKinematicTo - The actor is null!"); diff --git a/Engine/source/T3D/physics/physx3/px3Body.h b/Engine/source/T3D/physics/physx3/px3Body.h index 223418c35..29b90f343 100644 --- a/Engine/source/T3D/physics/physx3/px3Body.h +++ b/Engine/source/T3D/physics/physx3/px3Body.h @@ -117,6 +117,9 @@ public: F32 staticFriction ); virtual void applyCorrection( const MatrixF &xfm ); virtual void applyImpulse( const Point3F &origin, const Point3F &force ); + + virtual void findContact(SceneObject **contactObject, VectorF *contactNormal, + Vector *outOverlapObjects) const; virtual void moveKinematicTo(const MatrixF &xfm); }; diff --git a/Engine/source/assets/assetManager.cpp b/Engine/source/assets/assetManager.cpp index bfd21c699..66df77d11 100644 --- a/Engine/source/assets/assetManager.cpp +++ b/Engine/source/assets/assetManager.cpp @@ -76,7 +76,7 @@ AssetManager::AssetManager() : mMaxLoadedPrivateAssetsCount( 0 ), mAcquiredReferenceCount( 0 ), mEchoInfo( false ), - mIgnoreAutoUnload( false ) + mIgnoreAutoUnload( true ) { } diff --git a/Engine/source/component/componentInterface.cpp b/Engine/source/component/componentInterface.cpp deleted file mode 100644 index e489ca0f2..000000000 --- a/Engine/source/component/componentInterface.cpp +++ /dev/null @@ -1,82 +0,0 @@ -//----------------------------------------------------------------------------- -// 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 "component/simComponent.h" -#include "component/componentInterface.h" -#include "core/strings/findMatch.h" -#include "core/stringTable.h" - -bool ComponentInterfaceCache::add( const char *type, const char *name, const SimComponent *owner, ComponentInterface *cinterface ) -{ - if( ( mInterfaceList.size() == 0 ) || ( enumerate( NULL, type, name, owner ) == 0 ) ) - { - mInterfaceList.increment(); - // CodeReview [tom, 3/9/2007] Seems silly to keep calling last(), why not cache the var? Yes, I know I am pedantic. - mInterfaceList.last().type = ( type == NULL ? NULL : StringTable->insert( type ) ); - mInterfaceList.last().name = ( name == NULL ? NULL : StringTable->insert( name ) ); - mInterfaceList.last().owner = owner; - mInterfaceList.last().iface = cinterface; - - return true; - } - - return false; -} - -//------------------------------------------------------------------------------ - -void ComponentInterfaceCache::clear() -{ - mInterfaceList.clear(); -} - -//------------------------------------------------------------------------------ - -U32 ComponentInterfaceCache::enumerate( ComponentInterfaceList *list, const char *type /* = NULL */, - const char *name /* = NULL */, const SimComponent *owner /* = NULL */, bool notOwner /* = false */ ) const -{ - U32 numMatches = 0; - - for( _InterfaceEntryItr i = mInterfaceList.begin(); i != mInterfaceList.end(); i++ ) - { - // Early out if limiting results by component owner - if( owner != NULL && ( - ( (*i).owner == owner && notOwner ) || - ( (*i).owner != owner && !notOwner ) ) ) - continue; - - // Match the type, short circuit if type == NULL - if( type == NULL || FindMatch::isMatch( type, (*i).type ) ) - { - // Match the name - if( name == NULL || FindMatch::isMatch( name, (*i).name ) ) - { - numMatches++; - - if( list != NULL ) - list->push_back( (*i).iface ); - } - } - } - - return numMatches; -} \ No newline at end of file diff --git a/Engine/source/component/componentInterface.h b/Engine/source/component/componentInterface.h deleted file mode 100644 index 311862a1c..000000000 --- a/Engine/source/component/componentInterface.h +++ /dev/null @@ -1,234 +0,0 @@ -//----------------------------------------------------------------------------- -// 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 _COMPONENTINTERFACE_H_ -#define _COMPONENTINTERFACE_H_ - -#ifndef _TVECTOR_H_ -#include "core/util/tVector.h" -#endif - -#ifndef _SIMOBJECT_H_ -#include "console/simObject.h" -#endif - -#include "core/util/safeDelete.h" - - -class SimComponent; - - -// CodeReview [patw, 2, 13, 2007] The issue I have not addressed in this class is -// interface locking. I think that we want to do this, for sure, but I also want -// to keep it as light-weight as possible. For the most part, there should only -// ever be one thing doing something with a component at one time, but I can see -// many situations where this wouldn't be the case. When we decide to address -// the issues of locking, I believe it should be done here, at the ComponentInterface -// level. I would like lock functionality to be as centralized as possible, and -// so this is the place for it. The functionality is critical for safe useage of -// the ComponentProperty class, so implementation here would also be ideal. - -// CodeReview [patw, 2, 14, 2007] This really should be a ref-counted object -class ComponentInterface -{ - friend class SimComponent; -private: - SimObjectPtr mOwner; ///< SimComponent will directly modify this value - -public: - /// Default constructor - ComponentInterface() : mOwner(NULL) {}; - - /// Destructor - virtual ~ComponentInterface() - { - mOwner = NULL; - } - - /// This will return true if the interface is valid - virtual bool isValid() const - { - return mOwner != NULL; - } - - /// Get the owner of this interface - SimComponent *getOwner() { return mOwner; } - const SimComponent *getOwner() const { return mOwner; } -}; - -typedef VectorPtr ComponentInterfaceList; -typedef VectorPtr::iterator ComponentInterfaceListIterator; - -// These two asserts I found myself wanting a lot when doing interface methods -#ifdef TORQUE_ENABLE_ASSERTS -# define VALID_INTERFACE_ASSERT(OwningClassType) \ - AssertFatal( isValid(), "Interface validity check failed." ); \ - AssertFatal( dynamic_cast( getOwner() ) != NULL, avar( "Owner is not an instance of %s", #OwningClassType ) ) -#else -# define VALID_INTERFACE_ASSERT(OwningClassType) -#endif - -/// This class is designed to wrap an existing class or type easily to allow -/// a SimComponent to expose a property with custom processing code in an efficient -/// and safe way. Specialized templates could be written which include validation -/// on sets, and processing on gets. -/// -/// This class has a lot of "blow your leg off" potential, if you have bad aim. -/// I think that a lot of very intuitive functionality can be gained from using -/// this properly, however when implementing a specialized template, be mindful -/// of what you are doing, and - -// CodeReview [patw, 2, 13, 2007] I am very interested in making this as thin as -// possible. I really like the possibilities that it exposes as far as exposing -// "properties" to other components. I want it to be performant, however, so -// if anyone has notes on this, mark up the source, e-mail me, whatever. -template -class ComponentProperty : public ComponentInterface -{ - typedef ComponentInterface Parent; - -protected: - T *mValuePtr; - - // ComponentInterface Overrides -public: - - // Override this to add a check for valid memory. - virtual bool isValid() const - { - return ( mValuePtr != NULL ) && Parent::isValid(); - } - - // Operator overloads -public: - /// Dereferencing a value interface will allow get to do any processing and - /// return the reference to that - const T &operator*() - { - return get(); - } - - /// Assignment operator will invoke set. - const T &operator=( const T &lval ) - { - return set( lval ); - } - - // Constructors/Destructors, specialize these if needed -public: - /// Default Constructor. - ComponentProperty() : mValuePtr( NULL ) - { - mValuePtr = new T; - } - - /// Copy constructor - ComponentProperty( const T © ) - { - ComponentProperty(); - - // CodeReview [patw, 2, 13, 2007] So, the reasoning here is that I want to - // use the functionality that a specialized template implements in the set - // method. See the notes on the set method implementation. - set( copy ); - } - - /// Destructor, destroy memory - virtual ~ComponentProperty() - { - SAFE_DELETE( mValuePtr ); - } - - // This is the ComponentProperty interface that specializations of the class - // will be interested in. -public: - - /// Get the value associated with this interface. Processing code can be done - /// here for specialized implementations. - virtual const T &get() // 'const' is intentionally not used as a modifier here - { - return *mValuePtr; - } - - /// Set the value associated with this interface. Validation/processing code - /// can be done here. The copy-constructor uses the set method to do it's copy - /// so be mindful of that, or specialize the copy-constructor. - virtual const T &set( const T &t ) - { - // CodeReview [patw, 2, 13, 2007] So I am using the = operator here. Do you - // guys think that this should be the default behavior? I am trying to keep - // everything as object friendly as possible, so I figured I'd use this. - *mValuePtr = t; - return *mValuePtr; - } -}; - -/// This class is just designed to isolate the functionality of querying for, and -/// managing interfaces. -class ComponentInterfaceCache -{ - // CodeReview [patw, 2, 14, 2007] When we move this whole system to Juggernaught - // we may want to consider making safe pointers for ComponentInterfaces. Not - // sure why I put this note here. -private: - struct _InterfaceEntry - { - ComponentInterface *iface; - StringTableEntry type; - StringTableEntry name; - const SimComponent *owner; - }; - - Vector<_InterfaceEntry> mInterfaceList; - typedef Vector<_InterfaceEntry>::const_iterator _InterfaceEntryItr; - -public: - /// Add an interface to the cache. This function will return true if the interface - /// is added successfully. An interface will not be added successfully if an entry - /// in the list with the same values for 'type' and 'name' is present in the list. - /// - /// @param type Type of the interface being added. If NULL is passed, it will match any type string queried. - /// @param name Name of interface being added. If NULL is passed, it will match any name string queried. - /// @param owner The owner of the ComponentInterface being cached - /// @param cinterface The ComponentInterface being cached - virtual bool add( const char *type, const char *name, const SimComponent *owner, ComponentInterface *cinterface ); - - /// Clear the interface cache. This does not perform any operations on the contents - /// of the list. - virtual void clear(); - - /// Query the list for all of the interfaces it stores references to that match - /// the 'type' and 'name' parameters. The results of the query will be appended - /// to the list specified. Pattern matching is done using core/findMatch.h; for - /// more information on matching, see that code/header pair. Passing NULL for - /// one of these fields will match all values for that field. The return value - /// for the method will be the number of interfaces which match the query. - /// - /// @param list The list that this method will append search results on to. It is possible to pass NULL here and just receive the return value. - /// @param type An expression which the 'type' field on an added object must match to be included in results - /// @param name An expression which the 'name' field on an added object must match to be included in results - /// @param owner Limit results to components owned/not-owned by this SimComponent (see next param) - /// @param notOwner If set to true, this will enumerate only interfaces NOT owned by 'owner' - virtual U32 enumerate( ComponentInterfaceList *list, const char *type = NULL, const char *name = NULL, const SimComponent *owner = NULL, bool notOwner = false ) const; -}; - -#endif \ No newline at end of file diff --git a/Engine/source/component/dynamicConsoleMethodComponent.cpp b/Engine/source/component/dynamicConsoleMethodComponent.cpp deleted file mode 100644 index 75a88930c..000000000 --- a/Engine/source/component/dynamicConsoleMethodComponent.cpp +++ /dev/null @@ -1,209 +0,0 @@ -//----------------------------------------------------------------------------- -// 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 "component/dynamicConsoleMethodComponent.h" -#include "console/stringStack.h" - -extern StringStack STR; -extern ConsoleValueStack CSTK; - -IMPLEMENT_CO_NETOBJECT_V1(DynamicConsoleMethodComponent); - -ConsoleDocClass( DynamicConsoleMethodComponent, - "@brief Console object used for calling methods defined in script, from within other classes.\n\n" - "Not intended for game development, for editors or internal use only.\n\n " - "@internal"); - -//----------------------------------------------------------- -// Function name: SimComponent::handlesConsoleMethod -// Summary: -//----------------------------------------------------------- -bool DynamicConsoleMethodComponent::handlesConsoleMethod( const char *fname, S32 *routingId ) -{ - // CodeReview: Host object is now given priority over components for method - // redirection. [6/23/2007 Pat] - - // On this object? - if( isMethod( fname ) ) - { - *routingId = -1; // -1 denotes method on object -#ifdef TORQUE_DEBUG - // Inject Method. - injectMethodCall( fname ); -#endif - return true; - } - - // on this objects components? - S32 nI = 0; - VectorPtr &componentList = lockComponentList(); - for( SimComponentIterator nItr = componentList.begin(); nItr != componentList.end(); nItr++, nI++ ) - { - SimObject *pComponent = dynamic_cast(*nItr); - if( pComponent != NULL && pComponent->isMethod( fname ) ) - { - *routingId = -2; // -2 denotes method on component - unlockComponentList(); - -#ifdef TORQUE_DEBUG - // Inject Method. - injectMethodCall( fname ); -#endif - return true; - } - } - unlockComponentList(); - - return false; -} - -const char *DynamicConsoleMethodComponent::callMethod( S32 argc, const char* methodName, ... ) -{ - const char *argv[128]; - methodName = StringTable->insert( methodName ); - - argc++; - - va_list args; - va_start(args, methodName); - for(S32 i = 0; i < argc; i++) - argv[i+2] = va_arg(args, const char *); - va_end(args); - - // FIXME: the following seems a little excessive. I wonder why it's needed? - argv[0] = methodName; - argv[1] = methodName; - argv[2] = methodName; - - StringStackConsoleWrapper argsw(argc, argv); - - return callMethodArgList( argsw.count() , argsw ); -} - -#ifdef TORQUE_DEBUG -/// Inject Method Call. -void DynamicConsoleMethodComponent::injectMethodCall( const char* method ) -{ - // Get Call Method. - StringTableEntry callMethod = StringTable->insert( method ); - - // Find Call Method Metric. - callMethodMetricType::Iterator itr = mCallMethodMetrics.find( callMethod ); - - // Did we find the method? - if ( itr == mCallMethodMetrics.end() ) - { - // No, so set the call count to initially be 1. - itr = mCallMethodMetrics.insert( callMethod, 1 ); - } - else - { - // Increment Call Count. - itr->value++; - } -} -#endif - -const char* DynamicConsoleMethodComponent::callMethodArgList( U32 argc, ConsoleValueRef argv[], bool callThis /* = true */ ) -{ -#ifdef TORQUE_DEBUG - injectMethodCall( argv[0] ); -#endif - - return _callMethod( argc, argv, callThis ); -} - -// Call all components that implement methodName giving them a chance to operate -// Components are called in reverse order of addition -const char *DynamicConsoleMethodComponent::_callMethod( U32 argc, ConsoleValueRef argv[], bool callThis /* = true */ ) -{ - // Set Owner - SimObject *pThis = dynamic_cast( this ); - AssertFatal( pThis, "DynamicConsoleMethodComponent::callMethod : this should always exist!" ); - - const char *cbName = StringTable->insert(argv[0]); - - if( getComponentCount() > 0 ) - { - lockComponentList(); - for( S32 i = getComponentCount() - 1; i >= 0; i-- ) - //for( SimComponentIterator nItr = componentList.end(); nItr != componentList.begin(); nItr-- ) - { - argv[0] = cbName; - - SimComponent *pComponent = dynamic_cast( getComponent( i ) ); - AssertFatal( pComponent, "DynamicConsoleMethodComponent::callMethod - NULL component in list!" ); - - DynamicConsoleMethodComponent *pThisComponent = dynamic_cast( pComponent ); - AssertFatal( pThisComponent, "DynamicConsoleMethodComponent::callMethod - Non DynamicConsoleMethodComponent component attempting to callback!"); - - // Prevent stack corruption - STR.pushFrame(); - CSTK.pushFrame(); - // -- - - // Only call on first depth components - // Should isMethod check these calls? [11/22/2006 justind] - if(pComponent->isEnabled()) - Con::execute( pThisComponent, argc, argv ); - - // Prevent stack corruption - STR.popFrame(); - CSTK.popFrame(); - // -- - - // Bail if this was the first element - //if( nItr == componentList.begin() ) - // break; - } - unlockComponentList(); - } - - // Prevent stack corruption - STR.pushFrame(); - CSTK.pushFrame(); - // -- - - // Set Owner Field - const char* result = ""; - if(callThis) - result = Con::execute( pThis, argc, argv, true ); // true - exec method onThisOnly, not on DCMCs - - // Prevent stack corruption - STR.popFrame(); - CSTK.popFrame(); - // -- - return result; -} - -ConsoleMethod( DynamicConsoleMethodComponent, callMethod, void, 3, 64 , "(methodName, argi) Calls script defined method\n" - "@param methodName The method's name as a string\n" - "@param argi Any arguments to pass to the method\n" - "@return No return value" - "@note %obj.callMethod( %methodName, %arg1, %arg2, ... );\n") - -{ - object->callMethodArgList( argc - 1, argv + 2 ); -} - -////////////////////////////////////////////////////////////////////////// - diff --git a/Engine/source/component/dynamicConsoleMethodComponent.h b/Engine/source/component/dynamicConsoleMethodComponent.h deleted file mode 100644 index 32e594cd6..000000000 --- a/Engine/source/component/dynamicConsoleMethodComponent.h +++ /dev/null @@ -1,89 +0,0 @@ -//----------------------------------------------------------------------------- -// 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 _DYNAMIC_CONSOLEMETHOD_COMPONENT_H_ -#define _DYNAMIC_CONSOLEMETHOD_COMPONENT_H_ - -#ifndef _SIMCOMPONENT_H_ -#include "component/simComponent.h" -#endif - -#ifndef _CONSOLEINTERNAL_H_ -#include "console/consoleInternal.h" -#endif - -#ifndef _ICALLMETHOD_H_ -#include "console/ICallMethod.h" -#endif - -#ifdef TORQUE_DEBUG -#ifndef _TDICTIONARY_H_ -#include "core/util/tDictionary.h" -#endif -#endif - -//----------------------------------------------------------------------------- - -class DynamicConsoleMethodComponent : public SimComponent, public ICallMethod -{ -#ifdef TORQUE_DEBUG -public: - typedef Map callMethodMetricType; -#endif - -private: - typedef SimComponent Parent; - -#ifdef TORQUE_DEBUG - // Call Method Debug Stat. - callMethodMetricType mCallMethodMetrics; -#endif - -protected: - /// Internal callMethod : Actually does component notification and script method execution - /// @attention This method does some magic to the argc argv to make Con::execute act properly - /// as such it's internal and should not be exposed or used except by this class - virtual const char* _callMethod( U32 argc, ConsoleValueRef argv[], bool callThis = true ); - -public: - -#ifdef TORQUE_DEBUG - /// Call Method Metrics. - const callMethodMetricType& getCallMethodMetrics( void ) const { return mCallMethodMetrics; }; - - /// Inject Method Call. - void injectMethodCall( const char* method ); -#endif - - /// Call Method - virtual const char* callMethodArgList( U32 argc, ConsoleValueRef argv[], bool callThis = true ); - - /// Call Method format string - const char* callMethod( S32 argc, const char* methodName, ... ); - - // query for console method data - virtual bool handlesConsoleMethod(const char * fname, S32 * routingId); - - DECLARE_CONOBJECT(DynamicConsoleMethodComponent); -}; - -#endif \ No newline at end of file diff --git a/Engine/source/component/simComponent.cpp b/Engine/source/component/simComponent.cpp deleted file mode 100644 index 49cbef22e..000000000 --- a/Engine/source/component/simComponent.cpp +++ /dev/null @@ -1,452 +0,0 @@ -//----------------------------------------------------------------------------- -// 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/simObject.h" -#include "console/consoleTypes.h" -#include "component/simComponent.h" -#include "core/stream/stream.h" -#include "console/engineAPI.h" - -SimComponent::SimComponent() : mOwner( NULL ) -{ - mComponentList.clear(); - mMutex = Mutex::createMutex(); - - mEnabled = true; - mTemplate = false; -} - -SimComponent::~SimComponent() -{ - Mutex::destroyMutex( mMutex ); - mMutex = NULL; -} - -IMPLEMENT_CO_NETOBJECT_V1(SimComponent); - -ConsoleDocClass( SimComponent, - "@brief Legacy component system, soon to be deprecated.\n\n" - "Not intended for game development, for editors or internal use only.\n\n " - "@internal"); - -bool SimComponent::onAdd() -{ - if( !Parent::onAdd() ) - return false; - - // Register - _registerInterfaces( this ); - - if( !_registerComponents( this ) ) - return false; - - //Con::executef( this, 1, "onAdd" ); - - return true; -} - -void SimComponent::_registerInterfaces( SimComponent *owner ) -{ - // First call this to expose the interfaces that this component will cache - // before examining the list of subcomponents - registerInterfaces( owner ); - - // Early out to avoid mutex lock and such - if( !hasComponents() ) - return; - - VectorPtr &components = lockComponentList(); - for( SimComponentIterator i = components.begin(); i != components.end(); i++ ) - { - (*i)->mOwner = owner; - - // Tell the component itself to register it's interfaces - (*i)->registerInterfaces( owner ); - - (*i)->mOwner = NULL; // This tests to see if the object's onComponentRegister call will call up to the parent. - - // Recurse - (*i)->_registerInterfaces( owner ); - } - - unlockComponentList(); -} - -bool SimComponent::_registerComponents( SimComponent *owner ) -{ - // This method will return true if the object contains no components. See the - // documentation for SimComponent::onComponentRegister for more information - // on this behavior. - bool ret = true; - - // If this doesn't contain components, don't even lock the list. - if( hasComponents() ) - { - VectorPtr &components = lockComponentList(); - for( SimComponentIterator i = components.begin(); i != components.end(); i++ ) - { - if( !(*i)->onComponentRegister( owner ) ) - { - ret = false; - break; - } - - AssertFatal( (*i)->mOwner == owner, "Component failed to call parent onComponentRegister!" ); - - // Recurse - if( !(*i)->_registerComponents( owner ) ) - { - ret = false; - break; - } - } - - unlockComponentList(); - } - - return ret; -} - -void SimComponent::_unregisterComponents() -{ - if( !hasComponents() ) - return; - - VectorPtr &components = lockComponentList(); - for( SimComponentIterator i = components.begin(); i != components.end(); i++ ) - { - (*i)->onComponentUnRegister(); - - AssertFatal( (*i)->mOwner == NULL, "Component failed to call parent onUnRegister" ); - - // Recurse - (*i)->_unregisterComponents(); - } - - unlockComponentList(); -} - -void SimComponent::onRemove() -{ - //Con::executef(this, 1, "onRemove"); - - _unregisterComponents(); - - // Delete all components - VectorPtr&componentList = lockComponentList(); - while(componentList.size() > 0) - { - SimComponent *c = componentList[0]; - componentList.erase( componentList.begin() ); - - if( c->isProperlyAdded() ) - c->deleteObject(); - else if( !c->isRemoved() && !c->isDeleted() ) - delete c; - // else, something else is deleting this, don't mess with it - } - unlockComponentList(); - - Parent::onRemove(); -} - -////////////////////////////////////////////////////////////////////////// - -bool SimComponent::processArguments(S32 argc, ConsoleValueRef *argv) -{ - for(S32 i = 0; i < argc; i++) - { - SimComponent *obj = dynamic_cast (Sim::findObject(argv[i]) ); - if(obj) - addComponent(obj); - else - Con::printf("SimComponent::processArguments - Invalid Component Object \"%s\"", (const char*)argv[i]); - } - return true; -} - -////////////////////////////////////////////////////////////////////////// - -void SimComponent::initPersistFields() -{ - addGroup("Component"); - - addProtectedField( "Template", TypeBool, Offset(mTemplate, SimComponent), - &setIsTemplate, &defaultProtectedGetFn, - "Places the object in a component set for later use in new levels." ); - - endGroup("Component"); - - // Call Parent. - Parent::initPersistFields(); -} - -//------------------------------------------------------------------------------ - -bool SimComponent::getInterfaces( ComponentInterfaceList *list, const char *type /* = NULL */, const char *name /* = NULL */, - const SimComponent *owner /* = NULL */, bool notOwner /* = false */ ) -{ - AssertFatal( list != NULL, "Passing NULL for a list is not supported functionality for SimComponents." ); - return ( mInterfaceCache.enumerate( list, type, name, owner, notOwner ) > 0 ); -} - -bool SimComponent::registerCachedInterface( const char *type, const char *name, SimComponent *interfaceOwner, ComponentInterface *cinterface ) -{ - if( mInterfaceCache.add( type, name, interfaceOwner, cinterface ) ) - { - cinterface->mOwner = interfaceOwner; - - // Recurse - if( mOwner != NULL ) - return mOwner->registerCachedInterface( type, name, interfaceOwner, cinterface ); - - return true; - } - - // So this is not a good assert, because it will get triggered due to the recursive - // nature of interface caching. I want to keep it here, though, just so nobody - // else thinks, "Oh I'll add an assert here." - // - //AssertFatal( false, avar( "registerCachedInterface failed, probably because interface with type '%s', name '%s' and owner with SimObjectId '%d' already exists", - // type, name, interfaceOwner->getId() ) ); - - return false; -} - -////////////////////////////////////////////////////////////////////////// -// Component Management -////////////////////////////////////////////////////////////////////////// - -bool SimComponent::addComponentFromField( void* obj, const char* data ) -{ - SimComponent *pComponent = dynamic_cast( Sim::findObject( data ) ); - if( pComponent != NULL ) - static_cast(obj)->addComponent( pComponent ); - return false; -} - -// Add Component to this one -bool SimComponent::addComponent( SimComponent *component ) -{ - AssertFatal( dynamic_cast(component), "SimComponent - Cannot add non SimObject derived components!" ); - - MutexHandle mh; - if( mh.lock( mMutex, true ) ) - { - for( SimComponentIterator nItr = mComponentList.begin(); nItr != mComponentList.end(); nItr++ ) - { - SimComponent *pComponent = dynamic_cast(*nItr); - AssertFatal( pComponent, "SimComponent::addComponent - NULL component in list!" ); - if( pComponent == component ) - return true; - } - - if(component->onComponentAdd(this)) - { - component->mOwner = this; - mComponentList.push_back( component ); - return true; - } - } - - return false; -} - -// Remove Component from this one -bool SimComponent::removeComponent( SimComponent *component ) -{ - MutexHandle mh; - if( mh.lock( mMutex, true ) ) - { - for( SimComponentIterator nItr = mComponentList.begin(); nItr != mComponentList.end(); nItr++ ) - { - SimComponent *pComponent = dynamic_cast(*nItr); - AssertFatal( pComponent, "SimComponent::removeComponent - NULL component in list!" ); - if( pComponent == component ) - { - AssertFatal( component->mOwner == this, "Somehow we contain a component who doesn't think we are it's owner." ); - (*nItr)->onComponentRemove(this); - component->mOwner = NULL; - mComponentList.erase( nItr ); - return true; - } - } - } - return false; -} - -////////////////////////////////////////////////////////////////////////// - -bool SimComponent::onComponentAdd(SimComponent *target) -{ - Con::executef(this, "onComponentAdd", Con::getIntArg(target->getId())); - return true; -} - -void SimComponent::onComponentRemove(SimComponent *target) -{ - Con::executef(this, "onComponentRemove", Con::getIntArg(target->getId())); -} - -////////////////////////////////////////////////////////////////////////// - -ComponentInterface *SimComponent::getInterface(const char *type /* = NULL */, const char *name /* = NULL */, - const SimComponent *owner /* = NULL */, bool notOwner /* = false */) -{ - ComponentInterfaceList iLst; - - if( getInterfaces( &iLst, type, name, owner, notOwner ) ) - return iLst[0]; - - return NULL; -} - -////////////////////////////////////////////////////////////////////////// - -bool SimComponent::writeField(StringTableEntry fieldname, const char* value) -{ - if (!Parent::writeField(fieldname, value)) - return false; - - if( fieldname == StringTable->insert("owner") ) - return false; - - return true; -} - -void SimComponent::write(Stream &stream, U32 tabStop, U32 flags /* = 0 */) -{ - MutexHandle handle; - handle.lock(mMutex); // When this goes out of scope, it will unlock it - - // export selected only? - if((flags & SelectedOnly) && !isSelected()) - { - for(U32 i = 0; i < mComponentList.size(); i++) - mComponentList[i]->write(stream, tabStop, flags); - - return; - } - - stream.writeTabs(tabStop); - char buffer[1024]; - dSprintf(buffer, sizeof(buffer), "new %s(%s) {\r\n", getClassName(), getName() ? getName() : ""); - stream.write(dStrlen(buffer), buffer); - writeFields(stream, tabStop + 1); - - if(mComponentList.size()) - { - stream.write(2, "\r\n"); - - stream.writeTabs(tabStop+1); - stream.writeLine((U8 *)"// Note: This is a list of behaviors, not arbitrary SimObjects as in a SimGroup or SimSet.\r\n"); - - for(U32 i = 0; i < mComponentList.size(); i++) - mComponentList[i]->write(stream, tabStop + 1, flags); - } - - stream.writeTabs(tabStop); - stream.write(4, "};\r\n"); -} - -////////////////////////////////////////////////////////////////////////// -// Console Methods -////////////////////////////////////////////////////////////////////////// - -ConsoleMethod( SimComponent, addComponents, bool, 3, 64, "%obj.addComponents( %compObjName, %compObjName2, ... );\n" - "Adds additional components to current list.\n" - "@param Up to 62 component names\n" - "@return Returns true on success, false otherwise.") -{ - for(S32 i = 2; i < argc; i++) - { - SimComponent *obj = dynamic_cast (Sim::findObject(argv[i]) ); - if(obj) - object->addComponent(obj); - else - Con::printf("SimComponent::addComponents - Invalid Component Object \"%s\"", (const char*)argv[i]); - } - return true; -} - -ConsoleMethod( SimComponent, removeComponents, bool, 3, 64, "%obj.removeComponents( %compObjName, %compObjName2, ... );\n" - "Removes components by name from current list.\n" - "@param objNamex Up to 62 component names\n" - "@return Returns true on success, false otherwise.") -{ - for(S32 i = 2; i < argc; i++) - { - SimComponent *obj = dynamic_cast (Sim::findObject(argv[i]) ); - if(obj) - object->removeComponent(obj); - else - Con::printf("SimComponent::removeComponents - Invalid Component Object \"%s\"", (const char*)argv[i]); - } - return true; -} - -DefineConsoleMethod( SimComponent, getComponentCount, S32, (), , "() Get the current component count\n" - "@return The number of components in the list as an integer") -{ - return object->getComponentCount(); -} - -DefineConsoleMethod( SimComponent, getComponent, S32, (S32 idx), , "(idx) Get the component corresponding to the given index.\n" - "@param idx An integer index value corresponding to the desired component.\n" - "@return The id of the component at the given index as an integer") -{ - if(idx < 0 || idx >= object->getComponentCount()) - { - Con::errorf("SimComponent::getComponent - Invalid index %d", idx); - return 0; - } - - SimComponent *c = object->getComponent(idx); - return c ? c->getId() : 0; -} - -DefineConsoleMethod(SimComponent, setEnabled, void, (bool enabled), , "(enabled) Sets or unsets the enabled flag\n" - "@param enabled Boolean value\n" - "@return No return value") -{ - object->setEnabled(enabled); -} - -DefineConsoleMethod(SimComponent, isEnabled, bool, (), , "() Check whether SimComponent is currently enabled\n" - "@return true if enabled and false if not") -{ - return object->isEnabled(); -} - -DefineConsoleMethod(SimComponent, setIsTemplate, void, (bool templateFlag), , "(template) Sets or unsets the template flag\n" - "@param template Boolean value\n" - "@return No return value") -{ - object->setIsTemplate(templateFlag); -} - -DefineConsoleMethod(SimComponent, getIsTemplate, bool, (), , "() Check whether SimComponent is currently a template\n" - "@return true if is a template and false if not") -{ - return object->getIsTemplate(); -} diff --git a/Engine/source/component/simComponent.h b/Engine/source/component/simComponent.h deleted file mode 100644 index 70e41f69e..000000000 --- a/Engine/source/component/simComponent.h +++ /dev/null @@ -1,256 +0,0 @@ -//----------------------------------------------------------------------------- -// 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 _SIMCOMPONENT_H_ -#define _SIMCOMPONENT_H_ - -#ifndef _TVECTOR_H_ -#include "core/util/tVector.h" -#endif -#ifndef _STRINGTABLE_H_ -#include "core/stringTable.h" -#endif -#ifndef _NETOBJECT_H_ -#include "sim/netObject.h" -#endif -#ifndef _COMPONENTINTERFACE_H_ -#include "component/componentInterface.h" -#endif -#ifndef _PLATFORM_THREADS_MUTEX_H_ -#include "platform/threads/mutex.h" -#endif -#ifndef _STRINGFUNCTIONS_H_ -#include "core/strings/stringFunctions.h" -#endif - -// Forward refs -class Stream; -class ComponentInterface; -class ComponentInterfaceCache; - -class SimComponent : public NetObject -{ - typedef NetObject Parent; - -private: - VectorPtr mComponentList; ///< The Component List - void *mMutex; ///< Component List Mutex - - SimObjectPtr mOwner; ///< The component which owns this one. - - /// This is called internally to instruct the component to iterate over it's - // list of components and recursively call _registerInterfaces on their lists - // of components. - void _registerInterfaces( SimComponent *owner ); - - bool _registerComponents( SimComponent *owner ); - void _unregisterComponents(); - -protected: - ComponentInterfaceCache mInterfaceCache; ///< Stores the interfaces exposed by this component. - - bool mEnabled; - - bool mTemplate; - - // Non-const getOwner for derived classes - SimComponent *_getOwner() { return mOwner; } - - /// Returns a const reference to private mComponentList - typedef VectorPtr::iterator SimComponentIterator; - VectorPtr &lockComponentList() - { - Mutex::lockMutex( mMutex ); - return mComponentList; - }; - - void unlockComponentList() - { - Mutex::unlockMutex( mMutex ); - } - - /// onComponentRegister is called on each component by it's owner. If a component - /// has no owner, onComponentRegister will not be called on it. The purpose - /// of onComponentRegister is to allow a component to check for any external - /// interfaces, or other dependencies which it needs to function. If any component - /// in a component hierarchy returns false from it's onComponentRegister call - /// the entire hierarchy is invalid, and SimObject::onAdd will fail on the - /// top-level component. To put it another way, if a component contains other - /// components, it will be registered successfully with Sim iff each subcomponent - /// returns true from onComponentRegister. If a component does not contain - /// other components, it will not receive an onComponentRegister call. - /// - /// Overloads of this method must pass the call along to their parent, as is - /// shown in the example below. - /// - /// @code - /// bool FooComponent::onComponentRegister( SimComponent *owner ) - /// { - /// if( !Parent::onComponentRegister( owner ) ) - /// return false; - /// ... - /// } - /// @endcode - virtual bool onComponentRegister( SimComponent *owner ) - { - mOwner = owner; - return true; - } - - /// onUnregister is called when the owner is unregistering. Your object should - /// do cleanup here, as well as pass a call up the chain to the parent. - virtual void onComponentUnRegister() - { - mOwner = NULL; - } - - /// registerInterfaces is called on each component as it's owner is registering - /// it's interfaces. This is called before onComponentRegister, and should be used to - /// register all interfaces exposed by your component, as well as all callbacks - /// needed by your component. - virtual void registerInterfaces( SimComponent *owner ) - { - - } - -public: - DECLARE_CONOBJECT(SimComponent); - - /// Constructor - /// Add this component - SimComponent(); - - /// Destructor - /// Remove this component and destroy child references - virtual ~SimComponent(); - -public: - - virtual bool onAdd(); - virtual void onRemove(); - - static void initPersistFields(); - - virtual bool processArguments(S32 argc, ConsoleValueRef *argv); - - bool isEnabled() const { return mEnabled; } - - void setEnabled( bool value ) { mEnabled = value; } - - /// Will return true if this object contains components. - bool hasComponents() const { return ( mComponentList.size() > 0 ); }; - - /// The component which owns this object - const SimComponent *getOwner() const { return mOwner; }; - - // Component Information - inline virtual StringTableEntry getComponentName() { return StringTable->insert( getClassName() ); }; - - /// Protected 'Component' Field setter that will add a component to the list. - static bool addComponentFromField(void* obj, const char* data); - - /// Add Component to this one - virtual bool addComponent( SimComponent *component ); - - /// Remove Component from this one - virtual bool removeComponent( SimComponent *component ); - - /// Clear Child components of this one - virtual bool clearComponents() { mComponentList.clear(); return true; }; - - virtual bool onComponentAdd(SimComponent *target); - virtual void onComponentRemove(SimComponent *target); - - S32 getComponentCount() { return mComponentList.size(); } - SimComponent *getComponent(S32 idx) { return mComponentList[idx]; } - - SimComponentIterator find(SimComponentIterator first, SimComponentIterator last, SimComponent *value) - { - return ::find(first, last, value); - } - - static bool setIsTemplate( void *object, const char *index, const char *data ) - { static_cast(object)->setIsTemplate( dAtob( data ) ); return false; }; - virtual void setIsTemplate( const bool pTemplate ) { mTemplate = pTemplate; } - bool getIsTemplate() const { return mTemplate; } - - virtual void write(Stream &stream, U32 tabStop, U32 flags = 0); - virtual bool writeField(StringTableEntry fieldname, const char* value); - - - /// getInterfaces allows the caller to enumerate the interfaces exposed by - /// this component. This method can be overwritten to expose interfaces - /// which are not cached on the object, before passing the call to the Parent. - /// This can be used delay interface creation until it is queried for, instead - /// of creating it on initialization, and caching it. Returns false if no results - /// were found - /// - /// @param list The list that this method will append search results on to. - /// @param type An expression which the 'type' field on an added object must match to be included in results - /// @param name An expression which the 'name' field on an added object must match to be included in results - /// @param owner Limit results to components owned/not-owned by this SimComponent (see next param) - /// @param notOwner If set to true, this will enumerate only interfaces NOT owned by 'owner' - virtual bool getInterfaces( ComponentInterfaceList *list, const char *type = NULL, const char *name = NULL, const SimComponent *owner = NULL, bool notOwner = false ); // const omission intentional - - - /// These two methods allow for easy query of component interfaces if you know - /// exactly what you are looking for, and don't mind being passed back the first - /// matching result. - ComponentInterface *getInterface( const char *type = NULL, const char *name = NULL, const SimComponent *owner = NULL, bool notOwner = false ); - - template - T *getInterface( const char *type = NULL, const char *name = NULL, const SimComponent *owner = NULL, bool notOwner = false ); - - /// Add an interface to the cache. This function will return true if the interface - /// is added successfully. An interface will not be added successfully if an entry - /// in this components cache with the same values for 'type' and 'name' is present. - /// - /// @param type Type of the interface being added. If NULL is passed, it will match any type string queried. - /// @param name Name of interface being added. If NULL is passed, it will match any name string queried. - /// @param interfaceOwner The component which owns the interface being cached - /// @param cinterface The ComponentInterface being cached - bool registerCachedInterface( const char *type, const char *name, SimComponent *interfaceOwner, ComponentInterface *cinterface ); -}; - -////////////////////////////////////////////////////////////////////////// - -template -T *SimComponent::getInterface( const char *type /* = NULL */, const char *name /* = NULL */, - const SimComponent *owner /* = NULL */, bool notOwner /* = false */ ) -{ - ComponentInterfaceList iLst; - - if( getInterfaces( &iLst, type, name, owner, notOwner ) ) - { - ComponentInterfaceListIterator itr = iLst.begin(); - - while( dynamic_cast( *itr ) == NULL ) - itr++; - - if( itr != iLst.end() ) - return static_cast( *itr ); - } - - return NULL; -} - -#endif // _SIMCOMPONENT_H_ diff --git a/Engine/source/component/simpleComponent.h b/Engine/source/component/simpleComponent.h deleted file mode 100644 index e4c32bfb3..000000000 --- a/Engine/source/component/simpleComponent.h +++ /dev/null @@ -1,159 +0,0 @@ -//----------------------------------------------------------------------------- -// 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 _SIMPLECOMPONENT_H_ -#define _SIMPLECOMPONENT_H_ - -#ifndef _SIMCOMPONENT_H_ -#include "component/simComponent.h" -#endif - -#ifndef _COMPONENTINTERFACE_H_ -#include "component/componentInterface.h" -#endif - -/// This is a very simple interface. Interfaces provide ways for components to -/// interact with each-other, and query each-other for functionality. It makes it -/// possible for two components to be interdependent on one another, as well. An -/// interface should make accessor calls to it's owner for functionality, and -/// generally be as thin of a layer as possible. -class SimpleComponentInterface : public ComponentInterface -{ -public: - bool isFortyTwo( const U32 test ); -}; - -////////////////////////////////////////////////////////////////////////// -/// The purpose of this component is to provide a minimalistic component that -/// exposes a simple, cached interface -class SimpleComponent : public SimComponent -{ - typedef SimComponent Parent; - -protected: - SimpleComponentInterface mSCInterface; - -public: - // Components are still SimObjects, and need to be declared and implemented - // with the standard macros - DECLARE_CONOBJECT(SimpleComponent); - - ////////////////////////////////////////////////////////////////////////// - // SimComponent overloads. - - // This method is called on each component as it's parent is getting onAdd - // called. The purpose of overloading this method is to expose cached interfaces - // before onComponentRegister is called, so that other components can depend on the - // interfaces you expose in order to register properly. This functionality - // will be demonstrated in a more advanced example. - virtual void registerInterfaces( SimComponent *owner ) - { - // While it is not imperative that we pass this call to the Parent in this - // example, if there existed a class-heirarchy of components, it would be - // critical, so for good practice, call up to the Parent. - Parent::registerInterfaces( owner ); - - // Now we should go ahead and register our cached interface. What we are doing - // is telling the component which contains this component (if it exists) - // all of the interfaces that we expose. When this call is made, it will - // recurse up the owner list. - // - // For example, there exists components A, B, and C. - // A owns B, and B owns C. - // - // If C exposes a cached interface, it will expose it via registerCachedInterface - // when registerInterfaces is recursively called. It will add it's interface to - // it's cache list, and then pass the register call up to it's parent. The parent - // will also cache the interface, and continue to pass the cache call up the - // child->parent chain until there exists no parent. - // - // The result is that, if C exposes an interface 'foo', and A owns B, and - // B owns C, an interface request for 'foo' given to component A will result - // in 'foo' being returned, even though A does not expose 'foo'. This makes - // it possible for a component to query it's owner for an interface, and - // not care where that interface is exposed. It also allows for game code - // to work with any SimComponent and query that component for any interface - // it wants without knowing or caring exactly where it is coming from. - // - // registerCachedInterface returns a boolean value if it was successful. - // Success results in the caching of this interface throughout the full - // child->parent chain. An interface will be added to a cache list - // successfully iff there exists no entry in that list that has matching - // values for 'type', 'name' and 'owner'. - owner->registerCachedInterface( - // The first parameter is the 'type' of the interface, this is not to be - // confused with any kind of existing console, or c++ type. It is simply - // a string which is can be set to any value - "example", - - // The next parameter is the 'name' of the interface. This is also a string - // which can be set to any value - "isfortytwo", - - // The owner of the interface. Note that the value being assigned here - // is this instance of SimpleComponent, and not the 'owner' argument - // of the function registerInterfaces that we are calling from. - this, - - // And finally the interface; a pointer to an object with type ComponentInterface - &mSCInterface ); - } - - ////////////////////////////////////////////////////////////////////////// - // Specific functionality - - /// This is the test method, it will return true if the number provided - /// is forty two - bool isFortyTwo( const U32 test ) const - { - return ( test == 42 ); - } -}; - -////////////////////////////////////////////////////////////////////////// -// Interface implementation -// -// Since interfaces themselves implement very little functionality, it is a good -// idea to inline them if at all possible. Interdependent components will be using -// these interfaces constantly, and so putting as thin of a layer between the -// functionality they expose, and the functionality the component implements is -// a good design practice. -inline bool SimpleComponentInterface::isFortyTwo( const U32 test ) -{ - // This code block will test for a valid owner in a debug build before - // performing operations on it's owner. It is worth noting that the - // ComponentInterface::isValid() method can be overridden to include - // validation specific to your interface and/or component. - AssertFatal( isValid(), "SimpleComponentInterface has not been registered properly by the component which exposes it." ); - - // This is a sanity check. The owner of this interface should have the type - // SimpleComponent, otherwise this won't work. (See further interface examples - // for some ways around this) - AssertFatal( dynamic_cast( getOwner() ) != NULL, "Owner of SimpleComponentInterface is not a SimpleComponent" ); - - // Component interfaces rely on being registered to set their mOwner - // field. This field is initialized to NULL, and then gets set by - // SimComponent when the interface is registered. - return static_cast( getOwner() )->isFortyTwo( test ); -} - -#endif \ No newline at end of file diff --git a/Engine/source/component/test/moreAdvancedComponentTest.cpp b/Engine/source/component/test/moreAdvancedComponentTest.cpp deleted file mode 100644 index a6a7335f4..000000000 --- a/Engine/source/component/test/moreAdvancedComponentTest.cpp +++ /dev/null @@ -1,68 +0,0 @@ -//----------------------------------------------------------------------------- -// Copyright (c) 2014 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. -//----------------------------------------------------------------------------- - -#ifdef TORQUE_TESTS_ENABLED -#include "testing/unitTesting.h" -#include "component/moreAdvancedComponent.h" - -TEST(MoreAdvancedComponent, MoreAdvancedComponent) -{ - // Create component instances and compose them. - SimComponent *parentComponent = new SimComponent(); - SimpleComponent *simpleComponent = new SimpleComponent(); - MoreAdvancedComponent *moreAdvComponent = new MoreAdvancedComponent(); - // CodeReview note that the interface pointer isn't initialized in a ctor - // on the components, so it's bad memory against which you might - // be checking in testDependentInterface [3/3/2007 justind] - parentComponent->addComponent( simpleComponent ); - parentComponent->addComponent( moreAdvComponent ); - - simpleComponent->registerObject(); - moreAdvComponent->registerObject(); - - // Put a break-point here, follow the onAdd call, and observe the order in - // which the SimComponent::onAdd function executes. You will see the interfaces - // get cached, and the dependent interface query being made. - parentComponent->registerObject(); - - // If the MoreAdvancedComponent found an interface, than the parentComponent - // should have returned true, from onAdd, and should therefore be registered - // properly with the Sim - EXPECT_TRUE( parentComponent->isProperlyAdded() ) - << "Parent component not properly added!"; - - // Now lets test the interface. You can step through this, as well. - EXPECT_TRUE( moreAdvComponent->testDependentInterface() ) - << "Dependent interface test failed."; - - // CodeReview is there a reason we can't just delete the parentComponent here? [3/3/2007 justind] - // - // Clean up - parentComponent->removeComponent( simpleComponent ); - parentComponent->removeComponent( moreAdvComponent ); - - parentComponent->deleteObject(); - moreAdvComponent->deleteObject(); - simpleComponent->deleteObject(); -}; - -#endif \ No newline at end of file diff --git a/Engine/source/component/test/simComponentTest.cpp b/Engine/source/component/test/simComponentTest.cpp deleted file mode 100644 index 407e3adce..000000000 --- a/Engine/source/component/test/simComponentTest.cpp +++ /dev/null @@ -1,149 +0,0 @@ -//----------------------------------------------------------------------------- -// Copyright (c) 2014 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. -//----------------------------------------------------------------------------- - -#ifdef TORQUE_TESTS_ENABLED -#include "testing/unitTesting.h" -#include "component/simComponent.h" - -class CachedInterfaceExampleComponent : public SimComponent -{ - typedef SimComponent Parent; - - ComponentProperty mMyId; - static U32 smNumInstances; - ComponentProperty *mpU32; // CodeReview [patw, 2, 17, 2007] Make ref objects when this is in Jugg - -public: - DECLARE_CONOBJECT( CachedInterfaceExampleComponent ); - - CachedInterfaceExampleComponent() : mpU32( NULL ) - { - mMyId = ( ( 1 << 24 ) | smNumInstances++ ); - } - virtual ~CachedInterfaceExampleComponent() - { - smNumInstances--; - } - -public: - ////////////////////////////////////////////////////////////////////////// - - virtual void registerInterfaces( SimComponent *owner ) - { - // Register a cached interface for this - owner->registerCachedInterface( NULL, "aU32", this, &mMyId ); - } - - ////////////////////////////////////////////////////////////////////////// - - bool onComponentRegister( SimComponent *owner ) - { - // Call up to the parent first - if( !Parent::onComponentRegister( owner ) ) - return false; - - // We want to get an interface from another object in our containing component - // to simulate component interdependency. - ComponentInterfaceList list; - - // Enumerate the interfaces on the owner, only ignore interfaces that this object owns - if( !owner->getInterfaces( &list, NULL, "aU32", this, true ) ) - return false; - - // Sanity check before just assigning all willy-nilly - for( ComponentInterfaceListIterator i = list.begin(); i != list.end(); i++ ) - { - mpU32 = dynamic_cast *>( (*i) ); - - if( mpU32 != NULL ) - return true; - } - - return false; - } - - ////////////////////////////////////////////////////////////////////////// - - // CodeReview [patw, 2, 17, 2007] I'm going to make another lightweight interface - // for this functionality later - void unit_test() - { - EXPECT_TRUE( mpU32 != NULL ) - << "Pointer to dependent interface is NULL"; - if( mpU32 ) - { - EXPECT_TRUE( *(*mpU32) & ( 1 << 24 ) ) - << "Pointer to interface data is bogus."; - EXPECT_TRUE( *(*mpU32) != *mMyId ) - << "Two of me have the same ID, bad!"; - } - } -}; - -IMPLEMENT_CONOBJECT( CachedInterfaceExampleComponent ); -U32 CachedInterfaceExampleComponent::smNumInstances = 0; - -ConsoleDocClass( CachedInterfaceExampleComponent, - "@brief Legacy from older component system.\n\n" - "Not intended for game development, for editors or internal use only.\n\n " - "@internal"); - -TEST(SimComponent, Composition) -{ - SimComponent *testComponent = new SimComponent(); - CachedInterfaceExampleComponent *componentA = new CachedInterfaceExampleComponent(); - CachedInterfaceExampleComponent *componentB = new CachedInterfaceExampleComponent(); - - // Register sub-components - EXPECT_TRUE( componentA->registerObject() ) - << "Failed to register componentA"; - EXPECT_TRUE( componentB->registerObject() ) - << "Failed to register componentB"; - - // Add the components - EXPECT_TRUE( testComponent->addComponent( componentA ) ) - << "Failed to add component a to testComponent"; - EXPECT_TRUE( testComponent->addComponent( componentB ) ) - << "Failed to add component b to testComponent"; - - EXPECT_EQ( componentA->getOwner(), testComponent ) - << "testComponent did not properly set the mOwner field of componentA to NULL."; - EXPECT_EQ( componentB->getOwner(), testComponent ) - << "testComponent did not properly set the mOwner field of componentB to NULL."; - - // Register the object with the simulation, kicking off the interface registration - ASSERT_TRUE( testComponent->registerObject() ) - << "Failed to register testComponent"; - - { - SCOPED_TRACE("componentA"); - componentA->unit_test(); - } - { - SCOPED_TRACE("componentB"); - componentB->unit_test(); - } - - testComponent->deleteObject(); -}; - -#endif \ No newline at end of file diff --git a/Engine/source/component/test/simpleComponentTest.cpp b/Engine/source/component/test/simpleComponentTest.cpp deleted file mode 100644 index a528e918f..000000000 --- a/Engine/source/component/test/simpleComponentTest.cpp +++ /dev/null @@ -1,131 +0,0 @@ -//----------------------------------------------------------------------------- -// Copyright (c) 2014 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. -//----------------------------------------------------------------------------- - -#ifdef TORQUE_TESTS_ENABLED -#include "testing/unitTesting.h" -#include "component/simpleComponent.h" - -TEST(SimpleComponent, SimpleComponent) -{ - // When instantiating, and working with a SimObject in C++ code, such as - // a unit test, you *may not* allocate a SimObject off of the stack. - // - // For example: - // SimpleComponent sc; - // is a stack allocation. This memory is allocated off of the program stack - // when the function is called. SimObject deletion is done via SimObject::deleteObject() - // and the last command of this method is 'delete this;' That command will - // cause an assert if it is called on stack-allocated memory. Therefor, when - // instantiating SimObjects in C++ code, it is imperitive that you keep in - // mind that if any script calls 'delete()' on that SimObject, or any other - // C++ code calls 'deleteObject()' on that SimObject, it will crash. - SimpleComponent *sc = new SimpleComponent(); - - // SimObject::registerObject must be called on a SimObject before it is - // fully 'hooked in' to the engine. - // - // Tracing execution of this function will let you see onAdd get called on - // the component, and you will see it cache the interface we exposed. - sc->registerObject(); - - // It is *not* required that a component always be owned by a component (obviously) - // however I am using an owner so that you can trace execution of recursive - // calls to cache interfaces and such. - SimComponent *testOwner = new SimComponent(); - - // Add the test component to it's owner. This will set the 'mOwner' field - // of 'sc' to the address of 'testOwner' - testOwner->addComponent( sc ); - - // If you step-into this registerObject the same way as the previous one, - // you will be able to see the recursive caching of the exposed interface. - testOwner->registerObject(); - - // Now to prove that object composition is working properly, lets ask - // both of these components for their interface lists... - - // The ComponentInterfaceList is a typedef for type 'VectorPtr' - // and it will be used by getInterfaces() to store the results of the interface - // query. This is the "complete" way to obtain an interface, and it is too - // heavy-weight for most cases. A simplified query will be performed next, - // to demonstrate the usage of both. - ComponentInterfaceList iLst; - - // This query requests all interfaces, on all components, regardless of name - // or owner. - sc->getInterfaces( &iLst, - // This is the type field. I am passing NULL here to signify that the query - // should match all values of 'type' in the list. - NULL, - - // The name field, let's pass NULL again just so when you trace execution - // you can see how queries work in the simple case, first. - NULL ); - - // Lets process the list that we've gotten back, and find the interface that - // we want. - SimpleComponentInterface *scQueriedInterface = NULL; - - for( ComponentInterfaceListIterator i = iLst.begin(); i != iLst.end(); i++ ) - { - scQueriedInterface = dynamic_cast( *i ); - - if( scQueriedInterface != NULL ) - break; - } - - AssertFatal( scQueriedInterface != NULL, "No valid SimpleComponentInterface was found in query" ); - - // Lets do it again, only we will execute the query on the parent instead, - // in a simplified way. Remember the parent component doesn't expose any - // interfaces at all, so the success of this behavior is entirely dependent - // on the recursive registration that occurs in registerInterfaces() - SimpleComponentInterface *ownerQueriedInterface = testOwner->getInterface(); - - AssertFatal( ownerQueriedInterface != NULL, "No valid SimpleComponentInterface was found in query" ); - - // We should now have two pointers to the same interface obtained by querying - // different components. - EXPECT_EQ( ownerQueriedInterface, scQueriedInterface ) - << "This really shouldn't be possible to fail given the setup of the test"; - - // Lets call the method that was exposed on the component via the interface. - // Trace the execution of this function, if you wish. - EXPECT_TRUE( ownerQueriedInterface->isFortyTwo( 42 ) ) - << "Don't panic, but it's a bad day in the component system."; - EXPECT_TRUE( scQueriedInterface->isFortyTwo( 42 ) ) - << "Don't panic, but it's a bad day in the component system."; - - // So there you have it. Writing a simple component that exposes a cached - // interface, and testing it. It's time to clean up. - testOwner->removeComponent( sc ); - - sc->deleteObject(); - testOwner->deleteObject(); - - // Interfaces do not need to be freed. In Juggernaught, these will be ref-counted - // for more robust behavior. Right now, however, the values of our two interface - // pointers, scQueriedInterface and ownerQueriedInterface, reference invalid - // memory. -}; - -#endif \ No newline at end of file diff --git a/Engine/source/console/compiledEval.cpp b/Engine/source/console/compiledEval.cpp index bd4a7a5d9..a989826b4 100644 --- a/Engine/source/console/compiledEval.cpp +++ b/Engine/source/console/compiledEval.cpp @@ -994,6 +994,7 @@ breakContinue: // This error is usually caused by failing to call Parent::initPersistFields in the class' initPersistFields(). Con::warnf(ConsoleLogEntry::General, "%s: Register object failed for object %s of class %s.", getFileLine(ip), currentNewObject->getName(), currentNewObject->getClassName()); delete currentNewObject; + currentNewObject = NULL; ip = failJump; // Prevent stack value corruption CSTK.popFrame(); @@ -1094,6 +1095,9 @@ breakContinue: case OP_FINISH_OBJECT: { + if (currentNewObject) + currentNewObject->onPostAdd(); + //Assert( objectCreationStackIndex >= 0 ); // Restore the object info from the stack [7/9/2007 Black] currentNewObject = objectCreationStack[ --objectCreationStackIndex ].newObject; diff --git a/Engine/source/console/consoleObject.h b/Engine/source/console/consoleObject.h index 8ecbb8031..749129ba1 100644 --- a/Engine/source/console/consoleObject.h +++ b/Engine/source/console/consoleObject.h @@ -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 diff --git a/Engine/source/console/consoleTypes.cpp b/Engine/source/console/consoleTypes.cpp index 6c993c446..09765bf34 100644 --- a/Engine/source/console/consoleTypes.cpp +++ b/Engine/source/console/consoleTypes.cpp @@ -710,6 +710,31 @@ ConsoleSetType( TypeColorI ) Con::printf("Color must be set as { r, g, b [,a] }, { r g b [b] } or { stockColorName }"); } +//----------------------------------------------------------------------------- +// TypeSimObjectPtr +//----------------------------------------------------------------------------- +ConsoleType(SimObject, TypeSimObjectPtr, SimObject*, "") + +ConsoleSetType(TypeSimObjectPtr) +{ + if (argc == 1) + { + SimObject **obj = (SimObject **)dptr; + *obj = Sim::findObject(argv[0]); + } + else + Con::printf("(TypeSimObjectPtr) Cannot set multiple args to a single S32."); +} + +ConsoleGetType(TypeSimObjectPtr) +{ + SimObject **obj = (SimObject**)dptr; + static const U32 bufSize = 128; + char* returnBuffer = Con::getReturnBuffer(bufSize); + dSprintf(returnBuffer, bufSize, "%s", *obj ? (*obj)->getName() ? (*obj)->getName() : (*obj)->getIdString() : ""); + return returnBuffer; +} + //----------------------------------------------------------------------------- // TypeSimObjectName //----------------------------------------------------------------------------- diff --git a/Engine/source/console/consoleTypes.h b/Engine/source/console/consoleTypes.h index ab5b97d62..8080a8830 100644 --- a/Engine/source/console/consoleTypes.h +++ b/Engine/source/console/consoleTypes.h @@ -125,6 +125,8 @@ DefineConsoleType( TypeColorF, ColorF ) DefineConsoleType( TypeSimObjectName, SimObject* ) DefineConsoleType( TypeShader, GFXShader * ) +DefineConsoleType(TypeSimObjectPtr, SimObject*) + /// A persistent reference to an object. This reference indirectly goes /// through the referenced object's persistent ID. DefineConsoleType( TypeSimPersistId, SimPersistID* ) diff --git a/Engine/source/console/simObject.h b/Engine/source/console/simObject.h index eb6f3f0e0..8a38e8675 100644 --- a/Engine/source/console/simObject.h +++ b/Engine/source/console/simObject.h @@ -606,6 +606,10 @@ class SimObject: public ConsoleObject, public TamlCallbacks /// Called when the object's name is changed. virtual void onNameChange(const char *name); + /// Called when the adding of the object to the sim is complete, all sub-objects have been processed as well + // This is a special-case function that only really gets used with Entities/BehaviorObjects. + virtual void onPostAdd() {} + /// /// Specifically, these are called by setDataField /// when a static or dynamic field is modified, see diff --git a/Engine/source/gui/controls/guiTreeViewCtrl.cpp b/Engine/source/gui/controls/guiTreeViewCtrl.cpp index 99fb8b440..a5e833a19 100644 --- a/Engine/source/gui/controls/guiTreeViewCtrl.cpp +++ b/Engine/source/gui/controls/guiTreeViewCtrl.cpp @@ -36,7 +36,9 @@ #include "gui/editor/editorFunctions.h" #endif #include "console/engineAPI.h" - +#ifdef TORQUE_EXPERIMENTAL_EC +#include "T3D/entity.h" +#endif IMPLEMENT_CONOBJECT(GuiTreeViewCtrl); @@ -486,6 +488,14 @@ void GuiTreeViewCtrl::Item::getDisplayText(U32 bufLen, char *buf) { FrameAllocatorMarker txtAlloc; + //if we're doing the special case of forcing the item text, just skip the rest of this junk + if (mState.test(ForceItemName)) + { + StringTableEntry text = (mScriptInfo.mText) ? mScriptInfo.mText : StringTable->EmptyString(); + dStrncpy(buf, text, bufLen); + return; + } + if( mState.test( InspectorData ) ) { SimObject *pObject = getObject(); @@ -637,6 +647,20 @@ void GuiTreeViewCtrl::Item::getTooltipText(U32 bufLen, char *buf) bool GuiTreeViewCtrl::Item::isParent() const { +#ifdef TORQUE_EXPERIMENTAL_EC + //We might have a special case with entities + //So if our entity either has children, or has some component with the EditorInspect interface, we return true + if (mInspectorInfo.mObject) + { + Entity* e = dynamic_cast(mInspectorInfo.mObject.getObject()); + if (e) + { + if (e->size() > 0 || e->getComponentCount() != 0) + return true; + } + } +#endif + if(mState.test(VirtualParent)) { if( !isInspectorData() ) @@ -1518,6 +1542,11 @@ bool GuiTreeViewCtrl::isValidDragTarget( Item* item ) { bool isValid = true; + // First, check if we're just going to override this from manually setting the ForceAllowDrag flag + // If that's set, we're assuming special circumstances and will just let it go on it's way + if (item->isDragTargetAllowed()) + return true; + // If this is inspector data, first make sure the item accepts all // selected objects as children. This prevents bad surprises when // certain SimSet subclasses reject children and start shoving them @@ -3462,6 +3491,11 @@ void GuiTreeViewCtrl::onMouseDragged(const GuiEvent &event) if (mSelectedItems.size() == 0) return; + //Check through to make sure all attempted dragged items even allow it + for (U32 i = 0; i < mSelectedItems.size(); i++) + if (!mSelectedItems[i]->isDragAllowed()) + return; + // Give us a little delta before we actually start a mouse drag so that // if the user moves the mouse a little while clicking, he/she does not // accidentally trigger a drag. @@ -3756,6 +3790,28 @@ void GuiTreeViewCtrl::onMouseDown(const GuiEvent & event) if( !item->isInspectorData() && item->mState.test(Item::VirtualParent) ) onVirtualParentExpand(item); +#ifdef TORQUE_EXPERIMENTAL_EC + //Slightly hacky, but I'm not sure of a better setup until we get major update to the editors + //We check if our object is an entity, and if it is, we call a 'onInspect' function. + //This function is pretty much a special notifier to the entity so if it has any behaviors that do special + //stuff in the editor, it can fire that up + if (item->isInspectorData()) + { + Entity* e = dynamic_cast(item->getObject()); + //if (item->mScriptInfo.mText != StringTable->insert("Components")) + { + Entity* e = dynamic_cast(item->getObject()); + if (e) + { + if (item->isExpanded()) + e->onInspect(); + else + e->onEndInspect(); + } + } + } +#endif + mFlags.set( RebuildVisible ); scrollVisible(item); } @@ -4492,6 +4548,13 @@ bool GuiTreeViewCtrl::objectSearch( const SimObject *object, Item **item ) if ( !pItem ) continue; +#ifdef TORQUE_EXPERIMENTAL_EC + //A bit hackish, but we make a special exception here for items that are named 'Components', as they're merely + //virtual parents to act as a container to an Entity's components + if (pItem->mScriptInfo.mText == StringTable->insert("Components")) + continue; +#endif + SimObject *pObj = pItem->getObject(); if ( pObj && pObj == object ) @@ -4555,6 +4618,11 @@ bool GuiTreeViewCtrl::onVirtualParentBuild(Item *item, bool bForceFullUpdate) // Go through our items and purge those that have disappeared from // the set. +#ifdef TORQUE_EXPERIMENTAL_EC + //Entities will be a special case here, if we're an entity, skip this step + if (dynamic_cast(srcObj)) + return true; +#endif for( Item* ptr = item->mChild; ptr != NULL; ) { diff --git a/Engine/source/gui/controls/guiTreeViewCtrl.h b/Engine/source/gui/controls/guiTreeViewCtrl.h index e2360ed87..91f842b3d 100644 --- a/Engine/source/gui/controls/guiTreeViewCtrl.h +++ b/Engine/source/gui/controls/guiTreeViewCtrl.h @@ -75,7 +75,10 @@ class GuiTreeViewCtrl : public GuiArrayCtrl ShowClassName = BIT( 11 ), ShowObjectName = BIT( 12 ), ShowInternalName = BIT( 13 ), - ShowClassNameForUnnamed = BIT( 14 ) + ShowClassNameForUnnamed = BIT( 14 ), + ForceItemName = BIT(15), + ForceDragTarget = BIT(16), + DenyDrag = BIT(17), }; GuiTreeViewCtrl* mParentControl; @@ -169,6 +172,14 @@ class GuiTreeViewCtrl : public GuiArrayCtrl /// or false if it's just an item. bool isInspectorData() const { return mState.test(InspectorData); }; + /// Returns true if we've been manually set to allow dragging overrides. + /// As it's a manually set flag, by default it is false. + bool isDragTargetAllowed() const { return mState.test(ForceDragTarget); }; + + /// Returns true if we've been manually set to allow dragging overrides. + /// As it's a manually set flag, by default it is false. + bool isDragAllowed() const { return !mState.test(DenyDrag); }; + /// Returns true if we should show the expand art /// and make the item interact with the mouse as if /// it were a parent. diff --git a/Engine/source/gui/core/guiCanvas.h b/Engine/source/gui/core/guiCanvas.h index 0a2e44fa5..fa213f56a 100644 --- a/Engine/source/gui/core/guiCanvas.h +++ b/Engine/source/gui/core/guiCanvas.h @@ -37,7 +37,7 @@ #include "core/util/tSignal.h" #endif -#include "component/interfaces/IProcessInput.h" +#include "platform/input/IProcessInput.h" #include "windowManager/platformWindowMgr.h" #include "gfx/gfxFence.h" diff --git a/Engine/source/gui/editor/guiInspector.cpp b/Engine/source/gui/editor/guiInspector.cpp index af12b2810..c1f54a24d 100644 --- a/Engine/source/gui/editor/guiInspector.cpp +++ b/Engine/source/gui/editor/guiInspector.cpp @@ -29,6 +29,11 @@ #include "gui/containers/guiScrollCtrl.h" #include "gui/editor/inspector/customField.h" +#ifdef TORQUE_EXPERIMENTAL_EC +#include "gui/editor/inspector/entityGroup.h" +#include "gui/editor/inspector/mountingGroup.h" +#include "gui/editor/inspector/componentGroup.h" +#endif IMPLEMENT_CONOBJECT(GuiInspector); @@ -584,6 +589,43 @@ void GuiInspector::refresh() mGroups.push_back(general); addObject(general); +#ifdef TORQUE_EXPERIMENTAL_EC + //Entity inspector group + if (mTargets.first()->getClassRep()->isSubclassOf("Entity")) + { + GuiInspectorEntityGroup *components = new GuiInspectorEntityGroup("Components", this); + if (components != NULL) + { + components->registerObject(); + mGroups.push_back(components); + addObject(components); + } + + //Mounting group override + GuiInspectorGroup *mounting = new GuiInspectorMountingGroup("Mounting", this); + if (mounting != NULL) + { + mounting->registerObject(); + mGroups.push_back(mounting); + addObject(mounting); + } + } + + if (mTargets.first()->getClassRep()->isSubclassOf("Component")) + { + //Build the component field groups as the component describes it + Component* comp = dynamic_cast(mTargets.first().getPointer()); + + if (comp->getComponentFieldCount() > 0) + { + GuiInspectorComponentGroup *compGroup = new GuiInspectorComponentGroup("Component Fields", this); + compGroup->registerObject(); + mGroups.push_back(compGroup); + addObject(compGroup); + } + } +#endif + // Create the inspector groups for static fields. for( TargetVector::iterator iter = mTargets.begin(); iter != mTargets.end(); ++ iter ) diff --git a/Engine/source/gui/editor/inspector/componentGroup.cpp b/Engine/source/gui/editor/inspector/componentGroup.cpp new file mode 100644 index 000000000..7d6d30eaa --- /dev/null +++ b/Engine/source/gui/editor/inspector/componentGroup.cpp @@ -0,0 +1,252 @@ +//----------------------------------------------------------------------------- +// 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 "gui/buttons/guiIconButtonCtrl.h" +#include "gui/editor/guiInspector.h" +#include "gui/editor/inspector/componentGroup.h" +#include "core/strings/stringUnit.h" +#include "T3D/components/component.h" +#include "gui/editor/inspector/field.h" + +#include "console/engineAPI.h" + +IMPLEMENT_CONOBJECT(GuiInspectorComponentGroup); + +ConsoleDocClass(GuiInspectorComponentGroup, + "@brief Used to inspect an object's FieldDictionary (dynamic fields) instead " + "of regular persistent fields.\n\n" + "Editor use only.\n\n" + "@internal" + ); + +GuiInspectorComponentGroup::GuiInspectorComponentGroup(StringTableEntry groupName, SimObjectPtr parent) +: GuiInspectorGroup(groupName, parent) +{ + /*mNeedScroll=false;*/ +}; + +bool GuiInspectorComponentGroup::onAdd() +{ + if (!Parent::onAdd()) + return false; + + return true; +} + +//----------------------------------------------------------------------------- +// GuiInspectorComponentGroup - add custom controls +//----------------------------------------------------------------------------- +bool GuiInspectorComponentGroup::createContent() +{ + if(!Parent::createContent()) + return false; + + Con::evaluatef("%d.stack = %d;", this->getId(), mStack->getId()); + + Con::executef(this, "createContent"); + + return true; +} + +//----------------------------------------------------------------------------- +// GuiInspectorComponentGroup - inspectGroup override +//----------------------------------------------------------------------------- +bool GuiInspectorComponentGroup::inspectGroup() +{ + // We can't inspect a group without a target! + if (!mParent || !mParent->getNumInspectObjects()) + return false; + + clearFields(); + + // to prevent crazy resizing, we'll just freeze our stack for a sec.. + mStack->freeze(true); + + bool bNoGroup = false; + + bool bNewItems = false; + bool bMakingArray = false; + GuiStackControl *pArrayStack = NULL; + GuiRolloutCtrl *pArrayRollout = NULL; + bool bGrabItems = false; + + Component* comp = dynamic_cast(getInspector()->getInspectObject(0)); + + //if this isn't a component, what are we even doing here? + if (!comp) + return false; + + for (U32 i = 0; i < comp->getComponentFieldCount(); i++) + { + ComponentField* field = comp->getComponentField(i); + + bNewItems = true; + + GuiInspectorField *fieldGui = constructField(field->mFieldType); + if (fieldGui == NULL) + fieldGui = new GuiInspectorField(); + + fieldGui->init(mParent, this); + + AbstractClassRep::Field *refField; + + //check dynamics + SimFieldDictionary* fieldDictionary = comp->getFieldDictionary(); + SimFieldDictionaryIterator itr(fieldDictionary); + + while (*itr) + { + SimFieldDictionary::Entry* entry = *itr; + if (entry->slotName == field->mFieldName) + { + AbstractClassRep::Field f; + f.pFieldname = StringTable->insert(field->mFieldName); + + if (field->mFieldDescription) + f.pFieldDocs = field->mFieldDescription; + + f.type = field->mFieldType; + f.offset = -1; + f.elementCount = 1; + f.validator = NULL; + f.flag = 0; //change to be the component type + + f.setDataFn = &defaultProtectedSetFn; + f.getDataFn = &defaultProtectedGetFn; + f.writeDataFn = &defaultProtectedWriteFn; + + f.pFieldDocs = field->mFieldDescription; + + f.pGroupname = "Component Fields"; + + ConsoleBaseType* conType = ConsoleBaseType::getType(field->mFieldType); + AssertFatal(conType, "ConsoleObject::addField - invalid console type"); + f.table = conType->getEnumTable(); + + tempFields.push_back(f); + + refField = &f; + + break; + } + ++itr; + } + + if (!refField) + continue; + + fieldGui->setInspectorField(&tempFields[tempFields.size() - 1]); + + if (fieldGui->registerObject()) + { +#ifdef DEBUG_SPEW + Platform::outputDebugString("[GuiInspectorGroup] Adding field '%s'", + field->pFieldname); +#endif + + mChildren.push_back(fieldGui); + mStack->addObject(fieldGui); + } + else + { + SAFE_DELETE(fieldGui); + } + } + + mStack->freeze(false); + mStack->updatePanes(); + + // If we've no new items, there's no need to resize anything! + if (bNewItems == false && !mChildren.empty()) + return true; + + sizeToContents(); + + setUpdate(); + + return true; +} + +void GuiInspectorComponentGroup::updateAllFields() +{ + // We overload this to just reinspect the group. + inspectGroup(); +} + +void GuiInspectorComponentGroup::onMouseMove(const GuiEvent &event) +{ + //mParent->mOverDivider = false; +} +ConsoleMethod(GuiInspectorComponentGroup, inspectGroup, bool, 2, 2, "Refreshes the dynamic fields in the inspector.") +{ + return object->inspectGroup(); +} + +void GuiInspectorComponentGroup::clearFields() +{ + // delete everything else + mStack->clear(); + + // clear the mChildren list. + mChildren.clear(); +} + +SimFieldDictionary::Entry* GuiInspectorComponentGroup::findDynamicFieldInDictionary(StringTableEntry fieldName) +{ + SimFieldDictionary * fieldDictionary = mParent->getInspectObject()->getFieldDictionary(); + + for (SimFieldDictionaryIterator ditr(fieldDictionary); *ditr; ++ditr) + { + SimFieldDictionary::Entry * entry = (*ditr); + + if (entry->slotName == fieldName) + return entry; + } + + return NULL; +} + +void GuiInspectorComponentGroup::addDynamicField() +{ +} + +AbstractClassRep::Field* GuiInspectorComponentGroup::findObjectBehaviorField(Component* target, String fieldName) +{ + AbstractClassRep::FieldList& fieldList = target->getClassRep()->mFieldList; + for (AbstractClassRep::FieldList::iterator itr = fieldList.begin(); + itr != fieldList.end(); ++itr) + { + AbstractClassRep::Field* field = &(*itr); + String fldNm(field->pFieldname); + if (fldNm == fieldName) + return field; + } + return NULL; +} +ConsoleMethod(GuiInspectorComponentGroup, addDynamicField, void, 2, 2, "obj.addDynamicField();") +{ + object->addDynamicField(); +} + +ConsoleMethod(GuiInspectorComponentGroup, removeDynamicField, void, 3, 3, "") +{ +} diff --git a/Engine/source/gui/editor/inspector/componentGroup.h b/Engine/source/gui/editor/inspector/componentGroup.h new file mode 100644 index 000000000..34b748c98 --- /dev/null +++ b/Engine/source/gui/editor/inspector/componentGroup.h @@ -0,0 +1,70 @@ +//----------------------------------------------------------------------------- +// 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 GUI_INSPECTOR_COMPONENT_GROUP_H +#define GUI_INSPECTOR_COMPONENT_GROUP_H + +#include "gui/editor/inspector/group.h" +#include "console/simFieldDictionary.h" +#include "T3D/components/component.h" +#include "gui/controls/guiPopUpCtrlEx.h" + +class GuiInspectorComponentGroup : public GuiInspectorGroup +{ +private: + typedef GuiInspectorGroup Parent; + GuiControl* mAddCtrl; + + Vector tempFields; + +public: + DECLARE_CONOBJECT(GuiInspectorComponentGroup); + GuiInspectorComponentGroup() { /*mNeedScroll=false;*/ }; + GuiInspectorComponentGroup(StringTableEntry groupName, SimObjectPtr parent); + + //----------------------------------------------------------------------------- + // inspectGroup is overridden in GuiInspectorComponentGroup to inspect an + // objects FieldDictionary (dynamic fields) instead of regular persistent + // fields. + virtual bool onAdd(); + bool inspectGroup(); + virtual void updateAllFields(); + + void onMouseMove(const GuiEvent &event); + + // For scriptable dynamic field additions + void addDynamicField(); + + // Clear our fields (delete them) + void clearFields(); + + // Find an already existent field by name in the dictionary + virtual SimFieldDictionary::Entry* findDynamicFieldInDictionary(StringTableEntry fieldName); + + AbstractClassRep::Field* findObjectBehaviorField(Component* target, String fieldName); +protected: + // create our inner controls when we add + virtual bool createContent(); + +}; + +#endif diff --git a/Engine/source/gui/editor/inspector/dynamicGroup.cpp b/Engine/source/gui/editor/inspector/dynamicGroup.cpp index ef5d98b3a..78d8c4bd8 100644 --- a/Engine/source/gui/editor/inspector/dynamicGroup.cpp +++ b/Engine/source/gui/editor/inspector/dynamicGroup.cpp @@ -26,6 +26,10 @@ #include "gui/editor/inspector/dynamicField.h" #include "console/engineAPI.h" +#ifdef TORQUE_EXPERIMENTAL_EC +#include "T3D/components/component.h" +#endif + IMPLEMENT_CONOBJECT(GuiInspectorDynamicGroup); ConsoleDocClass( GuiInspectorDynamicGroup, @@ -122,6 +126,16 @@ bool GuiInspectorDynamicGroup::inspectGroup() SimFieldDictionary * fieldDictionary = target->getFieldDictionary(); for(SimFieldDictionaryIterator ditr(fieldDictionary); *ditr; ++ditr) { +#ifdef TORQUE_EXPERIMENTAL_EC + if (target->getClassRep()->isSubclassOf("Component")) + { + Component* compTarget = dynamic_cast(target); + + ComponentField* compField = compTarget->getComponentField((*ditr)->slotName); + if (compField) + continue; + } +#endif if( i == 0 ) { flist.increment(); diff --git a/Engine/source/gui/editor/inspector/entityGroup.cpp b/Engine/source/gui/editor/inspector/entityGroup.cpp new file mode 100644 index 000000000..c833d336d --- /dev/null +++ b/Engine/source/gui/editor/inspector/entityGroup.cpp @@ -0,0 +1,137 @@ +//----------------------------------------------------------------------------- +// 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 "gui/buttons/guiIconButtonCtrl.h" +#include "gui/editor/guiInspector.h" +#include "gui/editor/inspector/entityGroup.h" +#include "core/strings/stringUnit.h" +#include "T3D/components/component.h" + +#include "console/engineAPI.h" + +IMPLEMENT_CONOBJECT(GuiInspectorEntityGroup); + +ConsoleDocClass(GuiInspectorEntityGroup, + "@brief Used to inspect an object's FieldDictionary (dynamic fields) instead " + "of regular persistent fields.\n\n" + "Editor use only.\n\n" + "@internal" + ); + +bool GuiInspectorEntityGroup::onAdd() +{ + if (!Parent::onAdd()) + return false; + + return true; +} + +//----------------------------------------------------------------------------- +// GuiInspectorEntityGroup - add custom controls +//----------------------------------------------------------------------------- +bool GuiInspectorEntityGroup::createContent() +{ + if(!Parent::createContent()) + return false; + + Con::evaluatef("%d.stack = %d;", this->getId(), mStack->getId()); + + Con::executef(this, "createContent"); + + return true; +} + +//----------------------------------------------------------------------------- +// GuiInspectorEntityGroup - inspectGroup override +//----------------------------------------------------------------------------- +bool GuiInspectorEntityGroup::inspectGroup() +{ + const U32 numTargets = mParent->getNumInspectObjects(); + if (numTargets == 1) + { + Entity* target = dynamic_cast(mParent->getInspectObject(0)); + + Con::executef(this, "inspectObject", target->getIdString()); + } + + return true; +} + +void GuiInspectorEntityGroup::updateAllFields() +{ + // We overload this to just reinspect the group. + inspectGroup(); +} + +void GuiInspectorEntityGroup::onMouseMove(const GuiEvent &event) +{ + //mParent->mOverDivider = false; +} +ConsoleMethod(GuiInspectorEntityGroup, inspectGroup, bool, 2, 2, "Refreshes the dynamic fields in the inspector.") +{ + return object->inspectGroup(); +} + +void GuiInspectorEntityGroup::clearFields() +{ +} + +SimFieldDictionary::Entry* GuiInspectorEntityGroup::findDynamicFieldInDictionary(StringTableEntry fieldName) +{ + SimFieldDictionary * fieldDictionary = mParent->getInspectObject()->getFieldDictionary(); + + for (SimFieldDictionaryIterator ditr(fieldDictionary); *ditr; ++ditr) + { + SimFieldDictionary::Entry * entry = (*ditr); + + if (entry->slotName == fieldName) + return entry; + } + + return NULL; +} + +void GuiInspectorEntityGroup::addDynamicField() +{ +} + +AbstractClassRep::Field* GuiInspectorEntityGroup::findObjectBehaviorField(Component* target, String fieldName) +{ + AbstractClassRep::FieldList& fieldList = target->getClassRep()->mFieldList; + for (AbstractClassRep::FieldList::iterator itr = fieldList.begin(); + itr != fieldList.end(); ++itr) + { + AbstractClassRep::Field* field = &(*itr); + String fldNm(field->pFieldname); + if (fldNm == fieldName) + return field; + } + return NULL; +} +ConsoleMethod(GuiInspectorEntityGroup, addDynamicField, void, 2, 2, "obj.addDynamicField();") +{ + object->addDynamicField(); +} + +ConsoleMethod(GuiInspectorEntityGroup, removeDynamicField, void, 3, 3, "") +{ +} diff --git a/Engine/source/gui/editor/inspector/entityGroup.h b/Engine/source/gui/editor/inspector/entityGroup.h new file mode 100644 index 000000000..00944cda1 --- /dev/null +++ b/Engine/source/gui/editor/inspector/entityGroup.h @@ -0,0 +1,72 @@ +//----------------------------------------------------------------------------- +// 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 GUI_INSPECTOR_ENTITY_GROUP_H +#define GUI_INSPECTOR_ENTITY_GROUP_H + +#include "gui/editor/inspector/group.h" +#include "console/simFieldDictionary.h" +#include "T3D/components/component.h" +#include "gui/controls/guiPopUpCtrlEx.h" + +class GuiInspectorEntityGroup : public GuiInspectorGroup +{ +private: + typedef GuiInspectorGroup Parent; + GuiControl* mAddCtrl; + + GuiPopUpMenuCtrlEx* mAddBhvrList; + +public: + DECLARE_CONOBJECT(GuiInspectorEntityGroup); + GuiInspectorEntityGroup() { /*mNeedScroll=false;*/ }; + GuiInspectorEntityGroup(StringTableEntry groupName, SimObjectPtr parent) + : GuiInspectorGroup(groupName, parent) { /*mNeedScroll=false;*/ + }; + + //----------------------------------------------------------------------------- + // inspectGroup is overridden in GuiInspectorEntityGroup to inspect an + // objects FieldDictionary (dynamic fields) instead of regular persistent + // fields. + virtual bool onAdd(); + bool inspectGroup(); + virtual void updateAllFields(); + + void onMouseMove(const GuiEvent &event); + + // For scriptable dynamic field additions + void addDynamicField(); + + // Clear our fields (delete them) + void clearFields(); + + // Find an already existent field by name in the dictionary + virtual SimFieldDictionary::Entry* findDynamicFieldInDictionary(StringTableEntry fieldName); + + AbstractClassRep::Field* findObjectBehaviorField(Component* target, String fieldName); +protected: + // create our inner controls when we add + virtual bool createContent(); + +}; + +#endif diff --git a/Engine/source/gui/editor/inspector/mountingGroup.cpp b/Engine/source/gui/editor/inspector/mountingGroup.cpp new file mode 100644 index 000000000..7afa53105 --- /dev/null +++ b/Engine/source/gui/editor/inspector/mountingGroup.cpp @@ -0,0 +1,507 @@ +//----------------------------------------------------------------------------- +// 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 "gui/buttons/guiIconButtonCtrl.h" +#include "gui/editor/guiInspector.h" +#include "gui/editor/inspector/mountingGroup.h" +#include "core/strings/stringUnit.h" +#include "T3D/entity.h" +#include "T3D/components/component.h" + +//Need this to get node lists +#include "T3D/components/render/renderComponentInterface.h" + +IMPLEMENT_CONOBJECT(GuiInspectorMountingGroup); + +ConsoleDocClass( GuiInspectorMountingGroup, + "@brief Used to inspect an object's FieldDictionary (dynamic fields) instead " + "of regular persistent fields.\n\n" + "Editor use only.\n\n" + "@internal" +); + +//----------------------------------------------------------------------------- +// GuiInspectorMountingGroup - add custom controls +//----------------------------------------------------------------------------- +GuiInspectorMountingGroup::GuiInspectorMountingGroup( StringTableEntry groupName, SimObjectPtr parent ) + : GuiInspectorGroup( groupName, parent) +{ + mParentInspector = parent; + + targetMountCtrl = NULL; + mountCtrl = NULL; +}; + +bool GuiInspectorMountingGroup::createContent() +{ + if(!Parent::createContent()) + return false; + + //give the necessary padding for the nested controls so it looks nice. + setMargin(RectI(4,0,4,4)); + + return true; +} + +GuiControl* GuiInspectorMountingGroup::buildMenuCtrl() +{ + GuiControl* retCtrl = new GuiPopUpMenuCtrl(); + + // If we couldn't construct the control, bail! + if( retCtrl == NULL ) + return retCtrl; + + GuiPopUpMenuCtrl *menu = dynamic_cast(retCtrl); + + // Let's make it look pretty. + retCtrl->setDataField( StringTable->insert("profile"), NULL, "GuiPopUpMenuProfile" ); + //GuiInspectorTypeMenuBase::_registerEditControl( retCtrl ); + + char szName[512]; + dSprintf( szName, 512, "IE_%s_%d_%s_Field", retCtrl->getClassName(), mParentInspector->getInspectObject()->getId(), mCaption.c_str()); + + // Register the object + retCtrl->registerObject( szName ); + + // Configure it to update our value when the popup is closed + char szBuffer[512]; + dSprintf( szBuffer, 512, "%d.apply( %d.getText() );", getId(), menu->getId() ); + menu->setField("Command", szBuffer ); + + return menu; +} + +bool GuiInspectorMountingGroup::buildList(Entity* ent, GuiPopUpMenuCtrl* menu) +{ + RenderComponentInterface* renderInterface = ent->getComponent(); + + if (renderInterface) + { + TSShape* shape = renderInterface->getShape(); + S32 nodeCount = shape ? shape->nodes.size() : 0; + + for(U32 i=0; i < nodeCount; i++) + { + menu->addEntry(shape->names[i], i); + } + + return true; + } + + return false; +} + +//----------------------------------------------------------------------------- +// GuiInspectorMountingGroup - inspectGroup override +//----------------------------------------------------------------------------- +bool GuiInspectorMountingGroup::inspectGroup() +{ + // We can't inspect a group without a target! + if( !mParent->getNumInspectObjects() ) + return false; + + // to prevent crazy resizing, we'll just freeze our stack for a sec.. + mStack->freeze(true); + + bool bNoGroup = false; + + // Un-grouped fields are all sorted into the 'general' group + if ( dStricmp( mCaption, "General" ) == 0 ) + bNoGroup = true; + + // Just delete all fields and recreate them (like the dynamicGroup) + // because that makes creating controls for array fields a lot easier + clearFields(); + + bool bNewItems = false; + bool bMakingArray = false; + GuiStackControl *pArrayStack = NULL; + GuiRolloutCtrl *pArrayRollout = NULL; + bool bGrabItems = false; + + AbstractClassRep* commonAncestorClass = findCommonAncestorClass(); + AbstractClassRep::FieldList& fieldList = commonAncestorClass->mFieldList; + for( AbstractClassRep::FieldList::iterator itr = fieldList.begin(); + itr != fieldList.end(); ++ itr ) + { + AbstractClassRep::Field* field = &( *itr ); + if( field->type == AbstractClassRep::StartGroupFieldType ) + { + // If we're dealing with general fields, always set grabItems to true (to skip them) + if( bNoGroup == true ) + bGrabItems = true; + else if( dStricmp( field->pGroupname, mCaption ) == 0 ) + bGrabItems = true; + continue; + } + else if ( field->type == AbstractClassRep::EndGroupFieldType ) + { + // If we're dealing with general fields, always set grabItems to false (to grab them) + if( bNoGroup == true ) + bGrabItems = false; + else if( dStricmp( field->pGroupname, mCaption ) == 0 ) + bGrabItems = false; + continue; + } + + // Skip field if it has the HideInInspectors flag set. + + if( field->flag.test( AbstractClassRep::FIELD_HideInInspectors ) ) + continue; + + if( ( bGrabItems == true || ( bNoGroup == true && bGrabItems == false ) ) && itr->type != AbstractClassRep::DeprecatedFieldType ) + { + if( bNoGroup == true && bGrabItems == true ) + continue; + + // If the field already exists, just update it + GuiInspectorField *fieldGui = findField( field->pFieldname ); + if ( fieldGui != NULL ) + { + fieldGui->updateValue(); + continue; + } + + bNewItems = true; + + if(field->pFieldname == StringTable->insert("mountNode")) + { + fieldGui = new GuiInspectorNodeListField(); + + Entity* e = dynamic_cast(mParent->getInspectObject(0)); + if(e) + (dynamic_cast(fieldGui))->setTargetEntity(e); + } + else + { + fieldGui = constructField( field->type ); + if ( fieldGui == NULL ) + fieldGui = new GuiInspectorField(); + } + + fieldGui->init( mParent, this ); + fieldGui->setInspectorField( field ); + + if( fieldGui->registerObject() ) + { + #ifdef DEBUG_SPEW + Platform::outputDebugString( "[GuiInspectorGroup] Adding field '%s'", + field->pFieldname ); + #endif + + mChildren.push_back( fieldGui ); + mStack->addObject( fieldGui ); + } + else + { + SAFE_DELETE( fieldGui ); + } + } + } + mStack->freeze(false); + mStack->updatePanes(); + + // If we've no new items, there's no need to resize anything! + if( bNewItems == false && !mChildren.empty() ) + return true; + + sizeToContents(); + + setUpdate(); + + return true; +} + +void GuiInspectorMountingGroup::updateAllFields() +{ + // We overload this to just reinspect the group. + inspectGroup(); +} + +void GuiInspectorMountingGroup::onMouseMove(const GuiEvent &event) +{ + //mParent->mOverDivider = false; + bool test = false; +} +ConsoleMethod(GuiInspectorMountingGroup, inspectGroup, bool, 2, 2, "Refreshes the dynamic fields in the inspector.") +{ + return object->inspectGroup(); +} + +void GuiInspectorMountingGroup::clearFields() +{ +} + +bool GuiInspectorMountingGroup::resize( const Point2I &newPosition, const Point2I &newExtent ) +{ + if ( !Parent::resize( newPosition, newExtent ) ) + return false; + + //check if we're set up yet + if(!targetMountCtrl || !mountCtrl) + //no? bail + return false; + + targetMountCtrl->setExtent(newExtent.x, 18); + mountCtrl->setExtent(newExtent.x, 18); + + S32 dividerPos, dividerMargin; + mParentInspector->getDivider( dividerPos, dividerMargin ); + + Point2I fieldExtent = Point2I(newExtent.x, 18); + Point2I fieldPos = Point2I(newExtent.x, 18); + + S32 editWidth = dividerPos - dividerMargin; + + targetMountText->setPosition(0,0); + targetMountText->setExtent(fieldExtent.x - dividerPos - dividerMargin, fieldExtent.y); + + targetMountNode->setPosition(fieldExtent.x - dividerPos + dividerMargin, 1); + targetMountNode->setExtent(editWidth, fieldExtent.y - 1); + + mountText->setPosition(0,0); + mountText->setExtent(fieldExtent.x - dividerPos - dividerMargin, fieldExtent.y); + + mountNode->setPosition(fieldExtent.x - dividerPos + dividerMargin, 1); + mountNode->setExtent(editWidth, fieldExtent.y - 1); + + return true; +} + +SimFieldDictionary::Entry* GuiInspectorMountingGroup::findDynamicFieldInDictionary( StringTableEntry fieldName ) +{ + SimFieldDictionary * fieldDictionary = mParent->getInspectObject()->getFieldDictionary(); + + for(SimFieldDictionaryIterator ditr(fieldDictionary); *ditr; ++ditr) + { + SimFieldDictionary::Entry * entry = (*ditr); + + if( entry->slotName == fieldName ) + return entry; + } + + return NULL; +} + +void GuiInspectorMountingGroup::addDynamicField() +{ +} + +AbstractClassRep::Field* GuiInspectorMountingGroup::findObjectComponentField(Component* target, String fieldName) +{ + AbstractClassRep::FieldList& fieldList = target->getClassRep()->mFieldList; + for( AbstractClassRep::FieldList::iterator itr = fieldList.begin(); + itr != fieldList.end(); ++ itr ) + { + AbstractClassRep::Field* field = &( *itr ); + String fldNm(field->pFieldname); + if(fldNm == fieldName) + return field; + } + return NULL; +} +ConsoleMethod( GuiInspectorMountingGroup, addDynamicField, void, 2, 2, "obj.addDynamicField();" ) +{ + object->addDynamicField(); +} + +ConsoleMethod( GuiInspectorMountingGroup, removeDynamicField, void, 3, 3, "" ) +{ +} + +// +IMPLEMENT_CONOBJECT( GuiInspectorNodeListField ); + +ConsoleDocClass( GuiInspectorNodeListField, + "@brief A control that allows to edit the custom properties (text) of one or more SimObjects.\n\n" + "Editor use only.\n\n" + "@internal" +); + +GuiInspectorNodeListField::GuiInspectorNodeListField( GuiInspector *inspector, + GuiInspectorGroup* parent, + SimFieldDictionary::Entry* field, + SimObjectPtr target ) +{ + mInspector = inspector; + mParent = parent; + setBounds(0,0,100,20); + mTargetEntity = target; +} + +GuiInspectorNodeListField::GuiInspectorNodeListField() +{ + mInspector = NULL; + mParent = NULL; +} + +void GuiInspectorNodeListField::setData( const char* data, bool callbacks ) +{ + mCustomValue = data; + + //We aren't updating any mounting info if we're not mounted already + if(mTargetEntity.getObject()) + { + Entity* target = dynamic_cast(mTargetEntity->getObjectMount()); + if(target) + { + RenderComponentInterface* renderInterface = target->getComponent(); + if (renderInterface) + { + if (renderInterface->getShape()) + { + S32 nodeIdx = renderInterface->getShape()->findNode(data); + + target->mountObject(mTargetEntity, nodeIdx, MatrixF::Identity); + mTargetEntity->setMaskBits(Entity::MountedMask); + } + } + } + } + + // Force our edit to update + updateValue(); +} + +const char* GuiInspectorNodeListField::getData( U32 inspectObjectIndex ) +{ + return mCustomValue; +} + +void GuiInspectorNodeListField::updateValue() +{ + mMenu->clear(); + //mMenu->addEntry("Origin"); + + //if(mCustomValue.isEmpty()) + if(mTargetEntity.getObject()) + { + Entity* target = dynamic_cast(mTargetEntity->getObjectMount()); + if(target) + { + mMenu->addEntry("Origin"); + mMenu->setActive(true); + + RenderComponentInterface* renderInterface = target->getComponent(); + + if (renderInterface) + { + TSShape* shape = renderInterface->getShape(); + + S32 nodeCount = shape ? shape->nodes.size() : 0; + + for(U32 i=0; i < nodeCount; i++) + { + mMenu->addEntry(shape->names[i], i); + } + + S32 targetNode = mTargetEntity->getMountNode(); + if(targetNode != -1) + { + String name = shape->names[targetNode]; + mCustomValue = name; + } + else + { + mCustomValue = String("Origin"); + } + + setValue( mCustomValue ); + return; + } + } + } + + setValue("Not Mounted"); + mMenu->setActive(false); +} + +void GuiInspectorNodeListField::setDoc( const char* doc ) +{ + mDoc = StringTable->insert( doc, true ); +} + +void GuiInspectorNodeListField::setToolTip( StringTableEntry data ) +{ + static StringTableEntry sTooltipProfile = StringTable->insert( "tooltipProfile" ); + static StringTableEntry sHoverTime = StringTable->insert( "hovertime" ); + static StringTableEntry sTooltip = StringTable->insert( "tooltip" ); + + mEdit->setDataField( sTooltipProfile, NULL, "GuiToolTipProfile" ); + mEdit->setDataField( sHoverTime, NULL, "1000" ); + mEdit->setDataField( sTooltip, NULL, data ); +} + +bool GuiInspectorNodeListField::onAdd() +{ + if( !Parent::onAdd() ) + return false; + + return true; +} + +void GuiInspectorNodeListField::setInspectorField( AbstractClassRep::Field *field, + StringTableEntry caption, + const char*arrayIndex ) +{ + // Override the base just to be sure it doesn't get called. + // We don't use an AbstractClassRep::Field... + + mField = field; + mCaption = field->pFieldname; + mDoc = field->pFieldDocs; +} + +GuiControl* GuiInspectorNodeListField::constructEditControl() +{ + GuiControl* retCtrl = new GuiPopUpMenuCtrl(); + + mMenu = dynamic_cast(retCtrl); + + static StringTableEntry sProfile = StringTable->insert( "profile" ); + retCtrl->setDataField( sProfile, NULL, "ToolsGuiPopUpMenuEditProfile" ); + + // Register the object + retCtrl->registerObject(); + + char szBuffer[512]; + dSprintf( szBuffer, 512, "%d.apply( %d.getText() );", getId(), mMenu->getId() ); + mMenu->setField("Command", szBuffer ); + + return retCtrl; +} + +void GuiInspectorNodeListField::setValue( const char* newValue ) +{ + GuiPopUpMenuCtrl *ctrl = dynamic_cast( mEdit ); + if( ctrl != NULL ) + ctrl->setText( newValue ); +} + +void GuiInspectorNodeListField::_executeSelectedCallback() +{ +} + +void GuiInspectorNodeListField::setTargetEntity(SimObjectPtr target) +{ + mTargetEntity = target; +} \ No newline at end of file diff --git a/Engine/source/gui/editor/inspector/mountingGroup.h b/Engine/source/gui/editor/inspector/mountingGroup.h new file mode 100644 index 000000000..5c4bdac75 --- /dev/null +++ b/Engine/source/gui/editor/inspector/mountingGroup.h @@ -0,0 +1,152 @@ +//----------------------------------------------------------------------------- +// 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 GUI_INSPECTOR_MOUNTINGGROUP_H +#define GUI_INSPECTOR_MOUNTINGGROUP_H + +#include "gui/editor/inspector/group.h" +#include "console/simFieldDictionary.h" +#include "T3D/components/component.h" +#include "gui/controls/guiPopUpCtrlEx.h" + +#ifndef _GUI_INSPECTOR_TYPES_H_ +#include "gui/editor/guiInspectorTypes.h" +#endif + +#ifndef _ENTITY_H_ +#include "T3D/entity.h" +#endif + +class GuiInspectorMountingGroup; + +class GuiInspectorNodeListField : public GuiInspectorField +{ + typedef GuiInspectorField Parent; + friend class GuiInspectorMountingGroup; + +public: + + GuiInspectorNodeListField( GuiInspector *inspector, GuiInspectorGroup* parent, SimFieldDictionary::Entry* field, + SimObjectPtr target ); + GuiInspectorNodeListField(); + ~GuiInspectorNodeListField() {}; + + DECLARE_CONOBJECT( GuiInspectorNodeListField ); + + virtual void setData( const char* data, bool callbacks = true ); + virtual const char* getData( U32 inspectObjectIndex = 0 ); + virtual void updateValue(); + virtual StringTableEntry getFieldName() { return StringTable->EmptyString(); } + + virtual void setDoc( const char* doc ); + virtual void setToolTip( StringTableEntry data ); + + virtual bool onAdd(); + + virtual void setInspectorField( AbstractClassRep::Field *field, + StringTableEntry caption = NULL, + const char *arrayIndex = NULL ); + + virtual GuiControl* constructEditControl(); + + virtual void setValue( const char* newValue ); + + void setTargetEntity(SimObjectPtr target); + +protected: + + virtual void _executeSelectedCallback(); + +protected: + + String mCustomValue; + StringTableEntry mDoc; + + GuiPopUpMenuCtrl *mMenu; + + SimObjectPtr mTargetEntity; +}; + +class GuiInspectorMountingGroup : public GuiInspectorGroup +{ +private: + typedef GuiInspectorGroup Parent; + GuiControl* mAddCtrl; + + GuiPopUpMenuCtrlEx* mAddBhvrList; + + GuiTextCtrl *persistText; + GuiButtonCtrl *reloadFile; + GuiButtonCtrl *saveFile; + GuiButtonCtrl *overwriteFile; + GuiButtonCtrl *mBrowseButton; + GuiControl *filePath; + + GuiControl *targetMountCtrl; + GuiTextCtrl *targetMountText; + GuiPopUpMenuCtrl *targetMountNode; + + GuiControl *mountCtrl; + GuiTextCtrl *mountText; + GuiPopUpMenuCtrl *mountNode; + + GuiInspectorNodeListField* mountNodeList; + GuiInspectorNodeListField* targetMountNodeList; + + SimObjectPtr mParentInspector; + +public: + DECLARE_CONOBJECT(GuiInspectorMountingGroup); + GuiInspectorMountingGroup() { /*mNeedScroll=false;*/ }; + GuiInspectorMountingGroup( StringTableEntry groupName, SimObjectPtr parent ); + + //----------------------------------------------------------------------------- + // inspectGroup is overridden in GuiInspectorMountingGroup to inspect an + // objects FieldDictionary (dynamic fields) instead of regular persistent + // fields. + bool inspectGroup(); + virtual void updateAllFields(); + + void onMouseMove(const GuiEvent &event); + + // For scriptable dynamic field additions + void addDynamicField(); + + // Clear our fields (delete them) + void clearFields(); + + virtual bool resize( const Point2I &newPosition, const Point2I &newExtent ); + + // Find an already existent field by name in the dictionary + virtual SimFieldDictionary::Entry* findDynamicFieldInDictionary( StringTableEntry fieldName ); + + AbstractClassRep::Field* findObjectComponentField(Component* target, String fieldName); +protected: + // create our inner controls when we add + virtual bool createContent(); + + GuiControl* buildMenuCtrl(); + + bool buildList(Entity* ent, GuiPopUpMenuCtrl* menu); +}; + +#endif diff --git a/Engine/source/gui/worldEditor/editor.cpp b/Engine/source/gui/worldEditor/editor.cpp index cfcee744d..2f05c96ad 100644 --- a/Engine/source/gui/worldEditor/editor.cpp +++ b/Engine/source/gui/worldEditor/editor.cpp @@ -122,9 +122,9 @@ void EditManager::editorDisabled() static GameBase * getControlObj() { GameConnection * connection = GameConnection::getLocalClientConnection(); - ShapeBase* control = 0; + GameBase* control = 0; if(connection) - control = dynamic_cast(connection->getControlObject()); + control = connection->getControlObject(); return(control); } diff --git a/Engine/source/persistence/taml/taml.cpp b/Engine/source/persistence/taml/taml.cpp index caea660cd..dfe9266cf 100644 --- a/Engine/source/persistence/taml/taml.cpp +++ b/Engine/source/persistence/taml/taml.cpp @@ -284,6 +284,10 @@ SimObject* Taml::read( const char* pFilename ) // No, so warn. Con::warnf( "Taml::read() - Failed to load an object from the file '%s'.", mFilePathBuffer ); } + else + { + pSimObject->onPostAdd(); + } return pSimObject; } diff --git a/Engine/source/component/interfaces/IProcessInput.h b/Engine/source/platform/input/IProcessInput.h similarity index 100% rename from Engine/source/component/interfaces/IProcessInput.h rename to Engine/source/platform/input/IProcessInput.h diff --git a/Engine/source/platformX86UNIX/x86UNIXFileio.cpp b/Engine/source/platformX86UNIX/x86UNIXFileio.cpp index 6c2dd6955..6e51b85e1 100644 --- a/Engine/source/platformX86UNIX/x86UNIXFileio.cpp +++ b/Engine/source/platformX86UNIX/x86UNIXFileio.cpp @@ -1151,45 +1151,79 @@ bool dPathCopy(const char *fromName, const char *toName, bool nooverwrite) DIR *dip; struct dirent *d; - if (subPath && (dStrncmp(subPath, "", 1) != 0)) - { - if ((basePath[dStrlen(basePath) - 1]) == '/') - dSprintf(Path, 1024, "%s%s", basePath, subPath); - else - dSprintf(Path, 1024, "%s/%s", basePath, subPath); - } + dsize_t trLen = basePath ? dStrlen(basePath) : 0; + dsize_t subtrLen = subPath ? dStrlen(subPath) : 0; + char trail = trLen > 0 ? basePath[trLen - 1] : '\0'; + char subTrail = subtrLen > 0 ? subPath[subtrLen - 1] : '\0'; + char subLead = subtrLen > 0 ? subPath[0] : '\0'; + + if (trail == '/') + { + if (subPath && (dStrncmp(subPath, "", 1) != 0)) + { + if (subTrail == '/') + dSprintf(Path, 1024, "%s%s", basePath, subPath); + else + dSprintf(Path, 1024, "%s%s/", basePath, subPath); + } + else + dSprintf(Path, 1024, "%s", basePath); + } else - dSprintf(Path, 1024, "%s", basePath); + { + if (subPath && (dStrncmp(subPath, "", 1) != 0)) + { + if (subTrail == '/') + dSprintf(Path, 1024, "%s%s", basePath, subPath); + else + dSprintf(Path, 1024, "%s%s/", basePath, subPath); + } + else + dSprintf(Path, 1024, "%s/", basePath); + } + dip = opendir(Path); if (dip == NULL) return false; + ////////////////////////////////////////////////////////////////////////// // add path to our return list ( provided it is valid ) ////////////////////////////////////////////////////////////////////////// if (!Platform::isExcludedDirectory(subPath)) - { - if (noBasePath) - { - // We have a path and it's not an empty string or an excluded directory - if ( (subPath && (dStrncmp (subPath, "", 1) != 0)) ) - directoryVector.push_back(StringTable->insert(subPath)); - } - else - { - if ( (subPath && (dStrncmp(subPath, "", 1) != 0)) ) - { - char szPath[1024]; - dMemset(szPath, 0, 1024); - if ( (basePath[dStrlen(basePath) - 1]) != '/') - dSprintf(szPath, 1024, "%s%s", basePath, subPath); - else - dSprintf(szPath, 1024, "%s%s", basePath, &subPath[1]); - directoryVector.push_back(StringTable->insert(szPath)); - } - else - directoryVector.push_back(StringTable->insert(basePath)); - } - } + { + if (noBasePath) + { + // We have a path and it's not an empty string or an excluded directory + if ( (subPath && (dStrncmp (subPath, "", 1) != 0)) ) + directoryVector.push_back(StringTable->insert(subPath)); + } + else + { + if ( (subPath && (dStrncmp(subPath, "", 1) != 0)) ) + { + char szPath[1024]; + dMemset(szPath, 0, 1024); + if (trail == '/') + { + if ((basePath[dStrlen(basePath) - 1]) != '/') + dSprintf(szPath, 1024, "%s%s", basePath, &subPath[1]); + else + dSprintf(szPath, 1024, "%s%s", basePath, subPath); + } + else + { + if ((basePath[dStrlen(basePath) - 1]) != '/') + dSprintf(szPath, 1024, "%s%s", basePath, subPath); + else + dSprintf(szPath, 1024, "%s/%s", basePath, subPath); + } + + directoryVector.push_back(StringTable->insert(szPath)); + } + else + directoryVector.push_back(StringTable->insert(basePath)); + } + } ////////////////////////////////////////////////////////////////////////// // Iterate through and grab valid directories ////////////////////////////////////////////////////////////////////////// diff --git a/Engine/source/scene/sceneRenderState.cpp b/Engine/source/scene/sceneRenderState.cpp index 0aeb1d273..47bb8b440 100644 --- a/Engine/source/scene/sceneRenderState.cpp +++ b/Engine/source/scene/sceneRenderState.cpp @@ -26,7 +26,10 @@ #include "renderInstance/renderPassManager.h" #include "math/util/matrixSet.h" - +#ifdef TORQUE_EXPERIMENTAL_EC +#include "T3D/components/render/renderComponentInterface.h" +#include "T3D/components/component.h" +#endif //----------------------------------------------------------------------------- @@ -104,6 +107,20 @@ void SceneRenderState::renderObjects( SceneObject** objects, U32 numObjects ) SceneObject* object = objects[ i ]; object->prepRenderImage( this ); } + +#ifdef TORQUE_EXPERIMENTAL_EC + U32 interfaceCount = RenderComponentInterface::all.size(); + for (U32 i = 0; i < RenderComponentInterface::all.size(); i++) + { + Component* comp = dynamic_cast(RenderComponentInterface::all[i]); + + if (comp->isClientObject() && comp->isActive()) + { + RenderComponentInterface::all[i]->prepRenderImage(this); + } + } +#endif + PROFILE_END(); // Render what the objects have batched. diff --git a/Engine/source/sim/netObject.h b/Engine/source/sim/netObject.h index c6a9ca99e..ced8a2cf3 100644 --- a/Engine/source/sim/netObject.h +++ b/Engine/source/sim/netObject.h @@ -217,7 +217,7 @@ struct GhostInfo; /// the documentation on AbstractClassRep for more details. /// /// @nosubgrouping -class NetObject: public SimObject +class NetObject : public SimGroup { // The Ghost Manager needs read/write access friend class NetConnection; @@ -228,7 +228,7 @@ class NetObject: public SimObject friend class GhostAlwaysObjectEvent; private: - typedef SimObject Parent; + typedef SimGroup Parent; /// Mask indicating which states are dirty and need to be retransmitted on this /// object. diff --git a/Engine/source/windowManager/windowInputGenerator.cpp b/Engine/source/windowManager/windowInputGenerator.cpp index 608aadb8a..193d0248a 100644 --- a/Engine/source/windowManager/windowInputGenerator.cpp +++ b/Engine/source/windowManager/windowInputGenerator.cpp @@ -23,7 +23,7 @@ #include "windowManager/windowInputGenerator.h" #include "windowManager/platformWindow.h" #include "sim/actionMap.h" -#include "component/interfaces/IProcessInput.h" +#include "platform/input/IProcessInput.h" extern InputModifiers convertModifierBits(const U32 in); diff --git a/Templates/Full/game/art/art.module.taml b/Templates/Full/game/art/art.module.taml new file mode 100644 index 000000000..73855c09e --- /dev/null +++ b/Templates/Full/game/art/art.module.taml @@ -0,0 +1,13 @@ + + + \ No newline at end of file diff --git a/Templates/Full/game/art/shapes/actors/Soldier/soldier.asset.taml b/Templates/Full/game/art/shapes/actors/Soldier/soldier.asset.taml new file mode 100644 index 000000000..4ba732205 --- /dev/null +++ b/Templates/Full/game/art/shapes/actors/Soldier/soldier.asset.taml @@ -0,0 +1,3 @@ + diff --git a/Templates/Full/game/main.cs b/Templates/Full/game/main.cs index 955f5aa6c..2a261201d 100644 --- a/Templates/Full/game/main.cs +++ b/Templates/Full/game/main.cs @@ -254,6 +254,7 @@ else { //You can also explicitly decalre some modules here to be loaded by default if they are part of your game //Ex: ModuleDatabase.LoadExplicit( "AppCore" ); + ModuleDatabase.LoadGroup( "Game" ); if( !$isDedicated ) { diff --git a/Templates/Full/game/scripts/scripts.module.taml b/Templates/Full/game/scripts/scripts.module.taml new file mode 100644 index 000000000..e1a6b57be --- /dev/null +++ b/Templates/Full/game/scripts/scripts.module.taml @@ -0,0 +1,13 @@ + + + \ No newline at end of file diff --git a/Templates/Full/game/scripts/server/components/animationComponent.asset.taml b/Templates/Full/game/scripts/server/components/animationComponent.asset.taml new file mode 100644 index 000000000..62f15cdf9 --- /dev/null +++ b/Templates/Full/game/scripts/server/components/animationComponent.asset.taml @@ -0,0 +1,7 @@ + diff --git a/Templates/Full/game/scripts/server/components/cameraOrbiterComponent.asset.taml b/Templates/Full/game/scripts/server/components/cameraOrbiterComponent.asset.taml new file mode 100644 index 000000000..e03081564 --- /dev/null +++ b/Templates/Full/game/scripts/server/components/cameraOrbiterComponent.asset.taml @@ -0,0 +1,7 @@ + diff --git a/Templates/Full/game/scripts/server/components/collisionComponent.asset.taml b/Templates/Full/game/scripts/server/components/collisionComponent.asset.taml new file mode 100644 index 000000000..9796df18d --- /dev/null +++ b/Templates/Full/game/scripts/server/components/collisionComponent.asset.taml @@ -0,0 +1,7 @@ + diff --git a/Templates/Full/game/scripts/server/components/game/camera.asset.taml b/Templates/Full/game/scripts/server/components/game/camera.asset.taml new file mode 100644 index 000000000..5d00d1170 --- /dev/null +++ b/Templates/Full/game/scripts/server/components/game/camera.asset.taml @@ -0,0 +1,7 @@ + diff --git a/Templates/Full/game/scripts/server/components/game/camera.cs b/Templates/Full/game/scripts/server/components/game/camera.cs new file mode 100644 index 000000000..24363bd82 --- /dev/null +++ b/Templates/Full/game/scripts/server/components/game/camera.cs @@ -0,0 +1,196 @@ +//----------------------------------------------------------------------------- +// 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. +//----------------------------------------------------------------------------- + +function CameraComponent::onAdd(%this) +{ + %this.addComponentField(clientOwner, "The client that views this camera", "int", "1", ""); + + %test = %this.clientOwner; + + %barf = ClientGroup.getCount(); + + %clientID = %this.getClientID(); + if(%clientID && !isObject(%clientID.camera)) + { + %this.scopeToClient(%clientID); + %this.setDirty(); + + %clientID.setCameraObject(%this.owner); + %clientID.setControlCameraFov(%this.FOV); + + %clientID.camera = %this.owner; + } + + %res = $pref::Video::mode; + %derp = 0; +} + +function CameraComponent::onRemove(%this) +{ + %clientID = %this.getClientID(); + if(%clientID) + %clientID.clearCameraObject(); +} + +function CameraComponent::onInspectorUpdate(%this) +{ + //if(%this.clientOwner) + //%this.clientOwner.setCameraObject(%this.owner); +} + +function CameraComponent::getClientID(%this) +{ + return ClientGroup.getObject(%this.clientOwner-1); +} + +function CameraComponent::isClientCamera(%this, %client) +{ + %clientID = ClientGroup.getObject(%this.clientOwner-1); + + if(%client.getID() == %clientID) + return true; + else + return false; +} + +function CameraComponent::onClientConnect(%this, %client) +{ + //if(%this.isClientCamera(%client) && !isObject(%client.camera)) + //{ + %this.scopeToClient(%client); + %this.setDirty(); + + %client.setCameraObject(%this.owner); + %client.setControlCameraFov(%this.FOV); + + %client.camera = %this.owner; + //} + //else + //{ + // echo("CONNECTED CLIENT IS NOT CAMERA OWNER!"); + //} +} + +function CameraComponent::onClientDisconnect(%this, %client) +{ + Parent::onClientDisconnect(%this, %client); + + if(isClientCamera(%client)){ + %this.clearScopeToClient(%client); + %client.clearCameraObject(); + } +} + +//move to the editor later +GlobalActionMap.bind("keyboard", "alt c", "toggleEditorCam"); + +function switchCamera(%client, %newCamEntity) +{ + if(!isObject(%client) || !isObject(%newCamEntity)) + return error("SwitchCamera: No client or target camera!"); + + %cam = %newCamEntity.getComponent(CameraComponent); + + if(!isObject(%cam)) + return error("SwitchCamera: Target camera doesn't have a camera behavior!"); + + //TODO: Cleanup clientOwner for previous camera! + if(%cam.clientOwner == 0 || %cam.clientOwner $= "") + %cam.clientOwner = 0; + + %cam.scopeToClient(%client); + %cam.setDirty(); + + %client.setCameraObject(%newCamEntity); + %client.setControlCameraFov(%cam.FOV); + + %client.camera = %newCamEntity; +} + +function buildEditorCamera() +{ + if(isObject("EditorCamera")) + return EditorCamera; + + %camObj = SGOManager.spawn("SpectatorObject", false); + + %camObj.name = "EditorCamera"; + + %client = ClientGroup.getObject(0); + + %camObj.getComponent(SpectatorControls).setupControls(%client); + + MissionCleanup.add(%camObj); + + return %camObj; +} + +//TODO: Move this somewhere else! +function toggleEditorCam(%val) +{ + if(!%val) + return; + + %client = ClientGroup.getObject(0); + + if(!isObject(%client.camera)) + return error("ToggleEditorCam: no existing camera!"); + + %editorCam = buildEditorCamera(); + + //if this is our first switch, just go to the editor camera + if(%client.lastCam $= "" || %client.camera.getId() != %editorCam.getId()) + { + if(%client.lastCam $= "") + { + //set up the position + %editorCam.position = %client.camera.position; + %editorCam.rotation = %client.camera.rotation; + } + + %client.lastCam = %client.camera; + %client.lastController = %client.getControlObject(); + switchCamera(%client, %editorCam); + switchControlObject(%client, %editorCam); + } + else + { + switchCamera(%client, %client.lastCam); + switchControlObject(%client, %client.lastController); + %client.lastCam = %editorCam; + %client.lastController = %editorCam; + } +} + +function serverCmdSetClientAspectRatio(%client, %width, %height) +{ + echo("Client: " @ %client SPC "changing screen res to: " @ %width SPC %height); + %client.screenExtent = %width SPC %height; + %cam = %client.getCameraObject(); + + if(!isObject(%cam)) + return; + + %cameraComp = %cam.getComponent(CameraComponent); + + %cameraComp.ScreenAspect = %width SPC %height; +} \ No newline at end of file diff --git a/Templates/Full/game/scripts/server/components/game/controlObject.asset.taml b/Templates/Full/game/scripts/server/components/game/controlObject.asset.taml new file mode 100644 index 000000000..2c9d48e1c --- /dev/null +++ b/Templates/Full/game/scripts/server/components/game/controlObject.asset.taml @@ -0,0 +1,7 @@ + diff --git a/Templates/Full/game/scripts/server/components/game/controlObject.cs b/Templates/Full/game/scripts/server/components/game/controlObject.cs new file mode 100644 index 000000000..7f477ecca --- /dev/null +++ b/Templates/Full/game/scripts/server/components/game/controlObject.cs @@ -0,0 +1,89 @@ +//----------------------------------------------------------------------------- +// 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. +//----------------------------------------------------------------------------- + +//registerComponent("ControlObjectComponent", "Component", "Control Object", "Game", false, "Allows the behavior owner to operate as a camera."); + +function ControlObjectComponent::onAdd(%this) +{ + %this.addComponentField(clientOwner, "The shape to use for rendering", "int", "1", ""); + + %clientID = %this.getClientID(); + + if(%clientID && !isObject(%clientID.getControlObject())) + %clientID.setControlObject(%this.owner); +} + +function ControlObjectComponent::onRemove(%this) +{ + %clientID = %this.getClientID(); + + if(%clientID) + %clientID.setControlObject(0); +} + +function ControlObjectComponent::onClientConnect(%this, %client) +{ + if(%this.isControlClient(%client) && !isObject(%client.getControlObject())) + %client.setControlObject(%this.owner); +} + +function ControlObjectComponent::onClientDisconnect(%this, %client) +{ + if(%this.isControlClient(%client)) + %client.setControlObject(0); +} + +function ControlObjectComponent::getClientID(%this) +{ + return ClientGroup.getObject(%this.clientOwner-1); +} + +function ControlObjectComponent::isControlClient(%this, %client) +{ + %clientID = ClientGroup.getObject(%this.clientOwner-1); + + if(%client.getID() == %clientID) + return true; + else + return false; +} + +function ControlObjectComponent::onInspectorUpdate(%this, %field) +{ + %clientID = %this.getClientID(); + + if(%clientID && !isObject(%clientID.getControlObject())) + %clientID.setControlObject(%this.owner); +} + +function switchControlObject(%client, %newControlEntity) +{ + if(!isObject(%client) || !isObject(%newControlEntity)) + return error("SwitchControlObject: No client or target controller!"); + + %control = %newControlEntity.getComponent(ControlObjectComponent); + + if(!isObject(%control)) + return error("SwitchControlObject: Target controller has no conrol object behavior!"); + + %client.setControlObject(%newControlEntity); +} \ No newline at end of file diff --git a/Templates/Full/game/scripts/server/components/game/itemRotate.asset.taml b/Templates/Full/game/scripts/server/components/game/itemRotate.asset.taml new file mode 100644 index 000000000..8068b49f3 --- /dev/null +++ b/Templates/Full/game/scripts/server/components/game/itemRotate.asset.taml @@ -0,0 +1,7 @@ + diff --git a/Templates/Full/game/scripts/server/components/game/itemRotate.cs b/Templates/Full/game/scripts/server/components/game/itemRotate.cs new file mode 100644 index 000000000..947d19214 --- /dev/null +++ b/Templates/Full/game/scripts/server/components/game/itemRotate.cs @@ -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. +//----------------------------------------------------------------------------- + +//registerComponent("ItemRotationComponent", "Component", "Item Rotation", "Game", false, "Rotates the entity around the z axis, like an item pickup."); + +function ItemRotationComponent::onAdd(%this) +{ + %this.addComponentField(rotationsPerMinute, "Number of rotations per minute", "float", "5", ""); + %this.addComponentField(forward, "Rotate forward or backwards", "bool", "1", ""); + %this.addComponentField(horizontal, "Rotate horizontal or verticle, true for horizontal", "bool", "1", ""); +} + +function ItemRotationComponent::Update(%this) +{ + %tickRate = 0.032; + + //Rotations per second is calculated based on a standard update tick being 32ms. So we scale by the tick speed, then add that to our rotation to + //get a nice rotation speed. + if(%this.horizontal) + { + if(%this.forward) + %this.owner.rotation.z += ( ( 360 * %this.rotationsPerMinute ) / 60 ) * %tickRate; + else + %this.owner.rotation.z -= ( ( 360 * %this.rotationsPerMinute ) / 60 ) * %tickRate; + } + else + { + %this.owner.rotation.x += ( ( 360 * %this.rotationsPerMinute ) / 60 ) * %tickRate; + } +} \ No newline at end of file diff --git a/Templates/Full/game/scripts/server/components/game/playerSpawner.asset.taml b/Templates/Full/game/scripts/server/components/game/playerSpawner.asset.taml new file mode 100644 index 000000000..d181a86b4 --- /dev/null +++ b/Templates/Full/game/scripts/server/components/game/playerSpawner.asset.taml @@ -0,0 +1,7 @@ + diff --git a/Templates/Full/game/scripts/server/components/game/playerSpawner.cs b/Templates/Full/game/scripts/server/components/game/playerSpawner.cs new file mode 100644 index 000000000..fb6507d08 --- /dev/null +++ b/Templates/Full/game/scripts/server/components/game/playerSpawner.cs @@ -0,0 +1,70 @@ +//----------------------------------------------------------------------------- +// 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. +//----------------------------------------------------------------------------- + +//registerComponent("PlayerSpawner", "Component", +// "Player Spawner", "Game", false, "When a client connects, it spawns a player object for them and attaches them to it"); + +function PlayerSpawner::onAdd(%this) +{ + %this.clientCount = 1; + %this.friendlyName = "Player Spawner"; + %this.componentType = "Spawner"; + + %this.addComponentField("GameObjectName", "The name of the game object we spawn for the players", string, "PlayerObject"); +} + +function PlayerSpawner::onClientConnect(%this, %client) +{ + %playerObj = SGOManager.spawn(%this.GameObjectName); + + if(!isObject(%playerObj)) + return; + + %playerObj.position = %this.owner.position; + + MissionCleanup.add(%playerObj); + + for(%b = 0; %b < %playerObj.getComponentCount(); %b++) + { + %comp = %playerObj.getComponentByIndex(%b); + + if(%comp.isMethod("onClientConnect")) + %comp.onClientConnect(%client); + } + + switchControlObject(%client, %playerObj); + switchCamera(%client, %playerObj); + + //%playerObj.getComponent(FPSControls).setupControls(%client); + + %this.clientCount++; +} + +function PlayerSpawner::onClientDisConnect(%this, %client) +{ + +} + +function PlayerSpawner::getClientID(%this) +{ + return ClientGroup.getObject(%this.clientOwner-1); +} \ No newline at end of file diff --git a/Templates/Full/game/scripts/server/components/input/fpsControls.asset.taml b/Templates/Full/game/scripts/server/components/input/fpsControls.asset.taml new file mode 100644 index 000000000..34f31f181 --- /dev/null +++ b/Templates/Full/game/scripts/server/components/input/fpsControls.asset.taml @@ -0,0 +1,7 @@ + diff --git a/Templates/Full/game/scripts/server/components/input/fpsControls.cs b/Templates/Full/game/scripts/server/components/input/fpsControls.cs new file mode 100644 index 000000000..8331e409d --- /dev/null +++ b/Templates/Full/game/scripts/server/components/input/fpsControls.cs @@ -0,0 +1,247 @@ +//----------------------------------------------------------------------------- +// 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. +//----------------------------------------------------------------------------- + +//registerComponent("FPSControls", "Component", "FPS Controls", "Input", false, "First Person Shooter-type controls"); + +function FPSControls::onAdd(%this) +{ + // + %this.beginGroup("Keys"); + %this.addComponentField(forwardKey, "Key to bind to vertical thrust", keybind, "keyboard w"); + %this.addComponentField(backKey, "Key to bind to vertical thrust", keybind, "keyboard s"); + %this.addComponentField(leftKey, "Key to bind to horizontal thrust", keybind, "keyboard a"); + %this.addComponentField(rightKey, "Key to bind to horizontal thrust", keybind, "keyboard d"); + + %this.addComponentField(jump, "Key to bind to horizontal thrust", keybind, "keyboard space"); + %this.endGroup(); + + %this.beginGroup("Mouse"); + %this.addComponentField(pitchAxis, "Key to bind to horizontal thrust", keybind, "mouse yaxis"); + %this.addComponentField(yawAxis, "Key to bind to horizontal thrust", keybind, "mouse xaxis"); + %this.endGroup(); + + %this.addComponentField(moveSpeed, "Horizontal thrust force", float, 300.0); + %this.addComponentField(jumpStrength, "Vertical thrust force", float, 3.0); + // + + %control = %this.owner.getComponent( ControlObjectComponent ); + if(!%control) + return echo("SPECTATOR CONTROLS: No Control Object behavior!"); + + //%this.Physics = %this.owner.getComponent( PlayerPhysicsComponent ); + + //%this.Animation = %this.owner.getComponent( AnimationComponent ); + + //%this.Camera = %this.owner.getComponent( MountedCameraComponent ); + + //%this.Animation.playThread(0, "look"); + + %this.setupControls(%control.getClientID()); +} + +function FPSControls::onRemove(%this) +{ + Parent::onBehaviorRemove(%this); + + commandToClient(%control.clientOwnerID, 'removeInput', %this.forwardKey); + commandToClient(%control.clientOwnerID, 'removeInput', %this.backKey); + commandToClient(%control.clientOwnerID, 'removeInput', %this.leftKey); + commandToClient(%control.clientOwnerID, 'removeInput', %this.rightKey); + + commandToClient(%control.clientOwnerID, 'removeInput', %this.pitchAxis); + commandToClient(%control.clientOwnerID, 'removeInput', %this.yawAxis); +} + +function FPSControls::onBehaviorFieldUpdate(%this, %field) +{ + %controller = %this.owner.getBehavior( ControlObjectBehavior ); + commandToClient(%controller.clientOwnerID, 'updateInput', %this.getFieldValue(%field), %field); +} + +function FPSControls::onClientConnect(%this, %client) +{ + %this.setupControls(%client); +} + + +function FPSControls::setupControls(%this, %client) +{ + %control = %this.owner.getComponent( ControlObjectComponent ); + if(!%control.isControlClient(%client)) + { + echo("FPS CONTROLS: Client Did Not Match"); + return; + } + + %inputCommand = "FPSControls"; + + %test = %this.forwardKey; + + /*SetInput(%client, %this.forwardKey.x, %this.forwardKey.y, %inputCommand@"_forwardKey"); + SetInput(%client, %this.backKey.x, %this.backKey.y, %inputCommand@"_backKey"); + SetInput(%client, %this.leftKey.x, %this.leftKey.y, %inputCommand@"_leftKey"); + SetInput(%client, %this.rightKey.x, %this.rightKey.y, %inputCommand@"_rightKey"); + + SetInput(%client, %this.jump.x, %this.jump.y, %inputCommand@"_jump"); + + SetInput(%client, %this.pitchAxis.x, %this.pitchAxis.y, %inputCommand@"_pitchAxis"); + SetInput(%client, %this.yawAxis.x, %this.yawAxis.y, %inputCommand@"_yawAxis");*/ + + SetInput(%client, "keyboard", "w", %inputCommand@"_forwardKey"); + SetInput(%client, "keyboard", "s", %inputCommand@"_backKey"); + SetInput(%client, "keyboard", "a", %inputCommand@"_leftKey"); + SetInput(%client, "keyboard", "d", %inputCommand@"_rightKey"); + + SetInput(%client, "keyboard", "space", %inputCommand@"_jump"); + + SetInput(%client, "mouse", "yaxis", %inputCommand@"_pitchAxis"); + SetInput(%client, "mouse", "xaxis", %inputCommand@"_yawAxis"); + + SetInput(%client, "keyboard", "f", %inputCommand@"_flashlight"); + +} + +function FPSControls::onMoveTrigger(%this, %triggerID) +{ + //check if our jump trigger was pressed! + if(%triggerID == 2) + { + %this.owner.applyImpulse("0 0 0", "0 0 " @ %this.jumpStrength); + } +} + +function FPSControls::Update(%this) +{ + return; + + %moveVector = %this.owner.getMoveVector(); + %moveRotation = %this.owner.getMoveRotation(); + + %this.Physics.moveVector = "0 0 0"; + + if(%moveVector.x != 0) + { + %fv = VectorNormalize(%this.owner.getRightVector()); + + %forMove = VectorScale(%fv, (%moveVector.x));// * (%this.moveSpeed * 0.032))); + + //%this.Physics.velocity = VectorAdd(%this.Physics.velocity, %forMove); + + %this.Physics.moveVector = VectorAdd(%this.Physics.moveVector, %forMove); + + //if(%forMove > 0) + // %this.Animation.playThread(1, "run"); + } + /*else + { + %fv = VectorNormalize(%this.owner.getRightVector()); + + %forMove = VectorScale(%fv, (%moveVector.x * (%this.moveSpeed * 0.032))); + + if(%forMove <= 0) + %this.Animation.stopThread(1); + + }*/ + + if(%moveVector.y != 0) + { + %fv = VectorNormalize(%this.owner.getForwardVector()); + + %forMove = VectorScale(%fv, (%moveVector.y));// * (%this.moveSpeed * 0.032))); + + //%this.Physics.velocity = VectorAdd(%this.Physics.velocity, %forMove); + + %this.Physics.moveVector = VectorAdd(%this.Physics.moveVector, %forMove); + + //if(VectorLen(%this.Physics.velocity) < 2) + // %this.Physics.velocity = VectorAdd(%this.Physics.velocity, %forMove); + } + + /*if(%moveVector.z) + { + %fv = VectorNormalize(%this.owner.getUpVector()); + + %forMove = VectorScale(%fv, (%moveVector.z * (%this.moveSpeed * 0.032))); + + %this.Physics.velocity = VectorAdd(%this.Physics.velocity, %forMove); + }*/ + + if(%moveRotation.x != 0) + { + %look = mRadToDeg(%moveRotation.x) / 180; + + //%this.Animation.setThreadPos(0, %look); + + %this.owner.getComponent( MountedCameraComponent ).rotationOffset.x += mRadToDeg(%moveRotation.x); + + //%this.Camera.rotationOffset.x += mRadToDeg(%moveRotation.x); + } + // %this.owner.rotation.x += mRadToDeg(%moveRotation.x); + + if(%moveRotation.z != 0) + { + %zrot = mRadToDeg(%moveRotation.z); + %this.owner.getComponent( MountedCameraComponent ).rotationOffset.z += %zrot; + //%this.owner.rotation.z += %zrot; + } +} + +// +function FPSControls_forwardKey(%val) +{ + $mvForwardAction = %val; +} + +function FPSControls_backKey(%val) +{ + $mvBackwardAction = %val; +} + +function FPSControls_leftKey(%val) +{ + $mvLeftAction = %val; +} + +function FPSControls_rightKey(%val) +{ + $mvRightAction = %val; +} + +function FPSControls_yawAxis(%val) +{ + $mvYaw += getMouseAdjustAmount(%val); +} + +function FPSControls_pitchAxis(%val) +{ + $mvPitch += getMouseAdjustAmount(%val); +} + +function FPSControls_jump(%val) +{ + $mvTriggerCount2++; +} + +function FPSControls_flashLight(%val) +{ + $mvTriggerCount3++; +} \ No newline at end of file diff --git a/Templates/Full/game/scripts/server/components/input/inputManager.cs b/Templates/Full/game/scripts/server/components/input/inputManager.cs new file mode 100644 index 000000000..c8123d1e3 --- /dev/null +++ b/Templates/Full/game/scripts/server/components/input/inputManager.cs @@ -0,0 +1,82 @@ +//----------------------------------------------------------------------------- +// 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. +//----------------------------------------------------------------------------- + +function SetInput(%client, %device, %key, %command, %bindMap, %behav) +{ + commandToClient(%client, 'SetInput', %device, %key, %command, %bindMap, %behav); +} + +function RemoveInput(%client, %device, %key, %command, %bindMap) +{ + commandToClient(%client, 'removeInput', %device, %key, %command, %bindMap); +} + +function clientCmdSetInput(%device, %key, %command, %bindMap, %behav) +{ + //if we're requesting a custom bind map, set that up + if(%bindMap $= "") + %bindMap = moveMap; + + if (!isObject(%bindMap)){ + new ActionMap(moveMap); + moveMap.push(); + } + + //get our local + //%localID = ServerConnection.resolveGhostID(%behav); + + //%tmpl = %localID.getTemplate(); + //%tmpl.insantiateNamespace(%tmpl.getName()); + + //first, check if we have an existing command + %oldBind = %bindMap.getBinding(%command); + if(%oldBind !$= "") + %bindMap.unbind(getField(%oldBind, 0), getField(%oldBind, 1)); + + //now, set the requested bind + %bindMap.bind(%device, %key, %command); +} + +function clientCmdRemoveSpecCtrlInput(%device, %key, %bindMap) +{ + //if we're requesting a custom bind map, set that up + if(%bindMap $= "") + %bindMap = moveMap; + + if (!isObject(%bindMap)) + return; + + %bindMap.unbind(%device, %key); +} + +function clientCmdSetupClientBehavior(%bhvrGstID) +{ + %localID = ServerConnection.resolveGhostID(%bhvrGstID); + %tmpl = %localID.getTemplate(); + %tmpl.insantiateNamespace(%tmpl.getName()); +} + +function getMouseAdjustAmount(%val) +{ + // based on a default camera FOV of 90' + return(%val * ($cameraFov / 90) * 0.01) * $pref::Input::LinkMouseSensitivity; +} \ No newline at end of file diff --git a/Templates/Full/game/scripts/server/components/meshComponent.asset.taml b/Templates/Full/game/scripts/server/components/meshComponent.asset.taml new file mode 100644 index 000000000..d019cd893 --- /dev/null +++ b/Templates/Full/game/scripts/server/components/meshComponent.asset.taml @@ -0,0 +1,7 @@ + diff --git a/Templates/Full/game/scripts/server/components/playerControllerComponent.asset.taml b/Templates/Full/game/scripts/server/components/playerControllerComponent.asset.taml new file mode 100644 index 000000000..d5174644b --- /dev/null +++ b/Templates/Full/game/scripts/server/components/playerControllerComponent.asset.taml @@ -0,0 +1,7 @@ + diff --git a/Templates/Full/game/scripts/server/components/stateMachineComponent.asset.taml b/Templates/Full/game/scripts/server/components/stateMachineComponent.asset.taml new file mode 100644 index 000000000..a261bb194 --- /dev/null +++ b/Templates/Full/game/scripts/server/components/stateMachineComponent.asset.taml @@ -0,0 +1,7 @@ + diff --git a/Templates/Full/game/scripts/server/gameCore.cs b/Templates/Full/game/scripts/server/gameCore.cs index abff15c0a..bb7aed714 100644 --- a/Templates/Full/game/scripts/server/gameCore.cs +++ b/Templates/Full/game/scripts/server/gameCore.cs @@ -573,6 +573,31 @@ function GameCore::onClientEnterGame(%game, %client) %client.isAiControlled(), %client.isAdmin, %client.isSuperAdmin); + + %entityIds = parseMissionGroupForIds("Entity", ""); + %entityCount = getWordCount(%entityIds); + + for(%i=0; %i < %entityCount; %i++) + { + %entity = getWord(%entityIds, %i); + + for(%e=0; %e < %entity.getCount(); %e++) + { + %child = %entity.getObject(%e); + if(%child.getCLassName() $= "Entity") + %entityIds = %entityIds SPC %child.getID(); + } + + for(%c=0; %c < %entity.getComponentCount(); %c++) + { + %comp = %entity.getComponentByIndex(%c); + + if(%comp.isMethod("onClientConnect")) + { + %comp.onClientConnect(%client); + } + } + } } function GameCore::onClientLeaveGame(%game, %client) diff --git a/Templates/Full/game/scripts/server/gameObjects/GameObjectManager.cs b/Templates/Full/game/scripts/server/gameObjects/GameObjectManager.cs new file mode 100644 index 000000000..f0b618920 --- /dev/null +++ b/Templates/Full/game/scripts/server/gameObjects/GameObjectManager.cs @@ -0,0 +1,123 @@ +//----------------------------------------------------------------------------- +// 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. +//----------------------------------------------------------------------------- +function execGameObjects() +{ + //find all GameObjectAssets + %assetQuery = new AssetQuery(); + if(!AssetDatabase.findAssetType(%assetQuery, "GameObjectAsset")) + return; //if we didn't find ANY, just exit + + %count = %assetQuery.getCount(); + + for(%i=0; %i < %count; %i++) + { + %assetId = %assetQuery.getAsset(%i); + + %gameObjectAsset = AssetDatabase.acquireAsset(%assetId); + + if(isFile(%gameObjectAsset.scriptFilePath)) + exec(%gameObjectAsset.scriptFilePath); + } +} + +function findGameObject(%name) +{ + //find all GameObjectAssets + %assetQuery = new AssetQuery(); + if(!AssetDatabase.findAssetType(%assetQuery, "GameObjectAsset")) + return 0; //if we didn't find ANY, just exit + + %count = %assetQuery.getCount(); + + for(%i=0; %i < %count; %i++) + { + %assetId = %assetQuery.getAsset(%i); + + %gameObjectAsset = AssetDatabase.acquireAsset(%assetId); + + if(%gameObjectAsset.gameObjectName $= %name) + { + if(isFile(%gameObjectAsset.TAMLFilePath)) + { + return %gameObjectAsset; + } + } + } + + return 0; +} + +function spawnGameObject(%name, %addToMissionGroup) +{ + if(%addToMissionGroup $= "") + %addToMissionGroup = true; + + %gameObjectAsset = findGameObject(%name); + + if(isObject(%gameObjectAsset)) + { + %newSGOObject = TamlRead(%gameObjectAsset.TAMLFilePath); + + if(%addToMissionGroup == true) //save instance when saving level + MissionGroup.add(%newSGOObject); + else // clear instance on level exit + MissionCleanup.add(%newSGOObject); + + return %newSGOObject; + } + + return 0; +} + +function saveGameObject(%name, %tamlPath, %scriptPath) +{ + %gameObjectAsset = findGameObject(%name); + + //find if it already exists. If it does, we'll update it, if it does not, we'll make a new asset + if(isObject(%gameObjectAsset)) + { + %assetID = %gameObjectAsset.getAssetId(); + + %gameObjectAsset.TAMLFilePath = %tamlPath; + %gameObjectAsset.scriptFilePath = %scriptPath; + + TAMLWrite(%gameObjectAsset, AssetDatabase.getAssetFilePath(%assetID)); + AssetDatabase.refreshAsset(%assetID); + } + else + { + //Doesn't exist, so make a new one + %gameObjectAsset = new GameObjectAsset() + { + assetName = %name @ "Asset"; + gameObjectName = %name; + TAMLFilePath = %tamlPath; + scriptFilePath = %scriptPath; + }; + + //Save it alongside the taml file + %path = filePath(%tamlPath); + + TAMLWrite(%gameObjectAsset, %path @ "/" @ %name @ ".asset.taml"); + AssetDatabase.refreshAllAssets(true); + } +} \ No newline at end of file diff --git a/Templates/Full/game/scripts/server/gameObjects/ThirdPersonPlayerObject.asset.taml b/Templates/Full/game/scripts/server/gameObjects/ThirdPersonPlayerObject.asset.taml new file mode 100644 index 000000000..2d593a50b --- /dev/null +++ b/Templates/Full/game/scripts/server/gameObjects/ThirdPersonPlayerObject.asset.taml @@ -0,0 +1,5 @@ + diff --git a/Templates/Full/game/scripts/server/gameObjects/ThirdPersonPlayerObject.cs b/Templates/Full/game/scripts/server/gameObjects/ThirdPersonPlayerObject.cs new file mode 100644 index 000000000..3ab05a79d --- /dev/null +++ b/Templates/Full/game/scripts/server/gameObjects/ThirdPersonPlayerObject.cs @@ -0,0 +1,247 @@ +function ThirdPersonPlayerObject::onAdd(%this) +{ + %this.turnRate = 0.3; + + %this.phys = %this.getComponent("PlayerControllerComponent"); + %this.collision = %this.getComponent("CollisionComponent"); + %this.cam = %this.getComponent("CameraComponent"); + %this.camArm = %this.getComponent("CameraOrbiterComponent"); + %this.animation = %this.getComponent("AnimationComponent"); + %this.stateMachine = %this.getComponent("StateMachineComponent"); + %this.mesh = %this.getComponent("MeshComponent"); + + %this.stateMachine.forwardVector = 0; + + %this.crouch = false; + + %this.firstPerson = false; + + %this.crouchSpeedMod = 0.5; + + %this.aimOrbitDist = 1.5; + %this.regularOrbitDist = 5; + + %this.regularOrbitMaxPitch = 70; + %this.regularOrbitMinPitch = -10; + + %this.aimedMaxPitch = 90; + %this.aimedMinPitch = -90; +} + +function ThirdPersonPlayerObject::onRemove(%this) +{ + +} + +function ThirdPersonPlayerObject::moveVectorEvent(%this) +{ + %moveVector = %this.getMoveVector(); + + // forward of the camera on the x-z plane + %cameraForward = %this.cam.getForwardVector(); + + %cameraRight = %this.cam.getRightVector(); + + %moveVec = VectorAdd(VectorScale(%cameraRight, %moveVector.x), VectorScale(%cameraForward, %moveVector.y)); + + if(%this.aiming || %this.firstPerson) + { + %forMove = "0 0 0"; + + if(%moveVector.x != 0) + { + %this.phys.inputVelocity.x = %moveVector.x * 10; + } + else + { + %this.phys.inputVelocity.x = 0; + } + + if(%moveVector.y != 0) + { + + %this.phys.inputVelocity.y = %moveVector.y * 10; + } + else + { + %this.phys.inputVelocity.y = 0; + } + } + else + { + if(%moveVec.x == 0 && %moveVec.y == 0) + { + %this.phys.inputVelocity = "0 0 0"; + %this.stateMachine.forwardVector = 0; + } + else + { + %moveVec.z = 0; + + %curForVec = %this.getForwardVector(); + + %newForVec = VectorLerp(%curForVec, %moveVec, %this.turnRate); + + %this.setForwardVector(%newForVec); + + %this.phys.inputVelocity.y = 10; + + %this.stateMachine.forwardVector = 1; + } + } + + if(%this.crouch) + %this.phys.inputVelocity = VectorScale(%this.phys.inputVelocity, %this.crouchSpeedMod); +} + +function ThirdPersonPlayerObject::moveYawEvent(%this) +{ + %moveRotation = %this.getMoveRotation(); + + %camOrb = %this.getComponent("CameraOrbiterComponent"); + + if(%this.aiming || %this.firstPerson) + { + %this.rotation.z += %moveRotation.z * 10; + } + + %camOrb.rotation.z += %moveRotation.z * 10; +} + +function ThirdPersonPlayerObject::movePitchEvent(%this) +{ + %moveRotation = %this.getMoveRotation(); + + %camOrb = %this.getComponent("CameraOrbiterComponent"); + + %camOrb.rotation.x += %moveRotation.x * 10; +} + +function ThirdPersonPlayerObject::moveRollEvent(%this){} + +function ThirdPersonPlayerObject::moveTriggerEvent(%this, %triggerNum, %triggerValue) +{ + if(%triggerNum == 3 && %triggerValue) + { + if(%triggerValue) + { + %this.firstPerson = !%this.firstPerson; + + if(%this.firstPerson) + { + %this.rotation.z = %this.cam.rotationOffset.z; + %this.camArm.orbitDistance = 0; + %this.camArm.maxPitchAngle = %this.aimedMaxPitch; + %this.camArm.minPitchAngle = %this.aimedMinPitch; + + %this.cam.positionOffset = "0 0 0"; + %this.cam.rotationOffset = "0 0 0"; + } + else if(%this.aiming) + { + %this.camArm.orbitDistance = %this.aimOrbitDist; + + %this.camArm.maxPitchAngle = %this.aimedMaxPitch; + %this.camArm.minPitchAngle = %this.aimedMinPitch; + } + else + { + %this.camArm.orbitDistance = %this.regularOrbitDist; + + %this.camArm.maxPitchAngle = %this.regularOrbitMaxPitch; + %this.camArm.minPitchAngle = %this.regularOrbitMinPitch; + } + + commandToClient(localclientConnection, 'SetClientRenderShapeVisibility', + localclientConnection.getGhostID(%this.getComponent("MeshComponent")), !%this.firstPerson); + } + } + else if(%triggerNum == 2 && %triggerValue == true) + { + //get our best collision assuming up is 0 0 1 + %collisionAngle = %this.collision.getBestCollisionAngle("0 0 1"); + + if(%collisionAngle >= 80) + { + %surfaceNormal = %this.collision.getCollisionNormal(0); + %jumpVector = VectorScale(%surfaceNormal, 200); + echo("Jump surface Angle is at: " @ %surfaceNormal); + + %this.phys.applyImpulse(%this.position, %jumpVector); + %this.setForwardVector(%jumpVector); + } + else + %this.phys.applyImpulse(%this.position, "0 0 300"); + } + else if(%triggerNum == 4) + { + %this.crouch = %triggerValue; + } + else if(%triggerNum == 1) + { + %this.aiming = %triggerValue; + + if(%this.aiming) + { + %this.rotation.z = %this.cam.rotationOffset.z; + %this.camArm.orbitDistance = %this.aimOrbitDist; + %this.camArm.maxPitchAngle = %this.aimedMaxPitch; + %this.camArm.minPitchAngle = %this.aimedMinPitch; + } + else + { + %this.camArm.orbitDistance = %this.regularOrbitDist; + %this.camArm.maxPitchAngle = %this.regularOrbitMaxPitch; + %this.camArm.minPitchAngle = %this.regularOrbitMinPitch; + } + } +} + +function ThirdPersonPlayerObject::onCollisionEvent(%this, %colObject, %colNormal, %colPoint, %colMatID, %velocity) +{ + if(!%this.phys.isContacted()) + echo(%this @ " collided with " @ %colObject); +} + +function ThirdPersonPlayerObject::processTick(%this) +{ + %moveVec = %this.getMoveVector(); + %bestFit = ""; + + if(%this.crouch) + { + if(%moveVec.x != 0 || %moveVec.y != 0) + %bestFit = "Crouch_Forward"; + else + %bestFit = "Crouch_Root"; + } + else + { + if(%moveVec.x != 0 || %moveVec.y != 0) + %bestFit = "Run"; + else + %bestFit = "Root"; + } + + if(%this.animation.getThreadAnimation(0) !$= %bestFit) + %this.animation.playThread(0, %bestFit); +} + +//Used for first person mode +function clientCmdSetClientRenderShapeVisibility(%id, %visiblilty) +{ + %localID = ServerConnection.resolveGhostID(%id); + %localID.enabled = %visiblilty; +} + +function serverToClientObject( %serverObject ) +{ + assert( isObject( LocalClientConnection ), "serverToClientObject() - No local client connection found!" ); + assert( isObject( ServerConnection ), "serverToClientObject() - No server connection found!" ); + + %ghostId = LocalClientConnection.getGhostId( %serverObject ); + if ( %ghostId == -1 ) + return 0; + + return ServerConnection.resolveGhostID( %ghostId ); +} \ No newline at end of file diff --git a/Templates/Full/game/scripts/server/gameObjects/ThirdPersonPlayerObject.taml b/Templates/Full/game/scripts/server/gameObjects/ThirdPersonPlayerObject.taml new file mode 100644 index 000000000..df2397bb5 --- /dev/null +++ b/Templates/Full/game/scripts/server/gameObjects/ThirdPersonPlayerObject.taml @@ -0,0 +1,98 @@ + + + + + + + + + + + diff --git a/Templates/Full/game/scripts/server/scriptExec.cs b/Templates/Full/game/scripts/server/scriptExec.cs index a61204040..d48e268e7 100644 --- a/Templates/Full/game/scripts/server/scriptExec.cs +++ b/Templates/Full/game/scripts/server/scriptExec.cs @@ -58,3 +58,23 @@ exec("./turret.cs"); // Load our gametypes exec("./gameCore.cs"); // This is the 'core' of the gametype functionality. exec("./gameDM.cs"); // Overrides GameCore with DeathMatch functionality. + +//Entity/Component stuff +if(isFile("./components/game/camera.cs")) + exec("./components/game/camera.cs"); +if(isFile("./components/game/controlObject.cs")) + exec("./components/game/controlObject.cs"); +if(isFile("./components/game/itemRotate.cs")) + exec("./components/game/itemRotate.cs"); +if(isFile("./components/game/playerSpawner.cs")) + exec("./components/game/playerSpawner.cs"); +if(isFile("./components/input/fpsControls.cs")) + exec("./components/input/fpsControls.cs"); +if(isFile("./components/input/inputManager.cs")) + exec("./components/input/inputManager.cs"); + +if(isFile("./gameObjects/GameObjectManager.cs")) +{ + exec("./gameObjects/GameObjectManager.cs"); + execGameObjects(); +} \ No newline at end of file diff --git a/Templates/Full/game/tools/componentEditor/gui/superToolTipDlg.ed.gui b/Templates/Full/game/tools/componentEditor/gui/superToolTipDlg.ed.gui new file mode 100644 index 000000000..ef506941a --- /dev/null +++ b/Templates/Full/game/tools/componentEditor/gui/superToolTipDlg.ed.gui @@ -0,0 +1,45 @@ +%guiContent = new GuiControl(SuperTooltipDlg) { + canSaveDynamicFields = "0"; + Profile = "GuiTransparentProfileModeless"; + class = "SuperTooltip"; + HorizSizing = "right"; + VertSizing = "bottom"; + position = "0 0"; + Extent = "640 480"; + MinExtent = "8 2"; + canSave = "1"; + Visible = "1"; + hovertime = "1000"; + + new GuiControl(SuperTooltipWindow) { + canSaveDynamicFields = "0"; + Profile = "EditorTextEditBoldModeless"; + HorizSizing = "right"; + VertSizing = "bottom"; + position = "216 160"; + Extent = "221 134"; + MinExtent = "8 2"; + canSave = "1"; + Visible = "1"; + hovertime = "1000"; + internalName = "tooltipWindow"; + + new GuiMLTextCtrl(SuperTooltipMLText) { + canSaveDynamicFields = "0"; + Profile = "EditorMLTextProfileModeless"; + HorizSizing = "right"; + VertSizing = "bottom"; + position = "5 5"; + Extent = "210 14"; + MinExtent = "8 2"; + canSave = "1"; + Visible = "1"; + hovertime = "1000"; + lineSpacing = "2"; + allowColorChars = "0"; + maxChars = "-1"; + internalName = "tooltipMLText"; + }; + }; +}; + diff --git a/Engine/source/component/simpleComponent.cpp b/Templates/Full/game/tools/componentEditor/main.cs similarity index 80% rename from Engine/source/component/simpleComponent.cpp rename to Templates/Full/game/tools/componentEditor/main.cs index 246672c28..56d74830a 100644 --- a/Engine/source/component/simpleComponent.cpp +++ b/Templates/Full/game/tools/componentEditor/main.cs @@ -20,12 +20,9 @@ // IN THE SOFTWARE. //----------------------------------------------------------------------------- -#include "component/simpleComponent.h" +//Scripts +exec("./scripts/componentEditor.ed.cs"); +exec("./scripts/superToolTipDlg.ed.cs"); -IMPLEMENT_CONOBJECT(SimpleComponent); - -ConsoleDocClass( SimpleComponent, - "@brief The purpose of this component is to provide a minimalistic component that " - "exposes a simple, cached interface\n\n" - "Soon to be deprecated, internal only.\n\n " - "@internal"); \ No newline at end of file +//gui +exec("./gui/superToolTipDlg.ed.gui"); diff --git a/Templates/Full/game/tools/componentEditor/scripts/componentEditor.ed.cs b/Templates/Full/game/tools/componentEditor/scripts/componentEditor.ed.cs new file mode 100644 index 000000000..9a9ce33d6 --- /dev/null +++ b/Templates/Full/game/tools/componentEditor/scripts/componentEditor.ed.cs @@ -0,0 +1,233 @@ +//----------------------------------------------------------------------------- +// 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. +//----------------------------------------------------------------------------- + +function GuiInspectorEntityGroup::CreateContent(%this) +{ +} + +function GuiInspectorEntityGroup::InspectObject( %this, %targetObject ) +{ + %this.stack.clear(); + %this.stack.addGuiControl(%this.createAddComponentList()); +} + +function GuiInspectorEntityGroup::createAddComponentList(%this) +{ + %extent = %this.getExtent(); + + %container = new GuiControl() + { + Profile = "EditorContainerProfile"; + HorizSizing = "width"; + VertSizing = "bottom"; + Position = "0 0"; + Extent = %extent.x SPC "25"; + }; + + %componentList = new GuiPopUpMenuCtrlEx(QuickEditComponentList) + { + Profile = "GuiPopupMenuProfile"; + HorizSizing = "width"; + VertSizing = "bottom"; + position = "28 4"; + Extent = (%extent.x - 28) SPC "18"; + hovertime = "100"; + tooltip = "The component to add to the object"; + tooltipProfile = "EditorToolTipProfile"; + }; + + %addButton = new GuiIconButtonCtrl() { + class = AddComponentQuickEditButton; + Profile = "EditorButton"; + HorizSizing = "right"; + VertSizing = "bottom"; + Position = "2 0"; + Extent = "24 24"; + buttonMargin = "4 4"; + iconLocation = "Left"; + sizeIconToButton = "0"; + iconBitmap = "tools/gui/images/iconAdd.png"; + hovertime = "100"; + tooltip = "Add the selected component to the object"; + tooltipProfile = "EditorToolTipProfile"; + componentList = %componentList; + }; + + %componentList.refresh(); + + %container.add(%componentList); + %container.add(%addButton); + + if(!isObject("componentTooltipTheme")) + { + %theme = createsupertooltiptheme("componentTooltipTheme"); + %theme.addstyle("headerstyle", ""); + %theme.addstyle("headertwostyle", ""); + %theme.addstyle("basictextstyle", ""); + %theme.setdefaultstyle("title", "headerstyle"); + %theme.setdefaultstyle("paramtitle", "headertwostyle"); + %theme.setdefaultstyle("param", "basictextstyle"); + %theme.setspacing(3, 0); + } + + return %container; +} + +function QuickEditComponentList::refresh(%this) +{ + %this.clear(); + + //find all ComponentAssets + %assetQuery = new AssetQuery(); + if(!AssetDatabase.findAssetType(%assetQuery, "ComponentAsset")) + return; //if we didn't find ANY, just exit + + // Find all the types. + %count = %assetQuery.getCount(); + + %categories = ""; + for (%i = 0; %i < %count; %i++) + { + %assetId = %assetQuery.getAsset(%i); + + %componentAsset = AssetDatabase.acquireAsset(%assetId); + %componentType = %componentAsset.componentType; + if (!isInList(%componentType, %categories)) + %categories = %categories TAB %componentType; + } + + %categories = trim(%categories); + + %index = 0; + %categoryCount = getFieldCount(%categories); + for (%i = 0; %i < %categoryCount; %i++) + { + %category = getField(%categories, %i); + %this.addCategory(%category); + + for (%j = 0; %j < %count; %j++) + { + %assetId = %assetQuery.getAsset(%j); + + %componentAsset = AssetDatabase.acquireAsset(%assetId); + %componentType = %componentAsset.componentType; + %friendlyName = %componentAsset.friendlyName; + + if (%componentType $= %category) + { + //TODO: Haven't worked out getting categories to look distinct + //from entries in the drop-down so for now just indent them for the visual distinction + %spacedName = " " @ %friendlyName; + %this.add(%spacedName, %index); + %this.component[%index] = %componentAsset; + %index++; + } + } + } +} + +function QuickEditComponentList::onHotTrackItem( %this, %itemID ) +{ + %componentObj = %this.component[%itemID]; + if( isObject( %componentObj ) && %this.componentDesc != %componentObj ) + { + SuperTooltipDlg.init("componentTooltipTheme"); + SuperTooltipDlg.setTitle(%componentObj.friendlyName); + SuperTooltipDlg.addParam("", %componentObj.description @ "\n"); + + %fieldCount = %componentObj.getComponentFieldCount(); + for (%i = 0; %i < %fieldCount; %i++) + { + %name = getField(%componentObj.getComponentField(%i), 0); + + SuperTooltipDlg.addParam(%name, %description @ "\n"); + } + %position = %this.getGlobalPosition(); + SuperTooltipDlg.processTooltip( %position,0,1 ); + %this.opened = true; + %this.componentDesc = %componentObj; + } + else if( !isObject( %componentObj ) ) + { + if( %this.opened == true ) + SuperTooltipDlg.hide(); + %this.componentDesc = ""; + } +} + +function QuickEditComponentList::setProperty(%this, %object) +{ + %this.objectToAdd = %object; +} + +function QuickEditComponentList::onSelect(%this) +{ + if( %this.opened == true ) + SuperTooltipDlg.hide(); + + %this.componentToAdd = %this.component[%this.getSelected()]; +} + +function QuickEditComponentList::onCancel( %this ) +{ + if( %this.opened == true ) + SuperTooltipDlg.hide(); +} + +function AddComponentQuickEditButton::onClick(%this) +{ + %component = %this.componentList.componentToAdd; + + %componentName = %this.componentList.componentToAdd.componentName; + %componentClass = %this.componentList.componentToAdd.componentClass; + + %command = "$ComponentEditor::newComponent = new" SPC %componentClass SPC "(){ class = \"" + @ %componentName @ "\"; };"; + + eval(%command); + + %instance = $ComponentEditor::newComponent; + %undo = new UndoScriptAction() + { + actionName = "Added Component"; + class = UndoAddComponent; + object = %this.componentList.objectToAdd; + component = %instance; + }; + + %undo.addToManager(LevelBuilderUndoManager); + + %instance.owner = Inspector.getInspectObject(0); + %instance.owner.add(%instance); + + Inspector.schedule( 50, "refresh" ); + EWorldEditor.isDirty = true; +} + +function addComponent(%obj, %instance) +{ + echo("Adding the component!"); + %obj.addComponent(%instance); + Inspector.schedule( 50, "refresh" ); + EWorldEditor.isDirty = true; +} + diff --git a/Templates/Full/game/tools/componentEditor/scripts/superToolTipDlg.ed.cs b/Templates/Full/game/tools/componentEditor/scripts/superToolTipDlg.ed.cs new file mode 100644 index 000000000..7f25bd5e6 --- /dev/null +++ b/Templates/Full/game/tools/componentEditor/scripts/superToolTipDlg.ed.cs @@ -0,0 +1,155 @@ +function createSuperTooltipTheme(%name) +{ + %theme = new ScriptObject() + { + class = SuperTooltipTheme; + }; + + %theme.setName(%name); + + return %theme; +} + +function SuperTooltipTheme::addStyle(%this, %name, %style) +{ + %this.styles[%name] = %style; +} + +function SuperTooltipTheme::setDefaultStyle(%this, %type, %default) +{ + %this.defaultStyles[%type] = %default; +} + +function SuperTooltipTheme::setSpacing(%this, %verticalSpace, %horizontalSpace) +{ + %this.verticalSpace = %verticalSpace; + %this.horizontalSpace = %horizontalSpace; +} + +function SuperTooltipTheme::getStyle(%this, %name) +{ + return %this.styles[%name]; +} + +function SuperTooltip::init(%this, %theme) +{ + %this.clearTooltip(); + + if(isObject(%theme)) + %this.setTheme(%theme); +} + +function SuperTooltip::clearTooltip(%this) +{ + if(%this.paramCount > 0) + { + for(%i=0;%i<%this.paramCount;%i++) + %this.param[%i] = ""; + } + + %this.title = ""; + %this.paramCount = 0; +} + +function SuperTooltip::processTooltip(%this, %globalPos, %verticalAlign, %horizontalAlign) +{ + if (%verticalAlign $= "") + %verticalAlign = 1; + if (%horizontalAlign $= "") + %horizontalAlign = 0; + + %tooltipWindow = %this.findObjectByInternalName("tooltipWindow"); + + if(isObject(%tooltipWindow)) + %tooltipMLText = %tooltipWindow.findObjectByInternalName("tooltipMLText"); + else + return false; + + if(!isObject(%tooltipMLText)) + return false; + + %verticalSpace = %this.theme.verticalSpace; + %horizontalSpace = %this.theme.horizontalSpace; + + if (%verticalAlign == 1) + %verticalSpace = -%verticalSpace; + if (%horizontalAlign == 1) + %horizontalSpace = -%horizontalSpace; + + %text = %this.getFormatedText(); + %tooltipMLText.setText(%text); + + canvas.pushDialog(%this); + + %tooltipMLText.forceReflow(); + %MLExtent = %tooltipMLText.extent; + %MLHeight = getWord(%MLExtent, 1); + + %tooltipExtent = %tooltipWindow.extent; + %tooltipWidth = getWord(%tooltipExtent, 0); + %tooltipHeight = %MLHeight; + %tooltipWindow.extent = %tooltipWidth SPC %tooltipHeight; + + %globalPosX = getWord(%globalPos, 0); + %globalPosY = getWord(%globalPos, 1); + + %tooltipPosX = %globalPosX - (%horizontalAlign * %tooltipWidth) + %horizontalSpace; + %tooltipPosY = %globalPosY - (%verticalAlign * %tooltipHeight) + %verticalSpace; + + %tooltipWindow.setPosition(%tooltipPosX, %tooltipPosY); + + return true; +} + +function SuperTooltip::hide(%this) +{ + canvas.popDialog(%this); + + %this.clearTooltip(); +} + +function SuperTooltip::setTheme(%this, %theme) +{ + %this.theme = %theme; +} + +function SuperTooltip::setTitle(%this, %title, %style) +{ + if(%style !$= "") + %themeStyle = %this.theme.styles[%style]; + else + %themeStyle = %this.theme.getStyle(%this.theme.defaultStyles[Title]); + + %this.title = %themeStyle @ %title; +} + +function SuperTooltip::addParam(%this, %title, %text, %paramTitleStyle, %paramStyle) +{ + if(%paramTitleStyle !$= "") + %themeTitleStyle = %this.theme.styles[%paramTitleStyle]; + else + %themeTitleStyle = %this.theme.getStyle(%this.theme.defaultStyles[ParamTitle]); + + if(%paramStyle !$= "") + %themeStyle = %this.theme.styles[%paramStyle]; + else + %themeStyle = %this.theme.getStyle(%this.theme.defaultStyles[Param]); + + if (%title $= "") + %this.param[%this.paramCount] = %themeStyle @ %text @ "\n"; + else + %this.param[%this.paramCount] = %themeTitleStyle @ %title @ ": " @ %themeStyle @ %text @ "\n"; + %this.paramCount++; +} + +function SuperTooltip::getFormatedText(%this) +{ + %text = %this.title @ "\n\n"; + + for(%i=0;%i<%this.paramCount;%i++) + { + %text = %text @ %this.param[%i]; + } + + return %text; +} \ No newline at end of file diff --git a/Templates/Full/game/tools/worldEditor/gui/GeneralSettingsTab.ed.gui b/Templates/Full/game/tools/worldEditor/gui/GeneralSettingsTab.ed.gui index d89ff1a4d..b85e78ae0 100644 --- a/Templates/Full/game/tools/worldEditor/gui/GeneralSettingsTab.ed.gui +++ b/Templates/Full/game/tools/worldEditor/gui/GeneralSettingsTab.ed.gui @@ -204,6 +204,77 @@ editorSettingsWrite = "EditorGui.writeWorldEditorSettings();"; }; }; + new GuiControl() { + position = "0 0"; + extent = "430 18"; + minExtent = "8 2"; + horizSizing = "right"; + vertSizing = "bottom"; + profile = "ToolsGuiDefaultProfile"; + visible = "1"; + active = "1"; + tooltipProfile = "ToolsGuiToolTipProfile"; + hovertime = "1000"; + isContainer = "1"; + canSave = "1"; + canSaveDynamicFields = "0"; + + new GuiTextCtrl() { + text = "New Game Objects"; + maxLength = "1024"; + margin = "0 0 0 0"; + padding = "0 0 0 0"; + anchorTop = "1"; + anchorBottom = "0"; + anchorLeft = "1"; + anchorRight = "0"; + position = "5 1"; + extent = "70 16"; + minExtent = "8 2"; + horizSizing = "right"; + vertSizing = "bottom"; + profile = "ToolsGuiTextRightProfile"; + visible = "1"; + active = "1"; + tooltipProfile = "ToolsGuiToolTipProfile"; + hovertime = "1000"; + isContainer = "0"; + canSave = "1"; + canSaveDynamicFields = "0"; + }; + new GuiTextEditCtrl() { + historySize = "0"; + tabComplete = "0"; + sinkAllKeyEvents = "0"; + password = "0"; + passwordMask = "*"; + text = "scripts/server/gameObjects"; + maxLength = "1024"; + margin = "0 0 0 0"; + padding = "0 0 0 0"; + anchorTop = "1"; + anchorBottom = "0"; + anchorLeft = "1"; + anchorRight = "0"; + position = "81 0"; + extent = "345 17"; + minExtent = "8 2"; + horizSizing = "width"; + vertSizing = "bottom"; + profile = "ToolsGuiTextEditProfile"; + visible = "1"; + active = "1"; + tooltipProfile = "ToolsGuiToolTipProfile"; + hovertime = "1000"; + isContainer = "0"; + canSave = "1"; + canSaveDynamicFields = "1"; + class = "ESettingsWindowTextEdit"; + editorSettingsRead = "EditorGui.readWorldEditorSettings();"; + editorSettingsValue = "WorldEditor/newGameObjectDir"; + editorSettingsWrite = "EditorGui.writeWorldEditorSettings();"; + }; + }; }; }; }; diff --git a/Templates/Full/game/tools/worldEditor/scripts/EditorGui.ed.cs b/Templates/Full/game/tools/worldEditor/scripts/EditorGui.ed.cs index 6f80d9206..f9ab055af 100644 --- a/Templates/Full/game/tools/worldEditor/scripts/EditorGui.ed.cs +++ b/Templates/Full/game/tools/worldEditor/scripts/EditorGui.ed.cs @@ -1643,6 +1643,20 @@ function EditorTree::onRightMouseUp( %this, %itemId, %mouse, %obj ) object = -1; }; + + if(%obj.isMemberOfClass("Entity")) + { + %popup = ETEntityContextPopup; + if( !isObject( %popup ) ) + %popup = new PopupMenu( ETEntityContextPopup : ETSimGroupContextPopup ) + { + superClass = "MenuBuilder"; + isPopup = "1"; + + item[ 12 ] = "-"; + item[ 13 ] = "Convert to Game Object" TAB "" TAB "EWorldEditor.createGameObject( %this.object );"; + }; + } %popup.object = %obj; @@ -2204,6 +2218,155 @@ function EWorldEditor::deleteMissionObject( %this, %object ) EditorTree.buildVisibleTree( true ); } +function EWorldEditor::createGameObject( %this, %entity ) +{ + if(!isObject(GameObjectBuilder)) + { + new GuiControl(GameObjectBuilder, EditorGuiGroup) { + profile = "ToolsGuiDefaultProfile"; + horizSizing = "right"; + vertSizing = "bottom"; + position = "0 0"; + extent = "800 600"; + minExtent = "8 8"; + visible = "1"; + setFirstResponder = "0"; + modal = "1"; + helpTag = "0"; + + new GuiWindowCtrl(GameObjectBuilderTargetWindow) { + profile = "ToolsGuiWindowProfile"; + horizSizing = "center"; + vertSizing = "center"; + position = "384 205"; + extent = "256 102"; + minExtent = "256 8"; + visible = "1"; + setFirstResponder = "0"; + modal = "1"; + helpTag = "0"; + resizeWidth = "1"; + resizeHeight = "1"; + canMove = "1"; + canClose = "0"; + canMinimize = "0"; + canMaximize = "0"; + minSize = "50 50"; + text = "Create Object"; + + new GuiTextCtrl() { + profile = "GuiCenterTextProfile"; + horizSizing = "right"; + vertSizing = "bottom"; + position = "9 26"; + extent = "84 16"; + minExtent = "8 8"; + visible = "1"; + setFirstResponder = "0"; + modal = "1"; + helpTag = "0"; + text = "Object Name:"; + }; + new GuiTextEditCtrl(GameObjectBuilderObjectName) { + class = ObjectBuilderGuiTextEditCtrl; + profile = "ToolsGuiTextEditProfile"; + horizSizing = "width"; + vertSizing = "bottom"; + position = "78 26"; + extent = "172 18"; + minExtent = "8 8"; + visible = "1"; + setFirstResponder = "0"; + modal = "1"; + helpTag = "0"; + historySize = "0"; + }; + new GuiButtonCtrl(GameObjectBuilderOKButton) { + profile = "ToolsGuiButtonProfile"; + horizSizing = "width"; + vertSizing = "bottom"; + position = "7 250"; + extent = "156 24"; + minExtent = "8 8"; + visible = "1"; + setFirstResponder = "0"; + modal = "1"; + command = "EWorldEditor.buildGameObject();"; + helpTag = "0"; + text = "Create New"; + Accelerator = "return"; + }; + new GuiButtonCtrl(GameObjectBuilderCancelButton) { + profile = "ToolsGuiButtonProfile"; + horizSizing = "left"; + vertSizing = "bottom"; + position = "170 250"; + extent = "80 24"; + minExtent = "8 8"; + visible = "1"; + setFirstResponder = "0"; + modal = "1"; + command = "Canvas.popDialog(GameObjectBuilder);"; + helpTag = "0"; + text = "Cancel"; + Accelerator = "escape"; + }; + }; + }; + + GameObjectBuilderTargetWindow.extent = getWord(GameObjectBuilderTargetWindow.extent, 0) SPC 88; + GameObjectBuilderOKButton.position = getWord(GameObjectBuilderOKButton.position, 0) SPC 57; + GameObjectBuilderCancelButton.position = getWord(GameObjectBuilderCancelButton.position, 0) SPC 57; + } + + GameObjectBuilderObjectName.text = ""; + GameObjectBuilder.selectedEntity = %entity; + + Canvas.pushDialog(GameObjectBuilder); +} + +function EWorldEditor::buildGameObject(%this) +{ + if(GameObjectBuilderObjectName.getText() $= "") + { + error("Attempted to make a new Game Object with no name!"); + Canvas.popDialog(GameObjectBuilder); + return; + } + + %path = EditorSettings.value( "WorldEditor/newGameObjectDir" ); + %className = GameObjectBuilderObjectName.getText(); + GameObjectBuilder.selectedEntity.class = %className; + Inspector.inspect(GameObjectBuilder.selectedEntity); + + %file = new FileObject(); + + if(%file.openForWrite(%path @ "\\" @ %className @ ".cs")) + { + %file.writeline("function " @ %className @ "::onAdd(%this)\n{\n\n}\n"); + %file.writeline("function " @ %className @ "::onRemove(%this)\n{\n\n}\n"); + + //todo, pre-write any event functions of interest + + %file.close(); + } + + //set up the paths + %tamlPath = %path @ "/" @ %className @ ".taml"; + %scriptPath = %path @ "/" @ %className @ ".cs"; + saveGameObject(%className, %tamlPath, %scriptPath); + + //reload it + execGameObjects(); + + //now, add the script file and a ref to the taml into our SGO manifest so we can readily spawn it later. + TamlWrite(GameObjectBuilder.selectedEntity, %tamlpath); + + GameObjectBuilder.selectedEntity = ""; + + Canvas.popDialog(GameObjectBuilder); +} + function EWorldEditor::selectAllObjectsInSet( %this, %set, %deselect ) { if( !isObject( %set ) ) diff --git a/Templates/Full/game/tools/worldEditor/scripts/editorPrefs.ed.cs b/Templates/Full/game/tools/worldEditor/scripts/editorPrefs.ed.cs index 0cc14bff0..1704e06ad 100644 --- a/Templates/Full/game/tools/worldEditor/scripts/editorPrefs.ed.cs +++ b/Templates/Full/game/tools/worldEditor/scripts/editorPrefs.ed.cs @@ -34,6 +34,7 @@ EditorSettings.setDefaultValue( "orthoFOV", "50" ); EditorSettings.setDefaultValue( "orthoShowGrid", "1" ); EditorSettings.setDefaultValue( "currentEditor", "WorldEditorInspectorPlugin" ); EditorSettings.setDefaultValue( "newLevelFile", "tools/levels/BlankRoom.mis" ); +EditorSettings.setDefaultValue( "newGameObjectDir", "scripts/server/gameObjects" ); if( isFile( "C:/Program Files/Torsion/Torsion.exe" ) ) EditorSettings.setDefaultValue( "torsionPath", "C:/Program Files/Torsion/Torsion.exe" ); diff --git a/Templates/Full/game/tools/worldEditor/scripts/editors/creator.ed.cs b/Templates/Full/game/tools/worldEditor/scripts/editors/creator.ed.cs index 9f015f359..9dbfb91bb 100644 --- a/Templates/Full/game/tools/worldEditor/scripts/editors/creator.ed.cs +++ b/Templates/Full/game/tools/worldEditor/scripts/editors/creator.ed.cs @@ -85,6 +85,7 @@ function EWCreatorWindow::init( %this ) %this.registerMissionObject( "SFXSpace", "Sound Space" ); %this.registerMissionObject( "OcclusionVolume", "Occlusion Volume" ); %this.registerMissionObject( "AccumulationVolume", "Accumulation Volume" ); + %this.registerMissionObject( "Entity", "Entity" ); %this.endGroup(); @@ -303,6 +304,36 @@ function EWCreatorWindow::navigate( %this, %address ) %this.addShapeIcon( %obj ); } } + + //Add a separate folder for Game Objects + if(isClass("Entity")) + { + if(%address $= "") + { + %this.addFolderIcon("GameObjects"); + } + else + { + //find all GameObjectAssets + %assetQuery = new AssetQuery(); + if(!AssetDatabase.findAssetType(%assetQuery, "GameObjectAsset")) + return 0; //if we didn't find ANY, just exit + + %count = %assetQuery.getCount(); + + for(%i=0; %i < %count; %i++) + { + %assetId = %assetQuery.getAsset(%i); + + %gameObjectAsset = AssetDatabase.acquireAsset(%assetId); + + if(isFile(%gameObjectAsset.TAMLFilePath)) + { + %this.addGameObjectIcon( %gameObjectAsset.gameObjectName ); + } + } + } + } } if ( %this.tab $= "Meshes" ) @@ -737,6 +768,22 @@ function EWCreatorWindow::addPrefabIcon( %this, %fullPath ) %this.contentCtrl.addGuiControl( %ctrl ); } +function EWCreatorWindow::addGameObjectIcon( %this, %gameObjectName ) +{ + %ctrl = %this.createIcon(); + + %ctrl.altCommand = "spawnGameObject( \"" @ %gameObjectName @ "\", true );"; + %ctrl.iconBitmap = EditorIconRegistry::findIconByClassName( "Prefab" ); + %ctrl.text = %gameObjectName; + %ctrl.class = "CreatorGameObjectIconBtn"; + %ctrl.tooltip = "Spawn the " @ %gameObjectName @ " GameObject"; + + %ctrl.buttonType = "radioButton"; + %ctrl.groupNum = "-1"; + + %this.contentCtrl.addGuiControl( %ctrl ); +} + function CreatorPopupMenu::onSelect( %this, %id, %text ) { %split = strreplace( %text, "/", " " ); diff --git a/Tools/CMake/torque3d.cmake b/Tools/CMake/torque3d.cmake index b9972e467..6a4e4dd31 100644 --- a/Tools/CMake/torque3d.cmake +++ b/Tools/CMake/torque3d.cmake @@ -35,6 +35,8 @@ if(UNIX) # for asm files SET (CMAKE_ASM_NASM_OBJECT_FORMAT "elf") ENABLE_LANGUAGE (ASM_NASM) + + set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") endif() # TODO: fmod support @@ -86,6 +88,9 @@ if(WIN32) option(TORQUE_D3D11 "Allow Direct3D 11 render" OFF) endif() +option(TORQUE_EXPERIMENTAL_EC "Experimental Entity/Component systems" OFF) +mark_as_advanced(TORQUE_EXPERIMENTAL_EC) + ############################################################################### # options ############################################################################### @@ -173,8 +178,6 @@ addPathRec("${srcDir}/app") addPath("${srcDir}/sfx/media") addPath("${srcDir}/sfx/null") addPath("${srcDir}/sfx") -addPath("${srcDir}/component") -addPath("${srcDir}/component/interfaces") addPath("${srcDir}/console") addPath("${srcDir}/core") addPath("${srcDir}/core/stream") @@ -254,7 +257,13 @@ addPath("${srcDir}/ts/arch") addPath("${srcDir}/physics") addPath("${srcDir}/gui/3d") addPath("${srcDir}/postFx") + +if(NOT TORQUE_EXPERIMENTAL_EC) + set(BLACKLIST "entity.cpp;entity.h" ) +endif() addPath("${srcDir}/T3D") +set(BLACKLIST "" ) + addPath("${srcDir}/T3D/examples") addPath("${srcDir}/T3D/fps") addPath("${srcDir}/T3D/fx") @@ -264,6 +273,17 @@ addPath("${srcDir}/T3D/decal") addPath("${srcDir}/T3D/sfx") addPath("${srcDir}/T3D/gameBase") addPath("${srcDir}/T3D/turret") + +if( TORQUE_EXPERIMENTAL_EC ) + addPath("${srcDir}/T3D/components/") + addPath("${srcDir}/T3D/components/animation") + addPath("${srcDir}/T3D/components/camera") + addPath("${srcDir}/T3D/components/collision") + addPath("${srcDir}/T3D/components/game") + addPath("${srcDir}/T3D/components/physics") + addPath("${srcDir}/T3D/components/render") +endif() + addPath("${srcDir}/main/") addPath("${srcDir}/assets") addPath("${srcDir}/module") @@ -341,7 +361,11 @@ if(TORQUE_TOOLS) addPath("${srcDir}/environment/editors") addPath("${srcDir}/forest/editor") addPath("${srcDir}/gui/editor") + if(NOT TORQUE_EXPERIMENTAL_EC) + set(BLACKLIST "entityGroup.cpp;entityGroup.h;mountingGroup.cpp;mountingGroup.h;componentGroup.cpp;componentGroup.h" ) + endif() addPath("${srcDir}/gui/editor/inspector") + set(BLACKLIST "" ) endif() if(TORQUE_HIFI) @@ -406,6 +430,10 @@ if(TORQUE_DEDICATED) addDef(TORQUE_DEDICATED) endif() +if(TORQUE_EXPERIMENTAL_EC) + addDef(TORQUE_EXPERIMENTAL_EC) +endif() + #modules dir file(GLOB modules "modules/*.cmake") foreach(module ${modules}) @@ -704,7 +732,34 @@ endif() if(TORQUE_TEMPLATE) message("Prepare Template(${TORQUE_TEMPLATE}) install...") - INSTALL(DIRECTORY "${CMAKE_SOURCE_DIR}/Templates/${TORQUE_TEMPLATE}/game" DESTINATION "${TORQUE_APP_DIR}") + file(GLOB_RECURSE INSTALL_FILES_AND_DIRS "${CMAKE_SOURCE_DIR}/Templates/${TORQUE_TEMPLATE}/game/*") + + IF( NOT TORQUE_EXPERIMENTAL_EC) + list(REMOVE_ITEM INSTALL_FILES_AND_DIRS "${CMAKE_SOURCE_DIR}/Templates/${TORQUE_TEMPLATE}/game/art/art.module.taml") + list(REMOVE_ITEM INSTALL_FILES_AND_DIRS "${CMAKE_SOURCE_DIR}/Templates/${TORQUE_TEMPLATE}/game/art/shapes/actors/Soldier/soldier.asset.taml") + list(REMOVE_ITEM INSTALL_FILES_AND_DIRS "${CMAKE_SOURCE_DIR}/Templates/${TORQUE_TEMPLATE}/game/scripts/scripts.module.taml") + + foreach(ITEM ${INSTALL_FILES_AND_DIRS}) + get_filename_component( dir ${ITEM} DIRECTORY ) + get_filename_component( fileName ${ITEM} NAME ) + if( ${dir} STREQUAL ${CMAKE_SOURCE_DIR}/Templates/${TORQUE_TEMPLATE}/game/scripts/server/components + OR ${dir} STREQUAL ${CMAKE_SOURCE_DIR}/Templates/${TORQUE_TEMPLATE}/game/scripts/server/components/game + OR ${dir} STREQUAL ${CMAKE_SOURCE_DIR}/Templates/${TORQUE_TEMPLATE}/game/scripts/server/components/input + OR ${dir} STREQUAL ${CMAKE_SOURCE_DIR}/Templates/${TORQUE_TEMPLATE}/game/scripts/server/gameObjects + OR ${dir} STREQUAL ${CMAKE_SOURCE_DIR}/Templates/${TORQUE_TEMPLATE}/game/tools/componentEditor + OR ${dir} STREQUAL ${CMAKE_SOURCE_DIR}/Templates/${TORQUE_TEMPLATE}/game/tools/componentEditor/gui + OR ${dir} STREQUAL ${CMAKE_SOURCE_DIR}/Templates/${TORQUE_TEMPLATE}/game/tools/componentEditor/scripts ) + list(REMOVE_ITEM INSTALL_FILES_AND_DIRS ${dir}/${fileName}) + ENDIF() + endforeach() + ENDIF() + + foreach(ITEM ${INSTALL_FILES_AND_DIRS}) + get_filename_component( dir ${ITEM} DIRECTORY ) + STRING(REGEX REPLACE "${CMAKE_SOURCE_DIR}/Templates/${TORQUE_TEMPLATE}/" "${TORQUE_APP_DIR}/" INSTALL_DIR ${dir}) + install( FILES ${ITEM} DESTINATION ${INSTALL_DIR} ) + endforeach() + if(WIN32) INSTALL(FILES "${CMAKE_SOURCE_DIR}/Templates/${TORQUE_TEMPLATE}/cleanShaders.bat" DESTINATION "${TORQUE_APP_DIR}") INSTALL(FILES "${CMAKE_SOURCE_DIR}/Templates/${TORQUE_TEMPLATE}/DeleteCachedDTSs.bat" DESTINATION "${TORQUE_APP_DIR}")