/** * 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.revmeter; import java.awt.Color; import java.awt.FontMetrics; import java.awt.Polygon; import java.awt.Shape; import java.awt.geom.Area; import java.awt.geom.Rectangle2D; import java.io.IOException; import net.ctdp.rfdynhud.gamedata.LiveGameData; import net.ctdp.rfdynhud.gamedata.TelemetryData; import net.ctdp.rfdynhud.gamedata.VehiclePhysics; import net.ctdp.rfdynhud.gamedata.VehicleScoringInfo; import net.ctdp.rfdynhud.properties.AbstractPropertiesKeeper; import net.ctdp.rfdynhud.properties.BackgroundProperty; import net.ctdp.rfdynhud.properties.BooleanProperty; import net.ctdp.rfdynhud.properties.ColorProperty; import net.ctdp.rfdynhud.properties.FontProperty; import net.ctdp.rfdynhud.properties.GenericPropertiesIterator; import net.ctdp.rfdynhud.properties.ImageProperty; import net.ctdp.rfdynhud.properties.IntProperty; import net.ctdp.rfdynhud.properties.PropertiesContainer; import net.ctdp.rfdynhud.properties.Property; import net.ctdp.rfdynhud.properties.PropertyLoader; import net.ctdp.rfdynhud.properties.StringProperty; 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.Texture2DCanvas; import net.ctdp.rfdynhud.render.TextureImage2D; import net.ctdp.rfdynhud.render.TransformableTexture; 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.IntValue; import net.ctdp.rfdynhud.widgets.base.revneedlemeter.AbstractRevNeedleMeterWidget; import net.ctdp.rfdynhud.widgets.base.widget.Widget; import net.ctdp.rfdynhud.widgets.base.widget.WidgetPackage; import net.ctdp.rfdynhud.widgets.base.widget.WidgetSet; import net.ctdp.rfdynhud.widgets.standard._util.StandardWidgetSet; /** * The {@link RevMeterWidget} displays rev/RPM information. * * @author Marvin Froehlich (CTDP) */ public class RevMeterWidget extends AbstractRevNeedleMeterWidget { @Override protected int getMarkersBigStepLowerLimit() { return ( 300 ); } @Override protected int getMarkersSmallStepLowerLimit() { return ( 20 ); } protected final ColorProperty revMarkersMediumColor = new ColorProperty( "revMarkersMediumColor", "mediumColor", "#FFFF00" ); protected final ColorProperty revMarkersHighColor = new ColorProperty( "revMarkersHighColor", "highColor", "#FF0000" ); protected final BooleanProperty fillHighBackground = new BooleanProperty( "fillHighBackground", false ); protected final BooleanProperty interpolateMarkerColors = new BooleanProperty( "interpolateMarkerColors", "interpolateColors", false ); private void initShiftLights( int oldNumber, int newNumber ) { for ( int i = oldNumber; i < newNumber; i++ ) { if ( shiftLights[i] == null ) { shiftLights[i] = new ShiftLight( this, i + 1 ); GenericPropertiesIterator it = new GenericPropertiesIterator( shiftLights[i] ); while ( it.hasNext() ) AbstractPropertiesKeeper.setKeeper( it.next(), this ); if ( ( i == 0 ) && ( oldNumber == 0 ) && ( newNumber == 1 ) ) { shiftLights[0].activationRPM.setValue( -500 ); } else if ( ( i == 0 ) && ( oldNumber == 0 ) && ( newNumber == 2 ) ) { shiftLights[0].activationRPM.setValue( -200 ); } else if ( ( i == 1 ) && ( oldNumber < 2 ) && ( newNumber == 2 ) ) { shiftLights[1].activationRPM.setValue( -600 ); } } } } private final IntProperty numShiftLights = new IntProperty( "numShiftLights", 0, 0, 20 ) { @Override protected void onValueChanged( Integer oldValue, int newValue ) { if ( oldValue == null ) oldValue = 0; //if ( oldValue != null ) initShiftLights( oldValue, newValue ); } }; private final ShiftLight[] shiftLights = { null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null }; protected final BooleanProperty displayBoostBar = new BooleanProperty( "displayBoostBar", "displayBar", true ); protected final IntProperty boostBarPosX = new IntProperty( "boostBarPosX", "barPosX", 135 ); protected final IntProperty boostBarPosY = new IntProperty( "boostBarPosY", "barPosY", 671 ); protected final IntProperty boostBarWidth = new IntProperty( "boostBarWidth", "barWidth", 438 ); protected final IntProperty boostBarHeight = new IntProperty( "boostBarHeight", "barHeight", 27 ); protected final BooleanProperty displayBoostNumber = new BooleanProperty( "displayBoostNumber", "displayNumber", true ); protected final ImageProperty boostNumberBackgroundImageName = new ImageProperty( "boostNumberBackgroundImageName", "numBGImageName", "", false, true ); private TransformableTexture boostNumberBackgroundTexture = null; private TextureImage2D boostNumberBackgroundTexture_bak = null; private int boostNumberBackgroundTexPosX, boostNumberBackgroundTexPosY; protected final IntProperty boostNumberPosX = new IntProperty( "boostNumberPosX", "numberPosX", 392 ); protected final IntProperty boostNumberPosY = new IntProperty( "boostNumberPosY", "numberPosY", 544 ); protected final FontProperty boostNumberFont = new FontProperty( "boostNumberFont", "numberFont", FontProperty.STANDARD_FONT.getKey() ); protected final ColorProperty boostNumberFontColor = new ColorProperty( "boostNumberFontColor", "numberFontColor", "#FF0000" ); protected final BooleanProperty displayRPMString1 = new BooleanProperty( "displayRPMString1", "displayRPMString", true ); protected final BooleanProperty displayCurrRPM1 = new BooleanProperty( "displayCurrRPM1", "displayCurrRPM", true ); protected final BooleanProperty displayMaxRPM1 = new BooleanProperty( "displayMaxRPM1", "displayMaxRPM", true ); protected final BooleanProperty useBoostRevLimit1 = new BooleanProperty( "useBoostRevLimit1", "useBoostRevLimit", false ); protected final IntProperty rpmPosX1 = new IntProperty( "rpmPosX1", "RPMPosX", 170 ); protected final IntProperty rpmPosY1 = new IntProperty( "rpmPosY1", "RPMPosY", 603 ); protected final FontProperty rpmFont1 = new FontProperty( "rpmFont1", "font", FontProperty.STANDARD_FONT.getKey() ); protected final ColorProperty rpmFontColor1 = new ColorProperty( "rpmFontColor1", "fontColor", ColorProperty.STANDARD_FONT_COLOR.getKey() ); protected final StringProperty rpmJoinString1 = new StringProperty( "rpmJoinString1", "RPMJoinString", " / " ); protected final BooleanProperty displayRPMString2 = new BooleanProperty( "displayRPMString2", "displayRPMString", false ); protected final BooleanProperty displayCurrRPM2 = new BooleanProperty( "displayCurrRPM2", "displayCurrRPM", true ); protected final BooleanProperty displayMaxRPM2 = new BooleanProperty( "displayMaxRPM2", "displayMaxRPM", true ); protected final BooleanProperty useBoostRevLimit2 = new BooleanProperty( "useBoostRevLimit2", "useBoostRevLimit", false ); protected final IntProperty rpmPosX2 = new IntProperty( "rpmPosX2", "RPMPosX", 170 ); protected final IntProperty rpmPosY2 = new IntProperty( "rpmPosY2", "RPMPosY", 603 ); protected final FontProperty rpmFont2 = new FontProperty( "rpmFont2", "font", FontProperty.STANDARD_FONT.getKey() ); protected final ColorProperty rpmFontColor2 = new ColorProperty( "rpmFontColor2", "fontColor", ColorProperty.STANDARD_FONT_COLOR.getKey() ); protected final StringProperty rpmJoinString2 = new StringProperty( "rpmJoinString2", "RPMJoinString", " / " ); private DrawnString rpmString1 = null; private DrawnString rpmString2 = null; private DrawnString boostString = null; private final IntValue boost = new IntValue(); public RevMeterWidget( WidgetSet widgetSet, WidgetPackage widgetPackage, float width, float height ) { super( widgetSet, widgetPackage, width, height ); int initialShiftLights = getInitialNumberOfShiftLights(); if ( initialShiftLights != 0 ) { numShiftLights.setIntValue( initialShiftLights ); initShiftLights( 0, numShiftLights.getIntValue() ); } } public RevMeterWidget() { this( StandardWidgetSet.INSTANCE, StandardWidgetSet.WIDGET_PACKAGE, 16.3125f, 21.75f ); } protected int getInitialNumberOfShiftLights() { return ( 2 ); } @Override protected void saveMarkersProperties( PropertyWriter writer ) throws IOException { super.saveMarkersProperties( writer ); writer.writeProperty( revMarkersMediumColor, "The color used to draw the rev markers for medium boost." ); writer.writeProperty( revMarkersHighColor, "The color used to draw the rev markers for high revs." ); writer.writeProperty( fillHighBackground, "Fill the rev markers' background with medium and high color instead of coloring the markers." ); writer.writeProperty( interpolateMarkerColors, "Interpolate medium and high colors." ); } protected void saveShiftLightsProperties( PropertyWriter writer ) throws IOException { writer.writeProperty( numShiftLights, "The number of shift lights to render." ); for ( int i = 0; i < numShiftLights.getIntValue(); i++ ) shiftLights[i].saveProperties( writer ); } protected void saveBoostProperties( PropertyWriter writer ) throws IOException { writer.writeProperty( displayBoostBar, "Display a graphical bar for engine boost mapping?" ); writer.writeProperty( boostBarPosX, "The x-position of the boost bar." ); writer.writeProperty( boostBarPosY, "The y-position of the boost bar." ); writer.writeProperty( boostBarWidth, "The width of the boost bar." ); writer.writeProperty( boostBarHeight, "The height of the boost bar." ); writer.writeProperty( displayBoostNumber, "Display a number for engine boost mapping?" ); writer.writeProperty( boostNumberBackgroundImageName, "The name of the image to render behind the boost number." ); writer.writeProperty( boostNumberPosX, "The x-position of the boost number." ); writer.writeProperty( boostNumberPosY, "The y-position of the boost number." ); writer.writeProperty( boostNumberFont, "The font used to draw the boost number." ); writer.writeProperty( boostNumberFontColor, "The font color used to draw the boost bar." ); } protected void saveDigiRevsProperties( PropertyWriter writer ) throws IOException { writer.writeProperty( displayRPMString1, "whether to display the digital RPM/Revs string or not" ); writer.writeProperty( displayCurrRPM1, "whether to display the current revs or to hide them" ); writer.writeProperty( displayMaxRPM1, "whether to display the maximum revs or to hide them" ); writer.writeProperty( useBoostRevLimit1, "whether to use boost level to display max RPM" ); writer.writeProperty( rpmPosX1, "The offset in (background image space) pixels from the right of the Widget, where the text is to be placed." ); writer.writeProperty( rpmPosY1, "The offset in (background image space) pixels from the top of the Widget, where the text is to be placed." ); writer.writeProperty( rpmFont1, "The font used to draw the RPM." ); writer.writeProperty( rpmFontColor1, "The font color used to draw the RPM." ); writer.writeProperty( rpmJoinString1, "The String to use to join the current and max RPM." ); writer.writeProperty( displayRPMString2, "whether to display the digital RPM/Revs string or not" ); writer.writeProperty( displayCurrRPM2, "whether to display the current revs or to hide them" ); writer.writeProperty( displayMaxRPM2, "whether to display the maximum revs or to hide them" ); writer.writeProperty( useBoostRevLimit2, "whether to use boost level to display max RPM" ); writer.writeProperty( rpmPosX2, "The offset in (background image space) pixels from the right of the Widget, where the text is to be placed." ); writer.writeProperty( rpmPosY2, "The offset in (background image space) pixels from the top of the Widget, where the text is to be placed." ); writer.writeProperty( rpmFont2, "The font used to draw the RPM." ); writer.writeProperty( rpmFontColor2, "The font color used to draw the RPM." ); writer.writeProperty( rpmJoinString2, "The String to use to join the current and max RPM." ); } /** * {@inheritDoc} */ @Override public void saveProperties( PropertyWriter writer ) throws IOException { super.saveProperties( writer ); saveShiftLightsProperties( writer ); saveBoostProperties( writer ); saveDigiRevsProperties( writer ); } private boolean loadShiftLightProperty( PropertyLoader loader ) { for ( int i = 0; i < numShiftLights.getIntValue(); i++ ) if ( shiftLights[i].loadProperty( loader ) ) return ( true ); return ( false ); } private void handleBackwardsCompatiblity( PropertyLoader loader ) { if ( loader.getCurrentKey().equals( "displayRevMarkers" ) ) displayMarkers.loadValue( loader, loader.getCurrentValue() ); else if ( loader.getCurrentKey().equals( "displayRevMarkerNumbers" ) ) displayMarkerNumbers.loadValue( loader, loader.getCurrentValue() ); else if ( loader.getCurrentKey().equals( "revMarkersInnerRadius" ) ) markersInnerRadius.loadValue( loader, loader.getCurrentValue() ); else if ( loader.getCurrentKey().equals( "revMarkersLength" ) ) markersLength.loadValue( loader, loader.getCurrentValue() ); else if ( loader.getCurrentKey().equals( "revMarkersBigStep" ) ) markersBigStep.loadValue( loader, loader.getCurrentValue() ); else if ( loader.getCurrentKey().equals( "revMarkersSmallStep" ) ) markersSmallStep.loadValue( loader, loader.getCurrentValue() ); else if ( loader.getCurrentKey().equals( "revMarkersColor" ) ) markersColor.loadValue( loader, loader.getCurrentValue() ); else if ( loader.getCurrentKey().equals( "revMarkersFont" ) ) markersFont.loadValue( loader, loader.getCurrentValue() ); else if ( loader.getCurrentKey().equals( "revMarkersFontColor" ) ) markersFontColor.loadValue( loader, loader.getCurrentValue() ); else if ( loader.getCurrentKey().equals( "displayVelocity" ) ) displayValue.loadValue( loader, loader.getCurrentValue() ); else if ( loader.getCurrentKey().equals( "velocityBackgroundImageName" ) ) valueBackgroundImageName.loadValue( loader, loader.getCurrentValue() ); else if ( loader.getCurrentKey().equals( "velocityPosX" ) ) valuePosX.loadValue( loader, loader.getCurrentValue() ); else if ( loader.getCurrentKey().equals( "velocityPosY" ) ) valuePosY.loadValue( loader, loader.getCurrentValue() ); else if ( loader.getCurrentKey().equals( "velocityFont" ) ) valueFont.loadValue( loader, loader.getCurrentValue() ); else if ( loader.getCurrentKey().equals( "velocityFontColor" ) ) valueFontColor.loadValue( loader, loader.getCurrentValue() ); else if ( loader.getCurrentKey().equals( "needleAxisBottomOffset" ) ) needlePivotBottomOffset.loadValue( loader, loader.getCurrentValue() ); else if ( loader.getCurrentKey().equals( "rotationForZeroRPM" ) ) { needleRotationForMinValue.loadValue( loader, loader.getCurrentValue() ); needleRotationForMinValue.setFloatValue( -needleRotationForMinValue.getFloatValue() ); } else if ( loader.getCurrentKey().equals( "rotationForMaxRPM" ) ) { needleRotationForMaxValue.loadValue( loader, loader.getCurrentValue() ); needleRotationForMaxValue.setFloatValue( -needleRotationForMaxValue.getFloatValue() ); } } /** * {@inheritDoc} */ @Override public void loadProperty( PropertyLoader loader ) { super.loadProperty( loader ); if ( loader.getSourceVersion().getBuild() < 70 ) { handleBackwardsCompatiblity( loader ); } if ( loader.loadProperty( numShiftLights ) ) { for ( int i = 0; i < numShiftLights.getIntValue(); i++ ) shiftLights[i] = new ShiftLight( this, i + 1 ); } else if ( loadShiftLightProperty( loader ) ); else if ( loader.loadProperty( revMarkersMediumColor ) ); else if ( loader.loadProperty( revMarkersHighColor ) ); else if ( loader.loadProperty( fillHighBackground ) ); else if ( loader.loadProperty( interpolateMarkerColors ) ); else if ( loader.loadProperty( displayBoostBar ) ); else if ( loader.loadProperty( boostBarPosX ) ); else if ( loader.loadProperty( boostBarPosY ) ); else if ( loader.loadProperty( boostBarWidth ) ); else if ( loader.loadProperty( boostBarHeight ) ); else if ( loader.loadProperty( displayBoostNumber ) ); else if ( loader.loadProperty( boostNumberBackgroundImageName ) ); else if ( loader.loadProperty( boostNumberPosX ) ); else if ( loader.loadProperty( boostNumberPosY ) ); else if ( loader.loadProperty( boostNumberFont ) ); else if ( loader.loadProperty( boostNumberFontColor ) ); else if ( loader.loadProperty( displayRPMString1 ) ); else if ( loader.loadProperty( displayCurrRPM1 ) ); else if ( loader.loadProperty( displayMaxRPM1 ) ); else if ( loader.loadProperty( useBoostRevLimit1 ) ); else if ( loader.loadProperty( rpmPosX1 ) ); else if ( loader.loadProperty( rpmPosY1 ) ); else if ( loader.loadProperty( rpmFont1 ) ); else if ( loader.loadProperty( rpmFontColor1 ) ); else if ( loader.loadProperty( rpmJoinString1 ) ); else if ( loader.loadProperty( displayRPMString2 ) ); else if ( loader.loadProperty( displayCurrRPM2 ) ); else if ( loader.loadProperty( displayMaxRPM2 ) ); else if ( loader.loadProperty( useBoostRevLimit2 ) ); else if ( loader.loadProperty( rpmPosX2 ) ); else if ( loader.loadProperty( rpmPosY2 ) ); else if ( loader.loadProperty( rpmFont2 ) ); else if ( loader.loadProperty( rpmFontColor2 ) ); else if ( loader.loadProperty( rpmJoinString2 ) ); if ( ( loader.getSourceVersion().getBuild() < 70 ) && loader.getCurrentKey().equals( "/" ) ) { // Properties loading has finished for this Widget and the sourceversion is outdated. if ( this.getNeedleImage() == null ) peakNeedleImageName.setImageName( "" ); } } /** * {@inheritDoc} */ @Override protected void getMarkersProperties( PropertiesContainer propsCont, boolean forceAll ) { super.getMarkersProperties( propsCont, forceAll ); propsCont.addProperty( revMarkersMediumColor ); propsCont.addProperty( revMarkersHighColor ); propsCont.addProperty( fillHighBackground ); propsCont.addProperty( interpolateMarkerColors ); } /** * Collects the properties for the shift lights. * * @param propsCont the container to add the properties to * @param forceAll If <code>true</code>, all properties provided by this {@link Widget} must be added. * If <code>false</code>, only the properties, that are relevant for the current {@link Widget}'s situation have to be added, some can be ignored. */ protected void getShiftLightsProperties( PropertiesContainer propsCont, boolean forceAll ) { propsCont.addGroup( "Shift Lights" ); propsCont.addProperty( numShiftLights ); for ( int i = 0; i < numShiftLights.getIntValue(); i++ ) shiftLights[i].getProperties( propsCont, forceAll ); if ( forceAll ) { if ( numShiftLights.getIntValue() < 1 ) ShiftLight.DEFAULT_SHIFT_LIGHT1.getProperties( propsCont, forceAll ); if ( numShiftLights.getIntValue() < 2 ) ShiftLight.DEFAULT_SHIFT_LIGHT2.getProperties( propsCont, forceAll ); if ( numShiftLights.getIntValue() < 3 ) ShiftLight.DEFAULT_SHIFT_LIGHT3.getProperties( propsCont, forceAll ); if ( numShiftLights.getIntValue() < 4 ) ShiftLight.DEFAULT_SHIFT_LIGHT4.getProperties( propsCont, forceAll ); if ( numShiftLights.getIntValue() < 5 ) ShiftLight.DEFAULT_SHIFT_LIGHT5.getProperties( propsCont, forceAll ); } } /** * Collects the properties for the engine boost. * * @param propsCont the container to add the properties to * @param forceAll If <code>true</code>, all properties provided by this {@link Widget} must be added. * If <code>false</code>, only the properties, that are relevant for the current {@link Widget}'s situation have to be added, some can be ignored. */ protected void getBoostProperties( PropertiesContainer propsCont, boolean forceAll ) { propsCont.addGroup( "Engine Boost" ); propsCont.addProperty( displayBoostBar ); propsCont.addProperty( boostBarPosX ); propsCont.addProperty( boostBarPosY ); propsCont.addProperty( boostBarWidth ); propsCont.addProperty( boostBarHeight ); propsCont.addProperty( displayBoostNumber ); propsCont.addProperty( boostNumberBackgroundImageName ); propsCont.addProperty( boostNumberPosX ); propsCont.addProperty( boostNumberPosY ); propsCont.addProperty( boostNumberFont ); propsCont.addProperty( boostNumberFontColor ); } /** * Collects the properties for the digital revs. * * @param propsCont the container to add the properties to * @param forceAll If <code>true</code>, all properties provided by this {@link Widget} must be added. * If <code>false</code>, only the properties, that are relevant for the current {@link Widget}'s situation have to be added, some can be ignored. */ protected void getDigiRevsProperties( PropertiesContainer propsCont, boolean forceAll ) { propsCont.addGroup( "DigitalRevs1" ); propsCont.addProperty( displayRPMString1 ); propsCont.addProperty( displayCurrRPM1 ); propsCont.addProperty( displayMaxRPM1 ); propsCont.addProperty( useBoostRevLimit1 ); propsCont.addProperty( rpmPosX1 ); propsCont.addProperty( rpmPosY1 ); propsCont.addProperty( rpmFont1 ); propsCont.addProperty( rpmFontColor1 ); propsCont.addProperty( rpmJoinString1 ); propsCont.addGroup( "DigitalRevs2" ); propsCont.addProperty( displayRPMString2 ); propsCont.addProperty( displayCurrRPM2 ); propsCont.addProperty( displayMaxRPM2 ); propsCont.addProperty( useBoostRevLimit2 ); propsCont.addProperty( rpmPosX2 ); propsCont.addProperty( rpmPosY2 ); propsCont.addProperty( rpmFont2 ); propsCont.addProperty( rpmFontColor2 ); propsCont.addProperty( rpmJoinString2 ); } /** * {@inheritDoc} */ @Override protected String getDigiValuePropertiesGroupName() { return ( "Velocity" ); } /** * {@inheritDoc} */ @Override public void getProperties( PropertiesContainer propsCont, boolean forceAll ) { super.getProperties( propsCont, forceAll ); getShiftLightsProperties( propsCont, forceAll ); getBoostProperties( propsCont, forceAll ); getDigiRevsProperties( propsCont, forceAll ); } /** * {@inheritDoc} */ @Override protected boolean cloneProperty( Property src, Property trg ) { boolean result = super.cloneProperty( src, trg ); if ( trg == numShiftLights ) return ( true ); return ( result ); } @Override protected String getInitialBackground() { return ( BackgroundProperty.IMAGE_INDICATOR + "standard/rev_meter_bg.png" ); } @Override protected void onBackgroundChanged( boolean imageChanged, float deltaScaleX, float deltaScaleY ) { super.onBackgroundChanged( imageChanged, deltaScaleX, deltaScaleY ); for ( int i = 0; i < numShiftLights.getIntValue(); i++ ) shiftLights[i].onBackgroundChanged( imageChanged, deltaScaleX, deltaScaleY ); // TODO: Don't set to null! boostNumberBackgroundTexture = null; boostNumberBackgroundTexture_bak = null; if ( deltaScaleX > 0f ) { boostBarPosX.setIntValue( Math.round( boostBarPosX.getIntValue() * deltaScaleX ) ); boostBarPosY.setIntValue( Math.round( boostBarPosY.getIntValue() * deltaScaleY ) ); boostBarWidth.setIntValue( Math.round( boostBarWidth.getIntValue() * deltaScaleX ) ); boostBarHeight.setIntValue( Math.round( boostBarHeight.getIntValue() * deltaScaleY ) ); boostNumberPosX.setIntValue( Math.round( boostNumberPosX.getIntValue() * deltaScaleX ) ); boostNumberPosY.setIntValue( Math.round( boostNumberPosY.getIntValue() * deltaScaleY ) ); rpmPosX1.setIntValue( Math.round( rpmPosX1.getIntValue() * deltaScaleX ) ); rpmPosY1.setIntValue( Math.round( rpmPosY1.getIntValue() * deltaScaleY ) ); rpmPosX2.setIntValue( Math.round( rpmPosX2.getIntValue() * deltaScaleX ) ); rpmPosY2.setIntValue( Math.round( rpmPosY2.getIntValue() * deltaScaleY ) ); } } @Override protected String getMarkerLabelForValue( LiveGameData gameData, boolean isEditorMode, float value ) { return ( String.valueOf( Math.round( value / 1000 ) ) ); } private boolean loadBoostNumberBackgroundTexture( boolean isEditorMode ) { if ( !displayBoostNumber.getBooleanValue() ) { boostNumberBackgroundTexture = null; boostNumberBackgroundTexture_bak = null; return ( false ); } try { ImageTemplate it = boostNumberBackgroundImageName.getImage(); if ( it == null ) { boostNumberBackgroundTexture = null; boostNumberBackgroundTexture_bak = null; return ( false ); } float scale = getBackground().getScaleX(); int w = Math.round( it.getBaseWidth() * scale ); int h = Math.round( it.getBaseHeight() * scale ); boolean[] changeInfo = new boolean[ 2 ]; boostNumberBackgroundTexture = it.getScaledTransformableTexture( w, h, boostNumberBackgroundTexture, isEditorMode, changeInfo ); boostNumberBackgroundTexture.setDynamic( true ); if ( changeInfo[1] ) // redrawn { boostNumberBackgroundTexture_bak = TextureImage2D.getOrCreateDrawTexture( w, h, it.hasAlpha(), boostNumberBackgroundTexture_bak, isEditorMode ); boostNumberBackgroundTexture_bak.clear( boostNumberBackgroundTexture.getTexture(), true, null ); } } catch ( Throwable t ) { log( t ); return ( false ); } return ( true ); } @Override protected void initSubTextures( LiveGameData gameData, boolean isEditorMode, int widgetInnerWidth, int widgetInnerHeight, SubTextureCollector collector ) { super.initSubTextures( gameData, isEditorMode, widgetInnerWidth, widgetInnerHeight, collector ); for ( int s = 0; s < numShiftLights.getIntValue(); s++ ) shiftLights[s].loadTextures( isEditorMode, collector ); if ( loadBoostNumberBackgroundTexture( isEditorMode ) ) collector.add( boostNumberBackgroundTexture ); } /** * {@inheritDoc} */ @Override protected void initialize( LiveGameData gameData, boolean isEditorMode, DrawnStringFactory dsf, TextureImage2D texture, int width, int height ) { super.initialize( gameData, isEditorMode, dsf, texture, width, height ); final Texture2DCanvas texCanvas = texture.getTextureCanvas(); FontMetrics metrics = null; Rectangle2D bounds = null; double fw = 0, fh = 0; double fd = 0; int fx = 0, fy = 0; if ( displayBoostNumber.getBooleanValue() ) { metrics = boostNumberFont.getMetrics(); bounds = metrics.getStringBounds( "0", texCanvas ); fw = bounds.getWidth(); fd = metrics.getDescent(); fh = metrics.getAscent() - fd; if ( boostNumberBackgroundTexture == null ) { fx = Math.round( boostNumberPosX.getIntValue() * getBackground().getScaleX() ); fy = Math.round( boostNumberPosY.getIntValue() * getBackground().getScaleY() ); } else { boostNumberBackgroundTexPosX = Math.round( boostNumberPosX.getIntValue() * getBackground().getScaleX() - boostNumberBackgroundTexture.getWidth() / 2.0f ); boostNumberBackgroundTexPosY = Math.round( boostNumberPosY.getIntValue() * getBackground().getScaleY() - boostNumberBackgroundTexture.getHeight() / 2.0f ); fx = boostNumberBackgroundTexture.getWidth() / 2; fy = boostNumberBackgroundTexture.getHeight() / 2; } } boostString = dsf.newDrawnStringIf( displayBoostNumber.getBooleanValue(), "boostString", fx - (int)( fw / 2.0 ), fy - (int)( fd + fh / 2.0 ), Alignment.LEFT, false, boostNumberFont.getFont(), boostNumberFont.isAntiAliased(), boostNumberFontColor.getColor() ); rpmString1 = dsf.newDrawnStringIf( displayRPMString1.getBooleanValue(), "rpmString1", width - Math.round( rpmPosX1.getIntValue() * getBackground().getScaleX() ), Math.round( rpmPosY1.getIntValue() * getBackground().getScaleY() ), Alignment.RIGHT, false, rpmFont1.getFont(), rpmFont1.isAntiAliased(), rpmFontColor1.getColor() ); rpmString2 = dsf.newDrawnStringIf( displayRPMString2.getBooleanValue(), "rpmString2", width - Math.round( rpmPosX2.getIntValue() * getBackground().getScaleX() ), Math.round( rpmPosY2.getIntValue() * getBackground().getScaleY() ), Alignment.RIGHT, false, rpmFont2.getFont(), rpmFont2.isAntiAliased(), rpmFontColor2.getColor() ); } private Color interpolateColor( Color c0, Color c1, float alpha ) { return ( new Color( Math.max( 0, Math.min( Math.round( c0.getRed() + ( c1.getRed() - c0.getRed() ) * alpha ), 255 ) ), Math.max( 0, Math.min( Math.round( c0.getGreen() + ( c1.getGreen() - c0.getGreen() ) * alpha ), 255 ) ), Math.max( 0, Math.min( Math.round( c0.getBlue() + ( c1.getBlue() - c0.getBlue() ) * alpha ), 255 ) ) ) ); } private float lowRPM = -1f; private float mediumRPM = -1f; @Override protected void prepareMarkersBackground( LiveGameData gameData, boolean isEditorMode, Texture2DCanvas texCanvas, int offsetX, int offsetY, int width, int height, float innerRadius, float bigOuterRadius, float smallOuterRadius ) { super.prepareMarkersBackground( gameData, isEditorMode, texCanvas, offsetX, offsetY, width, height, innerRadius, bigOuterRadius, smallOuterRadius ); if ( !fillHighBackground.getBooleanValue() ) return; final float centerX = offsetX + width / 2; final float centerY = offsetY + height / 2; final float innerAspect = getMarkersOnCircle() ? 1.0f : getInnerSize().getAspect(); int nPoints = 360; int[] xPoints = new int[ nPoints ]; int[] yPoints = new int[ nPoints ]; final float TWO_PI = (float)( Math.PI * 2f ); for ( int i = 0; i < nPoints; i++ ) { float angle = i * ( TWO_PI / ( nPoints + 1 ) ); xPoints[i] = Math.round( centerX + (float)Math.cos( angle ) * innerRadius ); yPoints[i] = Math.round( centerY + -(float)Math.sin( angle ) * innerRadius / innerAspect ); } Shape oldClip = texCanvas.getClip(); Polygon p = new Polygon( xPoints, yPoints, nPoints ); Area area = new Area( oldClip ); area.subtract( new Area( p ) ); texCanvas.setClip( area ); int maxValue = (int)getMaxValue( gameData, isEditorMode ); float lowAngle = ( needleRotationForMinValue.getFloatValue() + ( needleRotationForMaxValue.getFloatValue() - needleRotationForMinValue.getFloatValue() ) * ( lowRPM / maxValue ) ); //float mediumAngle = ( needleRotationForMinValue.getFloatValue() + ( needleRotationForMaxValue.getFloatValue() - needleRotationForMinValue.getFloatValue() ) * ( mediumRPM / maxRPM ) ); float maxAngle = needleRotationForMaxValue.getFloatValue(); float oneDegree = 1; //(float)( Math.PI / 180.0 ); for ( float angle = lowAngle; angle < maxAngle - oneDegree; angle += oneDegree ) { angle = (float)Math.floor( angle ); int rpm = Math.round( ( angle - needleRotationForMinValue.getFloatValue() ) * maxValue / ( needleRotationForMaxValue.getFloatValue() - needleRotationForMinValue.getFloatValue() ) ); if ( rpm <= mediumRPM ) texCanvas.setColor( interpolateMarkerColors.getBooleanValue() ? interpolateColor( markersColor.getColor(), revMarkersMediumColor.getColor(), ( rpm - lowRPM ) / ( mediumRPM - lowRPM ) ) : revMarkersMediumColor.getColor() ); else texCanvas.setColor( interpolateMarkerColors.getBooleanValue() ? interpolateColor( revMarkersMediumColor.getColor(), revMarkersHighColor.getColor(), ( rpm - mediumRPM ) / ( maxValue - mediumRPM ) ) : revMarkersHighColor.getColor() ); texCanvas.fillArc( Math.round( centerX - smallOuterRadius ), Math.round( centerY - smallOuterRadius / innerAspect ), Math.round( smallOuterRadius + smallOuterRadius ), Math.round( ( smallOuterRadius + smallOuterRadius ) / innerAspect ), Math.round( 90f - angle ), ( angle < maxAngle - oneDegree - oneDegree ) ? -2 : -1 ); } texCanvas.setClip( oldClip ); } @Override protected Color getMarkerColorForValue( LiveGameData gameData, boolean isEditorMode, int value, int minValue, int maxValue ) { if ( fillHighBackground.getBooleanValue() || ( value <= lowRPM ) ) return ( super.getMarkerColorForValue( gameData, isEditorMode, value, minValue, maxValue ) ); if ( value <= mediumRPM ) return ( interpolateMarkerColors.getBooleanValue() ? interpolateColor( markersColor.getColor(), revMarkersMediumColor.getColor(), ( value - lowRPM ) / ( mediumRPM - lowRPM ) ) : revMarkersMediumColor.getColor() ); return ( interpolateMarkerColors.getBooleanValue() ? interpolateColor( revMarkersMediumColor.getColor(), revMarkersHighColor.getColor(), ( value - mediumRPM ) / ( maxValue - mediumRPM ) ) : revMarkersHighColor.getColor() ); } @Override protected void drawMarkers( LiveGameData gameData, boolean isEditorMode, Texture2DCanvas texCanvas, int offsetX, int offsetY, int width, int height ) { VehiclePhysics.Engine engine = gameData.getPhysics().getEngine(); /* PhysicsSetting boostRange = engine.getBoostRange(); int minBoost = ( engine.getRPMIncreasePerBoostLevel() > 0f ) ? (int)boostRange.getMinValue() : (int)boostRange.getMaxValue(); //int maxBoost = ( engine.getRPMIncreasePerBoostLevel() > 0f ) ? (int)boostRange.getMaxValue() : (int)boostRange.getMinValue(); int mediumBoost = Math.round( boostRange.getMinValue() + ( boostRange.getMaxValue() - boostRange.getMinValue() ) / 2f ); float baseMaxRPM = gameData.getTelemetryData().getEngineBaseMaxRPM(); lowRPM = engine.getMaxRPM( baseMaxRPM, minBoost ); mediumRPM = engine.getMaxRPM( baseMaxRPM, mediumBoost ); */ lowRPM = engine.getBaseLifetimeRPM(); mediumRPM = engine.getBaseLifetimeRPM() + engine.getHalfLifetimeRPMOffset(); super.drawMarkers( gameData, isEditorMode, texCanvas, offsetX, offsetY, width, height ); } /** * Draws the boost bar. * * @param boost * @param maxBoost * @param inverted * @param tempBoost * @param texCanvas * @param offsetX * @param offsetY * @param width * @param height */ protected void drawBoostBar( int boost, int maxBoost, boolean inverted, boolean tempBoost, Texture2DCanvas texCanvas, int offsetX, int offsetY, int width, int height ) { if ( inverted ) boost = maxBoost - boost + 1; TextureImage2D image = texCanvas.getImage(); image.clear( Color.BLACK, offsetX, offsetY, width, height, true, null ); texCanvas.setColor( Color.WHITE ); texCanvas.drawRect( offsetX, offsetY, width - 1, height - 1 ); int midBoost = ( ( maxBoost % 2 ) == 0 ) ? -maxBoost / 2 : maxBoost / 2 + 1; int x0 = 0; for ( int b = 1; b <= maxBoost; b++ ) { int right = Math.min( width * b / maxBoost, width - 1 ); if ( b <= boost ) { Color color; if ( maxBoost == 1 ) { color = Color.RED; } else if ( maxBoost == 2 ) { if ( b == 1 ) color = Color.GREEN; else color = Color.RED; } else { boolean isMidBoost = ( midBoost < 0 ) ? ( b == -midBoost || b == -midBoost + 1 ) : ( b == midBoost ); if ( isMidBoost ) { color = Color.YELLOW; } else if ( b < Math.abs( midBoost ) ) { int r = Math.round( ( b - 1 ) * 255f / ( Math.abs( midBoost ) - 1 ) ); color = new Color( r, 255, 0 ); } else if ( midBoost < 0 ) { int g = Math.round( ( -midBoost + 1 - b - midBoost - 1 ) * 255f / ( maxBoost + midBoost - 1 ) ); color = new Color( 255, g, 0 ); } else { int g = Math.round( ( midBoost - b + midBoost - 1 ) * 255f / ( maxBoost - midBoost ) ); color = new Color( 255, g, 0 ); } } image.clear( color, offsetX + x0 + 1, offsetY + 1, right - x0 - 1, height - 2, true, null ); } if ( b < maxBoost ) texCanvas.drawLine( offsetX + right, offsetY + 1, offsetX + right, offsetY + height - 2 ); x0 = right; } } @Override protected boolean doRenderNeedle( LiveGameData gameData, boolean isEditorMode ) { VehicleScoringInfo vsi = gameData.getScoringInfo().getViewedVehicleScoringInfo(); return ( vsi.isPlayer() ); } @Override protected void drawWidget( Clock clock, boolean needsCompleteRedraw, LiveGameData gameData, boolean isEditorMode, TextureImage2D texture, int offsetX, int offsetY, int width, int height ) { super.drawWidget( clock, needsCompleteRedraw, gameData, isEditorMode, texture, offsetX, offsetY, width, height ); TelemetryData telemData = gameData.getTelemetryData(); VehicleScoringInfo vsi = gameData.getScoringInfo().getViewedVehicleScoringInfo(); boost.update( vsi.isPlayer() ? telemData.getEffectiveEngineBoostMapping() : gameData.getPhysics().getEngine().getLowestBoostLevel() ); if ( needsCompleteRedraw || boost.hasChanged() ) { if ( displayBoostNumber.getBooleanValue() ) { if ( boostNumberBackgroundTexture == null ) { boostString.draw( offsetX, offsetY, boost.getValueAsString(), texture ); } else { if ( needsCompleteRedraw ) boostNumberBackgroundTexture.getTexture().clear( boostNumberBackgroundTexture_bak, true, null ); boostString.draw( 0, 0, boost.getValueAsString(), boostNumberBackgroundTexture.getTexture(), boostNumberBackgroundTexture_bak, 0, 0 ); } } if ( displayBoostBar.getBooleanValue() ) { int maxBoost = (int)gameData.getPhysics().getEngine().getBoostRange().getMaxValue(); boolean inverted = ( gameData.getPhysics().getEngine().getRPMIncreasePerBoostLevel() < 0f ); boolean tempBoost = false; drawBoostBar( boost.getValue(), maxBoost, inverted, tempBoost, texture.getTextureCanvas(), offsetX + Math.round( boostBarPosX.getIntValue() * getBackground().getScaleY() ), offsetY + Math.round( boostBarPosY.getIntValue() * getBackground().getScaleY() ), Math.round( boostBarWidth.getIntValue() * getBackground().getScaleX() ), Math.round( boostBarHeight.getIntValue() * getBackground().getScaleY() ) ); } } float rpm = getValue( gameData, isEditorMode ); //float maxRPM = telemData.getEngineMaxRPM(); float baseMaxRPM = gameData.getSetup().getEngine().getRevLimit(); float maxRPM = gameData.getPhysics().getEngine().getMaxRPM( baseMaxRPM ); float boostMaxRPM = gameData.getPhysics().getEngine().getMaxRPM( baseMaxRPM, boost.getValue() ); if ( displayRPMString1.getBooleanValue() && ( needsCompleteRedraw || clock.c() ) ) { String string = ""; if ( vsi.isPlayer() ) { if ( displayCurrRPM1.getBooleanValue() ) string = NumberUtil.formatFloat( rpm, 0, false ); if ( displayCurrRPM1.getBooleanValue() && displayMaxRPM1.getBooleanValue() ) string += rpmJoinString1.getStringValue(); if ( displayMaxRPM1.getBooleanValue() ) { float maxRPM2 = useBoostRevLimit1.getBooleanValue() ? boostMaxRPM : maxRPM; string += NumberUtil.formatFloat( maxRPM2, 0, false ); } } rpmString1.draw( offsetX, offsetY, string, texture ); } if ( displayRPMString2.getBooleanValue() && ( needsCompleteRedraw || clock.c() ) ) { String string = ""; if ( vsi.isPlayer() ) { if ( displayCurrRPM2.getBooleanValue() ) string = NumberUtil.formatFloat( rpm, 0, false ); if ( displayCurrRPM2.getBooleanValue() && displayMaxRPM2.getBooleanValue() ) string += rpmJoinString2.getStringValue(); if ( displayMaxRPM2.getBooleanValue() ) { float maxRPM2 = useBoostRevLimit2.getBooleanValue() ? boostMaxRPM : maxRPM; string += NumberUtil.formatFloat( maxRPM2, 0, false ); } } rpmString2.draw( offsetX, offsetY, string, texture ); } if ( numShiftLights.getIntValue() > 0 ) { float rpm2 = vsi.isPlayer() ? rpm : 0f; for ( int s = 0; s < numShiftLights.getIntValue(); s++ ) shiftLights[s].updateTextures( rpm2, boostMaxRPM, getBackground().getScaleX(), getBackground().getScaleY() ); } if ( boostNumberBackgroundTexture != null ) boostNumberBackgroundTexture.setTranslation( boostNumberBackgroundTexPosX, boostNumberBackgroundTexPosY ); } }