package org.osm2world.core.world.modules;
import static java.lang.Math.PI;
import static java.util.Arrays.asList;
import static java.util.Collections.*;
import static org.osm2world.core.math.VectorXYZ.Z_UNIT;
import static org.osm2world.core.target.common.material.Materials.PLASTIC_GREY;
import static org.osm2world.core.target.common.material.NamedTexCoordFunction.STRIP_WALL;
import static org.osm2world.core.target.common.material.TexCoordUtil.texCoordLists;
import static org.osm2world.core.world.modules.common.WorldModuleGeometryUtil.*;
import static org.osm2world.core.world.modules.common.WorldModuleParseUtil.*;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import org.osm2world.core.map_data.data.MapArea;
import org.osm2world.core.map_data.data.MapNode;
import org.osm2world.core.map_data.data.MapWaySegment;
import org.osm2world.core.map_data.data.overlaps.MapOverlap;
import org.osm2world.core.map_elevation.data.GroundState;
import org.osm2world.core.math.AxisAlignedBoundingBoxXZ;
import org.osm2world.core.math.LineSegmentXZ;
import org.osm2world.core.math.SimplePolygonXZ;
import org.osm2world.core.math.VectorXYZ;
import org.osm2world.core.math.VectorXZ;
import org.osm2world.core.target.RenderableToAllTargets;
import org.osm2world.core.target.Target;
import org.osm2world.core.target.common.material.Material;
import org.osm2world.core.target.common.material.Materials;
import org.osm2world.core.world.data.AbstractAreaWorldObject;
import org.osm2world.core.world.data.NoOutlineNodeWorldObject;
import org.osm2world.core.world.data.NoOutlineWaySegmentWorldObject;
import org.osm2world.core.world.data.WorldObject;
import org.osm2world.core.world.data.WorldObjectWithOutline;
import org.osm2world.core.world.modules.common.AbstractModule;
/**
* module for power infrastructure
*/
public final class PowerModule extends AbstractModule {
private static TowerConfig generateTowerConfig(MapNode node) {
List<MapWaySegment> powerLines = new ArrayList<MapWaySegment>();
for (MapWaySegment way : node.getConnectedWaySegments()) {
if (way.getTags().contains("power", "line")) {
powerLines.add(way);
}
}
VectorXZ dir = VectorXZ.NULL_VECTOR;
int cables = -1;
int voltage = -1;
for (MapWaySegment powerLine : powerLines) {
dir = dir.add(powerLine.getDirection());
try {
cables = Integer.valueOf(powerLine.getTags().getValue("cables"));
} catch (NumberFormatException e) {}
try {
voltage = Integer.valueOf(powerLine.getTags().getValue("voltage"));
} catch (NumberFormatException e) {}
}
dir = dir.mult(1.0/powerLines.size());
return new TowerConfig(node, cables, voltage, dir);
}
@Override
protected void applyToNode(MapNode node) {
if (node.getTags().contains("power", "cable_distribution_cabinet")) {
node.addRepresentation(new PowerCabinet(node));
}
if (node.getTags().contains("power", "pole")) {
node.addRepresentation(new Powerpole(node));
}
if (node.getTags().contains("power", "generator")
&& node.getTags().contains("generator:source", "wind")) {
node.addRepresentation(new WindTurbine(node));
}
if (node.getTags().contains("power", "tower")) {
TowerConfig config = generateTowerConfig(node);
if (node.getPrimaryRepresentation() == null) {
if (config.isHighVoltagePowerTower()) {
node.addRepresentation(new HighVoltagePowerTower(node, config));
} else {
node.addRepresentation(new PowerTower(node, config));
}
}
}
}
@Override
protected void applyToWaySegment(MapWaySegment segment) {
if (segment.getTags().contains("power", "minor_line")) {
segment.addRepresentation(new PowerMinorLine(segment));
}
if (segment.getTags().contains("power", "line")) {
segment.addRepresentation(new PowerLine(segment));
}
}
@Override
protected void applyToArea(MapArea area) {
if (area.getTags().contains("power", "generator")
&& area.getTags().contains("generator:method", "photovoltaic")) {
area.addRepresentation(new PhotovoltaicPlant(area));
}
}
private static final class PowerCabinet extends NoOutlineNodeWorldObject
implements RenderableToAllTargets {
public PowerCabinet(MapNode node) {
super(node);
}
@Override
public GroundState getGroundState() {
return GroundState.ON;
}
@Override
public void renderTo(Target<?> target) {
double directionAngle = parseDirection(node.getTags(), PI);
VectorXZ faceVector = VectorXZ.fromAngle(directionAngle);
target.drawBox(PLASTIC_GREY, getBase(),
faceVector, 1.5, 0.8, 0.3);
}
}
private final static class TowerConfig {
MapNode pos;
int cables;
int voltage;
VectorXZ direction;
public TowerConfig(MapNode pos, int cables, int voltage, VectorXZ direction) {
super();
this.pos = pos;
this.cables = cables;
this.voltage = voltage;
this.direction = direction;
}
public boolean isHighVoltagePowerTower() {
return voltage >= 50000 || cables >= 6;
}
}
private static final class Powerpole extends NoOutlineNodeWorldObject
implements RenderableToAllTargets {
public Powerpole(MapNode node) {
super(node);
}
@Override
public GroundState getGroundState() {
return GroundState.ON;
}
@Override
public void renderTo(Target<?> target) {
/* determine material */
Material material = null;
//TODO parse color
if (material == null) {
material = Materials.getSurfaceMaterial(
node.getTags().getValue("material"));
}
if (material == null) {
material = Materials.getSurfaceMaterial(
node.getTags().getValue("surface"), Materials.WOOD);
}
target.drawColumn(material, null, getBase(),
parseHeight(node.getTags(), 8f),
0.15, 0.15, false, true);
}
}
private static final class WindTurbine extends NoOutlineNodeWorldObject
implements RenderableToAllTargets {
public WindTurbine(MapNode node) {
super(node);
}
@Override
public GroundState getGroundState() {
return GroundState.ON;
}
@Override
public void renderTo(Target<?> target) {
float poleHeight = parseHeight(node.getTags(), 100f);
float poleRadiusBottom = parseWidth(node.getTags(), 5) / 2;
float poleRadiusTop = poleRadiusBottom / 2;
float nacelleHeight = poleHeight * 0.05f;
float nacelleDepth = poleHeight * 0.1f;
float bladeLength = poleHeight / 2;
/* determine material */
Material poleMaterial = null;
Material nacelleMaterial = Materials.STEEL;
Material bladeMaterial = Materials.STEEL; // probably fibre, but color matches roughly :)
//TODO parse color
if (poleMaterial == null) {
poleMaterial = Materials.getSurfaceMaterial(
node.getTags().getValue("material"));
}
if (poleMaterial == null) {
poleMaterial = Materials.getSurfaceMaterial(
node.getTags().getValue("surface"), Materials.STEEL);
}
/* draw pole */
target.drawColumn(poleMaterial, null,
getBase(),
poleHeight,
poleRadiusBottom, poleRadiusTop, false, false);
/* draw nacelle */
VectorXZ nacelleVector = VectorXZ.X_UNIT;
target.drawBox(nacelleMaterial,
getBase().addY(poleHeight).add(nacelleDepth/2 - poleRadiusTop*2, 0f, 0f),
nacelleVector, nacelleHeight, nacelleHeight, nacelleDepth);
/* draw blades */
// define first blade
List<VectorXYZ> bladeFront = asList(
getBase().addY(poleHeight).add(-poleRadiusTop*2, nacelleHeight/2, +nacelleHeight/2),
getBase().addY(poleHeight).add(-poleRadiusTop*2, nacelleHeight/2 - bladeLength, 0f),
getBase().addY(poleHeight).add(-poleRadiusTop*2, nacelleHeight/2, -nacelleHeight/2)
);
List<VectorXYZ> bladeBack = asList(
bladeFront.get(0),
bladeFront.get(2),
bladeFront.get(1)
);
// rotate and draw blades
double rotCenterY = getBase().y + poleHeight + nacelleHeight/2;
double rotCenterZ = node.getPos().getZ();
bladeFront = rotateShapeX(bladeFront, 60, rotCenterY, rotCenterZ);
bladeBack = rotateShapeX(bladeBack, 60, rotCenterY, rotCenterZ);
target.drawTriangleStrip(bladeMaterial, bladeFront, null);
target.drawTriangleStrip(bladeMaterial, bladeBack, null);
bladeFront = rotateShapeX(bladeFront, 120, rotCenterY, rotCenterZ);
bladeBack = rotateShapeX(bladeBack, 120, rotCenterY, rotCenterZ);
target.drawTriangleStrip(bladeMaterial, bladeFront, null);
target.drawTriangleStrip(bladeMaterial, bladeBack, null);
bladeFront = rotateShapeX(bladeFront, 120, rotCenterY, rotCenterZ);
bladeBack = rotateShapeX(bladeBack, 120, rotCenterY, rotCenterZ);
target.drawTriangleStrip(bladeMaterial, bladeFront, null);
target.drawTriangleStrip(bladeMaterial, bladeBack, null);
}
}
private static class PowerMinorLine
extends NoOutlineWaySegmentWorldObject
implements RenderableToAllTargets {
private static final float DEFAULT_THICKN = 0.05f; // width and height
private static final float DEFAULT_CLEARING_BL = 7.5f; // power pole height is 8
private static final Material material = Materials.PLASTIC;
public PowerMinorLine(MapWaySegment segment) {
super(segment);
}
@Override
public GroundState getGroundState() {
return GroundState.ON;
}
@Override
public void renderTo(Target<?> target) {
List<VectorXYZ> powerlineShape = asList(
new VectorXYZ(-DEFAULT_THICKN/2, DEFAULT_CLEARING_BL, 0),
new VectorXYZ(-DEFAULT_THICKN/2, DEFAULT_CLEARING_BL + DEFAULT_THICKN, 0),
new VectorXYZ(+DEFAULT_THICKN/2, DEFAULT_CLEARING_BL + DEFAULT_THICKN, 0),
new VectorXYZ(+DEFAULT_THICKN/2, DEFAULT_CLEARING_BL, 0)
);
List<VectorXYZ> path = getBaseline();
List<List<VectorXYZ>> strips = createShapeExtrusionAlong(
powerlineShape,
path,
nCopies(path.size(), VectorXYZ.Y_UNIT));
for (List<VectorXYZ> strip : strips) {
target.drawTriangleStrip(material, strip, null);
}
}
}
private final static class PowerLine
extends NoOutlineWaySegmentWorldObject
implements RenderableToAllTargets {
private static final float CABLE_THICKNESS = 0.05f;
// TODO: we need black plastic for cable material
private final static Material CABLE_MATERIAL = Materials.PLASTIC;
private static final double SLACK_SPAN = 6;
private static final double INTERPOLATION_STEPS = 10;
// TODO: Once createShapeExtrusionAlong supports arbitrary shapes, we want to switch to a circle instead of a polygon
private static final double diag = Math.sqrt(2)*CABLE_THICKNESS/2;
private static final List<VectorXYZ> powerlineShape = asList(
new VectorXYZ(-diag, 0, 0),
new VectorXYZ(-CABLE_THICKNESS/2, CABLE_THICKNESS/2, 0),
new VectorXYZ(0, diag, 0),
new VectorXYZ(CABLE_THICKNESS/2, CABLE_THICKNESS/2, 0),
new VectorXYZ(diag, 0, 0),
new VectorXYZ(+CABLE_THICKNESS/2, -CABLE_THICKNESS/2, 0),
new VectorXYZ(0, -diag, 0),
new VectorXYZ(-CABLE_THICKNESS/2, -CABLE_THICKNESS/2, 0),
new VectorXYZ(-diag, 0, 0)
);
private int cables = -1;
private int voltage = -1;
private TowerConfig start;
private TowerConfig end;
private List<VectorXYZ> startPos = null;
private List<VectorXYZ> endPos = null;
public PowerLine(MapWaySegment line) {
super(line);
}
private void addPos(VectorXYZ baseStart, VectorXYZ baseEnd, double gotoRight, double up) {
startPos.add(baseStart.add(start.direction.rightNormal().mult(gotoRight)).add(0, up, 0));
endPos.add(baseEnd.add(end.direction.rightNormal().mult(gotoRight)).add(0, up, 0));
}
private void addPos(VectorXYZ baseStart, VectorXYZ baseEnd, double gotoRight, double upStart, double upEnd) {
startPos.add(baseStart.add(start.direction.rightNormal().mult(gotoRight)).add(0, upStart, 0));
endPos.add(baseEnd.add(end.direction.rightNormal().mult(gotoRight)).add(0, upEnd, 0));
}
private void setup() {
startPos = new ArrayList<VectorXYZ>();
endPos = new ArrayList<VectorXYZ>();
// check number of power lines
try {
cables = Integer.valueOf(segment.getTags().getValue("cables"));
} catch (NumberFormatException e) {}
// check voltage
try {
voltage = Integer.valueOf(segment.getTags().getValue("voltage"));
} catch (NumberFormatException e) {}
if (cables <= 0) {
return;
}
// get the tower configurations for start and end tower
start = generateTowerConfig(segment.getStartNode());
end = generateTowerConfig(segment.getEndNode());
if (!start.isHighVoltagePowerTower() && !end.isHighVoltagePowerTower()) {
// normal PowerTower...
double startHeight = parseHeight(start.pos.getTags(), 14) + 0.25;
double endHeight = parseHeight(end.pos.getTags(), 14) + 0.25;
VectorXYZ baseStart = getStartXYZ().addY(startHeight - 0.5);
VectorXYZ baseEnd = getEndXYZ().addY(endHeight - 0.5);
// power lines at the top left and right
addPos(baseStart, baseEnd, 2, 0.5);
addPos(baseStart, baseEnd, -2, 0.5);
if (cables >= 3) {
// additional power line at the top center
addPos(baseStart, baseEnd, 0, 0.5);
}
if (cables >= 5) {
// further power lines at the left and right below the column
addPos(baseStart, baseEnd, 1.5, -0.5);
addPos(baseStart, baseEnd, -1.5, -0.5);
}
} else {
// High voltage PowerTower ...
float default_height = voltage > 150000 ? 40 : 30;
float pole_width = voltage > 150000 ? 16 : 13;
double startHeight = parseHeight(start.pos.getTags(), default_height);
double endHeight = parseHeight(end.pos.getTags(), default_height);
double heightS = 2.5 * (((int) (startHeight / 2.5)) / 5);
double heightE = 2.5 * (((int) (endHeight / 2.5)) / 5);
VectorXYZ baseStart = getStartXYZ().addY(-0.5);
VectorXYZ baseEnd = getEndXYZ().addY(-0.5);
// power line at the tower's top
addPos(baseStart, baseEnd, 0, 5*heightS, 5*heightE);
// power lines start a little bit below the tower's columns
baseStart = baseStart.add(0, -0.2, 0);
baseEnd = baseEnd.add(0, -0.2, 0);
// power lines at the base column
addPos(baseStart, baseEnd, 0.9*pole_width, startHeight/2, endHeight/2);
addPos(baseStart, baseEnd, -0.9*pole_width, startHeight/2, endHeight/2);
if ((cables > 3) && (cables <= 9)) {
addPos(baseStart, baseEnd, 0.45*pole_width, startHeight/2, endHeight/2);
addPos(baseStart, baseEnd, -0.45*pole_width, startHeight/2, endHeight/2);
} else if (cables > 9) {
addPos(baseStart, baseEnd, 0.6*pole_width, startHeight/2, endHeight/2);
addPos(baseStart, baseEnd, -0.6*pole_width, startHeight/2, endHeight/2);
addPos(baseStart, baseEnd, 0.3*pole_width, startHeight/2, endHeight/2);
addPos(baseStart, baseEnd, -0.3*pole_width, startHeight/2, endHeight/2);
}
// additional power lines at the upper column
if (cables >= 7) {
addPos(baseStart, baseEnd, 0.9*0.6*pole_width, 4*heightS, 4*heightE);
addPos(baseStart, baseEnd, -0.9*0.6*pole_width, 4*heightS, 4*heightE);
if (cables >= 9) {
addPos(baseStart, baseEnd, 0.45*0.6*pole_width, 4*heightS, 4*heightE);
addPos(baseStart, baseEnd, -0.45*0.6*pole_width, 4*heightS, 4*heightE);
}
}
}
}
@Override
public GroundState getGroundState() {
return GroundState.ABOVE;
}
@Override
public void renderTo(Target<?> target) {
// do initial setup for height and position calculation, if necessary
if (startPos == null) {
setup();
}
for (int i = 0; i < startPos.size(); i++) {
VectorXYZ start = startPos.get(i);
VectorXYZ end = endPos.get(i);
double lenToEnd = end.distanceToXZ(start);
double heightDiff = end.y - start.y;
double stepSize = lenToEnd / INTERPOLATION_STEPS;
VectorXZ dir = end.xz().subtract(start.xz()).normalize();
List<VectorXYZ> path = new ArrayList<VectorXYZ>();
for (int x = 0; x <= INTERPOLATION_STEPS; x++) {
double ratio = x / INTERPOLATION_STEPS;
// distance from start to position x
double dx = stepSize * x;
// use a simple parabola between two towers
double height = (1 - Math.pow(2.0*(ratio - 0.5), 2)) * -SLACK_SPAN;
// add a linear function to account for different tower/terrain heights
height += ratio * heightDiff;
path.add(start.add(dir.mult(dx)).add(0, height, 0));
}
List<List<VectorXYZ>> strips = createShapeExtrusionAlong(
powerlineShape, path,
nCopies(path.size(), VectorXYZ.Y_UNIT));
for (List<VectorXYZ> strip : strips) {
target.drawTriangleStrip(CABLE_MATERIAL, strip, null);
}
}
}
}
private static final class PowerTower extends NoOutlineNodeWorldObject
implements RenderableToAllTargets {
private TowerConfig config;
public PowerTower(MapNode node, TowerConfig config) {
super(node);
this.config = config;
}
@Override
public GroundState getGroundState() {
return GroundState.ON;
}
// TODO we're missing the ceramics to hold the power lines
@Override
public void renderTo(Target<?> target) {
VectorXYZ base = getBase().addY(-0.5);
double height = parseHeight(node.getTags(), 14);
Material material = Materials.getSurfaceMaterial(node.getTags().getValue("material"));
if (material == null) {
material = Materials.getSurfaceMaterial(node.getTags().getValue("surface"), Materials.STEEL);
}
// draw base column
target.drawColumn(material, null, base, height, 0.5, 0.25, true, true);
// draw cross "column"
target.drawBox(material, base.add(0, height, 0), config.direction, 0.25, 5, 0.25);
// draw pieces holding the power lines
base = base.add(0, height + 0.25, 0);
target.drawColumn(Materials.CONCRETE, null, base.add(config.direction.rightNormal().mult(2)), 0.5, 0.1, 0.1, true, true);
target.drawColumn(Materials.CONCRETE, null, base.add(config.direction.rightNormal().mult(-2)), 0.5, 0.1, 0.1, true, true);
if (config.cables >= 3) {
target.drawColumn(Materials.CONCRETE, null, base, 0.5, 0.1, 0.1, true, true);
}
if (config.cables >= 5) {
target.drawColumn(Materials.CONCRETE, null, base.add(config.direction.rightNormal().mult(1.5)), -0.5, 0.1, 0.1, true, true);
target.drawColumn(Materials.CONCRETE, null, base.add(config.direction.rightNormal().mult(-1.5)), -0.5, 0.1, 0.1, true, true);
}
}
}
private static final class HighVoltagePowerTower extends NoOutlineNodeWorldObject
implements RenderableToAllTargets {
private TowerConfig config;
private VectorXZ direction;
public HighVoltagePowerTower(MapNode node, TowerConfig config) {
super(node);
this.config = config;
this.direction = config.direction;
}
@Override
public GroundState getGroundState() {
return GroundState.ON;
}
/**
* parse height tag or provide a reasonable default based on the tower's voltage
*/
private double getTowerHeight() {
float default_height = config.voltage > 150000 ? 40 : 30;
double height = parseHeight(node.getTags(), default_height);
return height;
}
private VectorXZ[][] getCorners(VectorXZ center, double diameter) {
double half = diameter/2;
VectorXZ ortho = direction.rightNormal();
VectorXZ right_in = center.add(direction.mult(half));
VectorXZ left_in = center.add(direction.mult(-half));
VectorXZ right_out = center.add(direction.mult(half));
VectorXZ left_out = center.add(direction.mult(-half));
// TODO: if we can switch off backface culling we'd only need one face here
return new VectorXZ[][]{
new VectorXZ[]{
right_in.add(ortho.mult(-half)),
right_in.add(ortho.mult(half)),
left_in.add(ortho.mult(half)),
left_in.add(ortho.mult(-half))
},
new VectorXZ[]{
right_out.add(ortho.mult(half)),
right_out.add(ortho.mult(-half)),
left_out.add(ortho.mult(-half)),
left_out.add(ortho.mult(half))
}};
}
private void drawSegment(Target<?> target,
VectorXZ[] low, VectorXZ[] high, double base, double height) {
for (int a = 0; a < 4; a++) {
List<VectorXYZ> vs = new ArrayList<VectorXYZ>();
List<VectorXZ> tex = new ArrayList<VectorXZ>();
List<List<VectorXZ>> texList =
nCopies(Materials.POWER_TOWER_VERTICAL.getNumTextureLayers(), tex);
for (int i = 0; i < 2; i++) {
int idx = (a+i)%4;
vs.add(high[idx].xyz(height));
vs.add(low[idx].xyz(base));
tex.add(new VectorXZ(i, 1));
tex.add(new VectorXZ(i, 0));
}
target.drawTriangleStrip(Materials.POWER_TOWER_VERTICAL, vs, texList);
}
}
private void drawHorizontalSegment(Target<?> target,
VectorXZ left, VectorXZ right, double base,
double left_height, double right_height) {
List<VectorXYZ> vs = new ArrayList<VectorXYZ>();
List<VectorXZ> tex = new ArrayList<VectorXZ>();
List<List<VectorXZ>> texList =
nCopies(Materials.POWER_TOWER_HORIZONTAL.getNumTextureLayers(), tex);
vs.add(right.xyz(base));
vs.add(left.xyz(base));
vs.add(right.xyz(base+right_height));
vs.add(left.xyz(base+left_height));
tex.add(new VectorXZ(1, 1));
tex.add(new VectorXZ(0, 1));
tex.add(new VectorXZ(1, 0));
tex.add(new VectorXZ(0, 0));
target.drawTriangleStrip(Materials.POWER_TOWER_HORIZONTAL, vs, texList);
}
private void drawHorizontalTop(Target<?> target, VectorXZ[][] points,
double base, double border, double middle, double center) {
double[] height = new double[]{border, middle, center, center, middle, border};
int len = height.length;
for (int a = 0; a < len-1; a++) {
List<VectorXYZ> vs = new ArrayList<VectorXYZ>();
List<VectorXZ> tex = new ArrayList<VectorXZ>();
List<List<VectorXZ>> texList =
nCopies(Materials.POWER_TOWER_VERTICAL.getNumTextureLayers(), tex);
for (int i = 0; i < 2; i++) {
vs.add(points[1][a+i].xyz(base + height[a+i]));
vs.add(points[2][a+i].xyz(base + height[a+i]));
tex.add(new VectorXZ(0, i));
tex.add(new VectorXZ(1, i));
}
target.drawTriangleStrip(Materials.POWER_TOWER_VERTICAL, vs, texList);
}
}
private double drawPart(Target<?> target, double elevation,
int nr_segments, double segment_height, double ground_size,
double top_size) {
for (int i = 0; i < nr_segments; i++) {
double bottom = ground_size + i * (top_size - ground_size) / nr_segments;
double top = ground_size + (i + 1) * (top_size - ground_size) / nr_segments;
VectorXZ[][] low = getCorners(node.getPos(), bottom);
VectorXZ[][] high = getCorners(node.getPos(), top);
drawSegment(target, low[0], high[0], elevation, elevation + segment_height);
drawSegment(target, low[1], high[1], elevation, elevation + segment_height);
elevation += segment_height;
}
return elevation;
}
private VectorXZ[] getPoleCoordinates(VectorXZ base,
VectorXZ direction, double width, double size) {
return new VectorXZ[] {
base.add(direction.mult(-width - size)),
base.add(direction.mult(-width / 2 - size)),
base.add(direction.mult(-size)),
base.add(direction.mult(size)),
base.add(direction.mult(width / 2 + size)),
base.add(direction.mult(width + size))
};
}
private void drawHorizontalPole(Target<?> target, double elevation,
double diameter, double width) {
double half = diameter / 2;
VectorXZ ortho = direction.rightNormal();
// TODO: if we can switch off backface culling we'd only need one face here
VectorXZ[][] draw = new VectorXZ[][] {
getPoleCoordinates(node.getPos().add(direction.mult(-half)), ortho, width, half),
getPoleCoordinates(node.getPos().add(direction.mult(-half)), ortho.invert(), width, half),
getPoleCoordinates(node.getPos().add(direction.mult(half)), ortho.invert(), width, half),
getPoleCoordinates(node.getPos().add(direction.mult(half)), ortho, width, half)
};
for (int i = 0; i < 4; i++) {
drawHorizontalSegment(target, draw[i][0], draw[i][1], elevation, 0.1, diameter/2);
drawHorizontalSegment(target, draw[i][1], draw[i][2], elevation, diameter/2, diameter);
drawHorizontalSegment(target, draw[i][2], draw[i][3], elevation, diameter, diameter);
drawHorizontalSegment(target, draw[i][3], draw[i][4], elevation, diameter, diameter/2);
drawHorizontalSegment(target, draw[i][4], draw[i][5], elevation, diameter/2, 0.1);
}
drawHorizontalTop(target, draw, elevation, 0.1, diameter/2, diameter);
}
// TODO we're missing the ceramics to hold the power lines
@Override
public void renderTo(Target<?> target) {
float pole_width = config.voltage > 150000 ? 16 : 13;
float[] tower_width = config.voltage > 150000 ? new float[]{11,6,4f,0} : new float[]{8,5,3,0};
double height = getTowerHeight();
double segment_height = 2.5;
double base = getBase().y - 0.5;
int parts = (int) (height / segment_height);
int low_parts = parts / 5;
// draw the tower itself
double ele = drawPart(target, base, low_parts, segment_height, tower_width[0], tower_width[1]);
ele = drawPart(target, ele, 3 * low_parts, segment_height, tower_width[1], tower_width[2]);
drawPart(target, ele, low_parts, segment_height, tower_width[2], tower_width[3]);
// draw the vertical poles
drawHorizontalPole(target, base + height/2, 0.7*tower_width[1], pole_width);
if (config.cables > 6) {
drawHorizontalPole(target, ele, 0.55*tower_width[2], 0.6*pole_width);
}
}
}
private static final class PhotovoltaicPlant extends AbstractAreaWorldObject
implements RenderableToAllTargets {
//TODO create individual EleConnector for panels
/** compares vectors by x coordinate */
private static final Comparator<VectorXZ> X_COMPARATOR = new Comparator<VectorXZ>() {
@Override public int compare(VectorXZ v1, VectorXZ v2) {
return Double.compare(v1.x, v2.x);
}
};
protected PhotovoltaicPlant(MapArea area) {
super(area);
}
@Override
public GroundState getGroundState() {
return GroundState.ON;
}
@Override
public void renderTo(Target<?> target) {
/* construct panel geometry */
double panelAngle = PI / 4;
double panelHeight = 5;
VectorXYZ upVector = Z_UNIT.mult(panelHeight).rotateX(-panelAngle);
/* place and draw rows of panels */
AxisAlignedBoundingBoxXZ box = this.getAxisAlignedBoundingBoxXZ();
List<SimplePolygonXZ> obstacles = getGroundObstacles();
double posZ = box.minZ;
while (posZ + upVector.z < box.maxZ) {
LineSegmentXZ rowLine = new LineSegmentXZ(
new VectorXZ(box.minX - 10, posZ),
new VectorXZ(box.maxX + 10, posZ));
// calculate start and end points (maybe more than one each)
List<VectorXZ> intersections =
area.getPolygon().intersectionPositions(rowLine);
assert intersections.size() % 2 == 0;
sort(intersections, X_COMPARATOR);
// add more start/end points at ground-level obstacles
for (SimplePolygonXZ obstacle : obstacles) {
List<VectorXZ> obstacleIntersections =
obstacle.intersectionPositions(rowLine);
for (int i = 0; i + 1 < obstacleIntersections.size(); i += 2) {
int insertionIndexA = -binarySearch(intersections,
obstacleIntersections.get(i), X_COMPARATOR) - 1;
int insertionIndexB = -binarySearch(intersections,
obstacleIntersections.get(i + 1), X_COMPARATOR) - 1;
sort(obstacleIntersections, X_COMPARATOR);
if (insertionIndexA == insertionIndexB
&& insertionIndexA >= 0
&& insertionIndexA % 2 == 1) {
intersections.add(insertionIndexA,
obstacleIntersections.get(i+1));
intersections.add(insertionIndexA,
obstacleIntersections.get(i));
}
}
}
// draw row of panels between each start/end pair
assert intersections.size() % 2 == 0;
for (int i = 0; i + 1 < intersections.size(); i += 2) {
// TODO: take elevation into account
// Might necessitate individual panels or shorter strips.
// renderPanelsTo(target,
// eleProfile.getWithEle(intersections.get(i)),
// eleProfile.getWithEle(intersections.get(i+1)),
// upVector);
}
posZ += upVector.z * 1.5;
}
}
/**
* returns outlines from ground objects overlapping this area
*/
private List<SimplePolygonXZ> getGroundObstacles() {
List<SimplePolygonXZ> obstacles = new ArrayList<SimplePolygonXZ>();
for (MapOverlap<?, ?> overlap : area.getOverlaps()) {
for (WorldObject otherWO : overlap.getOther(area).getRepresentations()) {
if (otherWO.getGroundState() == GroundState.ON
&& otherWO instanceof WorldObjectWithOutline) {
obstacles.add(((WorldObjectWithOutline)otherWO).getOutlinePolygonXZ());
}
}
}
return obstacles;
}
private void renderPanelsTo(Target<?> target, VectorXYZ bottomLeft,
VectorXYZ bottomRight, VectorXYZ upVector) {
/* draw front */
List<VectorXYZ> vs = asList(
bottomLeft.add(upVector),
bottomLeft,
bottomRight.add(upVector),
bottomRight);
target.drawTriangleStrip(Materials.SOLAR_PANEL, vs,
texCoordLists(vs, Materials.SOLAR_PANEL, STRIP_WALL));
/* draw back */
vs = asList(vs.get(2), vs.get(3), vs.get(0), vs.get(1));
target.drawTriangleStrip(Materials.PLASTIC_GREY, vs,
texCoordLists(vs, Materials.PLASTIC_GREY, STRIP_WALL));
}
}
}