/** * 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.standard.wear; import java.awt.Color; import java.awt.Font; import java.io.IOException; import net.ctdp.rfdynhud.gamedata.LiveGameData; import net.ctdp.rfdynhud.gamedata.ScoringInfo; import net.ctdp.rfdynhud.gamedata.TelemetryData; import net.ctdp.rfdynhud.gamedata.VehiclePhysics; import net.ctdp.rfdynhud.gamedata.VehiclePhysics.TireCompound.CompoundWheel; import net.ctdp.rfdynhud.gamedata.VehicleScoringInfo; import net.ctdp.rfdynhud.gamedata.VehicleSetup; import net.ctdp.rfdynhud.gamedata.Wheel; import net.ctdp.rfdynhud.properties.BooleanProperty; import net.ctdp.rfdynhud.properties.EnumProperty; import net.ctdp.rfdynhud.properties.FontProperty; import net.ctdp.rfdynhud.properties.ImagePropertyWithTexture; import net.ctdp.rfdynhud.properties.PropertiesContainer; import net.ctdp.rfdynhud.properties.PropertyLoader; import net.ctdp.rfdynhud.properties.Size; import net.ctdp.rfdynhud.render.ByteOrderManager; import net.ctdp.rfdynhud.render.DrawnString; import net.ctdp.rfdynhud.render.DrawnString.Alignment; import net.ctdp.rfdynhud.render.DrawnStringFactory; import net.ctdp.rfdynhud.render.ImageTemplate; import net.ctdp.rfdynhud.render.TextureImage2D; import net.ctdp.rfdynhud.util.NumberUtil; import net.ctdp.rfdynhud.util.PropertyWriter; import net.ctdp.rfdynhud.util.SubTextureCollector; import net.ctdp.rfdynhud.valuemanagers.Clock; import net.ctdp.rfdynhud.values.FloatValue; import net.ctdp.rfdynhud.widgets.base.widget.Widget; import net.ctdp.rfdynhud.widgets.standard._util.StandardWidgetSet; /** * The {@link WearWidget} displays wear information for the tires. * * @author Marvin Froehlich (CTDP) */ public class WearWidget extends Widget { private static enum HundredPercentBase { SAFE_RANGE, GOOD_RANGE, BAD_RANGE, MAX_RANGE, ; } private final FontProperty font2 = new FontProperty( "font2", StandardWidgetSet.SMALLER_FONT.getKey() ); private final Size engineHeight = Size.newLocalSize( this, 0f, true, 10.0f, true ); private final Size tireSize = Size.newLocalSize( this, 10.0f, true, 10.0f, true ); private final Size brakeSize = Size.newLocalSize( this, 7.0f, true, 20.0f, true ); private int gap = 7; private final BooleanProperty displayEngine = new BooleanProperty( "displayEngine", true ); private final BooleanProperty displayTires = new BooleanProperty( "displayTires", true ); private final BooleanProperty displayBrakes = new BooleanProperty( "displayBrakes", true ); private Boolean displayBrakes2 = null; private final EnumProperty<HundredPercentBase> hundredPercentBase = new EnumProperty<HundredPercentBase>( "hundredPercentBase", "100% base", HundredPercentBase.SAFE_RANGE ); private final ImagePropertyWithTexture estimationImageName = new ImagePropertyWithTexture( "engineEstimationImage", "estimationImage", "standard/start_finish.png", false, true ); private final ImagePropertyWithTexture failImageName = new ImagePropertyWithTexture( "engineFailImage", "failImage", "standard/explode.png", false, true ); private final ImagePropertyWithTexture tireIcon = new ImagePropertyWithTexture( "tireIcon", "icon", "standard/tire_small.png", false, true ); private final ImagePropertyWithTexture brakeDiscIcon = new ImagePropertyWithTexture( "brakeDiscIcon", "icon", "standard/brake_disc_small.png", false, true ); private int maxIconWidth = 0; private final BooleanProperty displayWearPercent = new BooleanProperty( "displayWearPercent", "display wear %", true ); private final BooleanProperty displayCompoundName = new BooleanProperty( "displayCompoundName", "display comp. name", true ); private DrawnString engineHeaderString = null; private DrawnString engineWearString = null; private int engineWearStringMaxWidth = 0; private DrawnString engineVarianceString = null; private final BooleanProperty swapTireWearGripMeaning = new BooleanProperty( "swapTireWearGripMeaning", "swapWearGrip", false ); private DrawnString tiresHeaderString = null; private DrawnString tireWearFLString = null; private DrawnString tireWearFRString = null; private DrawnString tireWearRLString = null; private DrawnString tireWearRRString = null; private DrawnString tireGripFLString = null; private DrawnString tireGripFRString = null; private DrawnString tireGripRLString = null; private DrawnString tireGripRRString = null; private DrawnString brakesHeaderString = null; private DrawnString brakeWearFLString = null; private DrawnString brakeWearFRString = null; private DrawnString brakeWearRLString = null; private DrawnString brakeWearRRString = null; private DrawnString brakeWearVarianceFLString = null; private DrawnString brakeWearVarianceFRString = null; private DrawnString brakeWearVarianceRLString = null; private DrawnString brakeWearVarianceRRString = null; private final FloatValue engineLifetime = new FloatValue( -1f, 0.001f ); private final FloatValue brakeDiscWearFL = new FloatValue( -1f, 0.001f ); private final FloatValue brakeDiscWearFR = new FloatValue( -1f, 0.001f ); private final FloatValue brakeDiscWearRL = new FloatValue( -1f, 0.001f ); private final FloatValue brakeDiscWearRR = new FloatValue( -1f, 0.001f ); private static final float gripGood = 0.95f; private static final float gripBad = 0.85f; private static final float wearGood = 0.5f; private static final float wearBad = 0.25f; private static final byte[] colorGood = new byte[ 4 ]; private static final byte[] colorOk = new byte[ 4 ]; private static final byte[] colorBad = new byte[ 4 ]; static { colorGood[ByteOrderManager.RED] = (byte)0; colorGood[ByteOrderManager.GREEN] = (byte)255; colorGood[ByteOrderManager.BLUE] = (byte)0; colorGood[ByteOrderManager.ALPHA] = (byte)255; colorOk[ByteOrderManager.RED] = (byte)255; colorOk[ByteOrderManager.GREEN] = (byte)255; colorOk[ByteOrderManager.BLUE] = (byte)0; colorOk[ByteOrderManager.ALPHA] = (byte)255; colorBad[ByteOrderManager.RED] = (byte)255; colorBad[ByteOrderManager.GREEN] = (byte)0; colorBad[ByteOrderManager.BLUE] = (byte)0; colorBad[ByteOrderManager.ALPHA] = (byte)255; } private static final Color YELLOW2 = new Color( 234, 190, 37 ); private static final Color GREEN2 = new Color( 152, 234, 13 ); private int[] oldTireWear = { -1, -1, -1, -1 }; private float engineLifetimeAtLapStart = -1f; private float engineLifetimeLossPerLap = -1f; private boolean testLifetimeAtLapStart = false; public WearWidget() { super( StandardWidgetSet.INSTANCE, StandardWidgetSet.WIDGET_PACKAGE_TELEMETRY, 17.8125f, 30.416667f ); } @Override public void prepareForMenuItem() { super.prepareForMenuItem(); gap = 1; getFontProperty().setFont( "Dialog", Font.PLAIN, 5, false, true ); } /** * {@inheritDoc} */ @Override public void saveProperties( PropertyWriter writer ) throws IOException { super.saveProperties( writer ); writer.writeProperty( font2, "The used (smaller) font." ); writer.writeProperty( displayEngine, "Display the engine part of the Widget?" ); writer.writeProperty( engineHeight.getHeightProperty( "engineHeight", "height" ), "The height of the engine bar." ); writer.writeProperty( hundredPercentBase, "The value range to be used as 100% base." ); writer.writeProperty( displayWearPercent, "Display wear in percentage numbers?" ); writer.writeProperty( estimationImageName, "Image to display where the engine is expected to explode." ); writer.writeProperty( failImageName, "Image to display, if the engine WILL fail before the end of the race." ); writer.writeProperty( displayTires, "Display the tire part of the Widget?" ); writer.writeProperty( displayCompoundName, "Display the tire compound name in the header?" ); writer.writeProperty( tireIcon, "The image name for the tire icon." ); writer.writeProperty( tireSize.getWidthProperty( "tireWidth", "width" ), "The width of a tire image." ); writer.writeProperty( tireSize.getHeightProperty( "tireHeight", "height" ), "The height of a tire image." ); writer.writeProperty( swapTireWearGripMeaning, "Swap bar and line display for wear and grip?" ); writer.writeProperty( displayBrakes, "Display the brakes of the Widget?" ); writer.writeProperty( brakeDiscIcon, "The image name for the brake disc icon." ); writer.writeProperty( brakeSize.getWidthProperty( "brakeWidth", "width" ), "The width of a brake image." ); writer.writeProperty( brakeSize.getHeightProperty( "brakeHeight", "height" ), "The height of a brake image." ); } /** * {@inheritDoc} */ @Override public void loadProperty( PropertyLoader loader ) { super.loadProperty( loader ); if ( loader.loadProperty( font2 ) ); else if ( loader.loadProperty( displayEngine ) ); else if ( loader.loadProperty( engineHeight.getHeightProperty( "engineHeight", "height" ) ) ); else if ( loader.loadProperty( hundredPercentBase ) ); else if ( loader.loadProperty( displayWearPercent ) ); else if ( loader.loadProperty( estimationImageName ) ); else if ( loader.loadProperty( failImageName ) ); else if ( loader.loadProperty( displayTires ) ); else if ( loader.loadProperty( displayCompoundName ) ); else if ( loader.loadProperty( tireIcon ) ); else if ( loader.loadProperty( tireSize.getWidthProperty( "tireWidth", "width" ) ) ); else if ( loader.loadProperty( tireSize.getHeightProperty( "tireHeight", "height" ) ) ); else if ( loader.loadProperty( swapTireWearGripMeaning ) ); else if ( loader.loadProperty( displayBrakes ) ); else if ( loader.loadProperty( brakeDiscIcon ) ); else if ( loader.loadProperty( brakeSize.getWidthProperty( "brakeWidth", "width" ) ) ); else if ( loader.loadProperty( brakeSize.getHeightProperty( "brakeHeight", "height" ) ) ); } /** * {@inheritDoc} */ @Override protected void addFontPropertiesToContainer( PropertiesContainer propsCont, boolean forceAll ) { super.addFontPropertiesToContainer( propsCont, forceAll ); propsCont.addProperty( font2 ); } /** * {@inheritDoc} */ @Override public void getProperties( PropertiesContainer propsCont, boolean forceAll ) { super.getProperties( propsCont, forceAll ); propsCont.addGroup( "Engine" ); propsCont.addProperty( displayEngine ); propsCont.addProperty( engineHeight.getHeightProperty( "engineHeight", "height" ) ); propsCont.addProperty( hundredPercentBase ); propsCont.addProperty( displayWearPercent ); propsCont.addProperty( estimationImageName ); propsCont.addProperty( failImageName ); propsCont.addGroup( "Tires" ); propsCont.addProperty( displayTires ); propsCont.addProperty( displayCompoundName ); propsCont.addProperty( tireIcon ); propsCont.addProperty( tireSize.getWidthProperty( "tireWidth", "width" ) ); propsCont.addProperty( tireSize.getHeightProperty( "tireHeight" ) ); propsCont.addProperty( swapTireWearGripMeaning ); propsCont.addGroup( "Brakes" ); propsCont.addProperty( displayBrakes ); propsCont.addProperty( brakeDiscIcon ); propsCont.addProperty( brakeSize.getWidthProperty( "brakeWidth", "width" ) ); propsCont.addProperty( brakeSize.getHeightProperty( "brakeHeight", "height" ) ); } @Override public int getNeededData() { return ( Widget.NEEDED_DATA_SCORING/* | Widget.NEEDED_DATA_SETUP*/ ); } public final boolean getDisplayWearPercent() { return ( displayWearPercent.getBooleanValue() ); } private final boolean getDisplayWearPercent_engine() { return ( getDisplayWearPercent() ); } private final boolean getDisplayWearPercent_tires() { return ( true ); } private final boolean getDisplayWearPercent_brakes() { return ( true ); } /** * {@inheritDoc} */ @Override public void onCockpitEntered( LiveGameData gameData, boolean isEditorMode ) { super.onCockpitEntered( gameData, isEditorMode ); for ( int i = 0; i < oldTireWear.length; i++ ) oldTireWear[i] = -1; engineLifetimeAtLapStart = -1f; testLifetimeAtLapStart = false; engineLifetimeLossPerLap = -1f; //forceReinitialization(); displayBrakes2 = null; } /** * {@inheritDoc} */ @Override public void onVehicleSetupUpdated( LiveGameData gameData, boolean isEditorMode ) { super.onVehicleSetupUpdated( gameData, isEditorMode ); forceCompleteRedraw( false ); forceReinitialization(); } /** * {@inheritDoc} */ @Override protected Boolean onVehicleControlChanged( VehicleScoringInfo viewedVSI, LiveGameData gameData, boolean isEditorMode ) { super.onVehicleControlChanged( viewedVSI, gameData, isEditorMode ); Boolean oldDisplayBrakes = displayBrakes2; displayBrakes2 = displayBrakes.getBooleanValue() && viewedVSI.getVehicleControl().isLocalPlayer(); if ( displayBrakes2 != oldDisplayBrakes ) { forceReinitialization(); forceCompleteRedraw( false ); } return ( viewedVSI.isPlayer() ); } private final float getHundredPercentBaseLifetime( VehiclePhysics.Engine engine, double raceLengthPercentage ) { switch ( this.hundredPercentBase.getEnumValue() ) { case SAFE_RANGE: default: return ( engine.getSafeLifetimeTotal( raceLengthPercentage ) ); case GOOD_RANGE: return ( engine.getGoodLifetimeTotal( raceLengthPercentage ) ); case BAD_RANGE: return ( engine.getBadLifetimeTotal( raceLengthPercentage ) ); case MAX_RANGE: return ( engine.getMaxLifetimeTotal( raceLengthPercentage ) ); } } private final float getEngineMinLifetimePercent( VehiclePhysics.Engine engine, double raceLengthPercentage, float hundredPercentBase ) { switch ( this.hundredPercentBase.getEnumValue() ) { case SAFE_RANGE: default: return ( ( engine.getMaxLifetimeTotal( raceLengthPercentage ) - engine.getSafeLifetimeTotal( raceLengthPercentage ) ) * 100f / hundredPercentBase ); case GOOD_RANGE: return ( ( engine.getMaxLifetimeTotal( raceLengthPercentage ) - engine.getGoodLifetimeTotal( raceLengthPercentage ) ) * 100f / hundredPercentBase ); case BAD_RANGE: return ( ( engine.getMaxLifetimeTotal( raceLengthPercentage ) - engine.getBadLifetimeTotal( raceLengthPercentage ) ) * 100f / hundredPercentBase ); case MAX_RANGE: return ( 0.0f ); } } /** * {@inheritDoc} */ @Override public void onLapStarted( VehicleScoringInfo vsi, LiveGameData gameData, boolean isEditorMode ) { super.onLapStarted( vsi, gameData, isEditorMode ); VehicleScoringInfo pvsi = gameData.getScoringInfo().getPlayersVehicleScoringInfo(); if ( vsi.equals( pvsi ) && ( vsi.getLapsCompleted() >= 1 ) ) { float engineLifetime = gameData.getTelemetryData().getEngineLifetime(); if ( !testLifetimeAtLapStart ) { engineLifetimeAtLapStart = engineLifetime; engineLifetimeLossPerLap = -1f; testLifetimeAtLapStart = true; } else if ( vsi.getLapsRemaining( gameData.getScoringInfo().getEstimatedMaxLaps( vsi ) ) > 0.1f ) { engineLifetimeLossPerLap = engineLifetimeAtLapStart - engineLifetime; engineLifetimeAtLapStart = engineLifetime; } } } private void loadEstimationImage( boolean isEditorMode, int height ) { if ( estimationImageName.isNoImage() ) return; ImageTemplate it = estimationImageName.getImage(); int width = Math.round( height * it.getBaseAspect() ); estimationImageName.updateSize( width, height, isEditorMode ); } private void loadFailImage( boolean isEditorMode, int height ) { if ( failImageName.isNoImage() ) return; ImageTemplate it = failImageName.getImage(); int width = Math.round( height * it.getBaseAspect() ); failImageName.updateSize( width, height, isEditorMode ); } private void loadTireImage( boolean isEditorMode, int height ) { if ( tireIcon.isNoImage() ) return; ImageTemplate it = tireIcon.getImage(); int width = Math.round( height * it.getBaseAspect() ); tireIcon.updateSize( width, height, isEditorMode ); } private void loadBrakeDiscImage( boolean isEditorMode, int height ) { if ( brakeDiscIcon.isNoImage() ) return; ImageTemplate it = brakeDiscIcon.getImage(); int width = Math.round( height * it.getBaseAspect() ); brakeDiscIcon.updateSize( width, height, isEditorMode ); } /** * {@inheritDoc} */ @Override protected void initSubTextures( LiveGameData gameData, boolean isEditorMode, int widgetInnerWidth, int widgetInnerHeight, SubTextureCollector collector ) { } /** * {@inheritDoc} */ @Override protected void initialize( LiveGameData gameData, boolean isEditorMode, DrawnStringFactory dsf, TextureImage2D texture, int width, int height ) { final java.awt.Font font = getFont(); final boolean fontAntiAliased = isFontAntiAliased(); final java.awt.Font font2 = this.font2.getFont(); final boolean font2AntiAliased = this.font2.isAntiAliased(); final java.awt.Color fontColor = getFontColor(); boolean db = ( displayBrakes2 == null ) ? displayBrakes.getBooleanValue() : displayBrakes2.booleanValue(); maxIconWidth = 0; if ( displayTires.getBooleanValue() && !tireIcon.isNoImage() ) { final int tireHeight = Math.min( tireSize.getEffectiveHeight(), brakeSize.getEffectiveHeight() ); int tiresHeight = tireHeight * 2 + gap; loadTireImage( isEditorMode, tiresHeight ); maxIconWidth = Math.max( maxIconWidth, tireIcon.getTexture().getWidth() ); } if ( db && !brakeDiscIcon.isNoImage() ) { final int brakeHeight = Math.min( tireSize.getEffectiveHeight(), brakeSize.getEffectiveHeight() ); int brakesHeight = brakeHeight * 2 + gap; loadBrakeDiscImage( isEditorMode, brakesHeight ); maxIconWidth = Math.max( maxIconWidth, brakeDiscIcon.getTexture().getWidth() ); } int left0 = 2; //int left = left0 + maxIconWidth; int center = maxIconWidth + ( width - maxIconWidth ) / 2; int top = -2; DrawnString relY = null; if ( displayEngine.getBooleanValue() ) { engineHeaderString = dsf.newDrawnString( "engineHeaderString", left0, top, Alignment.LEFT, false, font, fontAntiAliased, fontColor, Loc.engine_header_prefix + ": ", gameData.getVehicleInfo().getEngineName() ); if ( getDisplayWearPercent_engine() ) { engineWearString = dsf.newDrawnString( "engineWearString", null, engineHeaderString, width, 2, Alignment.RIGHT, false, font, fontAntiAliased, fontColor, null, "%" ); engineVarianceString = dsf.newDrawnString( "engineVarianceString", null, engineWearString, width, 2, Alignment.RIGHT, false, font2, font2AntiAliased, fontColor, "(", "%)" ); final double raceLengthPercentage = gameData.getScoringInfo().getRaceLengthPercentage(); float minLifetime = Math.round( getEngineMinLifetimePercent( gameData.getPhysics().getEngine(), raceLengthPercentage, getHundredPercentBaseLifetime( gameData.getPhysics().getEngine(), raceLengthPercentage ) ) * 10f ) / 10f; if ( gameData.getPhysics().getEngine().hasLifetimeVariance() ) engineWearStringMaxWidth = Math.max( engineWearString.getWidth( "-" + NumberUtil.formatFloat( minLifetime, 1, true ) + "%" ), engineWearString.getWidth( "100%" ) ); else engineWearStringMaxWidth = engineWearString.getWidth( "100.0%" ); } else { engineWearString = null; engineVarianceString = null; } relY = engineHeaderString; top = engineHeight.getEffectiveHeight() + 10; loadEstimationImage( isEditorMode, engineHeight.getEffectiveHeight() ); loadFailImage( isEditorMode, engineHeight.getEffectiveHeight() ); } if ( displayTires.getBooleanValue() ) { final int tireWidth = tireSize.getEffectiveWidth(); final int tireHeight = tireSize.getEffectiveHeight(); tiresHeaderString = dsf.newDrawnString( "tiresHeaderString", null, relY, left0, top, Alignment.LEFT, false, font, fontAntiAliased, fontColor, Loc.tires_header_prefix + ":", null ); final boolean dwpt = getDisplayWearPercent_tires(); { tireWearFLString = dsf.newDrawnStringIf( dwpt, "tireWearFLString", null, tiresHeaderString, center - gap - tireWidth, 3, Alignment.RIGHT, false, font, fontAntiAliased, fontColor, null, "%" ); tireWearFRString = dsf.newDrawnStringIf( dwpt, "tireWearFRString", null, tiresHeaderString, center + gap + tireWidth, 3, Alignment.LEFT, false, font, fontAntiAliased, fontColor, null, "%" ); tireWearRLString = dsf.newDrawnStringIf( dwpt, "tireWearRLString", null, tiresHeaderString, center - gap - tireWidth, 3 + tireHeight + gap, Alignment.RIGHT, false, font, fontAntiAliased, fontColor, null, "%" ); tireWearRRString = dsf.newDrawnStringIf( dwpt, "tireWearRRString", null, tiresHeaderString, center + gap + tireWidth, 3 + tireHeight + gap, Alignment.LEFT, false, font, fontAntiAliased, fontColor, null, "%" ); tireGripFLString = dsf.newDrawnStringIf( dwpt, "tireGripFLString", null, tiresHeaderString, center - gap - tireWidth, 3 + tireHeight - 2, Alignment.RIGHT, true, font2, font2AntiAliased, fontColor, "(", "%)" ); tireGripFRString = dsf.newDrawnStringIf( dwpt, "tireGripFRString", null, tiresHeaderString, center + gap + tireWidth, 3 + tireHeight - 2, Alignment.LEFT, true, font2, font2AntiAliased, fontColor, "(", "%)" ); tireGripRLString = dsf.newDrawnStringIf( dwpt, "tireGripRLString", null, tiresHeaderString, center - gap - tireWidth, 3 + tireHeight - 2 + tireHeight + gap, Alignment.RIGHT, true, font2, font2AntiAliased, fontColor, "(", "%)" ); tireGripRRString = dsf.newDrawnStringIf( dwpt, "tireGripRRString", null, tiresHeaderString, center + gap + tireWidth, 3 + tireHeight - 2 + tireHeight + gap, Alignment.LEFT, true, font2, font2AntiAliased, fontColor, "(", "%)" ); } relY = tiresHeaderString; int tiresHeight = tireHeight * 2 + 15; top = tiresHeight; } if ( db ) { final int tireWidth = tireSize.getEffectiveWidth(); final int brakeWidth = brakeSize.getEffectiveWidth(); final int brakeHeight = brakeSize.getEffectiveHeight(); brakesHeaderString = dsf.newDrawnString( "brakesHeaderString", null, relY, left0, top, Alignment.LEFT, false, font, fontAntiAliased, fontColor ); final boolean dwpb = getDisplayWearPercent_brakes(); { int imgWidth = displayTires.getBooleanValue() && db ? Math.max( tireWidth, brakeWidth ) : ( displayTires.getBooleanValue() ? tireWidth : brakeWidth ); brakeWearFLString = dsf.newDrawnStringIf( dwpb, "brakeWearFLString", null, brakesHeaderString, center - gap - imgWidth, 2, Alignment.RIGHT, false, font, fontAntiAliased, fontColor, null, "%" ); brakeWearFRString = dsf.newDrawnStringIf( dwpb, "brakeWearFRString", null, brakesHeaderString, center + gap + imgWidth, 2, Alignment.LEFT, false, font, fontAntiAliased, fontColor, null, "%" ); brakeWearRLString = dsf.newDrawnStringIf( dwpb, "brakeWearRLString", null, brakesHeaderString, center - gap - imgWidth, 2 + brakeHeight + gap, Alignment.RIGHT, false, font, fontAntiAliased, fontColor, null, "%" ); brakeWearRRString = dsf.newDrawnStringIf( dwpb, "brakeWearRRString", null, brakesHeaderString, center + gap + imgWidth, 2 + brakeHeight + gap, Alignment.LEFT, false, font, fontAntiAliased, fontColor, null, "%" ); brakeWearVarianceFLString = dsf.newDrawnStringIf( dwpb, "brakeWearVarianceFLString", null, brakeWearFLString, center - gap - imgWidth, 2, Alignment.RIGHT, false, font2, font2AntiAliased, fontColor, "(", "%)" ); brakeWearVarianceFRString = dsf.newDrawnStringIf( dwpb, "brakeWearVarianceFRString", null, brakeWearFRString, center + gap + imgWidth, 2, Alignment.LEFT, false, font2, font2AntiAliased, fontColor, "(", "%)" ); brakeWearVarianceRLString = dsf.newDrawnStringIf( dwpb, "brakeWearVarianceRLString", null, brakeWearRLString, center - gap - imgWidth, 2, Alignment.RIGHT, false, font2, font2AntiAliased, fontColor, "(", "%)" ); brakeWearVarianceRRString = dsf.newDrawnStringIf( dwpb, "brakeWearVarianceRRString", null, brakeWearRRString, center + gap + imgWidth, 2, Alignment.LEFT, false, font2, font2AntiAliased, fontColor, "(", "%)" ); } } } private static void interpolateColor( byte[] color0, byte[] color1, float alpha, byte[] result ) { final float beta = 1f - alpha; result[ByteOrderManager.RED] = (byte)( ( color0[ByteOrderManager.RED] & 0xFF ) * beta + ( color1[ByteOrderManager.RED] & 0xFF ) * alpha ); result[ByteOrderManager.GREEN] = (byte)( ( color0[ByteOrderManager.GREEN] & 0xFF ) * beta + ( color1[ByteOrderManager.GREEN] & 0xFF ) * alpha ); result[ByteOrderManager.BLUE] = (byte)( ( color0[ByteOrderManager.BLUE] & 0xFF ) * beta + ( color1[ByteOrderManager.BLUE] & 0xFF ) * alpha ); } private void drawEngine( ScoringInfo scoringInfo, boolean isEditorMode, float lifetime, double raceLengthMultiplier, VehiclePhysics.Engine engine, TextureImage2D texture, final int x, final int y, final int width ) { final int h = engineHeight.getEffectiveHeight(); texture.getTextureCanvas().pushClip( x, y, width, h, true ); try { final int lowerSafeLifetime = engine.getLowerSafeLifetimeValue( raceLengthMultiplier ); final int lowerGoodLifetime = engine.getLowerGoodLifetimeValue( raceLengthMultiplier ); final int lowerBadLifetime = engine.getLowerBadLifetimeValue( raceLengthMultiplier ); final int minLifetime = engine.getMinLifetimeValue( raceLengthMultiplier ); final int safeLifetimeTotal = engine.getSafeLifetimeTotal( raceLengthMultiplier ); final int goodLifetimeTotal = engine.getGoodLifetimeTotal( raceLengthMultiplier ); final int badLifetimeTotal = engine.getBadLifetimeTotal( raceLengthMultiplier ); final int maxLifetimeTotal = engine.getMaxLifetimeTotal( raceLengthMultiplier ); final boolean hasVariance = engine.hasLifetimeVariance(); if ( hasVariance ) { int x0 = x; int x1 = -1; int x2 = -1; int x3 = -1; if ( lifetime >= lowerBadLifetime ) { int w2 = ( lowerBadLifetime - minLifetime ) * width / maxLifetimeTotal; texture.clear( Color.RED, x0, y, w2, h, false, null ); x0 += w2; } else { int w2 = (int)( ( lifetime - minLifetime ) * width / maxLifetimeTotal ); texture.clear( Color.RED, x0, y, w2, h, false, null ); x0 += w2; x1 = x + ( maxLifetimeTotal - badLifetimeTotal ) * width / maxLifetimeTotal; } if ( lifetime >= lowerGoodLifetime ) { int w2 = ( lowerGoodLifetime - lowerBadLifetime ) * width / maxLifetimeTotal; texture.clear( YELLOW2, x0, y, w2, h, false, null ); x0 += w2; } else { if ( lifetime >= lowerBadLifetime ) { int w2 = (int)( ( lifetime - lowerBadLifetime ) * width / maxLifetimeTotal ); texture.clear( YELLOW2, x0, y, w2, h, false, null ); x0 += w2; } x2 = x + ( maxLifetimeTotal - goodLifetimeTotal ) * width / maxLifetimeTotal; } if ( lifetime >= lowerSafeLifetime ) { int w2 = ( lowerSafeLifetime - lowerGoodLifetime ) * width / maxLifetimeTotal; texture.clear( GREEN2, x0, y, w2, h, false, null ); x0 += w2; int w3 = (int)( ( lifetime - lowerSafeLifetime ) * width / maxLifetimeTotal ); texture.clear( Color.GREEN, x0, y, w3, h, false, null ); x0 += w3; } else { if ( lifetime >= lowerGoodLifetime ) { int w2 = (int)( ( lifetime - lowerGoodLifetime ) * width / maxLifetimeTotal ); texture.clear( GREEN2, x0, y, w2, h, false, null ); x0 += w2; } x3 = x + ( maxLifetimeTotal - safeLifetimeTotal ) * width / maxLifetimeTotal; } int w_ = width - x0 + x; if ( w_ > 0 ) { texture.clear( Color.BLACK, x0, y, w_, h, false, null ); } if ( x1 > 0 ) { texture.getTextureCanvas().setColor( Color.RED ); texture.getTextureCanvas().drawLine( x1, y, x1, y + h - 1 ); } if ( x2 > 0 ) { texture.getTextureCanvas().setColor( YELLOW2 ); texture.getTextureCanvas().drawLine( x2, y, x2, y + h - 1 ); } if ( x3 > 0 ) { texture.getTextureCanvas().setColor( GREEN2 ); texture.getTextureCanvas().drawLine( x3, y, x3, y + h - 1 ); } } else { int w2 = (int)( lifetime * width / maxLifetimeTotal ); texture.clear( Color.GREEN, x, y, w2, h, false, null ); int w3 = width - w2; if ( w3 > 0 ) texture.clear( Color.BLACK, x + w2, y, w3, h, false, null ); } if ( estimationImageName.hasTexture() || failImageName.hasTexture() ) { if ( isEditorMode ) { if ( estimationImageName.hasTexture() ) texture.drawImage( estimationImageName.getTexture(), x + 10, y, false, null ); else texture.drawImage( failImageName.getTexture(), x + 10, y, false, null ); } else if ( scoringInfo.getSessionType().isRace() && ( engineLifetimeLossPerLap > 0f ) ) { final int maxLaps = scoringInfo.getEstimatedMaxLaps( scoringInfo.getPlayersVehicleScoringInfo() ); if ( maxLaps > 0 ) { int lapsRemaining = Math.max( 0, (int)scoringInfo.getPlayersVehicleScoringInfo().getLapsRemaining( maxLaps ) ); int x2 = (int)( ( engineLifetimeAtLapStart - ( engineLifetimeLossPerLap * ( lapsRemaining + 1 ) ) + maxLifetimeTotal - safeLifetimeTotal ) * width / maxLifetimeTotal ); if ( ( x2 <= 0 ) && failImageName.hasTexture() ) texture.drawImage( failImageName.getTexture(), x, y, false, null ); else if ( estimationImageName.hasTexture() ) texture.drawImage( estimationImageName.getTexture(), x + x2 - ( estimationImageName.getTexture().getWidth() / 2 ), y, false, null ); } } } texture.markDirty( x, y, width, h, null ); } finally { texture.getTextureCanvas().popClip(); } } private void drawTire( float wear, float grip, CompoundWheel compoundWheel, TextureImage2D texture, int x, int y ) { int w = tireSize.getEffectiveWidth(); int h = tireSize.getEffectiveHeight(); float barValue; float barGood; float barBad; float lineValue; if ( swapTireWearGripMeaning.getBooleanValue() ) { barValue = wear; barGood = wearGood; barBad = wearBad; final float minGrip = compoundWheel.getMinGrip(); float barRange = 1.0f - minGrip; lineValue = ( grip - minGrip ) / barRange; } else { final float minGrip = compoundWheel.getMinGrip(); float barRange = 1.0f - minGrip; barValue = ( grip - minGrip ) / barRange; barGood = ( gripGood - minGrip ) / barRange; barBad = ( gripBad - minGrip ) / barRange; lineValue = wear; } byte[] color = new byte[4]; color[ByteOrderManager.ALPHA] = (byte)255; if ( barValue <= barBad ) { System.arraycopy( colorBad, 0, color, 0, 3 ); } else if ( barValue < barGood ) { float alpha = ( barValue - barBad ) / ( barGood - barBad ); interpolateColor( colorBad, colorOk, alpha, color ); } else { System.arraycopy( colorGood, 0, color, 0, 3 ); } Color awtColor = new Color( color[ByteOrderManager.RED] & 0xFF, color[ByteOrderManager.GREEN] & 0xFF, color[ByteOrderManager.BLUE] & 0xFF ); int barHeight = Math.min( (int)( h * barValue ), h ); if ( barValue > 0.0f ) { texture.clear( Color.BLACK, x, y, w, h - barHeight, false, null ); } texture.clear( awtColor, x, y + h - barHeight, w, barHeight, false, null ); byte[] pixels = new byte[ w * 4 ]; for ( int i = 0; i < w; i++ ) { System.arraycopy( colorBad, 0, pixels, i * 4, 4 ); } texture.clearPixelLine( pixels, x, y + h - (int)( h * lineValue ), w, false, null ); texture.markDirty( x, y, w, h, null ); } private void drawBrake( float discThickness, Wheel wheel, VehiclePhysics.Brakes.WheelBrake brake, VehicleSetup setup, TextureImage2D texture, int x, int y ) { int w = brakeSize.getEffectiveWidth(); int h = brakeSize.getEffectiveHeight(); final float minDiscFailure = brake.getMinDiscFailure(); final float maxDiscFailure = brake.getMaxDiscFailure(); final float startDiscThickness = setup.getWheelAndTire( wheel ).getBrakeDiscThickness(); final float variance = brake.getDiscFailureVarianceRange(); discThickness = Math.max( minDiscFailure, discThickness ); final float positiveRange = startDiscThickness - maxDiscFailure; final float maxRange = startDiscThickness - minDiscFailure; final boolean hasVariance = brake.hasDiscFailureVariance(); if ( hasVariance ) { if ( discThickness >= maxDiscFailure ) { int h2 = (int)( variance * h / maxRange ); texture.clear( Color.RED, x, y + h - h2, w, h2, false, null ); int h3 = (int)( ( discThickness - maxDiscFailure ) * ( h - h2 ) / positiveRange ); texture.clear( Color.GREEN, x, y + h - h2 - h3, w, h3, false, null ); int h4 = h - h3 - h2; if ( h4 > 0 ) texture.clear( Color.BLACK, x, y, w, h4, false, null ); } else { int h2 = (int)( ( discThickness - minDiscFailure ) * h / maxRange ); texture.clear( Color.RED, x, y + h - h2, w, h2, false, null ); int y2 = h - (int)( variance * h / maxRange ); int h3 = h - h2; if ( h3 > 0 ) texture.clear( Color.BLACK, x, y, w, h3, false, null ); texture.getTextureCanvas().setColor( Color.RED ); texture.getTextureCanvas().drawLine( x, y + y2, x + w - 1, y + y2 ); } } else { int h2 = (int)( ( discThickness - minDiscFailure ) * h / maxRange ); texture.clear( Color.GREEN, x, y + h - h2, w, h2, false, null ); int h3 = h - h2; if ( h3 > 0 ) texture.clear( Color.BLACK, x, y, w, h3, false, null ); } texture.markDirty( x, y, w, h, null ); } @Override protected void drawWidget( Clock clock, boolean needsCompleteRedraw, LiveGameData gameData, boolean isEditorMode, TextureImage2D texture, int offsetX, int offsetY, int width, int height ) { final boolean c = clock.c(); final VehiclePhysics physics = gameData.getPhysics(); final TelemetryData telemData = gameData.getTelemetryData(); final VehicleSetup setup = gameData.getSetup(); //final int center = width / 2; boolean db = ( displayBrakes2 == null ) ? displayBrakes.getBooleanValue() : displayBrakes2.booleanValue(); if ( needsCompleteRedraw ) { if ( displayEngine.getBooleanValue() ) engineHeaderString.draw( offsetX, offsetY, "", texture ); if ( displayTires.getBooleanValue() ) { // TODO: Try to find a way to detect tire compound change on pit stop! if ( displayCompoundName.getBooleanValue() ) tiresHeaderString.draw( offsetX, offsetY, " " + setup.getGeneral().getFrontTireCompound().getName(), texture ); else tiresHeaderString.draw( offsetX, offsetY, "", texture ); } if ( db ) brakesHeaderString.draw( offsetX, offsetY, Loc.brakes_header_prefix + ":", texture ); } if ( displayEngine.getBooleanValue() ) { final double raceLengthPercentage = gameData.getScoringInfo().getRaceLengthPercentage(); float lifetime = gameData.getTelemetryData().getEngineLifetime(); final int hundredPercentBase; switch ( this.hundredPercentBase.getEnumValue() ) { case SAFE_RANGE: default: hundredPercentBase = physics.getEngine().getSafeLifetimeTotal( raceLengthPercentage ); engineLifetime.update( lifetime / hundredPercentBase ); break; case GOOD_RANGE: hundredPercentBase = physics.getEngine().getGoodLifetimeTotal( raceLengthPercentage ); engineLifetime.update( ( lifetime + hundredPercentBase - physics.getEngine().getSafeLifetimeTotal( raceLengthPercentage ) ) / hundredPercentBase ); break; case BAD_RANGE: hundredPercentBase = physics.getEngine().getBadLifetimeTotal( raceLengthPercentage ); engineLifetime.update( ( lifetime + hundredPercentBase - physics.getEngine().getSafeLifetimeTotal( raceLengthPercentage ) ) / hundredPercentBase ); break; case MAX_RANGE: hundredPercentBase = physics.getEngine().getMaxLifetimeTotal( raceLengthPercentage ); engineLifetime.update( ( lifetime + hundredPercentBase - physics.getEngine().getSafeLifetimeTotal( raceLengthPercentage ) ) / hundredPercentBase ); break; } lifetime = Math.max( -physics.getEngine().getLifetimeVarianceRange( raceLengthPercentage ), lifetime ); if ( needsCompleteRedraw || engineLifetime.hasChanged() ) { int engineWidth; if ( getDisplayWearPercent_engine() ) { boolean hasVariance = physics.getEngine().hasLifetimeVariance(); float lt = engineLifetime.getValue() * 100f; if ( !hasVariance ) lt = Math.max( 0f, lt ); engineWearString.draw( offsetX, offsetY, NumberUtil.formatFloat( lt, 1, true ), texture ); if ( hasVariance ) { final float variancePercent = getEngineMinLifetimePercent( physics.getEngine(), raceLengthPercentage, hundredPercentBase ); engineVarianceString.draw( offsetX, offsetY, NumberUtil.formatFloat( -variancePercent, 1, true ), texture ); } //engineWidth = width - engineHeaderString.getAbsX() - 5 - Math.max( engineVarianceString.getLastWidth(), engineWearString.getLastWidth() ); engineWidth = width - engineHeaderString.getAbsX() - 0 - engineWearStringMaxWidth; } else { engineWidth = width - ( engineHeaderString.getAbsX() * 2 ); } drawEngine( gameData.getScoringInfo(), isEditorMode, lifetime, raceLengthPercentage, physics.getEngine(), texture, offsetX + engineHeaderString.getAbsX(), offsetY + engineHeaderString.getAbsY() + engineHeaderString.getMaxHeight( false ) + 3, engineWidth ); } } if ( displayTires.getBooleanValue() ) { final int tireWidth = tireSize.getEffectiveWidth(); int top = 0, left = 0; float tireWearFLf = telemData.getTireWear( Wheel.FRONT_LEFT ); int tireWearFL = Math.round( tireWearFLf * 100f ); if ( needsCompleteRedraw || ( c && ( tireWearFL != oldTireWear[0] ) ) ) { oldTireWear[0] = tireWearFL; CompoundWheel wheel = setup.getGeneral().getFrontTireCompound().getWheel( Wheel.FRONT_LEFT ); float gripf = wheel.getWearGripFactor( tireWearFLf ); int grip = Math.round( gripf * 100f ); top = 0; left = 0; if ( getDisplayWearPercent_tires() ) { String string = String.valueOf( tireWearFL ); tireWearFLString.draw( offsetX, offsetY, string, texture ); string = String.valueOf( grip ); tireGripFLString.draw( offsetX, offsetY, string, texture ); left = tireWearFLString.getAbsX() + 3; top = tireWearFLString.getAbsY() + 2; } drawTire( tireWearFLf, gripf, wheel, texture, offsetX + left, offsetY + top ); } if ( needsCompleteRedraw && tireIcon.hasTexture() ) { top += ( tireSize.getEffectiveHeight() * 2 + gap - tireIcon.getTexture().getHeight() ) / 2; texture.drawImage( tireIcon.getTexture(), offsetX, offsetY + top, true, null ); } float tireWearFRf = telemData.getTireWear( Wheel.FRONT_RIGHT ); int tireWearFR = Math.round( tireWearFRf * 100f ); if ( needsCompleteRedraw || ( c && ( tireWearFR != oldTireWear[1] ) ) ) { oldTireWear[1] = tireWearFR; CompoundWheel wheel = setup.getGeneral().getFrontTireCompound().getWheel( Wheel.FRONT_RIGHT ); float gripf = wheel.getWearGripFactor( tireWearFRf ); int grip = Math.round( gripf * 100f ); top = 0; left = 0; if ( getDisplayWearPercent_tires() ) { String string = String.valueOf( tireWearFR ); tireWearFRString.draw( offsetX, offsetY, string, texture ); string = String.valueOf( grip ); tireGripFRString.draw( offsetX, offsetY, string, texture ); left = tireWearFRString.getAbsX() - tireWidth - 3; top = tireWearFRString.getAbsY() + 2; } drawTire( tireWearFRf, gripf, wheel, texture, offsetX + left, offsetY + top ); } float tireWearRLf = telemData.getTireWear( Wheel.REAR_LEFT ); int tireWearRL = Math.round( tireWearRLf * 100f ); if ( needsCompleteRedraw || ( c && ( tireWearRL != oldTireWear[2] ) ) ) { oldTireWear[2] = tireWearRL; CompoundWheel wheel = setup.getGeneral().getRearTireCompound().getWheel( Wheel.REAR_LEFT ); float gripf = wheel.getWearGripFactor( tireWearRLf ); int grip = Math.round( gripf * 100f ); top = 0; left = 0; if ( getDisplayWearPercent_tires() ) { String string = String.valueOf( tireWearRL ); tireWearRLString.draw( offsetX, offsetY, string, texture ); string = String.valueOf( grip ); tireGripRLString.draw( offsetX, offsetY, string, texture ); left = tireWearRLString.getAbsX() + 3; top = tireWearRLString.getAbsY() + 2; } drawTire( tireWearRLf, gripf, wheel, texture, offsetX + left, offsetY + top ); } float tireWearRRf = telemData.getTireWear( Wheel.REAR_RIGHT ); int tireWearRR = Math.round( tireWearRRf * 100f ); if ( needsCompleteRedraw || ( c && ( tireWearRR != oldTireWear[3] ) ) ) { oldTireWear[3] = tireWearRR; CompoundWheel wheel = setup.getGeneral().getRearTireCompound().getWheel( Wheel.REAR_RIGHT ); float gripf = wheel.getWearGripFactor( tireWearRRf ); int grip = Math.round( gripf * 100f ); top = 0; left = 0; if ( getDisplayWearPercent_tires() ) { String string = String.valueOf( tireWearRR ); tireWearRRString.draw( offsetX, offsetY, string, texture ); string = String.valueOf( grip ); tireGripRRString.draw( offsetX, offsetY, string, texture ); left = tireWearRRString.getAbsX() - tireWidth - 3; top = tireWearRRString.getAbsY() + 2; } drawTire( tireWearRRf, gripf, wheel, texture, offsetX + left, offsetY + top ); } } if ( db ) { final int brakeWidth = brakeSize.getEffectiveWidth(); int left = 0, top = 0; Wheel wheel = Wheel.FRONT_LEFT; VehiclePhysics.Brakes.WheelBrake brake = physics.getBrakes().getBrake( wheel ); float brakeDiscThickness = telemData.getBrakeDiscThickness( wheel ); brakeDiscWearFL.update( ( brakeDiscThickness - brake.getMaxDiscFailure() ) / ( setup.getWheelAndTire( wheel ).getBrakeDiscThickness() - brake.getMaxDiscFailure() ) ); if ( needsCompleteRedraw || ( c && brakeDiscWearFL.hasChanged( false ) ) ) { brakeDiscWearFL.setUnchanged(); left = 0; top = 0; if ( getDisplayWearPercent_brakes() ) { String string = NumberUtil.formatFloat( brakeDiscWearFL.getValue() * 100f, 1, true ); brakeWearFLString.draw( offsetX, offsetY, string, texture ); if ( brake.hasDiscFailureVariance() ) { float variancePercent = brake.getDiscFailureVarianceRange() * 100f / ( setup.getWheelAndTire( wheel ).getBrakeDiscThickness() - brake.getMaxDiscFailure() ); brakeWearVarianceFLString.draw( offsetX, offsetY, NumberUtil.formatFloat( -variancePercent, 1, true ), texture ); } left = brakeWearFLString.getAbsX() + 3; top = brakeWearFLString.getAbsY(); } drawBrake( brakeDiscThickness, wheel, brake, setup, texture, offsetX + left, offsetY + top ); } if ( needsCompleteRedraw && brakeDiscIcon.hasTexture() ) { top += ( brakeSize.getEffectiveHeight() * 2 + gap - brakeDiscIcon.getTexture().getHeight() ) / 2; texture.drawImage( brakeDiscIcon.getTexture(), offsetX, offsetY + top, true, null ); } wheel = Wheel.FRONT_RIGHT; brake = physics.getBrakes().getBrake( wheel ); brakeDiscThickness = telemData.getBrakeDiscThickness( wheel ); brakeDiscWearFR.update( ( brakeDiscThickness - brake.getMaxDiscFailure() ) / ( setup.getWheelAndTire( wheel ).getBrakeDiscThickness() - brake.getMaxDiscFailure() ) ); if ( needsCompleteRedraw || ( c && brakeDiscWearFR.hasChanged( false ) ) ) { brakeDiscWearFR.setUnchanged(); left = 0; top = 0; if ( getDisplayWearPercent_brakes() ) { String string = NumberUtil.formatFloat( brakeDiscWearFR.getValue() * 100f, 1, true ); brakeWearFRString.draw( offsetX, offsetY, string, texture ); if ( brake.hasDiscFailureVariance() ) { float variancePercent = brake.getDiscFailureVarianceRange() * 100f / ( setup.getWheelAndTire( wheel ).getBrakeDiscThickness() - brake.getMaxDiscFailure() ); brakeWearVarianceFRString.draw( offsetX, offsetY, NumberUtil.formatFloat( -variancePercent, 1, true ), texture ); } left = brakeWearFRString.getAbsX() - brakeWidth - 3; top = brakeWearFRString.getAbsY(); } drawBrake( brakeDiscThickness, wheel, brake, setup, texture, offsetX + left, offsetY + top ); } wheel = Wheel.REAR_LEFT; brake = physics.getBrakes().getBrake( wheel ); brakeDiscThickness = telemData.getBrakeDiscThickness( wheel ); brakeDiscWearRL.update( ( brakeDiscThickness - brake.getMaxDiscFailure() ) / ( setup.getWheelAndTire( wheel ).getBrakeDiscThickness() - brake.getMaxDiscFailure() ) ); if ( needsCompleteRedraw || ( c && brakeDiscWearRL.hasChanged( false ) ) ) { brakeDiscWearRL.setUnchanged(); left = 0; top = 0; if ( getDisplayWearPercent_brakes() ) { String string = NumberUtil.formatFloat( brakeDiscWearRL.getValue() * 100f, 1, true ); brakeWearRLString.draw( offsetX, offsetY, string, texture ); if ( brake.hasDiscFailureVariance() ) { float variancePercent = brake.getDiscFailureVarianceRange() * 100f / ( setup.getWheelAndTire( wheel ).getBrakeDiscThickness() - brake.getMaxDiscFailure() ); brakeWearVarianceRLString.draw( offsetX, offsetY, NumberUtil.formatFloat( -variancePercent, 1, true ), texture ); } left = brakeWearRLString.getAbsX() + 3; top = brakeWearRLString.getAbsY(); } drawBrake( brakeDiscThickness, wheel, brake, setup, texture, offsetX + left, offsetY + top ); } wheel = Wheel.REAR_RIGHT; brake = physics.getBrakes().getBrake( wheel ); brakeDiscThickness = telemData.getBrakeDiscThickness( wheel ); brakeDiscWearRR.update( ( brakeDiscThickness - brake.getMaxDiscFailure() ) / ( setup.getWheelAndTire( wheel ).getBrakeDiscThickness() - brake.getMaxDiscFailure() ) ); if ( needsCompleteRedraw || ( c && brakeDiscWearRR.hasChanged( false ) ) ) { brakeDiscWearRR.setUnchanged(); left = 0; top = 0; if ( getDisplayWearPercent_brakes() ) { String string = NumberUtil.formatFloat( brakeDiscWearRR.getValue() * 100f, 1, true ); brakeWearRRString.draw( offsetX, offsetY, string, texture ); if ( brake.hasDiscFailureVariance() ) { float variancePercent = brake.getDiscFailureVarianceRange() * 100f / ( setup.getWheelAndTire( wheel ).getBrakeDiscThickness() - brake.getMaxDiscFailure() ); brakeWearVarianceRRString.draw( offsetX, offsetY, NumberUtil.formatFloat( -variancePercent, 1, true ), texture ); } left = brakeWearRRString.getAbsX() - brakeWidth - 3; top = brakeWearRRString.getAbsY(); } drawBrake( brakeDiscThickness, wheel, brake, setup, texture, offsetX + left, offsetY + top ); } } } }