forked from 0ad/0ad
Ykkrosh
d39a4fac21
# Attempted to make compilation faster by including as little as possible in some .h files, and moving it into .cpp. Fixed BaseTechCollection memory leak. This was SVN commit r3992.
495 lines
14 KiB
C++
495 lines
14 KiB
C++
#include "precompiled.h"
|
|
|
|
#include <math.h>
|
|
|
|
#include "graphics/GameView.h"
|
|
#include "graphics/MiniPatch.h"
|
|
#include "graphics/Model.h"
|
|
#include "graphics/Terrain.h"
|
|
#include "graphics/TextureEntry.h"
|
|
#include "graphics/TextureManager.h"
|
|
#include "graphics/Unit.h"
|
|
#include "graphics/UnitManager.h"
|
|
#include "gui/MiniMap.h"
|
|
#include "lib/ogl.h"
|
|
#include "maths/Bound.h"
|
|
#include "ps/Game.h"
|
|
#include "ps/Interact.h"
|
|
#include "ps/Network/NetMessage.h"
|
|
#include "ps/Profile.h"
|
|
#include "renderer/Renderer.h"
|
|
#include "renderer/WaterManager.h"
|
|
#include "scripting/GameEvents.h"
|
|
#include "simulation/Entity.h"
|
|
#include "simulation/LOSManager.h"
|
|
|
|
|
|
bool g_TerrainModified = false;
|
|
|
|
// used by GetMapSpaceCoords (precalculated as an optimization).
|
|
// this was formerly access via inline asm, which required it to be
|
|
// static data instead of a class member. that is no longer the case,
|
|
// but we leave it because this is slightly more efficient.
|
|
static float m_scaleX, m_scaleY;
|
|
extern bool g_mouse_buttons[1];
|
|
|
|
|
|
static unsigned int ScaleColor(unsigned int color, float x)
|
|
{
|
|
unsigned int r = uint(float(color & 0xff) * x);
|
|
unsigned int g = uint(float((color>>8) & 0xff) * x);
|
|
unsigned int b = uint(float((color>>16) & 0xff) * x);
|
|
return (0xff000000 | r | g<<8 | b<<16);
|
|
}
|
|
|
|
CMiniMap::CMiniMap()
|
|
: m_TerrainTexture(0), m_TerrainData(0), m_MapSize(0), m_Terrain(0),
|
|
m_LOSTexture(0), m_LOSData(0), m_UnitManager(0)
|
|
{
|
|
AddSetting(GUIST_CColor, "fov_wedge_color");
|
|
AddSetting(GUIST_CStr, "tooltip");
|
|
AddSetting(GUIST_CStr, "tooltip_style");
|
|
m_Clicking = false;
|
|
}
|
|
|
|
CMiniMap::~CMiniMap()
|
|
{
|
|
Destroy();
|
|
}
|
|
|
|
void CMiniMap::HandleMessage(const SGUIMessage &Message)
|
|
{
|
|
switch(Message.type)
|
|
{
|
|
case GUIM_MOUSE_PRESS_LEFT:
|
|
{
|
|
SetCameraPos();
|
|
m_Clicking = true;
|
|
break;
|
|
}
|
|
case GUIM_MOUSE_RELEASE_LEFT:
|
|
{
|
|
SetCameraPos();
|
|
m_Clicking = false;
|
|
break;
|
|
}
|
|
case GUIM_MOUSE_ENTER:
|
|
{
|
|
g_Selection.m_mouseOverMM = true;
|
|
break;
|
|
}
|
|
case GUIM_MOUSE_LEAVE:
|
|
{
|
|
g_Selection.m_mouseOverMM = false;
|
|
break;
|
|
}
|
|
|
|
case GUIM_MOUSE_RELEASE_RIGHT:
|
|
{
|
|
CMiniMap::FireWorldClickEvent(SDL_BUTTON_RIGHT, 1);
|
|
break;
|
|
}
|
|
case GUIM_MOUSE_DBLCLICK_RIGHT:
|
|
{
|
|
CMiniMap::FireWorldClickEvent(SDL_BUTTON_RIGHT, 2);
|
|
break;
|
|
}
|
|
case GUIM_MOUSE_MOTION:
|
|
{
|
|
if (m_Clicking)
|
|
SetCameraPos();
|
|
break;
|
|
}
|
|
|
|
default:
|
|
break;
|
|
} // switch
|
|
}
|
|
|
|
void CMiniMap::SetCameraPos()
|
|
{
|
|
CTerrain *MMTerrain=g_Game->GetWorld()->GetTerrain();
|
|
CVector3D CamOrient=m_Camera->m_Orientation.GetTranslation();
|
|
|
|
//get center point of screen
|
|
int x = (int)g_Renderer.GetWidth()/2.f, y = (int)g_Renderer.GetHeight()/2.f;
|
|
CVector3D ScreenMiddle=m_Camera->GetWorldCoordinates(x,y);
|
|
|
|
//Get Vector required to go from camera position to ScreenMiddle
|
|
CVector3D TransVector;
|
|
TransVector.X=CamOrient.X-ScreenMiddle.X;
|
|
TransVector.Z=CamOrient.Z-ScreenMiddle.Z;
|
|
//world position of where mouse clicked
|
|
CVector3D Destination;
|
|
CPos MousePos = GetMousePos();
|
|
//X and Z according to proportion of mouse position and minimap
|
|
Destination.X = CELL_SIZE * m_MapSize *
|
|
( (MousePos.x - m_CachedActualSize.left) / m_CachedActualSize.GetWidth() );
|
|
Destination.Z = CELL_SIZE * m_MapSize * ( (m_CachedActualSize.bottom - MousePos.y) /
|
|
m_CachedActualSize.GetHeight() );
|
|
|
|
m_Camera->m_Orientation._14=Destination.X;
|
|
m_Camera->m_Orientation._34=Destination.Z;
|
|
m_Camera->m_Orientation._14+=TransVector.X;
|
|
m_Camera->m_Orientation._34+=TransVector.Z;
|
|
|
|
//Lock Y coord. No risk of zoom exceeding limit-Y does not increase
|
|
float Height=MMTerrain->getExactGroundLevel(
|
|
m_Camera->m_Orientation._14, m_Camera->m_Orientation._34) + g_YMinOffset;
|
|
|
|
if (m_Camera->m_Orientation._24 < Height)
|
|
{
|
|
m_Camera->m_Orientation._24=Height;
|
|
}
|
|
m_Camera->UpdateFrustum();
|
|
}
|
|
void CMiniMap::FireWorldClickEvent(uint button, int clicks)
|
|
{
|
|
//debug_printf("FireWorldClickEvent: button %d, clicks %d\n", button, clicks);
|
|
|
|
CPos MousePos = GetMousePos();
|
|
CVector2D Destination;
|
|
//X and Z according to proportion of mouse position and minimap
|
|
Destination.x = CELL_SIZE * m_MapSize *
|
|
( (MousePos.x - m_CachedActualSize.left) / m_CachedActualSize.GetWidth() );
|
|
Destination.y = CELL_SIZE * m_MapSize * ( (m_CachedActualSize.bottom - MousePos.y) /
|
|
m_CachedActualSize.GetHeight() );
|
|
|
|
g_JSGameEvents.FireWorldClick(
|
|
button,
|
|
clicks,
|
|
NMT_Goto,
|
|
-1,
|
|
NMT_Run,
|
|
-1,
|
|
NULL,
|
|
Destination.x,
|
|
Destination.y);
|
|
}
|
|
|
|
// render view rect : John M. Mena
|
|
// This sets up and draws the rectangle on the mini-map
|
|
// which represents the view of the camera in the world.
|
|
void CMiniMap::DrawViewRect()
|
|
{
|
|
// Get correct world coordinates based off corner of screen start
|
|
// at Bottom Left and going CW
|
|
CVector3D hitPt[4];
|
|
hitPt[0]=m_Camera->GetWorldCoordinates(0,g_Renderer.GetHeight());
|
|
hitPt[1]=m_Camera->GetWorldCoordinates(g_Renderer.GetWidth(),g_Renderer.GetHeight());
|
|
hitPt[2]=m_Camera->GetWorldCoordinates(g_Renderer.GetWidth(),0);
|
|
hitPt[3]=m_Camera->GetWorldCoordinates(0,0);
|
|
|
|
float ViewRect[4][2];
|
|
for (int i=0;i<4;i++) {
|
|
// convert to minimap space
|
|
float px=hitPt[i].X;
|
|
float pz=hitPt[i].Z;
|
|
ViewRect[i][0]=(m_CachedActualSize.GetWidth()*px/float(CELL_SIZE*m_MapSize));
|
|
ViewRect[i][1]=(m_CachedActualSize.GetHeight()*pz/float(CELL_SIZE*m_MapSize));
|
|
}
|
|
|
|
// Enable Scissoring as to restrict the rectangle
|
|
// to only the mini-map below by retrieving the mini-maps
|
|
// screen coords.
|
|
glScissor((int)m_CachedActualSize.left, 0, (int)m_CachedActualSize.right, (int)m_CachedActualSize.GetHeight());
|
|
glEnable(GL_SCISSOR_TEST);
|
|
glEnable(GL_LINE_SMOOTH);
|
|
glLineWidth(2);
|
|
glColor3f(1.0f, 0.3f, 0.3f);
|
|
|
|
// Draw the viewing rectangle with the ScEd's conversion algorithm
|
|
const float x = m_CachedActualSize.left, y = m_CachedActualSize.bottom;
|
|
glBegin(GL_LINE_LOOP);
|
|
glVertex2f(x+ViewRect[0][0], y-ViewRect[0][1]);
|
|
glVertex2f(x+ViewRect[1][0], y-ViewRect[1][1]);
|
|
glVertex2f(x+ViewRect[2][0], y-ViewRect[2][1]);
|
|
glVertex2f(x+ViewRect[3][0], y-ViewRect[3][1]);
|
|
glEnd();
|
|
|
|
// restore state
|
|
glDisable(GL_SCISSOR_TEST);
|
|
glDisable(GL_POINT_SMOOTH);
|
|
glLineWidth(1.0f);
|
|
}
|
|
|
|
void CMiniMap::Draw()
|
|
{
|
|
// The terrain isn't actually initialized until the map is loaded, which
|
|
// happens when the game is started, so abort until then.
|
|
if(!(GetGUI() && g_Game && g_Game->IsGameStarted()))
|
|
return;
|
|
|
|
// Set our globals in case they hadn't been set before
|
|
m_Camera = g_Game->GetView()->GetCamera();
|
|
m_Terrain = g_Game->GetWorld()->GetTerrain();
|
|
m_UnitManager = g_Game->GetWorld()->GetUnitManager();
|
|
m_Width = (u32)(m_CachedActualSize.right - m_CachedActualSize.left);
|
|
m_Height = (u32)(m_CachedActualSize.bottom - m_CachedActualSize.top);
|
|
m_MapSize = m_Terrain->GetVerticesPerSide();
|
|
m_TextureSize = round_up_to_pow2(m_MapSize);
|
|
|
|
m_scaleX = float(m_Width) / float(m_MapSize - 1);
|
|
m_scaleY = float(m_Height) / float(m_MapSize - 1);
|
|
|
|
if(!m_TerrainTexture)
|
|
CreateTextures();
|
|
|
|
// do not limit this as with LOS updates below - we must update
|
|
// immediately after changes are reported because this flag will be
|
|
// reset at the end of the frame.
|
|
if(g_TerrainModified)
|
|
RebuildTerrainTexture();
|
|
|
|
// only update 10x / second
|
|
// (note: since units only move a few pixels per second on the minimap,
|
|
// we can get away with infrequent updates; this is slow, ~20ms)
|
|
static double last_time;
|
|
const double cur_time = get_time();
|
|
if(cur_time - last_time > 100e-3) // 10 updates/sec
|
|
{
|
|
last_time = cur_time;
|
|
|
|
CLOSManager* losMgr = g_Game->GetWorld()->GetLOSManager();
|
|
if(losMgr->m_LOSSetting != CLOSManager::ALL_VISIBLE)
|
|
RebuildLOSTexture();
|
|
}
|
|
|
|
const float texCoordMax = ((float)m_MapSize - 1) / ((float)m_TextureSize);
|
|
const float x = m_CachedActualSize.left, y = m_CachedActualSize.bottom;
|
|
const float x2 = m_CachedActualSize.right, y2 = m_CachedActualSize.top;
|
|
const float z = GetBufferedZ();
|
|
|
|
// Draw the main textured quad
|
|
g_Renderer.BindTexture(0, m_TerrainTexture);
|
|
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
|
glBegin(GL_QUADS);
|
|
glTexCoord2f(0.0f, 0.0f);
|
|
glVertex3f(x, y, z);
|
|
glTexCoord2f(texCoordMax, 0.0f);
|
|
glVertex3f(x2, y, z);
|
|
glTexCoord2f(texCoordMax, texCoordMax);
|
|
glVertex3f(x2, y2, z);
|
|
glTexCoord2f(0.0f, texCoordMax);
|
|
glVertex3f(x, y2, z);
|
|
glEnd();
|
|
|
|
// Draw the LOS quad in black, using alpha values from the LOS texture
|
|
g_Renderer.BindTexture(0, m_LOSTexture);
|
|
glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE);
|
|
glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB_ARB, GL_REPLACE);
|
|
glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB_ARB, GL_PRIMARY_COLOR_ARB);
|
|
glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_RGB_ARB, GL_SRC_COLOR);
|
|
glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_ALPHA_ARB, GL_REPLACE);
|
|
glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_ALPHA_ARB, GL_TEXTURE);
|
|
glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_ALPHA_ARB, GL_SRC_ALPHA);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
|
glEnable(GL_BLEND);
|
|
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
|
glBegin(GL_QUADS);
|
|
glColor3f(0.0f, 0.0f, 0.0f);
|
|
glTexCoord2f(0.0f, 0.0f);
|
|
glVertex3f(x, y, z);
|
|
glTexCoord2f(texCoordMax, 0.0f);
|
|
glVertex3f(x2, y, z);
|
|
glTexCoord2f(texCoordMax, texCoordMax);
|
|
glVertex3f(x2, y2, z);
|
|
glTexCoord2f(0.0f, texCoordMax);
|
|
glVertex3f(x, y2, z);
|
|
glEnd();
|
|
glDisable(GL_BLEND);
|
|
|
|
// Draw unit points
|
|
const std::vector<CUnit *> &units = m_UnitManager->GetUnits();
|
|
std::vector<CUnit *>::const_iterator iter = units.begin();
|
|
CUnit *unit = 0;
|
|
CVector2D pos;
|
|
CLOSManager* losMgr = g_Game->GetWorld()->GetLOSManager();
|
|
glDisable(GL_DEPTH_TEST);
|
|
glEnable(GL_POINT_SMOOTH);
|
|
glDisable(GL_TEXTURE_2D);
|
|
glPointSize(3.0f);
|
|
// REMOVED: glColor3f(1.0f, 1.0f, 1.0f);
|
|
glBegin(GL_POINTS);
|
|
for(; iter != units.end(); ++iter)
|
|
{
|
|
unit = (CUnit *)(*iter);
|
|
if(unit && unit->GetEntity() && losMgr->GetUnitStatus(unit, g_Game->GetLocalPlayer()) != UNIT_HIDDEN)
|
|
{
|
|
CEntity* entity = unit->GetEntity();
|
|
CStrW& type = entity->m_minimapType;
|
|
|
|
if(type==L"Unit" || type==L"Structure" || type==L"Hero") {
|
|
// Use the player colour
|
|
const SPlayerColour& colour = unit->GetEntity()->GetPlayer()->GetColour();
|
|
glColor3f(colour.r, colour.g, colour.b);
|
|
}
|
|
else {
|
|
glColor3f(entity->m_minimapR/255.0f, entity->m_minimapG/255.0f, entity->m_minimapB/255.0f);
|
|
}
|
|
|
|
|
|
pos = GetMapSpaceCoords(unit->GetEntity()->m_position);
|
|
|
|
glVertex3f(x + pos.x, y - pos.y, z);
|
|
}
|
|
}
|
|
glEnd();
|
|
|
|
DrawViewRect();
|
|
|
|
// Reset everything back to normal
|
|
glPointSize(1.0f);
|
|
glDisable(GL_LINE_SMOOTH);
|
|
glEnable(GL_TEXTURE_2D);
|
|
glEnable(GL_DEPTH_TEST);
|
|
}
|
|
|
|
void CMiniMap::CreateTextures()
|
|
{
|
|
Destroy();
|
|
|
|
// Create terrain texture
|
|
glGenTextures(1, (GLuint *)&m_TerrainTexture);
|
|
g_Renderer.BindTexture(0, m_TerrainTexture);
|
|
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, m_TextureSize, m_TextureSize, 0, GL_BGRA_EXT, GL_UNSIGNED_BYTE, 0);
|
|
m_TerrainData = new u32[(m_MapSize - 1) * (m_MapSize - 1)];
|
|
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
|
|
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
|
|
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_CLAMP);
|
|
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_CLAMP);
|
|
|
|
// Create LOS texture
|
|
glGenTextures(1, (GLuint *)&m_LOSTexture);
|
|
g_Renderer.BindTexture(0, m_LOSTexture);
|
|
glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA8, m_TextureSize, m_TextureSize, 0, GL_ALPHA, GL_UNSIGNED_BYTE, 0);
|
|
m_LOSData = new u8[(m_MapSize - 1) * (m_MapSize - 1)];
|
|
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
|
|
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
|
|
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_CLAMP);
|
|
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_CLAMP);
|
|
|
|
// Rebuild and upload both of them
|
|
RebuildTerrainTexture();
|
|
RebuildLOSTexture();
|
|
}
|
|
|
|
|
|
void CMiniMap::RebuildTerrainTexture()
|
|
{
|
|
u32 x = 0;
|
|
u32 y = 0;
|
|
u32 w = m_MapSize - 1;
|
|
u32 h = m_MapSize - 1;
|
|
float waterHeight = g_Renderer.GetWaterManager()->m_WaterHeight;
|
|
|
|
for(u32 j = 0; j < h; j++)
|
|
{
|
|
u32 *dataPtr = m_TerrainData + ((y + j) * (m_MapSize - 1)) + x;
|
|
for(u32 i = 0; i < w; i++)
|
|
{
|
|
float avgHeight = ( m_Terrain->getVertexGroundLevel((int)i, (int)j)
|
|
+ m_Terrain->getVertexGroundLevel((int)i+1, (int)j)
|
|
+ m_Terrain->getVertexGroundLevel((int)i, (int)j+1)
|
|
+ m_Terrain->getVertexGroundLevel((int)i+1, (int)j+1)
|
|
) / 4.0f;
|
|
|
|
if(avgHeight < waterHeight)
|
|
{
|
|
*dataPtr++ = 0xff304080; // TODO: perhaps use the renderer's water color?
|
|
}
|
|
else
|
|
{
|
|
int hmap = ((int)m_Terrain->GetHeightMap()[(y + j) * m_MapSize + x + i]) >> 8;
|
|
int val = (hmap / 3) + 170;
|
|
|
|
u32 color = 0;
|
|
|
|
CMiniPatch *mp = m_Terrain->GetTile(x + i, y + j);
|
|
if(mp)
|
|
{
|
|
CTextureEntry *tex = mp->Tex1 ? g_TexMan.FindTexture(mp->Tex1) : 0;
|
|
color = tex ? tex->GetBaseColor() : 0xffffffff;
|
|
}
|
|
else
|
|
{
|
|
color = 0xffffffff;
|
|
}
|
|
|
|
*dataPtr++ = ScaleColor(color, float(val) / 255.0f);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Upload the texture
|
|
g_Renderer.BindTexture(0, m_TerrainTexture);
|
|
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, m_MapSize - 1, m_MapSize - 1, GL_BGRA_EXT, GL_UNSIGNED_BYTE, m_TerrainData);
|
|
}
|
|
|
|
TIMER_ADD_CLIENT(tc_minimap_rebuildlos);
|
|
|
|
void CMiniMap::RebuildLOSTexture()
|
|
{
|
|
PROFILE_START("rebuild minimap: los");
|
|
TIMER_ACCRUE(tc_minimap_rebuildlos);
|
|
|
|
CLOSManager* losMgr = g_Game->GetWorld()->GetLOSManager();
|
|
CPlayer* player = g_Game->GetLocalPlayer();
|
|
|
|
u32 x = 0;
|
|
u32 y = 0;
|
|
u32 w = m_MapSize - 1;
|
|
u32 h = m_MapSize - 1;
|
|
|
|
for(u32 j = 0; j < h; j++)
|
|
{
|
|
u8 *dataPtr = m_LOSData + ((y + j) * (m_MapSize - 1)) + x;
|
|
for(u32 i = 0; i < w; i++)
|
|
{
|
|
ELOSStatus status = losMgr->GetStatus((int) i, (int) j, player);
|
|
if(status == LOS_UNEXPLORED)
|
|
{
|
|
*dataPtr++ = 0xff;
|
|
}
|
|
else if(status == LOS_EXPLORED)
|
|
{
|
|
*dataPtr++ = (u8) (0xff * 0.3f);
|
|
}
|
|
else {
|
|
*dataPtr++ = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Upload the texture
|
|
g_Renderer.BindTexture(0, m_LOSTexture);
|
|
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, m_MapSize - 1, m_MapSize - 1, GL_ALPHA, GL_UNSIGNED_BYTE, m_LOSData);
|
|
|
|
PROFILE_END("rebuild minimap: los");
|
|
}
|
|
|
|
void CMiniMap::Destroy()
|
|
{
|
|
if(m_TerrainTexture)
|
|
glDeleteTextures(1, (GLuint *)&m_TerrainTexture);
|
|
|
|
if(m_LOSTexture)
|
|
glDeleteTextures(1, (GLuint *)&m_LOSTexture);
|
|
|
|
delete[] m_TerrainData; m_TerrainData = 0;
|
|
delete[] m_LOSData; m_LOSData = 0;
|
|
}
|
|
|
|
CVector2D CMiniMap::GetMapSpaceCoords(CVector3D worldPos)
|
|
{
|
|
float x = rintf(worldPos.X / CELL_SIZE);
|
|
float y = rintf(worldPos.Z / CELL_SIZE);
|
|
// Entity's Z coordinate is really its longitudinal coordinate on the terrain
|
|
|
|
// Calculate map space scale
|
|
return CVector2D(x * m_scaleX, y * m_scaleY);
|
|
}
|