Files
GTASource/rage/scaleform/Src/GFxPlayer/GFxCharacter.cpp
expvintl 419f2e4752 init
2025-02-23 17:40:52 +08:00

2888 lines
94 KiB
C++

/**********************************************************************
Filename : GFxCharacter.cpp
Content : Base functionality for characters, which are display
list objects.
Created : May 25, 2006 (moved from GFxPlayerImpl)
Authors : Michael Antonov
Notes :
Copyright : (c) 2001-2006 Scaleform Corp. All Rights Reserved.
Licensees may use this file in accordance with the valid Scaleform
Commercial License Agreement provided with the software.
This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING
THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR ANY PURPOSE.
**********************************************************************/
#include "GFxCharacter.h"
#include "GFxPlayerImpl.h"
#include "GFxSprite.h"
#include "AS/GASNumberObject.h"
#include "AS/GASArrayObject.h"
#include "AS/GASBitmapFilter.h"
#include "GMsgFormat.h"
#include "GMatrix3D.h"
// RAGE - add some diag context for actionscript calls
#include "../../../../base/src/diag/output.h"
// ***** GFxCharacter
GFxCharacter::GFxCharacter(GFxASCharacter* parent, GFxResourceId id)
:
Id(id),
Depth(-1),
CreateFrame(0),
Ratio(0.0f),
pParent(parent),
#ifndef GFC_NO_3D
pMatrix3D_1(NULL),
pPerspectiveMatrix3D(NULL),
pViewMatrix3D(NULL),
PerspFOV(0),
#endif
ClipDepth(0),
Flags(0)
{
GASSERT((parent == NULL && Id == GFxResourceId::InvalidId) ||
(parent != NULL));
}
GFxCharacter::~GFxCharacter()
{
#ifndef GFC_NO_3D
if (pMatrix3D_1)
{
delete pMatrix3D_1;
pMatrix3D_1 = NULL;
}
if (pPerspectiveMatrix3D)
{
delete pPerspectiveMatrix3D;
pPerspectiveMatrix3D = NULL;
}
if (pViewMatrix3D)
{
delete pViewMatrix3D;
pViewMatrix3D = NULL;
}
#endif
}
void GFxCharacter::CreateMatrix3D(GMatrix3DNewable** pmat)
{
#ifndef GFC_NO_3D
if (pmat == NULL)
pmat = &pMatrix3D_1;
if (*pmat == NULL)
{
*pmat = GHEAP_NEW(GetMovieRoot()->GetMovieHeap()) GMatrix3DNewable;
}
#else
GUNUSED(pmat);
#endif
}
#ifndef GFC_NO_3D
Float GFxCharacter::GetPerspectiveFOV(bool checkAncestors) const
{
if (PerspFOV != 0 || checkAncestors == false)
return PerspFOV;
if (pParent == NULL)
return GetMovieRoot() ? GetMovieRoot()->PerspFOV : PerspFOV;
return pParent->GetPerspectiveFOV(checkAncestors);
}
void GFxCharacter::SetPerspectiveFOV(Float fov)
{
GFxMovieRoot *pRoot = GetMovieRoot();
if (fov != PerspFOV && pRoot && pRoot->GetRenderer())
{
PerspFOV = fov;
GMatrix3D matView;
GMatrix3D matPersp;
pRoot->GetRenderer()->MakeViewAndPersp3D(pRoot->VisibleFrameRect, matView, matPersp, PerspFOV);
SetPerspective3D(matPersp);
SetView3D(matView);
}
}
// recompute view and perspective if necessary (usually when visible frame rect changes)
void GFxCharacter::UpdateViewAndPerspective()
{
GFxMovieRoot *pRoot = GetMovieRoot();
if (pRoot && pViewMatrix3D && pPerspectiveMatrix3D && pRoot->GetRenderer())
pRoot->GetRenderer()->MakeViewAndPersp3D(pRoot->VisibleFrameRect, *pViewMatrix3D, *pPerspectiveMatrix3D, PerspFOV);
}
#endif
void GFxCharacter::SetDirtyFlag()
{
GetMovieRoot()->SetDirtyFlag();
}
// Character transform implementation.
void GFxCharacter::GetWorldMatrix(Matrix *pmat) const
{
if (pParent)
{
pParent->GetWorldMatrix(pmat);
pmat->Prepend(GetMatrix());
}
else
{
*pmat = GetMatrix();
}
}
// Character transform implementation.
void GFxCharacter::GetLevelMatrix(Matrix *pmat) const
{
if (pParent)
{
pParent->GetLevelMatrix(pmat);
pmat->Prepend(GetMatrix());
}
else
{
*pmat = Matrix();
}
}
// Character color transform implementation.
void GFxCharacter::GetWorldCxform(Cxform *pcxform) const
{
if (pParent)
{
pParent->GetWorldCxform(pcxform);
pcxform->Concatenate(GetCxform());
}
else
{
*pcxform = GetCxform();
}
}
#ifndef GFC_NO_3D
bool GFxCharacter::Is3D(bool checkAncestors) const
{
if (checkAncestors) // return whether I or any of my ancestors is in 3D
{
// am I in 3D?
if (pMatrix3D_1 != NULL)
return true;
if (pParent)
return pParent->Is3D(checkAncestors);
}
return (pMatrix3D_1 != NULL);
}
// return the complete local matrix including both 2D and 3D
GMatrix3D GFxCharacter::GetLocalMatrix3D() const
{
GMatrix3D totalMat = (GetMatrix3D() ? *GetMatrix3D() : GMatrix3D::Identity);
totalMat.Append(GMatrix3D(GetMatrix()));
return totalMat;
}
// return the complete world matrix including both 2D and 3D
void GFxCharacter::GetWorldMatrix3D(GMatrix3D *pmat) const
{
if (pParent)
{
pParent->GetWorldMatrix3D(pmat);
pmat->Prepend(GetLocalMatrix3D());
}
else
{
*pmat = GetLocalMatrix3D();
}
}
const GMatrix3D* GFxCharacter::GetPerspective3D(bool checkAncestors) const
{
if (pPerspectiveMatrix3D || checkAncestors==false)
return pPerspectiveMatrix3D;
if (pParent)
return pParent->GetPerspective3D(checkAncestors);
if (GetMovieRoot())
return GetMovieRoot()->pPerspectiveMatrix3D;
return NULL;
}
const GMatrix3D* GFxCharacter::GetView3D(bool checkAncestors) const
{
if (pViewMatrix3D || checkAncestors==false)
return pViewMatrix3D;
if (pParent)
return pParent->GetView3D(checkAncestors);
if (GetMovieRoot())
return GetMovieRoot()->pViewMatrix3D;
return NULL;
}
#endif
#ifndef GFC_NO_3D
static float scaleX(float left, float width, float x)
{
return left + width * (x+1) / 2.f;
}
static float scaleY(float top, float height, float y)
{
return top + height * (-y+1) / 2.f;
}
#endif
//
// get 3D bounds and transform to stage coords
//
GRectF GFxCharacter::GetProjectedBounds(const GMatrix3D *pWorldMatrix3D) const
{
#ifndef GFC_NO_3D
GMatrix3D wvp3D;
if (pWorldMatrix3D == NULL)
GetWorldMatrix3D(&wvp3D);
else
wvp3D = *pWorldMatrix3D;
wvp3D.Append(*GetView3D(true));
wvp3D.Append(*GetPerspective3D(true));
#if 0
GViewport viewport;
GetMovieRoot()->GetViewport(&viewport);
#endif
GRectF visFrameRect = GetMovieRoot()->GetVisibleFrameRect();
// get screen space bounds
GRectF bounds3D = GetBounds(wvp3D, true); // returns bounds in -1 to 1 space
bounds3D.Left = scaleX(visFrameRect.Left, visFrameRect.Width(), bounds3D.Left);
float bottom = scaleY(visFrameRect.Top, visFrameRect.Height(), bounds3D.Top); // negate top and bottom to invert Y
bounds3D.Right = scaleX(visFrameRect.Left, visFrameRect.Width(), bounds3D.Right);
bounds3D.Top = scaleY(visFrameRect.Top, visFrameRect.Height(), bounds3D.Bottom);
bounds3D.Bottom = bottom;
return bounds3D;
#else
GUNUSED(pWorldMatrix3D);
return GRectF(0);
#endif
}
//
// get 3D non-axis-aligned bounds and transform to stage coords, bounds returned as an array of 4 pts
//
void GFxCharacter::GetProjectedBounds(GPointF *pts, const GMatrix3D *pWorldMatrix3D) const
{
#ifndef GFC_NO_3D
GMatrix3D wvp3D;
if (pWorldMatrix3D == NULL)
GetWorldMatrix3D(&wvp3D);
else
wvp3D = *pWorldMatrix3D;
wvp3D.Append(*GetView3D(true));
wvp3D.Append(*GetPerspective3D(true));
#if 0
GViewport viewport;
GetMovieRoot()->GetViewport(&viewport);
#endif
GRectF visFrameRect = GetMovieRoot()->GetVisibleFrameRect();
// get screen space bounds
GetBounds(pts, wvp3D, true); // returns bounds in -1 to 1 space
pts[0].x = scaleX(visFrameRect.Left, visFrameRect.Width(), pts[0].x);
pts[0].y = scaleY(visFrameRect.Top, visFrameRect.Height(), pts[0].y);
pts[1].x = scaleX(visFrameRect.Left, visFrameRect.Width(), pts[1].x);
pts[1].y = scaleY(visFrameRect.Top, visFrameRect.Height(), pts[1].y);
pts[2].x = scaleX(visFrameRect.Left, visFrameRect.Width(), pts[2].x);
pts[2].y = scaleY(visFrameRect.Top, visFrameRect.Height(), pts[2].y);
pts[3].x = scaleX(visFrameRect.Left, visFrameRect.Width(), pts[3].x);
pts[3].y = scaleY(visFrameRect.Top, visFrameRect.Height(), pts[3].y);
#else
GUNUSED2(pts, pWorldMatrix3D);
#endif
}
// Used during rendering.
// Temporary - used until blending logic is improved.
GRenderer::BlendType GFxCharacter::GetActiveBlendMode() const
{
GRenderer::BlendType blend = GRenderer::Blend_None;
const GFxCharacter* pchar = this;
while (pchar)
{
blend = pchar->GetBlendMode();
if (blend > GRenderer::Blend_Layer)
return blend;
pchar = pchar->GetParent();
}
// Return last blend mode.
return blend;
}
// This is not because of GFxMovieDefImpl dependency not available in the header.
UInt GFxCharacter::GetVersion() const
{
return GetResourceMovieDef()->GetVersion();
}
GASEnvironment* GFxCharacter::GetASEnvironment()
{
GFxASCharacter* pparent = GetParent();
while(pparent && !pparent->IsSprite())
pparent = pparent->GetParent();
if (!pparent)
return 0;
return pparent->ToSprite()->GetASEnvironment();
}
const GASEnvironment* GFxCharacter::GetASEnvironment() const
{
// Call non-const version. Const-ness really only matters for GFxSprite.
return const_cast<GFxCharacter*>(this)->GetASEnvironment();
}
// (needs to be implemented in .cpp, so that GFxMovieRoot is visible)
GFxLog* GFxCharacter::GetLog() const
{
// By default, GetMovieRoot will delegate to parent.
// GFxSprite should override GetMovieRoot to return the correct object.
return GetMovieRoot()->GetCachedLog();
}
bool GFxCharacter::IsVerboseAction() const
{
return GetMovieRoot()->IsVerboseAction();
}
bool GFxCharacter::IsVerboseActionErrors() const
{
return !GetMovieRoot()->IsSuppressActionErrors();
}
GFxScale9GridInfo* GFxCharacter::CreateScale9Grid(Float pixelScale) const
{
GFxASCharacter* parent = GetParent();
GMatrix2D shapeMtx = GetMatrix();
while (parent)
{
if (parent->GetScale9Grid())
{
GRectF bounds = parent->GetRectBounds(GMatrix2D());
return GHEAP_AUTO_NEW(this) GFxScale9GridInfo(parent->GetScale9Grid(),
parent->GetMatrix(), shapeMtx,
pixelScale, bounds);
}
shapeMtx.Append(parent->GetMatrix());
parent = parent->GetParent();
}
return 0;
}
void GFxCharacter::OnEventUnload()
{
SetUnloading();
// should it be before Event_Unload or after? (AB)
if (IsTopmostLevelFlagSet())
{
GFxMovieRoot* proot = GetMovieRoot();
proot->RemoveTopmostLevelCharacter(this);
}
if (!IsUnloaded())
{
OnEvent(GFxEventId::Event_Unload);
SetUnloaded();
}
}
bool GFxCharacter::CheckLastHitResult(Float x, Float y) const
{
return
(Flags & Mask_HitTest) != 0 &&
x == LastHitTestX && y == LastHitTestY;
}
void GFxCharacter::SetLastHitResult(Float x, Float y, bool result) const // TO DO: Revise "const"
{
LastHitTestX = x;
LastHitTestY = y;
Flags &= ~Mask_HitTest;
Flags |= result ? Mask_HitTestPositive : Mask_HitTestNegative;
}
void GFxCharacter::InvalidateHitResult() const // TO DO: Revise "const"
{
Flags &= ~Mask_HitTest;
}
// ***** GFxCharacterHandle
GFxCharacterHandle::GFxCharacterHandle(const GASString& name, GFxASCharacter *pparent, GFxASCharacter* pcharacter)
: Name(name), NamePath(name.GetManager()->CreateEmptyString()), OriginalName(name)
{
RefCount = 1;
pCharacter = pcharacter;
// Compute path based on parent
GString namePathBuff;
if (pparent)
{
pparent->GetAbsolutePath(&namePathBuff);
namePathBuff += ".";
}
namePathBuff += Name.ToCStr();
NamePath = name.GetManager()->CreateString(namePathBuff);
}
GFxCharacterHandle::~GFxCharacterHandle()
{
}
// Release a character reference, used when character dies
void GFxCharacterHandle::ReleaseCharacter()
{
pCharacter = 0;
}
// Changes the name.
void GFxCharacterHandle::ChangeName(const GASString& name, GFxASCharacter *pparent)
{
Name = name;
// Compute path based on parent
GString namePathBuff;
if (pparent)
{
pparent->GetAbsolutePath(&namePathBuff);
namePathBuff += ".";
}
namePathBuff += Name.ToCStr();
NamePath = name.GetManager()->CreateString(namePathBuff);
// Do we need to update paths in all parents ??
}
// Resolve the character, considering path if necessary.
GFxASCharacter* GFxCharacterHandle::ResolveCharacter(GFxMovieRoot *proot) const
{
if (pCharacter)
return pCharacter;
// Resolve a global path based on Root.
return proot->FindTarget(NamePath);
}
GFxASCharacter* GFxCharacterHandle::ForceResolveCharacter(GFxMovieRoot *proot) const
{
// Resolve a global path based on Root.
return proot->FindTarget(NamePath);
}
void GFxCharacterHandle::ResetName(GASStringContext* psc)
{
Name = psc->GetBuiltin(GASBuiltin_empty_);
NamePath = Name;
}
// ***** GFxASCharacter
// Constructor.
GFxASCharacter::GFxASCharacter(GFxMovieDefImpl* pbindingDefImpl,
GFxASCharacter* pparent, GFxResourceId id)
:
GFxCharacter(pparent, id),
pDefImpl(pbindingDefImpl),
pGeomData(0),
Flags(0),
TabIndex (0),
RollOverCnt(0),
pDisplayCallback(NULL),
DisplayCallbackUserPtr(NULL)
{
FocusGroupMask = 0;
//--FocusGroupMask; // make 0xFFFF
SetASCharacterFlag();
#ifndef GFC_USE_OLD_ADVANCE
pPlayNext = pPlayPrev = NULL;
pPlayNextOpt = NULL;
#endif
BlendMode = (UInt8)GRenderer::Blend_None;
pFilters = NULL;
SetVisibleFlag();
SetAcceptAnimMovesFlag();
SetEnabledFlag();
SetInstanceBasedNameFlag();
}
GFxASCharacter::~GFxASCharacter()
{
#ifndef GFC_USE_OLD_ADVANCE
GASSERT(!pPlayNext && !pPlayPrev); // actually already should be removed
#endif
// RAGE - context messages
#if __BANK
const char* name = NULL;
if (pNameHandle)
{
name = GetName().ToCStr(); // be careful not to create a name handle if one doesn't already exist
}
DIAG_CONTEXT_MESSAGE("Destroying ASChar %s", name);
pDefImpl = NULL; // RAGE - really shouldn't do this here, but I want the release of the resource to be within the DIAG_CONTEXT above
// and it won't be if we let the GPtr dtor clean up
#endif
if (pNameHandle)
pNameHandle->ReleaseCharacter();
if (pGeomData)
delete pGeomData;
if (pFilters)
delete pFilters;
}
UInt16 GFxASCharacter::GetFocusGroupMask() const
{
if (FocusGroupMask == 0)
return GetParent()->GetFocusGroupMask();
return FocusGroupMask;
}
UInt16 GFxASCharacter::GetFocusGroupMask()
{
if (FocusGroupMask == 0 && GetParent())
FocusGroupMask = GetParent()->GetFocusGroupMask();
return FocusGroupMask;
}
bool GFxASCharacter::IsFocusAllowed(GFxMovieRoot* proot, UInt controllerIdx) const
{
UInt focusIdx = proot->GetFocusGroupIndex(controllerIdx);
return (GetFocusGroupMask() & (1 << focusIdx)) != 0;
}
bool GFxASCharacter::IsFocusAllowed(GFxMovieRoot* proot, UInt controllerIdx)
{
UInt focusIdx = proot->GetFocusGroupIndex(controllerIdx);
return (GetFocusGroupMask() & (1 << focusIdx)) != 0;
}
void GFxASCharacter::OnEventUnload()
{
SetUnloading();
GFxMovieRoot* proot = GetMovieRoot();
RemoveFromPlaylist(proot);
if (proot->IsDraggingCharacter(this))
proot->StopDrag();
GFxCharacter::OnEventUnload();
// Need to check, is this character currently focused or not. If yes, we need
// to reset a pointer to currently focused character. OnEventUnload might be
// called from the sprite's destructor (so refcnt == 0 but weak ref is not set to NULL yet)
if (proot)
proot->ResetFocusForChar(this);
// need to release the character to avoid accidental reusing unloaded character.
if (pNameHandle)
pNameHandle->ReleaseCharacter();
}
bool GFxASCharacter::OnUnloading()
{
bool rv = GFxCharacter::OnUnloading();
GFxMovieRoot* proot = GetMovieRoot();
RemoveFromPlaylist(proot);
return rv;
}
void GFxASCharacter::AddToPlayList(GFxMovieRoot* proot)
{
#ifndef GFC_USE_OLD_ADVANCE
GASSERT(proot->pPlayListHead != this);
GASSERT(!pPlayNext && !pPlayPrev);
// printf("=------------- %s\n", pch->GetCharacterHandle()->GetNamePath().ToCStr()); //DBG
if (proot->pPlayListHead)
{
proot->pPlayListHead->pPlayPrev = this;
pPlayNext = proot->pPlayListHead;
}
proot->pPlayListHead = this;
proot->SetDirtyFlag();
#else
GUNUSED(proot);
#endif
}
void GFxASCharacter::AddToOptimizedPlayList(GFxMovieRoot* proot)
{
#ifndef GFC_USE_OLD_ADVANCE
if (IsOptAdvancedListFlagSet() || proot->IsOptAdvanceListInvalid() || IsUnloaded() || IsUnloading())
return;
GASSERT(proot->pPlayListHead == this || pPlayPrev);
GASSERT(proot->pPlayListOptHead != this && !pPlayNextOpt);
// find the place in the optimized playlist
GFxASCharacter* pcur = pPlayPrev;
for (; pcur && !pcur->IsOptAdvancedListFlagSet(); pcur = pcur->pPlayPrev)
;
if (pcur)
{
pPlayNextOpt = pcur->pPlayNextOpt;
pcur->pPlayNextOpt = this;
}
else
{
pPlayNextOpt = proot->pPlayListOptHead;
proot->pPlayListOptHead = this;
}
SetOptAdvancedListFlag();
proot->SetDirtyFlag();
#else
GUNUSED(proot);
#endif
}
void GFxASCharacter::RemoveFromPlaylist(GFxMovieRoot* proot)
{
RemoveFromPreDisplayList(proot);
#ifndef GFC_USE_OLD_ADVANCE
GASSERT(proot);
RemoveFromOptimizedPlaylist(proot);
if (pPlayNext)
pPlayNext->pPlayPrev = pPlayPrev;
if (pPlayPrev)
pPlayPrev->pPlayNext = pPlayNext;
else
{
if (proot->pPlayListHead == this)
proot->pPlayListHead = pPlayNext;
}
pPlayNext = pPlayPrev = NULL;
proot->SetDirtyFlag();
#else
GUNUSED(proot);
#endif
}
void GFxASCharacter::RemoveFromOptimizedPlaylist(GFxMovieRoot* proot)
{
#ifndef GFC_USE_OLD_ADVANCE
if (IsOptAdvancedListFlagSet())
{
if (!proot->IsOptAdvanceListInvalid())
{
GASSERT(proot);
// find previous element in optimized list
GFxASCharacter* pcur = pPlayPrev;
for (; pcur && !pcur->IsOptAdvancedListFlagSet(); pcur = pcur->pPlayPrev)
;
if (pcur)
pcur->pPlayNextOpt = pPlayNextOpt;
else
{
GASSERT(proot->pPlayListOptHead == this);
proot->pPlayListOptHead = pPlayNextOpt;
}
}
pPlayNextOpt = NULL;
ClearOptAdvancedListFlag();
proot->SetDirtyFlag();
}
#else
GUNUSED(proot);
#endif
}
void GFxASCharacter::UpdateAlphaFlag()
{
if ((fabs(ColorTransform.M_[3][0]) < 0.001f) &&
(fabs(ColorTransform.M_[3][1]) < 1.0f) )
SetAlpha0Flag();
else
ClearAlpha0Flag();
}
void GFxASCharacter::CloneInternalData(const GFxASCharacter* src)
{
GASSERT(src);
if (src->pGeomData)
SetGeomData(*src->pGeomData);
EventHandlers = src->EventHandlers;
}
GASGlobalContext* GFxASCharacter::GetGC() const
{
return GetMovieRoot()->pGlobalContext;
}
void GFxASCharacter::SetName(const GASString& name)
{
if (!name.IsEmpty())
ClearInstanceBasedNameFlag();
if (pNameHandle)
{
pNameHandle->ChangeName(name, pParent);
// TBD: Propagate update to all children ??
}
else
{
pNameHandle = *GHEAP_AUTO_NEW(this) GFxCharacterHandle(name, pParent, this);
}
}
void GFxASCharacter::SetOriginalName(const GASString& name)
{
GFxASCharacter::SetName(name);
GFxCharacterHandle* pnameHandle = GetCharacterHandle();
if (pnameHandle)
return pnameHandle->SetOriginalName(name);
}
const GASString& GFxASCharacter::GetOriginalName() const
{
GFxCharacterHandle* pnameHandle = GetCharacterHandle();
if (pnameHandle)
return pnameHandle->GetOriginalName();
return GetName();
}
// RAGE - use this to reduce the number of allocations when building a path
int FindParentList(const GFxASCharacter* start, const GFxASCharacter** parentArr)
{
const GFxASCharacter* pParent = start->GetParent();
int count = 0;
while(pParent)
{
*parentArr = pParent;
parentArr++;
count++;
pParent = pParent->GetParent();
};
return count;
}
// Determines the absolute path of the character.
void GFxASCharacter::GetAbsolutePath(GString *ppath) const
{
// RAGE - modified to reduce the number of allocations while building a path
const GFxASCharacter* parentArr[64];
int numParents = FindParentList(this, &parentArr[0]);
GASSERT(numParents < 64);
if (numParents > 0)
{
int totalLen = 0;
for(int i = numParents-1; i >= 0; i--)
{
const GASString& parentString = parentArr[i]->GetName();
totalLen += parentString.GetSize();
totalLen += 1;
}
const GASString& myName = GetName();
int nameLen = myName.GetSize();
totalLen += nameLen + 1;
char* pathBuf = (char*)alloca(totalLen);
char* pathPtr = &pathBuf[0];
for(int i = numParents-1; i >= 0; i--)
{
const GASString& parentString = parentArr[i]->GetName();
int parentLen = parentString.GetSize();
GASSERT(pathPtr + parentLen + 1 < &pathBuf[totalLen]);
memcpy(pathPtr, parentString.ToCStr(), parentLen);
pathPtr += parentLen;
*pathPtr = '.';
pathPtr++;
}
GASSERT(pathPtr + nameLen + 1 <= &pathBuf[totalLen]);
memcpy(pathPtr, myName.ToCStr(), nameLen);
pathPtr += nameLen;
*pathPtr = '\0';
*ppath = pathBuf;
GFC_DEBUG_ERROR1(totalLen >= 256, "very long path name. Is it correct? \"%s\"", pathBuf);
}
else
{
if (IsSprite())
G_Format(*ppath, "_level{0}", ((const GFxSprite*)this)->GetLevel());
else
{
// Non-sprite characters must have parents.
GASSERT(0);
ppath->Clear();
}
}
}
GFxCharacterHandle* GFxASCharacter::CreateCharacterHandle() const
{
if (!pNameHandle)
{
GFxMovieRoot* proot = GetMovieRoot();
// Hacky, but this can happen.
// Will clearing child handles recursively on parent release work better?
if (IsSprite() && ((const GFxSprite*)this)->IsUnloaded())
{
GFC_DEBUG_WARNING(1, "GetCharacterHandle called on unloaded sprite");
// Returns temp handle which is essentially useless.
pNameHandle = *GHEAP_NEW(proot->GetMovieHeap())
GFxCharacterHandle(GetASEnvironment()->GetBuiltin(GASBuiltin_empty_), 0, 0);
return pNameHandle;
}
// Create new instance names as necessary.
GASString name(proot->CreateNewInstanceName());
// SetName logic duplicated to take advantage of 'mutable' pNameHandle.
pNameHandle = *GHEAP_NEW(proot->GetMovieHeap())
GFxCharacterHandle(name, pParent, const_cast<GFxASCharacter*>(this));
}
return pNameHandle;
}
// Implement mouse-dragging for this pMovie.
void GFxASCharacter::DoMouseDrag()
{
GFxMovieRoot::DragState st;
GFxMovieRoot* proot = GetMovieRoot();
proot->GetDragState(&st);
if (this == st.pCharacter)
{
// We're being dragged!
GPointF worldMouse = proot->GetMouseState(0)->GetLastPosition();
GPointF parentMouse;
Matrix parentWorldMat;
if (pParent)
parentWorldMat = pParent->GetWorldMatrix();
parentWorldMat.TransformByInverse(&parentMouse, worldMouse);
#ifndef GFC_NO_3D
if (Is3D(true))
{
const GMatrix3D *pPersp = GetPerspective3D(true);
const GMatrix3D *pView = GetView3D(true);
if (pPersp)
GetMovieRoot()->ScreenToWorld.SetPerspective(*pPersp);
if (pView)
GetMovieRoot()->ScreenToWorld.SetView(*pView);
GetMovieRoot()->ScreenToWorld.SetWorld(pParent->GetWorldMatrix3D());
GetMovieRoot()->ScreenToWorld.GetWorldPoint(&parentMouse);
}
#endif
// if (!st.LockCenter) is not necessary, because then st.CenterDelta == 0.
parentMouse += st.CenterDelta;
if (st.Bound)
{
// Clamp mouse coords within a defined rectangle
parentMouse.x = G_Clamp(parentMouse.x, st.BoundLT.x, st.BoundRB.x);
parentMouse.y = G_Clamp(parentMouse.y, st.BoundLT.y, st.BoundRB.y);
}
// Once touched, object is no longer animated by the timeline
SetAcceptAnimMoves(0);
// Place our origin so that it coincides with the mouse coords
// in our parent frame.
SetStandardMember(M_x, TwipsToPixels(Double(parentMouse.x)), false);
SetStandardMember(M_y, TwipsToPixels(Double(parentMouse.y)), false);
//Matrix local = GetMatrix();
//local.M_[0][2] = parentMouse.x;
//local.M_[1][2] = parentMouse.y;
//SetMatrix(local);
}
}
// *** Shared ActionScript methods.
// Depth implementation - same in MovieClip, Button, TextField.
void GFxASCharacter::CharacterGetDepth(const GASFnCall& fn)
{
GFxASCharacter* pcharacter = fn.ThisPtr->ToASCharacter();
if (!pcharacter)
pcharacter = fn.Env->GetTarget();
GASSERT(pcharacter);
// Depth always starts at -16384,
// probably to allow user assigned depths to start at 0.
fn.Result->SetInt(pcharacter->GetDepth() - 16384);
}
// *** Standard member support
GFxASCharacter::MemberTableType GFxASCharacter::MemberTable[] =
{
{ "_x", GFxASCharacter::M_x, true },
{ "_y", GFxASCharacter::M_y, true },
{ "_xscale", GFxASCharacter::M_xscale, true },
{ "_yscale", GFxASCharacter::M_yscale, true },
{ "_currentframe", GFxASCharacter::M_currentframe, true },
{ "_totalframes", GFxASCharacter::M_totalframes, true },
{ "_alpha", GFxASCharacter::M_alpha, true },
{ "_visible", GFxASCharacter::M_visible, true },
{ "_width", GFxASCharacter::M_width, true },
{ "_height", GFxASCharacter::M_height, true },
{ "_rotation", GFxASCharacter::M_rotation, true },
{ "_target", GFxASCharacter::M_target, true },
{ "_framesloaded", GFxASCharacter::M_framesloaded, true },
{ "_name", GFxASCharacter::M_name, true },
{ "_droptarget", GFxASCharacter::M_droptarget, true },
{ "_url", GFxASCharacter::M_url, true },
{ "_highquality", GFxASCharacter::M_highquality, true },
{ "_focusrect", GFxASCharacter::M_focusrect, true },
{ "_soundbuftime", GFxASCharacter::M_soundbuftime, true },
// SWF 5+.
{ "_quality", GFxASCharacter::M_quality, true },
{ "_xmouse", GFxASCharacter::M_xmouse, true },
{ "_ymouse", GFxASCharacter::M_ymouse, true },
// Extra shared properties which can have a default implementation.
{ "_parent", GFxASCharacter::M_parent, false },
{ "blendMode", GFxASCharacter::M_blendMode, false },
{ "cacheAsBitmap", GFxASCharacter::M_cacheAsBitmap, false },
{ "filters", GFxASCharacter::M_filters, false },
{ "enabled", GFxASCharacter::M_enabled, false },
{ "trackAsMenu", GFxASCharacter::M_trackAsMenu, false },
{ "_lockroot", GFxASCharacter::M_lockroot, false },
{ "tabEnabled", GFxASCharacter::M_tabEnabled, false },
{ "tabIndex", GFxASCharacter::M_tabIndex, false },
{ "useHandCursor", GFxASCharacter::M_useHandCursor, false },
// Not shared.
{ "menu", GFxASCharacter::M_menu, false },
// MovieClip custom.
{ "focusEnabled", GFxASCharacter::M_focusEnabled, false },
{ "tabChildren", GFxASCharacter::M_tabChildren, false },
{ "transform", GFxASCharacter::M_transform, false },
{ "scale9Grid", GFxASCharacter::M_scale9Grid, false },
{ "hitArea", GFxASCharacter::M_hitArea, false },
// TextField custom.
{ "text", GFxASCharacter::M_text, false },
{ "textWidth", GFxASCharacter::M_textWidth, false },
{ "textHeight", GFxASCharacter::M_textHeight, false },
{ "textColor", GFxASCharacter::M_textColor, false },
{ "length", GFxASCharacter::M_length, false },
{ "html", GFxASCharacter::M_html, false },
{ "htmlText", GFxASCharacter::M_htmlText, false },
{ "styleSheet", GFxASCharacter::M_styleSheet, false },
{ "autoSize", GFxASCharacter::M_autoSize, false },
{ "wordWrap", GFxASCharacter::M_wordWrap, false },
{ "multiline", GFxASCharacter::M_multiline, false },
{ "border", GFxASCharacter::M_border, false },
{ "variable", GFxASCharacter::M_variable, false },
{ "selectable", GFxASCharacter::M_selectable, false },
{ "embedFonts", GFxASCharacter::M_embedFonts, false },
{ "antiAliasType", GFxASCharacter::M_antiAliasType, false },
{ "hscroll", GFxASCharacter::M_hscroll, false },
{ "scroll", GFxASCharacter::M_scroll, false },
{ "maxscroll", GFxASCharacter::M_maxscroll, false },
{ "maxhscroll", GFxASCharacter::M_maxhscroll, false },
{ "background", GFxASCharacter::M_background, false },
{ "backgroundColor",GFxASCharacter::M_backgroundColor, false },
{ "borderColor", GFxASCharacter::M_borderColor, false },
{ "bottomScroll", GFxASCharacter::M_bottomScroll, false },
{ "type", GFxASCharacter::M_type, false },
{ "maxChars", GFxASCharacter::M_maxChars, false },
{ "condenseWhite", GFxASCharacter::M_condenseWhite, false },
{ "mouseWheelEnabled", GFxASCharacter::M_mouseWheelEnabled, false },
{ "password", GFxASCharacter::M_password, false },
// GFx extensions
{ "shadowStyle", GFxASCharacter::M_shadowStyle, false },
{ "shadowColor", GFxASCharacter::M_shadowColor, false },
{ "hitTestDisable", GFxASCharacter::M_hitTestDisable, false },
{ "noTranslate", GFxASCharacter::M_noTranslate, false },
{ "caretIndex", GFxASCharacter::M_caretIndex, false },
{ "numLines", GFxASCharacter::M_numLines, false },
{ "verticalAutoSize",GFxASCharacter::M_verticalAutoSize, false },
{ "fontScaleFactor", GFxASCharacter::M_fontScaleFactor, false },
{ "verticalAlign", GFxASCharacter::M_verticalAlign, false },
{ "textAutoSize", GFxASCharacter::M_textAutoSize, false },
{ "useRichTextClipboard", GFxASCharacter::M_useRichTextClipboard, false },
{ "alwaysShowSelection", GFxASCharacter::M_alwaysShowSelection, false },
{ "selectionBeginIndex", GFxASCharacter::M_selectionBeginIndex, false },
{ "selectionEndIndex", GFxASCharacter::M_selectionEndIndex, false },
{ "selectionBkgColor", GFxASCharacter::M_selectionBkgColor, false },
{ "selectionTextColor", GFxASCharacter::M_selectionTextColor, false },
{ "inactiveSelectionBkgColor", GFxASCharacter::M_inactiveSelectionBkgColor, false },
{ "inactiveSelectionTextColor", GFxASCharacter::M_inactiveSelectionTextColor, false },
{ "noAutoSelection", GFxASCharacter::M_noAutoSelection, false },
{ "disableIME", GFxASCharacter::M_disableIME, false },
{ "topmostLevel", GFxASCharacter::M_topmostLevel, false },
{ "noAdvance", GFxASCharacter::M_noAdvance, false },
{ "focusGroupMask", GFxASCharacter::M_focusGroupMask, false },
// Dynamic Text
{ "autoFit", GFxASCharacter::M_autoFit, false },
{ "blurX", GFxASCharacter::M_blurX, false },
{ "blurY", GFxASCharacter::M_blurY, false },
{ "blurStrength", GFxASCharacter::M_blurStrength, false },
{ "outline", GFxASCharacter::M_outline, false },
{ "fauxBold", GFxASCharacter::M_fauxBold, false },
{ "fauxItalic", GFxASCharacter::M_fauxItalic, false },
{ "restrict", GFxASCharacter::M_restrict, false },
// Dynamic Text Shadow
{ "shadowAlpha", GFxASCharacter::M_shadowAlpha, false },
{ "shadowAngle", GFxASCharacter::M_shadowAngle, false },
{ "shadowBlurX", GFxASCharacter::M_shadowBlurX, false },
{ "shadowBlurY", GFxASCharacter::M_shadowBlurY, false },
{ "shadowDistance", GFxASCharacter::M_shadowDistance, false },
{ "shadowHideObject", GFxASCharacter::M_shadowHideObject, false },
{ "shadowKnockOut", GFxASCharacter::M_shadowKnockOut, false },
{ "shadowQuality", GFxASCharacter::M_shadowQuality, false },
{ "shadowStrength", GFxASCharacter::M_shadowStrength, false },
{ "shadowOutline", GFxASCharacter::M_shadowOutline, false },
#ifndef GFC_NO_3D
{ "_z", GFxASCharacter::M_z, true },
{ "_zscale", GFxASCharacter::M_zscale, true },
{ "_xrotation", GFxASCharacter::M_xrotation, true },
{ "_yrotation", GFxASCharacter::M_yrotation, true },
{ "_matrix3d", GFxASCharacter::M_matrix3d, true },
{ "_perspfov", GFxASCharacter::M_perspfov, true },
#endif
{ "$version", GFxASCharacter::M__version, false },
// Done.
{ 0, GFxASCharacter::M_InvalidMember, false }
};
void GFxASCharacter::InitStandardMembers(GASGlobalContext *pcontext)
{
GCOMPILER_ASSERT( (sizeof(MemberTable)/sizeof(MemberTable[0]))
== M_StandardMemberCount + 1);
// Add all standard members.
MemberTableType* pentry;
GASStringManager* pstrManager = pcontext->GetStringManager();
pcontext->StandardMemberMap.SetCapacity(M_StandardMemberCount);
for (pentry = MemberTable; pentry->pName; pentry++)
{
GASString name(pstrManager->CreateConstString(pentry->pName, strlen(pentry->pName),
GASString::Flag_StandardMember |
(pentry->CaseInsensitive?GASString::Flag_CaseInsensitive:0)));
pcontext->StandardMemberMap.Add(name, (SByte)pentry->Id);
}
}
bool GFxASCharacter::IsStandardMember(const GASString& memberName, GASString* pcaseInsensitiveName)
{
if (memberName.IsStandardMember())
return true;
// now need to check a special case. Flash 7+ uses case sensitive names
// for every variable/member but some exceptions. Some standard members
// still might be referenced case insensitively.
if (memberName.GetLength() > 0 && memberName.GetCharAt(0) == '_')
{
GASString lowerCase = memberName.ToLower();
if (lowerCase.IsCaseInsensitive())
{
if (pcaseInsensitiveName)
*pcaseInsensitiveName = lowerCase;
return true;
}
}
return false;
}
// Looks up a standard member and returns M_InvalidMember if it is not found.
GFxASCharacter::StandardMember GFxASCharacter::GetStandardMemberConstant(const GASString& memberName) const
{
SByte memberConstant = M_InvalidMember; // Has to be signed or conversion is incorrect!
GASString lowerCaseName = GetGC()->GetBuiltin(GASBuiltin_empty_);
if (IsStandardMember(memberName, &lowerCaseName))
{
GASGlobalContext* pcontext = GetGC();
bool caseSensitive = lowerCaseName.IsEmpty();
pcontext->StandardMemberMap.GetCaseCheck(memberName, &memberConstant, caseSensitive);
}
GASSERT((memberConstant != M_InvalidMember) ?
(memberName.IsStandardMember() || (lowerCaseName.IsCaseInsensitive() && lowerCaseName.IsStandardMember())) : 1);
return (StandardMember) memberConstant;
}
GFxASCharacter::GeomDataType& GFxASCharacter::GetGeomData(GFxASCharacter::GeomDataType& geomData) const
{
if (!pGeomData)
{
// fill GeomData using Matrix_1
const Matrix& m = GetMatrix();
geomData.X = int(m.GetX());
geomData.Y = int(m.GetY());
geomData.XScale = m.GetXScale()*(Double)100.;
geomData.YScale = m.GetYScale()*(Double)100.;
geomData.Rotation = (m.GetRotation()*(Double)180.)/GFC_MATH_PI;
geomData.OrigMatrix = Matrix_1;
#ifndef GFC_NO_3D
const GMatrix3D* pm3D = GetMatrix3D();
if (pm3D)
{
geomData.Z = pm3D->GetZ();
geomData.ZScale = pm3D->GetZScale() * (Double)100.;
float fX, fY;
pm3D->GetRotation(&fX, &fY, NULL);
geomData.XRotation = GFC_RADTODEG(fX);
geomData.YRotation = GFC_RADTODEG(fY);
}
#endif
}
else
{
geomData = *pGeomData;
}
return geomData;
}
void GFxASCharacter::SetGeomData(const GeomDataType& gd)
{
if (pGeomData)
*pGeomData = gd;
else
pGeomData = GHEAP_AUTO_NEW(this) GeomDataType(gd);
}
void GFxASCharacter::EnsureGeomDataCreated()
{
if (!pGeomData)
{
GeomDataType geomData;
SetGeomData(GetGeomData(geomData));
}
}
void GFxASCharacter::SetAcceptAnimMoves(bool accept)
{
if (!accept)
EnsureGeomDataCreated();
GFxMovieRoot* proot = GetMovieRoot();
SetAcceptAnimMovesFlag(accept);
// cache the flag locally
SetContinueAnimationFlag(proot->IsContinueAnimationFlagSet());
if (proot->IsContinueAnimationFlagSet() && accept)
{
// if continueAnimation flag is set and we restore timeline acceptance -
// remove the pGeomData to provide correct coordinates/scales by accessing
// via ActionScript.
delete pGeomData;
pGeomData = NULL;
}
proot->SetDirtyFlag();
}
// BlendMode lookup table.
// Should be moved elsewhere so that it can apply to buttons, etc.
static const char * GFx_BlendModeNames[1+ 14] =
{
"normal", // 0?
"normal",
"layer",
"multiply",
"screen",
"lighten",
"darken",
"difference",
"add",
"subtract",
"invert",
"alpha",
"erase",
"overlay",
"hardlight"
};
void GFxASCharacter_MatrixScaleAndRotate2x2(GMatrix2D& m, Float sx, Float sy, Float radians)
{
Float cosAngle = cosf(radians);
Float sinAngle = sinf(radians);
Float x00 = m.M_[0][0];
Float x01 = m.M_[0][1];
Float x10 = m.M_[1][0];
Float x11 = m.M_[1][1];
// avoid creating irreversible 2D matrices
//!AB: should be revised, since Matrix2D already has code that prevents from creation of irreversible matrices.
// The condition below causes a problem when xscale/yscale is set to 0.
//if (fabsf(sx) > 1e-4f && fabsf(sy) > 1e-4f)
{
m.M_[0][0] = (x00*cosAngle-x10*sinAngle)*sx;
m.M_[0][1] = (x01*cosAngle-x11*sinAngle)*sy;
m.M_[1][0] = (x00*sinAngle+x10*cosAngle)*sx;
m.M_[1][1] = (x01*sinAngle+x11*cosAngle)*sy;
}
}
// Handles built-in members. Return 0 if member is not found or not supported.
bool GFxASCharacter::SetStandardMember(StandardMember member, const GASValue& val, bool opcodeFlag)
{
if (opcodeFlag && ((member < M_BuiltInProperty_Begin) || (member > M_BuiltInProperty_End)))
{
LogScriptError("Invalid SetProperty request, property number %d\n", member);
return 0;
}
// Make sure that this character class supports the constant.
if (member == M_InvalidMember)
return 0;
if (!((member <= M_SharedPropertyEnd) && (GetStandardMemberBitMask() & (1<<member))))
return 0;
GASEnvironment* pEnv = GetASEnvironment ();
switch(member)
{
case M_x:
{
// NOTE: If updating this logic, also update GFxValue::ObjectInterface::SetDisplayInfo
GASNumber xval = val.ToNumber(pEnv);
if (val.IsUndefined() || GASNumberUtil::IsNaN(xval))
return 1;
if (GASNumberUtil::IsNEGATIVE_INFINITY(xval) || GASNumberUtil::IsPOSITIVE_INFINITY(xval))
xval = 0;
SetAcceptAnimMoves(0);
GASSERT(pGeomData);
Matrix m = GetMatrix();
pGeomData->X = int(floor(PixelsToTwips(xval)));
m.M_[0][2] = (Float) pGeomData->X;
if (m.IsValid())
SetMatrix(m);
return 1;
}
case M_y:
{
// NOTE: If updating this logic, also update GFxValue::ObjectInterface::SetDisplayInfo
GASNumber yval = val.ToNumber(pEnv);
if (val.IsUndefined() || GASNumberUtil::IsNaN(yval))
return 1;
if (GASNumberUtil::IsNEGATIVE_INFINITY(yval) || GASNumberUtil::IsPOSITIVE_INFINITY(yval))
yval = 0;
SetAcceptAnimMoves(0);
GASSERT(pGeomData);
Matrix m = GetMatrix();
pGeomData->Y = int(floor(PixelsToTwips(yval)));
m.M_[1][2] = (Float) pGeomData->Y;
if (m.IsValid())
SetMatrix(m);
return 1;
}
case M_xscale:
{
// NOTE: If updating this logic, also update GFxValue::ObjectInterface::SetDisplayInfo
Double newXScale = val.ToNumber(pEnv);
if (val.IsUndefined() || GASNumberUtil::IsNaN(newXScale) ||
GASNumberUtil::IsNEGATIVE_INFINITY(newXScale) || GASNumberUtil::IsPOSITIVE_INFINITY(newXScale))
{
return 1;
}
SetAcceptAnimMoves(0);
GASSERT(pGeomData);
const Matrix& chm = GetMatrix();
Matrix m = pGeomData->OrigMatrix;
m.M_[0][2] = chm.M_[0][2];
m.M_[1][2] = chm.M_[1][2];
Double origXScale = m.GetXScale();
pGeomData->XScale = newXScale;
if (origXScale == 0 || newXScale > 1E+16)
{
newXScale = 0;
origXScale = 1;
}
GFxASCharacter_MatrixScaleAndRotate2x2(m,
Float(newXScale/(origXScale*100.)),
Float(pGeomData->YScale/(m.GetYScale()*100.)),
Float(-m.GetRotation() + pGeomData->Rotation * GFC_MATH_PI / 180.));
if (m.IsValid())
SetMatrix(m);
return 1;
}
case M_yscale:
{
// NOTE: If updating this logic, also update GFxValue::ObjectInterface::SetDisplayInfo
Double newYScale = val.ToNumber(pEnv);
if (val.IsUndefined() || GASNumberUtil::IsNaN(newYScale) ||
GASNumberUtil::IsNEGATIVE_INFINITY(newYScale) || GASNumberUtil::IsPOSITIVE_INFINITY(newYScale))
{
return 1;
}
SetAcceptAnimMoves(0);
GASSERT(pGeomData);
const Matrix& chm = GetMatrix();
Matrix m = pGeomData->OrigMatrix;
m.M_[0][2] = chm.M_[0][2];
m.M_[1][2] = chm.M_[1][2];
Double origYScale = m.GetYScale();
pGeomData->YScale = newYScale;
if (origYScale == 0 || newYScale > 1E+16)
{
newYScale = 0;
origYScale = 1;
}
GFxASCharacter_MatrixScaleAndRotate2x2(m,
Float(pGeomData->XScale/(m.GetXScale()*100.)),
Float(newYScale/(origYScale* 100.)),
Float(-m.GetRotation() + pGeomData->Rotation * GFC_MATH_PI / 180.));
if (m.IsValid())
SetMatrix(m);
return 1;
}
case M_rotation:
{
// NOTE: If updating this logic, also update GFxValue::ObjectInterface::SetDisplayInfo
GASNumber rval = val.ToNumber(pEnv);
if (val.IsUndefined() || GASNumberUtil::IsNaN(rval))
return 1;
SetAcceptAnimMoves(0);
GASSERT(pGeomData);
Double r = fmod((Double)rval, (Double)360.);
if (r > 180)
r -= 360;
else if (r < -180)
r += 360;
pGeomData->Rotation = r;
const Matrix& chm = GetMatrix();
Matrix m = pGeomData->OrigMatrix;
m.M_[0][2] = chm.M_[0][2];
m.M_[1][2] = chm.M_[1][2];
Double origRotation = m.GetRotation();
// remove old rotation by rotate back and add new one
GFxASCharacter_MatrixScaleAndRotate2x2(m,
Float(pGeomData->XScale/(m.GetXScale()*100.)),
Float(pGeomData->YScale/(m.GetYScale()*100.)),
Float(-origRotation + r * GFC_MATH_PI / 180.));
if (m.IsValid())
SetMatrix(m);
return 1;
}
case M_width:
{
// MA: Width/Height modification in Flash is unusual in that it performs
// relative axis scaling in the x local axis (width scaling) and y local
// axis (height scaling). The resulting width after scaling does not
// actually equal the original, instead, it is related to rotation!
// AB: I am second on that! Looks like a bug in Flash.
// NOTE: Although it works for many cases, this is still not correct. Modification
// of width seems very strange (if not buggy) in Flash.
GASNumber wval = val.ToNumber(pEnv);
if (val.IsUndefined() || GASNumberUtil::IsNaN(wval) || GASNumberUtil::IsNEGATIVE_INFINITY(wval))
return 1;
if (GASNumberUtil::IsPOSITIVE_INFINITY(wval))
wval = 0;
SetAcceptAnimMoves(0);
GASSERT(pGeomData);
Matrix m = pGeomData->OrigMatrix;
const Matrix& chm = GetMatrix();
m.M_[0][2] = chm.M_[0][2];
m.M_[1][2] = chm.M_[1][2];
Matrix im = m;
im.AppendRotation(Float(-m.GetRotation() + pGeomData->Rotation * GFC_MATH_PI / 180.));
Float oldWidth = GetBounds(im).Width(); // width should be in local coords!
Float newWidth = Float(PixelsToTwips(wval));
Float multiplier = (fabsf(oldWidth) > 1e-6f) ? (newWidth / oldWidth) : 0.0f;
Double origXScale = m.GetXScale();
Double newXScale = origXScale * multiplier * 100;
pGeomData->XScale = newXScale;
if (origXScale == 0)
{
newXScale = 0;
origXScale = 1;
}
GFxASCharacter_MatrixScaleAndRotate2x2(m,
Float(fabs(newXScale/(origXScale*100.))),
Float(fabs(pGeomData->YScale/(m.GetYScale()*100.))),
Float(-m.GetRotation() + pGeomData->Rotation * GFC_MATH_PI / 180.));
pGeomData->XScale = fabs(pGeomData->XScale);
pGeomData->YScale = fabs(pGeomData->YScale);
if (m.IsValid())
SetMatrix(m);
return 1;
}
case M_height:
{
GASNumber hval = val.ToNumber(pEnv);
if (val.IsUndefined() || GASNumberUtil::IsNaN(hval) || GASNumberUtil::IsNEGATIVE_INFINITY(hval))
return 1;
if (GASNumberUtil::IsPOSITIVE_INFINITY(hval))
hval = 0;
SetAcceptAnimMoves(0);
GASSERT(pGeomData);
Matrix m = pGeomData->OrigMatrix;
const Matrix& chm = GetMatrix();
m.M_[0][2] = chm.M_[0][2];
m.M_[1][2] = chm.M_[1][2];
Matrix im = m;
im.AppendRotation(Float(-m.GetRotation() + pGeomData->Rotation * GFC_MATH_PI / 180.));
Float oldHeight = GetBounds(im).Height(); // height should be in local coords!
Float newHeight = Float(PixelsToTwips(hval));
Float multiplier = (fabsf(oldHeight) > 1e-6f) ? (newHeight / oldHeight) : 0.0f;
Double origYScale = m.GetYScale();
Double newYScale = origYScale * multiplier * 100;;
pGeomData->YScale = newYScale;
if (origYScale == 0)
{
newYScale = 0;
origYScale = 1;
}
GFxASCharacter_MatrixScaleAndRotate2x2(m,
Float(fabs(pGeomData->XScale/(m.GetXScale()*100.))),
Float(fabs(newYScale/(origYScale* 100.))),
Float(-m.GetRotation() + pGeomData->Rotation * GFC_MATH_PI / 180.));
pGeomData->XScale = fabs(pGeomData->XScale);
pGeomData->YScale = fabs(pGeomData->YScale);
if (m.IsValid())
SetMatrix(m);
return 1;
}
case M_alpha:
{
// NOTE: If updating this logic, also update GFxValue::ObjectInterface::SetDisplayInfo
GASNumber aval = val.ToNumber(pEnv);
if (val.IsUndefined() || GASNumberUtil::IsNaN(aval))
return 1;
// Set alpha modulate, in percent.
Cxform cx = GetCxform();
cx.M_[3][0] = Float(aval / 100.);
SetCxform(cx);
SetAcceptAnimMoves(0);
return 1;
}
case M_visible:
{
// NOTE: If updating this logic, also update GFxValue::ObjectInterface::SetDisplayInfo
SetVisible(val.ToBool(pEnv));
// Setting visibility does not affect AnimMoves.
return 1;
}
case M_blendMode:
{
// NOTE: Setting "blendMode" in Flash does NOT disconnect object from time-line,
// so just setting the value is the correct behavior.
if (val.GetType() == GASValue::STRING)
{
GASString asstr(val.ToString(pEnv));
GString str = asstr.ToCStr();
for (UInt i=1; i<(sizeof(GFx_BlendModeNames)/sizeof(GFx_BlendModeNames[0])); i++)
{
if (str == GFx_BlendModeNames[i])
{
SetBlendMode((BlendType) i);
return 1;
}
}
}
else
{
SInt mode = (SInt)val.ToNumber(pEnv);
mode = G_Max<SInt>(G_Min<SInt>(mode,14),1);
SetBlendMode((BlendType) mode);
}
return 1;
}
case M_name:
{
SetName(val.ToString(pEnv));
return 1;
}
case M_enabled:
{
SetEnabledFlag(val.ToBool(pEnv));
return 1;
}
case M_trackAsMenu:
{
SetTrackAsMenuFlag(val.ToBool(pEnv));
return 1;
}
// Read-only properties.
case M_droptarget:
case M_target:
case M_url:
case M_xmouse:
case M_ymouse:
case M_parent: // TBD: _parent is not documented as read only. Can it be changed ??
// These are only implemented for MovieClip. Technically, they should not be here;
// however, they are Flash built-ins. Since they are read-only, we can handle them here.
case M_currentframe:
case M_totalframes:
case M_framesloaded:
pEnv->LogScriptWarning("Attempt to write read-only property %s.%s, ignored\n",
GetName().ToCStr(), GFxASCharacter::MemberTable[member].pName);
return 1;
case M_tabEnabled:
if (!val.IsUndefined())
SetTabEnabledFlag(val.ToBool(GetASEnvironment()));
else
UndefineTabEnabledFlag();
return 1;
case M_tabIndex:
TabIndex = SInt16(val.ToNumber(GetASEnvironment()));
return 1;
case M_focusrect:
if (!val.IsUndefined())
SetFocusRectFlag(val.ToBool(GetASEnvironment()));
else
UndefineFocusRectFlag();
SetDirtyFlag();
return 1;
// Un-implemented properties:
case M_soundbuftime:
case M_highquality:
case M_quality:
// We have a default get below, so return 1.
return 1;
case M_useHandCursor:
if (!val.IsUndefined())
SetUseHandCursorFlag(val.ToBool(GetASEnvironment()));
else
UndefineUseHandCursorFlag();
return 1;
case M_filters:
{
#ifndef GFC_NO_FXPLAYER_AS_FILTERS
GASObject* pobj = val.ToObject(pEnv);
if (pobj && pobj->InstanceOf(pEnv, pEnv->GetPrototype(GASBuiltin_Array)))
{
GASArrayObject* parrobj = static_cast<GASArrayObject*>(pobj);
GArray<GFxFilterDesc> newfilters;
CharFilterDesc* pOldFilters = GetFilters();
bool hasfilters = pOldFilters && pOldFilters->HasFilters;
for (int i=0; i < parrobj->GetSize(); ++i)
{
GASValue* arrval = parrobj->GetElementPtr(i);
if (arrval)
{
GASObject* pavobj = arrval->ToObject(pEnv);
if (pavobj && pavobj->InstanceOf(pEnv, pEnv->GetPrototype(GASBuiltin_BitmapFilter)))
{
GASBitmapFilterObject* pfilterobj = static_cast<GASBitmapFilterObject*>(pavobj);
newfilters.PushBack(pfilterobj->GetFilter());
}
}
}
SetFilters(newfilters);
if (newfilters.GetSize() || hasfilters)
{
SetDirtyFlag();
SetAcceptAnimMoves(false);
}
}
#endif // GFC_NO_FXPLAYER_AS_FILTERS
return true;
}
break;
case M_cacheAsBitmap:
case M_lockroot:
// Allow at least property assignment in map for now.
return 0;
default:
// Property not handled, fall out.
return 0;
}
}
bool GFxASCharacter::GetStandardMember(StandardMember member, GASValue* val, bool opcodeFlag) const
{
if (opcodeFlag && ((member < M_BuiltInProperty_Begin) || (member > M_BuiltInProperty_End)))
{
GetASEnvironment()->LogScriptError("Invalid GetProperty query, property number %d\n", member);
return 0;
}
// Make sure that this character class supports the constant.
if (member == M_InvalidMember)
return 0;
if (!((member <= M_SharedPropertyEnd) && (GetStandardMemberBitMask() & (1<<member))))
return 0;
switch(member)
{
case M_x:
{
// NOTE: If updating this logic, also update GFxValue::ObjectInterface::GetDisplayInfo
GeomDataType geomData;
val->SetNumber(TwipsToPixels(Double(GetGeomData(geomData).X)));
return 1;
}
case M_y:
{
// NOTE: If updating this logic, also update GFxValue::ObjectInterface::GetDisplayInfo
GeomDataType geomData;
val->SetNumber(TwipsToPixels(Double(GetGeomData(geomData).Y)));
return 1;
}
case M_xscale:
{
// NOTE: If updating this logic, also update GFxValue::ObjectInterface::GetDisplayInfo
GeomDataType geomData;
val->SetNumber(GetGeomData(geomData).XScale); // result in percent
return 1;
}
case M_yscale:
{
// NOTE: If updating this logic, also update GFxValue::ObjectInterface::GetDisplayInfo
GeomDataType geomData;
val->SetNumber(GetGeomData(geomData).YScale); // result in percent
return 1;
}
case M_width:
{
//GRectF boundRect = GetLevelBounds();
//!AB: width and height of nested movieclips returned in the coordinate space of its parent!
GRectF boundRect = GetBounds(GetMatrix());
val->SetNumber(TwipsToPixels(floor((Double)boundRect.Width())));
return 1;
}
case M_height:
{
//GRectF boundRect = GetLevelBounds();
//!AB: width and height of nested movieclips returned in the coordinate space of its parent!
GRectF boundRect = GetBounds(GetMatrix());
val->SetNumber(TwipsToPixels(floor((Double)boundRect.Height())));
return 1;
}
case M_rotation:
{
// NOTE: If updating this logic, also update GFxValue::ObjectInterface::GetDisplayInfo
GeomDataType geomData;
// Verified against Macromedia player using samples/TestRotation.Swf
val->SetNumber(GetGeomData(geomData).Rotation);
return 1;
}
case M_alpha:
{
// NOTE: If updating this logic, also update GFxValue::ObjectInterface::GetDisplayInfo
// Alpha units are in percent.
val->SetNumber(GetCxform().M_[3][0] * 100.F);
return 1;
}
case M_visible:
{
// NOTE: If updating this logic, also update GFxValue::ObjectInterface::GetDisplayInfo
val->SetBool(GetVisible());
return 1;
}
case M_blendMode:
{
val->SetString(GetASEnvironment()->CreateString(GFx_BlendModeNames[GetBlendMode()]));
// Note: SWF 8 can sometimes report "undefined". TBD.
return 1;
}
case M_name:
{
val->SetString(GetName());
return 1;
}
case M_enabled:
{
val->SetBool(GetEnabled());
return 1;
}
case M_trackAsMenu:
{
val->SetBool(GetTrackAsMenu());
return 1;
}
case M_target:
{
// Full path to this object; e.G. "/sprite1/sprite2/ourSprite"
GStringBuffer name;
GPtr<GFxASCharacter> root = GetASRootMovie();
for (const GFxASCharacter* p = this; p != 0 && p != root; p = p->GetParent())
{
const GASString& cname = p->GetName();
name.Insert (cname.ToCStr(), 0);
name.Insert ("/", 0);
}
val->SetString(GetASEnvironment()->CreateString(name.ToCStr(),name.GetSize()));
return 1;
}
case M_parent:
{
if (pParent)
val->SetAsCharacter(pParent);
else
val->SetUndefined();
return 1;
}
case M_droptarget:
{
// Absolute path in slash syntax where we were last Dropped (?)
// @@ TODO
//val->SetString(GetASEnvironment()->GetBuiltin(GASBuiltin__root));
//return 1;
// Crude implementation..
val->SetUndefined();
GFxMovieRoot* proot = GetASEnvironment()->GetMovieRoot();
GPointF mousePos = proot->GetMouseState(0)->GetLastPosition();
GFxASCharacter* ptopCh = proot->GetTopMostEntity(mousePos, 0 /* controller */, true, this);
GStringBuffer name;
for (const GFxASCharacter* p = ptopCh; p != 0; p = p->GetParent())
{
const GASString& cname = p->GetName();
name.Insert (cname.ToCStr(), 0);
name.Insert ("/", 0);
}
val->SetString(GetASEnvironment()->CreateString(name.ToCStr(),name.GetSize()));
return 1;
}
case M_url:
{
// our URL.
GArray<char> urlArray;
const char* purl = GetResourceMovieDef()->GetFileURL();
UPInt urlSize = purl ? G_strlen(purl) : 0;
urlArray.Resize(urlSize + 1);
for (UPInt i = 0; i < urlSize; ++i)
urlArray[i] = (purl[i] == '\\') ? '/' : purl[i];
urlArray[urlSize] = 0;
GString urlStr;
GASGlobalContext::EscapePath(&urlArray[0], urlSize, &urlStr);
val->SetString(GetASEnvironment()->CreateString(urlStr));
return 1;
}
case M_highquality:
{
// Whether we're in high quality mode or not.
val->SetBool(true);
return 1;
}
case M_quality:
{
val->SetString(GetASEnvironment()->CreateString("HIGH"));
return 1;
}
case M_focusrect:
{
// Is a yellow rectangle visible around a focused GFxASCharacter Clip (?)
if (IsFocusRectFlagDefined())
val->SetBool(IsFocusRectFlagTrue());
else
val->SetNull();
return 1;
}
case M_soundbuftime:
{
// Number of seconds before sound starts to GFxStream.
val->SetNumber(0.0);
return 1;
}
case M_xmouse:
{
// Local coord of mouse IN PIXELS.
GPointF a = GetMovieRoot()->GetMouseState(0)->GetLastPosition();
GPointF b;
#ifndef GFC_NO_3D
if (Is3D(true))
{
const GMatrix3D *pPersp = GetPerspective3D(true);
const GMatrix3D *pView = GetView3D(true);
if (pPersp)
GetMovieRoot()->ScreenToWorld.SetPerspective(*pPersp);
if (pView)
GetMovieRoot()->ScreenToWorld.SetView(*pView);
GetMovieRoot()->ScreenToWorld.SetWorld(GetWorldMatrix3D());
GetMovieRoot()->ScreenToWorld.GetWorldPoint(&b);
}
else
#endif
{
Matrix m = GetWorldMatrix();
m.TransformByInverse(&b, a);
}
val->SetNumber(TwipsToPixels(floor(b.x+0.5)));
return 1;
}
case M_ymouse:
{
// Local coord of mouse IN PIXELS.
GPointF a = GetMovieRoot()->GetMouseState(0)->GetLastPosition();
GPointF b;
#ifndef GFC_NO_3D
if (Is3D(true))
{
const GMatrix3D *pPersp = GetPerspective3D(true);
const GMatrix3D *pView = GetView3D(true);
if (pPersp)
GetMovieRoot()->ScreenToWorld.SetPerspective(*pPersp);
if (pView)
GetMovieRoot()->ScreenToWorld.SetView(*pView);
GetMovieRoot()->ScreenToWorld.SetWorld(GetWorldMatrix3D());
GetMovieRoot()->ScreenToWorld.GetWorldPoint(&b);
}
else
#endif
{
Matrix m = GetWorldMatrix();
m.TransformByInverse(&b, a);
}
val->SetNumber(TwipsToPixels(floor(b.y+0.5)));
return 1;
}
case M_tabEnabled:
if (IsTabEnabledFlagDefined())
val->SetBool(IsTabEnabledFlagTrue());
else
val->SetUndefined();
return 1;
case M_tabIndex:
val->SetNumber((GASNumber)TabIndex);
return 1;
case M_useHandCursor:
if (!IsUseHandCursorFlagDefined())
return 0;
val->SetBool(IsUseHandCursorFlagTrue());
return 1;
case M_filters:
{
#ifndef GFC_NO_FXPLAYER_AS_FILTERS
GASEnvironment* penv = const_cast<GASEnvironment*>(GetASEnvironment());
GPtr<GASArrayObject> arrayObj = *GHEAP_NEW(penv->GetHeap()) GASArrayObject(penv);
CharFilterDesc* pFilters = GetFilters();
if (pFilters && pFilters->HasFilters)
{
UInt lastFilter = (UInt)pFilters->Filters.GetSize();
if (pFilters->LastIsTemp)
lastFilter--;
for (UInt i = 0 ; i < lastFilter; i++)
{
GASBitmapFilterObject* pfilterobj = GASBitmapFilterObject::CreateFromDesc(penv, pFilters->Filters[i]);
if (pfilterobj)
{
arrayObj->PushBack(pfilterobj);
pfilterobj->Release();
}
}
}
val->SetAsObject(arrayObj);
#endif // GFC_NO_FXPLAYER_AS_FILTERS
}
return 1;
// Un-implemented properties:
case M_cacheAsBitmap:
case M_lockroot:
// Allow at least property assignment in map for now.
return 0;
// These are only implemented for MovieClip. Technically, they should not be here;
// however, they are Flash built-ins. Return 0 so that GFxSprite properly handles them.
case M_currentframe:
case M_totalframes:
case M_framesloaded:
return 0;
default:
break;
}
return 0;
}
// Duplicate the object with the specified name and add it with a new name at a new depth.
GFxASCharacter* GFxASCharacter::CloneDisplayObject(const GASString& newname, SInt depth, const GASObjectInterface *psource)
{
GFxSprite* pparent = GetParent()->ToSprite();
if (!pparent)
return 0;
if ((depth < 0) || (depth > (2130690045 + 16384)))
return 0;
// Clone us.
GFxCharPosInfo pos( GetId(), depth,
1, GetCxform(), 1, GetMatrix(),
GetRatio(), GetClipDepth());
// true: replace if depth is occupied
GFxCharacter* pnewCh = pparent->AddDisplayObject(
pos, newname, 0, psource,
GFC_MAX_UINT, GFxDisplayList::Flags_ReplaceIfDepthIsOccupied, 0, this);
if (pnewCh)
return pnewCh->CharToASCharacter();
return NULL;
}
// Remove the object with the specified name.
void GFxASCharacter::RemoveDisplayObject()
{
if (!GetParent())
return;
GFxSprite* pparent = GetParent()->ToSprite();
if (!pparent)
return;
pparent->RemoveDisplayObject(GetDepth(), GetId());
}
void GFxASCharacter::CopyPhysicalProperties(const GFxASCharacter *poldChar)
{
// Copy physical properties, used by loadMovie().
SetDepth(poldChar->GetDepth());
SetCxform(poldChar->GetCxform());
SetMatrix(poldChar->GetMatrix());
EventHandlers = poldChar->EventHandlers;
if (poldChar->pGeomData)
SetGeomData(*poldChar->pGeomData);
}
void GFxASCharacter::MoveNameHandle(GFxASCharacter *poldChar)
{
// Re-link all ActionScript references.
pNameHandle = poldChar->pNameHandle;
poldChar->pNameHandle = 0;
if (pNameHandle)
pNameHandle->pCharacter = this;
}
static GINLINE GFxEventId GFx_TreatEventId(const GFxEventId& id)
{
// for keyDown/keyUp search by id only (w/o KeyCode/AsciiCode)
if (id.Id == GFxEventId::Event_KeyDown || id.Id == GFxEventId::Event_KeyUp)
return GFxEventId(id.Id);
return id;
}
bool GFxASCharacter::HasClipEventHandler(const GFxEventId& id) const
{
const EventsArray* evts = EventHandlers.Get(GFx_TreatEventId(id));
return evts != 0;
}
bool GFxASCharacter::InvokeClipEventHandlers(GASEnvironment* penv, const GFxEventId& id)
{
const EventsArray* evts = EventHandlers.Get(GFx_TreatEventId(id));
if (evts)
{
for (UPInt i = 0, n = evts->GetSize(); i < n; ++i)
{
const GASValue& method = (*evts)[i];
GAS_Invoke0(method, NULL, this, penv);
}
return true;
}
return false;
}
void GFxASCharacter::SetSingleClipEventHandler(const GFxEventId& id, const GASValue& method)
{
GASSERT(id.GetEventsCount() == 1);
EventsArray* evts = EventHandlers.Get(GFx_TreatEventId(id));
if (!evts)
{
// Local arrays need to be allocated for the right heap.
EventsArray *pea = GHEAP_AUTO_NEW(this) EventsArray;
pea->PushBack(method);
EventHandlers.Set(id, *pea);
delete pea;
}
else
{
evts->PushBack(method);
}
}
void GFxASCharacter::SetClipEventHandlers(const GFxEventId& id, const GASValue& method)
{
UInt numOfEvents = id.GetEventsCount();
GASSERT(numOfEvents > 0);
if (numOfEvents == 1)
{
SetSingleClipEventHandler(id, method);
}
else
{
// need to create multiple event handlers with the same method. This is
// necessary when "on(release, releaseOutside)" kind of handlers is used.
for (UInt i = 0, mask = 0x1; i < numOfEvents; mask <<= 1)
{
if (id.Id & mask)
{
++i;
GFxEventId copied = id;
copied.Id = mask;
SetSingleClipEventHandler(copied, method);
}
}
}
}
// Execute this even immediately (called for processing queued event actions).
bool GFxASCharacter::ExecuteEvent(const GFxEventId& id)
{
// Keep GASEnvironment alive during any method calls!
GPtr<GFxASCharacter> thisPtr(this);
GASEnvironment* env = GetASEnvironment();
// Keep target of GASEnvironment alive during any method calls!
// note, that env->GetTarget() is not always equal to this (for buttons, for example)
GPtr<GFxASCharacter> targetPtr(env->GetTarget());
#ifndef GFC_NO_KEYBOARD_SUPPORT
if (env && (id.Id == GFxEventId::Event_KeyDown || id.Id == GFxEventId::Event_KeyUp))
{
// We need to update listeners of KeyboardState (actually, there is only one
// listener - GASKeyAsObject). The UpdateListeners method updates last ascii and
// key codes in the singleton GASKeyAsObject, thus, ActionScript's Key.getAscii()
// and Key.getCode() will return actual values when they are being used inside the
// onClipEvent(keyDown / keyUp) event handlers.
GFxMovieRoot* proot = env->GetMovieRoot();
if (proot)
{
GFxKeyboardState* keyboardState = proot->GetKeyboardState(id.KeyboardIndex);
GASSERT(keyboardState);
keyboardState->UpdateListeners(id.KeyCode, id.AsciiCode, id.WcharCode);
}
}
#endif //#ifndef GFC_NO_KEYBOARD_SUPPORT
int handlerFoundCnt = 0;
GASValue method;
// First, check for built-in onClipEvent() handler.
if (HasClipEventHandler(id))
{
if (id.RollOverCnt == 0)
{
// Dispatch.
InvokeClipEventHandlers(env, id);
++handlerFoundCnt;
}
}
// Check for member function, it is called after onClipEvent().
// In ActionScript 2.0, event method names are CASE SENSITIVE.
// In ActionScript 1.0, event method names are CASE INSENSITIVE.
GASString methodName(id.GetFunctionName(env->GetSC()));
if (methodName.GetSize() > 0)
{
if (GetMemberRaw(env->GetSC(), methodName, &method))
{
if (method.IsProperty())
{
GASValue actualMethod;
method.GetPropertyValue(env, thisPtr, &actualMethod);
method = actualMethod;
}
if (!method.IsNull())
{
// check, do we need to pass mouse index as a parameter
if (env->CheckExtensions())
{
UInt nArgs = 0;
bool handlerFound = true;
if (id.RollOverCnt != 0)
{
// we need to check do we need to invoke nested drag/roll/Over/Out
// for multiple mice. If the corresponding handler function has 2
// or more parameters, then it is assumed to be ready for handling
// multiple rollovers, so, onRollOver/Out (and onDrag...) will be
// invoked for each mouse. If there are less than two parameters
// (or, most likely, no parameters at all) then onRollOver will be
// invoked only once when the first mouse cursor appears above the clip
// and onRollOut will be invoked once when the last cursor leave the clip.
GASFunctionRef mfref = method.ToFunction(env);
if (!mfref.IsNull())
{
if (mfref->GetNumArgs() < 2)
handlerFound = false;
}
}
if (handlerFound)
{
++handlerFoundCnt;
if (env->IsVerboseAction())
env->LogAction("\n!!! ExecuteEvent started '%s' = %p for %s\n", methodName.ToCStr(),
method.ToFunction(env).GetObjectPtr(), GetCharacterHandle()->GetNamePath().ToCStr());
// Enable button index for applicable events (including all aux events)
if (id.Id & GFxEventId::Event_AuxEventMask || GFxEventId::Event_DragOver ||
id.Id == GFxEventId::Event_DragOut || id.Id == GFxEventId::Event_ReleaseOutside ||
id.Id == GFxEventId::Event_Release || id.Id == GFxEventId::Event_Press)
{
const GFxButtonEventId& btnEvtId = static_cast<const GFxButtonEventId&>(id);
env->Push(GASValue(btnEvtId.ButtonId));
++nArgs;
}
if (id.Id == GFxEventId::Event_RollOver || id.Id == GFxEventId::Event_RollOut ||
id.Id == GFxEventId::Event_DragOver || id.Id == GFxEventId::Event_DragOut ||
id.Id == GFxEventId::Event_DragOverAux || id.Id == GFxEventId::Event_DragOutAux)
{
env->Push(GASValue(id.RollOverCnt));
++nArgs;
}
else if (id.Id == GFxEventId::Event_Press || id.Id == GFxEventId::Event_Release ||
id.Id == GFxEventId::Event_PressAux || id.Id == GFxEventId::Event_ReleaseAux)
{
// push a negative number if keyboard was used; 0, if mouse.
if (id.KeyCode == GFxKey::VoidSymbol)
env->Push(GASValue(0));
else
env->Push(GASValue(-1));
++nArgs;
}
if (id.MouseIndex >= 0 || nArgs > 0)
{
env->Push(GASValue(id.MouseIndex));
++nArgs;
}
GAS_Invoke(method, NULL, this, env, nArgs, env->GetTopIndex(), methodName.ToCStr());
if (nArgs > 0)
env->Drop(nArgs);
if (env->IsVerboseAction())
env->LogAction("!!! ExecuteEvent finished '%s' = %p for %s\n\n", methodName.ToCStr(),
method.ToFunction(env).GetObjectPtr(), GetCharacterHandle()->GetNamePath().ToCStr());
}
}
else
{
if (id.RollOverCnt == 0)
{
++handlerFoundCnt;
if (env->IsVerboseAction())
env->LogAction("\n!!! ExecuteEvent started '%s' = %p for %s\n", methodName.ToCStr(),
method.ToFunction(env).GetObjectPtr(), GetCharacterHandle()->GetNamePath().ToCStr());
GAS_Invoke0(method, NULL, this, env);
if (env->IsVerboseAction())
env->LogAction("!!! ExecuteEvent finished '%s' = %p for %s\n\n", methodName.ToCStr(),
method.ToFunction(env).GetObjectPtr(), GetCharacterHandle()->GetNamePath().ToCStr());
}
}
}
}
}
return handlerFoundCnt != 0;
}
bool GFxASCharacter::ExecuteFunction(const GASFunctionRef& function, const GASValueArray& params)
{
if (function.GetObjectPtr() != 0)
{
GASValue result;
GASEnvironment* penv = GetASEnvironment();
GASSERT(penv);
int nArgs = (int)params.GetSize();
if (nArgs > 0)
{
for (int i = nArgs - 1; i >= 0; --i)
penv->Push(params[i]);
}
function.Invoke(GASFnCall(&result, this, penv, nArgs, penv->GetTopIndex()));
if (nArgs > 0)
{
penv->Drop(nArgs);
}
return true;
}
return false;
}
bool GFxASCharacter::ExecuteCFunction(const GASCFunctionPtr function, const GASValueArray& params)
{
if (function != 0)
{
GASValue result;
GASEnvironment* penv = GetASEnvironment();
GASSERT(penv);
int nArgs = (int)params.GetSize();
if (nArgs > 0)
{
for (int i = nArgs - 1; i >= 0; --i)
penv->Push(params[i]);
}
function(GASFnCall(&result, this, penv, nArgs, penv->GetTopIndex()));
if (nArgs > 0)
{
penv->Drop(nArgs);
}
return true;
}
return false;
}
// set this.__proto__ = psrcObj->prototype
void GFxASCharacter::SetProtoToPrototypeOf(GASObjectInterface* psrcObj)
{
GASValue prototype;
GASStringContext *psc = GetASEnvironment()->GetSC();
if (psrcObj->GetMemberRaw(psc, psc->GetBuiltin(GASBuiltin_prototype), &prototype))
Set__proto__(psc, prototype.ToObject(NULL));
}
void GFxASCharacter::VisitMembers(GASStringContext *psc,
MemberVisitor *pvisitor,
UInt visitFlags,
const GASObjectInterface*) const
{
GPtr<GASObject> asObj = GetASObject();
if (asObj)
asObj->VisitMembers(psc, pvisitor, visitFlags, this);
}
// Delete a member field, if not read-only. Return true if deleted.
bool GFxASCharacter::DeleteMember(GASStringContext *psc, const GASString& name)
{
if (IsStandardMember(name))
{
StandardMember member = GetStandardMemberConstant(name);
if (member != M_InvalidMember && (member <= M_SharedPropertyEnd))
{
if (GetStandardMemberBitMask() & (1<<member))
{
switch(member)
{
case M_useHandCursor:
UndefineUseHandCursorFlag();
return true;
default:
return false;
}
}
}
}
GPtr<GASObject> asObj = GetASObject();
if (asObj)
{
if (asObj->DeleteMember(psc, name))
return true;
}
return false;
}
bool GFxASCharacter::SetMemberFlags(GASStringContext *psc, const GASString& name, const UByte flags)
{
GPtr<GASObject> asObj = GetASObject();
if (asObj)
return asObj->SetMemberFlags(psc, name, flags);
//TODO: Standard members?... (AB)
return false;
}
bool GFxASCharacter::SetMember(GASEnvironment* penv, const GASString& name, const GASValue& val, const GASPropFlags& flags)
{
#ifndef GFC_NO_3D
void GFxValue_UpdateTransform(GFxASCharacter* pchar);
#endif
if (IsStandardMember(name))
{
StandardMember member = GetStandardMemberConstant(name);
if (SetStandardMember(member, val, 0))
return true;
switch(member)
{
case M_topmostLevel:
if (GetASEnvironment()->CheckExtensions())
{
SetTopmostLevelFlag(val.ToBool(GetASEnvironment()));
if (IsTopmostLevelFlagSet())
GetMovieRoot()->AddTopmostLevelCharacter(this);
else
GetMovieRoot()->RemoveTopmostLevelCharacter(this);
}
break; // do not return - need to save it to members too
case M_noAdvance:
if (GetASEnvironment()->CheckExtensions())
{
#ifndef GFC_USE_OLD_ADVANCE
bool noAdv = val.ToBool(GetASEnvironment());
if (noAdv != IsNoAdvanceLocalFlagSet())
{
SetNoAdvanceLocalFlag(noAdv);
ModifyOptimizedPlayList(GetMovieRoot());
GFxASCharacter* pparent = GetParent();
if (pparent && !pparent->IsNoAdvanceLocalFlagSet())
PropagateNoAdvanceLocalFlag();
}
#else
SetNoAdvanceLocalFlag(val.ToBool(GetASEnvironment()));
#endif
}
break; // do not return - need to save it to members too
#ifndef GFC_NO_3D
// shadow code in GFxValueImpl.cpp SetDisplayInfo
case M_perspfov:
if (GetASEnvironment()->CheckExtensions())
{
// NOTE: If updating this logic, also update GFxValue::ObjectInterface::SetDisplayInfo
GASNumber perspval = val.ToNumber(GetASEnvironment());
if (val.IsUndefined() || GASNumberUtil::IsNaN(perspval))
{
break;
}
if (GASNumberUtil::IsNEGATIVE_INFINITY(perspval) || GASNumberUtil::IsPOSITIVE_INFINITY(perspval))
perspval = 0;
SetPerspectiveFOV(Float(perspval));
}
break;
case M_z:
if (GetASEnvironment()->CheckExtensions())
{
// NOTE: If updating this logic, also update GFxValue::ObjectInterface::SetDisplayInfo
GASNumber zval = val.ToNumber(GetASEnvironment());
if (val.IsUndefined() || GASNumberUtil::IsNaN(zval))
{
break;
}
if (GASNumberUtil::IsNEGATIVE_INFINITY(zval) || GASNumberUtil::IsPOSITIVE_INFINITY(zval))
zval = 0;
//SetAcceptAnimMoves(0);
EnsureGeomDataCreated(); // let timeline anim continue
GASSERT(pGeomData);
pGeomData->Z = zval;
GFxValue_UpdateTransform(this);
}
break;
case M_zscale:
if (GetASEnvironment()->CheckExtensions())
{
// NOTE: If updating this logic, also update GFxValue::ObjectInterface::SetDisplayInfo
Double newZScale = val.ToNumber(GetASEnvironment());
if (val.IsUndefined() || GASNumberUtil::IsNaN(newZScale) ||
GASNumberUtil::IsNEGATIVE_INFINITY(newZScale) || GASNumberUtil::IsPOSITIVE_INFINITY(newZScale))
{
break;
}
//SetAcceptAnimMoves(0);
EnsureGeomDataCreated(); // let timeline anim continue
GASSERT(pGeomData);
pGeomData->ZScale = newZScale;
GFxValue_UpdateTransform(this);
}
break;
case M_matrix3d:
if (GetASEnvironment()->CheckExtensions())
{
// NOTE: If updating this logic, also update GFxValue::ObjectInterface::SetDisplayInfo
GASObject *pObj = val.ToObject(penv);
if (pObj && pObj->GetObjectType() != GASObjectInterface::Object_Array)
{
break;
}
if (pObj == NULL)
{
// remove 3D transform
if (pMatrix3D_1)
{
delete pMatrix3D_1;
pMatrix3D_1 = NULL;
EnsureGeomDataCreated(); // let timeline anim continue
}
break;
}
GASArrayObject *arrayObj = static_cast<GASArrayObject*>(pObj);
GASSERT(arrayObj->GetSize()==16);
GMatrix3D m;
for(int i = 0, n = arrayObj->GetSize(); i < n; ++i)
{
GASValue* elem = arrayObj->GetElementPtr(i);
if (elem && elem->IsNumber())
m.GetAsFloatArray()[i] = (Float)elem->ToNumber(penv);
}
//SetAcceptAnimMoves(0);
EnsureGeomDataCreated(); // let timeline anim continue
GASSERT(pGeomData);
CreateMatrix3D();
if (m.IsValid())
SetMatrix3D(m);
}
break;
case M_yrotation:
case M_xrotation:
// shadow code in GFxValueImpl.cpp SetDisplayInfo
if (GetASEnvironment()->CheckExtensions())
{
// NOTE: If updating this logic, also update GFxValue::ObjectInterface::SetDisplayInfo
GASNumber rval = val.ToNumber(GetASEnvironment());
if (val.IsUndefined() || GASNumberUtil::IsNaN(rval))
{
break;
}
//SetAcceptAnimMoves(0);
EnsureGeomDataCreated(); // let timeline anim continue
GASSERT(pGeomData);
Double r = fmod((Double)rval, (Double)360.);
if (r > 180)
r -= 360;
else if (r < -180)
r += 360;
if (member == M_xrotation)
pGeomData->XRotation= r;
else
pGeomData->YRotation= r;
GFxValue_UpdateTransform(this);
}
break;
#endif
case M_focusGroupMask:
if (GetASEnvironment()->CheckExtensions())
{
if (val.IsUndefined())
{
break;
}
// NOTE: If updating this logic, also update GFxValue::ObjectInterface::SetDisplayInfo
UInt32 rval = val.ToUInt32(GetASEnvironment());
FocusGroupMask = (UInt16)rval;
PropagateFocusGroupMask(FocusGroupMask);
}
break;
default:
break;
}
}
// a special case for setting __proto__ to a movieclip, button or textfield
// need to set it into the character, not to the AS object, since
// Get__proto__ is no more virtual.
if (penv->IsCaseSensitive())
{
if (name == penv->GetBuiltin(GASBuiltin___proto__))
{
if (val.IsSet())
Set__proto__(penv->GetSC(), val.ToObject(NULL));
}
}
else
{
name.ResolveLowercase();
if (penv->GetBuiltin(GASBuiltin___proto__).CompareBuiltIn_CaseInsensitive_Unchecked(name))
{
if (val.IsSet())
Set__proto__(penv->GetSC(), val.ToObject(NULL));
}
}
// Note that MovieClipObject will also track setting of button
// handlers, i.e. 'onPress', etc.
GASObject* asObj = GetASObject();
if (asObj)
return asObj->SetMember(penv, name, val, flags);
return false;
}
bool GFxASCharacter::HasMember(GASStringContext *psc, const GASString& name, bool inclPrototypes)
{
if (IsStandardMember(name))
{
StandardMember member = GetStandardMemberConstant(name);
if (member != M_InvalidMember && (member <= M_SharedPropertyEnd))
{
if (GetStandardMemberBitMask() & (1<<member))
return true;
}
}
GPtr<GASObject> asObj = GetASObject();
if (asObj)
return asObj->HasMember(psc, name, inclPrototypes);
return false;
}
bool GFxASCharacter::FindMember(GASStringContext *psc, const GASString& name, GASMember* pmember)
{
GPtr<GASObject> obj = GetASObject();
return (obj) ? obj->FindMember(psc, name, pmember) : false;
}
void GFxASCharacter::Set__proto__(GASStringContext *psc, GASObject* protoObj)
{
GPtr<GASObject> obj = GetASObject();
if (obj)
{
obj->Set__proto__(psc, protoObj);
}
pProto = protoObj;
}
/*GASObject* GFxASCharacter::Get__proto__()
{
GPtr<GASObject> obj = GetASObject();
return (obj) ? obj->Get__proto__() : NULL;
}*/
bool GFxASCharacter::InstanceOf(GASEnvironment* penv, const GASObject* prototype, bool inclInterfaces) const
{
GPtr<GASObject> obj = GetASObject();
return (obj) ? obj->InstanceOf(penv, prototype, inclInterfaces) : false;
}
bool GFxASCharacter::Watch(GASStringContext *psc, const GASString& prop, const GASFunctionRef& callback, const GASValue& userData)
{
GPtr<GASObject> obj = GetASObject();
return (obj) ? obj->Watch(psc, prop, callback, userData) : false;
}
bool GFxASCharacter::Unwatch(GASStringContext *psc, const GASString& prop)
{
GPtr<GASObject> obj = GetASObject();
return (obj) ? obj->Unwatch(psc, prop) : false;
}
bool GFxASCharacter::IsFocusRectEnabled() const
{
if (IsFocusRectFlagDefined())
return IsFocusRectFlagTrue();
//!AB: _focusrect seems to ignore lockroot. that is why we specify "true" as a parameter
// of GetASRootMovie.
GFxASCharacter* prootMovie = this->GetASRootMovie(true);
if (prootMovie != this)
return prootMovie->IsFocusRectEnabled();
return true;
}
void GFxASCharacter::OnFocus(FocusEventType event, GFxASCharacter* oldOrNewFocusCh, UInt controllerIdx, GFxFocusMovedType)
{
GASValue focusMethodVal;
GASEnvironment* penv = GetASEnvironment();
if (!penv) // penv == NULL if object is removed from its parent's display list
return;
GASStringContext* psc = penv->GetSC();
GASString focusMethodName(psc->GetBuiltin((event == SetFocus) ?
GASBuiltin_onSetFocus : GASBuiltin_onKillFocus));
if (GetMemberRaw(psc, focusMethodName, &focusMethodVal))
{
GASFunctionRef focusMethod = focusMethodVal.ToFunction(NULL);
if (!focusMethod.IsNull())
{
UInt nargs = 1;
if (penv->CheckExtensions())
{
penv->Push(GASNumber(controllerIdx));
++nargs;
}
if (oldOrNewFocusCh)
penv->Push(GASValue(oldOrNewFocusCh));
else
penv->Push(GASValue::NULLTYPE);
GASValue result;
focusMethod.Invoke(GASFnCall(&result, GASValue(this), penv, nargs, penv->GetTopIndex()));
penv->Drop(nargs);
}
}
}
void GFxASCharacter::SetFilters(const GArray<GFxFilterDesc> f)
{
GFxMovieRoot* proot = GetMovieRoot();
if (!pFilters)
{
if (f.GetSize() == 0)
return;
pFilters = GHEAP_AUTO_NEW(this) CharFilterDesc(f);
}
else
pFilters->Filters = f;
pFilters->LastIsTemp = 0;
pFilters->HasFilters = (!proot->pRenderConfig
|| (proot->pRenderConfig->GetRendererCapBits() & GRenderer::Cap_RenderTargets)) ? (f.GetSize() > 0 ? 1 : 0) : 0;
#ifndef GFC_NO_FILTERS_PREPASS
bool oldPrePass = (Flags & Mask_NeedPreDisplay) != 0;
bool newPrePass = proot->pRenderConfig && GFxFilterDesc::UsesRenderTarget(f) &&
(proot->pRenderConfig->GetRendererCapBits() & GRenderer::Cap_RenderTargetPrePass);
if (newPrePass)
{
pFilters->HasFilters = 1;
pFilters->MainPassFilters.Clear();
// a single pass filter can be processed in the main pass
if (!f.Back().UsesBlurFilter() ||
!(proot->pRenderConfig->GetRenderer()->CheckFilterSupport(f.Back().Blur) & GRenderer::FilterSupport_Multipass))
pFilters->MainPassFilters.PushBack(f.Back());
else
{
// Set a ColorMatrix of 1 to render the prepass results in the main pass
GFxFilterDesc null;
null.SetIdentity();
pFilters->MainPassFilters.PushBack(null);
pFilters->Filters.PushBack(null);
pFilters->LastIsTemp = 1;
}
if (!oldPrePass)
AddToPreDisplayList(proot);
}
else if (oldPrePass)
{
pFilters->MainPassFilters.Clear();
pFilters->Filters.Clear();
RemoveFromPreDisplayList(proot);
}
#endif
}
void GFxASCharacter::OnRendererChanged()
{
if (pFilters)
{
if (pFilters->LastIsTemp)
pFilters->Filters.PopBack();
SetFilters(pFilters->Filters);
}
}
void GFxASCharacter::AddToPreDisplayList(GFxMovieRoot* proot)
{
if (!IsInPreDisplayList())
{
GASSERT(proot);
proot->AddToPreDisplayList(this);
Flags |= Mask_NeedPreDisplay;
// children must be processed before parents, so move parent to the end
GFxCharacter* pparent = GetParent();
while (pparent)
{
GFxASCharacter* pparentAS = pparent->CharToASCharacter();
if (pparentAS && pparentAS->IsInPreDisplayList())
{
pparentAS->RemoveFromPreDisplayList(proot);
pparentAS->AddToPreDisplayList(proot);
break;
}
pparent = pparent->GetParent();
}
}
}
void GFxASCharacter::RemoveFromPreDisplayList(GFxMovieRoot* proot)
{
if (IsInPreDisplayList())
{
GASSERT(proot);
proot->RemoveFromPreDisplayList(this);
Flags &= ~Mask_NeedPreDisplay;
}
}
// ***** GFxGenericCharacter
GFxGenericCharacter::GFxGenericCharacter(GFxCharacterDef* pdef, GFxASCharacter* pparent, GFxResourceId id)
: GFxCharacter(pparent, id)
{
pDef = pdef;
GASSERT(pDef);
}
GFxASCharacter* GFxGenericCharacter::GetTopMostMouseEntity(const GPointF &pt, const TopMostParams& params)
{
if (!GetVisible())
return 0;
Matrix m = GetMatrix();
GPointF p;
#ifndef GFC_NO_3D
if (Is3D(true))
{
const GMatrix3D *pPersp = GetPerspective3D(true);
const GMatrix3D *pView = GetView3D(true);
if (pPersp)
GetMovieRoot()->ScreenToWorld.SetPerspective(*pPersp);
if (pView)
GetMovieRoot()->ScreenToWorld.SetView(*pView);
GetMovieRoot()->ScreenToWorld.SetWorld(GetWorldMatrix3D());
GetMovieRoot()->ScreenToWorld.GetWorldPoint(&p);
}
else
#endif
{
m.TransformByInverse(&p, pt);
}
if ((ClipDepth == 0) && pDef->DefPointTestLocal(p, 1, this))
{
// The mouse is inside the shape.
// printf("INSIDE %s\n", pParent ? pParent->GetName().ToCStr() : "NULL");
GFxASCharacter* pParent = GetParent();
while (pParent && pParent->IsSprite())
{
// Parent sprite would have to be in button mode to grab attention.
GFxSprite * psprite = (GFxSprite*)pParent;
if (params.TestAll || psprite->ActsAsButton()
|| (psprite->GetHitAreaHolder() && psprite->GetHitAreaHolder()->ActsAsButton()))
{
// Check if sprite should be ignored
if (!params.IgnoreMC || (params.IgnoreMC != psprite))
return psprite;
}
pParent = pParent->GetParent ();
}
}
else
{
// printf("outside %s\n", pParent ? pParent->GetName().ToCStr() : "NULL");
}
return 0;
}
bool GFxGenericCharacter::PointTestLocal(const GPointF &pt, UInt8 hitTestMask) const
{
return pDef->DefPointTestLocal(pt, hitTestMask & HitTest_TestShape, this);
}