From 0cdd6472627dbd99c3ad3054533049d1b6ece4e1 Mon Sep 17 00:00:00 2001 From: kiselev-dv Date: Wed, 5 Jul 2017 16:15:09 -0300 Subject: [PATCH 1/9] Simple external obj loader --- .../core/external_models/ExternalModel.java | 51 +++ .../external_models/objparser/ObjParser.java | 405 ++++++++++++++++++ .../core/world/modules/BuildingModule.java | 18 +- 3 files changed, 472 insertions(+), 2 deletions(-) create mode 100644 src/org/osm2world/core/external_models/ExternalModel.java create mode 100644 src/org/osm2world/core/external_models/objparser/ObjParser.java diff --git a/src/org/osm2world/core/external_models/ExternalModel.java b/src/org/osm2world/core/external_models/ExternalModel.java new file mode 100644 index 000000000..8878cd7c9 --- /dev/null +++ b/src/org/osm2world/core/external_models/ExternalModel.java @@ -0,0 +1,51 @@ +package org.osm2world.core.external_models; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; + +import org.osm2world.core.external_models.objparser.ObjParser; +import org.osm2world.core.external_models.objparser.ObjParser.ObjFace; +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.world.data.WorldObject; + + +public class ExternalModel implements RenderableToAllTargets { + + private ObjParser model; + + private double rot = Math.toRadians(0.0); + private VectorXZ origin = new VectorXZ(0.0, 0.0); + private double scale = 1.0; + + public ExternalModel(WorldObject object, File model) { + this.model = new ObjParser(model); + + this.scale = 1.0 / 100.0; + + // Just for test + this.rot = Math.toRadians(30.0); + origin = object.getPrimaryMapElement().getAxisAlignedBoundingBoxXZ().center(); + } + + @Override + public void renderTo(Target target) { + for(ObjFace f : this.model.listFaces()) { + List vs = new ArrayList<>(f.vs.size()); + for (VectorXYZ src : f.vs) { + VectorXYZ t = src.rotateY(rot); + t = t.add(origin); + t = t.mult(scale); + vs.add(t); + } + if (f.material != null) { + target.drawTriangleFan(f.material, vs, f.texCoordLists); + } + } + } + + +} diff --git a/src/org/osm2world/core/external_models/objparser/ObjParser.java b/src/org/osm2world/core/external_models/objparser/ObjParser.java new file mode 100644 index 000000000..b15313fdc --- /dev/null +++ b/src/org/osm2world/core/external_models/objparser/ObjParser.java @@ -0,0 +1,405 @@ +package org.osm2world.core.external_models.objparser; + +import java.awt.Color; +import java.awt.image.BufferedImage; +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.imageio.ImageIO; + +import org.apache.commons.lang.StringUtils; +import org.osm2world.core.math.VectorXYZ; +import org.osm2world.core.math.VectorXZ; +import org.osm2world.core.target.common.TextureData; +import org.osm2world.core.target.common.TextureData.Wrap; +import org.osm2world.core.target.common.material.ConfMaterial; +import org.osm2world.core.target.common.material.Material; +import org.osm2world.core.target.common.material.Material.Interpolation; +import org.osm2world.core.target.common.material.Material.Shadow; +import org.osm2world.core.target.common.material.Material.Transparency; +import org.osm2world.core.target.common.material.NamedTexCoordFunction; + +public class ObjParser { + + private File objFile; + private Map materials = new HashMap<>(); + private ObjMaterial curentMtl; + + private List vertexes = new ArrayList<>(); + private List vertexNorms = new ArrayList<>(); + private List textureVertexes = new ArrayList<>(); + private Material faceMtl; + private List faces = new ArrayList<>(); + + private static final class ObjMaterial { + File materialFile; + public String name; + + float ni; + + float[] ka; + float[] kd; + float[] ks; + + String map_Ka; + String map_Kd; + + } + + public ObjParser(File objFile) { + this.objFile = objFile; + + try { + this.iterateOverObjFile(); + } + catch (Exception e) { + e.printStackTrace(); + } + } + + private void iterateOverObjFile() throws IOException { + BufferedReader br = new BufferedReader(new FileReader(objFile)); + String line = br.readLine(); + while (line != null) + { + line = StringUtils.strip(line); + if (line.startsWith("#")) { + handleObjComment(line); + } + else if (line.startsWith("call")){ + handleCall(line); + } + else if (line.startsWith("mtllib")){ + handleMtlLib(line); + } + else { + handleObjLine(line); + } + + line = br.readLine(); + } + br.close(); + clear(); + } + + private void handleCall(String line) { + System.err.println("call statement in obj is not supported"); + } + + private void handleMtlLib(String line) throws IOException { + String mtlPath = StringUtils.strip(line.replace("mtllib", "")); + File mtlFile = new File(mtlPath); + if(!mtlFile.isAbsolute()) { + mtlFile = new File(this.objFile.getParent(), mtlPath); + } + + parseMtlFile(mtlFile); + } + + private void parseMtlFile(File mtlFile) throws IOException { + BufferedReader br = new BufferedReader(new FileReader(mtlFile)); + String line = StringUtils.strip(br.readLine()); + while (line != null) + { + if (line.startsWith("#")) { + handleMtlComment(line); + } + else if(line.startsWith("newmtl")) { + handleMtlNewMtl(line, mtlFile); + } + else if(line.startsWith("Ka")) { + handleMtlKa(line); + } + else if(line.startsWith("Kd")) { + handleMtlKd(line); + } + else if(line.startsWith("Ks")) { + handleMtlKs(line); + } + else if(line.startsWith("Ni")) { + handleMtlNi(line); + } + else if(line.startsWith("map_Ka")) { + handleMtlMapKa(line); + } + else if(line.startsWith("map_Kd")) { + handleMtlMapKd(line); + } + + line = StringUtils.strip(br.readLine()); + line = StringUtils.remove(line, '\t'); + } + br.close(); + parseCurentMtl(); + } + + private void handleMtlMapKd(String line) { + this.curentMtl.map_Kd = line.replace("map_Kd ", "").trim(); + } + + private void handleMtlMapKa(String line) { + this.curentMtl.map_Ka = line.replace("map_Ka ", "").trim(); + } + + private void handleMtlNi(String line) { + this.curentMtl.ni = + Float.valueOf(line.replace("Ni ", "")); + } + + private void handleMtlKs(String line) { + String[] split = line.replace("Ks ", "").split(" "); + this.curentMtl.ks = new float[]{ + Float.valueOf(split[0]), + Float.valueOf(split[1]), + Float.valueOf(split[2]) + }; + } + + private void handleMtlKd(String line) { + String[] split = line.replace("Kd ", "").split(" "); + this.curentMtl.kd = new float[]{ + Float.valueOf(split[0]), + Float.valueOf(split[1]), + Float.valueOf(split[2]) + }; + } + + private void handleMtlKa(String line) { + String[] split = line.replace("Ka ", "").split(" "); + this.curentMtl.ka = new float[]{ + Float.valueOf(split[0]), + Float.valueOf(split[1]), + Float.valueOf(split[2]) + }; + } + + private void handleMtlNewMtl(String line, File mtlFile) throws IOException { + parseCurentMtl(); + + String name = StringUtils.strip(line.replace("newmtl", "")); + this.curentMtl = new ObjMaterial(); + this.curentMtl.name = name; + this.curentMtl.materialFile = mtlFile; + } + + private void parseCurentMtl() throws IOException { + if (this.curentMtl != null && this.curentMtl.name != null) { + + boolean mapsEmpty = this.curentMtl.map_Ka == null && this.curentMtl.map_Kd == null; + float ambientAverage = (this.curentMtl.ka[0] + this.curentMtl.ka[1] + this.curentMtl.ka[2]) / 3.0f; + float diffuseAverage = (this.curentMtl.kd[0] + this.curentMtl.kd[1] + this.curentMtl.kd[2]) / 3.0f; + boolean kaComponentsEqual = + this.curentMtl.ka[0] == this.curentMtl.ka[1] && this.curentMtl.ka[1] == this.curentMtl.ka[2]; + boolean kdComponentsEqual = + this.curentMtl.kd[0] == this.curentMtl.kd[1] && this.curentMtl.kd[1] == this.curentMtl.kd[2]; + + boolean kaCorrespondKd = Math.abs(ambientAverage + diffuseAverage - 1.0) < 0.0001f; + boolean colorable = mapsEmpty || !kaComponentsEqual || !kdComponentsEqual || !kaCorrespondKd; + + float specularAverage = (this.curentMtl.ks[0] + this.curentMtl.ks[1] + this.curentMtl.ks[2]) / 3.0f; + Float shiness = this.curentMtl.ni; + + Color diffuseColor = new Color( + (int)(this.curentMtl.kd[0] * 255), + (int)(this.curentMtl.kd[1] * 255), + (int)(this.curentMtl.kd[2] * 255)); + + Color ambientColor = new Color( + (int)(this.curentMtl.ka[0] * 255), + (int)(this.curentMtl.ka[1] * 255), + (int)(this.curentMtl.ka[2] * 255)); + + int rm = Math.min(diffuseColor.getRed() + ambientColor.getRed(), 255); + int gm = Math.min(diffuseColor.getGreen() + ambientColor.getGreen(), 255); + int bm = Math.min(diffuseColor.getBlue() + ambientColor.getBlue(), 255); + + Color meanColor = new Color(rm, gm, bm); + + ConfMaterial m = new ConfMaterial(Interpolation.FLAT, meanColor); + + m.setAmbientFactor(ambientAverage); + m.setDiffuseFactor(diffuseAverage); + + m.setSpecularFactor(specularAverage); + m.setShininess(shiness.intValue()); + + m.setInterpolation(Interpolation.FLAT); + m.setShadow(Shadow.FALSE); + m.setTransparency(Transparency.FALSE); + + if (!mapsEmpty) { + File texture = resolveTexture( + this.curentMtl.map_Ka, + this.curentMtl.materialFile); + + BufferedImage bimg = ImageIO.read(texture); + + List tl = new ArrayList<>(); + + tl.add(new TextureData(texture, bimg.getWidth(), bimg.getHeight(), + Wrap.REPEAT, NamedTexCoordFunction.GLOBAL_X_Z, colorable, false)); + + m.setTextureDataList(tl); + } + + this.materials.put(curentMtl.name, m); + } + } + + private File resolveTexture(String texture, File materialFile) { + return new File(materialFile.getParentFile(), texture); + } + + private void handleMtlComment(String line) { + // Do nothing + } + + private void handleObjLine(String line) { + if (line.startsWith("v ")) { + String[] split = line.replace("v ", "").trim().split(" "); + handleVertex(new VectorXYZ( + Double.valueOf(split[0]), + Double.valueOf(split[1]), + Double.valueOf(split[2]))); + } + else if (line.startsWith("vt ")) { + String[] split = line.replace("vt ", "").trim().split(" "); + handleTextureVertex(new VectorXZ( + Double.valueOf(split[0]), + Double.valueOf(split[1]))); + } + else if (line.startsWith("vn ")) { + String[] split = line.replace("vn ", "").trim().split(" "); + handleVertexNormal(new VectorXYZ( + Double.valueOf(split[0]), + Double.valueOf(split[1]), + Double.valueOf(split[2]))); + } + else if (line.startsWith("g ")) { + String groupName = line.replace("g ", "").trim(); + handleGroup(groupName); + } + else if (line.startsWith("usemtl ")) { + String mtlName = line.replace("usemtl ", "").trim(); + handleUseMtl(mtlName); + } + else if (line.startsWith("s ")) { + String smoothGroup = line.replace("s ", "").trim(); + handleSmoothGroup(smoothGroup); + } + else if (line.startsWith("f ")) { + String faceLine = line.replace("f ", "").trim(); + handleFace(faceLine); + } + } + + private void handleVertexNormal(VectorXYZ vectorXYZ) { + vertexNorms.add(vectorXYZ); + } + + public static final class ObjFace { + public Material material; + public List vs; + public List normals; + public List> texCoordLists; + } + + private void handleFace(String faceLine) { + ObjFace f = new ObjFace(); + f.material = this.faceMtl; + String[] points = faceLine.split(" "); + for (String pointRef : points) { + String[] components = pointRef.split("/"); + Integer vi = Integer.valueOf(components[0]); + if (f.vs == null) { + f.vs = new ArrayList<>(); + } + + if (vi >= 0) { + f.vs.add(this.vertexes.get(vi)); + } + else { + // it's negative so use minus + f.vs.add(this.vertexes.get(this.vertexes.size() + vi)); + } + + if (components.length > 1) { + if(StringUtils.stripToNull(components[1]) != null) { + + if (f.texCoordLists == null) { + f.texCoordLists = new ArrayList<>(); + f.texCoordLists.add(new ArrayList<>()); + } + + int tvi = Integer.valueOf(components[1]); + if (tvi >= 0) { + f.texCoordLists.get(0).add(textureVertexes.get(tvi)); + } + else { + f.texCoordLists.get(0).add(textureVertexes.get( + textureVertexes.size() + tvi)); + } + } + } + + if (components.length > 2) { + if(f.normals == null) { + f.normals = new ArrayList<>(); + } + int vni = Integer.valueOf(components[2]); + + if (vni >= 0) { + f.normals.add(this.vertexNorms.get(vni)); + } + else { + f.normals.add(this.vertexNorms.get( + this.vertexNorms.size() + vni)); + } + } + } + this.faces.add(f); + } + + public List listFaces() { + return faces; + } + + private void handleGroup(String groupName) { + + } + + private void handleSmoothGroup(String smoothGroup) { + + } + + private void handleUseMtl(String mtlName) { + this.faceMtl = this.materials.get(mtlName); + if (this.faceMtl == null) { + System.err.println("Warn: material " + mtlName + " not found"); + } + } + + private void handleTextureVertex(VectorXZ vectorXZ) { + this.textureVertexes.add(vectorXZ); + } + + private void handleVertex(VectorXYZ vectorXYZ) { + this.vertexes.add(vectorXYZ); + } + + private void handleObjComment(String line) { + // Do Nothing + } + + private void clear() { + this.textureVertexes = null; + this.vertexes = null; + } + +} diff --git a/src/org/osm2world/core/world/modules/BuildingModule.java b/src/org/osm2world/core/world/modules/BuildingModule.java index 0e71d17f0..8fbefb126 100644 --- a/src/org/osm2world/core/world/modules/BuildingModule.java +++ b/src/org/osm2world/core/world/modules/BuildingModule.java @@ -14,6 +14,7 @@ import static org.osm2world.core.world.modules.common.WorldModuleParseUtil.*; import java.awt.Color; +import java.io.File; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -25,6 +26,7 @@ import java.util.Set; import org.openstreetmap.josm.plugins.graphview.core.data.TagGroup; +import org.osm2world.core.external_models.ExternalModel; import org.osm2world.core.map_data.data.MapArea; import org.osm2world.core.map_data.data.MapData; import org.osm2world.core.map_data.data.MapElement; @@ -106,11 +108,18 @@ public static class Building implements AreaWorldObject, private final EleConnectorGroup outlineConnectors; + private ExternalModel model; + public Building(MapArea area, boolean useBuildingColors, boolean drawBuildingWindows) { this.area = area; + if (area.getOsmObject().id == 3696235) { + model = new ExternalModel(this, + new File("/opt/ep/data/house-model/haus06.obj")); + } + for (MapOverlap overlap : area.getOverlaps()) { MapElement other = overlap.getOther(area); if (other instanceof MapArea @@ -285,9 +294,14 @@ public PolygonXYZ getOutlinePolygon() { @Override public void renderTo(Target target) { - for (BuildingPart part : parts) { - part.renderTo(target); + if (this.model != null) { + this.model.renderTo(target); } +// else { +// for (BuildingPart part : parts) { +// part.renderTo(target); +// } +// } } } From 14bd0d61f05ad31dfe0e069cfe90a6d16002e8fc Mon Sep 17 00:00:00 2001 From: kiselev-dv Date: Tue, 18 Jul 2017 10:06:13 -0300 Subject: [PATCH 2/9] resolve paths relative to shell script --- build-adds/osm2world.sh | 5 +++-- .../objparser/{ObjParser.java => ObjModel.java} | 0 2 files changed, 3 insertions(+), 2 deletions(-) rename src/org/osm2world/core/external_models/objparser/{ObjParser.java => ObjModel.java} (100%) diff --git a/build-adds/osm2world.sh b/build-adds/osm2world.sh index 74e2d6cf0..6b5460468 100755 --- a/build-adds/osm2world.sh +++ b/build-adds/osm2world.sh @@ -11,6 +11,7 @@ if [[ $1 == --vm-params=* ]] fi # choose path for the native JOGL libs depending on system and java version +script_dir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" MACHINE_TYPE=`uname -m` KERNEL_NAME=`uname -s` @@ -27,6 +28,6 @@ fi # run OSM2World -export LD_LIBRARY_PATH=$lpsolvepath +export LD_LIBRARY_PATH=$script_dir/$lpsolvepath -java -Djava.library.path=$lpsolvepath $vmparams -jar OSM2World.jar --config texture_config.properties $@ +java -Djava.library.path=$lpsolvepath $vmparams -jar $script_dir/OSM2World.jar --config texture_config.properties $@ diff --git a/src/org/osm2world/core/external_models/objparser/ObjParser.java b/src/org/osm2world/core/external_models/objparser/ObjModel.java similarity index 100% rename from src/org/osm2world/core/external_models/objparser/ObjParser.java rename to src/org/osm2world/core/external_models/objparser/ObjModel.java From 05160b68445688e2a4a9b43c7407270fa7e9d7ab Mon Sep 17 00:00:00 2001 From: kiselev-dv Date: Wed, 18 Oct 2017 19:32:55 -0300 Subject: [PATCH 3/9] Merged with Tobias' patch --- src/org/osm2world/core/ConversionFacade.java | 2 + .../core/external_models/ExternalModel.java | 51 ----- .../creation/OSMToMapDataConverter.java | 3 +- .../osm2world/core/map_data/data/MapData.java | 10 +- src/org/osm2world/core/target/Target.java | 13 ++ .../core/target/common/AbstractTarget.java | 9 + .../core/target/common/material/Material.java | 12 ++ .../core/target/common/model/Model.java | 23 ++ .../common/model/obj/ExternalModel.java | 66 ++++++ .../model/obj/parser/ModelLinksProxy.java | 111 ++++++++++ .../common/model/obj/parser}/ObjModel.java | 196 +++++++++++------- .../core/world/modules/BuildingModule.java | 60 +++--- .../world/modules/ExternalModelModule.java | 179 ++++++++++++++++ 13 files changed, 578 insertions(+), 157 deletions(-) delete mode 100644 src/org/osm2world/core/external_models/ExternalModel.java create mode 100644 src/org/osm2world/core/target/common/model/Model.java create mode 100644 src/org/osm2world/core/target/common/model/obj/ExternalModel.java create mode 100644 src/org/osm2world/core/target/common/model/obj/parser/ModelLinksProxy.java rename src/org/osm2world/core/{external_models/objparser => target/common/model/obj/parser}/ObjModel.java (64%) create mode 100644 src/org/osm2world/core/world/modules/ExternalModelModule.java diff --git a/src/org/osm2world/core/ConversionFacade.java b/src/org/osm2world/core/ConversionFacade.java index 113f7cb0e..be7b53d71 100644 --- a/src/org/osm2world/core/ConversionFacade.java +++ b/src/org/osm2world/core/ConversionFacade.java @@ -46,6 +46,7 @@ import org.osm2world.core.world.modules.BridgeModule; import org.osm2world.core.world.modules.BuildingModule; import org.osm2world.core.world.modules.CliffModule; +import org.osm2world.core.world.modules.ExternalModelModule; import org.osm2world.core.world.modules.GolfModule; import org.osm2world.core.world.modules.InvisibleModule; import org.osm2world.core.world.modules.ParkingModule; @@ -130,6 +131,7 @@ public Collection getRenderables( private static final List createDefaultModuleList() { return Arrays.asList((WorldModule) + new ExternalModelModule(), new RoadModule(), new RailwayModule(), new BuildingModule(), diff --git a/src/org/osm2world/core/external_models/ExternalModel.java b/src/org/osm2world/core/external_models/ExternalModel.java deleted file mode 100644 index 8878cd7c9..000000000 --- a/src/org/osm2world/core/external_models/ExternalModel.java +++ /dev/null @@ -1,51 +0,0 @@ -package org.osm2world.core.external_models; - -import java.io.File; -import java.util.ArrayList; -import java.util.List; - -import org.osm2world.core.external_models.objparser.ObjParser; -import org.osm2world.core.external_models.objparser.ObjParser.ObjFace; -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.world.data.WorldObject; - - -public class ExternalModel implements RenderableToAllTargets { - - private ObjParser model; - - private double rot = Math.toRadians(0.0); - private VectorXZ origin = new VectorXZ(0.0, 0.0); - private double scale = 1.0; - - public ExternalModel(WorldObject object, File model) { - this.model = new ObjParser(model); - - this.scale = 1.0 / 100.0; - - // Just for test - this.rot = Math.toRadians(30.0); - origin = object.getPrimaryMapElement().getAxisAlignedBoundingBoxXZ().center(); - } - - @Override - public void renderTo(Target target) { - for(ObjFace f : this.model.listFaces()) { - List vs = new ArrayList<>(f.vs.size()); - for (VectorXYZ src : f.vs) { - VectorXYZ t = src.rotateY(rot); - t = t.add(origin); - t = t.mult(scale); - vs.add(t); - } - if (f.material != null) { - target.drawTriangleFan(f.material, vs, f.texCoordLists); - } - } - } - - -} diff --git a/src/org/osm2world/core/map_data/creation/OSMToMapDataConverter.java b/src/org/osm2world/core/map_data/creation/OSMToMapDataConverter.java index 5b3c8bc19..37a32dd02 100644 --- a/src/org/osm2world/core/map_data/creation/OSMToMapDataConverter.java +++ b/src/org/osm2world/core/map_data/creation/OSMToMapDataConverter.java @@ -58,7 +58,6 @@ public class OSMToMapDataConverter { private final Configuration config; private static final Tag MULTIPOLYON_TAG = new Tag("type", "multipolygon"); - public OSMToMapDataConverter(MapProjection mapProjection, Configuration config) { this.mapProjection = mapProjection; @@ -73,7 +72,7 @@ public MapData createMapData(OSMData osmData) throws IOException { createMapElements(osmData, mapNodes, mapWaySegs, mapAreas); - MapData mapData = new MapData(mapNodes, mapWaySegs, mapAreas, + MapData mapData = new MapData(mapProjection, mapNodes, mapWaySegs, mapAreas, calculateFileBoundary(osmData.getBounds())); calculateIntersectionsInMapData(mapData); diff --git a/src/org/osm2world/core/map_data/data/MapData.java b/src/org/osm2world/core/map_data/data/MapData.java index ae4d159ee..b2b878e2b 100644 --- a/src/org/osm2world/core/map_data/data/MapData.java +++ b/src/org/osm2world/core/map_data/data/MapData.java @@ -3,6 +3,7 @@ import java.util.Collection; import java.util.List; +import org.osm2world.core.map_data.creation.MapProjection; import org.osm2world.core.math.AxisAlignedBoundingBoxXZ; import org.osm2world.core.math.VectorXZ; import org.osm2world.core.osm.data.OSMData; @@ -23,13 +24,14 @@ public class MapData { final List mapNodes; final List mapWaySegments; final List mapAreas; + final MapProjection mapProjection; AxisAlignedBoundingBoxXZ fileBoundary; AxisAlignedBoundingBoxXZ dataBoundary; - public MapData(List mapNodes, List mapWaySegments, + public MapData(MapProjection mapProjection, List mapNodes, List mapWaySegments, List mapAreas, AxisAlignedBoundingBoxXZ fileBoundary) { - + this.mapProjection = mapProjection; this.mapNodes = mapNodes; this.mapWaySegments = mapWaySegments; this.mapAreas = mapAreas; @@ -145,5 +147,9 @@ public Iterable getWorldObjects() { public Iterable getWorldObjects(Class type) { return Iterables.filter(getWorldObjects(), type); } + + public MapProjection getMapProjection() { + return this.mapProjection; + } } diff --git a/src/org/osm2world/core/target/Target.java b/src/org/osm2world/core/target/Target.java index f076af1fc..e3d1b09f3 100644 --- a/src/org/osm2world/core/target/Target.java +++ b/src/org/osm2world/core/target/Target.java @@ -9,6 +9,7 @@ import org.osm2world.core.math.VectorXYZ; import org.osm2world.core.math.VectorXZ; import org.osm2world.core.target.common.material.Material; +import org.osm2world.core.target.common.model.Model; import org.osm2world.core.world.data.WorldObject; /** @@ -110,6 +111,18 @@ void drawColumn(Material material, Integer corners, VectorXYZ base, double height, double radiusBottom, double radiusTop, boolean drawBottom, boolean drawTop); + /** + * draws an instance of a 3D model + * + * @param model the model to be drawn + * @param direction rotation of the model in the XZ plane, as an angle in radians + * @param height height of the model; null for default (unspecified) height + * @param width width of the model; null for default (unspecified) width + * @param length length of the model; null for default (unspecified) length + */ + public void drawModel(Model model, VectorXYZ position, + double direction, Double height, Double width, Double length); + /** * gives the target the chance to perform finish/cleanup operations * after all objects have been drawn. diff --git a/src/org/osm2world/core/target/common/AbstractTarget.java b/src/org/osm2world/core/target/common/AbstractTarget.java index d4ceb050f..b150a24d6 100644 --- a/src/org/osm2world/core/target/common/AbstractTarget.java +++ b/src/org/osm2world/core/target/common/AbstractTarget.java @@ -16,6 +16,7 @@ import org.osm2world.core.target.Renderable; import org.osm2world.core.target.Target; import org.osm2world.core.target.common.material.Material; +import org.osm2world.core.target.common.model.Model; import org.osm2world.core.world.data.WorldObject; /** @@ -196,6 +197,14 @@ public void drawConvexPolygon(Material material, List vs, drawTriangleFan(material, vs, texCoordLists); } + @Override + public void drawModel(Model model, VectorXYZ position, + double direction, Double height, Double width, Double length) { + + model.render(this, position, direction, height, width, length); + + } + @Override public void finish() {} diff --git a/src/org/osm2world/core/target/common/material/Material.java b/src/org/osm2world/core/target/common/material/Material.java index 7f164485c..9a970036d 100644 --- a/src/org/osm2world/core/target/common/material/Material.java +++ b/src/org/osm2world/core/target/common/material/Material.java @@ -135,6 +135,18 @@ public Material brighter() { getTransparency(), getShadow(), getAmbientOcclusion(), getTextureDataList()); } + public Material withAmbientFactor(float newAmbientFactor) { + return new ImmutableMaterial(interpolation, getColor(), + newAmbientFactor, getDiffuseFactor(), getSpecularFactor(), getShininess(), + getTransparency(), getShadow(), getAmbientOcclusion(), getTextureDataList()); + } + + public Material withDefuseFactor(float newDefuseFactor) { + return new ImmutableMaterial(interpolation, getColor(), + getAmbientFactor(), newDefuseFactor, getSpecularFactor(), getShininess(), + getTransparency(), getShadow(), getAmbientOcclusion(), getTextureDataList()); + } + public Material darker() { return new ImmutableMaterial(interpolation, getColor().darker(), getAmbientFactor(), getDiffuseFactor(), getSpecularFactor(), getShininess(), diff --git a/src/org/osm2world/core/target/common/model/Model.java b/src/org/osm2world/core/target/common/model/Model.java new file mode 100644 index 000000000..0123c64f2 --- /dev/null +++ b/src/org/osm2world/core/target/common/model/Model.java @@ -0,0 +1,23 @@ +package org.osm2world.core.target.common.model; + +import org.osm2world.core.math.VectorXYZ; +import org.osm2world.core.target.Target; + +/** + * a single 3D model, typically loaded from a file or other resource + */ +public interface Model { + + /** + * draws an instance of the model to any {@link Target} + * + * @param target target for the model; != null + * @param direction rotation of the model in the XZ plane, as an angle in radians + * @param height height of the model; null for default (unspecified) height + * @param width width of the model; null for default (unspecified) width + * @param length length of the model; null for default (unspecified) length + */ + public void render(Target target, VectorXYZ position, + double direction, Double height, Double width, Double length); + +} diff --git a/src/org/osm2world/core/target/common/model/obj/ExternalModel.java b/src/org/osm2world/core/target/common/model/obj/ExternalModel.java new file mode 100644 index 000000000..845a42ce6 --- /dev/null +++ b/src/org/osm2world/core/target/common/model/obj/ExternalModel.java @@ -0,0 +1,66 @@ +package org.osm2world.core.target.common.model.obj; + +import java.util.ArrayList; +import java.util.List; + +import org.openstreetmap.josm.plugins.graphview.core.data.TagGroup; +import org.osm2world.core.map_data.creation.MapProjection; +import org.osm2world.core.math.AxisAlignedBoundingBoxXYZ; +import org.osm2world.core.math.AxisAlignedBoundingBoxXZ; +import org.osm2world.core.math.VectorXYZ; +import org.osm2world.core.math.VectorXZ; +import org.osm2world.core.target.Target; +import org.osm2world.core.target.common.model.Model; +import org.osm2world.core.target.common.model.obj.parser.ModelLinksProxy; +import org.osm2world.core.target.common.model.obj.parser.ObjModel; +import org.osm2world.core.target.common.model.obj.parser.ObjModel.ObjFace; +import org.osm2world.core.world.data.WorldObject; + + +public class ExternalModel implements Model { + + private ObjModel model; + + private VectorXZ originT = new VectorXZ(0.0, 0.0); + private double scale = 1.0; + private boolean zAxisUp = false; + + private ModelLinksProxy proxy; + + public ExternalModel(String link) { + this.proxy = new ModelLinksProxy("/opt/osm/3dlib"); + this.model = new ObjModel(link, proxy); + } + + @Override + public void render(Target target, VectorXYZ position, + double direction, Double height, Double width, + Double length) { + + for(ObjFace f : this.model.listFaces()) { + List vs = new ArrayList<>(f.vs.size()); + + for (VectorXYZ src : f.vs) { + src = src.mult(scale); + + if (zAxisUp) { + src = src.rotateX(Math.toRadians(-90.0)); + } + + src = src.rotateY(direction); + src = src.add(originT); + + vs.add(src); + } + +// Collections.reverse(vs); +// Collections.reverse(f.texCoordLists.get(0)); + + if (f.material != null) { + f.material = f.material.withAmbientFactor(0.9f); + target.drawTriangleFan(f.material, vs, f.texCoordLists); + } + } + } + +} diff --git a/src/org/osm2world/core/target/common/model/obj/parser/ModelLinksProxy.java b/src/org/osm2world/core/target/common/model/obj/parser/ModelLinksProxy.java new file mode 100644 index 000000000..d9cb8a885 --- /dev/null +++ b/src/org/osm2world/core/target/common/model/obj/parser/ModelLinksProxy.java @@ -0,0 +1,111 @@ +package org.osm2world.core.target.common.model.obj.parser; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URL; +import java.nio.channels.Channels; +import java.nio.channels.ReadableByteChannel; + +public class ModelLinksProxy { + + private String localCachePath; + + public ModelLinksProxy(String localCachePath) { + this.localCachePath = localCachePath; + } + + public static String resolveLink(URL base, String link) throws MalformedURLException { + if (new File(link).isAbsolute()) { + URL root = new URL(base.getProtocol() + "://" + base.getAuthority()); + return new URL(root, link).toString(); + } + else { + return new URL(base, link).toString(); + } + } + + public File getFile(String link) { + try { + if(isURL(link)) { + File file = getPathForObjUrl(link); + return saveFile(file, new URL(link)); + } + return null; + } + catch (Exception e) { + throw new RuntimeException(e); + } + } + + private File saveFile(File file, URL link) + throws IOException, FileNotFoundException, MalformedURLException, InterruptedException { + + if (!file.exists()) { + file.getParentFile().mkdirs(); + if(file.createNewFile()) { + FileOutputStream fileOutputStream = new FileOutputStream(file); + try { + java.nio.channels.FileLock lock = fileOutputStream.getChannel().lock(); + try { + saveFile(fileOutputStream, link); + } finally { + lock.release(); + } + } finally { + fileOutputStream.close(); + } + } + } + + // File exists, but might be locked by other thred/app for writing data + // wait untill it will be released + int timeout = 30 * 1000; + while(!file.canWrite()) { + if (timeout > 0) { + Thread.sleep(100); + timeout -= 100; + } + else { + break; + } + } + + return file; + } + + private void saveFile(FileOutputStream fileOutputStream, URL url) throws IOException { + ReadableByteChannel rbc = Channels.newChannel(url.openStream()); + fileOutputStream.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE); + } + + private File getPathForObjUrl(String link) { + try { + URL url = new URL(link); + String path = url.getPath(); + + return new File(localCachePath, path); + } + catch (Exception e) { + throw new RuntimeException(e); + } + } + + public File getLinkedFile(String local, String base) { + try { + String link = resolveLink(new URL(base), local); + return getFile(link); + } + catch (Exception e) { + e.printStackTrace(); + return null; + } + } + + private static boolean isURL(String link) { + return link.startsWith("http://") || link.startsWith("https://"); + } + +} diff --git a/src/org/osm2world/core/external_models/objparser/ObjModel.java b/src/org/osm2world/core/target/common/model/obj/parser/ObjModel.java similarity index 64% rename from src/org/osm2world/core/external_models/objparser/ObjModel.java rename to src/org/osm2world/core/target/common/model/obj/parser/ObjModel.java index b15313fdc..1c48ead87 100644 --- a/src/org/osm2world/core/external_models/objparser/ObjModel.java +++ b/src/org/osm2world/core/target/common/model/obj/parser/ObjModel.java @@ -1,4 +1,4 @@ -package org.osm2world.core.external_models.objparser; +package org.osm2world.core.target.common.model.obj.parser; import java.awt.Color; import java.awt.image.BufferedImage; @@ -6,6 +6,7 @@ import java.io.File; import java.io.FileReader; import java.io.IOException; +import java.net.URL; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -14,18 +15,20 @@ import javax.imageio.ImageIO; import org.apache.commons.lang.StringUtils; +import org.osm2world.core.math.AxisAlignedBoundingBoxXYZ; import org.osm2world.core.math.VectorXYZ; import org.osm2world.core.math.VectorXZ; import org.osm2world.core.target.common.TextureData; import org.osm2world.core.target.common.TextureData.Wrap; import org.osm2world.core.target.common.material.ConfMaterial; import org.osm2world.core.target.common.material.Material; +import org.osm2world.core.target.common.material.Material.AmbientOcclusion; import org.osm2world.core.target.common.material.Material.Interpolation; import org.osm2world.core.target.common.material.Material.Shadow; import org.osm2world.core.target.common.material.Material.Transparency; import org.osm2world.core.target.common.material.NamedTexCoordFunction; -public class ObjParser { +public class ObjModel { private File objFile; private Map materials = new HashMap<>(); @@ -36,9 +39,12 @@ public class ObjParser { private List textureVertexes = new ArrayList<>(); private Material faceMtl; private List faces = new ArrayList<>(); + private AxisAlignedBoundingBoxXYZ bbox; + private ModelLinksProxy proxy; + private String base; private static final class ObjMaterial { - File materialFile; + String materialFileLink; public String name; float ni; @@ -52,8 +58,41 @@ private static final class ObjMaterial { } - public ObjParser(File objFile) { - this.objFile = objFile; + private static String strip(String str) { + return StringUtils.strip(str, " \t"); + } + + private static String parseStringParam(String line, String key) { + return strip(StringUtils.removeStartIgnoreCase(line, key)); + } + + private float parseFloatParam(String line, String key) { + return Float.valueOf(strip(StringUtils.removeStartIgnoreCase(line, key))); + } + + private float[] parseFloatTriplet(String key, String line) { + String numbers = strip(StringUtils.removeStartIgnoreCase(line, key)); + String[] split = StringUtils.split(numbers, " \t"); + return new float[]{ + Float.valueOf(split[0]), + Float.valueOf(split[1]), + Float.valueOf(split[2]) + }; + } + + private float[] parseFloatDuplet(String key, String line) { + String numbers = strip(StringUtils.removeStartIgnoreCase(line, key)); + String[] split = StringUtils.split(numbers, " \t"); + return new float[]{ + Float.valueOf(split[0]), + Float.valueOf(split[1]) + }; + } + + public ObjModel(String link, ModelLinksProxy resolver) { + this.proxy = resolver; + this.base = link; + this.objFile = this.proxy.getFile(link); try { this.iterateOverObjFile(); @@ -69,13 +108,13 @@ private void iterateOverObjFile() throws IOException { while (line != null) { line = StringUtils.strip(line); - if (line.startsWith("#")) { + if (StringUtils.startsWithIgnoreCase(line, "#")) { handleObjComment(line); } - else if (line.startsWith("call")){ + else if (StringUtils.startsWithIgnoreCase(line, "call")){ handleCall(line); } - else if (line.startsWith("mtllib")){ + else if (StringUtils.startsWithIgnoreCase(line, "mtllib")){ handleMtlLib(line); } else { @@ -93,16 +132,14 @@ private void handleCall(String line) { } private void handleMtlLib(String line) throws IOException { - String mtlPath = StringUtils.strip(line.replace("mtllib", "")); - File mtlFile = new File(mtlPath); - if(!mtlFile.isAbsolute()) { - mtlFile = new File(this.objFile.getParent(), mtlPath); - } + String mtlPath = parseStringParam(line, "mtllib"); + String mtlLink = ModelLinksProxy.resolveLink(new URL(this.base), mtlPath); + File mtlFile = this.proxy.getLinkedFile(mtlLink, this.base); - parseMtlFile(mtlFile); + parseMtlFile(mtlFile, mtlLink); } - private void parseMtlFile(File mtlFile) throws IOException { + private void parseMtlFile(File mtlFile, String mtlLink) throws IOException { BufferedReader br = new BufferedReader(new FileReader(mtlFile)); String line = StringUtils.strip(br.readLine()); while (line != null) @@ -110,25 +147,25 @@ private void parseMtlFile(File mtlFile) throws IOException { if (line.startsWith("#")) { handleMtlComment(line); } - else if(line.startsWith("newmtl")) { - handleMtlNewMtl(line, mtlFile); + else if(StringUtils.startsWithIgnoreCase(line, "newmtl")) { + handleMtlNewMtl(line, mtlLink); } - else if(line.startsWith("Ka")) { + else if(StringUtils.startsWithIgnoreCase(line, "ka")) { handleMtlKa(line); } - else if(line.startsWith("Kd")) { + else if(StringUtils.startsWithIgnoreCase(line, "kd")) { handleMtlKd(line); } - else if(line.startsWith("Ks")) { + else if(StringUtils.startsWithIgnoreCase(line, "ks")) { handleMtlKs(line); } - else if(line.startsWith("Ni")) { + else if(StringUtils.startsWithIgnoreCase(line, "ni")) { handleMtlNi(line); } - else if(line.startsWith("map_Ka")) { + else if(StringUtils.startsWithIgnoreCase(line, "map_Ka")) { handleMtlMapKa(line); } - else if(line.startsWith("map_Kd")) { + else if(StringUtils.startsWithIgnoreCase(line, "map_Kd")) { handleMtlMapKd(line); } @@ -140,60 +177,55 @@ else if(line.startsWith("map_Kd")) { } private void handleMtlMapKd(String line) { - this.curentMtl.map_Kd = line.replace("map_Kd ", "").trim(); + this.curentMtl.map_Kd = parseStringParam(line, "map_kd"); } private void handleMtlMapKa(String line) { - this.curentMtl.map_Ka = line.replace("map_Ka ", "").trim(); + this.curentMtl.map_Ka = parseStringParam(line, "map_ka"); } private void handleMtlNi(String line) { - this.curentMtl.ni = - Float.valueOf(line.replace("Ni ", "")); + this.curentMtl.ni = parseFloatParam(line, "ni"); } private void handleMtlKs(String line) { - String[] split = line.replace("Ks ", "").split(" "); - this.curentMtl.ks = new float[]{ - Float.valueOf(split[0]), - Float.valueOf(split[1]), - Float.valueOf(split[2]) - }; + this.curentMtl.ks = parseFloatTriplet("Ks", line); } private void handleMtlKd(String line) { - String[] split = line.replace("Kd ", "").split(" "); - this.curentMtl.kd = new float[]{ - Float.valueOf(split[0]), - Float.valueOf(split[1]), - Float.valueOf(split[2]) - }; + this.curentMtl.kd = parseFloatTriplet("Kd", line); } private void handleMtlKa(String line) { - String[] split = line.replace("Ka ", "").split(" "); - this.curentMtl.ka = new float[]{ - Float.valueOf(split[0]), - Float.valueOf(split[1]), - Float.valueOf(split[2]) - }; + this.curentMtl.ka = parseFloatTriplet("Ka", line); } - private void handleMtlNewMtl(String line, File mtlFile) throws IOException { + private void handleMtlNewMtl(String line, String mtlLink) throws IOException { parseCurentMtl(); - - String name = StringUtils.strip(line.replace("newmtl", "")); + String name = parseStringParam(line, "newmtl"); this.curentMtl = new ObjMaterial(); this.curentMtl.name = name; - this.curentMtl.materialFile = mtlFile; + this.curentMtl.materialFileLink = mtlLink; } private void parseCurentMtl() throws IOException { if (this.curentMtl != null && this.curentMtl.name != null) { boolean mapsEmpty = this.curentMtl.map_Ka == null && this.curentMtl.map_Kd == null; + + if (this.curentMtl.ka == null) { + this.curentMtl.ka = new float[]{0.7f, 0.7f, 0.7f}; + } + if (this.curentMtl.kd == null) { + this.curentMtl.kd = new float[]{0.3f, 0.3f, 0.3f}; + } + if (this.curentMtl.ks == null) { + this.curentMtl.ks = new float[]{0.0f, 0.0f, 0.0f}; + } + float ambientAverage = (this.curentMtl.ka[0] + this.curentMtl.ka[1] + this.curentMtl.ka[2]) / 3.0f; float diffuseAverage = (this.curentMtl.kd[0] + this.curentMtl.kd[1] + this.curentMtl.kd[2]) / 3.0f; + boolean kaComponentsEqual = this.curentMtl.ka[0] == this.curentMtl.ka[1] && this.curentMtl.ka[1] == this.curentMtl.ka[2]; boolean kdComponentsEqual = @@ -222,7 +254,7 @@ private void parseCurentMtl() throws IOException { Color meanColor = new Color(rm, gm, bm); ConfMaterial m = new ConfMaterial(Interpolation.FLAT, meanColor); - + m.setAmbientFactor(ambientAverage); m.setDiffuseFactor(diffuseAverage); @@ -234,9 +266,12 @@ private void parseCurentMtl() throws IOException { m.setTransparency(Transparency.FALSE); if (!mapsEmpty) { + String textureLink = this.curentMtl.map_Ka == null + ? this.curentMtl.map_Kd : this.curentMtl.map_Ka; + File texture = resolveTexture( - this.curentMtl.map_Ka, - this.curentMtl.materialFile); + textureLink, + this.curentMtl.materialFileLink); BufferedImage bimg = ImageIO.read(texture); @@ -252,8 +287,8 @@ private void parseCurentMtl() throws IOException { } } - private File resolveTexture(String texture, File materialFile) { - return new File(materialFile.getParentFile(), texture); + private File resolveTexture(String texture, String materialFileLink) { + return this.proxy.getLinkedFile(texture, materialFileLink); } private void handleMtlComment(String line) { @@ -261,28 +296,21 @@ private void handleMtlComment(String line) { } private void handleObjLine(String line) { - if (line.startsWith("v ")) { - String[] split = line.replace("v ", "").trim().split(" "); - handleVertex(new VectorXYZ( - Double.valueOf(split[0]), - Double.valueOf(split[1]), - Double.valueOf(split[2]))); + if (StringUtils.startsWithIgnoreCase(line, "v ")) { + float[] xyz = parseFloatTriplet("v", line); + handleVertex(new VectorXYZ(-xyz[0], xyz[1], xyz[2]) + .rotateY(Math.toRadians(180.0))); } - else if (line.startsWith("vt ")) { - String[] split = line.replace("vt ", "").trim().split(" "); - handleTextureVertex(new VectorXZ( - Double.valueOf(split[0]), - Double.valueOf(split[1]))); + else if (StringUtils.startsWithIgnoreCase(line, "vt")) { + float[] uv = parseFloatDuplet("vt ", line); + handleTextureVertex(new VectorXZ(uv[0], uv[1])); } - else if (line.startsWith("vn ")) { - String[] split = line.replace("vn ", "").trim().split(" "); - handleVertexNormal(new VectorXYZ( - Double.valueOf(split[0]), - Double.valueOf(split[1]), - Double.valueOf(split[2]))); + else if (StringUtils.startsWithIgnoreCase(line, "vn")) { + float[] xyz = parseFloatTriplet("vn ", line); + handleVertexNormal(new VectorXYZ(xyz[0], xyz[1], xyz[2])); } - else if (line.startsWith("g ")) { - String groupName = line.replace("g ", "").trim(); + else if (StringUtils.startsWithIgnoreCase(line, "g ")) { + String groupName = parseStringParam(line, "g"); handleGroup(groupName); } else if (line.startsWith("usemtl ")) { @@ -322,13 +350,15 @@ private void handleFace(String faceLine) { } if (vi >= 0) { - f.vs.add(this.vertexes.get(vi)); + f.vs.add(this.vertexes.get(vi - 1)); } else { // it's negative so use minus f.vs.add(this.vertexes.get(this.vertexes.size() + vi)); } + updateBbox(f.vs); + if (components.length > 1) { if(StringUtils.stripToNull(components[1]) != null) { @@ -339,7 +369,7 @@ private void handleFace(String faceLine) { int tvi = Integer.valueOf(components[1]); if (tvi >= 0) { - f.texCoordLists.get(0).add(textureVertexes.get(tvi)); + f.texCoordLists.get(0).add(textureVertexes.get(tvi - 1)); } else { f.texCoordLists.get(0).add(textureVertexes.get( @@ -355,7 +385,7 @@ private void handleFace(String faceLine) { int vni = Integer.valueOf(components[2]); if (vni >= 0) { - f.normals.add(this.vertexNorms.get(vni)); + f.normals.add(this.vertexNorms.get(vni - 1)); } else { f.normals.add(this.vertexNorms.get( @@ -366,6 +396,16 @@ private void handleFace(String faceLine) { this.faces.add(f); } + private void updateBbox(List vs) { + if (this.bbox == null) { + this.bbox = new AxisAlignedBoundingBoxXYZ(vs); + } + else { + this.bbox = AxisAlignedBoundingBoxXYZ.union( + this.bbox, new AxisAlignedBoundingBoxXYZ(vs)); + } + } + public List listFaces() { return faces; } @@ -401,5 +441,9 @@ private void clear() { this.textureVertexes = null; this.vertexes = null; } + + public AxisAlignedBoundingBoxXYZ getBBOX() { + return this.bbox; + } } diff --git a/src/org/osm2world/core/world/modules/BuildingModule.java b/src/org/osm2world/core/world/modules/BuildingModule.java index 8fbefb126..3134a5602 100644 --- a/src/org/osm2world/core/world/modules/BuildingModule.java +++ b/src/org/osm2world/core/world/modules/BuildingModule.java @@ -2,19 +2,39 @@ import static com.google.common.base.Preconditions.checkArgument; import static java.lang.Double.POSITIVE_INFINITY; -import static java.lang.Math.*; +import static java.lang.Math.max; +import static java.lang.Math.min; +import static java.lang.Math.round; +import static java.lang.Math.sqrt; +import static java.lang.Math.toRadians; import static java.util.Arrays.asList; -import static java.util.Collections.*; -import static org.openstreetmap.josm.plugins.graphview.core.util.ValueStringParser.*; -import static org.osm2world.core.map_elevation.creation.EleConstraintEnforcer.ConstraintType.*; -import static org.osm2world.core.map_elevation.data.GroundState.*; -import static org.osm2world.core.math.GeometryUtil.*; -import static org.osm2world.core.target.common.material.NamedTexCoordFunction.*; -import static org.osm2world.core.target.common.material.TexCoordUtil.*; -import static org.osm2world.core.world.modules.common.WorldModuleParseUtil.*; +import static java.util.Collections.emptyList; +import static java.util.Collections.nCopies; +import static java.util.Collections.singleton; +import static java.util.Collections.singletonList; +import static org.openstreetmap.josm.plugins.graphview.core.util.ValueStringParser.parseAngle; +import static org.openstreetmap.josm.plugins.graphview.core.util.ValueStringParser.parseColor; +import static org.openstreetmap.josm.plugins.graphview.core.util.ValueStringParser.parseMeasure; +import static org.openstreetmap.josm.plugins.graphview.core.util.ValueStringParser.parseOsmDecimal; +import static org.osm2world.core.map_elevation.creation.EleConstraintEnforcer.ConstraintType.EXACT; +import static org.osm2world.core.map_elevation.creation.EleConstraintEnforcer.ConstraintType.MIN; +import static org.osm2world.core.map_elevation.data.GroundState.ABOVE; +import static org.osm2world.core.map_elevation.data.GroundState.BELOW; +import static org.osm2world.core.map_elevation.data.GroundState.ON; +import static org.osm2world.core.math.GeometryUtil.distanceFromLine; +import static org.osm2world.core.math.GeometryUtil.distanceFromLineSegment; +import static org.osm2world.core.math.GeometryUtil.insertIntoPolygon; +import static org.osm2world.core.math.GeometryUtil.interpolateBetween; +import static org.osm2world.core.math.GeometryUtil.interpolateValue; +import static org.osm2world.core.target.common.material.NamedTexCoordFunction.GLOBAL_X_Z; +import static org.osm2world.core.target.common.material.NamedTexCoordFunction.SLOPED_TRIANGLES; +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.target.common.material.TexCoordUtil.triangleTexCoordLists; +import static org.osm2world.core.world.modules.common.WorldModuleParseUtil.parseHeight; +import static org.osm2world.core.world.modules.common.WorldModuleParseUtil.parseWidth; import java.awt.Color; -import java.io.File; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -26,7 +46,7 @@ import java.util.Set; import org.openstreetmap.josm.plugins.graphview.core.data.TagGroup; -import org.osm2world.core.external_models.ExternalModel; +import org.osm2world.core.map_data.creation.MapProjection; import org.osm2world.core.map_data.data.MapArea; import org.osm2world.core.map_data.data.MapData; import org.osm2world.core.map_data.data.MapElement; @@ -108,18 +128,11 @@ public static class Building implements AreaWorldObject, private final EleConnectorGroup outlineConnectors; - private ExternalModel model; - public Building(MapArea area, boolean useBuildingColors, boolean drawBuildingWindows) { this.area = area; - - if (area.getOsmObject().id == 3696235) { - model = new ExternalModel(this, - new File("/opt/ep/data/house-model/haus06.obj")); - } - + for (MapOverlap overlap : area.getOverlaps()) { MapElement other = overlap.getOther(area); if (other instanceof MapArea @@ -294,14 +307,9 @@ public PolygonXYZ getOutlinePolygon() { @Override public void renderTo(Target target) { - if (this.model != null) { - this.model.renderTo(target); + for (BuildingPart part : parts) { + part.renderTo(target); } -// else { -// for (BuildingPart part : parts) { -// part.renderTo(target); -// } -// } } } diff --git a/src/org/osm2world/core/world/modules/ExternalModelModule.java b/src/org/osm2world/core/world/modules/ExternalModelModule.java new file mode 100644 index 000000000..805d54ee9 --- /dev/null +++ b/src/org/osm2world/core/world/modules/ExternalModelModule.java @@ -0,0 +1,179 @@ +package org.osm2world.core.world.modules; + +import static java.util.Collections.singletonList; +import static org.osm2world.core.world.modules.common.WorldModuleParseUtil.*; + +import java.net.MalformedURLException; +import java.net.URL; + +import org.osm2world.core.map_data.data.MapArea; +import org.osm2world.core.map_data.data.MapElement; +import org.osm2world.core.map_data.data.MapNode; +import org.osm2world.core.map_data.data.MapWaySegment; +import org.osm2world.core.map_elevation.creation.EleConstraintEnforcer; +import org.osm2world.core.map_elevation.data.EleConnector; +import org.osm2world.core.map_elevation.data.GroundState; +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.model.Model; +import org.osm2world.core.target.common.model.obj.ExternalModel; +import org.osm2world.core.world.data.AreaWorldObject; +import org.osm2world.core.world.data.NodeWorldObject; +import org.osm2world.core.world.data.WorldObject; +import org.osm2world.core.world.modules.common.AbstractModule; + +/** + * adds external 3D models to the world. + * The {@link Model} instances are loaded from files, + * and placed in the scene based on special OSM tags. + */ +public class ExternalModelModule extends AbstractModule { + + private abstract static class ExternalModelWorldObject + implements WorldObject, RenderableToAllTargets { + + // TODO will later require a better solution to allow models on bridges etc. + + protected final E element; + protected final Model model; + + protected EleConnector eleConnector = null; + + private ExternalModelWorldObject(E element, Model model) { + this.element = element; + this.model = model; + } + + @Override + public E getPrimaryMapElement() { + return element; + } + + @Override + public GroundState getGroundState() { + return GroundState.ON; + } + + @Override + public void defineEleConstraints(EleConstraintEnforcer enforcer) {} + + @Override + public void renderTo(Target target) { + + VectorXYZ position = eleConnector.getPosXYZ(); + double direction = parseDirection(element.getTags(), 0); + Double height = (double)parseHeight(element.getTags(), 0); + Double width = (double)parseWidth(element.getTags(), 0); + Double length = (double)parseLength(element.getTags(), 0); + + if (height == 0) { + height = null; + } + + if (width == 0) { + width = null; + } + + if (length == 0) { + length = null; + } + + target.drawModel(model, position, direction, height, width, length); + + } + + } + + private static class ExternalModelNodeWorldObject extends ExternalModelWorldObject + implements NodeWorldObject { + + private ExternalModelNodeWorldObject(MapNode node, Model model) { + super(node, model); + } + + @Override + public Iterable getEleConnectors() { + + if (eleConnector == null) { + VectorXZ pos = element.getPos(); + eleConnector = new EleConnector(pos, element, GroundState.ON); + } + + return singletonList(eleConnector); + + } + + } + + private static class ExternalModelAreaWorldObject extends ExternalModelWorldObject + implements AreaWorldObject { + + private ExternalModelAreaWorldObject(MapArea area, Model model) { + super(area, model); + } + + @Override + public Iterable getEleConnectors() { + + if (eleConnector == null) { + VectorXZ pos = element.getOuterPolygon().getCentroid(); + eleConnector = new EleConnector(pos, element, GroundState.ON); + } + + return singletonList(eleConnector); + + } + + } + + @Override + protected void applyToElement(MapElement element) { + + if (element.getPrimaryRepresentation() != null) return; + + // ways are not yet supported because there's no easy way to obtain the other way segments + if (element instanceof MapWaySegment) return; + + if (element.getTags().containsKey("model:url")) { + + // only use external models if enabled via config options + //if (!config.getBoolean("useExternalModels", false)) return; + if (!config.getBoolean("useExternalModels", true)) return; + + try { + + URL modelURL = new URL(element.getTags().getValue("model:url")); + + Model model = new ExternalModel(modelURL.toString()); + + /* place the model in the scene, wrapped in a world object */ + + if (element instanceof MapNode) { + + MapNode node = (MapNode)element; + NodeWorldObject worldObject = new ExternalModelNodeWorldObject(node, model); + node.addRepresentation(worldObject); + + } else if (element instanceof MapArea) { + + MapArea area = (MapArea)element; + AreaWorldObject worldObject = new ExternalModelAreaWorldObject(area, model); + area.addRepresentation(worldObject); + + } else { + + throw new Error("unsupported element type for external model: " + element); + + } + + } catch (MalformedURLException e) { + e.printStackTrace(); + } + + } + + } + +} From 4aeada5289d9e823965a3f79eb2285c11c4abeba Mon Sep 17 00:00:00 2001 From: kiselev-dv Date: Sun, 22 Oct 2017 15:07:33 -0300 Subject: [PATCH 4/9] Stub for parsing model:lat and model:lon --- .../world/modules/ExternalModelModule.java | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/src/org/osm2world/core/world/modules/ExternalModelModule.java b/src/org/osm2world/core/world/modules/ExternalModelModule.java index 805d54ee9..5fdd4e307 100644 --- a/src/org/osm2world/core/world/modules/ExternalModelModule.java +++ b/src/org/osm2world/core/world/modules/ExternalModelModule.java @@ -63,6 +63,7 @@ public void defineEleConstraints(EleConstraintEnforcer enforcer) {} public void renderTo(Target target) { VectorXYZ position = eleConnector.getPosXYZ(); + double direction = parseDirection(element.getTags(), 0); Double height = (double)parseHeight(element.getTags(), 0); Double width = (double)parseWidth(element.getTags(), 0); @@ -97,13 +98,28 @@ private ExternalModelNodeWorldObject(MapNode node, Model model) { public Iterable getEleConnectors() { if (eleConnector == null) { - VectorXZ pos = element.getPos(); + VectorXZ pos = getPosition(element); eleConnector = new EleConnector(pos, element, GroundState.ON); } return singletonList(eleConnector); } + + private VectorXZ getPosition(MapNode element) { + if (element.getTags().containsKey("model:lon") && + element.getTags().containsKey("model:lat")) { + // I need an access to mapProjection here, or at the moment + // of MapNode creation + + // Better to have it here because here I have full access to + // model itself nad model metadata + return element.getPos(); + } + else { + return element.getPos(); + } + } } From 1a47ce029b2ac1814b1bb7fecbdf31ab11282f67 Mon Sep 17 00:00:00 2001 From: kiselev-dv Date: Wed, 25 Oct 2017 16:22:52 -0300 Subject: [PATCH 5/9] Fix central offset --- .../target/common/model/obj/ExternalModel.java | 9 +++++---- .../core/world/modules/ExternalModelModule.java | 17 +---------------- 2 files changed, 6 insertions(+), 20 deletions(-) diff --git a/src/org/osm2world/core/target/common/model/obj/ExternalModel.java b/src/org/osm2world/core/target/common/model/obj/ExternalModel.java index 845a42ce6..c4ec2b122 100644 --- a/src/org/osm2world/core/target/common/model/obj/ExternalModel.java +++ b/src/org/osm2world/core/target/common/model/obj/ExternalModel.java @@ -30,6 +30,8 @@ public class ExternalModel implements Model { public ExternalModel(String link) { this.proxy = new ModelLinksProxy("/opt/osm/3dlib"); this.model = new ObjModel(link, proxy); + + originT = this.model.getBBOX().center().xz().invert(); } @Override @@ -37,6 +39,8 @@ public void render(Target target, VectorXYZ position, double direction, Double height, Double width, Double length) { + VectorXYZ translate = position.add(originT); + for(ObjFace f : this.model.listFaces()) { List vs = new ArrayList<>(f.vs.size()); @@ -48,14 +52,11 @@ public void render(Target target, VectorXYZ position, } src = src.rotateY(direction); - src = src.add(originT); + src = src.add(translate); vs.add(src); } -// Collections.reverse(vs); -// Collections.reverse(f.texCoordLists.get(0)); - if (f.material != null) { f.material = f.material.withAmbientFactor(0.9f); target.drawTriangleFan(f.material, vs, f.texCoordLists); diff --git a/src/org/osm2world/core/world/modules/ExternalModelModule.java b/src/org/osm2world/core/world/modules/ExternalModelModule.java index 5fdd4e307..f9dd547ad 100644 --- a/src/org/osm2world/core/world/modules/ExternalModelModule.java +++ b/src/org/osm2world/core/world/modules/ExternalModelModule.java @@ -98,28 +98,13 @@ private ExternalModelNodeWorldObject(MapNode node, Model model) { public Iterable getEleConnectors() { if (eleConnector == null) { - VectorXZ pos = getPosition(element); + VectorXZ pos = element.getPos(); eleConnector = new EleConnector(pos, element, GroundState.ON); } return singletonList(eleConnector); } - - private VectorXZ getPosition(MapNode element) { - if (element.getTags().containsKey("model:lon") && - element.getTags().containsKey("model:lat")) { - // I need an access to mapProjection here, or at the moment - // of MapNode creation - - // Better to have it here because here I have full access to - // model itself nad model metadata - return element.getPos(); - } - else { - return element.getPos(); - } - } } From c88bd66fe4deb01a026bfaf68c7cec1a1dc2918c Mon Sep 17 00:00:00 2001 From: kiselev-dv Date: Wed, 25 Oct 2017 17:05:30 -0300 Subject: [PATCH 6/9] Remove debug switch for useExternalModels --- .../core/target/common/model/obj/ExternalModel.java | 5 ----- .../osm2world/core/world/modules/ExternalModelModule.java | 3 +-- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/src/org/osm2world/core/target/common/model/obj/ExternalModel.java b/src/org/osm2world/core/target/common/model/obj/ExternalModel.java index c4ec2b122..a17ad15fd 100644 --- a/src/org/osm2world/core/target/common/model/obj/ExternalModel.java +++ b/src/org/osm2world/core/target/common/model/obj/ExternalModel.java @@ -3,10 +3,6 @@ import java.util.ArrayList; import java.util.List; -import org.openstreetmap.josm.plugins.graphview.core.data.TagGroup; -import org.osm2world.core.map_data.creation.MapProjection; -import org.osm2world.core.math.AxisAlignedBoundingBoxXYZ; -import org.osm2world.core.math.AxisAlignedBoundingBoxXZ; import org.osm2world.core.math.VectorXYZ; import org.osm2world.core.math.VectorXZ; import org.osm2world.core.target.Target; @@ -14,7 +10,6 @@ import org.osm2world.core.target.common.model.obj.parser.ModelLinksProxy; import org.osm2world.core.target.common.model.obj.parser.ObjModel; import org.osm2world.core.target.common.model.obj.parser.ObjModel.ObjFace; -import org.osm2world.core.world.data.WorldObject; public class ExternalModel implements Model { diff --git a/src/org/osm2world/core/world/modules/ExternalModelModule.java b/src/org/osm2world/core/world/modules/ExternalModelModule.java index f9dd547ad..7e341d063 100644 --- a/src/org/osm2world/core/world/modules/ExternalModelModule.java +++ b/src/org/osm2world/core/world/modules/ExternalModelModule.java @@ -140,8 +140,7 @@ protected void applyToElement(MapElement element) { if (element.getTags().containsKey("model:url")) { // only use external models if enabled via config options - //if (!config.getBoolean("useExternalModels", false)) return; - if (!config.getBoolean("useExternalModels", true)) return; + if (!config.getBoolean("useExternalModels", false)) return; try { From a51fb33780f5b97df184350182ad27735dbc5c4f Mon Sep 17 00:00:00 2001 From: kiselev-dv Date: Wed, 25 Oct 2017 17:26:24 -0300 Subject: [PATCH 7/9] Refactor models cache path into the config arg --- .../core/target/common/model/obj/ExternalModel.java | 4 +--- .../core/world/modules/ExternalModelModule.java | 13 ++++++++++++- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/src/org/osm2world/core/target/common/model/obj/ExternalModel.java b/src/org/osm2world/core/target/common/model/obj/ExternalModel.java index a17ad15fd..97fee28f0 100644 --- a/src/org/osm2world/core/target/common/model/obj/ExternalModel.java +++ b/src/org/osm2world/core/target/common/model/obj/ExternalModel.java @@ -20,10 +20,8 @@ public class ExternalModel implements Model { private double scale = 1.0; private boolean zAxisUp = false; - private ModelLinksProxy proxy; - public ExternalModel(String link) { - this.proxy = new ModelLinksProxy("/opt/osm/3dlib"); + public ExternalModel(String link, ModelLinksProxy proxy) { this.model = new ObjModel(link, proxy); originT = this.model.getBBOX().center().xz().invert(); diff --git a/src/org/osm2world/core/world/modules/ExternalModelModule.java b/src/org/osm2world/core/world/modules/ExternalModelModule.java index 7e341d063..a1a2433a3 100644 --- a/src/org/osm2world/core/world/modules/ExternalModelModule.java +++ b/src/org/osm2world/core/world/modules/ExternalModelModule.java @@ -6,6 +6,7 @@ import java.net.MalformedURLException; import java.net.URL; +import org.apache.commons.configuration.Configuration; import org.osm2world.core.map_data.data.MapArea; import org.osm2world.core.map_data.data.MapElement; import org.osm2world.core.map_data.data.MapNode; @@ -19,6 +20,7 @@ import org.osm2world.core.target.Target; import org.osm2world.core.target.common.model.Model; import org.osm2world.core.target.common.model.obj.ExternalModel; +import org.osm2world.core.target.common.model.obj.parser.ModelLinksProxy; import org.osm2world.core.world.data.AreaWorldObject; import org.osm2world.core.world.data.NodeWorldObject; import org.osm2world.core.world.data.WorldObject; @@ -31,6 +33,8 @@ */ public class ExternalModelModule extends AbstractModule { + private ModelLinksProxy modelLinksProxy; + private abstract static class ExternalModelWorldObject implements WorldObject, RenderableToAllTargets { @@ -129,6 +133,13 @@ public Iterable getEleConnectors() { } + @Override + public void setConfiguration(Configuration config) { + super.setConfiguration(config); + String modelsCachePath = this.config.getString("externalModelsCachePath", "models"); + modelLinksProxy = new ModelLinksProxy(modelsCachePath); + } + @Override protected void applyToElement(MapElement element) { @@ -146,7 +157,7 @@ protected void applyToElement(MapElement element) { URL modelURL = new URL(element.getTags().getValue("model:url")); - Model model = new ExternalModel(modelURL.toString()); + Model model = new ExternalModel(modelURL.toString(), modelLinksProxy); /* place the model in the scene, wrapped in a world object */ From e4fda9d4805ab5d2938aea0f240403593fc72e10 Mon Sep 17 00:00:00 2001 From: kiselev-dv Date: Wed, 25 Oct 2017 19:28:55 -0300 Subject: [PATCH 8/9] Organize imports, write debug for Downloaded files --- .../core/target/common/model/obj/parser/ModelLinksProxy.java | 1 + .../osm2world/core/target/common/model/obj/parser/ObjModel.java | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/src/org/osm2world/core/target/common/model/obj/parser/ModelLinksProxy.java b/src/org/osm2world/core/target/common/model/obj/parser/ModelLinksProxy.java index d9cb8a885..29f1b9204 100644 --- a/src/org/osm2world/core/target/common/model/obj/parser/ModelLinksProxy.java +++ b/src/org/osm2world/core/target/common/model/obj/parser/ModelLinksProxy.java @@ -77,6 +77,7 @@ private File saveFile(File file, URL link) } private void saveFile(FileOutputStream fileOutputStream, URL url) throws IOException { + System.err.println("Download " + url); ReadableByteChannel rbc = Channels.newChannel(url.openStream()); fileOutputStream.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE); } diff --git a/src/org/osm2world/core/target/common/model/obj/parser/ObjModel.java b/src/org/osm2world/core/target/common/model/obj/parser/ObjModel.java index 1c48ead87..ab4ef4c28 100644 --- a/src/org/osm2world/core/target/common/model/obj/parser/ObjModel.java +++ b/src/org/osm2world/core/target/common/model/obj/parser/ObjModel.java @@ -22,7 +22,6 @@ import org.osm2world.core.target.common.TextureData.Wrap; import org.osm2world.core.target.common.material.ConfMaterial; import org.osm2world.core.target.common.material.Material; -import org.osm2world.core.target.common.material.Material.AmbientOcclusion; import org.osm2world.core.target.common.material.Material.Interpolation; import org.osm2world.core.target.common.material.Material.Shadow; import org.osm2world.core.target.common.material.Material.Transparency; From e78e85144f2e3ba21a49b5cbabcb026c6cc9d1d2 Mon Sep 17 00:00:00 2001 From: kiselev-dv Date: Wed, 25 Oct 2017 19:29:27 -0300 Subject: [PATCH 9/9] Add useExternalModels to example_config --- build-adds/example_config.properties | 3 +++ 1 file changed, 3 insertions(+) diff --git a/build-adds/example_config.properties b/build-adds/example_config.properties index e94a02165..a9a90bd81 100644 --- a/build-adds/example_config.properties +++ b/build-adds/example_config.properties @@ -48,3 +48,6 @@ forceUnbufferedPNGRendering = false # If this is lower than the width or height of the requested png, performance suffers. # Increase it if your graphics hardware is capable of handling larger sizes. canvasLimit = 1024 + +# download external obj models for OSM objects with model:url +useExternalModels = true