/** * 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.map; import java.awt.BasicStroke; import java.awt.Color; import java.awt.Font; import java.awt.RenderingHints; import java.awt.Stroke; import java.awt.geom.AffineTransform; import java.awt.geom.GeneralPath; import java.awt.geom.Line2D; import java.awt.geom.Point2D; import java.awt.geom.QuadCurve2D; import java.awt.geom.Rectangle2D; import java.io.IOException; import net.ctdp.rfdynhud.gamedata.LiveGameData; import net.ctdp.rfdynhud.gamedata.ModInfo; import net.ctdp.rfdynhud.gamedata.ScoringInfo; import net.ctdp.rfdynhud.gamedata.Track; import net.ctdp.rfdynhud.gamedata.VehicleScoringInfo; import net.ctdp.rfdynhud.properties.BackgroundProperty; import net.ctdp.rfdynhud.properties.BooleanProperty; import net.ctdp.rfdynhud.properties.ColorProperty; import net.ctdp.rfdynhud.properties.EnumProperty; import net.ctdp.rfdynhud.properties.FontProperty; 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.render.BorderWrapper; import net.ctdp.rfdynhud.render.DrawnStringFactory; import net.ctdp.rfdynhud.render.Texture2DCanvas; import net.ctdp.rfdynhud.render.TextureImage2D; import net.ctdp.rfdynhud.render.TransformableTexture; import net.ctdp.rfdynhud.util.MapTools; import net.ctdp.rfdynhud.util.PropertyWriter; import net.ctdp.rfdynhud.util.SubTextureCollector; import net.ctdp.rfdynhud.valuemanagers.Clock; import net.ctdp.rfdynhud.widgets.WidgetsConfiguration; import net.ctdp.rfdynhud.widgets.base.widget.Widget; import net.ctdp.rfdynhud.widgets.standard._util.LabelPositioning; import net.ctdp.rfdynhud.widgets.standard._util.StandardWidgetSet; import org.jagatoo.logging.LogLevel; /** * The {@link MapWidget} renders a map overview of the current track. * * @author Marvin Froehlich (CTDP) */ public class MapWidget extends Widget { private final BooleanProperty rotationEnabled = new BooleanProperty( "rotationEnabled", false ); private final ColorProperty roadColorSec1 = new ColorProperty( "roadColorSec1", "colorSec1", "#000000" ); private final ColorProperty roadBoundaryColorSec1 = new ColorProperty( "roadBoundaryColorSec1", "boundaryColorSec1", "#FFFFFF" ); private final ColorProperty roadColorSec2 = new ColorProperty( "roadColorSec2", "colorSec2", "#000000" ); private final ColorProperty roadBoundaryColorSec2 = new ColorProperty( "roadBoundaryColorSec2", "boundaryColorSec2", "#FFFFFF" ); private final ColorProperty roadColorSec3 = new ColorProperty( "roadColorSec3", "colorSec3", "#000000" ); private final ColorProperty roadBoundaryColorSec3 = new ColorProperty( "roadBoundaryColorSec3", "boundaryColorSec3", "#FFFFFF" ); private final ColorProperty pitlaneColor = new ColorProperty( "pitlaneColor", "pitlaneColor", "#FFFF00" ); private final IntProperty roadWidth = new IntProperty( "roadWidth", "width", 4, 2, 20, false ) { @Override protected int fixValue( int value ) { value = super.fixValue( value ); return ( Math.round( value / 2f ) * 2 ); } }; private float pitlaneRoadWidth = 2f; private Track track = null; private float scale = 1f; private final IntProperty baseItemRadius = new IntProperty( "itemRadius", "radius", 9, 1, 100, false ) { @Override protected void onValueChanged( Integer oldValue, int newValue ) { super.onValueChanged( oldValue, newValue ); if ( getConfiguration() != null ) updateItemRadius(); forceAndSetDirty( false ); } }; private int itemRadius = baseItemRadius.getIntValue(); private void updateItemRadius() { itemRadius = Math.round( baseItemRadius.getIntValue() * getConfiguration().getGameResolution().getViewportHeight() / 960f ); } private int itemBlackBorderWidth = 2; private boolean needsBGClear = false; private final ColorProperty markColorNormal = new ColorProperty( "markColorNormal", "colorNormal", StandardWidgetSet.POSITION_ITEM_COLOR_NORMAL.getKey() ); private final ColorProperty markColorLeader = new ColorProperty( "markColorLeader", "colorLeader", StandardWidgetSet.POSITION_ITEM_COLOR_LEADER.getKey() ); private final ColorProperty markColorMe = new ColorProperty( "markColorMe", "colorMe", StandardWidgetSet.POSITION_ITEM_COLOR_ME.getKey() ); private final BooleanProperty useMyColorForMe1st = new BooleanProperty( "useMyColorForMe1st", false ); private final ColorProperty markColorNextInFront = new ColorProperty( "markColorNextInFront", "colorNextInFront", StandardWidgetSet.POSITION_ITEM_COLOR_NEXT_IN_FRONT.getKey() ); private final ColorProperty markColorNextBehind = new ColorProperty( "markColorNextBehind", "colorNextBehind", StandardWidgetSet.POSITION_ITEM_COLOR_NEXT_BEHIND.getKey() ); private final BooleanProperty displayPositionNumbers = new BooleanProperty( "displayPosNumbers", true ); private final BooleanProperty displayNameLabels = new BooleanProperty( "displayNameLabels", false ); private final EnumProperty<LabelPositioning> nameLabelPos = new EnumProperty<LabelPositioning>( "nameLabelPos", LabelPositioning.BELOW_RIGHT ); private final FontProperty nameLabelFont = new FontProperty( "nameLabelFont", StandardWidgetSet.POSITION_ITEM_FONT.getKey() ); private final ColorProperty nameLabelFontColor = new ColorProperty( "nameLabelFontColor", StandardWidgetSet.POSITION_ITEM_COLOR_NORMAL.getKey() ); private int maxDisplayedVehicles = -1; private static final int ANTI_ALIAS_RADIUS_OFFSET = 1; private TransformableTexture[] subTextures = null; private VehicleScoringInfo[] vsis = null; private int[] itemStates = null; private int numVehicles = 0; private final Point2D.Float position = new Point2D.Float(); public MapWidget() { super( StandardWidgetSet.INSTANCE, StandardWidgetSet.WIDGET_PACKAGE, 16f, 24f ); getBorderProperty().setBorder( "" ); getFontProperty().setFont( StandardWidgetSet.POSITION_ITEM_FONT.getKey() ); getFontColorProperty().setColor( StandardWidgetSet.POSITION_ITEM_FONT_COLOR.getKey() ); } @Override protected String getInitialBackground() { return ( BackgroundProperty.COLOR_INDICATOR + "#00000000" ); } @Override public void prepareForMenuItem() { super.prepareForMenuItem(); roadWidth.setIntValue( 4 ); pitlaneRoadWidth = 1f; //baseItemRadius.setIntValue( 20 ); itemRadius = 3; itemBlackBorderWidth = 0; } /** * {@inheritDoc} */ @Override public void saveProperties( PropertyWriter writer ) throws IOException { super.saveProperties( writer ); writer.writeProperty( rotationEnabled, "Map rotation enabled?" ); writer.writeProperty( roadColorSec1, "The color used for the road and sector 1 in #RRGGBBAA (hex)." ); writer.writeProperty( roadBoundaryColorSec1, "The color used for the road boundary and sector 1 in #RRGGBBAA (hex)." ); writer.writeProperty( roadColorSec2, "The color used for the road and sector 2 in #RRGGBBAA (hex)." ); writer.writeProperty( roadBoundaryColorSec2, "The color used for the road boundary and sector 2 in #RRGGBBAA (hex)." ); writer.writeProperty( roadColorSec3, "The color used for the road and sector 3 in #RRGGBBAA (hex)." ); writer.writeProperty( roadBoundaryColorSec3, "The color used for the road boundary and sector 3 in #RRGGBBAA (hex)." ); writer.writeProperty( pitlaneColor, "The color used for the pitlane in #RRGGBBAA (hex)." ); writer.writeProperty( roadWidth, "The width of the roadin absolute pixels." ); writer.writeProperty( baseItemRadius, "The abstract radius for any displayed driver item." ); writer.writeProperty( markColorNormal, "The color used for all, but special cars in #RRGGBBAA (hex)." ); writer.writeProperty( markColorLeader, "The color used for the leader's car in #RRGGBBAA (hex)." ); writer.writeProperty( markColorMe, "The color used for your own car in #RRGGBBAA (hex)." ); writer.writeProperty( useMyColorForMe1st, "Use 'markColorMe' for my item when I am at 1st place?" ); writer.writeProperty( markColorNextInFront, "The color used for the car in front of you in #RRGGBBAA (hex)." ); writer.writeProperty( markColorNextBehind, "The color used for the car behind you in #RRGGBBAA (hex)." ); writer.writeProperty( displayPositionNumbers, "Display numbers on the position markers?" ); writer.writeProperty( displayNameLabels, "Display name label near the position markers?" ); writer.writeProperty( nameLabelPos, "Positioning of the name labels." ); writer.writeProperty( nameLabelFont, "Font for the name labels." ); writer.writeProperty( nameLabelFontColor, "Font color for the name labels." ); } /** * {@inheritDoc} */ @Override public void loadProperty( PropertyLoader loader ) { super.loadProperty( loader ); if ( loader.loadProperty( rotationEnabled ) ); else if ( loader.loadProperty( roadColorSec1 ) ); else if ( loader.loadProperty( roadBoundaryColorSec1 ) ); else if ( loader.loadProperty( roadColorSec2 ) ); else if ( loader.loadProperty( roadBoundaryColorSec2 ) ); else if ( loader.loadProperty( roadColorSec3 ) ); else if ( loader.loadProperty( roadBoundaryColorSec3 ) ); else if ( loader.loadProperty( pitlaneColor ) ); else if ( loader.loadProperty( roadWidth ) ); else if ( loader.loadProperty( baseItemRadius ) ); else if ( loader.loadProperty( markColorNormal ) ); else if ( loader.loadProperty( markColorLeader ) ); else if ( loader.loadProperty( markColorMe ) ); else if ( loader.loadProperty( useMyColorForMe1st ) ); else if ( loader.loadProperty( markColorNextInFront ) ); else if ( loader.loadProperty( markColorNextBehind ) ); else if ( loader.loadProperty( displayPositionNumbers ) ); else if ( loader.loadProperty( displayNameLabels ) ); else if ( loader.loadProperty( nameLabelPos ) ); else if ( loader.loadProperty( nameLabelFont ) ); else if ( loader.loadProperty( nameLabelFontColor ) ); } /** * {@inheritDoc} */ @Override public void getProperties( PropertiesContainer propsCont, boolean forceAll ) { super.getProperties( propsCont, forceAll ); propsCont.addGroup( "Misc" ); propsCont.addProperty( rotationEnabled ); propsCont.addGroup( "Road" ); propsCont.addProperty( roadColorSec1 ); propsCont.addProperty( roadBoundaryColorSec1 ); propsCont.addProperty( roadColorSec2 ); propsCont.addProperty( roadBoundaryColorSec2 ); propsCont.addProperty( roadColorSec3 ); propsCont.addProperty( roadBoundaryColorSec3 ); propsCont.addProperty( pitlaneColor ); propsCont.addProperty( roadWidth ); propsCont.addGroup( "Items" ); propsCont.addProperty( baseItemRadius ); propsCont.addProperty( markColorNormal ); propsCont.addProperty( markColorLeader ); propsCont.addProperty( markColorMe ); propsCont.addProperty( useMyColorForMe1st ); propsCont.addProperty( markColorNextInFront ); propsCont.addProperty( markColorNextBehind ); propsCont.addProperty( displayPositionNumbers ); propsCont.addProperty( displayNameLabels ); //if ( displayNameLabels.getBooleanValue() || forceAll ) { //propsCont.addProperty( nameLabelPos ); propsCont.addProperty( nameLabelFont ); propsCont.addProperty( nameLabelFontColor ); } } /** * {@inheritDoc} */ @Override public void onPropertyChanged( Property property, Object oldValue, Object newValue ) { super.onPropertyChanged( property, oldValue, newValue ); needsBGClear = rotationEnabled.getBooleanValue(); if ( itemStates != null ) { for ( int i = 0; i < itemStates.length; i++ ) itemStates[i] = 0; } } /** * {@inheritDoc} */ @Override public boolean hasMasterCanvas( boolean isEditorMode ) { return ( !rotationEnabled.getBooleanValue() ); } /** * {@inheritDoc} */ @Override public void afterConfigurationLoaded( WidgetsConfiguration widgetsConfig, LiveGameData gameData, boolean isEditorMode ) { super.afterConfigurationLoaded( widgetsConfig, gameData, isEditorMode ); updateItemRadius(); } /** * {@inheritDoc} */ @Override public int getNeededData() { return ( Widget.NEEDED_DATA_SCORING ); } private final boolean getUseClassScoring() { return ( getConfiguration().getUseClassScoring() ); } private void initTrack( LiveGameData gameData ) { if ( gameData.getTrackInfo().isValid() ) { track = gameData.getTrackInfo().getTrack(); } else { log( LogLevel.EXCEPTION, "Warning: Track uninitialized." ); track = null; } } /** * {@inheritDoc} */ @Override public void onTrackChanged( String trackname, LiveGameData gameData, boolean isEditorMode ) { initTrack( gameData ); } private void initMaxDisplayedVehicles( boolean isEditorMode, ModInfo modInfo ) { if ( isEditorMode ) this.maxDisplayedVehicles = 22 + 1; else this.maxDisplayedVehicles = modInfo.getMaxOpponents() + 1; this.maxDisplayedVehicles = Math.max( 4, Math.min( maxDisplayedVehicles, 32 ) ); } private void updateVSIs( LiveGameData gameData, boolean isEditorMode ) { initMaxDisplayedVehicles( isEditorMode, gameData.getModInfo() ); if ( ( vsis == null ) || ( vsis.length < maxDisplayedVehicles ) ) { vsis = new VehicleScoringInfo[ maxDisplayedVehicles ]; if ( itemStates == null ) { itemStates = new int[ maxDisplayedVehicles ]; } else { int[] tmpItemStates = new int[ maxDisplayedVehicles ]; System.arraycopy( itemStates, 0, tmpItemStates, 0, itemStates.length ); itemStates = tmpItemStates; } for ( int i = 0; i < itemStates.length; i++ ) itemStates[i] = 0; } numVehicles = MapTools.getDisplayedVSIsForMap( gameData.getScoringInfo(), gameData.getScoringInfo().getViewedVehicleScoringInfo(), getUseClassScoring(), true, vsis ); } /** * {@inheritDoc} */ @Override public void onScoringInfoUpdated( LiveGameData gameData, boolean isEditorMode ) { updateVSIs( gameData, isEditorMode ); } @Override protected void initSubTextures( LiveGameData gameData, boolean isEditorMode, int widgetInnerWidth, int widgetInnerHeight, SubTextureCollector collector ) { updateItemRadius(); initMaxDisplayedVehicles( isEditorMode, gameData.getModInfo() ); int numTextures = maxDisplayedVehicles; int subTexOff = 0; if ( !hasMasterCanvas( isEditorMode ) ) { numTextures++; subTexOff++; } if ( ( subTextures == null ) || ( subTextures.length != numTextures ) ) { subTextures = new TransformableTexture[ numTextures ]; } if ( !hasMasterCanvas( isEditorMode ) && ( ( subTextures[0] == null ) || ( subTextures[0].getWidth() != widgetInnerWidth ) || ( subTextures[0].getHeight() != widgetInnerHeight ) ) ) { subTextures[0] = TransformableTexture.getOrCreate( getBorder().getWidgetWidth( widgetInnerWidth ), getBorder().getWidgetHeight( widgetInnerHeight ), isEditorMode, subTextures[0], isEditorMode ); } if ( subTextures[subTexOff] == null ) subTextures[subTexOff] = new TransformableTexture( 1, 1, isEditorMode, false ); java.awt.Dimension size = StandardWidgetSet.getPositionItemSize( itemRadius, displayNameLabels.getBooleanValue() ? nameLabelPos.getEnumValue() : null, nameLabelFont.getFont(), nameLabelFont.isAntiAliased() ); int w = size.width; int h = size.height; if ( ( subTextures[subTextures.length - 1] == null ) || ( subTextures[subTextures.length - 1].getWidth() != w ) || ( subTextures[subTextures.length - 1].getHeight() != h ) ) { for ( int i = 0; i < maxDisplayedVehicles; i++ ) { subTextures[subTexOff + i] = TransformableTexture.getOrCreate( w, h, isEditorMode, subTextures[subTexOff + i], isEditorMode ); subTextures[subTexOff + i].setVisible( false ); } } for ( int i = 0; i < subTextures.length; i++ ) collector.add( subTextures[i] ); } /** * {@inheritDoc} */ @Override protected void initialize( LiveGameData gameData, boolean isEditorMode, DrawnStringFactory dsf, TextureImage2D texture, int width, int height ) { initMaxDisplayedVehicles( isEditorMode, gameData.getModInfo() ); if ( isEditorMode ) updateVSIs( gameData, isEditorMode ); initTrack( gameData ); } private static boolean smoothed = true; private void drawPath( float[] xPoints, float[] yPoints, float[] xVectors, float[] yVectors, int n, Texture2DCanvas tc ) { if ( smoothed ) { boolean aa = tc.isAntialiazingEnabled(); tc.setAntialiazingEnabled( true ); GeneralPath path = new GeneralPath(); final int stepSize = 2; for ( int i = stepSize; i < n; i += stepSize ) { float ax = xPoints[i - stepSize]; float ay = yPoints[i - stepSize]; float bx = xPoints[i]; float by = yPoints[i]; float vx = xVectors[i - stepSize]; float vy = yVectors[i - stepSize]; float wx = -xVectors[i]; float wy = -yVectors[i]; // If the vectors are pretty similar, we can do without bezier. if ( ( Math.abs( vx + wx ) < 0.1f ) && ( Math.abs( vy + wy ) < 0.1f ) ) { Line2D.Float line = new Line2D.Float(); line.x1 = ax; line.y1 = ay; line.x2 = bx; line.y2 = by; path.append( line, true ); } else { float l = ( by * vx - ay * vx - bx * vy + ax * vy ) / ( wx * vy - wy * vx ); //float k = ( bx + l * wx - ax ) / vx; float cx = bx + l * wx; float cy = by + l * wy; double dsq = ( ax - bx ) * ( ax - bx ) + ( ay - by ) * ( ay - by ); double dsq2 = ( ax - cx ) * ( ax - cx ) + ( ay - cy ) * ( ay - cy ); double dsq3 = ( cx - bx ) * ( cx - bx ) + ( cy - by ) * ( cy - by ); if ( ( dsq2 > dsq ) || ( dsq3 > dsq ) ) { Line2D.Float line = new Line2D.Float(); line.x1 = ax; line.y1 = ay; line.x2 = bx; line.y2 = by; path.append( line, true ); } else { QuadCurve2D.Float curve = new QuadCurve2D.Float(); curve.ctrlx = cx; curve.ctrly = cy; curve.x1 = ax; curve.y1 = ay; curve.x2 = bx; curve.y2 = by; path.append( curve, true ); } } } tc.draw( path ); tc.setAntialiazingEnabled( aa ); } else { int[] xPoints_ = new int[ n ]; int[] yPoints_ = new int[ n ]; for ( int i = 0; i < n; i++ ) { xPoints_[i] = Math.round( xPoints[i] ); yPoints_[i] = Math.round( yPoints[i] ); } tc.drawPolyline( xPoints_, yPoints_, n ); } } private void drawTrack( Track track, TextureImage2D texture, int offsetX, int offsetY, int width, int height ) { Texture2DCanvas tc = texture.getTextureCanvas(); tc.setRenderingHint( RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON ); if ( track == null ) { tc.setColor( roadColorSec1.getColor() ); tc.drawArc( offsetX + 3, offsetY + 3, width - 6, height - 6, 0, 360 ); tc.setColor( Color.RED ); tc.setFont( new Font( "Monospaced", Font.PLAIN, 12 ) ); Rectangle2D bounds = tc.getFontMetrics().getStringBounds( "Ag", tc ); tc.drawString( "Couldn't read track data.", offsetX + 3, offsetY + (int)bounds.getHeight() ); tc.drawString( "Please see the log for more info.", offsetX + 3, offsetY + (int)bounds.getHeight() * 2 ); } else if ( track.getNumWaypoints( false ) > 0 ) { int off2 = ( isFontAntiAliased() ? ANTI_ALIAS_RADIUS_OFFSET : 0 ); int dia = off2 + itemRadius + itemRadius + off2; if ( rotationEnabled.getBooleanValue() ) { float xExtend = track.getXExtend( 1.0f ); float yExtend = track.getZExtend( 1.0f ); float dia2 = (float)Math.sqrt( xExtend * xExtend + yExtend * yExtend ); int wh = Math.min( width - dia + itemRadius + itemRadius - subTextures[1].getWidth(), height - dia ); scale = wh / dia2; //scale = track.getScale( (int)( wh * 0.9f ), (int)( wh * 0.9f ) ); } else { int w = width - dia - itemRadius - itemRadius - subTextures[0].getWidth(); int h = height - dia; scale = track.getScale( w, h ); } Point2D.Float p0 = new Point2D.Float(); Point2D.Float p1 = new Point2D.Float(); Point2D.Float v0 = new Point2D.Float(); Point2D.Float v1 = new Point2D.Float(); int x0 = offsetX + off2 + itemRadius + ( ( width - dia - track.getXExtend( scale ) ) / 2 ); int y0 = offsetY + off2 + itemRadius + ( ( height - dia - track.getZExtend( scale ) ) / 2 ); if ( rotationEnabled.getBooleanValue() ) { subTextures[0].setTranslation( -getBorder().getInnerLeftWidthWOPadding(), -getBorder().getInnerTopHeightWOPadding() ); //subTextures[0].setRotationCenter( x0 + track.getXExtend( scale ) / 2, y0 + track.getZExtend( scale ) / 2 ); subTextures[0].setRotationCenter( subTextures[0].getWidth() / 2, subTextures[0].getHeight() / 2 ); } Stroke oldStroke = tc.getStroke(); int n = track.getNumWaypoints( false ); float[] xPoints = new float[ n ]; float[] yPoints = new float[ n ]; float[] xVectors = new float[ n ]; float[] yVectors = new float[ n ]; track.getWaypointPosition( false, 0, scale, p0 ); xPoints[0] = x0 + p0.x; yPoints[0] = y0 + p0.y; track.getWaypointVector( false, 0, v0 ); xVectors[0] = v0.x; yVectors[0] = v0.y; byte oldSec = track.getWaypointSector( false, 0 ); int j = 1; for ( int i = 1; i < n; i++ ) { track.getWaypointPosition( false, i, scale, p1 ); track.getWaypointVector( false, i, v1 ); byte sec = track.getWaypointSector( false, i ); if ( sec != oldSec ) { if ( oldSec == 1 ) tc.setColor( roadBoundaryColorSec1.getColor() ); else if ( oldSec == 2 ) tc.setColor( roadBoundaryColorSec2.getColor() ); else if ( oldSec == 3 ) tc.setColor( roadBoundaryColorSec3.getColor() ); tc.setStroke( new BasicStroke( roadWidth.getIntValue() ) ); tc.setAntialiazingEnabled( true ); drawPath( xPoints, yPoints, xVectors, yVectors, j, tc ); if ( oldSec == 1 ) tc.setColor( roadColorSec1.getColor() ); else if ( oldSec == 2 ) tc.setColor( roadColorSec2.getColor() ); else if ( oldSec == 3 ) tc.setColor( roadColorSec3.getColor() ); tc.setStroke( new BasicStroke( roadWidth.getIntValue() - 1.5f ) ); tc.setAntialiazingEnabled( true ); drawPath( xPoints, yPoints, xVectors, yVectors, j, tc ); xPoints[0] = xPoints[j - 1]; yPoints[0] = yPoints[j - 1]; xVectors[0] = xVectors[j - 1]; yVectors[0] = yVectors[j - 1]; j = 1; } oldSec = sec; double dsq = ( p0.getX() - p1.getX() ) * ( p0.getX() - p1.getX() ) + ( p0.getY() - p1.getY() ) * ( p0.getY() - p1.getY() ); if ( ( dsq >= 4 * scale * 4 * scale ) || ( i == n - 1 ) ) { xPoints[j] = x0 + p1.x; yPoints[j] = y0 + p1.y; xVectors[j] = v1.x; yVectors[j] = v1.y; j++; Point2D.Float p = p1; p1 = p0; p0 = p; Point2D.Float v = v1; v1 = v0; v0 = v; } } if ( j > 0 ) { track.getWaypointPosition( false, 0, scale, p0 ); xPoints[j] = x0 + p0.x; yPoints[j] = y0 + p0.y; track.getWaypointVector( false, 0, v0 ); xVectors[j] = v0.x; yVectors[j] = v0.y; j++; if ( oldSec == 1 ) tc.setColor( roadBoundaryColorSec1.getColor() ); else if ( oldSec == 2 ) tc.setColor( roadBoundaryColorSec2.getColor() ); else if ( oldSec == 3 ) tc.setColor( roadBoundaryColorSec3.getColor() ); tc.setStroke( new BasicStroke( roadWidth.getFloatValue() ) ); tc.setAntialiazingEnabled( true ); drawPath( xPoints, yPoints, xVectors, yVectors, j, tc ); if ( oldSec == 1 ) tc.setColor( roadColorSec1.getColor() ); else if ( oldSec == 2 ) tc.setColor( roadColorSec2.getColor() ); else if ( oldSec == 3 ) tc.setColor( roadColorSec3.getColor() ); tc.setStroke( new BasicStroke( roadWidth.getIntValue() - 1.5f ) ); tc.setAntialiazingEnabled( true ); drawPath( xPoints, yPoints, xVectors, yVectors, j, tc ); j = 0; } tc.setColor( pitlaneColor.getColor() ); tc.setStroke( new BasicStroke( pitlaneRoadWidth ) ); tc.setAntialiazingEnabled( true ); n = track.getNumWaypoints( true ); xPoints = new float[ n + 1 ]; yPoints = new float[ n + 1 ]; xVectors = new float[ n + 1 ]; yVectors = new float[ n + 1 ]; int k = 0; track.getWaypointPosition( true, k, scale, p0 ); xPoints[0] = x0 + p0.x; yPoints[0] = y0 + p0.y; track.getWaypointVector( true, k, v0 ); xVectors[0] = v0.x; yVectors[0] = v0.y; j = 1; for ( int i = k + 1; i < n; i++ ) { track.getWaypointPosition( true, i, scale, p1 ); track.getWaypointVector( true, i, v1 ); double dsq = ( p0.getX() - p1.getX() ) * ( p0.getX() - p1.getX() ) + ( p0.getY() - p1.getY() ) * ( p0.getY() - p1.getY() ); if ( dsq > 50 * scale * 50 * scale ) { if ( k < i - 1 ) { track.getWaypointPosition( true, i - 1, scale, p0 ); track.getWaypointVector( true, i - 1, v0 ); xPoints[j - 1] = x0 + p0.x; yPoints[j - 1] = y0 + p0.y; xVectors[j - 1] = v0.x; yVectors[j - 1] = v0.y; } drawPath( xPoints, yPoints, xVectors, yVectors, j, tc ); xPoints[0] = x0 + p1.x; yPoints[0] = y0 + p1.y; xVectors[0] = v1.x; yVectors[0] = v1.y; j = 1; Point2D.Float p = p1; p1 = p0; p0 = p; Point2D.Float v = v1; v1 = v0; v0 = v; } else if ( ( dsq >= 4 * scale * 4 * scale ) || ( i == n - 1 ) ) { xPoints[j] = x0 + p1.x; yPoints[j] = y0 + p1.y; xVectors[j] = v1.x; yVectors[j] = v1.y; j++; k = i; Point2D.Float p = p1; p1 = p0; p0 = p; Point2D.Float v = v1; v1 = v0; v0 = v; } } track.getWaypointPosition( true, 0, scale, p0 ); track.getWaypointPosition( true, n - 1, scale, p1 ); double dsq = ( p0.getX() - p1.getX() ) * ( p0.getX() - p1.getX() ) + ( p0.getY() - p1.getY() ) * ( p0.getY() - p1.getY() ); if ( dsq <= 50 * scale * 50 * scale ) { track.getWaypointVector( true, 0, v0 ); xPoints[j] = x0 + p0.x; yPoints[j] = y0 + p0.y; xVectors[j] = v0.x; yVectors[j] = v0.y; j++; } drawPath( xPoints, yPoints, xVectors, yVectors, j, tc ); tc.setStroke( oldStroke ); } else { scale = 1f; } } @Override protected void drawBorder( boolean isEditorMode, BorderWrapper border, TextureImage2D texture, int offsetX, int offsetY, int width, int height ) { if ( hasMasterCanvas( isEditorMode ) ) super.drawBorder( isEditorMode, border, texture, offsetX, offsetY, width, height ); else super.drawBorder( isEditorMode, border, subTextures[0].getTexture(), 0, 0, width, height ); } @Override protected void drawBackground( LiveGameData gameData, boolean isEditorMode, TextureImage2D texture, int offsetX, int offsetY, int width, int height, boolean isRoot ) { if ( hasMasterCanvas( isEditorMode ) ) { super.drawBackground( gameData, isEditorMode, texture, offsetX, offsetY, width, height, isRoot ); drawTrack( track, texture, offsetX, offsetY, width, height ); } else { if ( isEditorMode && needsBGClear && isRoot ) { texture.clear( offsetX, offsetY, width, height, false, null ); } offsetX = getBorder().getInnerLeftWidthWOPadding(); offsetY = getBorder().getInnerTopHeightWOPadding(); super.drawBackground( gameData, isEditorMode, subTextures[0].getTexture(), offsetX, offsetY, width, height, isRoot ); drawTrack( track, subTextures[0].getTexture(), offsetX, offsetY, width, height ); } } private final AffineTransform at = new AffineTransform(); @Override public void drawWidget( Clock clock, boolean needsCompleteRedraw, LiveGameData gameData, boolean isEditorMode, TextureImage2D texture, int offsetX, int offsetY, int width, int height ) { if ( isEditorMode && needsBGClear ) { int offX_ = offsetX - getBorder().getInnerLeftWidthWOPadding(); int offY_ = offsetY - getBorder().getInnerTopHeightWOPadding(); int w_ = getBorder().getWidgetWidth( width ); int h_ = getBorder().getWidgetWidth( height ); texture.getTextureCanvas().pushClip( offX_, offY_, w_, h_ ); try { clearRegion( texture, offX_, offY_, w_, h_ ); } finally { texture.getTextureCanvas().popClip(); } needsBGClear = false; } if ( track != null ) { final ScoringInfo scoringInfo = gameData.getScoringInfo(); final VehicleScoringInfo viewedVSI = scoringInfo.getViewedVehicleScoringInfo(); final boolean useClassScoring = getUseClassScoring(); int off2 = ( isFontAntiAliased() ? ANTI_ALIAS_RADIUS_OFFSET : 0 ); int x0 = off2 - itemRadius + ( ( width - track.getXExtend( scale ) ) / 2 ); int y0 = off2 - itemRadius + ( ( height - track.getZExtend( scale ) ) / 2 ); short ownPlace = scoringInfo.getOwnPlace( useClassScoring ); final Font font = getFont(); final boolean posNumberFontAntiAliased = isFontAntiAliased(); int subTexOff = hasMasterCanvas( isEditorMode ) ? 0 : 1; if ( rotationEnabled.getBooleanValue() ) { float rotation = track.getInterpolatedAngleToRoad( scoringInfo ); subTextures[0].setRotation( rotation ); at.setToRotation( rotation, x0 + track.getXExtend( scale ) / 2, y0 + track.getZExtend( scale ) / 2 ); } else { at.setToIdentity(); } for ( int i = 0; i < numVehicles; i++ ) { VehicleScoringInfo vsi = vsis[i]; if ( vsi != null ) { short place = vsi.getPlace( useClassScoring ); float lapDistance = vsi.getLapDistance(); TransformableTexture tt = subTextures[subTexOff + i]; subTextures[subTexOff + i].setVisible( true ); int itemState = ( place << 0 ) | ( vsi.getDriverId() << 9 ); track.getInterpolatedPosition( vsi.isInPits(), lapDistance, scale, position ); position.x += x0; position.y += y0; Color color = null; if ( ( place == 1 ) && ( !useClassScoring || ( vsi.getVehicleClassId() == viewedVSI.getVehicleClassId() ) ) ) { itemState |= 1 << 26; if ( vsi.isPlayer() && useMyColorForMe1st.getBooleanValue() ) color = markColorMe.getColor(); else color = markColorLeader.getColor(); } else if ( vsi.isPlayer() ) { itemState |= 1 << 27; color = markColorMe.getColor(); } else if ( ( place == ownPlace - 1 ) && ( !useClassScoring || ( vsi.getVehicleClassId() == viewedVSI.getVehicleClassId() ) ) ) { itemState |= 1 << 28; color = markColorNextInFront.getColor(); } else if ( ( place == ownPlace + 1 ) && ( !useClassScoring || ( vsi.getVehicleClassId() == viewedVSI.getVehicleClassId() ) ) ) { itemState |= 1 << 29; color = markColorNextBehind.getColor(); } else { itemState |= 1 << 30; color = markColorNormal.getColor(); } if ( itemStates[i] != itemState ) { itemStates[i] = itemState; StandardWidgetSet.drawPositionItem( tt.getTexture(), 0, 0, itemRadius, place, color, itemBlackBorderWidth, displayPositionNumbers.getBooleanValue() ? font : null, posNumberFontAntiAliased, getFontColor(), displayNameLabels.getBooleanValue() ? nameLabelPos.getEnumValue() : null, vsi.getDriverNameTLC(), nameLabelFont.getFont(), nameLabelFont.isAntiAliased(), nameLabelFontColor.getColor() ); } if ( rotationEnabled.getBooleanValue() ) at.transform( position, position ); tt.setTranslation( position.x, position.y ); } else { subTextures[subTexOff + i].setVisible( false ); } } for ( int i = numVehicles; i < maxDisplayedVehicles; i++ ) subTextures[subTexOff + i].setVisible( false ); } } }