├── .gitignore ├── LICENSE ├── README.md ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── images ├── 2014-10-21_first_light.png ├── 2014-10-27_version0-3-0.png ├── 2015-03-07_version0-5-0.png ├── 2015-03-25_version0-7-0.png ├── 2015-04-25_version0-9-3.png ├── 2015-05-23_version0-10-5.png └── 2016-05-17_shattered_planes.png ├── natives └── what-is-this-folder-good-for.txt ├── settings.gradle └── src ├── main ├── java │ └── org │ │ └── terasology │ │ └── world │ │ └── viewer │ │ ├── MainFrame.java │ │ ├── ModuleTableModel.java │ │ ├── SelectWorldGenDialog.java │ │ ├── ThreadSafeRegion.java │ │ ├── WorldViewer.java │ │ ├── camera │ │ ├── Camera.java │ │ ├── CameraKeyController.java │ │ ├── CameraListener.java │ │ ├── CameraMouseController.java │ │ └── RepaintingCameraListener.java │ │ ├── config │ │ ├── ClassTypeAdapter.java │ │ ├── Config.java │ │ ├── ConfigData.java │ │ ├── ConfigEntry.java │ │ ├── ViewConfig.java │ │ ├── WorldConfig.java │ │ └── WorldGenConfigData.java │ │ ├── core │ │ ├── ConfigPanel.java │ │ ├── FacetPanel.java │ │ ├── FacetTableModel.java │ │ ├── Reorderable.java │ │ ├── TableRowTransferHandler.java │ │ ├── TileThreadFactory.java │ │ ├── Viewer.java │ │ └── ZoomOverlayUpdater.java │ │ ├── env │ │ ├── DummyPermissionProviderFactory.java │ │ ├── TinyEnvironment.java │ │ └── TinyModuleManager.java │ │ ├── gui │ │ ├── CursorPositionListener.java │ │ ├── FacetListCellRenderer.java │ │ ├── ListItemTransferHandler.java │ │ ├── RepaintingMouseListener.java │ │ ├── UIBindings.java │ │ └── WorldGenCellRenderer.java │ │ └── overlay │ │ ├── AbstractOverlay.java │ │ ├── GridOverlay.java │ │ ├── Overlay.java │ │ ├── PixelOverlay.java │ │ ├── ScreenOverlay.java │ │ ├── TextOverlay.java │ │ ├── TooltipOverlay.java │ │ └── WorldOverlay.java └── resources │ ├── VersionInfo.template │ ├── icons │ ├── gooey_sweet_red_16.png │ ├── gooey_sweet_red_32.png │ └── gooey_sweet_red_64.png │ ├── logback.xml │ └── splash │ └── splash.jpg └── test └── java └── org └── terasology └── world └── viewer └── core └── ViewerTest.java /.gitignore: -------------------------------------------------------------------------------- 1 | # Gradle files 2 | /.gradle 3 | /build 4 | 5 | # IntelliJ project files 6 | *.iml 7 | *.ipr 8 | *.iws 9 | 10 | # Eclipse project files 11 | .classpath 12 | .project 13 | .settings/ 14 | /bin 15 | 16 | # CheckStyle 17 | .checkstyle 18 | 19 | # PMD and eclipse-PMD 20 | /.eclipse-pmd 21 | /.pmd 22 | /.ruleset 23 | 24 | /src/main/java/org/terasology/world/viewer/version/ 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | WorldViewer 2 | ========= 3 | 4 | A world-generator based map viewer for Terasology 5 | 6 | ![image1](images/2015-05-23_version0-10-5.png "Screenshot of Perlin-based world rendering") 7 | 8 | 9 | Overview 10 | ----------- 11 | 12 | This a 2D map viewer for Terasology world generators. Its main purpose is to preview generated facets for faster and easier debugging. 13 | 14 | To some extent, [FacadeAWT](https://github.com/MovingBlocks/FacadeAWT) and [Minimap](https://github.com/Terasology/minimap) are similar to this project. 15 | 16 | You can watch one of the following video tutorials to get an idea how it works: 17 | 18 | [![youtube1](http://img.youtube.com/vi/aLY6gnSW20E/mqdefault.jpg "YouTube: WorldViewer 0.7.0 -- Demo")](http://www.youtube.com/watch?v=aLY6gnSW20E) 19 | [![youtube2](http://img.youtube.com/vi/HY0nh6A-BMA/mqdefault.jpg "YouTube: WorldViewer - HeightMap Configuration")](http://www.youtube.com/watch?v=HY0nh6A-BMA) 20 | 21 | 22 | Download 23 | ----------- 24 | 25 | [![Build Status](http://jenkins.terasology.org/job/WorldViewer/badge/icon)](http://jenkins.terasology.org/job/WorldViewerNightly/) 26 | 27 | You can download the [latest build](http://jenkins.terasology.org/job/WorldViewer/lastSuccessfulBuild/artifact/build/distributions/WorldViewer.zip) from our Jenkins server. 28 | 29 | You need [Java 8](http://java.com/download) and at least 3 GB memory to run WorldViewer. 30 | 31 | 32 | License 33 | ------------- 34 | 35 | This software is licensed under the [Apache 2.0 License](http://www.apache.org/licenses/LICENSE-2.0.html). 36 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id "org.standardout.versioneye" version "1.0.1" 3 | } 4 | 5 | apply plugin: 'java' 6 | apply plugin: 'maven' 7 | apply plugin: 'eclipse' 8 | apply plugin: 'idea' 9 | apply plugin: 'checkstyle' 10 | apply plugin: 'pmd' 11 | apply plugin: 'findbugs' 12 | apply plugin: 'application' 13 | 14 | group = 'org.terasology' 15 | version = getFullVersion() 16 | 17 | sourceCompatibility = 1.8 18 | targetCompatibility = 1.8 19 | 20 | mainClassName = 'org.terasology.world.viewer.WorldViewer' 21 | applicationDefaultJvmArgs = ["-Xmx3g"] 22 | 23 | // We use both Maven Central and our own Artifactory instance, which contains module builds, extra libs, and so on 24 | repositories { 25 | mavenCentral() 26 | maven { 27 | url "http://artifactory.terasology.org/artifactory/virtual-repo-live" 28 | } 29 | } 30 | 31 | def getGitDesc() { 32 | def cmd = 'git describe --long --match *.*.0' 33 | 34 | try { 35 | def proc = cmd.execute() 36 | proc.waitFor() // wait for the command to finish 37 | def desc = proc.in.text.trim() // "out" from the process is "in" for gradle 38 | return desc; 39 | } catch (IOException e) { 40 | logger.warn("Could not run '$cmd'") 41 | return "0.0.0-0-0000000" 42 | } 43 | } 44 | 45 | def getVersionInfo() { 46 | def desc = getGitDesc() 47 | def matcher = desc =~ "(\\d+)\\.(\\d+)\\.0-(\\d+)-g([0-9A-Za-z]*)" 48 | if (!matcher || !matcher.matches()) { 49 | logger.warn("Version '$desc' does not match format: 'd.d.0-d-gHHHHHHH'") 50 | return ["0", "0", "0", "0000000"] 51 | } else { 52 | def major = matcher.group(1) 53 | def minor = matcher.group(2) 54 | def patch = matcher.group(3) 55 | def sha = matcher.group(4) 56 | return [major, minor, patch, sha] 57 | } 58 | } 59 | 60 | def getFullVersion() { 61 | def (major, minor, patch, sha) = getVersionInfo() 62 | return "$major.$minor.$patch+$sha"; 63 | } 64 | 65 | task createVersionFile(type:Copy) { 66 | description = 'Creates a java version file based on the template in the resources folder' 67 | 68 | inputs.property('version', version) // trigger executing by setting a property 69 | 70 | from ('src/main/resources/VersionInfo.template') 71 | into ('src/main/java/org/terasology/world/viewer/version') 72 | rename '(.*).template', '$1.java' 73 | 74 | def (major, minor, patch, sha) = getVersionInfo() 75 | 76 | expand( 77 | BUILD_VERSION_MAJOR: major, 78 | BUILD_VERSION_MINOR: minor, 79 | BUILD_VERSION_PATCH: patch, 80 | BUILD_SHA: sha, 81 | BUILD_TIME: java.time.ZonedDateTime.now().toString()); 82 | } 83 | 84 | 85 | task wrapper(type: Wrapper) { 86 | gradleVersion = '2.3' 87 | } 88 | 89 | compileJava.dependsOn createVersionFile 90 | eclipseProject.dependsOn createVersionFile 91 | 92 | configurations { 93 | codeMetrics 94 | } 95 | 96 | dependencies { 97 | 98 | codeMetrics(group: 'org.terasology.config', name: 'codemetrics', version: '1.0.0', ext: 'zip') 99 | 100 | checkstyle ('com.puppycrawl.tools:checkstyle:6.7') 101 | pmd ('net.sourceforge.pmd:pmd-core:5.3.3') 102 | pmd ('net.sourceforge.pmd:pmd-java:5.3.3') 103 | 104 | compile (group: 'org.terasology.modules', name: 'Core', version: '1.2.1-SNAPSHOT') 105 | // compile (group: 'org.terasology.modules', name: 'Cities', version: '+') 106 | // compile (group: 'org.terasology.modules', name: 'Pathfinding', version: '+') 107 | // compile (group: 'org.terasology.modules', name: 'TutorialWorldGeneration', version: '+') 108 | // compile (group: 'org.terasology.modules', name: 'LightAndShadow', version: '+') 109 | // compile (group: 'org.terasology.modules', name: 'WoodAndStone', version: '+') 110 | // compile (group: 'org.terasology.modules', name: 'PolyWorld', version: '+') 111 | 112 | // Mockups for Environments 113 | compile (group: 'org.mockito', name: 'mockito-all', version: '1.10.19') 114 | } 115 | 116 | task sourceJar(type: Jar) { 117 | description = "Create a JAR with all sources" 118 | from sourceSets.main.allSource 119 | classifier = 'sources' 120 | } 121 | 122 | task javadocJar(type: Jar, dependsOn: javadoc) { 123 | description = "Create a JAR with the JavaDoc for the java sources" 124 | from javadoc.destinationDir 125 | classifier = 'javadoc' 126 | } 127 | 128 | 129 | 130 | // Define the artifacts we want to publish (the .pom will also be included since the Maven plugin is active) 131 | artifacts { 132 | archives sourceJar 133 | archives javadocJar 134 | } 135 | 136 | jar { 137 | manifest { 138 | attributes("Main-Class": mainClassName) 139 | attributes("Class-Path" : configurations.runtime.collect { it.getName() }.join(" ")) 140 | attributes("Implementation-Title": "WorldViewer") 141 | attributes("Implementation-Version": version) 142 | } 143 | } 144 | 145 | distZip { 146 | // remove the version number from the zip file name 147 | // the name of the zip file will thus be independent from the version 148 | version = '' 149 | } 150 | 151 | checkstyle { 152 | ignoreFailures = true 153 | config = resources.text.fromArchiveEntry(configurations.codeMetrics, "checkstyle/checkstyle.xml") 154 | // this assigns the property variable ${samedir} in checkstyle.xml 155 | configProperties.samedir = config.asFile().parent 156 | } 157 | 158 | configure([checkstyleMain, checkstyleTest]) { 159 | doFirst { 160 | def suppressions = resources.text.fromArchiveEntry(configurations.codeMetrics, "checkstyle/suppressions.xml") 161 | // the asFile() method extracts the file from the zip archive and puts it next to checkstyle.xml 162 | // this procedure should not be done in the config phase, since the clean task would erase the file before it can be used 163 | suppressions.asFile() 164 | } 165 | } 166 | 167 | pmd { 168 | ignoreFailures = true 169 | ruleSetConfig = resources.text.fromArchiveEntry(configurations.codeMetrics, "pmd/pmd.xml") 170 | ruleSets = [] 171 | } 172 | 173 | findbugs { 174 | ignoreFailures = true 175 | effort = 'max' 176 | reportLevel = 'medium' 177 | toolVersion = '3.0.1' 178 | excludeFilterConfig = resources.text.fromArchiveEntry(configurations.codeMetrics, "findbugs/findbugs-exclude.xml") 179 | } 180 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | #Fri Apr 03 11:04:39 CEST 2015 2 | versioneye.projectkey=maven2_worldviewer_1 3 | versioneye.projectid=551e5807fe6d9b0793000e3f 4 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MovingBlocks/WorldViewer/b8796a299cb6f8077e0c5f503b259ddbc0f2b552/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Sun Mar 01 18:01:12 CET 2015 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.3-bin.zip 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # For Cygwin, ensure paths are in UNIX format before anything is touched. 46 | if $cygwin ; then 47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 48 | fi 49 | 50 | # Attempt to set APP_HOME 51 | # Resolve links: $0 may be a link 52 | PRG="$0" 53 | # Need this for relative symlinks. 54 | while [ -h "$PRG" ] ; do 55 | ls=`ls -ld "$PRG"` 56 | link=`expr "$ls" : '.*-> \(.*\)$'` 57 | if expr "$link" : '/.*' > /dev/null; then 58 | PRG="$link" 59 | else 60 | PRG=`dirname "$PRG"`"/$link" 61 | fi 62 | done 63 | SAVED="`pwd`" 64 | cd "`dirname \"$PRG\"`/" >&- 65 | APP_HOME="`pwd -P`" 66 | cd "$SAVED" >&- 67 | 68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 69 | 70 | # Determine the Java command to use to start the JVM. 71 | if [ -n "$JAVA_HOME" ] ; then 72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 73 | # IBM's JDK on AIX uses strange locations for the executables 74 | JAVACMD="$JAVA_HOME/jre/sh/java" 75 | else 76 | JAVACMD="$JAVA_HOME/bin/java" 77 | fi 78 | if [ ! -x "$JAVACMD" ] ; then 79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 80 | 81 | Please set the JAVA_HOME variable in your environment to match the 82 | location of your Java installation." 83 | fi 84 | else 85 | JAVACMD="java" 86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 87 | 88 | Please set the JAVA_HOME variable in your environment to match the 89 | location of your Java installation." 90 | fi 91 | 92 | # Increase the maximum file descriptors if we can. 93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 94 | MAX_FD_LIMIT=`ulimit -H -n` 95 | if [ $? -eq 0 ] ; then 96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 97 | MAX_FD="$MAX_FD_LIMIT" 98 | fi 99 | ulimit -n $MAX_FD 100 | if [ $? -ne 0 ] ; then 101 | warn "Could not set maximum file descriptor limit: $MAX_FD" 102 | fi 103 | else 104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 105 | fi 106 | fi 107 | 108 | # For Darwin, add options to specify how the application appears in the dock 109 | if $darwin; then 110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 111 | fi 112 | 113 | # For Cygwin, switch paths to Windows format before running java 114 | if $cygwin ; then 115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 158 | function splitJvmOpts() { 159 | JVM_OPTS=("$@") 160 | } 161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 163 | 164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 165 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /images/2014-10-21_first_light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MovingBlocks/WorldViewer/b8796a299cb6f8077e0c5f503b259ddbc0f2b552/images/2014-10-21_first_light.png -------------------------------------------------------------------------------- /images/2014-10-27_version0-3-0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MovingBlocks/WorldViewer/b8796a299cb6f8077e0c5f503b259ddbc0f2b552/images/2014-10-27_version0-3-0.png -------------------------------------------------------------------------------- /images/2015-03-07_version0-5-0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MovingBlocks/WorldViewer/b8796a299cb6f8077e0c5f503b259ddbc0f2b552/images/2015-03-07_version0-5-0.png -------------------------------------------------------------------------------- /images/2015-03-25_version0-7-0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MovingBlocks/WorldViewer/b8796a299cb6f8077e0c5f503b259ddbc0f2b552/images/2015-03-25_version0-7-0.png -------------------------------------------------------------------------------- /images/2015-04-25_version0-9-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MovingBlocks/WorldViewer/b8796a299cb6f8077e0c5f503b259ddbc0f2b552/images/2015-04-25_version0-9-3.png -------------------------------------------------------------------------------- /images/2015-05-23_version0-10-5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MovingBlocks/WorldViewer/b8796a299cb6f8077e0c5f503b259ddbc0f2b552/images/2015-05-23_version0-10-5.png -------------------------------------------------------------------------------- /images/2016-05-17_shattered_planes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MovingBlocks/WorldViewer/b8796a299cb6f8077e0c5f503b259ddbc0f2b552/images/2016-05-17_shattered_planes.png -------------------------------------------------------------------------------- /natives/what-is-this-folder-good-for.txt: -------------------------------------------------------------------------------- 1 | We need this folder to trick PathManager: it tries to find the TS root folder by searching for a folder "natives" 2 | 3 | TODO: change PathManager so that the root folder can be specified explicitly -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'WorldViewer' 2 | -------------------------------------------------------------------------------- /src/main/java/org/terasology/world/viewer/MainFrame.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 MovingBlocks 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.terasology.world.viewer; 18 | 19 | import java.awt.BorderLayout; 20 | import java.awt.Dimension; 21 | import java.util.List; 22 | import java.util.Set; 23 | 24 | import javax.swing.Box; 25 | import javax.swing.BoxLayout; 26 | import javax.swing.JFrame; 27 | import javax.swing.JLabel; 28 | import javax.swing.JPanel; 29 | import javax.swing.Timer; 30 | import javax.swing.border.EmptyBorder; 31 | 32 | import org.slf4j.Logger; 33 | import org.slf4j.LoggerFactory; 34 | import org.terasology.context.Context; 35 | import org.terasology.engine.Observer; 36 | import org.terasology.engine.module.ModuleManager; 37 | import org.terasology.registry.CoreRegistry; 38 | import org.terasology.world.generation.WorldFacet; 39 | import org.terasology.world.generator.WorldGenerator; 40 | import org.terasology.world.viewer.config.Config; 41 | import org.terasology.world.viewer.core.ConfigPanel; 42 | import org.terasology.world.viewer.core.FacetPanel; 43 | import org.terasology.world.viewer.core.Viewer; 44 | import org.terasology.world.viewer.layers.FacetLayer; 45 | import org.terasology.world.viewer.layers.FacetLayers; 46 | import org.terasology.world.viewer.camera.Camera; 47 | 48 | import com.google.common.collect.Lists; 49 | 50 | /** 51 | * The main MapViewer JFrame 52 | */ 53 | public class MainFrame extends JFrame { 54 | 55 | private static final long serialVersionUID = -8474971565041036025L; 56 | 57 | private static final Logger logger = LoggerFactory.getLogger(MainFrame.class); 58 | 59 | private static final int MAX_TILES = 3000; 60 | 61 | private final Config config; 62 | private final Timer statusBarTimer; 63 | 64 | /** 65 | * A thread-safe list (required for parallel tile rendering) 66 | */ 67 | private List layerList; 68 | 69 | private final Viewer viewer; 70 | private final FacetPanel layerPanel; 71 | private final ConfigPanel configPanel; 72 | private final JPanel statusBar = new JPanel(); 73 | 74 | 75 | public MainFrame(Context context, Config config) { 76 | 77 | this.config = config; 78 | 79 | configPanel = new ConfigPanel(context, config); 80 | WorldGenerator worldGen = configPanel.getWorldGen(); 81 | 82 | configPanel.addObserver(new Observer() { 83 | 84 | private WorldGenerator oldWorldGen = worldGen; 85 | 86 | @Override 87 | public void update(WorldGenerator wg) { 88 | if (wg != oldWorldGen) { 89 | config.storeLayers(oldWorldGen.getUri(), layerList); 90 | reload(wg); 91 | oldWorldGen = wg; 92 | } 93 | } 94 | }); 95 | 96 | layerPanel = new FacetPanel(); 97 | 98 | viewer = new Viewer(config.getViewConfig(), MAX_TILES); 99 | 100 | reload(worldGen); 101 | 102 | configPanel.addObserver(wg -> viewer.invalidateWorld()); 103 | 104 | add(layerPanel, BorderLayout.EAST); 105 | add(configPanel, BorderLayout.WEST); 106 | add(viewer, BorderLayout.CENTER); 107 | add(statusBar, BorderLayout.SOUTH); 108 | 109 | JLabel cameraLabel = new JLabel(); 110 | cameraLabel.setPreferredSize(new Dimension(170, 0)); 111 | JLabel tileCountLabel = new JLabel(); 112 | tileCountLabel.setPreferredSize(new Dimension(220, 0)); 113 | JLabel memoryLabel = new JLabel(); 114 | memoryLabel.setPreferredSize(new Dimension(140, 0)); 115 | statusBarTimer = new Timer(50, event -> { 116 | Camera camera = viewer.getCamera(); 117 | int camX = (int) camera.getPos().getX(); 118 | int camZ = (int) camera.getPos().getY(); 119 | int zoom = (int) (camera.getZoom() * 100); 120 | cameraLabel.setText(String.format("Camera: %d/%d at %d%%", camX, camZ, zoom)); 121 | 122 | int pendingTiles = viewer.getPendingTiles(); 123 | int cachedTiles = viewer.getCachedTiles(); 124 | tileCountLabel.setText(String.format("Tiles: %d/%d cached, %d queued", cachedTiles, MAX_TILES, pendingTiles)); 125 | 126 | Runtime runtime = Runtime.getRuntime(); 127 | long maxMem = runtime.maxMemory(); 128 | long totalMemory = runtime.totalMemory(); 129 | long freeMem = runtime.freeMemory(); 130 | long allocMemory = totalMemory - freeMem; 131 | long oneMeg = 1024 * 1024; 132 | memoryLabel.setText(String.format("Memory: %d/%d MB", allocMemory / oneMeg, maxMem / oneMeg)); 133 | }); 134 | statusBarTimer.setInitialDelay(0); 135 | statusBarTimer.start(); 136 | 137 | statusBar.setLayout(new BoxLayout(statusBar, BoxLayout.LINE_AXIS)); 138 | statusBar.add(new JLabel("Drag with right mouse button to pan, mouse wheel to zoom")); 139 | statusBar.add(Box.createHorizontalGlue()); 140 | statusBar.add(cameraLabel); 141 | statusBar.add(Box.createHorizontalGlue()); 142 | statusBar.add(tileCountLabel); 143 | statusBar.add(Box.createHorizontalStrut(20)); 144 | statusBar.add(memoryLabel); 145 | statusBar.setBorder(new EmptyBorder(2, 5, 2, 5)); 146 | 147 | setMinimumSize(new Dimension(850, 530)); 148 | } 149 | 150 | private void reload(WorldGenerator worldGen) { 151 | 152 | Set> facets = worldGen.getWorld().getAllFacets(); 153 | 154 | ModuleManager moduleManager = CoreRegistry.get(ModuleManager.class); 155 | 156 | // Create with default values first 157 | List loadedLayers = FacetLayers.createLayersFor(facets, moduleManager.getEnvironment()); 158 | 159 | // Then try to replace them with those from the config file 160 | try { 161 | loadedLayers = config.loadLayers(worldGen.getUri(), loadedLayers); 162 | } catch (RuntimeException e) { 163 | logger.warn("Could not load layers - using default", e); 164 | } 165 | 166 | // assign to thread-safe implementation 167 | layerList = Lists.newCopyOnWriteArrayList(loadedLayers); 168 | 169 | viewer.setWorldGen(worldGen, layerList); 170 | 171 | layerPanel.setLayers(layerList); 172 | } 173 | 174 | @Override 175 | public void dispose() { 176 | super.dispose(); 177 | 178 | statusBarTimer.stop(); 179 | 180 | viewer.close(); 181 | 182 | config.storeLayers(config.getWorldConfig().getWorldGen(), layerList); 183 | } 184 | 185 | } 186 | -------------------------------------------------------------------------------- /src/main/java/org/terasology/world/viewer/ModuleTableModel.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 MovingBlocks 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.terasology.world.viewer; 18 | 19 | import java.util.List; 20 | 21 | import javax.swing.table.AbstractTableModel; 22 | 23 | import org.terasology.module.ArchiveModule; 24 | import org.terasology.module.Module; 25 | import org.terasology.module.PathModule; 26 | 27 | import com.google.common.collect.ImmutableList; 28 | 29 | /** 30 | * A swing-based table model for modules. 31 | */ 32 | public class ModuleTableModel extends AbstractTableModel { 33 | 34 | private static final long serialVersionUID = -2702157486079272558L; 35 | private final List modules; 36 | 37 | private final List columnNames = ImmutableList.of("Name", "Version", "Type"); 38 | 39 | public ModuleTableModel(List modules) { 40 | this.modules = modules; 41 | } 42 | 43 | @Override 44 | public int getRowCount() { 45 | return modules.size(); 46 | } 47 | 48 | @Override 49 | public int getColumnCount() { 50 | return 3; 51 | } 52 | 53 | @Override 54 | public String getColumnName(int column) { 55 | return columnNames.get(column); 56 | } 57 | 58 | @Override 59 | public Object getValueAt(int rowIndex, int columnIndex) { 60 | Module module = modules.get(rowIndex); 61 | 62 | switch (columnIndex) { 63 | case 0: 64 | return module.getId(); 65 | 66 | case 1: 67 | return module.getVersion(); 68 | 69 | case 2: 70 | if (module instanceof ArchiveModule) { 71 | return "jar"; 72 | } 73 | if (module instanceof PathModule) { 74 | return "path"; 75 | } 76 | return "unknown"; 77 | 78 | default: 79 | throw new UnsupportedOperationException("Invalid column index"); 80 | 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/main/java/org/terasology/world/viewer/SelectWorldGenDialog.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 MovingBlocks 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.terasology.world.viewer; 18 | 19 | import java.awt.Dimension; 20 | import java.awt.GridBagConstraints; 21 | import java.awt.GridBagLayout; 22 | import java.awt.Insets; 23 | import java.io.File; 24 | import java.util.Arrays; 25 | import java.util.List; 26 | 27 | import javax.swing.BorderFactory; 28 | import javax.swing.JButton; 29 | import javax.swing.JComboBox; 30 | import javax.swing.JDialog; 31 | import javax.swing.JFileChooser; 32 | import javax.swing.JLabel; 33 | import javax.swing.JOptionPane; 34 | import javax.swing.JPanel; 35 | import javax.swing.JScrollPane; 36 | import javax.swing.JTable; 37 | import javax.swing.JTextField; 38 | import javax.swing.ListSelectionModel; 39 | import javax.swing.border.EmptyBorder; 40 | import javax.swing.filechooser.FileFilter; 41 | import javax.swing.table.TableModel; 42 | 43 | import org.terasology.engine.SimpleUri; 44 | import org.terasology.engine.module.ModuleManager; 45 | import org.terasology.module.Module; 46 | import org.terasology.registry.CoreRegistry; 47 | import org.terasology.world.generator.internal.WorldGeneratorInfo; 48 | import org.terasology.world.generator.internal.WorldGeneratorManager; 49 | import org.terasology.world.viewer.config.WorldConfig; 50 | import org.terasology.world.viewer.env.TinyEnvironment; 51 | import org.terasology.world.viewer.gui.WorldGenCellRenderer; 52 | 53 | import com.google.common.collect.Lists; 54 | 55 | /** 56 | * A modal dialogs for the selection of a world generator. 57 | */ 58 | public class SelectWorldGenDialog extends JDialog { 59 | 60 | private static final long serialVersionUID = 257345717408006930L; 61 | 62 | private final JOptionPane optionPane; 63 | private final JComboBox wgSelectCombo; 64 | private final JTextField seedText; 65 | 66 | private JTable moduleList; 67 | 68 | private JFileChooser jarFileChooser; 69 | 70 | private JFileChooser folderChooser; 71 | 72 | public SelectWorldGenDialog(WorldConfig wgConfig) { 73 | super(null, "Select World Generator", ModalityType.APPLICATION_MODAL); 74 | 75 | // since file choosers are fields, the LRU folder is kept 76 | jarFileChooser = new JFileChooser(); 77 | JarFileFilter filter = new JarFileFilter(); 78 | jarFileChooser.addChoosableFileFilter(filter); 79 | jarFileChooser.setFileFilter(filter); 80 | jarFileChooser.setMultiSelectionEnabled(true); 81 | jarFileChooser.setDialogTitle("Select module JARs to import"); 82 | 83 | folderChooser = new JFileChooser(); 84 | folderChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); 85 | folderChooser.setMultiSelectionEnabled(true); 86 | folderChooser.setDialogTitle("Select module folders to import"); 87 | 88 | JPanel panel = new JPanel(new GridBagLayout()); 89 | GridBagConstraints gbc = new GridBagConstraints(); 90 | gbc.fill = GridBagConstraints.BOTH; 91 | gbc.insets = new Insets(5, 5, 5, 5); 92 | panel.setBorder(new EmptyBorder(0, 10, 10, 10)); 93 | 94 | gbc.gridy = 0; 95 | panel.add(new JLabel("World Generator"), gbc.clone()); 96 | wgSelectCombo = new JComboBox<>(); 97 | wgSelectCombo.setRenderer(new WorldGenCellRenderer()); 98 | panel.add(wgSelectCombo, gbc.clone()); 99 | 100 | gbc.gridy = 1; 101 | panel.add(new JLabel("Seed"), gbc.clone()); 102 | seedText = new JTextField(wgConfig.getWorldSeed()); 103 | panel.add(seedText, gbc.clone()); 104 | 105 | gbc.gridwidth = 2; 106 | 107 | gbc.gridy = 2; 108 | panel.add(new JLabel("Loaded modules:"), gbc.clone()); 109 | 110 | gbc.gridy = 3; 111 | moduleList = new JTable() { 112 | 113 | private static final long serialVersionUID = 3315774652323052959L; 114 | 115 | @Override 116 | public boolean getScrollableTracksViewportHeight() { 117 | return getPreferredSize().height < getParent().getHeight(); 118 | } 119 | }; 120 | moduleList.setBorder(BorderFactory.createEtchedBorder()); 121 | moduleList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); 122 | moduleList.getTableHeader().setReorderingAllowed(false); 123 | moduleList.setBorder(BorderFactory.createEmptyBorder()); 124 | JScrollPane tableScrollPane = new JScrollPane(moduleList); 125 | tableScrollPane.setPreferredSize(new Dimension(270, 150)); 126 | panel.add(tableScrollPane, gbc.clone()); 127 | 128 | gbc.gridy = 4; 129 | JButton importJarButton = new JButton("Import Module JAR"); 130 | importJarButton.addActionListener(e -> showImportModuleJarDialog()); 131 | panel.add(importJarButton, gbc.clone()); 132 | 133 | gbc.gridy = 5; 134 | JButton importFolderButton = new JButton("Import Module Folder"); 135 | importFolderButton.addActionListener(e -> showImportModuleFolderDialog()); 136 | panel.add(importFolderButton, gbc.clone()); 137 | 138 | 139 | gbc.gridy = 6; 140 | String infoText = "Note: You can skip this dialog by
supplying the -skip cmd. line argument"; 141 | panel.add(new JLabel(infoText), gbc.clone()); 142 | 143 | optionPane = new JOptionPane(panel, JOptionPane.PLAIN_MESSAGE, JOptionPane.OK_CANCEL_OPTION); 144 | optionPane.addPropertyChangeListener(e -> { 145 | if (isVisible() && (e.getPropertyName().equals(JOptionPane.VALUE_PROPERTY))) { 146 | setVisible(false); 147 | 148 | updateConfig(wgConfig); 149 | } 150 | }); 151 | 152 | updateWorldGenCombo(); 153 | updateModuleList(); 154 | 155 | trySelect(wgConfig.getWorldGen()); 156 | 157 | setContentPane(optionPane); 158 | setResizable(false); 159 | } 160 | 161 | private void updateModuleList() { 162 | ModuleManager moduleManager = CoreRegistry.get(ModuleManager.class); 163 | List modules = Lists.newArrayList(moduleManager.getEnvironment()); 164 | 165 | // Sort by display name (alphabetically, case-insensitive, localized) 166 | modules.sort((o1, o2) -> String.CASE_INSENSITIVE_ORDER.compare( 167 | o1.getMetadata().getDisplayName().value(), 168 | o2.getMetadata().getDisplayName().value())); 169 | 170 | TableModel dataModel = new ModuleTableModel(modules); 171 | moduleList.setModel(dataModel); 172 | moduleList.getColumnModel().getColumn(0).setPreferredWidth(120); 173 | moduleList.getColumnModel().getColumn(1).setPreferredWidth(10); 174 | moduleList.getColumnModel().getColumn(2).setPreferredWidth(10); 175 | } 176 | 177 | private void updateWorldGenCombo() { 178 | List worldGens = CoreRegistry.get(WorldGeneratorManager.class).getWorldGenerators(); 179 | 180 | wgSelectCombo.removeAllItems(); 181 | for (WorldGeneratorInfo wg : worldGens) { 182 | wgSelectCombo.addItem(wg); 183 | } 184 | } 185 | 186 | private void showImportModuleJarDialog() { 187 | if (jarFileChooser.showOpenDialog(this) == JFileChooser.APPROVE_OPTION) { 188 | TinyEnvironment.addModules(Arrays.asList(jarFileChooser.getSelectedFiles())); 189 | } 190 | updateWorldGenCombo(); 191 | updateModuleList(); 192 | } 193 | 194 | private void showImportModuleFolderDialog() { 195 | if (folderChooser.showOpenDialog(this) == JFileChooser.APPROVE_OPTION) { 196 | TinyEnvironment.addModules(Arrays.asList(folderChooser.getSelectedFiles())); 197 | } 198 | updateWorldGenCombo(); 199 | updateModuleList(); 200 | } 201 | 202 | private void updateConfig(WorldConfig wgConfig) { 203 | int idx = wgSelectCombo.getSelectedIndex(); 204 | if (idx >= 0) { 205 | WorldGeneratorInfo info = wgSelectCombo.getItemAt(idx); 206 | wgConfig.setWorldGen(info.getUri()); 207 | } 208 | 209 | wgConfig.setWorldSeed(seedText.getText()); 210 | } 211 | 212 | private void trySelect(SimpleUri worldGen) { 213 | for (int idx = 0; idx < wgSelectCombo.getItemCount(); idx++) { 214 | WorldGeneratorInfo wg = wgSelectCombo.getItemAt(idx); 215 | if (wg.getUri().equals(worldGen)) { 216 | wgSelectCombo.setSelectedIndex(idx); 217 | } 218 | } 219 | } 220 | 221 | /** 222 | * @return JOptionPane.OK_OPTION or JOptionPane.CANCEL_OPTION 223 | */ 224 | public int getAnswer() { 225 | if (optionPane.getValue() == null) { 226 | return JOptionPane.CANCEL_OPTION; 227 | } 228 | 229 | if (optionPane.getValue().equals(JOptionPane.UNINITIALIZED_VALUE)) { 230 | return JOptionPane.CANCEL_OPTION; 231 | } 232 | 233 | return (Integer) optionPane.getValue(); 234 | } 235 | 236 | /** 237 | * A filter for jar files (case-insensitive). 238 | */ 239 | private static class JarFileFilter extends FileFilter { 240 | @Override 241 | public boolean accept(File file) { 242 | // show directories, too 243 | return file.isDirectory() || file.getName().toLowerCase().endsWith(".jar"); 244 | } 245 | 246 | @Override 247 | public String getDescription() { 248 | return "(*.jar) Jar File"; 249 | } 250 | } 251 | } 252 | 253 | -------------------------------------------------------------------------------- /src/main/java/org/terasology/world/viewer/ThreadSafeRegion.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 MovingBlocks 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.terasology.world.viewer; 18 | 19 | import org.terasology.math.Region3i; 20 | import org.terasology.world.generation.Region; 21 | import org.terasology.world.generation.WorldFacet; 22 | 23 | /** 24 | * A thread-safe wrapping class for {@link Region} that 25 | * synchronizes access to {@link #getFacet(Class)}. It assumes that 26 | * {@link #getRegion()} does not need synchronizing. 27 | */ 28 | public class ThreadSafeRegion implements Region { 29 | 30 | private final Region base; 31 | 32 | /** 33 | * @param base the underlying original region this implementation uses 34 | */ 35 | public ThreadSafeRegion(Region base) { 36 | this.base = base; 37 | } 38 | 39 | @Override 40 | public synchronized T getFacet(Class dataType) { 41 | return base.getFacet(dataType); 42 | } 43 | 44 | @Override 45 | public Region3i getRegion() { 46 | return base.getRegion(); 47 | } 48 | 49 | @Override 50 | public String toString() { 51 | return "ThreadSafeRegion [" + getRegion() + "]"; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/org/terasology/world/viewer/WorldViewer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 MovingBlocks 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.terasology.world.viewer; 18 | 19 | import java.awt.Image; 20 | import java.awt.Rectangle; 21 | import java.awt.event.WindowAdapter; 22 | import java.awt.event.WindowEvent; 23 | import java.awt.image.BufferedImage; 24 | import java.io.File; 25 | import java.io.IOException; 26 | import java.net.URL; 27 | import java.nio.file.Path; 28 | import java.nio.file.Paths; 29 | import java.time.format.DateTimeFormatter; 30 | import java.time.format.FormatStyle; 31 | import java.util.ArrayList; 32 | import java.util.List; 33 | 34 | import javax.imageio.ImageIO; 35 | import javax.swing.JFrame; 36 | import javax.swing.SwingUtilities; 37 | import javax.swing.UIManager; 38 | import javax.swing.WindowConstants; 39 | 40 | import org.slf4j.Logger; 41 | import org.slf4j.LoggerFactory; 42 | import org.terasology.context.Context; 43 | import org.terasology.splash.SplashScreen; 44 | import org.terasology.splash.SplashScreenBuilder; 45 | import org.terasology.splash.overlay.AnimatedBoxRowOverlay; 46 | import org.terasology.splash.overlay.RectOverlay; 47 | import org.terasology.splash.overlay.TextOverlay; 48 | import org.terasology.world.viewer.config.Config; 49 | import org.terasology.world.viewer.env.TinyEnvironment; 50 | import org.terasology.world.viewer.version.VersionInfo; 51 | 52 | /** 53 | * Preview generated world in Swing 54 | */ 55 | public final class WorldViewer { 56 | 57 | private static final Logger logger = LoggerFactory.getLogger(WorldViewer.class); 58 | 59 | private static final Path CONFIG_PATH = Paths.get(System.getProperty("user.home"), ".worldviewer.json"); 60 | 61 | private WorldViewer() { 62 | // don't create instances 63 | } 64 | 65 | /** 66 | * @param args (ignored) 67 | */ 68 | public static void main(String[] args) { 69 | 70 | logStatus(); 71 | 72 | try { 73 | 74 | SplashScreen splashScreen = createSplashScreen(); 75 | splashScreen.post("Loading ..."); 76 | 77 | // FullEnvironment.setup(); 78 | Context context = TinyEnvironment.createContext(splashScreen); 79 | 80 | Config config = Config.load(CONFIG_PATH); 81 | 82 | splashScreen.close(); 83 | SwingUtilities.invokeLater(() -> { 84 | setupLookAndFeel(); 85 | createAndShowMainFrame(context, config); 86 | }); 87 | } catch (IOException e) { 88 | System.err.println("Could not load modules: " + e.getMessage()); 89 | return; 90 | } 91 | } 92 | 93 | private static SplashScreen createSplashScreen() { 94 | SplashScreenBuilder builder = new SplashScreenBuilder(); 95 | int imageHeight = 332; 96 | int maxTextWidth = 450; 97 | int width = 600; 98 | int height = 30; 99 | int left = 20; 100 | int top = imageHeight - height - 20; 101 | 102 | Rectangle rectRc = new Rectangle(left, top, width, height); 103 | Rectangle textRc = new Rectangle(left + 10, top + 5, maxTextWidth, height); 104 | Rectangle boxRc = new Rectangle(left + maxTextWidth + 10, top, width - maxTextWidth - 20, height); 105 | return builder 106 | .add(new RectOverlay(rectRc)) 107 | .add(new TextOverlay(textRc)) 108 | .add(new AnimatedBoxRowOverlay(boxRc)) 109 | .build(); 110 | } 111 | 112 | private static void setupLookAndFeel() { 113 | try { 114 | UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); 115 | } catch (Exception e) { 116 | // we don't really care about l&f that much, so we just eat the exception 117 | logger.error("Cannot set look & feel", e); 118 | } 119 | } 120 | 121 | private static void logStatus() { 122 | logger.info("Starting ..."); 123 | 124 | DateTimeFormatter dateFormat = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM); 125 | logger.debug("Java: {} {} {}", System.getProperty("java.version"), System.getProperty("java.vendor"), System.getProperty("java.home")); 126 | logger.debug("Java VM: {} {} {}", System.getProperty("java.vm.name"), System.getProperty("java.vm.vendor"), System.getProperty("java.vm.version")); 127 | logger.debug("OS: {} {} {}", System.getProperty("os.name"), System.getProperty("os.arch"), System.getProperty("os.version")); 128 | logger.debug("Max. Memory: {} MB", Runtime.getRuntime().maxMemory() / (1024 * 1024)); 129 | logger.debug("Version: {}", VersionInfo.getVersion()); 130 | logger.debug("Built: {}", VersionInfo.getBuildTime().format(dateFormat)); 131 | logger.debug("Commit: {}", VersionInfo.getBuildCommit()); 132 | 133 | String classpath = System.getProperty("java.class.path"); 134 | String[] cpEntries = classpath.split(File.pathSeparator); 135 | for (String cpEntry : cpEntries) { 136 | logger.debug("Classpath: " + cpEntry); 137 | } 138 | } 139 | 140 | private static void createAndShowMainFrame(Context context, Config config) { 141 | JFrame frame = new MainFrame(context, config); 142 | frame.setIconImages(loadIcons()); 143 | frame.setTitle("WorldViewer " + VersionInfo.getVersion()); 144 | frame.setSize(1280, 720); 145 | frame.setLocationRelativeTo(null); 146 | frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); 147 | frame.setVisible(true); 148 | frame.addWindowListener(new WindowAdapter() { 149 | @Override 150 | public void windowClosed(WindowEvent e) { 151 | Config.save(CONFIG_PATH, config); 152 | 153 | // just in case some other thread is still running 154 | System.exit(0); 155 | } 156 | }); 157 | 158 | // frame.setAlwaysOnTop(true); 159 | 160 | // align right border at the right border of the default screen 161 | // GraphicsDevice gd = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice(); 162 | // int screenWidth = gd.getDisplayMode().getWidth(); 163 | // frame.setLocation(screenWidth - frame.getWidth(), 40); 164 | } 165 | 166 | private static List loadIcons() { 167 | List icons = new ArrayList(); 168 | int[] sizes = {16, 32, 64}; 169 | for (int size : sizes) { 170 | String name = String.format("/icons/gooey_sweet_red_%d.png", size); 171 | URL resUrl = WorldViewer.class.getResource(name); 172 | try { 173 | BufferedImage iconImage = ImageIO.read(resUrl); 174 | icons.add(iconImage); 175 | } catch (IOException e) { 176 | logger.warn("Could not load icon: {}", name); 177 | } 178 | } 179 | return icons; 180 | } 181 | } 182 | -------------------------------------------------------------------------------- /src/main/java/org/terasology/world/viewer/camera/Camera.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 MovingBlocks 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.terasology.world.viewer.camera; 18 | 19 | import java.util.Collection; 20 | 21 | import org.terasology.math.TeraMath; 22 | import org.terasology.math.geom.ImmutableVector2f; 23 | import org.terasology.math.geom.Rect2i; 24 | import org.terasology.math.geom.Vector2f; 25 | 26 | import com.google.common.collect.Lists; 27 | 28 | /** 29 | * Defines a simple camera 30 | */ 31 | public class Camera { 32 | private final Vector2f pos = new Vector2f(); 33 | private final Collection listeners = Lists.newLinkedList(); 34 | private float zoom = 1.0f; 35 | 36 | public float getZoom() { 37 | return zoom; 38 | } 39 | 40 | public void setZoom(float zoom) { 41 | this.zoom = zoom; 42 | for (CameraListener listener : listeners) { 43 | listener.onZoomChange(); 44 | } 45 | } 46 | 47 | public ImmutableVector2f getPos() { 48 | return new ImmutableVector2f(pos.x, pos.y); 49 | } 50 | 51 | /** 52 | * @param dx the x translation 53 | * @param dy the y translation 54 | */ 55 | public void translate(float dx, float dy) { 56 | this.pos.addX(dx / zoom); 57 | this.pos.addY(dy / zoom); 58 | for (CameraListener listener : listeners) { 59 | listener.onPosChange(); 60 | } 61 | } 62 | 63 | public void addListener(CameraListener listener) { 64 | listeners.add(listener); 65 | } 66 | 67 | public void removeListener(CameraListener listener) { 68 | listeners.remove(listener); 69 | } 70 | 71 | /** 72 | * @param width the width of the window 73 | * @param height the height of the window 74 | * @return the window that is currently visible by the camera 75 | */ 76 | public Rect2i getVisibleArea(int width, int height) { 77 | int cx = TeraMath.floorToInt(pos.getX()); 78 | int cy = TeraMath.floorToInt(pos.getY()); 79 | 80 | // Compensate rounding errors by adding 2px to the visible window size 81 | int w = (int) (width / getZoom()) + 2; 82 | int h = (int) (height / getZoom()) + 2; 83 | int minX = cx - w / 2; 84 | int minY = cy - h / 2; 85 | Rect2i visWorld = Rect2i.createFromMinAndSize(minX, minY, w, h); 86 | return visWorld; 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/main/java/org/terasology/world/viewer/camera/CameraKeyController.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 MovingBlocks 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.terasology.world.viewer.camera; 18 | 19 | import java.awt.event.KeyAdapter; 20 | import java.awt.event.KeyEvent; 21 | 22 | /** 23 | * Controls a camera based on keyboard interaction 24 | */ 25 | public class CameraKeyController extends KeyAdapter { 26 | 27 | private final Camera camera; 28 | 29 | public CameraKeyController(Camera camera) { 30 | this.camera = camera; 31 | } 32 | 33 | @Override 34 | public void keyPressed(KeyEvent e) { 35 | int moveInterval = 64; 36 | 37 | if (e.getKeyCode() == KeyEvent.VK_LEFT) { 38 | camera.translate(-moveInterval, 0); 39 | } 40 | if (e.getKeyCode() == KeyEvent.VK_RIGHT) { 41 | camera.translate(moveInterval, 0); 42 | } 43 | if (e.getKeyCode() == KeyEvent.VK_UP) { 44 | camera.translate(0, -moveInterval); 45 | } 46 | if (e.getKeyCode() == KeyEvent.VK_DOWN) { 47 | camera.translate(0, moveInterval); 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/org/terasology/world/viewer/camera/CameraListener.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 MovingBlocks 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.terasology.world.viewer.camera; 18 | 19 | /** 20 | * Notified when the camera setting changes 21 | */ 22 | public interface CameraListener { 23 | 24 | void onPosChange(); 25 | 26 | void onZoomChange(); 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/org/terasology/world/viewer/camera/CameraMouseController.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 MovingBlocks 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.terasology.world.viewer.camera; 18 | 19 | import java.awt.Component; 20 | import java.awt.Point; 21 | import java.awt.event.MouseAdapter; 22 | import java.awt.event.MouseEvent; 23 | import java.awt.event.MouseWheelEvent; 24 | import java.math.RoundingMode; 25 | 26 | import javax.swing.SwingUtilities; 27 | 28 | import org.terasology.math.TeraMath; 29 | 30 | import com.google.common.math.DoubleMath; 31 | 32 | /** 33 | * Controls a camera based on mouse interaction 34 | */ 35 | public class CameraMouseController extends MouseAdapter { 36 | 37 | private Point draggedPoint; 38 | private final Camera camera; 39 | 40 | /** 41 | * zoom = 2 ^ (zoomLevel * zoomDelta) 42 | */ 43 | private int zoomLevel; 44 | 45 | /** 46 | * 2^(-8 * 0.25) = 25% 47 | */ 48 | private int minZoomLevel = -8; 49 | 50 | /** 51 | * 2^(20 * 0.25) = 3200% 52 | */ 53 | private int maxZoomLevel = 20; 54 | 55 | private final float zoomDelta = 0.25f; 56 | 57 | public CameraMouseController(Camera camera) { 58 | this.camera = camera; 59 | this.zoomLevel = findZoomLevel(camera.getZoom()); 60 | } 61 | 62 | @Override 63 | public void mouseDragged(MouseEvent e) { 64 | if (draggedPoint != null) { 65 | int dx = draggedPoint.x - e.getX(); 66 | int dy = draggedPoint.y - e.getY(); 67 | draggedPoint.setLocation(e.getPoint()); 68 | camera.translate(dx, dy); 69 | } 70 | } 71 | 72 | @Override 73 | public void mousePressed(MouseEvent e) { 74 | if (SwingUtilities.isRightMouseButton(e)) { 75 | draggedPoint = e.getPoint(); 76 | } 77 | } 78 | 79 | @Override 80 | public void mouseReleased(MouseEvent e) { 81 | draggedPoint = null; 82 | } 83 | 84 | private int findZoomLevel(float zoom) { 85 | double est = Math.log(zoom) / Math.log(2); 86 | return DoubleMath.roundToInt(est / zoomDelta, RoundingMode.HALF_UP); 87 | } 88 | 89 | @Override 90 | public void mouseWheelMoved(MouseWheelEvent e) { 91 | zoomLevel += e.getWheelRotation(); 92 | 93 | zoomLevel = TeraMath.clamp(zoomLevel, minZoomLevel, maxZoomLevel); 94 | 95 | // Zoom only in deterministic steps 96 | // Don't concatenate with previous zooms to avoid rounding errors 97 | float zoom = (float) Math.pow(2.0, zoomLevel * zoomDelta); 98 | 99 | // This cast is safe since MouseWheelEvent takes only Component sources 100 | Component source = (Component) e.getSource(); 101 | 102 | int relX = e.getX() - source.getWidth() / 2; 103 | int relY = e.getY() - source.getHeight() / 2; 104 | 105 | // move the camera to the cursor position 106 | camera.translate(relX, relY); 107 | 108 | // zoom 109 | camera.setZoom(zoom); 110 | 111 | // revert the camera movement from above 112 | camera.translate(-relX, -relY); 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /src/main/java/org/terasology/world/viewer/camera/RepaintingCameraListener.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 MovingBlocks 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.terasology.world.viewer.camera; 18 | 19 | import java.awt.Component; 20 | 21 | /** 22 | * Repaints a component when the camera moves 23 | * or changes zoom. 24 | */ 25 | public class RepaintingCameraListener implements CameraListener { 26 | private Component comp; 27 | 28 | public RepaintingCameraListener(Component comp) { 29 | this.comp = comp; 30 | } 31 | 32 | @Override 33 | public void onZoomChange() { 34 | comp.repaint(); 35 | } 36 | 37 | @Override 38 | public void onPosChange() { 39 | comp.repaint(); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/org/terasology/world/viewer/config/ClassTypeAdapter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 MovingBlocks 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.terasology.world.viewer.config; 18 | 19 | import java.lang.reflect.Type; 20 | 21 | import com.google.gson.JsonDeserializationContext; 22 | import com.google.gson.JsonDeserializer; 23 | import com.google.gson.JsonElement; 24 | import com.google.gson.JsonParseException; 25 | import com.google.gson.JsonSerializationContext; 26 | import com.google.gson.JsonSerializer; 27 | 28 | /** 29 | * Serializes Class instances. 30 | */ 31 | public class ClassTypeAdapter implements JsonDeserializer>, JsonSerializer> { 32 | 33 | @Override 34 | public Class deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { 35 | try { 36 | return Class.forName(json.getAsString()); 37 | } catch (ClassNotFoundException e) { 38 | throw new JsonParseException(e); 39 | } 40 | } 41 | 42 | @Override 43 | public JsonElement serialize(Class src, Type typeOfSrc, JsonSerializationContext context) { 44 | return context.serialize(src.getName()); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/org/terasology/world/viewer/config/Config.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 MovingBlocks 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.terasology.world.viewer.config; 18 | 19 | import java.io.BufferedWriter; 20 | import java.io.IOException; 21 | import java.io.Reader; 22 | import java.lang.reflect.Constructor; 23 | import java.nio.file.Files; 24 | import java.nio.file.Path; 25 | import java.util.Iterator; 26 | import java.util.List; 27 | 28 | import org.slf4j.Logger; 29 | import org.slf4j.LoggerFactory; 30 | import org.terasology.engine.SimpleUri; 31 | import org.terasology.engine.TerasologyConstants; 32 | import org.terasology.naming.Version; 33 | import org.terasology.naming.gson.VersionTypeAdapter; 34 | import org.terasology.utilities.gson.UriTypeAdapterFactory; 35 | import org.terasology.world.viewer.layers.FacetLayer; 36 | import org.terasology.world.viewer.layers.FacetLayerConfig; 37 | import org.terasology.world.viewer.version.VersionInfo; 38 | 39 | import com.google.common.collect.Lists; 40 | import com.google.gson.Gson; 41 | import com.google.gson.GsonBuilder; 42 | import com.google.gson.JsonElement; 43 | import com.google.gson.JsonParseException; 44 | 45 | 46 | /** 47 | * The root class for all configs 48 | */ 49 | public class Config { 50 | 51 | private static final Logger logger = LoggerFactory.getLogger(Config.class); 52 | 53 | private static final Gson GSON = new GsonBuilder() 54 | .registerTypeAdapter(Class.class, new ClassTypeAdapter()) 55 | .registerTypeAdapter(Version.class, new VersionTypeAdapter()) 56 | .registerTypeAdapterFactory(new UriTypeAdapterFactory()) 57 | .setPrettyPrinting().create(); 58 | 59 | private static final Version OLDEST_COMPATIBLE_VERSION = new Version(0, 8, 1); 60 | 61 | private ConfigData data; 62 | 63 | public Config() { 64 | data = new ConfigData(); 65 | } 66 | 67 | public Config(ConfigData data) { 68 | this.data = data; 69 | } 70 | 71 | ConfigData getData() { 72 | return data; 73 | } 74 | 75 | public ViewConfig getViewConfig() { 76 | return data.viewConfig; 77 | } 78 | 79 | public WorldConfig getWorldConfig() { 80 | return data.worldConfig; 81 | } 82 | 83 | public void storeLayers(SimpleUri wgUri, List layers) { 84 | WorldGenConfigData wgConfig = data.worldGenConfigs.get(wgUri); 85 | if (wgConfig == null) { 86 | wgConfig = new WorldGenConfigData(); 87 | data.worldGenConfigs.put(wgUri, wgConfig); 88 | } else { 89 | wgConfig.layers.clear(); 90 | } 91 | 92 | for (FacetLayer layer : layers) { 93 | JsonElement jsonTree = GSON.toJsonTree(layer.getConfig()); 94 | wgConfig.layers.add(new ConfigEntry(layer, jsonTree, layer.isVisible())); 95 | } 96 | } 97 | 98 | public List loadLayers(SimpleUri wgUri, List defaultFacets) { 99 | List confLayers = Lists.newArrayList(); 100 | List defLayers = Lists.newArrayList(defaultFacets); 101 | 102 | WorldGenConfigData wgData = data.worldGenConfigs.get(wgUri); 103 | if (wgData == null) { 104 | // no info stored for this world gen -> use defaults 105 | return defLayers; 106 | } 107 | 108 | for (ConfigEntry entry : wgData.layers) { 109 | Class facetClass = entry.getFacetClass(); 110 | 111 | // if a "similar" entry exists somewhere in the default config 112 | // replace it with a configured one 113 | if (removeDefault(facetClass, defLayers)) { 114 | FacetLayer layer; 115 | if (entry.getConfigClass() != null) { 116 | FacetLayerConfig conf = GSON.fromJson(entry.getData(), entry.getConfigClass()); 117 | layer = createInstance(facetClass, conf); 118 | } else { 119 | layer = createInstance(facetClass); 120 | } 121 | 122 | if (layer != null) { 123 | layer.setVisible(entry.isVisible()); 124 | confLayers.add(layer); 125 | } 126 | } else { 127 | logger.warn("Found entry that does not correspond to any default layer: {}", facetClass); 128 | } 129 | } 130 | 131 | for (FacetLayer layer : defLayers) { 132 | logger.info("No stored config available for {} - using defaults", layer.getClass()); 133 | confLayers.add(layer); 134 | } 135 | 136 | return confLayers; 137 | } 138 | 139 | private FacetLayer createInstance(Class facetClass) { 140 | try { 141 | Constructor c = facetClass.getConstructor(); 142 | return c.newInstance(); 143 | } catch (NoSuchMethodException e) { 144 | logger.warn("Class {} does not have a public default constructor", facetClass); 145 | return null; 146 | } catch (ReflectiveOperationException e) { 147 | logger.warn("Could not create an instance of {}", facetClass); 148 | return null; 149 | } 150 | } 151 | 152 | private FacetLayer createInstance(Class facetClass, FacetLayerConfig conf) { 153 | try { 154 | Constructor c = facetClass.getConstructor(conf.getClass()); 155 | return c.newInstance(conf); 156 | } catch (NoSuchMethodException e) { 157 | logger.warn("Class {} does not have a public constructor for {}", facetClass, conf); 158 | return null; 159 | } catch (ReflectiveOperationException e) { 160 | logger.warn("Could not create an instance of {} with {}", facetClass, conf); 161 | return null; 162 | } 163 | } 164 | 165 | private boolean removeDefault(Class facetClass, List defLayers) { 166 | Iterator it = defLayers.iterator(); 167 | 168 | while (it.hasNext()) { 169 | if (facetClass.isInstance(it.next())) { 170 | it.remove(); 171 | return true; 172 | } 173 | } 174 | 175 | return false; 176 | } 177 | 178 | /** 179 | * Loads a JSON format configuration file as a new Config 180 | * @param configFile the json file 181 | * @return The loaded configuration 182 | */ 183 | public static Config load(Path configFile) { 184 | 185 | if (!configFile.toFile().exists()) { 186 | logger.info("Config file does not exist - creating new config"); 187 | return new Config(); 188 | } 189 | 190 | logger.info("Reading config file {}", configFile); 191 | 192 | try (Reader reader = Files.newBufferedReader(configFile, TerasologyConstants.CHARSET)) { 193 | // peek into config file to find the version 194 | Version foundVersion = new Version(0, 0, 0); 195 | JsonElement tree = GSON.fromJson(reader, JsonElement.class); 196 | if (tree != null && tree.isJsonObject()) { 197 | JsonElement versionElement = tree.getAsJsonObject().get("version"); 198 | if (versionElement != null) { 199 | foundVersion = new Version(versionElement.getAsString()); 200 | } 201 | } 202 | if (foundVersion.compareTo(OLDEST_COMPATIBLE_VERSION) < 0) { 203 | logger.info("Config file is outdated (v{}) - creating new config", foundVersion); 204 | return new Config(); 205 | } 206 | ConfigData data = GSON.fromJson(tree, ConfigData.class); 207 | 208 | return new Config(data); 209 | } 210 | 211 | catch (JsonParseException | IOException e) { 212 | logger.error("Could not load config file", e); 213 | return new Config(); 214 | } 215 | } 216 | 217 | public static void save(Path configFile, Config config) { 218 | logger.info("Writing config file to {}", configFile); 219 | 220 | try (BufferedWriter writer = Files.newBufferedWriter(configFile, TerasologyConstants.CHARSET)) { 221 | ConfigData data = config.getData(); 222 | data.version = VersionInfo.getVersion(); 223 | GSON.toJson(data, writer); 224 | } 225 | catch (JsonParseException | IOException e) { 226 | logger.error("Could not save config file", e); 227 | } 228 | } 229 | } 230 | 231 | -------------------------------------------------------------------------------- /src/main/java/org/terasology/world/viewer/config/ConfigData.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 MovingBlocks 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.terasology.world.viewer.config; 18 | 19 | import java.util.Map; 20 | 21 | import org.terasology.engine.SimpleUri; 22 | import org.terasology.naming.Version; 23 | 24 | import com.google.common.collect.Maps; 25 | 26 | /** 27 | * The root class for all configs 28 | */ 29 | class ConfigData { 30 | 31 | Version version = new Version(0, 0, 0); 32 | 33 | ViewConfig viewConfig = new ViewConfig(); 34 | 35 | WorldConfig worldConfig = new WorldConfig(); 36 | 37 | Map worldGenConfigs = Maps.newHashMap(); 38 | } 39 | 40 | 41 | -------------------------------------------------------------------------------- /src/main/java/org/terasology/world/viewer/config/ConfigEntry.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 MovingBlocks 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.terasology.world.viewer.config; 18 | 19 | import org.terasology.world.viewer.layers.FacetLayer; 20 | import org.terasology.world.viewer.layers.FacetLayerConfig; 21 | 22 | import com.google.gson.JsonElement; 23 | 24 | class ConfigEntry { 25 | 26 | private Class facetClass; 27 | private Class configClass; 28 | private JsonElement data; 29 | private boolean visible; 30 | 31 | public ConfigEntry(FacetLayer layer, JsonElement data, boolean visible) { 32 | this.facetClass = layer.getClass(); 33 | this.configClass = layer.getConfig() != null ? layer.getConfig().getClass() : null; 34 | this.data = data; 35 | this.visible = visible; 36 | } 37 | 38 | public Class getFacetClass() { 39 | return facetClass; 40 | } 41 | 42 | public Class getConfigClass() { 43 | return configClass; 44 | } 45 | 46 | public JsonElement getData() { 47 | return data; 48 | } 49 | 50 | public boolean isVisible() { 51 | return visible; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/org/terasology/world/viewer/config/ViewConfig.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 MovingBlocks 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.terasology.world.viewer.config; 18 | 19 | import org.terasology.math.geom.Vector2i; 20 | 21 | /** 22 | * Stores view-related config params. 23 | */ 24 | public class ViewConfig { 25 | 26 | private Vector2i camPos = new Vector2i(0, 0); 27 | private float zoomFactor = 1f; 28 | 29 | public Vector2i getCamPos() { 30 | return camPos; 31 | } 32 | 33 | public void setCamPos(Vector2i camPos) { 34 | this.camPos = camPos; 35 | } 36 | 37 | public float getZoomFactor() { 38 | return zoomFactor; 39 | } 40 | 41 | public void setZoomFactor(float zoomFactor) { 42 | this.zoomFactor = zoomFactor; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/org/terasology/world/viewer/config/WorldConfig.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 MovingBlocks 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.terasology.world.viewer.config; 18 | 19 | import org.terasology.engine.SimpleUri; 20 | 21 | 22 | /** 23 | * Stores world-related config params. 24 | */ 25 | public class WorldConfig { 26 | 27 | private SimpleUri worldGen = new SimpleUri("core", "facetedperlin"); 28 | 29 | private String worldSeed = "sdfsfdf"; 30 | 31 | public SimpleUri getWorldGen() { 32 | return worldGen; 33 | } 34 | 35 | public void setWorldGen(SimpleUri worldGen) { 36 | this.worldGen = worldGen; 37 | } 38 | 39 | public String getWorldSeed() { 40 | return worldSeed; 41 | } 42 | 43 | public void setWorldSeed(String worldSeed) { 44 | this.worldSeed = worldSeed; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/org/terasology/world/viewer/config/WorldGenConfigData.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 MovingBlocks 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.terasology.world.viewer.config; 18 | 19 | import java.util.List; 20 | 21 | import com.google.common.collect.Lists; 22 | 23 | /** 24 | */ 25 | public class WorldGenConfigData { 26 | List layers = Lists.newArrayList(); 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/org/terasology/world/viewer/core/ConfigPanel.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 MovingBlocks 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.terasology.world.viewer.core; 18 | 19 | import java.awt.BorderLayout; 20 | import java.awt.Color; 21 | import java.awt.Container; 22 | import java.awt.Dimension; 23 | import java.awt.Font; 24 | import java.awt.GridBagConstraints; 25 | import java.awt.GridBagLayout; 26 | import java.awt.Insets; 27 | import java.awt.event.ActionEvent; 28 | import java.lang.annotation.Annotation; 29 | import java.lang.reflect.Field; 30 | import java.util.List; 31 | import java.util.Map.Entry; 32 | 33 | import javax.swing.BorderFactory; 34 | import javax.swing.JButton; 35 | import javax.swing.JCheckBox; 36 | import javax.swing.JComboBox; 37 | import javax.swing.JComponent; 38 | import javax.swing.JLabel; 39 | import javax.swing.JOptionPane; 40 | import javax.swing.JPanel; 41 | import javax.swing.JSpinner; 42 | import javax.swing.SwingConstants; 43 | import javax.swing.border.EmptyBorder; 44 | import javax.swing.border.MatteBorder; 45 | 46 | import org.slf4j.Logger; 47 | import org.slf4j.LoggerFactory; 48 | import org.terasology.context.Context; 49 | import org.terasology.engine.Observer; 50 | import org.terasology.engine.SimpleUri; 51 | import org.terasology.entitySystem.Component; 52 | import org.terasology.registry.CoreRegistry; 53 | import org.terasology.world.generator.WorldConfigurator; 54 | import org.terasology.world.generator.WorldGenerator; 55 | import org.terasology.world.generator.internal.WorldGeneratorManager; 56 | import org.terasology.world.viewer.SelectWorldGenDialog; 57 | import org.terasology.world.viewer.config.Config; 58 | import org.terasology.world.viewer.config.WorldConfig; 59 | import org.terasology.world.viewer.gui.UIBindings; 60 | 61 | import com.google.common.collect.Lists; 62 | import com.google.gson.Gson; 63 | import com.google.gson.JsonElement; 64 | import com.google.gson.JsonObject; 65 | 66 | public class ConfigPanel extends JPanel { 67 | 68 | private static final long serialVersionUID = -2350103799660220648L; 69 | 70 | private static final Logger logger = LoggerFactory.getLogger(ConfigPanel.class); 71 | 72 | private final List> observers = Lists.newArrayList(); 73 | 74 | private Context context; 75 | private Config config; 76 | 77 | private WorldGenerator worldGen; 78 | 79 | private JLabel worldGenLabel = new JLabel(); 80 | private JLabel seedLabel = new JLabel(); 81 | private JLabel seaLevelLabel = new JLabel(); 82 | 83 | private JPanel configPanel; 84 | 85 | public ConfigPanel(Context context, Config config) { 86 | 87 | setLayout(new BorderLayout()); 88 | setBorder(new EmptyBorder(5, 5, 5, 5)); 89 | 90 | this.context = context; 91 | this.config = config; 92 | 93 | JPanel wgSelectPanel = new JPanel(new GridBagLayout()); 94 | wgSelectPanel.setBorder(BorderFactory.createTitledBorder("World Generator")); 95 | 96 | reloadWorldGen(config.getWorldConfig()); 97 | 98 | GridBagConstraints gbc = new GridBagConstraints(); 99 | gbc.insets = new Insets(5, 5, 5, 5); 100 | gbc.fill = GridBagConstraints.BOTH; 101 | gbc.gridy = 0; 102 | wgSelectPanel.add(new JLabel("Generator Type:"), gbc.clone()); 103 | wgSelectPanel.add(worldGenLabel, gbc.clone()); 104 | gbc.gridy = 1; 105 | wgSelectPanel.add(new JLabel("World Seed:"), gbc.clone()); 106 | wgSelectPanel.add(seedLabel, gbc.clone()); 107 | gbc.gridy = 2; 108 | wgSelectPanel.add(new JLabel("Sea Level Height:"), gbc.clone()); 109 | wgSelectPanel.add(seaLevelLabel, gbc.clone()); 110 | add(wgSelectPanel, BorderLayout.NORTH); 111 | 112 | JButton button = new JButton("Change World Generator"); 113 | button.addActionListener(this::editWorldGen); 114 | add(button, BorderLayout.SOUTH); 115 | } 116 | 117 | /** 118 | * Adds an observer 119 | * @param obs the observer to add 120 | */ 121 | public void addObserver(Observer obs) { 122 | observers.add(obs); 123 | } 124 | 125 | public void removeObserver(Observer obs) { 126 | observers.remove(obs); 127 | } 128 | 129 | private void editWorldGen(ActionEvent event) { 130 | WorldConfig wgConfig = config.getWorldConfig(); 131 | SelectWorldGenDialog dialog = new SelectWorldGenDialog(wgConfig); 132 | dialog.pack(); 133 | dialog.setLocationRelativeTo(null); 134 | dialog.setVisible(true); 135 | dialog.dispose(); 136 | if (dialog.getAnswer() == JOptionPane.OK_OPTION) { 137 | reloadWorldGen(config.getWorldConfig()); 138 | } 139 | } 140 | 141 | private void reloadWorldGen(WorldConfig wgConfig) { 142 | SimpleUri worldGenUri = wgConfig.getWorldGen(); 143 | String worldSeed = wgConfig.getWorldSeed(); 144 | WorldGeneratorManager worldGeneratorManager = CoreRegistry.get(WorldGeneratorManager.class); 145 | try { 146 | worldGen = worldGeneratorManager.createGenerator(worldGenUri, context); 147 | worldGen.setWorldSeed(worldSeed); 148 | worldGen.initialize(); 149 | 150 | String wgName = worldGeneratorManager.getWorldGeneratorInfo(worldGenUri).getDisplayName(); 151 | int seaLevel = worldGen.getWorld().getSeaLevel(); 152 | worldGenLabel.setText(wgName); 153 | seedLabel.setText(worldSeed); 154 | seaLevelLabel.setText(seaLevel + " blocks"); 155 | 156 | if (configPanel != null) { 157 | remove(configPanel); 158 | } 159 | configPanel = createConfigPanel(worldGen.getConfigurator()); 160 | add(configPanel, BorderLayout.CENTER); 161 | 162 | // then notify all observers 163 | for (Observer obs : observers) { 164 | obs.update(worldGen); 165 | } 166 | } catch (Exception ex) { 167 | String message = "Could not create world generator
" + ex + ""; 168 | logger.error("Could not create world generator {}", worldGenUri, ex); 169 | JOptionPane.showMessageDialog(null, message, "Error", JOptionPane.ERROR_MESSAGE); 170 | } 171 | } 172 | 173 | private void notifyObservers(String group, Field field, Object value) { 174 | WorldConfigurator configurator = worldGen.getConfigurator(); 175 | Component comp = configurator.getProperties().get(group); 176 | Component clone = cloneAndSet(comp, field.getName(), value); 177 | 178 | // first notify the world generator about the new component 179 | configurator.setProperty(group, clone); 180 | 181 | // then notify all observers 182 | for (Observer obs : observers) { 183 | obs.update(worldGen); 184 | } 185 | } 186 | 187 | private static Component cloneAndSet(Component object, String field, Object value) { 188 | Gson gson = new Gson(); 189 | JsonObject json = (JsonObject) gson.toJsonTree(object); 190 | JsonElement jsonValue = gson.toJsonTree(value); 191 | json.add(field, jsonValue); 192 | Component clone = gson.fromJson(json, object.getClass()); 193 | return clone; 194 | } 195 | 196 | private JPanel createConfigPanel(WorldConfigurator configurator) { 197 | JPanel panel = new JPanel(); 198 | panel.setLayout(new GridBagLayout()); 199 | 200 | GridBagConstraints gbc = new GridBagConstraints(); 201 | gbc.gridx = 0; 202 | gbc.gridy = GridBagConstraints.RELATIVE; 203 | gbc.fill = GridBagConstraints.HORIZONTAL; 204 | gbc.gridwidth = 2; 205 | gbc.weightx = 1.0; 206 | gbc.insets.top = 10; 207 | gbc.insets.bottom = 5; 208 | 209 | for (Entry entry : configurator.getProperties().entrySet()) { 210 | String label = entry.getKey(); 211 | Component ccomp = entry.getValue(); 212 | 213 | JLabel caption = new JLabel(" " + label, SwingConstants.LEADING); // add a little space for the label text 214 | caption.setFont(caption.getFont().deriveFont(Font.BOLD)); 215 | caption.setBorder(new MatteBorder(0, 0, 1, 0, Color.GRAY)); 216 | panel.add(caption, gbc.clone()); 217 | 218 | processComponent(panel, label, ccomp); 219 | } 220 | 221 | return panel; 222 | } 223 | 224 | private void processComponent(Container panel, String key, Component ccomp) { 225 | for (Field field : ccomp.getClass().getDeclaredFields()) { 226 | Annotation[] anns = field.getAnnotations(); 227 | // check only on annotated fields 228 | if (anns.length > 0) { 229 | try { 230 | boolean isAcc = field.isAccessible(); 231 | if (!isAcc) { 232 | field.setAccessible(true); 233 | } else { 234 | logger.warn("Field '{}' should be private", field); 235 | } 236 | process(panel, key, ccomp, field); 237 | if (!isAcc) { 238 | field.setAccessible(false); 239 | } 240 | } catch (IllegalArgumentException e) { 241 | logger.warn("Could not access field \"{}-{}\"", ccomp.getClass(), field.getName(), e); 242 | } 243 | } 244 | } 245 | } 246 | 247 | private void process(Container parent, String key, Component component, Field field) { 248 | GridBagConstraints gbc = new GridBagConstraints(); 249 | gbc.gridy = GridBagConstraints.RELATIVE; 250 | gbc.fill = GridBagConstraints.HORIZONTAL; 251 | 252 | JComponent comp = null; 253 | 254 | JSpinner spinner = UIBindings.processRangeAnnotation(component, field); 255 | if (spinner != null) { 256 | spinner.addChangeListener(event -> notifyObservers(key, field, spinner.getValue())); 257 | comp = spinner; 258 | } 259 | 260 | JCheckBox checkbox = UIBindings.processCheckboxAnnotation(component, field, "active"); 261 | if (checkbox != null) { 262 | checkbox.addChangeListener(event -> notifyObservers(key, field, checkbox.isSelected())); 263 | comp = checkbox; 264 | } 265 | 266 | JComboBox listCombo = UIBindings.processListAnnotation(component, field); 267 | if (listCombo != null) { 268 | listCombo.addActionListener(event -> notifyObservers(key, field, listCombo.getSelectedItem())); 269 | comp = listCombo; 270 | } 271 | 272 | JComboBox enumCombo = UIBindings.processEnumAnnotation(component, field); 273 | if (enumCombo != null) { 274 | enumCombo.addActionListener(event -> notifyObservers(key, field, enumCombo.getSelectedItem())); 275 | comp = enumCombo; 276 | } 277 | 278 | if (comp != null) { 279 | gbc.insets.left = 5; 280 | gbc.insets.right = 5; 281 | gbc.insets.bottom = 2; 282 | gbc.gridx = 0; 283 | JLabel label = new JLabel(comp.getName()); 284 | label.setToolTipText(comp.getToolTipText()); 285 | 286 | // TODO: find a better way to configure the max. width of the labels 287 | comp.setPreferredSize(new Dimension(60, 23)); 288 | label.setPreferredSize(new Dimension(100, 23)); 289 | 290 | parent.add(label, gbc.clone()); 291 | gbc.insets.left = 5; 292 | gbc.insets.right = 5; 293 | gbc.gridx = 1; 294 | parent.add(comp, gbc.clone()); 295 | } 296 | } 297 | 298 | public WorldGenerator getWorldGen() { 299 | return worldGen; 300 | } 301 | } 302 | -------------------------------------------------------------------------------- /src/main/java/org/terasology/world/viewer/core/FacetPanel.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 MovingBlocks 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.terasology.world.viewer.core; 18 | 19 | import java.awt.BorderLayout; 20 | import java.awt.CardLayout; 21 | import java.awt.GridBagConstraints; 22 | import java.awt.GridBagLayout; 23 | import java.awt.GridLayout; 24 | import java.lang.reflect.Field; 25 | import java.util.List; 26 | 27 | import javax.swing.BorderFactory; 28 | import javax.swing.DefaultListSelectionModel; 29 | import javax.swing.DropMode; 30 | import javax.swing.JCheckBox; 31 | import javax.swing.JComboBox; 32 | import javax.swing.JComponent; 33 | import javax.swing.JLabel; 34 | import javax.swing.JPanel; 35 | import javax.swing.JSpinner; 36 | import javax.swing.JTable; 37 | import javax.swing.ListSelectionModel; 38 | import javax.swing.border.EmptyBorder; 39 | import javax.swing.table.TableModel; 40 | 41 | import org.slf4j.Logger; 42 | import org.slf4j.LoggerFactory; 43 | import org.terasology.world.viewer.gui.UIBindings; 44 | import org.terasology.world.viewer.layers.FacetLayer; 45 | import org.terasology.world.viewer.layers.FacetLayerConfig; 46 | 47 | /** 48 | * The facet layer configuration panel (at the left) 49 | */ 50 | public class FacetPanel extends JPanel { 51 | 52 | private static final long serialVersionUID = -4395448394330407251L; 53 | 54 | private static final Logger logger = LoggerFactory.getLogger(FacetPanel.class); 55 | 56 | private final JPanel configPanel; 57 | 58 | private JTable facetList; 59 | 60 | public FacetPanel() { 61 | setBorder(BorderFactory.createEtchedBorder()); 62 | setLayout(new GridBagLayout()); 63 | 64 | GridBagConstraints gbc = new GridBagConstraints(); 65 | gbc.gridx = 0; 66 | gbc.gridy = 0; 67 | gbc.fill = GridBagConstraints.HORIZONTAL; 68 | 69 | facetList = new JTable(); 70 | 71 | facetList.setTransferHandler(new TableRowTransferHandler(facetList)); 72 | facetList.setDropMode(DropMode.INSERT_ROWS); 73 | facetList.setDragEnabled(true); 74 | facetList.getTableHeader().setReorderingAllowed(false); 75 | add(facetList.getTableHeader(), gbc.clone()); 76 | gbc.gridy++; 77 | add(facetList, gbc.clone()); 78 | 79 | JLabel listInfoText = new JLabel("Drag layers to change rendering order"); 80 | listInfoText.setAlignmentX(0.5f); 81 | gbc.gridy++; 82 | add(listInfoText, gbc.clone()); 83 | 84 | configPanel = new JPanel(); 85 | configPanel.setBorder(BorderFactory.createTitledBorder("Config")); 86 | gbc.gridy++; 87 | gbc.weighty = 1.0; 88 | gbc.fill = GridBagConstraints.BOTH; 89 | gbc.insets.top = 10; 90 | add(configPanel, gbc.clone()); 91 | } 92 | 93 | public void setLayers(List facets) { 94 | TableModel listModel = new FacetTableModel(facets); 95 | facetList.setModel(listModel); 96 | facetList.setSelectionModel(new DefaultListSelectionModel()); 97 | facetList.getColumnModel().getColumn(0).setMaxWidth(30); 98 | facetList.getColumnModel().getColumn(0).setResizable(false); 99 | facetList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); 100 | 101 | for (FacetLayer facetLayer : facets) { 102 | facetLayer.addObserver(layer -> facetList.repaint()); 103 | } 104 | 105 | CardLayout cardLayout = new CardLayout(); 106 | configPanel.setLayout(cardLayout); 107 | configPanel.removeAll(); 108 | for (FacetLayer layer : facets) { 109 | configPanel.add(createConfigs(layer), Integer.toString(System.identityHashCode(layer))); 110 | } 111 | 112 | facetList.getSelectionModel().addListSelectionListener(e -> { 113 | int selIdx = facetList.getSelectedRow(); 114 | if (selIdx > -1) { 115 | FacetLayer layer = facets.get(selIdx); 116 | String id = Integer.toString(System.identityHashCode(layer)); 117 | cardLayout.show(configPanel, id); 118 | } 119 | }); 120 | } 121 | 122 | protected JPanel createConfigs(FacetLayer layer) { 123 | JPanel panelWrap = new JPanel(new BorderLayout()); 124 | JPanel panel = new JPanel(); 125 | panel.setLayout(new GridLayout(0, 2)); 126 | 127 | FacetLayerConfig config = layer.getConfig(); 128 | if (config != null) { 129 | for (Field field : config.getClass().getDeclaredFields()) { 130 | 131 | if (field.getAnnotations().length > 0) { 132 | field.setAccessible(true); 133 | 134 | processAnnotations(panel, layer, field); 135 | } 136 | } 137 | } 138 | 139 | panel.setBorder(new EmptyBorder(0, 5, 0, 0)); 140 | panelWrap.add(panel, BorderLayout.NORTH); 141 | return panelWrap; 142 | } 143 | 144 | private void processAnnotations(JPanel panel, FacetLayer layer, Field field) { 145 | FacetLayerConfig config = layer.getConfig(); 146 | JComponent comp = null; 147 | 148 | JSpinner spinner = UIBindings.processRangeAnnotation(config, field); 149 | if (spinner != null) { 150 | spinner.addChangeListener(event -> { 151 | Number v = (Number) spinner.getValue(); 152 | try { 153 | if (field.getType().equals(int.class) || field.getType().equals(Integer.class)) { 154 | field.setInt(config, v.intValue()); 155 | } else { 156 | field.setFloat(config, v.floatValue()); 157 | } 158 | layer.notifyObservers(); 159 | } catch (IllegalAccessException e) { 160 | logger.warn("Could not set field '{}:{}'", layer, field, e); 161 | } 162 | }); 163 | comp = spinner; 164 | } 165 | 166 | JCheckBox checkbox = UIBindings.processCheckboxAnnotation(config, field, "visible"); 167 | if (checkbox != null) { 168 | checkbox.addChangeListener(event -> { 169 | try { 170 | field.setBoolean(config, checkbox.isSelected()); 171 | layer.notifyObservers(); 172 | } catch (IllegalAccessException e) { 173 | logger.warn("Could not set field '{}:{}'", layer, field, e); 174 | } 175 | }); 176 | comp = checkbox; 177 | } 178 | 179 | JComboBox listCombo = UIBindings.processListAnnotation(config, field); 180 | if (listCombo != null) { 181 | listCombo.addActionListener(event -> { 182 | String v = listCombo.getSelectedItem().toString(); // this should be a String already 183 | try { 184 | field.set(config, v); 185 | layer.notifyObservers(); 186 | } catch (IllegalAccessException e) { 187 | logger.warn("Could not set field '{}:{}'", layer, field, e); 188 | } 189 | }); 190 | comp = listCombo; 191 | } 192 | 193 | JComboBox enumCombo = UIBindings.processEnumAnnotation(config, field); 194 | if (enumCombo != null) { 195 | enumCombo.addActionListener(event -> { 196 | String v = enumCombo.getSelectedItem().toString(); // this should be a String already 197 | try { 198 | field.set(config, v); 199 | layer.notifyObservers(); 200 | } catch (IllegalAccessException e) { 201 | logger.warn("Could not set field '{}:{}'", layer, field, e); 202 | } 203 | }); 204 | comp = enumCombo; 205 | } 206 | 207 | if (comp != null) { 208 | JLabel label = new JLabel(comp.getName()); 209 | label.setToolTipText(comp.getToolTipText()); 210 | 211 | panel.add(label); 212 | panel.add(comp); 213 | } 214 | } 215 | } 216 | -------------------------------------------------------------------------------- /src/main/java/org/terasology/world/viewer/core/FacetTableModel.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 MovingBlocks 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.terasology.world.viewer.core; 18 | 19 | import java.util.List; 20 | 21 | import javax.swing.table.AbstractTableModel; 22 | 23 | import org.terasology.world.viewer.layers.FacetLayer; 24 | 25 | import com.google.common.collect.ImmutableList; 26 | 27 | /** 28 | * A {@link javax.swing.table.TableModel} that works on a list of {@link FacetLayer}s. 29 | */ 30 | public class FacetTableModel extends AbstractTableModel implements Reorderable { 31 | 32 | private static final long serialVersionUID = -585013620986986118L; 33 | 34 | private final List layers; 35 | 36 | private final ImmutableList columnNames = ImmutableList.of("On", "Name"); 37 | 38 | /** 39 | * A list of layers to display. It will be reordered! 40 | * @param layers the list of layers 41 | */ 42 | public FacetTableModel(List layers) { 43 | this.layers = layers; 44 | } 45 | 46 | @Override 47 | public String getColumnName(int column) { 48 | return columnNames.get(column); 49 | } 50 | 51 | @Override 52 | public int getRowCount() { 53 | return layers.size(); 54 | } 55 | 56 | @Override 57 | public int getColumnCount() { 58 | return 2; 59 | } 60 | 61 | @Override 62 | public Object getValueAt(int rowIndex, int columnIndex) { 63 | FacetLayer layer = layers.get(rowIndex); 64 | switch (columnIndex) { 65 | case 0: 66 | return Boolean.valueOf(layer.isVisible()); 67 | 68 | case 1: 69 | return layer.toString(); 70 | 71 | default: 72 | return null; 73 | } 74 | } 75 | 76 | @Override 77 | public Class getColumnClass(int column) { 78 | return getValueAt(0, column).getClass(); 79 | } 80 | 81 | @Override 82 | public void setValueAt(Object value, int rowIndex, int columnIndex) { 83 | FacetLayer layer = layers.get(rowIndex); 84 | switch (columnIndex) { 85 | case 0: 86 | layer.setVisible((Boolean) value); 87 | } 88 | } 89 | 90 | @Override 91 | public boolean isCellEditable(int rowIndex, int columnIndex) { 92 | return columnIndex == 0; 93 | } 94 | 95 | @Override 96 | public void reorder(int fromIndex, int toIndex) { 97 | 98 | FacetLayer layer = layers.get(fromIndex); 99 | 100 | layers.add(toIndex, layer); 101 | fireTableRowsInserted(toIndex, toIndex); 102 | 103 | if (toIndex < fromIndex) { 104 | layers.remove(fromIndex + 1); 105 | fireTableRowsDeleted(fromIndex + 1, fromIndex + 1); 106 | } else { 107 | layers.remove(fromIndex); 108 | fireTableRowsDeleted(fromIndex, fromIndex); 109 | } 110 | 111 | layer.notifyObservers(); 112 | } 113 | 114 | } 115 | -------------------------------------------------------------------------------- /src/main/java/org/terasology/world/viewer/core/Reorderable.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 MovingBlocks 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.terasology.world.viewer.core; 18 | 19 | /** 20 | * A "mix-in" interface to indicate support for reordering elements 21 | */ 22 | public interface Reorderable { 23 | void reorder(int fromIndex, int toIndex); 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/org/terasology/world/viewer/core/TableRowTransferHandler.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 MovingBlocks 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.terasology.world.viewer.core; 18 | 19 | import java.awt.Cursor; 20 | import java.awt.datatransfer.DataFlavor; 21 | import java.awt.datatransfer.Transferable; 22 | import java.awt.datatransfer.UnsupportedFlavorException; 23 | import java.awt.dnd.DragSource; 24 | import java.io.IOException; 25 | 26 | import javax.activation.ActivationDataFlavor; 27 | import javax.activation.DataHandler; 28 | import javax.swing.JComponent; 29 | import javax.swing.JTable; 30 | import javax.swing.TransferHandler; 31 | 32 | import org.slf4j.Logger; 33 | import org.slf4j.LoggerFactory; 34 | 35 | /** 36 | * Handles drag & drop row reordering. 37 | * Adapted from http://docs.oracle.com/javase/tutorial/uiswing/components/table.html 38 | */ 39 | public class TableRowTransferHandler extends TransferHandler { 40 | 41 | private static final long serialVersionUID = -831581058608343529L; 42 | 43 | private static final Logger logger = LoggerFactory.getLogger(TableRowTransferHandler.class); 44 | 45 | private final DataFlavor localObjectFlavor = new ActivationDataFlavor(Integer.class, DataFlavor.javaJVMLocalObjectMimeType, "Integer Row Index"); 46 | private final JTable table; 47 | 48 | public TableRowTransferHandler(JTable table) { 49 | this.table = table; 50 | } 51 | 52 | @Override 53 | protected Transferable createTransferable(JComponent c) { 54 | return new DataHandler(Integer.valueOf(table.getSelectedRow()), localObjectFlavor.getMimeType()); 55 | } 56 | 57 | @Override 58 | public boolean canImport(TransferHandler.TransferSupport info) { 59 | boolean b = info.getComponent() == table && info.isDrop() && info.isDataFlavorSupported(localObjectFlavor); 60 | table.setCursor(b ? DragSource.DefaultMoveDrop : DragSource.DefaultMoveNoDrop); 61 | return b; 62 | } 63 | 64 | @Override 65 | public int getSourceActions(JComponent c) { 66 | return TransferHandler.COPY_OR_MOVE; 67 | } 68 | 69 | @Override 70 | public boolean importData(TransferHandler.TransferSupport info) { 71 | JTable target = (JTable) info.getComponent(); 72 | JTable.DropLocation dl = (JTable.DropLocation) info.getDropLocation(); 73 | int index = dl.getRow(); 74 | int max = table.getModel().getRowCount(); 75 | if (index < 0 || index >= max) { 76 | index = max; 77 | } 78 | target.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); 79 | try { 80 | Integer rowFrom = (Integer) info.getTransferable().getTransferData(localObjectFlavor); 81 | if (rowFrom != -1 && rowFrom != index && rowFrom != index - 1) { 82 | ((Reorderable) table.getModel()).reorder(rowFrom, index); 83 | if (index > rowFrom) { 84 | index--; 85 | } 86 | target.getSelectionModel().addSelectionInterval(index, index); 87 | return true; 88 | } 89 | } catch (IOException | UnsupportedFlavorException e) { 90 | logger.warn("Could not get transfer data", e); 91 | } 92 | 93 | return false; 94 | } 95 | 96 | @Override 97 | protected void exportDone(JComponent c, Transferable t, int act) { 98 | table.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); 99 | } 100 | 101 | } 102 | -------------------------------------------------------------------------------- /src/main/java/org/terasology/world/viewer/core/TileThreadFactory.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 MovingBlocks 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.terasology.world.viewer.core; 18 | 19 | import java.util.concurrent.ThreadFactory; 20 | import java.util.concurrent.atomic.AtomicInteger; 21 | 22 | /** 23 | * Creates daemon threads with low thread priority. 24 | */ 25 | class TileThreadFactory implements ThreadFactory { 26 | 27 | private final AtomicInteger threadNumber = new AtomicInteger(1); 28 | private final String namePrefix = "TileThreadPool-thread-"; 29 | 30 | @Override 31 | public Thread newThread(Runnable r) { 32 | Thread t = new Thread(r, namePrefix + threadNumber.getAndIncrement()); 33 | t.setDaemon(true); 34 | t.setPriority(Thread.MIN_PRIORITY); 35 | return t; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/org/terasology/world/viewer/core/Viewer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 MovingBlocks 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.terasology.world.viewer.core; 18 | 19 | import java.awt.Color; 20 | import java.awt.Cursor; 21 | import java.awt.Font; 22 | import java.awt.FontMetrics; 23 | import java.awt.Graphics; 24 | import java.awt.Graphics2D; 25 | import java.awt.Point; 26 | import java.awt.RenderingHints; 27 | import java.awt.event.KeyAdapter; 28 | import java.awt.event.MouseAdapter; 29 | import java.awt.geom.AffineTransform; 30 | import java.awt.image.BufferedImage; 31 | import java.awt.image.DataBufferInt; 32 | import java.awt.image.DirectColorModel; 33 | import java.awt.image.Raster; 34 | import java.awt.image.WritableRaster; 35 | import java.math.RoundingMode; 36 | import java.util.ArrayList; 37 | import java.util.Collection; 38 | import java.util.Collections; 39 | import java.util.Deque; 40 | import java.util.HashSet; 41 | import java.util.List; 42 | import java.util.Set; 43 | import java.util.concurrent.BlockingQueue; 44 | import java.util.concurrent.Callable; 45 | import java.util.concurrent.ConcurrentHashMap; 46 | import java.util.concurrent.ExecutionException; 47 | import java.util.concurrent.Future; 48 | import java.util.concurrent.FutureTask; 49 | import java.util.concurrent.LinkedBlockingQueue; 50 | import java.util.concurrent.RunnableFuture; 51 | import java.util.concurrent.ThreadPoolExecutor; 52 | import java.util.concurrent.TimeUnit; 53 | 54 | import javax.swing.JComponent; 55 | 56 | import org.slf4j.Logger; 57 | import org.slf4j.LoggerFactory; 58 | import org.terasology.math.Region3i; 59 | import org.terasology.math.TeraMath; 60 | import org.terasology.math.geom.BaseVector2i; 61 | import org.terasology.math.geom.ImmutableVector2i; 62 | import org.terasology.math.geom.Rect2i; 63 | import org.terasology.math.geom.Vector2i; 64 | import org.terasology.math.geom.Vector3i; 65 | import org.terasology.rendering.nui.HorizontalAlign; 66 | import org.terasology.world.chunks.ChunkConstants; 67 | import org.terasology.world.generation.Region; 68 | import org.terasology.world.generation.World; 69 | import org.terasology.world.generator.WorldGenerator; 70 | import org.terasology.world.viewer.ThreadSafeRegion; 71 | import org.terasology.world.viewer.camera.Camera; 72 | import org.terasology.world.viewer.camera.CameraKeyController; 73 | import org.terasology.world.viewer.camera.CameraMouseController; 74 | import org.terasology.world.viewer.camera.RepaintingCameraListener; 75 | import org.terasology.world.viewer.color.ColorModels; 76 | import org.terasology.world.viewer.config.ViewConfig; 77 | import org.terasology.world.viewer.gui.CursorPositionListener; 78 | import org.terasology.world.viewer.gui.RepaintingMouseListener; 79 | import org.terasology.world.viewer.layers.FacetLayer; 80 | import org.terasology.world.viewer.overlay.GridOverlay; 81 | import org.terasology.world.viewer.overlay.Overlay; 82 | import org.terasology.world.viewer.overlay.PixelOverlay; 83 | import org.terasology.world.viewer.overlay.ScreenOverlay; 84 | import org.terasology.world.viewer.overlay.TextOverlay; 85 | import org.terasology.world.viewer.overlay.TooltipOverlay; 86 | import org.terasology.world.viewer.overlay.WorldOverlay; 87 | 88 | import com.google.common.base.Stopwatch; 89 | import com.google.common.cache.CacheBuilder; 90 | import com.google.common.cache.CacheLoader; 91 | import com.google.common.cache.LoadingCache; 92 | import com.google.common.collect.Lists; 93 | import com.google.common.collect.Sets; 94 | import com.google.common.math.IntMath; 95 | 96 | /** 97 | * The main viewer component 98 | */ 99 | public final class Viewer extends JComponent { 100 | 101 | private static final Logger logger = LoggerFactory.getLogger(Viewer.class); 102 | 103 | private static final int TILE_SIZE_X = ChunkConstants.SIZE_X * 4; 104 | private static final int TILE_SIZE_Y = ChunkConstants.SIZE_Z * 4; 105 | 106 | private static final long serialVersionUID = 4178713176841691478L; 107 | 108 | private final BufferedImage dummyImg; 109 | private final BufferedImage failedImg; 110 | 111 | /** 112 | * Contains both queued tasks and those that are in progress. 113 | */ 114 | private final Collection> taskList; 115 | private final ThreadPoolExecutor threadPool; 116 | 117 | private final LoadingCache regionCache; 118 | private final LoadingCache imageCache; 119 | 120 | private final Camera camera = new Camera(); 121 | 122 | private final CursorPositionListener curPosListener; 123 | 124 | private final ViewConfig viewConfig; 125 | 126 | private final Deque worldOverlays = Lists.newLinkedList(); 127 | private final Deque screenOverlays = Lists.newLinkedList(); 128 | 129 | private WorldGenerator worldGen; 130 | private List facetLayers; 131 | 132 | /** 133 | * @param viewConfig the view config 134 | * @param cacheSize maximum number of cached tiles 135 | */ 136 | public Viewer(ViewConfig viewConfig, int cacheSize) { 137 | this.viewConfig = viewConfig; 138 | 139 | int minThreads = Runtime.getRuntime().availableProcessors() * 2; 140 | int maxThreads = Runtime.getRuntime().availableProcessors() * 2; 141 | 142 | BlockingQueue queue = new LinkedBlockingQueue(); // unlimited queue size 143 | TileThreadFactory threadFactory = new TileThreadFactory(); 144 | threadPool = new ThreadPoolExecutor(minThreads, maxThreads, 60, TimeUnit.SECONDS, queue, threadFactory); 145 | taskList = Sets.newSetFromMap(new ConcurrentHashMap<>(cacheSize)); // estimated size 146 | 147 | CacheLoader regionLoader = new CacheLoader() { 148 | 149 | @Override 150 | public Region load(ImmutableVector2i tilePos) { 151 | Region region = createRegion(tilePos); 152 | return region; 153 | } 154 | }; 155 | 156 | CacheLoader imageLoader = new CacheLoader() { 157 | 158 | @Override 159 | public BufferedImage load(ImmutableVector2i pos) throws Exception { 160 | enqueueTile(pos); 161 | return dummyImg; 162 | } 163 | }; 164 | 165 | regionCache = CacheBuilder.newBuilder().maximumSize(cacheSize).build(regionLoader); 166 | imageCache = CacheBuilder.newBuilder().maximumSize(cacheSize).build(imageLoader); 167 | 168 | Vector2i camPos = viewConfig.getCamPos(); 169 | camera.translate(camPos.getX(), camPos.getY()); 170 | camera.setZoom(viewConfig.getZoomFactor()); 171 | camera.addListener(new RepaintingCameraListener(this)); 172 | 173 | worldOverlays.addLast(new GridOverlay(TILE_SIZE_X, TILE_SIZE_Y)); 174 | worldOverlays.addLast(new PixelOverlay(10)); 175 | 176 | TextOverlay zoomOverlay = new TextOverlay(() -> String.format("Zoom: %3d%%", (int) (camera.getZoom() * 100))); 177 | zoomOverlay.setHorizontalAlign(HorizontalAlign.RIGHT); 178 | zoomOverlay.setMargins(5, 5, 5, 5); 179 | zoomOverlay.setInsets(8, 5, 5, 5); 180 | zoomOverlay.setFont(new Font("Dialog", Font.BOLD, 15)); 181 | zoomOverlay.setFrame(new Color(192, 192, 192, 128)); 182 | zoomOverlay.setBackground(new Color(92, 92, 92, 160)); 183 | zoomOverlay.setVisible(false); 184 | camera.addListener(new ZoomOverlayUpdater(this, zoomOverlay)); 185 | screenOverlays.add(zoomOverlay); 186 | ScreenOverlay tooltipOverlay = new TooltipOverlay(screen -> { 187 | Rect2i visWorld = camera.getVisibleArea(getWidth(), getHeight()); 188 | return getTooltip(toWorld(visWorld, screen)); 189 | }); 190 | screenOverlays.add(tooltipOverlay); 191 | 192 | setCursor(Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR)); 193 | 194 | // add camera controls 195 | KeyAdapter keyCameraController = new CameraKeyController(camera); 196 | MouseAdapter mouseCameraController = new CameraMouseController(camera); 197 | addKeyListener(keyCameraController); 198 | addMouseListener(mouseCameraController); 199 | addMouseMotionListener(mouseCameraController); 200 | addMouseWheelListener(mouseCameraController); 201 | 202 | // add tooltip mouse listeners 203 | curPosListener = new CursorPositionListener(); 204 | addMouseMotionListener(curPosListener); 205 | addMouseListener(curPosListener); 206 | 207 | // TODO: the listener should be attached to the cursor position Point 208 | MouseAdapter repaintListener = new RepaintingMouseListener(this); 209 | addMouseListener(repaintListener); 210 | addMouseMotionListener(repaintListener); 211 | 212 | dummyImg = createStaticImage(TILE_SIZE_X, TILE_SIZE_Y, null); 213 | failedImg = createStaticImage(TILE_SIZE_X, TILE_SIZE_Y, "FAILED"); 214 | } 215 | 216 | private static BufferedImage createStaticImage(int width, int height, String text) { 217 | 218 | BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); 219 | Graphics2D g = image.createGraphics(); 220 | if (text != null) { 221 | g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON); 222 | g.setFont(g.getFont().deriveFont(Font.BOLD, 20f)); 223 | FontMetrics fm = g.getFontMetrics(); 224 | int ty = height / 2 - (fm.getHeight() / 2 - fm.getAscent()); 225 | int tx = width / 2 - fm.stringWidth(text) / 2; 226 | g.drawString(text, tx, ty); 227 | } 228 | g.setColor(Color.GRAY); 229 | g.drawRect(0, 0, width - 1, height - 1); 230 | g.dispose(); 231 | return image; 232 | } 233 | 234 | /** 235 | * @return the number of tiles that is currently waiting for being processed 236 | */ 237 | public int getPendingTiles() { 238 | return taskList.size(); 239 | } 240 | 241 | /** 242 | * @return the number of tile images in the cache 243 | */ 244 | public int getCachedTiles() { 245 | return (int) imageCache.size(); 246 | } 247 | 248 | public Camera getCamera() { 249 | return camera; 250 | } 251 | 252 | @Override 253 | public boolean isFocusable() { 254 | return true; 255 | } 256 | 257 | @Override 258 | public void paint(Graphics g1) { 259 | Graphics2D g = (Graphics2D) g1; 260 | AffineTransform orgTrans = g.getTransform(); 261 | 262 | Rect2i visWorld = camera.getVisibleArea(getWidth(), getHeight()); 263 | Rect2i visTiles = worldToTileArea(visWorld); 264 | 265 | g.scale(camera.getZoom(), camera.getZoom()); 266 | g.translate(-visWorld.minX(), -visWorld.minY()); 267 | 268 | drawTiles(g, visTiles); 269 | 270 | Point curPos = curPosListener.getCursorPosition(); 271 | 272 | ImmutableVector2i screenCursor = null; 273 | ImmutableVector2i worldCursor = null; 274 | if (curPos != null) { 275 | screenCursor = new ImmutableVector2i(curPos.x, curPos.y); 276 | worldCursor = toWorld(visWorld, screenCursor); 277 | } 278 | 279 | // draw world overlays 280 | for (Overlay ovly : worldOverlays) { 281 | if (ovly.isVisible()) { 282 | ovly.render(g, visWorld, worldCursor); 283 | } 284 | } 285 | 286 | g.setTransform(orgTrans); 287 | 288 | // draw screen overlays 289 | Rect2i windowRect = Rect2i.createFromMinAndSize(0, 0, getWidth(), getHeight()); 290 | for (Overlay ovly : screenOverlays) { 291 | if (ovly.isVisible()) { 292 | ovly.render(g, windowRect, screenCursor); 293 | } 294 | } 295 | } 296 | 297 | private ImmutableVector2i toWorld(Rect2i visWorld, BaseVector2i screen) { 298 | int wx = visWorld.minX() + TeraMath.floorToInt(screen.getX() / camera.getZoom()); 299 | int wy = visWorld.minY() + TeraMath.floorToInt(screen.getY() / camera.getZoom()); 300 | return new ImmutableVector2i(wx, wy); 301 | } 302 | 303 | /** 304 | * @param wg the world generator to use 305 | * @param newLayers the facet config 306 | */ 307 | public void setWorldGen(WorldGenerator wg, List newLayers) { 308 | this.worldGen = wg; 309 | this.facetLayers = newLayers; 310 | 311 | // clear tile cache and repaint if any of the facet configs has changed 312 | for (FacetLayer layer : newLayers) { 313 | layer.addObserver(l -> updateImageCache()); 314 | } 315 | 316 | invalidateWorld(); 317 | } 318 | 319 | public void invalidateWorld() { 320 | worldGen.initialize(); 321 | 322 | regionCache.invalidateAll(); 323 | updateImageCache(); 324 | } 325 | 326 | public void close() { 327 | int cx = (int) camera.getPos().getX(); 328 | int cy = (int) camera.getPos().getY(); 329 | 330 | viewConfig.setCamPos(new Vector2i(cx, cy)); 331 | viewConfig.setZoomFactor(camera.getZoom()); 332 | 333 | threadPool.shutdownNow(); 334 | } 335 | 336 | private static Rect2i worldToTileArea(Rect2i area) { 337 | int chunkMinX = IntMath.divide(area.minX(), TILE_SIZE_X, RoundingMode.FLOOR); 338 | int chunkMinZ = IntMath.divide(area.minY(), TILE_SIZE_Y, RoundingMode.FLOOR); 339 | 340 | int chunkMaxX = IntMath.divide(area.maxX(), TILE_SIZE_X, RoundingMode.FLOOR); 341 | int chunkMaxZ = IntMath.divide(area.maxY(), TILE_SIZE_Y, RoundingMode.FLOOR); 342 | 343 | return Rect2i.createFromMinAndMax(chunkMinX, chunkMinZ, chunkMaxX, chunkMaxZ); 344 | } 345 | 346 | private void drawTiles(Graphics2D g, Rect2i visChunks) { 347 | 348 | Object hint; 349 | 350 | if (camera.getZoom() < 1) { 351 | // Render with bi-linear interpolation when zoomed out 352 | hint = RenderingHints.VALUE_INTERPOLATION_BILINEAR; 353 | } else { 354 | // Render with nearest neighbor interpolation when zoomed in (or at 100%) 355 | hint = RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR; 356 | } 357 | g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, hint); 358 | 359 | for (int z = visChunks.minY(); z <= visChunks.maxY(); z++) { 360 | for (int x = visChunks.minX(); x <= visChunks.maxX(); x++) { 361 | ImmutableVector2i pos = new ImmutableVector2i(x, z); 362 | BufferedImage image = imageCache.getUnchecked(pos); 363 | g.drawImage(image, x * TILE_SIZE_X, z * TILE_SIZE_Y, null); 364 | } 365 | } 366 | 367 | g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR); 368 | } 369 | 370 | private Region getRegion(BaseVector2i pos) { 371 | 372 | int tileX = IntMath.divide(pos.getX(), TILE_SIZE_X, RoundingMode.FLOOR); 373 | int tileY = IntMath.divide(pos.getY(), TILE_SIZE_Y, RoundingMode.FLOOR); 374 | 375 | ImmutableVector2i tilePos = new ImmutableVector2i(tileX, tileY); 376 | Region region = regionCache.getUnchecked(tilePos); 377 | return region; 378 | } 379 | 380 | private String getTooltip(BaseVector2i world) { 381 | Region region = getRegion(world); 382 | 383 | StringBuffer sb = new StringBuffer(); 384 | for (FacetLayer layer : facetLayers) { 385 | if (layer.isVisible()) { 386 | try { 387 | String layerText = layer.getWorldText(region, world.getX(), world.getY()); 388 | if (layerText != null) { 389 | sb.append("\n").append(layerText); 390 | } 391 | } catch (Exception e) { 392 | sb.append("\n"); 393 | } 394 | } 395 | } 396 | 397 | String tooltip = String.format("%d / %d%s", world.getX(), world.getY(), sb.toString()); 398 | return tooltip; 399 | } 400 | 401 | private Region createRegion(ImmutableVector2i chunkPos) { 402 | 403 | int vertChunks = 4; // 4 chunks high (relevant for trees, etc) 404 | 405 | int minX = chunkPos.getX() * TILE_SIZE_X; 406 | int minZ = chunkPos.getY() * TILE_SIZE_Y; 407 | int height = vertChunks * ChunkConstants.SIZE_Y; 408 | Region3i area3d = Region3i.createFromMinAndSize(new Vector3i(minX, 0, minZ), new Vector3i(TILE_SIZE_X, height, TILE_SIZE_Y)); 409 | World world = worldGen.getWorld(); 410 | 411 | // The region needs to be thread-safe, since the rendering of the tooltip 412 | // might access Region.getFacet() at the same time as a thread from the thread pool 413 | // that uses it to render to a BufferedImage. 414 | // This is often irrelevant, but composed facets such as Perlin's surface height facet, 415 | // which consists of the ground layer plus hills and mountains plus rivers 416 | // the method could return a partly created facet if accessed in parallel. 417 | Region region = new ThreadSafeRegion(world.getWorldData(area3d)); 418 | 419 | return region; 420 | } 421 | 422 | /** 423 | * Called whenever a facet layer configuration changes 424 | */ 425 | private void updateImageCache() { 426 | for (Future task : taskList) { 427 | task.cancel(true); 428 | } 429 | 430 | Set cachedTiles = imageCache.asMap().keySet(); 431 | Set oldTiles = new HashSet<>(cachedTiles); 432 | 433 | Rect2i visWorld = camera.getVisibleArea(getWidth(), getHeight()); 434 | Rect2i visTileArea = worldToTileArea(visWorld); 435 | 436 | List visTiles = new ArrayList<>(visTileArea.area()); 437 | 438 | // the iterator vector cannot be used for permanent storage - it must be copied 439 | for (BaseVector2i pos : visTileArea.contents()) { 440 | visTiles.add(new ImmutableVector2i(pos)); 441 | } 442 | 443 | // shuffle the order of new tasks 444 | // If the queue is cleared repeatedly before all tasks are run 445 | // some tiles will updated only in the last iteration 446 | // Plus, it looks nicer :-) 447 | Collections.shuffle(visTiles); 448 | 449 | for (ImmutableVector2i tile : visTiles) { 450 | oldTiles.remove(tile); 451 | enqueueTile(tile); 452 | } 453 | 454 | imageCache.invalidateAll(oldTiles); 455 | } 456 | 457 | private void enqueueTile(ImmutableVector2i pos) { 458 | RunnableFuture task = new FutureTask(new UpdateImageCache(pos)) { 459 | 460 | @Override 461 | protected void done() { 462 | if (!isCancelled()) { 463 | BufferedImage result; 464 | try { 465 | result = get(); 466 | } catch (ExecutionException | InterruptedException e) { 467 | logger.error("Could not rasterize tile {}", pos, e); 468 | result = failedImg; 469 | } 470 | imageCache.put(pos, result); 471 | repaint(); 472 | } 473 | taskList.remove(this); 474 | } 475 | }; 476 | taskList.add(task); 477 | threadPool.execute(task); 478 | } 479 | 480 | /** 481 | * Note: this method must be thread-safe! 482 | * @param region the thread-safe region 483 | * @return an image of that region 484 | */ 485 | BufferedImage rasterize(Region region) { 486 | 487 | Vector3i extent = region.getRegion().size(); 488 | int width = extent.x; 489 | int height = extent.z; 490 | 491 | DirectColorModel colorModel = ColorModels.ARGB; 492 | 493 | int[] masks = colorModel.getMasks(); 494 | DataBufferInt imageBuffer = new DataBufferInt(width * height); 495 | WritableRaster raster = Raster.createPackedRaster(imageBuffer, width, height, width, masks, null); 496 | BufferedImage image = new BufferedImage(colorModel, raster, false, null); 497 | 498 | Graphics2D g = image.createGraphics(); 499 | g.setColor(Color.BLACK); 500 | g.fillRect(0, 0, width, height); 501 | 502 | try { 503 | Stopwatch sw = Stopwatch.createStarted(); 504 | 505 | for (FacetLayer layer : facetLayers) { 506 | if (layer.isVisible()) { 507 | layer.render(image, region); 508 | } 509 | } 510 | 511 | if (logger.isTraceEnabled()) { 512 | logger.trace("Rendered region in {}ms.", sw.elapsed(TimeUnit.MILLISECONDS)); 513 | } 514 | } finally { 515 | g.dispose(); 516 | } 517 | 518 | return image; 519 | } 520 | 521 | private class UpdateImageCache implements Callable { 522 | 523 | private final ImmutableVector2i pos; 524 | 525 | public UpdateImageCache(ImmutableVector2i pos) { 526 | this.pos = pos; 527 | } 528 | 529 | @Override 530 | public BufferedImage call() { 531 | Region region = regionCache.getUnchecked(pos); 532 | BufferedImage image; 533 | image = rasterize(region); 534 | return image; 535 | } 536 | } 537 | } 538 | -------------------------------------------------------------------------------- /src/main/java/org/terasology/world/viewer/core/ZoomOverlayUpdater.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 MovingBlocks 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.terasology.world.viewer.core; 18 | 19 | import java.awt.Component; 20 | 21 | import javax.swing.Timer; 22 | 23 | import org.terasology.world.viewer.overlay.TextOverlay; 24 | import org.terasology.world.viewer.camera.CameraListener; 25 | 26 | 27 | /** 28 | */ 29 | public class ZoomOverlayUpdater implements CameraListener { 30 | 31 | private final TextOverlay overlay; 32 | private final Timer timer; 33 | private final Component comp; 34 | 35 | /** 36 | * Uses 1000 milliSec visible time 37 | * @param comp the parent component (needed to send repaint events) 38 | * @param zoomOverlay the overlay 39 | */ 40 | public ZoomOverlayUpdater(Component comp, TextOverlay zoomOverlay) { 41 | this(comp, zoomOverlay, 1000); 42 | } 43 | 44 | /** 45 | * @param comp the parent component (needed to send repaint events) 46 | * @param zoomOverlay the overlay 47 | * @param visTime the time in millisecs the overlay is visible for 48 | */ 49 | public ZoomOverlayUpdater(Component comp, TextOverlay zoomOverlay, int visTime) { 50 | this.comp = comp; 51 | this.overlay = zoomOverlay; 52 | timer = new Timer(visTime, e -> { 53 | overlay.setVisible(false); 54 | comp.repaint(); 55 | }); 56 | timer.setRepeats(false); 57 | } 58 | 59 | @Override 60 | public void onPosChange() { 61 | // ignore 62 | } 63 | 64 | @Override 65 | public void onZoomChange() { 66 | overlay.setVisible(true); 67 | comp.repaint(); 68 | timer.restart(); 69 | } 70 | 71 | } 72 | -------------------------------------------------------------------------------- /src/main/java/org/terasology/world/viewer/env/DummyPermissionProviderFactory.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 MovingBlocks 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"){ } 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.terasology.world.viewer.env; 18 | 19 | import java.security.Permission; 20 | 21 | import org.terasology.module.Module; 22 | import org.terasology.module.sandbox.PermissionProvider; 23 | import org.terasology.module.sandbox.PermissionProviderFactory; 24 | 25 | public class DummyPermissionProviderFactory implements PermissionProviderFactory { 26 | 27 | @Override 28 | public PermissionProvider createPermissionProviderFor(Module module) { 29 | return new PermissionProvider() { 30 | 31 | @Override 32 | public boolean isPermitted(Permission permission, Class context) { 33 | return true; 34 | } 35 | 36 | @Override 37 | public boolean isPermitted(Class type) { 38 | return true; 39 | } 40 | }; 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/org/terasology/world/viewer/env/TinyEnvironment.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 MovingBlocks 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"){ } 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.terasology.world.viewer.env; 18 | 19 | import java.io.File; 20 | import java.io.IOException; 21 | import java.util.Collections; 22 | import java.util.HashSet; 23 | import java.util.List; 24 | import java.util.Set; 25 | 26 | import org.mockito.Matchers; 27 | import org.mockito.Mockito; 28 | import org.slf4j.Logger; 29 | import org.slf4j.LoggerFactory; 30 | import org.terasology.assets.AssetFactory; 31 | import org.terasology.assets.management.AssetManager; 32 | import org.terasology.assets.module.ModuleAwareAssetTypeManager; 33 | import org.terasology.audio.StaticSound; 34 | import org.terasology.audio.StaticSoundData; 35 | import org.terasology.audio.StreamingSound; 36 | import org.terasology.audio.StreamingSoundData; 37 | import org.terasology.audio.nullAudio.NullSound; 38 | import org.terasology.audio.nullAudio.NullStreamingSound; 39 | import org.terasology.config.Config; 40 | import org.terasology.context.Context; 41 | import org.terasology.context.internal.ContextImpl; 42 | import org.terasology.engine.module.ModuleManager; 43 | import org.terasology.engine.subsystem.headless.assets.HeadlessTexture; 44 | import org.terasology.entitySystem.entity.EntityManager; 45 | import org.terasology.entitySystem.entity.EntityRef; 46 | import org.terasology.entitySystem.entity.internal.PojoEntityManager; 47 | import org.terasology.entitySystem.prefab.Prefab; 48 | import org.terasology.entitySystem.prefab.PrefabData; 49 | import org.terasology.entitySystem.prefab.internal.PojoPrefab; 50 | import org.terasology.module.Module; 51 | import org.terasology.module.ModuleEnvironment; 52 | import org.terasology.registry.CoreRegistry; 53 | import org.terasology.rendering.assets.texture.PNGTextureFormat; 54 | import org.terasology.rendering.assets.texture.Texture; 55 | import org.terasology.rendering.assets.texture.TextureData; 56 | import org.terasology.splash.SplashScreen; 57 | import org.terasology.world.block.Block; 58 | import org.terasology.world.block.BlockManager; 59 | import org.terasology.world.block.BlockUri; 60 | import org.terasology.world.block.family.AttachedToSurfaceFamilyFactory; 61 | import org.terasology.world.block.family.BlockFamily; 62 | import org.terasology.world.block.family.DefaultBlockFamilyFactoryRegistry; 63 | import org.terasology.world.block.family.HorizontalBlockFamilyFactory; 64 | import org.terasology.world.block.family.SymmetricFamily; 65 | import org.terasology.world.block.loader.BlockFamilyDefinition; 66 | import org.terasology.world.block.loader.BlockFamilyDefinitionData; 67 | import org.terasology.world.block.loader.BlockFamilyDefinitionFormat; 68 | import org.terasology.world.block.shapes.BlockShape; 69 | import org.terasology.world.block.shapes.BlockShapeData; 70 | import org.terasology.world.block.shapes.BlockShapeImpl; 71 | import org.terasology.world.block.sounds.BlockSounds; 72 | import org.terasology.world.block.sounds.BlockSoundsData; 73 | import org.terasology.world.generator.internal.WorldGeneratorManager; 74 | import org.terasology.world.generator.plugin.WorldGeneratorPlugin; 75 | import org.terasology.world.generator.plugin.WorldGeneratorPluginLibrary; 76 | 77 | /** 78 | * Setup a tiny Terasology environment 79 | */ 80 | public final class TinyEnvironment { 81 | 82 | private static final Logger logger = LoggerFactory.getLogger(TinyEnvironment.class); 83 | 84 | private TinyEnvironment() { 85 | // empty 86 | } 87 | 88 | /** 89 | * Default setup order 90 | * @param splashScreen the splash screen 91 | * @return the generated context that refers to all created systems 92 | * @throws IOException if the engine could not be loaded 93 | */ 94 | public static Context createContext(SplashScreen splashScreen) throws IOException { 95 | 96 | Context context = new ContextImpl(); 97 | CoreRegistry.setContext(context); 98 | 99 | splashScreen.post("Loading config .."); 100 | setupConfig(); 101 | 102 | splashScreen.post("Loading module manager .."); 103 | setupModuleManager(); 104 | 105 | splashScreen.post("Loading asset manager .."); 106 | setupAssetManager(context); 107 | 108 | splashScreen.post("Loading block manager .."); 109 | setupBlockManager(); 110 | 111 | splashScreen.post("Loading world generators .."); 112 | setupWorldGen(context); 113 | 114 | splashScreen.post("Loading entity manager .."); 115 | // Entity Manager 116 | PojoEntityManager entityManager = new PojoEntityManager(); 117 | CoreRegistry.put(EntityManager.class, entityManager); 118 | 119 | return context; 120 | } 121 | 122 | private static void setupModuleManager() throws IOException { 123 | TinyModuleManager modMan = new TinyModuleManager(); 124 | CoreRegistry.put(ModuleManager.class, modMan); 125 | CoreRegistry.put(TinyModuleManager.class, modMan); 126 | } 127 | 128 | private static void setupConfig() { 129 | Config config = new Config(); 130 | CoreRegistry.put(Config.class, config); 131 | } 132 | 133 | private static void setupAssetManager(Context context) { 134 | ModuleAwareAssetTypeManager assetTypeManager = new ModuleAwareAssetTypeManager(); 135 | 136 | assetTypeManager.registerCoreAssetType(Prefab.class, 137 | (AssetFactory) PojoPrefab::new, false, "prefabs"); 138 | assetTypeManager.registerCoreAssetType(BlockShape.class, 139 | (AssetFactory) BlockShapeImpl::new, "shapes"); 140 | assetTypeManager.registerCoreAssetType(BlockSounds.class, 141 | (AssetFactory) BlockSounds::new, "blockSounds"); 142 | assetTypeManager.registerCoreAssetType(Texture.class, 143 | (AssetFactory) HeadlessTexture::new, "textures", "fonts"); 144 | assetTypeManager.registerCoreAssetType(BlockFamilyDefinition.class, 145 | (AssetFactory) BlockFamilyDefinition::new, "blocks"); 146 | 147 | assetTypeManager.registerCoreAssetType(StaticSound.class, 148 | (AssetFactory) NullSound::new, "sounds"); 149 | assetTypeManager.registerCoreAssetType(StreamingSound.class, 150 | (AssetFactory) NullStreamingSound::new, "music"); 151 | 152 | DefaultBlockFamilyFactoryRegistry blockFamilyFactoryRegistry = new DefaultBlockFamilyFactoryRegistry(); 153 | blockFamilyFactoryRegistry.setBlockFamilyFactory("horizontal", new HorizontalBlockFamilyFactory()); 154 | blockFamilyFactoryRegistry.setBlockFamilyFactory("alignToSurface", new AttachedToSurfaceFamilyFactory()); 155 | assetTypeManager.registerCoreFormat(BlockFamilyDefinition.class, new BlockFamilyDefinitionFormat(assetTypeManager.getAssetManager(), blockFamilyFactoryRegistry)); 156 | 157 | assetTypeManager.registerCoreAssetType(Texture.class, 158 | (AssetFactory) HeadlessTexture::new, "textures", "fonts"); 159 | assetTypeManager.registerCoreFormat(Texture.class, new PNGTextureFormat(Texture.FilterMode.NEAREST, 160 | path -> path.getName(2).toString().equals("textures"))); 161 | assetTypeManager.registerCoreFormat(Texture.class, new PNGTextureFormat(Texture.FilterMode.LINEAR, 162 | path -> path.getName(2).toString().equals("fonts"))); 163 | 164 | assetTypeManager.switchEnvironment(context.get(ModuleManager.class).getEnvironment()); 165 | 166 | context.put(ModuleAwareAssetTypeManager.class, assetTypeManager); 167 | context.put(AssetManager.class, assetTypeManager.getAssetManager()); 168 | } 169 | 170 | public static void addModules(List jars) { 171 | TinyModuleManager moduleManager = CoreRegistry.get(TinyModuleManager.class); 172 | ModuleEnvironment oldEnv = moduleManager.getEnvironment(); 173 | 174 | List existingMods = oldEnv.getModulesOrderedByDependencies(); 175 | 176 | Set mods = new HashSet<>(existingMods); 177 | for (File file : jars) { 178 | try { 179 | Module mod = moduleManager.load(file.toPath()); 180 | mods.add(mod); 181 | } catch (IOException e) { 182 | logger.error("Failed to load a module from {}", file); 183 | } 184 | } 185 | 186 | // TODO: merge with #setupAssetManager() 187 | ModuleEnvironment newEnv = moduleManager.loadEnvironment(mods, true); 188 | ModuleAwareAssetTypeManager assetTypeManager = CoreRegistry.get(ModuleAwareAssetTypeManager.class); 189 | assetTypeManager.switchEnvironment(newEnv); 190 | 191 | CoreRegistry.get(WorldGeneratorManager.class).refresh(); 192 | } 193 | 194 | private static void setupBlockManager() { 195 | BlockManager blockManager = Mockito.mock(BlockManager.class); 196 | Block air = new Block(); 197 | air.setTranslucent(true); 198 | air.setTargetable(false); 199 | air.setPenetrable(true); 200 | air.setReplacementAllowed(true); 201 | air.setShadowCasting(false); 202 | air.setAttachmentAllowed(false); 203 | air.setHardness(0); 204 | air.setId((short) 0); 205 | air.setDisplayName("Air"); 206 | air.setUri(BlockManager.AIR_ID); 207 | 208 | BlockFamily airFamily = new SymmetricFamily(BlockManager.AIR_ID, air); 209 | 210 | Mockito.when(blockManager.getBlock(Matchers.any())).thenReturn(air); 211 | Mockito.when(blockManager.getBlock(Matchers.any())).thenReturn(air); 212 | Mockito.when(blockManager.getBlockFamily(Matchers.any())).thenReturn(airFamily); 213 | 214 | CoreRegistry.put(BlockManager.class, blockManager); 215 | } 216 | 217 | private static void setupWorldGen(Context context) { 218 | CoreRegistry.put(WorldGeneratorManager.class, new WorldGeneratorManager(context)); 219 | CoreRegistry.put(WorldGeneratorPluginLibrary.class, new WorldGeneratorPluginLibrary() { 220 | 221 | @Override 222 | public List instantiateAllOfType(Class ofType) { 223 | return Collections.emptyList(); 224 | } 225 | }); 226 | } 227 | } 228 | -------------------------------------------------------------------------------- /src/main/java/org/terasology/world/viewer/env/TinyModuleManager.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 MovingBlocks 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"){ } 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.terasology.world.viewer.env; 18 | 19 | import java.io.File; 20 | import java.io.IOException; 21 | import java.io.InputStream; 22 | import java.io.InputStreamReader; 23 | import java.io.Reader; 24 | import java.net.URISyntaxException; 25 | import java.net.URL; 26 | import java.nio.file.Files; 27 | import java.nio.file.InvalidPathException; 28 | import java.nio.file.LinkOption; 29 | import java.nio.file.Path; 30 | import java.nio.file.Paths; 31 | import java.util.Arrays; 32 | import java.util.Collection; 33 | import java.util.Collections; 34 | import java.util.LinkedHashSet; 35 | import java.util.List; 36 | import java.util.Set; 37 | import java.util.jar.Attributes; 38 | import java.util.jar.Manifest; 39 | 40 | import org.slf4j.Logger; 41 | import org.slf4j.LoggerFactory; 42 | import org.terasology.engine.TerasologyConstants; 43 | import org.terasology.engine.module.ModuleExtension; 44 | import org.terasology.engine.module.ModuleManager; 45 | import org.terasology.engine.module.StandardModuleExtension; 46 | import org.terasology.module.ClasspathModule; 47 | import org.terasology.module.DependencyInfo; 48 | import org.terasology.module.Module; 49 | import org.terasology.module.ModuleEnvironment; 50 | import org.terasology.module.ModuleLoader; 51 | import org.terasology.module.ModuleMetadata; 52 | import org.terasology.module.ModuleMetadataReader; 53 | import org.terasology.module.ModuleRegistry; 54 | import org.terasology.module.TableModuleRegistry; 55 | import org.terasology.module.sandbox.BytecodeInjector; 56 | import org.terasology.module.sandbox.PermissionProviderFactory; 57 | 58 | import com.google.common.collect.Sets; 59 | 60 | public class TinyModuleManager implements ModuleManager { 61 | 62 | private static final Logger logger = LoggerFactory.getLogger(TinyModuleManager.class); 63 | 64 | private final ModuleRegistry registry = new TableModuleRegistry(); 65 | private final ModuleMetadataReader metadataReader = new ModuleMetadataReader(); 66 | private final ModuleLoader moduleLoader = new ModuleLoader(metadataReader); 67 | private final PermissionProviderFactory securityManager = new DummyPermissionProviderFactory(); 68 | 69 | private ModuleEnvironment environment; 70 | 71 | public TinyModuleManager() throws IOException { 72 | for (ModuleExtension ext : StandardModuleExtension.values()) { 73 | metadataReader.registerExtension(ext.getKey(), ext.getValueType()); 74 | } 75 | 76 | moduleLoader.setModuleInfoPath(TerasologyConstants.MODULE_INFO_FILENAME); 77 | 78 | Module engineModule = loadEngineModule(); 79 | registry.add(engineModule); 80 | 81 | loadModules(); 82 | 83 | DependencyInfo engineDep = new DependencyInfo(); 84 | engineDep.setId(engineModule.getId()); 85 | engineDep.setMinVersion(engineModule.getVersion()); 86 | engineDep.setMaxVersion(engineModule.getVersion().getNextPatchVersion()); 87 | 88 | for (Module mod : registry) { 89 | if (mod != engineModule) { 90 | mod.getMetadata().getDependencies().add(engineDep); 91 | } 92 | } 93 | 94 | loadEnvironment(Sets.newHashSet(registry), true); 95 | } 96 | 97 | private Module loadEngineModule() { 98 | // TODO: define an explicit marker class and rename package for class Terasology (which is not in engine) 99 | Class marker = org.terasology.game.Game.class; 100 | try (Reader reader = new InputStreamReader(marker.getResourceAsStream("/engine-module.txt"), TerasologyConstants.CHARSET)) { 101 | ModuleMetadata metadata = metadataReader.read(reader); 102 | return ClasspathModule.create(metadata, marker, Module.class); 103 | } catch (IOException e) { 104 | throw new RuntimeException("Failed to read engine metadata", e); 105 | } catch (URISyntaxException e) { 106 | throw new RuntimeException("Failed to convert engine library location to path", e); 107 | } 108 | } 109 | 110 | @Override 111 | public ModuleEnvironment loadEnvironment(Set modules, boolean asPrimary) { 112 | List injectors = Collections.emptyList(); 113 | ModuleEnvironment newEnvironment = new ModuleEnvironment(modules, securityManager, injectors); 114 | if (asPrimary) { 115 | if (environment != null) { 116 | environment.close(); 117 | } 118 | environment = newEnvironment; 119 | } 120 | return newEnvironment; 121 | } 122 | 123 | @Override 124 | public ModuleRegistry getRegistry() { 125 | return registry; 126 | } 127 | 128 | @Override 129 | public ModuleMetadataReader getModuleMetadataReader() { 130 | return metadataReader; 131 | } 132 | 133 | @Override 134 | public ModuleEnvironment getEnvironment() { 135 | return environment; 136 | } 137 | 138 | private void loadModules() throws IOException { 139 | Collection cpEntries = getClassPath(); 140 | 141 | for (String pathStr : cpEntries) { 142 | try { 143 | // the path normalization is critical. Otherwise the module classpath is not unique 144 | // and world gens. cannot be resolved. 145 | // Example: getModuleProviding uses getCodeSource().getLocation() to find the right module 146 | Path modulePath = Paths.get(pathStr).normalize(); 147 | 148 | // The eclipse JUnit runner adds src/test/resources even if it doesn't exist 149 | if (Files.exists(modulePath, LinkOption.NOFOLLOW_LINKS)) { 150 | logger.debug("Checking entry {}", modulePath); 151 | Path codeLoc = moduleLoader.getDirectoryCodeLocation(); 152 | if (modulePath.endsWith(codeLoc)) { 153 | for (int i = 0; i < codeLoc.getNameCount(); i++) { 154 | modulePath = modulePath.getParent(); 155 | } 156 | } 157 | Module mod = moduleLoader.load(modulePath); 158 | if (mod != null) { 159 | logger.info("Loading module: {}", mod); 160 | registry.add(mod); 161 | } 162 | } else { 163 | logger.debug("Ignoring non-existing entry {}", modulePath); 164 | } 165 | } catch (InvalidPathException e) { 166 | logger.warn("Ignoring invalid path: {}", pathStr); 167 | } 168 | } 169 | } 170 | 171 | private static Collection getClassPath() throws IOException { 172 | // If the application is launched from the command line through java -jar 173 | // the classpath attribute is ignored and read from the jar's MANIFEST.MF file 174 | // instead. The classpath will then just contain WorldViewer.jar. We need to 175 | // manually parse the entries in that case :-( 176 | 177 | Collection entries = new LinkedHashSet<>(); 178 | 179 | String className = TinyModuleManager.class.getSimpleName() + ".class"; 180 | String classPath = TinyModuleManager.class.getResource(className).toString(); 181 | if (classPath.startsWith("jar")) { 182 | String manifestPath = classPath.substring(0, classPath.lastIndexOf("!") + 1) + "/META-INF/MANIFEST.MF"; 183 | try (InputStream is = new URL(manifestPath).openStream()) { 184 | Manifest manifest = new Manifest(is); 185 | String classpath = manifest.getMainAttributes().getValue(Attributes.Name.CLASS_PATH); 186 | entries.addAll(Arrays.asList(classpath.split(" "))); 187 | } 188 | } 189 | 190 | String classpath = System.getProperty("java.class.path"); 191 | entries.addAll(Arrays.asList(classpath.split(File.pathSeparator))); 192 | 193 | return entries; 194 | } 195 | 196 | public Module load(Path path) throws IOException { 197 | Module module = moduleLoader.load(path); 198 | if (!registry.contains(module)) { 199 | logger.info("Module loaded: {}", module); 200 | registry.add(module); 201 | } 202 | 203 | return module; 204 | } 205 | } 206 | 207 | -------------------------------------------------------------------------------- /src/main/java/org/terasology/world/viewer/gui/CursorPositionListener.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 MovingBlocks 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.terasology.world.viewer.gui; 18 | 19 | import java.awt.Point; 20 | import java.awt.event.MouseAdapter; 21 | import java.awt.event.MouseEvent; 22 | 23 | /** 24 | * Stores the last known cursor position 25 | */ 26 | public class CursorPositionListener extends MouseAdapter { 27 | 28 | private Point curPos; 29 | 30 | /** 31 | * @return the cursor position or null if outside 32 | */ 33 | public Point getCursorPosition() { 34 | return curPos; 35 | } 36 | 37 | @Override 38 | public void mouseMoved(MouseEvent e) { 39 | curPos = e.getPoint(); 40 | } 41 | 42 | @Override 43 | public void mouseDragged(MouseEvent e) { 44 | curPos = e.getPoint(); 45 | } 46 | 47 | @Override 48 | public void mouseExited(MouseEvent e) { 49 | curPos = null; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/org/terasology/world/viewer/gui/FacetListCellRenderer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 MovingBlocks 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.terasology.world.viewer.gui; 18 | 19 | import java.awt.Component; 20 | 21 | import javax.swing.JCheckBox; 22 | import javax.swing.JList; 23 | import javax.swing.ListCellRenderer; 24 | 25 | import org.terasology.world.viewer.layers.FacetLayer; 26 | 27 | /** 28 | * Renders cells as checkboxes. Interaction does not seem to be possible. 29 | */ 30 | public class FacetListCellRenderer implements ListCellRenderer { 31 | 32 | private final JCheckBox checkBox = new JCheckBox(); 33 | 34 | @Override 35 | public Component getListCellRendererComponent(JList list, FacetLayer layer, int index, boolean isSelected, boolean cellHasFocus) { 36 | checkBox.setText(layer.toString()); 37 | checkBox.setSelected(layer.isVisible()); 38 | checkBox.setBackground(isSelected ? list.getSelectionBackground() : list.getBackground()); 39 | return checkBox; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/org/terasology/world/viewer/gui/ListItemTransferHandler.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 MovingBlocks 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.terasology.world.viewer.gui; 18 | 19 | import java.awt.datatransfer.DataFlavor; 20 | import java.awt.datatransfer.Transferable; 21 | import java.awt.datatransfer.UnsupportedFlavorException; 22 | import java.io.IOException; 23 | import java.util.ArrayList; 24 | import java.util.List; 25 | 26 | import javax.activation.ActivationDataFlavor; 27 | import javax.activation.DataHandler; 28 | import javax.swing.DefaultListModel; 29 | import javax.swing.JComponent; 30 | import javax.swing.JList; 31 | import javax.swing.TransferHandler; 32 | 33 | import org.slf4j.Logger; 34 | import org.slf4j.LoggerFactory; 35 | 36 | import com.google.common.collect.Lists; 37 | 38 | /** 39 | * Works only for {@link JList} with {@link DefaultListModel}. 40 | * Adapted from http://stackoverflow.com/questions/16586562/reordering-jlist-with-drag-and-drop 41 | * @param the item type 42 | */ 43 | public class ListItemTransferHandler extends TransferHandler { 44 | private static final long serialVersionUID = -8755359045727856083L; 45 | 46 | private static final Logger logger = LoggerFactory.getLogger(ListItemTransferHandler.class); 47 | 48 | private final DataFlavor localObjectFlavor; 49 | 50 | private List transferedObjects; 51 | 52 | private int[] indices; 53 | 54 | /** 55 | * Location where items were added 56 | */ 57 | private int addIndex = -1; 58 | 59 | /** 60 | * Number of items added. 61 | */ 62 | private int addCount; 63 | 64 | public ListItemTransferHandler() { 65 | localObjectFlavor = new ActivationDataFlavor(ArrayList.class, DataFlavor.javaJVMLocalObjectMimeType, "ArrayList of items"); 66 | } 67 | 68 | @Override 69 | protected Transferable createTransferable(JComponent c) { 70 | @SuppressWarnings("unchecked") 71 | JList list = (JList) c; 72 | indices = list.getSelectedIndices(); 73 | transferedObjects = Lists.newArrayList(list.getSelectedValuesList()); 74 | return new DataHandler(transferedObjects, localObjectFlavor.getMimeType()); 75 | } 76 | 77 | @Override 78 | public boolean canImport(TransferSupport info) { 79 | if (!info.isDrop() || !info.isDataFlavorSupported(localObjectFlavor)) { 80 | return false; 81 | } 82 | 83 | return true; 84 | } 85 | 86 | @Override 87 | public int getSourceActions(JComponent c) { 88 | return MOVE; // TransferHandler.COPY_OR_MOVE; 89 | } 90 | 91 | @Override 92 | public boolean importData(TransferSupport info) { 93 | 94 | if (!canImport(info)) { 95 | return false; 96 | } 97 | 98 | @SuppressWarnings("unchecked") 99 | JList target = (JList) info.getComponent(); 100 | JList.DropLocation dl = (JList.DropLocation) info.getDropLocation(); 101 | DefaultListModel listModel = (DefaultListModel) target.getModel(); 102 | 103 | int index = dl.getIndex(); 104 | int max = listModel.getSize(); 105 | if (index < 0 || index > max) { 106 | index = max; 107 | } 108 | addIndex = index; 109 | 110 | try { 111 | @SuppressWarnings("unchecked") 112 | List values = (List) info.getTransferable().getTransferData(localObjectFlavor); 113 | 114 | addCount = values.size(); 115 | for (int i = 0; i < values.size(); i++) { 116 | int idx = index++; 117 | listModel.add(idx, values.get(i)); 118 | target.addSelectionInterval(idx, idx); 119 | } 120 | return true; 121 | } catch (UnsupportedFlavorException | IOException e) { 122 | logger.warn("Could not import dnd data", e); 123 | return false; 124 | } 125 | } 126 | 127 | @Override 128 | protected void exportDone(JComponent c, Transferable data, int action) { 129 | if (action == MOVE) { 130 | removeEntries(c); 131 | } 132 | } 133 | 134 | private void removeEntries(JComponent c) { 135 | if (indices != null) { 136 | @SuppressWarnings("unchecked") 137 | JList source = (JList) c; 138 | DefaultListModel model = (DefaultListModel) source.getModel(); 139 | 140 | if (addCount > 0) { 141 | // http://java-swing-tips.googlecode.com/svn/trunk/DnDReorderList/src/java/example/MainPanel.java 142 | for (int i = 0; i < indices.length; i++) { 143 | if (indices[i] >= addIndex) { 144 | indices[i] += addCount; 145 | } 146 | } 147 | } 148 | for (int i = indices.length - 1; i >= 0; i--) { 149 | model.remove(indices[i]); 150 | } 151 | } 152 | indices = null; 153 | addCount = 0; 154 | addIndex = -1; 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /src/main/java/org/terasology/world/viewer/gui/RepaintingMouseListener.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 MovingBlocks 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.terasology.world.viewer.gui; 18 | 19 | import java.awt.Component; 20 | import java.awt.event.MouseAdapter; 21 | import java.awt.event.MouseEvent; 22 | import java.awt.event.MouseWheelEvent; 23 | 24 | /** 25 | * Triggers a repaint on any mouse interaction 26 | */ 27 | public class RepaintingMouseListener extends MouseAdapter { 28 | 29 | private final Component comp; 30 | 31 | /** 32 | * @param comp the component to repaint 33 | */ 34 | public RepaintingMouseListener(Component comp) { 35 | this.comp = comp; 36 | } 37 | 38 | @Override 39 | public void mouseMoved(MouseEvent e) { 40 | comp.repaint(); 41 | } 42 | 43 | @Override 44 | public void mouseDragged(MouseEvent e) { 45 | comp.repaint(); 46 | } 47 | 48 | @Override 49 | public void mouseExited(MouseEvent e) { 50 | comp.repaint(); 51 | } 52 | 53 | @Override 54 | public void mouseClicked(MouseEvent e) { 55 | comp.repaint(); 56 | } 57 | 58 | @Override 59 | public void mousePressed(MouseEvent e) { 60 | comp.repaint(); 61 | } 62 | 63 | @Override 64 | public void mouseReleased(MouseEvent e) { 65 | comp.repaint(); 66 | } 67 | 68 | @Override 69 | public void mouseEntered(MouseEvent e) { 70 | comp.repaint(); 71 | } 72 | 73 | @Override 74 | public void mouseWheelMoved(MouseWheelEvent e) { 75 | comp.repaint(); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/main/java/org/terasology/world/viewer/gui/UIBindings.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 MovingBlocks 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.terasology.world.viewer.gui; 18 | 19 | import java.lang.reflect.Field; 20 | 21 | import javax.swing.ComboBoxModel; 22 | import javax.swing.DefaultComboBoxModel; 23 | import javax.swing.JCheckBox; 24 | import javax.swing.JComboBox; 25 | import javax.swing.JSpinner; 26 | import javax.swing.SpinnerNumberModel; 27 | 28 | import org.slf4j.Logger; 29 | import org.slf4j.LoggerFactory; 30 | import org.terasology.rendering.nui.properties.Checkbox; 31 | import org.terasology.rendering.nui.properties.OneOf.Enum; 32 | import org.terasology.rendering.nui.properties.OneOf.List; 33 | import org.terasology.rendering.nui.properties.Range; 34 | 35 | /** 36 | * Provides a set of static methods that creates 37 | * Swing UI elements based on individual fields. 38 | */ 39 | public final class UIBindings { 40 | 41 | private static final Logger logger = LoggerFactory.getLogger(UIBindings.class); 42 | 43 | private UIBindings() { 44 | // no instances 45 | } 46 | 47 | public static JCheckBox processCheckboxAnnotation(Object config, Field field, String text) { 48 | Checkbox checkbox = field.getAnnotation(Checkbox.class); 49 | 50 | if (checkbox != null) { 51 | try { 52 | boolean initial = field.getBoolean(config); 53 | JCheckBox component = createCheckbox(text, initial); 54 | component.setName(checkbox.label().isEmpty() ? field.getName() : checkbox.label()); 55 | component.setToolTipText(checkbox.description().isEmpty() ? null : checkbox.description()); 56 | 57 | return component; 58 | } catch (IllegalAccessException e) { 59 | logger.warn("Unable to read field {}", field); 60 | } 61 | } 62 | 63 | return null; 64 | } 65 | 66 | public static JCheckBox createCheckbox(String text, boolean initial) { 67 | JCheckBox checkBox = new JCheckBox(text); 68 | checkBox.setSelected(initial); 69 | 70 | return checkBox; 71 | } 72 | 73 | public static JSpinner processRangeAnnotation(Object config, Field field) { 74 | Range range = field.getAnnotation(Range.class); 75 | 76 | if (range != null) { 77 | double min = range.min(); 78 | double max = range.max(); 79 | double stepSize = range.increment(); 80 | try { 81 | double initial = field.getDouble(config); 82 | JSpinner spinner = createSpinner(min, stepSize, max, initial); 83 | spinner.setName(range.label().isEmpty() ? field.getName() : range.label()); 84 | spinner.setToolTipText(range.description().isEmpty() ? null : range.description()); 85 | 86 | return spinner; 87 | } catch (IllegalAccessException e) { 88 | logger.warn("Unable to read field {}", field); 89 | } 90 | } 91 | 92 | return null; 93 | } 94 | 95 | public static JSpinner createSpinner(double min, double stepSize, double max, double initial) { 96 | 97 | SpinnerNumberModel model = new SpinnerNumberModel(initial, min, max, stepSize); 98 | JSpinner spinner = new JSpinner(model); 99 | return spinner; 100 | } 101 | 102 | /** 103 | * Maps an @Enum field to a combobox 104 | * @param config the object instance that is bound 105 | * @param field the (potentially annotated field) 106 | * @return a combobox for the annotated field or null if not applicable 107 | */ 108 | public static JComboBox processEnumAnnotation(Object config, Field field) { 109 | Enum en = field.getAnnotation(Enum.class); 110 | Class clazz = field.getType(); // the enum class 111 | 112 | if (en != null && clazz.isEnum()) { 113 | try { 114 | Object init = field.get(config); 115 | JComboBox combo = createCombo(clazz.getEnumConstants(), init); 116 | combo.setName(en.label().isEmpty() ? field.getName() : en.label()); 117 | combo.setToolTipText(en.description().isEmpty() ? null : en.description()); 118 | return combo; 119 | } catch (IllegalAccessException e) { 120 | logger.warn("Unable to read field {}", field); 121 | } 122 | } 123 | 124 | return null; 125 | } 126 | 127 | public static JComboBox processListAnnotation(Object config, Field field) { 128 | List list = field.getAnnotation(List.class); 129 | 130 | if (list != null) { 131 | try { 132 | String init = field.get(config).toString(); // this should be a String already 133 | JComboBox combo = createCombo(list.items(), init); 134 | combo.setName(list.label().isEmpty() ? field.getName() : list.label()); 135 | combo.setToolTipText(list.description().isEmpty() ? null : list.description()); 136 | return combo; 137 | } catch (IllegalAccessException e) { 138 | logger.warn("Unable to read field {}", field); 139 | } 140 | } 141 | 142 | return null; 143 | } 144 | 145 | public static JComboBox createCombo(T[] elements, T initValue) { 146 | 147 | ComboBoxModel model = new DefaultComboBoxModel(elements); 148 | JComboBox combo = new JComboBox(model); 149 | combo.setSelectedItem(initValue); 150 | return combo; 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /src/main/java/org/terasology/world/viewer/gui/WorldGenCellRenderer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 MovingBlocks 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.terasology.world.viewer.gui; 18 | 19 | import java.awt.Component; 20 | 21 | import javax.swing.DefaultListCellRenderer; 22 | import javax.swing.JList; 23 | 24 | import org.terasology.world.generator.internal.WorldGeneratorInfo; 25 | 26 | /** 27 | * It actually implements ListCellRenderer, but since DefaultListCellRenderer 28 | * uses Object, this isn't allowed in Java. 29 | */ 30 | public class WorldGenCellRenderer extends DefaultListCellRenderer { 31 | 32 | private static final long serialVersionUID = -3375088206153260363L; 33 | 34 | @Override 35 | public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { 36 | String text = (value == null) ? null : ((WorldGeneratorInfo) value).getDisplayName(); 37 | return super.getListCellRendererComponent(list, text, index, isSelected, cellHasFocus); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/org/terasology/world/viewer/overlay/AbstractOverlay.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 MovingBlocks 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.terasology.world.viewer.overlay; 18 | 19 | 20 | /** 21 | */ 22 | public abstract class AbstractOverlay implements Overlay { 23 | 24 | private boolean isVisible = true; 25 | 26 | @Override 27 | public void setVisible(boolean yesno) { 28 | isVisible = yesno; 29 | } 30 | 31 | @Override 32 | public boolean isVisible() { 33 | return isVisible; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/org/terasology/world/viewer/overlay/GridOverlay.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 MovingBlocks 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.terasology.world.viewer.overlay; 18 | 19 | import java.awt.BasicStroke; 20 | import java.awt.Color; 21 | import java.awt.Graphics2D; 22 | import java.math.RoundingMode; 23 | 24 | import org.terasology.math.geom.ImmutableVector2i; 25 | import org.terasology.math.geom.Rect2i; 26 | 27 | import com.google.common.math.IntMath; 28 | 29 | /** 30 | * Renders a grid that is aligned along tile borders 31 | */ 32 | public class GridOverlay extends AbstractOverlay implements WorldOverlay { 33 | 34 | private Color originGridColor = new Color(192, 192, 192, 224); 35 | private Color majorGridColor = new Color(128, 128, 128, 160); 36 | private Color minorGridColor = new Color(128, 128, 128, 64); 37 | 38 | private int majorToMinor = 8; 39 | 40 | private int tileSizeX; 41 | private int tileSizeY; 42 | 43 | public GridOverlay(int tileSizeX, int tileSizeY) { 44 | this.tileSizeX = tileSizeX; 45 | this.tileSizeY = tileSizeY; 46 | } 47 | 48 | @Override 49 | public void render(Graphics2D g, Rect2i area, ImmutableVector2i cursor) { 50 | int tileMinX = IntMath.divide(area.minX(), tileSizeX, RoundingMode.FLOOR); 51 | int tileMinZ = IntMath.divide(area.minY(), tileSizeY, RoundingMode.FLOOR); 52 | 53 | int tileMaxX = IntMath.divide(area.maxX(), tileSizeX, RoundingMode.CEILING); 54 | int tileMaxZ = IntMath.divide(area.maxY(), tileSizeY, RoundingMode.CEILING); 55 | 56 | g.setStroke(new BasicStroke(0)); 57 | 58 | for (int z = tileMinZ; z < tileMaxZ; z++) { 59 | g.setColor((z == 0) ? originGridColor : (z % majorToMinor == 0) ? majorGridColor : minorGridColor); 60 | g.drawLine(tileMinX * tileSizeX, z * tileSizeY, tileMaxX * tileSizeX, z * tileSizeY); 61 | } 62 | 63 | for (int x = tileMinX; x < tileMaxX; x++) { 64 | g.setColor((x == 0) ? originGridColor : (x % majorToMinor == 0) ? majorGridColor : minorGridColor); 65 | g.drawLine(x * tileSizeX, tileMinZ * tileSizeY, x * tileSizeX, tileMaxZ * tileSizeY); 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/main/java/org/terasology/world/viewer/overlay/Overlay.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 MovingBlocks 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.terasology.world.viewer.overlay; 18 | 19 | import java.awt.Graphics2D; 20 | 21 | import org.terasology.math.geom.ImmutableVector2i; 22 | import org.terasology.math.geom.Rect2i; 23 | 24 | /** 25 | * Overlays are rendered live on top of the 2D world. 26 | */ 27 | public interface Overlay { 28 | 29 | void render(Graphics2D g, Rect2i area, ImmutableVector2i cursor); 30 | 31 | void setVisible(boolean yesno); 32 | 33 | boolean isVisible(); 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/org/terasology/world/viewer/overlay/PixelOverlay.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 MovingBlocks 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.terasology.world.viewer.overlay; 18 | 19 | import java.awt.BasicStroke; 20 | import java.awt.Color; 21 | import java.awt.Graphics2D; 22 | import java.awt.geom.AffineTransform; 23 | 24 | import org.terasology.math.geom.ImmutableVector2i; 25 | import org.terasology.math.geom.Rect2i; 26 | 27 | /** 28 | * Renders a grid over ever world block (pixel). 29 | * Makes sense only for large zoom factors. 30 | */ 31 | public class PixelOverlay extends AbstractOverlay implements WorldOverlay { 32 | 33 | private Color gridColor = new Color(96, 96, 96, 96); 34 | private float minScaleFactor; 35 | 36 | /** 37 | * @param minScaleFactor the minimum scale factor that is required for the pixel grid to become visible 38 | */ 39 | public PixelOverlay(float minScaleFactor) { 40 | this.minScaleFactor = minScaleFactor; 41 | } 42 | 43 | @Override 44 | public void render(Graphics2D g, Rect2i area, ImmutableVector2i cursor) { 45 | AffineTransform at = g.getTransform(); 46 | if (at.getScaleX() < minScaleFactor || at.getScaleX() < minScaleFactor) { 47 | return; 48 | } 49 | 50 | g.setColor(gridColor); 51 | g.setStroke(new BasicStroke(0)); 52 | 53 | for (int z = area.minY(); z < area.maxY(); z++) { 54 | g.drawLine(area.minX(), z, area.maxX(), z); 55 | } 56 | 57 | for (int x = area.minX(); x < area.maxX(); x++) { 58 | g.drawLine(x, area.minY(), x, area.maxY()); 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/main/java/org/terasology/world/viewer/overlay/ScreenOverlay.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 MovingBlocks 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"){ } 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.terasology.world.viewer.overlay; 18 | 19 | /** 20 | * Indicates that the overlay lives in screen space. 21 | */ 22 | public interface ScreenOverlay extends Overlay { 23 | 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/org/terasology/world/viewer/overlay/TextOverlay.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 MovingBlocks 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.terasology.world.viewer.overlay; 18 | 19 | import java.awt.Color; 20 | import java.awt.Font; 21 | import java.awt.FontMetrics; 22 | import java.awt.Graphics2D; 23 | import java.awt.Paint; 24 | import java.awt.RenderingHints; 25 | import java.util.function.Supplier; 26 | 27 | import org.terasology.math.geom.ImmutableVector2i; 28 | import org.terasology.math.geom.Rect2i; 29 | import org.terasology.math.geom.Vector2i; 30 | import org.terasology.rendering.nui.HorizontalAlign; 31 | import org.terasology.rendering.nui.VerticalAlign; 32 | 33 | /** 34 | * Renders a grid that is aligned along tile borders 35 | */ 36 | public class TextOverlay extends AbstractOverlay implements ScreenOverlay { 37 | 38 | private final Supplier textSupp; 39 | 40 | private Color color = Color.WHITE; 41 | 42 | private int inLeft; 43 | private int inTop; 44 | private int inRight; 45 | private int inBottom; 46 | 47 | private int mgLeft; 48 | private int mgTop; 49 | private int mgRight; 50 | private int mgBottom; 51 | 52 | private Font font; 53 | 54 | private VerticalAlign alignVert = VerticalAlign.TOP; 55 | private HorizontalAlign alignHorz = HorizontalAlign.LEFT; 56 | 57 | private Paint background; 58 | private Paint frame; 59 | 60 | /** 61 | * @param textSupp the text supplier 62 | */ 63 | public TextOverlay(Supplier textSupp) { 64 | this.textSupp = textSupp; 65 | } 66 | 67 | public VerticalAlign getVerticalAlign() { 68 | return alignVert; 69 | } 70 | 71 | public TextOverlay setVerticalAlign(VerticalAlign alignV) { 72 | this.alignVert = alignV; 73 | return this; 74 | } 75 | 76 | public HorizontalAlign getHorizontalAlign() { 77 | return alignHorz; 78 | } 79 | 80 | public TextOverlay setHorizontalAlign(HorizontalAlign alignH) { 81 | this.alignHorz = alignH; 82 | return this; 83 | } 84 | 85 | public TextOverlay setMargins(int left, int top, int right, int bottom) { 86 | this.mgLeft = left; 87 | this.mgTop = top; 88 | this.mgRight = right; 89 | this.mgBottom = bottom; 90 | return this; 91 | } 92 | 93 | public TextOverlay setInsets(int left, int top, int right, int bottom) { 94 | this.inLeft = left; 95 | this.inTop = top; 96 | this.inRight = right; 97 | this.inBottom = bottom; 98 | return this; 99 | } 100 | 101 | public void setFont(Font font) { 102 | this.font = font; 103 | } 104 | 105 | @Override 106 | public void render(Graphics2D g, Rect2i area, ImmutableVector2i cursor) { 107 | 108 | Rect2i mgArea = Rect2i.createFromMinAndMax( 109 | area.minX() + mgLeft, 110 | area.minY() + mgTop, 111 | area.maxX() - mgRight, 112 | area.maxY() - mgBottom); 113 | 114 | String text = textSupp.get(); 115 | if (text == null) { 116 | return; 117 | } 118 | 119 | Font oldFont = g.getFont(); 120 | Object oldHint = g.getRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING); 121 | 122 | g.setFont(font); // null fonts are silenty ignored 123 | g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON); 124 | 125 | FontMetrics fm = g.getFontMetrics(); 126 | String[] lines = text.split("\n"); 127 | 128 | if (background != null || frame != null) { 129 | Vector2i bbox = getBBox(fm, lines); 130 | bbox.addX(inLeft + inRight); 131 | bbox.addY(inTop + inBottom); 132 | int x = mgArea.minX() + alignHorz.getOffset(bbox.getX(), mgArea.width()); 133 | int y = mgArea.minY() + alignVert.getOffset(bbox.getY(), mgArea.height()); 134 | 135 | if (background != null) { 136 | g.setPaint(background); 137 | g.fillRect(x, y, bbox.getX(), bbox.getY()); 138 | } 139 | 140 | if (frame != null) { 141 | g.setPaint(frame); 142 | g.drawRect(x, y, bbox.getX(), bbox.getY()); 143 | } 144 | } 145 | 146 | int y = 0; 147 | 148 | Rect2i textArea = Rect2i.createFromMinAndMax( 149 | mgArea.minX() + inLeft, 150 | mgArea.minY() + inTop, 151 | mgArea.maxX() - inRight, 152 | mgArea.maxY() - inBottom); 153 | 154 | switch (alignVert) { 155 | case TOP: 156 | y = textArea.minY() + fm.getAscent(); 157 | break; 158 | 159 | case MIDDLE: 160 | double lineCenter = fm.getHeight() / 2 - fm.getAscent(); 161 | double textCenter = (lines.length - 1) * 0.5 * fm.getHeight() + lineCenter; 162 | double centerY = (textArea.maxY() + textArea.minY()) * 0.5; 163 | y = (int) (centerY - textCenter); 164 | break; 165 | 166 | case BOTTOM: 167 | int textHeight = (lines.length - 1) * fm.getHeight(); 168 | y = textArea.maxY() - textHeight; 169 | break; 170 | } 171 | 172 | g.setColor(color); 173 | 174 | for (String line : lines) { 175 | int x = 0; 176 | int textWidth = fm.stringWidth(line); 177 | 178 | switch (alignHorz) { 179 | case LEFT: 180 | x = textArea.minX(); 181 | break; 182 | 183 | case CENTER: 184 | double centerX = (textArea.maxX() + textArea.minX()) * 0.5; 185 | x = (int) (centerX - textWidth); 186 | break; 187 | 188 | case RIGHT: 189 | x = textArea.maxX() - textWidth; 190 | break; 191 | } 192 | 193 | g.drawString(line, x, y); 194 | 195 | y += fm.getHeight(); 196 | } 197 | 198 | g.setFont(oldFont); 199 | g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, oldHint); 200 | } 201 | 202 | public void setBackground(Paint background) { 203 | this.background = background; 204 | } 205 | 206 | public void setFrame(Paint frame) { 207 | this.frame = frame; 208 | } 209 | 210 | private Vector2i getBBox(FontMetrics fm, String[] lines) { 211 | 212 | int maxWidth = 0; 213 | int height = 0; 214 | 215 | for (String line : lines) { 216 | int textWidth = fm.stringWidth(line); 217 | if (textWidth > maxWidth) { 218 | maxWidth = textWidth; 219 | } 220 | height += fm.getHeight(); 221 | } 222 | 223 | return new Vector2i(maxWidth, height); 224 | } 225 | } 226 | 227 | 228 | -------------------------------------------------------------------------------- /src/main/java/org/terasology/world/viewer/overlay/TooltipOverlay.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 MovingBlocks 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.terasology.world.viewer.overlay; 18 | 19 | import java.awt.Color; 20 | import java.awt.FontMetrics; 21 | import java.awt.Graphics2D; 22 | import java.util.function.Function; 23 | 24 | import org.terasology.math.geom.ImmutableVector2i; 25 | import org.terasology.math.geom.Rect2i; 26 | 27 | /** 28 | * Shows the tooltip 29 | */ 30 | public class TooltipOverlay extends AbstractOverlay implements ScreenOverlay { 31 | 32 | private Function tooltipTextFunc; 33 | 34 | public TooltipOverlay(Function tooltipTextFunc) { 35 | this.tooltipTextFunc = tooltipTextFunc; 36 | } 37 | 38 | @Override 39 | public void render(Graphics2D g, Rect2i area, ImmutableVector2i cursor) { 40 | if (cursor == null) { 41 | return; 42 | } 43 | 44 | String text = tooltipTextFunc.apply(cursor); 45 | int wx = cursor.getX(); 46 | int wy = cursor.getY(); 47 | int offX = 5; 48 | int offY = 5; 49 | 50 | String[] lines = text.split("\n"); 51 | 52 | g.setColor(Color.WHITE); 53 | FontMetrics fm = g.getFontMetrics(); 54 | 55 | int x = wx + offX; 56 | int y = wy + offY + fm.getAscent(); 57 | 58 | int maxHeight = lines.length * fm.getHeight(); 59 | int maxWidth = 0; 60 | for (String line : lines) { 61 | int width = fm.stringWidth(line); 62 | if (width > maxWidth) { 63 | maxWidth = width; 64 | } 65 | } 66 | 67 | int inset = 2; 68 | g.setColor(new Color(64, 64, 64, 128)); 69 | g.fillRect(wx + offX - inset, wy + offY - inset, maxWidth + 2 * inset, maxHeight + 2 * inset); 70 | 71 | g.setColor(new Color(192, 192, 192, 128)); 72 | g.drawRect(wx + offX - inset, wy + offY - inset, maxWidth + 2 * inset, maxHeight + 2 * inset); 73 | 74 | g.setColor(Color.WHITE); 75 | 76 | for (String line : lines) { 77 | g.drawString(line, x, y); 78 | y += fm.getHeight(); 79 | } 80 | 81 | g.dispose(); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/main/java/org/terasology/world/viewer/overlay/WorldOverlay.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 MovingBlocks 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.terasology.world.viewer.overlay; 18 | 19 | /** 20 | * Indicates that the overlay uses world coordinates. 21 | */ 22 | public interface WorldOverlay extends Overlay { 23 | 24 | } 25 | -------------------------------------------------------------------------------- /src/main/resources/VersionInfo.template: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 MovingBlocks 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.terasology.world.viewer.version; 18 | 19 | import java.time.ZonedDateTime; 20 | 21 | import org.terasology.naming.Version; 22 | 23 | /** 24 | * Do not modify this file! All changes will be overwritten. 25 | * This file is auto-generated by the gradle task createVersionFile 26 | * See build.gradle for the task and for the definition of the version string 27 | */ 28 | public final class VersionInfo { 29 | 30 | private VersionInfo() { 31 | // no instances! 32 | } 33 | 34 | /** 35 | * @return The version identifier as defined by the build script 36 | */ 37 | public static Version getVersion() { 38 | return new Version($BUILD_VERSION_MAJOR, $BUILD_VERSION_MINOR, $BUILD_VERSION_PATCH); 39 | } 40 | 41 | /** 42 | * @return The commit that was used for the build 43 | */ 44 | public static String getBuildCommit() { 45 | return "$BUILD_SHA"; 46 | } 47 | 48 | /** 49 | * @return The timestamp when the build was started 50 | */ 51 | public static ZonedDateTime getBuildTime() { 52 | return ZonedDateTime.parse("$BUILD_TIME"); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/main/resources/icons/gooey_sweet_red_16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MovingBlocks/WorldViewer/b8796a299cb6f8077e0c5f503b259ddbc0f2b552/src/main/resources/icons/gooey_sweet_red_16.png -------------------------------------------------------------------------------- /src/main/resources/icons/gooey_sweet_red_32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MovingBlocks/WorldViewer/b8796a299cb6f8077e0c5f503b259ddbc0f2b552/src/main/resources/icons/gooey_sweet_red_32.png -------------------------------------------------------------------------------- /src/main/resources/icons/gooey_sweet_red_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MovingBlocks/WorldViewer/b8796a299cb6f8077e0c5f503b259ddbc0f2b552/src/main/resources/icons/gooey_sweet_red_64.png -------------------------------------------------------------------------------- /src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/main/resources/splash/splash.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MovingBlocks/WorldViewer/b8796a299cb6f8077e0c5f503b259ddbc0f2b552/src/main/resources/splash/splash.jpg -------------------------------------------------------------------------------- /src/test/java/org/terasology/world/viewer/core/ViewerTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 MovingBlocks 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"){ } 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.terasology.world.viewer.core; 18 | 19 | import java.awt.Graphics2D; 20 | import java.awt.image.BufferedImage; 21 | import java.io.IOException; 22 | import java.util.List; 23 | import java.util.Set; 24 | 25 | import org.junit.Before; 26 | import org.junit.Test; 27 | import org.terasology.context.Context; 28 | import org.terasology.engine.SimpleUri; 29 | import org.terasology.engine.module.ModuleManager; 30 | import org.terasology.registry.CoreRegistry; 31 | import org.terasology.splash.SplashScreenBuilder; 32 | import org.terasology.world.generation.WorldFacet; 33 | import org.terasology.world.generator.UnresolvedWorldGeneratorException; 34 | import org.terasology.world.generator.WorldGenerator; 35 | import org.terasology.world.generator.internal.WorldGeneratorManager; 36 | import org.terasology.world.viewer.config.ViewConfig; 37 | import org.terasology.world.viewer.env.TinyEnvironment; 38 | import org.terasology.world.viewer.layers.FacetLayer; 39 | import org.terasology.world.viewer.layers.FacetLayers; 40 | 41 | public class ViewerTest { 42 | 43 | private Context context; 44 | 45 | @Before 46 | public void setup() throws IOException { 47 | context = TinyEnvironment.createContext(new SplashScreenBuilder().build()); 48 | } 49 | 50 | @Test 51 | public void testViewer() throws UnresolvedWorldGeneratorException { 52 | WorldGeneratorManager worldGeneratorManager = CoreRegistry.get(WorldGeneratorManager.class); 53 | WorldGenerator worldGen = worldGeneratorManager.createGenerator(new SimpleUri("core:facetedperlin"), context); 54 | String worldSeed = "asdf"; 55 | worldGen.setWorldSeed(worldSeed); 56 | worldGen.initialize(); 57 | 58 | ModuleManager moduleManager = CoreRegistry.get(ModuleManager.class); 59 | 60 | Set> facets = worldGen.getWorld().getAllFacets(); 61 | List loadedLayers = FacetLayers.createLayersFor(facets, moduleManager.getEnvironment()); 62 | 63 | BufferedImage img = new BufferedImage(300, 300, BufferedImage.TYPE_INT_RGB); 64 | Viewer viewer = new Viewer(new ViewConfig(), 100); 65 | viewer.setWorldGen(worldGen, loadedLayers); 66 | viewer.setSize(300, 300); 67 | Graphics2D g = img.createGraphics(); 68 | viewer.paint(g); 69 | g.dispose(); 70 | } 71 | } 72 | --------------------------------------------------------------------------------