/**
* Copyright (C) 2009-2014 Cars and Tracks Development Project (CTDP).
*
* This program 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 2
* of the License, or (at your option) any later version.
*
* This program 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 this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
package net.ctdp.rfdynhud.widgets.etv2010.timing;
import java.awt.Color;
import java.awt.Font;
import java.awt.geom.Rectangle2D;
import java.io.IOException;
import net.ctdp.rfdynhud.gamedata.Laptime;
import net.ctdp.rfdynhud.gamedata.LiveGameData;
import net.ctdp.rfdynhud.gamedata.ScoringInfo;
import net.ctdp.rfdynhud.gamedata.VehicleScoringInfo;
import net.ctdp.rfdynhud.properties.BooleanProperty;
import net.ctdp.rfdynhud.properties.EnumProperty;
import net.ctdp.rfdynhud.properties.IntProperty;
import net.ctdp.rfdynhud.properties.Property;
import net.ctdp.rfdynhud.properties.PropertyLoader;
import net.ctdp.rfdynhud.properties.PropertiesContainer;
import net.ctdp.rfdynhud.render.DrawnString;
import net.ctdp.rfdynhud.render.DrawnString.Alignment;
import net.ctdp.rfdynhud.render.DrawnStringFactory;
import net.ctdp.rfdynhud.render.TextureImage2D;
import net.ctdp.rfdynhud.util.PropertyWriter;
import net.ctdp.rfdynhud.util.SubTextureCollector;
import net.ctdp.rfdynhud.util.TimingUtil;
import net.ctdp.rfdynhud.valuemanagers.Clock;
import net.ctdp.rfdynhud.values.BoolValue;
import net.ctdp.rfdynhud.values.EnumValue;
import net.ctdp.rfdynhud.values.FloatValue;
import net.ctdp.rfdynhud.values.IntValue;
import net.ctdp.rfdynhud.values.LapState;
import net.ctdp.rfdynhud.values.ValidityTest;
import net.ctdp.rfdynhud.widgets.etv2010._base.ETVTimingWidgetBase;
import net.ctdp.rfdynhud.widgets.etv2010._util.ETVImages.BGType;
import net.ctdp.rfdynhud.widgets.etv2010._util.ETVUtils;
import net.ctdp.rfdynhud.widgets.etv2010._util.ETVWidgetSet;
/**
* The {@link ETVTimingWidget} displays the current lap time.
*
* @author Marvin Froehlich (CTDP)
*/
public class ETVTimingWidget extends ETVTimingWidgetBase
{
private static enum DisplayType
{
ALWAYS,
IF_LAP_VALID,
AT_SECTORS,
;
}
private final IntProperty positionFontSize = new IntProperty( "positionFontSize", 200 );
private final BooleanProperty alwaysShowFull1000 = new BooleanProperty( "alwaysShowFull1000", "showFull1000", false );
protected final IntProperty positionItemGap = new IntProperty( "positionItemGap", 5, 0, 100 );
private final BooleanProperty visibleInRaceSession = new BooleanProperty( "visibleInRaceSession", "visibleInRace", false );
private final EnumProperty<DisplayType> displayType = new EnumProperty<DisplayType>( "displayType", DisplayType.AT_SECTORS );
private Font positionFont = null;
private DrawnString drivernameString = null;
private DrawnString laptimeString = null;
private DrawnString bigPositionString = null;
private DrawnString relPositionString = null;
private DrawnString relTimeString = null;
private int laptimeOffsetX = 0;
private final IntValue vsiID = new IntValue();
private final IntValue ownPlace = new IntValue();
private final FloatValue ownLaptime = new FloatValue();
private final IntValue relPlace = new IntValue();
private final FloatValue relLaptime = new FloatValue( -100000f, FloatValue.DEFAULT_COMPARE_PRECISION, ValidityTest.GREATER_THAN, -99999f );
private final EnumValue<LapState> lapState = new EnumValue<LapState>();
private Laptime referenceTimeAbs = null;
private Laptime referenceTimePers = null;
private int referencePlace = 0;
private final BoolValue hasRefTime = new BoolValue();
public ETVTimingWidget()
{
super( ETVWidgetSet.INSTANCE, ETVWidgetSet.WIDGET_PACKAGE, 20.0f, 8.496094f );
}
/**
* {@inheritDoc}
*/
@Override
public void prepareForMenuItem()
{
super.prepareForMenuItem();
positionItemGap.setIntValue( 0 );
}
/**
* {@inheritDoc}
*/
@Override
public void saveProperties( PropertyWriter writer ) throws IOException
{
super.saveProperties( writer );
writer.writeProperty( visibleInRaceSession, "Whether this Widget is displayed at all in the race session." );
writer.writeProperty( displayType, "Always display or just at sector boundaries or always if valid time was made?" );
writer.writeProperty( positionFontSize, "Font size for the position in percent relative to the normal font size." );
writer.writeProperty( alwaysShowFull1000, "Show full thousands in compare time, even if sector not completed?" );
writer.writeProperty( positionItemGap, "The gap between the main elements and the position element in pixels." );
}
/**
* {@inheritDoc}
*/
@Override
public void loadProperty( PropertyLoader loader )
{
super.loadProperty( loader );
if ( loader.loadProperty( visibleInRaceSession ) );
else if ( loader.loadProperty( displayType ) );
else if ( loader.loadProperty( positionFontSize ) );
else if ( loader.loadProperty( alwaysShowFull1000 ) );
else if ( loader.loadProperty( positionItemGap ) );
}
/**
* {@inheritDoc}
*/
@Override
protected void addVisibilityPropertiesToContainer( PropertiesContainer propsCont, boolean forceAll )
{
super.addVisibilityPropertiesToContainer( propsCont, forceAll );
propsCont.addProperty( visibleInRaceSession );
propsCont.addProperty( displayType );
}
/**
* {@inheritDoc}
*/
@Override
protected void getPropertiesForParentGroup( PropertiesContainer propsCont, boolean forceAll )
{
super.getPropertiesForParentGroup( propsCont, forceAll );
propsCont.addProperty( positionFontSize );
propsCont.addProperty( alwaysShowFull1000 );
}
/**
* {@inheritDoc}
*/
@Override
protected void getItemGapProperty( PropertiesContainer propsCont, boolean forceAll )
{
super.getItemGapProperty( propsCont, forceAll );
propsCont.addProperty( positionItemGap );
}
/**
* {@inheritDoc}
*/
@Override
public void getProperties( PropertiesContainer propsCont, boolean forceAll )
{
super.getProperties( propsCont, forceAll );
}
/**
* {@inheritDoc}
*/
@Override
public void onPropertyChanged( Property property, Object oldValue, Object newValue )
{
super.onPropertyChanged( property, oldValue, newValue );
if ( property == positionFontSize )
{
positionFont = null;
}
else if ( property == getFontProperty() )
{
positionFont = null;
}
}
private final Font getPositionFont()
{
if ( positionFont == null )
{
Font base = getFont();
positionFont = base.deriveFont( base.getSize() * ( positionFontSize.getIntValue() / 100f ) );
}
return ( positionFont );
}
@Override
public void onCockpitEntered( LiveGameData gameData, boolean isEditorMode )
{
super.onCockpitEntered( gameData, isEditorMode );
vsiID.reset();
ownPlace.reset();
ownLaptime.reset();
relPlace.reset();
relLaptime.reset();
lapState.reset();
referenceTimeAbs = null;
referenceTimePers = null;
referencePlace = 0;
hasRefTime.reset();
forceCompleteRedraw( true );
}
/**
* {@inheritDoc}
*/
@Override
protected void initSubTextures( LiveGameData gameData, boolean isEditorMode, int widgetInnerWidth, int widgetInnerHeight, SubTextureCollector collector )
{
}
/**
* {@inheritDoc}
*/
@Override
protected Boolean updateVisibility( LiveGameData gameData, boolean isEditorMode )
{
/*Boolean result = */super.updateVisibility( gameData, isEditorMode );
ScoringInfo scoringInfo = gameData.getScoringInfo();
VehicleScoringInfo vsi = scoringInfo.getViewedVehicleScoringInfo();
VehicleScoringInfo refVSI = ( scoringInfo.getCompareVSI() == null ) ? scoringInfo.getFastestLapVSI() : scoringInfo.getCompareVSI();
Laptime refTime = refVSI.getFastestLaptime();
if ( ( refTime != null ) && ( refTime.getLapTime() < 0f ) )
refTime = null;
LapState ls = isEditorMode ? LapState.AFTER_SECTOR1_START : LapState.getLapState( gameData.getTrackInfo().getTrack(), vsi );
if ( !isEditorMode && ( ls == LapState.AFTER_SECTOR1_START ) && ( vsi.getStintLength() < 2.0f ) )
ls = LapState.SOMEWHERE;
if ( ( ( ls == LapState.BEFORE_SECTOR1_END ) || ( ls == LapState.AFTER_SECTOR2_START ) ) && ( ( refTime == null ) || ( refTime.getSector1() <= 0f ) ) )
ls = LapState.SOMEWHERE;
if ( ( ( ls == LapState.BEFORE_SECTOR2_END ) || ( ls == LapState.AFTER_SECTOR3_START ) ) && ( ( refTime == null ) || ( refTime.getSector2( true ) <= 0f ) ) )
ls = LapState.SOMEWHERE;
lapState.update( ls );
if ( isEditorMode || ( ls != LapState.AFTER_SECTOR1_START ) )
{
referenceTimeAbs = refTime;
referenceTimePers = vsi.getFastestLaptime();
if ( ( referenceTimePers != null ) && ( referenceTimePers.getLapTime() < 0f ) )
referenceTimePers = null;
referencePlace = isEditorMode ? 1 : refVSI.getPlace( getConfiguration().getUseClassScoring() );
}
if ( isEditorMode )
{
return ( true );
}
if ( scoringInfo.getSessionType().isRace() )
{
return ( visibleInRaceSession.getBooleanValue() );
}
if ( displayType.getEnumValue() == DisplayType.ALWAYS )
{
return ( true );
}
if ( displayType.getEnumValue() == DisplayType.IF_LAP_VALID )
{
if ( vsi.isInPits() )
return ( false );
if ( !scoringInfo.getSessionType().isRace() && ( ls == LapState.OUTLAP ) )
return ( false );
if ( ( vsi.getLaptime( vsi.getCurrentLap() ) == null ) || ( vsi.getLaptime( vsi.getCurrentLap() ).isInlap() == Boolean.TRUE ) )
return ( false );
return ( true );
}
if ( refTime == null )
{
return ( ls == LapState.BEFORE_SECTOR3_END );
}
if ( ( ls == LapState.SOMEWHERE ) || ( ls == LapState.OUTLAP ) || vsi.isInPits() || ( vsi.getLaptime( vsi.getCurrentLap() ) == null ) || ( vsi.getLaptime( vsi.getCurrentLap() ).isInlap() == Boolean.TRUE ) )
{
return ( false );
}
return ( true );
}
/**
* {@inheritDoc}
*/
@Override
protected boolean checkForChanges( LiveGameData gameData, boolean isEditorMode, TextureImage2D texture, int width, int height )
{
if ( lapState.hasChanged() )
return ( true );
//if ( isEditorMode )
{
hasRefTime.update( referenceTimeAbs != null );
if ( hasRefTime.hasChanged() )
return ( true );
}
return ( false );
}
private final Coords coords = new Coords();
/**
* {@inheritDoc}
*/
@Override
protected void initialize( LiveGameData gameData, boolean isEditorMode, DrawnStringFactory dsf, TextureImage2D texture, int width, int height )
{
Rectangle2D bigPosBounds = TextureImage2D.getStringBounds( "00", getPositionFont(), isFontAntiAliased() );
Rectangle2D posBounds = TextureImage2D.getStringBounds( "00", getFontProperty() );
int gap = itemGap.getIntValue();
coords.update( getImages(), width, height, gap, positionItemGap.getIntValue(), bigPosBounds, posBounds );
int vMiddle = ETVUtils.getLabeledDataVMiddle( coords.rowHeight + gap + coords.rowHeight, bigPosBounds );
bigPositionString = dsf.newDrawnString( "bigPositionString", width - coords.bigPosWidth + coords.bigPosCenter, 0 + vMiddle, Alignment.CENTER, false, getPositionFont(), isFontAntiAliased(), captionColor.getColor() );
vMiddle = ETVUtils.getLabeledDataVMiddle( coords.rowHeight, posBounds );
drivernameString = dsf.newDrawnString( "drivernameString", coords.rowOffset2 + coords.dataRightB, 0 * ( coords.rowHeight + gap ) + vMiddle, Alignment.RIGHT, false, getFont(), isFontAntiAliased(), getFontColor() );
laptimeString = dsf.newDrawnString( "laptimeString", coords.rowOffset1 + coords.dataRightB, 1 * ( coords.rowHeight + gap ) + vMiddle, Alignment.RIGHT, false, getFont(), isFontAntiAliased(), getFontColor() );
relPositionString = dsf.newDrawnString( "relPositionString", coords.posCenterA, 2 * ( coords.rowHeight + gap ) + vMiddle, Alignment.CENTER, false, getFont(), isFontAntiAliased(), captionColor.getColor() );
relTimeString = dsf.newDrawnString( "relTimeString", coords.dataRightA, 2 * ( coords.rowHeight + gap ) + vMiddle, Alignment.RIGHT, false, getFont(), isFontAntiAliased(), getFontColor() );
laptimeOffsetX = -TextureImage2D.getStringWidth( "00", getFontProperty() );
forceCompleteRedraw( true );
}
@Override
protected void drawBackground( LiveGameData gameData, boolean isEditorMode, TextureImage2D texture, int offsetX, int offsetY, int width, int height, boolean isRoot )
{
super.drawBackground( gameData, isEditorMode, texture, offsetX, offsetY, width, height, isRoot );
final ScoringInfo scoringInfo = gameData.getScoringInfo();
VehicleScoringInfo vsi = scoringInfo.getViewedVehicleScoringInfo();
final boolean useImages = this.useImages.getBooleanValue();
final int gap = itemGap.getIntValue();
// driver name field
if ( useImages )
ETVUtils.drawDataBackgroundI( offsetX + coords.rowOffset2, offsetY + 0 * ( coords.rowHeight + gap ), coords.mainFieldWidthB, coords.rowHeight, getImages(), BGType.NEUTRAL, texture, false );
else
ETVUtils.drawDataBackground( offsetX + coords.rowOffset2, offsetY + 0 * ( coords.rowHeight + gap ), coords.mainFieldWidthB, coords.rowHeight, dataBackgroundColor.getColor(), texture, false );
// lap time field
if ( useImages )
ETVUtils.drawDataBackgroundI( offsetX + coords.rowOffset1, offsetY + 1 * ( coords.rowHeight + gap ), coords.mainFieldWidthB, coords.rowHeight, getImages(), BGType.NEUTRAL, texture, false );
else
ETVUtils.drawDataBackground( offsetX + coords.rowOffset1, offsetY + 1 * ( coords.rowHeight + gap ), coords.mainFieldWidthB, coords.rowHeight, dataBackgroundColor.getColor(), texture, false );
// reference time field
if ( isEditorMode || ( referenceTimeAbs != null ) )
{
BGType bgType = BGType.NEUTRAL;
Color capBgColor = captionBackgroundColor.getColor();
Color dataBgColor = dataBackgroundColor.getColor();
if ( referencePlace == 1 )
{
bgType = BGType.POSITION_FIRST;
capBgColor = captionBackgroundColor1st.getColor();
dataBgColor = dataBackgroundColor1st.getColor();
}
if ( ( referenceTimeAbs != null ) && ( isEditorMode || ( lapState.getValue().isAfterSectorStart() && !vsi.isInPits() ) ) )
{
switch ( vsi.getSector() )
{
case 1:
if ( ( vsi.getLaptime( vsi.getLapsCompleted() ) == null ) || ( vsi.getLaptime( vsi.getLapsCompleted() ).isOutlap() == Boolean.TRUE ) )
{
}
else if ( vsi.getLastLapTime() < referenceTimeAbs.getLapTime() )
{
bgType = BGType.FASTEST;
dataBgColor = dataBackgroundColorFastest.getColor();
}
else if ( ( referenceTimePers != null ) && ( vsi.getLastLapTime() < referenceTimePers.getLapTime() ) )
{
bgType = BGType.FASTER;
dataBgColor = dataBackgroundColorFaster.getColor();
}
else
{
bgType = BGType.SLOWER;
dataBgColor = dataBackgroundColorSlower.getColor();
}
break;
case 2:
if ( vsi.getCurrentSector1() < referenceTimeAbs.getSector1() )
{
bgType = BGType.FASTEST;
dataBgColor = dataBackgroundColorFastest.getColor();
}
else if ( ( referenceTimePers != null ) && ( vsi.getCurrentSector1() < referenceTimePers.getSector1() ) )
{
bgType = BGType.FASTER;
dataBgColor = dataBackgroundColorFaster.getColor();
}
else
{
bgType = BGType.SLOWER;
dataBgColor = dataBackgroundColorSlower.getColor();
}
break;
case 3:
if ( vsi.getCurrentSector2( true ) < referenceTimeAbs.getSector2( true ) )
{
bgType = BGType.FASTEST;
dataBgColor = dataBackgroundColorFastest.getColor();
}
else if ( ( referenceTimePers != null ) && ( vsi.getCurrentSector2( true ) < referenceTimePers.getSector2( true ) ) )
{
bgType = BGType.FASTER;
dataBgColor = dataBackgroundColorFaster.getColor();
}
else
{
bgType = BGType.SLOWER;
dataBgColor = dataBackgroundColorSlower.getColor();
}
break;
}
}
if ( bgType != null )
{
if ( useImages )
ETVUtils.drawLabeledDataBackgroundI( offsetX, offsetY + 2 * ( coords.rowHeight + gap ), coords.mainFieldWidthA, coords.rowHeight, "00", getFontProperty(), getImages(), bgType, texture, false );
else
ETVUtils.drawLabeledDataBackground( offsetX, offsetY + 2 * ( coords.rowHeight + gap ), coords.mainFieldWidthA, coords.rowHeight, "00", getFontProperty(), capBgColor, dataBgColor, texture, false );
}
}
Color capBgColor = captionBackgroundColor.getColor();
boolean first = ( vsi.getPlace( getConfiguration().getUseClassScoring() ) == 1 );
if ( first )
capBgColor = captionBackgroundColor1st.getColor();
if ( useImages )
ETVUtils.drawBigPositionBackgroundI( offsetX + width - coords.bigPosWidth, offsetY, coords.bigPosWidth, coords.rowHeight + gap + coords.rowHeight, getImages(), first, texture, false );
else
ETVUtils.drawDataBackground( offsetX + width - coords.bigPosWidth, offsetY, coords.bigPosWidth, coords.rowHeight + gap + coords.rowHeight, capBgColor, texture, false );
}
@Override
public void drawWidget( Clock clock, boolean needsCompleteRedraw, LiveGameData gameData, boolean isEditorMode, TextureImage2D texture, int offsetX, int offsetY, int width, int height )
{
final ScoringInfo scoringInfo = gameData.getScoringInfo();
VehicleScoringInfo vsi = scoringInfo.getViewedVehicleScoringInfo();
vsiID.update( vsi.getDriverId() );
if ( needsCompleteRedraw || ( clock.c() && vsiID.hasChanged() ) )
{
drivernameString.draw( offsetX, offsetY, vsi.getDriverNameShort( getShowNamesInAllUppercase() ), texture );
}
//drivernameString.draw( offsetX, offsetY, lapState.getValue().SHORT, texture );
ownPlace.update( vsi.getPlace( getConfiguration().getUseClassScoring() ) );
if ( needsCompleteRedraw || ( clock.c() && ownPlace.hasChanged() ) )
{
bigPositionString.draw( offsetX, offsetY, ownPlace.getValueAsString(), captionColor.getColor(), texture );
}
LapState ls = lapState.getValue();
if ( isEditorMode && ( ( ls == LapState.SOMEWHERE ) || ls.isBeforeSectorEnd() ) )
{
ownLaptime.update( vsi.getCurrentLaptime() );
}
else if ( ls.isAfterSectorStart() )
{
switch ( vsi.getSector() )
{
case 1:
ownLaptime.update( vsi.getLastLapTime() );
break;
case 2:
ownLaptime.update( vsi.getCurrentSector1() );
break;
case 3:
ownLaptime.update( vsi.getCurrentSector2( true ) );
break;
}
}
else
{
ownLaptime.update( vsi.getCurrentLaptime() );
}
if ( needsCompleteRedraw || ( clock.c() && ownLaptime.hasChanged() ) )
{
if ( ownLaptime.isValid() )
{
if ( alwaysShowFull1000.getBooleanValue() || ls.isAfterSectorStart() )
laptimeString.draw( offsetX, offsetY, TimingUtil.getTimeAsLaptimeString( ownLaptime.getValue() ), texture );
else
laptimeString.draw( offsetX + laptimeOffsetX, offsetY, TimingUtil.getTimeAsString( ownLaptime.getValue(), false, false, true, false ), texture );
}
else
{
laptimeString.draw( offsetX, offsetY, "", texture );
}
}
//debug( "abs: ", referenceTimeAbs );
//debug( "pers: ", referenceTimePers );
if ( isEditorMode || ( referenceTimeAbs != null ) )
{
relPlace.update( referencePlace );
if ( needsCompleteRedraw || ( clock.c() && relPlace.hasChanged() ) )
{
relPositionString.draw( offsetX, offsetY, relPlace.getValueAsString(), texture );
}
Color dataColor = getFontColor();
if ( referenceTimeAbs == null )
{
relLaptime.update( relLaptime.getResetValue() );
}
else if ( ( ls == LapState.OUTLAP ) || ( ls == LapState.SOMEWHERE ) || ls.isBeforeSectorEnd() )
{
float lt = -1f;
switch ( vsi.getSector() )
{
case 1:
lt = referenceTimeAbs.getSector1();
break;
case 2:
lt = referenceTimeAbs.getSector2( true );
break;
case 3:
lt = referenceTimeAbs.getLapTime();
break;
}
if ( lt <= 0f )
lt = relLaptime.getResetValue();
relLaptime.update( lt );
}
else if ( ls.isAfterSectorStart() )
{
switch ( vsi.getSector() )
{
case 1:
relLaptime.update( vsi.getLastLapTime() - referenceTimeAbs.getLapTime() );
if ( vsi.getLastLapTime() < referenceTimeAbs.getLapTime() )
dataColor = dataColorFastest.getColor();
else if ( ( referenceTimePers != null ) && ( vsi.getLastLapTime() < referenceTimePers.getLapTime() ) )
dataColor = dataColorFaster.getColor();
else
dataColor = dataColorSlower.getColor();
break;
case 2:
relLaptime.update( vsi.getCurrentSector1() - referenceTimeAbs.getSector1() );
if ( vsi.getCurrentSector1() < referenceTimeAbs.getSector1() )
dataColor = dataColorFastest.getColor();
else if ( ( referenceTimePers != null ) && ( vsi.getCurrentSector1() < referenceTimePers.getSector1() ) )
dataColor = dataColorFaster.getColor();
else
dataColor = dataColorSlower.getColor();
break;
case 3:
relLaptime.update( vsi.getCurrentSector2( true ) - referenceTimeAbs.getSector2( true ) );
if ( vsi.getCurrentSector2( true ) < referenceTimeAbs.getSector2( true ) )
dataColor = dataColorFastest.getColor();
else if ( ( referenceTimePers != null ) && ( vsi.getCurrentSector2( true ) < referenceTimePers.getSector2( true ) ) )
dataColor = dataColorFaster.getColor();
else
dataColor = dataColorSlower.getColor();
break;
}
}
else
{
relLaptime.update( relLaptime.getResetValue() );
}
if ( needsCompleteRedraw || ( clock.c() && relLaptime.hasChanged() ) )
{
if ( relLaptime.isValid() && ownLaptime.isValid() )
{
if ( ls.isAfterSectorStart() )
relTimeString.draw( offsetX, offsetY, TimingUtil.getTimeAsGapString( relLaptime.getValue() ), dataColor, texture );
else if ( ( ls == LapState.OUTLAP ) || ( ls == LapState.SOMEWHERE ) || ls.isBeforeSectorEnd() )
relTimeString.draw( offsetX, offsetY, TimingUtil.getTimeAsLaptimeString( relLaptime.getValue() ), texture );
}
else
{
relTimeString.draw( offsetX, offsetY, "", texture );
}
}
}
}
}