diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..f190e4bc19841cf1aa289fc09f5b260ea70f0b82 --- /dev/null +++ b/.gitignore @@ -0,0 +1,274 @@ + +# Created by https://www.gitignore.io/api/java,maven,macos,linux,eclipse,windows,netbeans,intellij +# Edit at https://www.gitignore.io/?templates=java,maven,macos,linux,eclipse,windows,netbeans,intellij + + +# User specific +.sonarlint/ +Servers/ + +### Eclipse ### + +.metadata +bin/ +tmp/ +*.tmp +*.bak +*.swp +*~.nib +local.properties +.settings/ +.loadpath +.recommenders + +# External tool builders +.externalToolBuilders/ + +# Locally stored "Eclipse launch configurations" +*.launch + +# PyDev specific (Python IDE for Eclipse) +*.pydevproject + +# CDT-specific (C/C++ Development Tooling) +.cproject + +# CDT- autotools +.autotools + +# Java annotation processor (APT) +.factorypath + +# PDT-specific (PHP Development Tools) +.buildpath + +# sbteclipse plugin +.target + +# Tern plugin +.tern-project + +# TeXlipse plugin +.texlipse + +# STS (Spring Tool Suite) +.springBeans + +# Code Recommenders +.recommenders/ + +# Annotation Processing +.apt_generated/ + +# Scala IDE specific (Scala & Java development for Eclipse) +.cache-main +.scala_dependencies +.worksheet + +### Eclipse Patch ### +# Eclipse Core +.project + +# JDT-specific (Eclipse Java Development Tools) +.classpath + +# Annotation Processing +.apt_generated + +.sts4-cache/ + +### Intellij ### +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/usage.statistics.xml +.idea/**/dictionaries +.idea/**/shelf + +# Generated files +.idea/**/contentModel.xml + +# Sensitive or high-churn files +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml +.idea/**/dbnavigator.xml + +# Gradle +.idea/**/gradle.xml +.idea/**/libraries + +# Gradle and Maven with auto-import +# When using Gradle or Maven with auto-import, you should exclude module files, +# since they will be recreated, and may cause churn. Uncomment if using +# auto-import. +# .idea/modules.xml +# .idea/*.iml +# .idea/modules + +# CMake +cmake-build-*/ + +# Mongo Explorer plugin +.idea/**/mongoSettings.xml + +# File-based project format +*.iws + +# IntelliJ +out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +# Editor-based Rest Client +.idea/httpRequests + +# Android studio 3.1+ serialized cache file +.idea/caches/build_file_checksums.ser + +### Intellij Patch ### +# Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 + +# *.iml +# modules.xml +# .idea/misc.xml +# *.ipr + +# Sonarlint plugin +.idea/sonarlint + +### Java ### +# Compiled class file +*.class + +# Log file +*.log + +# BlueJ files +*.ctxt + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files # +*.jar +*.war +*.nar +*.ear +*.zip +*.tar.gz +*.rar + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* + +### Linux ### +*~ + +# temporary files which can be created if a process still has a handle open of a deleted file +.fuse_hidden* + +# KDE directory preferences +.directory + +# Linux trash folder which might appear on any partition or disk +.Trash-* + +# .nfs files are created when an open file is removed but is still being accessed +.nfs* + +### macOS ### +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +### Maven ### +target/ +pom.xml.tag +pom.xml.releaseBackup +pom.xml.versionsBackup +pom.xml.next +release.properties +dependency-reduced-pom.xml +buildNumber.properties +.mvn/timing.properties +.mvn/wrapper/maven-wrapper.jar + +### NetBeans ### +**/nbproject/private/ +**/nbproject/Makefile-*.mk +**/nbproject/Package-*.bash +build/ +nbbuild/ +dist/ +nbdist/ +.nb-gradle/ + +### Windows ### +# Windows thumbnail cache files +Thumbs.db +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +[Dd]esktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msix +*.msm +*.msp + +# Windows shortcuts +*.lnk + +# End of https://www.gitignore.io/api/java,maven,macos,linux,eclipse,windows,netbeans,intellij diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..d645695673349e3947e8e5ae42332d0ac3164cd7 --- /dev/null +++ b/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/README.md b/README.md index 1019e93d258ef46be26629bcee0ed5ddb996f31d..ba34ed7d590f6acfb5e6e0ba204428e1a349e537 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,11 @@ # CityGMLViewer -A CityGMLViewer based on OpenGL. Can display CityGML 2 and 3 \ No newline at end of file +A CityGMLViewer based on OpenGL. Can display CityGML 2 and 3 + +## Installation + +Use [Maven](https://maven.apache.org/) to build the viewer. + +```bash +mvn package +``` diff --git a/color.properties b/color.properties new file mode 100644 index 0000000000000000000000000000000000000000..6e1a6bc11884ae8fe0b713881b61c00d4740442d --- /dev/null +++ b/color.properties @@ -0,0 +1,10 @@ +groundColor=0.9411765 0.9019608 0.54901963 +roofColor=1 0 0 +doorColor=1 0.784313 0 +windowColor=0.0 0.5019608 0.5019608 +wallColor=1 1 1 +bridgeColor=1 0.49803922 0.3137255 +landColor=0.64705884 0.16470589 0.16470589 +transportationColor=1 1 0 +vegetationColor=0.5647059 0.93333334 0.5647059 +waterColor=0.5294118 0.80784315 0.98039216 \ No newline at end of file diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000000000000000000000000000000000000..3ebaf0b52b56aa65608dba63fda646067c661f7a --- /dev/null +++ b/pom.xml @@ -0,0 +1,193 @@ +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + <groupId>de.hft.stuttgart</groupId> + <artifactId>citygml-viewer-lwjgl</artifactId> + <version>0.0.1</version> + + <properties> + <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> + <maven.compiler.source>1.8</maven.compiler.source> + <maven.compiler.target>1.8</maven.compiler.target> + <lwjgl.version>3.2.3</lwjgl.version> + <joml.version>1.10.1</joml.version> + </properties> + + <profiles> + <profile> + <id>lwjgl-natives-linux-amd64</id> + <activation> + <os> + <family>unix</family> + <arch>amd64</arch> + </os> + </activation> + <properties> + <lwjgl.natives>natives-linux</lwjgl.natives> + </properties> + </profile> + <profile> + <id>lwjgl-natives-macos-amd64</id> + <activation> + <os> + <family>mac</family> + <arch>amd64</arch> + </os> + </activation> + <properties> + <lwjgl.natives>natives-macos</lwjgl.natives> + </properties> + </profile> + <profile> + <id>lwjgl-natives-windows-amd64</id> + <activation> + <os> + <family>windows</family> + <arch>amd64</arch> + </os> + </activation> + <properties> + <lwjgl.natives>natives-windows</lwjgl.natives> + </properties> + </profile> + </profiles> + + <dependencyManagement> + <dependencies> + <dependency> + <groupId>org.lwjgl</groupId> + <artifactId>lwjgl-bom</artifactId> + <version>${lwjgl.version}</version> + <scope>import</scope> + <type>pom</type> + </dependency> + </dependencies> + </dependencyManagement> + + <dependencies> + <!-- https://mvnrepository.com/artifact/org.citygml4j/citygml4j --> + <dependency> + <groupId>org.citygml4j</groupId> + <artifactId>citygml4j</artifactId> + <version>3.0.0-rc.2</version> + </dependency> + <!-- https://mvnrepository.com/artifact/org.locationtech.proj4j/proj4j --> + <dependency> + <groupId>org.locationtech.proj4j</groupId> + <artifactId>proj4j</artifactId> + <version>1.1.3</version> + </dependency> + <dependency> + <groupId>org.lwjgl</groupId> + <artifactId>lwjgl</artifactId> + </dependency> + <dependency> + <groupId>org.lwjgl</groupId> + <artifactId>lwjgl-glfw</artifactId> + </dependency> + <dependency> + <groupId>org.lwjgl</groupId> + <artifactId>lwjgl-opengl</artifactId> + </dependency> + <dependency> + <groupId>org.lwjgl</groupId> + <artifactId>lwjgl-nfd</artifactId> + </dependency> + <dependency> + <groupId>org.lwjgl</groupId> + <artifactId>lwjgl</artifactId> + <classifier>natives-windows</classifier> + </dependency> + <dependency> + <groupId>org.lwjgl</groupId> + <artifactId>lwjgl-glfw</artifactId> + <classifier>natives-windows</classifier> + </dependency> + <dependency> + <groupId>org.lwjgl</groupId> + <artifactId>lwjgl-opengl</artifactId> + <classifier>natives-windows</classifier> + </dependency> + <dependency> + <groupId>org.lwjgl</groupId> + <artifactId>lwjgl</artifactId> + <classifier>natives-linux</classifier> + </dependency> + <dependency> + <groupId>org.lwjgl</groupId> + <artifactId>lwjgl-glfw</artifactId> + <classifier>natives-linux</classifier> + </dependency> + <dependency> + <groupId>org.lwjgl</groupId> + <artifactId>lwjgl-opengl</artifactId> + <classifier>natives-linux</classifier> + </dependency> + <dependency> + <groupId>org.lwjgl</groupId> + <artifactId>lwjgl-nfd</artifactId> + <classifier>natives-linux</classifier> + </dependency> + <dependency> + <groupId>org.lwjgl</groupId> + <artifactId>lwjgl-nfd</artifactId> + <classifier>natives-windows</classifier> + </dependency> + <dependency> + <groupId>org.joml</groupId> + <artifactId>joml</artifactId> + <version>${joml.version}</version> + </dependency> + <!-- https://mvnrepository.com/artifact/org.jogamp.jogl/jogl-all-main --> + <dependency> + <groupId>org.jogamp.jogl</groupId> + <artifactId>jogl-all-main</artifactId> + <version>2.3.2</version> + </dependency> + <!-- https://mvnrepository.com/artifact/org.jogamp.gluegen/gluegen-rt-main --> + <dependency> + <groupId>org.jogamp.gluegen</groupId> + <artifactId>gluegen-rt-main</artifactId> + <version>2.3.2</version> + </dependency> + </dependencies> + + <build> + <plugins> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-jar-plugin</artifactId> + <version>3.2.0</version> + <configuration> + <archive> + <manifest> + <classpathPrefix>libs/</classpathPrefix> + <mainClass>de.hft.stuttgart.citygml.viewer.CityGMLViewer</mainClass> + <addClasspath>true</addClasspath> + </manifest> + </archive> + </configuration> + </plugin> + <plugin> + <artifactId>maven-assembly-plugin</artifactId> + <configuration> + <appendAssemblyId>false</appendAssemblyId> + <finalName>${project.artifactId}-${project.version}</finalName> + <descriptors> + <descriptor>${project.basedir}/src/assembly/assembly.xml</descriptor> + </descriptors> + </configuration> + <executions> + <execution> + <id>create-archive</id> + <phase>package</phase> + <goals> + <goal>single</goal> + </goals> + </execution> + </executions> + </plugin> + </plugins> + </build> + +</project> \ No newline at end of file diff --git a/src/assembly/assembly.xml b/src/assembly/assembly.xml new file mode 100644 index 0000000000000000000000000000000000000000..5eb2d19351d18e9f7101966f5a9d62c35c22a7fd --- /dev/null +++ b/src/assembly/assembly.xml @@ -0,0 +1,36 @@ +<assembly + xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2 http://maven.apache.org/xsd/assembly-1.1.2.xsd"> + <id>zip</id> + <formats> + <format>zip</format> + </formats> + <includeBaseDirectory>false</includeBaseDirectory> + <dependencySets> + <dependencySet> + <outputDirectory>libs</outputDirectory> + <excludes> + <exclude>${project.groupId}:${project.artifactId}:jar:*</exclude> + </excludes> + </dependencySet> + </dependencySets> + <fileSets> + <fileSet> + <directory>${project.build.directory}</directory> + <outputDirectory>/</outputDirectory> + <includes> + <include>${project.artifactId}-${project.version}.jar</include> + </includes> + </fileSet> + <fileSet> + <directory>${project.basedir}/src/assembly</directory> + <outputDirectory>/</outputDirectory> + <includes> + <include>start.bat</include> + <include>color.properties</include> + </includes> + <filtered>true</filtered> + </fileSet> + </fileSets> +</assembly> \ No newline at end of file diff --git a/src/assembly/color.properties b/src/assembly/color.properties new file mode 100644 index 0000000000000000000000000000000000000000..6e1a6bc11884ae8fe0b713881b61c00d4740442d --- /dev/null +++ b/src/assembly/color.properties @@ -0,0 +1,10 @@ +groundColor=0.9411765 0.9019608 0.54901963 +roofColor=1 0 0 +doorColor=1 0.784313 0 +windowColor=0.0 0.5019608 0.5019608 +wallColor=1 1 1 +bridgeColor=1 0.49803922 0.3137255 +landColor=0.64705884 0.16470589 0.16470589 +transportationColor=1 1 0 +vegetationColor=0.5647059 0.93333334 0.5647059 +waterColor=0.5294118 0.80784315 0.98039216 \ No newline at end of file diff --git a/src/assembly/start.bat b/src/assembly/start.bat new file mode 100644 index 0000000000000000000000000000000000000000..b932956b7c16fbf5dc718152b0039ac7b60ac814 --- /dev/null +++ b/src/assembly/start.bat @@ -0,0 +1 @@ +start javaw -jar ${project.artifactId}-${project.version}.jar \ No newline at end of file diff --git a/src/main/java/de/hft/stuttgart/citygml/viewer/Camera.java b/src/main/java/de/hft/stuttgart/citygml/viewer/Camera.java new file mode 100644 index 0000000000000000000000000000000000000000..8c5a80bf0161cce5b4134f4ee4d404474faa5dec --- /dev/null +++ b/src/main/java/de/hft/stuttgart/citygml/viewer/Camera.java @@ -0,0 +1,127 @@ +/*- + * Copyright 2021 Hochschule für Technik Stuttgart + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package de.hft.stuttgart.citygml.viewer; + +import java.nio.FloatBuffer; + +import org.joml.Math; +import org.joml.Matrix4f; +import org.joml.Vector3f; +import org.lwjgl.system.MemoryUtil; + +public class Camera { + + private static final float MIN_DISTANCE = 1f; + private static final float PI = (float) Math.PI; + + private Matrix4f projMatrix; + private Matrix4f viewMatrix; + private Matrix4f projViewMatrix; + + private float rotateAroundX = 2f; + private float rotateAroundZ = 3f; + + private Vector3f eyeVec = new Vector3f(); + private Vector3f center = new Vector3f(); + private Vector3f up = new Vector3f(); + private float distance = 5; + + private static final float SCROLL_PERCENTAGE = 0.1f; + + private UniformLocation uniformLocation; + private FloatBuffer matrixBuffer = MemoryUtil.memAllocFloat(16); + + private boolean isOrtho = false; + + public Camera(Shader shader) { + uniformLocation = shader.getUniformLocation("projViewModel"); + projViewMatrix = new Matrix4f(); + viewMatrix = new Matrix4f(); + projMatrix = new Matrix4f(); + up.z = 1; + } + + public void reshape(int width, int height, float distance) { + if (isOrtho) { + projMatrix.setOrtho(0, width, height, 0, 0, distance); + } else { + // 90 degree fow + float fow = PI / 2f; + float aspectRatio = (float) width / height; + projMatrix.setPerspective(fow, aspectRatio, MIN_DISTANCE, distance); + + } + updateMatrix(); + } + + public void setOrtho(boolean isOrtho) { + this.isOrtho = isOrtho; + } + + private void updateMatrix() { + // view matrix + if (isOrtho) { + projMatrix.get(matrixBuffer); + } else { + float sx = Math.sin(rotateAroundX); + float cx = Math.cos(rotateAroundX); + float sy = Math.sin(rotateAroundZ); + float cy = Math.cos(rotateAroundZ); + float sxDistance = distance * sx; + eyeVec.x = sxDistance * cy; + eyeVec.y = sxDistance * sy; + eyeVec.z = distance * cx; + viewMatrix.identity(); + viewMatrix.translate(0, 0, distance); + viewMatrix.lookAt(eyeVec, center, up); + projMatrix.mul(viewMatrix, projViewMatrix); + // model matrix is identity + projViewMatrix.get(matrixBuffer); + + } + uniformLocation.uploadMat4(matrixBuffer); + } + + public void drag(double dragDiffX, double dragDiffY) { + rotateAroundX += dragDiffY / 500f; + rotateAroundZ -= dragDiffX / 500f; + if (rotateAroundX > Math.PI) { + rotateAroundX = (float) Math.PI - 0.001f; + } else if (rotateAroundX < 0.001) { + rotateAroundX = (float) 0.001; + } + updateMatrix(); + } + + public void setDistance(float distance) { + this.distance = distance; + updateMatrix(); + } + + public void scroll(int scroll) { + int addedDistance = (int) (distance * scroll * SCROLL_PERCENTAGE); + distance += addedDistance; + updateMatrix(); + } + + public void free() { + MemoryUtil.memFree(matrixBuffer); + } + + public void move(double dragDiffX, double dragDiffY) { + } + +} diff --git a/src/main/java/de/hft/stuttgart/citygml/viewer/CityGMLViewer.java b/src/main/java/de/hft/stuttgart/citygml/viewer/CityGMLViewer.java new file mode 100644 index 0000000000000000000000000000000000000000..eb9fae20cad1be54161b8ab0ea2cecaebc843023 --- /dev/null +++ b/src/main/java/de/hft/stuttgart/citygml/viewer/CityGMLViewer.java @@ -0,0 +1,415 @@ +/*- + * Copyright 2021 Hochschule für Technik Stuttgart + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package de.hft.stuttgart.citygml.viewer; + +import java.awt.geom.AffineTransform; +import java.awt.image.AffineTransformOp; +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.DoubleBuffer; +import java.nio.FloatBuffer; +import java.util.ArrayList; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; + +import javax.imageio.ImageIO; +import javax.xml.parsers.ParserConfigurationException; + +import org.citygml4j.CityGMLContext; +import org.citygml4j.CityGMLContextException; +import org.citygml4j.model.core.AbstractCityObject; +import org.citygml4j.model.core.AbstractFeature; +import org.citygml4j.xml.reader.ChunkingOptions; +import org.citygml4j.xml.reader.CityGMLInputFactory; +import org.citygml4j.xml.reader.CityGMLReadException; +import org.citygml4j.xml.reader.CityGMLReader; +import org.lwjgl.PointerBuffer; +import org.lwjgl.glfw.Callbacks; +import org.lwjgl.glfw.GLFW; +import org.lwjgl.glfw.GLFWErrorCallback; +import org.lwjgl.glfw.GLFWMouseButtonCallback; +import org.lwjgl.glfw.GLFWScrollCallback; +import org.lwjgl.glfw.GLFWWindowSizeCallbackI; +import org.lwjgl.opengl.GL; +import org.lwjgl.opengl.GL11; +import org.lwjgl.opengl.GL13; +import org.lwjgl.opengl.GL30; +import org.lwjgl.system.MemoryStack; +import org.lwjgl.system.MemoryUtil; +import org.lwjgl.util.nfd.NativeFileDialog; +import org.xml.sax.SAXException; + +import com.jogamp.opengl.GLException; + +import de.hft.stuttgart.citygml.viewer.datastructure.BoundingBox; +import de.hft.stuttgart.citygml.viewer.math.Vector3d; +import de.hft.stuttgart.citygml.viewer.parser.CityGMLParser; +import de.hft.stuttgart.citygml.viewer.parser.FeatureMapper; +import de.hft.stuttgart.citygml.viewer.parser.ObservedInputStream; +import de.hft.stuttgart.citygml.viewer.parser.ParserConfiguration; + +public class CityGMLViewer { + + private static final Logger log = Logger.getLogger(CityGMLViewer.class.getName()); + + private static Camera camera; + + private static boolean mouse1Down = false; + private static boolean mouse2Down = false; + + private static double x; + private static double y; + + private static int width = 1024; + private static int height = 768; + + private static List<PolygonViewInformation> viewing = new ArrayList<>(); + private static float cameraViewDistance = 10000f; + + private static long windowId; + + private static ProgressBar bar; + + public static void main(String[] args) { + File f = null; + if (args != null && args.length == 1) { + f = new File(args[0]); + if (!f.exists() || f.isDirectory()) { + // no file given + f = null; + } + } + if (f == null) { + f = showFileChooserDialog(); + } + try { + setupWindow(f); + } finally { + for (PolygonViewInformation view : viewing) { + view.destroy(); + } + } + } + + private static File showFileChooserDialog() { + File f = null; + try (MemoryStack stack = MemoryStack.stackPush()) { + File defaultPath = new File("bla"); + PointerBuffer pathPointer = stack.mallocPointer(1); + int success = NativeFileDialog.NFD_OpenDialog("gml,xml", defaultPath.getAbsolutePath(), pathPointer); + if (success == NativeFileDialog.NFD_OKAY) { + String filePath = pathPointer.getStringUTF8(0); + f = new File(filePath); + } else { + System.exit(0); + } + } + return f; + } + + private static void screenShot() { + // Creating an rbg array of total pixels + int[] pixels = new int[width * height]; + int bindex; + // allocate space for RBG pixels + ByteBuffer fb = ByteBuffer.allocateDirect(width * height * 3); + + // grab a copy of the current frame contents as RGB + GL11.glReadPixels(0, 0, width, height, GL11.GL_RGB, GL11.GL_UNSIGNED_BYTE, fb); + + BufferedImage imageIn = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); + // convert RGB data in ByteBuffer to integer array + for (int i = 0; i < pixels.length; i++) { + bindex = i * 3; + pixels[i] = (fb.get(bindex) << 16) + (fb.get(bindex + 1) << 8) + (fb.get(bindex + 2)); + } + // Allocate colored pixel to buffered Image + imageIn.setRGB(0, 0, width, height, pixels, 0, width); + + // Creating the transformation direction (horizontal) + AffineTransform at = AffineTransform.getScaleInstance(1, -1); + at.translate(0, -imageIn.getHeight(null)); + + // Applying transformation + AffineTransformOp opRotated = new AffineTransformOp(at, AffineTransformOp.TYPE_BILINEAR); + BufferedImage imageOut = opRotated.filter(imageIn, null); + + try { + int nr = 0; + File f = new File("screen_" + nr + ".png"); + while (f.exists()) { + nr++; + f = new File("screen_" + nr + ".png"); + } + ImageIO.write(imageOut, "png", f); + } catch (Exception e) { + e.printStackTrace(); + } + } + + private static void setupWindow(File f) { + GLFW.glfwSetErrorCallback(GLFWErrorCallback.createPrint(System.err)); + if (!GLFW.glfwInit()) { + throw new IllegalStateException("Unable to initialize GLFW"); + } + String title = "CityGMLViewer"; + + GLFW.glfwDefaultWindowHints(); // Loads GLFW's default window settings + GLFW.glfwWindowHint(GLFW.GLFW_VISIBLE, GLFW.GLFW_FALSE); // Sets window to be visible + GLFW.glfwWindowHint(GLFW.GLFW_RESIZABLE, GLFW.GLFW_TRUE); // Sets whether the window is resizable + GLFW.glfwWindowHint(GLFW.GLFW_OPENGL_PROFILE, GLFW.GLFW_OPENGL_CORE_PROFILE); + GLFW.glfwWindowHint(GLFW.GLFW_CONTEXT_VERSION_MAJOR, 3); + GLFW.glfwWindowHint(GLFW.GLFW_CONTEXT_VERSION_MINOR, 3); + GLFW.glfwWindowHint(GLFW.GLFW_SAMPLES, 2); + + windowId = GLFW.glfwCreateWindow(width, height, title, 0, 0); + if (windowId == 0) { + GLFW.glfwTerminate(); + throw new IllegalStateException("Failed to create window"); + } + + GLFW.glfwSetWindowSizeCallback(windowId, new GLFWWindowSizeCallbackI() { + + @Override + public void invoke(long window, int width, int height) { + CityGMLViewer.width = width; + CityGMLViewer.height = height; + if (camera != null) { + camera.reshape(width, height, cameraViewDistance); + } + if (bar != null) { + bar.updateWindowSize(width, height); + } + GL11.glViewport(0, 0, width, height); + } + }); + + GLFW.glfwSetKeyCallback(windowId, (window, key, scancode, action, mods) -> { + if (key == GLFW.GLFW_KEY_ESCAPE && action == GLFW.GLFW_PRESS) { + GLFW.glfwSetWindowShouldClose(window, true); + } else if (key == GLFW.GLFW_KEY_F9 && action == GLFW.GLFW_PRESS) { + screenShot(); + } + }); + + GLFW.glfwSetMouseButtonCallback(windowId, new GLFWMouseButtonCallback() { + + @Override + public void invoke(long window, int button, int action, int mods) { + if (button == GLFW.GLFW_MOUSE_BUTTON_1 && action == GLFW.GLFW_PRESS) { + mouse1Down = true; + try (MemoryStack stack = MemoryStack.stackPush()) { + DoubleBuffer xBuffer = stack.mallocDouble(1); + DoubleBuffer yBuffer = stack.mallocDouble(1); + GLFW.glfwGetCursorPos(windowId, xBuffer, yBuffer); + x = xBuffer.get(0); + y = yBuffer.get(0); + } + } else if (button == GLFW.GLFW_MOUSE_BUTTON_1 && action == GLFW.GLFW_RELEASE) { + mouse1Down = false; + } else if (button == GLFW.GLFW_MOUSE_BUTTON_2 && action == GLFW.GLFW_PRESS) { + mouse2Down = true; + try (MemoryStack stack = MemoryStack.stackPush()) { + DoubleBuffer xBuffer = stack.mallocDouble(1); + DoubleBuffer yBuffer = stack.mallocDouble(1); + GLFW.glfwGetCursorPos(windowId, xBuffer, yBuffer); + x = xBuffer.get(0); + y = yBuffer.get(0); + } + } else if (button == GLFW.GLFW_MOUSE_BUTTON_2 && action == GLFW.GLFW_RELEASE) { + mouse2Down = false; + } + } + }); + + GLFW.glfwSetScrollCallback(windowId, new GLFWScrollCallback() { + + @Override + public void invoke(long window, double xoffset, double yoffset) { + camera.scroll((int) (-yoffset)); + } + }); + + GLFW.glfwSetCursorPosCallback(windowId, (window, xPos, yPos) -> { + if (mouse1Down) { + try (MemoryStack stack = MemoryStack.stackPush()) { + double dragDiffX = xPos - x; + double dragDiffY = yPos - y; + camera.drag(dragDiffX, dragDiffY); + x = xPos; + y = yPos; + } + } else if (mouse2Down) { + try (MemoryStack stack = MemoryStack.stackPush()) { + double dragDiffX = xPos - x; + double dragDiffY = yPos - y; + camera.move(dragDiffX, dragDiffY); + x = xPos; + y = yPos; + } + } + }); + + GLFW.glfwMakeContextCurrent(windowId); // glfwSwapInterval needs a context on the calling thread, otherwise will + // cause + // NO_CURRENT_CONTEXT error + org.lwjgl.opengl.GLCapabilities caps = GL.createCapabilities(); // Will let lwjgl know we want to use this + // context as the context to draw with + if (!caps.OpenGL33) { + log.warning("OpenGL 33 is not supported"); + } + GLFW.glfwSwapInterval(1); // How many draws to swap the buffer + + GL11.glEnable(GL11.GL_DEPTH_TEST); + GL11.glEnable(GL13.GL_MULTISAMPLE); + GL11.glDisable(GL11.GL_CULL_FACE); + + GL11.glViewport(0, 0, width, height); + GLFW.glfwShowWindow(windowId); // Shows the window + + if (log.isLoggable(Level.INFO)) { + log.info("OpenGL error code after displaying the window: " + GL11.glGetError()); + } + + FloatBuffer clearColor = null; + FloatBuffer clearDepth = null; + + try { + Shader program = new Shader("vertex.vert", "fragment.frag"); + program.use(); + if (log.isLoggable(Level.INFO)) { + log.info("OpenGL error code after shader loading: " + GL11.glGetError()); + } + camera = new Camera(program); + camera.setOrtho(true); + camera.reshape(width, height, cameraViewDistance); + bar = new ProgressBar(windowId, width, height); + bar.drawProgress(0); + if (log.isLoggable(Level.INFO)) { + log.info("OpenGL error code after drawing initial progress bar: " + GL11.glGetError()); + } + + loadGmlFile(f); + bar.free(); + bar = null; + if (log.isLoggable(Level.INFO)) { + log.info("OpenGL error code after loading GML file: " + GL11.glGetError()); + } + camera.setOrtho(false); + camera.reshape(width, height, cameraViewDistance); + + clearColor = MemoryUtil.memAllocFloat(4); + clearColor.put(0, 0.9411765f).put(1, 1f).put(2, 1f).put(3, 1f); + clearDepth = MemoryUtil.memAllocFloat(1); + clearDepth.put(0, 1f); + + if (log.isLoggable(Level.INFO)) { + log.info("OpenGL error code before loop: " + GL11.glGetError()); + } + while (!GLFW.glfwWindowShouldClose(windowId)) { + /* Do something */ + GL30.glClearBufferfv(GL11.GL_COLOR, 0, clearColor); + GL30.glClearBufferfv(GL11.GL_DEPTH, 0, clearDepth); + + for (PolygonViewInformation view : viewing) { + view.draw(); + } + + Thread.sleep(15); + + GLFW.glfwSwapBuffers(windowId); + GLFW.glfwPollEvents(); + } + } catch (InterruptedException e) { + e.printStackTrace(); + Thread.currentThread().interrupt(); + } finally { + MemoryUtil.memFree(clearColor); + MemoryUtil.memFree(clearDepth); + if (camera != null) { + camera.free(); + } + } + + GLFW.glfwDestroyWindow(windowId); + Callbacks.glfwFreeCallbacks(windowId); + GLFW.glfwTerminate(); + } + + private static void loadGmlFile(File f) { + try { + File file = f; + ParserConfiguration config = new ParserConfiguration(); + CityGMLParser.parseEpsgCodeFromFile(file, config); + CityGMLContext context = CityGMLContext.newInstance(); + CityGMLInputFactory in = context.createCityGMLInputFactory() + .withChunking(ChunkingOptions.chunkByCityModelMembers()); + try (ObservedInputStream ois = new ObservedInputStream(file); + CityGMLReader reader = in.createCityGMLReader(file.getAbsolutePath(), ois)) { + ois.addListener(p -> bar.drawProgress((int) (p * 82))); + FeatureMapper mapper = new FeatureMapper(config); + while (reader.hasNext()) { + AbstractFeature chunk = reader.next(); + if (chunk instanceof AbstractCityObject) { + AbstractCityObject aco = (AbstractCityObject) chunk; + aco.accept(mapper); + } + } + BoundingBox bbox = BoundingBox.of(mapper); + bar.drawProgress(81); + Vector3d center = bbox.getCenter(); + mapper.movePolygonsBy(center); + bar.drawProgress(82); + long nrOfPolygons = (long) mapper.getLod1Polygons().size() + mapper.getLod2Polygons().size() + + mapper.getLod3Polygons().size() + mapper.getLod4Polygons().size(); + long[] count = new long[1]; + PolygonListener l = () -> { + count[0]++; + bar.drawProgress(82 + (int) (count[0] * 9 / nrOfPolygons)); + }; + if (!mapper.getLod1Polygons().isEmpty()) { + PolygonViewInformation lod1ViewInfo = new PolygonViewInformation(mapper.getLod1Polygons(), l); + viewing.add(lod1ViewInfo); + } + if (!mapper.getLod2Polygons().isEmpty()) { + PolygonViewInformation lod2ViewInfo = new PolygonViewInformation(mapper.getLod2Polygons(), l); + viewing.add(lod2ViewInfo); + } + if (!mapper.getLod3Polygons().isEmpty()) { + PolygonViewInformation lod3ViewInfo = new PolygonViewInformation(mapper.getLod3Polygons(), l); + viewing.add(lod3ViewInfo); + } + if (!mapper.getLod3Polygons().isEmpty()) { + PolygonViewInformation lod4ViewInfo = new PolygonViewInformation(mapper.getLod4Polygons(), l); + viewing.add(lod4ViewInfo); + } + + double longestSide = bbox.getDiagonalLength() * 0.2; + double d = longestSide / Math.tan(Math.toRadians(30) / 2); + double translateZ = -d; + camera.setDistance((float) translateZ); + cameraViewDistance = (float) longestSide * 20f; + camera.reshape(width, height, cameraViewDistance); + } + } catch (GLException | IOException | ParserConfigurationException | SAXException | CityGMLContextException + | CityGMLReadException e) { + e.printStackTrace(); + } + } +} diff --git a/src/main/java/de/hft/stuttgart/citygml/viewer/CityGmlHandler.java b/src/main/java/de/hft/stuttgart/citygml/viewer/CityGmlHandler.java new file mode 100644 index 0000000000000000000000000000000000000000..4377dd4b3308b79fdb0f0683838fc2e08d8f4d69 --- /dev/null +++ b/src/main/java/de/hft/stuttgart/citygml/viewer/CityGmlHandler.java @@ -0,0 +1,82 @@ +/*- + * Copyright 2021 Hochschule für Technik Stuttgart + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package de.hft.stuttgart.citygml.viewer; + +import org.xml.sax.Attributes; +import org.xml.sax.SAXException; +import org.xml.sax.helpers.DefaultHandler; + +import de.hft.stuttgart.citygml.viewer.math.Vector3d; +import de.hft.stuttgart.citygml.viewer.parser.EnvelopeFoundException; + +public class CityGmlHandler extends DefaultHandler { + + private boolean foundEnvelope = false; + + private Vector3d lowerCorner; + private Vector3d upperCorner; + private String epsg; + + private boolean nextContentIsLowerCorner = false; + private boolean nextContentIsUpperCorner = false; + + @Override + public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { + if (foundEnvelope) { + if (qName.endsWith("lowerCorner")) { + nextContentIsLowerCorner = true; + } else if (qName.endsWith("upperCorner")) { + nextContentIsUpperCorner = true; + } + } else if (qName.endsWith("Envelope")) { + foundEnvelope = true; + epsg = attributes.getValue("srsName"); + } + } + + public Vector3d getLowerCorner() { + return lowerCorner; + } + + public Vector3d getUpperCorner() { + return upperCorner; + } + + public String getEpsg() { + return epsg; + } + + @Override + public void characters(char[] ch, int start, int length) throws SAXException { + if (nextContentIsLowerCorner) { + lowerCorner = parsePoint(ch, start, length); + nextContentIsLowerCorner = false; + } else if (nextContentIsUpperCorner) { + upperCorner = parsePoint(ch, start, length); + nextContentIsUpperCorner = false; + throw new EnvelopeFoundException("Found envelope, stop parsing"); + } + } + + private Vector3d parsePoint(char[] ch, int start, int length) { + String s = new String(ch, start, length); + String[] coords = s.split("\\s+"); + double x = Double.parseDouble(coords[0]); + double y = Double.parseDouble(coords[1]); + double z = Double.parseDouble(coords[2]); + return new Vector3d(x, y, z); + } +} diff --git a/src/main/java/de/hft/stuttgart/citygml/viewer/PolygonListener.java b/src/main/java/de/hft/stuttgart/citygml/viewer/PolygonListener.java new file mode 100644 index 0000000000000000000000000000000000000000..ab0a8095b8515ab610381be2c0bfa130e05afec6 --- /dev/null +++ b/src/main/java/de/hft/stuttgart/citygml/viewer/PolygonListener.java @@ -0,0 +1,22 @@ +/*- + * Copyright 2021 Hochschule für Technik Stuttgart + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package de.hft.stuttgart.citygml.viewer; + +public interface PolygonListener { + + public void finishedPolygon(); + +} diff --git a/src/main/java/de/hft/stuttgart/citygml/viewer/PolygonViewInformation.java b/src/main/java/de/hft/stuttgart/citygml/viewer/PolygonViewInformation.java new file mode 100644 index 0000000000000000000000000000000000000000..15b0e417f67244b5a90a3d020fbd12b8ebda1e34 --- /dev/null +++ b/src/main/java/de/hft/stuttgart/citygml/viewer/PolygonViewInformation.java @@ -0,0 +1,157 @@ +/*- + * Copyright 2021 Hochschule für Technik Stuttgart + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package de.hft.stuttgart.citygml.viewer; + +import static org.lwjgl.opengl.GL11.GL_FLOAT; + +import java.awt.Color; +import java.nio.Buffer; +import java.nio.FloatBuffer; +import java.nio.IntBuffer; +import java.util.ArrayList; +import java.util.List; + +import org.lwjgl.opengl.GL11; +import org.lwjgl.opengl.GL15; +import org.lwjgl.opengl.GL30; +import org.lwjgl.opengl.GL44; +import org.lwjgl.opengl.GL45; +import org.lwjgl.system.MemoryUtil; + +import de.hft.stuttgart.citygml.viewer.datastructure.BufferConstants; +import de.hft.stuttgart.citygml.viewer.datastructure.Polygon; +import de.hft.stuttgart.citygml.viewer.datastructure.TesselatedPolygon; +import de.hft.stuttgart.citygml.viewer.datastructure.Triangle3d; +import de.hft.stuttgart.citygml.viewer.math.Vector3d; + +public class PolygonViewInformation { + + private static final Vector3d AXIS = new Vector3d(19, 0.8, 1.5).normalize(); + + + private int vao; + private IntBuffer buffers; + private boolean draw = false; + private int elements; + + private PolygonListener listener; + + public PolygonViewInformation(List<Polygon> polygons, PolygonListener listener) { + this.listener = listener; + vao = GL45.glCreateVertexArrays(); + GL45.glVertexArrayAttribBinding(vao, BufferConstants.ATTRIBUTE_POSITION, BufferConstants.BINDING_INDEX_0); + GL45.glVertexArrayAttribBinding(vao, BufferConstants.ATTRIBUTE_COLOR, BufferConstants.BINDING_INDEX_0); + + GL45.glVertexArrayAttribFormat(vao, BufferConstants.ATTRIBUTE_POSITION, 3, GL_FLOAT, false, 0); + GL45.glVertexArrayAttribFormat(vao, BufferConstants.ATTRIBUTE_COLOR, 3, GL_FLOAT, false, 3 * 4); + + GL45.glEnableVertexArrayAttrib(vao, BufferConstants.ATTRIBUTE_POSITION); + GL45.glEnableVertexArrayAttrib(vao, BufferConstants.ATTRIBUTE_COLOR); + + buffers = MemoryUtil.memAllocInt(BufferConstants.SIZE); + GL45.glCreateBuffers(buffers); + GL45.glVertexArrayElementBuffer(vao, buffers.get(BufferConstants.ELEMENT)); + GL45.glVertexArrayVertexBuffer(vao, BufferConstants.BINDING_INDEX_0, buffers.get(BufferConstants.VERTEX), 0, (3 + 3) * 4); + setupBuffers(polygons); + } + + private void setupBuffers(List<Polygon> polys) { + List<TesselatedPolygon> tessPolys = new ArrayList<>(); + for (Polygon poly : polys) { + tessPolys.add(TesselatedPolygon.of(poly)); + listener.finishedPolygon(); + } + + int nrOfTriangles = 0; + for (TesselatedPolygon tessPoly : tessPolys) { + nrOfTriangles += tessPoly.getTriangles().size(); + } + + // 6 float for 3 vertices per triangle + int moreSpace = nrOfTriangles * 3 * 3 * 2; + FloatBuffer vertexData = MemoryUtil.memAllocFloat(moreSpace); + IntBuffer elementData = MemoryUtil.memAllocInt(nrOfTriangles * 3); + float[] rgba = new float[4]; + for (TesselatedPolygon tessPoly : tessPolys) { + Color baseColor = tessPoly.getColor(); + double cos = tessPoly.getNormal().dot(AXIS); + double acos = Math.acos(cos); + // normalize to range [0.3, 0.9] + acos = acos / Math.PI; + acos = acos * 0.6 + 0.3; + Color derivedColor = new Color((int) (baseColor.getRed() * acos), (int) (baseColor.getGreen() * acos), + (int) (baseColor.getBlue() * acos)); + derivedColor.getComponents(rgba); + for (Triangle3d tri : tessPoly.getTriangles()) { + addPoint(vertexData, tri.getP1(), rgba[0], rgba[1], rgba[2]); + addPoint(vertexData, tri.getP2(), rgba[0], rgba[1], rgba[2]); + addPoint(vertexData, tri.getP3(), rgba[0], rgba[1], rgba[2]); + } + listener.finishedPolygon(); + } + for (int i = 0; i < elementData.capacity(); i++) { + elementData.put(i); + } + ((Buffer) elementData).flip(); + ((Buffer) vertexData).flip(); + + GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, buffers.get(BufferConstants.VERTEX)); + GL44.glBufferStorage(GL15.GL_ARRAY_BUFFER, vertexData, 0); + GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0); + + GL15.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, buffers.get(BufferConstants.ELEMENT)); + GL44.glBufferStorage(GL15.GL_ELEMENT_ARRAY_BUFFER, elementData, 0); + GL15.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, 0); + + MemoryUtil.memFree(elementData); + MemoryUtil.memFree(vertexData); + + draw = true; + elements = elementData.capacity(); + } + + private static void addPoint(FloatBuffer lod1VertexData, Vector3d point, float red, float green, float blue) { + lod1VertexData.put((float) point.getX()); + lod1VertexData.put((float) point.getY()); + lod1VertexData.put((float) point.getZ()); + lod1VertexData.put(red); + lod1VertexData.put(green); + lod1VertexData.put(blue); + } + + public boolean shouldDraw() { + return draw; + } + + public void setShouldDraw(boolean draw) { + this.draw = draw; + } + + public int getVao() { + return vao; + } + + public void destroy() { + MemoryUtil.memFree(buffers); + } + + public void draw() { + if (draw) { + GL30.glBindVertexArray(vao); + GL11.glDrawElements(GL11.GL_TRIANGLES, elements, GL11.GL_UNSIGNED_INT, 0); + } + } +} diff --git a/src/main/java/de/hft/stuttgart/citygml/viewer/ProgressBar.java b/src/main/java/de/hft/stuttgart/citygml/viewer/ProgressBar.java new file mode 100644 index 0000000000000000000000000000000000000000..6ee0d19b2c82975d70a33fbd9f920e00c81e46a6 --- /dev/null +++ b/src/main/java/de/hft/stuttgart/citygml/viewer/ProgressBar.java @@ -0,0 +1,186 @@ +/*- + * Copyright 2021 Hochschule für Technik Stuttgart + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package de.hft.stuttgart.citygml.viewer; + +import java.nio.FloatBuffer; +import java.nio.IntBuffer; + +import org.lwjgl.glfw.GLFW; +import org.lwjgl.opengl.GL11; +import org.lwjgl.opengl.GL15; +import org.lwjgl.opengl.GL20; +import org.lwjgl.opengl.GL30; +import org.lwjgl.system.MemoryStack; +import org.lwjgl.system.MemoryUtil; + +public class ProgressBar { + + private int currentProgress; + + private FloatBuffer clearColor; + private FloatBuffer clearDepth; + + private int width; + private int height; + + private long windowId; + + private FloatBuffer box; + private int vao; + private int whiteVertexBuffer; + private int indexBufferName; + private int colorBufferName; + + public ProgressBar(long windowId, int width, int height) { + this.windowId = windowId; + this.width = width; + this.height = height; + clearColor = MemoryUtil.memAllocFloat(4); + clearColor.put(0, 0f).put(1, 0f).put(2, 0f).put(3, 1f); + clearDepth = MemoryUtil.memAllocFloat(1); + clearDepth.put(0, 1f); + box = MemoryUtil.memAllocFloat(8 * 3); + + vao = GL30.glGenVertexArrays(); + GL30.glBindVertexArray(vao); + + whiteVertexBuffer = GL15.glGenBuffers(); + GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, whiteVertexBuffer); + GL20.glVertexAttribPointer(0, 3, GL11.GL_FLOAT, false, 0, 0); + GL20.glEnableVertexAttribArray(0); + + indexBufferName = GL15.glGenBuffers(); + GL15.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, indexBufferName); + try (MemoryStack stack = MemoryStack.stackPush()) { + // index + IntBuffer indexBuffer = stack.mallocInt(12); + indexBuffer.put(0, 0); + indexBuffer.put(1, 1); + indexBuffer.put(2, 2); + indexBuffer.put(3, 0); + indexBuffer.put(4, 2); + indexBuffer.put(5, 3); + indexBuffer.put(6, 4); + indexBuffer.put(7, 5); + indexBuffer.put(8, 6); + indexBuffer.put(9, 4); + indexBuffer.put(10, 6); + indexBuffer.put(11, 7); + GL15.glBufferData(GL15.GL_ELEMENT_ARRAY_BUFFER, indexBuffer, GL15.GL_STATIC_DRAW); + + // color + FloatBuffer color = stack.mallocFloat(8 * 3); + for (int i = 0; i < 4 * 3; i++) { + color.put(i, 1); + } + color.put(12, 0); + color.put(13, 0); + color.put(14, 1); + color.put(15, 0); + color.put(16, 0); + color.put(17, 1); + color.put(18, 0); + color.put(19, 0); + color.put(20, 1); + color.put(21, 0); + color.put(22, 0); + color.put(23, 1); + GL20.glEnableVertexAttribArray(1); + colorBufferName = GL15.glGenBuffers(); + GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, colorBufferName); + GL15.glBufferData(GL15.GL_ARRAY_BUFFER, color, GL15.GL_STATIC_DRAW); + GL20.glVertexAttribPointer(1, 3, GL11.GL_FLOAT, false, 0, 0); + } + GL30.glBindVertexArray(0); + + } + + public void updateWindowSize(int width, int height) { + this.width = width; + this.height = height; + forceDraw(currentProgress); + } + + private void draw() { + } + + public void drawProgress(int progress) { + if (progress == currentProgress) { + return; + } + forceDraw(progress); + } + + private void forceDraw(int progress) { + GL30.glClearBufferfv(GL11.GL_COLOR, 0, clearColor); + GL30.glClearBufferfv(GL11.GL_DEPTH, 0, clearDepth); + + float progressX = width / 2f - 150f; + float progressY = height / 2f - 30f; + float progressWidth = 300; + float progressHeight = 60; + float okWidth = (progress / 100f) * progressWidth; + + box.put(0, progressX + okWidth); + box.put(1, progressY); + box.put(2, 0); + box.put(3, progressX + progressWidth); + box.put(4, progressY); + box.put(5, 0); + + box.put(6, progressX + progressWidth); + box.put(7, progressY + progressHeight); + box.put(8, 0); + box.put(9, progressX + okWidth); + box.put(10, progressY + progressHeight); + box.put(11, 0); + + box.put(12, progressX); + box.put(13, progressY); + box.put(14, 0); + box.put(15, progressX + okWidth); + box.put(16, progressY); + box.put(17, 0); + box.put(18, progressX + okWidth); + box.put(19, progressY + progressHeight); + box.put(20, 0); + box.put(21, progressX); + box.put(22, progressY + progressHeight); + box.put(23, 0); + + GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, whiteVertexBuffer); + GL15.glBufferData(GL15.GL_ARRAY_BUFFER, box, GL15.GL_STATIC_DRAW); + + GL30.glBindVertexArray(vao); + GL11.glDrawElements(GL11.GL_TRIANGLES, 12, GL11.GL_UNSIGNED_INT, 0); + GLFW.glfwSwapBuffers(windowId); + GLFW.glfwPollEvents(); + currentProgress = progress; + + draw(); + } + + public void free() { + MemoryUtil.memFree(clearDepth); + MemoryUtil.memFree(clearColor); + MemoryUtil.memFree(box); + GL15.glDeleteBuffers(whiteVertexBuffer); + GL15.glDeleteBuffers(indexBufferName); + GL15.glDeleteBuffers(colorBufferName); + GL30.glDeleteVertexArrays(vao); + } + +} diff --git a/src/main/java/de/hft/stuttgart/citygml/viewer/Shader.java b/src/main/java/de/hft/stuttgart/citygml/viewer/Shader.java new file mode 100644 index 0000000000000000000000000000000000000000..8432b62becf5850126be8633f881a15a45cba470 --- /dev/null +++ b/src/main/java/de/hft/stuttgart/citygml/viewer/Shader.java @@ -0,0 +1,79 @@ +/*- + * Copyright 2021 Hochschule für Technik Stuttgart + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package de.hft.stuttgart.citygml.viewer; + +import static org.lwjgl.opengl.GL20.glAttachShader; +import static org.lwjgl.opengl.GL20.glCompileShader; +import static org.lwjgl.opengl.GL20.glCreateShader; +import static org.lwjgl.opengl.GL20.glDeleteShader; +import static org.lwjgl.opengl.GL20.glLinkProgram; +import static org.lwjgl.opengl.GL20.glShaderSource; +import static org.lwjgl.opengl.GL20.glUseProgram; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.UncheckedIOException; + +import org.lwjgl.opengl.GL20; + +public class Shader { + + private int programId; + + public Shader(String vertexPath, String fragmentPath) { + int vertexShader = glCreateShader(GL20.GL_VERTEX_SHADER); + glShaderSource(vertexShader, readFully(vertexPath)); + glCompileShader(vertexShader); + int fragmentShader = glCreateShader(GL20.GL_FRAGMENT_SHADER); + glShaderSource(fragmentShader, readFully(fragmentPath)); + glCompileShader(fragmentShader); + + programId = GL20.glCreateProgram(); + glAttachShader(programId, vertexShader); + glAttachShader(programId, fragmentShader); + glLinkProgram(programId); + + glDeleteShader(vertexShader); + glDeleteShader(fragmentShader); + } + + public void use() { + glUseProgram(programId); + } + + public static void unUse() { + glUseProgram(0); + } + + private String readFully(String path) { + try (BufferedReader br = new BufferedReader(new InputStreamReader(getClass().getClassLoader().getResourceAsStream(path)))) { + char[] buffer = new char[1024]; + StringBuilder builder = new StringBuilder(); + while (br.read(buffer) != -1) { + builder.append(buffer); + } + return builder.toString(); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + public UniformLocation getUniformLocation(String uniformName) { + return new UniformLocation(GL20.glGetUniformLocation(programId, uniformName)); + } + +} diff --git a/src/main/java/de/hft/stuttgart/citygml/viewer/UniformLocation.java b/src/main/java/de/hft/stuttgart/citygml/viewer/UniformLocation.java new file mode 100644 index 0000000000000000000000000000000000000000..60cc802b90984ffc313b15077b17a0cd6e98daad --- /dev/null +++ b/src/main/java/de/hft/stuttgart/citygml/viewer/UniformLocation.java @@ -0,0 +1,35 @@ +/*- + * Copyright 2021 Hochschule für Technik Stuttgart + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package de.hft.stuttgart.citygml.viewer; + +import java.nio.FloatBuffer; + +import org.lwjgl.opengl.GL20; + +public class UniformLocation { + + private int location; + + public UniformLocation(int location) { + this.location = location; + + } + + public void uploadMat4(FloatBuffer buf) { + GL20.glUniformMatrix4fv(location, false, buf); + } + +} diff --git a/src/main/java/de/hft/stuttgart/citygml/viewer/datastructure/BoundingBox.java b/src/main/java/de/hft/stuttgart/citygml/viewer/datastructure/BoundingBox.java new file mode 100644 index 0000000000000000000000000000000000000000..ebf1033933d5136e7d6acd37461aae58f0fa5ad0 --- /dev/null +++ b/src/main/java/de/hft/stuttgart/citygml/viewer/datastructure/BoundingBox.java @@ -0,0 +1,165 @@ +/*- + * Copyright 2021 Hochschule für Technik Stuttgart + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package de.hft.stuttgart.citygml.viewer.datastructure; + +import java.util.Arrays; +import java.util.List; + +import de.hft.stuttgart.citygml.viewer.math.BoundingBoxCalculator; +import de.hft.stuttgart.citygml.viewer.math.Vector3d; +import de.hft.stuttgart.citygml.viewer.parser.FeatureMapper; + +/** + * An axis aligned bounding box represented by its two corners + * + * @author Matthias Betz + * + */ +public class BoundingBox { + + private Vector3d[] bbox; + + /** + * Creates an axis aligned bounding box containing all points of all polygons + * + * @param polygons containing the points from which the box will be created + * @return the bounding box around all points + */ + public static BoundingBox of(List<Polygon> polygons) { + return BoundingBoxCalculator.calculateBoundingBox(polygons); + } + + public static BoundingBox ofPoints(List<? extends Vector3d> points) { + return BoundingBoxCalculator.calculateBoundingBoxFromPoints(points); + } + + /** + * Creates a new bounding box with two vectors as corner points. + * + * @param box the array of length 2 containing both corners + * @return the new bounding box + */ + public static BoundingBox of(Vector3d[] box) { + return new BoundingBox(box); + } + + private BoundingBox(Vector3d[] bbox) { + if (bbox == null || bbox.length != 2) { + throw new IllegalArgumentException("BoundingBox must be an array of the length 2"); + } + this.bbox = bbox; + } + + /** + * Calculates the volume of the box + * + * @return the volume of the box + */ + public double getVolume() { + double length = getDepth(); + double width = getWidth(); + double height = getHeight(); + return height * width * length; + } + + /** + * Calculates the center of the bounding box + * + * @return the center of the bounding box + */ + public Vector3d getCenter() { + return bbox[0].plus(bbox[1].minus(bbox[0]).mult(0.5)); + } + + /** + * Getter for the corner array + * + * @return the array containing the corner points + */ + public Vector3d[] getBox() { + return bbox; + } + + /** + * Calculates the width of the bounding box + * + * @return the width of the bounding box + */ + public double getWidth() { + return bbox[1].getY() - bbox[0].getY(); + } + + /** + * Calculates the height of the bounding box + * + * @return the height of the bounding box + */ + public double getHeight() { + return bbox[1].getZ() - bbox[0].getZ(); + } + + /** + * Calculates the depth of the bounding box + * + * @return the depth of the bounding box + */ + public double getDepth() { + return bbox[1].getX() - bbox[0].getX(); + } + + /** + * Returns the length of the longest side of the bounding box, ignoring the + * height. Only X and Y axis are considered + * + * @return the length of the longest side in X or Y direction + */ + public double getLongestSide() { + double width = getWidth(); + double depth = getDepth(); + if (width > depth) { + return width; + } else { + return depth; + } + } + + /** + * Calculates the direction vector from the lower corner to the upper corner + * + * @return the direction vector from the lower corner to the upper corner + */ + public Vector3d getDiagonal() { + return bbox[1].minus(bbox[0]); + } + + /** + * Calculates the distance between the corner points + * + * @return the distance between the corner points + */ + public double getDiagonalLength() { + return bbox[1].getDistance(bbox[0]); + } + + public static BoundingBox of(FeatureMapper mapper) { + return BoundingBoxCalculator.calculateBoundingBox(mapper); + } + + @Override + public String toString() { + return Arrays.toString(bbox); + } +} diff --git a/src/main/java/de/hft/stuttgart/citygml/viewer/datastructure/BufferConstants.java b/src/main/java/de/hft/stuttgart/citygml/viewer/datastructure/BufferConstants.java new file mode 100644 index 0000000000000000000000000000000000000000..85b8b188737d91b3135eb74ddd22d9a86cffc517 --- /dev/null +++ b/src/main/java/de/hft/stuttgart/citygml/viewer/datastructure/BufferConstants.java @@ -0,0 +1,33 @@ +/*- + * Copyright 2021 Hochschule für Technik Stuttgart + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package de.hft.stuttgart.citygml.viewer.datastructure; + +public class BufferConstants { + + private BufferConstants() { + } + + public static final int VERTEX = 0; + public static final int ELEMENT = 1; + public static final int SIZE = 2; + + public static final int ATTRIBUTE_POSITION = 0; + public static final int ATTRIBUTE_COLOR = 1; + + public static final int UNIFORM_0 = 1; + + public static final int BINDING_INDEX_0 = 0; +} diff --git a/src/main/java/de/hft/stuttgart/citygml/viewer/datastructure/JoglTesselator.java b/src/main/java/de/hft/stuttgart/citygml/viewer/datastructure/JoglTesselator.java new file mode 100644 index 0000000000000000000000000000000000000000..949b152cc011f31a239491a23c91499fad05d9b3 --- /dev/null +++ b/src/main/java/de/hft/stuttgart/citygml/viewer/datastructure/JoglTesselator.java @@ -0,0 +1,142 @@ +/*- + * Copyright 2021 Hochschule für Technik Stuttgart + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package de.hft.stuttgart.citygml.viewer.datastructure; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import com.jogamp.opengl.GL; +import com.jogamp.opengl.glu.GLU; +import com.jogamp.opengl.glu.GLUtessellator; +import com.jogamp.opengl.glu.GLUtessellatorCallbackAdapter; + +import de.hft.stuttgart.citygml.viewer.math.Vector3d; + +/** + * Tesselator to create triangles out of polygons. + * + * @author Matthias Betz + * + */ +public class JoglTesselator { + + private static final String DATA_WAS_NOT_A_LIST = "data was not a list"; + private static final GLU glu = new GLU(); + private static final GLUtessellator tess = GLU.gluNewTess(); + private static final GLUtessellatorCallbackAdapter callback; + private static final ArrayList<Primitive> primitives = new ArrayList<>(); + + static { + callback = new GLUtessellatorCallbackAdapter() { + + private Primitive lastPrimitive; + + @Override + public void beginData(int type, Object data) { + if (!(data instanceof ArrayList<?>)) { + throw new IllegalStateException(DATA_WAS_NOT_A_LIST); + } + @SuppressWarnings("unchecked") + ArrayList<Vector3d> vertices = (ArrayList<Vector3d>) data; + lastPrimitive = new Primitive(type, vertices); + primitives.add(lastPrimitive); + } + + @Override + public void vertexData(Object vertex, Object data) { + if (!(data instanceof ArrayList)) { + throw new IllegalStateException(DATA_WAS_NOT_A_LIST); + } + if (!(vertex instanceof Integer)) { + throw new IllegalStateException("vertex has to be an Integer"); + } + lastPrimitive.addIndex((Integer) vertex); + } + + @Override + public void combineData(double[] coords, Object[] vertexIndices, float[] weights, Object[] outgoingData, + Object data) { + if (!(data instanceof ArrayList<?>)) { + throw new IllegalStateException(DATA_WAS_NOT_A_LIST); + } + @SuppressWarnings("unchecked") + ArrayList<Vector3d> vertices = (ArrayList<Vector3d>) data; + // to ensure the coordinates are valid + double[] myCoords = Arrays.copyOf(coords, 3); + Vector3d newVertex = new Vector3d(myCoords); + outgoingData[0] = vertices.size(); + vertices.add(newVertex); + } + + @Override + public void errorData(int errnum, Object data) { + throw new TesselationException("GLU Error " + glu.gluErrorString(errnum)); + } + + }; + GLU.gluTessCallback(tess, GLU.GLU_TESS_BEGIN_DATA, callback); + GLU.gluTessCallback(tess, GLU.GLU_TESS_VERTEX_DATA, callback); + GLU.gluTessCallback(tess, GLU.GLU_TESS_ERROR_DATA, callback); + GLU.gluTessCallback(tess, GLU.GLU_TESS_COMBINE_DATA, callback); + GLU.gluTessCallback(tess, GLU.GLU_TESS_END_DATA, callback); + } + + public static TesselatedPolygon tesselatePolygon(Polygon p) { + ArrayList<Vector3d> vertices = new ArrayList<>(); + Vector3d normal = p.calculateNormal(); + if (p.getExteriorRing().getVertices().isEmpty()) { + return new TesselatedPolygon(vertices, Collections.emptyList()); + } + synchronized (tess) { + GLU.gluTessProperty(tess, GLU.GLU_TESS_BOUNDARY_ONLY, GL.GL_FALSE); + GLU.gluTessBeginPolygon(tess, vertices); + GLU.gluTessNormal(tess, normal.getX(), normal.getY(), normal.getZ()); + + // exterior ring + Ring ext = p.getExteriorRing(); + createContour(vertices, ext); + + // interior rings + for (Ring lr : p.getInteriorRings()) { + createContour(vertices, lr); + } + + GLU.gluTessEndPolygon(tess); + ArrayList<Integer> indices = new ArrayList<>(); + for (Primitive primitive : primitives) { + primitive.fillListWithIndices(indices); + } + primitives.clear(); + return new TesselatedPolygon(vertices, indices); + } + } + + private static void createContour(List<Vector3d> vertices, Ring lr) { + GLU.gluTessBeginContour(tess); + for (Vector3d v : lr.getVertices()) { + vertices.add(v); + GLU.gluTessVertex(tess, v.getCoordinates(), 0, vertices.size() - 1); + } + GLU.gluTessEndContour(tess); + } + + private JoglTesselator() { + // only static usage + } + +} diff --git a/src/main/java/de/hft/stuttgart/citygml/viewer/datastructure/Polygon.java b/src/main/java/de/hft/stuttgart/citygml/viewer/datastructure/Polygon.java new file mode 100644 index 0000000000000000000000000000000000000000..b3ccf15ec9ca401f12a3cc9b3eb241754cd31388 --- /dev/null +++ b/src/main/java/de/hft/stuttgart/citygml/viewer/datastructure/Polygon.java @@ -0,0 +1,65 @@ +/*- + * Copyright 2021 Hochschule für Technik Stuttgart + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package de.hft.stuttgart.citygml.viewer.datastructure; + +import java.awt.Color; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import de.hft.stuttgart.citygml.viewer.math.Vector3d; + +public class Polygon { + + private Ring exteriorRing; + private List<Ring> interiorRings; + private Color color; + + public Polygon(Color color) { + this.color = color; + } + + public Color getColor() { + return color; + } + + public Ring getExteriorRing() { + return exteriorRing; + } + + public void setExteriorRing(Ring exteriorRing) { + this.exteriorRing = exteriorRing; + } + + public void addInteriorRing(Ring innerRing) { + if (interiorRings == null) { + interiorRings = new ArrayList<>(); + } + interiorRings.add(innerRing); + } + + public List<Ring> getInteriorRings() { + if (interiorRings == null) { + return Collections.emptyList(); + } + return interiorRings; + } + + public Vector3d calculateNormal() { + return exteriorRing.calculateNormal(); + } + +} diff --git a/src/main/java/de/hft/stuttgart/citygml/viewer/datastructure/Primitive.java b/src/main/java/de/hft/stuttgart/citygml/viewer/datastructure/Primitive.java new file mode 100644 index 0000000000000000000000000000000000000000..3b465185419039bddf5df3f86ce938d9a77066c4 --- /dev/null +++ b/src/main/java/de/hft/stuttgart/citygml/viewer/datastructure/Primitive.java @@ -0,0 +1,103 @@ +/*- + * Copyright 2021 Hochschule für Technik Stuttgart + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package de.hft.stuttgart.citygml.viewer.datastructure; + +import java.util.ArrayList; +import java.util.List; + +import com.jogamp.opengl.GL; + +import de.hft.stuttgart.citygml.viewer.math.Vector3d; + +/** + * Result of the tesselation process. Primitives can be converted to triangles. + * + * @author Matthias Betz + * + */ +public class Primitive { + + private static final double AREA_EPSILON = 0.00001; + + private final int type; + private final List<Integer> pIndices = new ArrayList<>(); + private final List<Vector3d> vertices; + + public Primitive(int type, List<Vector3d> vertices) { + this.vertices = vertices; + this.type = type; + } + + public void fillListWithIndices(List<Integer> indices) { + switch (type) { + case GL.GL_TRIANGLES: + for (int i = 0; i < pIndices.size(); i += 3) { + addToListIfAreaNonNull(pIndices.get(i), pIndices.get(i + 1), pIndices.get(i + 2), indices); + } + break; + case GL.GL_TRIANGLE_STRIP: + for (int i = 0; i < pIndices.size() - 2; i++) { + if (i % 2 == 0) { + addToListIfAreaNonNull(pIndices.get(i), pIndices.get(i + 1), pIndices.get(i + 2), indices); + } else { + addToListIfAreaNonNull(pIndices.get(i), pIndices.get(i + 2), pIndices.get(i + 1), indices); + } + } + break; + case GL.GL_TRIANGLE_FAN: + Integer first = pIndices.get(0); + for (int i = 0; i < pIndices.size() - 2; i++) { + addToListIfAreaNonNull(first, pIndices.get(i + 1), pIndices.get(i + 2), indices); + } + break; + default: + throw new IllegalStateException("unknown type found: " + type); + } + } + + public void addIndex(Integer i) { + pIndices.add(i); + } + + private void addToListIfAreaNonNull(Integer i1, Integer i2, Integer i3, List<Integer> indices) { + Vector3d v1 = vertices.get(i1); + Vector3d v2 = vertices.get(i2); + Vector3d v3 = vertices.get(i3); + double area = Math.abs(calculateAreaOfTriangle(v1, v2, v3)); + if (area > AREA_EPSILON) { + indices.add(i1); + indices.add(i2); + indices.add(i3); + } + } + + /** + * Calculates the area of a triangle given by the 3 points + * + * @param v1 the first point of the triangle + * @param v2 the second point of the triangle + * @param v3 the last point of the triangle + * @return the area of the triangle + */ + private static double calculateAreaOfTriangle(Vector3d v1, Vector3d v2, Vector3d v3) { + double a = v1.getDistance(v2); + double b = v2.getDistance(v3); + double c = v1.getDistance(v3); + double s = 0.5d * (a + b + c); + return Math.sqrt(s * (s - a) * (s - b) * (s - c)); + } + +} diff --git a/src/main/java/de/hft/stuttgart/citygml/viewer/datastructure/Ring.java b/src/main/java/de/hft/stuttgart/citygml/viewer/datastructure/Ring.java new file mode 100644 index 0000000000000000000000000000000000000000..823a27eabb967ac1af72022aba6f12219b20235e --- /dev/null +++ b/src/main/java/de/hft/stuttgart/citygml/viewer/datastructure/Ring.java @@ -0,0 +1,68 @@ +/*- + * Copyright 2021 Hochschule für Technik Stuttgart + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package de.hft.stuttgart.citygml.viewer.datastructure; + +import java.util.ArrayList; +import java.util.List; + +import de.hft.stuttgart.citygml.viewer.math.Vector3d; + +public class Ring { + + private List<Vector3d> vertices = new ArrayList<>(); + + public List<Vector3d> getVertices() { + return vertices; + } + + public void addPoint(Vector3d v) { + vertices.add(v); + } + + public Vector3d calculateNormal() { + double[] coords = new double[3]; + for (int i = 0; i < vertices.size() - 1; i++) { + Vector3d current = vertices.get(i + 0); + Vector3d next = vertices.get(i + 1); + coords[0] += (current.getZ() + next.getZ()) * (current.getY() - next.getY()); + coords[1] += (current.getX() + next.getX()) * (current.getZ() - next.getZ()); + coords[2] += (current.getY() + next.getY()) * (current.getX() - next.getX()); + } + + if (coords[0] == 0 && coords[1] == 0 && coords[2] == 0) { + // no valid normal vector found + if (vertices.size() < 3) { + // no three points, return x-axis + return new Vector3d(1, 0, 0); + } + + Vector3d v1 = vertices.get(0); + Vector3d v2 = vertices.get(1); + Vector3d v3 = vertices.get(2); + return calculateNormalWithCross(v1, v2, v3); + } + Vector3d v = new Vector3d(coords); + v.normalize(); + return v; + } + + private Vector3d calculateNormalWithCross(Vector3d v1, Vector3d v2, Vector3d v3) { + Vector3d dir1 = v2.minus(v1); + Vector3d dir2 = v3.minus(v1); + Vector3d cross = dir1.cross(dir2); + return cross.normalize(); + } +} diff --git a/src/main/java/de/hft/stuttgart/citygml/viewer/datastructure/TesselatedPolygon.java b/src/main/java/de/hft/stuttgart/citygml/viewer/datastructure/TesselatedPolygon.java new file mode 100644 index 0000000000000000000000000000000000000000..7fce866e4b2e0a10bfa627b897284fe7c8516919 --- /dev/null +++ b/src/main/java/de/hft/stuttgart/citygml/viewer/datastructure/TesselatedPolygon.java @@ -0,0 +1,67 @@ +/*- + * Copyright 2021 Hochschule für Technik Stuttgart + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package de.hft.stuttgart.citygml.viewer.datastructure; + +import java.awt.Color; +import java.util.ArrayList; +import java.util.List; + +import de.hft.stuttgart.citygml.viewer.math.Vector3d; + +public class TesselatedPolygon { + + + private List<Triangle3d> triangles; + private Vector3d normal; + private Color color; + + public static TesselatedPolygon of(Polygon p) { + TesselatedPolygon tessP = JoglTesselator.tesselatePolygon(p); + tessP.normal = p.calculateNormal(); + tessP.color = p.getColor(); + return tessP; + } + + public TesselatedPolygon(List<Triangle3d> triangles) { + this.triangles = triangles; + } + + public TesselatedPolygon(List<Vector3d> vertices, List<Integer> indices) { + if (indices.size() % 3 != 0) { + throw new IllegalArgumentException("Number of indices is not a multiple of 3"); + } + triangles = new ArrayList<>(); + for (int i = 0; i < indices.size(); i = i + 3) { + Vector3d v1 = vertices.get(indices.get(i + 0)); + Vector3d v2 = vertices.get(indices.get(i + 1)); + Vector3d v3 = vertices.get(indices.get(i + 2)); + triangles.add(new Triangle3d(v1, v2, v3)); + } + } + + public List<Triangle3d> getTriangles() { + return triangles; + } + + public Vector3d getNormal() { + return normal; + } + + public Color getColor() { + return color; + } + +} diff --git a/src/main/java/de/hft/stuttgart/citygml/viewer/datastructure/TesselationException.java b/src/main/java/de/hft/stuttgart/citygml/viewer/datastructure/TesselationException.java new file mode 100644 index 0000000000000000000000000000000000000000..f0cf2ef48afc09a478ba8bdd1a72966a6c6e20f7 --- /dev/null +++ b/src/main/java/de/hft/stuttgart/citygml/viewer/datastructure/TesselationException.java @@ -0,0 +1,49 @@ +/*- + * Copyright 2021 Hochschule für Technik Stuttgart + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package de.hft.stuttgart.citygml.viewer.datastructure; + +/** + * Thrown when something went wrong with the tesselation process. + * + * @author Matthias Betz + * + */ +public class TesselationException extends RuntimeException { + + public TesselationException() { + super(); + } + + public TesselationException(String message, Throwable cause, boolean enableSuppression, + boolean writableStackTrace) { + super(message, cause, enableSuppression, writableStackTrace); + } + + public TesselationException(String message, Throwable cause) { + super(message, cause); + } + + public TesselationException(Throwable cause) { + super(cause); + } + + private static final long serialVersionUID = -2010522579830781136L; + + public TesselationException(String message) { + super(message); + } + +} diff --git a/src/main/java/de/hft/stuttgart/citygml/viewer/datastructure/Triangle3d.java b/src/main/java/de/hft/stuttgart/citygml/viewer/datastructure/Triangle3d.java new file mode 100644 index 0000000000000000000000000000000000000000..3fc6d162eff64373947de994f6bf82f4884b9644 --- /dev/null +++ b/src/main/java/de/hft/stuttgart/citygml/viewer/datastructure/Triangle3d.java @@ -0,0 +1,100 @@ +/*- + * Copyright 2021 Hochschule für Technik Stuttgart + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package de.hft.stuttgart.citygml.viewer.datastructure; + +import de.hft.stuttgart.citygml.viewer.math.Vector3d; + +/** + * A three dimensional triangle described by three points + * + * @author Matthias Betz + * + */ +public class Triangle3d { + + private Vector3d p1; + private Vector3d p2; + private Vector3d p3; + + public Triangle3d(Vector3d p1, Vector3d p2, Vector3d p3) { + this.p1 = p1; + this.p2 = p2; + this.p3 = p3; + } + + public Vector3d getP1() { + return p1; + } + + public Vector3d getP2() { + return p2; + } + + public Vector3d getP3() { + return p3; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((p1 == null) ? 0 : p1.hashCode()); + result = prime * result + ((p2 == null) ? 0 : p2.hashCode()); + result = prime * result + ((p3 == null) ? 0 : p3.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + Triangle3d other = (Triangle3d) obj; + if (p1 == null) { + if (other.p1 != null) { + return false; + } + } else if (!p1.equals(other.p1)) { + return false; + } + if (p2 == null) { + if (other.p2 != null) { + return false; + } + } else if (!p2.equals(other.p2)) { + return false; + } + if (p3 == null) { + if (other.p3 != null) { + return false; + } + } else if (!p3.equals(other.p3)) { + return false; + } + return true; + } + + @Override + public String toString() { + return "Triangle3d [p1=" + p1 + ", p2=" + p2 + ", p3=" + p3 + "]"; + } +} diff --git a/src/main/java/de/hft/stuttgart/citygml/viewer/math/BoundingBoxCalculator.java b/src/main/java/de/hft/stuttgart/citygml/viewer/math/BoundingBoxCalculator.java new file mode 100644 index 0000000000000000000000000000000000000000..46973e521dfc65d58d8fe92c47a4812a8bfd54b8 --- /dev/null +++ b/src/main/java/de/hft/stuttgart/citygml/viewer/math/BoundingBoxCalculator.java @@ -0,0 +1,162 @@ +/*- + * Copyright 2021 Hochschule für Technik Stuttgart + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package de.hft.stuttgart.citygml.viewer.math; + +import java.util.ArrayList; +import java.util.List; + +import de.hft.stuttgart.citygml.viewer.datastructure.BoundingBox; +import de.hft.stuttgart.citygml.viewer.datastructure.Polygon; +import de.hft.stuttgart.citygml.viewer.parser.FeatureMapper; + +/** + * Utility class for calculating axis aligned bounding boxes for different + * inputs. + * + * @author Matthias Betz + * + */ +public class BoundingBoxCalculator { + + private BoundingBoxCalculator() { + // only static use + } + + /** + * This will calculate two points where every other point in the geometry is + * between those two. If the geometry does not contain points or is null, an + * empty array is returned. This is considered an axis aligned bounding box. + * Only the exterior rings of the polygons is used as inner rings should be + * within the exterior or they are faulty. + * + * @param a list of polygons containing the vertices + * @return an BoundingBox containing the two end points of the bounding box. + */ + public static BoundingBox calculateBoundingBox(List<Polygon> polygons) { + double lowX = Double.MAX_VALUE; + double highX = Double.NEGATIVE_INFINITY; + double lowY = Double.MAX_VALUE; + double highY = Double.NEGATIVE_INFINITY; + double lowZ = Double.MAX_VALUE; + double highZ = Double.NEGATIVE_INFINITY; + for (Polygon p : polygons) { + // only need to check exterior rings + for (Vector3d v : p.getExteriorRing().getVertices()) { + if (v.getX() < lowX) { + lowX = v.getX(); + } + if (v.getX() > highX) { + highX = v.getX(); + } + if (v.getY() < lowY) { + lowY = v.getY(); + } + if (v.getY() > highY) { + highY = v.getY(); + } + if (v.getZ() < lowZ) { + lowZ = v.getZ(); + } + if (v.getZ() > highZ) { + highZ = v.getZ(); + } + } + } + Vector3d[] result = new Vector3d[2]; + result[0] = new Vector3d(lowX, lowY, lowZ); + result[1] = new Vector3d(highX, highY, highZ); + return BoundingBox.of(result); + } + + public static BoundingBox calculateBoundingBoxFromPoints(List<? extends Vector3d> points) { + double lowX = Double.MAX_VALUE; + double highX = Double.NEGATIVE_INFINITY; + double lowY = Double.MAX_VALUE; + double highY = Double.NEGATIVE_INFINITY; + double lowZ = Double.MAX_VALUE; + double highZ = Double.NEGATIVE_INFINITY; + // only need to check exterior rings + for (Vector3d v : points) { + if (v.getX() < lowX) { + lowX = v.getX(); + } + if (v.getX() > highX) { + highX = v.getX(); + } + if (v.getY() < lowY) { + lowY = v.getY(); + } + if (v.getY() > highY) { + highY = v.getY(); + } + if (v.getZ() < lowZ) { + lowZ = v.getZ(); + } + if (v.getZ() > highZ) { + highZ = v.getZ(); + } + } + Vector3d[] result = new Vector3d[2]; + result[0] = new Vector3d(lowX, lowY, lowZ); + result[1] = new Vector3d(highX, highY, highZ); + return BoundingBox.of(result); + } + + public static BoundingBox calculateBoundingBox(FeatureMapper mapper) { + List<List<Polygon>> polygonList = new ArrayList<>(); + polygonList.add(mapper.getLod1Polygons()); + polygonList.add(mapper.getLod2Polygons()); + polygonList.add(mapper.getLod3Polygons()); + polygonList.add(mapper.getLod4Polygons()); + + double lowX = Double.MAX_VALUE; + double highX = Double.NEGATIVE_INFINITY; + double lowY = Double.MAX_VALUE; + double highY = Double.NEGATIVE_INFINITY; + double lowZ = Double.MAX_VALUE; + double highZ = Double.NEGATIVE_INFINITY; + for (List<Polygon> polygons : polygonList) { + + for (Polygon p : polygons) { + // only need to check exterior rings + for (Vector3d v : p.getExteriorRing().getVertices()) { + if (v.getX() < lowX) { + lowX = v.getX(); + } + if (v.getX() > highX) { + highX = v.getX(); + } + if (v.getY() < lowY) { + lowY = v.getY(); + } + if (v.getY() > highY) { + highY = v.getY(); + } + if (v.getZ() < lowZ) { + lowZ = v.getZ(); + } + if (v.getZ() > highZ) { + highZ = v.getZ(); + } + } + } + } + Vector3d[] result = new Vector3d[2]; + result[0] = new Vector3d(lowX, lowY, lowZ); + result[1] = new Vector3d(highX, highY, highZ); + return BoundingBox.of(result); + } +} diff --git a/src/main/java/de/hft/stuttgart/citygml/viewer/math/Vector3d.java b/src/main/java/de/hft/stuttgart/citygml/viewer/math/Vector3d.java new file mode 100644 index 0000000000000000000000000000000000000000..08466788fc44012e12c56f57e6f01f7c153791de --- /dev/null +++ b/src/main/java/de/hft/stuttgart/citygml/viewer/math/Vector3d.java @@ -0,0 +1,299 @@ +/*- + * Copyright 2021 Hochschule für Technik Stuttgart + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package de.hft.stuttgart.citygml.viewer.math; + +import java.io.Serializable; +import java.util.Arrays; + +public class Vector3d implements Serializable { + + private static final long serialVersionUID = 3495650092142761365L; + + private static final Vector3d ORIGIN = new Vector3d(); + + public static final int X = 0; + public static final int Y = 1; + public static final int Z = 2; + + private double[] coords; + + public Vector3d() { + this(0d, 0d, 0d); + } + + public Vector3d(double x, double y, double z) { + coords = new double[3]; + coords[0] = x; + coords[1] = y; + coords[2] = z; + } + + public Vector3d(double[] coords) { + if (coords == null) { + throw new IllegalArgumentException("Coordinates can not be null"); + } + this.coords = coords; + } + + public Vector3d(Vector3d vec) { + coords = new double[3]; + coords[0] = vec.getX(); + coords[1] = vec.getY(); + coords[2] = vec.getZ(); + } + + public double getX() { + return coords[0]; + } + + public double getY() { + return coords[1]; + } + + public double getZ() { + return coords[2]; + } + + public void setX(double x) { + coords[0] = x; + } + + public void setY(double y) { + coords[1] = y; + } + + public void setZ(double z) { + coords[2] = z; + } + + /** + * Getter for all coordinates. The changes made to this array are reflected in + * this vector. + * + * @return the coordiantes array + */ + public double[] getCoordinates() { + return coords; + } + + public void setCoordinates(double[] coordiantes) { + if (coordiantes == null || coordiantes.length != 3) { + throw new IllegalArgumentException("Vector must have exactly 3 coordinates"); + } + this.coords = coordiantes; + } + + public double getLength() { + return getDistance(ORIGIN); + } + + public double getSquaredLength() { + double x = coords[0]; + double y = coords[1]; + double z = coords[2]; + return x * x + y * y + z * z; + } + + public double getDistance(Vector3d other) { + return Math.sqrt(getDistanceSquare(other)); + } + + public double getDistanceSquare(Vector3d other) { + double x = coords[0] - other.getX(); + double y = coords[1] - other.getY(); + double z = coords[2] - other.getZ(); + return x * x + y * y + z * z; + } + + /** + * adds another vector to this one. The result is + * <code><br>(a1 + b1)<br>(a2 + b2)<br>(a3 + b3)</code> + * + * @param b the other vector + * @return the sum of both vectors as a new vector. Values in this instance + * remain unchanged. + */ + public Vector3d plus(Vector3d b) { + double x = coords[0] + b.getX(); + double y = coords[1] + b.getY(); + double z = coords[2] + b.getZ(); + return new Vector3d(x, y, z); + } + + /** + * add a value to a coordinate. This will change the coordinates of this vector. + * + * @param coordinateIndex one of <code>Vector3d.X, Vector3d.Y, Vector3d.Z</code> + * @param value the added value + * @return the current instance for chaining commands + */ + public Vector3d plus(int coordinateIndex, double value) { + if (coordinateIndex < 0 || coordinateIndex > 2) { + throw new IllegalArgumentException("coordinateIndex has to be between 0 and 2"); + } + coords[coordinateIndex] += value; + return this; + } + + /** + * subtract a value to a coordinate. This will change the coordinates of this + * vector. + * + * @param coordinateIndex one of <code>Vector3d.X, Vector3d.Y, Vector3d.Z</code> + * @param value the subtracted value + * @return the current instance for chaining commands + */ + public Vector3d minus(int coordinateIndex, double value) { + if (coordinateIndex < 0 || coordinateIndex > 2) { + throw new IllegalArgumentException("coordinateIndex has to be between 0 and 2"); + } + coords[coordinateIndex] -= value; + return this; + } + + /** + * subtract another vector from this one. The result is + * <code><br>(a1 - b1)<br>(a2 - b2)<br>(a3 - b3)</code> + * + * @param b the other vector + * @return the result as a new vector. Values in this instance remain unchanged. + */ + public Vector3d minus(Vector3d b) { + double x = coords[0] - b.getX(); + double y = coords[1] - b.getY(); + double z = coords[2] - b.getZ(); + return new Vector3d(x, y, z); + } + + /** + * multiply this vector with a scalar. + * + * @param scalar the scalar + * @return the result as a new vector. Values in this instance remain unchanged. + */ + public Vector3d mult(double scalar) { + double x = coords[0] * scalar; + double y = coords[1] * scalar; + double z = coords[2] * scalar; + return new Vector3d(x, y, z); + } + + /** + * normalizes this vector. This method changes the coordinates of this instance. + */ + public Vector3d normalize() { + double length = getLength(); + // if the length is already 1, do nothing + final double epsilon = 0.0000001; + if (Math.abs(1 - length) < epsilon) { + return this; + } + coords[0] /= length; + coords[1] /= length; + coords[2] /= length; + return this; + } + + /** + * calculates the cross product. The result is + * <code><br>(a2 * b3 - a3 * b2)<br>(a3 * b1 - a1 * b3)<br>(a1 * b2 - a2 * b1)</code> + * + * @param b the other vector + * @return the result as a new vector. Values in this instance remain unchanged. + */ + public Vector3d cross(Vector3d b) { + double x = coords[1] * b.getZ() - coords[2] * b.getY(); + double y = coords[2] * b.getX() - coords[0] * b.getZ(); + double z = coords[0] * b.getY() - coords[1] * b.getX(); + return new Vector3d(x, y, z); + } + + /** + * Calculates the dot product. The result is + * <code><br>(a1 * b1) + (a2 * b2) + (a3 * b3)<br></code> + * + * @param b the other vector + * @return the dot product + */ + public double dot(Vector3d b) { + return coords[0] * b.getX() + coords[1] * b.getY() + coords[2] * b.getZ(); + } + + /** + * creates a new copy of this vector. + * + * @return a copy of this vector + */ + public Vector3d copy() { + return new Vector3d(coords[0], coords[1], coords[2]); + } + + @Override + public String toString() { + final int maxLen = 5; + StringBuilder builder = new StringBuilder(); + builder.append("Vector3d [coords="); + builder.append(coords != null ? Arrays.toString(Arrays.copyOf(coords, Math.min(coords.length, maxLen))) : null); + builder.append("]"); + return builder.toString(); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + Arrays.hashCode(coords); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + Vector3d other = (Vector3d) obj; + return Arrays.equals(coords, other.coords); + } + + /** + * Two vectors are equals, when the distance between both is less than epsilon + * + * @param other the other vector + * @param epsilon the distance where the vectors are considered the same + * @return true if they are within epsilon distance, false otherwise. + */ + public boolean equalsWithEpsilon(Vector3d other, double epsilon) { + if (other == this) { + return true; + } + Vector3d dif = this.minus(other); + double sqLength = dif.getSquaredLength(); + return sqLength < (epsilon * epsilon); + } + + public double getCoordinate(int axis) { + return coords[axis]; + } + + public void plus(double radius) { + coords[0] += radius; + coords[1] += radius; + coords[2] += radius; + } +} diff --git a/src/main/java/de/hft/stuttgart/citygml/viewer/parser/CityGMLParser.java b/src/main/java/de/hft/stuttgart/citygml/viewer/parser/CityGMLParser.java new file mode 100644 index 0000000000000000000000000000000000000000..8fdfd5dc7a56b4e2e380c17a2d195efedd870ba6 --- /dev/null +++ b/src/main/java/de/hft/stuttgart/citygml/viewer/parser/CityGMLParser.java @@ -0,0 +1,205 @@ +/*- + * Copyright 2021 Hochschule für Technik Stuttgart + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package de.hft.stuttgart.citygml.viewer.parser; + +import java.io.BufferedInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import javax.xml.XMLConstants; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.parsers.SAXParser; +import javax.xml.parsers.SAXParserFactory; + +import org.locationtech.proj4j.BasicCoordinateTransform; +import org.locationtech.proj4j.CRSFactory; +import org.locationtech.proj4j.CoordinateReferenceSystem; +import org.locationtech.proj4j.ProjCoordinate; +import org.locationtech.proj4j.units.Units; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; +import org.xml.sax.SAXNotRecognizedException; +import org.xml.sax.SAXNotSupportedException; + +import de.hft.stuttgart.citygml.viewer.CityGmlHandler; +import de.hft.stuttgart.citygml.viewer.math.Vector3d; + +public class CityGMLParser { + + private static final Logger log = Logger.getLogger(CityGMLParser.class.getName()); + + private static final SAXParserFactory FACTORY; + private static final CRSFactory CRS_FACTORY = new CRSFactory(); + + // EPSG:31467 + private static final Pattern P_EPSG = Pattern.compile("^(EPSG:\\d+)$"); + // urn:ogc:def:crs,crs:EPSG:6.12:31467,crs:EPSG:6.12:5783 + // or + // urn:ogc:def:crs,crs:EPSG::28992 + private static final Pattern P_OGC = Pattern.compile("urn:ogc:def:crs,crs:EPSG:[\\d\\.]*:([\\d]+)\\D*"); + + private static final Pattern P_OGC2 = Pattern.compile("urn:ogc:def:crs:EPSG:[\\d\\.]*:([\\d]+)\\D*"); + + // urn:adv:crs:DE_DHDN_3GK3*DE_DHHN92_NH + // urn:adv:crs:ETRS89_UTM32*DE_DHHN92_NH + private static final Pattern P_URN = Pattern.compile("urn:adv:crs:([^\\*]+)"); + private static final String WGS_84 = "EPSG:4326"; + + + + static { + FACTORY = SAXParserFactory.newInstance(); + try { + FACTORY.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true); + } catch (SAXNotRecognizedException | SAXNotSupportedException | ParserConfigurationException e) { + e.printStackTrace(); + } + } + + public static void parseEpsgCodeFromFile(File file, ParserConfiguration config) + throws IOException, ParserConfigurationException, SAXException { + try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file))) { + parseEpsgCodeFromStream(bis, config); + } + } + + private static void parseEpsgCodeFromStream(InputStream is, ParserConfiguration config) + throws ParserConfigurationException, SAXException { + SAXParser parser = FACTORY.newSAXParser(); + CityGmlHandler handler = new CityGmlHandler(); + try { + parser.parse(new InputSource(is), handler); + } catch (EnvelopeFoundException e) { + try { + parseCoordinateSystem(config, handler); + } catch (Exception e2) { + log.log(Level.WARNING, "Caught unknown error while parsing EPSG, assuming metric coordinate system", e2); + } + } catch (Exception e) { + log.log(Level.WARNING, "Caught unknown error while parsing EPSG, assuming metric coordinate system", e); + } + } + + private static void parseCoordinateSystem(ParserConfiguration config, CityGmlHandler handler) { + if (handler.getEpsg() == null) { + return; + } + CoordinateReferenceSystem crs = crsFromSrsName(handler.getEpsg()); + if (crs == null) { + // could not find a coordinate system for srsName + // assuming metric system + return; + } + if (crs.getProjection().getUnits() == Units.METRES) { + // coordinate system is in meters, do not convert + return; + } + Vector3d low = handler.getLowerCorner(); + Vector3d up = handler.getUpperCorner(); + double centerLong = low.getX() + ((up.getX() - low.getX()) / 2); + double centerLat = low.getY() + ((up.getY() - low.getY()) / 2); + if (!crs.getName().equals(WGS_84)) { + // need to convert coordinates first to WGS84, then find UTM Zone + CoordinateReferenceSystem wgs84 = crsFromSrsName(WGS_84); + ProjCoordinate p1 = new ProjCoordinate(); + p1.setValue(centerLong, centerLat); + ProjCoordinate p2 = new ProjCoordinate(); + BasicCoordinateTransform bct = new BasicCoordinateTransform(crs, wgs84); + bct.transform(p1, p2); + centerLong = p2.x; + centerLat = p2.y; + } + int zone = (int) (31 + Math.round(centerLong / 6)); + CoordinateReferenceSystem utm; + if (centerLat < 0) { + // south + utm = CRS_FACTORY.createFromParameters("UTM", "+proj=utm +ellps=WGS84 +units=m +zone=" + zone + " +south"); + } else { + // north + utm = CRS_FACTORY.createFromParameters("UTM", "+proj=utm +ellps=WGS84 +units=m +zone=" + zone); + } + config.setCoordinateSystem(crs, utm); + } + + /** + * The srsName (The name by which this reference system is identified) inside + * the CityGML file can have multiple formats. This method tries to parse the + * string and detect the corresponding reference system. If it is found, it + * returns a proj4j.CoordinateReferenceSystem. It throws an + * IllegalArgumentException otherwise. + * + * This method should be able to parse any EPSG id : e.g. "EPSG:1234". German + * Citygmls might also have "DE_DHDN_3GK3" or "ETRS89_UTM32" as srsName, so + * those are also included. It isn't guaranteed that those formats are correctly + * parsed, though. + * + * The EPSG ids and parameters are defined in resources ('nad/epsg') inside + * proj4j-0.1.0.jar. Some EPSG ids are missing though, e.g. 7415 + * + * @param srsName + * @return CoordinateReferenceSystem + */ + private static CoordinateReferenceSystem crsFromSrsName(String srsName) { + srsName = srsName.trim(); + Matcher mEPSG = P_EPSG.matcher(srsName); + if (mEPSG.find()) { + if ("EPSG:4979".contentEquals(srsName)) { + srsName = "EPSG:4236"; + } else if ("EPSG:7415".contentEquals(srsName)) { + return CRS_FACTORY.createFromParameters("EPSG:7415", + "+proj=sterea +lat_0=52.15616055555555 +lon_0=5.38763888888889 +k=0.9999079 +x_0=155000 +y_0=463000 +ellps=bessel +towgs84=565.417,50.3319,465.552,-0.398957,0.343988,-1.8774,4.0725 +units=m +no_defs"); + } + return CRS_FACTORY.createFromName(srsName); + } + + Matcher mOGC = P_OGC.matcher(srsName); + if (mOGC.find()) { + return CRS_FACTORY.createFromName("EPSG:" + mOGC.group(1)); + } + Matcher mOGC2 = P_OGC2.matcher(srsName); + if (mOGC2.find()) { + return CRS_FACTORY.createFromName("EPSG:" + mOGC2.group(1)); + } + Matcher mURN = P_URN.matcher(srsName); + // NOTE: Could use a HashMap if the switch/case becomes too long. + if (mURN.find()) { + switch (mURN.group(1)) { + case "DE_DHDN_3GK2": + return CRS_FACTORY.createFromName("EPSG:31466"); + case "DE_DHDN_3GK3": + return CRS_FACTORY.createFromName("EPSG:31467"); + case "DE_DHDN_3GK4": + return CRS_FACTORY.createFromName("EPSG:31468"); + case "DE_DHDN_3GK5": + return CRS_FACTORY.createFromName("EPSG:31469"); + case "ETRS89_UTM32": + return CRS_FACTORY.createFromName("EPSG:25832"); + default: + return null; + } + } + if (srsName.equals("http://www.opengis.net/def/crs/EPSG/0/6697")) { + return CRS_FACTORY.createFromParameters("EPSG:6697", "+proj=longlat +ellps=GRS80 +no_defs +axis=neu"); + } + return null; + } +} diff --git a/src/main/java/de/hft/stuttgart/citygml/viewer/parser/EnvelopeFoundException.java b/src/main/java/de/hft/stuttgart/citygml/viewer/parser/EnvelopeFoundException.java new file mode 100644 index 0000000000000000000000000000000000000000..8dfe1116452bb2ab7e900798a132c5c307b53cde --- /dev/null +++ b/src/main/java/de/hft/stuttgart/citygml/viewer/parser/EnvelopeFoundException.java @@ -0,0 +1,47 @@ +/*- + * Copyright 2021 Hochschule für Technik Stuttgart + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package de.hft.stuttgart.citygml.viewer.parser; + +import org.xml.sax.SAXException; + +/** + * To stop the SAXParser from further parsing when the relevant section has + * already been found. + * + * @author Matthias Betz + * + */ +public class EnvelopeFoundException extends SAXException { + + private static final long serialVersionUID = -9188617211115043815L; + + public EnvelopeFoundException() { + super(); + } + + public EnvelopeFoundException(Exception e) { + super(e); + } + + public EnvelopeFoundException(String message, Exception e) { + super(message, e); + } + + public EnvelopeFoundException(String message) { + super(message); + } + +} diff --git a/src/main/java/de/hft/stuttgart/citygml/viewer/parser/FeatureMapper.java b/src/main/java/de/hft/stuttgart/citygml/viewer/parser/FeatureMapper.java new file mode 100644 index 0000000000000000000000000000000000000000..680894e521d3de41f27e5963f7379a804650a4cf --- /dev/null +++ b/src/main/java/de/hft/stuttgart/citygml/viewer/parser/FeatureMapper.java @@ -0,0 +1,498 @@ +/*- + * Copyright 2021 Hochschule für Technik Stuttgart + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package de.hft.stuttgart.citygml.viewer.parser; + +import java.awt.Color; +import java.io.FileReader; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Properties; + +import org.citygml4j.model.bridge.AbstractBridge; +import org.citygml4j.model.building.AbstractBuilding; +import org.citygml4j.model.building.BuildingInstallation; +import org.citygml4j.model.construction.AbstractFillingElement; +import org.citygml4j.model.construction.Door; +import org.citygml4j.model.construction.GroundSurface; +import org.citygml4j.model.construction.RoofSurface; +import org.citygml4j.model.construction.Window; +import org.citygml4j.model.core.AbstractThematicSurface; +import org.citygml4j.model.core.ImplicitGeometry; +import org.citygml4j.model.core.ImplicitGeometryProperty; +import org.citygml4j.model.deprecated.bridge.DeprecatedPropertiesOfAbstractBridge; +import org.citygml4j.model.deprecated.building.DeprecatedPropertiesOfAbstractBuilding; +import org.citygml4j.model.deprecated.building.DeprecatedPropertiesOfBuildingInstallation; +import org.citygml4j.model.deprecated.core.DeprecatedPropertiesOfAbstractThematicSurface; +import org.citygml4j.model.deprecated.transportation.DeprecatedPropertiesOfAbstractTransportationSpace; +import org.citygml4j.model.deprecated.transportation.TransportationComplex; +import org.citygml4j.model.deprecated.vegetation.DeprecatedPropertiesOfPlantCover; +import org.citygml4j.model.deprecated.vegetation.DeprecatedPropertiesOfSolitaryVegetationObject; +import org.citygml4j.model.deprecated.waterbody.DeprecatedPropertiesOfWaterBody; +import org.citygml4j.model.landuse.LandUse; +import org.citygml4j.model.relief.TINRelief; +import org.citygml4j.model.transportation.AuxiliaryTrafficArea; +import org.citygml4j.model.transportation.TrafficArea; +import org.citygml4j.model.vegetation.PlantCover; +import org.citygml4j.model.vegetation.SolitaryVegetationObject; +import org.citygml4j.model.waterbody.WaterBody; +import org.citygml4j.visitor.ObjectWalker; +import org.locationtech.proj4j.BasicCoordinateTransform; +import org.locationtech.proj4j.ProjCoordinate; +import org.xmlobjects.gml.model.geometry.AbstractGeometry; +import org.xmlobjects.gml.model.geometry.GeometricPositionList; +import org.xmlobjects.gml.model.geometry.GeometryProperty; +import org.xmlobjects.gml.model.geometry.primitives.AbstractRing; +import org.xmlobjects.gml.model.geometry.primitives.AbstractRingProperty; +import org.xmlobjects.gml.model.geometry.primitives.LinearRing; +import org.xmlobjects.gml.model.geometry.primitives.Triangle; +import org.xmlobjects.gml.model.geometry.primitives.TriangulatedSurface; + +import de.hft.stuttgart.citygml.viewer.datastructure.Polygon; +import de.hft.stuttgart.citygml.viewer.datastructure.Ring; +import de.hft.stuttgart.citygml.viewer.math.Vector3d; + +public class FeatureMapper extends ObjectWalker { + + private Color groundColor = new Color(0.9411765f, 0.9019608f, 0.54901963f); + private Color roofColor = Color.RED; + private Color doorColor = Color.ORANGE; + private Color windowColor = new Color(0.0f, 0.5019608f, 0.5019608f); + private Color wallColor = Color.WHITE; + private Color bridgeColor = new Color(1.0f, 0.49803922f, 0.3137255f); + private Color landColor = new Color(0.64705884f, 0.16470589f, 0.16470589f); + private Color transportationColor = new Color(1.0f, 1.0f, 0.0f); + private Color vegetationColor = new Color(0.5647059f, 0.93333334f, 0.5647059f); + private Color waterColor = new Color(0.5294118f, 0.80784315f, 0.98039216f); + + private ParserConfiguration config; + private List<Polygon> lod1Polygons; + private List<Polygon> lod2Polygons; + private List<Polygon> lod3Polygons; + private List<Polygon> lod4Polygons; + + private List<Polygon> currentPolygons = null; + private Ring currentRing; + private Color currentColor; + + private ProjCoordinate p1 = new ProjCoordinate(); + private ProjCoordinate p2 = new ProjCoordinate(); + + public FeatureMapper(ParserConfiguration config) throws IOException { + try (FileReader reader = new FileReader("color.properties")) { + Properties props = new Properties(); + props.load(reader); + Color parsedGroundColor = parseColor(props.getProperty("groundColor")); + if (parsedGroundColor != null) { + groundColor = parsedGroundColor; + } + Color parsedRoofColor = parseColor(props.getProperty("roofColor")); + if (parsedRoofColor != null) { + roofColor = parsedRoofColor; + } + Color parsedDoorColor = parseColor(props.getProperty("doorColor")); + if (parsedDoorColor != null) { + doorColor = parsedDoorColor; + } + Color parsedWindowColor = parseColor(props.getProperty("windowColor")); + if (parsedWindowColor != null) { + windowColor = parsedWindowColor; + } + Color parsedWallColor = parseColor(props.getProperty("wallColor")); + if (parsedWallColor != null) { + wallColor = parsedWallColor; + } + Color parsedBridgeColor = parseColor(props.getProperty("bridgeColor")); + if (parsedBridgeColor != null) { + bridgeColor = parsedBridgeColor; + } + Color parsedLandColor = parseColor(props.getProperty("landColor")); + if (parsedLandColor != null) { + landColor = parsedLandColor; + } + Color parsedTransportationColor = parseColor(props.getProperty("transportationColor")); + if (parsedTransportationColor != null) { + transportationColor = parsedTransportationColor; + } + Color parsedVegetationColor = parseColor(props.getProperty("vegetationColor")); + if (parsedVegetationColor != null) { + vegetationColor = parsedVegetationColor; + } + Color parsedWaterColor = parseColor(props.getProperty("waterColor")); + if (parsedWaterColor != null) { + waterColor = parsedWaterColor; + } + } + this.config = config; + lod1Polygons = new ArrayList<>(); + lod2Polygons = new ArrayList<>(); + lod3Polygons = new ArrayList<>(); + lod4Polygons = new ArrayList<>(); + } + + private Color parseColor(String colorString) { + if (colorString == null) { + return null; + } + try { + String[] split = colorString.split(" "); + float r = Float.parseFloat(split[0]); + float g = Float.parseFloat(split[1]); + float b = Float.parseFloat(split[2]); + return new Color(r, g, b); + } catch (Exception e) { + e.printStackTrace(); + return null; + } + } + + @Override + public void visit(org.xmlobjects.gml.model.geometry.primitives.Polygon gmlPoly) { + if (currentPolygons == null) { + return; + } + Polygon viewerPoly = new Polygon(currentColor); + // parse rings + Ring extRing = new Ring(); + viewerPoly.setExteriorRing(extRing); + mapRing(gmlPoly.getExterior(), extRing); + + if (gmlPoly.getInterior() != null) { + for (AbstractRingProperty arp : gmlPoly.getInterior()) { + if (arp.getObject() != null) { + Ring innerRing = new Ring(); + viewerPoly.addInteriorRing(innerRing); + mapRing(arp, innerRing); + } + } + } + currentPolygons.add(viewerPoly); + } + + @Override + public void visit(Triangle triangle) { + if (currentPolygons == null) { + return; + } + // triangle is only a special case of a polygon + // so use a polygon + Polygon viewerPoly = new Polygon(currentColor); + // parse rings + Ring extRing = new Ring(); + viewerPoly.setExteriorRing(extRing); + mapRing(triangle.getExterior(), extRing); + currentPolygons.add(viewerPoly); + } + + private void mapRing(AbstractRingProperty gmlRing, Ring viewerRing) { + if (gmlRing == null || gmlRing.getObject() == null) { + return; + } + AbstractRing ringGeometry = gmlRing.getObject(); + currentRing = viewerRing; + // jump to LinearRing or Ring visit + ringGeometry.accept(this); + } + + @Override + public void visit(LinearRing linearRing) { + if (linearRing.getControlPoints() != null) { + int dimension = getDimension(linearRing); + parseControlPoints(linearRing.getControlPoints(), dimension); + } + } + + private int getDimension(LinearRing linearRing) { + int dimension = 3; + if (linearRing.getSrsDimension() != null) { + dimension = linearRing.getSrsDimension(); + } + return dimension; + } + + private void parseControlPoints(GeometricPositionList controlPoints, int dimension) { + List<Double> coords = controlPoints.toCoordinateList3D(); + switch (dimension) { + case 1: + for (int i = 0; i < coords.size(); i++) { + createVertex(coords.get(i), 0, 0); + } + break; + case 2: + for (int i = 0; i < coords.size(); i = i + 2) { + createVertex(coords.get(i + 0), coords.get(i + 1), 0); + } + break; + case 3: + for (int i = 0; i < coords.size(); i = i + 3) { + createVertex(coords.get(i + 0), coords.get(i + 1), coords.get(i + 2)); + } + break; + default: + throw new UnsupportedOperationException("Cannot parse Coordinates with dimension:" + dimension); + } + } + + private void createVertex(double x, double y, double z) { + // transform into utm, if available + BasicCoordinateTransform trans = config.getTargetTransform(); + if (trans != null) { + p1.setValue(x, y); + trans.transform(p1, p2); + x = p2.x; + y = p2.y; + } + Vector3d v = new Vector3d(x, y, z); + currentRing.addPoint(v); + } + + @Override + public void visit(AbstractBuilding ab) { + super.visit(ab); + DeprecatedPropertiesOfAbstractBuilding deprecatedProperties = ab.getDeprecatedProperties(); + parseAbstractGeometry(deprecatedProperties.getLod1MultiSurface(), lod1Polygons, wallColor); + parseAbstractGeometry(ab.getLod2MultiSurface(), lod2Polygons, wallColor); + parseAbstractGeometry(ab.getLod3MultiSurface(), lod3Polygons, wallColor); + parseAbstractGeometry(deprecatedProperties.getLod4MultiSurface(), lod4Polygons, wallColor); + parseAbstractGeometry(ab.getLod1Solid(), lod1Polygons, wallColor); + parseAbstractGeometry(ab.getLod2Solid(), lod2Polygons, wallColor); + parseAbstractGeometry(ab.getLod3Solid(), lod3Polygons, wallColor); + parseAbstractGeometry(deprecatedProperties.getLod4Solid(), lod4Polygons, wallColor); + } + + @Override + public void visit(TINRelief tinRelief) { + super.visit(tinRelief); + if (tinRelief.getTin() == null || tinRelief.getTin().getObject() == null) { + return; + } + TriangulatedSurface object = tinRelief.getTin().getObject(); + currentPolygons = lod1Polygons; + currentColor = landColor; + object.accept(this); + currentPolygons = null; + currentColor = null; + } + + @Override + public void visit(WaterBody wb) { + super.visit(wb); + DeprecatedPropertiesOfWaterBody deprecatedProperties = wb.getDeprecatedProperties(); + parseAbstractGeometry(deprecatedProperties.getLod1MultiSurface(), lod1Polygons, waterColor); + parseAbstractGeometry(wb.getLod1Solid(), lod1Polygons, waterColor); + parseAbstractGeometry(wb.getLod2Solid(), lod2Polygons, waterColor); + parseAbstractGeometry(wb.getLod3Solid(), lod3Polygons, waterColor); + parseAbstractGeometry(deprecatedProperties.getLod4Solid(), lod4Polygons, waterColor); + } + + @Override + public void visit(PlantCover pc) { + super.visit(pc); + DeprecatedPropertiesOfPlantCover deprecatedProperties = pc.getDeprecatedProperties(); + parseAbstractGeometry(deprecatedProperties.getLod1MultiSurface(), lod1Polygons, vegetationColor); + parseAbstractGeometry(pc.getLod2MultiSurface(), lod2Polygons, vegetationColor); + parseAbstractGeometry(pc.getLod3MultiSurface(), lod3Polygons, vegetationColor); + parseAbstractGeometry(deprecatedProperties.getLod4MultiSurface(), lod4Polygons, vegetationColor); + parseAbstractGeometry(deprecatedProperties.getLod1MultiSolid(), lod1Polygons, vegetationColor); + parseAbstractGeometry(deprecatedProperties.getLod2MultiSolid(), lod2Polygons, vegetationColor); + parseAbstractGeometry(deprecatedProperties.getLod3MultiSolid(), lod3Polygons, vegetationColor); + parseAbstractGeometry(deprecatedProperties.getLod4MultiSolid(), lod4Polygons, vegetationColor); + } + + @Override + public void visit(SolitaryVegetationObject svo) { + super.visit(svo); + DeprecatedPropertiesOfSolitaryVegetationObject deprecatedProperties = svo.getDeprecatedProperties(); + parseAbstractGeometry(deprecatedProperties.getLod1Geometry(), lod1Polygons, vegetationColor); + parseAbstractGeometry(deprecatedProperties.getLod2Geometry(), lod2Polygons, vegetationColor); + parseAbstractGeometry(deprecatedProperties.getLod3Geometry(), lod3Polygons, vegetationColor); + parseAbstractGeometry(deprecatedProperties.getLod4Geometry(), lod4Polygons, vegetationColor); + parseImplicitGeometry(svo.getLod1ImplicitRepresentation(), lod1Polygons, vegetationColor); + parseImplicitGeometry(svo.getLod2ImplicitRepresentation(), lod2Polygons, vegetationColor); + parseImplicitGeometry(svo.getLod3ImplicitRepresentation(), lod3Polygons, vegetationColor); + parseImplicitGeometry(deprecatedProperties.getLod4ImplicitRepresentation(), lod4Polygons, vegetationColor); + } + + @Override + public void visit(AbstractThematicSurface abs) { + super.visit(abs); + Color color = getBuildingSurfaceColor(abs); + DeprecatedPropertiesOfAbstractThematicSurface deprecatedProperties = abs.getDeprecatedProperties(); + parseAbstractGeometry(abs.getLod2MultiSurface(), lod2Polygons, color); + parseAbstractGeometry(abs.getLod3MultiSurface(), lod3Polygons, color); + parseAbstractGeometry(deprecatedProperties.getLod4MultiSurface(), lod4Polygons, color); + } + + @Override + public void visit(LandUse landUse) { + super.visit(landUse); + DeprecatedPropertiesOfAbstractThematicSurface deprecatedProperties = landUse.getDeprecatedProperties(); + parseAbstractGeometry(landUse.getLod1MultiSurface(), lod1Polygons, landColor); + parseAbstractGeometry(landUse.getLod2MultiSurface(), lod2Polygons, landColor); + parseAbstractGeometry(landUse.getLod3MultiSurface(), lod3Polygons, landColor); + parseAbstractGeometry(deprecatedProperties.getLod4MultiSurface(), lod4Polygons, landColor); + } + + @Override + public void visit(AuxiliaryTrafficArea ata) { + super.visit(ata); + DeprecatedPropertiesOfAbstractThematicSurface deprecatedProperties = ata.getDeprecatedProperties(); + parseAbstractGeometry(ata.getLod2MultiSurface(), lod2Polygons, transportationColor); + parseAbstractGeometry(ata.getLod3MultiSurface(), lod3Polygons, transportationColor); + parseAbstractGeometry(deprecatedProperties.getLod4MultiSurface(), lod4Polygons, transportationColor); + } + + @Override + public void visit(TrafficArea ta) { + super.visit(ta); + DeprecatedPropertiesOfAbstractThematicSurface deprecatedProperties = ta.getDeprecatedProperties(); + parseAbstractGeometry(ta.getLod2MultiSurface(), lod2Polygons, transportationColor); + parseAbstractGeometry(ta.getLod3MultiSurface(), lod3Polygons, transportationColor); + parseAbstractGeometry(deprecatedProperties.getLod4MultiSurface(), lod4Polygons, transportationColor); + } + + @Override + public void visit(TransportationComplex tc) { + super.visit(tc); + DeprecatedPropertiesOfAbstractTransportationSpace deprecatedProperties = tc.getDeprecatedProperties(); + parseAbstractGeometry(deprecatedProperties.getLod1MultiSurface(), lod1Polygons, transportationColor); + parseAbstractGeometry(tc.getLod2MultiSurface(), lod2Polygons, transportationColor); + parseAbstractGeometry(tc.getLod3MultiSurface(), lod3Polygons, transportationColor); + parseAbstractGeometry(deprecatedProperties.getLod4MultiSurface(), lod4Polygons, transportationColor); + } + + private Color getBuildingSurfaceColor(AbstractThematicSurface abs) { + if (abs instanceof RoofSurface) { + return roofColor; + } + if (abs instanceof GroundSurface) { + return groundColor; + } + return wallColor; + } + + @Override + public void visit(AbstractFillingElement ao) { + super.visit(ao); + Color color = getOpeningColor(ao); + parseAbstractGeometry(ao.getLod1Solid(), lod1Polygons, color); + parseAbstractGeometry(ao.getLod2Solid(), lod2Polygons, color); + parseAbstractGeometry(ao.getLod3Solid(), lod3Polygons, color); + parseAbstractGeometry(ao.getLod2MultiSurface(), lod2Polygons, color); + parseAbstractGeometry(ao.getLod3MultiSurface(), lod3Polygons, color); + parseImplicitGeometry(ao.getLod1ImplicitRepresentation(), lod1Polygons, color); + parseImplicitGeometry(ao.getLod2ImplicitRepresentation(), lod2Polygons, color); + parseImplicitGeometry(ao.getLod3ImplicitRepresentation(), lod3Polygons, color); + } + + private Color getOpeningColor(AbstractFillingElement ao) { + if (ao instanceof Door) { + return doorColor; + } + if (ao instanceof Window) { + return windowColor; + } + return wallColor; + } + + @Override + public void visit(AbstractBridge ab) { + super.visit(ab); + DeprecatedPropertiesOfAbstractBridge deprecatedProperties = ab.getDeprecatedProperties(); + parseAbstractGeometry(deprecatedProperties.getLod1MultiSurface(), lod1Polygons, bridgeColor); + parseAbstractGeometry(ab.getLod2MultiSurface(), lod2Polygons, bridgeColor); + parseAbstractGeometry(ab.getLod3MultiSurface(), lod3Polygons, bridgeColor); + parseAbstractGeometry(deprecatedProperties.getLod4MultiSurface(), lod4Polygons, bridgeColor); + parseAbstractGeometry(ab.getLod1Solid(), lod1Polygons, bridgeColor); + parseAbstractGeometry(ab.getLod2Solid(), lod2Polygons, bridgeColor); + parseAbstractGeometry(ab.getLod3Solid(), lod3Polygons, bridgeColor); + parseAbstractGeometry(deprecatedProperties.getLod4Solid(), lod4Polygons, bridgeColor); + } + + @Override + public void visit(BuildingInstallation bi) { + super.visit(bi); + DeprecatedPropertiesOfBuildingInstallation deprecatedProperties = bi.getDeprecatedProperties(); + parseAbstractGeometry(deprecatedProperties.getLod2Geometry(), lod2Polygons, wallColor); + parseAbstractGeometry(deprecatedProperties.getLod3Geometry(), lod3Polygons, wallColor); + parseAbstractGeometry(deprecatedProperties.getLod4Geometry(), lod4Polygons, wallColor); + parseImplicitGeometry(bi.getLod2ImplicitRepresentation(), lod2Polygons, wallColor); + parseImplicitGeometry(bi.getLod3ImplicitRepresentation(), lod3Polygons, wallColor); + parseImplicitGeometry(deprecatedProperties.getLod4ImplicitRepresentation(), lod4Polygons, wallColor); + } + + private void parseImplicitGeometry(ImplicitGeometryProperty irp, List<Polygon> polygons, Color color) { + if (irp == null || irp.getObject() == null) { + return; + } + ImplicitGeometry implicitGeometry = irp.getObject(); + parseAbstractGeometry(implicitGeometry.getRelativeGeometry(), polygons, color); + } + + private void parseAbstractGeometry(GeometryProperty<? extends AbstractGeometry> geom, List<Polygon> polygons, + Color color) { + if (geom == null || geom.getObject() == null) { + return; + } + currentColor = color; + currentPolygons = polygons; + geom.getObject().accept(this); + currentColor = null; + currentPolygons = null; + } + + public List<Polygon> getLod1Polygons() { + return lod1Polygons; + } + + public List<Polygon> getLod2Polygons() { + return lod2Polygons; + } + + public List<Polygon> getLod3Polygons() { + return lod3Polygons; + } + + public List<Polygon> getLod4Polygons() { + return lod4Polygons; + } + + public void movePolygonsBy(Vector3d center) { + movePolygonsBy(lod1Polygons, center); + movePolygonsBy(lod2Polygons, center); + movePolygonsBy(lod3Polygons, center); + movePolygonsBy(lod4Polygons, center); + } + + private void movePolygonsBy(List<Polygon> polygons, Vector3d center) { + for (Polygon p : polygons) { + for (Vector3d v : p.getExteriorRing().getVertices()) { + movePointBy(v, center); + } + for (Ring r : p.getInteriorRings()) { + for (Vector3d v : r.getVertices()) { + movePointBy(v, center); + } + } + } + } + + private void movePointBy(Vector3d v, Vector3d center) { + v.setX(v.getX() - center.getX()); + v.setY(v.getY() - center.getY()); + v.setZ(v.getZ() - center.getZ()); + } +} diff --git a/src/main/java/de/hft/stuttgart/citygml/viewer/parser/InputStreamListener.java b/src/main/java/de/hft/stuttgart/citygml/viewer/parser/InputStreamListener.java new file mode 100644 index 0000000000000000000000000000000000000000..866ea4f0d61c66c11f338fe6ce8dbb2591752bf4 --- /dev/null +++ b/src/main/java/de/hft/stuttgart/citygml/viewer/parser/InputStreamListener.java @@ -0,0 +1,35 @@ +/*- + * Copyright 2021 Hochschule für Technik Stuttgart + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package de.hft.stuttgart.citygml.viewer.parser; + +import java.util.EventListener; + +/** + * Implementations of this listener will be informed about the (file) loading progress of ObservedInputStream + * instances. You have to register your implementation of this interface at the ObservedInputStream instance. + * + * @author Marcel Bruse + */ +public interface InputStreamListener extends EventListener { + + /** + * This method will be called by the observed input stream when a new chunk of bytes has been read. + * + * @param progress Should be a value between 0 and 1. 0 means no progress and 1 means loading finished. + */ + public void updateProgress(float progress); + +} diff --git a/src/main/java/de/hft/stuttgart/citygml/viewer/parser/LOD.java b/src/main/java/de/hft/stuttgart/citygml/viewer/parser/LOD.java new file mode 100644 index 0000000000000000000000000000000000000000..9145dad15e22916ca8371b9f6f14b7d8cd25d60a --- /dev/null +++ b/src/main/java/de/hft/stuttgart/citygml/viewer/parser/LOD.java @@ -0,0 +1,22 @@ +/*- + * Copyright 2021 Hochschule für Technik Stuttgart + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package de.hft.stuttgart.citygml.viewer.parser; + +public enum LOD { + + LOD_1, LOD_2, LOD_3, LOD_4 + +} diff --git a/src/main/java/de/hft/stuttgart/citygml/viewer/parser/ObservedInputStream.java b/src/main/java/de/hft/stuttgart/citygml/viewer/parser/ObservedInputStream.java new file mode 100644 index 0000000000000000000000000000000000000000..5d4672ff931e8f776daf92c88ab1d8b9a33462c6 --- /dev/null +++ b/src/main/java/de/hft/stuttgart/citygml/viewer/parser/ObservedInputStream.java @@ -0,0 +1,115 @@ +/*- + * Copyright 2021 Hochschule für Technik Stuttgart + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package de.hft.stuttgart.citygml.viewer.parser; + +import java.io.BufferedInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FilterInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; + +/** + * This ObservedInputStream will update its registered listeners about the reading process of the underlying file. + * Every time when the underlying input stream is read the registered listeners will be informed and updated with the + * new progress. + * + * @author Marcel Bruse + */ +public class ObservedInputStream extends FilterInputStream { + + /** List of registered progress listeners. */ + private ArrayList<InputStreamListener> listeners = new ArrayList<>(); + + /** The number of bytes of the file to be read. */ + private long fileLength; + + /** The number of bytes read so far. */ + private long location; + + /** + * Instantiates the ObservedInputStream with a given file handle. + * + * @param file The file that should be loaded. + * @throws FileNotFoundException If there is on file, you will get some of this. + */ + public ObservedInputStream(File file) throws FileNotFoundException { + super(new BufferedInputStream(new FileInputStream(file))); + fileLength = file.length(); + } + + public ObservedInputStream(String fileName) throws FileNotFoundException { + this(new File(fileName)); + } + + public ObservedInputStream(InputStream in, long fileLength) { + super(new BufferedInputStream(in)); + this.fileLength = fileLength; + } + + /** + * Calls all the registered listeners if the file loading process makes any progress. + * It calculates and passes the progress in percent. + */ + private void updateProgress() { + for (InputStreamListener l : listeners) { + l.updateProgress((float) location / fileLength); + } + } + + /** + * Calls super and counts the number of read bytes. + * + * @see FilterInputStream + */ + @Override + public int read() throws IOException { + final int data = super.read(); + if (data != -1) { + location++; + updateProgress(); + } + return data; + } + + /** + * Calls super and counts the number of read bytes. + * + * @see FilterInputStream + */ + @Override + public int read(byte[] b, int off, int len) throws IOException { + final int byteCount = super.read(b, off, len); + if (byteCount != -1) { + location += byteCount; + updateProgress(); + } + return byteCount; + } + + /** + * Registers listeners. Every registered listener will get informed if the file loading process makes any + * progress. + * + * @param ps The progress listener to be registered. + */ + public void addListener(InputStreamListener ps) { + listeners.add(ps); + } + +} diff --git a/src/main/java/de/hft/stuttgart/citygml/viewer/parser/ParserConfiguration.java b/src/main/java/de/hft/stuttgart/citygml/viewer/parser/ParserConfiguration.java new file mode 100644 index 0000000000000000000000000000000000000000..219721090b2f3336182c6ffd973cf67a0e25c6cd --- /dev/null +++ b/src/main/java/de/hft/stuttgart/citygml/viewer/parser/ParserConfiguration.java @@ -0,0 +1,78 @@ +/*- + * Copyright 2021 Hochschule für Technik Stuttgart + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package de.hft.stuttgart.citygml.viewer.parser; + +import java.io.Serializable; + +import org.locationtech.proj4j.BasicCoordinateTransform; +import org.locationtech.proj4j.CRSFactory; +import org.locationtech.proj4j.CoordinateReferenceSystem; + +/** + * Container class to store the configuration needed to parse a file. Also + * contains information on transformation of vertices. + * + * @author Matthias Betz + * + */ +public class ParserConfiguration implements Serializable { + + private static final long serialVersionUID = 6209047092991074661L; + + private static final CRSFactory CRS_FACTORY = new CRSFactory(); + + private transient BasicCoordinateTransform targetTransform = null; + private transient BasicCoordinateTransform originalTransform = null; + + private String targetTransformString; + private String originalTransformString; + private boolean hasTransformation = false; + + public void setCoordinateSystem(CoordinateReferenceSystem crs, CoordinateReferenceSystem tgtCrs) { + if (crs != null && tgtCrs != null) { + hasTransformation = true; + targetTransformString = tgtCrs.getParameterString(); + originalTransformString = crs.getParameterString(); + targetTransform = new BasicCoordinateTransform(crs, tgtCrs); + originalTransform = new BasicCoordinateTransform(tgtCrs, crs); + } + } + + public BasicCoordinateTransform getTargetTransform() { + if (hasTransformation && targetTransform == null) { + createCoordinateTransforms(); + } + return targetTransform; + } + + private void createCoordinateTransforms() { + CoordinateReferenceSystem tgtCrs = null; + CoordinateReferenceSystem crs = null; + synchronized (CRS_FACTORY) { + tgtCrs = CRS_FACTORY.createFromParameters("Target", targetTransformString); + crs = CRS_FACTORY.createFromParameters("Original", originalTransformString); + } + targetTransform = new BasicCoordinateTransform(crs, tgtCrs); + originalTransform = new BasicCoordinateTransform(tgtCrs, crs); + } + + public BasicCoordinateTransform getOriginalTransform() { + if (hasTransformation && originalTransform == null) { + createCoordinateTransforms(); + } + return originalTransform; + } +} diff --git a/src/main/resources/fragment.frag b/src/main/resources/fragment.frag new file mode 100644 index 0000000000000000000000000000000000000000..771dc5959481f64139059787fa484517b3364b67 --- /dev/null +++ b/src/main/resources/fragment.frag @@ -0,0 +1,16 @@ +#version 330 + + +// Incoming interpolated (between vertices) color. +in vec3 interpolatedColor; + + +// Outgoing final color. +layout (location = 0) out vec4 outputColor; + + +void main() +{ + // We simply pad the interpolatedColor + outputColor = vec4(interpolatedColor, 1); +} \ No newline at end of file diff --git a/src/main/resources/vertex.vert b/src/main/resources/vertex.vert new file mode 100644 index 0000000000000000000000000000000000000000..c23f4a8a6ae345aba4b2d605839ecae8472d412c --- /dev/null +++ b/src/main/resources/vertex.vert @@ -0,0 +1,25 @@ +#version 330 + + +// Incoming vertex position, Model Space. +layout (location = 0) in vec3 position; + +// Incoming vertex color. +layout (location = 1) in vec3 color; + +// Projection, view and model matrix. +uniform mat4 projViewModel; + + +// Outgoing color. +out vec3 interpolatedColor; + + +void main() { + + // Normally gl_Position is in Clip Space and we calculate it by multiplying together all the matrices + gl_Position = projViewModel * vec4(position, 1); + + // We assign the color to the outgoing variable. + interpolatedColor = color; +} \ No newline at end of file