/* =========================================================================== Doom 3 BFG Edition GPL Source Code Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Doom 3 BFG Edition Source Code. If not, see . In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. =========================================================================== */ #pragma hdrstop #include "../idlib/precompiled.h" /* =================== idSWF::HitTest =================== */ idSWFScriptObject* idSWF::HitTest( idSWFSpriteInstance* spriteInstance, const swfRenderState_t& renderState, int x, int y, idSWFScriptObject* parentObject ) { if( spriteInstance->parent != NULL ) { swfDisplayEntry_t* thisDisplayEntry = spriteInstance->parent->FindDisplayEntry( spriteInstance->depth ); if( thisDisplayEntry->cxf.mul.w + thisDisplayEntry->cxf.add.w < 0.001f ) { return NULL; } } if( !spriteInstance->isVisible ) { return NULL; } if( spriteInstance->scriptObject->HasValidProperty( "onRelease" ) || spriteInstance->scriptObject->HasValidProperty( "onPress" ) || spriteInstance->scriptObject->HasValidProperty( "onRollOver" ) || spriteInstance->scriptObject->HasValidProperty( "onRollOut" ) || spriteInstance->scriptObject->HasValidProperty( "onDrag" ) ) { parentObject = spriteInstance->scriptObject; } // rather than returning the first object we find, we actually want to return the last object we find idSWFScriptObject* returnObject = NULL; float xOffset = spriteInstance->xOffset; float yOffset = spriteInstance->yOffset; for( int i = 0; i < spriteInstance->displayList.Num(); i++ ) { const swfDisplayEntry_t& display = spriteInstance->displayList[i]; idSWFDictionaryEntry* entry = FindDictionaryEntry( display.characterID ); if( entry == NULL ) { continue; } swfRenderState_t renderState2; renderState2.matrix = display.matrix.Multiply( renderState.matrix ); renderState2.ratio = display.ratio; if( entry->type == SWF_DICT_SPRITE ) { idSWFScriptObject* object = HitTest( display.spriteInstance, renderState2, x, y, parentObject ); if( object != NULL && object->Get( "_visible" ).ToBool() ) { returnObject = object; } } else if( entry->type == SWF_DICT_SHAPE && ( parentObject != NULL ) ) { idSWFShape* shape = entry->shape; for( int i = 0; i < shape->fillDraws.Num(); i++ ) { const idSWFShapeDrawFill& fill = shape->fillDraws[i]; for( int j = 0; j < fill.indices.Num(); j += 3 ) { idVec2 xy1 = renderState2.matrix.Transform( fill.startVerts[fill.indices[j + 0]] ); idVec2 xy2 = renderState2.matrix.Transform( fill.startVerts[fill.indices[j + 1]] ); idVec2 xy3 = renderState2.matrix.Transform( fill.startVerts[fill.indices[j + 2]] ); idMat3 edgeEquations; edgeEquations[0].Set( xy1.x + xOffset, xy1.y + yOffset, 1.0f ); edgeEquations[1].Set( xy2.x + xOffset, xy2.y + yOffset, 1.0f ); edgeEquations[2].Set( xy3.x + xOffset, xy3.y + yOffset, 1.0f ); edgeEquations.InverseSelf(); idVec3 p( x, y, 1.0f ); idVec3 signs = p * edgeEquations; bool bx = signs.x > 0; bool by = signs.y > 0; bool bz = signs.z > 0; if( bx == by && bx == bz ) { // point inside returnObject = parentObject; } } } } else if( entry->type == SWF_DICT_MORPH ) { // FIXME: this should be roughly the same as SWF_DICT_SHAPE } else if( entry->type == SWF_DICT_TEXT ) { // FIXME: this should be roughly the same as SWF_DICT_SHAPE } else if( entry->type == SWF_DICT_EDITTEXT ) { idSWFScriptObject* editObject = NULL; if( display.textInstance->scriptObject.HasProperty( "onRelease" ) || display.textInstance->scriptObject.HasProperty( "onPress" ) ) { // if the edit box itself can be clicked, then we want to return it when it's clicked on editObject = &display.textInstance->scriptObject; } else if( parentObject != NULL ) { // otherwise, we want to return the parent object editObject = parentObject; } if( editObject == NULL ) { continue; } if( display.textInstance->text.IsEmpty() ) { continue; } const idSWFEditText* shape = entry->edittext; const idSWFEditText* text = display.textInstance->GetEditText(); float textLength = display.textInstance->GetTextLength(); float lengthDiff = fabs( shape->bounds.br.x - shape->bounds.tl.x ) - textLength; idVec3 tl; idVec3 tr; idVec3 br; idVec3 bl; float xOffset = spriteInstance->xOffset; float yOffset = spriteInstance->yOffset; float topOffset = 0.0f; if( text->align == SWF_ET_ALIGN_LEFT ) { tl.ToVec2() = renderState2.matrix.Transform( idVec2( shape->bounds.tl.x + xOffset, shape->bounds.tl.y + topOffset + yOffset ) ); tr.ToVec2() = renderState2.matrix.Transform( idVec2( shape->bounds.br.x - lengthDiff + xOffset, shape->bounds.tl.y + topOffset + yOffset ) ); br.ToVec2() = renderState2.matrix.Transform( idVec2( shape->bounds.br.x - lengthDiff + xOffset, shape->bounds.br.y + topOffset + yOffset ) ); bl.ToVec2() = renderState2.matrix.Transform( idVec2( shape->bounds.tl.x + xOffset, shape->bounds.br.y + topOffset + yOffset ) ); } else if( text->align == SWF_ET_ALIGN_RIGHT ) { tl.ToVec2() = renderState2.matrix.Transform( idVec2( shape->bounds.tl.x + lengthDiff + xOffset, shape->bounds.tl.y + topOffset + yOffset ) ); tr.ToVec2() = renderState2.matrix.Transform( idVec2( shape->bounds.br.x + xOffset, shape->bounds.tl.y + topOffset + yOffset ) ); br.ToVec2() = renderState2.matrix.Transform( idVec2( shape->bounds.br.x + xOffset, shape->bounds.br.y + topOffset + yOffset ) ); bl.ToVec2() = renderState2.matrix.Transform( idVec2( shape->bounds.tl.x + lengthDiff + xOffset, shape->bounds.br.y + topOffset + yOffset ) ); } else if( text->align == SWF_ET_ALIGN_CENTER ) { float middle = ( ( shape->bounds.br.x + xOffset ) + ( shape->bounds.tl.x + xOffset ) ) / 2.0f; tl.ToVec2() = renderState2.matrix.Transform( idVec2( middle - ( textLength / 2.0f ), shape->bounds.tl.y + topOffset + yOffset ) ); tr.ToVec2() = renderState2.matrix.Transform( idVec2( middle + ( textLength / 2.0f ), shape->bounds.tl.y + topOffset + yOffset ) ); br.ToVec2() = renderState2.matrix.Transform( idVec2( middle + ( textLength / 2.0f ), shape->bounds.br.y + topOffset + yOffset ) ); bl.ToVec2() = renderState2.matrix.Transform( idVec2( middle - ( textLength / 2.0f ), shape->bounds.br.y + topOffset + yOffset ) ); } else { tl.ToVec2() = renderState2.matrix.Transform( idVec2( shape->bounds.tl.x + xOffset, shape->bounds.tl.y + topOffset + yOffset ) ); tr.ToVec2() = renderState2.matrix.Transform( idVec2( shape->bounds.br.x + xOffset, shape->bounds.tl.y + topOffset + yOffset ) ); br.ToVec2() = renderState2.matrix.Transform( idVec2( shape->bounds.br.x + xOffset, shape->bounds.br.y + topOffset + yOffset ) ); bl.ToVec2() = renderState2.matrix.Transform( idVec2( shape->bounds.tl.x + xOffset, shape->bounds.br.y + topOffset + yOffset ) ); } tl.z = 1.0f; tr.z = 1.0f; br.z = 1.0f; bl.z = 1.0f; idMat3 edgeEquations; edgeEquations[0] = tl; edgeEquations[1] = tr; edgeEquations[2] = br; edgeEquations.InverseSelf(); idVec3 p( x, y, 1.0f ); idVec3 signs = p * edgeEquations; bool bx = signs.x > 0; bool by = signs.y > 0; bool bz = signs.z > 0; if( bx == by && bx == bz ) { // point inside top right triangle returnObject = editObject; } edgeEquations[0] = tl; edgeEquations[1] = br; edgeEquations[2] = bl; edgeEquations.InverseSelf(); signs = p * edgeEquations; bx = signs.x > 0; by = signs.y > 0; bz = signs.z > 0; if( bx == by && bx == bz ) { // point inside bottom left triangle returnObject = editObject; } } } return returnObject; } /* =================== idSWF::HandleEvent =================== */ bool idSWF::HandleEvent( const sysEvent_t* event ) { if( !IsLoaded() || !IsActive() || ( !inhibitControl && useInhibtControl ) ) { return false; } if( event->evType == SE_KEY ) { if( event->evValue == K_MOUSE1 ) { mouseEnabled = true; idSWFScriptVar var; if( event->evValue2 ) { idSWFScriptVar waitInput = globals->Get( "waitInput" ); if( waitInput.IsFunction() ) { useMouse = false; idSWFParmList waitParms; waitParms.Append( event->evValue ); waitInput.GetFunction()->Call( NULL, waitParms ); waitParms.Clear(); } else { useMouse = true; } idSWFScriptObject* hitObject = HitTest( mainspriteInstance, swfRenderState_t(), mouseX, mouseY, NULL ); if( hitObject != NULL ) { mouseObject = hitObject; mouseObject->AddRef(); var = hitObject->Get( "onPress" ); if( var.IsFunction() ) { idSWFParmList parms; parms.Append( event->inputDevice ); var.GetFunction()->Call( hitObject, parms ); parms.Clear(); return true; } idSWFScriptVar var = hitObject->Get( "onDrag" ); if( var.IsFunction() ) { idSWFParmList parms; parms.Append( mouseX ); parms.Append( mouseY ); parms.Append( true ); var.GetFunction()->Call( hitObject, parms ); parms.Clear(); return true; } } idSWFParmList parms; parms.Append( hitObject ); Invoke( "setHitObject", parms ); } else { if( mouseObject ) { var = mouseObject->Get( "onRelease" ); if( var.IsFunction() ) { idSWFParmList parms; parms.Append( mouseObject ); // FIXME: Remove this var.GetFunction()->Call( mouseObject, parms ); } mouseObject->Release(); mouseObject = NULL; } if( hoverObject ) { hoverObject->Release(); hoverObject = NULL; } if( var.IsFunction() ) { return true; } } return false; } const char* keyName = idKeyInput::KeyNumToString( ( keyNum_t )event->evValue ); idSWFScriptVar var = shortcutKeys->Get( keyName ); // anything more than 32 levels of indirection we can be pretty sure is an infinite loop for( int runaway = 0; runaway < 32; runaway++ ) { idSWFParmList eventParms; eventParms.Clear(); eventParms.Append( event->inputDevice ); if( var.IsString() ) { // alias to another key var = shortcutKeys->Get( var.ToString() ); continue; } else if( var.IsObject() ) { // if this object is a sprite, send fake mouse events to it idSWFScriptObject* object = var.GetObject(); // make sure we don't send an onRelease event unless we have already sent that object an onPress bool wasPressed = object->Get( "_pressed" ).ToBool(); object->Set( "_pressed", event->evValue2 ); if( event->evValue2 ) { var = object->Get( "onPress" ); } else if( wasPressed ) { var = object->Get( "onRelease" ); } if( var.IsFunction() ) { var.GetFunction()->Call( object, eventParms ); return true; } } else if( var.IsFunction() ) { if( event->evValue2 ) { // anonymous functions only respond to key down events var.GetFunction()->Call( NULL, eventParms ); return true; } return false; } idSWFScriptVar useFunction = globals->Get( "useFunction" ); if( useFunction.IsFunction() && event->evValue2 ) { const char* action = idKeyInput::GetBinding( event->evValue ); if( idStr::Cmp( "_use", action ) == 0 ) { useFunction.GetFunction()->Call( NULL, idSWFParmList() ); } } idSWFScriptVar waitInput = globals->Get( "waitInput" ); if( waitInput.IsFunction() ) { useMouse = false; if( event->evValue2 ) { idSWFParmList waitParms; waitParms.Append( event->evValue ); waitInput.GetFunction()->Call( NULL, waitParms ); } } else { useMouse = true; } idSWFScriptVar focusWindow = globals->Get( "focusWindow" ); if( focusWindow.IsObject() ) { idSWFScriptVar onKey = focusWindow.GetObject()->Get( "onKey" ); if( onKey.IsFunction() ) { // make sure we don't send an onRelease event unless we have already sent that object an onPress idSWFScriptObject* object = focusWindow.GetObject(); bool wasPressed = object->Get( "_kpressed" ).ToBool(); object->Set( "_kpressed", event->evValue2 ); if( event->evValue2 || wasPressed ) { idSWFParmList parms; parms.Append( event->evValue ); parms.Append( event->evValue2 ); onKey.GetFunction()->Call( focusWindow.GetObject(), parms ).ToBool(); return true; } else if( event->evValue == K_LSHIFT || event->evValue == K_RSHIFT ) { idSWFParmList parms; parms.Append( event->evValue ); parms.Append( event->evValue2 ); onKey.GetFunction()->Call( focusWindow.GetObject(), parms ).ToBool(); } } } return false; } idLib::Warning( "Circular reference in %s shortcutKeys.%s", filename.c_str(), keyName ); } else if( event->evType == SE_CHAR ) { idSWFScriptVar focusWindow = globals->Get( "focusWindow" ); if( focusWindow.IsObject() ) { idSWFScriptVar onChar = focusWindow.GetObject()->Get( "onChar" ); if( onChar.IsFunction() ) { idSWFParmList parms; parms.Append( event->evValue ); parms.Append( idKeyInput::KeyNumToString( ( keyNum_t )event->evValue ) ); onChar.GetFunction()->Call( focusWindow.GetObject(), parms ).ToBool(); return true; } } } else if( event->evType == SE_MOUSE_ABSOLUTE || event->evType == SE_MOUSE ) { mouseEnabled = true; isMouseInClientArea = true; // Mouse position in screen space needs to be converted to SWF space if( event->evType == SE_MOUSE_ABSOLUTE ) { const float pixelAspect = renderSystem->GetPixelAspect(); const float sysWidth = renderSystem->GetWidth() * ( pixelAspect > 1.0f ? pixelAspect : 1.0f ); const float sysHeight = renderSystem->GetHeight() / ( pixelAspect < 1.0f ? pixelAspect : 1.0f ); float scale = swfScale * sysHeight / ( float )frameHeight; float invScale = 1.0f / scale; float tx = 0.5f * ( sysWidth - ( frameWidth * scale ) ); float ty = 0.5f * ( sysHeight - ( frameHeight * scale ) ); mouseX = idMath::Ftoi( ( static_cast( event->evValue ) - tx ) * invScale ); mouseY = idMath::Ftoi( ( static_cast( event->evValue2 ) - ty ) * invScale ); } else { mouseX += event->evValue; mouseY += event->evValue2; mouseX = Max( Min( mouseX, idMath::Ftoi( frameWidth + renderBorder ) ), idMath::Ftoi( 0.0f - renderBorder ) ); mouseY = Max( Min( mouseY, idMath::Ftoi( frameHeight ) ), 0 ); } bool retVal = false; idSWFScriptObject* hitObject = HitTest( mainspriteInstance, swfRenderState_t(), mouseX, mouseY, NULL ); if( hitObject != NULL ) { hasHitObject = true; } else { hasHitObject = false; } if( hitObject != hoverObject ) { // First check to see if we should call onRollOut on our previous hoverObject if( hoverObject != NULL ) { idSWFScriptVar var = hoverObject->Get( "onRollOut" ); if( var.IsFunction() ) { var.GetFunction()->Call( hoverObject, idSWFParmList() ); retVal = true; } hoverObject->Release(); hoverObject = NULL; } // Then call onRollOver on our hitObject if( hitObject != NULL ) { hoverObject = hitObject; hoverObject->AddRef(); idSWFScriptVar var = hitObject->Get( "onRollOver" ); if( var.IsFunction() ) { var.GetFunction()->Call( hitObject, idSWFParmList() ); retVal = true; } } } if( mouseObject != NULL ) { idSWFScriptVar var = mouseObject->Get( "onDrag" ); if( var.IsFunction() ) { idSWFParmList parms; parms.Append( mouseX ); parms.Append( mouseY ); parms.Append( false ); var.GetFunction()->Call( mouseObject, parms ); return true; } } return retVal; } else if( event->evType == SE_MOUSE_LEAVE ) { isMouseInClientArea = false; } else if( event->evType == SE_JOYSTICK ) { idSWFParmList parms; parms.Append( event->evValue ); parms.Append( event->evValue2 / 32.0f ); Invoke( "onJoystick", parms ); } return false; }