├── settings.gradle
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── 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
├── src
├── main
│ ├── resources
│ │ ├── splash
│ │ │ └── splash.jpg
│ │ ├── icons
│ │ │ ├── gooey_sweet_red_16.png
│ │ │ ├── gooey_sweet_red_32.png
│ │ │ └── gooey_sweet_red_64.png
│ │ ├── logback.xml
│ │ └── VersionInfo.template
│ └── java
│ │ └── org
│ │ └── terasology
│ │ └── world
│ │ └── viewer
│ │ ├── overlay
│ │ ├── WorldOverlay.java
│ │ ├── ScreenOverlay.java
│ │ ├── AbstractOverlay.java
│ │ ├── Overlay.java
│ │ ├── PixelOverlay.java
│ │ ├── GridOverlay.java
│ │ ├── TooltipOverlay.java
│ │ └── TextOverlay.java
│ │ ├── core
│ │ ├── Reorderable.java
│ │ ├── TileThreadFactory.java
│ │ ├── ZoomOverlayUpdater.java
│ │ ├── FacetTableModel.java
│ │ ├── TableRowTransferHandler.java
│ │ ├── FacetPanel.java
│ │ ├── ConfigPanel.java
│ │ └── Viewer.java
│ │ ├── camera
│ │ ├── CameraListener.java
│ │ ├── RepaintingCameraListener.java
│ │ ├── CameraKeyController.java
│ │ ├── Camera.java
│ │ └── CameraMouseController.java
│ │ ├── config
│ │ ├── WorldGenConfigData.java
│ │ ├── ConfigData.java
│ │ ├── ViewConfig.java
│ │ ├── WorldConfig.java
│ │ ├── ClassTypeAdapter.java
│ │ ├── ConfigEntry.java
│ │ └── Config.java
│ │ ├── gui
│ │ ├── CursorPositionListener.java
│ │ ├── FacetListCellRenderer.java
│ │ ├── WorldGenCellRenderer.java
│ │ ├── RepaintingMouseListener.java
│ │ ├── ListItemTransferHandler.java
│ │ └── UIBindings.java
│ │ ├── env
│ │ ├── DummyPermissionProviderFactory.java
│ │ ├── TinyModuleManager.java
│ │ └── TinyEnvironment.java
│ │ ├── ThreadSafeRegion.java
│ │ ├── ModuleTableModel.java
│ │ ├── MainFrame.java
│ │ ├── WorldViewer.java
│ │ └── SelectWorldGenDialog.java
└── test
│ └── java
│ └── org
│ └── terasology
│ └── world
│ └── viewer
│ └── core
│ └── ViewerTest.java
├── gradle.properties
├── natives
└── what-is-this-folder-good-for.txt
├── .gitignore
├── README.md
├── gradlew.bat
├── gradlew
└── LICENSE
/settings.gradle:
--------------------------------------------------------------------------------
1 | rootProject.name = 'WorldViewer'
2 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MovingBlocks/WorldViewer/HEAD/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/images/2014-10-21_first_light.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MovingBlocks/WorldViewer/HEAD/images/2014-10-21_first_light.png
--------------------------------------------------------------------------------
/images/2014-10-27_version0-3-0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MovingBlocks/WorldViewer/HEAD/images/2014-10-27_version0-3-0.png
--------------------------------------------------------------------------------
/images/2015-03-07_version0-5-0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MovingBlocks/WorldViewer/HEAD/images/2015-03-07_version0-5-0.png
--------------------------------------------------------------------------------
/images/2015-03-25_version0-7-0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MovingBlocks/WorldViewer/HEAD/images/2015-03-25_version0-7-0.png
--------------------------------------------------------------------------------
/images/2015-04-25_version0-9-3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MovingBlocks/WorldViewer/HEAD/images/2015-04-25_version0-9-3.png
--------------------------------------------------------------------------------
/images/2015-05-23_version0-10-5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MovingBlocks/WorldViewer/HEAD/images/2015-05-23_version0-10-5.png
--------------------------------------------------------------------------------
/src/main/resources/splash/splash.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MovingBlocks/WorldViewer/HEAD/src/main/resources/splash/splash.jpg
--------------------------------------------------------------------------------
/images/2016-05-17_shattered_planes.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MovingBlocks/WorldViewer/HEAD/images/2016-05-17_shattered_planes.png
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | #Fri Apr 03 11:04:39 CEST 2015
2 | versioneye.projectkey=maven2_worldviewer_1
3 | versioneye.projectid=551e5807fe6d9b0793000e3f
4 |
--------------------------------------------------------------------------------
/src/main/resources/icons/gooey_sweet_red_16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MovingBlocks/WorldViewer/HEAD/src/main/resources/icons/gooey_sweet_red_16.png
--------------------------------------------------------------------------------
/src/main/resources/icons/gooey_sweet_red_32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MovingBlocks/WorldViewer/HEAD/src/main/resources/icons/gooey_sweet_red_32.png
--------------------------------------------------------------------------------
/src/main/resources/icons/gooey_sweet_red_64.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MovingBlocks/WorldViewer/HEAD/src/main/resources/icons/gooey_sweet_red_64.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
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/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/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/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/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/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/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/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/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/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/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/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/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/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 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | WorldViewer
2 | =========
3 |
4 | A world-generator based map viewer for Terasology
5 |
6 | 
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 | [](http://www.youtube.com/watch?v=aLY6gnSW20E)
19 | [](http://www.youtube.com/watch?v=HY0nh6A-BMA)
20 |
21 |
22 | Download
23 | -----------
24 |
25 | [](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 |
--------------------------------------------------------------------------------
/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/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/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 extends FacetLayer> 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/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/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/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/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/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/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 extends FacetLayer> facetClass;
27 | private Class extends FacetLayerConfig> 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 extends FacetLayer> getFacetClass() {
39 | return facetClass;
40 | }
41 |
42 | public Class extends FacetLayerConfig> 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/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/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/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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 super ImmutableVector2i, String> tooltipTextFunc;
33 |
34 | public TooltipOverlay(Function super ImmutableVector2i, String> 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/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/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 |
--------------------------------------------------------------------------------
/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/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/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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/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/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/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/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/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 extends FacetLayer> 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 extends FacetLayer> facetClass) {
140 | try {
141 | Constructor extends FacetLayer> 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 extends FacetLayer> facetClass, FacetLayerConfig conf) {
153 | try {
154 | Constructor extends FacetLayer> 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 extends FacetLayer> 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/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------